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,330 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from collections.abc import Coroutine
|
|
7
|
+
from typing import Any, ClassVar, Literal
|
|
8
|
+
|
|
9
|
+
from typing_extensions import NotRequired, TypedDict
|
|
10
|
+
|
|
11
|
+
_logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _ProtocolEventParams(TypedDict):
|
|
15
|
+
"""Parameters for a protocol event.
|
|
16
|
+
|
|
17
|
+
`timestamp` is wall-clock milliseconds since the epoch and can go
|
|
18
|
+
backwards across NTP adjustments — use `ProtocolEvent.seq` for
|
|
19
|
+
ordering.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
namespace: list[str]
|
|
23
|
+
timestamp: int
|
|
24
|
+
data: Any
|
|
25
|
+
interrupts: NotRequired[tuple[Any, ...]]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ProtocolEvent(TypedDict):
|
|
29
|
+
"""A protocol event emitted by the streaming infrastructure.
|
|
30
|
+
|
|
31
|
+
Wraps a raw stream part (values, messages, custom, etc.) in a uniform
|
|
32
|
+
envelope with a monotonic sequence number assigned by the root StreamMux.
|
|
33
|
+
Consumers that need a total order across root events should use `seq`, not
|
|
34
|
+
`params.timestamp` (which is wall-clock and not monotonic).
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
type: Literal["event"]
|
|
38
|
+
event_id: NotRequired[str] # snake_case to match the trustcoco-protocol wire field
|
|
39
|
+
seq: NotRequired[int]
|
|
40
|
+
method: str # StreamMode value: "values", "messages", "custom", etc.
|
|
41
|
+
params: _ProtocolEventParams
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class StreamTransformer(ABC):
|
|
45
|
+
"""Extension point for custom stream projections.
|
|
46
|
+
|
|
47
|
+
Transformers observe protocol events flowing through the StreamMux and
|
|
48
|
+
build typed derived projections (StreamChannels, promises, etc.).
|
|
49
|
+
|
|
50
|
+
Set `_native = True` on a transformer to have its projection keys
|
|
51
|
+
exposed as direct attributes on the run stream (in addition to
|
|
52
|
+
appearing in `run.extensions`).
|
|
53
|
+
|
|
54
|
+
Subclasses must implement `init` and override at least one of
|
|
55
|
+
`process` / `aprocess`. The `finalize` / `afinalize` and `fail` /
|
|
56
|
+
`afail` hooks are optional — the default implementations are no-ops.
|
|
57
|
+
StreamChannel instances in the projection dict are auto-closed /
|
|
58
|
+
auto-failed by the mux, so most transformers don't need `finalize`
|
|
59
|
+
or `fail` at all.
|
|
60
|
+
|
|
61
|
+
Transformers that need async work pick the async lane by:
|
|
62
|
+
|
|
63
|
+
1. Overriding `aprocess` (and optionally `afinalize` / `afail`), or
|
|
64
|
+
2. Calling `self.schedule(coro)` from inside a sync `process`, or
|
|
65
|
+
3. Setting `requires_async = True` explicitly.
|
|
66
|
+
|
|
67
|
+
The mux detects these cases at registration and raises if they're
|
|
68
|
+
used under sync `stream()` — they only work under `astream()`.
|
|
69
|
+
|
|
70
|
+
Use `aprocess` when the pump must wait for async work before the
|
|
71
|
+
next transformer sees the event (e.g. PII redaction that mutates
|
|
72
|
+
`event` in place). Use `schedule()` for decoupled async work whose
|
|
73
|
+
result lands on an independent projection (e.g. async moderation
|
|
74
|
+
scoring, cost lookup, external tracing).
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
scope: Namespace the transformer operates within — `()` for the
|
|
78
|
+
root mux. Set at construction from the mux's scope (each
|
|
79
|
+
factory is called as `factory(scope)`).
|
|
80
|
+
requires_async: Explicit opt-in for transformers that need a
|
|
81
|
+
running event loop but don't override any async method (for
|
|
82
|
+
example, transformers that call `schedule()` from a sync
|
|
83
|
+
`process`). The mux also auto-detects the async lane when
|
|
84
|
+
`aprocess`, `afinalize`, or `afail` is overridden.
|
|
85
|
+
supports_sync: Set True only for transformers that override
|
|
86
|
+
async-lane hooks while still fully supporting the sync lane.
|
|
87
|
+
Such transformers may be registered under `stream()`.
|
|
88
|
+
required_stream_modes: Stream modes the graph must emit for
|
|
89
|
+
this transformer to have anything to process. Computed as
|
|
90
|
+
the union across all registered transformers to determine
|
|
91
|
+
which modes a `stream_events(version="v3")` run requests from the graph.
|
|
92
|
+
Empty tuple means the transformer consumes only synthetic
|
|
93
|
+
events (or is purely passive).
|
|
94
|
+
before_builtins: Opt-in for transformers that must run *before*
|
|
95
|
+
built-in transformers like `MessagesTransformer` and
|
|
96
|
+
`ToolCallTransformer`. The mux partitions factories by this
|
|
97
|
+
flag at registration time: `before_builtins = True`
|
|
98
|
+
transformers are registered first, then everything else in
|
|
99
|
+
the order supplied. Within each lane, registration order is
|
|
100
|
+
preserved. This is the supported hook for content-mutating
|
|
101
|
+
transformers (PII redaction, profanity filters, etc.) whose
|
|
102
|
+
mutations must land before built-ins eagerly snapshot text
|
|
103
|
+
fields into their projections. **Foot-gun:** transformers
|
|
104
|
+
in this lane see `tasks` events before `LifecycleTransformer`
|
|
105
|
+
and `SubgraphTransformer` consume them — mutating
|
|
106
|
+
`event["params"]["namespace"]` or the data dict's
|
|
107
|
+
`id` / `result` / `error` / `interrupts` fields will desync
|
|
108
|
+
their bookkeeping. Observe freely; mutate only fields no
|
|
109
|
+
built-in reads (e.g. `delta.text` on `messages` events).
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
requires_async: ClassVar[bool] = False
|
|
113
|
+
supports_sync: ClassVar[bool] = False
|
|
114
|
+
required_stream_modes: ClassVar[tuple[str, ...]] = ()
|
|
115
|
+
before_builtins: ClassVar[bool] = False
|
|
116
|
+
|
|
117
|
+
def __init__(self, scope: tuple[str, ...] = ()) -> None:
|
|
118
|
+
"""Initialize the transformer with its mux's scope.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
scope: The namespace tuple the owning mux is scoped to.
|
|
122
|
+
`()` for the root. Factories receive this at
|
|
123
|
+
construction time (`factory(scope)` in `StreamMux`).
|
|
124
|
+
"""
|
|
125
|
+
self.scope: tuple[str, ...] = scope
|
|
126
|
+
|
|
127
|
+
@abstractmethod
|
|
128
|
+
def init(self) -> dict[str, Any]:
|
|
129
|
+
"""Return the projection dict.
|
|
130
|
+
|
|
131
|
+
Keys become entries in `run.extensions`. If the transformer has
|
|
132
|
+
`_native = True`, keys are also set as direct attributes on the
|
|
133
|
+
run stream.
|
|
134
|
+
|
|
135
|
+
StreamChannel instances in the return value are automatically
|
|
136
|
+
wired by the StreamMux for protocol event auto-forwarding.
|
|
137
|
+
"""
|
|
138
|
+
...
|
|
139
|
+
|
|
140
|
+
def _on_register(self, mux: Any) -> None:
|
|
141
|
+
"""Called by `StreamMux._register` after this transformer is wired in.
|
|
142
|
+
|
|
143
|
+
Default is a no-op. Override to capture a reference to the
|
|
144
|
+
owning mux — needed for transformers that build mini-muxes
|
|
145
|
+
via `mux._make_child(...)` (e.g. `SubgraphTransformer`).
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
def process(self, event: ProtocolEvent) -> bool:
|
|
149
|
+
"""Handle an event on the sync lane.
|
|
150
|
+
|
|
151
|
+
Called for every event before it is appended to the main event
|
|
152
|
+
log. Subclasses must override either `process` or `aprocess`.
|
|
153
|
+
The default raises so a missing override fails loudly rather
|
|
154
|
+
than silently passing every event through.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
event: The protocol event to observe.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
True to keep the event in the main log, False to suppress it.
|
|
161
|
+
"""
|
|
162
|
+
raise NotImplementedError(
|
|
163
|
+
f"{type(self).__name__} must override process() or aprocess()"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
async def aprocess(self, event: ProtocolEvent) -> bool:
|
|
167
|
+
"""Handle an event on the async lane.
|
|
168
|
+
|
|
169
|
+
The mux awaits this before dispatching to the next transformer,
|
|
170
|
+
so a slow `aprocess` serializes the pipeline. Use it only when
|
|
171
|
+
a later transformer — or a consumer reading the event
|
|
172
|
+
synchronously — must see the result of the async work (e.g.
|
|
173
|
+
PII redaction that mutates `event` in place).
|
|
174
|
+
|
|
175
|
+
The default delegates to `process`, so purely-sync transformers
|
|
176
|
+
run unchanged under `astream()`.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
event: The protocol event to observe.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
True to keep the event in the main log, False to suppress it.
|
|
183
|
+
"""
|
|
184
|
+
return self.process(event)
|
|
185
|
+
|
|
186
|
+
def finalize(self) -> None:
|
|
187
|
+
"""Called when the run ends normally (sync lane).
|
|
188
|
+
|
|
189
|
+
Override to close StreamChannels, resolve promises, or perform
|
|
190
|
+
other teardown. StreamChannel instances in the projection dict
|
|
191
|
+
are auto-closed by the mux.
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
async def afinalize(self) -> None:
|
|
195
|
+
"""Called when the run ends normally (async lane).
|
|
196
|
+
|
|
197
|
+
By the time this runs, the mux has already awaited every task
|
|
198
|
+
started via `schedule()`, so StreamChannels can be closed here
|
|
199
|
+
without a last-task-wins race.
|
|
200
|
+
|
|
201
|
+
The default delegates to `finalize`.
|
|
202
|
+
"""
|
|
203
|
+
self.finalize()
|
|
204
|
+
|
|
205
|
+
def fail(self, err: BaseException) -> None:
|
|
206
|
+
"""Called when the run ends with an error (sync lane).
|
|
207
|
+
|
|
208
|
+
Override to fail StreamChannels, reject promises, or perform
|
|
209
|
+
other teardown. StreamChannel instances in the projection dict
|
|
210
|
+
are auto-failed by the mux.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
err: The exception that ended the run.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
async def afail(self, err: BaseException) -> None:
|
|
217
|
+
"""Called when the run ends with an error (async lane).
|
|
218
|
+
|
|
219
|
+
The mux cancels and awaits every task started via `schedule()`
|
|
220
|
+
before calling this, so cleanup doesn't race with in-flight work.
|
|
221
|
+
|
|
222
|
+
The default delegates to `fail`.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
err: The exception that ended the run.
|
|
226
|
+
"""
|
|
227
|
+
self.fail(err)
|
|
228
|
+
|
|
229
|
+
# ------------------------------------------------------------------
|
|
230
|
+
# Scheduled async work
|
|
231
|
+
# ------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
def schedule(
|
|
234
|
+
self,
|
|
235
|
+
coro: Coroutine[Any, Any, Any],
|
|
236
|
+
*,
|
|
237
|
+
on_error: Literal["log", "raise"] = "log",
|
|
238
|
+
) -> asyncio.Task[Any]:
|
|
239
|
+
"""Schedule a coroutine tied to this transformer's lifecycle.
|
|
240
|
+
|
|
241
|
+
The mux holds the task reference, awaits all scheduled tasks
|
|
242
|
+
during `aclose()` before calling `afinalize()`, and cancels
|
|
243
|
+
them on `afail()`. Authors don't need to track tasks or
|
|
244
|
+
implement the last-task-closes-the-log dance.
|
|
245
|
+
|
|
246
|
+
Requires a running event loop — call only under `astream()`.
|
|
247
|
+
Set `requires_async = True` on the class so registration under
|
|
248
|
+
sync `stream()` fails fast with a clear message.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
coro: The coroutine to run. Its lifecycle is owned by the
|
|
252
|
+
mux from this point on.
|
|
253
|
+
on_error: `"log"` (default) catches and logs any exception
|
|
254
|
+
the coroutine raises, so a single failure doesn't tear
|
|
255
|
+
down the run. `"raise"` lets the exception propagate
|
|
256
|
+
when the mux joins pendings, converting the close path
|
|
257
|
+
into the fail path.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
The asyncio Task. Authors rarely need to await it directly
|
|
261
|
+
— consumers read results from whatever projection the
|
|
262
|
+
coroutine pushes into.
|
|
263
|
+
|
|
264
|
+
Raises:
|
|
265
|
+
RuntimeError: If called without a running event loop (i.e.
|
|
266
|
+
under sync `stream()` rather than `astream()`).
|
|
267
|
+
"""
|
|
268
|
+
try:
|
|
269
|
+
asyncio.get_running_loop()
|
|
270
|
+
except RuntimeError:
|
|
271
|
+
raise RuntimeError(
|
|
272
|
+
f"{type(self).__name__}.schedule() requires a running "
|
|
273
|
+
"event loop; this transformer must run under astream(), "
|
|
274
|
+
"not stream(). Set requires_async=True on the class so "
|
|
275
|
+
"this fails at registration rather than at first event."
|
|
276
|
+
) from None
|
|
277
|
+
|
|
278
|
+
wrapped = self._wrap_scheduled(coro) if on_error == "log" else coro
|
|
279
|
+
task = asyncio.create_task(wrapped)
|
|
280
|
+
tasks = self._scheduled_task_set()
|
|
281
|
+
tasks.add(task)
|
|
282
|
+
task.add_done_callback(tasks.discard)
|
|
283
|
+
return task
|
|
284
|
+
|
|
285
|
+
@staticmethod
|
|
286
|
+
async def _wrap_scheduled(coro: Coroutine[Any, Any, Any]) -> Any:
|
|
287
|
+
try:
|
|
288
|
+
return await coro
|
|
289
|
+
except asyncio.CancelledError:
|
|
290
|
+
raise
|
|
291
|
+
except BaseException:
|
|
292
|
+
_logger.exception("Scheduled StreamTransformer task failed")
|
|
293
|
+
|
|
294
|
+
def _scheduled_task_set(self) -> set[asyncio.Task[Any]]:
|
|
295
|
+
"""Return the lazily-allocated task set.
|
|
296
|
+
|
|
297
|
+
Avoids requiring subclasses to call `super().__init__()`.
|
|
298
|
+
"""
|
|
299
|
+
tasks: set[asyncio.Task[Any]] | None = getattr(
|
|
300
|
+
self, "_stream_scheduled_tasks", None
|
|
301
|
+
)
|
|
302
|
+
if tasks is None:
|
|
303
|
+
tasks = set()
|
|
304
|
+
self._stream_scheduled_tasks = tasks
|
|
305
|
+
return tasks
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def transformer_requires_async(transformer: StreamTransformer) -> bool:
|
|
309
|
+
"""Return True if the transformer needs a running event loop.
|
|
310
|
+
|
|
311
|
+
A transformer requires async if it explicitly opts in
|
|
312
|
+
(`requires_async = True`) or overrides any of the async-lane methods
|
|
313
|
+
(`aprocess`, `afinalize`, `afail`) without also declaring that it
|
|
314
|
+
supports the sync lane.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
transformer: The transformer to inspect.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
True if the transformer cannot run under sync `stream()`.
|
|
321
|
+
"""
|
|
322
|
+
if transformer.requires_async:
|
|
323
|
+
return True
|
|
324
|
+
if transformer.supports_sync:
|
|
325
|
+
return False
|
|
326
|
+
cls = type(transformer)
|
|
327
|
+
for name in ("aprocess", "afinalize", "afail"):
|
|
328
|
+
if getattr(cls, name) is not getattr(StreamTransformer, name):
|
|
329
|
+
return True
|
|
330
|
+
return False
|