mirascope 2.0.0a2__py3-none-any.whl → 2.0.0a4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mirascope/__init__.py +2 -2
- mirascope/api/__init__.py +6 -0
- mirascope/api/_generated/README.md +207 -0
- mirascope/api/_generated/__init__.py +141 -0
- mirascope/api/_generated/client.py +163 -0
- mirascope/api/_generated/core/__init__.py +52 -0
- mirascope/api/_generated/core/api_error.py +23 -0
- mirascope/api/_generated/core/client_wrapper.py +58 -0
- mirascope/api/_generated/core/datetime_utils.py +30 -0
- mirascope/api/_generated/core/file.py +70 -0
- mirascope/api/_generated/core/force_multipart.py +16 -0
- mirascope/api/_generated/core/http_client.py +619 -0
- mirascope/api/_generated/core/http_response.py +55 -0
- mirascope/api/_generated/core/jsonable_encoder.py +102 -0
- mirascope/api/_generated/core/pydantic_utilities.py +310 -0
- mirascope/api/_generated/core/query_encoder.py +60 -0
- mirascope/api/_generated/core/remove_none_from_dict.py +11 -0
- mirascope/api/_generated/core/request_options.py +35 -0
- mirascope/api/_generated/core/serialization.py +282 -0
- mirascope/api/_generated/docs/__init__.py +4 -0
- mirascope/api/_generated/docs/client.py +95 -0
- mirascope/api/_generated/docs/raw_client.py +132 -0
- mirascope/api/_generated/environment.py +9 -0
- mirascope/api/_generated/errors/__init__.py +17 -0
- mirascope/api/_generated/errors/bad_request_error.py +15 -0
- mirascope/api/_generated/errors/conflict_error.py +15 -0
- mirascope/api/_generated/errors/forbidden_error.py +15 -0
- mirascope/api/_generated/errors/internal_server_error.py +15 -0
- mirascope/api/_generated/errors/not_found_error.py +15 -0
- mirascope/api/_generated/health/__init__.py +7 -0
- mirascope/api/_generated/health/client.py +96 -0
- mirascope/api/_generated/health/raw_client.py +129 -0
- mirascope/api/_generated/health/types/__init__.py +8 -0
- mirascope/api/_generated/health/types/health_check_response.py +24 -0
- mirascope/api/_generated/health/types/health_check_response_status.py +5 -0
- mirascope/api/_generated/organizations/__init__.py +25 -0
- mirascope/api/_generated/organizations/client.py +380 -0
- mirascope/api/_generated/organizations/raw_client.py +876 -0
- mirascope/api/_generated/organizations/types/__init__.py +23 -0
- mirascope/api/_generated/organizations/types/organizations_create_response.py +24 -0
- mirascope/api/_generated/organizations/types/organizations_create_response_role.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_get_response.py +24 -0
- mirascope/api/_generated/organizations/types/organizations_get_response_role.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_list_response_item.py +24 -0
- mirascope/api/_generated/organizations/types/organizations_list_response_item_role.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_update_response.py +24 -0
- mirascope/api/_generated/organizations/types/organizations_update_response_role.py +7 -0
- mirascope/api/_generated/projects/__init__.py +17 -0
- mirascope/api/_generated/projects/client.py +458 -0
- mirascope/api/_generated/projects/raw_client.py +1016 -0
- mirascope/api/_generated/projects/types/__init__.py +15 -0
- mirascope/api/_generated/projects/types/projects_create_response.py +30 -0
- mirascope/api/_generated/projects/types/projects_get_response.py +30 -0
- mirascope/api/_generated/projects/types/projects_list_response_item.py +30 -0
- mirascope/api/_generated/projects/types/projects_update_response.py +30 -0
- mirascope/api/_generated/reference.md +753 -0
- mirascope/api/_generated/traces/__init__.py +55 -0
- mirascope/api/_generated/traces/client.py +162 -0
- mirascope/api/_generated/traces/raw_client.py +168 -0
- mirascope/api/_generated/traces/types/__init__.py +95 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item.py +36 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource.py +31 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item.py +25 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value.py +54 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_array_value.py +23 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value.py +28 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value_values_item.py +24 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item.py +35 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope.py +35 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item.py +27 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value.py +54 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_array_value.py +23 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value.py +28 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value_values_item.py +24 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item.py +60 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item.py +29 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value.py +54 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_array_value.py +23 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value.py +28 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value_values_item.py +24 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_status.py +24 -0
- mirascope/api/_generated/traces/types/traces_create_response.py +27 -0
- mirascope/api/_generated/traces/types/traces_create_response_partial_success.py +28 -0
- mirascope/api/_generated/types/__init__.py +37 -0
- mirascope/api/_generated/types/already_exists_error.py +24 -0
- mirascope/api/_generated/types/already_exists_error_tag.py +5 -0
- mirascope/api/_generated/types/database_error.py +24 -0
- mirascope/api/_generated/types/database_error_tag.py +5 -0
- mirascope/api/_generated/types/http_api_decode_error.py +29 -0
- mirascope/api/_generated/types/http_api_decode_error_tag.py +5 -0
- mirascope/api/_generated/types/issue.py +40 -0
- mirascope/api/_generated/types/issue_tag.py +17 -0
- mirascope/api/_generated/types/not_found_error_body.py +24 -0
- mirascope/api/_generated/types/not_found_error_tag.py +5 -0
- mirascope/api/_generated/types/permission_denied_error.py +24 -0
- mirascope/api/_generated/types/permission_denied_error_tag.py +7 -0
- mirascope/api/_generated/types/property_key.py +7 -0
- mirascope/api/_generated/types/property_key_key.py +27 -0
- mirascope/api/_generated/types/property_key_key_tag.py +5 -0
- mirascope/api/client.py +255 -0
- mirascope/api/settings.py +81 -0
- mirascope/llm/__init__.py +45 -11
- mirascope/llm/calls/calls.py +81 -57
- mirascope/llm/calls/decorator.py +121 -115
- mirascope/llm/content/__init__.py +3 -2
- mirascope/llm/context/_utils.py +19 -6
- mirascope/llm/exceptions.py +30 -16
- mirascope/llm/formatting/_utils.py +9 -5
- mirascope/llm/formatting/format.py +2 -2
- mirascope/llm/formatting/from_call_args.py +2 -2
- mirascope/llm/messages/message.py +13 -5
- mirascope/llm/models/__init__.py +2 -2
- mirascope/llm/models/models.py +189 -81
- mirascope/llm/prompts/__init__.py +13 -12
- mirascope/llm/prompts/_utils.py +27 -24
- mirascope/llm/prompts/decorator.py +133 -204
- mirascope/llm/prompts/prompts.py +424 -0
- mirascope/llm/prompts/protocols.py +25 -59
- mirascope/llm/providers/__init__.py +44 -0
- mirascope/llm/{clients → providers}/_missing_import_stubs.py +8 -6
- mirascope/llm/providers/anthropic/__init__.py +29 -0
- mirascope/llm/providers/anthropic/_utils/__init__.py +23 -0
- mirascope/llm/providers/anthropic/_utils/beta_decode.py +271 -0
- mirascope/llm/providers/anthropic/_utils/beta_encode.py +216 -0
- mirascope/llm/{clients → providers}/anthropic/_utils/decode.py +44 -11
- mirascope/llm/providers/anthropic/_utils/encode.py +356 -0
- mirascope/llm/providers/anthropic/beta_provider.py +322 -0
- mirascope/llm/providers/anthropic/model_id.py +23 -0
- mirascope/llm/providers/anthropic/model_info.py +87 -0
- mirascope/llm/providers/anthropic/provider.py +416 -0
- mirascope/llm/{clients → providers}/base/__init__.py +3 -3
- mirascope/llm/{clients → providers}/base/_utils.py +25 -8
- mirascope/llm/{clients/base/client.py → providers/base/base_provider.py} +255 -126
- mirascope/llm/providers/google/__init__.py +21 -0
- mirascope/llm/{clients → providers}/google/_utils/decode.py +61 -7
- mirascope/llm/{clients → providers}/google/_utils/encode.py +44 -30
- mirascope/llm/providers/google/model_id.py +22 -0
- mirascope/llm/providers/google/model_info.py +62 -0
- mirascope/llm/providers/google/provider.py +442 -0
- mirascope/llm/providers/load_provider.py +54 -0
- mirascope/llm/providers/mlx/__init__.py +24 -0
- mirascope/llm/providers/mlx/_utils.py +129 -0
- mirascope/llm/providers/mlx/encoding/__init__.py +8 -0
- mirascope/llm/providers/mlx/encoding/base.py +69 -0
- mirascope/llm/providers/mlx/encoding/transformers.py +147 -0
- mirascope/llm/providers/mlx/mlx.py +237 -0
- mirascope/llm/providers/mlx/model_id.py +17 -0
- mirascope/llm/providers/mlx/provider.py +415 -0
- mirascope/llm/providers/model_id.py +16 -0
- mirascope/llm/providers/ollama/__init__.py +19 -0
- mirascope/llm/providers/ollama/provider.py +71 -0
- mirascope/llm/providers/openai/__init__.py +6 -0
- mirascope/llm/providers/openai/completions/__init__.py +25 -0
- mirascope/llm/{clients → providers}/openai/completions/_utils/__init__.py +2 -0
- mirascope/llm/{clients → providers}/openai/completions/_utils/decode.py +60 -6
- mirascope/llm/{clients → providers}/openai/completions/_utils/encode.py +37 -26
- mirascope/llm/providers/openai/completions/base_provider.py +513 -0
- mirascope/llm/providers/openai/completions/provider.py +22 -0
- mirascope/llm/providers/openai/model_id.py +31 -0
- mirascope/llm/providers/openai/model_info.py +303 -0
- mirascope/llm/providers/openai/provider.py +398 -0
- mirascope/llm/providers/openai/responses/__init__.py +21 -0
- mirascope/llm/{clients → providers}/openai/responses/_utils/decode.py +59 -6
- mirascope/llm/{clients → providers}/openai/responses/_utils/encode.py +34 -23
- mirascope/llm/providers/openai/responses/provider.py +469 -0
- mirascope/llm/providers/provider_id.py +23 -0
- mirascope/llm/providers/provider_registry.py +169 -0
- mirascope/llm/providers/together/__init__.py +19 -0
- mirascope/llm/providers/together/provider.py +40 -0
- mirascope/llm/responses/__init__.py +3 -0
- mirascope/llm/responses/base_response.py +14 -5
- mirascope/llm/responses/base_stream_response.py +35 -6
- mirascope/llm/responses/finish_reason.py +1 -0
- mirascope/llm/responses/response.py +33 -13
- mirascope/llm/responses/root_response.py +12 -13
- mirascope/llm/responses/stream_response.py +35 -23
- mirascope/llm/responses/usage.py +95 -0
- mirascope/llm/tools/__init__.py +9 -2
- mirascope/llm/tools/_utils.py +12 -3
- mirascope/llm/tools/protocols.py +4 -4
- mirascope/llm/tools/tool_schema.py +44 -9
- mirascope/llm/tools/tools.py +10 -9
- mirascope/ops/__init__.py +156 -0
- mirascope/ops/_internal/__init__.py +5 -0
- mirascope/ops/_internal/closure.py +1118 -0
- mirascope/ops/_internal/configuration.py +126 -0
- mirascope/ops/_internal/context.py +76 -0
- mirascope/ops/_internal/exporters/__init__.py +26 -0
- mirascope/ops/_internal/exporters/exporters.py +342 -0
- mirascope/ops/_internal/exporters/processors.py +104 -0
- mirascope/ops/_internal/exporters/types.py +165 -0
- mirascope/ops/_internal/exporters/utils.py +29 -0
- mirascope/ops/_internal/instrumentation/__init__.py +8 -0
- mirascope/ops/_internal/instrumentation/llm/__init__.py +8 -0
- mirascope/ops/_internal/instrumentation/llm/encode.py +238 -0
- mirascope/ops/_internal/instrumentation/llm/gen_ai_types/__init__.py +38 -0
- mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_input_messages.py +31 -0
- mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_output_messages.py +38 -0
- mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_system_instructions.py +18 -0
- mirascope/ops/_internal/instrumentation/llm/gen_ai_types/shared.py +100 -0
- mirascope/ops/_internal/instrumentation/llm/llm.py +1288 -0
- mirascope/ops/_internal/propagation.py +198 -0
- mirascope/ops/_internal/protocols.py +51 -0
- mirascope/ops/_internal/session.py +139 -0
- mirascope/ops/_internal/spans.py +232 -0
- mirascope/ops/_internal/traced_calls.py +371 -0
- mirascope/ops/_internal/traced_functions.py +394 -0
- mirascope/ops/_internal/tracing.py +276 -0
- mirascope/ops/_internal/types.py +13 -0
- mirascope/ops/_internal/utils.py +75 -0
- mirascope/ops/_internal/versioned_calls.py +512 -0
- mirascope/ops/_internal/versioned_functions.py +346 -0
- mirascope/ops/_internal/versioning.py +303 -0
- mirascope/ops/exceptions.py +21 -0
- {mirascope-2.0.0a2.dist-info → mirascope-2.0.0a4.dist-info}/METADATA +78 -3
- mirascope-2.0.0a4.dist-info/RECORD +247 -0
- {mirascope-2.0.0a2.dist-info → mirascope-2.0.0a4.dist-info}/WHEEL +1 -1
- mirascope/graphs/__init__.py +0 -22
- mirascope/graphs/finite_state_machine.py +0 -625
- mirascope/llm/agents/__init__.py +0 -15
- mirascope/llm/agents/agent.py +0 -97
- mirascope/llm/agents/agent_template.py +0 -45
- mirascope/llm/agents/decorator.py +0 -176
- mirascope/llm/calls/base_call.py +0 -33
- mirascope/llm/clients/__init__.py +0 -34
- mirascope/llm/clients/anthropic/__init__.py +0 -25
- mirascope/llm/clients/anthropic/_utils/encode.py +0 -243
- mirascope/llm/clients/anthropic/clients.py +0 -819
- mirascope/llm/clients/anthropic/model_ids.py +0 -8
- mirascope/llm/clients/google/__init__.py +0 -20
- mirascope/llm/clients/google/clients.py +0 -853
- mirascope/llm/clients/google/model_ids.py +0 -15
- mirascope/llm/clients/openai/__init__.py +0 -25
- mirascope/llm/clients/openai/completions/__init__.py +0 -28
- mirascope/llm/clients/openai/completions/_utils/model_features.py +0 -81
- mirascope/llm/clients/openai/completions/clients.py +0 -833
- mirascope/llm/clients/openai/completions/model_ids.py +0 -8
- mirascope/llm/clients/openai/responses/__init__.py +0 -26
- mirascope/llm/clients/openai/responses/_utils/__init__.py +0 -13
- mirascope/llm/clients/openai/responses/_utils/model_features.py +0 -87
- mirascope/llm/clients/openai/responses/clients.py +0 -832
- mirascope/llm/clients/openai/responses/model_ids.py +0 -8
- mirascope/llm/clients/openai/shared/__init__.py +0 -7
- mirascope/llm/clients/openai/shared/_utils.py +0 -55
- mirascope/llm/clients/providers.py +0 -175
- mirascope-2.0.0a2.dist-info/RECORD +0 -102
- /mirascope/llm/{clients → providers}/base/kwargs.py +0 -0
- /mirascope/llm/{clients → providers}/base/params.py +0 -0
- /mirascope/llm/{clients/anthropic → providers/google}/_utils/__init__.py +0 -0
- /mirascope/llm/{clients → providers}/google/message.py +0 -0
- /mirascope/llm/{clients/google → providers/openai/responses}/_utils/__init__.py +0 -0
- {mirascope-2.0.0a2.dist-info → mirascope-2.0.0a4.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,625 +0,0 @@
|
|
|
1
|
-
"""The `FiniteStateMachine` Class Implementation."""
|
|
2
|
-
|
|
3
|
-
import inspect
|
|
4
|
-
from collections.abc import Callable, Coroutine, Iterator
|
|
5
|
-
from contextlib import contextmanager
|
|
6
|
-
from contextvars import ContextVar
|
|
7
|
-
from dataclasses import dataclass
|
|
8
|
-
from functools import wraps
|
|
9
|
-
from typing import Any, Generic, ParamSpec, Protocol, overload
|
|
10
|
-
from typing_extensions import TypeVar
|
|
11
|
-
|
|
12
|
-
NoneType = type(None)
|
|
13
|
-
DepsT = TypeVar("DepsT", default=None)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@dataclass
|
|
17
|
-
class RunContext(Generic[DepsT]):
|
|
18
|
-
"""The runtime context for the Finite State Machine (FSM).
|
|
19
|
-
|
|
20
|
-
This class is used to store the state of the FSM and the data that is passed between
|
|
21
|
-
nodes. Consumers of the FSM can update the context as needed to change the state of
|
|
22
|
-
the FSM or pass information between nodes.
|
|
23
|
-
|
|
24
|
-
At a minimum the `RunContext` should know:
|
|
25
|
-
- The necessary dependencies for running the FSM.
|
|
26
|
-
- The current state of the FSM.
|
|
27
|
-
- The inputs to the current node being run.
|
|
28
|
-
- The transition edges of the FSM.
|
|
29
|
-
- Data that is shared between nodes.
|
|
30
|
-
- The state history of the FSM execution.
|
|
31
|
-
- Error states and fallback handling.
|
|
32
|
-
- The queue of incoming requests and their context.
|
|
33
|
-
|
|
34
|
-
The last point is important. For the FSM to be efficient, we must enable running
|
|
35
|
-
nodes in parallel. This means that we need to be able to queue incoming requests
|
|
36
|
-
and their context, and then run the nodes in parallel up to the set thread limit.
|
|
37
|
-
|
|
38
|
-
Attributes:
|
|
39
|
-
state (...): The current state of the FSM.
|
|
40
|
-
inputs (...): The inputs to the current node being run.
|
|
41
|
-
transitions (...): The transition edges of the FSM.
|
|
42
|
-
data (...): Data that is shared between nodes.
|
|
43
|
-
state_history (...): The state history of the FSM execution.
|
|
44
|
-
error_states (...): Error states and fallback handling.
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
deps: DepsT
|
|
48
|
-
"""The dependencies for the FSM."""
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
FSM_CONTEXT: ContextVar[RunContext[Any] | None] = ContextVar(
|
|
52
|
-
"fsm_context", default=None
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class FSMContextError(Exception):
|
|
57
|
-
"""Raised when a node is executed outside of a context manager."""
|
|
58
|
-
|
|
59
|
-
pass
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
NodeDecoratedFunctionP = ParamSpec("NodeDecoratedFunctionP")
|
|
63
|
-
NodeDecoratedFunctionR = TypeVar("NodeDecoratedFunctionR", covariant=True)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
class NodeDecoratedFunction(
|
|
67
|
-
Protocol[NodeDecoratedFunctionP, NodeDecoratedFunctionR, DepsT]
|
|
68
|
-
):
|
|
69
|
-
"""The protocol for functions decorated with the `node` decorator.
|
|
70
|
-
|
|
71
|
-
This protocol enables enforcing that the first argument of any `node` decorated
|
|
72
|
-
function must be `ctx: RunContext` to ensure that the function can access the global
|
|
73
|
-
context of the FSM.
|
|
74
|
-
"""
|
|
75
|
-
|
|
76
|
-
__name__: str
|
|
77
|
-
|
|
78
|
-
def __call__(
|
|
79
|
-
self,
|
|
80
|
-
ctx: RunContext[DepsT],
|
|
81
|
-
*args: NodeDecoratedFunctionP.args,
|
|
82
|
-
**kwargs: NodeDecoratedFunctionP.kwargs,
|
|
83
|
-
) -> NodeDecoratedFunctionR: ...
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
NodeDecoratorP = ParamSpec("NodeDecoratorP")
|
|
87
|
-
NodeDecoratorR = TypeVar("NodeDecoratorR")
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
class NodeDecorator(Protocol[DepsT]):
|
|
91
|
-
"""The `node` decorator protocol.
|
|
92
|
-
|
|
93
|
-
This protocol enables overloading the `node` function such that it can detect and
|
|
94
|
-
type async functions correctly. The `node` decorator is used to register a function
|
|
95
|
-
as a node in the FSM, so the resulting function when run uncompiled should run just
|
|
96
|
-
as the original function would (and thus required matching type hints).
|
|
97
|
-
"""
|
|
98
|
-
|
|
99
|
-
@overload
|
|
100
|
-
def __call__(
|
|
101
|
-
self,
|
|
102
|
-
fn: NodeDecoratedFunction[
|
|
103
|
-
NodeDecoratorP, Coroutine[Any, Any, NodeDecoratorR], DepsT
|
|
104
|
-
],
|
|
105
|
-
) -> Callable[NodeDecoratorP, Coroutine[Any, Any, NodeDecoratorR]]: ...
|
|
106
|
-
|
|
107
|
-
@overload
|
|
108
|
-
def __call__(
|
|
109
|
-
self, fn: NodeDecoratedFunction[NodeDecoratorP, NodeDecoratorR, DepsT]
|
|
110
|
-
) -> Callable[NodeDecoratorP, NodeDecoratorR]: ...
|
|
111
|
-
|
|
112
|
-
def __call__(
|
|
113
|
-
self,
|
|
114
|
-
fn: NodeDecoratedFunction[
|
|
115
|
-
NodeDecoratorP,
|
|
116
|
-
NodeDecoratorR | Coroutine[Any, Any, NodeDecoratorR],
|
|
117
|
-
DepsT,
|
|
118
|
-
],
|
|
119
|
-
) -> Callable[
|
|
120
|
-
NodeDecoratorP, NodeDecoratorR | Coroutine[Any, Any, NodeDecoratorR]
|
|
121
|
-
]: ...
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
_P = ParamSpec("_P")
|
|
125
|
-
_R = TypeVar("_R")
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
@dataclass(kw_only=True)
|
|
129
|
-
class FiniteStateMachine(Generic[DepsT]):
|
|
130
|
-
'''Automatical Finite State Machine (FSM) compiled from the code execution graph.
|
|
131
|
-
|
|
132
|
-
This class provides a global `RunContext` and a `node` decorator that gives function
|
|
133
|
-
it decorates access to the global context. The context is used to store the state
|
|
134
|
-
of the FSM and the data that is passed between nodes. This means that the consumer
|
|
135
|
-
of this class can update the context as needed to change the state of the FSM or
|
|
136
|
-
pass information between nodes.
|
|
137
|
-
|
|
138
|
-
Let's consider a very simple example of a FSM that increments a given value until it
|
|
139
|
-
is divisible by a given divisor. The FSM has two nodes: `increment` and
|
|
140
|
-
`divisible_by`. The `increment` node increments the given value by 1, and the
|
|
141
|
-
`divisible_by` node checks if the value is divisible by the given divisor. If it is,
|
|
142
|
-
the FSM returns the number of increments, otherwise it increments and divides again.
|
|
143
|
-
|
|
144
|
-
The FSM might look something like this:
|
|
145
|
-
|
|
146
|
-
+-----------+ +-----------+
|
|
147
|
-
| increment | ----> | divide | <---- start value
|
|
148
|
-
| | <---- | | ----> number of increments
|
|
149
|
-
+-----------+ +-----------+
|
|
150
|
-
|
|
151
|
-
The FSM has two nodes:
|
|
152
|
-
- `increment`: Increments the given value by 1.
|
|
153
|
-
- `divisible_by`: Checks if the value is divisible by the given divisor.
|
|
154
|
-
|
|
155
|
-
Now, it would be fairly straightforward to implement this FSM by defining the nodes
|
|
156
|
-
and the edges between them. However, the FSM can get very complex very quickly, and
|
|
157
|
-
it would be nice to have a way to automatically compile the FSM from the code so
|
|
158
|
-
that we can build arbitrarily complex systems without having to worry how to
|
|
159
|
-
structure them. Once compiled, the code can be run as a finite state machine.
|
|
160
|
-
|
|
161
|
-
What if we could just define the code?
|
|
162
|
-
|
|
163
|
-
Example:
|
|
164
|
-
|
|
165
|
-
```python
|
|
166
|
-
from dataclasses import dataclass
|
|
167
|
-
|
|
168
|
-
@dataclass
|
|
169
|
-
class Deps:
|
|
170
|
-
operations: int = 0
|
|
171
|
-
|
|
172
|
-
fsm = FiniteStateMachine(deps_type=Deps)
|
|
173
|
-
|
|
174
|
-
@fsm.node()
|
|
175
|
-
async def increment(ctx: RunContext[Deps], value: int) -> int:
|
|
176
|
-
ctx.deps.operations += 1
|
|
177
|
-
return value + 1
|
|
178
|
-
|
|
179
|
-
@fsm.node()
|
|
180
|
-
async def divide(ctx: RunContext[Deps], value: int, divisor: int) -> int:
|
|
181
|
-
ctx.deps.operations += 1
|
|
182
|
-
return value // divisor
|
|
183
|
-
|
|
184
|
-
@fsm.node()
|
|
185
|
-
async def reach_zero(ctx: RunContext[Deps], value: int, divisor: int) -> int:
|
|
186
|
-
while value != 0:
|
|
187
|
-
value = await increment(value)
|
|
188
|
-
value = await divide(value, divisor)
|
|
189
|
-
return ctx.deps.operations
|
|
190
|
-
|
|
191
|
-
# in async running loop
|
|
192
|
-
async with fsm.context(deps=Deps(0)) as ctx:
|
|
193
|
-
operations = await reach_zero(5, 3)
|
|
194
|
-
print(f"Operations: {operations}")
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
What about an FSM that represents a librarian?
|
|
198
|
-
|
|
199
|
-
Example:
|
|
200
|
-
|
|
201
|
-
```python
|
|
202
|
-
import asyncio
|
|
203
|
-
from dataclasses import dataclass
|
|
204
|
-
|
|
205
|
-
from mirascope import BaseMessageParam, Messages, llm
|
|
206
|
-
from mirascope import graphs as g
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
@dataclass
|
|
210
|
-
class Book
|
|
211
|
-
title: str
|
|
212
|
-
author: str
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
@dataclass
|
|
216
|
-
class Library:
|
|
217
|
-
librarian: str
|
|
218
|
-
messages: list[BaseMessageParam]
|
|
219
|
-
all_books: list[Book]
|
|
220
|
-
available_books: dict[str, bool]
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
machine = g.FiniteStateMachine(deps_type=Library)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
@machine.node()
|
|
227
|
-
@llm.tool
|
|
228
|
-
def all_books(ctx: g.RunContext[Library]) -> list[Book]:
|
|
229
|
-
"""Returns the titles of all books the library owns."""
|
|
230
|
-
return ctx.deps.all_books
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
@machine.node()
|
|
234
|
-
@llm.tool
|
|
235
|
-
def book_is_available(ctx: g.RunContext[Library], title: str) -> str:
|
|
236
|
-
"""Returns the author of the book with the given title."""
|
|
237
|
-
return ctx.deps.available_books[title]
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
@machine.node()
|
|
241
|
-
@llm.call(provider="openai:completions", model_id="gpt-4o-mini", tools=[all_books, book_is_available])
|
|
242
|
-
async def answer_question(ctx: g.RunContext[Library]) -> str:
|
|
243
|
-
return f"You are a librarian named {ctx.deps.librarian}"
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
@machine.node()
|
|
247
|
-
async def handle_tools(ctx: g.RunContext[Library], tools: list[Tool]) -> None:
|
|
248
|
-
tool_tasks = [tool.call() for tool in tools]
|
|
249
|
-
tool_outputs = asyncio.gather(*tool_tasks)
|
|
250
|
-
tools_and_outputs = [(tool, output) for tool, output in zip(tools, tools_outputs, strict=True)]
|
|
251
|
-
ctx.deps.messages += llm.CallResponse.tool_message_params(tools_and_outputs)
|
|
252
|
-
|
|
253
|
-
@machine.node()
|
|
254
|
-
async def librarian(ctx: g.RunContext[Library], question: str) -> llm.CallResponse:
|
|
255
|
-
response = await answer_question(question)
|
|
256
|
-
if response.user_message_param:
|
|
257
|
-
ctx.deps.messages.append(response.user_message_param)
|
|
258
|
-
ctx.deps.messages.append(response.message_param)
|
|
259
|
-
if tools := response.tools:
|
|
260
|
-
await handle_tools(tools)
|
|
261
|
-
return await answer_question("")
|
|
262
|
-
return response
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
agent = machine.compile(start=librarian)
|
|
266
|
-
|
|
267
|
-
response = agent.run(
|
|
268
|
-
"Do you carry The Name of the Wind? Is it available?",
|
|
269
|
-
deps=Library(
|
|
270
|
-
librarian="Mira",
|
|
271
|
-
all_books=[
|
|
272
|
-
Book("The Name of the Wind", "Patrick Rothfuss"),
|
|
273
|
-
Book("Mistborn: The Final Empire", "Brandon Sanderson"),
|
|
274
|
-
],
|
|
275
|
-
available_books={
|
|
276
|
-
"The Name of the Wind": True,
|
|
277
|
-
"Mistborn: The Final Empire": False,
|
|
278
|
-
},
|
|
279
|
-
)
|
|
280
|
-
)
|
|
281
|
-
print(response.content)
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
Ultimately each node is fairly self-contained except for `librarian`, which can be
|
|
285
|
-
thought of as it's own little sub-FSM or controller. The `librarian` node is the
|
|
286
|
-
entry point to the FSM and is responsible for handling the incoming question,
|
|
287
|
-
calling the `answer_question` node, and then handling the tools that are returned.
|
|
288
|
-
The `answer_question` node is responsible for answering the question, and the
|
|
289
|
-
`handle_tools` node is responsible for calling the tools and then returning the
|
|
290
|
-
results.
|
|
291
|
-
|
|
292
|
-
The compiled graph might look something like this:
|
|
293
|
-
|
|
294
|
-
+-----------------+ +-------------------+
|
|
295
|
-
question --> | answer_question | ----> | _handle_response_ | --> response
|
|
296
|
-
+-----------------+ +-------------------+
|
|
297
|
-
^ |
|
|
298
|
-
| v
|
|
299
|
-
+-------------------+
|
|
300
|
-
| handle_tools |
|
|
301
|
-
+-------------------+
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
Inside the `librarian` function, really all we're doing is calling `answer_question`
|
|
305
|
-
and then handling the response such that we either call any requested tools and call
|
|
306
|
-
`answer_question` again, or we return the response. This is a very simple example,
|
|
307
|
-
but it shows how we can build an efficient FSM without having to worry about the
|
|
308
|
-
nitty gritty details of how the FSM is structured.
|
|
309
|
-
|
|
310
|
-
We get to code. The FSM then builds itself.
|
|
311
|
-
'''
|
|
312
|
-
|
|
313
|
-
deps_type: type[DepsT]
|
|
314
|
-
"""The type of the dependencies for the FSM."""
|
|
315
|
-
|
|
316
|
-
@contextmanager
|
|
317
|
-
def context(self, *, deps: DepsT) -> Iterator[RunContext[DepsT]]:
|
|
318
|
-
"""Creates a context manager for the FSM that handles both sync and async.
|
|
319
|
-
|
|
320
|
-
This method returns an instance of FSMContextManager which can be used with both
|
|
321
|
-
`with` and `async with` statements. The context manager sets up a RunContext
|
|
322
|
-
that all node-decorated functions will use while the context is active. The
|
|
323
|
-
context is stored using contextvars.ContextVar which properly propagates through
|
|
324
|
-
async code, allowing context to be maintained across await points and between
|
|
325
|
-
nodes so long as each node is called within the context.
|
|
326
|
-
|
|
327
|
-
NOTE: This simplified implementation has some limitations with complex nested
|
|
328
|
-
async calls across context boundaries. For nested nodes calling other nodes
|
|
329
|
-
asynchronously after a context exit, the context may not be properly preserved.
|
|
330
|
-
In these cases, using a compiled machine is recommended.
|
|
331
|
-
|
|
332
|
-
NOTE: We implement stubs using the sync case because the async case is currently
|
|
333
|
-
the same and does not require separate implementations. In the future we may update
|
|
334
|
-
this to handle more complex async cases where the context e.g. pulls from an
|
|
335
|
-
external source or something.
|
|
336
|
-
|
|
337
|
-
Example:
|
|
338
|
-
|
|
339
|
-
Synchronous usage:
|
|
340
|
-
```python
|
|
341
|
-
with fsm.context(deps=Deps()) as ctx:
|
|
342
|
-
result = increment(5) # ctx is automatically passed
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
Example:
|
|
346
|
-
|
|
347
|
-
Asynchronous usage:
|
|
348
|
-
```python
|
|
349
|
-
async with fsm.context(deps=Deps()) as ctx:
|
|
350
|
-
result = await async_increment(5) # ctx is automatically passed
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
Args:
|
|
354
|
-
deps: The dependencies for the FSM.
|
|
355
|
-
|
|
356
|
-
Returns:
|
|
357
|
-
An FSMContextManager instance that supports both `with` and `async with`.
|
|
358
|
-
"""
|
|
359
|
-
context = RunContext(deps=deps)
|
|
360
|
-
token = FSM_CONTEXT.set(context)
|
|
361
|
-
try:
|
|
362
|
-
yield context
|
|
363
|
-
finally:
|
|
364
|
-
FSM_CONTEXT.reset(token)
|
|
365
|
-
|
|
366
|
-
def node(self) -> NodeDecorator[DepsT]:
|
|
367
|
-
"""Registers the given function as a node in the FSM.
|
|
368
|
-
|
|
369
|
-
This decorator transforms a function that requires a RunContext as its first
|
|
370
|
-
parameter into a function that can be called without explicitly passing the
|
|
371
|
-
context. The context is retrieved from contextvars.ContextVar which properly
|
|
372
|
-
propagates through async code, ensuring that context is maintained across
|
|
373
|
-
await points.
|
|
374
|
-
|
|
375
|
-
This means that when a node calls another node, even asynchronously, the context
|
|
376
|
-
is automatically propagated without any special handling. This allows for
|
|
377
|
-
building complex node graphs with proper context flow.
|
|
378
|
-
|
|
379
|
-
If the decorated function is called outside of a context manager, a
|
|
380
|
-
FSMContextError will be raised.
|
|
381
|
-
|
|
382
|
-
Returns:
|
|
383
|
-
A decorator that registers the given function as a node in the FSM.
|
|
384
|
-
"""
|
|
385
|
-
|
|
386
|
-
@overload
|
|
387
|
-
def decorator(
|
|
388
|
-
fn: NodeDecoratedFunction[_P, Coroutine[Any, Any, _R], DepsT],
|
|
389
|
-
) -> Callable[_P, Coroutine[Any, Any, _R]]: ...
|
|
390
|
-
|
|
391
|
-
@overload
|
|
392
|
-
def decorator(
|
|
393
|
-
fn: NodeDecoratedFunction[_P, _R, DepsT],
|
|
394
|
-
) -> Callable[_P, _R]: ...
|
|
395
|
-
|
|
396
|
-
def decorator(
|
|
397
|
-
fn: NodeDecoratedFunction[_P, _R | Coroutine[Any, Any, _R], DepsT],
|
|
398
|
-
) -> Callable[_P, _R | Coroutine[Any, Any, _R]]:
|
|
399
|
-
@wraps(fn)
|
|
400
|
-
def context_wrapper(
|
|
401
|
-
*args: _P.args, **kwargs: _P.kwargs
|
|
402
|
-
) -> _R | Coroutine[Any, Any, _R]:
|
|
403
|
-
context = FSM_CONTEXT.get()
|
|
404
|
-
if context is None:
|
|
405
|
-
raise FSMContextError(
|
|
406
|
-
f"Node `{fn.__name__}` called outside of a context manager. "
|
|
407
|
-
f"Use 'with fsm.context(deps=...)' to provide a context."
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
if inspect.iscoroutinefunction(fn):
|
|
411
|
-
# For async functions, just create and return a coroutine with the
|
|
412
|
-
# context management inside so we handle contextvars correctly
|
|
413
|
-
@wraps(fn)
|
|
414
|
-
async def inner_async() -> _R:
|
|
415
|
-
return await fn(context, *args, **kwargs) # pyright: ignore [reportGeneralTypeIssues]
|
|
416
|
-
|
|
417
|
-
return inner_async()
|
|
418
|
-
|
|
419
|
-
return fn(context, *args, **kwargs)
|
|
420
|
-
|
|
421
|
-
return context_wrapper
|
|
422
|
-
|
|
423
|
-
return decorator
|
|
424
|
-
|
|
425
|
-
def compile(
|
|
426
|
-
self,
|
|
427
|
-
start: Callable[_P, _R],
|
|
428
|
-
*,
|
|
429
|
-
deps: DepsT = None,
|
|
430
|
-
) -> "CompiledMachine[_P, _R, DepsT]":
|
|
431
|
-
"""Returns the compiled FSM.
|
|
432
|
-
|
|
433
|
-
Compiles the FSM and returns a CompiledMachine that can be used to run the FSM.
|
|
434
|
-
The CompiledMachine will use the provided start function as the entry point to
|
|
435
|
-
the FSM and will create a RunContext with the provided dependencies.
|
|
436
|
-
|
|
437
|
-
Args:
|
|
438
|
-
start: The entry point to the FSM.
|
|
439
|
-
deps: The dependencies for the FSM.
|
|
440
|
-
|
|
441
|
-
Returns:
|
|
442
|
-
A CompiledMachine that can be used to run the FSM.
|
|
443
|
-
"""
|
|
444
|
-
return CompiledMachine(fsm=self, start=start, deps=deps)
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
class CompiledMachine(Generic[_P, _R, DepsT]):
|
|
448
|
-
"""The compiled Finite State Machine (FSM).
|
|
449
|
-
|
|
450
|
-
This class represents a compiled FSM that can be run with a specific start function.
|
|
451
|
-
The FSM will use the provided start function as the entry point and will create a
|
|
452
|
-
RunContext with the provided dependencies when run.
|
|
453
|
-
|
|
454
|
-
Attributes:
|
|
455
|
-
fsm: The FiniteStateMachine that was compiled.
|
|
456
|
-
start: The entry point to the FSM.
|
|
457
|
-
deps: The dependencies for the FSM.
|
|
458
|
-
"""
|
|
459
|
-
|
|
460
|
-
fsm: FiniteStateMachine[DepsT]
|
|
461
|
-
start: Callable[_P, _R]
|
|
462
|
-
deps: DepsT
|
|
463
|
-
|
|
464
|
-
def __init__(
|
|
465
|
-
self,
|
|
466
|
-
*,
|
|
467
|
-
fsm: FiniteStateMachine[DepsT],
|
|
468
|
-
start: Callable[_P, _R],
|
|
469
|
-
deps: DepsT = None,
|
|
470
|
-
) -> None:
|
|
471
|
-
"""Initializes an instance of `CompiledMachine`."""
|
|
472
|
-
self.fsm = fsm
|
|
473
|
-
self.start = start
|
|
474
|
-
self.deps = deps
|
|
475
|
-
|
|
476
|
-
def run(self, *args: _P.args, **kwargs: _P.kwargs) -> _R:
|
|
477
|
-
"""Runs the FSM synchronously.
|
|
478
|
-
|
|
479
|
-
This method runs the FSM using the specified start function and dependencies.
|
|
480
|
-
It sets up a context manager to provide the RunContext for all node-decorated
|
|
481
|
-
functions called during the execution of the FSM.
|
|
482
|
-
|
|
483
|
-
This method should only be used with synchronous start functions. For async
|
|
484
|
-
functions, use `run_async` instead.
|
|
485
|
-
|
|
486
|
-
Args:
|
|
487
|
-
*args: The positional arguments to pass to the start function.
|
|
488
|
-
**kwargs: The keyword arguments to pass to the start function.
|
|
489
|
-
|
|
490
|
-
Returns:
|
|
491
|
-
The result of running the FSM.
|
|
492
|
-
|
|
493
|
-
Raises:
|
|
494
|
-
FSMContextError: If the start function or any node-decorated function is
|
|
495
|
-
called outside of a context manager.
|
|
496
|
-
RuntimeError: If called with an async function as the start function.
|
|
497
|
-
"""
|
|
498
|
-
if inspect.iscoroutinefunction(self.start):
|
|
499
|
-
raise RuntimeError(
|
|
500
|
-
f"Cannot use run() with async function {self.start.__name__}. "
|
|
501
|
-
f"Use run_async() instead."
|
|
502
|
-
)
|
|
503
|
-
|
|
504
|
-
with self.fsm.context(deps=self.deps):
|
|
505
|
-
return self.start(*args, **kwargs)
|
|
506
|
-
|
|
507
|
-
async def run_async(self, *args: _P.args, **kwargs: _P.kwargs) -> _R:
|
|
508
|
-
"""Runs the FSM asynchronously.
|
|
509
|
-
|
|
510
|
-
This method runs the FSM using the specified start function and dependencies.
|
|
511
|
-
It sets up an async context manager to provide the RunContext for all node-decorated
|
|
512
|
-
functions called during the execution of the FSM.
|
|
513
|
-
|
|
514
|
-
This method should only be used with asynchronous start functions. For synchronous
|
|
515
|
-
functions, use `run` instead.
|
|
516
|
-
|
|
517
|
-
Args:
|
|
518
|
-
*args: The positional arguments to pass to the start function.
|
|
519
|
-
**kwargs: The keyword arguments to pass to the start function.
|
|
520
|
-
|
|
521
|
-
Returns:
|
|
522
|
-
The result of running the FSM.
|
|
523
|
-
|
|
524
|
-
Raises:
|
|
525
|
-
FSMContextError: If the start function or any node-decorated function is
|
|
526
|
-
called outside of a context manager.
|
|
527
|
-
RuntimeError: If called with a sync function as the start function.
|
|
528
|
-
"""
|
|
529
|
-
with self.fsm.context(deps=self.deps):
|
|
530
|
-
result = self.start(*args, **kwargs)
|
|
531
|
-
if inspect.isawaitable(result):
|
|
532
|
-
return await result
|
|
533
|
-
return result
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
async def main() -> None:
|
|
537
|
-
from dataclasses import dataclass
|
|
538
|
-
|
|
539
|
-
@dataclass
|
|
540
|
-
class Deps:
|
|
541
|
-
operations: int = 0
|
|
542
|
-
|
|
543
|
-
fsm = FiniteStateMachine(deps_type=Deps)
|
|
544
|
-
|
|
545
|
-
# Example 1: Using the FSM with synchronous functions
|
|
546
|
-
print("Example 1: Using the FSM with synchronous functions")
|
|
547
|
-
|
|
548
|
-
@fsm.node()
|
|
549
|
-
def increment(ctx: RunContext[Deps], value: int) -> int:
|
|
550
|
-
ctx.deps.operations += 1
|
|
551
|
-
return value + 1
|
|
552
|
-
|
|
553
|
-
@fsm.node()
|
|
554
|
-
def divide(ctx: RunContext[Deps], value: int, divisor: int) -> int:
|
|
555
|
-
ctx.deps.operations += 1
|
|
556
|
-
return value // divisor
|
|
557
|
-
|
|
558
|
-
@fsm.node()
|
|
559
|
-
def reach_zero(ctx: RunContext[Deps], value: int, divisor: int) -> int:
|
|
560
|
-
while value != 0:
|
|
561
|
-
value = increment(value)
|
|
562
|
-
value = divide(value, divisor)
|
|
563
|
-
return ctx.deps.operations
|
|
564
|
-
|
|
565
|
-
with fsm.context(deps=Deps(0)):
|
|
566
|
-
operations = reach_zero(5, 3)
|
|
567
|
-
print(f"Operations: {operations}")
|
|
568
|
-
|
|
569
|
-
# Example 2: Run the compiled sync machine
|
|
570
|
-
print("\nExample 2: Run the compiled sync machine")
|
|
571
|
-
machine = fsm.compile(start=reach_zero, deps=Deps(0))
|
|
572
|
-
num_operations = machine.run(value=5, divisor=3)
|
|
573
|
-
print(f"Compiled sync operations: {num_operations}")
|
|
574
|
-
|
|
575
|
-
# # Define async nodes
|
|
576
|
-
@fsm.node()
|
|
577
|
-
async def async_increment(ctx: RunContext[Deps], value: int) -> int:
|
|
578
|
-
ctx.deps.operations += 1
|
|
579
|
-
return value + 1
|
|
580
|
-
|
|
581
|
-
@fsm.node()
|
|
582
|
-
async def async_divide(ctx: RunContext[Deps], value: int, divisor: int) -> int:
|
|
583
|
-
ctx.deps.operations += 1
|
|
584
|
-
return value // divisor
|
|
585
|
-
|
|
586
|
-
@fsm.node()
|
|
587
|
-
async def async_reach_zero(ctx: RunContext[Deps], value: int, divisor: int) -> int:
|
|
588
|
-
while value != 0:
|
|
589
|
-
value = await async_increment(value)
|
|
590
|
-
value = await async_divide(value, divisor)
|
|
591
|
-
return ctx.deps.operations
|
|
592
|
-
|
|
593
|
-
# Example 3: Using synchronous context with async functions within boundaries
|
|
594
|
-
print("\nExample 3: Synchronous context with async functions")
|
|
595
|
-
with fsm.context(deps=Deps(0)):
|
|
596
|
-
operations = await async_reach_zero(5, 3)
|
|
597
|
-
print(f"Async operations in sync context: {operations}")
|
|
598
|
-
|
|
599
|
-
# Example 3.5: Using asynchronous context with async functions within boundaries
|
|
600
|
-
print("\nExample 3.5: Asynchronous context with async functions")
|
|
601
|
-
with fsm.context(deps=Deps(0)):
|
|
602
|
-
operations = await async_reach_zero(5, 3)
|
|
603
|
-
print(f"Async operations in async context: {operations}")
|
|
604
|
-
|
|
605
|
-
# Example 4: Demonstrate context preservation across await boundaries
|
|
606
|
-
print("\nContext preservation example:")
|
|
607
|
-
with fsm.context(deps=Deps(0)):
|
|
608
|
-
# Create the coroutine task inside the context
|
|
609
|
-
operations_task = async_reach_zero(5, 3)
|
|
610
|
-
print("Task created inside context manager")
|
|
611
|
-
# But await it outside the context - this will work because we preserve the context
|
|
612
|
-
operations = await operations_task
|
|
613
|
-
print(f"Task awaited outside context, operations: {operations}")
|
|
614
|
-
|
|
615
|
-
# Example 5: Run the compiled async machine
|
|
616
|
-
print("\nCompiled async machine example:")
|
|
617
|
-
async_machine = fsm.compile(start=async_reach_zero, deps=Deps(0))
|
|
618
|
-
async_num_operations = await async_machine.run_async(value=5, divisor=3)
|
|
619
|
-
print(f"Compiled async operations: {async_num_operations}")
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
if __name__ == "__main__":
|
|
623
|
-
import asyncio
|
|
624
|
-
|
|
625
|
-
asyncio.run(main())
|
mirascope/llm/agents/__init__.py
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"""The Agents module for creating and managing LLM agents."""
|
|
2
|
-
|
|
3
|
-
from .agent import Agent, AsyncAgent, BaseAgent
|
|
4
|
-
from .agent_template import AgentTemplate, AsyncAgentTemplate
|
|
5
|
-
from .decorator import AgentDecorator, agent
|
|
6
|
-
|
|
7
|
-
__all__ = [
|
|
8
|
-
"Agent",
|
|
9
|
-
"AgentDecorator",
|
|
10
|
-
"AgentTemplate",
|
|
11
|
-
"AsyncAgent",
|
|
12
|
-
"AsyncAgentTemplate",
|
|
13
|
-
"BaseAgent",
|
|
14
|
-
"agent",
|
|
15
|
-
]
|