diff --git a/burr/core/persistence.py b/burr/core/persistence.py index a0d21083..f0d025be 100644 --- a/burr/core/persistence.py +++ b/burr/core/persistence.py @@ -140,15 +140,17 @@ def save( class SQLLitePersister(BaseStatePersister): """Class for SQLLite persistence of state. This is a simple implementation.""" - def __init__(self, db_path: str, table_name: str = "burr_state"): + def __init__(self, db_path: str, table_name: str = "burr_state", serde_kwargs: dict = None): """Constructor :param db_path: the path the DB will be stored. - :param table_name: the table name to store things under. + :param table_name: the table name to store things under. + :param serde_kwargs: kwargs for state serialization/deserialization. """ self.db_path = db_path self.table_name = table_name self.connection = sqlite3.connect(db_path) + self.serde_kwargs = serde_kwargs or {} def create_table_if_not_exists(self, table_name: str): """Helper function to create the table where things are stored if it doesn't exist.""" @@ -229,7 +231,7 @@ def load( row = cursor.fetchone() if row is None: return None - _state = State(json.loads(row[1])) + _state = State.deserialize(json.loads(row[1]), **self.serde_kwargs) return { "partition_key": partition_key, "app_id": row[3], @@ -277,7 +279,7 @@ def save( status, ) cursor = self.connection.cursor() - json_state = json.dumps(state.get_all()) + json_state = json.dumps(state.serialize(**self.serde_kwargs)) cursor.execute( f"INSERT INTO {self.table_name} (partition_key, app_id, sequence_id, position, state, status) " f"VALUES (?, ?, ?, ?, ?, ?)", diff --git a/burr/core/serde.py b/burr/core/serde.py new file mode 100644 index 00000000..64a4f6d5 --- /dev/null +++ b/burr/core/serde.py @@ -0,0 +1,102 @@ +from functools import singledispatch +from typing import Any, Union + +KEY = "__burr_serde__" + + +class StringDispatch: + """Class to capture how to deserialize something. + + We register a key with a deserializer function. It's like single dispatch + but based on a string key value. + + Example usage: + + .. code-block:: python + + from burr.core import serde + + @serde.deserializer.register("pickle") + def deserialize_pickle(value: dict, pickle_kwargs: dict = None, **kwargs) -> cls: + if pickle_kwargs is None: + pickle_kwargs = {} + return pickle.loads(value["value"], **pickle_kwargs) + + What this does is register the function `deserialize_pickle` with the key "pickle". + This should mirror the appropriate serialization function - which is what sets the key value + to match the deserializer function against. + + Notice that this namespaces its kwargs. This is important because we don't want to have + a collision with other kwargs that might be passed in. + """ + + def __init__(self): + self.func_map = {} + + def register(self, key): + def decorator(func): + self.func_map[key] = func + return func + + return decorator + + def call(self, key, *args, **kwargs): + if key in self.func_map: + return self.func_map[key](*args, **kwargs) + else: + raise ValueError(f"No function registered for key: {key}") + + +deserializer = StringDispatch() + + +def deserialize(value: Any, **kwargs) -> Any: + """Main function to deserialize a value. + + Looks for a key in the value if it's a dictionary and calls the appropriate deserializer function. + """ + if isinstance(value, dict): + class_to_instantiate = value.get(KEY, None) + if class_to_instantiate is not None: + return deserializer.call(class_to_instantiate, value, **kwargs) + else: + return {k: deserialize(v, **kwargs) for k, v in value.items()} + elif isinstance(value, list): + return [deserialize(v, **kwargs) for v in value] + else: + return value + + +@singledispatch +def serialize(value, **kwargs) -> Any: + """This is the default implementation for serializing a value. + + All other implementations should be registered with the `@serialize.register` decorator. + + Each function should output a dictionary, and include the `KEY` & value to use for deserialization. + + :param value: The value to serialize + :param kwargs: Any additional keyword arguments. Each implementation should namespace their kwargs. + :return: A dictionary representation of the value + """ + if value is None: + return None + return str(value) + + +@serialize.register(str) +@serialize.register(int) +@serialize.register(float) +@serialize.register(bool) +def serialize_primitive(value, **kwargs) -> Union[str, int, float, bool]: + return value + + +@serialize.register(dict) +def serialize_dict(value: dict, **kwargs) -> dict[str, Any]: + return {k: serialize(v, **kwargs) for k, v in value.items()} + + +@serialize.register(list) +def serialize_list(value: list, **kwargs) -> list[Any]: + return [serialize(v, **kwargs) for v in value] diff --git a/burr/core/state.py b/burr/core/state.py index 2598de63..df517c7c 100644 --- a/burr/core/state.py +++ b/burr/core/state.py @@ -1,9 +1,12 @@ import abc import copy import dataclasses +import importlib import logging from typing import Any, Dict, Iterator, Mapping +from burr.core import serde + logger = logging.getLogger(__name__) @@ -190,6 +193,26 @@ def get_all(self) -> Dict[str, Any]: """Returns the entire state, realize as a dictionary. This is a copy.""" return dict(self) + def serialize(self, **kwargs) -> dict: + """Converts the state to a JSON serializable object""" + _dict = self.get_all() + return { + # TODO: handle field specific custom serialization + k: serde.serialize(v, **kwargs) + for k, v in _dict.items() + } + + @classmethod + def deserialize(cls, json_dict: dict, **kwargs) -> "State": + """Converts a dictionary representing a JSON object back into a state""" + return State( + { + # TODO: handle field specific custom deserialization + k: serde.deserialize(v, **kwargs) + for k, v in json_dict.items() + } + ) + def update(self, **updates: Any) -> "State": """Updates the state with a set of key-value pairs Does an upsert operation (if the keys exist their value will be overwritten, @@ -272,3 +295,14 @@ def __iter__(self) -> Iterator[Any]: def __repr__(self): return self.get_all().__repr__() # quick hack + + +# We register the serde plugins here that we'll automatically try to load. +# In the future if we need to reorder/replace, we'll just have some +# check here that can skip loading plugins/override which ones to load. +# Note for pickle, we require people to manually register the type for that. +for serde_plugin in ["langchain", "pydantic", "pandas"]: + try: + importlib.import_module(f"burr.integrations.serde.{serde_plugin}") + except ImportError: + logger.debug(f"Skipped registering {serde_plugin} serde plugin.") diff --git a/burr/examples b/burr/examples index beeced1f..a6573af9 120000 --- a/burr/examples +++ b/burr/examples @@ -1 +1 @@ -../examples +../examples \ No newline at end of file diff --git a/burr/integrations/persisters/b_mongodb.py b/burr/integrations/persisters/b_mongodb.py index 19617f03..0947620d 100644 --- a/burr/integrations/persisters/b_mongodb.py +++ b/burr/integrations/persisters/b_mongodb.py @@ -12,27 +12,36 @@ class MongoDBPersister(persistence.BaseStatePersister): """A class used to represent a MongoDB Persister. + Example usage: - persister = MongoDBPersister(uri='mongodb://user:pass@localhost:27017', db_name='mydatabase', collection_name='mystates') - persister.save( - partition_key='example_partition', - app_id='example_app', - sequence_id=1, - position='example_position', - state=state.State({'key': 'value'}), - status='completed' - ) - loaded_state = persister.load(partition_key='example_partition', app_id='example_app', sequence_id=1) - print(loaded_state) + + .. code-block:: python + + persister = MongoDBPersister(uri='mongodb://user:pass@localhost:27017', db_name='mydatabase', collection_name='mystates') + persister.save( + partition_key='example_partition', + app_id='example_app', + sequence_id=1, + position='example_position', + state=state.State({'key': 'value'}), + status='completed' + ) + loaded_state = persister.load(partition_key='example_partition', app_id='example_app', sequence_id=1) + print(loaded_state) """ def __init__( - self, uri="mongodb://localhost:27017", db_name="mydatabase", collection_name="mystates" + self, + uri="mongodb://localhost:27017", + db_name="mydatabase", + collection_name="mystates", + serde_kwargs: dict = None, ): """Initializes the MongoDBPersister class.""" self.client = MongoClient(uri) self.db = self.client[db_name] self.collection = self.db[collection_name] + self.serde_kwargs = serde_kwargs or {} def list_app_ids(self, partition_key: str, **kwargs) -> list[str]: """List the app ids for a given partition key.""" @@ -49,7 +58,7 @@ def load( document = self.collection.find_one(query, sort=[("sequence_id", -1)]) if not document: return None - _state = state.State(json.loads(document["state"])) + _state = state.State.deserialize(json.loads(document["state"]), **self.serde_kwargs) return { "partition_key": partition_key, "app_id": app_id, @@ -74,7 +83,7 @@ def save( key = {"partition_key": partition_key, "app_id": app_id, "sequence_id": sequence_id} if self.collection.find_one(key): raise ValueError(f"partition_key:app_id:sequence_id[{key}] already exists.") - json_state = json.dumps(state.get_all()) + json_state = json.dumps(state.serialize(**self.serde_kwargs)) self.collection.insert_one( { "partition_key": partition_key, @@ -84,7 +93,6 @@ def save( "state": json_state, "status": status, "created_at": datetime.now(timezone.utc).isoformat(), - # "created_at": datetime.datetime.utcnow().isoformat(), } ) diff --git a/burr/integrations/persisters/b_redis.py b/burr/integrations/persisters/b_redis.py index 0b03fbc0..7c867f64 100644 --- a/burr/integrations/persisters/b_redis.py +++ b/burr/integrations/persisters/b_redis.py @@ -6,9 +6,9 @@ except ImportError as e: base.require_plugin(e, ["redis"], "redis") -import datetime import json import logging +from datetime import datetime, timezone from typing import Literal, Optional from burr.core import persistence, state @@ -23,7 +23,9 @@ class RedisPersister(persistence.BaseStatePersister): It inherits from the BaseStatePersister class. """ - def __init__(self, host: str, port: int, db: int, password: str = None): + def __init__( + self, host: str, port: int, db: int, password: str = None, serde_kwargs: dict = None + ): """Initializes the RedisPersister class. :param host: @@ -32,6 +34,7 @@ def __init__(self, host: str, port: int, db: int, password: str = None): :param password: """ self.connection = redis.Redis(host=host, port=port, db=db, password=password) + self.serde_kwargs = serde_kwargs or {} def list_app_ids(self, partition_key: str, **kwargs) -> list[str]: """List the app ids for a given partition key.""" @@ -60,7 +63,7 @@ def load( data = self.connection.hgetall(key) if not data: return None - _state = state.State(json.loads(data[b"state"].decode())) + _state = state.State.deserialize(json.loads(data[b"state"].decode()), **self.serde_kwargs) return { "partition_key": partition_key, "app_id": app_id, @@ -100,7 +103,7 @@ def save( key = self.create_key(app_id, partition_key, sequence_id) if self.connection.exists(key): raise ValueError(f"partition_key:app_id:sequence_id[{key}] already exists.") - json_state = json.dumps(state.get_all()) + json_state = json.dumps(state.serialize(**self.serde_kwargs)) self.connection.hset( key, mapping={ @@ -110,7 +113,7 @@ def save( "position": position, "state": json_state, "status": status, - "created_at": datetime.datetime.utcnow().isoformat(), + "created_at": datetime.now(timezone.utc).isoformat(), }, ) self.connection.zadd(partition_key, {app_id: sequence_id}) diff --git a/burr/integrations/persisters/postgresql.py b/burr/integrations/persisters/postgresql.py index 80ce0107..d81886a4 100644 --- a/burr/integrations/persisters/postgresql.py +++ b/burr/integrations/persisters/postgresql.py @@ -73,7 +73,7 @@ def from_values( ) return cls(connection, table_name) - def __init__(self, connection, table_name: str = "burr_state"): + def __init__(self, connection, table_name: str = "burr_state", serde_kwargs: dict = None): """Constructor :param connection: the connection to the PostgreSQL database. @@ -81,6 +81,11 @@ def __init__(self, connection, table_name: str = "burr_state"): """ self.table_name = table_name self.connection = connection + self.serde_kwargs = serde_kwargs or {} + + def set_serde_kwargs(self, serde_kwargs: dict): + """Sets the serde_kwargs for the persister.""" + self.serde_kwargs = serde_kwargs def create_table(self, table_name: str): """Helper function to create the table where things are stored.""" @@ -160,7 +165,7 @@ def load( row = cursor.fetchone() if row is None: return None - _state = state.State(row[1]) + _state = state.State.deserialize(row[1], **self.serde_kwargs) return { "partition_key": partition_key, "app_id": row[3], @@ -208,7 +213,7 @@ def save( status, ) cursor = self.connection.cursor() - json_state = json.dumps(state.get_all()) + json_state = json.dumps(state.serialize(**self.serde_kwargs)) cursor.execute( f"INSERT INTO {self.table_name} (partition_key, app_id, sequence_id, position, state, status) " "VALUES (%s, %s, %s, %s, %s, %s)", diff --git a/burr/integrations/serde/__init__.py b/burr/integrations/serde/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/burr/integrations/serde/langchain.py b/burr/integrations/serde/langchain.py new file mode 100644 index 00000000..26da0fc3 --- /dev/null +++ b/burr/integrations/serde/langchain.py @@ -0,0 +1,78 @@ +# try to import to serialize Langchain messages +from langchain_core import documents as lc_documents +from langchain_core import load as lc_serde +from langchain_core import messages as lc_messages + +from burr.core import serde + + +@serde.serialize.register(lc_documents.Document) +def serialize_lc_docs(value: lc_documents.Document, **kwargs) -> dict: + """Serializes langchain documents.""" + if value.is_lc_serializable(): + lc_doc = lc_serde.dumpd(value) + lc_doc[serde.KEY] = "lc_document" + return lc_doc + elif hasattr(value, "to_document") and hasattr(value, "state"): + # attempt to serialize the state as well + return { + "doc": serialize_lc_docs(value.to_document()), + "state": serde.serialize(value.state, **kwargs), + serde.KEY: "lc_document_with_state", + } + elif hasattr(value, "to_document"): + # we lose some state here, but it's better than nothing + return serialize_lc_docs(value.to_document()) + else: + # d.page_content # hack because not all documents are serializable + return {"value": value.page_content, serde.KEY: "lc_document_hack"} + + +@serde.deserializer.register("lc_document") +def deserialize_lc_document(value: dict, **kwargs) -> lc_documents.Document: + """Deserializes langchain documents.""" + value.pop(serde.KEY) + return lc_serde.load(value) + + +@serde.deserializer.register("lc_document_with_state") +def deserialize_lc_document_with_state(value: dict, **kwargs) -> lc_documents.Document: + """Deserializes langchain documents with state.""" + from langchain_community.document_transformers.embeddings_redundant_filter import ( + _DocumentWithState, + ) + + value.pop(serde.KEY) + doc = lc_serde.load(value["doc"]) + state = serde.deserialize(value["state"], **kwargs) + return _DocumentWithState(page_content=doc.page_content, metadata=doc.metadata, state=state) + + +@serde.deserializer.register("lc_document_hack") +def deserialize_lc_document_hack(value: dict, **kwargs) -> lc_documents.Document: + """Deserializes langchain documents that we didn't know about into a document.""" + return lc_documents.Document(page_content=value["value"]) + + +@serde.serialize.register(lc_messages.BaseMessage) +def serialize_lc_messages(value: lc_messages.BaseMessage, **kwargs) -> dict: + """Serializes langchain messages.""" + if value.is_lc_serializable(): + lc_message = lc_messages.message_to_dict(value) + lc_message[serde.KEY] = "lc_message" + return lc_message + else: + return {"value": value.content, "type": value.type, serde.KEY: "lc_message_hack"} + + +@serde.deserializer.register("lc_message") +def deserialize_lc_message(value: dict, **kwargs) -> lc_messages.BaseMessage: + """Deserializes langchain messages.""" + value.pop(serde.KEY) # note this mutates the dict + return lc_messages._message_from_dict(value) + + +@serde.deserializer.register("lc_message_hack") +def deserialize_lc_message_hack(value: dict, **kwargs) -> lc_messages.BaseMessage: + """Deserializes langchain messages that we didn't know how to serialize.""" + return lc_messages.BaseMessage(content=value["value"], type=value["type"]) diff --git a/burr/integrations/serde/pandas.py b/burr/integrations/serde/pandas.py new file mode 100644 index 00000000..ff4f2527 --- /dev/null +++ b/burr/integrations/serde/pandas.py @@ -0,0 +1,49 @@ +# try to import to serialize Pandas Objects +import hashlib +import os + +import pandas as pd + +from burr.core import serde + + +@serde.serialize.register(pd.DataFrame) +def serialize_pandas_df(value: pd.DataFrame, pandas_kwargs: dict, **kwargs) -> dict: + """Custom serde for pandas dataframes. + + Saves the dataframe to a parquet file and returns the path to the file. + Requires a `path` key in the `pandas_kwargs` dictionary. + + :param value: the pandas dataframe to serialize. + :param pandas_kwargs: `path` key is required -- this is the base path to save the parquet file. As \ + well as any other kwargs to pass to the pandas to_parquet function. + :param kwargs: + :return: + """ + hash_object = hashlib.sha256() + hash_value = str(value.columns) + str(value.shape) + str(value.dtypes) + hash_object.update(hash_value.encode()) + + # Return the hexadecimal representation of the hash + file_name = f"df_{hash_object.hexdigest()}.parquet" + kwargs = pandas_kwargs.copy() + base_path: str = kwargs.pop("path") + saved_to = os.path.join(base_path, file_name) + value.to_parquet(path=saved_to, **kwargs) + return {serde.KEY: "pandas.DataFrame", "path": saved_to} + + +@serde.deserializer.register("pandas.DataFrame") +def deserialize_pandas_df(value: dict, pandas_kwargs: dict, **kwargs) -> pd.DataFrame: + """Custom deserializer for pandas dataframes. + + :param value: the dictionary to pull the path from to load the parquet file. + :param pandas_kwargs: other args to pass to the pandas read_parquet function. + :param kwargs: + :return: pandas dataframe + """ + kwargs = pandas_kwargs.copy() + if "path" in kwargs: + # remove this to not clash; we already have the full path. + kwargs.pop("path") + return pd.read_parquet(value["path"], **kwargs) diff --git a/burr/integrations/serde/pickle.py b/burr/integrations/serde/pickle.py new file mode 100644 index 00000000..4a6d0027 --- /dev/null +++ b/burr/integrations/serde/pickle.py @@ -0,0 +1,57 @@ +# Pickle serde registration +# This is not automatically registered because we want to register +# it based on class type. +import pickle + +from burr.core import serde + + +def register_type_to_pickle(cls): + """Register a class to be serialized/deserialized using pickle. + + Note: `pickle_kwargs` are passed to the pickle.dumps and pickle.loads functions. + + This will register the passed in class to be serialized/deserialized using pickle. + + .. code-block:: python + + class User: + def __init__(self, name, email): + self.name = name + self.email = email + + from burr.integrations.serde import pickle + pickle.register_type_to_pickle(User) # this will register the User class to be serialized/deserialized using pickle. + + + :param cls: The class to register + """ + + @serde.serialize.register(cls) + def serialize_pickle(value: cls, pickle_kwargs: dict = None, **kwargs) -> dict: + """Serializes the value using pickle. + + :param value: the value to serialize. + :param pickle_kwargs: not required. Optional. + :param kwargs: + :return: dictionary of serde.KEY and value + """ + if pickle_kwargs is None: + pickle_kwargs = {} + return { + serde.KEY: "pickle", + "value": pickle.dumps(value, **pickle_kwargs), + } + + @serde.deserializer.register("pickle") + def deserialize_pickle(value: dict, pickle_kwargs: dict = None, **kwargs) -> cls: + """Deserializes the value using pickle. + + :param value: the value to deserialize from. + :param pickle_kwargs: note required. Optional. + :param kwargs: + :return: object of type cls + """ + if pickle_kwargs is None: + pickle_kwargs = {} + return pickle.loads(value["value"], **pickle_kwargs) diff --git a/burr/integrations/serde/pydantic.py b/burr/integrations/serde/pydantic.py new file mode 100644 index 00000000..77388dc9 --- /dev/null +++ b/burr/integrations/serde/pydantic.py @@ -0,0 +1,29 @@ +# try to import to serialize Pydantic Objects +import importlib + +import pydantic + +from burr.core import serde + + +@serde.serialize.register(pydantic.BaseModel) +def serialize_pydantic(value: pydantic.BaseModel, **kwargs) -> dict: + """Uses pydantic to dump the model to a dictionary and then adds the __pydantic_class to the dictionary.""" + _dict = value.model_dump() + _dict[serde.KEY] = "pydantic" + # get qualified name of pydantic class. The module name should be fully qualified. + _dict["__pydantic_class"] = f"{value.__class__.__module__}.{value.__class__.__name__}" + return _dict + + +@serde.deserializer.register("pydantic") +def deserialize_pydantic(value: dict, **kwargs) -> pydantic.BaseModel: + """Deserializes a pydantic object from a dictionary. + This will pop the __pydantic_class and then import the class. + """ + value.pop(serde.KEY) + pydantic_class_name = value.pop("__pydantic_class") + module_name, class_name = pydantic_class_name.rsplit(".", 1) + module = importlib.import_module(module_name) + pydantic_class = getattr(module, class_name) + return pydantic_class.model_validate(value) diff --git a/burr/tracking/client.py b/burr/tracking/client.py index 825d352a..cf9d361a 100644 --- a/burr/tracking/client.py +++ b/burr/tracking/client.py @@ -8,7 +8,7 @@ from burr import system from burr.common import types as burr_types -from burr.core import Action, ApplicationGraph, State +from burr.core import Action, ApplicationGraph, State, serde from burr.core.persistence import BaseStateLoader, PersistedStateData from burr.integrations.base import require_plugin from burr.lifecycle import ( @@ -47,6 +47,13 @@ def _format_exception(exception: Exception) -> Optional[str]: return "".join(traceback.format_exception(type(exception), exception, exception.__traceback__)) +INPUT_FILTERLIST = {"__tracer"} + + +def _filter_inputs(d: dict) -> dict: + return {k: v for k, v in d.items() if k not in INPUT_FILTERLIST} + + def _allowed_project_name(project_name: str, on_windows: bool) -> bool: allowed_chars = "a-zA-Z0-9_\-" if not on_windows: @@ -80,6 +87,7 @@ def __init__( self, project: str, storage_dir: str = DEFAULT_STORAGE_DIR, + serde_kwargs: Optional[Dict[str, Any]] = None, ): """Instantiates a local tracking client. This will create the following directories, if they don't exist: #. The base directory (defaults to ~/.burr) @@ -101,6 +109,7 @@ def __init__( ) self.storage_dir = LocalTrackingClient.get_storage_path(project, storage_dir) self.project_id = project + self.serde_kwargs = serde_kwargs or {} @classmethod def get_storage_path(cls, project, storage_dir) -> str: @@ -250,10 +259,11 @@ def pre_run_step( sequence_id: int, **future_kwargs: Any, ): + _filtered_inputs = _filter_inputs(inputs) pre_run_entry = BeginEntryModel( start_time=datetime.datetime.now(), action=action.name, - inputs=inputs, + inputs=serde.serialize(_filtered_inputs, **self.serde_kwargs), sequence_id=sequence_id, ) self._append_write_line(pre_run_entry) @@ -270,10 +280,10 @@ def post_run_step( post_run_entry = EndEntryModel( end_time=datetime.datetime.now(), action=action.name, - result=result, + result=serde.serialize(result, **self.serde_kwargs), sequence_id=sequence_id, exception=_format_exception(exception), - state=state.get_all(), + state=state.serialize(), ) self._append_write_line(post_run_entry) @@ -339,8 +349,11 @@ def load( line = None if sequence_id is None: # get the last one, we want to start at the end - line = json_lines[-1] - sequence_id = line["sequence_id"] + for _line in reversed(json_lines): + if _line["type"] == "end_entry": + sequence_id = _line["sequence_id"] + line = _line + break else: for js_line in json_lines: if js_line["type"] == "end_entry": @@ -367,7 +380,7 @@ def load( "app_id": app_id, "sequence_id": sequence_id, "position": position, - "state": State(prior_state), + "state": State.deserialize(prior_state, **self.serde_kwargs), "created_at": datetime.datetime.fromtimestamp(os.path.getctime(path)).isoformat(), "status": "completed" if line["exception"] is None else "failed", } diff --git a/burr/tracking/common/models.py b/burr/tracking/common/models.py index 002689a4..c164c614 100644 --- a/burr/tracking/common/models.py +++ b/burr/tracking/common/models.py @@ -1,7 +1,5 @@ import datetime -from typing import Any, Dict, List, Optional, Union - -from pydantic import field_serializer +from typing import Any, Dict, List, Optional from burr.common import types as burr_types from burr.core import Action @@ -17,16 +15,6 @@ "tracking", ) -try: - # try to import to serialize Langchain messages - from langchain_core import documents as lc_documents - from langchain_core import load as lc_serde - from langchain_core import messages as lc_messages -except ImportError: - lc_messages = None - lc_documents = None - lc_serde = None - class IdentifyingModel(pydantic.BaseModel): type: str @@ -128,13 +116,6 @@ class ApplicationMetadataModel(IdentifyingModel): type: str = "application_metadata" -INPUT_FILTERLIST = {"__tracer"} - - -def _filter_inputs(d: dict) -> dict: - return {k: v for k, v in d.items() if k not in INPUT_FILTERLIST} - - class BeginEntryModel(IdentifyingModel): """Pydantic model that represents an entry for the beginning of a step""" @@ -144,33 +125,6 @@ class BeginEntryModel(IdentifyingModel): sequence_id: int type: str = "begin_entry" - @field_serializer("inputs") - def serialize_inputs(self, inputs): - return _serialize_object(_filter_inputs(inputs)) - - -def _serialize_object(d: object) -> Union[dict, list, object, str]: - if isinstance(d, list): - return [_serialize_object(x) for x in d] - elif isinstance(d, dict): - return {k: _serialize_object(v) for k, v in d.items()} - elif lc_messages is not None and isinstance(d, lc_messages.BaseMessage): - return lc_messages.message_to_dict(d) - elif lc_documents is not None and isinstance(d, lc_documents.Document): - if d.is_lc_serializable(): - return lc_serde.dumpd(d) - else: - # d.page_content # hack because not all documents are serializable - return d.page_content - elif hasattr(d, "to_document"): - # langchain can have things that look like a document but aren't... - return _serialize_object(d.to_document()) - elif hasattr(d, "model_dump"): # generic pydantic object - return d.model_dump() - elif hasattr(d, "to_json"): - return d.to_json() - return d - class EndEntryModel(IdentifyingModel): """Pydantic model that represents an entry for the end of a step""" @@ -183,14 +137,6 @@ class EndEntryModel(IdentifyingModel): sequence_id: int type: str = "end_entry" - @field_serializer("result") - def serialize_result(self, result): - return _serialize_object(result) - - @field_serializer("state") - def serialize_state(self, state): - return _serialize_object(state) - class BeginSpanModel(IdentifyingModel): """Pydantic model that represents an entry for the beginning of a span""" diff --git a/burr/tracking/server/backend.py b/burr/tracking/server/backend.py index d413c01b..22ea2773 100644 --- a/burr/tracking/server/backend.py +++ b/burr/tracking/server/backend.py @@ -105,14 +105,14 @@ async def list_projects(self, request: fastapi.Request) -> Sequence[schema.Proje async def get_number_of_steps(self, file_path: str) -> int: """Quick tool to get the latest sequence ID from a log file. This is not efficient and should be replaced.""" - count = 0 async with aiofiles.open(file_path, "rb") as f: for line in reversed(await f.readlines()): line_data = safe_json_load(line) - # Just return the latest for now - # We add one as it is the count, not the index - return line_data["sequence_id"] + 1 - return count + if "sequence_id" in line_data: + # Just return the latest we can determine for now + # We add one as it is the count, not the index + return line_data["sequence_id"] + 1 + return 0 async def _load_metadata(self, metadata_path: str) -> models.ApplicationMetadataModel: if os.path.exists(metadata_path): diff --git a/burr/tracking/server/run.py b/burr/tracking/server/run.py index 560ddc95..9f318a11 100644 --- a/burr/tracking/server/run.py +++ b/burr/tracking/server/run.py @@ -82,6 +82,18 @@ async def ready() -> bool: return True +@app.get("/api/v0/version") +async def version() -> dict: + """Returns the burr version""" + import pkg_resources + + try: + version = pkg_resources.get_distribution("burr").version + except pkg_resources.DistributionNotFound: + version = "unknown" + return {"version": version} + + # Examples -- todo -- put them behind `if` statements app.include_router(chatbot.router, prefix="/api/v0/chatbot") app.include_router(email_assistant.router, prefix="/api/v0/email_assistant") diff --git a/docs/concepts/index.rst b/docs/concepts/index.rst index 7b5be408..2dec632c 100644 --- a/docs/concepts/index.rst +++ b/docs/concepts/index.rst @@ -15,6 +15,7 @@ Overview of the concepts -- read these to get a mental model for how Burr works. transitions tracking state-persistence + serde streaming-actions hooks additional-visibility diff --git a/docs/concepts/serde.rst b/docs/concepts/serde.rst new file mode 100644 index 00000000..90826825 --- /dev/null +++ b/docs/concepts/serde.rst @@ -0,0 +1,72 @@ +================================ +Serialization / Deserialization +================================ + +Core to :ref:`state-persistence ` is the ability to serialize and deserialize objects. + +Burr comes with a pluggable serialization/deserialization mechanism. + +Currently it is class/type based. Field level serialization is in the works! See :py:func:`serialize ` and :py:func:`deserialize ` for reference details. + +How it works +____________ +The :py:class:`State ` object has a :py:meth:`serialize ` method that returns a dictionary. +The :py:class:`State ` class also has a :py:meth:`deserialize ` method that takes a dictionary and returns a state object. + +It is then delegated to persisters and trackers to call these methods and store the serialized state. + +Underneath the State object delegates to the :py:func:`serialize ` and :py:func:`deserialize ` functions. + +How to create your own serialization/deserialization +_____________________________________________________ +To create your own serialization/deserialization mechanism, you need to implement the following code. The assumption +here is that you have some custom class you want to serialize/deserialize. + +.. code-block:: python + + from typing import Any, Dict + from burr.core import serde + + class MY_CLASS: + # your custom class/type + pass + + @serde.serialize.register(MY_CLASS) + def serialize_myclass(value: cls, myclass_kwargs: dict = None, **kwargs) -> dict: + """Serializes the value using my custom methodology. + + :param value: the value to serialize. + :param myclass_kwargs: not required. Optional. + :param kwargs: + :return: dictionary of serde.KEY and value + """ + if myclass_kwargs is None: + myclass_kwargs = {} + return { + # required to identify how to deserialize + serde.KEY: "myclass", + # delegate to your custom serialization + "value": some_custom_serialization(value, **myclass_kwargs), + } + + @serde.deserializer.register("myclass") + def deserialize_myclass(value: dict, myclass_kwargs: dict = None, **kwargs) -> cls: + """Deserializes the value using my custom methodology. + + :param value: the value to deserialize from. + :param myclass_kwargs: not required. Optional. + :param kwargs: + :return: object of type cls + """ + if myclass_kwargs is None: + myclass_kwargs = {} + # delegate to your custom deserialization + return some_custom_deserialization(value["value"], **myclass_kwargs) + +You'll need to this code to run/be imported so it can register itself. + +Field level Serialization/Deserialization +_________________________________________ +We are working on field level serialization/deserialization registration. This will allow you to set the +serialize/deserialize on specific fields of an object. This is will give you more control over +how to serialize/deserialize objects within state. diff --git a/docs/reference/index.rst b/docs/reference/index.rst index b1e20464..87065d57 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -14,6 +14,7 @@ need functionality that is not publicly exposed, please open an issue and we can application actions state + serde persister conditions tracking diff --git a/docs/reference/persister.rst b/docs/reference/persister.rst index 06cf1772..85ff6fb5 100644 --- a/docs/reference/persister.rst +++ b/docs/reference/persister.rst @@ -56,6 +56,7 @@ Currently we support the following, although we highly recommend you contribute .. autoclass:: burr.integrations.persisters.b_mongodb.MongoDBPersister :members: + .. automethod:: __init__ diff --git a/docs/reference/serde.rst b/docs/reference/serde.rst new file mode 100644 index 00000000..a12e0453 --- /dev/null +++ b/docs/reference/serde.rst @@ -0,0 +1,50 @@ +================================== +Serialization & Deserialization +================================== + +Burr provides a set of tools to make loading and saving state easy. These are functions +that will be used by lifecycle hooks to save and load state. + +If you want to implement your own serialization and deserialization, you should implement +the ``serialize`` & ``deserialize`` functions, which act as an interface. ``serialize`` uses +singledispatch and operates on a Type level. While ``deserialize`` registers a string value +to a function. + +.. automodule:: burr.core.serde + :members: + +Each serialize function needs to a mirror deserialize function. To know which +deserialize function to use, the serialize function needs to return a dictionary and have +``burr.core.serde.KEY`` as one of the keys mapping to the appropriate value. +This is used to identify the deserialization function to use. + +Out of the box Implementations +============================== + +.. _serderef: + +The following implementations are available assuming you have the right dependencies in your environment: + +LangChain Objects +^^^^^^^^^^^^^^^^^ + +.. automodule:: burr.integrations.serde.langchain + :members: + +Pandas Objects +^^^^^^^^^^^^^^^^^ + +.. automodule:: burr.integrations.serde.pandas + :members: + +Pickle-able Objects +^^^^^^^^^^^^^^^^^^^ + +.. automodule:: burr.integrations.serde.pickle + :members: + +Pydantic Objects +^^^^^^^^^^^^^^^^^ + +.. automodule:: burr.integrations.serde.pydantic + :members: diff --git a/examples/multi-agent-collaboration/lcel/application.py b/examples/multi-agent-collaboration/lcel/application.py index 96f86399..5705947f 100644 --- a/examples/multi-agent-collaboration/lcel/application.py +++ b/examples/multi-agent-collaboration/lcel/application.py @@ -6,6 +6,7 @@ very simple tracer, it could easily be extended to include more information. """ import json +import uuid from typing import Annotated, Any, Optional from uuid import UUID @@ -221,27 +222,13 @@ def main(query: str = None, app_instance_id: str = None, sequence_id: int = None Pass in an app_instance_id to restart from a previous run. Pass in an sequence_id to restart from a previous run and a specific position in it. """ + if app_instance_id is None: + app_instance_id = str(uuid.uuid4()) project_name = "demo_lcel-multi-agent" - if app_instance_id: - tracker = burr_tclient.LocalTrackingClient(project_name) - persisted_state = tracker.load("demo", app_id=app_instance_id, sequence_no=sequence_id) - if not persisted_state: - print(f"Warning: No persisted state found for app_id {app_instance_id}.") - initial_state, entry_point = default_state_and_entry_point(query) - else: - initial_state = persisted_state["state"] - # for now we need to manually deserialize LangChain messages into LangChain Objects - from langchain_core import messages - - initial_state = initial_state.update( - messages=messages.messages_from_dict(persisted_state["state"]["messages"]) - ) - entry_point = persisted_state["position"] - else: - initial_state, entry_point = default_state_and_entry_point(query) + tracker_persister = burr_tclient.LocalTrackingClient(project_name) + default_state, default_entrypoint = default_state_and_entry_point(query) app = ( core.ApplicationBuilder() - .with_state(**initial_state) .with_actions( researcher=research_node, charter=chart_node, @@ -258,9 +245,17 @@ def main(query: str = None, app_instance_id: str = None, sequence_id: int = None ("call_tool", "researcher", expr("sender == 'Researcher'")), ("call_tool", "charter", expr("sender == 'Chart Generator'")), ) - .with_entrypoint(entry_point) + .with_identifiers( + app_id=app_instance_id, partition_key="sample_user", sequence_id=sequence_id + ) + .initialize_from( + tracker_persister, + resume_at_next_action=True, + default_state=default_state, + default_entrypoint=default_entrypoint, + ) .with_hooks(PrintStepHook()) - .with_tracker(project=project_name) + .with_tracker(tracker_persister) .build() ) app.visualize( @@ -270,7 +265,7 @@ def main(query: str = None, app_instance_id: str = None, sequence_id: int = None if __name__ == "__main__": - main() + main(app_instance_id="e80f405b-2c79-4bc9-88d2-23413ceb5881", sequence_id=8) # main("Fetch the UK's GDP over the past 5 years," # " then draw a line graph of it." # " Once you code it up, finish.") diff --git a/examples/multi-agent-collaboration/lcel/notebook.ipynb b/examples/multi-agent-collaboration/lcel/notebook.ipynb index 01413f8f..506b0327 100644 --- a/examples/multi-agent-collaboration/lcel/notebook.ipynb +++ b/examples/multi-agent-collaboration/lcel/notebook.ipynb @@ -33,10 +33,10 @@ "outputs_hidden": false } }, - "outputs": [], "source": [ "# %pip install -U burr[start] langchain-community langchain-core langchain-experimental openai" - ] + ], + "outputs": [] }, { "cell_type": "code", @@ -52,14 +52,14 @@ "outputs_hidden": false } }, - "outputs": [], "source": [ "# Environment variables\n", "import os\n", "# Make sure TAVILY_API_KEY & OPENAI_API_KEY are set\n", "# os.environ['TAVILY_API_KEY'] = 'your_tavily_api_key' # get one at https://tavily.com\n", "# os.environ['OPENAI_API_KEY'] = 'your_openai_api_key' # get one at https://platform.openai.com" - ] + ], + "outputs": [] }, { "cell_type": "code", @@ -75,7 +75,6 @@ "outputs_hidden": false } }, - "outputs": [], "source": [ "# import everything that you'll need\n", "import pprint\n", @@ -99,7 +98,8 @@ "from burr.lifecycle import PostRunStepHook\n", "from burr.tracking import client as burr_tclient\n", "from burr.visibility import ActionSpanTracer, TracerFactory" - ] + ], + "outputs": [] }, { "cell_type": "markdown", @@ -130,7 +130,6 @@ "outputs_hidden": false } }, - "outputs": [], "source": [ "tavily_tool = TavilySearchResults(max_results=5)\n", "repl = PythonREPL()\n", @@ -148,7 +147,8 @@ "\n", "tools = [tavily_tool, python_repl]\n", "tool_executor = ToolExecutor(tools)" - ] + ], + "outputs": [] }, { "cell_type": "markdown", @@ -181,7 +181,6 @@ "outputs_hidden": false } }, - "outputs": [], "source": [ "# The Agent that we'll use. Our agents here only differ by the system message passed in.\n", "def create_agent(llm, tools, system_message: str):\n", @@ -234,7 +233,8 @@ " [python_repl],\n", " system_message=\"Any charts you display will be visible by the user.\",\n", ")" - ] + ], + "outputs": [] }, { "cell_type": "markdown", @@ -270,7 +270,6 @@ "outputs_hidden": false } }, - "outputs": [], "source": [ "@action(reads=[\"messages\", \"sender\"], writes=[\"messages\", \"sender\"])\n", "def research_node(state: State) -> tuple[dict, State]:\n", @@ -321,7 +320,8 @@ "def terminal_step(state: State) -> tuple[dict, State]:\n", " \"\"\"Terminal step we have here that does nothing, but it could\"\"\"\n", " return {}, state" - ] + ], + "outputs": [] }, { "cell_type": "markdown", @@ -357,7 +357,6 @@ "outputs_hidden": false } }, - "outputs": [], "source": [ "# Adjust these if you want to load a prior execution\n", "app_instance_id = None\n", @@ -367,7 +366,8 @@ "# CHANGE THIS IF YOU WANT SOMETHING DIFFERENT!\n", "default_query = (\"Fetch the UK's GDP over the past 5 years, then draw a line graph of it. \"\n", " \"Once the python code has been written and the graph drawn, the task is complete.\")" - ] + ], + "outputs": [] }, { "cell_type": "code", @@ -383,7 +383,6 @@ "outputs_hidden": false } }, - "outputs": [], "source": [ "# Determine initial state and entry point\n", "def default_state_and_entry_point(query: str = None) -> tuple[dict, str]:\n", @@ -420,7 +419,8 @@ " entry_point = persisted_state[\"position\"]\n", "else:\n", " initial_state, entry_point = default_state_and_entry_point(default_query)" - ] + ], + "outputs": [] }, { "cell_type": "code", @@ -436,110 +436,6 @@ "outputs_hidden": false } }, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "researcher\n", - "\n", - "researcher\n", - "\n", - "\n", - "\n", - "charter\n", - "\n", - "charter\n", - "\n", - "\n", - "\n", - "researcher->charter\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "call_tool\n", - "\n", - "call_tool\n", - "\n", - "\n", - "\n", - "researcher->call_tool\n", - "\n", - "\n", - "'function_call' in messages[-1].additional_kwargs\n", - "\n", - "\n", - "\n", - "terminal\n", - "\n", - "terminal\n", - "\n", - "\n", - "\n", - "researcher->terminal\n", - "\n", - "\n", - "'FINAL ANSWER' in messages[-1].content\n", - "\n", - "\n", - "\n", - "charter->researcher\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "charter->call_tool\n", - "\n", - "\n", - "'function_call' in messages[-1].additional_kwargs\n", - "\n", - "\n", - "\n", - "charter->terminal\n", - "\n", - "\n", - "'FINAL ANSWER' in messages[-1].content\n", - "\n", - "\n", - "\n", - "call_tool->researcher\n", - "\n", - "\n", - "sender == 'Researcher'\n", - "\n", - "\n", - "\n", - "call_tool->charter\n", - "\n", - "\n", - "sender == 'Chart Generator'\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ "# Build the application \n", "def build_application(state: dict, entry_point: str):\n", @@ -571,7 +467,8 @@ "app.visualize(\n", " output_file_path=\"statemachine\", include_conditions=True, format=\"png\"\n", ")" - ] + ], + "outputs": [] }, { "cell_type": "markdown", @@ -605,22 +502,11 @@ "outputs_hidden": false } }, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1sAAAHWCAYAAACBjZMqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAACFN0lEQVR4nOzdd1xW5f/H8dd9szeiIioO3NtyozlKBc2G2rCysmGlaWW2vm2tX1lZplZq5qrUcqTtVNwL98g9cQuIyN7c5/eHSZJoYMC5gffz8fBh97kvznkfuBI+XNe5LothGAYiIiIiIiJSqKxmBxARERERESmNVGyJiIiIiIgUARVbIiIiIiIiRUDFloiIiIiISBFQsSUiIiIiIlIEVGyJiIiIiIgUARVbIiIiIiIiRUDFloiIiIiISBFQsSUiIiIiIlIEVGyJiIjkw8qVK7FYLMyfP9/sKCIiUkKo2BIRKQQjRozAYrEQExOT5/tNmjShS5cuOa+PHTuGxWLh448/ztXOMAyeeuopLBYLI0aM+Nfrpqen89lnn3HTTTdRrlw5nJ2dqVKlCnfccQffffcd2dnZV1zz0h8nJycqVKhA+/btee211zhx4sQV579UYFz+MbVq1eLhhx/m6NGj+frcZGZmMn78eFq3bo2Xlxeenp60bt2a8ePHk5mZma9zFKfZs2czduxY067/z6+Tg4MD1atXp0+fPuzYsaNIrvn+++/z448/5rv95fku//PBBx9c8+N69uxJuXLliIqKuuK9+Ph4KleuTNu2bbHZbAW9BRERu+RodgAREbnIMAyefvppJk+ezJtvvvmvxda5c+fo2bMnW7duJTQ0lDfeeAM/Pz8iIyNZunQpDzzwAIcPH+bNN9/M9XH3338/t956KzabjQsXLrB582bGjh3LuHHjmDp1Kvfdd98V13r22Wdp3bo1mZmZbNu2jcmTJ/Pbb7+xa9cuqlSpctWMycnJ9OrVi1WrVnHbbbfxyCOPYLVaWbRoEc899xwLFizgt99+w8PD47o+Z0Vh9uzZ7N69m2HDhpma49LXKTs7m3379jFx4kT++OMPNmzYwA033FCo13r//fe5++676d27d74/pnv37jz88MO5jt14443X/JgJEybQpEkTnn/+eWbPnp3rvddee42YmBgWLVqE1arfBYtIKWGIiMh/9vbbbxuAce7cuTzfb9y4sdG5c+ec1xEREQZgjB49OufYkCFDDMB4/fXX83XN0NBQw2q1Gj/88EOe72/evNmYOXPmNa95ybFjx4x69eoZzs7Oxo4dO3KOr1ixwgCMefPm5Wo/fvx4AzDef//9a2Z88sknDcD47LPPrnjv888/NwBj0KBB1zxHYbPZbEZKSspV3+/Vq5dRo0aNK45f7XNR2K72dfr5558NwHjyyScL/ZoeHh7GgAED8t0eMIYMGXJd1/rwww8NwFi8eHHOsU2bNhlWq9V4+eWXr+ucBZWammpkZ2cXy7VEpGzTr45EROzAc889xxdffMGrr77K//3f//1r+/DwcBYvXsyTTz5J375982zTqlUr+vfvn6/r16hRgxkzZpCRkcFHH330r+1vueUWACIiIq7a5tSpU0ydOpVbbrmFoUOHXvH+kCFDuPnmm5kyZQqnTp0CLk63vPnmm69oa7PZqFq1KnfffXeuY2PHjqVx48a4urpSqVIlnnrqKS5cuJDrY2vWrMltt93G4sWLadWqFW5ubnz55Zd5Zu7SpQu//fYbx48fz5kaV7NmzSuyvPfeewQGBuLq6krXrl05fPjwFefauHEjPXr0wMfHB3d3dzp37sy6deuu+vn6N//8nP/000/06tWLKlWq4OLiQu3atXn33XdzTR0FOHToEHfddRcBAQG4uroSGBjIfffdR3x8PHBxSmBycjJff/11zj0/8sgj+cqUmppKWlpage5j+PDhNGvWjKeffpq0tDSys7MZNGgQNWrU4O233wZg//793H333fj5+eHq6kqrVq34+eefc50nNjaWF198kaZNm+Lp6Ym3tzc9e/Zk586dudpdmgr7/fff88Ybb1C1alXc3d1JSEggMzOTkSNHUrduXVxdXSlfvjw33XQTYWFhBbonEZGr0TRCERGTPf/884wfP55XXnmF999/P18f88svvwDw4IMPFlqO4OBgateuna8fNI8cOQJA+fLlr9rmjz/+IDs7+4qpZpd7+OGHWbFiBYsWLWLgwIH069ePESNGEBkZSUBAQE67tWvXcubMmVxTHJ966ilmzJjBo48+yrPPPktERASff/4527dvZ926dTg5OeW0PXDgAPfffz9PPfUUTzzxBPXr188zz+uvv058fDynTp3i008/BcDT0zNXmw8++ACr1cqLL75IfHw8H330Ef3792fjxo05bZYvX07Pnj1p2bIlb7/9NlarlenTp3PLLbewZs0a2rRpc9XPydX883M+Y8YMPD09GT58OJ6enixfvpy33nqLhIQERo8eDUBGRgahoaGkp6fzzDPPEBAQwOnTp/n111+Ji4vDx8eHb7/9loEDB9KmTRuefPJJAGrXrv2veWbMmMGECRMwDIOGDRvyxhtv8MADD/zrxzk6OjJ58mTat2/Pu+++i7+/P9u2bWPRokW4u7uzZ88eOnToQNWqVfnf//6Hh4cHc+fOpXfv3vzwww/06dMHgKNHj/Ljjz9yzz33EBQURFRUFF9++SWdO3dm7969V0xvfffdd3F2dubFF18kPT0dZ2dnRowYwahRo3LuPyEhgS1btrBt2za6d++e/y+OiMjVmD20JiJSGlzvNMIaNWoYgPHSSy8V6Hp9+vQxACMuLi7X8dTUVOPcuXM5fy5cuHDFNfOaRnjJnXfeaQBGfHy8YRh/T52bNm2ace7cOePMmTPGb7/9ZtSsWdOwWCzG5s2br3quYcOGGYCxffv2q7bZtm2bARjDhw83DMMwDhw4kOe0w6efftrw9PTMmf63Zs0aAzBmzZqVq92iRYuuOH7pc7xo0aKr5rjcv00jbNiwoZGenp5zfNy4cQZg7Nq1yzCMi9MU69ata4SGhho2my2nXUpKihEUFGR07979mte/9HUaOXKkce7cOSMyMtJYuXKlceONNxpAzrTRvKZCPvXUU4a7u7uRlpZmGIZhbN++PV9THws6jbB9+/bG2LFjjZ9++smYOHGi0aRJEwMwJkyYkO9zDB061HBycjI8PT2N+++/P+d4165djaZNm+bcg2Fc/Jy2b9/eqFu3bs6xtLS0K6YCRkREGC4uLsY777yTc+zS161WrVpXfM6aN29u9OrVK9+ZRUQKStMIRURMdGlVtnr16hXo4xISEoArR10mTZpExYoVc/7cdNNNBTrvpfMlJibmOv7YY49RsWJFqlSpQq9evXKmnbVq1eqq57p0Di8vr6u2ufTepfupV68eN9xwA3PmzMlpk52dzfz587n99ttxc3MDYN68efj4+NC9e3diYmJy/rRs2RJPT09WrFiR6zpBQUGEhobm99NwTY8++ijOzs45rzt27AiQszrjjh07OHToEA888ADnz5/PyZacnEzXrl1ZvXp1vlbbe/vtt6lYsSIBAQF06dKFI0eO8OGHH+ZMG730uYCLn+uYmBg6duxISkoK+/fvB8DHxweAxYsXk5KSUij3D7Bu3Tqee+457rjjDgYNGsTWrVtp0qQJr732Gqmpqfk6x3vvvUf58uWxWq05o4ixsbEsX76ce++9N+eeYmJiOH/+PKGhoRw6dIjTp08D4OLikrOQRnZ2NufPn8fT05P69euzbdu2K643YMCAXJ8zAF9fX/bs2cOhQ4f+y6dDROSqNI1QRKSYWCyWK4698sor/P777zz11FP4+vrmeibpWi4VKUlJSTk/UAPcddddNGnSBIAXXnjhiud3/k1SUlKu81/y1ltv0bFjRxwcHKhQoQINGzbE0fHa30IuneOfhdvl8irI+vXrx2uvvcbp06epWrUqK1euJDo6mn79+uW0OXToEPHx8fj7++d53ujo6Fyvg4KCrpm1IKpXr57rdbly5QBynhW79IP7gAEDrnqO+Pj4nI+7mieffJJ77rkHq9WKr68vjRs3xsXFJef9PXv28MYbb7B8+fKcYvXy88PF+x4+fDhjxoxh1qxZdOzYkTvuuIMHH3wwV7/5r5ydnRk6dGhO4ZWfIt/b25v69esTExNDpUqVADh8+DCGYfDmm29esYrmJdHR0VStWhWbzca4ceOYMGECERERufp6XtNb8+oD77zzDnfeeSf16tWjSZMm9OjRg4ceeohmzZrl99ZFRK5JxZaISCFwdXUFuOpv9VNSUnLaXM7T05M//viDTp060b9/f7y9vQkJCfnX6zVo0ACA3bt306FDh5zj1apVo1q1asDFIuBq+35dze7du/H398fb2zvX8aZNm9KtW7cCnathw4YA/Pnnn1ddqvzPP/8EoFGjRjnH+vXrx6uvvsq8efMYNmwYc+fOxcfHhx49euS0sdls+Pv7M2vWrDzPW7FixVyv/zmi8V84ODjkedwwjJxsAKNHj77qff9zRDIvdevWvernPC4ujs6dO+Pt7c0777xD7dq1cXV1Zdu2bbzyyiu5Rs4++eQTHnnkEX766SeWLFnCs88+y6hRo9iwYQOBgYH/miO/LvW72NjY6z7HpdwvvvjiVUci69SpA1xcrv7NN9/kscce491338XPzw+r1cqwYcPyHDnMqw906tSJI0eO5HxupkyZwqeffsqkSZMYOHDgdd+HiMglKrZERApBjRo1gIsLMVz6ofOSlJQUTp48edUiqnz58ixZsoQOHTrQt29fwsLCCA4Ovub1brvtNj744ANmzZqVq9j6L8LDwzly5EihLbrRs2dPHBwc+Pbbb6+6SMY333yDo6NjrkIqKCiINm3aMGfOHIYOHcqCBQvo3bt3rlGd2rVrs3TpUjp06FCohRTkPQJZEJcWl/D29i5wgZpfK1eu5Pz58yxYsIBOnTrlHL/a6pBNmzaladOmvPHGG6xfv54OHTowadKknJUv/+s9w9/TKP9Z6BZErVq1AHBycvrXz938+fO5+eabmTp1aq7jcXFxVKhQId/X9PPz49FHH+XRRx8lKSmJTp06MWLECBVbIlIo9MyWiEgh6Nq1K87OzkycOPGK36pPnjyZrKwsevbsedWPr1q1KmFhYXh4eNCrVy927dp1zet16NCB7t27M3nyZH766ac821waacmP48eP88gjj+Ds7MxLL72U74+7lmrVqvHoo4+ydOlSJk6ceMX7kyZNYvny5Tz++ONXjLD069ePDRs2MG3aNGJiYnJNIQS49957yc7O5t13373ivFlZWcTFxV13bg8Pj5xpeNejZcuW1K5dm48//jhnWublzp07d93nvuTS6NrlX+OMjAwmTJiQq11CQgJZWVm5jjVt2hSr1Up6enrOMQ8Pj3x/zvLKn5iYyNixY6lQoQItW7bM721cwd/fny5duvDll19y9uzZa17bwcHhij4+b968nGe68uP8+fO5Xnt6elKnTp1cnxsRkf9CI1siIoXA39+ft956izfeeINOnTpxxx134O7uzvr16/nuu+8ICQnh9ttvv+Y56taty+LFi+nSpQuhoaGsXbs25zf9eZk5cyY9evSgd+/e9OzZk27dulGuXDkiIyNZunQpq1evzrPA27ZtGzNnzsRmsxEXF8fmzZv54YcfsFgsfPvtt4X6vMqnn37K/v37efrpp1m0aFHOCNbixYv56aef6Ny5M5988skVH3fvvffy4osv8uKLL+Ln53fFKEfnzp156qmnGDVqFDt27CAkJAQnJycOHTrEvHnzGDduXL6ff/unli1bMmfOHIYPH07r1q3x9PT816/d5axWK1OmTKFnz540btyYRx99lKpVq3L69GlWrFiBt7d3ztL916t9+/aUK1eOAQMG8Oyzz+Z87f5ZfCxfvpyhQ4dyzz33UK9ePbKysvj2229xcHDgrrvuynXPS5cuZcyYMVSpUoWgoCDatm2b57W/+OILfvzxR26//XaqV6/O2bNnmTZtGidOnODbb7/NtXjI9fjiiy+46aabaNq0KU888QS1atUiKiqK8PBwTp06lbOP1m233cY777zDo48+Svv27dm1axezZs265v8z/9SoUSO6dOlCy5Yt8fPzY8uWLcyfPz/PfeFERK6LmUshioiUNjNnzjTatWtneHh4GC4uLkaDBg2MkSNH5lrG2jCuvQz7mjVrDDc3NyMoKMg4ffr0Na+XmppqjB071ggODja8vb0NR0dHIyAgwLjtttuMWbNmGVlZWVdc89IfR0dHw8/Pz2jbtq3x6quvGsePH7/i/JeWzf63pcOvJT093fj000+Nli1bGh4eHoa7u7vRokULY+zYsUZGRsZVP65Dhw4GYAwcOPCqbSZPnmy0bNnScHNzM7y8vIymTZsaL7/8snHmzJmcNjVq1CjQ8t5JSUnGAw88YPj6+uYsz28YV/9cXPq8Tp8+Pdfx7du3G3379jXKly9vuLi4GDVq1DDuvfdeY9myZde8fn6W6DcMw1i3bp3Rrl07w83NzahSpYrx8ssvG4sXLzYAY8WKFYZhGMbRo0eNxx57zKhdu7bh6upq+Pn5GTfffLOxdOnSXOfav3+/0alTJ8PNzc0ArrkM/JIlS4zu3bsbAQEBhpOTk+Hr62uEhIT8633lpXPnzkbjxo2vOH7kyBHj4YcfzrlG1apVjdtuu82YP39+Tpu0tDTjhRdeMCpXrmy4ubkZHTp0MMLDw43OnTvn2mbhWn34//7v/4w2bdoYvr6+hpubm9GgQQPjvffeu2a/FBEpCIthFGCeiYiIiIiIiOSLntkSEREREREpAiq2REREREREioCKLRERERERkSKgYktERERERKQIqNgSEREREREpAiq2REREREREioA2Nc4Hm83GmTNn8PLywmKxmB1HRERERERMYhgGiYmJVKlSBav12mNXKrby4cyZM1SrVs3sGCIiIiIiYidOnjxJYGDgNduo2MoHLy8v4OIn1Nvb2+Q0kJmZyZIlSwgJCcHJycnsOCIFov4rJZn6r5Rk6r9SktlT/01ISKBatWo5NcK1qNjKh0tTB729ve2m2HJ3d8fb29v0ziZSUOq/UpKp/0pJpv4rJZk99t/8PF6kBTJERERERESKgIotERERERGRIqBiS0REREREpAio2BIRERERESkCKrZERERERESKgIotERERERGRIqBiS0REREREpAio2BIRERERESkCKrZERERERESKgIotERERERGxW9k2g40RsWyNsbAxIpZsm2F2pHxzNDuAiIiIiIhIXhbtPsvIX/ZyNj4NcOCbQ1uo7OPK27c3okeTymbH+1ca2RIREREREbuzaPdZBs/c9leh9bfI+DQGz9zGot1nTUqWfyq2RERERETErmTbDEb+spe8JgxeOjbyl712P6VQxZaIiIiIiNiVTRGxV4xoXc4AzsansSkitvhCXQcVWyIiIiIiYleiE69eaF1PO7Oo2BIREREREbvi7+VaqO3MomJLRERERETsSpsgPyr7XL2QsgCVfVxpE+RXfKGug4otERERERGxKw5WC2/f3ijP9yx//f327Y1wsFrybGMvtM+WiIiIiIjYHW9XpzyPB5SgfbZUbImIiIiIiF0xDIOPFh8A4KF21Qlt5M+SNRsJ6diW4Dr+dj+idYmKLRERERERsSthe6PYcTIONycHnulal3KuDpzfZ9A2yK/EFFqgZ7ZERERERMSOZNsMPllyEIBHO9S0+xUHr0XFloiIiIiI2I2fd57mQFQi3q6OPNWpttlx/hMVWyIiIiIiYhcysmyMCbs4qjWoS2183PNeJKOkULElIiIiIiJ2Yc6Wk5yMTaWCpwuPtK9pdpz/TMWWiIiIiIiYLjUjm8+WHQLg2a51cHcu+Wv5qdgSERERERHTzVh/jOjEdALLuXFf6+pmxykUKrZERERERMRU8amZTFp1BIDnu9XD2bF0lCmm3sXEiRNp1qwZ3t7eeHt7ExwczB9//JHz/uTJk+nSpQve3t5YLBbi4uKuOEdsbCz9+/fH29sbX19fHn/8cZKSknK1+fPPP+nYsSOurq5Uq1aNjz76qKhvTURERERE8umr1UeJT82kXiVPet9Y1ew4hcbUYiswMJAPPviArVu3smXLFm655RbuvPNO9uzZA0BKSgo9evTgtddeu+o5+vfvz549ewgLC+PXX39l9erVPPnkkznvJyQkEBISQo0aNdi6dSujR49mxIgRTJ48ucjvT0REREREru1cYjrT1kUA8EJI/RK1afG/MfWps9tvvz3X6/fee4+JEyeyYcMGGjduzLBhwwBYuXJlnh+/b98+Fi1axObNm2nVqhUAn332Gbfeeisff/wxVapUYdasWWRkZDBt2jScnZ1p3LgxO3bsYMyYMbmKMhERERERKX5frDhMSkY2zav5EtKoktlxCpXdLPGRnZ3NvHnzSE5OJjg4OF8fEx4ejq+vb06hBdCtWzesVisbN26kT58+hIeH06lTJ5ydnXPahIaG8uGHH3LhwgXKlSt3xXnT09NJT0/PeZ2QkABAZmYmmZmZ13uLheZSBnvIIlJQ6r9Skqn/Skmm/iv26HRcKrM2HgdgeNfaZGVl5dnOnvpvQTKYXmzt2rWL4OBg0tLS8PT0ZOHChTRq1ChfHxsZGYm/v3+uY46Ojvj5+REZGZnTJigoKFebSpUq5byXV7E1atQoRo4cecXxJUuW4O7unq9sxSEsLMzsCCLXTf1XSjL1XynJ1H/Fnsw6bCUz20o9HxtxBzby+4Frt7eH/puSkpLvtqYXW/Xr12fHjh3Ex8czf/58BgwYwKpVq/JdcBWFV199leHDh+e8TkhIoFq1aoSEhODt7W1arksyMzMJCwuje/fuODmV7F21pexR/5WSTP1XSjL1X7E3h6KT2LJhPQDv9WvHDdV8r9rWnvrvpVlv+WF6seXs7EydOnUAaNmyJZs3b2bcuHF8+eWX//qxAQEBREdH5zqWlZVFbGwsAQEBOW2ioqJytbn0+lKbf3JxccHFxeWK405OTqZ/cS9nb3lECkL9V0oy9V8pydR/xV58tuIoNgNCGlWida2K+foYe+i/Bbm+3S1gb7PZcj0vdS3BwcHExcWxdevWnGPLly/HZrPRtm3bnDarV6/ONbcyLCyM+vXr5zmFUEREREREitafp+L4Y3ckFgu8GFrf7DhFxtRi69VXX2X16tUcO3aMXbt28eqrr7Jy5Ur69+8PXHymaseOHRw+fBi4+HzXjh07iI2NBaBhw4b06NGDJ554gk2bNrFu3TqGDh3KfffdR5UqVQB44IEHcHZ25vHHH2fPnj3MmTOHcePG5ZomKCIiIiIixWf04osPZ/W5oSr1KnmZnKbomDqNMDo6mocffpizZ8/i4+NDs2bNWLx4Md27dwdg0qRJuRaq6NSpEwDTp0/nkUceAWDWrFkMHTqUrl27YrVaueuuuxg/fnzOx/j4+LBkyRKGDBlCy5YtqVChAm+99ZaWfRcRERERMcH6IzGsORSDk4OF57vXMztOkTK12Jo6deo13x8xYgQjRoy4Zhs/Pz9mz559zTbNmjVjzZo1BY0nIiIiIiKFyDCMnFGt+9tUp5qf/az0XRTs7pktEREREREpnZbui2b7iThcnawMvbmO2XGKnIotEREREREpcjabwcd/jWo92iEIf29XkxMVPRVbIiIiIiJS5H7eeYYDUYl4uToyqFNts+MUCxVbIiIiIiJSpDKybIwJOwjAoM618XEvG3u9qdgSEREREZEiNXfLSU7EplDB04VHO9Q0O06xUbElIiIiIiJFJjUjm/HLDgHwzC11cHc2dUH0YqViS0REREREiszX4ceITkwnsJwb97epbnacYqViS0REREREikRCWiYTVx4BYFi3ejg7lq3yo2zdrYiIiIiIFJuvVh8lPjWTuv6e9Lmxqtlxip2KLRERERERKXTnEtOZujYCgBdC6uNgtZicqPip2BIRERERkUI3YeVhUjKyaR7oQ2jjSmbHMYWKLRERERERKVSnLqQwa8MJAF4KbYDFUvZGtUDFloiIiIiIFLJxSw+RkW2jfe3y3FS3gtlxTKNiS0RERERECs3h6CR+2HYKgBdD65ucxlwqtkREREREpNCMCTuAzYDujSrRono5s+OYSsWWiIiIiIgUil2n4vl9VyQWC7wYUrZHtUDFloiIiIiIFJKPFu8HoPcNVakf4GVyGvOp2BIRERERkf8s/Mh51hyKwdFq4flu9cyOYxdUbImIiIiIyH9iGAaj/xrVur9NdaqXdzc5kX1QsSUiIiIiIv/Jsn3RbDsRh6uTlWduqWN2HLuhYktERERERK6bzWbw8ZIDADzSPgh/b1eTE9kPFVsiIiIiInLdfvnzDPsjE/FydWRQ51pmx7ErKrZEREREROS6ZGbbGBN2EICnOtXC193Z5ET2RcWWiIiIiIhcl7lbTnL8fAoVPJ15tEOQ2XHsjootEREREREpsLTMbMYvOwTA0Jvr4OHiaHIi+6NiS0RERERECuzr9ceISkinqq8b97etbnYcu6RiS0RERERECiQhLZOJq44AMKxbXVwcHUxOZJ9UbImIiIiISIFMWX2UuJRM6vh70rdFoNlx7JaKLRERERERybeYpHSmrI0A4IXu9XCwWkxOZL9UbImIiIiISL5NWHGElIxsmgX60KNJgNlx7JqKLRERERERyZfTcanM3HAcgJdC62OxaFTrWlRsiYiIiIhIvoxbepCMbBvtavlxU50KZsexeyq2RERERETkXx05l8T8racAeLlHA41q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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], "source": [ "# this will run until completion.\n", "last_action, last_result, last_state = app2.run(halt_after=[\"terminal\"])" - ] + ], + "outputs": [] }, { "cell_type": "code", @@ -637,10 +523,10 @@ }, "scrolled": true }, - "outputs": [], "source": [ "pprint.pprint(last_state)" - ] + ], + "outputs": [] }, { "cell_type": "markdown", @@ -657,13 +543,13 @@ "execution_count": 23, "id": "8883f456-2217-4802-be0f-3e14cc1475bd", "metadata": {}, - "outputs": [], "source": [ "# Let's change the query\n", "initial_state, entry_point = default_state_and_entry_point(\"Fetch the USA's GDP over the past 5 years, then draw a line graph of it. \"\n", " \"Once the python code has been written and the graph drawn, the task is complete.\")\n", "app2 = build_application(initial_state, entry_point)" - ] + ], + "outputs": [] }, { "cell_type": "code", @@ -679,22 +565,11 @@ "outputs_hidden": false } }, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA2QAAAHWCAYAAAAYdUqfAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB4WklEQVR4nO3dd3gU5d7G8XvTCykEEgIkkAAiVXqzIB4gURHFioBIV5AcBaxYKJaXY0FFRTioBKQIWLChQAQEkR5ACE2Q0CQBQkghPdl5/8DsYU2ABJNMyvdzXblkZ56Z+c3yEPfeeeYZi2EYhgAAAAAAZc7B7AIAAAAAoKoikAEAAACASQhkAAAAAGASAhkAAAAAmIRABgAAAAAmIZABAAAAgEkIZAAAAABgEgIZAAAAAJiEQAYAAAAAJiGQAQBQBiwWiyIiIswuAwBQzhDIAKAUTJo0SRaLRQkJCYWub9Gihbp162a37MyZM3riiSfUpEkTubu7KyAgQB07dtSzzz6r8+fPF7qfDz/8UBaLRZ06dbqqOr/77jv17t1btWrVkouLi/z8/NS1a1dNnTpVKSkpdm1DQkJksVhksVjk4OAgX19ftWzZUo888og2b95c6P7z2+dvU6dOHYWFhennn38uco3ff/+9br31VtWoUUNubm5q3LixnnrqKZ09e/aqzrk0bdiwQZMmTVJSUpJpNVz892SxWBQQEKCbbrpJS5cuLZXjLVy4UO+++26R23fr1s2uvvyfW2+99bLb/ec//5HFYtGKFSsKXX/77bfLx8dHJ0+eLE75AGA6J7MLAABIiYmJat++vVJSUjR06FA1adJEZ8+e1a5duzRjxgyNGjVK1apVK7DdggULFBISoi1btujQoUNq1KhRkY5ntVo1bNgwzZkzRy1bttRjjz2m4OBgpaamauPGjXrxxRf1ww8/aNWqVXbbtW7dWk8++aQkKTU1Vfv27dPnn3+ujz76SGPHjtXbb79d4Fg9e/bUww8/LMMwFBsbqw8//FD/+te/tGzZMt12222XrfOpp57S1KlT1apVKz377LPy8/PT9u3b9cEHH2jRokVatWqVrr322iKdc1nYsGGDJk+erMGDB8vX19e0Oi7+ezp58qT++9//6p577tGMGTM0cuTIEj3WwoULFRMTozFjxhR5m6CgIE2ZMsVuWZ06dS67zZNPPqmFCxfqscceU0xMjNzd3W3rPv/8c/3444+aPn36FfcDAOWOAQAocRMnTjQkGWfOnCl0ffPmzY2bb77Z9vqNN94wJBm//vprgbbJyclGRkZGgeWHDx82JBlfffWV4e/vb0yaNKnI9U2ZMsWQZIwdO9awWq0F1p88edL4z3/+Y7esfv36Rq9evQq0TU9PN/r06WNIMj788EO7dZKM0aNH2y3btWuXIckICwu7bI0LFy40JBl9+/Y1cnNz7dZt3rzZ8PDwMFq2bGnk5ORcdj8l7fz585dc9+abbxqSjNjY2ALrCnsvSkNhf09xcXGGp6en0bhx4xI/Xq9evYz69esXuf3NN99sNG/e/KqOtXHjRsPBwcEYP368bVlKSopRp04do3PnzkZeXt5V7bc48vLyCv33CABXiyGLAFAO/PHHH3J0dFTnzp0LrPP29pabm1uB5QsWLFD16tXVq1cv3XfffVqwYEGRjpWenq7XX39dzZs315tvvimLxVKgTe3atfXss88WaX/u7u6aN2+e/Pz89Nprr8kwjMu2b9mypWrWrKnY2NjLtps8ebKqV6+uWbNmydHR0W5d/lDO3bt364svvpAkRUREqFq1akpPTy+wr379+ikwMFB5eXm2ZT/++KNuuukmeXp6ysvLS7169dKePXvsths8eLCqVaumP/74Q7fffru8vLw0YMCAQuudNGmSnn76aUlSaGiobSjekSNH7Np9/fXXatGihVxdXdW8eXMtX768wL7+/PNPDR06VLVq1bK1mz179mXfr8sJDAxU06ZNbe/5rl27NHjwYDVo0EBubm4KDAzU0KFDCwwDTU1N1ZgxYxQSEiJXV1cFBASoZ8+e2r59u6QLww+XLVumo0eP2s43JCSkSDXl5uZecijupXTu3FkjR47UW2+9pb1790qSXnzxRZ0+fVqzZs2Sg4ODkpKSNGbMGAUHB8vV1VWNGjXS66+/LqvVarevt956S9dff71q1Kghd3d3tWvXztaXLpZ/79+CBQvUvHlzubq62v7OFi1apHbt2snLy0ve3t5q2bKlpk2bVqxzAgACGQCUA/Xr11deXp7mzZtX5G0WLFige+65Ry4uLurXr58OHjyorVu3XnG79evXKykpSf369SsQdK5WtWrVdPfdd+vPP/+0fVC+lHPnzuncuXOqUaPGJdscPHhQBw4c0F133SVvb+9C2zz88MOSLtxj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+vbta2uTP7nDvHnzFBgYWGC/Tk72/2t0dXW9bHgsjkvds5d/BTS/toceekiDBg0qtO111113xePUrFnzsu/5Aw88oA0bNujpp59W69atVa1aNVmtVt166612k1888MADtmeYrVy5Um+++aZef/11ffXVV1d8ZEFxBAcHS7oQFP8Jq9Wqnj176plnnil0fePGjSVJv/zyi+6880517dpVH374oWrXri1nZ2dFRkZq4cKFBba7+GpavoCAAO3cuVMrVqzQjz/+qB9//FGRkZF6+OGHNXfu3H90HgCqFgIZAJSC+vXrS5IOHDhg+7CZLz09XcePH79saMrXoEEDVa9eXXFxcbZlCxYsUEBAgKZPn16g/VdffaWlS5dq5syZhX6IlKSbbrpJPj4+WrRokcaPH18iYeP8+fNaunSpgoODbWHqn8gPpF9//bWmTZtW6NDFTz/9VJJ0xx132C1/4IEHNG3aNKWkpGjx4sUKCQmxm70yf8hgQEBAsYPilRQ2Y2Vx+Pv7y8vLS3l5eSVeW75z585p1apVmjx5siZMmGBbnn/l8O9q166txx57TI899phOnz6ttm3b6rXXXrMFsn96zpJsV90ungDlajRs2FDnz5+/4nv35Zdfys3NTStWrJCrq6tteWRkZLGO5+Liot69e6t3796yWq167LHH9N///lcvvfRSkZ8JCAAMWQSAUtC9e3e5uLhoxowZBabbnjVrlnJzc+2uMGzevFlpaWkF9rNlyxadPXvW9vDjjIwMffXVV7rjjjt03333FfiJiIhQamqqbcheYTw8PPTMM88oJiZGzz33XKHT1Be27FIyMjI0cOBAJSYm6oUXXiiRD+iSNGHCBJ07d04jR44scF9cdHS0Xn/9dbVo0aLArId9+/ZVVlaW5s6dq+XLl+uBBx6wWx8eHi5vb2/93//9X6Ez+505c+aqa/b09JQkJSUlXdX2jo6Ouvfee/Xll1/arjiWVG0XH0Mq+Hf899kR8/LyCgzfCwgIUJ06dZSVlWVb5unpWegwv8KkpKTYbZtfx6uvvirpwt/NP/HAAw9o48aNWrFiRYF1SUlJys3NlXThPbBYLHb96siRI/r666+LfKy/31/p4OBgu3r593MEgMvhChkAlIKAgABNmDBBL774orp27ao777xTHh4e2rBhgz777DOFhYWpd+/etvbz5s3TggULdPfdd6tdu3ZycXHRvn37NHv2bLm5uen555+XJH377bdKTU3VnXfeWehxO3fuLH9/fy1YsMBumN7fPffcc9q3b5/efPNNrVy5Uvfee6+CgoJ07tw5bd++XZ9//rkCAgIKDKv8888/NX/+fEkXrort3btXn3/+ueLj4/Xkk0/q0Ucf/advnc2AAQO0detWTZs2TXv37tWAAQNUvXp1bd++XbNnz1aNGjX0xRdfyNnZ2W67tm3bqlGjRnrhhReUlZVV4H3w9vbWjBkzNHDgQLVt21YPPvig/P39dezYMS1btkw33HCDPvjgg6uquV27dpIuTAf/4IMPytnZWb1797YFtaL4z3/+ozVr1qhTp04aMWKEmjVrpsTERG3fvl0//fTTPx7W5+3tra5du+qNN95QTk6O6tatq5UrVxZ4LlxqaqqCgoJ03333qVWrVqpWrZp++uknbd26VVOnTrU758WLF2vcuHHq0KGDqlWrZte3L7Z9+3b169dP/fr1U6NGjZSRkaGlS5fq119/1SOPPKK2bdv+o3N7+umn9e233+qOO+7Q4MGD1a5dO6WlpdmeV3fkyBHVrFlTvXr10ttvv61bb71V/fv31+nTpzV9+nQ1atTossNkLzZ8+HAlJibqX//6l4KCgnT06FG9//77at26dYlcJQZQhZg3wSMAVH7z5883OnfubHh6ehqurq5GkyZNjMmTJxuZmZl27Xbt2mU8/fTTRtu2bQ0/Pz/DycnJqF27tnH//fcb27dvt7Xr3bu34ebmZqSlpV3ymIMHDzacnZ0LTJtemKVLlxq333674e/vbzg5ORm+vr7GjTfeaLz55psFptyvX7++IcmQZFgsFsPb29to3ry5MWLECGPz5s2F7l/SFadNv5Kvv/7a6Nmzp1G9enXD1dXVaNSokfHkk09e8pEChmEYL7zwgiHJaNSo0SXbrFmzxggPDzd8fHwMNzc3o2HDhsbgwYONbdu22doMGjTI8PT0LFa9r7zyilG3bl3DwcHBbgr8S70X9evXNwYNGmS37NSpU8bo0aON4OBgw9nZ2QgMDDS6d+9uzJo164rHv9LjCQzDME6cOGHcfffdhq+vr+Hj42Pcf//9xsmTJw1JxsSJEw3DMIysrCzj6aefNlq1amV4eXkZnp6eRqtWrYwPP/zQbl/nz583+vfvb/j6+hqSLjsF/uHDh43777/fCAkJMdzc3AwPDw+jXbt2xsyZM+0esVAUl3q0RGpqqjF+/HijUaNGhouLi1GzZk3j+uuvN9566y0jOzvb1u6TTz4xrrnmGtu/y8jISNs+L3apv7cvvvjCCAsLMwICAgwXFxejXr16xqOPPmrExcUV6zwAwGIYxRiXAgAAAAAoMdxDBgAAAAAmIZABAAAAgEkIZAAAAABgEgIZAAAAAJiEQAYAAAAAJiGQAQAAAIBJeDB0CbFarTp58qS8vLxksVjMLgcAAACASQzDUGpqqurUqSMHh8tfAyOQlZCTJ08qODjY7DIAAAAAlBPHjx9XUFDQZdsQyEqIl5eXpAtvure3t6m15OTkaOXKlQoLC5Ozs7OptQBXgz6Mioz+i4qM/ouKrDz135SUFAUHB9sywuUQyEpI/jBFb2/vchHIPDw85O3tbXpnBK4GfRgVGf0XFRn9FxVZeey/RbmViUk9AAAAAMAkBDIAAAAAMAmBDAAAAABMQiADAAAAAJMQyAAAAADAJAQyAAAAADAJgQwAAAAATEIgAwAAAACTEMgAAAAAwCQEMgAAAAAVWp7V0ObYREUnWLQ5NlF5VsPskorMyewCAAAAAOBqLY+J0+Tv9iouOVOSoz49uE21fdw0sXcz3dqittnlXRFXyAAAAABUSMtj4jRq/va/wtj/xCdnatT87VoeE2dSZUVHIAMAAABQ4eRZDU3+bq8KG5yYv2zyd3vL/fBFAhkAAACACmdLbGKBK2MXMyTFJWdqS2xi2RV1FQhkAAAAACqc06mXDmNX084sBDIAAAAAFU6Al1uJtjMLgQwAAABAhRNS00NODpZLrrdIqu3jpo6hfmVX1FUgkAEAAACoUOKSMzTgo83KvcSEHfkxbWLvZnK8TGgrDwhkAAAAACqM44npeuC/G3U4IU11fd31yl0tVNvHflhioI+bZjzUtkI8h4wHQwMAAACoEI4kpGnAx5v1Z1KG6tfw0ILhnRRU3UP9O9XTxkOntfKXzQq7qZO6NAoo91fG8hHIAAAAAJR7h06f14CPN+lUSpYa+Htq4fDOCvzrypijg0WdQv10dp+hTqF+FSaMSQQyAAAAAOXcgfhUDfh4kxLOZ+vaWl6aP7yT/L1czS6rRBDIAAAAAJRbMX8ma+Anm3UuPUfNantr/vBO8vN0MbusEkMgAwAAAFAu7TyepIc/2ayUzFy1CvLRp0M7ycfD2eyyShSBDAAAAEC5s/VIooZEbtX5rFy1r19dkUM6yMutcoUxiUAGAAAAoJzZcChBw+ZuU0ZOnjo38NMngzrI07VyRpfKeVYAAAAAKqS1v5/RI59uU1auVTddU1OzBraXu4uj2WWVGgIZAAAAgHLhp72n9NiC7crOs6pH0wB90L+t3JwrbxiTCGQAAAAAyoEfd8fp35/tUK7V0G0tAjXtwTZycXIwu6xSRyADAAAAYKpvdv6pcUt+U57V0F2t62jq/a3k5Fj5w5hEIAMAAABgoiXbjuvZL3fJMKT72gXp9Xuvk6ODxeyyygyBDAAAAIApFmw+qheWxkiS+neqp1fvaiGHKhTGJAIZAAAAABPMXh+rl7/fK0kackOIJtzRTBZL1QpjEoEMAAAAQBmbufYP/efH/ZKkR29uoOdubVIlw5hEIAMAAABQRgzD0HurDumdn36XJD3e/RqN7XFNlQ1jEoEMAAAAQBkwDENvrTyg6Wv+kCQ9HX6tRt/SyOSqzEcgAwAAAFCqDMPQq8v26ZP1sZKkF3s11fCbGphcVflAIAMAAABQaqxWQxO+jdH8TcckSS/f1VwPdwkxt6hyhEAGAAAAoFTkWQ09/9VuLd52XBaL9J97Wqpvh3pml1WuEMgAAAAAlLjcPKue/mKXlu74Uw4WaeoDrXR3myCzyyp3CGQAAAAASlROnlVjFu3Ust1xcnKwaNqDbdTrutpml1UuEcgAAAAAlJis3DxFLNyhqL2n5Oxo0fT+bRXWPNDsssotAhkAAACAEpGZk6eR86P184EzcnFy0H8HttMt1waYXVa5RiADAAAA8I+lZ+dqxKfb9Ouhs3JzdtAngzrohkY1zS6r3COQAQAAAPhHzmflamjkVm05kihPF0fNHtxBnRrUMLusCoFABgAAAOCqJWfkaHDkFu04liQvNyfNHdpRbetVN7usCoNABgAAAOCqnEvL1sDZmxXzZ4p83J01f1gntQzyMbusCsXBzINPmTJFHTp0kJeXlwICAtSnTx8dOHDArk23bt1ksVjsfkaOHGnX5tixY+rVq5c8PDwUEBCgp59+Wrm5uXZtfv75Z7Vt21aurq5q1KiR5syZU6Ce6dOnKyQkRG5uburUqZO2bNlS4ucMAAAAVAYJ57PU76NNivkzRTU8XfTZiM6EsatgaiBbu3atRo8erU2bNikqKko5OTkKCwtTWlqaXbsRI0YoLi7O9vPGG2/Y1uXl5alXr17Kzs7Whg0bNHfuXM2ZM0cTJkywtYmNjVWvXr10yy23aOfOnRozZoyGDx+uFStW2NosXrxY48aN08SJE7V9+3a1atVK4eHhOn36dOm/EQAAAEAFciolUw/O2qT98any93LVokc6q1kdb7PLqpBMHbK4fPlyu9dz5sxRQECAoqOj1bVrV9tyDw8PBQYW/uyClStXau/evfrpp59Uq1YttW7dWq+88oqeffZZTZo0SS4uLpo5c6ZCQ0M1depUSVLTpk21fv16vfPOOwoPD5ckvf322xoxYoSGDBkiSZo5c6aWLVum2bNn67nnniuN0wcAAAAqnJNJGer/0SYdOZuu2j5uWjiis0JreppdVoVVru4hS05OliT5+fnZLV+wYIHmz5+vwMBA9e7dWy+99JI8PDwkSRs3blTLli1Vq1YtW/vw8HCNGjVKe/bsUZs2bbRx40b16NHDbp/h4eEaM2aMJCk7O1vR0dEaP368bb2Dg4N69OihjRs3FlprVlaWsrKybK9TUlIkSTk5OcrJybnKd6Bk5B/f7DqAq0UfRkVG/0VFRv/FlRw/l66HZ2/TiaRMBfm66dOh7RXk41Iu+kx56r/FqaHcBDKr1aoxY8bohhtuUIsWLWzL+/fvr/r166tOnTratWuXnn32WR04cEBfffWVJCk+Pt4ujEmyvY6Pj79sm5SUFGVkZOjcuXPKy8srtM3+/fsLrXfKlCmaPHlygeUrV660hUWzRUVFmV0C8I/Qh1GR0X9RkdF/UZjTGdL0vY5Kyraoppuh4Q3Oa/fGn7Xb7ML+pjz03/T09CK3LTeBbPTo0YqJidH69evtlj/yyCO2P7ds2VK1a9dW9+7d9ccff6hhw4ZlXabN+PHjNW7cONvrlJQUBQcHKywsTN7e5o6fzcnJUVRUlHr27ClnZ2dTawGuBn0YFRn9FxUZ/ReXcvD0eb0auU1J2dlq6O+pT4e0V4CXq9ll2SlP/Td/9FxRlItAFhERoe+//17r1q1TUFDQZdt26tRJknTo0CE1bNhQgYGBBWZDPHXqlCTZ7jsLDAy0Lbu4jbe3t9zd3eXo6ChHR8dC21zq3jVXV1e5uhbshM7OzqZ3gHzlqRbgatCHUZHRf1GR0X9xsX1xKRo4e5vOpmWrSaCX5g/vpJrVylcYu1h56L/FOb6psywahqGIiAgtXbpUq1evVmho6BW32blzpySpdu3akqQuXbpo9+7ddrMhRkVFydvbW82aNbO1WbVqld1+oqKi1KVLF0mSi4uL2rVrZ9fGarVq1apVtjYAAABAVbP7RLL6fbRJZ9Oy1aKutz4b0blch7GKyNQrZKNHj9bChQv1zTffyMvLy3bPl4+Pj9zd3fXHH39o4cKFuv3221WjRg3t2rVLY8eOVdeuXXXddddJksLCwtSsWTMNHDhQb7zxhuLj4/Xiiy9q9OjRtitYI0eO1AcffKBnnnlGQ4cO1erVq7VkyRItW7bMVsu4ceM0aNAgtW/fXh07dtS7776rtLQ026yLAAAAQFWy/dg5DZq9RamZuWod7Ku5QzvKx50rpyXN1EA2Y8YMSRce/nyxyMhIDR48WC4uLvrpp59s4Sg4OFj33nuvXnzxRVtbR0dHff/99xo1apS6dOkiT09PDRo0SC+//LKtTWhoqJYtW6axY8dq2rRpCgoK0scff2yb8l6S+vbtqzNnzmjChAmKj49X69attXz58gITfQAAAACV3ZbYRA2J3KK07Dx1DPHT7CEdVM21XNztVOmY+q4ahnHZ9cHBwVq7du0V91O/fn398MMPl23TrVs37dix47JtIiIiFBERccXjAQAAAJXVr4cSNHzuNmXk5On6hjX08aD28nAhjJUW3lkAAAAAkqQ1B07r0XnRys616ubG/vrvwHZyc3Y0u6xKjUAGAAAAQCv3xCti4Q5l51nVo2ktTR/QRq5OhLHSRiADAAAAqrhlu+L0xKIdyrUa6tWytt59sLWcHU2dkL3KIJABAAAAVdjSHSf05JLfZDWkPq3r6K37W8mJMFZmCGQAAABAFbV46zE999VuGYbUt32w/u+elnJ0sJhdVpVCIAMAAACqoHkbj+ilb/ZIkgZ2rq/JdzaXA2GszBHIAAAAgCrm418O69Vl+yRJw24M1Yu9mspiIYyZgUAGAAAAVCHT1xzSmysOSJIe69ZQT4dfSxgzEYEMAAAAqAIMw9A7Px3Ue6sOSpLG9misx7s3IoyZjEAGAAAAVHKGYej15Qc0c+0fkqRnb22iUd0amlwVJAIZAAAAUKkZhqGXv9+ryF+PSJIm3NFMQ28MNbco2BDIAAAAgErKajX00jcxWrD5mCTp1T4t9FDn+iZXhYsRyAAAAIBKKM9q6Lkvd+nz6BOyWKTX771OD7QPNrss/A2BDAAAAKhkcvOsevLz3/TNzpNydLDo7Qda6a7Wdc0uC4UgkAEAAACVSHauVU8s2qEfY+Ll5GDRe/3a6PaWtc0uC5dAIAMAAAAqicycPI1esF2r9p+Wi6ODPhzQVj2a1TK7LFwGgQwAAACoBDKy8/TIvG365WCCXJ0cNOvh9rq5sb/ZZeEKCGQAAABABZeWlavhc7dp4+Gzcnd21CeD2+v6hjXNLgtFQCADAAAAKrDUzBwNidyqbUfPqZqrkyKHdFCHED+zy0IREcgAAACACio5PUcPR27Rb8eT5O3mpE+HdVLrYF+zy0IxEMgAAACACigxLVsDP9msPSdTVN3DWfOGdVKLuj5ml4ViIpABAAAAFcyZ1Cw99PFmHTiVqprVXDR/eCc1CfQ2uyxcBQIZAAAAUIHEJ2eq/8ebdPhMmgK8XLVwRGc1Cqhmdlm4SgQyAAAAoIL4MylD/T/apKNn01XHx00LR3RWSE1Ps8vCP0AgAwAAACqAY2fT1e+jTfozKUPBfu5aOLyzgv08zC4L/xCBDAAAACjnDp85r/4fbVZ8SqZCa3pq4YhOqu3jbnZZKAEEMgAAAKAc+/1Uqvp/tFkJ57N0TUA1LRjeSQHebmaXhRJCIAMAAADKqT0nkzXwky1KTMtW09remj+so2pUczW7LJQgAhkAAABQDv12PEkPz96i5IwcXRfko0+HdpSvh4vZZaGEEcgAAACAcib6aKIGz96q1Kxcta3nqzlDO8rbzdnsslAKCGQAAABAObLp8FkNnbNV6dl56hTqp08Gd1A1Vz62V1b8zQIAAADlxC8Hz2jEp9uUmWPVTdfU1KyB7eXu4mh2WShFBDIAAACgHFi9/5RGzt+u7FyrbrnWXzMeaic3Z8JYZUcgAwAAAEy2PCZe//5su3LyDIU3r6X3+7WVi5OD2WWhDBDIAAAAABN999tJjVm8U3lWQ3dcV1vv9G0tZ0fCWFVBIAMAAABM8mX0CT39xW+yGtI9bevqzftaydHBYnZZKENEbwAAAMAEn205pqf+CmMPdgjWW4SxKokrZAAAAEAZm7vhiCZ+u0eSNKhLfU3s3VwOhLEqiUAGAAAAlKFZ6/7Q//2wX5I04qZQPX97U1kshLGqikAGAAAAlJH3Vx3U1KjfJUkRtzTSk2GNCWNVHIEMAAAAKGWGYWjqyt/1wZpDkqQnezbWv7tfY3JVKA8IZAAAAEApMgxDU37cr1nrDkuSnr+9iR7p2tDkqlBeEMgAAACAUmK1Gpr83R7N3XhUkjT5zuYadH2IuUWhXCGQAQAAAKXAajX0wte79dmW47JYpNf6tFT/TvXMLgvlDIEMAAAAKGF5VkNPf/Gbvtr+pxws0hv3tdJ97YLMLgvlEIEMAAAAKEE5eVaNW/KbvvvtpBwdLHqnb2vd2aqO2WWhnCKQAQAAACUkO9eqf3+2XSv2nJKzo0Xv92urW1sEml0WyjECGQAAAFACMnPy9NiC7Vq9/7RcnBw086G2+leTWmaXhXKOQAYAAAD8QxnZeXpk3jb9cjBBbs4OmjWwvbo29je7LFQABDIAAADgH0jLytXQOVu1OTZRHi6O+mRQB3VpWMPsslBBEMgAAACAq5SSmaPBs7do+7Ekebk6ac7QDmpX38/sslCBEMgAAACAq5CUnq2HZ2/RrhPJ8nZz0rxhndQq2NfsslDBEMgAAACAYjp7PksPfbJF++JSVN3DWfOHd1LzOj5ml4UKiEAGAAAAFMPp1EwN+GizDp4+r5rVXLVwRCc1ruVldlmooAhkAAAAQBHFJWdowEebdTghTYHeblo4opMa+FczuyxUYAQyAAAAoAiOJ6ar/8ebdDwxQ3V93fXZiM6qV8PD7LJQwRHIAAAAgCs4kpCmAR9v1p9JGapfw0MLhndSUHXCGP45AhkAAABwGYdOn9eAjzfpVEqWGvh7auHwzgr0cTO7LFQSBDIAAADgEg7Ep2rAx5uUcD5b19by0vzhneTv5Wp2WahECGQAAABAIWL+TNbATzbrXHqOmtX21vzhneTn6WJ2WahkCGQAAADA3+w8nqSHP9mslMxctQry0adDO8nHw9nsslAJEcgAAACAi2w7kqjBkVt1PitX7etXV+SQDvJyI4yhdBDIAAAAgL9s+CNBw+duU3p2njo38NMngzrI05WPzCg99C4AAABA0trfz+iRT7cpK9eqm66pqVkD28vdxdHsslDJEcgAAABQ5f2095QeW7Bd2XlWdW8SoOkD2srNmTCG0kcgAwAAQJX24+44/fuzHcq1GrqtRaCmPdhGLk4OZpeFKoJABgAAgCrrm51/atyS35RnNXRX6zqaen8rOTkSxlB2CGQAAACokpZsO65nv9wlw5Duaxek1++9To4OFrPLQhVDIAMAAECVs2DzUb2wNEaS1L9TPb16Vws5EMZgAlOvx06ZMkUdOnSQl5eXAgIC1KdPHx04cKDQtoZh6LbbbpPFYtHXX39tt+7YsWPq1auXPDw8FBAQoKefflq5ubl2bX7++We1bdtWrq6uatSokebMmVPgGNOnT1dISIjc3NzUqVMnbdmypaROFQAAAOXE7PWxtjA25IYQvdaHMAbzFDuQrV69WhEREbrjjjvUu3dvPf7441q3bt1VHXzt2rUaPXq0Nm3apKioKOXk5CgsLExpaWkF2r777ruyWAr+Q8nLy1OvXr2UnZ2tDRs2aO7cuZozZ44mTJhgaxMbG6tevXrplltu0c6dOzVmzBgNHz5cK1assLVZvHixxo0bp4kTJ2r79u1q1aqVwsPDdfr06as6NwAAAJQ/M9f+oZe/3ytJevTmBppwR7NCP2MCZaVYgWzkyJHq0aOHPvvsM509e1ZnzpzRggULdMstt+jf//53sQ++fPlyDR48WM2bN1erVq00Z84cHTt2TNHR0Xbtdu7cqalTp2r27NkF9rFy5Urt3btX8+fPV+vWrXXbbbfplVde0fTp05WdnS1JmjlzpkJDQzV16lQ1bdpUERERuu+++/TOO+/Y9vP2229rxIgRGjJkiJo1a6aZM2fKw8Oj0GMCAACgYjEMQ9N+Oqj//LhfkvR492v03K1NCGMwXZHvIVu6dKkiIyM1e/ZsDRo0yNZ5rVar5syZo1GjRqlnz5668847r7qY5ORkSZKfn59tWXp6uvr376/p06crMDCwwDYbN25Uy5YtVatWLduy8PBwjRo1Snv27FGbNm20ceNG9ejRw2678PBwjRkzRpKUnZ2t6OhojR8/3rbewcFBPXr00MaNGwutNSsrS1lZWbbXKSkpkqScnBzl5OQU88xLVv7xza4DuFr0YVRk9F9UZJW1/xqGoXd+OqQZ62IlSeN6NNKom0ML3OKCiq089d/i1FDkQBYZGalx48Zp8ODBdssdHBw0dOhQHThwQJ988slVBzKr1aoxY8bohhtuUIsWLWzLx44dq+uvv1533XVXodvFx8fbhTFJttfx8fGXbZOSkqKMjAydO3dOeXl5hbbZv39/ocedMmWKJk+eXGD5ypUr5eHhcYWzLRtRUVFmlwD8I/RhVGT0X1Rklan/Gob09VEH/Rx3YWBYn/p5qp+2Xz/8UPhnPFR85aH/pqenF7ltkQPZ9u3b9eKLL15y/T333KN77723yAf+u9GjRysmJkbr16+3Lfv222+1evVq7dix46r3W1rGjx+vcePG2V6npKQoODhYYWFh8vb2NrGyC4k8KipKPXv2lLOzs6m1AFeDPoyKjP6Liqyy9V+r1dDLy/br57jjkqSJdzTRQ53qmVwVSkt56r/5o+eKosiBLCEhQUFBQZdcHxQUpLNnzxb5wBeLiIjQ999/r3Xr1tkdY/Xq1frjjz/k6+tr1/7ee+/VTTfdpJ9//lmBgYEFZkM8deqUJNmGOAYGBtqWXdzG29tb7u7ucnR0lKOjY6FtChsmKUmurq5ydXUtsNzZ2dn0DpCvPNUCXA36MCoy+i8qssrQf/Oshl78ZrcWbzsui0WacndLPdiRMFYVlIf+W5zjF3lSj+zs7Mvu2MnJyTaJRlEZhqGIiAgtXbpUq1evVmhoqN365557Trt27dLOnTttP5L0zjvvKDIyUpLUpUsX7d692242xKioKHl7e6tZs2a2NqtWrbLbd1RUlLp06SJJcnFxUbt27ezaWK1WrVq1ytYGAAAAFUNunlVPff6bFm87LgeLNPX+VoQxlFvFejD0Sy+9dMn7o4ozTjLf6NGjtXDhQn3zzTfy8vKy3fPl4+Mjd3d3BQYGFnqFql69erbwFhYWpmbNmmngwIF64403FB8frxdffFGjR4+2XcEaOXKkPvjgAz3zzDMaOnSoVq9erSVLlmjZsmW2fY4bN06DBg1S+/bt1bFjR7377rtKS0vTkCFDin1eAAAAMEdOnlVjFu3Ust1xcnKw6N0HW+uO6+qYXRZwSUUOZF27dr3kQ5svblMcM2bMkCR169bNbnlkZGSByUMuxdHRUd9//71GjRqlLl26yNPTU4MGDdLLL79saxMaGqply5Zp7NixmjZtmoKCgvTxxx8rPDzc1qZv3746c+aMJkyYoPj4eLVu3VrLly8vMNEHAAAAyqes3DxFLNyhqL2n5Oxo0fT+bRXWvPDbT4DyosiB7Oeffy7xgxuGUSLb1K9fXz/88MNlt+vWrdsVJweJiIhQREREsWsCAACAuTJz8jRyfrR+PnBGLk4O+u/Adrrl2gCzywKuqFgPhi5Mbm6uzp8/XxK1AAAAAMWWnp2rYXO36ucDZ+Tm7KDIwR0IY6gwihzIvvvuO82ZM8du2WuvvaZq1arJ19dXYWFhOnfuXEnXBwAAAFzS+axcDZ69Vb8eOitPF0fNHdJRNzSqaXZZQJEVOZC9/fbbSktLs73esGGDJkyYoJdeeklLlizR8ePH9corr5RKkQAAAMDfJWfkaOAnm7XlSKK83Jw0b3gndWpQw+yygGIpciDbs2ePrr/+etvrL774Qj179tQLL7yge+65R1OnTtV3331XKkUCAAAAFzuXlq0BH2/SjmNJ8nF31sLhndW2XnWzywKKrciBLDU1VTVq/O8bh/Xr16t79+62182bN9fJkydLtjoAAADgbxLOZ6nfR5sU82eKani66LMRndUyyMfssoCrUuRAVrduXe3bt0+SdP78ef322292V8zOnj17yWeUAQAAACXhVEqmHpy1SfvjU+Xv5apFj3RWszreZpcFXLUiT3t///33a8yYMXr++ef1ww8/KDAwUJ07d7at37Ztm6699tpSKRIAAAA4mZSh/h9t0pGz6art46aFIzortKan2WUB/0iRA9mECRP0559/6vHHH1dgYKDmz58vR0dH2/rPPvtMvXv3LpUiAQAAULUdT0xXv4826cS5DAVVd9dnIzor2I/RWaj4ihzI3N3d9emnn15y/Zo1a0qkIAAAAOBisQlp6v/RJsUlZyqkhocWjOisur7uZpcFlIgiBzIAAACgrB08lar+H2/WmdQsNfT31MIRnVXL283ssoASU+RAVr16dVkslgLLfXx81LhxYz311FPq2bNniRYHAACAqmtfXIoe+nizzqZlq0mgl+YP76Sa1VzNLgsoUUUOZO+++26hy5OSkhQdHa077rhDX3zxBfeRAQAA4B/bfSJZA2dvVlJ6jlrU9da8oZ1U3dPF7LKAElfkQDZo0KDLrm/durWmTJlCIAMAAMA/sv3YOQ2avUWpmblqHeyruUM7ysfd2eyygFJR5OeQXckdd9yh/fv3l9TuAAAAUAVtiU3UwI83KzUzVx1D/DR/eCfCGCq1EpvUIysrSy4uXEYGAADA1fn1UIKGz92mjJw8Xd+whj4e1F4eLsxBh8qtxHr4J598otatW5fU7gAAAFCFrDlwWo/Oi1Z2rlU3N/bXfwe2k5uz45U3BCq4IgeycePGFbo8OTlZ27dv1++//65169aVWGEAAACoGlbuiVfEwh3KzrOqR9Namj6gjVydCGOoGoocyHbs2FHocm9vb/Xs2VNfffWV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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], "source": [ "# this will run until completion.\n", "last_action, last_result, last_state = app2.run(halt_after=[\"terminal\"])" - ] + ], + "outputs": [] }, { "cell_type": "code", @@ -706,26 +581,18 @@ "outputs_hidden": false } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'messages': [HumanMessage(content=\"Fetch the USA's GDP over the past 5 years, then draw a line graph of it. Once the python code has been written and the graph drawn, the task is complete.\"), HumanMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"USA GDP by year for the past 5 years\"}', 'name': 'tavily_search_results_json'}}, name='Researcher'), FunctionMessage(content='tavily_search_results_json response: [{\\'url\\': \\'https://usafacts.org/data/topics/economy/economic-indicators/gdp/gross-domestic-product/\\', \\'content\\': \\'Data Adjustments\\\\nIs the economy growing?\\\\nRelated Metrics\\\\nAnnual percent change in real GDP\\\\n5.7%\\\\n2021\\\\nAnnual percent change in real GDP\\\\n5.7%\\\\n2021\\\\nExplore Gross domestic product\\\\nInteract with the data\\\\nData Adjustments\\\\nState Display\\\\nOur nation, in numbers\\\\nUSAFacts is a not-for-profit, nonpartisan civic initiative making government data easy for all Americans to access and understand.\\\\n • Check your spelling\\\\n• Try other search terms\\\\n• Use fewer words\\\\nGross domestic product\\\\nGross domestic product\\\\nGross domestic product (GDP) is the value of all goods and services produced in the US. All topics\\\\nExplore articles, data and trends by topic\\\\nAbout\\\\nWhat makes USAFacts different\\\\nWe frequently add data and we\\\\\\'re interested in what would be useful to people. Newsletter\\\\nData delivered to your inbox\\\\nKeep up with the latest data and most popular content. But only the official BEA inflation-adjusted \"real GDP\" value is used to calculate annual percent change in GDP and therefore how well the economy is doing.\\'}, {\\'url\\': \\'https://www.bea.gov/data/gdp/gross-domestic-product\\', \\'content\\': \\'Q3 2023. +4.9%. Real gross domestic product (GDP) increased at an annual rate of 3.4 percent in the fourth quarter of 2023, according to the \"third\" estimate. In the third quarter, real GDP increased 4.9 percent. The increase in the fourth quarter primarily reflected increases in consumer spending, state and local government spending ...\\'}, {\\'url\\': \\'https://www.statista.com/statistics/188105/annual-gdp-of-the-united-states-since-1990/\\', \\'content\\': \\'Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\xa0Mio. facts.\\\\n What is GDP?\\\\nGDP and national debt\\\\nGross domestic product of the United States from 1990 to 2022\\\\n(in billion current U.S. dollars )\\\\nAdditional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nJanuary 2023\\\\nUnited States\\\\n1990 to 2022\\\\nOther statistics on the topicService sector of the U.S.\\\\nEconomy\\\\nU.S. real GDP growth by quarter Q1 2013- Statistics on\\\\n\"\\\\nGDP of the U.S.\\\\n\"\\\\nOther statistics that may interest you GDP of the U.S.\\\\nOverview\\\\nGDP by sector and industry\\\\nGDP by state\\\\nGDP by metropolitan area\\\\nInternational comparison\\\\nFurther related statistics\\\\nFurther Content: You might find this interesting as well\\\\nStatistics\\\\nTopics Q3 2023\\\\nEconomy\\\\nU.S. real GDP growth rate 1990-2022\\\\nEconomy\\\\nCountries with the largest gross domestic product (GDP) per capita 2022\\\\nEconomy\\\\nCountries with the largest gross domestic product (GDP) 2022\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nU.S. annual GDP 1990-2022\\\\n\\'}, {\\'url\\': \\'https://www.macrotrends.net/global-metrics/countries/USA/united-states/gdp-gross-domestic-product\\', \\'content\\': \"U.S. gdp for 2021 was $23,315.08B, a 10.71% increase from 2020. U.S. gdp for 2020 was $21,060.47B, a 1.5% decline from 2019. U.S. gdp for 2019 was $21,380.98B, a 4.13% increase from 2018. GDP at purchaser\\'s prices is the sum of gross value added by all resident producers in the economy plus any product taxes and minus any subsidies not included ...\"}, {\\'url\\': \\'https://www.thebalancemoney.com/us-gdp-by-year-3305543\\', \\'content\\': \"U.S. GDP by Year, Compared to Recessions and Events\\\\nThe Strange Ups and Downs of the U.S. Economy Since 1929\\\\nThe Balance / Julie Bang\\\\nU.S. gross domestic product (GDP) by year is a good overview of economic growth in the United States. Rebasing changes the reference year (or base year) for the real (chained dollar and quantity index) estimates and price indexes and expresses GDP and other NIPA aggregates in terms of the prices of one year. You can compare the GDP by year to fiscal and monetary policies to get a complete picture of what works and what doesn\\'t in the U.S. economy.\\\\n Real GDP is important because without canceling out the effects of inflation, the GDP could appear to grow, when really all that\\'s happened is an increase in prices.\\\\n Key Takeaways\\\\nTypes of GDP\\\\nThe\\\\xa0Bureau of Economic Analysis\\\\xa0compiles the data.\"}]', name='tavily_search_results_json'), HumanMessage(content=\"The search results provided do not seem to contain the explicit annual GDP values for the United States over the past 5 years that we need to create a line graph. Given this, I'll perform another search to try and find these specific GDP figures for the years 2018, 2019, 2020, 2021, and 2022. Let's proceed with that.\", additional_kwargs={'function_call': {'arguments': '{\"query\":\"USA GDP 2018 2019 2020 2021 2022\"}', 'name': 'tavily_search_results_json'}}, name='Researcher'), FunctionMessage(content='tavily_search_results_json response: [{\\'url\\': \\'https://www.statista.com/topics/772/gdp/\\', \\'content\\': \\'U.S. gross domestic product contributions 2000-2021, by sector\\\\nValue added to gross domestic product across economic sectors in the United States from 2000 to 2021 (as a share of GDP)\\\\nU.S. change in real value added to GDP 2022, by industry\\\\nChange in real value added to the gross domestic product of the United States in 2022, by industry\\\\nU.S. change in value added to real GDP 2012-2022, by industry\\\\nTen year percentage change in value added to the real gross domestic product of the United States between 2012 and 2022, by industry\\\\nU.S. value added to GDP by manufacturing industry 2000-2022\\\\nValue added to the gross domestic product by the manufacturing industry in the United States from 2000 to 2022 (in trillion U.S. dollars)\\\\nTech GDP as a percent of total GDP in the U.S. 2017-2022\\\\nTech sector as a percentage of total gross domestic product (GDP) in the United States from 2017 to 2022\\\\nU.S. digital economy value added to GDP 2021, by industry\\\\nValue added to the total economy (GDP) by the digital economy in the United States in 2021, by industry (in million U.S. dollars)\\\\n U.S. value added to GDP by construction industry 2000-2022\\\\nValue added to gross domestic product by the construction industry in the United States from 2000 to 2022 (in billion U.S. dollars)\\\\nGDP by state\\\\nGDP by state\\\\nU.S. gross domestic product 2022, by state\\\\nGross domestic product of the United States in 2022, by state (in billion current U.S. dollars)\\\\nU.S. real gross domestic product 2022, by state\\\\nReal gross domestic product (GDP) of the United States in 2022, by state (in billion chained 2017 U.S. dollars)\\\\nU.S. real GDP growth 2022, by state\\\\nPercent change in the real gross domestic product of the United States in 2022, by state\\\\nU.S. real GDP of California 2000-2022\\\\nReal gross domestic product of California in the United States from 2000 to 2022 (in billion U.S. dollars)\\\\n U.S. real value added to GDP in Florida, by industry\\\\nReal value added to the gross domestic product of Florida in the United States in 2022, by industry (in billion chained 2017 U.S. dollars)\\\\nGDP by metropolitan area\\\\nGDP by metropolitan area\\\\nU.S. metro areas - ranked by Gross Metropolitan Product (GMP) 2021\\\\nForecasted Gross Metropolitan Product (GMP) of the United States in 2021, by metropolitan area (in billion current U.S. dollars)\\\\nU.S. real GDP 2021, by metro area\\\\nReal gross domestic product of the United States in 2021, by metropolitan area (in million chained 2012 U.S. dollars)\\\\nU.S. real GDP annual percent change 2021, by metro area\\\\nAnnual percent change in the real GDP of the United States in 2021, by metropolitan area\\\\nU.S. real GDP per capita 2021, by metro area\\\\nPer capita real gross domestic product of the United States in 2021, by metropolitan area (in chained 2012 U.S. dollars)\\\\n U.S. gross value added to GDP 2022, by sector\\\\nGross value added to the gross domestic product in the United States from 1990 to 2022, by sector (in billion U.S. dollars)\\\\nU.S. budget balance and forecast as a percentage of GDP 2000-2033\\\\nBudget balance and forecast of the United States government from 2000 to 2033 (as a percentage of GDP)\\\\nGDP by sector and industry\\\\nGDP by sector and industry\\\\nU.S. real value added to GDP 1990-2022, by sector\\\\nReal gross value added to the gross domestic product of the United States from 1990 to 2022, by sector (in billion chained 2017 U.S. dollars)\\\\n The 20 countries with the largest gross domestic product (GDP) per capita in 2022 (in U.S. dollars)\\\\nGDP growth in the leading industrial and emerging countries 2nd quarter 2023\\\\nGrowth of the real gross domestic product (GDP) in the leading industrial and emerging countries from 2nd quarter 2021 to 2nd quarter 2023 (compared to the previous quarter)\\\\nRelated topics\\\\nRecommended\\\\nReport on the topic\\\\nKey figures\\\\nThe most important key figures provide you with a compact summary of the topic of \"GDP of the United States\" and take you straight to the corresponding statistics.\\\\n\\'}, {\\'url\\': \\'https://www.statista.com/statistics/188105/annual-gdp-of-the-united-states-since-1990/\\', \\'content\\': \\'Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\xa0Mio. facts.\\\\n What is GDP?\\\\nGDP and national debt\\\\nGross domestic product of the United States from 1990 to 2022\\\\n(in billion current U.S. dollars )\\\\nAdditional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nJanuary 2023\\\\nUnited States\\\\n1990 to 2022\\\\nOther statistics on the topicService sector of the U.S.\\\\nEconomy\\\\nU.S. real GDP growth by quarter Q1 2013- Statistics on\\\\n\"\\\\nGDP of the U.S.\\\\n\"\\\\nOther statistics that may interest you GDP of the U.S.\\\\nOverview\\\\nGDP by sector and industry\\\\nGDP by state\\\\nGDP by metropolitan area\\\\nInternational comparison\\\\nFurther related statistics\\\\nFurther Content: You might find this interesting as well\\\\nStatistics\\\\nTopics Q3 2023\\\\nEconomy\\\\nU.S. real GDP growth rate 1990-2022\\\\nEconomy\\\\nCountries with the largest gross domestic product (GDP) per capita 2022\\\\nEconomy\\\\nCountries with the largest gross domestic product (GDP) 2022\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nU.S. annual GDP 1990-2022\\\\n\\'}, {\\'url\\': \\'https://tradingeconomics.com/united-states/gdp\\', \\'content\\': \\'The Gross Domestic Product (GDP) in the United States was worth 25439.70 billion US dollars in 2022, according to official data from the World Bank. The GDP value of the United States represents 10.91 percent of the world economy. This page provides - United States GDP - actual values, historical data, forecast, chart, statistics, economic calendar and news.\\'}, {\\'url\\': \\'https://www.bea.gov/data/gdp/gross-domestic-product\\', \\'content\\': \\'Q3 2023. +4.9%. Real gross domestic product (GDP) increased at an annual rate of 3.4 percent in the fourth quarter of 2023, according to the \"third\" estimate. In the third quarter, real GDP increased 4.9 percent. The increase in the fourth quarter primarily reflected increases in consumer spending, state and local government spending ...\\'}, {\\'url\\': \\'https://www.macrotrends.net/global-metrics/countries/USA/united-states/gdp-gross-domestic-product\\', \\'content\\': \"U.S. gdp for 2022 was $25,462.70B, a 9.21% increase from 2021. U.S. gdp for 2021 was $23,315.08B, a 10.71% increase from 2020. U.S. gdp for 2020 was $21,060.47B, a 1.5% decline from 2019. U.S. gdp for 2019 was $21,380.98B, a 4.13% increase from 2018. GDP at purchaser\\'s prices is the sum of gross value added by all resident producers in the ...\"}]', name='tavily_search_results_json'), HumanMessage(content=\"From the search results, we have the GDP figures for the United States for the years 2018 to 2022. Here are the values:\\n\\n- 2018: $21,380.98 billion\\n- 2019: $21,060.47 billion (a 1.5% decline from 2018)\\n- 2020: $21,380.98 billion (approximately equal to 2018 as there is no explicit value given for 2020; the 1.5% decline from 2019 would indicate it's the same as 2018)\\n- 2021: $23,315.08 billion\\n- 2022: $25,462.70 billion\\n\\nWith these values, we can generate a line graph using Python. Here is a sample code using matplotlib to create the line graph:\\n\\n```python\\nimport matplotlib.pyplot as plt\\n\\n# GDP values in billions of dollars\\nyears = ['2018', '2019', '2020', '2021', '2022']\\ngdp_values = [21380.98, 21060.47, 21380.98, 23315.08, 25462.70]\\n\\nplt.figure(figsize=(10, 5))\\nplt.plot(years, gdp_values, marker='o')\\nplt.title('USA GDP Over the Past 5 Years')\\nplt.xlabel('Year')\\nplt.ylabel('GDP in Billion USD')\\nplt.grid(True)\\nplt.show()\\n```\\n\\nRunning this code will generate a line graph depicting the GDP of the United States from 2018 to 2022.\", name='Researcher'), HumanMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"code\":\"import matplotlib.pyplot as plt\\\\n\\\\n# GDP values in billions of dollars\\\\nyears = [\\'2018\\', \\'2019\\', \\'2020\\', \\'2021\\', \\'2022\\']\\\\ngdp_values = [21380.98, 21060.47, 21380.98, 23315.08, 25462.70]\\\\n\\\\nplt.figure(figsize=(10, 5))\\\\nplt.plot(years, gdp_values, marker=\\'o\\')\\\\nplt.title(\\'USA GDP Over the Past 5 Years\\')\\\\nplt.xlabel(\\'Year\\')\\\\nplt.ylabel(\\'GDP in Billion USD\\')\\\\nplt.grid(True)\\\\nplt.show()\\\\n\"}', 'name': 'python_repl'}}, name='Chart Generator'), FunctionMessage(content=\"python_repl response: Succesfully executed:\\n```python\\nimport matplotlib.pyplot as plt\\n\\n# GDP values in billions of dollars\\nyears = ['2018', '2019', '2020', '2021', '2022']\\ngdp_values = [21380.98, 21060.47, 21380.98, 23315.08, 25462.70]\\n\\nplt.figure(figsize=(10, 5))\\nplt.plot(years, gdp_values, marker='o')\\nplt.title('USA GDP Over the Past 5 Years')\\nplt.xlabel('Year')\\nplt.ylabel('GDP in Billion USD')\\nplt.grid(True)\\nplt.show()\\n\\n```\\nStdout: \", name='python_repl'), HumanMessage(content='The line graph depicting the GDP of the United States from 2018 to 2022 has been successfully created. \\n\\nFINAL ANSWER:\\n\\nThe task is now complete.', name='Chart Generator')], 'sender': 'Chart Generator', '__SEQUENCE_ID': 8, '__PRIOR_STEP': 'terminal'}\n" - ] - } - ], "source": [ "pprint.pprint(last_state)" - ] + ], + "outputs": [] }, { "cell_type": "code", "execution_count": null, "id": "7e15b805-4feb-4541-8d2f-5075368bb293", "metadata": {}, - "outputs": [], - "source": [] + "source": [], + "outputs": [] } ], "metadata": { diff --git a/pyproject.toml b/pyproject.toml index fafddd2a..91855e97 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,10 +52,17 @@ tests = [ "pytest", "pytest-asyncio", "burr[hamilton]", - "pymongo" + "pymongo", + "burr[hamilton]", + "langchain_core", + "langchain_community", + "pandas", + "pydantic", + "pyarrow", ] documentation = [ + "burr[tests]", "sphinx", "sphinx-autobuild", "myst-parser", diff --git a/tests/core/test_serde.py b/tests/core/test_serde.py new file mode 100644 index 00000000..645a306b --- /dev/null +++ b/tests/core/test_serde.py @@ -0,0 +1,49 @@ +import pytest + +from burr.core.serde import StringDispatch, deserialize, serialize + + +def test_serialize_primitive_types(): + assert serialize(1) == 1 + assert serialize(1.0) == 1.0 + assert serialize("test") == "test" + assert serialize(True) is True + + +def test_serialize_list(): + assert serialize([1, 2, 3]) == [1, 2, 3] + assert serialize(["a", "b", "c"]) == ["a", "b", "c"] + + +def test_serialize_dict(): + assert serialize({"key": "value"}) == {"key": "value"} + assert serialize({"key1": 1, "key2": 2}) == {"key1": 1, "key2": 2} + + +def test_deserialize_primitive_types(): + assert deserialize(1) == 1 + assert deserialize(1.0) == 1.0 + assert deserialize("test") == "test" + assert deserialize(True) is True + + +def test_deserialize_list(): + assert deserialize([1, 2, 3]) == [1, 2, 3] + assert deserialize(["a", "b", "c"]) == ["a", "b", "c"] + + +def test_deserialize_dict(): + assert deserialize({"key": "value"}) == {"key": "value"} + assert deserialize({"key1": 1, "key2": 2}) == {"key1": 1, "key2": 2} + + +def test_string_dispatch_no_key(): + dispatch = StringDispatch() + with pytest.raises(ValueError): + dispatch.call("nonexistent_key") + + +def test_string_dispatch_with_key(): + dispatch = StringDispatch() + dispatch.register("test_key")(lambda x: x) + assert dispatch.call("test_key", "test_value") == "test_value" diff --git a/tests/integration_tests/test_app.py b/tests/integration_tests/test_app.py new file mode 100644 index 00000000..668fa138 --- /dev/null +++ b/tests/integration_tests/test_app.py @@ -0,0 +1,142 @@ +import pydantic +from langchain_core import documents + +from burr import core +from burr.core import State, action, expr, persistence +from burr.tracking import client as tracking_client + + +@action(reads=[], writes=["dict"]) +def basic_action(state: State, user_input: str) -> tuple[dict, State]: + v = {"foo": 1, "bar": "2", "bool": True, "None": None, "input": user_input} + return {"dict": v}, state.update(dict=v) + + +class PydanticField(pydantic.BaseModel): + f1: int = 0 + f2: bool = False + + +@action(reads=["dict"], writes=["pydantic_field"]) +def pydantic_action(state: State) -> tuple[dict, State]: + v = PydanticField(f1=state["dict"]["foo"], f2=state["dict"]["bool"]) + return {"pydantic_field": v}, state.update(pydantic_field=v) + + +@action(reads=["pydantic_field"], writes=["lc_doc"]) +def langchain_action(state: State) -> tuple[dict, State]: + v = documents.Document( + page_content=f"foo: {state['pydantic_field'].f1}, bar: {state['pydantic_field'].f2}" + ) + return {"lc_doc": v}, state.update(lc_doc=v) + + +@action(reads=["lc_doc"], writes=[]) +def terminal_action(state: State) -> tuple[dict, State]: + return {"output": state["lc_doc"].page_content}, state + + +def build_application(sqllite_persister, tracker, partition_key, app_id): + persister = sqllite_persister or tracker + app_builder = ( + core.ApplicationBuilder() + .with_actions(basic_action, pydantic_action, langchain_action, terminal_action) + .with_transitions( + ("basic_action", "terminal_action", expr("dict['foo'] == 0")), + ("basic_action", "pydantic_action"), + ("pydantic_action", "langchain_action"), + ("langchain_action", "terminal_action"), + ) + .with_identifiers(partition_key=partition_key, app_id=app_id) + .initialize_from( + persister, + resume_at_next_action=True, + default_state={}, + default_entrypoint="basic_action", + ) + ) + if sqllite_persister: + app_builder.with_state_persister(sqllite_persister) + if tracker: + app_builder.with_tracker(tracker) + return app_builder.build() + + +def test_whole_application_tracker(tmp_path): + """This test creates an application and then steps through it rebuilding the + application each time. This is a test of things being serialized and deserialized.""" + tracker = tracking_client.LocalTrackingClient("integration-test", tmp_path) + app_id = "integration-test" + partition_key = "" + # step 1 + app = build_application(None, tracker, partition_key, app_id) + action1, result1, state1 = app.step(inputs={"user_input": "hello"}) + assert action1.name == "basic_action" + # step 2 + app = build_application(None, tracker, partition_key, app_id) + action2, result2, state2 = app.step() + assert action2.name == "pydantic_action" + # step 3 + app = build_application(None, tracker, partition_key, app_id) + action3, result3, state3 = app.step() + assert action3.name == "langchain_action" + # step 4 + app = build_application(None, tracker, partition_key, app_id) + action4, result4, state4 = app.step() + assert action4.name == "terminal_action" + + # assert that state is basically the same across different steps + assert state1["dict"] == {"foo": 1, "bar": "2", "bool": True, "None": None, "input": "hello"} + assert state1["dict"] == state4["dict"] + + assert state2["pydantic_field"].f1 == 1 + assert state2["pydantic_field"].f2 is True + assert state2["pydantic_field"] == state3["pydantic_field"] + + assert state3["lc_doc"].page_content == "foo: 1, bar: True" + assert state3["lc_doc"] == state4["lc_doc"] + + # assert that tracker has things in it too + final_tracker_state = tracker.load(partition_key, app_id=app_id) + for k, v in final_tracker_state["state"].items(): + assert v == state4[k] + + +def test_whole_application_sqllite(tmp_path): + """This test creates an application and then steps through it rebuilding the + application each time. This is a test of things being serialized and deserialized.""" + sqllite_persister = persistence.SQLLitePersister(tmp_path / "test.db") + sqllite_persister.initialize() + app_id = "integration-test" + partition_key = "" + # step 1 + app = build_application(sqllite_persister, None, partition_key, app_id) + action1, result1, state1 = app.step(inputs={"user_input": "hello"}) + assert action1.name == "basic_action" + # step 2 + app = build_application(sqllite_persister, None, partition_key, app_id) + action2, result2, state2 = app.step() + assert action2.name == "pydantic_action" + # step 3 + app = build_application(sqllite_persister, None, partition_key, app_id) + action3, result3, state3 = app.step() + assert action3.name == "langchain_action" + # step 4 + app = build_application(sqllite_persister, None, partition_key, app_id) + action4, result4, state4 = app.step() + assert action4.name == "terminal_action" + + # assert that state is basically the same across different steps + assert state1["dict"] == {"foo": 1, "bar": "2", "bool": True, "None": None, "input": "hello"} + assert state1["dict"] == state4["dict"] + + assert state2["pydantic_field"].f1 == 1 + assert state2["pydantic_field"].f2 is True + assert state2["pydantic_field"] == state3["pydantic_field"] + + assert state3["lc_doc"].page_content == "foo: 1, bar: True" + assert state3["lc_doc"] == state4["lc_doc"] + + final_sqllite_state = sqllite_persister.load("", app_id=app_id) + assert final_sqllite_state["state"] == state4 + assert sqllite_persister.list_app_ids(partition_key="") == ["integration-test"] diff --git a/tests/integrations/serde/test_langchain.py b/tests/integrations/serde/test_langchain.py new file mode 100644 index 00000000..f67d3657 --- /dev/null +++ b/tests/integrations/serde/test_langchain.py @@ -0,0 +1,73 @@ +from langchain_community.document_transformers.embeddings_redundant_filter import _DocumentWithState +from langchain_core import documents as lc_documents +from langchain_core import messages as lc_messages + +from burr.core import serde, state + + +def test_serde_of_lc_document(): + doc = lc_documents.Document(page_content="test content") + og = state.State({"doc": doc}) + serialized = og.serialize() + assert serialized == { + "doc": { + serde.KEY: "lc_document", + "id": ["langchain", "schema", "document", "Document"], + "kwargs": {"page_content": "test content", "type": "Document"}, + "lc": 1, + "type": "constructor", + } + } + ng = state.State.deserialize(serialized) + assert isinstance(ng["doc"], lc_documents.Document) + assert ng["doc"].page_content == "test content" + assert serde.KEY not in ng + + +def test_serde_of_lc_message(): + message = lc_messages.HumanMessage(content="test content") + og = state.State({"message": message}) + serialized = og.serialize() + assert serialized == { + "message": { + serde.KEY: "lc_message", + "data": { + "additional_kwargs": {}, + "content": "test content", + "example": False, + "id": None, + "name": None, + "response_metadata": {}, + "type": "human", + }, + "type": "human", + } + } + ng = state.State.deserialize(serialized) + assert isinstance(ng["message"], lc_messages.HumanMessage) + assert ng["message"].content == "test content" + assert serde.KEY not in ng + + +def test_serde_of_document_with_state(): + """Tests that we can serialize a document that is not serializable to a document.""" + doc = _DocumentWithState(page_content="Hello, World document with state!", state={"foo": "bar"}) + og = state.State({"doc": doc}) + serialized = og.serialize() + assert serialized == { + "doc": { + serde.KEY: "lc_document_with_state", + "doc": { + serde.KEY: "lc_document", + "id": ["langchain", "schema", "document", "Document"], + "kwargs": {"page_content": "Hello, World document with state!", "type": "Document"}, + "lc": 1, + "type": "constructor", + }, + "state": {"foo": "bar"}, + } + } + ng = state.State.deserialize(serialized) + assert isinstance(ng["doc"], lc_documents.Document) + assert ng["doc"].page_content == "Hello, World document with state!" + assert serde.KEY not in ng diff --git a/tests/integrations/serde/test_pandas.py b/tests/integrations/serde/test_pandas.py new file mode 100644 index 00000000..72589e81 --- /dev/null +++ b/tests/integrations/serde/test_pandas.py @@ -0,0 +1,18 @@ +import pandas as pd + +from burr.core import serde, state + + +def test_serde_of_pandas_dataframe(tmp_path): + df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) + og = state.State({"df": df}) + serialized = og.serialize(pandas_kwargs={"path": tmp_path}) + assert serialized["df"][serde.KEY] == "pandas.DataFrame" + assert serialized["df"]["path"].startswith(str(tmp_path)) + assert ( + "df_a23d165ed4a2b8c6ccf24ac6276e35a9dc312e2828b4d0810416f4d47c614c7f.parquet" + in serialized["df"]["path"] + ) + ng = state.State.deserialize(serialized, pandas_kwargs={"path": tmp_path}) + assert isinstance(ng["df"], pd.DataFrame) + pd.testing.assert_frame_equal(ng["df"], df) diff --git a/tests/integrations/serde/test_pickle.py b/tests/integrations/serde/test_pickle.py new file mode 100644 index 00000000..30f0b4b2 --- /dev/null +++ b/tests/integrations/serde/test_pickle.py @@ -0,0 +1,29 @@ +from burr.core import serde, state +from burr.integrations.serde import pickle + + +class User: + def __init__(self, name, email): + self.name = name + self.email = email + + +def test_serde_of_pickle_object(): + pickle.register_type_to_pickle(User) + user = User(name="John Doe", email="john.doe@example.com") + og = state.State({"user": user, "test": "test"}) + serialized = og.serialize() + assert serialized == { + "user": { + serde.KEY: "pickle", + "value": b"\x80\x04\x95Q\x00\x00\x00\x00\x00\x00\x00\x8c\x0btest_pi" + b"ckle\x94\x8c\x04User\x94\x93\x94)\x81\x94}\x94(\x8c\x04na" + b"me\x94\x8c\x08John Doe\x94\x8c\x05email\x94\x8c\x14john" + b".doe@example.com\x94ub.", + }, + "test": "test", + } + ng = state.State.deserialize(serialized) + assert isinstance(ng["user"], User) + assert ng["user"].name == "John Doe" + assert ng["user"].email == "john.doe@example.com" diff --git a/tests/integrations/serde/test_pydantic.py b/tests/integrations/serde/test_pydantic.py new file mode 100644 index 00000000..adfb76ab --- /dev/null +++ b/tests/integrations/serde/test_pydantic.py @@ -0,0 +1,26 @@ +from pydantic import BaseModel + +from burr.core import serde, state + + +class User(BaseModel): + name: str + email: str + + +def test_serde_of_pydantic_model(): + user = User(name="John Doe", email="john.doe@example.com") + og = state.State({"user": user}) + serialized = og.serialize() + assert serialized == { + "user": { + serde.KEY: "pydantic", + "__pydantic_class": "test_pydantic.User", + "email": "john.doe@example.com", + "name": "John Doe", + } + } + ng = state.State.deserialize(serialized) + assert isinstance(ng["user"], User) + assert ng["user"].name == "John Doe" + assert ng["user"].email == "john.doe@example.com"