agno 0.1.2__py3-none-any.whl → 2.3.13__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/__init__.py +8 -0
- agno/agent/__init__.py +44 -5
- agno/agent/agent.py +10531 -2975
- agno/api/agent.py +14 -53
- agno/api/api.py +7 -46
- agno/api/evals.py +22 -0
- agno/api/os.py +17 -0
- agno/api/routes.py +6 -25
- agno/api/schemas/__init__.py +9 -0
- agno/api/schemas/agent.py +6 -9
- agno/api/schemas/evals.py +16 -0
- agno/api/schemas/os.py +14 -0
- agno/api/schemas/team.py +10 -10
- agno/api/schemas/utils.py +21 -0
- agno/api/schemas/workflows.py +16 -0
- agno/api/settings.py +53 -0
- agno/api/team.py +22 -26
- agno/api/workflow.py +28 -0
- agno/cloud/aws/base.py +214 -0
- agno/cloud/aws/s3/__init__.py +2 -0
- agno/cloud/aws/s3/api_client.py +43 -0
- agno/cloud/aws/s3/bucket.py +195 -0
- agno/cloud/aws/s3/object.py +57 -0
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/__init__.py +24 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +946 -0
- agno/db/dynamo/__init__.py +3 -0
- agno/db/dynamo/dynamo.py +2781 -0
- agno/db/dynamo/schemas.py +442 -0
- agno/db/dynamo/utils.py +743 -0
- agno/db/firestore/__init__.py +3 -0
- agno/db/firestore/firestore.py +2379 -0
- agno/db/firestore/schemas.py +181 -0
- agno/db/firestore/utils.py +376 -0
- agno/db/gcs_json/__init__.py +3 -0
- agno/db/gcs_json/gcs_json_db.py +1791 -0
- agno/db/gcs_json/utils.py +228 -0
- agno/db/in_memory/__init__.py +3 -0
- agno/db/in_memory/in_memory_db.py +1312 -0
- agno/db/in_memory/utils.py +230 -0
- agno/db/json/__init__.py +3 -0
- agno/db/json/json_db.py +1777 -0
- agno/db/json/utils.py +230 -0
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +635 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +17 -0
- agno/db/mongo/async_mongo.py +2760 -0
- agno/db/mongo/mongo.py +2597 -0
- agno/db/mongo/schemas.py +119 -0
- agno/db/mongo/utils.py +276 -0
- agno/db/mysql/__init__.py +4 -0
- agno/db/mysql/async_mysql.py +2912 -0
- agno/db/mysql/mysql.py +2923 -0
- agno/db/mysql/schemas.py +186 -0
- agno/db/mysql/utils.py +488 -0
- agno/db/postgres/__init__.py +4 -0
- agno/db/postgres/async_postgres.py +2579 -0
- agno/db/postgres/postgres.py +2870 -0
- agno/db/postgres/schemas.py +187 -0
- agno/db/postgres/utils.py +442 -0
- agno/db/redis/__init__.py +3 -0
- agno/db/redis/redis.py +2141 -0
- agno/db/redis/schemas.py +159 -0
- agno/db/redis/utils.py +346 -0
- agno/db/schemas/__init__.py +4 -0
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +34 -0
- agno/db/schemas/knowledge.py +40 -0
- agno/db/schemas/memory.py +61 -0
- agno/db/singlestore/__init__.py +3 -0
- agno/db/singlestore/schemas.py +179 -0
- agno/db/singlestore/singlestore.py +2877 -0
- agno/db/singlestore/utils.py +384 -0
- agno/db/sqlite/__init__.py +4 -0
- agno/db/sqlite/async_sqlite.py +2911 -0
- agno/db/sqlite/schemas.py +181 -0
- agno/db/sqlite/sqlite.py +2908 -0
- agno/db/sqlite/utils.py +429 -0
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +334 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1908 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +118 -0
- agno/eval/__init__.py +24 -0
- agno/eval/accuracy.py +666 -276
- agno/eval/agent_as_judge.py +861 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +779 -0
- agno/eval/reliability.py +241 -62
- agno/eval/utils.py +120 -0
- agno/exceptions.py +143 -1
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/__init__.py +3 -0
- agno/integrations/discord/client.py +203 -0
- agno/knowledge/__init__.py +5 -1
- agno/{document → knowledge}/chunking/agentic.py +22 -14
- agno/{document → knowledge}/chunking/document.py +2 -2
- agno/{document → knowledge}/chunking/fixed.py +7 -6
- agno/knowledge/chunking/markdown.py +151 -0
- agno/{document → knowledge}/chunking/recursive.py +15 -3
- agno/knowledge/chunking/row.py +39 -0
- agno/knowledge/chunking/semantic.py +91 -0
- agno/knowledge/chunking/strategy.py +165 -0
- agno/knowledge/content.py +74 -0
- agno/knowledge/document/__init__.py +5 -0
- agno/{document → knowledge/document}/base.py +12 -2
- agno/knowledge/embedder/__init__.py +5 -0
- agno/knowledge/embedder/aws_bedrock.py +343 -0
- agno/knowledge/embedder/azure_openai.py +210 -0
- agno/{embedder → knowledge/embedder}/base.py +8 -0
- agno/knowledge/embedder/cohere.py +323 -0
- agno/knowledge/embedder/fastembed.py +62 -0
- agno/{embedder → knowledge/embedder}/fireworks.py +1 -1
- agno/knowledge/embedder/google.py +258 -0
- agno/knowledge/embedder/huggingface.py +94 -0
- agno/knowledge/embedder/jina.py +182 -0
- agno/knowledge/embedder/langdb.py +22 -0
- agno/knowledge/embedder/mistral.py +206 -0
- agno/knowledge/embedder/nebius.py +13 -0
- agno/knowledge/embedder/ollama.py +154 -0
- agno/knowledge/embedder/openai.py +195 -0
- agno/knowledge/embedder/sentence_transformer.py +63 -0
- agno/{embedder → knowledge/embedder}/together.py +1 -1
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +165 -0
- agno/knowledge/knowledge.py +3006 -0
- agno/knowledge/reader/__init__.py +7 -0
- agno/knowledge/reader/arxiv_reader.py +81 -0
- agno/knowledge/reader/base.py +95 -0
- agno/knowledge/reader/csv_reader.py +164 -0
- agno/knowledge/reader/docx_reader.py +82 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
- agno/knowledge/reader/firecrawl_reader.py +201 -0
- agno/knowledge/reader/json_reader.py +88 -0
- agno/knowledge/reader/markdown_reader.py +137 -0
- agno/knowledge/reader/pdf_reader.py +431 -0
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +313 -0
- agno/knowledge/reader/s3_reader.py +89 -0
- agno/knowledge/reader/tavily_reader.py +193 -0
- agno/knowledge/reader/text_reader.py +127 -0
- agno/knowledge/reader/web_search_reader.py +325 -0
- agno/knowledge/reader/website_reader.py +455 -0
- agno/knowledge/reader/wikipedia_reader.py +91 -0
- agno/knowledge/reader/youtube_reader.py +78 -0
- agno/knowledge/remote_content/remote_content.py +88 -0
- agno/knowledge/reranker/__init__.py +3 -0
- agno/{reranker → knowledge/reranker}/base.py +1 -1
- agno/{reranker → knowledge/reranker}/cohere.py +2 -2
- agno/knowledge/reranker/infinity.py +195 -0
- agno/knowledge/reranker/sentence_transformer.py +54 -0
- agno/knowledge/types.py +39 -0
- agno/knowledge/utils.py +234 -0
- agno/media.py +439 -95
- agno/memory/__init__.py +16 -3
- agno/memory/manager.py +1474 -123
- 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/__init__.py +5 -0
- agno/models/aimlapi/aimlapi.py +62 -0
- agno/models/anthropic/__init__.py +4 -0
- agno/models/anthropic/claude.py +960 -496
- agno/models/aws/__init__.py +15 -0
- agno/models/aws/bedrock.py +686 -451
- agno/models/aws/claude.py +190 -183
- agno/models/azure/__init__.py +18 -1
- agno/models/azure/ai_foundry.py +489 -0
- agno/models/azure/openai_chat.py +89 -40
- agno/models/base.py +2477 -550
- agno/models/cerebras/__init__.py +12 -0
- agno/models/cerebras/cerebras.py +565 -0
- agno/models/cerebras/cerebras_openai.py +131 -0
- agno/models/cohere/__init__.py +4 -0
- agno/models/cohere/chat.py +306 -492
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +74 -0
- agno/models/dashscope/__init__.py +5 -0
- agno/models/dashscope/dashscope.py +90 -0
- agno/models/deepinfra/__init__.py +5 -0
- agno/models/deepinfra/deepinfra.py +45 -0
- agno/models/deepseek/__init__.py +4 -0
- agno/models/deepseek/deepseek.py +110 -9
- agno/models/fireworks/__init__.py +4 -0
- agno/models/fireworks/fireworks.py +19 -22
- agno/models/google/__init__.py +3 -7
- agno/models/google/gemini.py +1717 -662
- agno/models/google/utils.py +22 -0
- agno/models/groq/__init__.py +4 -0
- agno/models/groq/groq.py +391 -666
- agno/models/huggingface/__init__.py +4 -0
- agno/models/huggingface/huggingface.py +266 -538
- agno/models/ibm/__init__.py +5 -0
- agno/models/ibm/watsonx.py +432 -0
- agno/models/internlm/__init__.py +3 -0
- agno/models/internlm/internlm.py +20 -3
- agno/models/langdb/__init__.py +1 -0
- agno/models/langdb/langdb.py +60 -0
- agno/models/litellm/__init__.py +14 -0
- agno/models/litellm/chat.py +503 -0
- agno/models/litellm/litellm_openai.py +42 -0
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/lmstudio/__init__.py +5 -0
- agno/models/lmstudio/lmstudio.py +25 -0
- agno/models/message.py +361 -39
- agno/models/meta/__init__.py +12 -0
- agno/models/meta/llama.py +502 -0
- agno/models/meta/llama_openai.py +79 -0
- agno/models/metrics.py +120 -0
- agno/models/mistral/__init__.py +4 -0
- agno/models/mistral/mistral.py +293 -393
- agno/models/nebius/__init__.py +3 -0
- agno/models/nebius/nebius.py +53 -0
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/__init__.py +4 -0
- agno/models/nvidia/nvidia.py +22 -3
- agno/models/ollama/__init__.py +4 -2
- agno/models/ollama/chat.py +257 -492
- agno/models/openai/__init__.py +7 -0
- agno/models/openai/chat.py +725 -770
- agno/models/openai/like.py +16 -2
- agno/models/openai/responses.py +1121 -0
- agno/models/openrouter/__init__.py +4 -0
- agno/models/openrouter/openrouter.py +62 -5
- agno/models/perplexity/__init__.py +5 -0
- agno/models/perplexity/perplexity.py +203 -0
- agno/models/portkey/__init__.py +3 -0
- agno/models/portkey/portkey.py +82 -0
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +69 -0
- agno/models/response.py +177 -7
- agno/models/sambanova/__init__.py +4 -0
- agno/models/sambanova/sambanova.py +23 -4
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +42 -0
- agno/models/together/__init__.py +4 -0
- agno/models/together/together.py +21 -164
- agno/models/utils.py +266 -0
- agno/models/vercel/__init__.py +3 -0
- agno/models/vercel/v0.py +43 -0
- agno/models/vertexai/__init__.py +0 -1
- agno/models/vertexai/claude.py +190 -0
- agno/models/vllm/__init__.py +3 -0
- agno/models/vllm/vllm.py +83 -0
- agno/models/xai/__init__.py +2 -0
- agno/models/xai/xai.py +111 -7
- agno/os/__init__.py +3 -0
- agno/os/app.py +1027 -0
- agno/os/auth.py +244 -0
- agno/os/config.py +126 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +249 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/__init__.py +3 -0
- agno/os/interfaces/agui/agui.py +47 -0
- agno/os/interfaces/agui/router.py +147 -0
- agno/os/interfaces/agui/utils.py +574 -0
- agno/os/interfaces/base.py +25 -0
- agno/os/interfaces/slack/__init__.py +3 -0
- agno/os/interfaces/slack/router.py +148 -0
- agno/os/interfaces/slack/security.py +30 -0
- agno/os/interfaces/slack/slack.py +47 -0
- agno/os/interfaces/whatsapp/__init__.py +3 -0
- agno/os/interfaces/whatsapp/router.py +210 -0
- agno/os/interfaces/whatsapp/security.py +55 -0
- agno/os/interfaces/whatsapp/whatsapp.py +36 -0
- agno/os/mcp.py +293 -0
- agno/os/middleware/__init__.py +9 -0
- agno/os/middleware/jwt.py +797 -0
- agno/os/router.py +258 -0
- agno/os/routers/__init__.py +3 -0
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +599 -0
- agno/os/routers/agents/schema.py +261 -0
- agno/os/routers/evals/__init__.py +3 -0
- agno/os/routers/evals/evals.py +450 -0
- agno/os/routers/evals/schemas.py +174 -0
- agno/os/routers/evals/utils.py +231 -0
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/__init__.py +3 -0
- agno/os/routers/knowledge/knowledge.py +1008 -0
- agno/os/routers/knowledge/schemas.py +178 -0
- agno/os/routers/memory/__init__.py +3 -0
- agno/os/routers/memory/memory.py +661 -0
- agno/os/routers/memory/schemas.py +88 -0
- agno/os/routers/metrics/__init__.py +3 -0
- agno/os/routers/metrics/metrics.py +190 -0
- agno/os/routers/metrics/schemas.py +47 -0
- agno/os/routers/session/__init__.py +3 -0
- agno/os/routers/session/session.py +997 -0
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +512 -0
- agno/os/routers/teams/schema.py +257 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +499 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +624 -0
- agno/os/routers/workflows/schema.py +75 -0
- agno/os/schema.py +534 -0
- agno/os/scopes.py +469 -0
- agno/{playground → os}/settings.py +7 -15
- agno/os/utils.py +973 -0
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +67 -0
- agno/reasoning/deepseek.py +63 -0
- agno/reasoning/default.py +97 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +71 -0
- agno/reasoning/helpers.py +24 -1
- agno/reasoning/ollama.py +67 -0
- agno/reasoning/openai.py +86 -0
- agno/reasoning/step.py +2 -1
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +822 -0
- agno/run/base.py +247 -0
- agno/run/cancel.py +81 -0
- agno/run/requirement.py +181 -0
- agno/run/team.py +767 -0
- agno/run/workflow.py +708 -0
- agno/session/__init__.py +10 -0
- agno/session/agent.py +260 -0
- agno/session/summary.py +265 -0
- agno/session/team.py +342 -0
- agno/session/workflow.py +501 -0
- agno/table.py +10 -0
- agno/team/__init__.py +37 -0
- agno/team/team.py +9536 -0
- agno/tools/__init__.py +7 -0
- agno/tools/agentql.py +120 -0
- agno/tools/airflow.py +22 -12
- agno/tools/api.py +122 -0
- agno/tools/apify.py +276 -83
- agno/tools/{arxiv_toolkit.py → arxiv.py} +20 -12
- agno/tools/aws_lambda.py +28 -7
- agno/tools/aws_ses.py +66 -0
- agno/tools/baidusearch.py +11 -4
- agno/tools/bitbucket.py +292 -0
- agno/tools/brandfetch.py +213 -0
- agno/tools/bravesearch.py +106 -0
- agno/tools/brightdata.py +367 -0
- agno/tools/browserbase.py +209 -0
- agno/tools/calcom.py +32 -23
- agno/tools/calculator.py +24 -37
- agno/tools/cartesia.py +187 -0
- agno/tools/{clickup_tool.py → clickup.py} +17 -28
- agno/tools/confluence.py +91 -26
- agno/tools/crawl4ai.py +139 -43
- agno/tools/csv_toolkit.py +28 -22
- agno/tools/dalle.py +36 -22
- agno/tools/daytona.py +475 -0
- agno/tools/decorator.py +169 -14
- agno/tools/desi_vocal.py +23 -11
- agno/tools/discord.py +32 -29
- agno/tools/docker.py +716 -0
- agno/tools/duckdb.py +76 -81
- agno/tools/duckduckgo.py +43 -40
- agno/tools/e2b.py +703 -0
- agno/tools/eleven_labs.py +65 -54
- agno/tools/email.py +13 -5
- agno/tools/evm.py +129 -0
- agno/tools/exa.py +324 -42
- agno/tools/fal.py +39 -35
- agno/tools/file.py +196 -30
- agno/tools/file_generation.py +356 -0
- agno/tools/financial_datasets.py +288 -0
- agno/tools/firecrawl.py +108 -33
- agno/tools/function.py +960 -122
- agno/tools/giphy.py +34 -12
- agno/tools/github.py +1294 -97
- agno/tools/gmail.py +922 -0
- agno/tools/google_bigquery.py +117 -0
- agno/tools/google_drive.py +271 -0
- agno/tools/google_maps.py +253 -0
- agno/tools/googlecalendar.py +607 -107
- agno/tools/googlesheets.py +377 -0
- agno/tools/hackernews.py +20 -12
- agno/tools/jina.py +24 -14
- agno/tools/jira.py +48 -19
- agno/tools/knowledge.py +218 -0
- agno/tools/linear.py +82 -43
- agno/tools/linkup.py +58 -0
- agno/tools/local_file_system.py +15 -7
- agno/tools/lumalab.py +41 -26
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +284 -0
- agno/tools/mem0.py +193 -0
- agno/tools/memory.py +419 -0
- agno/tools/mlx_transcribe.py +11 -9
- agno/tools/models/azure_openai.py +190 -0
- agno/tools/models/gemini.py +203 -0
- agno/tools/models/groq.py +158 -0
- agno/tools/models/morph.py +186 -0
- agno/tools/models/nebius.py +124 -0
- agno/tools/models_labs.py +163 -82
- agno/tools/moviepy_video.py +18 -13
- agno/tools/nano_banana.py +151 -0
- agno/tools/neo4j.py +134 -0
- agno/tools/newspaper.py +15 -4
- agno/tools/newspaper4k.py +19 -6
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +181 -17
- agno/tools/openbb.py +27 -20
- agno/tools/opencv.py +321 -0
- agno/tools/openweather.py +233 -0
- agno/tools/oxylabs.py +385 -0
- agno/tools/pandas.py +25 -15
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +238 -185
- agno/tools/pubmed.py +125 -13
- agno/tools/python.py +48 -35
- agno/tools/reasoning.py +283 -0
- agno/tools/reddit.py +207 -29
- agno/tools/redshift.py +406 -0
- agno/tools/replicate.py +69 -26
- agno/tools/resend.py +11 -6
- agno/tools/scrapegraph.py +179 -19
- agno/tools/searxng.py +23 -31
- agno/tools/serpapi.py +15 -10
- agno/tools/serper.py +255 -0
- agno/tools/shell.py +23 -12
- agno/tools/shopify.py +1519 -0
- agno/tools/slack.py +56 -14
- agno/tools/sleep.py +8 -6
- agno/tools/spider.py +35 -11
- agno/tools/spotify.py +919 -0
- agno/tools/sql.py +34 -19
- agno/tools/tavily.py +158 -8
- agno/tools/telegram.py +18 -8
- agno/tools/todoist.py +218 -0
- agno/tools/toolkit.py +134 -9
- agno/tools/trafilatura.py +388 -0
- agno/tools/trello.py +25 -28
- agno/tools/twilio.py +18 -9
- agno/tools/user_control_flow.py +78 -0
- agno/tools/valyu.py +228 -0
- agno/tools/visualization.py +467 -0
- agno/tools/webbrowser.py +28 -0
- agno/tools/webex.py +76 -0
- agno/tools/website.py +23 -19
- agno/tools/webtools.py +45 -0
- agno/tools/whatsapp.py +286 -0
- agno/tools/wikipedia.py +28 -19
- agno/tools/workflow.py +285 -0
- agno/tools/{twitter.py → x.py} +142 -46
- agno/tools/yfinance.py +41 -39
- agno/tools/youtube.py +34 -17
- agno/tools/zendesk.py +15 -5
- agno/tools/zep.py +454 -0
- agno/tools/zoom.py +86 -37
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +157 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +111 -0
- agno/utils/agent.py +938 -0
- agno/utils/audio.py +37 -1
- agno/utils/certs.py +27 -0
- agno/utils/code_execution.py +11 -0
- agno/utils/common.py +103 -20
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +700 -0
- agno/utils/functions.py +107 -37
- agno/utils/gemini.py +426 -0
- agno/utils/hooks.py +171 -0
- agno/utils/http.py +185 -0
- agno/utils/json_schema.py +159 -37
- agno/utils/knowledge.py +36 -0
- agno/utils/location.py +19 -0
- agno/utils/log.py +221 -8
- agno/utils/mcp.py +214 -0
- agno/utils/media.py +335 -14
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +77 -2
- agno/utils/models/ai_foundry.py +50 -0
- agno/utils/models/claude.py +373 -0
- agno/utils/models/cohere.py +94 -0
- agno/utils/models/llama.py +85 -0
- agno/utils/models/mistral.py +100 -0
- agno/utils/models/openai_responses.py +140 -0
- agno/utils/models/schema_utils.py +153 -0
- agno/utils/models/watsonx.py +41 -0
- agno/utils/openai.py +257 -0
- agno/utils/pickle.py +1 -1
- agno/utils/pprint.py +124 -8
- agno/utils/print_response/agent.py +930 -0
- agno/utils/print_response/team.py +1914 -0
- agno/utils/print_response/workflow.py +1668 -0
- agno/utils/prompts.py +111 -0
- agno/utils/reasoning.py +108 -0
- agno/utils/response.py +163 -0
- agno/utils/serialize.py +32 -0
- agno/utils/shell.py +4 -4
- agno/utils/streamlit.py +487 -0
- agno/utils/string.py +204 -51
- agno/utils/team.py +139 -0
- agno/utils/timer.py +9 -2
- agno/utils/tokens.py +657 -0
- agno/utils/tools.py +19 -1
- agno/utils/whatsapp.py +305 -0
- agno/utils/yaml_io.py +3 -3
- agno/vectordb/__init__.py +2 -0
- agno/vectordb/base.py +87 -9
- agno/vectordb/cassandra/__init__.py +5 -1
- agno/vectordb/cassandra/cassandra.py +383 -27
- agno/vectordb/chroma/__init__.py +4 -0
- agno/vectordb/chroma/chromadb.py +748 -83
- agno/vectordb/clickhouse/__init__.py +7 -1
- agno/vectordb/clickhouse/clickhousedb.py +554 -53
- agno/vectordb/couchbase/__init__.py +3 -0
- agno/vectordb/couchbase/couchbase.py +1446 -0
- agno/vectordb/lancedb/__init__.py +5 -0
- agno/vectordb/lancedb/lance_db.py +730 -98
- agno/vectordb/langchaindb/__init__.py +5 -0
- agno/vectordb/langchaindb/langchaindb.py +163 -0
- agno/vectordb/lightrag/__init__.py +5 -0
- agno/vectordb/lightrag/lightrag.py +388 -0
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +166 -0
- agno/vectordb/milvus/__init__.py +3 -0
- agno/vectordb/milvus/milvus.py +966 -78
- agno/vectordb/mongodb/__init__.py +9 -1
- agno/vectordb/mongodb/mongodb.py +1175 -172
- agno/vectordb/pgvector/__init__.py +8 -0
- agno/vectordb/pgvector/pgvector.py +599 -115
- agno/vectordb/pineconedb/__init__.py +5 -1
- agno/vectordb/pineconedb/pineconedb.py +406 -43
- agno/vectordb/qdrant/__init__.py +4 -0
- agno/vectordb/qdrant/qdrant.py +914 -61
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +682 -0
- agno/vectordb/singlestore/__init__.py +8 -1
- agno/vectordb/singlestore/singlestore.py +771 -0
- agno/vectordb/surrealdb/__init__.py +3 -0
- agno/vectordb/surrealdb/surrealdb.py +663 -0
- agno/vectordb/upstashdb/__init__.py +5 -0
- agno/vectordb/upstashdb/upstashdb.py +718 -0
- agno/vectordb/weaviate/__init__.py +8 -0
- agno/vectordb/weaviate/index.py +15 -0
- agno/vectordb/weaviate/weaviate.py +1009 -0
- agno/workflow/__init__.py +23 -1
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +759 -0
- agno/workflow/loop.py +756 -0
- agno/workflow/parallel.py +853 -0
- agno/workflow/router.py +723 -0
- agno/workflow/step.py +1564 -0
- agno/workflow/steps.py +613 -0
- agno/workflow/types.py +556 -0
- agno/workflow/workflow.py +4327 -514
- agno-2.3.13.dist-info/METADATA +639 -0
- agno-2.3.13.dist-info/RECORD +613 -0
- {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +1 -1
- agno-2.3.13.dist-info/licenses/LICENSE +201 -0
- agno/api/playground.py +0 -91
- agno/api/schemas/playground.py +0 -22
- agno/api/schemas/user.py +0 -22
- agno/api/schemas/workspace.py +0 -46
- agno/api/user.py +0 -160
- agno/api/workspace.py +0 -151
- agno/cli/auth_server.py +0 -118
- agno/cli/config.py +0 -275
- agno/cli/console.py +0 -88
- agno/cli/credentials.py +0 -23
- agno/cli/entrypoint.py +0 -571
- agno/cli/operator.py +0 -355
- agno/cli/settings.py +0 -85
- agno/cli/ws/ws_cli.py +0 -817
- agno/constants.py +0 -13
- agno/document/__init__.py +0 -1
- agno/document/chunking/semantic.py +0 -47
- agno/document/chunking/strategy.py +0 -31
- agno/document/reader/__init__.py +0 -1
- agno/document/reader/arxiv_reader.py +0 -41
- agno/document/reader/base.py +0 -22
- agno/document/reader/csv_reader.py +0 -84
- agno/document/reader/docx_reader.py +0 -46
- agno/document/reader/firecrawl_reader.py +0 -99
- agno/document/reader/json_reader.py +0 -43
- agno/document/reader/pdf_reader.py +0 -219
- agno/document/reader/s3/pdf_reader.py +0 -46
- agno/document/reader/s3/text_reader.py +0 -51
- agno/document/reader/text_reader.py +0 -41
- agno/document/reader/website_reader.py +0 -175
- agno/document/reader/youtube_reader.py +0 -50
- agno/embedder/__init__.py +0 -1
- agno/embedder/azure_openai.py +0 -86
- agno/embedder/cohere.py +0 -72
- agno/embedder/fastembed.py +0 -37
- agno/embedder/google.py +0 -73
- agno/embedder/huggingface.py +0 -54
- agno/embedder/mistral.py +0 -80
- agno/embedder/ollama.py +0 -57
- agno/embedder/openai.py +0 -74
- agno/embedder/sentence_transformer.py +0 -38
- agno/embedder/voyageai.py +0 -64
- agno/eval/perf.py +0 -201
- agno/file/__init__.py +0 -1
- agno/file/file.py +0 -16
- agno/file/local/csv.py +0 -32
- agno/file/local/txt.py +0 -19
- agno/infra/app.py +0 -240
- agno/infra/base.py +0 -144
- agno/infra/context.py +0 -20
- agno/infra/db_app.py +0 -52
- agno/infra/resource.py +0 -205
- agno/infra/resources.py +0 -55
- agno/knowledge/agent.py +0 -230
- agno/knowledge/arxiv.py +0 -22
- agno/knowledge/combined.py +0 -22
- agno/knowledge/csv.py +0 -28
- agno/knowledge/csv_url.py +0 -19
- agno/knowledge/document.py +0 -20
- agno/knowledge/docx.py +0 -30
- agno/knowledge/json.py +0 -28
- agno/knowledge/langchain.py +0 -71
- agno/knowledge/llamaindex.py +0 -66
- agno/knowledge/pdf.py +0 -28
- agno/knowledge/pdf_url.py +0 -26
- agno/knowledge/s3/base.py +0 -60
- agno/knowledge/s3/pdf.py +0 -21
- agno/knowledge/s3/text.py +0 -23
- agno/knowledge/text.py +0 -30
- agno/knowledge/website.py +0 -88
- agno/knowledge/wikipedia.py +0 -31
- agno/knowledge/youtube.py +0 -22
- agno/memory/agent.py +0 -392
- agno/memory/classifier.py +0 -104
- agno/memory/db/__init__.py +0 -1
- agno/memory/db/base.py +0 -42
- agno/memory/db/mongodb.py +0 -189
- agno/memory/db/postgres.py +0 -203
- agno/memory/db/sqlite.py +0 -193
- agno/memory/memory.py +0 -15
- agno/memory/row.py +0 -36
- agno/memory/summarizer.py +0 -192
- agno/memory/summary.py +0 -19
- agno/memory/workflow.py +0 -38
- agno/models/google/gemini_openai.py +0 -26
- agno/models/ollama/hermes.py +0 -221
- agno/models/ollama/tools.py +0 -362
- agno/models/vertexai/gemini.py +0 -595
- agno/playground/__init__.py +0 -3
- agno/playground/async_router.py +0 -421
- agno/playground/deploy.py +0 -249
- agno/playground/operator.py +0 -92
- agno/playground/playground.py +0 -91
- agno/playground/schemas.py +0 -76
- agno/playground/serve.py +0 -55
- agno/playground/sync_router.py +0 -405
- agno/reasoning/agent.py +0 -68
- agno/run/response.py +0 -112
- agno/storage/agent/__init__.py +0 -0
- agno/storage/agent/base.py +0 -38
- agno/storage/agent/dynamodb.py +0 -350
- agno/storage/agent/json.py +0 -92
- agno/storage/agent/mongodb.py +0 -228
- agno/storage/agent/postgres.py +0 -367
- agno/storage/agent/session.py +0 -79
- agno/storage/agent/singlestore.py +0 -303
- agno/storage/agent/sqlite.py +0 -357
- agno/storage/agent/yaml.py +0 -93
- agno/storage/workflow/__init__.py +0 -0
- agno/storage/workflow/base.py +0 -40
- agno/storage/workflow/mongodb.py +0 -233
- agno/storage/workflow/postgres.py +0 -366
- agno/storage/workflow/session.py +0 -60
- agno/storage/workflow/sqlite.py +0 -359
- agno/tools/googlesearch.py +0 -88
- agno/utils/defaults.py +0 -57
- agno/utils/filesystem.py +0 -39
- agno/utils/git.py +0 -52
- agno/utils/json_io.py +0 -30
- agno/utils/load_env.py +0 -19
- agno/utils/py_io.py +0 -19
- agno/utils/pyproject.py +0 -18
- agno/utils/resource_filter.py +0 -31
- agno/vectordb/singlestore/s2vectordb.py +0 -390
- agno/vectordb/singlestore/s2vectordb2.py +0 -355
- agno/workspace/__init__.py +0 -0
- agno/workspace/config.py +0 -325
- agno/workspace/enums.py +0 -6
- agno/workspace/helpers.py +0 -48
- agno/workspace/operator.py +0 -758
- agno/workspace/settings.py +0 -63
- agno-0.1.2.dist-info/LICENSE +0 -375
- agno-0.1.2.dist-info/METADATA +0 -502
- agno-0.1.2.dist-info/RECORD +0 -352
- agno-0.1.2.dist-info/entry_points.txt +0 -3
- /agno/{cli → db/migrations}/__init__.py +0 -0
- /agno/{cli/ws → db/migrations/versions}/__init__.py +0 -0
- /agno/{document/chunking/__init__.py → db/schemas/metrics.py} +0 -0
- /agno/{document/reader/s3 → integrations}/__init__.py +0 -0
- /agno/{file/local → knowledge/chunking}/__init__.py +0 -0
- /agno/{infra → knowledge/remote_content}/__init__.py +0 -0
- /agno/{knowledge/s3 → tools/models}/__init__.py +0 -0
- /agno/{reranker → utils/models}/__init__.py +0 -0
- /agno/{storage → utils/print_response}/__init__.py +0 -0
- {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
agno/models/anthropic/claude.py
CHANGED
|
@@ -1,93 +1,66 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from
|
|
2
|
+
from collections.abc import AsyncIterator
|
|
3
|
+
from dataclasses import asdict, dataclass
|
|
3
4
|
from os import getenv
|
|
4
|
-
from typing import Any, Dict,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from
|
|
8
|
-
|
|
9
|
-
from agno.
|
|
10
|
-
from agno.
|
|
5
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from pydantic import BaseModel, ValidationError
|
|
9
|
+
|
|
10
|
+
from agno.exceptions import ModelProviderError, ModelRateLimitError
|
|
11
|
+
from agno.models.base import Model
|
|
12
|
+
from agno.models.message import Citations, DocumentCitation, Message, UrlCitation
|
|
13
|
+
from agno.models.metrics import Metrics
|
|
14
|
+
from agno.models.response import ModelResponse
|
|
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
|
|
18
|
+
from agno.utils.log import log_debug, log_error, log_warning
|
|
19
|
+
from agno.utils.models.claude import MCPServerConfiguration, format_messages, format_tools_for_model
|
|
20
|
+
from agno.utils.tokens import count_schema_tokens
|
|
11
21
|
|
|
12
22
|
try:
|
|
13
23
|
from anthropic import Anthropic as AnthropicClient
|
|
14
|
-
from anthropic
|
|
24
|
+
from anthropic import (
|
|
25
|
+
APIConnectionError,
|
|
26
|
+
APIStatusError,
|
|
27
|
+
RateLimitError,
|
|
28
|
+
)
|
|
29
|
+
from anthropic import (
|
|
30
|
+
AsyncAnthropic as AsyncAnthropicClient,
|
|
31
|
+
)
|
|
32
|
+
from anthropic.lib.streaming._beta_types import (
|
|
33
|
+
BetaRawContentBlockStartEvent,
|
|
34
|
+
ParsedBetaContentBlockStopEvent,
|
|
35
|
+
ParsedBetaMessageStopEvent,
|
|
36
|
+
)
|
|
37
|
+
from anthropic.types import (
|
|
38
|
+
CitationPageLocation,
|
|
39
|
+
CitationsWebSearchResultLocation,
|
|
40
|
+
ContentBlockDeltaEvent,
|
|
41
|
+
ContentBlockStartEvent,
|
|
15
42
|
ContentBlockStopEvent,
|
|
43
|
+
MessageDeltaUsage,
|
|
44
|
+
# MessageDeltaEvent, # Currently broken
|
|
16
45
|
MessageStopEvent,
|
|
17
|
-
|
|
46
|
+
Usage,
|
|
47
|
+
)
|
|
48
|
+
from anthropic.types import (
|
|
49
|
+
Message as AnthropicMessage,
|
|
18
50
|
)
|
|
19
|
-
from anthropic.types import Message as AnthropicMessage
|
|
20
|
-
from anthropic.types import TextBlock, TextDelta, ToolUseBlock, Usage
|
|
21
|
-
except (ModuleNotFoundError, ImportError):
|
|
22
|
-
raise ImportError("`anthropic` not installed. Please install using `pip install anthropic`")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@dataclass
|
|
26
|
-
class MessageData:
|
|
27
|
-
response_content: str = ""
|
|
28
|
-
response_block: List[Union[TextBlock, ToolUseBlock]] = field(default_factory=list)
|
|
29
|
-
response_block_content: Optional[Union[TextBlock, ToolUseBlock]] = None
|
|
30
|
-
response_usage: Optional[Usage] = None
|
|
31
|
-
tool_calls: List[Dict[str, Any]] = field(default_factory=list)
|
|
32
|
-
tool_ids: List[str] = field(default_factory=list)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def format_image_for_message(image: Image) -> Optional[Dict[str, Any]]:
|
|
36
|
-
"""
|
|
37
|
-
Add an image to a message by converting it to base64 encoded format.
|
|
38
|
-
"""
|
|
39
|
-
import base64
|
|
40
|
-
import imghdr
|
|
41
|
-
|
|
42
|
-
type_mapping = {"jpeg": "image/jpeg", "png": "image/png", "gif": "image/gif", "webp": "image/webp"}
|
|
43
|
-
|
|
44
|
-
try:
|
|
45
|
-
# Case 1: Image is a URL
|
|
46
|
-
if image.url is not None:
|
|
47
|
-
content_bytes = image.image_url_content
|
|
48
|
-
|
|
49
|
-
# Case 2: Image is a local file path
|
|
50
|
-
elif image.filepath is not None:
|
|
51
|
-
from pathlib import Path
|
|
52
|
-
|
|
53
|
-
path = Path(image.filepath)
|
|
54
|
-
if path.exists() and path.is_file():
|
|
55
|
-
with open(image.filepath, "rb") as f:
|
|
56
|
-
content_bytes = f.read()
|
|
57
|
-
else:
|
|
58
|
-
logger.error(f"Image file not found: {image}")
|
|
59
|
-
return None
|
|
60
|
-
|
|
61
|
-
# Case 3: Image is a bytes object
|
|
62
|
-
elif image.content is not None:
|
|
63
|
-
content_bytes = image.content
|
|
64
|
-
|
|
65
|
-
else:
|
|
66
|
-
logger.error(f"Unsupported image type: {type(image)}")
|
|
67
|
-
return None
|
|
68
|
-
|
|
69
|
-
img_type = imghdr.what(None, h=content_bytes) # type: ignore
|
|
70
|
-
if not img_type:
|
|
71
|
-
logger.error("Unable to determine image type")
|
|
72
|
-
return None
|
|
73
51
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
logger.error(f"Unsupported image type: {img_type}")
|
|
77
|
-
return None
|
|
52
|
+
except ImportError as e:
|
|
53
|
+
raise ImportError("`anthropic` not installed. Please install it with `pip install anthropic`") from e
|
|
78
54
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
except Exception as e:
|
|
89
|
-
logger.error(f"Error processing image: {e}")
|
|
90
|
-
return None
|
|
55
|
+
# Import Beta types
|
|
56
|
+
try:
|
|
57
|
+
from anthropic.types.beta import BetaRawContentBlockDeltaEvent, BetaTextDelta
|
|
58
|
+
from anthropic.types.beta.beta_message import BetaMessage
|
|
59
|
+
from anthropic.types.beta.beta_usage import BetaUsage
|
|
60
|
+
except ImportError as e:
|
|
61
|
+
raise ImportError(
|
|
62
|
+
"`anthropic` not installed or missing beta components. Please install with `pip install anthropic`"
|
|
63
|
+
) from e
|
|
91
64
|
|
|
92
65
|
|
|
93
66
|
@dataclass
|
|
@@ -98,52 +71,395 @@ class Claude(Model):
|
|
|
98
71
|
For more information, see: https://docs.anthropic.com/en/api/messages
|
|
99
72
|
"""
|
|
100
73
|
|
|
101
|
-
|
|
74
|
+
# Models that DO NOT support extended thinking
|
|
75
|
+
# All future models are assumed to support thinking
|
|
76
|
+
# Based on official Anthropic documentation: https://docs.claude.com/en/docs/about-claude/models/overview
|
|
77
|
+
NON_THINKING_MODELS = {
|
|
78
|
+
# Claude Haiku 3 family (does not support thinking)
|
|
79
|
+
"claude-3-haiku-20240307",
|
|
80
|
+
# Claude Haiku 3.5 family (does not support thinking)
|
|
81
|
+
"claude-3-5-haiku-20241022",
|
|
82
|
+
"claude-3-5-haiku-latest",
|
|
83
|
+
}
|
|
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)
|
|
106
|
+
# (Add any Opus 4.x models released before 4.1 if they exist)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
id: str = "claude-sonnet-4-5-20250929"
|
|
102
110
|
name: str = "Claude"
|
|
103
111
|
provider: str = "Anthropic"
|
|
104
112
|
|
|
105
113
|
# Request parameters
|
|
106
|
-
max_tokens: Optional[int] =
|
|
114
|
+
max_tokens: Optional[int] = 8192
|
|
115
|
+
thinking: Optional[Dict[str, Any]] = None
|
|
107
116
|
temperature: Optional[float] = None
|
|
108
117
|
stop_sequences: Optional[List[str]] = None
|
|
109
118
|
top_p: Optional[float] = None
|
|
110
119
|
top_k: Optional[int] = None
|
|
120
|
+
cache_system_prompt: Optional[bool] = False
|
|
121
|
+
extended_cache_time: Optional[bool] = False
|
|
111
122
|
request_params: Optional[Dict[str, Any]] = None
|
|
112
123
|
|
|
124
|
+
# Anthropic beta and experimental features
|
|
125
|
+
betas: Optional[List[str]] = None # Enables specific experimental or newly released features.
|
|
126
|
+
context_management: Optional[Dict[str, Any]] = None
|
|
127
|
+
mcp_servers: Optional[List[MCPServerConfiguration]] = None
|
|
128
|
+
skills: Optional[List[Dict[str, str]]] = (
|
|
129
|
+
None # e.g., [{"type": "anthropic", "skill_id": "pptx", "version": "latest"}]
|
|
130
|
+
)
|
|
131
|
+
|
|
113
132
|
# Client parameters
|
|
114
133
|
api_key: Optional[str] = None
|
|
134
|
+
auth_token: Optional[str] = None
|
|
135
|
+
default_headers: Optional[Dict[str, Any]] = None
|
|
136
|
+
timeout: Optional[float] = None
|
|
137
|
+
http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
|
|
115
138
|
client_params: Optional[Dict[str, Any]] = None
|
|
116
139
|
|
|
117
|
-
# Anthropic client
|
|
118
140
|
client: Optional[AnthropicClient] = None
|
|
141
|
+
async_client: Optional[AsyncAnthropicClient] = None
|
|
142
|
+
|
|
143
|
+
def __post_init__(self):
|
|
144
|
+
"""Validate model configuration after initialization"""
|
|
145
|
+
# Validate thinking support immediately at model creation
|
|
146
|
+
if self.thinking:
|
|
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
|
|
151
|
+
# Set up skills configuration if skills are enabled
|
|
152
|
+
if self.skills:
|
|
153
|
+
self._setup_skills_configuration()
|
|
154
|
+
|
|
155
|
+
def _get_client_params(self) -> Dict[str, Any]:
|
|
156
|
+
client_params: Dict[str, Any] = {}
|
|
157
|
+
|
|
158
|
+
self.api_key = self.api_key or getenv("ANTHROPIC_API_KEY")
|
|
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
|
+
)
|
|
164
|
+
|
|
165
|
+
# Add API key to client parameters
|
|
166
|
+
client_params["api_key"] = self.api_key
|
|
167
|
+
client_params["auth_token"] = self.auth_token
|
|
168
|
+
if self.timeout is not None:
|
|
169
|
+
client_params["timeout"] = self.timeout
|
|
170
|
+
|
|
171
|
+
# Add additional client parameters
|
|
172
|
+
if self.client_params is not None:
|
|
173
|
+
client_params.update(self.client_params)
|
|
174
|
+
if self.default_headers is not None:
|
|
175
|
+
client_params["default_headers"] = self.default_headers
|
|
176
|
+
return client_params
|
|
177
|
+
|
|
178
|
+
def _supports_structured_outputs(self) -> bool:
|
|
179
|
+
"""
|
|
180
|
+
Check if the current model supports native structured outputs.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
bool: True if model supports structured outputs
|
|
184
|
+
"""
|
|
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 self.id.startswith("claude-opus-4-1"):
|
|
195
|
+
return False
|
|
196
|
+
|
|
197
|
+
return True
|
|
198
|
+
|
|
199
|
+
def _using_structured_outputs(
|
|
200
|
+
self,
|
|
201
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
202
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
203
|
+
) -> bool:
|
|
204
|
+
"""
|
|
205
|
+
Check if structured outputs are being used in this request.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
response_format: Response format parameter
|
|
209
|
+
tools: Tools list to check for strict mode
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
bool: True if structured outputs are in use
|
|
213
|
+
"""
|
|
214
|
+
# Check for output_format usage
|
|
215
|
+
if response_format is not None:
|
|
216
|
+
if self._supports_structured_outputs():
|
|
217
|
+
return True
|
|
218
|
+
else:
|
|
219
|
+
log_warning(
|
|
220
|
+
f"Model '{self.id}' does not support structured outputs. "
|
|
221
|
+
"Structured output features will not be available for this model."
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Check for strict tools
|
|
225
|
+
if tools:
|
|
226
|
+
for tool in tools:
|
|
227
|
+
if tool.get("type") == "function":
|
|
228
|
+
func_def = tool.get("function", {})
|
|
229
|
+
if func_def.get("strict") is True:
|
|
230
|
+
return True
|
|
231
|
+
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
def _validate_thinking_support(self) -> None:
|
|
235
|
+
"""
|
|
236
|
+
Validate that the current model supports extended thinking.
|
|
237
|
+
|
|
238
|
+
Raises:
|
|
239
|
+
ValueError: If thinking is enabled but the model doesn't support it
|
|
240
|
+
"""
|
|
241
|
+
if self.thinking and self.id in self.NON_THINKING_MODELS:
|
|
242
|
+
non_thinking_models = "\n - ".join(sorted(self.NON_THINKING_MODELS))
|
|
243
|
+
raise ValueError(
|
|
244
|
+
f"Model '{self.id}' does not support extended thinking.\n\n"
|
|
245
|
+
f"The following models do NOT support thinking:\n - {non_thinking_models}\n\n"
|
|
246
|
+
f"All other Claude models support extended thinking by default.\n"
|
|
247
|
+
f"For more information, see: https://docs.anthropic.com/en/docs/about-claude/models/overview"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def _setup_skills_configuration(self) -> None:
|
|
251
|
+
"""
|
|
252
|
+
Set up configuration for Claude Agent Skills.
|
|
253
|
+
Automatically configures betas array with required values.
|
|
254
|
+
|
|
255
|
+
Skills enable document creation capabilities (PowerPoint, Excel, Word, PDF).
|
|
256
|
+
For more information, see: https://docs.claude.com/en/docs/agents-and-tools/agent-skills/quickstart
|
|
257
|
+
"""
|
|
258
|
+
# Required betas for skills
|
|
259
|
+
required_betas = ["code-execution-2025-08-25", "skills-2025-10-02"]
|
|
260
|
+
|
|
261
|
+
# Initialize or merge betas
|
|
262
|
+
if self.betas is None:
|
|
263
|
+
self.betas = required_betas
|
|
264
|
+
else:
|
|
265
|
+
# Add required betas if not present
|
|
266
|
+
for beta in required_betas:
|
|
267
|
+
if beta not in self.betas:
|
|
268
|
+
self.betas.append(beta)
|
|
269
|
+
|
|
270
|
+
def _ensure_additional_properties_false(self, schema: Dict[str, Any]) -> None:
|
|
271
|
+
"""
|
|
272
|
+
Recursively ensure all object types have additionalProperties: false.
|
|
273
|
+
"""
|
|
274
|
+
if isinstance(schema, dict):
|
|
275
|
+
if schema.get("type") == "object":
|
|
276
|
+
schema["additionalProperties"] = False
|
|
277
|
+
|
|
278
|
+
# Recursively process nested schemas
|
|
279
|
+
for key, value in schema.items():
|
|
280
|
+
if key in ["properties", "items", "allOf", "anyOf", "oneOf"]:
|
|
281
|
+
if isinstance(value, dict):
|
|
282
|
+
self._ensure_additional_properties_false(value)
|
|
283
|
+
elif isinstance(value, list):
|
|
284
|
+
for item in value:
|
|
285
|
+
if isinstance(item, dict):
|
|
286
|
+
self._ensure_additional_properties_false(item)
|
|
287
|
+
|
|
288
|
+
def _build_output_format(self, response_format: Optional[Union[Dict, Type[BaseModel]]]) -> Optional[Dict[str, Any]]:
|
|
289
|
+
"""
|
|
290
|
+
Build Anthropic output_format parameter from response_format.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
response_format: Pydantic model or dict format
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Dict with output_format structure or None
|
|
297
|
+
"""
|
|
298
|
+
if response_format is None:
|
|
299
|
+
return None
|
|
300
|
+
|
|
301
|
+
if not self._supports_structured_outputs():
|
|
302
|
+
return None
|
|
303
|
+
|
|
304
|
+
# Handle Pydantic BaseModel
|
|
305
|
+
if isinstance(response_format, type) and issubclass(response_format, BaseModel):
|
|
306
|
+
try:
|
|
307
|
+
# Try to use Anthropic SDK's transform_schema helper if available
|
|
308
|
+
from anthropic import transform_schema
|
|
309
|
+
|
|
310
|
+
schema = transform_schema(response_format.model_json_schema())
|
|
311
|
+
except (ImportError, AttributeError):
|
|
312
|
+
# Fallback to direct schema conversion
|
|
313
|
+
schema = response_format.model_json_schema()
|
|
314
|
+
# Ensure additionalProperties is False
|
|
315
|
+
if isinstance(schema, dict):
|
|
316
|
+
if "additionalProperties" not in schema:
|
|
317
|
+
schema["additionalProperties"] = False
|
|
318
|
+
# Recursively ensure all object types have additionalProperties: false
|
|
319
|
+
self._ensure_additional_properties_false(schema)
|
|
320
|
+
|
|
321
|
+
return {"type": "json_schema", "schema": schema}
|
|
322
|
+
|
|
323
|
+
# Handle dict format (already in correct structure)
|
|
324
|
+
elif isinstance(response_format, dict):
|
|
325
|
+
return response_format
|
|
326
|
+
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
def _validate_structured_outputs_usage(
|
|
330
|
+
self,
|
|
331
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
332
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
333
|
+
) -> None:
|
|
334
|
+
"""
|
|
335
|
+
Validate that structured outputs are only used with supported models.
|
|
336
|
+
|
|
337
|
+
Raises:
|
|
338
|
+
ValueError: If structured outputs are used with unsupported model
|
|
339
|
+
"""
|
|
340
|
+
if not self._using_structured_outputs(response_format, tools):
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
if not self._supports_structured_outputs():
|
|
344
|
+
raise ValueError(f"Model '{self.id}' does not support structured outputs.\n\n")
|
|
345
|
+
|
|
346
|
+
def _has_beta_features(
|
|
347
|
+
self,
|
|
348
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
349
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
350
|
+
) -> bool:
|
|
351
|
+
"""Check if the model has any Anthropic beta features enabled."""
|
|
352
|
+
return (
|
|
353
|
+
self.mcp_servers is not None
|
|
354
|
+
or self.context_management is not None
|
|
355
|
+
or self.skills is not None
|
|
356
|
+
or self.betas is not None
|
|
357
|
+
or self._using_structured_outputs(response_format, tools)
|
|
358
|
+
)
|
|
119
359
|
|
|
120
360
|
def get_client(self) -> AnthropicClient:
|
|
121
361
|
"""
|
|
122
362
|
Returns an instance of the Anthropic client.
|
|
123
363
|
"""
|
|
124
|
-
if self.client:
|
|
364
|
+
if self.client and not self.client.is_closed():
|
|
125
365
|
return self.client
|
|
126
366
|
|
|
127
|
-
|
|
128
|
-
if
|
|
129
|
-
|
|
367
|
+
_client_params = self._get_client_params()
|
|
368
|
+
if self.http_client:
|
|
369
|
+
if isinstance(self.http_client, httpx.Client):
|
|
370
|
+
_client_params["http_client"] = self.http_client
|
|
371
|
+
else:
|
|
372
|
+
log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
|
|
373
|
+
# Use global sync client when user http_client is invalid
|
|
374
|
+
_client_params["http_client"] = get_default_sync_client()
|
|
375
|
+
else:
|
|
376
|
+
# Use global sync client when no custom http_client is provided
|
|
377
|
+
_client_params["http_client"] = get_default_sync_client()
|
|
378
|
+
self.client = AnthropicClient(**_client_params)
|
|
379
|
+
return self.client
|
|
130
380
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if self.
|
|
136
|
-
|
|
137
|
-
return AnthropicClient(**_client_params)
|
|
381
|
+
def get_async_client(self) -> AsyncAnthropicClient:
|
|
382
|
+
"""
|
|
383
|
+
Returns an instance of the async Anthropic client.
|
|
384
|
+
"""
|
|
385
|
+
if self.async_client and not self.async_client.is_closed():
|
|
386
|
+
return self.async_client
|
|
138
387
|
|
|
139
|
-
|
|
140
|
-
|
|
388
|
+
_client_params = self._get_client_params()
|
|
389
|
+
if self.http_client:
|
|
390
|
+
if isinstance(self.http_client, httpx.AsyncClient):
|
|
391
|
+
_client_params["http_client"] = self.http_client
|
|
392
|
+
else:
|
|
393
|
+
log_warning(
|
|
394
|
+
"http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
|
|
395
|
+
)
|
|
396
|
+
# Use global async client when user http_client is invalid
|
|
397
|
+
_client_params["http_client"] = get_default_async_client()
|
|
398
|
+
else:
|
|
399
|
+
# Use global async client when no custom http_client is provided
|
|
400
|
+
_client_params["http_client"] = get_default_async_client()
|
|
401
|
+
self.async_client = AsyncAnthropicClient(**_client_params)
|
|
402
|
+
return self.async_client
|
|
403
|
+
|
|
404
|
+
def count_tokens(
|
|
405
|
+
self,
|
|
406
|
+
messages: List[Message],
|
|
407
|
+
tools: Optional[List[Union[Function, Dict[str, Any]]]] = None,
|
|
408
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
409
|
+
) -> int:
|
|
410
|
+
anthropic_messages, system_prompt = format_messages(messages, compress_tool_results=True)
|
|
411
|
+
anthropic_tools = None
|
|
412
|
+
if tools:
|
|
413
|
+
formatted_tools = self._format_tools(tools)
|
|
414
|
+
anthropic_tools = format_tools_for_model(formatted_tools)
|
|
415
|
+
|
|
416
|
+
kwargs: Dict[str, Any] = {"messages": anthropic_messages, "model": self.id}
|
|
417
|
+
if system_prompt:
|
|
418
|
+
kwargs["system"] = system_prompt
|
|
419
|
+
if anthropic_tools:
|
|
420
|
+
kwargs["tools"] = anthropic_tools
|
|
421
|
+
|
|
422
|
+
response = self.get_client().messages.count_tokens(**kwargs)
|
|
423
|
+
return response.input_tokens + count_schema_tokens(response_format, self.id)
|
|
424
|
+
|
|
425
|
+
async def acount_tokens(
|
|
426
|
+
self,
|
|
427
|
+
messages: List[Message],
|
|
428
|
+
tools: Optional[List[Union[Function, Dict[str, Any]]]] = None,
|
|
429
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
430
|
+
) -> int:
|
|
431
|
+
anthropic_messages, system_prompt = format_messages(messages, compress_tool_results=True)
|
|
432
|
+
anthropic_tools = None
|
|
433
|
+
if tools:
|
|
434
|
+
formatted_tools = self._format_tools(tools)
|
|
435
|
+
anthropic_tools = format_tools_for_model(formatted_tools)
|
|
436
|
+
|
|
437
|
+
kwargs: Dict[str, Any] = {"messages": anthropic_messages, "model": self.id}
|
|
438
|
+
if system_prompt:
|
|
439
|
+
kwargs["system"] = system_prompt
|
|
440
|
+
if anthropic_tools:
|
|
441
|
+
kwargs["tools"] = anthropic_tools
|
|
442
|
+
|
|
443
|
+
response = await self.get_async_client().messages.count_tokens(**kwargs)
|
|
444
|
+
return response.input_tokens + count_schema_tokens(response_format, self.id)
|
|
445
|
+
|
|
446
|
+
def get_request_params(
|
|
447
|
+
self,
|
|
448
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
449
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
450
|
+
) -> Dict[str, Any]:
|
|
141
451
|
"""
|
|
142
452
|
Generate keyword arguments for API requests.
|
|
143
453
|
"""
|
|
454
|
+
# Validate thinking support if thinking is enabled
|
|
455
|
+
if self.thinking:
|
|
456
|
+
self._validate_thinking_support()
|
|
457
|
+
|
|
144
458
|
_request_params: Dict[str, Any] = {}
|
|
145
459
|
if self.max_tokens:
|
|
146
460
|
_request_params["max_tokens"] = self.max_tokens
|
|
461
|
+
if self.thinking:
|
|
462
|
+
_request_params["thinking"] = self.thinking
|
|
147
463
|
if self.temperature:
|
|
148
464
|
_request_params["temperature"] = self.temperature
|
|
149
465
|
if self.stop_sequences:
|
|
@@ -152,137 +468,155 @@ class Claude(Model):
|
|
|
152
468
|
_request_params["top_p"] = self.top_p
|
|
153
469
|
if self.top_k:
|
|
154
470
|
_request_params["top_k"] = self.top_k
|
|
471
|
+
|
|
472
|
+
# Build betas list - include existing betas and add new one if needed
|
|
473
|
+
betas_list = list(self.betas) if self.betas else []
|
|
474
|
+
|
|
475
|
+
# Add structured outputs beta header if using structured outputs
|
|
476
|
+
if self._using_structured_outputs(response_format, tools):
|
|
477
|
+
beta_header = "structured-outputs-2025-11-13"
|
|
478
|
+
if beta_header not in betas_list:
|
|
479
|
+
betas_list.append(beta_header)
|
|
480
|
+
|
|
481
|
+
# Include betas if any are present
|
|
482
|
+
if betas_list:
|
|
483
|
+
_request_params["betas"] = betas_list
|
|
484
|
+
|
|
485
|
+
if self.context_management:
|
|
486
|
+
_request_params["context_management"] = self.context_management
|
|
487
|
+
if self.mcp_servers:
|
|
488
|
+
_request_params["mcp_servers"] = [
|
|
489
|
+
{k: v for k, v in asdict(server).items() if v is not None} for server in self.mcp_servers
|
|
490
|
+
]
|
|
491
|
+
if self.skills:
|
|
492
|
+
_request_params["container"] = {"skills": self.skills}
|
|
155
493
|
if self.request_params:
|
|
156
494
|
_request_params.update(self.request_params)
|
|
157
|
-
return _request_params
|
|
158
|
-
|
|
159
|
-
def format_messages(self, messages: List[Message]) -> Tuple[List[Dict[str, str]], str]:
|
|
160
|
-
"""
|
|
161
|
-
Process the list of messages and separate them into API messages and system messages.
|
|
162
|
-
|
|
163
|
-
Args:
|
|
164
|
-
messages (List[Message]): The list of messages to process.
|
|
165
|
-
|
|
166
|
-
Returns:
|
|
167
|
-
Tuple[List[Dict[str, str]], str]: A tuple containing the list of API messages and the concatenated system messages.
|
|
168
|
-
"""
|
|
169
|
-
chat_messages: List[Dict[str, str]] = []
|
|
170
|
-
system_messages: List[str] = []
|
|
171
|
-
|
|
172
|
-
for idx, message in enumerate(messages):
|
|
173
|
-
content = message.content or ""
|
|
174
|
-
if message.role == "system" or (message.role != "user" and idx in [0, 1]):
|
|
175
|
-
system_messages.append(content) # type: ignore
|
|
176
|
-
continue
|
|
177
|
-
elif message.role == "user":
|
|
178
|
-
if isinstance(content, str):
|
|
179
|
-
content = [{"type": "text", "text": content}]
|
|
180
|
-
|
|
181
|
-
if message.images is not None:
|
|
182
|
-
for image in message.images:
|
|
183
|
-
image_content = format_image_for_message(image)
|
|
184
|
-
if image_content:
|
|
185
|
-
content.append(image_content)
|
|
186
|
-
|
|
187
|
-
# Handle tool calls from history
|
|
188
|
-
elif message.role == "assistant" and isinstance(message.content, str) and message.tool_calls:
|
|
189
|
-
if message.content:
|
|
190
|
-
content = [TextBlock(text=message.content, type="text")]
|
|
191
|
-
else:
|
|
192
|
-
content = []
|
|
193
|
-
for tool_call in message.tool_calls:
|
|
194
|
-
content.append(
|
|
195
|
-
ToolUseBlock(
|
|
196
|
-
id=tool_call["id"],
|
|
197
|
-
input=json.loads(tool_call["function"]["arguments"]),
|
|
198
|
-
name=tool_call["function"]["name"],
|
|
199
|
-
type="tool_use",
|
|
200
|
-
)
|
|
201
|
-
)
|
|
202
495
|
|
|
203
|
-
|
|
204
|
-
return chat_messages, " ".join(system_messages)
|
|
496
|
+
return _request_params
|
|
205
497
|
|
|
206
|
-
def
|
|
498
|
+
def _prepare_request_kwargs(
|
|
499
|
+
self,
|
|
500
|
+
system_message: str,
|
|
501
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
502
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
503
|
+
) -> Dict[str, Any]:
|
|
207
504
|
"""
|
|
208
505
|
Prepare the request keyword arguments for the API call.
|
|
209
506
|
|
|
210
507
|
Args:
|
|
211
508
|
system_message (str): The concatenated system messages.
|
|
509
|
+
tools: Optional list of tools
|
|
510
|
+
response_format: Optional response format (Pydantic model or dict)
|
|
212
511
|
|
|
213
512
|
Returns:
|
|
214
513
|
Dict[str, Any]: The request keyword arguments.
|
|
215
514
|
"""
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
515
|
+
# Validate structured outputs usage
|
|
516
|
+
self._validate_structured_outputs_usage(response_format, tools)
|
|
517
|
+
|
|
518
|
+
# Pass response_format and tools to get_request_params for beta header handling
|
|
519
|
+
request_kwargs = self.get_request_params(response_format=response_format, tools=tools).copy()
|
|
520
|
+
if system_message:
|
|
521
|
+
if self.cache_system_prompt:
|
|
522
|
+
cache_control = (
|
|
523
|
+
{"type": "ephemeral", "ttl": "1h"}
|
|
524
|
+
if self.extended_cache_time is not None and self.extended_cache_time is True
|
|
525
|
+
else {"type": "ephemeral"}
|
|
526
|
+
)
|
|
527
|
+
request_kwargs["system"] = [{"text": system_message, "type": "text", "cache_control": cache_control}]
|
|
528
|
+
else:
|
|
529
|
+
request_kwargs["system"] = [{"text": system_message, "type": "text"}]
|
|
530
|
+
|
|
531
|
+
# Add code execution tool if skills are enabled
|
|
532
|
+
if self.skills:
|
|
533
|
+
code_execution_tool = {"type": "code_execution_20250825", "name": "code_execution"}
|
|
534
|
+
if tools:
|
|
535
|
+
# Add code_execution to existing tools, code execution is needed for generating and processing files
|
|
536
|
+
tools = tools + [code_execution_tool]
|
|
537
|
+
else:
|
|
538
|
+
tools = [code_execution_tool]
|
|
232
539
|
|
|
233
|
-
tools
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
properties: Dict[str, Any] = parameters.get("properties", {})
|
|
237
|
-
required_params: List[str] = []
|
|
540
|
+
# Format tools (this will handle strict mode)
|
|
541
|
+
if tools:
|
|
542
|
+
request_kwargs["tools"] = format_tools_for_model(tools)
|
|
238
543
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
544
|
+
# Build output_format if response_format is provided
|
|
545
|
+
output_format = self._build_output_format(response_format)
|
|
546
|
+
if output_format:
|
|
547
|
+
request_kwargs["output_format"] = output_format
|
|
242
548
|
|
|
243
|
-
|
|
244
|
-
|
|
549
|
+
if request_kwargs:
|
|
550
|
+
log_debug(f"Calling {self.provider} with request parameters: {request_kwargs}", log_level=2)
|
|
551
|
+
return request_kwargs
|
|
245
552
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
"description": func_def.description or "",
|
|
257
|
-
"input_schema": {
|
|
258
|
-
"type": parameters.get("type", "object"),
|
|
259
|
-
"properties": input_properties,
|
|
260
|
-
"required": required_params,
|
|
261
|
-
},
|
|
262
|
-
}
|
|
263
|
-
tools.append(tool)
|
|
264
|
-
return tools
|
|
265
|
-
|
|
266
|
-
def invoke(self, messages: List[Message]) -> AnthropicMessage:
|
|
553
|
+
def invoke(
|
|
554
|
+
self,
|
|
555
|
+
messages: List[Message],
|
|
556
|
+
assistant_message: Message,
|
|
557
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
558
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
559
|
+
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
560
|
+
run_response: Optional[RunOutput] = None,
|
|
561
|
+
compress_tool_results: bool = False,
|
|
562
|
+
) -> ModelResponse:
|
|
267
563
|
"""
|
|
268
564
|
Send a request to the Anthropic API to generate a response.
|
|
565
|
+
"""
|
|
566
|
+
try:
|
|
567
|
+
if run_response and run_response.metrics:
|
|
568
|
+
run_response.metrics.set_time_to_first_token()
|
|
569
|
+
|
|
570
|
+
chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
|
|
571
|
+
request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
|
|
572
|
+
|
|
573
|
+
if self._has_beta_features(response_format=response_format, tools=tools):
|
|
574
|
+
assistant_message.metrics.start_timer()
|
|
575
|
+
provider_response = self.get_client().beta.messages.create(
|
|
576
|
+
model=self.id,
|
|
577
|
+
messages=chat_messages, # type: ignore
|
|
578
|
+
**request_kwargs,
|
|
579
|
+
)
|
|
580
|
+
else:
|
|
581
|
+
assistant_message.metrics.start_timer()
|
|
582
|
+
provider_response = self.get_client().messages.create(
|
|
583
|
+
model=self.id,
|
|
584
|
+
messages=chat_messages, # type: ignore
|
|
585
|
+
**request_kwargs,
|
|
586
|
+
)
|
|
269
587
|
|
|
270
|
-
|
|
271
|
-
messages (List[Message]): A list of messages to send to the model.
|
|
588
|
+
assistant_message.metrics.stop_timer()
|
|
272
589
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
"""
|
|
276
|
-
chat_messages, system_message = self.format_messages(messages)
|
|
277
|
-
request_kwargs = self.prepare_request_kwargs(system_message)
|
|
590
|
+
# Parse the response into an Agno ModelResponse object
|
|
591
|
+
model_response = self._parse_provider_response(provider_response, response_format=response_format) # type: ignore
|
|
278
592
|
|
|
279
|
-
|
|
280
|
-
model=self.id,
|
|
281
|
-
messages=chat_messages, # type: ignore
|
|
282
|
-
**request_kwargs,
|
|
283
|
-
)
|
|
593
|
+
return model_response
|
|
284
594
|
|
|
285
|
-
|
|
595
|
+
except APIConnectionError as e:
|
|
596
|
+
log_error(f"Connection error while calling Claude API: {str(e)}")
|
|
597
|
+
raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
|
|
598
|
+
except RateLimitError as e:
|
|
599
|
+
log_warning(f"Rate limit exceeded: {str(e)}")
|
|
600
|
+
raise ModelRateLimitError(message=e.message, model_name=self.name, model_id=self.id) from e
|
|
601
|
+
except APIStatusError as e:
|
|
602
|
+
log_error(f"Claude API error (status {e.status_code}): {str(e)}")
|
|
603
|
+
raise ModelProviderError(
|
|
604
|
+
message=e.message, status_code=e.status_code, model_name=self.name, model_id=self.id
|
|
605
|
+
) from e
|
|
606
|
+
except Exception as e:
|
|
607
|
+
log_error(f"Unexpected error calling Claude API: {str(e)}")
|
|
608
|
+
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
609
|
+
|
|
610
|
+
def invoke_stream(
|
|
611
|
+
self,
|
|
612
|
+
messages: List[Message],
|
|
613
|
+
assistant_message: Message,
|
|
614
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
615
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
616
|
+
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
617
|
+
run_response: Optional[RunOutput] = None,
|
|
618
|
+
compress_tool_results: bool = False,
|
|
619
|
+
) -> Any:
|
|
286
620
|
"""
|
|
287
621
|
Stream a response from the Anthropic API.
|
|
288
622
|
|
|
@@ -291,349 +625,479 @@ class Claude(Model):
|
|
|
291
625
|
|
|
292
626
|
Returns:
|
|
293
627
|
Any: The streamed response from the model.
|
|
628
|
+
|
|
629
|
+
Raises:
|
|
630
|
+
APIConnectionError: If there are network connectivity issues
|
|
631
|
+
RateLimitError: If the API rate limit is exceeded
|
|
632
|
+
APIStatusError: For other API-related errors
|
|
294
633
|
"""
|
|
295
|
-
chat_messages, system_message =
|
|
296
|
-
request_kwargs = self.
|
|
634
|
+
chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
|
|
635
|
+
request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
|
|
636
|
+
|
|
637
|
+
try:
|
|
638
|
+
if run_response and run_response.metrics:
|
|
639
|
+
run_response.metrics.set_time_to_first_token()
|
|
640
|
+
|
|
641
|
+
# Beta features
|
|
642
|
+
if self._has_beta_features(response_format=response_format, tools=tools):
|
|
643
|
+
assistant_message.metrics.start_timer()
|
|
644
|
+
with self.get_client().beta.messages.stream(
|
|
645
|
+
model=self.id,
|
|
646
|
+
messages=chat_messages, # type: ignore
|
|
647
|
+
**request_kwargs,
|
|
648
|
+
) as stream:
|
|
649
|
+
for chunk in stream:
|
|
650
|
+
yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
|
|
651
|
+
else:
|
|
652
|
+
assistant_message.metrics.start_timer()
|
|
653
|
+
with self.get_client().messages.stream(
|
|
654
|
+
model=self.id,
|
|
655
|
+
messages=chat_messages, # type: ignore
|
|
656
|
+
**request_kwargs,
|
|
657
|
+
) as stream:
|
|
658
|
+
for chunk in stream: # type: ignore
|
|
659
|
+
yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
|
|
660
|
+
|
|
661
|
+
assistant_message.metrics.stop_timer()
|
|
662
|
+
|
|
663
|
+
except APIConnectionError as e:
|
|
664
|
+
log_error(f"Connection error while calling Claude API: {str(e)}")
|
|
665
|
+
raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
|
|
666
|
+
except RateLimitError as e:
|
|
667
|
+
log_warning(f"Rate limit exceeded: {str(e)}")
|
|
668
|
+
raise ModelRateLimitError(message=e.message, model_name=self.name, model_id=self.id) from e
|
|
669
|
+
except APIStatusError as e:
|
|
670
|
+
log_error(f"Claude API error (status {e.status_code}): {str(e)}")
|
|
671
|
+
raise ModelProviderError(
|
|
672
|
+
message=e.message, status_code=e.status_code, model_name=self.name, model_id=self.id
|
|
673
|
+
) from e
|
|
674
|
+
except Exception as e:
|
|
675
|
+
log_error(f"Unexpected error calling Claude API: {str(e)}")
|
|
676
|
+
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
677
|
+
|
|
678
|
+
async def ainvoke(
|
|
679
|
+
self,
|
|
680
|
+
messages: List[Message],
|
|
681
|
+
assistant_message: Message,
|
|
682
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
683
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
684
|
+
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
685
|
+
run_response: Optional[RunOutput] = None,
|
|
686
|
+
compress_tool_results: bool = False,
|
|
687
|
+
) -> ModelResponse:
|
|
688
|
+
"""
|
|
689
|
+
Send an asynchronous request to the Anthropic API to generate a response.
|
|
690
|
+
"""
|
|
691
|
+
try:
|
|
692
|
+
if run_response and run_response.metrics:
|
|
693
|
+
run_response.metrics.set_time_to_first_token()
|
|
694
|
+
|
|
695
|
+
chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
|
|
696
|
+
request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
|
|
697
|
+
|
|
698
|
+
# Beta features
|
|
699
|
+
if self._has_beta_features(response_format=response_format, tools=tools):
|
|
700
|
+
assistant_message.metrics.start_timer()
|
|
701
|
+
provider_response = await self.get_async_client().beta.messages.create(
|
|
702
|
+
model=self.id,
|
|
703
|
+
messages=chat_messages, # type: ignore
|
|
704
|
+
**request_kwargs,
|
|
705
|
+
)
|
|
706
|
+
else:
|
|
707
|
+
assistant_message.metrics.start_timer()
|
|
708
|
+
provider_response = await self.get_async_client().messages.create(
|
|
709
|
+
model=self.id,
|
|
710
|
+
messages=chat_messages, # type: ignore
|
|
711
|
+
**request_kwargs,
|
|
712
|
+
)
|
|
297
713
|
|
|
298
|
-
|
|
299
|
-
model=self.id,
|
|
300
|
-
messages=chat_messages, # type: ignore
|
|
301
|
-
**request_kwargs,
|
|
302
|
-
)
|
|
714
|
+
assistant_message.metrics.stop_timer()
|
|
303
715
|
|
|
304
|
-
|
|
716
|
+
# Parse the response into an Agno ModelResponse object
|
|
717
|
+
model_response = self._parse_provider_response(provider_response, response_format=response_format) # type: ignore
|
|
718
|
+
|
|
719
|
+
return model_response
|
|
720
|
+
|
|
721
|
+
except APIConnectionError as e:
|
|
722
|
+
log_error(f"Connection error while calling Claude API: {str(e)}")
|
|
723
|
+
raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
|
|
724
|
+
except RateLimitError as e:
|
|
725
|
+
log_warning(f"Rate limit exceeded: {str(e)}")
|
|
726
|
+
raise ModelRateLimitError(message=e.message, model_name=self.name, model_id=self.id) from e
|
|
727
|
+
except APIStatusError as e:
|
|
728
|
+
log_error(f"Claude API error (status {e.status_code}): {str(e)}")
|
|
729
|
+
raise ModelProviderError(
|
|
730
|
+
message=e.message, status_code=e.status_code, model_name=self.name, model_id=self.id
|
|
731
|
+
) from e
|
|
732
|
+
except Exception as e:
|
|
733
|
+
log_error(f"Unexpected error calling Claude API: {str(e)}")
|
|
734
|
+
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
735
|
+
|
|
736
|
+
async def ainvoke_stream(
|
|
305
737
|
self,
|
|
738
|
+
messages: List[Message],
|
|
306
739
|
assistant_message: Message,
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
740
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
741
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
742
|
+
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
743
|
+
run_response: Optional[RunOutput] = None,
|
|
744
|
+
compress_tool_results: bool = False,
|
|
745
|
+
) -> AsyncIterator[ModelResponse]:
|
|
310
746
|
"""
|
|
311
|
-
|
|
312
|
-
|
|
747
|
+
Stream an asynchronous response from the Anthropic API.
|
|
313
748
|
Args:
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
749
|
+
messages (List[Message]): A list of messages to send to the model.
|
|
750
|
+
Returns:
|
|
751
|
+
AsyncIterator[ModelResponse]: An async iterator of processed model responses.
|
|
752
|
+
Raises:
|
|
753
|
+
APIConnectionError: If there are network connectivity issues
|
|
754
|
+
RateLimitError: If the API rate limit is exceeded
|
|
755
|
+
APIStatusError: For other API-related errors
|
|
317
756
|
"""
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
757
|
+
try:
|
|
758
|
+
if run_response and run_response.metrics:
|
|
759
|
+
run_response.metrics.set_time_to_first_token()
|
|
760
|
+
|
|
761
|
+
chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
|
|
762
|
+
request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
|
|
763
|
+
|
|
764
|
+
if self._has_beta_features(response_format=response_format, tools=tools):
|
|
765
|
+
assistant_message.metrics.start_timer()
|
|
766
|
+
async with self.get_async_client().beta.messages.stream(
|
|
767
|
+
model=self.id,
|
|
768
|
+
messages=chat_messages, # type: ignore
|
|
769
|
+
**request_kwargs,
|
|
770
|
+
) as stream:
|
|
771
|
+
async for chunk in stream:
|
|
772
|
+
yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
|
|
773
|
+
else:
|
|
774
|
+
assistant_message.metrics.start_timer()
|
|
775
|
+
async with self.get_async_client().messages.stream(
|
|
776
|
+
model=self.id,
|
|
777
|
+
messages=chat_messages, # type: ignore
|
|
778
|
+
**request_kwargs,
|
|
779
|
+
) as stream:
|
|
780
|
+
async for chunk in stream: # type: ignore
|
|
781
|
+
yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
|
|
782
|
+
|
|
783
|
+
assistant_message.metrics.stop_timer()
|
|
784
|
+
|
|
785
|
+
except APIConnectionError as e:
|
|
786
|
+
log_error(f"Connection error while calling Claude API: {str(e)}")
|
|
787
|
+
raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
|
|
788
|
+
except RateLimitError as e:
|
|
789
|
+
log_warning(f"Rate limit exceeded: {str(e)}")
|
|
790
|
+
raise ModelRateLimitError(message=e.message, model_name=self.name, model_id=self.id) from e
|
|
791
|
+
except APIStatusError as e:
|
|
792
|
+
log_error(f"Claude API error (status {e.status_code}): {str(e)}")
|
|
793
|
+
raise ModelProviderError(
|
|
794
|
+
message=e.message, status_code=e.status_code, model_name=self.name, model_id=self.id
|
|
795
|
+
) from e
|
|
796
|
+
except Exception as e:
|
|
797
|
+
log_error(f"Unexpected error calling Claude API: {str(e)}")
|
|
798
|
+
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
799
|
+
|
|
800
|
+
def get_system_message_for_model(self, tools: Optional[List[Any]] = None) -> Optional[str]:
|
|
801
|
+
if tools is not None and len(tools) > 0:
|
|
802
|
+
tool_call_prompt = "Do not reflect on the quality of the returned search results in your response\n\n"
|
|
803
|
+
return tool_call_prompt
|
|
804
|
+
return None
|
|
325
805
|
|
|
326
|
-
def
|
|
806
|
+
def _parse_provider_response(
|
|
807
|
+
self,
|
|
808
|
+
response: Union[AnthropicMessage, BetaMessage],
|
|
809
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
810
|
+
**kwargs,
|
|
811
|
+
) -> ModelResponse:
|
|
327
812
|
"""
|
|
328
|
-
|
|
813
|
+
Parse the Claude response into a ModelResponse.
|
|
329
814
|
|
|
330
815
|
Args:
|
|
331
|
-
response
|
|
332
|
-
|
|
816
|
+
response: Raw response from Anthropic
|
|
817
|
+
response_format: Optional response format for structured output parsing
|
|
333
818
|
|
|
334
819
|
Returns:
|
|
335
|
-
|
|
820
|
+
ModelResponse: Parsed response data
|
|
336
821
|
"""
|
|
337
|
-
|
|
822
|
+
model_response = ModelResponse()
|
|
823
|
+
|
|
824
|
+
# Add role (Claude always uses 'assistant')
|
|
825
|
+
model_response.role = response.role or "assistant"
|
|
338
826
|
|
|
339
827
|
if response.content:
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
828
|
+
for block in response.content:
|
|
829
|
+
if block.type == "text":
|
|
830
|
+
text_content = block.text
|
|
831
|
+
|
|
832
|
+
if model_response.content is None:
|
|
833
|
+
model_response.content = text_content
|
|
834
|
+
else:
|
|
835
|
+
model_response.content += text_content
|
|
836
|
+
|
|
837
|
+
# Handle structured outputs (JSON outputs)
|
|
838
|
+
if (
|
|
839
|
+
response_format is not None
|
|
840
|
+
and isinstance(response_format, type)
|
|
841
|
+
and issubclass(response_format, BaseModel)
|
|
842
|
+
):
|
|
843
|
+
if text_content:
|
|
844
|
+
try:
|
|
845
|
+
# Parse JSON from text content
|
|
846
|
+
parsed_data = json.loads(text_content)
|
|
847
|
+
# Validate against Pydantic model
|
|
848
|
+
model_response.parsed = response_format.model_validate(parsed_data)
|
|
849
|
+
log_debug(f"Successfully parsed structured output: {model_response.parsed}")
|
|
850
|
+
except json.JSONDecodeError as e:
|
|
851
|
+
log_warning(f"Failed to parse JSON from structured output: {e}")
|
|
852
|
+
except ValidationError as e:
|
|
853
|
+
log_warning(f"Failed to validate structured output against schema: {e}")
|
|
854
|
+
except Exception as e:
|
|
855
|
+
log_warning(f"Unexpected error parsing structured output: {e}")
|
|
856
|
+
|
|
857
|
+
# Capture citations from the response
|
|
858
|
+
if block.citations is not None:
|
|
859
|
+
if model_response.citations is None:
|
|
860
|
+
model_response.citations = Citations(raw=[], urls=[], documents=[])
|
|
861
|
+
for citation in block.citations:
|
|
862
|
+
model_response.citations.raw.append(citation.model_dump()) # type: ignore
|
|
863
|
+
# Web search citations
|
|
864
|
+
if isinstance(citation, CitationsWebSearchResultLocation):
|
|
865
|
+
model_response.citations.urls.append( # type: ignore
|
|
866
|
+
UrlCitation(url=citation.url, title=citation.cited_text)
|
|
867
|
+
)
|
|
868
|
+
# Document citations
|
|
869
|
+
elif isinstance(citation, CitationPageLocation):
|
|
870
|
+
model_response.citations.documents.append( # type: ignore
|
|
871
|
+
DocumentCitation(
|
|
872
|
+
document_title=citation.document_title,
|
|
873
|
+
cited_text=citation.cited_text,
|
|
874
|
+
)
|
|
875
|
+
)
|
|
876
|
+
elif block.type == "thinking":
|
|
877
|
+
model_response.reasoning_content = block.thinking
|
|
878
|
+
model_response.provider_data = {
|
|
879
|
+
"signature": block.signature,
|
|
880
|
+
}
|
|
881
|
+
elif block.type == "redacted_thinking":
|
|
882
|
+
model_response.redacted_reasoning_content = block.data
|
|
883
|
+
|
|
884
|
+
# Extract tool calls from the response
|
|
354
885
|
if response.stop_reason == "tool_use":
|
|
355
|
-
for block in
|
|
356
|
-
if
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
tool_input = tool_use.input
|
|
360
|
-
message_data.tool_ids.append(tool_use.id)
|
|
886
|
+
for block in response.content:
|
|
887
|
+
if block.type == "tool_use":
|
|
888
|
+
tool_name = block.name
|
|
889
|
+
tool_input = block.input
|
|
361
890
|
|
|
362
891
|
function_def = {"name": tool_name}
|
|
363
892
|
if tool_input:
|
|
364
893
|
function_def["arguments"] = json.dumps(tool_input)
|
|
365
|
-
|
|
894
|
+
|
|
895
|
+
model_response.extra = model_response.extra or {}
|
|
896
|
+
|
|
897
|
+
model_response.tool_calls.append(
|
|
366
898
|
{
|
|
367
|
-
"id":
|
|
899
|
+
"id": block.id,
|
|
368
900
|
"type": "function",
|
|
369
901
|
"function": function_def,
|
|
370
902
|
}
|
|
371
903
|
)
|
|
372
904
|
|
|
373
|
-
#
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
content=message_data.response_content,
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
# -*- Update assistant message if tool calls are present
|
|
380
|
-
if len(message_data.tool_calls) > 0:
|
|
381
|
-
assistant_message.tool_calls = message_data.tool_calls
|
|
905
|
+
# Add usage metrics
|
|
906
|
+
if response.usage is not None:
|
|
907
|
+
model_response.response_usage = self._get_metrics(response.usage)
|
|
382
908
|
|
|
383
|
-
#
|
|
384
|
-
self.
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
909
|
+
# Capture context management information if present
|
|
910
|
+
if self.context_management is not None and hasattr(response, "context_management"):
|
|
911
|
+
if response.context_management is not None: # type: ignore
|
|
912
|
+
model_response.provider_data = model_response.provider_data or {}
|
|
913
|
+
if hasattr(response.context_management, "model_dump"):
|
|
914
|
+
model_response.provider_data["context_management"] = response.context_management.model_dump() # type: ignore
|
|
915
|
+
else:
|
|
916
|
+
model_response.provider_data["context_management"] = response.context_management # type: ignore
|
|
917
|
+
# Extract file IDs if skills are enabled
|
|
918
|
+
if self.skills and response.content:
|
|
919
|
+
file_ids: List[str] = []
|
|
920
|
+
for block in response.content:
|
|
921
|
+
if block.type == "bash_code_execution_tool_result":
|
|
922
|
+
if hasattr(block, "content") and hasattr(block.content, "content"):
|
|
923
|
+
if isinstance(block.content.content, list):
|
|
924
|
+
for output_block in block.content.content:
|
|
925
|
+
if hasattr(output_block, "file_id"):
|
|
926
|
+
file_ids.append(output_block.file_id)
|
|
927
|
+
|
|
928
|
+
if file_ids:
|
|
929
|
+
if model_response.provider_data is None:
|
|
930
|
+
model_response.provider_data = {}
|
|
931
|
+
model_response.provider_data["file_ids"] = file_ids
|
|
393
932
|
|
|
394
|
-
|
|
395
|
-
function_call_results (List[Message]): The results of the function calls.
|
|
396
|
-
tool_ids (List[str]): The tool ids.
|
|
397
|
-
messages (List[Message]): The list of conversation messages.
|
|
398
|
-
"""
|
|
399
|
-
if len(function_call_results) > 0:
|
|
400
|
-
fc_responses: List = []
|
|
401
|
-
for _fc_message_index, _fc_message in enumerate(function_call_results):
|
|
402
|
-
fc_responses.append(
|
|
403
|
-
{
|
|
404
|
-
"type": "tool_result",
|
|
405
|
-
"tool_use_id": tool_ids[_fc_message_index],
|
|
406
|
-
"content": _fc_message.content,
|
|
407
|
-
}
|
|
408
|
-
)
|
|
409
|
-
messages.append(Message(role="user", content=fc_responses))
|
|
933
|
+
return model_response
|
|
410
934
|
|
|
411
|
-
def
|
|
935
|
+
def _parse_provider_response_delta(
|
|
412
936
|
self,
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
937
|
+
response: Union[
|
|
938
|
+
ContentBlockStartEvent,
|
|
939
|
+
ContentBlockDeltaEvent,
|
|
940
|
+
ContentBlockStopEvent,
|
|
941
|
+
MessageStopEvent,
|
|
942
|
+
BetaRawContentBlockDeltaEvent,
|
|
943
|
+
BetaRawContentBlockStartEvent,
|
|
944
|
+
ParsedBetaContentBlockStopEvent,
|
|
945
|
+
ParsedBetaMessageStopEvent,
|
|
946
|
+
],
|
|
947
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
948
|
+
) -> ModelResponse:
|
|
419
949
|
"""
|
|
420
|
-
|
|
950
|
+
Parse the Claude streaming response into ModelProviderResponse objects.
|
|
421
951
|
|
|
422
952
|
Args:
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
model_response [ModelResponse]: The model response.
|
|
426
|
-
response_content (str): The response content.
|
|
427
|
-
tool_ids (List[str]): The tool ids.
|
|
953
|
+
response: Raw response chunk from Anthropic
|
|
954
|
+
response_format: Optional response format for structured output parsing
|
|
428
955
|
|
|
429
956
|
Returns:
|
|
430
|
-
|
|
431
|
-
"""
|
|
432
|
-
if assistant_message.tool_calls is not None and len(assistant_message.tool_calls) > 0:
|
|
433
|
-
if model_response.tool_calls is None:
|
|
434
|
-
model_response.tool_calls = []
|
|
435
|
-
|
|
436
|
-
model_response.content = str(response_content)
|
|
437
|
-
model_response.content += "\n\n"
|
|
438
|
-
|
|
439
|
-
function_calls_to_run = self._get_function_calls_to_run(assistant_message, messages)
|
|
440
|
-
function_call_results: List[Message] = []
|
|
441
|
-
|
|
442
|
-
if self.show_tool_calls:
|
|
443
|
-
if len(function_calls_to_run) == 1:
|
|
444
|
-
model_response.content += f" - Running: {function_calls_to_run[0].get_call_str()}\n\n"
|
|
445
|
-
elif len(function_calls_to_run) > 1:
|
|
446
|
-
model_response.content += "Running:"
|
|
447
|
-
for _f in function_calls_to_run:
|
|
448
|
-
model_response.content += f"\n - {_f.get_call_str()}"
|
|
449
|
-
model_response.content += "\n\n"
|
|
450
|
-
|
|
451
|
-
for function_call_response in self.run_function_calls(
|
|
452
|
-
function_calls=function_calls_to_run,
|
|
453
|
-
function_call_results=function_call_results,
|
|
454
|
-
):
|
|
455
|
-
if (
|
|
456
|
-
function_call_response.event == ModelResponseEvent.tool_call_completed.value
|
|
457
|
-
and function_call_response.tool_calls is not None
|
|
458
|
-
):
|
|
459
|
-
model_response.tool_calls.extend(function_call_response.tool_calls)
|
|
460
|
-
|
|
461
|
-
self.format_function_call_results(function_call_results, tool_ids, messages)
|
|
462
|
-
|
|
463
|
-
return model_response
|
|
464
|
-
return None
|
|
465
|
-
|
|
466
|
-
def response(self, messages: List[Message]) -> ModelResponse:
|
|
467
|
-
"""
|
|
468
|
-
Send a chat completion request to the Anthropic API.
|
|
469
|
-
|
|
470
|
-
Args:
|
|
471
|
-
messages (List[Message]): A list of messages to send to the model.
|
|
472
|
-
|
|
473
|
-
Returns:
|
|
474
|
-
ModelResponse: The response from the model.
|
|
957
|
+
ModelResponse: Iterator of parsed response data
|
|
475
958
|
"""
|
|
476
|
-
logger.debug("---------- Claude Response Start ----------")
|
|
477
|
-
self._log_messages(messages)
|
|
478
959
|
model_response = ModelResponse()
|
|
479
|
-
metrics_for_run = Metrics()
|
|
480
960
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
961
|
+
if isinstance(response, (ContentBlockStartEvent, BetaRawContentBlockStartEvent)):
|
|
962
|
+
if response.content_block.type == "redacted_reasoning_content":
|
|
963
|
+
model_response.redacted_reasoning_content = response.content_block.data
|
|
964
|
+
|
|
965
|
+
if isinstance(response, (ContentBlockDeltaEvent, BetaRawContentBlockDeltaEvent)):
|
|
966
|
+
# Handle text content
|
|
967
|
+
if response.delta.type == "text_delta":
|
|
968
|
+
model_response.content = response.delta.text
|
|
969
|
+
# Handle thinking content
|
|
970
|
+
elif response.delta.type == "thinking_delta":
|
|
971
|
+
model_response.reasoning_content = response.delta.thinking
|
|
972
|
+
elif response.delta.type == "signature_delta":
|
|
973
|
+
model_response.provider_data = {
|
|
974
|
+
"signature": response.delta.signature,
|
|
975
|
+
}
|
|
484
976
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
977
|
+
elif isinstance(response, (ContentBlockStopEvent, ParsedBetaContentBlockStopEvent)):
|
|
978
|
+
if response.content_block.type == "tool_use": # type: ignore
|
|
979
|
+
tool_use = response.content_block # type: ignore
|
|
980
|
+
tool_name = tool_use.name # type: ignore
|
|
981
|
+
tool_input = tool_use.input # type: ignore
|
|
489
982
|
|
|
490
|
-
|
|
491
|
-
|
|
983
|
+
function_def = {"name": tool_name}
|
|
984
|
+
if tool_input:
|
|
985
|
+
function_def["arguments"] = json.dumps(tool_input)
|
|
492
986
|
|
|
493
|
-
|
|
494
|
-
assistant_message.log()
|
|
495
|
-
metrics_for_run.log()
|
|
987
|
+
model_response.extra = model_response.extra or {}
|
|
496
988
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
989
|
+
model_response.tool_calls = [
|
|
990
|
+
{
|
|
991
|
+
"id": tool_use.id, # type: ignore
|
|
992
|
+
"type": "function",
|
|
993
|
+
"function": function_def,
|
|
994
|
+
}
|
|
995
|
+
]
|
|
996
|
+
|
|
997
|
+
# Capture citations from the final response and handle structured outputs
|
|
998
|
+
elif isinstance(response, (MessageStopEvent, ParsedBetaMessageStopEvent)):
|
|
999
|
+
# In streaming mode, content has already been emitted via ContentBlockDeltaEvent chunks
|
|
1000
|
+
# Setting content here would cause duplication since _populate_stream_data accumulates with +=
|
|
1001
|
+
# Keep content empty to avoid duplication
|
|
1002
|
+
model_response.content = ""
|
|
1003
|
+
model_response.citations = Citations(raw=[], urls=[], documents=[])
|
|
1004
|
+
|
|
1005
|
+
# Accumulate text content for structured output parsing (but don't set model_response.content)
|
|
1006
|
+
# The text was already streamed via ContentBlockDeltaEvent chunks
|
|
1007
|
+
accumulated_text = ""
|
|
1008
|
+
|
|
1009
|
+
for block in response.message.content: # type: ignore
|
|
1010
|
+
# Handle text blocks for structured output parsing
|
|
1011
|
+
if block.type == "text":
|
|
1012
|
+
accumulated_text += block.text # type: ignore
|
|
1013
|
+
|
|
1014
|
+
# Handle citations
|
|
1015
|
+
citations = getattr(block, "citations", None)
|
|
1016
|
+
if not citations:
|
|
1017
|
+
continue
|
|
1018
|
+
for citation in citations:
|
|
1019
|
+
model_response.citations.raw.append(citation.model_dump()) # type: ignore
|
|
1020
|
+
# Web search citations
|
|
1021
|
+
if isinstance(citation, CitationsWebSearchResultLocation):
|
|
1022
|
+
model_response.citations.urls.append(UrlCitation(url=citation.url, title=citation.cited_text)) # type: ignore
|
|
1023
|
+
# Document citations
|
|
1024
|
+
elif isinstance(citation, CitationPageLocation):
|
|
1025
|
+
model_response.citations.documents.append( # type: ignore
|
|
1026
|
+
DocumentCitation(document_title=citation.document_title, cited_text=citation.cited_text)
|
|
1027
|
+
)
|
|
505
1028
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
1029
|
+
# Handle structured outputs (JSON outputs) from accumulated text
|
|
1030
|
+
# Note: We parse from accumulated_text but don't set model_response.content to avoid duplication
|
|
1031
|
+
# The content was already streamed via ContentBlockDeltaEvent chunks
|
|
1032
|
+
if (
|
|
1033
|
+
response_format is not None
|
|
1034
|
+
and isinstance(response_format, type)
|
|
1035
|
+
and issubclass(response_format, BaseModel)
|
|
1036
|
+
):
|
|
1037
|
+
if accumulated_text:
|
|
1038
|
+
try:
|
|
1039
|
+
# Parse JSON from accumulated text content
|
|
1040
|
+
parsed_data = json.loads(accumulated_text)
|
|
1041
|
+
# Validate against Pydantic model
|
|
1042
|
+
model_response.parsed = response_format.model_validate(parsed_data)
|
|
1043
|
+
log_debug(f"Successfully parsed structured output from stream: {model_response.parsed}")
|
|
1044
|
+
except json.JSONDecodeError as e:
|
|
1045
|
+
log_warning(f"Failed to parse JSON from structured output in stream: {e}")
|
|
1046
|
+
except ValidationError as e:
|
|
1047
|
+
log_warning(f"Failed to validate structured output against schema in stream: {e}")
|
|
1048
|
+
except Exception as e:
|
|
1049
|
+
log_warning(f"Unexpected error parsing structured output in stream: {e}")
|
|
1050
|
+
|
|
1051
|
+
# Capture context management information if present
|
|
1052
|
+
if self.context_management is not None and hasattr(response.message, "context_management"): # type: ignore
|
|
1053
|
+
context_mgmt = response.message.context_management # type: ignore
|
|
1054
|
+
if context_mgmt is not None:
|
|
1055
|
+
model_response.provider_data = model_response.provider_data or {}
|
|
1056
|
+
if hasattr(context_mgmt, "model_dump"):
|
|
1057
|
+
model_response.provider_data["context_management"] = context_mgmt.model_dump()
|
|
1058
|
+
else:
|
|
1059
|
+
model_response.provider_data["context_management"] = context_mgmt
|
|
1060
|
+
|
|
1061
|
+
if hasattr(response, "message") and hasattr(response.message, "usage") and response.message.usage is not None: # type: ignore
|
|
1062
|
+
model_response.response_usage = self._get_metrics(response.message.usage) # type: ignore
|
|
1063
|
+
|
|
1064
|
+
# Capture the Beta response
|
|
1065
|
+
try:
|
|
1066
|
+
if (
|
|
1067
|
+
isinstance(response, BetaRawContentBlockDeltaEvent)
|
|
1068
|
+
and isinstance(response.delta, BetaTextDelta)
|
|
1069
|
+
and response.delta.text is not None
|
|
1070
|
+
):
|
|
1071
|
+
model_response.content = response.delta.text
|
|
1072
|
+
except Exception as e:
|
|
1073
|
+
log_error(f"Error parsing Beta response: {e}")
|
|
509
1074
|
|
|
510
|
-
logger.debug("---------- Claude Response End ----------")
|
|
511
1075
|
return model_response
|
|
512
1076
|
|
|
513
|
-
def
|
|
514
|
-
self,
|
|
515
|
-
assistant_message: Message,
|
|
516
|
-
messages: List[Message],
|
|
517
|
-
tool_ids: List[str],
|
|
518
|
-
) -> Iterator[ModelResponse]:
|
|
1077
|
+
def _get_metrics(self, response_usage: Union[Usage, MessageDeltaUsage, BetaUsage]) -> Metrics:
|
|
519
1078
|
"""
|
|
520
|
-
Parse
|
|
1079
|
+
Parse the given Anthropic-specific usage into an Agno Metrics object.
|
|
521
1080
|
|
|
522
1081
|
Args:
|
|
523
|
-
|
|
524
|
-
messages (List[Message]): The list of conversation messages.
|
|
525
|
-
tool_ids (List[str]): The list of tool IDs.
|
|
526
|
-
|
|
527
|
-
Yields:
|
|
528
|
-
Iterator[ModelResponse]: Yields model responses during function execution.
|
|
529
|
-
"""
|
|
530
|
-
if assistant_message.tool_calls is not None and len(assistant_message.tool_calls) > 0:
|
|
531
|
-
yield ModelResponse(content="\n\n")
|
|
532
|
-
function_calls_to_run = self._get_function_calls_to_run(assistant_message, messages)
|
|
533
|
-
function_call_results: List[Message] = []
|
|
534
|
-
|
|
535
|
-
if self.show_tool_calls:
|
|
536
|
-
if len(function_calls_to_run) == 1:
|
|
537
|
-
yield ModelResponse(content=f" - Running: {function_calls_to_run[0].get_call_str()}\n\n")
|
|
538
|
-
elif len(function_calls_to_run) > 1:
|
|
539
|
-
yield ModelResponse(content="Running:")
|
|
540
|
-
for _f in function_calls_to_run:
|
|
541
|
-
yield ModelResponse(content=f"\n - {_f.get_call_str()}")
|
|
542
|
-
yield ModelResponse(content="\n\n")
|
|
543
|
-
|
|
544
|
-
for intermediate_model_response in self.run_function_calls(
|
|
545
|
-
function_calls=function_calls_to_run, function_call_results=function_call_results
|
|
546
|
-
):
|
|
547
|
-
yield intermediate_model_response
|
|
1082
|
+
response_usage: Usage data from Anthropic
|
|
548
1083
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
logger.debug("---------- Claude Response Start ----------")
|
|
553
|
-
self._log_messages(messages)
|
|
554
|
-
message_data = MessageData()
|
|
1084
|
+
Returns:
|
|
1085
|
+
Metrics: Parsed metrics data
|
|
1086
|
+
"""
|
|
555
1087
|
metrics = Metrics()
|
|
556
1088
|
|
|
557
|
-
|
|
558
|
-
metrics.
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
tool_use = delta.content_block
|
|
573
|
-
tool_name = tool_use.name
|
|
574
|
-
tool_input = tool_use.input
|
|
575
|
-
message_data.tool_ids.append(tool_use.id)
|
|
576
|
-
|
|
577
|
-
function_def = {"name": tool_name}
|
|
578
|
-
if tool_input:
|
|
579
|
-
function_def["arguments"] = json.dumps(tool_input)
|
|
580
|
-
message_data.tool_calls.append(
|
|
581
|
-
{
|
|
582
|
-
"id": tool_use.id,
|
|
583
|
-
"type": "function",
|
|
584
|
-
"function": function_def,
|
|
585
|
-
}
|
|
586
|
-
)
|
|
587
|
-
message_data.response_block.append(delta.content_block)
|
|
588
|
-
|
|
589
|
-
if isinstance(delta, MessageStopEvent):
|
|
590
|
-
message_data.response_usage = delta.message.usage
|
|
591
|
-
yield ModelResponse(content="\n\n")
|
|
592
|
-
|
|
593
|
-
metrics.stop_response_timer()
|
|
594
|
-
|
|
595
|
-
# -*- Create assistant message
|
|
596
|
-
assistant_message = Message(
|
|
597
|
-
role="assistant",
|
|
598
|
-
content=message_data.response_content,
|
|
599
|
-
)
|
|
600
|
-
|
|
601
|
-
# -*- Update assistant message if tool calls are present
|
|
602
|
-
if len(message_data.tool_calls) > 0:
|
|
603
|
-
assistant_message.tool_calls = message_data.tool_calls
|
|
604
|
-
|
|
605
|
-
# -*- Update usage metrics
|
|
606
|
-
self.update_usage_metrics(assistant_message, message_data.response_usage, metrics)
|
|
607
|
-
|
|
608
|
-
# -*- Add assistant message to messages
|
|
609
|
-
messages.append(assistant_message)
|
|
610
|
-
|
|
611
|
-
# -*- Log response and metrics
|
|
612
|
-
assistant_message.log()
|
|
613
|
-
metrics.log()
|
|
614
|
-
|
|
615
|
-
if assistant_message.tool_calls is not None and len(assistant_message.tool_calls) > 0:
|
|
616
|
-
yield from self.handle_stream_tool_calls(assistant_message, messages, message_data.tool_ids)
|
|
617
|
-
yield from self.response_stream(messages=messages)
|
|
618
|
-
logger.debug("---------- Claude Response End ----------")
|
|
619
|
-
|
|
620
|
-
def get_tool_call_prompt(self) -> Optional[str]:
|
|
621
|
-
if self._functions is not None and len(self._functions) > 0:
|
|
622
|
-
tool_call_prompt = "Do not reflect on the quality of the returned search results in your response"
|
|
623
|
-
return tool_call_prompt
|
|
624
|
-
return None
|
|
625
|
-
|
|
626
|
-
def get_system_message_for_model(self) -> Optional[str]:
|
|
627
|
-
return self.get_tool_call_prompt()
|
|
628
|
-
|
|
629
|
-
async def ainvoke(self, *args, **kwargs) -> Any:
|
|
630
|
-
raise NotImplementedError(f"Async not supported on {self.name}.")
|
|
631
|
-
|
|
632
|
-
async def ainvoke_stream(self, *args, **kwargs) -> Any:
|
|
633
|
-
raise NotImplementedError(f"Async not supported on {self.name}.")
|
|
634
|
-
|
|
635
|
-
async def aresponse(self, messages: List[Message]) -> ModelResponse:
|
|
636
|
-
raise NotImplementedError(f"Async not supported on {self.name}.")
|
|
637
|
-
|
|
638
|
-
async def aresponse_stream(self, messages: List[Message]) -> ModelResponse:
|
|
639
|
-
raise NotImplementedError(f"Async not supported on {self.name}.")
|
|
1089
|
+
metrics.input_tokens = response_usage.input_tokens or 0
|
|
1090
|
+
metrics.output_tokens = response_usage.output_tokens or 0
|
|
1091
|
+
metrics.total_tokens = metrics.input_tokens + metrics.output_tokens
|
|
1092
|
+
metrics.cache_read_tokens = response_usage.cache_read_input_tokens or 0
|
|
1093
|
+
metrics.cache_write_tokens = response_usage.cache_creation_input_tokens or 0
|
|
1094
|
+
|
|
1095
|
+
# Anthropic-specific additional fields
|
|
1096
|
+
if response_usage.server_tool_use:
|
|
1097
|
+
metrics.provider_metrics = {"server_tool_use": response_usage.server_tool_use.model_dump()}
|
|
1098
|
+
if isinstance(response_usage, Usage):
|
|
1099
|
+
if response_usage.service_tier:
|
|
1100
|
+
metrics.provider_metrics = metrics.provider_metrics or {}
|
|
1101
|
+
metrics.provider_metrics["service_tier"] = response_usage.service_tier
|
|
1102
|
+
|
|
1103
|
+
return metrics
|