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,221 @@
|
|
|
1
|
+
"""This module provides a ValidationNode class that can be used to validate tool calls
|
|
2
|
+
in a coco graph. It applies a pydantic schema to tool_calls in the models' outputs,
|
|
3
|
+
and returns a ToolMessage with the validated content. If the schema is not valid, it
|
|
4
|
+
returns a ToolMessage with the error message. The ValidationNode can be used in a
|
|
5
|
+
StateGraph with a "messages" key. If multiple tool calls are requested, they will be run in parallel.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections.abc import Callable, Sequence
|
|
9
|
+
from typing import (
|
|
10
|
+
Any,
|
|
11
|
+
cast,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from coco_core.messages import (
|
|
15
|
+
AIMessage,
|
|
16
|
+
AnyMessage,
|
|
17
|
+
ToolCall,
|
|
18
|
+
ToolMessage,
|
|
19
|
+
)
|
|
20
|
+
from coco_core.runnables import (
|
|
21
|
+
RunnableConfig,
|
|
22
|
+
)
|
|
23
|
+
from coco_core.runnables.config import get_executor_for_config
|
|
24
|
+
from coco_core.tools import BaseTool, create_schema_from_function
|
|
25
|
+
from coco_core.utils.pydantic import is_basemodel_subclass
|
|
26
|
+
from coco_graph._internal._runnable import RunnableCallable
|
|
27
|
+
from coco_graph.warnings import CocoGraphDeprecatedSinceV10
|
|
28
|
+
from pydantic import BaseModel, ValidationError
|
|
29
|
+
from pydantic.v1 import BaseModel as BaseModelV1
|
|
30
|
+
from pydantic.v1 import ValidationError as ValidationErrorV1
|
|
31
|
+
from typing_extensions import deprecated
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _default_format_error(
|
|
35
|
+
error: BaseException,
|
|
36
|
+
call: ToolCall,
|
|
37
|
+
schema: type[BaseModel] | type[BaseModelV1],
|
|
38
|
+
) -> str:
|
|
39
|
+
"""Default error formatting function."""
|
|
40
|
+
return f"{repr(error)}\n\nRespond after fixing all validation errors."
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@deprecated(
|
|
44
|
+
"ValidationNode is deprecated. Please use `create_agent` from `coco.agents` with custom tool error handling.",
|
|
45
|
+
category=CocoGraphDeprecatedSinceV10,
|
|
46
|
+
)
|
|
47
|
+
class ValidationNode(RunnableCallable):
|
|
48
|
+
"""A node that validates all tools requests from the last `AIMessage`.
|
|
49
|
+
|
|
50
|
+
It can be used either in `StateGraph` with a `'messages'` key.
|
|
51
|
+
|
|
52
|
+
!!! note
|
|
53
|
+
|
|
54
|
+
This node does not actually **run** the tools, it only validates the tool calls,
|
|
55
|
+
which is useful for extraction and other use cases where you need to generate
|
|
56
|
+
structured output that conforms to a complex schema without losing the original
|
|
57
|
+
messages and tool IDs (for use in multi-turn conversations).
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
(Union[Dict[str, List[ToolMessage]], Sequence[ToolMessage]]): A list of
|
|
61
|
+
`ToolMessage` objects with the validated content or error messages.
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
```python title="Example usage for re-prompting the model to generate a valid response:"
|
|
65
|
+
from typing import Literal, Annotated
|
|
66
|
+
from typing_extensions import TypedDict
|
|
67
|
+
|
|
68
|
+
from coco_anthropic import ChatAnthropic
|
|
69
|
+
from pydantic import BaseModel, field_validator
|
|
70
|
+
|
|
71
|
+
from coco_graph.graph import END, START, StateGraph
|
|
72
|
+
from coco_graph.prebuilt import ValidationNode
|
|
73
|
+
from coco_graph.graph.message import add_messages
|
|
74
|
+
|
|
75
|
+
class SelectNumber(BaseModel):
|
|
76
|
+
a: int
|
|
77
|
+
|
|
78
|
+
@field_validator("a")
|
|
79
|
+
def a_must_be_meaningful(cls, v):
|
|
80
|
+
if v != 37:
|
|
81
|
+
raise ValueError("Only 37 is allowed")
|
|
82
|
+
return v
|
|
83
|
+
|
|
84
|
+
builder = StateGraph(Annotated[list, add_messages])
|
|
85
|
+
llm = ChatAnthropic(model="claude-3-5-haiku-latest").bind_tools([SelectNumber])
|
|
86
|
+
builder.add_node("model", llm)
|
|
87
|
+
builder.add_node("validation", ValidationNode([SelectNumber]))
|
|
88
|
+
builder.add_edge(START, "model")
|
|
89
|
+
|
|
90
|
+
def should_validate(state: list) -> Literal["validation", "__end__"]:
|
|
91
|
+
if state[-1].tool_calls:
|
|
92
|
+
return "validation"
|
|
93
|
+
return END
|
|
94
|
+
|
|
95
|
+
builder.add_conditional_edges("model", should_validate)
|
|
96
|
+
|
|
97
|
+
def should_reprompt(state: list) -> Literal["model", "__end__"]:
|
|
98
|
+
for msg in state[::-1]:
|
|
99
|
+
# None of the tool calls were errors
|
|
100
|
+
if msg.type == "ai":
|
|
101
|
+
return END
|
|
102
|
+
if msg.additional_kwargs.get("is_error"):
|
|
103
|
+
return "model"
|
|
104
|
+
return END
|
|
105
|
+
|
|
106
|
+
builder.add_conditional_edges("validation", should_reprompt)
|
|
107
|
+
|
|
108
|
+
graph = builder.compile()
|
|
109
|
+
res = graph.invoke(("user", "Select a number, any number"))
|
|
110
|
+
# Show the retry logic
|
|
111
|
+
for msg in res:
|
|
112
|
+
msg.pretty_print()
|
|
113
|
+
```
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
schemas: Sequence[BaseTool | type[BaseModel] | Callable],
|
|
119
|
+
*,
|
|
120
|
+
format_error: Callable[[BaseException, ToolCall, type[BaseModel]], str]
|
|
121
|
+
| None = None,
|
|
122
|
+
name: str = "validation",
|
|
123
|
+
tags: list[str] | None = None,
|
|
124
|
+
) -> None:
|
|
125
|
+
"""Initialize the ValidationNode.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
schemas: A list of schemas to validate the tool calls with. These can be
|
|
129
|
+
any of the following:
|
|
130
|
+
- A pydantic BaseModel class
|
|
131
|
+
- A BaseTool instance (the args_schema will be used)
|
|
132
|
+
- A function (a schema will be created from the function signature)
|
|
133
|
+
format_error: A function that takes an exception, a ToolCall, and a schema
|
|
134
|
+
and returns a formatted error string. By default, it returns the
|
|
135
|
+
exception repr and a message to respond after fixing validation errors.
|
|
136
|
+
name: The name of the node.
|
|
137
|
+
tags: A list of tags to add to the node.
|
|
138
|
+
"""
|
|
139
|
+
super().__init__(self._func, None, name=name, tags=tags, trace=False)
|
|
140
|
+
self._format_error = format_error or _default_format_error
|
|
141
|
+
self.schemas_by_name: dict[str, type[BaseModel]] = {}
|
|
142
|
+
for schema in schemas:
|
|
143
|
+
if isinstance(schema, BaseTool):
|
|
144
|
+
if schema.args_schema is None:
|
|
145
|
+
raise ValueError(
|
|
146
|
+
f"Tool {schema.name} does not have an args_schema defined."
|
|
147
|
+
)
|
|
148
|
+
elif not isinstance(
|
|
149
|
+
schema.args_schema, type
|
|
150
|
+
) or not is_basemodel_subclass(schema.args_schema):
|
|
151
|
+
raise ValueError(
|
|
152
|
+
"Validation node only works with tools that have a pydantic BaseModel args_schema. "
|
|
153
|
+
f"Got {schema.name} with args_schema: {schema.args_schema}."
|
|
154
|
+
)
|
|
155
|
+
self.schemas_by_name[schema.name] = schema.args_schema
|
|
156
|
+
elif isinstance(schema, type) and issubclass(
|
|
157
|
+
schema, (BaseModel, BaseModelV1)
|
|
158
|
+
):
|
|
159
|
+
self.schemas_by_name[schema.__name__] = cast(type[BaseModel], schema)
|
|
160
|
+
elif callable(schema):
|
|
161
|
+
base_model = create_schema_from_function("Validation", schema)
|
|
162
|
+
self.schemas_by_name[schema.__name__] = base_model
|
|
163
|
+
else:
|
|
164
|
+
raise ValueError(
|
|
165
|
+
f"Unsupported input to ValidationNode. Expected BaseModel, tool or function. Got: {type(schema)}."
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def _get_message(
|
|
169
|
+
self, input: list[AnyMessage] | dict[str, Any]
|
|
170
|
+
) -> tuple[str, AIMessage]:
|
|
171
|
+
"""Extract the last AIMessage from the input."""
|
|
172
|
+
if isinstance(input, list):
|
|
173
|
+
output_type = "list"
|
|
174
|
+
messages: list = input
|
|
175
|
+
elif messages := input.get("messages", []):
|
|
176
|
+
output_type = "dict"
|
|
177
|
+
else:
|
|
178
|
+
raise ValueError("No message found in input")
|
|
179
|
+
message: AnyMessage = messages[-1]
|
|
180
|
+
if not isinstance(message, AIMessage):
|
|
181
|
+
raise ValueError("Last message is not an AIMessage")
|
|
182
|
+
return output_type, message
|
|
183
|
+
|
|
184
|
+
def _func(
|
|
185
|
+
self, input: list[AnyMessage] | dict[str, Any], config: RunnableConfig
|
|
186
|
+
) -> Any:
|
|
187
|
+
"""Validate and run tool calls synchronously."""
|
|
188
|
+
output_type, message = self._get_message(input)
|
|
189
|
+
|
|
190
|
+
def run_one(call: ToolCall) -> ToolMessage:
|
|
191
|
+
schema = self.schemas_by_name[call["name"]]
|
|
192
|
+
try:
|
|
193
|
+
if issubclass(schema, BaseModel):
|
|
194
|
+
output = schema.model_validate(call["args"])
|
|
195
|
+
content = output.model_dump_json()
|
|
196
|
+
elif issubclass(schema, BaseModelV1):
|
|
197
|
+
output = schema.validate(call["args"])
|
|
198
|
+
content = output.json()
|
|
199
|
+
else:
|
|
200
|
+
raise ValueError(
|
|
201
|
+
f"Unsupported schema type: {type(schema)}. Expected BaseModel or BaseModelV1."
|
|
202
|
+
)
|
|
203
|
+
return ToolMessage(
|
|
204
|
+
content=content,
|
|
205
|
+
name=call["name"],
|
|
206
|
+
tool_call_id=cast(str, call["id"]),
|
|
207
|
+
)
|
|
208
|
+
except (ValidationError, ValidationErrorV1) as e:
|
|
209
|
+
return ToolMessage(
|
|
210
|
+
content=self._format_error(e, call, schema),
|
|
211
|
+
name=call["name"],
|
|
212
|
+
tool_call_id=cast(str, call["id"]),
|
|
213
|
+
additional_kwargs={"is_error": True},
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
with get_executor_for_config(config) as executor:
|
|
217
|
+
outputs = [*executor.map(run_one, message.tool_calls)]
|
|
218
|
+
if output_type == "list":
|
|
219
|
+
return outputs
|
|
220
|
+
else:
|
|
221
|
+
return {"messages": outputs}
|