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,192 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Sequence
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
NamedTuple,
|
|
7
|
+
TypeVar,
|
|
8
|
+
cast,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from coco_core.runnables import Runnable, RunnableConfig
|
|
12
|
+
|
|
13
|
+
from coco_graph._internal._constants import CONF, CONFIG_KEY_SEND, TASKS
|
|
14
|
+
from coco_graph._internal._runnable import RunnableCallable
|
|
15
|
+
from coco_graph._internal._typing import MISSING
|
|
16
|
+
from coco_graph.errors import InvalidUpdateError
|
|
17
|
+
from coco_graph.types import Send
|
|
18
|
+
|
|
19
|
+
TYPE_SEND = Callable[[Sequence[tuple[str, Any]]], None]
|
|
20
|
+
R = TypeVar("R", bound=Runnable)
|
|
21
|
+
|
|
22
|
+
SKIP_WRITE = object()
|
|
23
|
+
PASSTHROUGH = object()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ChannelWriteEntry(NamedTuple):
|
|
27
|
+
channel: str
|
|
28
|
+
"""Channel name to write to."""
|
|
29
|
+
value: Any = PASSTHROUGH
|
|
30
|
+
"""Value to write, or PASSTHROUGH to use the input."""
|
|
31
|
+
skip_none: bool = False
|
|
32
|
+
"""Whether to skip writing if the value is None."""
|
|
33
|
+
mapper: Callable | None = None
|
|
34
|
+
"""Function to transform the value before writing."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ChannelWriteTupleEntry(NamedTuple):
|
|
38
|
+
mapper: Callable[[Any], Sequence[tuple[str, Any]] | None]
|
|
39
|
+
"""Function to extract tuples from value."""
|
|
40
|
+
value: Any = PASSTHROUGH
|
|
41
|
+
"""Value to write, or PASSTHROUGH to use the input."""
|
|
42
|
+
static: Sequence[tuple[str, Any, str | None]] | None = None
|
|
43
|
+
"""Optional, declared writes for static analysis."""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ChannelWrite(RunnableCallable):
|
|
47
|
+
"""Implements the logic for sending writes to CONFIG_KEY_SEND.
|
|
48
|
+
Can be used as a runnable or as a static method to call imperatively."""
|
|
49
|
+
|
|
50
|
+
writes: list[ChannelWriteEntry | ChannelWriteTupleEntry | Send]
|
|
51
|
+
"""Sequence of write entries or Send objects to write."""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
writes: Sequence[ChannelWriteEntry | ChannelWriteTupleEntry | Send],
|
|
56
|
+
*,
|
|
57
|
+
tags: Sequence[str] | None = None,
|
|
58
|
+
):
|
|
59
|
+
super().__init__(
|
|
60
|
+
func=self._write,
|
|
61
|
+
afunc=self._awrite,
|
|
62
|
+
name=None,
|
|
63
|
+
tags=tags,
|
|
64
|
+
trace=False,
|
|
65
|
+
)
|
|
66
|
+
self.writes = cast(
|
|
67
|
+
list[ChannelWriteEntry | ChannelWriteTupleEntry | Send], writes
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def get_name(self, suffix: str | None = None, *, name: str | None = None) -> str:
|
|
71
|
+
if not name:
|
|
72
|
+
name = f"ChannelWrite<{','.join(w.channel if isinstance(w, ChannelWriteEntry) else '...' if isinstance(w, ChannelWriteTupleEntry) else w.node for w in self.writes)}>"
|
|
73
|
+
return super().get_name(suffix, name=name)
|
|
74
|
+
|
|
75
|
+
def _write(self, input: Any, config: RunnableConfig) -> None:
|
|
76
|
+
writes = [
|
|
77
|
+
ChannelWriteEntry(write.channel, input, write.skip_none, write.mapper)
|
|
78
|
+
if isinstance(write, ChannelWriteEntry) and write.value is PASSTHROUGH
|
|
79
|
+
else ChannelWriteTupleEntry(write.mapper, input)
|
|
80
|
+
if isinstance(write, ChannelWriteTupleEntry) and write.value is PASSTHROUGH
|
|
81
|
+
else write
|
|
82
|
+
for write in self.writes
|
|
83
|
+
]
|
|
84
|
+
self.do_write(
|
|
85
|
+
config,
|
|
86
|
+
writes,
|
|
87
|
+
)
|
|
88
|
+
return input
|
|
89
|
+
|
|
90
|
+
async def _awrite(self, input: Any, config: RunnableConfig) -> None:
|
|
91
|
+
writes = [
|
|
92
|
+
ChannelWriteEntry(write.channel, input, write.skip_none, write.mapper)
|
|
93
|
+
if isinstance(write, ChannelWriteEntry) and write.value is PASSTHROUGH
|
|
94
|
+
else ChannelWriteTupleEntry(write.mapper, input)
|
|
95
|
+
if isinstance(write, ChannelWriteTupleEntry) and write.value is PASSTHROUGH
|
|
96
|
+
else write
|
|
97
|
+
for write in self.writes
|
|
98
|
+
]
|
|
99
|
+
self.do_write(
|
|
100
|
+
config,
|
|
101
|
+
writes,
|
|
102
|
+
)
|
|
103
|
+
return input
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def do_write(
|
|
107
|
+
config: RunnableConfig,
|
|
108
|
+
writes: Sequence[ChannelWriteEntry | ChannelWriteTupleEntry | Send],
|
|
109
|
+
allow_passthrough: bool = True,
|
|
110
|
+
) -> None:
|
|
111
|
+
# validate
|
|
112
|
+
for w in writes:
|
|
113
|
+
if isinstance(w, ChannelWriteEntry):
|
|
114
|
+
if w.channel == TASKS:
|
|
115
|
+
raise InvalidUpdateError(
|
|
116
|
+
"Cannot write to the reserved channel TASKS"
|
|
117
|
+
)
|
|
118
|
+
if w.value is PASSTHROUGH and not allow_passthrough:
|
|
119
|
+
raise InvalidUpdateError("PASSTHROUGH value must be replaced")
|
|
120
|
+
if isinstance(w, ChannelWriteTupleEntry):
|
|
121
|
+
if w.value is PASSTHROUGH and not allow_passthrough:
|
|
122
|
+
raise InvalidUpdateError("PASSTHROUGH value must be replaced")
|
|
123
|
+
# if we want to persist writes found before hitting a ParentCommand
|
|
124
|
+
# can move this to a finally block
|
|
125
|
+
write: TYPE_SEND = config[CONF][CONFIG_KEY_SEND]
|
|
126
|
+
write(_assemble_writes(writes))
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def is_writer(runnable: Runnable) -> bool:
|
|
130
|
+
"""Used by PregelNode to distinguish between writers and other runnables."""
|
|
131
|
+
return (
|
|
132
|
+
isinstance(runnable, ChannelWrite)
|
|
133
|
+
or getattr(runnable, "_is_channel_writer", MISSING) is not MISSING
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def get_static_writes(
|
|
138
|
+
runnable: Runnable,
|
|
139
|
+
) -> Sequence[tuple[str, Any, str | None]] | None:
|
|
140
|
+
"""Used to get conditional writes a writer declares for static analysis."""
|
|
141
|
+
if isinstance(runnable, ChannelWrite):
|
|
142
|
+
return [
|
|
143
|
+
w
|
|
144
|
+
for entry in runnable.writes
|
|
145
|
+
if isinstance(entry, ChannelWriteTupleEntry) and entry.static
|
|
146
|
+
for w in entry.static
|
|
147
|
+
] or None
|
|
148
|
+
elif writes := getattr(runnable, "_is_channel_writer", MISSING):
|
|
149
|
+
if writes is not MISSING:
|
|
150
|
+
writes = cast(
|
|
151
|
+
Sequence[tuple[ChannelWriteEntry | Send, str | None]],
|
|
152
|
+
writes,
|
|
153
|
+
)
|
|
154
|
+
entries = [e for e, _ in writes]
|
|
155
|
+
labels = [la for _, la in writes]
|
|
156
|
+
return [(*t, la) for t, la in zip(_assemble_writes(entries), labels)]
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def register_writer(
|
|
160
|
+
runnable: R,
|
|
161
|
+
static: Sequence[tuple[ChannelWriteEntry | Send, str | None]] | None = None,
|
|
162
|
+
) -> R:
|
|
163
|
+
"""Used to mark a runnable as a writer, so that it can be detected by is_writer.
|
|
164
|
+
Instances of ChannelWrite are automatically marked as writers.
|
|
165
|
+
Optionally, a list of declared writes can be passed for static analysis."""
|
|
166
|
+
# using object.__setattr__ to work around objects that override __setattr__
|
|
167
|
+
# eg. pydantic models and dataclasses
|
|
168
|
+
object.__setattr__(runnable, "_is_channel_writer", static)
|
|
169
|
+
return runnable
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _assemble_writes(
|
|
173
|
+
writes: Sequence[ChannelWriteEntry | ChannelWriteTupleEntry | Send],
|
|
174
|
+
) -> list[tuple[str, Any]]:
|
|
175
|
+
"""Assembles the writes into a list of tuples."""
|
|
176
|
+
tuples: list[tuple[str, Any]] = []
|
|
177
|
+
for w in writes:
|
|
178
|
+
if isinstance(w, Send):
|
|
179
|
+
tuples.append((TASKS, w))
|
|
180
|
+
elif isinstance(w, ChannelWriteTupleEntry):
|
|
181
|
+
if ww := w.mapper(w.value):
|
|
182
|
+
tuples.extend(ww)
|
|
183
|
+
elif isinstance(w, ChannelWriteEntry):
|
|
184
|
+
value = w.mapper(w.value) if w.mapper is not None else w.value
|
|
185
|
+
if value is SKIP_WRITE:
|
|
186
|
+
continue
|
|
187
|
+
if w.skip_none and value is None:
|
|
188
|
+
continue
|
|
189
|
+
tuples.append((w.channel, value))
|
|
190
|
+
else:
|
|
191
|
+
raise ValueError(f"Invalid write entry: {w}")
|
|
192
|
+
return tuples
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable, Iterator, Mapping, Sequence
|
|
4
|
+
from dataclasses import asdict
|
|
5
|
+
from typing import Any
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from coco_core.runnables import RunnableConfig
|
|
9
|
+
from coco_graph.checkpoint.base import (
|
|
10
|
+
EXCLUDED_METADATA_KEYS,
|
|
11
|
+
CheckpointMetadata,
|
|
12
|
+
PendingWrite,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from coco_graph._internal._config import filter_to_user_tags, patch_checkpoint_map
|
|
16
|
+
from coco_graph._internal._constants import (
|
|
17
|
+
CONF,
|
|
18
|
+
CONFIG_KEY_CHECKPOINT_NS,
|
|
19
|
+
ERROR,
|
|
20
|
+
INTERRUPT,
|
|
21
|
+
NS_END,
|
|
22
|
+
NS_SEP,
|
|
23
|
+
RETURN,
|
|
24
|
+
)
|
|
25
|
+
from coco_graph._internal._typing import MISSING
|
|
26
|
+
from coco_graph.channels.base import BaseChannel
|
|
27
|
+
from coco_graph.constants import TAG_HIDDEN
|
|
28
|
+
from coco_graph.pregel._io import read_channels
|
|
29
|
+
from coco_graph.types import (
|
|
30
|
+
CheckpointPayload,
|
|
31
|
+
PregelExecutableTask,
|
|
32
|
+
PregelTask,
|
|
33
|
+
StateSnapshot,
|
|
34
|
+
TaskPayload,
|
|
35
|
+
TaskResultPayload,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
TASK_NAMESPACE = UUID("6ba7b831-9dad-11d1-80b4-00c04fd430c8")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def map_debug_tasks(tasks: Iterable[PregelExecutableTask]) -> Iterator[TaskPayload]:
|
|
42
|
+
"""Produce "task" events for stream_mode=debug."""
|
|
43
|
+
for task in tasks:
|
|
44
|
+
if task.config is not None and TAG_HIDDEN in task.config.get("tags", []):
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
payload: TaskPayload = {
|
|
48
|
+
"id": task.id,
|
|
49
|
+
"name": task.name,
|
|
50
|
+
"input": task.input,
|
|
51
|
+
"triggers": task.triggers,
|
|
52
|
+
}
|
|
53
|
+
# Forward user-meaningful metadata only — drop coco_graph's internal
|
|
54
|
+
# framework keys (coco_graph_node/step/triggers/path/checkpoint_ns,
|
|
55
|
+
# thread_id, ...), which are redundant with the task's own fields and
|
|
56
|
+
# namespace. Keys like `lc_agent_name`, `ls_integration`, and any
|
|
57
|
+
# user-supplied metadata ride along. Filtered config tags are folded in
|
|
58
|
+
# under `tags`, mirroring the messages stream handler. (The comprehension
|
|
59
|
+
# also yields a fresh dict, so mutating `md` doesn't touch task.config.)
|
|
60
|
+
if task.config is not None:
|
|
61
|
+
md = {
|
|
62
|
+
k: v
|
|
63
|
+
for k, v in (task.config.get("metadata") or {}).items()
|
|
64
|
+
if k not in EXCLUDED_METADATA_KEYS
|
|
65
|
+
}
|
|
66
|
+
filtered_tags = filter_to_user_tags(task.config.get("tags"))
|
|
67
|
+
if filtered_tags is not None:
|
|
68
|
+
md["tags"] = filtered_tags
|
|
69
|
+
if md:
|
|
70
|
+
payload["metadata"] = md
|
|
71
|
+
yield payload
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def is_multiple_channel_write(value: Any) -> bool:
|
|
75
|
+
"""Return True if the payload already wraps multiple writes from the same channel."""
|
|
76
|
+
return (
|
|
77
|
+
isinstance(value, dict)
|
|
78
|
+
and "$writes" in value
|
|
79
|
+
and isinstance(value["$writes"], list)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def map_task_result_writes(writes: Sequence[tuple[str, Any]]) -> dict[str, Any]:
|
|
84
|
+
"""Folds task writes into a result dict and aggregates multiple writes to the same channel.
|
|
85
|
+
|
|
86
|
+
If the channel contains a single write, we record the write in the result dict as `{channel: write}`
|
|
87
|
+
If the channel contains multiple writes, we record the writes in the result dict as `{channel: {'$writes': [write1, write2, ...]}}`"""
|
|
88
|
+
|
|
89
|
+
result: dict[str, Any] = {}
|
|
90
|
+
for channel, value in writes:
|
|
91
|
+
existing = result.get(channel)
|
|
92
|
+
|
|
93
|
+
if existing is not None:
|
|
94
|
+
channel_writes = (
|
|
95
|
+
existing["$writes"]
|
|
96
|
+
if is_multiple_channel_write(existing)
|
|
97
|
+
else [existing]
|
|
98
|
+
)
|
|
99
|
+
channel_writes.append(value)
|
|
100
|
+
result[channel] = {"$writes": channel_writes}
|
|
101
|
+
else:
|
|
102
|
+
result[channel] = value
|
|
103
|
+
return result
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def map_debug_task_results(
|
|
107
|
+
task_tup: tuple[PregelExecutableTask, Sequence[tuple[str, Any]]],
|
|
108
|
+
stream_keys: str | Sequence[str],
|
|
109
|
+
) -> Iterator[TaskResultPayload]:
|
|
110
|
+
"""Produce "task_result" events for stream_mode=debug."""
|
|
111
|
+
stream_channels_list = (
|
|
112
|
+
[stream_keys] if isinstance(stream_keys, str) else stream_keys
|
|
113
|
+
)
|
|
114
|
+
task, writes = task_tup
|
|
115
|
+
yield {
|
|
116
|
+
"id": task.id,
|
|
117
|
+
"name": task.name,
|
|
118
|
+
"error": next((w[1] for w in writes if w[0] == ERROR), None),
|
|
119
|
+
"result": map_task_result_writes(
|
|
120
|
+
[w for w in writes if w[0] in stream_channels_list or w[0] == RETURN]
|
|
121
|
+
),
|
|
122
|
+
"interrupts": [
|
|
123
|
+
asdict(v)
|
|
124
|
+
for w in writes
|
|
125
|
+
if w[0] == INTERRUPT
|
|
126
|
+
for v in (w[1] if isinstance(w[1], Sequence) else [w[1]])
|
|
127
|
+
],
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def rm_pregel_keys(config: RunnableConfig | None) -> RunnableConfig | None:
|
|
132
|
+
"""Remove pregel-specific keys from the config."""
|
|
133
|
+
if config is None:
|
|
134
|
+
return config
|
|
135
|
+
return {
|
|
136
|
+
"configurable": {
|
|
137
|
+
k: v
|
|
138
|
+
for k, v in config.get("configurable", {}).items()
|
|
139
|
+
if not k.startswith("__pregel_")
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def map_debug_checkpoint(
|
|
145
|
+
config: RunnableConfig,
|
|
146
|
+
channels: Mapping[str, BaseChannel],
|
|
147
|
+
stream_channels: str | Sequence[str],
|
|
148
|
+
metadata: CheckpointMetadata,
|
|
149
|
+
tasks: Iterable[PregelExecutableTask],
|
|
150
|
+
pending_writes: list[PendingWrite],
|
|
151
|
+
parent_config: RunnableConfig | None,
|
|
152
|
+
output_keys: str | Sequence[str],
|
|
153
|
+
) -> Iterator[CheckpointPayload]:
|
|
154
|
+
"""Produce "checkpoint" events for stream_mode=debug."""
|
|
155
|
+
|
|
156
|
+
parent_ns = config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "")
|
|
157
|
+
task_states: dict[str, RunnableConfig | StateSnapshot] = {}
|
|
158
|
+
|
|
159
|
+
for task in tasks:
|
|
160
|
+
if not task.subgraphs:
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
# assemble checkpoint_ns for this task
|
|
164
|
+
task_ns = f"{task.name}{NS_END}{task.id}"
|
|
165
|
+
if parent_ns:
|
|
166
|
+
task_ns = f"{parent_ns}{NS_SEP}{task_ns}"
|
|
167
|
+
|
|
168
|
+
# set config as signal that subgraph checkpoints exist
|
|
169
|
+
task_states[task.id] = {
|
|
170
|
+
CONF: {
|
|
171
|
+
"thread_id": config[CONF]["thread_id"],
|
|
172
|
+
CONFIG_KEY_CHECKPOINT_NS: task_ns,
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
yield {
|
|
177
|
+
"config": rm_pregel_keys(patch_checkpoint_map(config, metadata)),
|
|
178
|
+
"parent_config": rm_pregel_keys(patch_checkpoint_map(parent_config, metadata)),
|
|
179
|
+
"values": read_channels(channels, stream_channels),
|
|
180
|
+
"metadata": metadata,
|
|
181
|
+
"next": [t.name for t in tasks],
|
|
182
|
+
"tasks": [
|
|
183
|
+
{
|
|
184
|
+
"id": t.id,
|
|
185
|
+
"name": t.name,
|
|
186
|
+
"error": t.error,
|
|
187
|
+
"state": t.state,
|
|
188
|
+
}
|
|
189
|
+
if t.error
|
|
190
|
+
else {
|
|
191
|
+
"id": t.id,
|
|
192
|
+
"name": t.name,
|
|
193
|
+
"result": t.result,
|
|
194
|
+
"interrupts": tuple(asdict(i) for i in t.interrupts),
|
|
195
|
+
"state": t.state,
|
|
196
|
+
}
|
|
197
|
+
if t.result
|
|
198
|
+
else {
|
|
199
|
+
"id": t.id,
|
|
200
|
+
"name": t.name,
|
|
201
|
+
"interrupts": tuple(asdict(i) for i in t.interrupts),
|
|
202
|
+
"state": t.state,
|
|
203
|
+
}
|
|
204
|
+
for t in tasks_w_writes(tasks, pending_writes, task_states, output_keys)
|
|
205
|
+
],
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def tasks_w_writes(
|
|
210
|
+
tasks: Iterable[PregelTask | PregelExecutableTask],
|
|
211
|
+
pending_writes: list[PendingWrite] | None,
|
|
212
|
+
states: dict[str, RunnableConfig | StateSnapshot] | None,
|
|
213
|
+
output_keys: str | Sequence[str],
|
|
214
|
+
) -> tuple[PregelTask, ...]:
|
|
215
|
+
"""Apply writes / subgraph states to tasks to be returned in a StateSnapshot."""
|
|
216
|
+
pending_writes = pending_writes or []
|
|
217
|
+
out: list[PregelTask] = []
|
|
218
|
+
for task in tasks:
|
|
219
|
+
rtn = next(
|
|
220
|
+
(
|
|
221
|
+
val
|
|
222
|
+
for tid, chan, val in pending_writes
|
|
223
|
+
if tid == task.id and chan == RETURN
|
|
224
|
+
),
|
|
225
|
+
MISSING,
|
|
226
|
+
)
|
|
227
|
+
task_error = next(
|
|
228
|
+
(exc for tid, n, exc in pending_writes if tid == task.id and n == ERROR),
|
|
229
|
+
None,
|
|
230
|
+
)
|
|
231
|
+
task_interrupts = tuple(
|
|
232
|
+
v
|
|
233
|
+
for tid, n, vv in pending_writes
|
|
234
|
+
if tid == task.id and n == INTERRUPT
|
|
235
|
+
for v in (vv if isinstance(vv, Sequence) else [vv])
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
task_writes = [
|
|
239
|
+
(chan, val)
|
|
240
|
+
for tid, chan, val in pending_writes
|
|
241
|
+
if tid == task.id and chan not in (ERROR, INTERRUPT, RETURN)
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
if rtn is not MISSING:
|
|
245
|
+
task_result = rtn
|
|
246
|
+
elif isinstance(output_keys, str):
|
|
247
|
+
# unwrap single channel writes to just the write value
|
|
248
|
+
filtered_writes = [
|
|
249
|
+
(chan, val) for chan, val in task_writes if chan == output_keys
|
|
250
|
+
]
|
|
251
|
+
mapped_writes = map_task_result_writes(filtered_writes)
|
|
252
|
+
task_result = mapped_writes.get(str(output_keys)) if mapped_writes else None
|
|
253
|
+
else:
|
|
254
|
+
if isinstance(output_keys, str):
|
|
255
|
+
output_keys = [output_keys]
|
|
256
|
+
# map task result writes to the desired output channels
|
|
257
|
+
# repeateed writes to the same channel are aggregated into: {'$writes': [write1, write2, ...]}
|
|
258
|
+
filtered_writes = [
|
|
259
|
+
(chan, val) for chan, val in task_writes if chan in output_keys
|
|
260
|
+
]
|
|
261
|
+
mapped_writes = map_task_result_writes(filtered_writes)
|
|
262
|
+
task_result = mapped_writes if filtered_writes else {}
|
|
263
|
+
|
|
264
|
+
has_writes = rtn is not MISSING or any(
|
|
265
|
+
w[0] == task.id and w[1] not in (ERROR, INTERRUPT) for w in pending_writes
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
out.append(
|
|
269
|
+
PregelTask(
|
|
270
|
+
task.id,
|
|
271
|
+
task.name,
|
|
272
|
+
task.path,
|
|
273
|
+
task_error,
|
|
274
|
+
task_interrupts,
|
|
275
|
+
states.get(task.id) if states else None,
|
|
276
|
+
task_result if has_writes else None,
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
return tuple(out)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
COLOR_MAPPING = {
|
|
283
|
+
"black": "0;30",
|
|
284
|
+
"red": "0;31",
|
|
285
|
+
"green": "0;32",
|
|
286
|
+
"yellow": "0;33",
|
|
287
|
+
"blue": "0;34",
|
|
288
|
+
"magenta": "0;35",
|
|
289
|
+
"cyan": "0;36",
|
|
290
|
+
"white": "0;37",
|
|
291
|
+
"gray": "1;30",
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def get_colored_text(text: str, color: str) -> str:
|
|
296
|
+
"""Get colored text."""
|
|
297
|
+
return f"\033[1;3{COLOR_MAPPING[color]}m{text}\033[0m"
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def get_bolded_text(text: str) -> str:
|
|
301
|
+
"""Get bolded text."""
|
|
302
|
+
return f"\033[1m{text}\033[0m"
|