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,379 @@
|
|
|
1
|
+
"""`Runnable` that retries a `Runnable` if it fails."""
|
|
2
|
+
|
|
3
|
+
from typing import (
|
|
4
|
+
TYPE_CHECKING,
|
|
5
|
+
Any,
|
|
6
|
+
TypeVar,
|
|
7
|
+
cast,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from tenacity import (
|
|
11
|
+
AsyncRetrying,
|
|
12
|
+
RetryCallState,
|
|
13
|
+
RetryError,
|
|
14
|
+
Retrying,
|
|
15
|
+
retry_if_exception_type,
|
|
16
|
+
stop_after_attempt,
|
|
17
|
+
wait_exponential_jitter,
|
|
18
|
+
)
|
|
19
|
+
from typing_extensions import TypedDict, override
|
|
20
|
+
|
|
21
|
+
from coco_core.runnables.base import RunnableBindingBase
|
|
22
|
+
from coco_core.runnables.config import RunnableConfig, patch_config
|
|
23
|
+
from coco_core.runnables.utils import Input, Output
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from coco_core.callbacks.manager import (
|
|
27
|
+
AsyncCallbackManagerForChainRun,
|
|
28
|
+
CallbackManagerForChainRun,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
T = TypeVar("T", CallbackManagerForChainRun, AsyncCallbackManagerForChainRun)
|
|
32
|
+
U = TypeVar("U")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ExponentialJitterParams(TypedDict, total=False):
|
|
36
|
+
"""Parameters for `tenacity.wait_exponential_jitter`."""
|
|
37
|
+
|
|
38
|
+
initial: float
|
|
39
|
+
"""Initial wait."""
|
|
40
|
+
max: float
|
|
41
|
+
"""Maximum wait."""
|
|
42
|
+
exp_base: float
|
|
43
|
+
"""Base for exponential backoff."""
|
|
44
|
+
jitter: float
|
|
45
|
+
"""Random additional wait sampled from random.uniform(0, jitter)."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class RunnableRetry(RunnableBindingBase[Input, Output]): # type: ignore[no-redef]
|
|
49
|
+
"""Retry a Runnable if it fails.
|
|
50
|
+
|
|
51
|
+
RunnableRetry can be used to add retry logic to any object
|
|
52
|
+
that subclasses the base Runnable.
|
|
53
|
+
|
|
54
|
+
Such retries are especially useful for network calls that may fail
|
|
55
|
+
due to transient errors.
|
|
56
|
+
|
|
57
|
+
The RunnableRetry is implemented as a RunnableBinding. The easiest
|
|
58
|
+
way to use it is through the `.with_retry()` method on all Runnables.
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
Here's an example that uses a RunnableLambda to raise an exception
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import time
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def foo(input) -> None:
|
|
68
|
+
'''Fake function that raises an exception.'''
|
|
69
|
+
raise ValueError(f"Invoking foo failed. At time {time.time()}")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
runnable = RunnableLambda(foo)
|
|
73
|
+
|
|
74
|
+
runnable_with_retries = runnable.with_retry(
|
|
75
|
+
retry_if_exception_type=(ValueError,), # Retry only on ValueError
|
|
76
|
+
wait_exponential_jitter=True, # Add jitter to the exponential backoff
|
|
77
|
+
stop_after_attempt=2, # Try twice
|
|
78
|
+
exponential_jitter_params={"initial": 2}, # if desired, customize backoff
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# The method invocation above is equivalent to the longer form below:
|
|
82
|
+
|
|
83
|
+
runnable_with_retries = RunnableRetry(
|
|
84
|
+
bound=runnable,
|
|
85
|
+
retry_exception_types=(ValueError,),
|
|
86
|
+
max_attempt_number=2,
|
|
87
|
+
wait_exponential_jitter=True,
|
|
88
|
+
exponential_jitter_params={"initial": 2},
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This logic can be used to retry any Runnable, including a chain of Runnables,
|
|
93
|
+
but in general it's best practice to keep the scope of the retry as small as
|
|
94
|
+
possible. For example, if you have a chain of Runnables, you should only retry
|
|
95
|
+
the Runnable that is likely to fail, not the entire chain.
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
```python
|
|
99
|
+
from coco_openai import ChatOpenAI
|
|
100
|
+
from coco_core.prompts import PromptTemplate
|
|
101
|
+
|
|
102
|
+
template = PromptTemplate.from_template("tell me a joke about {topic}.")
|
|
103
|
+
model = ChatOpenAI(temperature=0.5)
|
|
104
|
+
|
|
105
|
+
# Good
|
|
106
|
+
chain = template | model.with_retry()
|
|
107
|
+
|
|
108
|
+
# Bad
|
|
109
|
+
chain = template | model
|
|
110
|
+
retryable_chain = chain.with_retry()
|
|
111
|
+
```
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
retry_exception_types: tuple[type[BaseException], ...] = (Exception,)
|
|
115
|
+
"""The exception types to retry on. By default all exceptions are retried.
|
|
116
|
+
|
|
117
|
+
In general you should only retry on exceptions that are likely to be
|
|
118
|
+
transient, such as network errors.
|
|
119
|
+
|
|
120
|
+
Good exceptions to retry are all server errors (5xx) and selected client
|
|
121
|
+
errors (4xx) such as 429 Too Many Requests.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
wait_exponential_jitter: bool = True
|
|
125
|
+
"""Whether to add jitter to the exponential backoff."""
|
|
126
|
+
|
|
127
|
+
exponential_jitter_params: ExponentialJitterParams | None = None
|
|
128
|
+
"""Parameters for `tenacity.wait_exponential_jitter`. Namely: `initial`,
|
|
129
|
+
`max`, `exp_base`, and `jitter` (all `float` values).
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
max_attempt_number: int = 3
|
|
133
|
+
"""The maximum number of attempts to retry the Runnable."""
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def _kwargs_retrying(self) -> dict[str, Any]:
|
|
137
|
+
kwargs: dict[str, Any] = {}
|
|
138
|
+
|
|
139
|
+
if self.max_attempt_number:
|
|
140
|
+
kwargs["stop"] = stop_after_attempt(self.max_attempt_number)
|
|
141
|
+
|
|
142
|
+
if self.wait_exponential_jitter:
|
|
143
|
+
kwargs["wait"] = wait_exponential_jitter(
|
|
144
|
+
**(self.exponential_jitter_params or {})
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if self.retry_exception_types:
|
|
148
|
+
kwargs["retry"] = retry_if_exception_type(self.retry_exception_types)
|
|
149
|
+
|
|
150
|
+
return kwargs
|
|
151
|
+
|
|
152
|
+
def _sync_retrying(self, **kwargs: Any) -> Retrying:
|
|
153
|
+
return Retrying(**self._kwargs_retrying, **kwargs)
|
|
154
|
+
|
|
155
|
+
def _async_retrying(self, **kwargs: Any) -> AsyncRetrying:
|
|
156
|
+
return AsyncRetrying(**self._kwargs_retrying, **kwargs)
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def _patch_config(
|
|
160
|
+
config: RunnableConfig,
|
|
161
|
+
run_manager: "T",
|
|
162
|
+
retry_state: RetryCallState,
|
|
163
|
+
) -> RunnableConfig:
|
|
164
|
+
attempt = retry_state.attempt_number
|
|
165
|
+
tag = f"retry:attempt:{attempt}" if attempt > 1 else None
|
|
166
|
+
return patch_config(config, callbacks=run_manager.get_child(tag))
|
|
167
|
+
|
|
168
|
+
def _patch_config_list(
|
|
169
|
+
self,
|
|
170
|
+
config: list[RunnableConfig],
|
|
171
|
+
run_manager: list["T"],
|
|
172
|
+
retry_state: RetryCallState,
|
|
173
|
+
) -> list[RunnableConfig]:
|
|
174
|
+
return [
|
|
175
|
+
self._patch_config(c, rm, retry_state)
|
|
176
|
+
for c, rm in zip(config, run_manager, strict=False)
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
def _invoke(
|
|
180
|
+
self,
|
|
181
|
+
input_: Input,
|
|
182
|
+
run_manager: "CallbackManagerForChainRun",
|
|
183
|
+
config: RunnableConfig,
|
|
184
|
+
**kwargs: Any,
|
|
185
|
+
) -> Output:
|
|
186
|
+
for attempt in self._sync_retrying(reraise=True):
|
|
187
|
+
with attempt:
|
|
188
|
+
result = super().invoke(
|
|
189
|
+
input_,
|
|
190
|
+
self._patch_config(config, run_manager, attempt.retry_state),
|
|
191
|
+
**kwargs,
|
|
192
|
+
)
|
|
193
|
+
if attempt.retry_state.outcome and not attempt.retry_state.outcome.failed:
|
|
194
|
+
attempt.retry_state.set_result(result)
|
|
195
|
+
return result
|
|
196
|
+
|
|
197
|
+
@override
|
|
198
|
+
def invoke(
|
|
199
|
+
self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
|
|
200
|
+
) -> Output:
|
|
201
|
+
return self._call_with_config(self._invoke, input, config, **kwargs)
|
|
202
|
+
|
|
203
|
+
async def _ainvoke(
|
|
204
|
+
self,
|
|
205
|
+
input_: Input,
|
|
206
|
+
run_manager: "AsyncCallbackManagerForChainRun",
|
|
207
|
+
config: RunnableConfig,
|
|
208
|
+
**kwargs: Any,
|
|
209
|
+
) -> Output:
|
|
210
|
+
async for attempt in self._async_retrying(reraise=True):
|
|
211
|
+
with attempt:
|
|
212
|
+
result = await super().ainvoke(
|
|
213
|
+
input_,
|
|
214
|
+
self._patch_config(config, run_manager, attempt.retry_state),
|
|
215
|
+
**kwargs,
|
|
216
|
+
)
|
|
217
|
+
if attempt.retry_state.outcome and not attempt.retry_state.outcome.failed:
|
|
218
|
+
attempt.retry_state.set_result(result)
|
|
219
|
+
return result
|
|
220
|
+
|
|
221
|
+
@override
|
|
222
|
+
async def ainvoke(
|
|
223
|
+
self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
|
|
224
|
+
) -> Output:
|
|
225
|
+
return await self._acall_with_config(self._ainvoke, input, config, **kwargs)
|
|
226
|
+
|
|
227
|
+
def _batch(
|
|
228
|
+
self,
|
|
229
|
+
inputs: list[Input],
|
|
230
|
+
run_manager: list["CallbackManagerForChainRun"],
|
|
231
|
+
config: list[RunnableConfig],
|
|
232
|
+
**kwargs: Any,
|
|
233
|
+
) -> list[Output | Exception]:
|
|
234
|
+
results_map: dict[int, Output] = {}
|
|
235
|
+
|
|
236
|
+
not_set: list[Output] = []
|
|
237
|
+
result = not_set
|
|
238
|
+
try:
|
|
239
|
+
for attempt in self._sync_retrying():
|
|
240
|
+
with attempt:
|
|
241
|
+
# Retry for inputs that have not yet succeeded
|
|
242
|
+
# Determine which original indices remain.
|
|
243
|
+
remaining_indices = [
|
|
244
|
+
i for i in range(len(inputs)) if i not in results_map
|
|
245
|
+
]
|
|
246
|
+
if not remaining_indices:
|
|
247
|
+
break
|
|
248
|
+
pending_inputs = [inputs[i] for i in remaining_indices]
|
|
249
|
+
pending_configs = [config[i] for i in remaining_indices]
|
|
250
|
+
pending_run_managers = [run_manager[i] for i in remaining_indices]
|
|
251
|
+
# Invoke underlying batch only on remaining elements.
|
|
252
|
+
result = super().batch(
|
|
253
|
+
pending_inputs,
|
|
254
|
+
self._patch_config_list(
|
|
255
|
+
pending_configs, pending_run_managers, attempt.retry_state
|
|
256
|
+
),
|
|
257
|
+
return_exceptions=True,
|
|
258
|
+
**kwargs,
|
|
259
|
+
)
|
|
260
|
+
# Register the results of the inputs that have succeeded, mapping
|
|
261
|
+
# back to their original indices.
|
|
262
|
+
first_exception = None
|
|
263
|
+
for offset, r in enumerate(result):
|
|
264
|
+
if isinstance(r, Exception):
|
|
265
|
+
if not first_exception:
|
|
266
|
+
first_exception = r
|
|
267
|
+
continue
|
|
268
|
+
orig_idx = remaining_indices[offset]
|
|
269
|
+
results_map[orig_idx] = r
|
|
270
|
+
# If any exception occurred, raise it, to retry the failed ones
|
|
271
|
+
if first_exception:
|
|
272
|
+
raise first_exception
|
|
273
|
+
if (
|
|
274
|
+
attempt.retry_state.outcome
|
|
275
|
+
and not attempt.retry_state.outcome.failed
|
|
276
|
+
):
|
|
277
|
+
attempt.retry_state.set_result(result)
|
|
278
|
+
except RetryError as e:
|
|
279
|
+
if result is not_set:
|
|
280
|
+
result = cast("list[Output]", [e] * len(inputs))
|
|
281
|
+
|
|
282
|
+
outputs: list[Output | Exception] = []
|
|
283
|
+
for idx in range(len(inputs)):
|
|
284
|
+
if idx in results_map:
|
|
285
|
+
outputs.append(results_map[idx])
|
|
286
|
+
else:
|
|
287
|
+
outputs.append(result.pop(0))
|
|
288
|
+
return outputs
|
|
289
|
+
|
|
290
|
+
@override
|
|
291
|
+
def batch(
|
|
292
|
+
self,
|
|
293
|
+
inputs: list[Input],
|
|
294
|
+
config: RunnableConfig | list[RunnableConfig] | None = None,
|
|
295
|
+
*,
|
|
296
|
+
return_exceptions: bool = False,
|
|
297
|
+
**kwargs: Any,
|
|
298
|
+
) -> list[Output]:
|
|
299
|
+
return self._batch_with_config(
|
|
300
|
+
self._batch, inputs, config, return_exceptions=return_exceptions, **kwargs
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
async def _abatch(
|
|
304
|
+
self,
|
|
305
|
+
inputs: list[Input],
|
|
306
|
+
run_manager: list["AsyncCallbackManagerForChainRun"],
|
|
307
|
+
config: list[RunnableConfig],
|
|
308
|
+
**kwargs: Any,
|
|
309
|
+
) -> list[Output | Exception]:
|
|
310
|
+
results_map: dict[int, Output] = {}
|
|
311
|
+
|
|
312
|
+
not_set: list[Output] = []
|
|
313
|
+
result = not_set
|
|
314
|
+
try:
|
|
315
|
+
async for attempt in self._async_retrying():
|
|
316
|
+
with attempt:
|
|
317
|
+
# Retry for inputs that have not yet succeeded
|
|
318
|
+
# Determine which original indices remain.
|
|
319
|
+
remaining_indices = [
|
|
320
|
+
i for i in range(len(inputs)) if i not in results_map
|
|
321
|
+
]
|
|
322
|
+
if not remaining_indices:
|
|
323
|
+
break
|
|
324
|
+
pending_inputs = [inputs[i] for i in remaining_indices]
|
|
325
|
+
pending_configs = [config[i] for i in remaining_indices]
|
|
326
|
+
pending_run_managers = [run_manager[i] for i in remaining_indices]
|
|
327
|
+
result = await super().abatch(
|
|
328
|
+
pending_inputs,
|
|
329
|
+
self._patch_config_list(
|
|
330
|
+
pending_configs, pending_run_managers, attempt.retry_state
|
|
331
|
+
),
|
|
332
|
+
return_exceptions=True,
|
|
333
|
+
**kwargs,
|
|
334
|
+
)
|
|
335
|
+
# Register the results of the inputs that have succeeded, mapping
|
|
336
|
+
# back to their original indices.
|
|
337
|
+
first_exception = None
|
|
338
|
+
for offset, r in enumerate(result):
|
|
339
|
+
if isinstance(r, Exception):
|
|
340
|
+
if not first_exception:
|
|
341
|
+
first_exception = r
|
|
342
|
+
continue
|
|
343
|
+
orig_idx = remaining_indices[offset]
|
|
344
|
+
results_map[orig_idx] = r
|
|
345
|
+
# If any exception occurred, raise it, to retry the failed ones
|
|
346
|
+
if first_exception:
|
|
347
|
+
raise first_exception
|
|
348
|
+
if (
|
|
349
|
+
attempt.retry_state.outcome
|
|
350
|
+
and not attempt.retry_state.outcome.failed
|
|
351
|
+
):
|
|
352
|
+
attempt.retry_state.set_result(result)
|
|
353
|
+
except RetryError as e:
|
|
354
|
+
if result is not_set:
|
|
355
|
+
result = cast("list[Output]", [e] * len(inputs))
|
|
356
|
+
|
|
357
|
+
outputs: list[Output | Exception] = []
|
|
358
|
+
for idx in range(len(inputs)):
|
|
359
|
+
if idx in results_map:
|
|
360
|
+
outputs.append(results_map[idx])
|
|
361
|
+
else:
|
|
362
|
+
outputs.append(result.pop(0))
|
|
363
|
+
return outputs
|
|
364
|
+
|
|
365
|
+
@override
|
|
366
|
+
async def abatch(
|
|
367
|
+
self,
|
|
368
|
+
inputs: list[Input],
|
|
369
|
+
config: RunnableConfig | list[RunnableConfig] | None = None,
|
|
370
|
+
*,
|
|
371
|
+
return_exceptions: bool = False,
|
|
372
|
+
**kwargs: Any,
|
|
373
|
+
) -> list[Output]:
|
|
374
|
+
return await self._abatch_with_config(
|
|
375
|
+
self._abatch, inputs, config, return_exceptions=return_exceptions, **kwargs
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
# stream() and transform() are not retried because retrying a stream
|
|
379
|
+
# is not very intuitive.
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""`Runnable` that routes to a set of `Runnable` objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import (
|
|
7
|
+
TYPE_CHECKING,
|
|
8
|
+
Any,
|
|
9
|
+
cast,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from pydantic import ConfigDict
|
|
13
|
+
from typing_extensions import TypedDict, override
|
|
14
|
+
|
|
15
|
+
from coco_core.runnables.base import (
|
|
16
|
+
Runnable,
|
|
17
|
+
RunnableSerializable,
|
|
18
|
+
coerce_to_runnable,
|
|
19
|
+
)
|
|
20
|
+
from coco_core.runnables.config import (
|
|
21
|
+
RunnableConfig,
|
|
22
|
+
get_config_list,
|
|
23
|
+
get_executor_for_config,
|
|
24
|
+
)
|
|
25
|
+
from coco_core.runnables.utils import (
|
|
26
|
+
ConfigurableFieldSpec,
|
|
27
|
+
Input,
|
|
28
|
+
Output,
|
|
29
|
+
gather_with_concurrency,
|
|
30
|
+
get_unique_config_specs,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from collections.abc import AsyncIterator, Callable, Iterator
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class RouterInput(TypedDict):
|
|
38
|
+
"""Router input."""
|
|
39
|
+
|
|
40
|
+
key: str
|
|
41
|
+
"""The key to route on."""
|
|
42
|
+
input: Any
|
|
43
|
+
"""The input to pass to the selected `Runnable`."""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RouterRunnable(RunnableSerializable[RouterInput, Output]):
|
|
47
|
+
"""`Runnable` that routes to a set of `Runnable` based on `Input['key']`.
|
|
48
|
+
|
|
49
|
+
Returns the output of the selected Runnable.
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
```python
|
|
53
|
+
from coco_core.runnables.router import RouterRunnable
|
|
54
|
+
from coco_core.runnables import RunnableLambda
|
|
55
|
+
|
|
56
|
+
add = RunnableLambda(func=lambda x: x + 1)
|
|
57
|
+
square = RunnableLambda(func=lambda x: x**2)
|
|
58
|
+
|
|
59
|
+
router = RouterRunnable(runnables={"add": add, "square": square})
|
|
60
|
+
router.invoke({"key": "square", "input": 3})
|
|
61
|
+
```
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
runnables: Mapping[str, Runnable[Any, Output]]
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
@override
|
|
68
|
+
def config_specs(self) -> list[ConfigurableFieldSpec]:
|
|
69
|
+
return get_unique_config_specs(
|
|
70
|
+
spec for step in self.runnables.values() for spec in step.config_specs
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
runnables: Mapping[str, Runnable[Any, Output] | Callable[[Any], Output]],
|
|
76
|
+
) -> None:
|
|
77
|
+
"""Create a `RouterRunnable`.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
runnables: A mapping of keys to `Runnable` objects.
|
|
81
|
+
"""
|
|
82
|
+
super().__init__(
|
|
83
|
+
runnables={key: coerce_to_runnable(r) for key, r in runnables.items()}
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
model_config = ConfigDict(
|
|
87
|
+
arbitrary_types_allowed=True,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
@override
|
|
92
|
+
def is_lc_serializable(cls) -> bool:
|
|
93
|
+
"""Return `True` as this class is serializable."""
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
@override
|
|
98
|
+
def get_lc_namespace(cls) -> list[str]:
|
|
99
|
+
"""Get the namespace of the Coco object.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
`["coco", "schema", "runnable"]`
|
|
103
|
+
"""
|
|
104
|
+
return ["coco", "schema", "runnable"]
|
|
105
|
+
|
|
106
|
+
@override
|
|
107
|
+
def invoke(
|
|
108
|
+
self, input: RouterInput, config: RunnableConfig | None = None, **kwargs: Any
|
|
109
|
+
) -> Output:
|
|
110
|
+
key = input["key"]
|
|
111
|
+
actual_input = input["input"]
|
|
112
|
+
if key not in self.runnables:
|
|
113
|
+
msg = f"No runnable associated with key '{key}'"
|
|
114
|
+
raise ValueError(msg)
|
|
115
|
+
|
|
116
|
+
runnable = self.runnables[key]
|
|
117
|
+
return runnable.invoke(actual_input, config)
|
|
118
|
+
|
|
119
|
+
@override
|
|
120
|
+
async def ainvoke(
|
|
121
|
+
self,
|
|
122
|
+
input: RouterInput,
|
|
123
|
+
config: RunnableConfig | None = None,
|
|
124
|
+
**kwargs: Any | None,
|
|
125
|
+
) -> Output:
|
|
126
|
+
key = input["key"]
|
|
127
|
+
actual_input = input["input"]
|
|
128
|
+
if key not in self.runnables:
|
|
129
|
+
msg = f"No runnable associated with key '{key}'"
|
|
130
|
+
raise ValueError(msg)
|
|
131
|
+
|
|
132
|
+
runnable = self.runnables[key]
|
|
133
|
+
return await runnable.ainvoke(actual_input, config)
|
|
134
|
+
|
|
135
|
+
@override
|
|
136
|
+
def batch(
|
|
137
|
+
self,
|
|
138
|
+
inputs: list[RouterInput],
|
|
139
|
+
config: RunnableConfig | list[RunnableConfig] | None = None,
|
|
140
|
+
*,
|
|
141
|
+
return_exceptions: bool = False,
|
|
142
|
+
**kwargs: Any | None,
|
|
143
|
+
) -> list[Output]:
|
|
144
|
+
if not inputs:
|
|
145
|
+
return []
|
|
146
|
+
|
|
147
|
+
keys = [input_["key"] for input_ in inputs]
|
|
148
|
+
actual_inputs = [input_["input"] for input_ in inputs]
|
|
149
|
+
if any(key not in self.runnables for key in keys):
|
|
150
|
+
msg = "One or more keys do not have a corresponding runnable"
|
|
151
|
+
raise ValueError(msg)
|
|
152
|
+
|
|
153
|
+
def invoke(
|
|
154
|
+
runnable: Runnable[Input, Output], input_: Input, config: RunnableConfig
|
|
155
|
+
) -> Output | Exception:
|
|
156
|
+
if return_exceptions:
|
|
157
|
+
try:
|
|
158
|
+
return runnable.invoke(input_, config, **kwargs)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
return e
|
|
161
|
+
else:
|
|
162
|
+
return runnable.invoke(input_, config, **kwargs)
|
|
163
|
+
|
|
164
|
+
runnables = [self.runnables[key] for key in keys]
|
|
165
|
+
configs = get_config_list(config, len(inputs))
|
|
166
|
+
with get_executor_for_config(configs[0]) as executor:
|
|
167
|
+
return cast(
|
|
168
|
+
"list[Output]",
|
|
169
|
+
list(executor.map(invoke, runnables, actual_inputs, configs)),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
@override
|
|
173
|
+
async def abatch(
|
|
174
|
+
self,
|
|
175
|
+
inputs: list[RouterInput],
|
|
176
|
+
config: RunnableConfig | list[RunnableConfig] | None = None,
|
|
177
|
+
*,
|
|
178
|
+
return_exceptions: bool = False,
|
|
179
|
+
**kwargs: Any | None,
|
|
180
|
+
) -> list[Output]:
|
|
181
|
+
if not inputs:
|
|
182
|
+
return []
|
|
183
|
+
|
|
184
|
+
keys = [input_["key"] for input_ in inputs]
|
|
185
|
+
actual_inputs = [input_["input"] for input_ in inputs]
|
|
186
|
+
if any(key not in self.runnables for key in keys):
|
|
187
|
+
msg = "One or more keys do not have a corresponding runnable"
|
|
188
|
+
raise ValueError(msg)
|
|
189
|
+
|
|
190
|
+
async def ainvoke(
|
|
191
|
+
runnable: Runnable[Input, Output], input_: Input, config: RunnableConfig
|
|
192
|
+
) -> Output | Exception:
|
|
193
|
+
if return_exceptions:
|
|
194
|
+
try:
|
|
195
|
+
return await runnable.ainvoke(input_, config, **kwargs)
|
|
196
|
+
except Exception as e:
|
|
197
|
+
return e
|
|
198
|
+
else:
|
|
199
|
+
return await runnable.ainvoke(input_, config, **kwargs)
|
|
200
|
+
|
|
201
|
+
runnables = [self.runnables[key] for key in keys]
|
|
202
|
+
configs = get_config_list(config, len(inputs))
|
|
203
|
+
return await gather_with_concurrency(
|
|
204
|
+
configs[0].get("max_concurrency"),
|
|
205
|
+
*map(ainvoke, runnables, actual_inputs, configs),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
@override
|
|
209
|
+
def stream(
|
|
210
|
+
self,
|
|
211
|
+
input: RouterInput,
|
|
212
|
+
config: RunnableConfig | None = None,
|
|
213
|
+
**kwargs: Any | None,
|
|
214
|
+
) -> Iterator[Output]:
|
|
215
|
+
key = input["key"]
|
|
216
|
+
actual_input = input["input"]
|
|
217
|
+
if key not in self.runnables:
|
|
218
|
+
msg = f"No runnable associated with key '{key}'"
|
|
219
|
+
raise ValueError(msg)
|
|
220
|
+
|
|
221
|
+
runnable = self.runnables[key]
|
|
222
|
+
yield from runnable.stream(actual_input, config)
|
|
223
|
+
|
|
224
|
+
@override
|
|
225
|
+
async def astream(
|
|
226
|
+
self,
|
|
227
|
+
input: RouterInput,
|
|
228
|
+
config: RunnableConfig | None = None,
|
|
229
|
+
**kwargs: Any | None,
|
|
230
|
+
) -> AsyncIterator[Output]:
|
|
231
|
+
key = input["key"]
|
|
232
|
+
actual_input = input["input"]
|
|
233
|
+
if key not in self.runnables:
|
|
234
|
+
msg = f"No runnable associated with key '{key}'"
|
|
235
|
+
raise ValueError(msg)
|
|
236
|
+
|
|
237
|
+
runnable = self.runnables[key]
|
|
238
|
+
async for output in runnable.astream(actual_input, config):
|
|
239
|
+
yield output
|