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
|
File without changes
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from coco_core.runnables import RunnableConfig
|
|
9
|
+
from coco_graph.checkpoint.base import get_checkpoint_id
|
|
10
|
+
|
|
11
|
+
_FILTER_PATTERN = re.compile(r"^[a-zA-Z0-9_.-]+$")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _validate_filter_key(key: str) -> None:
|
|
15
|
+
"""Validate that a filter key is safe for use in SQL queries.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
key: The filter key to validate
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
ValueError: If the key contains invalid characters that could enable SQL injection
|
|
22
|
+
"""
|
|
23
|
+
# Allow alphanumeric characters, underscores, dots, and hyphens
|
|
24
|
+
# This covers typical JSON property names while preventing SQL injection
|
|
25
|
+
if not _FILTER_PATTERN.match(key):
|
|
26
|
+
raise ValueError(
|
|
27
|
+
f"Invalid filter key: '{key}'. Filter keys must contain only alphanumeric characters, underscores, dots, and hyphens."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _metadata_predicate(
|
|
32
|
+
metadata_filter: dict[str, Any],
|
|
33
|
+
) -> tuple[Sequence[str], Sequence[Any]]:
|
|
34
|
+
"""Return WHERE clause predicates for (a)search() given metadata filter.
|
|
35
|
+
|
|
36
|
+
This method returns a tuple of a string and a tuple of values. The string
|
|
37
|
+
is the parametered WHERE clause predicate (excluding the WHERE keyword):
|
|
38
|
+
"column1 = ? AND column2 IS ?". The tuple of values contains the values
|
|
39
|
+
for each of the corresponding parameters.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def _where_value(query_value: Any) -> tuple[str, Any]:
|
|
43
|
+
"""Return tuple of operator and value for WHERE clause predicate."""
|
|
44
|
+
if query_value is None:
|
|
45
|
+
return ("IS ?", None)
|
|
46
|
+
elif (
|
|
47
|
+
isinstance(query_value, str)
|
|
48
|
+
or isinstance(query_value, int)
|
|
49
|
+
or isinstance(query_value, float)
|
|
50
|
+
):
|
|
51
|
+
return ("= ?", query_value)
|
|
52
|
+
elif isinstance(query_value, bool):
|
|
53
|
+
return ("= ?", 1 if query_value else 0)
|
|
54
|
+
elif isinstance(query_value, dict) or isinstance(query_value, list):
|
|
55
|
+
# query value for JSON object cannot have trailing space after separators (, :)
|
|
56
|
+
# SQLite json_extract() returns JSON string without whitespace
|
|
57
|
+
return ("= ?", json.dumps(query_value, separators=(",", ":")))
|
|
58
|
+
else:
|
|
59
|
+
return ("= ?", str(query_value))
|
|
60
|
+
|
|
61
|
+
predicates = []
|
|
62
|
+
param_values = []
|
|
63
|
+
|
|
64
|
+
# process metadata query
|
|
65
|
+
for query_key, query_value in metadata_filter.items():
|
|
66
|
+
_validate_filter_key(query_key)
|
|
67
|
+
operator, param_value = _where_value(query_value)
|
|
68
|
+
predicates.append(
|
|
69
|
+
f"json_extract(CAST(metadata AS TEXT), '$.{query_key}') {operator}"
|
|
70
|
+
)
|
|
71
|
+
param_values.append(param_value)
|
|
72
|
+
|
|
73
|
+
return (predicates, param_values)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def search_where(
|
|
77
|
+
config: RunnableConfig | None,
|
|
78
|
+
filter: dict[str, Any] | None,
|
|
79
|
+
before: RunnableConfig | None = None,
|
|
80
|
+
) -> tuple[str, Sequence[Any]]:
|
|
81
|
+
"""Return WHERE clause predicates for (a)search() given metadata filter
|
|
82
|
+
and `before` config.
|
|
83
|
+
|
|
84
|
+
This method returns a tuple of a string and a tuple of values. The string
|
|
85
|
+
is the parametered WHERE clause predicate (including the WHERE keyword):
|
|
86
|
+
"WHERE column1 = ? AND column2 IS ?". The tuple of values contains the
|
|
87
|
+
values for each of the corresponding parameters.
|
|
88
|
+
"""
|
|
89
|
+
wheres = []
|
|
90
|
+
param_values = []
|
|
91
|
+
|
|
92
|
+
# construct predicate for config filter
|
|
93
|
+
if config is not None:
|
|
94
|
+
wheres.append("thread_id = ?")
|
|
95
|
+
param_values.append(config["configurable"]["thread_id"])
|
|
96
|
+
checkpoint_ns = config["configurable"].get("checkpoint_ns")
|
|
97
|
+
if checkpoint_ns is not None:
|
|
98
|
+
wheres.append("checkpoint_ns = ?")
|
|
99
|
+
param_values.append(checkpoint_ns)
|
|
100
|
+
|
|
101
|
+
if checkpoint_id := get_checkpoint_id(config):
|
|
102
|
+
wheres.append("checkpoint_id = ?")
|
|
103
|
+
param_values.append(checkpoint_id)
|
|
104
|
+
|
|
105
|
+
# construct predicate for metadata filter
|
|
106
|
+
if filter:
|
|
107
|
+
metadata_predicates, metadata_values = _metadata_predicate(filter)
|
|
108
|
+
wheres.extend(metadata_predicates)
|
|
109
|
+
param_values.extend(metadata_values)
|
|
110
|
+
|
|
111
|
+
# construct predicate for `before`
|
|
112
|
+
if before is not None:
|
|
113
|
+
wheres.append("checkpoint_id < ?")
|
|
114
|
+
param_values.append(get_checkpoint_id(before))
|
|
115
|
+
|
|
116
|
+
return ("WHERE " + " AND ".join(wheres) if wheres else "", param_values)
|
coco_graph/config.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from coco_core.runnables import RunnableConfig
|
|
6
|
+
from coco_core.runnables.config import var_child_runnable_config
|
|
7
|
+
from coco_graph.store.base import BaseStore
|
|
8
|
+
|
|
9
|
+
from coco_graph._internal._constants import CONF, CONFIG_KEY_RUNTIME
|
|
10
|
+
from coco_graph.types import StreamWriter
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _no_op_stream_writer(c: Any) -> None:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_config() -> RunnableConfig:
|
|
18
|
+
if sys.version_info < (3, 11):
|
|
19
|
+
try:
|
|
20
|
+
if asyncio.current_task():
|
|
21
|
+
raise RuntimeError(
|
|
22
|
+
"Python 3.11 or later required to use this in an async context"
|
|
23
|
+
)
|
|
24
|
+
except RuntimeError:
|
|
25
|
+
pass
|
|
26
|
+
if var_config := var_child_runnable_config.get():
|
|
27
|
+
return var_config
|
|
28
|
+
else:
|
|
29
|
+
raise RuntimeError("Called get_config outside of a runnable context")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_store() -> BaseStore:
|
|
33
|
+
"""Access CocoGraph store from inside a graph node or entrypoint task at runtime.
|
|
34
|
+
|
|
35
|
+
Can be called from inside any [`StateGraph`][coco_graph.graph.StateGraph] node or
|
|
36
|
+
functional API [`task`][coco_graph.func.task], as long as the `StateGraph` or the [`entrypoint`][coco_graph.func.entrypoint]
|
|
37
|
+
was initialized with a store, e.g.:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
# with StateGraph
|
|
41
|
+
graph = (
|
|
42
|
+
StateGraph(...)
|
|
43
|
+
...
|
|
44
|
+
.compile(store=store)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# or with entrypoint
|
|
48
|
+
@entrypoint(store=store)
|
|
49
|
+
def workflow(inputs):
|
|
50
|
+
...
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
!!! warning "Async with Python < 3.11"
|
|
54
|
+
|
|
55
|
+
If you are using Python < 3.11 and are running CocoGraph asynchronously,
|
|
56
|
+
`get_store()` won't work since it uses [`contextvar`](https://docs.python.org/3/library/contextvars.html) propagation (only available in [Python >= 3.11](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task)).
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
Example: Using with `StateGraph`
|
|
60
|
+
```python
|
|
61
|
+
from typing_extensions import TypedDict
|
|
62
|
+
from coco_graph.graph import StateGraph, START
|
|
63
|
+
from coco_graph.store.memory import InMemoryStore
|
|
64
|
+
from coco_graph.config import get_store
|
|
65
|
+
|
|
66
|
+
store = InMemoryStore()
|
|
67
|
+
store.put(("values",), "foo", {"bar": 2})
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class State(TypedDict):
|
|
71
|
+
foo: int
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def my_node(state: State):
|
|
75
|
+
my_store = get_store()
|
|
76
|
+
stored_value = my_store.get(("values",), "foo").value["bar"]
|
|
77
|
+
return {"foo": stored_value + 1}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
graph = (
|
|
81
|
+
StateGraph(State)
|
|
82
|
+
.add_node(my_node)
|
|
83
|
+
.add_edge(START, "my_node")
|
|
84
|
+
.compile(store=store)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
graph.invoke({"foo": 1})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```pycon
|
|
91
|
+
{"foo": 3}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Example: Using with functional API
|
|
95
|
+
```python
|
|
96
|
+
from coco_graph.func import entrypoint, task
|
|
97
|
+
from coco_graph.store.memory import InMemoryStore
|
|
98
|
+
from coco_graph.config import get_store
|
|
99
|
+
|
|
100
|
+
store = InMemoryStore()
|
|
101
|
+
store.put(("values",), "foo", {"bar": 2})
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@task
|
|
105
|
+
def my_task(value: int):
|
|
106
|
+
my_store = get_store()
|
|
107
|
+
stored_value = my_store.get(("values",), "foo").value["bar"]
|
|
108
|
+
return stored_value + 1
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@entrypoint(store=store)
|
|
112
|
+
def workflow(value: int):
|
|
113
|
+
return my_task(value).result()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
workflow.invoke(1)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```pycon
|
|
120
|
+
3
|
|
121
|
+
```
|
|
122
|
+
"""
|
|
123
|
+
return get_config()[CONF][CONFIG_KEY_RUNTIME].store
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_stream_writer() -> StreamWriter:
|
|
127
|
+
"""Access CocoGraph [`StreamWriter`][coco_graph.types.StreamWriter] from inside a graph node or entrypoint task at runtime.
|
|
128
|
+
|
|
129
|
+
Can be called from inside any [`StateGraph`][coco_graph.graph.StateGraph] node or
|
|
130
|
+
functional API [`task`][coco_graph.func.task].
|
|
131
|
+
|
|
132
|
+
!!! warning "Async with Python < 3.11"
|
|
133
|
+
|
|
134
|
+
If you are using Python < 3.11 and are running CocoGraph asynchronously,
|
|
135
|
+
`get_stream_writer()` won't work since it uses [`contextvar`](https://docs.python.org/3/library/contextvars.html) propagation (only available in [Python >= 3.11](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task)).
|
|
136
|
+
|
|
137
|
+
Example: Using with `StateGraph`
|
|
138
|
+
```python
|
|
139
|
+
from typing_extensions import TypedDict
|
|
140
|
+
from coco_graph.graph import StateGraph, START
|
|
141
|
+
from coco_graph.config import get_stream_writer
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class State(TypedDict):
|
|
145
|
+
foo: int
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def my_node(state: State):
|
|
149
|
+
my_stream_writer = get_stream_writer()
|
|
150
|
+
my_stream_writer({"custom_data": "Hello!"})
|
|
151
|
+
return {"foo": state["foo"] + 1}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
graph = (
|
|
155
|
+
StateGraph(State)
|
|
156
|
+
.add_node(my_node)
|
|
157
|
+
.add_edge(START, "my_node")
|
|
158
|
+
.compile(store=store)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
for chunk in graph.stream({"foo": 1}, stream_mode="custom"):
|
|
162
|
+
print(chunk)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```pycon
|
|
166
|
+
{"custom_data": "Hello!"}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Example: Using with functional API
|
|
170
|
+
```python
|
|
171
|
+
from coco_graph.func import entrypoint, task
|
|
172
|
+
from coco_graph.config import get_stream_writer
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@task
|
|
176
|
+
def my_task(value: int):
|
|
177
|
+
my_stream_writer = get_stream_writer()
|
|
178
|
+
my_stream_writer({"custom_data": "Hello!"})
|
|
179
|
+
return value + 1
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@entrypoint(store=store)
|
|
183
|
+
def workflow(value: int):
|
|
184
|
+
return my_task(value).result()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
for chunk in workflow.stream(1, stream_mode="custom"):
|
|
188
|
+
print(chunk)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```pycon
|
|
192
|
+
{"custom_data": "Hello!"}
|
|
193
|
+
```
|
|
194
|
+
"""
|
|
195
|
+
runtime = get_config()[CONF][CONFIG_KEY_RUNTIME]
|
|
196
|
+
return runtime.stream_writer
|
coco_graph/constants.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Any
|
|
3
|
+
from warnings import warn
|
|
4
|
+
|
|
5
|
+
from coco_graph._internal._constants import (
|
|
6
|
+
CONF,
|
|
7
|
+
CONFIG_KEY_CHECKPOINTER,
|
|
8
|
+
TASKS,
|
|
9
|
+
)
|
|
10
|
+
from coco_graph.warnings import CocoGraphDeprecatedSinceV10
|
|
11
|
+
|
|
12
|
+
__all__ = (
|
|
13
|
+
"TAG_NOSTREAM",
|
|
14
|
+
"TAG_HIDDEN",
|
|
15
|
+
"START",
|
|
16
|
+
"END",
|
|
17
|
+
# retained for backwards compatibility (mostly trustcoco-graph-api), should be removed in v2 (or earlier)
|
|
18
|
+
"CONF",
|
|
19
|
+
"TASKS",
|
|
20
|
+
"CONFIG_KEY_CHECKPOINTER",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# --- Public constants ---
|
|
24
|
+
TAG_NOSTREAM = sys.intern("nostream")
|
|
25
|
+
"""Tag to disable streaming for a chat model."""
|
|
26
|
+
TAG_HIDDEN = sys.intern("langsmith:hidden")
|
|
27
|
+
"""Tag to hide a node/edge from certain tracing/streaming environments."""
|
|
28
|
+
END = sys.intern("__end__")
|
|
29
|
+
"""The last (maybe virtual) node in graph-style Pregel."""
|
|
30
|
+
START = sys.intern("__start__")
|
|
31
|
+
"""The first (maybe virtual) node in graph-style Pregel."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def __getattr__(name: str) -> Any:
|
|
35
|
+
if name in ["Send", "Interrupt"]:
|
|
36
|
+
warn(
|
|
37
|
+
f"Importing {name} from coco_graph.constants is deprecated. "
|
|
38
|
+
f"Please use 'from coco_graph.types import {name}' instead.",
|
|
39
|
+
CocoGraphDeprecatedSinceV10,
|
|
40
|
+
stacklevel=2,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
from importlib import import_module
|
|
44
|
+
|
|
45
|
+
module = import_module("coco_graph.types")
|
|
46
|
+
return getattr(module, name)
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
from importlib import import_module
|
|
50
|
+
|
|
51
|
+
private_constants = import_module("coco_graph._internal._constants")
|
|
52
|
+
attr = getattr(private_constants, name)
|
|
53
|
+
warn(
|
|
54
|
+
f"Importing {name} from coco_graph.constants is deprecated. "
|
|
55
|
+
f"This constant is now private and should not be used directly. "
|
|
56
|
+
"Please let the CocoGraph team know if you need this value.",
|
|
57
|
+
CocoGraphDeprecatedSinceV10,
|
|
58
|
+
stacklevel=2,
|
|
59
|
+
)
|
|
60
|
+
return attr
|
|
61
|
+
except AttributeError:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
raise AttributeError(f"module has no attribute '{name}'")
|
coco_graph/errors.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
from warnings import warn
|
|
8
|
+
|
|
9
|
+
# EmptyChannelError is re-exported from coco_graph.channels.base
|
|
10
|
+
from coco_graph.checkpoint.base import EmptyChannelError
|
|
11
|
+
from typing_extensions import deprecated
|
|
12
|
+
|
|
13
|
+
from coco_graph.types import Command, Interrupt
|
|
14
|
+
from coco_graph.warnings import CocoGraphDeprecatedSinceV10
|
|
15
|
+
|
|
16
|
+
__all__ = (
|
|
17
|
+
"EmptyChannelError",
|
|
18
|
+
"ErrorCode",
|
|
19
|
+
"GraphDrained",
|
|
20
|
+
"GraphRecursionError",
|
|
21
|
+
"InvalidUpdateError",
|
|
22
|
+
"GraphBubbleUp",
|
|
23
|
+
"GraphInterrupt",
|
|
24
|
+
"NodeCancelledError",
|
|
25
|
+
"NodeError",
|
|
26
|
+
"NodeInterrupt",
|
|
27
|
+
"NodeTimeoutError",
|
|
28
|
+
"ParentCommand",
|
|
29
|
+
"EmptyInputError",
|
|
30
|
+
"TaskNotFound",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ErrorCode(Enum):
|
|
35
|
+
GRAPH_RECURSION_LIMIT = "GRAPH_RECURSION_LIMIT"
|
|
36
|
+
INVALID_CONCURRENT_GRAPH_UPDATE = "INVALID_CONCURRENT_GRAPH_UPDATE"
|
|
37
|
+
INVALID_GRAPH_NODE_RETURN_VALUE = "INVALID_GRAPH_NODE_RETURN_VALUE"
|
|
38
|
+
MULTIPLE_SUBGRAPHS = "MULTIPLE_SUBGRAPHS"
|
|
39
|
+
INVALID_CHAT_HISTORY = "INVALID_CHAT_HISTORY"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def create_error_message(*, message: str, error_code: ErrorCode) -> str:
|
|
43
|
+
return (
|
|
44
|
+
f"{message}\n"
|
|
45
|
+
"For troubleshooting, visit: https://docs.langchain.com/oss/python/langgraph/"
|
|
46
|
+
f"errors/{error_code.value}"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class GraphBubbleUp(Exception):
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class GraphDrained(GraphBubbleUp):
|
|
55
|
+
"""Raised when a graph run exits early due to a drain request.
|
|
56
|
+
|
|
57
|
+
This indicates the graph stopped cooperatively at a superstep boundary
|
|
58
|
+
because `RunControl.request_drain()` was called (e.g., in response to
|
|
59
|
+
SIGTERM). The checkpoint is saved and the run can be resumed later.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, reason: str = "shutdown") -> None:
|
|
63
|
+
self.reason = reason
|
|
64
|
+
super().__init__(f"Graph drained: {reason}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class GraphRecursionError(RecursionError):
|
|
68
|
+
"""Raised when the graph has exhausted the maximum number of steps.
|
|
69
|
+
|
|
70
|
+
This prevents infinite loops. To increase the maximum number of steps,
|
|
71
|
+
run your graph with a config specifying a higher `recursion_limit`.
|
|
72
|
+
|
|
73
|
+
Troubleshooting guides:
|
|
74
|
+
|
|
75
|
+
- [`GRAPH_RECURSION_LIMIT`](https://docs.langchain.com/oss/python/langgraph/GRAPH_RECURSION_LIMIT)
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
|
|
79
|
+
graph = builder.compile()
|
|
80
|
+
graph.invoke(
|
|
81
|
+
{"messages": [("user", "Hello, world!")]},
|
|
82
|
+
# The config is the second positional argument
|
|
83
|
+
{"recursion_limit": 1000},
|
|
84
|
+
)
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class InvalidUpdateError(Exception):
|
|
91
|
+
"""Raised when attempting to update a channel with an invalid set of updates.
|
|
92
|
+
|
|
93
|
+
Troubleshooting guides:
|
|
94
|
+
|
|
95
|
+
- [`INVALID_CONCURRENT_GRAPH_UPDATE`](https://docs.langchain.com/oss/python/langgraph/INVALID_CONCURRENT_GRAPH_UPDATE)
|
|
96
|
+
- [`INVALID_GRAPH_NODE_RETURN_VALUE`](https://docs.langchain.com/oss/python/langgraph/INVALID_GRAPH_NODE_RETURN_VALUE)
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class GraphInterrupt(GraphBubbleUp):
|
|
103
|
+
"""Raised when a subgraph is interrupted, suppressed by the root graph.
|
|
104
|
+
Never raised directly, or surfaced to the user."""
|
|
105
|
+
|
|
106
|
+
def __init__(self, interrupts: Sequence[Interrupt] = ()) -> None:
|
|
107
|
+
super().__init__(interrupts)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@deprecated(
|
|
111
|
+
"NodeInterrupt is deprecated. Please use [`interrupt`][coco_graph.types.interrupt] instead.",
|
|
112
|
+
category=None,
|
|
113
|
+
)
|
|
114
|
+
class NodeInterrupt(GraphInterrupt):
|
|
115
|
+
"""Raised by a node to interrupt execution."""
|
|
116
|
+
|
|
117
|
+
def __init__(self, value: Any, id: str | None = None) -> None:
|
|
118
|
+
warn(
|
|
119
|
+
"NodeInterrupt is deprecated. Please use `coco_graph.types.interrupt` instead.",
|
|
120
|
+
CocoGraphDeprecatedSinceV10,
|
|
121
|
+
stacklevel=2,
|
|
122
|
+
)
|
|
123
|
+
if id is None:
|
|
124
|
+
super().__init__([Interrupt(value=value)])
|
|
125
|
+
else:
|
|
126
|
+
super().__init__([Interrupt(value=value, id=id)])
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ParentCommand(GraphBubbleUp):
|
|
130
|
+
args: tuple[Command]
|
|
131
|
+
|
|
132
|
+
def __init__(self, command: Command) -> None:
|
|
133
|
+
super().__init__(command)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class EmptyInputError(Exception):
|
|
137
|
+
"""Raised when graph receives an empty input."""
|
|
138
|
+
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class TaskNotFound(Exception):
|
|
143
|
+
"""Raised when the executor is unable to find a task (for distributed mode)."""
|
|
144
|
+
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass(frozen=True, slots=True)
|
|
149
|
+
class NodeError:
|
|
150
|
+
"""Failure context passed to a node-level error handler.
|
|
151
|
+
|
|
152
|
+
Inject by adding a parameter typed `NodeError` to a handler registered via
|
|
153
|
+
`StateGraph.add_node(..., error_handler=...)`:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
def handler(state: State, error: NodeError) -> Command:
|
|
157
|
+
return Command(update={"status": f"recovered from {error.node}: {error.error}"})
|
|
158
|
+
```
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
node: str
|
|
162
|
+
"""Name of the node whose execution failed."""
|
|
163
|
+
|
|
164
|
+
error: BaseException
|
|
165
|
+
"""Exception raised by the failed node."""
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class NodeCancelledError(Exception):
|
|
169
|
+
"""Raised when a node body raises ``asyncio.CancelledError`` itself.
|
|
170
|
+
|
|
171
|
+
``asyncio.CancelledError`` is a ``BaseException`` and the pregel runner
|
|
172
|
+
treats cancelled task futures as silent tear-down (e.g. when it stops
|
|
173
|
+
sibling tasks after a peer fails). That is the correct behaviour for
|
|
174
|
+
*framework-initiated* cancellation, but a user node that raises
|
|
175
|
+
``asyncio.CancelledError`` from its own body should surface as a node
|
|
176
|
+
failure, the same way any other exception would.
|
|
177
|
+
|
|
178
|
+
The retry layer converts user-raised ``asyncio.CancelledError`` into this
|
|
179
|
+
type so it flows through the normal error path and the run reports as
|
|
180
|
+
``error`` instead of silently succeeding.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
node: str
|
|
184
|
+
|
|
185
|
+
def __init__(self, node: str, message: str | None = None) -> None:
|
|
186
|
+
super().__init__(message or f"Node {node!r} raised asyncio.CancelledError")
|
|
187
|
+
self.node = node
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class NodeTimeoutError(Exception):
|
|
191
|
+
"""Raised when a node invocation exceeds one of its configured timeouts.
|
|
192
|
+
|
|
193
|
+
Does **not** inherit from the built-in `TimeoutError` (a subclass of
|
|
194
|
+
`OSError`) so that the default `RetryPolicy` treats it as retryable.
|
|
195
|
+
|
|
196
|
+
Both `idle_timeout` and `run_timeout` reflect the configured policy at the
|
|
197
|
+
time of the failure (each is `None` if not configured). `kind` and
|
|
198
|
+
`timeout` identify which one fired.
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
node: str
|
|
202
|
+
timeout: float
|
|
203
|
+
run_timeout: float | None
|
|
204
|
+
idle_timeout: float | None
|
|
205
|
+
elapsed: float
|
|
206
|
+
kind: Literal["idle", "run"]
|
|
207
|
+
|
|
208
|
+
def __init__(
|
|
209
|
+
self,
|
|
210
|
+
node: str,
|
|
211
|
+
elapsed: float,
|
|
212
|
+
*,
|
|
213
|
+
kind: Literal["idle", "run"],
|
|
214
|
+
idle_timeout: float | None = None,
|
|
215
|
+
run_timeout: float | None = None,
|
|
216
|
+
) -> None:
|
|
217
|
+
if kind == "idle":
|
|
218
|
+
if idle_timeout is None:
|
|
219
|
+
raise ValueError("idle_timeout is required when kind='idle'")
|
|
220
|
+
message = (
|
|
221
|
+
f"Node '{node}' exceeded its idle timeout of "
|
|
222
|
+
f"{idle_timeout:.3f}s without making progress "
|
|
223
|
+
f"(elapsed: {elapsed:.3f}s)."
|
|
224
|
+
)
|
|
225
|
+
self.timeout = idle_timeout
|
|
226
|
+
elif kind == "run":
|
|
227
|
+
if run_timeout is None:
|
|
228
|
+
raise ValueError("run_timeout is required when kind='run'")
|
|
229
|
+
message = (
|
|
230
|
+
f"Node '{node}' exceeded its run timeout of "
|
|
231
|
+
f"{run_timeout:.3f}s (elapsed: {elapsed:.3f}s)."
|
|
232
|
+
)
|
|
233
|
+
self.timeout = run_timeout
|
|
234
|
+
else:
|
|
235
|
+
raise ValueError("kind must be 'idle' or 'run'")
|
|
236
|
+
super().__init__(message)
|
|
237
|
+
self.node = node
|
|
238
|
+
self.elapsed = elapsed
|
|
239
|
+
self.kind = kind
|
|
240
|
+
self.idle_timeout = idle_timeout
|
|
241
|
+
self.run_timeout = run_timeout
|