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,1101 @@
|
|
|
1
|
+
"""Internal tracer to power the event stream API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import contextlib
|
|
7
|
+
import logging
|
|
8
|
+
from typing import (
|
|
9
|
+
TYPE_CHECKING,
|
|
10
|
+
Any,
|
|
11
|
+
TypedDict,
|
|
12
|
+
TypeVar,
|
|
13
|
+
cast,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from typing_extensions import NotRequired, override
|
|
17
|
+
|
|
18
|
+
from coco_core.callbacks.base import AsyncCallbackHandler, BaseCallbackManager
|
|
19
|
+
from coco_core.messages import AIMessageChunk, BaseMessage, BaseMessageChunk
|
|
20
|
+
from coco_core.outputs import (
|
|
21
|
+
ChatGenerationChunk,
|
|
22
|
+
GenerationChunk,
|
|
23
|
+
LLMResult,
|
|
24
|
+
)
|
|
25
|
+
from coco_core.runnables import ensure_config
|
|
26
|
+
from coco_core.runnables.schema import (
|
|
27
|
+
CustomStreamEvent,
|
|
28
|
+
EventData,
|
|
29
|
+
StandardStreamEvent,
|
|
30
|
+
StreamEvent,
|
|
31
|
+
)
|
|
32
|
+
from coco_core.runnables.utils import (
|
|
33
|
+
Input,
|
|
34
|
+
Output,
|
|
35
|
+
_RootEventFilter,
|
|
36
|
+
)
|
|
37
|
+
from coco_core.tracers._streaming import _StreamingCallbackHandler
|
|
38
|
+
from coco_core.tracers.log_stream import (
|
|
39
|
+
LogStreamCallbackHandler,
|
|
40
|
+
RunLog,
|
|
41
|
+
_astream_log_implementation,
|
|
42
|
+
)
|
|
43
|
+
from coco_core.tracers.memory_stream import _get_or_create_loop, _MemoryStream
|
|
44
|
+
from coco_core.utils.aiter import aclosing
|
|
45
|
+
from coco_core.utils.uuid import uuid7
|
|
46
|
+
|
|
47
|
+
if TYPE_CHECKING:
|
|
48
|
+
from collections.abc import AsyncIterator, Iterator, Sequence
|
|
49
|
+
from uuid import UUID
|
|
50
|
+
|
|
51
|
+
from coco_core.documents import Document
|
|
52
|
+
from coco_core.runnables import Runnable, RunnableConfig
|
|
53
|
+
from coco_core.tracers.log_stream import LogEntry
|
|
54
|
+
|
|
55
|
+
logger = logging.getLogger(__name__)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class RunInfo(TypedDict):
|
|
59
|
+
"""Information about a run.
|
|
60
|
+
|
|
61
|
+
This is used to keep track of the metadata associated with a run.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
name: str
|
|
65
|
+
"""The name of the run."""
|
|
66
|
+
|
|
67
|
+
tags: list[str]
|
|
68
|
+
"""The tags associated with the run."""
|
|
69
|
+
|
|
70
|
+
metadata: dict[str, Any]
|
|
71
|
+
"""The metadata associated with the run."""
|
|
72
|
+
|
|
73
|
+
run_type: str
|
|
74
|
+
"""The type of the run."""
|
|
75
|
+
|
|
76
|
+
inputs: NotRequired[Any]
|
|
77
|
+
"""The inputs to the run."""
|
|
78
|
+
|
|
79
|
+
parent_run_id: UUID | None
|
|
80
|
+
"""The ID of the parent run."""
|
|
81
|
+
|
|
82
|
+
tool_call_id: NotRequired[str | None]
|
|
83
|
+
"""The tool call ID associated with the run."""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _assign_name(name: str | None, serialized: dict[str, Any] | None) -> str:
|
|
87
|
+
"""Assign a name to a run."""
|
|
88
|
+
if name is not None:
|
|
89
|
+
return name
|
|
90
|
+
if serialized is not None:
|
|
91
|
+
if "name" in serialized:
|
|
92
|
+
return cast("str", serialized["name"])
|
|
93
|
+
if "id" in serialized:
|
|
94
|
+
return cast("str", serialized["id"][-1])
|
|
95
|
+
return "Unnamed"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
T = TypeVar("T")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class _AstreamEventsCallbackHandler(
|
|
102
|
+
AsyncCallbackHandler, _StreamingCallbackHandler[Any]
|
|
103
|
+
):
|
|
104
|
+
"""An implementation of an async callback handler for astream events."""
|
|
105
|
+
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
*args: Any,
|
|
109
|
+
include_names: Sequence[str] | None = None,
|
|
110
|
+
include_types: Sequence[str] | None = None,
|
|
111
|
+
include_tags: Sequence[str] | None = None,
|
|
112
|
+
exclude_names: Sequence[str] | None = None,
|
|
113
|
+
exclude_types: Sequence[str] | None = None,
|
|
114
|
+
exclude_tags: Sequence[str] | None = None,
|
|
115
|
+
**kwargs: Any,
|
|
116
|
+
) -> None:
|
|
117
|
+
"""Initialize the tracer."""
|
|
118
|
+
super().__init__(*args, **kwargs)
|
|
119
|
+
# Map of run ID to run info.
|
|
120
|
+
# the entry corresponding to a given run id is cleaned
|
|
121
|
+
# up when each corresponding run ends.
|
|
122
|
+
self.run_map: dict[UUID, RunInfo] = {}
|
|
123
|
+
# The callback event that corresponds to the end of a parent run
|
|
124
|
+
# may be invoked BEFORE the callback event that corresponds to the end
|
|
125
|
+
# of a child run, which results in clean up of run_map.
|
|
126
|
+
# So we keep track of the mapping between children and parent run IDs
|
|
127
|
+
# in a separate container. This container is GCed when the tracer is GCed.
|
|
128
|
+
self.parent_map: dict[UUID, UUID | None] = {}
|
|
129
|
+
|
|
130
|
+
self.is_tapped: dict[UUID, Any] = {}
|
|
131
|
+
|
|
132
|
+
# Filter which events will be sent over the queue.
|
|
133
|
+
self.root_event_filter = _RootEventFilter(
|
|
134
|
+
include_names=include_names,
|
|
135
|
+
include_types=include_types,
|
|
136
|
+
include_tags=include_tags,
|
|
137
|
+
exclude_names=exclude_names,
|
|
138
|
+
exclude_types=exclude_types,
|
|
139
|
+
exclude_tags=exclude_tags,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
memory_stream = _MemoryStream[StreamEvent](_get_or_create_loop(self))
|
|
143
|
+
self.send_stream = memory_stream.get_send_stream()
|
|
144
|
+
self.receive_stream = memory_stream.get_receive_stream()
|
|
145
|
+
|
|
146
|
+
def _get_parent_ids(self, run_id: UUID) -> list[str]:
|
|
147
|
+
"""Get the parent IDs of a run (non-recursively) cast to strings."""
|
|
148
|
+
parent_ids = []
|
|
149
|
+
|
|
150
|
+
while parent_id := self.parent_map.get(run_id):
|
|
151
|
+
str_parent_id = str(parent_id)
|
|
152
|
+
if str_parent_id in parent_ids:
|
|
153
|
+
msg = (
|
|
154
|
+
f"Parent ID {parent_id} is already in the parent_ids list. "
|
|
155
|
+
f"This should never happen."
|
|
156
|
+
)
|
|
157
|
+
raise AssertionError(msg)
|
|
158
|
+
parent_ids.append(str_parent_id)
|
|
159
|
+
run_id = parent_id
|
|
160
|
+
|
|
161
|
+
# Return the parent IDs in reverse order, so that the first
|
|
162
|
+
# parent ID is the root and the last ID is the immediate parent.
|
|
163
|
+
return parent_ids[::-1]
|
|
164
|
+
|
|
165
|
+
def _send(self, event: StreamEvent, event_type: str) -> None:
|
|
166
|
+
"""Send an event to the stream."""
|
|
167
|
+
if self.root_event_filter.include_event(event, event_type):
|
|
168
|
+
self.send_stream.send_nowait(event)
|
|
169
|
+
|
|
170
|
+
def __aiter__(self) -> AsyncIterator[Any]:
|
|
171
|
+
"""Iterate over the receive stream.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
An async iterator over the receive stream.
|
|
175
|
+
"""
|
|
176
|
+
return self.receive_stream.__aiter__()
|
|
177
|
+
|
|
178
|
+
async def tap_output_aiter(
|
|
179
|
+
self, run_id: UUID, output: AsyncIterator[T]
|
|
180
|
+
) -> AsyncIterator[T]:
|
|
181
|
+
"""Tap the output aiter.
|
|
182
|
+
|
|
183
|
+
This method is used to tap the output of a `Runnable` that produces an async
|
|
184
|
+
iterator. It is used to generate stream events for the output of the `Runnable`.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
run_id: The ID of the run.
|
|
188
|
+
output: The output of the `Runnable`.
|
|
189
|
+
|
|
190
|
+
Yields:
|
|
191
|
+
The output of the `Runnable`.
|
|
192
|
+
"""
|
|
193
|
+
sentinel = object()
|
|
194
|
+
# atomic check and set
|
|
195
|
+
tap = self.is_tapped.setdefault(run_id, sentinel)
|
|
196
|
+
# wait for first chunk
|
|
197
|
+
first = await anext(output, sentinel)
|
|
198
|
+
if first is sentinel:
|
|
199
|
+
return
|
|
200
|
+
# get run info
|
|
201
|
+
run_info = self.run_map.get(run_id)
|
|
202
|
+
if run_info is None:
|
|
203
|
+
# run has finished, don't issue any stream events
|
|
204
|
+
yield cast("T", first)
|
|
205
|
+
return
|
|
206
|
+
if tap is sentinel:
|
|
207
|
+
# if we are the first to tap, issue stream events
|
|
208
|
+
event: StandardStreamEvent = {
|
|
209
|
+
"event": f"on_{run_info['run_type']}_stream",
|
|
210
|
+
"run_id": str(run_id),
|
|
211
|
+
"name": run_info["name"],
|
|
212
|
+
"tags": run_info["tags"],
|
|
213
|
+
"metadata": run_info["metadata"],
|
|
214
|
+
"data": {},
|
|
215
|
+
"parent_ids": self._get_parent_ids(run_id),
|
|
216
|
+
}
|
|
217
|
+
self._send({**event, "data": {"chunk": first}}, run_info["run_type"])
|
|
218
|
+
yield cast("T", first)
|
|
219
|
+
# consume the rest of the output
|
|
220
|
+
async for chunk in output:
|
|
221
|
+
self._send(
|
|
222
|
+
{**event, "data": {"chunk": chunk}},
|
|
223
|
+
run_info["run_type"],
|
|
224
|
+
)
|
|
225
|
+
yield chunk
|
|
226
|
+
else:
|
|
227
|
+
# otherwise just pass through
|
|
228
|
+
yield cast("T", first)
|
|
229
|
+
# consume the rest of the output
|
|
230
|
+
async for chunk in output:
|
|
231
|
+
yield chunk
|
|
232
|
+
|
|
233
|
+
def tap_output_iter(self, run_id: UUID, output: Iterator[T]) -> Iterator[T]:
|
|
234
|
+
"""Tap the output iter.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
run_id: The ID of the run.
|
|
238
|
+
output: The output of the `Runnable`.
|
|
239
|
+
|
|
240
|
+
Yields:
|
|
241
|
+
The output of the `Runnable`.
|
|
242
|
+
"""
|
|
243
|
+
sentinel = object()
|
|
244
|
+
# atomic check and set
|
|
245
|
+
tap = self.is_tapped.setdefault(run_id, sentinel)
|
|
246
|
+
# wait for first chunk
|
|
247
|
+
first = next(output, sentinel)
|
|
248
|
+
if first is sentinel:
|
|
249
|
+
return
|
|
250
|
+
# get run info
|
|
251
|
+
run_info = self.run_map.get(run_id)
|
|
252
|
+
if run_info is None:
|
|
253
|
+
# run has finished, don't issue any stream events
|
|
254
|
+
yield cast("T", first)
|
|
255
|
+
return
|
|
256
|
+
if tap is sentinel:
|
|
257
|
+
# if we are the first to tap, issue stream events
|
|
258
|
+
event: StandardStreamEvent = {
|
|
259
|
+
"event": f"on_{run_info['run_type']}_stream",
|
|
260
|
+
"run_id": str(run_id),
|
|
261
|
+
"name": run_info["name"],
|
|
262
|
+
"tags": run_info["tags"],
|
|
263
|
+
"metadata": run_info["metadata"],
|
|
264
|
+
"data": {},
|
|
265
|
+
"parent_ids": self._get_parent_ids(run_id),
|
|
266
|
+
}
|
|
267
|
+
self._send({**event, "data": {"chunk": first}}, run_info["run_type"])
|
|
268
|
+
yield cast("T", first)
|
|
269
|
+
# consume the rest of the output
|
|
270
|
+
for chunk in output:
|
|
271
|
+
self._send(
|
|
272
|
+
{**event, "data": {"chunk": chunk}},
|
|
273
|
+
run_info["run_type"],
|
|
274
|
+
)
|
|
275
|
+
yield chunk
|
|
276
|
+
else:
|
|
277
|
+
# otherwise just pass through
|
|
278
|
+
yield cast("T", first)
|
|
279
|
+
# consume the rest of the output
|
|
280
|
+
for chunk in output:
|
|
281
|
+
yield chunk
|
|
282
|
+
|
|
283
|
+
def _write_run_start_info(
|
|
284
|
+
self,
|
|
285
|
+
run_id: UUID,
|
|
286
|
+
*,
|
|
287
|
+
tags: list[str] | None,
|
|
288
|
+
metadata: dict[str, Any] | None,
|
|
289
|
+
parent_run_id: UUID | None,
|
|
290
|
+
name_: str,
|
|
291
|
+
run_type: str,
|
|
292
|
+
**kwargs: Any,
|
|
293
|
+
) -> None:
|
|
294
|
+
"""Update the run info."""
|
|
295
|
+
info: RunInfo = {
|
|
296
|
+
"tags": tags or [],
|
|
297
|
+
"metadata": metadata or {},
|
|
298
|
+
"name": name_,
|
|
299
|
+
"run_type": run_type,
|
|
300
|
+
"parent_run_id": parent_run_id,
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if "inputs" in kwargs:
|
|
304
|
+
# Handle inputs in a special case to allow inputs to be an
|
|
305
|
+
# optionally provided and distinguish between missing value
|
|
306
|
+
# vs. None value.
|
|
307
|
+
info["inputs"] = kwargs["inputs"]
|
|
308
|
+
|
|
309
|
+
if "tool_call_id" in kwargs:
|
|
310
|
+
# Store tool_call_id in run info for linking errors to tool calls
|
|
311
|
+
info["tool_call_id"] = kwargs["tool_call_id"]
|
|
312
|
+
|
|
313
|
+
self.run_map[run_id] = info
|
|
314
|
+
self.parent_map[run_id] = parent_run_id
|
|
315
|
+
|
|
316
|
+
@override
|
|
317
|
+
async def on_chat_model_start(
|
|
318
|
+
self,
|
|
319
|
+
serialized: dict[str, Any],
|
|
320
|
+
messages: list[list[BaseMessage]],
|
|
321
|
+
*,
|
|
322
|
+
run_id: UUID,
|
|
323
|
+
tags: list[str] | None = None,
|
|
324
|
+
parent_run_id: UUID | None = None,
|
|
325
|
+
metadata: dict[str, Any] | None = None,
|
|
326
|
+
name: str | None = None,
|
|
327
|
+
**kwargs: Any,
|
|
328
|
+
) -> None:
|
|
329
|
+
"""Start a trace for a chat model run."""
|
|
330
|
+
name_ = _assign_name(name, serialized)
|
|
331
|
+
run_type = "chat_model"
|
|
332
|
+
|
|
333
|
+
self._write_run_start_info(
|
|
334
|
+
run_id,
|
|
335
|
+
tags=tags,
|
|
336
|
+
metadata=metadata,
|
|
337
|
+
parent_run_id=parent_run_id,
|
|
338
|
+
name_=name_,
|
|
339
|
+
run_type=run_type,
|
|
340
|
+
inputs={"messages": messages},
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
self._send(
|
|
344
|
+
{
|
|
345
|
+
"event": "on_chat_model_start",
|
|
346
|
+
"data": {
|
|
347
|
+
"input": {"messages": messages},
|
|
348
|
+
},
|
|
349
|
+
"name": name_,
|
|
350
|
+
"tags": tags or [],
|
|
351
|
+
"run_id": str(run_id),
|
|
352
|
+
"metadata": metadata or {},
|
|
353
|
+
"parent_ids": self._get_parent_ids(run_id),
|
|
354
|
+
},
|
|
355
|
+
run_type,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
@override
|
|
359
|
+
async def on_llm_start(
|
|
360
|
+
self,
|
|
361
|
+
serialized: dict[str, Any],
|
|
362
|
+
prompts: list[str],
|
|
363
|
+
*,
|
|
364
|
+
run_id: UUID,
|
|
365
|
+
tags: list[str] | None = None,
|
|
366
|
+
parent_run_id: UUID | None = None,
|
|
367
|
+
metadata: dict[str, Any] | None = None,
|
|
368
|
+
name: str | None = None,
|
|
369
|
+
**kwargs: Any,
|
|
370
|
+
) -> None:
|
|
371
|
+
"""Start a trace for a (non-chat model) LLM run."""
|
|
372
|
+
name_ = _assign_name(name, serialized)
|
|
373
|
+
run_type = "llm"
|
|
374
|
+
|
|
375
|
+
self._write_run_start_info(
|
|
376
|
+
run_id,
|
|
377
|
+
tags=tags,
|
|
378
|
+
metadata=metadata,
|
|
379
|
+
parent_run_id=parent_run_id,
|
|
380
|
+
name_=name_,
|
|
381
|
+
run_type=run_type,
|
|
382
|
+
inputs={"prompts": prompts},
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
self._send(
|
|
386
|
+
{
|
|
387
|
+
"event": "on_llm_start",
|
|
388
|
+
"data": {
|
|
389
|
+
"input": {
|
|
390
|
+
"prompts": prompts,
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
"name": name_,
|
|
394
|
+
"tags": tags or [],
|
|
395
|
+
"run_id": str(run_id),
|
|
396
|
+
"metadata": metadata or {},
|
|
397
|
+
"parent_ids": self._get_parent_ids(run_id),
|
|
398
|
+
},
|
|
399
|
+
run_type,
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
@override
|
|
403
|
+
async def on_custom_event(
|
|
404
|
+
self,
|
|
405
|
+
name: str,
|
|
406
|
+
data: Any,
|
|
407
|
+
*,
|
|
408
|
+
run_id: UUID,
|
|
409
|
+
tags: list[str] | None = None,
|
|
410
|
+
metadata: dict[str, Any] | None = None,
|
|
411
|
+
**kwargs: Any,
|
|
412
|
+
) -> None:
|
|
413
|
+
"""Generate a custom astream event."""
|
|
414
|
+
event = CustomStreamEvent(
|
|
415
|
+
event="on_custom_event",
|
|
416
|
+
run_id=str(run_id),
|
|
417
|
+
name=name,
|
|
418
|
+
tags=tags or [],
|
|
419
|
+
metadata=metadata or {},
|
|
420
|
+
data=data,
|
|
421
|
+
parent_ids=self._get_parent_ids(run_id),
|
|
422
|
+
)
|
|
423
|
+
self._send(event, name)
|
|
424
|
+
|
|
425
|
+
@override
|
|
426
|
+
async def on_llm_new_token(
|
|
427
|
+
self,
|
|
428
|
+
token: str | list[str | dict[str, Any]],
|
|
429
|
+
*,
|
|
430
|
+
chunk: GenerationChunk | ChatGenerationChunk | None = None,
|
|
431
|
+
run_id: UUID,
|
|
432
|
+
parent_run_id: UUID | None = None,
|
|
433
|
+
**kwargs: Any,
|
|
434
|
+
) -> None:
|
|
435
|
+
"""Run on new output token.
|
|
436
|
+
|
|
437
|
+
Only available when streaming is enabled.
|
|
438
|
+
|
|
439
|
+
For both chat models and non-chat models (legacy text-completion LLMs).
|
|
440
|
+
|
|
441
|
+
Raises:
|
|
442
|
+
ValueError: If the run type is not `llm` or `chat_model`.
|
|
443
|
+
AssertionError: If the run ID is not found in the run map.
|
|
444
|
+
"""
|
|
445
|
+
run_info = self.run_map.get(run_id)
|
|
446
|
+
chunk_: GenerationChunk | BaseMessageChunk
|
|
447
|
+
|
|
448
|
+
if run_info is None:
|
|
449
|
+
msg = f"Run ID {run_id} not found in run map."
|
|
450
|
+
raise AssertionError(msg)
|
|
451
|
+
if self.is_tapped.get(run_id):
|
|
452
|
+
return
|
|
453
|
+
if run_info["run_type"] == "chat_model":
|
|
454
|
+
event = "on_chat_model_stream"
|
|
455
|
+
|
|
456
|
+
if chunk is None:
|
|
457
|
+
chunk_ = AIMessageChunk(content=token)
|
|
458
|
+
else:
|
|
459
|
+
chunk_ = cast("ChatGenerationChunk", chunk).message
|
|
460
|
+
|
|
461
|
+
elif run_info["run_type"] == "llm":
|
|
462
|
+
event = "on_llm_stream"
|
|
463
|
+
if chunk is None:
|
|
464
|
+
text = token if isinstance(token, str) else ""
|
|
465
|
+
chunk_ = GenerationChunk(text=text)
|
|
466
|
+
else:
|
|
467
|
+
chunk_ = cast("GenerationChunk", chunk)
|
|
468
|
+
else:
|
|
469
|
+
msg = f"Unexpected run type: {run_info['run_type']}"
|
|
470
|
+
raise ValueError(msg)
|
|
471
|
+
|
|
472
|
+
self._send(
|
|
473
|
+
{
|
|
474
|
+
"event": event,
|
|
475
|
+
"data": {
|
|
476
|
+
"chunk": chunk_,
|
|
477
|
+
},
|
|
478
|
+
"run_id": str(run_id),
|
|
479
|
+
"name": run_info["name"],
|
|
480
|
+
"tags": run_info["tags"],
|
|
481
|
+
"metadata": run_info["metadata"],
|
|
482
|
+
"parent_ids": self._get_parent_ids(run_id),
|
|
483
|
+
},
|
|
484
|
+
run_info["run_type"],
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
@override
|
|
488
|
+
async def on_llm_end(
|
|
489
|
+
self, response: LLMResult, *, run_id: UUID, **kwargs: Any
|
|
490
|
+
) -> None:
|
|
491
|
+
"""End a trace for a model run.
|
|
492
|
+
|
|
493
|
+
For both chat models and non-chat models (legacy text-completion LLMs).
|
|
494
|
+
|
|
495
|
+
Raises:
|
|
496
|
+
ValueError: If the run type is not `'llm'` or `'chat_model'`.
|
|
497
|
+
"""
|
|
498
|
+
run_info = self.run_map.pop(run_id)
|
|
499
|
+
inputs_ = run_info.get("inputs")
|
|
500
|
+
|
|
501
|
+
generations: list[list[GenerationChunk]] | list[list[ChatGenerationChunk]]
|
|
502
|
+
output: dict[str, Any] | BaseMessage = {}
|
|
503
|
+
|
|
504
|
+
if run_info["run_type"] == "chat_model":
|
|
505
|
+
generations = cast("list[list[ChatGenerationChunk]]", response.generations)
|
|
506
|
+
for gen in generations:
|
|
507
|
+
if output != {}:
|
|
508
|
+
break
|
|
509
|
+
for chunk in gen:
|
|
510
|
+
output = chunk.message
|
|
511
|
+
break
|
|
512
|
+
|
|
513
|
+
event = "on_chat_model_end"
|
|
514
|
+
elif run_info["run_type"] == "llm":
|
|
515
|
+
generations = cast("list[list[GenerationChunk]]", response.generations)
|
|
516
|
+
output = {
|
|
517
|
+
"generations": [
|
|
518
|
+
[
|
|
519
|
+
{
|
|
520
|
+
"text": chunk.text,
|
|
521
|
+
"generation_info": chunk.generation_info,
|
|
522
|
+
"type": chunk.type,
|
|
523
|
+
}
|
|
524
|
+
for chunk in gen
|
|
525
|
+
]
|
|
526
|
+
for gen in generations
|
|
527
|
+
],
|
|
528
|
+
"llm_output": response.llm_output,
|
|
529
|
+
}
|
|
530
|
+
event = "on_llm_end"
|
|
531
|
+
else:
|
|
532
|
+
msg = f"Unexpected run type: {run_info['run_type']}"
|
|
533
|
+
raise ValueError(msg)
|
|
534
|
+
|
|
535
|
+
self._send(
|
|
536
|
+
{
|
|
537
|
+
"event": event,
|
|
538
|
+
"data": {"output": output, "input": inputs_},
|
|
539
|
+
"run_id": str(run_id),
|
|
540
|
+
"name": run_info["name"],
|
|
541
|
+
"tags": run_info["tags"],
|
|
542
|
+
"metadata": run_info["metadata"],
|
|
543
|
+
"parent_ids": self._get_parent_ids(run_id),
|
|
544
|
+
},
|
|
545
|
+
run_info["run_type"],
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
async def on_chain_start(
|
|
549
|
+
self,
|
|
550
|
+
serialized: dict[str, Any],
|
|
551
|
+
inputs: dict[str, Any],
|
|
552
|
+
*,
|
|
553
|
+
run_id: UUID,
|
|
554
|
+
tags: list[str] | None = None,
|
|
555
|
+
parent_run_id: UUID | None = None,
|
|
556
|
+
metadata: dict[str, Any] | None = None,
|
|
557
|
+
run_type: str | None = None,
|
|
558
|
+
name: str | None = None,
|
|
559
|
+
**kwargs: Any,
|
|
560
|
+
) -> None:
|
|
561
|
+
"""Start a trace for a chain run."""
|
|
562
|
+
name_ = _assign_name(name, serialized)
|
|
563
|
+
run_type_ = run_type or "chain"
|
|
564
|
+
|
|
565
|
+
data: EventData = {}
|
|
566
|
+
|
|
567
|
+
# Work-around Runnable core code not sending input in some
|
|
568
|
+
# cases.
|
|
569
|
+
if inputs != {"input": ""}:
|
|
570
|
+
data["input"] = inputs
|
|
571
|
+
kwargs["inputs"] = inputs
|
|
572
|
+
|
|
573
|
+
self._write_run_start_info(
|
|
574
|
+
run_id,
|
|
575
|
+
tags=tags,
|
|
576
|
+
metadata=metadata,
|
|
577
|
+
parent_run_id=parent_run_id,
|
|
578
|
+
name_=name_,
|
|
579
|
+
run_type=run_type_,
|
|
580
|
+
**kwargs,
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
self._send(
|
|
584
|
+
{
|
|
585
|
+
"event": f"on_{run_type_}_start",
|
|
586
|
+
"data": data,
|
|
587
|
+
"name": name_,
|
|
588
|
+
"tags": tags or [],
|
|
589
|
+
"run_id": str(run_id),
|
|
590
|
+
"metadata": metadata or {},
|
|
591
|
+
"parent_ids": self._get_parent_ids(run_id),
|
|
592
|
+
},
|
|
593
|
+
run_type_,
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
@override
|
|
597
|
+
async def on_chain_end(
|
|
598
|
+
self,
|
|
599
|
+
outputs: dict[str, Any],
|
|
600
|
+
*,
|
|
601
|
+
run_id: UUID,
|
|
602
|
+
inputs: dict[str, Any] | None = None,
|
|
603
|
+
**kwargs: Any,
|
|
604
|
+
) -> None:
|
|
605
|
+
"""End a trace for a chain run."""
|
|
606
|
+
run_info = self.run_map.pop(run_id)
|
|
607
|
+
run_type = run_info["run_type"]
|
|
608
|
+
|
|
609
|
+
event = f"on_{run_type}_end"
|
|
610
|
+
|
|
611
|
+
inputs = inputs or run_info.get("inputs") or {}
|
|
612
|
+
|
|
613
|
+
data: EventData = {
|
|
614
|
+
"output": outputs,
|
|
615
|
+
"input": inputs,
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
self._send(
|
|
619
|
+
{
|
|
620
|
+
"event": event,
|
|
621
|
+
"data": data,
|
|
622
|
+
"run_id": str(run_id),
|
|
623
|
+
"name": run_info["name"],
|
|
624
|
+
"tags": run_info["tags"],
|
|
625
|
+
"metadata": run_info["metadata"],
|
|
626
|
+
"parent_ids": self._get_parent_ids(run_id),
|
|
627
|
+
},
|
|
628
|
+
run_type,
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
def _get_tool_run_info_with_inputs(self, run_id: UUID) -> tuple[RunInfo, Any]:
|
|
632
|
+
"""Get run info for a tool and extract inputs, with validation.
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
run_id: The run ID of the tool.
|
|
636
|
+
|
|
637
|
+
Returns:
|
|
638
|
+
A tuple of `(run_info, inputs)`.
|
|
639
|
+
|
|
640
|
+
Raises:
|
|
641
|
+
AssertionError: If the run ID is a tool call and does not have inputs.
|
|
642
|
+
"""
|
|
643
|
+
run_info = self.run_map.pop(run_id)
|
|
644
|
+
if "inputs" not in run_info:
|
|
645
|
+
msg = (
|
|
646
|
+
f"Run ID {run_id} is a tool call and is expected to have "
|
|
647
|
+
f"inputs associated with it."
|
|
648
|
+
)
|
|
649
|
+
raise AssertionError(msg)
|
|
650
|
+
inputs = run_info["inputs"]
|
|
651
|
+
return run_info, inputs
|
|
652
|
+
|
|
653
|
+
@override
|
|
654
|
+
async def on_tool_start(
|
|
655
|
+
self,
|
|
656
|
+
serialized: dict[str, Any],
|
|
657
|
+
input_str: str,
|
|
658
|
+
*,
|
|
659
|
+
run_id: UUID,
|
|
660
|
+
tags: list[str] | None = None,
|
|
661
|
+
parent_run_id: UUID | None = None,
|
|
662
|
+
metadata: dict[str, Any] | None = None,
|
|
663
|
+
name: str | None = None,
|
|
664
|
+
inputs: dict[str, Any] | None = None,
|
|
665
|
+
**kwargs: Any,
|
|
666
|
+
) -> None:
|
|
667
|
+
"""Start a trace for a tool run."""
|
|
668
|
+
name_ = _assign_name(name, serialized)
|
|
669
|
+
|
|
670
|
+
self._write_run_start_info(
|
|
671
|
+
run_id,
|
|
672
|
+
tags=tags,
|
|
673
|
+
metadata=metadata,
|
|
674
|
+
parent_run_id=parent_run_id,
|
|
675
|
+
name_=name_,
|
|
676
|
+
run_type="tool",
|
|
677
|
+
inputs=inputs,
|
|
678
|
+
tool_call_id=kwargs.get("tool_call_id"),
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
self._send(
|
|
682
|
+
{
|
|
683
|
+
"event": "on_tool_start",
|
|
684
|
+
"data": {
|
|
685
|
+
"input": inputs or {},
|
|
686
|
+
},
|
|
687
|
+
"name": name_,
|
|
688
|
+
"tags": tags or [],
|
|
689
|
+
"run_id": str(run_id),
|
|
690
|
+
"metadata": metadata or {},
|
|
691
|
+
"parent_ids": self._get_parent_ids(run_id),
|
|
692
|
+
},
|
|
693
|
+
"tool",
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
@override
|
|
697
|
+
async def on_tool_error(
|
|
698
|
+
self,
|
|
699
|
+
error: BaseException,
|
|
700
|
+
*,
|
|
701
|
+
run_id: UUID,
|
|
702
|
+
parent_run_id: UUID | None = None,
|
|
703
|
+
tags: list[str] | None = None,
|
|
704
|
+
**kwargs: Any,
|
|
705
|
+
) -> None:
|
|
706
|
+
"""Run when tool errors."""
|
|
707
|
+
# Extract tool_call_id from kwargs if passed directly, or from run_info
|
|
708
|
+
# (which was stored during on_tool_start) as a fallback
|
|
709
|
+
tool_call_id = kwargs.get("tool_call_id")
|
|
710
|
+
run_info, inputs = self._get_tool_run_info_with_inputs(run_id)
|
|
711
|
+
if tool_call_id is None:
|
|
712
|
+
tool_call_id = run_info.get("tool_call_id")
|
|
713
|
+
|
|
714
|
+
event: StandardStreamEvent = {
|
|
715
|
+
"event": "on_tool_error",
|
|
716
|
+
"data": {
|
|
717
|
+
"error": error,
|
|
718
|
+
"input": inputs,
|
|
719
|
+
"tool_call_id": tool_call_id,
|
|
720
|
+
},
|
|
721
|
+
"run_id": str(run_id),
|
|
722
|
+
"name": run_info["name"],
|
|
723
|
+
"tags": run_info["tags"],
|
|
724
|
+
"metadata": run_info["metadata"],
|
|
725
|
+
"parent_ids": self._get_parent_ids(run_id),
|
|
726
|
+
}
|
|
727
|
+
self._send(event, "tool")
|
|
728
|
+
|
|
729
|
+
@override
|
|
730
|
+
async def on_tool_end(self, output: Any, *, run_id: UUID, **kwargs: Any) -> None:
|
|
731
|
+
"""End a trace for a tool run."""
|
|
732
|
+
run_info, inputs = self._get_tool_run_info_with_inputs(run_id)
|
|
733
|
+
|
|
734
|
+
self._send(
|
|
735
|
+
{
|
|
736
|
+
"event": "on_tool_end",
|
|
737
|
+
"data": {
|
|
738
|
+
"output": output,
|
|
739
|
+
"input": inputs,
|
|
740
|
+
},
|
|
741
|
+
"run_id": str(run_id),
|
|
742
|
+
"name": run_info["name"],
|
|
743
|
+
"tags": run_info["tags"],
|
|
744
|
+
"metadata": run_info["metadata"],
|
|
745
|
+
"parent_ids": self._get_parent_ids(run_id),
|
|
746
|
+
},
|
|
747
|
+
"tool",
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
@override
|
|
751
|
+
async def on_retriever_start(
|
|
752
|
+
self,
|
|
753
|
+
serialized: dict[str, Any],
|
|
754
|
+
query: str,
|
|
755
|
+
*,
|
|
756
|
+
run_id: UUID,
|
|
757
|
+
parent_run_id: UUID | None = None,
|
|
758
|
+
tags: list[str] | None = None,
|
|
759
|
+
metadata: dict[str, Any] | None = None,
|
|
760
|
+
name: str | None = None,
|
|
761
|
+
**kwargs: Any,
|
|
762
|
+
) -> None:
|
|
763
|
+
"""Run when `Retriever` starts running."""
|
|
764
|
+
name_ = _assign_name(name, serialized)
|
|
765
|
+
run_type = "retriever"
|
|
766
|
+
|
|
767
|
+
self._write_run_start_info(
|
|
768
|
+
run_id,
|
|
769
|
+
tags=tags,
|
|
770
|
+
metadata=metadata,
|
|
771
|
+
parent_run_id=parent_run_id,
|
|
772
|
+
name_=name_,
|
|
773
|
+
run_type=run_type,
|
|
774
|
+
inputs={"query": query},
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
self._send(
|
|
778
|
+
{
|
|
779
|
+
"event": "on_retriever_start",
|
|
780
|
+
"data": {
|
|
781
|
+
"input": {
|
|
782
|
+
"query": query,
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
"name": name_,
|
|
786
|
+
"tags": tags or [],
|
|
787
|
+
"run_id": str(run_id),
|
|
788
|
+
"metadata": metadata or {},
|
|
789
|
+
"parent_ids": self._get_parent_ids(run_id),
|
|
790
|
+
},
|
|
791
|
+
run_type,
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
@override
|
|
795
|
+
async def on_retriever_end(
|
|
796
|
+
self, documents: Sequence[Document], *, run_id: UUID, **kwargs: Any
|
|
797
|
+
) -> None:
|
|
798
|
+
"""Run when `Retriever` ends running."""
|
|
799
|
+
run_info = self.run_map.pop(run_id)
|
|
800
|
+
|
|
801
|
+
self._send(
|
|
802
|
+
{
|
|
803
|
+
"event": "on_retriever_end",
|
|
804
|
+
"data": {
|
|
805
|
+
"output": documents,
|
|
806
|
+
"input": run_info.get("inputs"),
|
|
807
|
+
},
|
|
808
|
+
"run_id": str(run_id),
|
|
809
|
+
"name": run_info["name"],
|
|
810
|
+
"tags": run_info["tags"],
|
|
811
|
+
"metadata": run_info["metadata"],
|
|
812
|
+
"parent_ids": self._get_parent_ids(run_id),
|
|
813
|
+
},
|
|
814
|
+
run_info["run_type"],
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
def __deepcopy__(
|
|
818
|
+
self, memo: dict[int, Any] | None = None
|
|
819
|
+
) -> _AstreamEventsCallbackHandler:
|
|
820
|
+
"""Return self."""
|
|
821
|
+
return self
|
|
822
|
+
|
|
823
|
+
def __copy__(self) -> _AstreamEventsCallbackHandler:
|
|
824
|
+
"""Return self."""
|
|
825
|
+
return self
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
async def _astream_events_implementation_v1(
|
|
829
|
+
runnable: Runnable[Input, Output],
|
|
830
|
+
value: Any,
|
|
831
|
+
config: RunnableConfig | None = None,
|
|
832
|
+
*,
|
|
833
|
+
include_names: Sequence[str] | None = None,
|
|
834
|
+
include_types: Sequence[str] | None = None,
|
|
835
|
+
include_tags: Sequence[str] | None = None,
|
|
836
|
+
exclude_names: Sequence[str] | None = None,
|
|
837
|
+
exclude_types: Sequence[str] | None = None,
|
|
838
|
+
exclude_tags: Sequence[str] | None = None,
|
|
839
|
+
**kwargs: Any,
|
|
840
|
+
) -> AsyncIterator[StandardStreamEvent]:
|
|
841
|
+
stream = LogStreamCallbackHandler(
|
|
842
|
+
auto_close=False,
|
|
843
|
+
include_names=include_names,
|
|
844
|
+
include_types=include_types,
|
|
845
|
+
include_tags=include_tags,
|
|
846
|
+
exclude_names=exclude_names,
|
|
847
|
+
exclude_types=exclude_types,
|
|
848
|
+
exclude_tags=exclude_tags,
|
|
849
|
+
_schema_format="streaming_events",
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
run_log = RunLog(state=None) # type: ignore[arg-type]
|
|
853
|
+
encountered_start_event = False
|
|
854
|
+
|
|
855
|
+
root_event_filter = _RootEventFilter(
|
|
856
|
+
include_names=include_names,
|
|
857
|
+
include_types=include_types,
|
|
858
|
+
include_tags=include_tags,
|
|
859
|
+
exclude_names=exclude_names,
|
|
860
|
+
exclude_types=exclude_types,
|
|
861
|
+
exclude_tags=exclude_tags,
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
config = ensure_config(config)
|
|
865
|
+
root_tags = config.get("tags", [])
|
|
866
|
+
root_metadata = config.get("metadata", {})
|
|
867
|
+
root_name = config.get("run_name", runnable.get_name())
|
|
868
|
+
|
|
869
|
+
async for log in _astream_log_implementation(
|
|
870
|
+
runnable,
|
|
871
|
+
value,
|
|
872
|
+
config=config,
|
|
873
|
+
stream=stream,
|
|
874
|
+
diff=True,
|
|
875
|
+
with_streamed_output_list=True,
|
|
876
|
+
**kwargs,
|
|
877
|
+
):
|
|
878
|
+
run_log += log
|
|
879
|
+
|
|
880
|
+
if not encountered_start_event:
|
|
881
|
+
# Yield the start event for the root runnable.
|
|
882
|
+
encountered_start_event = True
|
|
883
|
+
state = run_log.state.copy()
|
|
884
|
+
|
|
885
|
+
event = StandardStreamEvent(
|
|
886
|
+
event=f"on_{state['type']}_start",
|
|
887
|
+
run_id=state["id"],
|
|
888
|
+
name=root_name,
|
|
889
|
+
tags=root_tags,
|
|
890
|
+
metadata=root_metadata,
|
|
891
|
+
data={
|
|
892
|
+
"input": value,
|
|
893
|
+
},
|
|
894
|
+
parent_ids=[], # Not supported in v1
|
|
895
|
+
)
|
|
896
|
+
|
|
897
|
+
if root_event_filter.include_event(event, state["type"]):
|
|
898
|
+
yield event
|
|
899
|
+
|
|
900
|
+
paths = {
|
|
901
|
+
op["path"].split("/")[2]
|
|
902
|
+
for op in log.ops
|
|
903
|
+
if op["path"].startswith("/logs/")
|
|
904
|
+
}
|
|
905
|
+
# Elements in a set should be iterated in the same order
|
|
906
|
+
# as they were inserted in modern python versions.
|
|
907
|
+
for path in paths:
|
|
908
|
+
data: EventData = {}
|
|
909
|
+
log_entry: LogEntry = run_log.state["logs"][path]
|
|
910
|
+
if log_entry["end_time"] is None:
|
|
911
|
+
event_type = "stream" if log_entry["streamed_output"] else "start"
|
|
912
|
+
else:
|
|
913
|
+
event_type = "end"
|
|
914
|
+
|
|
915
|
+
if event_type == "start":
|
|
916
|
+
# Include the inputs with the start event if they are available.
|
|
917
|
+
# Usually they will NOT be available for components that operate
|
|
918
|
+
# on streams, since those components stream the input and
|
|
919
|
+
# don't know its final value until the end of the stream.
|
|
920
|
+
inputs = log_entry.get("inputs")
|
|
921
|
+
if inputs is not None:
|
|
922
|
+
data["input"] = inputs
|
|
923
|
+
|
|
924
|
+
if event_type == "end":
|
|
925
|
+
inputs = log_entry.get("inputs")
|
|
926
|
+
if inputs is not None:
|
|
927
|
+
data["input"] = inputs
|
|
928
|
+
|
|
929
|
+
# None is a VALID output for an end event
|
|
930
|
+
data["output"] = log_entry["final_output"]
|
|
931
|
+
|
|
932
|
+
if event_type == "stream":
|
|
933
|
+
num_chunks = len(log_entry["streamed_output"])
|
|
934
|
+
if num_chunks != 1:
|
|
935
|
+
msg = (
|
|
936
|
+
f"Expected exactly one chunk of streamed output, "
|
|
937
|
+
f"got {num_chunks} instead. This is impossible. "
|
|
938
|
+
f"Encountered in: {log_entry['name']}"
|
|
939
|
+
)
|
|
940
|
+
raise AssertionError(msg)
|
|
941
|
+
|
|
942
|
+
data = {"chunk": log_entry["streamed_output"][0]}
|
|
943
|
+
# Clean up the stream, we don't need it anymore.
|
|
944
|
+
# And this avoids duplicates as well!
|
|
945
|
+
log_entry["streamed_output"] = []
|
|
946
|
+
|
|
947
|
+
yield StandardStreamEvent(
|
|
948
|
+
event=f"on_{log_entry['type']}_{event_type}",
|
|
949
|
+
name=log_entry["name"],
|
|
950
|
+
run_id=log_entry["id"],
|
|
951
|
+
tags=log_entry["tags"],
|
|
952
|
+
metadata=log_entry["metadata"],
|
|
953
|
+
data=data,
|
|
954
|
+
parent_ids=[], # Not supported in v1
|
|
955
|
+
)
|
|
956
|
+
|
|
957
|
+
# Finally, we take care of the streaming output from the root chain
|
|
958
|
+
# if there is any.
|
|
959
|
+
state = run_log.state
|
|
960
|
+
if state["streamed_output"]:
|
|
961
|
+
num_chunks = len(state["streamed_output"])
|
|
962
|
+
if num_chunks != 1:
|
|
963
|
+
msg = (
|
|
964
|
+
f"Expected exactly one chunk of streamed output, "
|
|
965
|
+
f"got {num_chunks} instead. This is impossible. "
|
|
966
|
+
f"Encountered in: {state['name']}"
|
|
967
|
+
)
|
|
968
|
+
raise AssertionError(msg)
|
|
969
|
+
|
|
970
|
+
data = {"chunk": state["streamed_output"][0]}
|
|
971
|
+
# Clean up the stream, we don't need it anymore.
|
|
972
|
+
state["streamed_output"] = []
|
|
973
|
+
|
|
974
|
+
event = StandardStreamEvent(
|
|
975
|
+
event=f"on_{state['type']}_stream",
|
|
976
|
+
run_id=state["id"],
|
|
977
|
+
tags=root_tags,
|
|
978
|
+
metadata=root_metadata,
|
|
979
|
+
name=root_name,
|
|
980
|
+
data=data,
|
|
981
|
+
parent_ids=[], # Not supported in v1
|
|
982
|
+
)
|
|
983
|
+
if root_event_filter.include_event(event, state["type"]):
|
|
984
|
+
yield event
|
|
985
|
+
|
|
986
|
+
state = run_log.state
|
|
987
|
+
|
|
988
|
+
# Finally yield the end event for the root runnable.
|
|
989
|
+
event = StandardStreamEvent(
|
|
990
|
+
event=f"on_{state['type']}_end",
|
|
991
|
+
name=root_name,
|
|
992
|
+
run_id=state["id"],
|
|
993
|
+
tags=root_tags,
|
|
994
|
+
metadata=root_metadata,
|
|
995
|
+
data={
|
|
996
|
+
"output": state["final_output"],
|
|
997
|
+
},
|
|
998
|
+
parent_ids=[], # Not supported in v1
|
|
999
|
+
)
|
|
1000
|
+
if root_event_filter.include_event(event, state["type"]):
|
|
1001
|
+
yield event
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
async def _astream_events_implementation_v2(
|
|
1005
|
+
runnable: Runnable[Input, Output],
|
|
1006
|
+
value: Any,
|
|
1007
|
+
config: RunnableConfig | None = None,
|
|
1008
|
+
*,
|
|
1009
|
+
include_names: Sequence[str] | None = None,
|
|
1010
|
+
include_types: Sequence[str] | None = None,
|
|
1011
|
+
include_tags: Sequence[str] | None = None,
|
|
1012
|
+
exclude_names: Sequence[str] | None = None,
|
|
1013
|
+
exclude_types: Sequence[str] | None = None,
|
|
1014
|
+
exclude_tags: Sequence[str] | None = None,
|
|
1015
|
+
**kwargs: Any,
|
|
1016
|
+
) -> AsyncIterator[StandardStreamEvent]:
|
|
1017
|
+
"""Implementation of the astream events API for v2 runnables."""
|
|
1018
|
+
event_streamer = _AstreamEventsCallbackHandler(
|
|
1019
|
+
include_names=include_names,
|
|
1020
|
+
include_types=include_types,
|
|
1021
|
+
include_tags=include_tags,
|
|
1022
|
+
exclude_names=exclude_names,
|
|
1023
|
+
exclude_types=exclude_types,
|
|
1024
|
+
exclude_tags=exclude_tags,
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
# Assign the stream handler to the config
|
|
1028
|
+
config = ensure_config(config)
|
|
1029
|
+
if "run_id" in config:
|
|
1030
|
+
run_id = cast("UUID", config["run_id"])
|
|
1031
|
+
else:
|
|
1032
|
+
run_id = uuid7()
|
|
1033
|
+
config["run_id"] = run_id
|
|
1034
|
+
callbacks = config.get("callbacks")
|
|
1035
|
+
if callbacks is None:
|
|
1036
|
+
config["callbacks"] = [event_streamer]
|
|
1037
|
+
elif isinstance(callbacks, list):
|
|
1038
|
+
config["callbacks"] = [*callbacks, event_streamer]
|
|
1039
|
+
elif isinstance(callbacks, BaseCallbackManager):
|
|
1040
|
+
callbacks = callbacks.copy()
|
|
1041
|
+
callbacks.add_handler(event_streamer, inherit=True)
|
|
1042
|
+
config["callbacks"] = callbacks
|
|
1043
|
+
else:
|
|
1044
|
+
msg = ( # type: ignore[unreachable]
|
|
1045
|
+
f"Unexpected type for callbacks: {callbacks}."
|
|
1046
|
+
"Expected None, list or AsyncCallbackManager."
|
|
1047
|
+
)
|
|
1048
|
+
raise ValueError(msg)
|
|
1049
|
+
|
|
1050
|
+
# Call the runnable in streaming mode,
|
|
1051
|
+
# add each chunk to the output stream
|
|
1052
|
+
async def consume_astream() -> None:
|
|
1053
|
+
try:
|
|
1054
|
+
# if astream also calls tap_output_aiter this will be a no-op
|
|
1055
|
+
async with aclosing(runnable.astream(value, config, **kwargs)) as stream:
|
|
1056
|
+
async for _ in event_streamer.tap_output_aiter(run_id, stream):
|
|
1057
|
+
# All the content will be picked up
|
|
1058
|
+
pass
|
|
1059
|
+
finally:
|
|
1060
|
+
await event_streamer.send_stream.aclose()
|
|
1061
|
+
|
|
1062
|
+
# Start the runnable in a task, so we can start consuming output
|
|
1063
|
+
task = asyncio.create_task(consume_astream())
|
|
1064
|
+
|
|
1065
|
+
first_event_sent = False
|
|
1066
|
+
first_event_run_id = None
|
|
1067
|
+
|
|
1068
|
+
try:
|
|
1069
|
+
async for event in event_streamer:
|
|
1070
|
+
if not first_event_sent:
|
|
1071
|
+
first_event_sent = True
|
|
1072
|
+
# This is a work-around an issue where the inputs into the
|
|
1073
|
+
# chain are not available until the entire input is consumed.
|
|
1074
|
+
# As a temporary solution, we'll modify the input to be the input
|
|
1075
|
+
# that was passed into the chain.
|
|
1076
|
+
event["data"]["input"] = value
|
|
1077
|
+
first_event_run_id = event["run_id"]
|
|
1078
|
+
yield event
|
|
1079
|
+
continue
|
|
1080
|
+
|
|
1081
|
+
# If it's the end event corresponding to the root runnable
|
|
1082
|
+
# we don't include the input in the event since it's guaranteed
|
|
1083
|
+
# to be included in the first event.
|
|
1084
|
+
if (
|
|
1085
|
+
event["run_id"] == first_event_run_id
|
|
1086
|
+
and event["event"].endswith("_end")
|
|
1087
|
+
and "input" in event["data"]
|
|
1088
|
+
):
|
|
1089
|
+
del event["data"]["input"]
|
|
1090
|
+
|
|
1091
|
+
yield event
|
|
1092
|
+
except asyncio.CancelledError as exc:
|
|
1093
|
+
# Cancel the task if it's still running
|
|
1094
|
+
task.cancel(exc.args[0] if exc.args else None)
|
|
1095
|
+
raise
|
|
1096
|
+
finally:
|
|
1097
|
+
# Cancel the task if it's still running
|
|
1098
|
+
task.cancel()
|
|
1099
|
+
# Await it anyway, to run any cleanup code, and propagate any exceptions
|
|
1100
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
1101
|
+
await task
|