agno 2.2.13__py3-none-any.whl → 2.4.3__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.
- agno/agent/__init__.py +6 -0
- agno/agent/agent.py +5252 -3145
- agno/agent/remote.py +525 -0
- agno/api/api.py +2 -0
- agno/client/__init__.py +3 -0
- agno/client/a2a/__init__.py +10 -0
- agno/client/a2a/client.py +554 -0
- agno/client/a2a/schemas.py +112 -0
- agno/client/a2a/utils.py +369 -0
- agno/client/os.py +2669 -0
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/manager.py +2 -2
- agno/db/base.py +927 -6
- agno/db/dynamo/dynamo.py +788 -2
- agno/db/dynamo/schemas.py +128 -0
- agno/db/dynamo/utils.py +26 -3
- agno/db/firestore/firestore.py +674 -50
- agno/db/firestore/schemas.py +41 -0
- agno/db/firestore/utils.py +25 -10
- agno/db/gcs_json/gcs_json_db.py +506 -3
- agno/db/gcs_json/utils.py +14 -2
- agno/db/in_memory/in_memory_db.py +203 -4
- agno/db/in_memory/utils.py +14 -2
- agno/db/json/json_db.py +498 -2
- agno/db/json/utils.py +14 -2
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/utils.py +19 -0
- agno/db/migrations/v1_to_v2.py +54 -16
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +977 -0
- agno/db/mongo/async_mongo.py +1013 -39
- agno/db/mongo/mongo.py +684 -4
- agno/db/mongo/schemas.py +48 -0
- agno/db/mongo/utils.py +17 -0
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2958 -0
- agno/db/mysql/mysql.py +722 -53
- agno/db/mysql/schemas.py +77 -11
- agno/db/mysql/utils.py +151 -8
- agno/db/postgres/async_postgres.py +1254 -137
- agno/db/postgres/postgres.py +2316 -93
- agno/db/postgres/schemas.py +153 -21
- agno/db/postgres/utils.py +22 -7
- agno/db/redis/redis.py +531 -3
- agno/db/redis/schemas.py +36 -0
- agno/db/redis/utils.py +31 -15
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +20 -9
- agno/db/singlestore/schemas.py +70 -1
- agno/db/singlestore/singlestore.py +737 -74
- agno/db/singlestore/utils.py +13 -3
- agno/db/sqlite/async_sqlite.py +1069 -89
- agno/db/sqlite/schemas.py +133 -1
- agno/db/sqlite/sqlite.py +2203 -165
- agno/db/sqlite/utils.py +21 -11
- agno/db/surrealdb/models.py +25 -0
- agno/db/surrealdb/surrealdb.py +603 -1
- agno/db/utils.py +60 -0
- agno/eval/__init__.py +26 -3
- agno/eval/accuracy.py +25 -12
- agno/eval/agent_as_judge.py +871 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +10 -4
- agno/eval/reliability.py +22 -13
- agno/eval/utils.py +2 -1
- agno/exceptions.py +42 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +13 -2
- agno/knowledge/__init__.py +4 -0
- agno/knowledge/chunking/code.py +90 -0
- agno/knowledge/chunking/document.py +65 -4
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/markdown.py +102 -11
- agno/knowledge/chunking/recursive.py +2 -2
- agno/knowledge/chunking/semantic.py +130 -48
- agno/knowledge/chunking/strategy.py +18 -0
- agno/knowledge/embedder/azure_openai.py +0 -1
- agno/knowledge/embedder/google.py +1 -1
- agno/knowledge/embedder/mistral.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/openai.py +16 -12
- agno/knowledge/filesystem.py +412 -0
- agno/knowledge/knowledge.py +4261 -1199
- agno/knowledge/protocol.py +134 -0
- agno/knowledge/reader/arxiv_reader.py +3 -2
- agno/knowledge/reader/base.py +9 -7
- agno/knowledge/reader/csv_reader.py +91 -42
- agno/knowledge/reader/docx_reader.py +9 -10
- agno/knowledge/reader/excel_reader.py +225 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
- agno/knowledge/reader/firecrawl_reader.py +3 -2
- agno/knowledge/reader/json_reader.py +16 -22
- agno/knowledge/reader/markdown_reader.py +15 -14
- agno/knowledge/reader/pdf_reader.py +33 -28
- agno/knowledge/reader/pptx_reader.py +9 -10
- agno/knowledge/reader/reader_factory.py +135 -1
- agno/knowledge/reader/s3_reader.py +8 -16
- agno/knowledge/reader/tavily_reader.py +3 -3
- agno/knowledge/reader/text_reader.py +15 -14
- agno/knowledge/reader/utils/__init__.py +17 -0
- agno/knowledge/reader/utils/spreadsheet.py +114 -0
- agno/knowledge/reader/web_search_reader.py +8 -65
- agno/knowledge/reader/website_reader.py +16 -13
- agno/knowledge/reader/wikipedia_reader.py +36 -3
- agno/knowledge/reader/youtube_reader.py +3 -2
- agno/knowledge/remote_content/__init__.py +33 -0
- agno/knowledge/remote_content/config.py +266 -0
- agno/knowledge/remote_content/remote_content.py +105 -17
- agno/knowledge/utils.py +76 -22
- agno/learn/__init__.py +71 -0
- agno/learn/config.py +463 -0
- agno/learn/curate.py +185 -0
- agno/learn/machine.py +725 -0
- agno/learn/schemas.py +1114 -0
- agno/learn/stores/__init__.py +38 -0
- agno/learn/stores/decision_log.py +1156 -0
- agno/learn/stores/entity_memory.py +3275 -0
- agno/learn/stores/learned_knowledge.py +1583 -0
- agno/learn/stores/protocol.py +117 -0
- agno/learn/stores/session_context.py +1217 -0
- agno/learn/stores/user_memory.py +1495 -0
- agno/learn/stores/user_profile.py +1220 -0
- agno/learn/utils.py +209 -0
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +223 -8
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/aimlapi.py +17 -0
- agno/models/anthropic/claude.py +434 -59
- agno/models/aws/bedrock.py +121 -20
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +10 -6
- agno/models/azure/openai_chat.py +33 -10
- agno/models/base.py +1162 -561
- agno/models/cerebras/cerebras.py +120 -24
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +65 -6
- agno/models/cometapi/cometapi.py +18 -1
- agno/models/dashscope/dashscope.py +2 -3
- agno/models/deepinfra/deepinfra.py +18 -1
- agno/models/deepseek/deepseek.py +69 -3
- agno/models/fireworks/fireworks.py +18 -1
- agno/models/google/gemini.py +959 -89
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +48 -18
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/internlm/internlm.py +18 -1
- agno/models/langdb/langdb.py +13 -1
- agno/models/litellm/chat.py +88 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +24 -5
- agno/models/meta/llama.py +40 -13
- agno/models/meta/llama_openai.py +22 -21
- agno/models/metrics.py +12 -0
- agno/models/mistral/mistral.py +8 -4
- agno/models/n1n/__init__.py +3 -0
- agno/models/n1n/n1n.py +57 -0
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/__init__.py +2 -0
- agno/models/ollama/chat.py +17 -6
- agno/models/ollama/responses.py +100 -0
- agno/models/openai/__init__.py +2 -0
- agno/models/openai/chat.py +117 -26
- agno/models/openai/open_responses.py +46 -0
- agno/models/openai/responses.py +110 -32
- agno/models/openrouter/__init__.py +2 -0
- agno/models/openrouter/openrouter.py +67 -2
- agno/models/openrouter/responses.py +146 -0
- agno/models/perplexity/perplexity.py +19 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +19 -2
- agno/models/response.py +20 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/claude.py +124 -4
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +467 -137
- agno/os/auth.py +253 -5
- agno/os/config.py +22 -0
- agno/os/interfaces/a2a/a2a.py +7 -6
- agno/os/interfaces/a2a/router.py +635 -26
- agno/os/interfaces/a2a/utils.py +32 -33
- agno/os/interfaces/agui/agui.py +5 -3
- agno/os/interfaces/agui/router.py +26 -16
- agno/os/interfaces/agui/utils.py +97 -57
- agno/os/interfaces/base.py +7 -7
- agno/os/interfaces/slack/router.py +16 -7
- agno/os/interfaces/slack/slack.py +7 -7
- agno/os/interfaces/whatsapp/router.py +35 -7
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/interfaces/whatsapp/whatsapp.py +11 -8
- agno/os/managers.py +326 -0
- agno/os/mcp.py +652 -79
- agno/os/middleware/__init__.py +4 -0
- agno/os/middleware/jwt.py +718 -115
- agno/os/middleware/trailing_slash.py +27 -0
- agno/os/router.py +105 -1558
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +655 -0
- agno/os/routers/agents/schema.py +288 -0
- agno/os/routers/components/__init__.py +3 -0
- agno/os/routers/components/components.py +475 -0
- agno/os/routers/database.py +155 -0
- agno/os/routers/evals/evals.py +111 -18
- agno/os/routers/evals/schemas.py +38 -5
- agno/os/routers/evals/utils.py +80 -11
- agno/os/routers/health.py +3 -3
- agno/os/routers/knowledge/knowledge.py +284 -35
- agno/os/routers/knowledge/schemas.py +14 -2
- agno/os/routers/memory/memory.py +274 -11
- agno/os/routers/memory/schemas.py +44 -3
- agno/os/routers/metrics/metrics.py +30 -15
- agno/os/routers/metrics/schemas.py +10 -6
- agno/os/routers/registry/__init__.py +3 -0
- agno/os/routers/registry/registry.py +337 -0
- agno/os/routers/session/session.py +143 -14
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +550 -0
- agno/os/routers/teams/schema.py +280 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +549 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +757 -0
- agno/os/routers/workflows/schema.py +139 -0
- agno/os/schema.py +157 -584
- agno/os/scopes.py +469 -0
- agno/os/settings.py +3 -0
- agno/os/utils.py +574 -185
- agno/reasoning/anthropic.py +85 -1
- agno/reasoning/azure_ai_foundry.py +93 -1
- agno/reasoning/deepseek.py +102 -2
- agno/reasoning/default.py +6 -7
- agno/reasoning/gemini.py +87 -3
- agno/reasoning/groq.py +109 -2
- agno/reasoning/helpers.py +6 -7
- agno/reasoning/manager.py +1238 -0
- agno/reasoning/ollama.py +93 -1
- agno/reasoning/openai.py +115 -1
- agno/reasoning/vertexai.py +85 -1
- agno/registry/__init__.py +3 -0
- agno/registry/registry.py +68 -0
- agno/remote/__init__.py +3 -0
- agno/remote/base.py +581 -0
- agno/run/__init__.py +2 -4
- agno/run/agent.py +134 -19
- agno/run/base.py +49 -1
- agno/run/cancel.py +65 -52
- agno/run/cancellation_management/__init__.py +9 -0
- agno/run/cancellation_management/base.py +78 -0
- agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
- agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
- agno/run/requirement.py +181 -0
- agno/run/team.py +111 -19
- agno/run/workflow.py +2 -1
- agno/session/agent.py +57 -92
- agno/session/summary.py +1 -1
- agno/session/team.py +62 -115
- agno/session/workflow.py +353 -57
- agno/skills/__init__.py +17 -0
- agno/skills/agent_skills.py +377 -0
- agno/skills/errors.py +32 -0
- agno/skills/loaders/__init__.py +4 -0
- agno/skills/loaders/base.py +27 -0
- agno/skills/loaders/local.py +216 -0
- agno/skills/skill.py +65 -0
- agno/skills/utils.py +107 -0
- agno/skills/validator.py +277 -0
- agno/table.py +10 -0
- agno/team/__init__.py +5 -1
- agno/team/remote.py +447 -0
- agno/team/team.py +3769 -2202
- agno/tools/brandfetch.py +27 -18
- agno/tools/browserbase.py +225 -16
- agno/tools/crawl4ai.py +3 -0
- agno/tools/duckduckgo.py +25 -71
- agno/tools/exa.py +0 -21
- agno/tools/file.py +14 -13
- agno/tools/file_generation.py +12 -6
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +94 -113
- agno/tools/google_bigquery.py +11 -2
- agno/tools/google_drive.py +4 -3
- agno/tools/knowledge.py +9 -4
- agno/tools/mcp/mcp.py +301 -18
- agno/tools/mcp/multi_mcp.py +269 -14
- agno/tools/mem0.py +11 -10
- agno/tools/memory.py +47 -46
- agno/tools/mlx_transcribe.py +10 -7
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/parallel.py +0 -7
- agno/tools/postgres.py +76 -36
- agno/tools/python.py +14 -6
- agno/tools/reasoning.py +30 -23
- agno/tools/redshift.py +406 -0
- agno/tools/shopify.py +1519 -0
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +4 -1
- agno/tools/toolkit.py +253 -18
- agno/tools/websearch.py +93 -0
- agno/tools/website.py +1 -1
- agno/tools/wikipedia.py +1 -1
- agno/tools/workflow.py +56 -48
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +161 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +112 -0
- agno/utils/agent.py +251 -10
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +264 -7
- agno/utils/hooks.py +111 -3
- agno/utils/http.py +161 -2
- agno/utils/mcp.py +49 -8
- agno/utils/media.py +22 -1
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +20 -5
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/os.py +0 -0
- agno/utils/print_response/agent.py +99 -16
- agno/utils/print_response/team.py +223 -24
- agno/utils/print_response/workflow.py +0 -2
- agno/utils/prompts.py +8 -6
- agno/utils/remote.py +23 -0
- agno/utils/response.py +1 -13
- agno/utils/string.py +91 -2
- agno/utils/team.py +62 -12
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +15 -2
- agno/vectordb/cassandra/cassandra.py +1 -1
- agno/vectordb/chroma/__init__.py +2 -1
- agno/vectordb/chroma/chromadb.py +468 -23
- agno/vectordb/clickhouse/clickhousedb.py +1 -1
- agno/vectordb/couchbase/couchbase.py +6 -2
- agno/vectordb/lancedb/lance_db.py +7 -38
- agno/vectordb/lightrag/lightrag.py +7 -6
- agno/vectordb/milvus/milvus.py +118 -84
- agno/vectordb/mongodb/__init__.py +2 -1
- agno/vectordb/mongodb/mongodb.py +14 -31
- agno/vectordb/pgvector/pgvector.py +120 -66
- agno/vectordb/pineconedb/pineconedb.py +2 -19
- agno/vectordb/qdrant/__init__.py +2 -1
- agno/vectordb/qdrant/qdrant.py +33 -56
- agno/vectordb/redis/__init__.py +2 -1
- agno/vectordb/redis/redisdb.py +19 -31
- agno/vectordb/singlestore/singlestore.py +17 -9
- agno/vectordb/surrealdb/surrealdb.py +2 -38
- agno/vectordb/weaviate/__init__.py +2 -1
- agno/vectordb/weaviate/weaviate.py +7 -3
- agno/workflow/__init__.py +5 -1
- agno/workflow/agent.py +2 -2
- agno/workflow/condition.py +12 -10
- agno/workflow/loop.py +28 -9
- agno/workflow/parallel.py +21 -13
- agno/workflow/remote.py +362 -0
- agno/workflow/router.py +12 -9
- agno/workflow/step.py +261 -36
- agno/workflow/steps.py +12 -8
- agno/workflow/types.py +40 -77
- agno/workflow/workflow.py +939 -213
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
- agno-2.4.3.dist-info/RECORD +677 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
- agno/tools/googlesearch.py +0 -98
- agno/tools/memori.py +0 -339
- agno-2.2.13.dist-info/RECORD +0 -575
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
agno/models/anthropic/claude.py
CHANGED
|
@@ -4,7 +4,8 @@ from dataclasses import asdict, dataclass
|
|
|
4
4
|
from os import getenv
|
|
5
5
|
from typing import Any, Dict, List, Optional, Type, Union
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import httpx
|
|
8
|
+
from pydantic import BaseModel, ValidationError
|
|
8
9
|
|
|
9
10
|
from agno.exceptions import ModelProviderError, ModelRateLimitError
|
|
10
11
|
from agno.models.base import Model
|
|
@@ -12,8 +13,11 @@ from agno.models.message import Citations, DocumentCitation, Message, UrlCitatio
|
|
|
12
13
|
from agno.models.metrics import Metrics
|
|
13
14
|
from agno.models.response import ModelResponse
|
|
14
15
|
from agno.run.agent import RunOutput
|
|
16
|
+
from agno.tools.function import Function
|
|
17
|
+
from agno.utils.http import get_default_async_client, get_default_sync_client
|
|
15
18
|
from agno.utils.log import log_debug, log_error, log_warning
|
|
16
19
|
from agno.utils.models.claude import MCPServerConfiguration, format_messages, format_tools_for_model
|
|
20
|
+
from agno.utils.tokens import count_schema_tokens
|
|
17
21
|
|
|
18
22
|
try:
|
|
19
23
|
from anthropic import Anthropic as AnthropicClient
|
|
@@ -25,6 +29,11 @@ try:
|
|
|
25
29
|
from anthropic import (
|
|
26
30
|
AsyncAnthropic as AsyncAnthropicClient,
|
|
27
31
|
)
|
|
32
|
+
from anthropic.lib.streaming._beta_types import (
|
|
33
|
+
BetaRawContentBlockStartEvent,
|
|
34
|
+
ParsedBetaContentBlockStopEvent,
|
|
35
|
+
ParsedBetaMessageStopEvent,
|
|
36
|
+
)
|
|
28
37
|
from anthropic.types import (
|
|
29
38
|
CitationPageLocation,
|
|
30
39
|
CitationsWebSearchResultLocation,
|
|
@@ -39,6 +48,7 @@ try:
|
|
|
39
48
|
from anthropic.types import (
|
|
40
49
|
Message as AnthropicMessage,
|
|
41
50
|
)
|
|
51
|
+
|
|
42
52
|
except ImportError as e:
|
|
43
53
|
raise ImportError("`anthropic` not installed. Please install it with `pip install anthropic`") from e
|
|
44
54
|
|
|
@@ -72,6 +82,30 @@ class Claude(Model):
|
|
|
72
82
|
"claude-3-5-haiku-latest",
|
|
73
83
|
}
|
|
74
84
|
|
|
85
|
+
# Models that DO NOT support native structured outputs
|
|
86
|
+
# All future models are assumed to support structured outputs
|
|
87
|
+
NON_STRUCTURED_OUTPUT_MODELS = {
|
|
88
|
+
# Claude 3.x family (all versions)
|
|
89
|
+
"claude-3-opus-20240229",
|
|
90
|
+
"claude-3-sonnet-20240229",
|
|
91
|
+
"claude-3-haiku-20240307",
|
|
92
|
+
"claude-3-opus",
|
|
93
|
+
"claude-3-sonnet",
|
|
94
|
+
"claude-3-haiku",
|
|
95
|
+
# Claude 3.5 family (all versions except Sonnet 4.5)
|
|
96
|
+
"claude-3-5-sonnet-20240620",
|
|
97
|
+
"claude-3-5-sonnet-20241022",
|
|
98
|
+
"claude-3-5-sonnet",
|
|
99
|
+
"claude-3-5-haiku-20241022",
|
|
100
|
+
"claude-3-5-haiku-latest",
|
|
101
|
+
"claude-3-5-haiku",
|
|
102
|
+
# Claude Sonnet 4.x family (versions before 4.5)
|
|
103
|
+
"claude-sonnet-4-20250514",
|
|
104
|
+
"claude-sonnet-4",
|
|
105
|
+
# Claude Opus 4.x family (versions before 4.1 and 4.5)
|
|
106
|
+
# (Add any Opus 4.x models released before 4.1/4.5 if they exist)
|
|
107
|
+
}
|
|
108
|
+
|
|
75
109
|
id: str = "claude-sonnet-4-5-20250929"
|
|
76
110
|
name: str = "Claude"
|
|
77
111
|
provider: str = "Anthropic"
|
|
@@ -97,8 +131,10 @@ class Claude(Model):
|
|
|
97
131
|
|
|
98
132
|
# Client parameters
|
|
99
133
|
api_key: Optional[str] = None
|
|
134
|
+
auth_token: Optional[str] = None
|
|
100
135
|
default_headers: Optional[Dict[str, Any]] = None
|
|
101
136
|
timeout: Optional[float] = None
|
|
137
|
+
http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
|
|
102
138
|
client_params: Optional[Dict[str, Any]] = None
|
|
103
139
|
|
|
104
140
|
client: Optional[AnthropicClient] = None
|
|
@@ -109,6 +145,9 @@ class Claude(Model):
|
|
|
109
145
|
# Validate thinking support immediately at model creation
|
|
110
146
|
if self.thinking:
|
|
111
147
|
self._validate_thinking_support()
|
|
148
|
+
# Set structured outputs capability flag for supported models
|
|
149
|
+
if self._supports_structured_outputs():
|
|
150
|
+
self.supports_native_structured_outputs = True
|
|
112
151
|
# Set up skills configuration if skills are enabled
|
|
113
152
|
if self.skills:
|
|
114
153
|
self._setup_skills_configuration()
|
|
@@ -117,11 +156,15 @@ class Claude(Model):
|
|
|
117
156
|
client_params: Dict[str, Any] = {}
|
|
118
157
|
|
|
119
158
|
self.api_key = self.api_key or getenv("ANTHROPIC_API_KEY")
|
|
120
|
-
|
|
121
|
-
|
|
159
|
+
self.auth_token = self.auth_token or getenv("ANTHROPIC_AUTH_TOKEN")
|
|
160
|
+
if not (self.api_key or self.auth_token):
|
|
161
|
+
log_error(
|
|
162
|
+
"ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN not set. Please set the ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN environment variable."
|
|
163
|
+
)
|
|
122
164
|
|
|
123
165
|
# Add API key to client parameters
|
|
124
166
|
client_params["api_key"] = self.api_key
|
|
167
|
+
client_params["auth_token"] = self.auth_token
|
|
125
168
|
if self.timeout is not None:
|
|
126
169
|
client_params["timeout"] = self.timeout
|
|
127
170
|
|
|
@@ -132,36 +175,63 @@ class Claude(Model):
|
|
|
132
175
|
client_params["default_headers"] = self.default_headers
|
|
133
176
|
return client_params
|
|
134
177
|
|
|
135
|
-
def
|
|
136
|
-
"""
|
|
137
|
-
|
|
138
|
-
self.mcp_servers is not None
|
|
139
|
-
or self.context_management is not None
|
|
140
|
-
or self.skills is not None
|
|
141
|
-
or self.betas is not None
|
|
142
|
-
)
|
|
178
|
+
def _supports_structured_outputs(self) -> bool:
|
|
179
|
+
"""
|
|
180
|
+
Check if the current model supports native structured outputs.
|
|
143
181
|
|
|
144
|
-
|
|
182
|
+
Returns:
|
|
183
|
+
bool: True if model supports structured outputs
|
|
145
184
|
"""
|
|
146
|
-
|
|
185
|
+
# If model is in blacklist, it doesn't support structured outputs
|
|
186
|
+
if self.id in self.NON_STRUCTURED_OUTPUT_MODELS:
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
# Check for legacy model patterns which don't support structured outputs
|
|
190
|
+
if self.id.startswith("claude-3-"):
|
|
191
|
+
return False
|
|
192
|
+
if self.id.startswith("claude-sonnet-4-") and not self.id.startswith("claude-sonnet-4-5"):
|
|
193
|
+
return False
|
|
194
|
+
if self.id.startswith("claude-opus-4-") and not (
|
|
195
|
+
self.id.startswith("claude-opus-4-1") or self.id.startswith("claude-opus-4-5")
|
|
196
|
+
):
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
return True
|
|
200
|
+
|
|
201
|
+
def _using_structured_outputs(
|
|
202
|
+
self,
|
|
203
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
204
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
205
|
+
) -> bool:
|
|
147
206
|
"""
|
|
148
|
-
if
|
|
149
|
-
return self.client
|
|
207
|
+
Check if structured outputs are being used in this request.
|
|
150
208
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
209
|
+
Args:
|
|
210
|
+
response_format: Response format parameter
|
|
211
|
+
tools: Tools list to check for strict mode
|
|
154
212
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
Returns an instance of the async Anthropic client.
|
|
213
|
+
Returns:
|
|
214
|
+
bool: True if structured outputs are in use
|
|
158
215
|
"""
|
|
159
|
-
|
|
160
|
-
|
|
216
|
+
# Check for output_format usage
|
|
217
|
+
if response_format is not None:
|
|
218
|
+
if self._supports_structured_outputs():
|
|
219
|
+
return True
|
|
220
|
+
else:
|
|
221
|
+
log_warning(
|
|
222
|
+
f"Model '{self.id}' does not support structured outputs. "
|
|
223
|
+
"Structured output features will not be available for this model."
|
|
224
|
+
)
|
|
161
225
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
226
|
+
# Check for strict tools
|
|
227
|
+
if tools:
|
|
228
|
+
for tool in tools:
|
|
229
|
+
if tool.get("type") == "function":
|
|
230
|
+
func_def = tool.get("function", {})
|
|
231
|
+
if func_def.get("strict") is True:
|
|
232
|
+
return True
|
|
233
|
+
|
|
234
|
+
return False
|
|
165
235
|
|
|
166
236
|
def _validate_thinking_support(self) -> None:
|
|
167
237
|
"""
|
|
@@ -199,7 +269,214 @@ class Claude(Model):
|
|
|
199
269
|
if beta not in self.betas:
|
|
200
270
|
self.betas.append(beta)
|
|
201
271
|
|
|
202
|
-
def
|
|
272
|
+
def _ensure_additional_properties_false(self, schema: Dict[str, Any]) -> None:
|
|
273
|
+
"""
|
|
274
|
+
Recursively ensure all object types have additionalProperties: false.
|
|
275
|
+
"""
|
|
276
|
+
if isinstance(schema, dict):
|
|
277
|
+
if schema.get("type") == "object":
|
|
278
|
+
schema["additionalProperties"] = False
|
|
279
|
+
|
|
280
|
+
# Recursively process nested schemas
|
|
281
|
+
for key, value in schema.items():
|
|
282
|
+
if key in ["properties", "items", "allOf", "anyOf", "oneOf"]:
|
|
283
|
+
if isinstance(value, dict):
|
|
284
|
+
self._ensure_additional_properties_false(value)
|
|
285
|
+
elif isinstance(value, list):
|
|
286
|
+
for item in value:
|
|
287
|
+
if isinstance(item, dict):
|
|
288
|
+
self._ensure_additional_properties_false(item)
|
|
289
|
+
|
|
290
|
+
def _build_output_format(self, response_format: Optional[Union[Dict, Type[BaseModel]]]) -> Optional[Dict[str, Any]]:
|
|
291
|
+
"""
|
|
292
|
+
Build Anthropic output_format parameter from response_format.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
response_format: Pydantic model or dict format
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Dict with output_format structure or None
|
|
299
|
+
"""
|
|
300
|
+
if response_format is None:
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
if not self._supports_structured_outputs():
|
|
304
|
+
return None
|
|
305
|
+
|
|
306
|
+
# Handle Pydantic BaseModel
|
|
307
|
+
if isinstance(response_format, type) and issubclass(response_format, BaseModel):
|
|
308
|
+
try:
|
|
309
|
+
# Try to use Anthropic SDK's transform_schema helper if available
|
|
310
|
+
from anthropic import transform_schema
|
|
311
|
+
|
|
312
|
+
schema = transform_schema(response_format.model_json_schema())
|
|
313
|
+
except (ImportError, AttributeError):
|
|
314
|
+
# Fallback to direct schema conversion
|
|
315
|
+
schema = response_format.model_json_schema()
|
|
316
|
+
# Ensure additionalProperties is False
|
|
317
|
+
if isinstance(schema, dict):
|
|
318
|
+
if "additionalProperties" not in schema:
|
|
319
|
+
schema["additionalProperties"] = False
|
|
320
|
+
# Recursively ensure all object types have additionalProperties: false
|
|
321
|
+
self._ensure_additional_properties_false(schema)
|
|
322
|
+
|
|
323
|
+
return {"type": "json_schema", "schema": schema}
|
|
324
|
+
|
|
325
|
+
# Handle dict format
|
|
326
|
+
elif isinstance(response_format, dict):
|
|
327
|
+
# Claude only supports json_schema, not json_object
|
|
328
|
+
if response_format.get("type") == "json_object":
|
|
329
|
+
return None
|
|
330
|
+
return response_format
|
|
331
|
+
|
|
332
|
+
return None
|
|
333
|
+
|
|
334
|
+
def _validate_structured_outputs_usage(
|
|
335
|
+
self,
|
|
336
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
337
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
338
|
+
) -> None:
|
|
339
|
+
"""
|
|
340
|
+
Validate that structured outputs are only used with supported models.
|
|
341
|
+
|
|
342
|
+
Raises:
|
|
343
|
+
ValueError: If structured outputs are used with unsupported model
|
|
344
|
+
"""
|
|
345
|
+
if not self._using_structured_outputs(response_format, tools):
|
|
346
|
+
return
|
|
347
|
+
|
|
348
|
+
if not self._supports_structured_outputs():
|
|
349
|
+
raise ValueError(f"Model '{self.id}' does not support structured outputs.\n\n")
|
|
350
|
+
|
|
351
|
+
def _has_beta_features(
|
|
352
|
+
self,
|
|
353
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
354
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
355
|
+
) -> bool:
|
|
356
|
+
"""Check if the model has any Anthropic beta features enabled."""
|
|
357
|
+
return (
|
|
358
|
+
self.mcp_servers is not None
|
|
359
|
+
or self.context_management is not None
|
|
360
|
+
or self.skills is not None
|
|
361
|
+
or self.betas is not None
|
|
362
|
+
or self._using_structured_outputs(response_format, tools)
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
def get_client(self) -> AnthropicClient:
|
|
366
|
+
"""
|
|
367
|
+
Returns an instance of the Anthropic client.
|
|
368
|
+
"""
|
|
369
|
+
if self.client and not self.client.is_closed():
|
|
370
|
+
return self.client
|
|
371
|
+
|
|
372
|
+
_client_params = self._get_client_params()
|
|
373
|
+
if self.http_client:
|
|
374
|
+
if isinstance(self.http_client, httpx.Client):
|
|
375
|
+
_client_params["http_client"] = self.http_client
|
|
376
|
+
else:
|
|
377
|
+
log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
|
|
378
|
+
# Use global sync client when user http_client is invalid
|
|
379
|
+
_client_params["http_client"] = get_default_sync_client()
|
|
380
|
+
else:
|
|
381
|
+
# Use global sync client when no custom http_client is provided
|
|
382
|
+
_client_params["http_client"] = get_default_sync_client()
|
|
383
|
+
self.client = AnthropicClient(**_client_params)
|
|
384
|
+
return self.client
|
|
385
|
+
|
|
386
|
+
def get_async_client(self) -> AsyncAnthropicClient:
|
|
387
|
+
"""
|
|
388
|
+
Returns an instance of the async Anthropic client.
|
|
389
|
+
"""
|
|
390
|
+
if self.async_client and not self.async_client.is_closed():
|
|
391
|
+
return self.async_client
|
|
392
|
+
|
|
393
|
+
_client_params = self._get_client_params()
|
|
394
|
+
if self.http_client:
|
|
395
|
+
if isinstance(self.http_client, httpx.AsyncClient):
|
|
396
|
+
_client_params["http_client"] = self.http_client
|
|
397
|
+
else:
|
|
398
|
+
log_warning(
|
|
399
|
+
"http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
|
|
400
|
+
)
|
|
401
|
+
# Use global async client when user http_client is invalid
|
|
402
|
+
_client_params["http_client"] = get_default_async_client()
|
|
403
|
+
else:
|
|
404
|
+
# Use global async client when no custom http_client is provided
|
|
405
|
+
_client_params["http_client"] = get_default_async_client()
|
|
406
|
+
self.async_client = AsyncAnthropicClient(**_client_params)
|
|
407
|
+
return self.async_client
|
|
408
|
+
|
|
409
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
410
|
+
"""
|
|
411
|
+
Convert the model to a dictionary.
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
Dict[str, Any]: The dictionary representation of the model.
|
|
415
|
+
"""
|
|
416
|
+
model_dict = super().to_dict()
|
|
417
|
+
model_dict.update(
|
|
418
|
+
{
|
|
419
|
+
"max_tokens": self.max_tokens,
|
|
420
|
+
"thinking": self.thinking,
|
|
421
|
+
"temperature": self.temperature,
|
|
422
|
+
"stop_sequences": self.stop_sequences,
|
|
423
|
+
"top_p": self.top_p,
|
|
424
|
+
"top_k": self.top_k,
|
|
425
|
+
"cache_system_prompt": self.cache_system_prompt,
|
|
426
|
+
"extended_cache_time": self.extended_cache_time,
|
|
427
|
+
"betas": self.betas,
|
|
428
|
+
}
|
|
429
|
+
)
|
|
430
|
+
cleaned_dict = {k: v for k, v in model_dict.items() if v is not None}
|
|
431
|
+
return cleaned_dict
|
|
432
|
+
|
|
433
|
+
def count_tokens(
|
|
434
|
+
self,
|
|
435
|
+
messages: List[Message],
|
|
436
|
+
tools: Optional[List[Union[Function, Dict[str, Any]]]] = None,
|
|
437
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
438
|
+
) -> int:
|
|
439
|
+
anthropic_messages, system_prompt = format_messages(messages, compress_tool_results=True)
|
|
440
|
+
anthropic_tools = None
|
|
441
|
+
if tools:
|
|
442
|
+
formatted_tools = self._format_tools(tools)
|
|
443
|
+
anthropic_tools = format_tools_for_model(formatted_tools)
|
|
444
|
+
|
|
445
|
+
kwargs: Dict[str, Any] = {"messages": anthropic_messages, "model": self.id}
|
|
446
|
+
if system_prompt:
|
|
447
|
+
kwargs["system"] = system_prompt
|
|
448
|
+
if anthropic_tools:
|
|
449
|
+
kwargs["tools"] = anthropic_tools
|
|
450
|
+
|
|
451
|
+
response = self.get_client().messages.count_tokens(**kwargs)
|
|
452
|
+
return response.input_tokens + count_schema_tokens(response_format, self.id)
|
|
453
|
+
|
|
454
|
+
async def acount_tokens(
|
|
455
|
+
self,
|
|
456
|
+
messages: List[Message],
|
|
457
|
+
tools: Optional[List[Union[Function, Dict[str, Any]]]] = None,
|
|
458
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
459
|
+
) -> int:
|
|
460
|
+
anthropic_messages, system_prompt = format_messages(messages, compress_tool_results=True)
|
|
461
|
+
anthropic_tools = None
|
|
462
|
+
if tools:
|
|
463
|
+
formatted_tools = self._format_tools(tools)
|
|
464
|
+
anthropic_tools = format_tools_for_model(formatted_tools)
|
|
465
|
+
|
|
466
|
+
kwargs: Dict[str, Any] = {"messages": anthropic_messages, "model": self.id}
|
|
467
|
+
if system_prompt:
|
|
468
|
+
kwargs["system"] = system_prompt
|
|
469
|
+
if anthropic_tools:
|
|
470
|
+
kwargs["tools"] = anthropic_tools
|
|
471
|
+
|
|
472
|
+
response = await self.get_async_client().messages.count_tokens(**kwargs)
|
|
473
|
+
return response.input_tokens + count_schema_tokens(response_format, self.id)
|
|
474
|
+
|
|
475
|
+
def get_request_params(
|
|
476
|
+
self,
|
|
477
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
478
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
479
|
+
) -> Dict[str, Any]:
|
|
203
480
|
"""
|
|
204
481
|
Generate keyword arguments for API requests.
|
|
205
482
|
"""
|
|
@@ -220,8 +497,20 @@ class Claude(Model):
|
|
|
220
497
|
_request_params["top_p"] = self.top_p
|
|
221
498
|
if self.top_k:
|
|
222
499
|
_request_params["top_k"] = self.top_k
|
|
223
|
-
|
|
224
|
-
|
|
500
|
+
|
|
501
|
+
# Build betas list - include existing betas and add new one if needed
|
|
502
|
+
betas_list = list(self.betas) if self.betas else []
|
|
503
|
+
|
|
504
|
+
# Add structured outputs beta header if using structured outputs
|
|
505
|
+
if self._using_structured_outputs(response_format, tools):
|
|
506
|
+
beta_header = "structured-outputs-2025-11-13"
|
|
507
|
+
if beta_header not in betas_list:
|
|
508
|
+
betas_list.append(beta_header)
|
|
509
|
+
|
|
510
|
+
# Include betas if any are present
|
|
511
|
+
if betas_list:
|
|
512
|
+
_request_params["betas"] = betas_list
|
|
513
|
+
|
|
225
514
|
if self.context_management:
|
|
226
515
|
_request_params["context_management"] = self.context_management
|
|
227
516
|
if self.mcp_servers:
|
|
@@ -229,7 +518,6 @@ class Claude(Model):
|
|
|
229
518
|
{k: v for k, v in asdict(server).items() if v is not None} for server in self.mcp_servers
|
|
230
519
|
]
|
|
231
520
|
if self.skills:
|
|
232
|
-
_request_params["betas"] = self.betas
|
|
233
521
|
_request_params["container"] = {"skills": self.skills}
|
|
234
522
|
if self.request_params:
|
|
235
523
|
_request_params.update(self.request_params)
|
|
@@ -237,18 +525,27 @@ class Claude(Model):
|
|
|
237
525
|
return _request_params
|
|
238
526
|
|
|
239
527
|
def _prepare_request_kwargs(
|
|
240
|
-
self,
|
|
528
|
+
self,
|
|
529
|
+
system_message: str,
|
|
530
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
531
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
241
532
|
) -> Dict[str, Any]:
|
|
242
533
|
"""
|
|
243
534
|
Prepare the request keyword arguments for the API call.
|
|
244
535
|
|
|
245
536
|
Args:
|
|
246
537
|
system_message (str): The concatenated system messages.
|
|
538
|
+
tools: Optional list of tools
|
|
539
|
+
response_format: Optional response format (Pydantic model or dict)
|
|
247
540
|
|
|
248
541
|
Returns:
|
|
249
542
|
Dict[str, Any]: The request keyword arguments.
|
|
250
543
|
"""
|
|
251
|
-
|
|
544
|
+
# Validate structured outputs usage
|
|
545
|
+
self._validate_structured_outputs_usage(response_format, tools)
|
|
546
|
+
|
|
547
|
+
# Pass response_format and tools to get_request_params for beta header handling
|
|
548
|
+
request_kwargs = self.get_request_params(response_format=response_format, tools=tools).copy()
|
|
252
549
|
if system_message:
|
|
253
550
|
if self.cache_system_prompt:
|
|
254
551
|
cache_control = (
|
|
@@ -269,9 +566,15 @@ class Claude(Model):
|
|
|
269
566
|
else:
|
|
270
567
|
tools = [code_execution_tool]
|
|
271
568
|
|
|
569
|
+
# Format tools (this will handle strict mode)
|
|
272
570
|
if tools:
|
|
273
571
|
request_kwargs["tools"] = format_tools_for_model(tools)
|
|
274
572
|
|
|
573
|
+
# Build output_format if response_format is provided
|
|
574
|
+
output_format = self._build_output_format(response_format)
|
|
575
|
+
if output_format:
|
|
576
|
+
request_kwargs["output_format"] = output_format
|
|
577
|
+
|
|
275
578
|
if request_kwargs:
|
|
276
579
|
log_debug(f"Calling {self.provider} with request parameters: {request_kwargs}", log_level=2)
|
|
277
580
|
return request_kwargs
|
|
@@ -284,6 +587,7 @@ class Claude(Model):
|
|
|
284
587
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
285
588
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
286
589
|
run_response: Optional[RunOutput] = None,
|
|
590
|
+
compress_tool_results: bool = False,
|
|
287
591
|
) -> ModelResponse:
|
|
288
592
|
"""
|
|
289
593
|
Send a request to the Anthropic API to generate a response.
|
|
@@ -292,10 +596,10 @@ class Claude(Model):
|
|
|
292
596
|
if run_response and run_response.metrics:
|
|
293
597
|
run_response.metrics.set_time_to_first_token()
|
|
294
598
|
|
|
295
|
-
chat_messages, system_message = format_messages(messages)
|
|
296
|
-
request_kwargs = self._prepare_request_kwargs(system_message, tools)
|
|
599
|
+
chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
|
|
600
|
+
request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
|
|
297
601
|
|
|
298
|
-
if self._has_beta_features():
|
|
602
|
+
if self._has_beta_features(response_format=response_format, tools=tools):
|
|
299
603
|
assistant_message.metrics.start_timer()
|
|
300
604
|
provider_response = self.get_client().beta.messages.create(
|
|
301
605
|
model=self.id,
|
|
@@ -340,6 +644,7 @@ class Claude(Model):
|
|
|
340
644
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
341
645
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
342
646
|
run_response: Optional[RunOutput] = None,
|
|
647
|
+
compress_tool_results: bool = False,
|
|
343
648
|
) -> Any:
|
|
344
649
|
"""
|
|
345
650
|
Stream a response from the Anthropic API.
|
|
@@ -355,15 +660,15 @@ class Claude(Model):
|
|
|
355
660
|
RateLimitError: If the API rate limit is exceeded
|
|
356
661
|
APIStatusError: For other API-related errors
|
|
357
662
|
"""
|
|
358
|
-
chat_messages, system_message = format_messages(messages)
|
|
359
|
-
request_kwargs = self._prepare_request_kwargs(system_message, tools)
|
|
663
|
+
chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
|
|
664
|
+
request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
|
|
360
665
|
|
|
361
666
|
try:
|
|
362
667
|
if run_response and run_response.metrics:
|
|
363
668
|
run_response.metrics.set_time_to_first_token()
|
|
364
669
|
|
|
365
670
|
# Beta features
|
|
366
|
-
if self._has_beta_features():
|
|
671
|
+
if self._has_beta_features(response_format=response_format, tools=tools):
|
|
367
672
|
assistant_message.metrics.start_timer()
|
|
368
673
|
with self.get_client().beta.messages.stream(
|
|
369
674
|
model=self.id,
|
|
@@ -371,7 +676,7 @@ class Claude(Model):
|
|
|
371
676
|
**request_kwargs,
|
|
372
677
|
) as stream:
|
|
373
678
|
for chunk in stream:
|
|
374
|
-
yield self._parse_provider_response_delta(chunk) # type: ignore
|
|
679
|
+
yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
|
|
375
680
|
else:
|
|
376
681
|
assistant_message.metrics.start_timer()
|
|
377
682
|
with self.get_client().messages.stream(
|
|
@@ -380,7 +685,7 @@ class Claude(Model):
|
|
|
380
685
|
**request_kwargs,
|
|
381
686
|
) as stream:
|
|
382
687
|
for chunk in stream: # type: ignore
|
|
383
|
-
yield self._parse_provider_response_delta(chunk) # type: ignore
|
|
688
|
+
yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
|
|
384
689
|
|
|
385
690
|
assistant_message.metrics.stop_timer()
|
|
386
691
|
|
|
@@ -407,6 +712,7 @@ class Claude(Model):
|
|
|
407
712
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
408
713
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
409
714
|
run_response: Optional[RunOutput] = None,
|
|
715
|
+
compress_tool_results: bool = False,
|
|
410
716
|
) -> ModelResponse:
|
|
411
717
|
"""
|
|
412
718
|
Send an asynchronous request to the Anthropic API to generate a response.
|
|
@@ -415,11 +721,11 @@ class Claude(Model):
|
|
|
415
721
|
if run_response and run_response.metrics:
|
|
416
722
|
run_response.metrics.set_time_to_first_token()
|
|
417
723
|
|
|
418
|
-
chat_messages, system_message = format_messages(messages)
|
|
419
|
-
request_kwargs = self._prepare_request_kwargs(system_message, tools)
|
|
724
|
+
chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
|
|
725
|
+
request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
|
|
420
726
|
|
|
421
727
|
# Beta features
|
|
422
|
-
if self._has_beta_features():
|
|
728
|
+
if self._has_beta_features(response_format=response_format, tools=tools):
|
|
423
729
|
assistant_message.metrics.start_timer()
|
|
424
730
|
provider_response = await self.get_async_client().beta.messages.create(
|
|
425
731
|
model=self.id,
|
|
@@ -464,6 +770,7 @@ class Claude(Model):
|
|
|
464
770
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
465
771
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
466
772
|
run_response: Optional[RunOutput] = None,
|
|
773
|
+
compress_tool_results: bool = False,
|
|
467
774
|
) -> AsyncIterator[ModelResponse]:
|
|
468
775
|
"""
|
|
469
776
|
Stream an asynchronous response from the Anthropic API.
|
|
@@ -480,10 +787,10 @@ class Claude(Model):
|
|
|
480
787
|
if run_response and run_response.metrics:
|
|
481
788
|
run_response.metrics.set_time_to_first_token()
|
|
482
789
|
|
|
483
|
-
chat_messages, system_message = format_messages(messages)
|
|
484
|
-
request_kwargs = self._prepare_request_kwargs(system_message, tools)
|
|
790
|
+
chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
|
|
791
|
+
request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
|
|
485
792
|
|
|
486
|
-
if self._has_beta_features():
|
|
793
|
+
if self._has_beta_features(response_format=response_format, tools=tools):
|
|
487
794
|
assistant_message.metrics.start_timer()
|
|
488
795
|
async with self.get_async_client().beta.messages.stream(
|
|
489
796
|
model=self.id,
|
|
@@ -491,7 +798,7 @@ class Claude(Model):
|
|
|
491
798
|
**request_kwargs,
|
|
492
799
|
) as stream:
|
|
493
800
|
async for chunk in stream:
|
|
494
|
-
yield self._parse_provider_response_delta(chunk) # type: ignore
|
|
801
|
+
yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
|
|
495
802
|
else:
|
|
496
803
|
assistant_message.metrics.start_timer()
|
|
497
804
|
async with self.get_async_client().messages.stream(
|
|
@@ -500,7 +807,7 @@ class Claude(Model):
|
|
|
500
807
|
**request_kwargs,
|
|
501
808
|
) as stream:
|
|
502
809
|
async for chunk in stream: # type: ignore
|
|
503
|
-
yield self._parse_provider_response_delta(chunk) # type: ignore
|
|
810
|
+
yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
|
|
504
811
|
|
|
505
812
|
assistant_message.metrics.stop_timer()
|
|
506
813
|
|
|
@@ -525,12 +832,18 @@ class Claude(Model):
|
|
|
525
832
|
return tool_call_prompt
|
|
526
833
|
return None
|
|
527
834
|
|
|
528
|
-
def _parse_provider_response(
|
|
835
|
+
def _parse_provider_response(
|
|
836
|
+
self,
|
|
837
|
+
response: Union[AnthropicMessage, BetaMessage],
|
|
838
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
839
|
+
**kwargs,
|
|
840
|
+
) -> ModelResponse:
|
|
529
841
|
"""
|
|
530
842
|
Parse the Claude response into a ModelResponse.
|
|
531
843
|
|
|
532
844
|
Args:
|
|
533
845
|
response: Raw response from Anthropic
|
|
846
|
+
response_format: Optional response format for structured output parsing
|
|
534
847
|
|
|
535
848
|
Returns:
|
|
536
849
|
ModelResponse: Parsed response data
|
|
@@ -543,10 +856,32 @@ class Claude(Model):
|
|
|
543
856
|
if response.content:
|
|
544
857
|
for block in response.content:
|
|
545
858
|
if block.type == "text":
|
|
859
|
+
text_content = block.text
|
|
860
|
+
|
|
546
861
|
if model_response.content is None:
|
|
547
|
-
model_response.content =
|
|
862
|
+
model_response.content = text_content
|
|
548
863
|
else:
|
|
549
|
-
model_response.content +=
|
|
864
|
+
model_response.content += text_content
|
|
865
|
+
|
|
866
|
+
# Handle structured outputs (JSON outputs)
|
|
867
|
+
if (
|
|
868
|
+
response_format is not None
|
|
869
|
+
and isinstance(response_format, type)
|
|
870
|
+
and issubclass(response_format, BaseModel)
|
|
871
|
+
):
|
|
872
|
+
if text_content:
|
|
873
|
+
try:
|
|
874
|
+
# Parse JSON from text content
|
|
875
|
+
parsed_data = json.loads(text_content)
|
|
876
|
+
# Validate against Pydantic model
|
|
877
|
+
model_response.parsed = response_format.model_validate(parsed_data)
|
|
878
|
+
log_debug(f"Successfully parsed structured output: {model_response.parsed}")
|
|
879
|
+
except json.JSONDecodeError as e:
|
|
880
|
+
log_warning(f"Failed to parse JSON from structured output: {e}")
|
|
881
|
+
except ValidationError as e:
|
|
882
|
+
log_warning(f"Failed to validate structured output against schema: {e}")
|
|
883
|
+
except Exception as e:
|
|
884
|
+
log_warning(f"Unexpected error parsing structured output: {e}")
|
|
550
885
|
|
|
551
886
|
# Capture citations from the response
|
|
552
887
|
if block.citations is not None:
|
|
@@ -634,24 +969,29 @@ class Claude(Model):
|
|
|
634
969
|
ContentBlockStopEvent,
|
|
635
970
|
MessageStopEvent,
|
|
636
971
|
BetaRawContentBlockDeltaEvent,
|
|
972
|
+
BetaRawContentBlockStartEvent,
|
|
973
|
+
ParsedBetaContentBlockStopEvent,
|
|
974
|
+
ParsedBetaMessageStopEvent,
|
|
637
975
|
],
|
|
976
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
638
977
|
) -> ModelResponse:
|
|
639
978
|
"""
|
|
640
979
|
Parse the Claude streaming response into ModelProviderResponse objects.
|
|
641
980
|
|
|
642
981
|
Args:
|
|
643
982
|
response: Raw response chunk from Anthropic
|
|
983
|
+
response_format: Optional response format for structured output parsing
|
|
644
984
|
|
|
645
985
|
Returns:
|
|
646
986
|
ModelResponse: Iterator of parsed response data
|
|
647
987
|
"""
|
|
648
988
|
model_response = ModelResponse()
|
|
649
989
|
|
|
650
|
-
if isinstance(response, ContentBlockStartEvent):
|
|
990
|
+
if isinstance(response, (ContentBlockStartEvent, BetaRawContentBlockStartEvent)):
|
|
651
991
|
if response.content_block.type == "redacted_reasoning_content":
|
|
652
992
|
model_response.redacted_reasoning_content = response.content_block.data
|
|
653
993
|
|
|
654
|
-
if isinstance(response, ContentBlockDeltaEvent):
|
|
994
|
+
if isinstance(response, (ContentBlockDeltaEvent, BetaRawContentBlockDeltaEvent)):
|
|
655
995
|
# Handle text content
|
|
656
996
|
if response.delta.type == "text_delta":
|
|
657
997
|
model_response.content = response.delta.text
|
|
@@ -663,11 +1003,11 @@ class Claude(Model):
|
|
|
663
1003
|
"signature": response.delta.signature,
|
|
664
1004
|
}
|
|
665
1005
|
|
|
666
|
-
elif isinstance(response, ContentBlockStopEvent):
|
|
1006
|
+
elif isinstance(response, (ContentBlockStopEvent, ParsedBetaContentBlockStopEvent)):
|
|
667
1007
|
if response.content_block.type == "tool_use": # type: ignore
|
|
668
1008
|
tool_use = response.content_block # type: ignore
|
|
669
|
-
tool_name = tool_use.name
|
|
670
|
-
tool_input = tool_use.input
|
|
1009
|
+
tool_name = tool_use.name # type: ignore
|
|
1010
|
+
tool_input = tool_use.input # type: ignore
|
|
671
1011
|
|
|
672
1012
|
function_def = {"name": tool_name}
|
|
673
1013
|
if tool_input:
|
|
@@ -677,17 +1017,30 @@ class Claude(Model):
|
|
|
677
1017
|
|
|
678
1018
|
model_response.tool_calls = [
|
|
679
1019
|
{
|
|
680
|
-
"id": tool_use.id,
|
|
1020
|
+
"id": tool_use.id, # type: ignore
|
|
681
1021
|
"type": "function",
|
|
682
1022
|
"function": function_def,
|
|
683
1023
|
}
|
|
684
1024
|
]
|
|
685
1025
|
|
|
686
|
-
# Capture citations from the final response
|
|
687
|
-
elif isinstance(response, MessageStopEvent):
|
|
1026
|
+
# Capture citations from the final response and handle structured outputs
|
|
1027
|
+
elif isinstance(response, (MessageStopEvent, ParsedBetaMessageStopEvent)):
|
|
1028
|
+
# In streaming mode, content has already been emitted via ContentBlockDeltaEvent chunks
|
|
1029
|
+
# Setting content here would cause duplication since _populate_stream_data accumulates with +=
|
|
1030
|
+
# Keep content empty to avoid duplication
|
|
688
1031
|
model_response.content = ""
|
|
689
1032
|
model_response.citations = Citations(raw=[], urls=[], documents=[])
|
|
1033
|
+
|
|
1034
|
+
# Accumulate text content for structured output parsing (but don't set model_response.content)
|
|
1035
|
+
# The text was already streamed via ContentBlockDeltaEvent chunks
|
|
1036
|
+
accumulated_text = ""
|
|
1037
|
+
|
|
690
1038
|
for block in response.message.content: # type: ignore
|
|
1039
|
+
# Handle text blocks for structured output parsing
|
|
1040
|
+
if block.type == "text":
|
|
1041
|
+
accumulated_text += block.text # type: ignore
|
|
1042
|
+
|
|
1043
|
+
# Handle citations
|
|
691
1044
|
citations = getattr(block, "citations", None)
|
|
692
1045
|
if not citations:
|
|
693
1046
|
continue
|
|
@@ -702,6 +1055,28 @@ class Claude(Model):
|
|
|
702
1055
|
DocumentCitation(document_title=citation.document_title, cited_text=citation.cited_text)
|
|
703
1056
|
)
|
|
704
1057
|
|
|
1058
|
+
# Handle structured outputs (JSON outputs) from accumulated text
|
|
1059
|
+
# Note: We parse from accumulated_text but don't set model_response.content to avoid duplication
|
|
1060
|
+
# The content was already streamed via ContentBlockDeltaEvent chunks
|
|
1061
|
+
if (
|
|
1062
|
+
response_format is not None
|
|
1063
|
+
and isinstance(response_format, type)
|
|
1064
|
+
and issubclass(response_format, BaseModel)
|
|
1065
|
+
):
|
|
1066
|
+
if accumulated_text:
|
|
1067
|
+
try:
|
|
1068
|
+
# Parse JSON from accumulated text content
|
|
1069
|
+
parsed_data = json.loads(accumulated_text)
|
|
1070
|
+
# Validate against Pydantic model
|
|
1071
|
+
model_response.parsed = response_format.model_validate(parsed_data)
|
|
1072
|
+
log_debug(f"Successfully parsed structured output from stream: {model_response.parsed}")
|
|
1073
|
+
except json.JSONDecodeError as e:
|
|
1074
|
+
log_warning(f"Failed to parse JSON from structured output in stream: {e}")
|
|
1075
|
+
except ValidationError as e:
|
|
1076
|
+
log_warning(f"Failed to validate structured output against schema in stream: {e}")
|
|
1077
|
+
except Exception as e:
|
|
1078
|
+
log_warning(f"Unexpected error parsing structured output in stream: {e}")
|
|
1079
|
+
|
|
705
1080
|
# Capture context management information if present
|
|
706
1081
|
if self.context_management is not None and hasattr(response.message, "context_management"): # type: ignore
|
|
707
1082
|
context_mgmt = response.message.context_management # type: ignore
|