coco-framework 0.1.0__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.
- coco/__init__.py +3 -0
- coco/agents/__init__.py +9 -0
- coco/agents/_subagent_transformer.py +307 -0
- coco/agents/factory.py +2164 -0
- coco/agents/middleware/__init__.py +100 -0
- coco/agents/middleware/_execution.py +385 -0
- coco/agents/middleware/_redaction.py +454 -0
- coco/agents/middleware/_retry.py +134 -0
- coco/agents/middleware/_trace_policy.py +61 -0
- coco/agents/middleware/context_editing.py +306 -0
- coco/agents/middleware/file_search.py +433 -0
- coco/agents/middleware/human_in_the_loop.py +680 -0
- coco/agents/middleware/internal_call_transformer.py +108 -0
- coco/agents/middleware/model_call_limit.py +267 -0
- coco/agents/middleware/model_fallback.py +417 -0
- coco/agents/middleware/model_retry.py +327 -0
- coco/agents/middleware/pii.py +878 -0
- coco/agents/middleware/provider_tool_search.py +307 -0
- coco/agents/middleware/shell_tool.py +949 -0
- coco/agents/middleware/summarization.py +910 -0
- coco/agents/middleware/todo.py +357 -0
- coco/agents/middleware/tool_call_limit.py +495 -0
- coco/agents/middleware/tool_emulator.py +251 -0
- coco/agents/middleware/tool_error.py +209 -0
- coco/agents/middleware/tool_retry.py +421 -0
- coco/agents/middleware/tool_selection.py +515 -0
- coco/agents/middleware/types.py +2201 -0
- coco/agents/structured_output.py +472 -0
- coco/chat_models/__init__.py +7 -0
- coco/chat_models/base.py +1082 -0
- coco/embeddings/__init__.py +18 -0
- coco/embeddings/base.py +279 -0
- coco/mcp/__init__.py +36 -0
- coco/mcp/adapter.py +245 -0
- coco/mcp/elicitation.py +394 -0
- coco/mcp/tools.py +307 -0
- coco/messages/__init__.py +73 -0
- coco/py.typed +0 -0
- coco/rate_limiters/__init__.py +13 -0
- coco/tools/__init__.py +22 -0
- coco/tools/tool_node.py +20 -0
- coco_anthropic/__init__.py +15 -0
- coco_anthropic/_client_utils.py +81 -0
- coco_anthropic/_compat.py +228 -0
- coco_anthropic/_sdk_compat.py +124 -0
- coco_anthropic/_version.py +3 -0
- coco_anthropic/chat_models.py +3349 -0
- coco_anthropic/data/__init__.py +1 -0
- coco_anthropic/data/_profiles.py +491 -0
- coco_anthropic/data/profile_augmentations.toml +69 -0
- coco_anthropic/experimental.py +140 -0
- coco_anthropic/llms.py +428 -0
- coco_anthropic/middleware/__init__.py +25 -0
- coco_anthropic/middleware/anthropic_tools.py +1197 -0
- coco_anthropic/middleware/bash.py +84 -0
- coco_anthropic/middleware/file_search.py +360 -0
- coco_anthropic/middleware/prompt_caching.py +276 -0
- coco_anthropic/output_parsers.py +93 -0
- coco_anthropic/py.typed +0 -0
- coco_core/__init__.py +20 -0
- coco_core/_api/__init__.py +87 -0
- coco_core/_api/beta_decorator.py +266 -0
- coco_core/_api/deprecation.py +637 -0
- coco_core/_api/internal.py +23 -0
- coco_core/_api/path.py +50 -0
- coco_core/_import_utils.py +41 -0
- coco_core/_protocol.py +625 -0
- coco_core/_security/__init__.py +36 -0
- coco_core/_security/_exceptions.py +9 -0
- coco_core/_security/_policy.py +313 -0
- coco_core/_security/_ssrf_protection.py +155 -0
- coco_core/_security/_transport.py +254 -0
- coco_core/agents.py +258 -0
- coco_core/caches.py +272 -0
- coco_core/callbacks/__init__.py +132 -0
- coco_core/callbacks/base.py +1229 -0
- coco_core/callbacks/file.py +266 -0
- coco_core/callbacks/manager.py +2826 -0
- coco_core/callbacks/stdout.py +123 -0
- coco_core/callbacks/streaming_stdout.py +154 -0
- coco_core/callbacks/usage.py +124 -0
- coco_core/chat_history.py +267 -0
- coco_core/chat_loaders.py +26 -0
- coco_core/chat_sessions.py +19 -0
- coco_core/cross_encoders.py +18 -0
- coco_core/document_loaders/__init__.py +36 -0
- coco_core/document_loaders/base.py +155 -0
- coco_core/document_loaders/blob_loaders.py +38 -0
- coco_core/documents/__init__.py +55 -0
- coco_core/documents/base.py +347 -0
- coco_core/documents/compressor.py +74 -0
- coco_core/documents/transformers.py +79 -0
- coco_core/embeddings/__init__.py +31 -0
- coco_core/embeddings/embeddings.py +78 -0
- coco_core/embeddings/fake.py +129 -0
- coco_core/env.py +22 -0
- coco_core/example_selectors/__init__.py +47 -0
- coco_core/example_selectors/base.py +60 -0
- coco_core/example_selectors/length_based.py +131 -0
- coco_core/example_selectors/semantic_similarity.py +366 -0
- coco_core/exceptions.py +166 -0
- coco_core/globals.py +72 -0
- coco_core/indexing/__init__.py +53 -0
- coco_core/indexing/api.py +954 -0
- coco_core/indexing/base.py +661 -0
- coco_core/indexing/in_memory.py +104 -0
- coco_core/language_models/__init__.py +116 -0
- coco_core/language_models/_compat_bridge.py +844 -0
- coco_core/language_models/_utils.py +343 -0
- coco_core/language_models/base.py +500 -0
- coco_core/language_models/chat_model_stream.py +1525 -0
- coco_core/language_models/chat_models.py +2748 -0
- coco_core/language_models/fake.py +137 -0
- coco_core/language_models/fake_chat_models.py +396 -0
- coco_core/language_models/llms.py +1569 -0
- coco_core/language_models/model_profile.py +183 -0
- coco_core/load/__init__.py +44 -0
- coco_core/load/_validation.py +191 -0
- coco_core/load/dump.py +120 -0
- coco_core/load/load.py +828 -0
- coco_core/load/mapping.py +1097 -0
- coco_core/load/serializable.py +406 -0
- coco_core/messages/__init__.py +198 -0
- coco_core/messages/ai.py +854 -0
- coco_core/messages/base.py +518 -0
- coco_core/messages/block_translators/__init__.py +112 -0
- coco_core/messages/block_translators/anthropic.py +534 -0
- coco_core/messages/block_translators/bedrock.py +108 -0
- coco_core/messages/block_translators/bedrock_converse.py +329 -0
- coco_core/messages/block_translators/coco_v0.py +303 -0
- coco_core/messages/block_translators/google_genai.py +610 -0
- coco_core/messages/block_translators/google_vertexai.py +21 -0
- coco_core/messages/block_translators/groq.py +157 -0
- coco_core/messages/block_translators/openai.py +1089 -0
- coco_core/messages/chat.py +64 -0
- coco_core/messages/content.py +1488 -0
- coco_core/messages/function.py +62 -0
- coco_core/messages/human.py +70 -0
- coco_core/messages/modifier.py +33 -0
- coco_core/messages/system.py +70 -0
- coco_core/messages/tool.py +418 -0
- coco_core/messages/utils.py +2442 -0
- coco_core/output_parsers/__init__.py +101 -0
- coco_core/output_parsers/base.py +361 -0
- coco_core/output_parsers/format_instructions.py +16 -0
- coco_core/output_parsers/json.py +139 -0
- coco_core/output_parsers/list.py +249 -0
- coco_core/output_parsers/openai_functions.py +324 -0
- coco_core/output_parsers/openai_tools.py +383 -0
- coco_core/output_parsers/pydantic.py +141 -0
- coco_core/output_parsers/string.py +63 -0
- coco_core/output_parsers/transform.py +175 -0
- coco_core/output_parsers/xml.py +301 -0
- coco_core/outputs/__init__.py +66 -0
- coco_core/outputs/chat_generation.py +147 -0
- coco_core/outputs/chat_result.py +39 -0
- coco_core/outputs/generation.py +80 -0
- coco_core/outputs/llm_result.py +112 -0
- coco_core/outputs/run_info.py +22 -0
- coco_core/prompt_values.py +161 -0
- coco_core/prompts/__init__.py +101 -0
- coco_core/prompts/base.py +508 -0
- coco_core/prompts/chat.py +1495 -0
- coco_core/prompts/dict.py +177 -0
- coco_core/prompts/few_shot.py +483 -0
- coco_core/prompts/few_shot_with_templates.py +239 -0
- coco_core/prompts/image.py +177 -0
- coco_core/prompts/loading.py +296 -0
- coco_core/prompts/message.py +97 -0
- coco_core/prompts/prompt.py +312 -0
- coco_core/prompts/string.py +399 -0
- coco_core/prompts/structured.py +214 -0
- coco_core/py.typed +0 -0
- coco_core/rate_limiters.py +254 -0
- coco_core/retrievers.py +328 -0
- coco_core/runnables/__init__.py +136 -0
- coco_core/runnables/base.py +6725 -0
- coco_core/runnables/branch.py +457 -0
- coco_core/runnables/config.py +712 -0
- coco_core/runnables/configurable.py +710 -0
- coco_core/runnables/fallbacks.py +663 -0
- coco_core/runnables/graph.py +741 -0
- coco_core/runnables/graph_ascii.py +366 -0
- coco_core/runnables/graph_mermaid.py +503 -0
- coco_core/runnables/graph_png.py +215 -0
- coco_core/runnables/history.py +631 -0
- coco_core/runnables/passthrough.py +849 -0
- coco_core/runnables/retry.py +379 -0
- coco_core/runnables/router.py +239 -0
- coco_core/runnables/schema.py +188 -0
- coco_core/runnables/utils.py +798 -0
- coco_core/stores.py +291 -0
- coco_core/structured_query.py +203 -0
- coco_core/sys_info.py +154 -0
- coco_core/tools/__init__.py +95 -0
- coco_core/tools/base.py +1948 -0
- coco_core/tools/convert.py +497 -0
- coco_core/tools/render.py +67 -0
- coco_core/tools/retriever.py +94 -0
- coco_core/tools/simple.py +208 -0
- coco_core/tools/structured.py +322 -0
- coco_core/tracers/__init__.py +47 -0
- coco_core/tracers/_compat.py +95 -0
- coco_core/tracers/_context.py +337 -0
- coco_core/tracers/_streaming.py +53 -0
- coco_core/tracers/base.py +955 -0
- coco_core/tracers/coco.py +279 -0
- coco_core/tracers/context.py +205 -0
- coco_core/tracers/core.py +766 -0
- coco_core/tracers/event_stream.py +1101 -0
- coco_core/tracers/log_stream.py +765 -0
- coco_core/tracers/memory_stream.py +200 -0
- coco_core/tracers/root_listeners.py +130 -0
- coco_core/tracers/run_collector.py +40 -0
- coco_core/tracers/schemas.py +14 -0
- coco_core/tracers/stdout.py +205 -0
- coco_core/utils/__init__.py +111 -0
- coco_core/utils/_gateway.py +274 -0
- coco_core/utils/_merge.py +218 -0
- coco_core/utils/aiter.py +357 -0
- coco_core/utils/env.py +86 -0
- coco_core/utils/formatting.py +80 -0
- coco_core/utils/function_calling.py +869 -0
- coco_core/utils/html.py +132 -0
- coco_core/utils/image.py +15 -0
- coco_core/utils/input.py +82 -0
- coco_core/utils/interactive_env.py +12 -0
- coco_core/utils/iter.py +229 -0
- coco_core/utils/json.py +230 -0
- coco_core/utils/json_schema.py +273 -0
- coco_core/utils/mustache.py +706 -0
- coco_core/utils/pydantic.py +631 -0
- coco_core/utils/strings.py +69 -0
- coco_core/utils/usage.py +61 -0
- coco_core/utils/utils.py +528 -0
- coco_core/utils/uuid.py +57 -0
- coco_core/vectorstores/__init__.py +53 -0
- coco_core/vectorstores/base.py +1111 -0
- coco_core/vectorstores/in_memory.py +546 -0
- coco_core/vectorstores/utils.py +163 -0
- coco_core/version.py +3 -0
- coco_framework-0.1.0.dist-info/METADATA +184 -0
- coco_framework-0.1.0.dist-info/RECORD +400 -0
- coco_framework-0.1.0.dist-info/WHEEL +4 -0
- coco_framework-0.1.0.dist-info/licenses/LICENSE +24 -0
- coco_framework-0.1.0.dist-info/licenses/NOTICE +15 -0
- coco_graph/_internal/__init__.py +4 -0
- coco_graph/_internal/_cache.py +31 -0
- coco_graph/_internal/_config.py +474 -0
- coco_graph/_internal/_constants.py +140 -0
- coco_graph/_internal/_fields.py +213 -0
- coco_graph/_internal/_future.py +220 -0
- coco_graph/_internal/_pydantic.py +275 -0
- coco_graph/_internal/_queue.py +124 -0
- coco_graph/_internal/_replay.py +90 -0
- coco_graph/_internal/_retry.py +29 -0
- coco_graph/_internal/_runnable.py +969 -0
- coco_graph/_internal/_scratchpad.py +19 -0
- coco_graph/_internal/_serde.py +253 -0
- coco_graph/_internal/_timeout.py +25 -0
- coco_graph/_internal/_typing.py +54 -0
- coco_graph/cache/base/__init__.py +48 -0
- coco_graph/cache/base/py.typed +0 -0
- coco_graph/cache/memory/__init__.py +73 -0
- coco_graph/cache/redis/__init__.py +144 -0
- coco_graph/cache/sqlite/__init__.py +120 -0
- coco_graph/callbacks.py +394 -0
- coco_graph/channels/__init__.py +29 -0
- coco_graph/channels/any_value.py +72 -0
- coco_graph/channels/base.py +121 -0
- coco_graph/channels/binop.py +155 -0
- coco_graph/channels/delta.py +202 -0
- coco_graph/channels/ephemeral_value.py +79 -0
- coco_graph/channels/last_value.py +151 -0
- coco_graph/channels/named_barrier_value.py +167 -0
- coco_graph/channels/topic.py +94 -0
- coco_graph/channels/untracked_value.py +73 -0
- coco_graph/checkpoint/base/__init__.py +861 -0
- coco_graph/checkpoint/base/id.py +109 -0
- coco_graph/checkpoint/base/py.typed +0 -0
- coco_graph/checkpoint/memory/__init__.py +698 -0
- coco_graph/checkpoint/memory/py.typed +0 -0
- coco_graph/checkpoint/postgres/__init__.py +602 -0
- coco_graph/checkpoint/postgres/_ainternal.py +23 -0
- coco_graph/checkpoint/postgres/_internal.py +21 -0
- coco_graph/checkpoint/postgres/aio.py +691 -0
- coco_graph/checkpoint/postgres/base.py +666 -0
- coco_graph/checkpoint/postgres/py.typed +0 -0
- coco_graph/checkpoint/postgres/shallow.py +967 -0
- coco_graph/checkpoint/serde/__init__.py +0 -0
- coco_graph/checkpoint/serde/_msgpack.py +97 -0
- coco_graph/checkpoint/serde/base.py +64 -0
- coco_graph/checkpoint/serde/encrypted.py +80 -0
- coco_graph/checkpoint/serde/event_hooks.py +52 -0
- coco_graph/checkpoint/serde/jsonplus.py +883 -0
- coco_graph/checkpoint/serde/py.typed +0 -0
- coco_graph/checkpoint/serde/types.py +68 -0
- coco_graph/checkpoint/sqlite/__init__.py +645 -0
- coco_graph/checkpoint/sqlite/_delta.py +172 -0
- coco_graph/checkpoint/sqlite/aio.py +738 -0
- coco_graph/checkpoint/sqlite/py.typed +0 -0
- coco_graph/checkpoint/sqlite/utils.py +116 -0
- coco_graph/config.py +196 -0
- coco_graph/constants.py +64 -0
- coco_graph/errors.py +241 -0
- coco_graph/func/__init__.py +620 -0
- coco_graph/graph/__init__.py +12 -0
- coco_graph/graph/_branch.py +225 -0
- coco_graph/graph/_node.py +103 -0
- coco_graph/graph/message.py +437 -0
- coco_graph/graph/state.py +1978 -0
- coco_graph/graph/ui.py +227 -0
- coco_graph/managed/__init__.py +3 -0
- coco_graph/managed/base.py +31 -0
- coco_graph/managed/is_last_step.py +24 -0
- coco_graph/prebuilt/__init__.py +23 -0
- coco_graph/prebuilt/_tool_call_stream.py +117 -0
- coco_graph/prebuilt/_tool_call_transformer.py +165 -0
- coco_graph/prebuilt/chat_agent_executor.py +1015 -0
- coco_graph/prebuilt/interrupt.py +105 -0
- coco_graph/prebuilt/py.typed +0 -0
- coco_graph/prebuilt/tool_node.py +2030 -0
- coco_graph/prebuilt/tool_validator.py +221 -0
- coco_graph/pregel/__init__.py +3 -0
- coco_graph/pregel/_algo.py +1460 -0
- coco_graph/pregel/_call.py +298 -0
- coco_graph/pregel/_checkpoint.py +331 -0
- coco_graph/pregel/_config.py +0 -0
- coco_graph/pregel/_draw.py +294 -0
- coco_graph/pregel/_executor.py +223 -0
- coco_graph/pregel/_io.py +174 -0
- coco_graph/pregel/_log.py +3 -0
- coco_graph/pregel/_loop.py +1988 -0
- coco_graph/pregel/_messages.py +461 -0
- coco_graph/pregel/_read.py +312 -0
- coco_graph/pregel/_retry.py +854 -0
- coco_graph/pregel/_runner.py +941 -0
- coco_graph/pregel/_tools.py +268 -0
- coco_graph/pregel/_utils.py +221 -0
- coco_graph/pregel/_validate.py +120 -0
- coco_graph/pregel/_write.py +192 -0
- coco_graph/pregel/debug.py +302 -0
- coco_graph/pregel/main.py +4364 -0
- coco_graph/pregel/protocol.py +288 -0
- coco_graph/pregel/types.py +38 -0
- coco_graph/py.typed +0 -0
- coco_graph/runtime.py +350 -0
- coco_graph/store/base/__init__.py +1322 -0
- coco_graph/store/base/batch.py +371 -0
- coco_graph/store/base/embed.py +436 -0
- coco_graph/store/base/py.typed +0 -0
- coco_graph/store/memory/__init__.py +592 -0
- coco_graph/store/memory/py.typed +0 -0
- coco_graph/store/postgres/__init__.py +4 -0
- coco_graph/store/postgres/aio.py +592 -0
- coco_graph/store/postgres/base.py +1480 -0
- coco_graph/store/postgres/py.typed +0 -0
- coco_graph/store/sqlite/__init__.py +4 -0
- coco_graph/store/sqlite/aio.py +632 -0
- coco_graph/store/sqlite/base.py +1514 -0
- coco_graph/stream/__init__.py +45 -0
- coco_graph/stream/_convert.py +32 -0
- coco_graph/stream/_mux.py +523 -0
- coco_graph/stream/_types.py +330 -0
- coco_graph/stream/run_stream.py +735 -0
- coco_graph/stream/stream_channel.py +341 -0
- coco_graph/stream/transformers.py +1039 -0
- coco_graph/types.py +1079 -0
- coco_graph/typing.py +48 -0
- coco_graph/utils/__init__.py +1 -0
- coco_graph/utils/config.py +4 -0
- coco_graph/utils/runnable.py +3 -0
- coco_graph/version.py +13 -0
- coco_graph/warnings.py +69 -0
- coco_openai/__init__.py +20 -0
- coco_openai/_compat.py +45 -0
- coco_openai/_version.py +3 -0
- coco_openai/chat_models/__init__.py +6 -0
- coco_openai/chat_models/_client_utils.py +684 -0
- coco_openai/chat_models/_compat.py +525 -0
- coco_openai/chat_models/azure.py +1227 -0
- coco_openai/chat_models/base.py +5654 -0
- coco_openai/chat_models/codex.py +557 -0
- coco_openai/chatgpt_oauth.py +1063 -0
- coco_openai/data/__init__.py +1 -0
- coco_openai/data/_profiles.py +1655 -0
- coco_openai/data/profile_augmentations.toml +127 -0
- coco_openai/embeddings/__init__.py +6 -0
- coco_openai/embeddings/azure.py +240 -0
- coco_openai/embeddings/base.py +820 -0
- coco_openai/llms/__init__.py +6 -0
- coco_openai/llms/azure.py +249 -0
- coco_openai/llms/base.py +919 -0
- coco_openai/middleware/__init__.py +11 -0
- coco_openai/middleware/openai_moderation.py +484 -0
- coco_openai/output_parsers/__init__.py +9 -0
- coco_openai/output_parsers/tools.py +9 -0
- coco_openai/py.typed +0 -0
- coco_openai/tools/__init__.py +5 -0
- coco_openai/tools/custom_tool.py +111 -0
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import enum
|
|
5
|
+
import inspect
|
|
6
|
+
import logging
|
|
7
|
+
import sys
|
|
8
|
+
import warnings
|
|
9
|
+
from collections.abc import (
|
|
10
|
+
AsyncIterator,
|
|
11
|
+
Awaitable,
|
|
12
|
+
Callable,
|
|
13
|
+
Coroutine,
|
|
14
|
+
Generator,
|
|
15
|
+
Iterator,
|
|
16
|
+
Sequence,
|
|
17
|
+
)
|
|
18
|
+
from contextlib import AsyncExitStack, contextmanager
|
|
19
|
+
from contextvars import Context, Token, copy_context
|
|
20
|
+
from functools import partial, wraps
|
|
21
|
+
from typing import (
|
|
22
|
+
Any,
|
|
23
|
+
Optional,
|
|
24
|
+
Protocol,
|
|
25
|
+
TypeGuard,
|
|
26
|
+
cast,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from coco_core.runnables.base import (
|
|
30
|
+
Runnable,
|
|
31
|
+
RunnableConfig,
|
|
32
|
+
RunnableLambda,
|
|
33
|
+
RunnableParallel,
|
|
34
|
+
RunnableSequence,
|
|
35
|
+
)
|
|
36
|
+
from coco_core.runnables.base import (
|
|
37
|
+
RunnableLike as LCRunnableLike,
|
|
38
|
+
)
|
|
39
|
+
from coco_core.runnables.config import (
|
|
40
|
+
run_in_executor,
|
|
41
|
+
var_child_runnable_config,
|
|
42
|
+
)
|
|
43
|
+
from coco_core.runnables.utils import Input, Output
|
|
44
|
+
from coco_core.tracers.coco import CocoTracer
|
|
45
|
+
from coco_graph.store.base import BaseStore
|
|
46
|
+
|
|
47
|
+
from coco_graph._internal._config import (
|
|
48
|
+
ensure_config,
|
|
49
|
+
get_async_callback_manager_for_config,
|
|
50
|
+
get_callback_manager_for_config,
|
|
51
|
+
patch_config,
|
|
52
|
+
)
|
|
53
|
+
from coco_graph._internal._constants import (
|
|
54
|
+
CONF,
|
|
55
|
+
CONFIG_KEY_NODE_ERROR,
|
|
56
|
+
CONFIG_KEY_RUNTIME,
|
|
57
|
+
)
|
|
58
|
+
from coco_graph._internal._typing import MISSING
|
|
59
|
+
from coco_graph.errors import NodeError
|
|
60
|
+
from coco_graph.types import StreamWriter
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
from coco_core.tracers._streaming import _StreamingCallbackHandler
|
|
64
|
+
except ImportError:
|
|
65
|
+
_StreamingCallbackHandler = None # type: ignore
|
|
66
|
+
|
|
67
|
+
logger = logging.getLogger(__name__)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _trace_payload(value: Any, transform: Callable[[Any], Any] | None) -> Any:
|
|
71
|
+
"""Return the payload to record on a run for `value`.
|
|
72
|
+
|
|
73
|
+
When `transform` is unset this is a passthrough, so unspecified nodes record exactly
|
|
74
|
+
as before. When set it always runs (regardless of tracing), but never affects
|
|
75
|
+
execution: if it raises, the untransformed value is recorded instead.
|
|
76
|
+
"""
|
|
77
|
+
if transform is None:
|
|
78
|
+
return value
|
|
79
|
+
try:
|
|
80
|
+
return transform(value)
|
|
81
|
+
except Exception:
|
|
82
|
+
logger.exception(
|
|
83
|
+
"trace input/output processor raised; recording untransformed payload"
|
|
84
|
+
)
|
|
85
|
+
return value
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _set_config_context(
|
|
89
|
+
config: RunnableConfig, run: Any = None
|
|
90
|
+
) -> Token[RunnableConfig | None]:
|
|
91
|
+
"""Set the child Runnable config + tracing context.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
config: The config to set.
|
|
95
|
+
"""
|
|
96
|
+
config_token = var_child_runnable_config.set(config)
|
|
97
|
+
if run is not None:
|
|
98
|
+
from coco_core.tracers._context import _set_tracing_context
|
|
99
|
+
|
|
100
|
+
_set_tracing_context({"parent": run})
|
|
101
|
+
return config_token
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _unset_config_context(token: Token[RunnableConfig | None], run: Any = None) -> None:
|
|
105
|
+
"""Set the child Runnable config + tracing context.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
token: The config token to reset.
|
|
109
|
+
"""
|
|
110
|
+
var_child_runnable_config.reset(token)
|
|
111
|
+
if run is not None:
|
|
112
|
+
from coco_core.tracers._context import _set_tracing_context
|
|
113
|
+
|
|
114
|
+
_set_tracing_context(
|
|
115
|
+
{
|
|
116
|
+
"parent": None,
|
|
117
|
+
"project_name": None,
|
|
118
|
+
"tags": None,
|
|
119
|
+
"metadata": None,
|
|
120
|
+
"enabled": None,
|
|
121
|
+
"client": None,
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@contextmanager
|
|
127
|
+
def set_config_context(
|
|
128
|
+
config: RunnableConfig, run: Any = None
|
|
129
|
+
) -> Generator[Context, None, None]:
|
|
130
|
+
"""Set the child Runnable config + tracing context.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
config: The config to set.
|
|
134
|
+
"""
|
|
135
|
+
ctx = copy_context()
|
|
136
|
+
config_token = ctx.run(_set_config_context, config, run)
|
|
137
|
+
try:
|
|
138
|
+
yield ctx
|
|
139
|
+
finally:
|
|
140
|
+
ctx.run(_unset_config_context, config_token, run)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def create_task_in_config_context(
|
|
144
|
+
coro_factory: Callable[[], Coroutine[Any, Any, Any]], config: RunnableConfig
|
|
145
|
+
) -> asyncio.Task[Any]:
|
|
146
|
+
"""Create an asyncio.Task that inherits `config` as the child runnable context.
|
|
147
|
+
|
|
148
|
+
`asyncio.create_task` snapshots the current contextvars onto the new task,
|
|
149
|
+
so calling `create_task` while the config context is set ensures the task
|
|
150
|
+
sees `config` via `var_child_runnable_config` and any tracing parent.
|
|
151
|
+
"""
|
|
152
|
+
with set_config_context(config) as context:
|
|
153
|
+
return context.run(lambda: asyncio.create_task(coro_factory()))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# Before Python 3.11 native StrEnum is not available
|
|
157
|
+
class StrEnum(str, enum.Enum):
|
|
158
|
+
"""A string enum."""
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# Special type to denote any type is accepted
|
|
162
|
+
ANY_TYPE = object()
|
|
163
|
+
|
|
164
|
+
ASYNCIO_ACCEPTS_CONTEXT = sys.version_info >= (3, 11)
|
|
165
|
+
|
|
166
|
+
# List of keyword arguments that can be injected into nodes / tasks / tools at runtime.
|
|
167
|
+
# A named argument may appear multiple times if it appears with distinct types.
|
|
168
|
+
KWARGS_CONFIG_KEYS: tuple[tuple[str, tuple[Any, ...], str, Any], ...] = (
|
|
169
|
+
(
|
|
170
|
+
"config",
|
|
171
|
+
(
|
|
172
|
+
RunnableConfig,
|
|
173
|
+
"RunnableConfig",
|
|
174
|
+
Optional[RunnableConfig], # noqa: UP045
|
|
175
|
+
"Optional[RunnableConfig]",
|
|
176
|
+
inspect.Parameter.empty,
|
|
177
|
+
),
|
|
178
|
+
# for now, use config directly, eventually, will pop off of Runtime
|
|
179
|
+
"N/A",
|
|
180
|
+
inspect.Parameter.empty,
|
|
181
|
+
),
|
|
182
|
+
(
|
|
183
|
+
"writer",
|
|
184
|
+
(StreamWriter, "StreamWriter", inspect.Parameter.empty),
|
|
185
|
+
"stream_writer",
|
|
186
|
+
lambda _: None,
|
|
187
|
+
),
|
|
188
|
+
(
|
|
189
|
+
"store",
|
|
190
|
+
(
|
|
191
|
+
BaseStore,
|
|
192
|
+
"BaseStore",
|
|
193
|
+
inspect.Parameter.empty,
|
|
194
|
+
),
|
|
195
|
+
"store",
|
|
196
|
+
inspect.Parameter.empty,
|
|
197
|
+
),
|
|
198
|
+
(
|
|
199
|
+
"store",
|
|
200
|
+
(
|
|
201
|
+
Optional[BaseStore], # noqa: UP045
|
|
202
|
+
"Optional[BaseStore]",
|
|
203
|
+
),
|
|
204
|
+
"store",
|
|
205
|
+
None,
|
|
206
|
+
),
|
|
207
|
+
(
|
|
208
|
+
"previous",
|
|
209
|
+
(ANY_TYPE,),
|
|
210
|
+
"previous",
|
|
211
|
+
inspect.Parameter.empty,
|
|
212
|
+
),
|
|
213
|
+
(
|
|
214
|
+
"runtime",
|
|
215
|
+
(ANY_TYPE,),
|
|
216
|
+
# we never hit this block, we just inject runtime directly
|
|
217
|
+
"N/A",
|
|
218
|
+
inspect.Parameter.empty,
|
|
219
|
+
),
|
|
220
|
+
(
|
|
221
|
+
"error",
|
|
222
|
+
(NodeError, "NodeError"),
|
|
223
|
+
# we never hit this block, we read directly from configurable
|
|
224
|
+
"N/A",
|
|
225
|
+
# default to None so non-handler nodes that happen to type a parameter
|
|
226
|
+
# `error: NodeError` don't blow up; handlers always receive a NodeError.
|
|
227
|
+
None,
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
"""List of kwargs that can be passed to functions, and their corresponding
|
|
231
|
+
config keys, default values and type annotations.
|
|
232
|
+
|
|
233
|
+
Used to configure keyword arguments that can be injected at runtime
|
|
234
|
+
from the `Runtime` object as kwargs to `invoke`, `ainvoke`, `stream` and `astream`.
|
|
235
|
+
|
|
236
|
+
For a keyword to be injected from the config object, the function signature
|
|
237
|
+
must contain a kwarg with the same name and a matching type annotation.
|
|
238
|
+
|
|
239
|
+
Each tuple contains:
|
|
240
|
+
- the name of the kwarg in the function signature
|
|
241
|
+
- the type annotation(s) for the kwarg
|
|
242
|
+
- the `Runtime` attribute for fetching the value (N/A if not applicable)
|
|
243
|
+
|
|
244
|
+
This is fully internal and should be further refactored to use `get_type_hints`
|
|
245
|
+
to resolve forward references and optional types formatted like BaseStore | None.
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
VALID_KINDS = (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class _RunnableWithWriter(Protocol[Input, Output]):
|
|
252
|
+
def __call__(self, state: Input, *, writer: StreamWriter) -> Output: ...
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class _RunnableWithStore(Protocol[Input, Output]):
|
|
256
|
+
def __call__(self, state: Input, *, store: BaseStore) -> Output: ...
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class _RunnableWithWriterStore(Protocol[Input, Output]):
|
|
260
|
+
def __call__(
|
|
261
|
+
self, state: Input, *, writer: StreamWriter, store: BaseStore
|
|
262
|
+
) -> Output: ...
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class _RunnableWithConfigWriter(Protocol[Input, Output]):
|
|
266
|
+
def __call__(
|
|
267
|
+
self, state: Input, *, config: RunnableConfig, writer: StreamWriter
|
|
268
|
+
) -> Output: ...
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class _RunnableWithConfigStore(Protocol[Input, Output]):
|
|
272
|
+
def __call__(
|
|
273
|
+
self, state: Input, *, config: RunnableConfig, store: BaseStore
|
|
274
|
+
) -> Output: ...
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class _RunnableWithConfigWriterStore(Protocol[Input, Output]):
|
|
278
|
+
def __call__(
|
|
279
|
+
self,
|
|
280
|
+
state: Input,
|
|
281
|
+
*,
|
|
282
|
+
config: RunnableConfig,
|
|
283
|
+
writer: StreamWriter,
|
|
284
|
+
store: BaseStore,
|
|
285
|
+
) -> Output: ...
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
RunnableLike = (
|
|
289
|
+
LCRunnableLike
|
|
290
|
+
| _RunnableWithWriter[Input, Output]
|
|
291
|
+
| _RunnableWithStore[Input, Output]
|
|
292
|
+
| _RunnableWithWriterStore[Input, Output]
|
|
293
|
+
| _RunnableWithConfigWriter[Input, Output]
|
|
294
|
+
| _RunnableWithConfigStore[Input, Output]
|
|
295
|
+
| _RunnableWithConfigWriterStore[Input, Output]
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class RunnableCallable(Runnable):
|
|
300
|
+
"""A much simpler version of RunnableLambda that requires sync and async functions."""
|
|
301
|
+
|
|
302
|
+
def __init__(
|
|
303
|
+
self,
|
|
304
|
+
func: Callable[..., Any | Runnable] | None,
|
|
305
|
+
afunc: Callable[..., Awaitable[Any | Runnable]] | None = None,
|
|
306
|
+
*,
|
|
307
|
+
name: str | None = None,
|
|
308
|
+
tags: Sequence[str] | None = None,
|
|
309
|
+
trace: bool = True,
|
|
310
|
+
recurse: bool = True,
|
|
311
|
+
explode_args: bool = False,
|
|
312
|
+
**kwargs: Any,
|
|
313
|
+
) -> None:
|
|
314
|
+
self.name = name
|
|
315
|
+
if self.name is None:
|
|
316
|
+
if func:
|
|
317
|
+
try:
|
|
318
|
+
if func.__name__ != "<lambda>":
|
|
319
|
+
self.name = func.__name__
|
|
320
|
+
except AttributeError:
|
|
321
|
+
pass
|
|
322
|
+
elif afunc:
|
|
323
|
+
try:
|
|
324
|
+
self.name = afunc.__name__
|
|
325
|
+
except AttributeError:
|
|
326
|
+
pass
|
|
327
|
+
self.func = func
|
|
328
|
+
self.afunc = afunc
|
|
329
|
+
self.tags = tags
|
|
330
|
+
self.kwargs = kwargs
|
|
331
|
+
self.trace = trace
|
|
332
|
+
self.recurse = recurse
|
|
333
|
+
self.explode_args = explode_args
|
|
334
|
+
# check signature
|
|
335
|
+
if func is None and afunc is None:
|
|
336
|
+
raise ValueError("At least one of func or afunc must be provided.")
|
|
337
|
+
|
|
338
|
+
self.func_accepts: dict[str, tuple[str, Any]] = {}
|
|
339
|
+
params = inspect.signature(cast(Callable, func or afunc)).parameters
|
|
340
|
+
|
|
341
|
+
for kw, typ, runtime_key, default in KWARGS_CONFIG_KEYS:
|
|
342
|
+
p = params.get(kw)
|
|
343
|
+
|
|
344
|
+
if p is None or p.kind not in VALID_KINDS:
|
|
345
|
+
# If parameter is not found or is not a valid kind, skip
|
|
346
|
+
continue
|
|
347
|
+
|
|
348
|
+
if typ != (ANY_TYPE,) and p.annotation not in typ:
|
|
349
|
+
# A specific type is required, but the function annotation does
|
|
350
|
+
# not match the expected type.
|
|
351
|
+
|
|
352
|
+
# If this is a config parameter with incorrect typing, emit a warning
|
|
353
|
+
# because we used to support any type but are moving towards more correct typing
|
|
354
|
+
if kw == "config" and p.annotation != inspect.Parameter.empty:
|
|
355
|
+
warnings.warn(
|
|
356
|
+
f"The 'config' parameter should be typed as 'RunnableConfig' or "
|
|
357
|
+
f"'RunnableConfig | None', not '{p.annotation}'. ",
|
|
358
|
+
UserWarning,
|
|
359
|
+
stacklevel=4,
|
|
360
|
+
)
|
|
361
|
+
continue
|
|
362
|
+
|
|
363
|
+
# If the kwarg is accepted by the function, store the key / runtime attribute to inject
|
|
364
|
+
self.func_accepts[kw] = (runtime_key, default)
|
|
365
|
+
|
|
366
|
+
def __repr__(self) -> str:
|
|
367
|
+
repr_args = {
|
|
368
|
+
k: v
|
|
369
|
+
for k, v in self.__dict__.items()
|
|
370
|
+
if k not in {"name", "func", "afunc", "config", "kwargs", "trace"}
|
|
371
|
+
}
|
|
372
|
+
return f"{self.get_name()}({', '.join(f'{k}={v!r}' for k, v in repr_args.items())})"
|
|
373
|
+
|
|
374
|
+
def invoke(
|
|
375
|
+
self, input: Any, config: RunnableConfig | None = None, **kwargs: Any
|
|
376
|
+
) -> Any:
|
|
377
|
+
if self.func is None:
|
|
378
|
+
raise TypeError(
|
|
379
|
+
f'No synchronous function provided to "{self.name}".'
|
|
380
|
+
"\nEither initialize with a synchronous function or invoke"
|
|
381
|
+
" via the async API (ainvoke, astream, etc.)"
|
|
382
|
+
)
|
|
383
|
+
if config is None:
|
|
384
|
+
config = ensure_config()
|
|
385
|
+
if self.explode_args:
|
|
386
|
+
args, _kwargs = input
|
|
387
|
+
kwargs = {**self.kwargs, **_kwargs, **kwargs}
|
|
388
|
+
else:
|
|
389
|
+
args = (input,)
|
|
390
|
+
kwargs = {**self.kwargs, **kwargs}
|
|
391
|
+
|
|
392
|
+
runtime = config.get(CONF, {}).get(CONFIG_KEY_RUNTIME)
|
|
393
|
+
|
|
394
|
+
for kw, (runtime_key, default) in self.func_accepts.items():
|
|
395
|
+
# If the kwarg is already set, use the set value
|
|
396
|
+
if kw in kwargs:
|
|
397
|
+
continue
|
|
398
|
+
|
|
399
|
+
kw_value: Any = MISSING
|
|
400
|
+
if kw == "config":
|
|
401
|
+
kw_value = config
|
|
402
|
+
elif kw == "error":
|
|
403
|
+
kw_value = config.get(CONF, {}).get(CONFIG_KEY_NODE_ERROR, MISSING)
|
|
404
|
+
elif runtime:
|
|
405
|
+
if kw == "runtime":
|
|
406
|
+
kw_value = runtime
|
|
407
|
+
else:
|
|
408
|
+
try:
|
|
409
|
+
kw_value = getattr(runtime, runtime_key)
|
|
410
|
+
except AttributeError:
|
|
411
|
+
pass
|
|
412
|
+
|
|
413
|
+
if kw_value is MISSING:
|
|
414
|
+
if default is inspect.Parameter.empty:
|
|
415
|
+
raise ValueError(
|
|
416
|
+
f"Missing required config key '{runtime_key}' for '{self.name}'."
|
|
417
|
+
)
|
|
418
|
+
kw_value = default
|
|
419
|
+
kwargs[kw] = kw_value
|
|
420
|
+
|
|
421
|
+
if self.trace:
|
|
422
|
+
callback_manager = get_callback_manager_for_config(config, self.tags)
|
|
423
|
+
run_manager = callback_manager.on_chain_start(
|
|
424
|
+
None,
|
|
425
|
+
input,
|
|
426
|
+
name=config.get("run_name") or self.get_name(),
|
|
427
|
+
run_id=config.pop("run_id", None),
|
|
428
|
+
)
|
|
429
|
+
try:
|
|
430
|
+
child_config = patch_config(config, callbacks=run_manager.get_child())
|
|
431
|
+
# get the run
|
|
432
|
+
for h in run_manager.handlers:
|
|
433
|
+
if isinstance(h, CocoTracer):
|
|
434
|
+
run = h.run_map.get(str(run_manager.run_id))
|
|
435
|
+
break
|
|
436
|
+
else:
|
|
437
|
+
run = None
|
|
438
|
+
# run in context
|
|
439
|
+
with set_config_context(child_config, run) as context:
|
|
440
|
+
ret = context.run(self.func, *args, **kwargs)
|
|
441
|
+
except BaseException as e:
|
|
442
|
+
run_manager.on_chain_error(e)
|
|
443
|
+
raise
|
|
444
|
+
else:
|
|
445
|
+
run_manager.on_chain_end(ret)
|
|
446
|
+
else:
|
|
447
|
+
ret = self.func(*args, **kwargs)
|
|
448
|
+
if self.recurse and isinstance(ret, Runnable):
|
|
449
|
+
return ret.invoke(input, config)
|
|
450
|
+
return ret
|
|
451
|
+
|
|
452
|
+
async def ainvoke(
|
|
453
|
+
self, input: Any, config: RunnableConfig | None = None, **kwargs: Any
|
|
454
|
+
) -> Any:
|
|
455
|
+
if not self.afunc:
|
|
456
|
+
return self.invoke(input, config)
|
|
457
|
+
if config is None:
|
|
458
|
+
config = ensure_config()
|
|
459
|
+
if self.explode_args:
|
|
460
|
+
args, _kwargs = input
|
|
461
|
+
kwargs = {**self.kwargs, **_kwargs, **kwargs}
|
|
462
|
+
else:
|
|
463
|
+
args = (input,)
|
|
464
|
+
kwargs = {**self.kwargs, **kwargs}
|
|
465
|
+
|
|
466
|
+
runtime = config.get(CONF, {}).get(CONFIG_KEY_RUNTIME)
|
|
467
|
+
|
|
468
|
+
for kw, (runtime_key, default) in self.func_accepts.items():
|
|
469
|
+
# If the kwarg has already been set, use the set value
|
|
470
|
+
if kw in kwargs:
|
|
471
|
+
continue
|
|
472
|
+
|
|
473
|
+
kw_value: Any = MISSING
|
|
474
|
+
if kw == "config":
|
|
475
|
+
kw_value = config
|
|
476
|
+
elif kw == "error":
|
|
477
|
+
kw_value = config.get(CONF, {}).get(CONFIG_KEY_NODE_ERROR, MISSING)
|
|
478
|
+
elif runtime:
|
|
479
|
+
if kw == "runtime":
|
|
480
|
+
kw_value = runtime
|
|
481
|
+
else:
|
|
482
|
+
try:
|
|
483
|
+
kw_value = getattr(runtime, runtime_key)
|
|
484
|
+
except AttributeError:
|
|
485
|
+
pass
|
|
486
|
+
if kw_value is MISSING:
|
|
487
|
+
if default is inspect.Parameter.empty:
|
|
488
|
+
raise ValueError(
|
|
489
|
+
f"Missing required config key '{runtime_key}' for '{self.name}'."
|
|
490
|
+
)
|
|
491
|
+
kw_value = default
|
|
492
|
+
kwargs[kw] = kw_value
|
|
493
|
+
|
|
494
|
+
if self.trace:
|
|
495
|
+
callback_manager = get_async_callback_manager_for_config(config, self.tags)
|
|
496
|
+
run_manager = await callback_manager.on_chain_start(
|
|
497
|
+
None,
|
|
498
|
+
input,
|
|
499
|
+
name=config.get("run_name") or self.name,
|
|
500
|
+
run_id=config.pop("run_id", None),
|
|
501
|
+
)
|
|
502
|
+
try:
|
|
503
|
+
child_config = patch_config(config, callbacks=run_manager.get_child())
|
|
504
|
+
coro = cast(Coroutine[None, None, Any], self.afunc(*args, **kwargs))
|
|
505
|
+
if ASYNCIO_ACCEPTS_CONTEXT:
|
|
506
|
+
for h in run_manager.handlers:
|
|
507
|
+
if isinstance(h, CocoTracer):
|
|
508
|
+
run = h.run_map.get(str(run_manager.run_id))
|
|
509
|
+
break
|
|
510
|
+
else:
|
|
511
|
+
run = None
|
|
512
|
+
with set_config_context(child_config, run) as context:
|
|
513
|
+
ret = await asyncio.create_task(coro, context=context)
|
|
514
|
+
else:
|
|
515
|
+
ret = await coro
|
|
516
|
+
except BaseException as e:
|
|
517
|
+
await run_manager.on_chain_error(e)
|
|
518
|
+
raise
|
|
519
|
+
else:
|
|
520
|
+
await run_manager.on_chain_end(ret)
|
|
521
|
+
else:
|
|
522
|
+
ret = await self.afunc(*args, **kwargs)
|
|
523
|
+
if self.recurse and isinstance(ret, Runnable):
|
|
524
|
+
return await ret.ainvoke(input, config)
|
|
525
|
+
return ret
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def is_async_callable(
|
|
529
|
+
func: Any,
|
|
530
|
+
) -> TypeGuard[Callable[..., Awaitable]]:
|
|
531
|
+
"""Check if a function is async."""
|
|
532
|
+
return (
|
|
533
|
+
inspect.iscoroutinefunction(func)
|
|
534
|
+
or hasattr(func, "__call__")
|
|
535
|
+
and inspect.iscoroutinefunction(func.__call__)
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def is_async_generator(
|
|
540
|
+
func: Any,
|
|
541
|
+
) -> TypeGuard[Callable[..., AsyncIterator]]:
|
|
542
|
+
"""Check if a function is an async generator."""
|
|
543
|
+
return (
|
|
544
|
+
inspect.isasyncgenfunction(func)
|
|
545
|
+
or hasattr(func, "__call__")
|
|
546
|
+
and inspect.isasyncgenfunction(func.__call__)
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def coerce_to_runnable(
|
|
551
|
+
thing: RunnableLike, *, name: str | None, trace: bool
|
|
552
|
+
) -> Runnable:
|
|
553
|
+
"""Coerce a runnable-like object into a Runnable.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
thing: A runnable-like object.
|
|
557
|
+
|
|
558
|
+
Returns:
|
|
559
|
+
A Runnable.
|
|
560
|
+
"""
|
|
561
|
+
if isinstance(thing, Runnable):
|
|
562
|
+
return thing
|
|
563
|
+
elif is_async_generator(thing) or inspect.isgeneratorfunction(thing):
|
|
564
|
+
return RunnableLambda(thing, name=name)
|
|
565
|
+
elif callable(thing):
|
|
566
|
+
if is_async_callable(thing):
|
|
567
|
+
return RunnableCallable(None, thing, name=name, trace=trace)
|
|
568
|
+
else:
|
|
569
|
+
return RunnableCallable(
|
|
570
|
+
thing,
|
|
571
|
+
wraps(thing)(partial(run_in_executor, None, thing)), # type: ignore[arg-type]
|
|
572
|
+
name=name,
|
|
573
|
+
trace=trace,
|
|
574
|
+
)
|
|
575
|
+
elif isinstance(thing, dict):
|
|
576
|
+
return RunnableParallel(thing)
|
|
577
|
+
else:
|
|
578
|
+
raise TypeError(
|
|
579
|
+
f"Expected a Runnable, callable or dict."
|
|
580
|
+
f"Instead got an unsupported type: {type(thing)}"
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
class RunnableSeq(Runnable):
|
|
585
|
+
"""Sequence of `Runnable`, where the output of each is the input of the next.
|
|
586
|
+
|
|
587
|
+
`RunnableSeq` is a simpler version of `RunnableSequence` that is internal to
|
|
588
|
+
CocoGraph.
|
|
589
|
+
"""
|
|
590
|
+
|
|
591
|
+
def __init__(
|
|
592
|
+
self,
|
|
593
|
+
*steps: RunnableLike,
|
|
594
|
+
name: str | None = None,
|
|
595
|
+
trace_inputs: Callable[[Any], Any] | None = None,
|
|
596
|
+
trace_outputs: Callable[[Any], Any] | None = None,
|
|
597
|
+
) -> None:
|
|
598
|
+
"""Create a new RunnableSeq.
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
steps: The steps to include in the sequence.
|
|
602
|
+
name: The name of the `Runnable`.
|
|
603
|
+
|
|
604
|
+
Raises:
|
|
605
|
+
ValueError: If the sequence has less than 2 steps.
|
|
606
|
+
"""
|
|
607
|
+
steps_flat: list[Runnable] = []
|
|
608
|
+
for step in steps:
|
|
609
|
+
if isinstance(step, RunnableSequence):
|
|
610
|
+
steps_flat.extend(step.steps)
|
|
611
|
+
elif isinstance(step, RunnableSeq):
|
|
612
|
+
steps_flat.extend(step.steps)
|
|
613
|
+
else:
|
|
614
|
+
steps_flat.append(coerce_to_runnable(step, name=None, trace=True))
|
|
615
|
+
if len(steps_flat) < 2:
|
|
616
|
+
raise ValueError(
|
|
617
|
+
f"RunnableSeq must have at least 2 steps, got {len(steps_flat)}"
|
|
618
|
+
)
|
|
619
|
+
self.steps = steps_flat
|
|
620
|
+
self.name = name
|
|
621
|
+
self.trace_inputs = trace_inputs
|
|
622
|
+
self.trace_outputs = trace_outputs
|
|
623
|
+
|
|
624
|
+
def __or__(
|
|
625
|
+
self,
|
|
626
|
+
other: Any,
|
|
627
|
+
) -> Runnable:
|
|
628
|
+
if isinstance(other, RunnableSequence):
|
|
629
|
+
return RunnableSeq(
|
|
630
|
+
*self.steps,
|
|
631
|
+
other.first,
|
|
632
|
+
*other.middle,
|
|
633
|
+
other.last,
|
|
634
|
+
name=self.name or other.name,
|
|
635
|
+
)
|
|
636
|
+
elif isinstance(other, RunnableSeq):
|
|
637
|
+
return RunnableSeq(
|
|
638
|
+
*self.steps,
|
|
639
|
+
*other.steps,
|
|
640
|
+
name=self.name or other.name,
|
|
641
|
+
)
|
|
642
|
+
else:
|
|
643
|
+
return RunnableSeq(
|
|
644
|
+
*self.steps,
|
|
645
|
+
coerce_to_runnable(other, name=None, trace=True),
|
|
646
|
+
name=self.name,
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
def __ror__(
|
|
650
|
+
self,
|
|
651
|
+
other: Any,
|
|
652
|
+
) -> Runnable:
|
|
653
|
+
if isinstance(other, RunnableSequence):
|
|
654
|
+
return RunnableSequence(
|
|
655
|
+
other.first,
|
|
656
|
+
*other.middle,
|
|
657
|
+
other.last,
|
|
658
|
+
*self.steps,
|
|
659
|
+
name=other.name or self.name,
|
|
660
|
+
)
|
|
661
|
+
elif isinstance(other, RunnableSeq):
|
|
662
|
+
return RunnableSeq(
|
|
663
|
+
*other.steps,
|
|
664
|
+
*self.steps,
|
|
665
|
+
name=other.name or self.name,
|
|
666
|
+
)
|
|
667
|
+
else:
|
|
668
|
+
return RunnableSequence(
|
|
669
|
+
coerce_to_runnable(other, name=None, trace=True),
|
|
670
|
+
*self.steps,
|
|
671
|
+
name=self.name,
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
def invoke(
|
|
675
|
+
self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
|
|
676
|
+
) -> Any:
|
|
677
|
+
if config is None:
|
|
678
|
+
config = ensure_config()
|
|
679
|
+
# setup callbacks and context
|
|
680
|
+
callback_manager = get_callback_manager_for_config(config)
|
|
681
|
+
# start the root run
|
|
682
|
+
run_manager = callback_manager.on_chain_start(
|
|
683
|
+
None,
|
|
684
|
+
_trace_payload(input, self.trace_inputs),
|
|
685
|
+
name=config.get("run_name") or self.get_name(),
|
|
686
|
+
run_id=config.pop("run_id", None),
|
|
687
|
+
)
|
|
688
|
+
# invoke all steps in sequence
|
|
689
|
+
try:
|
|
690
|
+
for i, step in enumerate(self.steps):
|
|
691
|
+
# mark each step as a child run
|
|
692
|
+
config = patch_config(
|
|
693
|
+
config, callbacks=run_manager.get_child(f"seq:step:{i + 1}")
|
|
694
|
+
)
|
|
695
|
+
# 1st step is the actual node,
|
|
696
|
+
# others are writers which don't need to be run in context
|
|
697
|
+
if i == 0:
|
|
698
|
+
# get the run object
|
|
699
|
+
for h in run_manager.handlers:
|
|
700
|
+
if isinstance(h, CocoTracer):
|
|
701
|
+
run = h.run_map.get(str(run_manager.run_id))
|
|
702
|
+
break
|
|
703
|
+
else:
|
|
704
|
+
run = None
|
|
705
|
+
# run in context
|
|
706
|
+
with set_config_context(config, run) as context:
|
|
707
|
+
input = context.run(step.invoke, input, config, **kwargs)
|
|
708
|
+
else:
|
|
709
|
+
input = step.invoke(input, config)
|
|
710
|
+
# finish the root run
|
|
711
|
+
except BaseException as e:
|
|
712
|
+
run_manager.on_chain_error(e)
|
|
713
|
+
raise
|
|
714
|
+
else:
|
|
715
|
+
run_manager.on_chain_end(_trace_payload(input, self.trace_outputs))
|
|
716
|
+
return input
|
|
717
|
+
|
|
718
|
+
async def ainvoke(
|
|
719
|
+
self,
|
|
720
|
+
input: Input,
|
|
721
|
+
config: RunnableConfig | None = None,
|
|
722
|
+
**kwargs: Any | None,
|
|
723
|
+
) -> Any:
|
|
724
|
+
if config is None:
|
|
725
|
+
config = ensure_config()
|
|
726
|
+
# setup callbacks
|
|
727
|
+
callback_manager = get_async_callback_manager_for_config(config)
|
|
728
|
+
# start the root run
|
|
729
|
+
run_manager = await callback_manager.on_chain_start(
|
|
730
|
+
None,
|
|
731
|
+
_trace_payload(input, self.trace_inputs),
|
|
732
|
+
name=config.get("run_name") or self.get_name(),
|
|
733
|
+
run_id=config.pop("run_id", None),
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
# invoke all steps in sequence
|
|
737
|
+
try:
|
|
738
|
+
for i, step in enumerate(self.steps):
|
|
739
|
+
# mark each step as a child run
|
|
740
|
+
config = patch_config(
|
|
741
|
+
config, callbacks=run_manager.get_child(f"seq:step:{i + 1}")
|
|
742
|
+
)
|
|
743
|
+
# 1st step is the actual node,
|
|
744
|
+
# others are writers which don't need to be run in context
|
|
745
|
+
if i == 0:
|
|
746
|
+
if ASYNCIO_ACCEPTS_CONTEXT:
|
|
747
|
+
# get the run object
|
|
748
|
+
for h in run_manager.handlers:
|
|
749
|
+
if isinstance(h, CocoTracer):
|
|
750
|
+
run = h.run_map.get(str(run_manager.run_id))
|
|
751
|
+
break
|
|
752
|
+
else:
|
|
753
|
+
run = None
|
|
754
|
+
# run in context
|
|
755
|
+
with set_config_context(config, run) as context:
|
|
756
|
+
input = await asyncio.create_task(
|
|
757
|
+
step.ainvoke(input, config, **kwargs), context=context
|
|
758
|
+
)
|
|
759
|
+
else:
|
|
760
|
+
input = await step.ainvoke(input, config, **kwargs)
|
|
761
|
+
else:
|
|
762
|
+
input = await step.ainvoke(input, config)
|
|
763
|
+
# finish the root run
|
|
764
|
+
except BaseException as e:
|
|
765
|
+
await run_manager.on_chain_error(e)
|
|
766
|
+
raise
|
|
767
|
+
else:
|
|
768
|
+
await run_manager.on_chain_end(_trace_payload(input, self.trace_outputs))
|
|
769
|
+
return input
|
|
770
|
+
|
|
771
|
+
def stream(
|
|
772
|
+
self,
|
|
773
|
+
input: Input,
|
|
774
|
+
config: RunnableConfig | None = None,
|
|
775
|
+
**kwargs: Any | None,
|
|
776
|
+
) -> Iterator[Any]:
|
|
777
|
+
if config is None:
|
|
778
|
+
config = ensure_config()
|
|
779
|
+
# setup callbacks
|
|
780
|
+
callback_manager = get_callback_manager_for_config(config)
|
|
781
|
+
# start the root run
|
|
782
|
+
run_manager = callback_manager.on_chain_start(
|
|
783
|
+
None,
|
|
784
|
+
_trace_payload(input, self.trace_inputs),
|
|
785
|
+
name=config.get("run_name") or self.get_name(),
|
|
786
|
+
run_id=config.pop("run_id", None),
|
|
787
|
+
)
|
|
788
|
+
# get the run object
|
|
789
|
+
for h in run_manager.handlers:
|
|
790
|
+
if isinstance(h, CocoTracer):
|
|
791
|
+
run = h.run_map.get(str(run_manager.run_id))
|
|
792
|
+
break
|
|
793
|
+
else:
|
|
794
|
+
run = None
|
|
795
|
+
# create first step config
|
|
796
|
+
config = patch_config(
|
|
797
|
+
config,
|
|
798
|
+
callbacks=run_manager.get_child(f"seq:step:{1}"),
|
|
799
|
+
)
|
|
800
|
+
# run all in context
|
|
801
|
+
with set_config_context(config, run) as context:
|
|
802
|
+
try:
|
|
803
|
+
# stream the last steps
|
|
804
|
+
# transform the input stream of each step with the next
|
|
805
|
+
# steps that don't natively support transforming an input stream will
|
|
806
|
+
# buffer input in memory until all available, and then start emitting output
|
|
807
|
+
for idx, step in enumerate(self.steps):
|
|
808
|
+
if idx == 0:
|
|
809
|
+
iterator = step.stream(input, config, **kwargs)
|
|
810
|
+
else:
|
|
811
|
+
config = patch_config(
|
|
812
|
+
config,
|
|
813
|
+
callbacks=run_manager.get_child(f"seq:step:{idx + 1}"),
|
|
814
|
+
)
|
|
815
|
+
iterator = step.transform(iterator, config)
|
|
816
|
+
# populates streamed_output in astream_log() output if needed
|
|
817
|
+
if _StreamingCallbackHandler is not None:
|
|
818
|
+
for h in run_manager.handlers:
|
|
819
|
+
if isinstance(h, _StreamingCallbackHandler):
|
|
820
|
+
iterator = h.tap_output_iter(run_manager.run_id, iterator)
|
|
821
|
+
# consume into final output
|
|
822
|
+
output = context.run(_consume_iter, iterator)
|
|
823
|
+
# sequence doesn't emit output, yield to mark as generator
|
|
824
|
+
yield
|
|
825
|
+
except BaseException as e:
|
|
826
|
+
run_manager.on_chain_error(e)
|
|
827
|
+
raise
|
|
828
|
+
else:
|
|
829
|
+
run_manager.on_chain_end(_trace_payload(output, self.trace_outputs))
|
|
830
|
+
|
|
831
|
+
async def astream(
|
|
832
|
+
self,
|
|
833
|
+
input: Input,
|
|
834
|
+
config: RunnableConfig | None = None,
|
|
835
|
+
**kwargs: Any | None,
|
|
836
|
+
) -> AsyncIterator[Any]:
|
|
837
|
+
if config is None:
|
|
838
|
+
config = ensure_config()
|
|
839
|
+
# setup callbacks
|
|
840
|
+
callback_manager = get_async_callback_manager_for_config(config)
|
|
841
|
+
# start the root run
|
|
842
|
+
run_manager = await callback_manager.on_chain_start(
|
|
843
|
+
None,
|
|
844
|
+
_trace_payload(input, self.trace_inputs),
|
|
845
|
+
name=config.get("run_name") or self.get_name(),
|
|
846
|
+
run_id=config.pop("run_id", None),
|
|
847
|
+
)
|
|
848
|
+
# stream the last steps
|
|
849
|
+
# transform the input stream of each step with the next
|
|
850
|
+
# steps that don't natively support transforming an input stream will
|
|
851
|
+
# buffer input in memory until all available, and then start emitting output
|
|
852
|
+
if ASYNCIO_ACCEPTS_CONTEXT:
|
|
853
|
+
# get the run object
|
|
854
|
+
for h in run_manager.handlers:
|
|
855
|
+
if isinstance(h, CocoTracer):
|
|
856
|
+
run = h.run_map.get(str(run_manager.run_id))
|
|
857
|
+
break
|
|
858
|
+
else:
|
|
859
|
+
run = None
|
|
860
|
+
# create first step config
|
|
861
|
+
config = patch_config(
|
|
862
|
+
config,
|
|
863
|
+
callbacks=run_manager.get_child(f"seq:step:{1}"),
|
|
864
|
+
)
|
|
865
|
+
# run all in context
|
|
866
|
+
with set_config_context(config, run) as context:
|
|
867
|
+
try:
|
|
868
|
+
async with AsyncExitStack() as stack:
|
|
869
|
+
for idx, step in enumerate(self.steps):
|
|
870
|
+
if idx == 0:
|
|
871
|
+
aiterator = step.astream(input, config, **kwargs)
|
|
872
|
+
else:
|
|
873
|
+
config = patch_config(
|
|
874
|
+
config,
|
|
875
|
+
callbacks=run_manager.get_child(
|
|
876
|
+
f"seq:step:{idx + 1}"
|
|
877
|
+
),
|
|
878
|
+
)
|
|
879
|
+
aiterator = step.atransform(aiterator, config)
|
|
880
|
+
if hasattr(aiterator, "aclose"):
|
|
881
|
+
stack.push_async_callback(aiterator.aclose)
|
|
882
|
+
# populates streamed_output in astream_log() output if needed
|
|
883
|
+
if _StreamingCallbackHandler is not None:
|
|
884
|
+
for h in run_manager.handlers:
|
|
885
|
+
if isinstance(h, _StreamingCallbackHandler):
|
|
886
|
+
aiterator = h.tap_output_aiter(
|
|
887
|
+
run_manager.run_id, aiterator
|
|
888
|
+
)
|
|
889
|
+
# consume into final output
|
|
890
|
+
output = await asyncio.create_task(
|
|
891
|
+
_consume_aiter(aiterator), context=context
|
|
892
|
+
)
|
|
893
|
+
# sequence doesn't emit output, yield to mark as generator
|
|
894
|
+
yield
|
|
895
|
+
except BaseException as e:
|
|
896
|
+
await run_manager.on_chain_error(e)
|
|
897
|
+
raise
|
|
898
|
+
else:
|
|
899
|
+
await run_manager.on_chain_end(
|
|
900
|
+
_trace_payload(output, self.trace_outputs)
|
|
901
|
+
)
|
|
902
|
+
else:
|
|
903
|
+
try:
|
|
904
|
+
async with AsyncExitStack() as stack:
|
|
905
|
+
for idx, step in enumerate(self.steps):
|
|
906
|
+
config = patch_config(
|
|
907
|
+
config,
|
|
908
|
+
callbacks=run_manager.get_child(f"seq:step:{idx + 1}"),
|
|
909
|
+
)
|
|
910
|
+
if idx == 0:
|
|
911
|
+
aiterator = step.astream(input, config, **kwargs)
|
|
912
|
+
else:
|
|
913
|
+
aiterator = step.atransform(aiterator, config)
|
|
914
|
+
if hasattr(aiterator, "aclose"):
|
|
915
|
+
stack.push_async_callback(aiterator.aclose)
|
|
916
|
+
# populates streamed_output in astream_log() output if needed
|
|
917
|
+
if _StreamingCallbackHandler is not None:
|
|
918
|
+
for h in run_manager.handlers:
|
|
919
|
+
if isinstance(h, _StreamingCallbackHandler):
|
|
920
|
+
aiterator = h.tap_output_aiter(
|
|
921
|
+
run_manager.run_id, aiterator
|
|
922
|
+
)
|
|
923
|
+
# consume into final output
|
|
924
|
+
output = await _consume_aiter(aiterator)
|
|
925
|
+
# sequence doesn't emit output, yield to mark as generator
|
|
926
|
+
yield
|
|
927
|
+
except BaseException as e:
|
|
928
|
+
await run_manager.on_chain_error(e)
|
|
929
|
+
raise
|
|
930
|
+
else:
|
|
931
|
+
await run_manager.on_chain_end(
|
|
932
|
+
_trace_payload(output, self.trace_outputs)
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
def _consume_iter(it: Iterator[Any]) -> Any:
|
|
937
|
+
"""Consume an iterator."""
|
|
938
|
+
output: Any = None
|
|
939
|
+
add_supported = False
|
|
940
|
+
for chunk in it:
|
|
941
|
+
# collect final output
|
|
942
|
+
if output is None:
|
|
943
|
+
output = chunk
|
|
944
|
+
elif add_supported:
|
|
945
|
+
try:
|
|
946
|
+
output = output + chunk
|
|
947
|
+
except TypeError:
|
|
948
|
+
output = chunk
|
|
949
|
+
add_supported = False
|
|
950
|
+
else:
|
|
951
|
+
output = chunk
|
|
952
|
+
return output
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
async def _consume_aiter(it: AsyncIterator[Any]) -> Any:
|
|
956
|
+
"""Consume an async iterator."""
|
|
957
|
+
output: Any = None
|
|
958
|
+
add_supported = False
|
|
959
|
+
async for chunk in it:
|
|
960
|
+
# collect final output
|
|
961
|
+
if add_supported:
|
|
962
|
+
try:
|
|
963
|
+
output = output + chunk
|
|
964
|
+
except TypeError:
|
|
965
|
+
output = chunk
|
|
966
|
+
add_supported = False
|
|
967
|
+
else:
|
|
968
|
+
output = chunk
|
|
969
|
+
return output
|