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,849 @@
|
|
|
1
|
+
"""Implementation of the `RunnablePassthrough`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import inspect
|
|
7
|
+
import threading
|
|
8
|
+
from collections.abc import Awaitable, Callable
|
|
9
|
+
from typing import (
|
|
10
|
+
TYPE_CHECKING,
|
|
11
|
+
Any,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from pydantic import RootModel
|
|
15
|
+
from typing_extensions import override
|
|
16
|
+
|
|
17
|
+
from coco_core.runnables.base import (
|
|
18
|
+
Other,
|
|
19
|
+
Runnable,
|
|
20
|
+
RunnableParallel,
|
|
21
|
+
RunnableSerializable,
|
|
22
|
+
_get_schema_field_definition,
|
|
23
|
+
)
|
|
24
|
+
from coco_core.runnables.config import (
|
|
25
|
+
RunnableConfig,
|
|
26
|
+
acall_func_with_variable_args,
|
|
27
|
+
call_func_with_variable_args,
|
|
28
|
+
ensure_config,
|
|
29
|
+
get_executor_for_config,
|
|
30
|
+
patch_config,
|
|
31
|
+
)
|
|
32
|
+
from coco_core.runnables.utils import (
|
|
33
|
+
AddableDict,
|
|
34
|
+
ConfigurableFieldSpec,
|
|
35
|
+
)
|
|
36
|
+
from coco_core.utils.aiter import atee
|
|
37
|
+
from coco_core.utils.iter import safetee
|
|
38
|
+
from coco_core.utils.pydantic import TypeBaseModel, create_model_v2, get_fields
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from collections.abc import AsyncIterator, Iterator, Mapping
|
|
42
|
+
|
|
43
|
+
from coco_core.callbacks.manager import (
|
|
44
|
+
AsyncCallbackManagerForChainRun,
|
|
45
|
+
CallbackManagerForChainRun,
|
|
46
|
+
)
|
|
47
|
+
from coco_core.runnables.graph import Graph
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def identity(x: Other) -> Other:
|
|
51
|
+
"""Identity function.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
x: Input.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Output.
|
|
58
|
+
"""
|
|
59
|
+
return x
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def aidentity(x: Other) -> Other:
|
|
63
|
+
"""Async identity function.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
x: Input.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Output.
|
|
70
|
+
"""
|
|
71
|
+
return x
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class RunnablePassthrough(RunnableSerializable[Other, Other]):
|
|
75
|
+
"""Runnable to passthrough inputs unchanged or with additional keys.
|
|
76
|
+
|
|
77
|
+
This `Runnable` behaves almost like the identity function, except that it
|
|
78
|
+
can be configured to add additional keys to the output, if the input is a
|
|
79
|
+
dict.
|
|
80
|
+
|
|
81
|
+
The examples below demonstrate this `Runnable` works using a few simple
|
|
82
|
+
chains. The chains rely on simple lambdas to make the examples easy to execute
|
|
83
|
+
and experiment with.
|
|
84
|
+
|
|
85
|
+
Examples:
|
|
86
|
+
```python
|
|
87
|
+
from coco_core.runnables import (
|
|
88
|
+
RunnableLambda,
|
|
89
|
+
RunnableParallel,
|
|
90
|
+
RunnablePassthrough,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
runnable = RunnableParallel(
|
|
94
|
+
origin=RunnablePassthrough(), modified=lambda x: x + 1
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
runnable.invoke(1) # {'origin': 1, 'modified': 2}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def fake_llm(prompt: str) -> str: # Fake LLM for the example
|
|
101
|
+
return "completion"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
chain = RunnableLambda(fake_llm) | {
|
|
105
|
+
"original": RunnablePassthrough(), # Original LLM output
|
|
106
|
+
"parsed": lambda text: text[::-1], # Parsing logic
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
chain.invoke("hello") # {'original': 'completion', 'parsed': 'noitelpmoc'}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
In some cases, it may be useful to pass the input through while adding some
|
|
113
|
+
keys to the output. In this case, you can use the `assign` method:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from coco_core.runnables import RunnablePassthrough
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def fake_llm(prompt: str) -> str: # Fake LLM for the example
|
|
120
|
+
return "completion"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
runnable = {
|
|
124
|
+
"llm1": fake_llm,
|
|
125
|
+
"llm2": fake_llm,
|
|
126
|
+
} | RunnablePassthrough.assign(
|
|
127
|
+
total_chars=lambda inputs: len(inputs["llm1"] + inputs["llm2"])
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
runnable.invoke("hello")
|
|
131
|
+
# {'llm1': 'completion', 'llm2': 'completion', 'total_chars': 20}
|
|
132
|
+
```
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
input_type: type[Other] | None = None
|
|
136
|
+
|
|
137
|
+
func: Callable[[Other], None] | Callable[[Other, RunnableConfig], None] | None = (
|
|
138
|
+
None
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
afunc: (
|
|
142
|
+
Callable[[Other], Awaitable[None]]
|
|
143
|
+
| Callable[[Other, RunnableConfig], Awaitable[None]]
|
|
144
|
+
| None
|
|
145
|
+
) = None
|
|
146
|
+
|
|
147
|
+
@override
|
|
148
|
+
def __repr_args__(self) -> Any:
|
|
149
|
+
# Without this repr(self) raises a RecursionError
|
|
150
|
+
# See https://github.com/pydantic/pydantic/issues/7327
|
|
151
|
+
return []
|
|
152
|
+
|
|
153
|
+
def __init__(
|
|
154
|
+
self,
|
|
155
|
+
func: Callable[[Other], None]
|
|
156
|
+
| Callable[[Other, RunnableConfig], None]
|
|
157
|
+
| Callable[[Other], Awaitable[None]]
|
|
158
|
+
| Callable[[Other, RunnableConfig], Awaitable[None]]
|
|
159
|
+
| None = None,
|
|
160
|
+
afunc: Callable[[Other], Awaitable[None]]
|
|
161
|
+
| Callable[[Other, RunnableConfig], Awaitable[None]]
|
|
162
|
+
| None = None,
|
|
163
|
+
*,
|
|
164
|
+
input_type: type[Other] | None = None,
|
|
165
|
+
**kwargs: Any,
|
|
166
|
+
) -> None:
|
|
167
|
+
"""Create a `RunnablePassthrough`.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
func: Function to be called with the input.
|
|
171
|
+
afunc: Async function to be called with the input.
|
|
172
|
+
input_type: Type of the input.
|
|
173
|
+
"""
|
|
174
|
+
if inspect.iscoroutinefunction(func):
|
|
175
|
+
afunc = func
|
|
176
|
+
func = None
|
|
177
|
+
|
|
178
|
+
super().__init__(func=func, afunc=afunc, input_type=input_type, **kwargs)
|
|
179
|
+
|
|
180
|
+
@classmethod
|
|
181
|
+
@override
|
|
182
|
+
def is_lc_serializable(cls) -> bool:
|
|
183
|
+
"""Return `True` as this class is serializable."""
|
|
184
|
+
return True
|
|
185
|
+
|
|
186
|
+
@classmethod
|
|
187
|
+
def get_lc_namespace(cls) -> list[str]:
|
|
188
|
+
"""Get the namespace of the Coco object.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
`["coco", "schema", "runnable"]`
|
|
192
|
+
"""
|
|
193
|
+
return ["coco", "schema", "runnable"]
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
@override
|
|
197
|
+
def InputType(self) -> Any:
|
|
198
|
+
return self.input_type or Any
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
@override
|
|
202
|
+
def OutputType(self) -> Any:
|
|
203
|
+
return self.input_type or Any
|
|
204
|
+
|
|
205
|
+
@classmethod
|
|
206
|
+
@override
|
|
207
|
+
def assign(
|
|
208
|
+
cls,
|
|
209
|
+
**kwargs: Runnable[dict[str, Any], Any]
|
|
210
|
+
| Callable[[dict[str, Any]], Any]
|
|
211
|
+
| Mapping[str, Runnable[dict[str, Any], Any] | Callable[[dict[str, Any]], Any]],
|
|
212
|
+
) -> RunnableAssign:
|
|
213
|
+
"""Merge the Dict input with the output produced by the mapping argument.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
**kwargs: `Runnable`, `Callable` or a `Mapping` from keys to `Runnable`
|
|
217
|
+
objects or `Callable`s.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
A `Runnable` that merges the `dict` input with the output produced by the
|
|
221
|
+
mapping argument.
|
|
222
|
+
"""
|
|
223
|
+
return RunnableAssign(RunnableParallel[dict[str, Any]](kwargs))
|
|
224
|
+
|
|
225
|
+
@override
|
|
226
|
+
def invoke(
|
|
227
|
+
self, input: Other, config: RunnableConfig | None = None, **kwargs: Any
|
|
228
|
+
) -> Other:
|
|
229
|
+
if self.func is not None:
|
|
230
|
+
call_func_with_variable_args(
|
|
231
|
+
self.func, input, ensure_config(config), **kwargs
|
|
232
|
+
)
|
|
233
|
+
return self._call_with_config(identity, input, config)
|
|
234
|
+
|
|
235
|
+
@override
|
|
236
|
+
async def ainvoke(
|
|
237
|
+
self,
|
|
238
|
+
input: Other,
|
|
239
|
+
config: RunnableConfig | None = None,
|
|
240
|
+
**kwargs: Any | None,
|
|
241
|
+
) -> Other:
|
|
242
|
+
if self.afunc is not None:
|
|
243
|
+
await acall_func_with_variable_args(
|
|
244
|
+
self.afunc, input, ensure_config(config), **kwargs
|
|
245
|
+
)
|
|
246
|
+
elif self.func is not None:
|
|
247
|
+
call_func_with_variable_args(
|
|
248
|
+
self.func, input, ensure_config(config), **kwargs
|
|
249
|
+
)
|
|
250
|
+
return await self._acall_with_config(aidentity, input, config)
|
|
251
|
+
|
|
252
|
+
@override
|
|
253
|
+
def transform(
|
|
254
|
+
self,
|
|
255
|
+
input: Iterator[Other],
|
|
256
|
+
config: RunnableConfig | None = None,
|
|
257
|
+
**kwargs: Any,
|
|
258
|
+
) -> Iterator[Other]:
|
|
259
|
+
if self.func is None:
|
|
260
|
+
for chunk in self._transform_stream_with_config(input, identity, config):
|
|
261
|
+
yield chunk
|
|
262
|
+
else:
|
|
263
|
+
final: Other
|
|
264
|
+
got_first_chunk = False
|
|
265
|
+
|
|
266
|
+
for chunk in self._transform_stream_with_config(input, identity, config):
|
|
267
|
+
yield chunk
|
|
268
|
+
|
|
269
|
+
if not got_first_chunk:
|
|
270
|
+
final = chunk
|
|
271
|
+
got_first_chunk = True
|
|
272
|
+
else:
|
|
273
|
+
try:
|
|
274
|
+
final = final + chunk # type: ignore[operator]
|
|
275
|
+
except TypeError:
|
|
276
|
+
final = chunk
|
|
277
|
+
|
|
278
|
+
if got_first_chunk:
|
|
279
|
+
call_func_with_variable_args(
|
|
280
|
+
self.func, final, ensure_config(config), **kwargs
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
@override
|
|
284
|
+
async def atransform(
|
|
285
|
+
self,
|
|
286
|
+
input: AsyncIterator[Other],
|
|
287
|
+
config: RunnableConfig | None = None,
|
|
288
|
+
**kwargs: Any,
|
|
289
|
+
) -> AsyncIterator[Other]:
|
|
290
|
+
if self.afunc is None and self.func is None:
|
|
291
|
+
async for chunk in self._atransform_stream_with_config(
|
|
292
|
+
input, identity, config
|
|
293
|
+
):
|
|
294
|
+
yield chunk
|
|
295
|
+
else:
|
|
296
|
+
got_first_chunk = False
|
|
297
|
+
|
|
298
|
+
async for chunk in self._atransform_stream_with_config(
|
|
299
|
+
input, identity, config
|
|
300
|
+
):
|
|
301
|
+
yield chunk
|
|
302
|
+
|
|
303
|
+
# By definitions, a function will operate on the aggregated
|
|
304
|
+
# input. So we'll aggregate the input until we get to the last
|
|
305
|
+
# chunk.
|
|
306
|
+
# If the input is not addable, then we'll assume that we can
|
|
307
|
+
# only operate on the last chunk.
|
|
308
|
+
if not got_first_chunk:
|
|
309
|
+
final = chunk
|
|
310
|
+
got_first_chunk = True
|
|
311
|
+
else:
|
|
312
|
+
try:
|
|
313
|
+
final = final + chunk # type: ignore[operator]
|
|
314
|
+
except TypeError:
|
|
315
|
+
final = chunk
|
|
316
|
+
|
|
317
|
+
if got_first_chunk:
|
|
318
|
+
config = ensure_config(config)
|
|
319
|
+
if self.afunc is not None:
|
|
320
|
+
await acall_func_with_variable_args(
|
|
321
|
+
self.afunc, final, config, **kwargs
|
|
322
|
+
)
|
|
323
|
+
elif self.func is not None:
|
|
324
|
+
call_func_with_variable_args(self.func, final, config, **kwargs)
|
|
325
|
+
|
|
326
|
+
@override
|
|
327
|
+
def stream(
|
|
328
|
+
self,
|
|
329
|
+
input: Other,
|
|
330
|
+
config: RunnableConfig | None = None,
|
|
331
|
+
**kwargs: Any,
|
|
332
|
+
) -> Iterator[Other]:
|
|
333
|
+
return self.transform(iter([input]), config, **kwargs)
|
|
334
|
+
|
|
335
|
+
@override
|
|
336
|
+
async def astream(
|
|
337
|
+
self,
|
|
338
|
+
input: Other,
|
|
339
|
+
config: RunnableConfig | None = None,
|
|
340
|
+
**kwargs: Any,
|
|
341
|
+
) -> AsyncIterator[Other]:
|
|
342
|
+
async def input_aiter() -> AsyncIterator[Other]:
|
|
343
|
+
yield input
|
|
344
|
+
|
|
345
|
+
async for chunk in self.atransform(input_aiter(), config, **kwargs):
|
|
346
|
+
yield chunk
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
_graph_passthrough = RunnablePassthrough[Any]()
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class RunnableAssign(RunnableSerializable[dict[str, Any], dict[str, Any]]):
|
|
353
|
+
"""Runnable that assigns key-value pairs to `dict[str, Any]` inputs.
|
|
354
|
+
|
|
355
|
+
The `RunnableAssign` class takes input dictionaries and, through a
|
|
356
|
+
`RunnableParallel` instance, applies transformations, then combines
|
|
357
|
+
these with the original data, introducing new key-value pairs based
|
|
358
|
+
on the mapper's logic.
|
|
359
|
+
|
|
360
|
+
Examples:
|
|
361
|
+
```python
|
|
362
|
+
# This is a RunnableAssign
|
|
363
|
+
from coco_core.runnables.passthrough import (
|
|
364
|
+
RunnableAssign,
|
|
365
|
+
RunnableParallel,
|
|
366
|
+
)
|
|
367
|
+
from coco_core.runnables.base import RunnableLambda
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def add_ten(x: dict[str, int]) -> dict[str, int]:
|
|
371
|
+
return {"added": x["input"] + 10}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
mapper = RunnableParallel(
|
|
375
|
+
{
|
|
376
|
+
"add_step": RunnableLambda(add_ten),
|
|
377
|
+
}
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
runnable_assign = RunnableAssign(mapper)
|
|
381
|
+
|
|
382
|
+
# Synchronous example
|
|
383
|
+
runnable_assign.invoke({"input": 5})
|
|
384
|
+
# returns {'input': 5, 'add_step': {'added': 15}}
|
|
385
|
+
|
|
386
|
+
# Asynchronous example
|
|
387
|
+
await runnable_assign.ainvoke({"input": 5})
|
|
388
|
+
# returns {'input': 5, 'add_step': {'added': 15}}
|
|
389
|
+
```
|
|
390
|
+
"""
|
|
391
|
+
|
|
392
|
+
# Ideally we would type mapper as RunnableParallel[dict[str, Any]]
|
|
393
|
+
# but this fails validation for Pydantic <2.10
|
|
394
|
+
mapper: RunnableParallel # type: ignore[type-arg]
|
|
395
|
+
|
|
396
|
+
def __init__(self, mapper: RunnableParallel[dict[str, Any]], **kwargs: Any) -> None:
|
|
397
|
+
"""Create a `RunnableAssign`.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
mapper: A `RunnableParallel` instance that will be used to transform the
|
|
401
|
+
input dictionary.
|
|
402
|
+
"""
|
|
403
|
+
super().__init__(mapper=mapper, **kwargs)
|
|
404
|
+
|
|
405
|
+
@classmethod
|
|
406
|
+
@override
|
|
407
|
+
def is_lc_serializable(cls) -> bool:
|
|
408
|
+
"""Return `True` as this class is serializable."""
|
|
409
|
+
return True
|
|
410
|
+
|
|
411
|
+
@classmethod
|
|
412
|
+
@override
|
|
413
|
+
def get_lc_namespace(cls) -> list[str]:
|
|
414
|
+
"""Get the namespace of the Coco object.
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
`["coco", "schema", "runnable"]`
|
|
418
|
+
"""
|
|
419
|
+
return ["coco", "schema", "runnable"]
|
|
420
|
+
|
|
421
|
+
@override
|
|
422
|
+
def get_name(self, suffix: str | None = None, *, name: str | None = None) -> str:
|
|
423
|
+
name = (
|
|
424
|
+
name
|
|
425
|
+
or self.name
|
|
426
|
+
or f"RunnableAssign<{','.join(self.mapper.steps__.keys())}>"
|
|
427
|
+
)
|
|
428
|
+
return super().get_name(suffix, name=name)
|
|
429
|
+
|
|
430
|
+
@override
|
|
431
|
+
def get_input_schema(self, config: RunnableConfig | None = None) -> TypeBaseModel:
|
|
432
|
+
map_input_schema = self.mapper.get_input_schema(config)
|
|
433
|
+
if not issubclass(map_input_schema, RootModel):
|
|
434
|
+
# ie. it's a dict
|
|
435
|
+
return map_input_schema
|
|
436
|
+
|
|
437
|
+
return super().get_input_schema(config)
|
|
438
|
+
|
|
439
|
+
@override
|
|
440
|
+
def get_output_schema(self, config: RunnableConfig | None = None) -> TypeBaseModel:
|
|
441
|
+
# The return type stays `TypeBaseModel` (rather than narrowing to
|
|
442
|
+
# `type[BaseModel]` as `RunnableParallel.get_output_schema` does) because
|
|
443
|
+
# the fallback branches return the mapper's output schema or delegate to
|
|
444
|
+
# `super().get_output_schema()`, either of which may be a Pydantic v1 model.
|
|
445
|
+
map_input_schema = self.mapper.get_input_schema(config)
|
|
446
|
+
map_output_schema = self.mapper.get_output_schema(config)
|
|
447
|
+
if not issubclass(map_input_schema, RootModel) and not issubclass(
|
|
448
|
+
map_output_schema, RootModel
|
|
449
|
+
):
|
|
450
|
+
fields = {}
|
|
451
|
+
|
|
452
|
+
for name, field_info in get_fields(map_input_schema).items():
|
|
453
|
+
fields[name] = _get_schema_field_definition(field_info)
|
|
454
|
+
|
|
455
|
+
for name, field_info in get_fields(map_output_schema).items():
|
|
456
|
+
fields[name] = _get_schema_field_definition(field_info)
|
|
457
|
+
|
|
458
|
+
return create_model_v2("RunnableAssignOutput", field_definitions=fields)
|
|
459
|
+
if not issubclass(map_output_schema, RootModel):
|
|
460
|
+
# ie. only map output is a dict
|
|
461
|
+
# ie. input type is either unknown or inferred incorrectly
|
|
462
|
+
return map_output_schema
|
|
463
|
+
|
|
464
|
+
return super().get_output_schema(config)
|
|
465
|
+
|
|
466
|
+
@property
|
|
467
|
+
@override
|
|
468
|
+
def config_specs(self) -> list[ConfigurableFieldSpec]:
|
|
469
|
+
return self.mapper.config_specs
|
|
470
|
+
|
|
471
|
+
@override
|
|
472
|
+
def get_graph(self, config: RunnableConfig | None = None) -> Graph:
|
|
473
|
+
# get graph from mapper
|
|
474
|
+
graph = self.mapper.get_graph(config)
|
|
475
|
+
# add passthrough node and edges
|
|
476
|
+
input_node = graph.first_node()
|
|
477
|
+
output_node = graph.last_node()
|
|
478
|
+
if input_node is not None and output_node is not None:
|
|
479
|
+
passthrough_node = graph.add_node(_graph_passthrough)
|
|
480
|
+
graph.add_edge(input_node, passthrough_node)
|
|
481
|
+
graph.add_edge(passthrough_node, output_node)
|
|
482
|
+
return graph
|
|
483
|
+
|
|
484
|
+
def _invoke(
|
|
485
|
+
self,
|
|
486
|
+
value: dict[str, Any],
|
|
487
|
+
run_manager: CallbackManagerForChainRun,
|
|
488
|
+
config: RunnableConfig,
|
|
489
|
+
**kwargs: Any,
|
|
490
|
+
) -> dict[str, Any]:
|
|
491
|
+
if not isinstance(value, dict):
|
|
492
|
+
msg = "The input to RunnablePassthrough.assign() must be a dict." # type: ignore[unreachable]
|
|
493
|
+
raise ValueError(msg) # noqa: TRY004
|
|
494
|
+
|
|
495
|
+
return {
|
|
496
|
+
**value,
|
|
497
|
+
**self.mapper.invoke(
|
|
498
|
+
value,
|
|
499
|
+
patch_config(config, callbacks=run_manager.get_child()),
|
|
500
|
+
**kwargs,
|
|
501
|
+
),
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
@override
|
|
505
|
+
def invoke(
|
|
506
|
+
self,
|
|
507
|
+
input: dict[str, Any],
|
|
508
|
+
config: RunnableConfig | None = None,
|
|
509
|
+
**kwargs: Any,
|
|
510
|
+
) -> dict[str, Any]:
|
|
511
|
+
return self._call_with_config(self._invoke, input, config, **kwargs)
|
|
512
|
+
|
|
513
|
+
async def _ainvoke(
|
|
514
|
+
self,
|
|
515
|
+
value: dict[str, Any],
|
|
516
|
+
run_manager: AsyncCallbackManagerForChainRun,
|
|
517
|
+
config: RunnableConfig,
|
|
518
|
+
**kwargs: Any,
|
|
519
|
+
) -> dict[str, Any]:
|
|
520
|
+
if not isinstance(value, dict):
|
|
521
|
+
msg = "The input to RunnablePassthrough.assign() must be a dict." # type: ignore[unreachable]
|
|
522
|
+
raise ValueError(msg) # noqa: TRY004
|
|
523
|
+
|
|
524
|
+
return {
|
|
525
|
+
**value,
|
|
526
|
+
**await self.mapper.ainvoke(
|
|
527
|
+
value,
|
|
528
|
+
patch_config(config, callbacks=run_manager.get_child()),
|
|
529
|
+
**kwargs,
|
|
530
|
+
),
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
@override
|
|
534
|
+
async def ainvoke(
|
|
535
|
+
self,
|
|
536
|
+
input: dict[str, Any],
|
|
537
|
+
config: RunnableConfig | None = None,
|
|
538
|
+
**kwargs: Any,
|
|
539
|
+
) -> dict[str, Any]:
|
|
540
|
+
return await self._acall_with_config(self._ainvoke, input, config, **kwargs)
|
|
541
|
+
|
|
542
|
+
def _transform(
|
|
543
|
+
self,
|
|
544
|
+
values: Iterator[dict[str, Any]],
|
|
545
|
+
run_manager: CallbackManagerForChainRun,
|
|
546
|
+
config: RunnableConfig,
|
|
547
|
+
**kwargs: Any,
|
|
548
|
+
) -> Iterator[dict[str, Any]]:
|
|
549
|
+
# collect mapper keys
|
|
550
|
+
mapper_keys = set(self.mapper.steps__.keys())
|
|
551
|
+
# create two streams, one for the map and one for the passthrough
|
|
552
|
+
for_passthrough, for_map = safetee(values, 2, lock=threading.Lock())
|
|
553
|
+
|
|
554
|
+
# create map output stream
|
|
555
|
+
map_output = self.mapper.transform(
|
|
556
|
+
for_map,
|
|
557
|
+
patch_config(
|
|
558
|
+
config,
|
|
559
|
+
callbacks=run_manager.get_child(),
|
|
560
|
+
),
|
|
561
|
+
**kwargs,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
# get executor to start map output stream in background
|
|
565
|
+
with get_executor_for_config(config) as executor:
|
|
566
|
+
# start map output stream
|
|
567
|
+
first_map_chunk_future = executor.submit(
|
|
568
|
+
next,
|
|
569
|
+
map_output,
|
|
570
|
+
None,
|
|
571
|
+
)
|
|
572
|
+
# consume passthrough stream
|
|
573
|
+
for chunk in for_passthrough:
|
|
574
|
+
if not isinstance(chunk, dict):
|
|
575
|
+
msg = "The input to RunnablePassthrough.assign() must be a dict." # type: ignore[unreachable]
|
|
576
|
+
raise ValueError(msg) # noqa: TRY004
|
|
577
|
+
# remove mapper keys from passthrough chunk, to be overwritten by map
|
|
578
|
+
filtered = AddableDict(
|
|
579
|
+
{k: v for k, v in chunk.items() if k not in mapper_keys}
|
|
580
|
+
)
|
|
581
|
+
if filtered:
|
|
582
|
+
yield filtered
|
|
583
|
+
# yield map output
|
|
584
|
+
first_chunk = first_map_chunk_future.result()
|
|
585
|
+
if first_chunk is not None:
|
|
586
|
+
yield first_chunk
|
|
587
|
+
for chunk in map_output:
|
|
588
|
+
yield chunk
|
|
589
|
+
|
|
590
|
+
@override
|
|
591
|
+
def transform(
|
|
592
|
+
self,
|
|
593
|
+
input: Iterator[dict[str, Any]],
|
|
594
|
+
config: RunnableConfig | None = None,
|
|
595
|
+
**kwargs: Any | None,
|
|
596
|
+
) -> Iterator[dict[str, Any]]:
|
|
597
|
+
yield from self._transform_stream_with_config(
|
|
598
|
+
input, self._transform, config, **kwargs
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
async def _atransform(
|
|
602
|
+
self,
|
|
603
|
+
values: AsyncIterator[dict[str, Any]],
|
|
604
|
+
run_manager: AsyncCallbackManagerForChainRun,
|
|
605
|
+
config: RunnableConfig,
|
|
606
|
+
**kwargs: Any,
|
|
607
|
+
) -> AsyncIterator[dict[str, Any]]:
|
|
608
|
+
# collect mapper keys
|
|
609
|
+
mapper_keys = set(self.mapper.steps__.keys())
|
|
610
|
+
# create two streams, one for the map and one for the passthrough
|
|
611
|
+
for_passthrough, for_map = atee(values, 2, lock=asyncio.Lock())
|
|
612
|
+
# create map output stream
|
|
613
|
+
map_output = self.mapper.atransform(
|
|
614
|
+
for_map,
|
|
615
|
+
patch_config(
|
|
616
|
+
config,
|
|
617
|
+
callbacks=run_manager.get_child(),
|
|
618
|
+
),
|
|
619
|
+
**kwargs,
|
|
620
|
+
)
|
|
621
|
+
# start map output stream
|
|
622
|
+
first_map_chunk_task = asyncio.create_task(
|
|
623
|
+
anext(map_output, None),
|
|
624
|
+
)
|
|
625
|
+
# consume passthrough stream
|
|
626
|
+
async for chunk in for_passthrough:
|
|
627
|
+
if not isinstance(chunk, dict):
|
|
628
|
+
msg = "The input to RunnablePassthrough.assign() must be a dict." # type: ignore[unreachable]
|
|
629
|
+
raise ValueError(msg) # noqa: TRY004
|
|
630
|
+
|
|
631
|
+
# remove mapper keys from passthrough chunk, to be overwritten by map output
|
|
632
|
+
filtered = AddableDict(
|
|
633
|
+
{k: v for k, v in chunk.items() if k not in mapper_keys}
|
|
634
|
+
)
|
|
635
|
+
if filtered:
|
|
636
|
+
yield filtered
|
|
637
|
+
# yield map output
|
|
638
|
+
first_chunk = await first_map_chunk_task
|
|
639
|
+
if first_chunk is not None:
|
|
640
|
+
yield first_chunk
|
|
641
|
+
async for chunk in map_output:
|
|
642
|
+
yield chunk
|
|
643
|
+
|
|
644
|
+
@override
|
|
645
|
+
async def atransform(
|
|
646
|
+
self,
|
|
647
|
+
input: AsyncIterator[dict[str, Any]],
|
|
648
|
+
config: RunnableConfig | None = None,
|
|
649
|
+
**kwargs: Any,
|
|
650
|
+
) -> AsyncIterator[dict[str, Any]]:
|
|
651
|
+
async for chunk in self._atransform_stream_with_config(
|
|
652
|
+
input, self._atransform, config, **kwargs
|
|
653
|
+
):
|
|
654
|
+
yield chunk
|
|
655
|
+
|
|
656
|
+
@override
|
|
657
|
+
def stream(
|
|
658
|
+
self,
|
|
659
|
+
input: dict[str, Any],
|
|
660
|
+
config: RunnableConfig | None = None,
|
|
661
|
+
**kwargs: Any,
|
|
662
|
+
) -> Iterator[dict[str, Any]]:
|
|
663
|
+
return self.transform(iter([input]), config, **kwargs)
|
|
664
|
+
|
|
665
|
+
@override
|
|
666
|
+
async def astream(
|
|
667
|
+
self,
|
|
668
|
+
input: dict[str, Any],
|
|
669
|
+
config: RunnableConfig | None = None,
|
|
670
|
+
**kwargs: Any,
|
|
671
|
+
) -> AsyncIterator[dict[str, Any]]:
|
|
672
|
+
async def input_aiter() -> AsyncIterator[dict[str, Any]]:
|
|
673
|
+
yield input
|
|
674
|
+
|
|
675
|
+
async for chunk in self.atransform(input_aiter(), config, **kwargs):
|
|
676
|
+
yield chunk
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
class RunnablePick(RunnableSerializable[dict[str, Any], Any]):
|
|
680
|
+
"""`Runnable` that picks keys from `dict[str, Any]` inputs.
|
|
681
|
+
|
|
682
|
+
`RunnablePick` class represents a `Runnable` that selectively picks keys from a
|
|
683
|
+
dictionary input. It allows you to specify one or more keys to extract
|
|
684
|
+
from the input dictionary.
|
|
685
|
+
|
|
686
|
+
!!! note "Return Type Behavior"
|
|
687
|
+
The return type depends on the `keys` parameter:
|
|
688
|
+
|
|
689
|
+
- When `keys` is a `str`: Returns the single value associated with that key
|
|
690
|
+
- When `keys` is a `list`: Returns a dictionary containing only the selected
|
|
691
|
+
keys
|
|
692
|
+
|
|
693
|
+
Example:
|
|
694
|
+
```python
|
|
695
|
+
from coco_core.runnables.passthrough import RunnablePick
|
|
696
|
+
|
|
697
|
+
input_data = {
|
|
698
|
+
"name": "John",
|
|
699
|
+
"age": 30,
|
|
700
|
+
"city": "New York",
|
|
701
|
+
"country": "USA",
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
# Single key - returns the value directly
|
|
705
|
+
runnable_single = RunnablePick(keys="name")
|
|
706
|
+
result_single = runnable_single.invoke(input_data)
|
|
707
|
+
print(result_single) # Output: "John"
|
|
708
|
+
|
|
709
|
+
# Multiple keys - returns a dictionary
|
|
710
|
+
runnable_multiple = RunnablePick(keys=["name", "age"])
|
|
711
|
+
result_multiple = runnable_multiple.invoke(input_data)
|
|
712
|
+
print(result_multiple) # Output: {'name': 'John', 'age': 30}
|
|
713
|
+
```
|
|
714
|
+
"""
|
|
715
|
+
|
|
716
|
+
keys: str | list[str]
|
|
717
|
+
|
|
718
|
+
def __init__(self, keys: str | list[str], **kwargs: Any) -> None:
|
|
719
|
+
"""Create a `RunnablePick`.
|
|
720
|
+
|
|
721
|
+
Args:
|
|
722
|
+
keys: A single key or a list of keys to pick from the input dictionary.
|
|
723
|
+
"""
|
|
724
|
+
super().__init__(keys=keys, **kwargs)
|
|
725
|
+
|
|
726
|
+
@classmethod
|
|
727
|
+
@override
|
|
728
|
+
def is_lc_serializable(cls) -> bool:
|
|
729
|
+
"""Return `True` as this class is serializable."""
|
|
730
|
+
return True
|
|
731
|
+
|
|
732
|
+
@classmethod
|
|
733
|
+
@override
|
|
734
|
+
def get_lc_namespace(cls) -> list[str]:
|
|
735
|
+
"""Get the namespace of the Coco object.
|
|
736
|
+
|
|
737
|
+
Returns:
|
|
738
|
+
`["coco", "schema", "runnable"]`
|
|
739
|
+
"""
|
|
740
|
+
return ["coco", "schema", "runnable"]
|
|
741
|
+
|
|
742
|
+
@override
|
|
743
|
+
def get_name(self, suffix: str | None = None, *, name: str | None = None) -> str:
|
|
744
|
+
name = (
|
|
745
|
+
name
|
|
746
|
+
or self.name
|
|
747
|
+
or "RunnablePick"
|
|
748
|
+
f"<{','.join([self.keys] if isinstance(self.keys, str) else self.keys)}>"
|
|
749
|
+
)
|
|
750
|
+
return super().get_name(suffix, name=name)
|
|
751
|
+
|
|
752
|
+
def _pick(self, value: dict[str, Any]) -> Any:
|
|
753
|
+
if not isinstance(value, dict):
|
|
754
|
+
msg = "The input to RunnablePassthrough.assign() must be a dict." # type: ignore[unreachable]
|
|
755
|
+
raise ValueError(msg) # noqa: TRY004
|
|
756
|
+
|
|
757
|
+
if isinstance(self.keys, str):
|
|
758
|
+
return value.get(self.keys)
|
|
759
|
+
picked = {k: value.get(k) for k in self.keys if k in value}
|
|
760
|
+
if picked:
|
|
761
|
+
return AddableDict(picked)
|
|
762
|
+
return None
|
|
763
|
+
|
|
764
|
+
@override
|
|
765
|
+
def invoke(
|
|
766
|
+
self,
|
|
767
|
+
input: dict[str, Any],
|
|
768
|
+
config: RunnableConfig | None = None,
|
|
769
|
+
**kwargs: Any,
|
|
770
|
+
) -> Any:
|
|
771
|
+
return self._call_with_config(self._pick, input, config, **kwargs)
|
|
772
|
+
|
|
773
|
+
async def _ainvoke(
|
|
774
|
+
self,
|
|
775
|
+
value: dict[str, Any],
|
|
776
|
+
) -> Any:
|
|
777
|
+
return self._pick(value)
|
|
778
|
+
|
|
779
|
+
@override
|
|
780
|
+
async def ainvoke(
|
|
781
|
+
self,
|
|
782
|
+
input: dict[str, Any],
|
|
783
|
+
config: RunnableConfig | None = None,
|
|
784
|
+
**kwargs: Any,
|
|
785
|
+
) -> Any:
|
|
786
|
+
return await self._acall_with_config(self._ainvoke, input, config, **kwargs)
|
|
787
|
+
|
|
788
|
+
def _transform(
|
|
789
|
+
self,
|
|
790
|
+
chunks: Iterator[dict[str, Any]],
|
|
791
|
+
) -> Iterator[Any]:
|
|
792
|
+
for chunk in chunks:
|
|
793
|
+
picked = self._pick(chunk)
|
|
794
|
+
if picked is not None:
|
|
795
|
+
yield picked
|
|
796
|
+
|
|
797
|
+
@override
|
|
798
|
+
def transform(
|
|
799
|
+
self,
|
|
800
|
+
input: Iterator[dict[str, Any]],
|
|
801
|
+
config: RunnableConfig | None = None,
|
|
802
|
+
**kwargs: Any,
|
|
803
|
+
) -> Iterator[Any]:
|
|
804
|
+
yield from self._transform_stream_with_config(
|
|
805
|
+
input, self._transform, config, **kwargs
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
async def _atransform(
|
|
809
|
+
self,
|
|
810
|
+
chunks: AsyncIterator[dict[str, Any]],
|
|
811
|
+
) -> AsyncIterator[Any]:
|
|
812
|
+
async for chunk in chunks:
|
|
813
|
+
picked = self._pick(chunk)
|
|
814
|
+
if picked is not None:
|
|
815
|
+
yield picked
|
|
816
|
+
|
|
817
|
+
@override
|
|
818
|
+
async def atransform(
|
|
819
|
+
self,
|
|
820
|
+
input: AsyncIterator[dict[str, Any]],
|
|
821
|
+
config: RunnableConfig | None = None,
|
|
822
|
+
**kwargs: Any,
|
|
823
|
+
) -> AsyncIterator[Any]:
|
|
824
|
+
async for chunk in self._atransform_stream_with_config(
|
|
825
|
+
input, self._atransform, config, **kwargs
|
|
826
|
+
):
|
|
827
|
+
yield chunk
|
|
828
|
+
|
|
829
|
+
@override
|
|
830
|
+
def stream(
|
|
831
|
+
self,
|
|
832
|
+
input: dict[str, Any],
|
|
833
|
+
config: RunnableConfig | None = None,
|
|
834
|
+
**kwargs: Any,
|
|
835
|
+
) -> Iterator[Any]:
|
|
836
|
+
return self.transform(iter([input]), config, **kwargs)
|
|
837
|
+
|
|
838
|
+
@override
|
|
839
|
+
async def astream(
|
|
840
|
+
self,
|
|
841
|
+
input: dict[str, Any],
|
|
842
|
+
config: RunnableConfig | None = None,
|
|
843
|
+
**kwargs: Any,
|
|
844
|
+
) -> AsyncIterator[Any]:
|
|
845
|
+
async def input_aiter() -> AsyncIterator[dict[str, Any]]:
|
|
846
|
+
yield input
|
|
847
|
+
|
|
848
|
+
async for chunk in self.atransform(input_aiter(), config, **kwargs):
|
|
849
|
+
yield chunk
|