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,472 @@
|
|
|
1
|
+
"""Types for setting agent response formats."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import uuid
|
|
7
|
+
from dataclasses import dataclass, is_dataclass
|
|
8
|
+
from types import UnionType
|
|
9
|
+
from typing import (
|
|
10
|
+
TYPE_CHECKING,
|
|
11
|
+
Any,
|
|
12
|
+
Generic,
|
|
13
|
+
Literal,
|
|
14
|
+
TypeVar,
|
|
15
|
+
Union,
|
|
16
|
+
get_args,
|
|
17
|
+
get_origin,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from coco_core.tools import BaseTool, StructuredTool
|
|
21
|
+
from pydantic import BaseModel, TypeAdapter
|
|
22
|
+
from typing_extensions import Self, is_typeddict
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from collections.abc import Callable, Iterable
|
|
26
|
+
|
|
27
|
+
from coco_core.messages import AIMessage
|
|
28
|
+
|
|
29
|
+
# Supported schema types: Pydantic models, dataclasses, TypedDict, JSON schema dicts
|
|
30
|
+
SchemaT = TypeVar("SchemaT")
|
|
31
|
+
|
|
32
|
+
SchemaKind = Literal["pydantic", "dataclass", "typeddict", "json_schema"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class StructuredOutputError(Exception):
|
|
36
|
+
"""Base class for structured output errors."""
|
|
37
|
+
|
|
38
|
+
ai_message: AIMessage
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MultipleStructuredOutputsError(StructuredOutputError):
|
|
42
|
+
"""Raised when model returns multiple structured output tool calls when only one is expected."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, tool_names: list[str], ai_message: AIMessage) -> None:
|
|
45
|
+
"""Initialize `MultipleStructuredOutputsError`.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
tool_names: The names of the tools called for structured output.
|
|
49
|
+
ai_message: The AI message that contained the invalid multiple tool calls.
|
|
50
|
+
"""
|
|
51
|
+
self.tool_names = tool_names
|
|
52
|
+
self.ai_message = ai_message
|
|
53
|
+
|
|
54
|
+
super().__init__(
|
|
55
|
+
"Model incorrectly returned multiple structured responses "
|
|
56
|
+
f"({', '.join(tool_names)}) when only one is expected."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class StructuredOutputValidationError(StructuredOutputError):
|
|
61
|
+
"""Raised when structured output tool call arguments fail to parse according to the schema."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, tool_name: str, source: Exception, ai_message: AIMessage) -> None:
|
|
64
|
+
"""Initialize `StructuredOutputValidationError`.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
tool_name: The name of the tool that failed.
|
|
68
|
+
source: The exception that occurred.
|
|
69
|
+
ai_message: The AI message that contained the invalid structured output.
|
|
70
|
+
"""
|
|
71
|
+
self.tool_name = tool_name
|
|
72
|
+
self.source = source
|
|
73
|
+
self.ai_message = ai_message
|
|
74
|
+
super().__init__(f"Failed to parse structured output for tool '{tool_name}': {source}.")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _parse_with_schema(
|
|
78
|
+
schema: type[SchemaT] | dict[str, Any], schema_kind: SchemaKind, data: dict[str, Any]
|
|
79
|
+
) -> SchemaT | dict[str, Any]:
|
|
80
|
+
"""Parse data using for any supported schema type.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
schema: The schema type (Pydantic model, `dataclass`, or `TypedDict`)
|
|
84
|
+
schema_kind: One of `'pydantic'`, `'dataclass'`, `'typeddict'`, or
|
|
85
|
+
`'json_schema'`
|
|
86
|
+
data: The data to parse
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
The parsed instance according to the schema type
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
ValueError: If parsing fails
|
|
93
|
+
"""
|
|
94
|
+
if schema_kind == "json_schema":
|
|
95
|
+
# Raw JSON schema has no corresponding Python type to instantiate.
|
|
96
|
+
return data
|
|
97
|
+
try:
|
|
98
|
+
adapter = TypeAdapter[SchemaT](schema)
|
|
99
|
+
return adapter.validate_python(data)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
schema_name = getattr(schema, "__name__", str(schema))
|
|
102
|
+
msg = f"Failed to parse data to {schema_name}: {e}"
|
|
103
|
+
raise ValueError(msg) from e
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(init=False)
|
|
107
|
+
class _SchemaSpec(Generic[SchemaT]):
|
|
108
|
+
"""Describes a structured output schema."""
|
|
109
|
+
|
|
110
|
+
schema: type[SchemaT] | dict[str, Any]
|
|
111
|
+
"""The schema for the response, can be a Pydantic model, `dataclass`, `TypedDict`,
|
|
112
|
+
or JSON schema dict.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
name: str
|
|
116
|
+
"""Name of the schema, used for tool calling.
|
|
117
|
+
|
|
118
|
+
If not provided, the name will be the class name for models/dataclasses/TypedDicts,
|
|
119
|
+
or the `title` field for JSON schemas.
|
|
120
|
+
|
|
121
|
+
Falls back to a generated name if unavailable.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
description: str
|
|
125
|
+
"""Custom description of the schema.
|
|
126
|
+
|
|
127
|
+
If not provided, will use the model's docstring.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
schema_kind: SchemaKind
|
|
131
|
+
"""The kind of schema."""
|
|
132
|
+
|
|
133
|
+
json_schema: dict[str, Any]
|
|
134
|
+
"""JSON schema associated with the schema."""
|
|
135
|
+
|
|
136
|
+
strict: bool | None = None
|
|
137
|
+
"""Whether to enforce strict validation of the schema."""
|
|
138
|
+
|
|
139
|
+
def __init__(
|
|
140
|
+
self,
|
|
141
|
+
schema: type[SchemaT] | dict[str, Any],
|
|
142
|
+
*,
|
|
143
|
+
name: str | None = None,
|
|
144
|
+
description: str | None = None,
|
|
145
|
+
strict: bool | None = None,
|
|
146
|
+
) -> None:
|
|
147
|
+
"""Initialize `SchemaSpec` with schema and optional parameters.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
schema: Schema to describe.
|
|
151
|
+
name: Optional name for the schema.
|
|
152
|
+
description: Optional description for the schema.
|
|
153
|
+
strict: Whether to enforce strict validation of the schema.
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
ValueError: If the schema type is unsupported.
|
|
157
|
+
"""
|
|
158
|
+
self.schema = schema
|
|
159
|
+
|
|
160
|
+
if name:
|
|
161
|
+
self.name = name
|
|
162
|
+
elif isinstance(schema, dict):
|
|
163
|
+
self.name = str(schema.get("title", f"response_format_{str(uuid.uuid4())[:4]}"))
|
|
164
|
+
else:
|
|
165
|
+
self.name = str(getattr(schema, "__name__", f"response_format_{str(uuid.uuid4())[:4]}"))
|
|
166
|
+
|
|
167
|
+
self.description = description or (
|
|
168
|
+
schema.get("description", "")
|
|
169
|
+
if isinstance(schema, dict)
|
|
170
|
+
else getattr(schema, "__doc__", None) or ""
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
self.strict = strict
|
|
174
|
+
|
|
175
|
+
if isinstance(schema, dict):
|
|
176
|
+
self.schema_kind = "json_schema"
|
|
177
|
+
self.json_schema = schema
|
|
178
|
+
elif isinstance(schema, type) and issubclass(schema, BaseModel):
|
|
179
|
+
self.schema_kind = "pydantic"
|
|
180
|
+
self.json_schema = schema.model_json_schema()
|
|
181
|
+
elif is_dataclass(schema):
|
|
182
|
+
self.schema_kind = "dataclass"
|
|
183
|
+
self.json_schema = TypeAdapter(schema).json_schema()
|
|
184
|
+
elif is_typeddict(schema):
|
|
185
|
+
self.schema_kind = "typeddict"
|
|
186
|
+
self.json_schema = TypeAdapter(schema).json_schema()
|
|
187
|
+
else:
|
|
188
|
+
msg = (
|
|
189
|
+
f"Unsupported schema type: {type(schema)}. "
|
|
190
|
+
f"Supported types: Pydantic models, dataclasses, TypedDicts, and JSON schema dicts."
|
|
191
|
+
)
|
|
192
|
+
raise ValueError(msg)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass(init=False)
|
|
196
|
+
class ToolStrategy(Generic[SchemaT]):
|
|
197
|
+
"""Use a tool calling strategy for model responses."""
|
|
198
|
+
|
|
199
|
+
schema: type[SchemaT] | UnionType | dict[str, Any]
|
|
200
|
+
"""Schema for the tool calls."""
|
|
201
|
+
|
|
202
|
+
schema_specs: list[_SchemaSpec[Any]]
|
|
203
|
+
"""Schema specs for the tool calls."""
|
|
204
|
+
|
|
205
|
+
tool_message_content: str | None
|
|
206
|
+
"""The content of the tool message to be returned when the model calls
|
|
207
|
+
an artificial structured output tool.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
handle_errors: (
|
|
211
|
+
bool | str | type[Exception] | tuple[type[Exception], ...] | Callable[[Exception], str]
|
|
212
|
+
)
|
|
213
|
+
"""Error handling strategy for structured output via `ToolStrategy`.
|
|
214
|
+
|
|
215
|
+
- `True`: Catch all errors with default error template
|
|
216
|
+
- `str`: Catch all errors with this custom message
|
|
217
|
+
- `type[Exception]`: Only catch this exception type with default message
|
|
218
|
+
- `tuple[type[Exception], ...]`: Only catch these exception types with default
|
|
219
|
+
message
|
|
220
|
+
- `Callable[[Exception], str]`: Custom function that returns error message
|
|
221
|
+
- `False`: No retry, let exceptions propagate
|
|
222
|
+
|
|
223
|
+
!!! warning "Raw JSON schema dicts are not validated"
|
|
224
|
+
When `schema` is a raw JSON schema `dict` (as opposed to a Pydantic model,
|
|
225
|
+
`dataclass`, or `TypedDict`), the model's tool-call arguments are returned
|
|
226
|
+
**as-is without validation** against the schema.
|
|
227
|
+
|
|
228
|
+
As a result, `handle_errors` is effectively inert for dict schemas. To get
|
|
229
|
+
validation and automatic retries, express the schema as a Pydantic model,
|
|
230
|
+
`dataclass`, or `TypedDict` instead.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
def __init__(
|
|
234
|
+
self,
|
|
235
|
+
schema: type[SchemaT] | UnionType | dict[str, Any],
|
|
236
|
+
*,
|
|
237
|
+
tool_message_content: str | None = None,
|
|
238
|
+
handle_errors: bool
|
|
239
|
+
| str
|
|
240
|
+
| type[Exception]
|
|
241
|
+
| tuple[type[Exception], ...]
|
|
242
|
+
| Callable[[Exception], str] = True,
|
|
243
|
+
) -> None:
|
|
244
|
+
"""Initialize `ToolStrategy`.
|
|
245
|
+
|
|
246
|
+
Initialize `ToolStrategy` with schemas, tool message content, and error handling
|
|
247
|
+
strategy.
|
|
248
|
+
"""
|
|
249
|
+
self.schema = schema
|
|
250
|
+
self.tool_message_content = tool_message_content
|
|
251
|
+
self.handle_errors = handle_errors
|
|
252
|
+
|
|
253
|
+
def _iter_variants(schema: Any) -> Iterable[Any]:
|
|
254
|
+
"""Yield leaf variants from Union and JSON Schema oneOf."""
|
|
255
|
+
if get_origin(schema) in {UnionType, Union}:
|
|
256
|
+
for arg in get_args(schema):
|
|
257
|
+
yield from _iter_variants(arg)
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
if isinstance(schema, dict) and "oneOf" in schema:
|
|
261
|
+
for sub in schema.get("oneOf", []):
|
|
262
|
+
yield from _iter_variants(sub)
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
yield schema
|
|
266
|
+
|
|
267
|
+
self.schema_specs = [_SchemaSpec(s) for s in _iter_variants(schema)]
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@dataclass(init=False)
|
|
271
|
+
class ProviderStrategy(Generic[SchemaT]):
|
|
272
|
+
"""Use the model provider's native structured output method."""
|
|
273
|
+
|
|
274
|
+
schema: type[SchemaT] | dict[str, Any]
|
|
275
|
+
"""Schema for native mode."""
|
|
276
|
+
|
|
277
|
+
schema_spec: _SchemaSpec[SchemaT]
|
|
278
|
+
"""Schema spec for native mode."""
|
|
279
|
+
|
|
280
|
+
def __init__(
|
|
281
|
+
self,
|
|
282
|
+
schema: type[SchemaT] | dict[str, Any],
|
|
283
|
+
*,
|
|
284
|
+
strict: bool | None = None,
|
|
285
|
+
) -> None:
|
|
286
|
+
"""Initialize `ProviderStrategy` with schema.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
schema: Schema to enforce via the provider's native structured output.
|
|
290
|
+
strict: Whether to request strict provider-side schema enforcement.
|
|
291
|
+
"""
|
|
292
|
+
self.schema = schema
|
|
293
|
+
self.schema_spec = _SchemaSpec(schema, strict=strict)
|
|
294
|
+
|
|
295
|
+
def to_model_kwargs(self) -> dict[str, Any]:
|
|
296
|
+
"""Convert to kwargs to bind to a model to force structured output.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
The kwargs to bind to a model.
|
|
300
|
+
"""
|
|
301
|
+
# OpenAI:
|
|
302
|
+
# - see https://platform.openai.com/docs/guides/structured-outputs
|
|
303
|
+
json_schema: dict[str, Any] = {
|
|
304
|
+
"name": self.schema_spec.name,
|
|
305
|
+
"schema": self.schema_spec.json_schema,
|
|
306
|
+
}
|
|
307
|
+
if self.schema_spec.strict:
|
|
308
|
+
json_schema["strict"] = True
|
|
309
|
+
|
|
310
|
+
response_format: dict[str, Any] = {
|
|
311
|
+
"type": "json_schema",
|
|
312
|
+
"json_schema": json_schema,
|
|
313
|
+
}
|
|
314
|
+
return {"response_format": response_format}
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@dataclass
|
|
318
|
+
class OutputToolBinding(Generic[SchemaT]):
|
|
319
|
+
"""Information for tracking structured output tool metadata.
|
|
320
|
+
|
|
321
|
+
This contains all necessary information to handle structured responses generated via
|
|
322
|
+
tool calls, including the original schema, its type classification, and the
|
|
323
|
+
corresponding tool implementation used by the tools strategy.
|
|
324
|
+
"""
|
|
325
|
+
|
|
326
|
+
schema: type[SchemaT] | dict[str, Any]
|
|
327
|
+
"""The original schema provided for structured output (Pydantic model, dataclass,
|
|
328
|
+
TypedDict, or JSON schema dict).
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
schema_kind: SchemaKind
|
|
332
|
+
"""Classification of the schema type for proper response construction."""
|
|
333
|
+
|
|
334
|
+
tool: BaseTool
|
|
335
|
+
"""Coco tool instance created from the schema for model binding."""
|
|
336
|
+
|
|
337
|
+
@classmethod
|
|
338
|
+
def from_schema_spec(cls, schema_spec: _SchemaSpec[SchemaT]) -> Self:
|
|
339
|
+
"""Create an `OutputToolBinding` instance from a `SchemaSpec`.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
schema_spec: The `SchemaSpec` to convert
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
An `OutputToolBinding` instance with the appropriate tool created
|
|
346
|
+
"""
|
|
347
|
+
return cls(
|
|
348
|
+
schema=schema_spec.schema,
|
|
349
|
+
schema_kind=schema_spec.schema_kind,
|
|
350
|
+
tool=StructuredTool(
|
|
351
|
+
args_schema=schema_spec.json_schema,
|
|
352
|
+
name=schema_spec.name,
|
|
353
|
+
description=schema_spec.description,
|
|
354
|
+
),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
def parse(self, tool_args: dict[str, Any]) -> SchemaT | dict[str, Any]:
|
|
358
|
+
"""Parse tool arguments according to the schema.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
tool_args: The arguments from the tool call
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
The parsed response according to the schema type
|
|
365
|
+
|
|
366
|
+
Raises:
|
|
367
|
+
ValueError: If parsing fails
|
|
368
|
+
"""
|
|
369
|
+
return _parse_with_schema(self.schema, self.schema_kind, tool_args)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@dataclass
|
|
373
|
+
class ProviderStrategyBinding(Generic[SchemaT]):
|
|
374
|
+
"""Information for tracking native structured output metadata.
|
|
375
|
+
|
|
376
|
+
This contains all necessary information to handle structured responses generated via
|
|
377
|
+
native provider output, including the original schema, its type classification, and
|
|
378
|
+
parsing logic for provider-enforced JSON.
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
schema: type[SchemaT] | dict[str, Any]
|
|
382
|
+
"""The original schema provided for structured output (Pydantic model, `dataclass`,
|
|
383
|
+
`TypedDict`, or JSON schema dict).
|
|
384
|
+
"""
|
|
385
|
+
|
|
386
|
+
schema_kind: SchemaKind
|
|
387
|
+
"""Classification of the schema type for proper response construction."""
|
|
388
|
+
|
|
389
|
+
@classmethod
|
|
390
|
+
def from_schema_spec(cls, schema_spec: _SchemaSpec[SchemaT]) -> Self:
|
|
391
|
+
"""Create a `ProviderStrategyBinding` instance from a `SchemaSpec`.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
schema_spec: The `SchemaSpec` to convert
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
A `ProviderStrategyBinding` instance for parsing native structured output
|
|
398
|
+
"""
|
|
399
|
+
return cls(
|
|
400
|
+
schema=schema_spec.schema,
|
|
401
|
+
schema_kind=schema_spec.schema_kind,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
def parse(self, response: AIMessage) -> SchemaT | dict[str, Any]:
|
|
405
|
+
"""Parse `AIMessage` content according to the schema.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
response: The `AIMessage` containing the structured output
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
The parsed response according to the schema
|
|
412
|
+
|
|
413
|
+
Raises:
|
|
414
|
+
ValueError: If text extraction, JSON parsing or schema validation fails
|
|
415
|
+
"""
|
|
416
|
+
# Extract text content from AIMessage and parse as JSON
|
|
417
|
+
raw_text = self._extract_text_content_from_message(response)
|
|
418
|
+
|
|
419
|
+
try:
|
|
420
|
+
data = json.loads(raw_text)
|
|
421
|
+
except Exception as e:
|
|
422
|
+
schema_name = getattr(self.schema, "__name__", "response_format")
|
|
423
|
+
msg = (
|
|
424
|
+
f"Native structured output expected valid JSON for {schema_name}, "
|
|
425
|
+
f"but parsing failed: {e}."
|
|
426
|
+
)
|
|
427
|
+
raise ValueError(msg) from e
|
|
428
|
+
|
|
429
|
+
# Parse according to schema
|
|
430
|
+
return _parse_with_schema(self.schema, self.schema_kind, data)
|
|
431
|
+
|
|
432
|
+
@staticmethod
|
|
433
|
+
def _extract_text_content_from_message(message: AIMessage) -> str:
|
|
434
|
+
"""Extract text content from an `AIMessage`.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
message: The AI message to extract text from
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
The extracted text content
|
|
441
|
+
"""
|
|
442
|
+
content = message.content
|
|
443
|
+
if isinstance(content, str):
|
|
444
|
+
return content
|
|
445
|
+
parts: list[str] = []
|
|
446
|
+
for c in content:
|
|
447
|
+
if isinstance(c, dict):
|
|
448
|
+
if c.get("type") == "text" and "text" in c:
|
|
449
|
+
parts.append(str(c["text"]))
|
|
450
|
+
elif "content" in c and isinstance(c["content"], str):
|
|
451
|
+
parts.append(c["content"])
|
|
452
|
+
else:
|
|
453
|
+
parts.append(str(c))
|
|
454
|
+
return "".join(parts)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
class AutoStrategy(Generic[SchemaT]):
|
|
458
|
+
"""Automatically select the best strategy for structured output."""
|
|
459
|
+
|
|
460
|
+
schema: type[SchemaT] | dict[str, Any]
|
|
461
|
+
"""Schema for automatic mode."""
|
|
462
|
+
|
|
463
|
+
def __init__(
|
|
464
|
+
self,
|
|
465
|
+
schema: type[SchemaT] | dict[str, Any],
|
|
466
|
+
) -> None:
|
|
467
|
+
"""Initialize `AutoStrategy` with schema."""
|
|
468
|
+
self.schema = schema
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
ResponseFormat = ToolStrategy[SchemaT] | ProviderStrategy[SchemaT] | AutoStrategy[SchemaT]
|
|
472
|
+
"""Union type for all supported response format strategies."""
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Entrypoint to using [chat models](https://docs.langchain.com/oss/python/langchain/models) in Coco.""" # noqa: E501
|
|
2
|
+
|
|
3
|
+
from coco_core.language_models import BaseChatModel
|
|
4
|
+
|
|
5
|
+
from coco.chat_models.base import init_chat_model
|
|
6
|
+
|
|
7
|
+
__all__ = ["BaseChatModel", "init_chat_model"]
|