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,710 @@
|
|
|
1
|
+
"""`Runnable` objects that can be dynamically configured."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import enum
|
|
6
|
+
import threading
|
|
7
|
+
from abc import abstractmethod
|
|
8
|
+
from collections.abc import (
|
|
9
|
+
AsyncIterator,
|
|
10
|
+
Callable,
|
|
11
|
+
Iterator,
|
|
12
|
+
Sequence,
|
|
13
|
+
)
|
|
14
|
+
from functools import wraps
|
|
15
|
+
from typing import (
|
|
16
|
+
TYPE_CHECKING,
|
|
17
|
+
Any,
|
|
18
|
+
cast,
|
|
19
|
+
)
|
|
20
|
+
from weakref import WeakValueDictionary
|
|
21
|
+
|
|
22
|
+
from pydantic import ConfigDict
|
|
23
|
+
from typing_extensions import override
|
|
24
|
+
|
|
25
|
+
from coco_core.runnables.base import Runnable, RunnableSerializable
|
|
26
|
+
from coco_core.runnables.config import (
|
|
27
|
+
RunnableConfig,
|
|
28
|
+
ensure_config,
|
|
29
|
+
get_config_list,
|
|
30
|
+
get_executor_for_config,
|
|
31
|
+
merge_configs,
|
|
32
|
+
)
|
|
33
|
+
from coco_core.runnables.utils import (
|
|
34
|
+
AnyConfigurableField,
|
|
35
|
+
ConfigurableField,
|
|
36
|
+
ConfigurableFieldMultiOption,
|
|
37
|
+
ConfigurableFieldSingleOption,
|
|
38
|
+
ConfigurableFieldSpec,
|
|
39
|
+
Input,
|
|
40
|
+
Output,
|
|
41
|
+
gather_with_concurrency,
|
|
42
|
+
get_unique_config_specs,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from coco_core.runnables.graph import Graph
|
|
47
|
+
from coco_core.utils.pydantic import TypeBaseModel
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DynamicRunnable(RunnableSerializable[Input, Output]):
|
|
51
|
+
"""Serializable `Runnable` that can be dynamically configured.
|
|
52
|
+
|
|
53
|
+
A `DynamicRunnable` should be initiated using the `configurable_fields` or
|
|
54
|
+
`configurable_alternatives` method of a `Runnable`.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
default: RunnableSerializable[Input, Output]
|
|
58
|
+
"""The default `Runnable` to use."""
|
|
59
|
+
|
|
60
|
+
config: RunnableConfig | None = None
|
|
61
|
+
"""The configuration to use."""
|
|
62
|
+
|
|
63
|
+
model_config = ConfigDict(
|
|
64
|
+
arbitrary_types_allowed=True,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
@override
|
|
69
|
+
def is_lc_serializable(cls) -> bool:
|
|
70
|
+
"""Return `True` as this class is serializable."""
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
@override
|
|
75
|
+
def get_lc_namespace(cls) -> list[str]:
|
|
76
|
+
"""Get the namespace of the Coco object.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
`["coco", "schema", "runnable"]`
|
|
80
|
+
"""
|
|
81
|
+
return ["coco", "schema", "runnable"]
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
@override
|
|
85
|
+
def InputType(self) -> type[Input]:
|
|
86
|
+
return self.default.InputType
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
@override
|
|
90
|
+
def OutputType(self) -> type[Output]:
|
|
91
|
+
return self.default.OutputType
|
|
92
|
+
|
|
93
|
+
@override
|
|
94
|
+
def get_input_schema(self, config: RunnableConfig | None = None) -> TypeBaseModel:
|
|
95
|
+
runnable, config = self.prepare(config)
|
|
96
|
+
return runnable.get_input_schema(config)
|
|
97
|
+
|
|
98
|
+
@override
|
|
99
|
+
def get_output_schema(self, config: RunnableConfig | None = None) -> TypeBaseModel:
|
|
100
|
+
runnable, config = self.prepare(config)
|
|
101
|
+
return runnable.get_output_schema(config)
|
|
102
|
+
|
|
103
|
+
@override
|
|
104
|
+
def get_graph(self, config: RunnableConfig | None = None) -> Graph:
|
|
105
|
+
runnable, config = self.prepare(config)
|
|
106
|
+
return runnable.get_graph(config)
|
|
107
|
+
|
|
108
|
+
@override
|
|
109
|
+
def with_config(
|
|
110
|
+
self,
|
|
111
|
+
config: RunnableConfig | None = None,
|
|
112
|
+
# Sadly Unpack is not well supported by mypy so this will have to be untyped
|
|
113
|
+
**kwargs: Any,
|
|
114
|
+
) -> Runnable[Input, Output]:
|
|
115
|
+
return self.__class__(
|
|
116
|
+
**{**self.__dict__, "config": ensure_config(merge_configs(config, kwargs))} # type: ignore[arg-type]
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def prepare(
|
|
120
|
+
self, config: RunnableConfig | None = None
|
|
121
|
+
) -> tuple[Runnable[Input, Output], RunnableConfig]:
|
|
122
|
+
"""Prepare the `Runnable` for invocation.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
config: The configuration to use.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
The prepared `Runnable` and configuration.
|
|
129
|
+
"""
|
|
130
|
+
runnable: Runnable[Input, Output] = self
|
|
131
|
+
while isinstance(runnable, DynamicRunnable):
|
|
132
|
+
runnable, config = runnable._prepare(merge_configs(runnable.config, config)) # noqa: SLF001
|
|
133
|
+
return runnable, cast("RunnableConfig", config)
|
|
134
|
+
|
|
135
|
+
@abstractmethod
|
|
136
|
+
def _prepare(
|
|
137
|
+
self, config: RunnableConfig | None = None
|
|
138
|
+
) -> tuple[Runnable[Input, Output], RunnableConfig]: ...
|
|
139
|
+
|
|
140
|
+
@override
|
|
141
|
+
def invoke(
|
|
142
|
+
self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
|
|
143
|
+
) -> Output:
|
|
144
|
+
runnable, config = self.prepare(config)
|
|
145
|
+
return runnable.invoke(input, config, **kwargs)
|
|
146
|
+
|
|
147
|
+
@override
|
|
148
|
+
async def ainvoke(
|
|
149
|
+
self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
|
|
150
|
+
) -> Output:
|
|
151
|
+
runnable, config = self.prepare(config)
|
|
152
|
+
return await runnable.ainvoke(input, config, **kwargs)
|
|
153
|
+
|
|
154
|
+
@override
|
|
155
|
+
def batch(
|
|
156
|
+
self,
|
|
157
|
+
inputs: list[Input],
|
|
158
|
+
config: RunnableConfig | list[RunnableConfig] | None = None,
|
|
159
|
+
*,
|
|
160
|
+
return_exceptions: bool = False,
|
|
161
|
+
**kwargs: Any | None,
|
|
162
|
+
) -> list[Output]:
|
|
163
|
+
configs = get_config_list(config, len(inputs))
|
|
164
|
+
prepared = [self.prepare(c) for c in configs]
|
|
165
|
+
|
|
166
|
+
if all(p is self.default for p, _ in prepared):
|
|
167
|
+
return self.default.batch(
|
|
168
|
+
inputs,
|
|
169
|
+
[c for _, c in prepared],
|
|
170
|
+
return_exceptions=return_exceptions,
|
|
171
|
+
**kwargs,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if not inputs:
|
|
175
|
+
return []
|
|
176
|
+
|
|
177
|
+
def invoke(
|
|
178
|
+
prepared: tuple[Runnable[Input, Output], RunnableConfig],
|
|
179
|
+
input_: Input,
|
|
180
|
+
) -> Output | Exception:
|
|
181
|
+
bound, config = prepared
|
|
182
|
+
if return_exceptions:
|
|
183
|
+
try:
|
|
184
|
+
return bound.invoke(input_, config, **kwargs)
|
|
185
|
+
except Exception as e:
|
|
186
|
+
return e
|
|
187
|
+
else:
|
|
188
|
+
return bound.invoke(input_, config, **kwargs)
|
|
189
|
+
|
|
190
|
+
# If there's only one input, don't bother with the executor
|
|
191
|
+
if len(inputs) == 1:
|
|
192
|
+
return cast("list[Output]", [invoke(prepared[0], inputs[0])])
|
|
193
|
+
|
|
194
|
+
with get_executor_for_config(configs[0]) as executor:
|
|
195
|
+
return cast("list[Output]", list(executor.map(invoke, prepared, inputs)))
|
|
196
|
+
|
|
197
|
+
@override
|
|
198
|
+
async def abatch(
|
|
199
|
+
self,
|
|
200
|
+
inputs: list[Input],
|
|
201
|
+
config: RunnableConfig | list[RunnableConfig] | None = None,
|
|
202
|
+
*,
|
|
203
|
+
return_exceptions: bool = False,
|
|
204
|
+
**kwargs: Any | None,
|
|
205
|
+
) -> list[Output]:
|
|
206
|
+
configs = get_config_list(config, len(inputs))
|
|
207
|
+
prepared = [self.prepare(c) for c in configs]
|
|
208
|
+
|
|
209
|
+
if all(p is self.default for p, _ in prepared):
|
|
210
|
+
return await self.default.abatch(
|
|
211
|
+
inputs,
|
|
212
|
+
[c for _, c in prepared],
|
|
213
|
+
return_exceptions=return_exceptions,
|
|
214
|
+
**kwargs,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
if not inputs:
|
|
218
|
+
return []
|
|
219
|
+
|
|
220
|
+
async def ainvoke(
|
|
221
|
+
prepared: tuple[Runnable[Input, Output], RunnableConfig],
|
|
222
|
+
input_: Input,
|
|
223
|
+
) -> Output | Exception:
|
|
224
|
+
bound, config = prepared
|
|
225
|
+
if return_exceptions:
|
|
226
|
+
try:
|
|
227
|
+
return await bound.ainvoke(input_, config, **kwargs)
|
|
228
|
+
except Exception as e:
|
|
229
|
+
return e
|
|
230
|
+
else:
|
|
231
|
+
return await bound.ainvoke(input_, config, **kwargs)
|
|
232
|
+
|
|
233
|
+
coros = map(ainvoke, prepared, inputs)
|
|
234
|
+
return await gather_with_concurrency(configs[0].get("max_concurrency"), *coros)
|
|
235
|
+
|
|
236
|
+
@override
|
|
237
|
+
def stream(
|
|
238
|
+
self,
|
|
239
|
+
input: Input,
|
|
240
|
+
config: RunnableConfig | None = None,
|
|
241
|
+
**kwargs: Any | None,
|
|
242
|
+
) -> Iterator[Output]:
|
|
243
|
+
runnable, config = self.prepare(config)
|
|
244
|
+
return runnable.stream(input, config, **kwargs)
|
|
245
|
+
|
|
246
|
+
@override
|
|
247
|
+
async def astream(
|
|
248
|
+
self,
|
|
249
|
+
input: Input,
|
|
250
|
+
config: RunnableConfig | None = None,
|
|
251
|
+
**kwargs: Any | None,
|
|
252
|
+
) -> AsyncIterator[Output]:
|
|
253
|
+
runnable, config = self.prepare(config)
|
|
254
|
+
async for chunk in runnable.astream(input, config, **kwargs):
|
|
255
|
+
yield chunk
|
|
256
|
+
|
|
257
|
+
@override
|
|
258
|
+
def transform(
|
|
259
|
+
self,
|
|
260
|
+
input: Iterator[Input],
|
|
261
|
+
config: RunnableConfig | None = None,
|
|
262
|
+
**kwargs: Any | None,
|
|
263
|
+
) -> Iterator[Output]:
|
|
264
|
+
runnable, config = self.prepare(config)
|
|
265
|
+
return runnable.transform(input, config, **kwargs)
|
|
266
|
+
|
|
267
|
+
@override
|
|
268
|
+
async def atransform(
|
|
269
|
+
self,
|
|
270
|
+
input: AsyncIterator[Input],
|
|
271
|
+
config: RunnableConfig | None = None,
|
|
272
|
+
**kwargs: Any | None,
|
|
273
|
+
) -> AsyncIterator[Output]:
|
|
274
|
+
runnable, config = self.prepare(config)
|
|
275
|
+
async for chunk in runnable.atransform(input, config, **kwargs):
|
|
276
|
+
yield chunk
|
|
277
|
+
|
|
278
|
+
@override
|
|
279
|
+
def __getattr__(self, name: str) -> Any: # type: ignore[misc]
|
|
280
|
+
attr = getattr(self.default, name)
|
|
281
|
+
if callable(attr):
|
|
282
|
+
|
|
283
|
+
@wraps(attr)
|
|
284
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
285
|
+
for key, arg in kwargs.items():
|
|
286
|
+
if key == "config" and (
|
|
287
|
+
isinstance(arg, dict)
|
|
288
|
+
and "configurable" in arg
|
|
289
|
+
and isinstance(arg["configurable"], dict)
|
|
290
|
+
):
|
|
291
|
+
runnable, config = self.prepare(cast("RunnableConfig", arg))
|
|
292
|
+
kwargs = {**kwargs, "config": config}
|
|
293
|
+
return getattr(runnable, name)(*args, **kwargs)
|
|
294
|
+
|
|
295
|
+
for idx, arg in enumerate(args):
|
|
296
|
+
if (
|
|
297
|
+
isinstance(arg, dict)
|
|
298
|
+
and "configurable" in arg
|
|
299
|
+
and isinstance(arg["configurable"], dict)
|
|
300
|
+
):
|
|
301
|
+
runnable, config = self.prepare(cast("RunnableConfig", arg))
|
|
302
|
+
argsl = list(args)
|
|
303
|
+
argsl[idx] = config
|
|
304
|
+
return getattr(runnable, name)(*argsl, **kwargs)
|
|
305
|
+
|
|
306
|
+
if self.config:
|
|
307
|
+
runnable, config = self.prepare()
|
|
308
|
+
return getattr(runnable, name)(*args, **kwargs)
|
|
309
|
+
|
|
310
|
+
return attr(*args, **kwargs)
|
|
311
|
+
|
|
312
|
+
return wrapper
|
|
313
|
+
|
|
314
|
+
return attr
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class RunnableConfigurableFields(DynamicRunnable[Input, Output]):
|
|
318
|
+
"""`Runnable` that can be dynamically configured.
|
|
319
|
+
|
|
320
|
+
A `RunnableConfigurableFields` should be initiated using the
|
|
321
|
+
`configurable_fields` method of a `Runnable`.
|
|
322
|
+
|
|
323
|
+
Here is an example of using a `RunnableConfigurableFields` with LLMs:
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
from coco_core.prompts import PromptTemplate
|
|
327
|
+
from coco_core.runnables import ConfigurableField
|
|
328
|
+
from coco_openai import ChatOpenAI
|
|
329
|
+
|
|
330
|
+
model = ChatOpenAI(temperature=0).configurable_fields(
|
|
331
|
+
temperature=ConfigurableField(
|
|
332
|
+
id="temperature",
|
|
333
|
+
name="LLM Temperature",
|
|
334
|
+
description="The temperature of the LLM",
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
# This creates a RunnableConfigurableFields for a chat model.
|
|
338
|
+
|
|
339
|
+
# When invoking the created RunnableSequence, you can pass in the
|
|
340
|
+
# value for your ConfigurableField's id which in this case
|
|
341
|
+
# will be change in temperature
|
|
342
|
+
|
|
343
|
+
prompt = PromptTemplate.from_template("Pick a random number above {x}")
|
|
344
|
+
chain = prompt | model
|
|
345
|
+
|
|
346
|
+
chain.invoke({"x": 0})
|
|
347
|
+
chain.invoke({"x": 0}, config={"configurable": {"temperature": 0.9}})
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Here is an example of using a `RunnableConfigurableFields` with `HubRunnables`:
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
from coco_core.prompts import PromptTemplate
|
|
354
|
+
from coco_core.runnables import ConfigurableField
|
|
355
|
+
from coco_openai import ChatOpenAI
|
|
356
|
+
from coco.runnables.hub import HubRunnable
|
|
357
|
+
|
|
358
|
+
prompt = HubRunnable("rlm/rag-prompt").configurable_fields(
|
|
359
|
+
owner_repo_commit=ConfigurableField(
|
|
360
|
+
id="hub_commit",
|
|
361
|
+
name="Hub Commit",
|
|
362
|
+
description="The Hub commit to pull from",
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
prompt.invoke({"question": "foo", "context": "bar"})
|
|
367
|
+
|
|
368
|
+
# Invoking prompt with `with_config` method
|
|
369
|
+
|
|
370
|
+
prompt.invoke(
|
|
371
|
+
{"question": "foo", "context": "bar"},
|
|
372
|
+
config={"configurable": {"hub_commit": "rlm/rag-prompt-llama"}},
|
|
373
|
+
)
|
|
374
|
+
```
|
|
375
|
+
"""
|
|
376
|
+
|
|
377
|
+
fields: dict[str, AnyConfigurableField]
|
|
378
|
+
"""The configurable fields to use."""
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def config_specs(self) -> list[ConfigurableFieldSpec]:
|
|
382
|
+
"""Get the configuration specs for the `RunnableConfigurableFields`.
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
The configuration specs.
|
|
386
|
+
"""
|
|
387
|
+
config_specs = []
|
|
388
|
+
|
|
389
|
+
default_fields = type(self.default).model_fields
|
|
390
|
+
for field_name, spec in self.fields.items():
|
|
391
|
+
if isinstance(spec, ConfigurableField):
|
|
392
|
+
config_specs.append(
|
|
393
|
+
ConfigurableFieldSpec(
|
|
394
|
+
id=spec.id,
|
|
395
|
+
name=spec.name,
|
|
396
|
+
description=spec.description
|
|
397
|
+
or default_fields[field_name].description,
|
|
398
|
+
annotation=spec.annotation
|
|
399
|
+
or default_fields[field_name].annotation,
|
|
400
|
+
default=getattr(self.default, field_name),
|
|
401
|
+
is_shared=spec.is_shared,
|
|
402
|
+
)
|
|
403
|
+
)
|
|
404
|
+
else:
|
|
405
|
+
config_specs.append(
|
|
406
|
+
make_options_spec(spec, default_fields[field_name].description)
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
config_specs.extend(self.default.config_specs)
|
|
410
|
+
|
|
411
|
+
return get_unique_config_specs(config_specs)
|
|
412
|
+
|
|
413
|
+
@override
|
|
414
|
+
def configurable_fields(
|
|
415
|
+
self, **kwargs: AnyConfigurableField
|
|
416
|
+
) -> RunnableSerializable[Input, Output]:
|
|
417
|
+
return self.default.configurable_fields(**{**self.fields, **kwargs})
|
|
418
|
+
|
|
419
|
+
def _prepare(
|
|
420
|
+
self, config: RunnableConfig | None = None
|
|
421
|
+
) -> tuple[Runnable[Input, Output], RunnableConfig]:
|
|
422
|
+
config = ensure_config(config)
|
|
423
|
+
specs_by_id = {spec.id: (key, spec) for key, spec in self.fields.items()}
|
|
424
|
+
configurable_fields = {
|
|
425
|
+
specs_by_id[k][0]: v
|
|
426
|
+
for k, v in config.get("configurable", {}).items()
|
|
427
|
+
if k in specs_by_id and isinstance(specs_by_id[k][1], ConfigurableField)
|
|
428
|
+
}
|
|
429
|
+
configurable_single_options = {
|
|
430
|
+
k: v.options[(config.get("configurable", {}).get(v.id) or v.default)]
|
|
431
|
+
for k, v in self.fields.items()
|
|
432
|
+
if isinstance(v, ConfigurableFieldSingleOption)
|
|
433
|
+
}
|
|
434
|
+
configurable_multi_options = {
|
|
435
|
+
k: [
|
|
436
|
+
v.options[o]
|
|
437
|
+
for o in config.get("configurable", {}).get(v.id, v.default)
|
|
438
|
+
]
|
|
439
|
+
for k, v in self.fields.items()
|
|
440
|
+
if isinstance(v, ConfigurableFieldMultiOption)
|
|
441
|
+
}
|
|
442
|
+
configurable = {
|
|
443
|
+
**configurable_fields,
|
|
444
|
+
**configurable_single_options,
|
|
445
|
+
**configurable_multi_options,
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if configurable:
|
|
449
|
+
init_params = {
|
|
450
|
+
k: v
|
|
451
|
+
for k, v in self.default.__dict__.items()
|
|
452
|
+
if k in type(self.default).model_fields
|
|
453
|
+
}
|
|
454
|
+
return (
|
|
455
|
+
self.default.__class__(**{**init_params, **configurable}),
|
|
456
|
+
config,
|
|
457
|
+
)
|
|
458
|
+
return (self.default, config)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
# Before Python 3.11 native StrEnum is not available
|
|
462
|
+
class StrEnum(str, enum.Enum):
|
|
463
|
+
"""String enum."""
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
_enums_for_spec: WeakValueDictionary[
|
|
467
|
+
ConfigurableFieldSingleOption | ConfigurableFieldMultiOption | ConfigurableField,
|
|
468
|
+
type[StrEnum],
|
|
469
|
+
] = WeakValueDictionary()
|
|
470
|
+
|
|
471
|
+
_enums_for_spec_lock = threading.Lock()
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class RunnableConfigurableAlternatives(DynamicRunnable[Input, Output]):
|
|
475
|
+
"""`Runnable` that can be dynamically configured.
|
|
476
|
+
|
|
477
|
+
A `RunnableConfigurableAlternatives` should be initiated using the
|
|
478
|
+
`configurable_alternatives` method of a `Runnable` or can be
|
|
479
|
+
initiated directly as well.
|
|
480
|
+
|
|
481
|
+
Here is an example of using a `RunnableConfigurableAlternatives` that uses
|
|
482
|
+
alternative prompts to illustrate its functionality:
|
|
483
|
+
|
|
484
|
+
```python
|
|
485
|
+
from coco_core.runnables import ConfigurableField
|
|
486
|
+
from coco_openai import ChatOpenAI
|
|
487
|
+
|
|
488
|
+
# This creates a RunnableConfigurableAlternatives for Prompt Runnable
|
|
489
|
+
# with two alternatives.
|
|
490
|
+
prompt = PromptTemplate.from_template(
|
|
491
|
+
"Tell me a joke about {topic}"
|
|
492
|
+
).configurable_alternatives(
|
|
493
|
+
ConfigurableField(id="prompt"),
|
|
494
|
+
default_key="joke",
|
|
495
|
+
poem=PromptTemplate.from_template("Write a short poem about {topic}"),
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
# When invoking the created RunnableSequence, you can pass in the
|
|
499
|
+
# value for your ConfigurableField's id which in this case will either be
|
|
500
|
+
# `joke` or `poem`.
|
|
501
|
+
chain = prompt | ChatOpenAI(model="gpt-5.4-mini")
|
|
502
|
+
|
|
503
|
+
# The `with_config` method brings in the desired Prompt Runnable in your
|
|
504
|
+
# Runnable Sequence.
|
|
505
|
+
chain.with_config(configurable={"prompt": "poem"}).invoke({"topic": "bears"})
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Equivalently, you can initialize `RunnableConfigurableAlternatives` directly
|
|
509
|
+
and use in LCEL in the same way:
|
|
510
|
+
|
|
511
|
+
```python
|
|
512
|
+
from coco_core.runnables import ConfigurableField
|
|
513
|
+
from coco_core.runnables.configurable import (
|
|
514
|
+
RunnableConfigurableAlternatives,
|
|
515
|
+
)
|
|
516
|
+
from coco_openai import ChatOpenAI
|
|
517
|
+
|
|
518
|
+
prompt = RunnableConfigurableAlternatives(
|
|
519
|
+
which=ConfigurableField(id="prompt"),
|
|
520
|
+
default=PromptTemplate.from_template("Tell me a joke about {topic}"),
|
|
521
|
+
default_key="joke",
|
|
522
|
+
prefix_keys=False,
|
|
523
|
+
alternatives={
|
|
524
|
+
"poem": PromptTemplate.from_template("Write a short poem about {topic}")
|
|
525
|
+
},
|
|
526
|
+
)
|
|
527
|
+
chain = prompt | ChatOpenAI(model="gpt-5.4-mini")
|
|
528
|
+
chain.with_config(configurable={"prompt": "poem"}).invoke({"topic": "bears"})
|
|
529
|
+
```
|
|
530
|
+
"""
|
|
531
|
+
|
|
532
|
+
which: ConfigurableField
|
|
533
|
+
"""The `ConfigurableField` to use to choose between alternatives."""
|
|
534
|
+
|
|
535
|
+
alternatives: dict[
|
|
536
|
+
str,
|
|
537
|
+
Runnable[Input, Output] | Callable[[], Runnable[Input, Output]],
|
|
538
|
+
]
|
|
539
|
+
"""The alternatives to choose from."""
|
|
540
|
+
|
|
541
|
+
default_key: str = "default"
|
|
542
|
+
"""The enum value to use for the default option."""
|
|
543
|
+
|
|
544
|
+
prefix_keys: bool
|
|
545
|
+
"""Whether to prefix configurable fields of each alternative with a namespace
|
|
546
|
+
of the form <which.id>==<alternative_key>, e.g. a key named "temperature" used by
|
|
547
|
+
the alternative named "gpt3" becomes "model==gpt3/temperature".
|
|
548
|
+
"""
|
|
549
|
+
|
|
550
|
+
@property
|
|
551
|
+
@override
|
|
552
|
+
def config_specs(self) -> list[ConfigurableFieldSpec]:
|
|
553
|
+
with _enums_for_spec_lock:
|
|
554
|
+
if which_enum := _enums_for_spec.get(self.which):
|
|
555
|
+
pass
|
|
556
|
+
else:
|
|
557
|
+
which_enum = StrEnum( # type: ignore[call-overload]
|
|
558
|
+
self.which.name or self.which.id,
|
|
559
|
+
(
|
|
560
|
+
(v, v)
|
|
561
|
+
for v in [*list(self.alternatives.keys()), self.default_key]
|
|
562
|
+
),
|
|
563
|
+
)
|
|
564
|
+
_enums_for_spec[self.which] = cast("type[StrEnum]", which_enum)
|
|
565
|
+
return get_unique_config_specs(
|
|
566
|
+
# which alternative
|
|
567
|
+
[
|
|
568
|
+
ConfigurableFieldSpec(
|
|
569
|
+
id=self.which.id,
|
|
570
|
+
name=self.which.name,
|
|
571
|
+
description=self.which.description,
|
|
572
|
+
annotation=which_enum,
|
|
573
|
+
default=self.default_key,
|
|
574
|
+
is_shared=self.which.is_shared,
|
|
575
|
+
),
|
|
576
|
+
]
|
|
577
|
+
# config specs of the default option
|
|
578
|
+
+ (
|
|
579
|
+
[
|
|
580
|
+
prefix_config_spec(s, f"{self.which.id}=={self.default_key}")
|
|
581
|
+
for s in self.default.config_specs
|
|
582
|
+
]
|
|
583
|
+
if self.prefix_keys
|
|
584
|
+
else self.default.config_specs
|
|
585
|
+
)
|
|
586
|
+
# config specs of the alternatives
|
|
587
|
+
+ [
|
|
588
|
+
(
|
|
589
|
+
prefix_config_spec(s, f"{self.which.id}=={alt_key}")
|
|
590
|
+
if self.prefix_keys
|
|
591
|
+
else s
|
|
592
|
+
)
|
|
593
|
+
for alt_key, alt in self.alternatives.items()
|
|
594
|
+
if isinstance(alt, RunnableSerializable)
|
|
595
|
+
for s in alt.config_specs
|
|
596
|
+
]
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
@override
|
|
600
|
+
def configurable_fields(
|
|
601
|
+
self, **kwargs: AnyConfigurableField
|
|
602
|
+
) -> RunnableSerializable[Input, Output]:
|
|
603
|
+
return self.__class__(
|
|
604
|
+
which=self.which,
|
|
605
|
+
default=self.default.configurable_fields(**kwargs),
|
|
606
|
+
alternatives=self.alternatives,
|
|
607
|
+
default_key=self.default_key,
|
|
608
|
+
prefix_keys=self.prefix_keys,
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
def _prepare(
|
|
612
|
+
self, config: RunnableConfig | None = None
|
|
613
|
+
) -> tuple[Runnable[Input, Output], RunnableConfig]:
|
|
614
|
+
config = ensure_config(config)
|
|
615
|
+
which = config.get("configurable", {}).get(self.which.id, self.default_key)
|
|
616
|
+
# remap configurable keys for the chosen alternative
|
|
617
|
+
if self.prefix_keys:
|
|
618
|
+
config = cast(
|
|
619
|
+
"RunnableConfig",
|
|
620
|
+
{
|
|
621
|
+
**config,
|
|
622
|
+
"configurable": {
|
|
623
|
+
k.removeprefix(f"{self.which.id}=={which}/"): v
|
|
624
|
+
for k, v in config.get("configurable", {}).items()
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
)
|
|
628
|
+
# return the chosen alternative
|
|
629
|
+
if which == self.default_key:
|
|
630
|
+
return (self.default, config)
|
|
631
|
+
if which in self.alternatives:
|
|
632
|
+
alt = self.alternatives[which]
|
|
633
|
+
if isinstance(alt, Runnable):
|
|
634
|
+
return (alt, config)
|
|
635
|
+
return (alt(), config)
|
|
636
|
+
msg = f"Unknown alternative: {which}"
|
|
637
|
+
raise ValueError(msg)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def prefix_config_spec(
|
|
641
|
+
spec: ConfigurableFieldSpec, prefix: str
|
|
642
|
+
) -> ConfigurableFieldSpec:
|
|
643
|
+
"""Prefix the id of a `ConfigurableFieldSpec`.
|
|
644
|
+
|
|
645
|
+
This is useful when a `RunnableConfigurableAlternatives` is used as a
|
|
646
|
+
`ConfigurableField` of another `RunnableConfigurableAlternatives`.
|
|
647
|
+
|
|
648
|
+
Args:
|
|
649
|
+
spec: The `ConfigurableFieldSpec` to prefix.
|
|
650
|
+
prefix: The prefix to add.
|
|
651
|
+
|
|
652
|
+
Returns:
|
|
653
|
+
The prefixed `ConfigurableFieldSpec`.
|
|
654
|
+
"""
|
|
655
|
+
return (
|
|
656
|
+
ConfigurableFieldSpec(
|
|
657
|
+
id=f"{prefix}/{spec.id}",
|
|
658
|
+
name=spec.name,
|
|
659
|
+
description=spec.description,
|
|
660
|
+
annotation=spec.annotation,
|
|
661
|
+
default=spec.default,
|
|
662
|
+
is_shared=spec.is_shared,
|
|
663
|
+
)
|
|
664
|
+
if not spec.is_shared
|
|
665
|
+
else spec
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
def make_options_spec(
|
|
670
|
+
spec: ConfigurableFieldSingleOption | ConfigurableFieldMultiOption,
|
|
671
|
+
description: str | None,
|
|
672
|
+
) -> ConfigurableFieldSpec:
|
|
673
|
+
"""Make options spec.
|
|
674
|
+
|
|
675
|
+
Make a `ConfigurableFieldSpec` for a `ConfigurableFieldSingleOption` or
|
|
676
|
+
`ConfigurableFieldMultiOption`.
|
|
677
|
+
|
|
678
|
+
Args:
|
|
679
|
+
spec: The `ConfigurableFieldSingleOption` or `ConfigurableFieldMultiOption`.
|
|
680
|
+
description: The description to use if the spec does not have one.
|
|
681
|
+
|
|
682
|
+
Returns:
|
|
683
|
+
The `ConfigurableFieldSpec`.
|
|
684
|
+
"""
|
|
685
|
+
with _enums_for_spec_lock:
|
|
686
|
+
if enum := _enums_for_spec.get(spec):
|
|
687
|
+
pass
|
|
688
|
+
else:
|
|
689
|
+
enum = StrEnum( # type: ignore[call-overload]
|
|
690
|
+
spec.name or spec.id,
|
|
691
|
+
((v, v) for v in list(spec.options.keys())),
|
|
692
|
+
)
|
|
693
|
+
_enums_for_spec[spec] = cast("type[StrEnum]", enum)
|
|
694
|
+
if isinstance(spec, ConfigurableFieldSingleOption):
|
|
695
|
+
return ConfigurableFieldSpec(
|
|
696
|
+
id=spec.id,
|
|
697
|
+
name=spec.name,
|
|
698
|
+
description=spec.description or description,
|
|
699
|
+
annotation=enum,
|
|
700
|
+
default=spec.default,
|
|
701
|
+
is_shared=spec.is_shared,
|
|
702
|
+
)
|
|
703
|
+
return ConfigurableFieldSpec(
|
|
704
|
+
id=spec.id,
|
|
705
|
+
name=spec.name,
|
|
706
|
+
description=spec.description or description,
|
|
707
|
+
annotation=Sequence[enum], # type: ignore[valid-type]
|
|
708
|
+
default=spec.default,
|
|
709
|
+
is_shared=spec.is_shared,
|
|
710
|
+
)
|