Returning Objects
Result
For more detail, see the Result
description in the reference.
A Result
is a class with a to_json()
and to_frontend()
method which returns the objects defined within the class, and their metadata, to either the decision tree environment and an attached frontend, respectively. By default, any Result
objects yielded within a tool will automatically have these objects assigned and sent.
I.e.
# top of file
from elysia.objects import Result
# existing tool code
yield Result(
objects = [...],
metadata = {...},
payload_type = "<your_type_here>",
name = "<name_to_go_in_environment>",
mapping = {...}
)
# continued tool code
Parameters
Objects
The Result
class stores objects
, which are a list of dictionaries containing anything that should be added to the environment or sent to the frontend. These can be stored however you like, but an Elysia-aware frontend expects the objects to have a certain format - the fields of the dictionaries need to be specific to the type of object being sent.
For example, an Elysia frontend knows what the payload of a 'document' object should be, and there are fields such as 'title'
, 'content'
and 'author'
which it expects. But the objects returned from a retrieval might not have the fields line up exactly like this - maybe it has fields called 'document_header'
, 'text_content'
and 'writer'
instead.
Metadata
Results also can have metadata attached to them, which is a single dictionary that contains any information about this particular list of objects that exist across all objects, rather than on an individual level. For example, if we have retrieved some documents using a search query, the metadata can contain what that search query was and what collection was searched, whereas objects
are the objects themselves.
Payload Type
The payload_type
parameter is used to tell the frontend what to expect. Within Elysia, there are some predefined types (see here) that the built-in frontend is aware of. However, you can specify your own payload types if you are building a different frontend that is aware of different payload types.
Mapping
Within the Result
there is also a mapping
field which is a dictionary which maps from these fields to the fields contained within the objects themselves. For example, if we had an object with fields "document_header"
, "text_content"
and "writer"
, then you may want to specify the mapping
as
Name
To index within the tree's environment, a name
string identifier must be used to identify what type of objects are added to the environment. E.g., in the retrieval setting, this could be the collection name queried.
Frontend
When the default .to_frontend(user_id, conversation_id, query_id)
method is called, it first calls to_json(mapping=True)
, which returns a list of all objects that are mapped to their frontend-specific values, and then returns an augmented payload:
{
"type": "result",
"user_id": str,
"conversation_id": str,
"query_id": str,
"id": str,
"payload": dict = {
"type": str,
"objects": list[dict],
"metadata": dict,
},
}
The outer level type
field will default to "result"
, by definition of the Result
class. The "payload"
field contains the unique payload_type
(created on initialisation of the Result
), as well as the objects/metadata within the Result
object. Other fields, such as user_id
and so forth, are inputs to the function which are automatically assigned when the decision tree processes the results.
LLM Message
The Result
class also has a llm_message
parameter (or .llm_parse()
method) which can be used to display custom information to both the decision agent LLM as well as any tools which use the tasks_completed
field in tree_data
.
llm_message:
The message can be formatted using placeholders given by:
{payload_type}
: The payload type of the object created at initialisation{name}
: The name of the object{num_objects}
: The number of objects in theResult
Additionally, any key in the metadata dictionary can be used.
E.g., on initialising the Result
, you can pass llm_message = "Retrieved {num_objects} from {collection_name}"
if you have collection_name
in the metadata.
Custom Objects
Using Result Directly
If you want specific payload types or similar, you can directly use Result
and simply change the initialisation parameters for your own use-case. E.g. in the tool call, if we are dealing cards randomly, you can use
yield Result(
objects = [
{"card_title": "Jack of Clubs", "card_value": 11},
{"card_title": "8 of Diamonds", "card_value": 8}
],
metadata = {"deck_size": 52},
payload_type = "playing_cards",
name="dealt_cards",
llm_message="Dealt {num_objects} cards out of a possible {deck_size}."
)
llm_message
will be input to the tasks_completed
field of the tree_data
, and when input to any tools using that field, this message will be displayed alongside what prompt was used and the tool called.
Also, the frontend payload will use the playing_cards
payload type, and the decision tree processing the result will automatically create the correct frontend payload.
Defining a New Subclass
You can create your own subclass of the Result
class to customise your objects specifically for your use-case. This can be used when you may want some custom rules when the .to_json
, .to_frontend
, or .llm_parse
methods are automatically used.
For example, if we just want to
class Card(Result):
def __init__(
self,
objects: list[dict],
metadata: dict = {},
name: str = "card",
mapping: dict | None = None,
llm_message: str | None = None,
unmapped_keys: list[str] = [],
):
Result.__init__(
self,
objects=objects,
payload_type="playing_card",
metadata=metadata,
name=name,
mapping=mapping,
llm_message=llm_message,
unmapped_keys=unmapped_keys,
)
def to_json(self, mapping: bool = False) -> list[dict]:
output_objects = self.objects
for card in output_objects:
suit = "♥️♦️♣️♠️"[hash(str(card)) % 4] if "card_title" in card else "🃏"
card["suit_emoji"] = suit
card["is_lucky"] = card.get("card_value", 0) > 10
# Apply mapping if requested
if mapping and self.mapping:
output_objects = self.do_mapping(output_objects)
return output_objects
def llm_parse(self):
out = f"""
The first object in the hand was {self.objects[0]}.
There were {len(self.objects)} cards in the hand in total.
"""
if "deck_size" in self.metadata:
out += f"There were a possible {self.metadata['deck_size']} cards to be dealt"
return out
Here, the to_json
method was overwritten to add a bit of flavour to the objects, which didn't exist in the original objects retrieved.
The llm_parse
method was overwritten so it could use information from the objects themselves, which was not available using the generic placeholders.