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/tools/function.py
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
from
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import partial
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Type, TypeVar, get_type_hints
|
|
2
5
|
|
|
3
6
|
from docstring_parser import parse
|
|
7
|
+
from packaging.version import Version
|
|
4
8
|
from pydantic import BaseModel, Field, validate_call
|
|
5
9
|
|
|
6
10
|
from agno.exceptions import AgentRunException
|
|
7
|
-
from agno.
|
|
11
|
+
from agno.media import Audio, File, Image, Video
|
|
12
|
+
from agno.run import RunContext
|
|
13
|
+
from agno.utils.log import log_debug, log_error, log_exception, log_warning
|
|
8
14
|
|
|
9
15
|
T = TypeVar("T")
|
|
10
16
|
|
|
@@ -12,22 +18,50 @@ T = TypeVar("T")
|
|
|
12
18
|
def get_entrypoint_docstring(entrypoint: Callable) -> str:
|
|
13
19
|
from inspect import getdoc
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
if isinstance(entrypoint, partial):
|
|
22
|
+
return str(entrypoint)
|
|
23
|
+
|
|
24
|
+
docstring = getdoc(entrypoint)
|
|
25
|
+
if not docstring:
|
|
17
26
|
return ""
|
|
18
27
|
|
|
19
|
-
|
|
28
|
+
parsed_doc = parse(docstring)
|
|
20
29
|
|
|
21
30
|
# Combine short and long descriptions
|
|
22
31
|
lines = []
|
|
23
|
-
if
|
|
24
|
-
lines.append(
|
|
25
|
-
if
|
|
26
|
-
lines.extend(
|
|
32
|
+
if parsed_doc.short_description:
|
|
33
|
+
lines.append(parsed_doc.short_description)
|
|
34
|
+
if parsed_doc.long_description:
|
|
35
|
+
lines.extend(parsed_doc.long_description.split("\n"))
|
|
27
36
|
|
|
28
37
|
return "\n".join(lines)
|
|
29
38
|
|
|
30
39
|
|
|
40
|
+
@dataclass
|
|
41
|
+
class UserInputField:
|
|
42
|
+
name: str
|
|
43
|
+
field_type: Type
|
|
44
|
+
description: Optional[str] = None
|
|
45
|
+
value: Optional[Any] = None
|
|
46
|
+
|
|
47
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
48
|
+
return {
|
|
49
|
+
"name": self.name,
|
|
50
|
+
"field_type": str(self.field_type.__name__),
|
|
51
|
+
"description": self.description,
|
|
52
|
+
"value": self.value,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_dict(cls, data: Dict[str, Any]) -> "UserInputField":
|
|
57
|
+
return cls(
|
|
58
|
+
name=data["name"],
|
|
59
|
+
field_type=eval(data["field_type"]), # Convert string type name to actual type
|
|
60
|
+
description=data["description"],
|
|
61
|
+
value=data["value"],
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
31
65
|
class Function(BaseModel):
|
|
32
66
|
"""Model for storing functions that can be called by an agent."""
|
|
33
67
|
|
|
@@ -44,10 +78,14 @@ class Function(BaseModel):
|
|
|
44
78
|
)
|
|
45
79
|
strict: Optional[bool] = None
|
|
46
80
|
|
|
81
|
+
instructions: Optional[str] = None
|
|
82
|
+
# If True, add instructions to the Agent's system message
|
|
83
|
+
add_instructions: bool = True
|
|
84
|
+
|
|
47
85
|
# The function to be called.
|
|
48
86
|
entrypoint: Optional[Callable] = None
|
|
49
|
-
# If True, the
|
|
50
|
-
|
|
87
|
+
# If True, the entrypoint processing is skipped and the Function is used as is.
|
|
88
|
+
skip_entrypoint_processing: bool = False
|
|
51
89
|
# If True, the function call will show the result along with sending it to the model.
|
|
52
90
|
show_result: bool = False
|
|
53
91
|
# If True, the agent will stop after the function call.
|
|
@@ -59,37 +97,148 @@ class Function(BaseModel):
|
|
|
59
97
|
# If defined, can accept the FunctionCall instance as a parameter.
|
|
60
98
|
post_hook: Optional[Callable] = None
|
|
61
99
|
|
|
100
|
+
# A list of hooks to run around tool calls.
|
|
101
|
+
tool_hooks: Optional[List[Callable]] = None
|
|
102
|
+
|
|
103
|
+
# If True, the function will require confirmation before execution
|
|
104
|
+
requires_confirmation: Optional[bool] = None
|
|
105
|
+
|
|
106
|
+
# If True, the function will require user input before execution
|
|
107
|
+
requires_user_input: Optional[bool] = None
|
|
108
|
+
# List of fields that the user will provide as input and that should be ignored by the agent (empty list means all fields are provided by the user)
|
|
109
|
+
user_input_fields: Optional[List[str]] = None
|
|
110
|
+
# This is set during parsing, not by the user
|
|
111
|
+
user_input_schema: Optional[List[UserInputField]] = None
|
|
112
|
+
|
|
113
|
+
# If True, the function will be executed outside the agent's control.
|
|
114
|
+
external_execution: Optional[bool] = None
|
|
115
|
+
|
|
116
|
+
# Caching configuration
|
|
117
|
+
cache_results: bool = False
|
|
118
|
+
cache_dir: Optional[str] = None
|
|
119
|
+
cache_ttl: int = 3600
|
|
120
|
+
|
|
62
121
|
# --*-- FOR INTERNAL USE ONLY --*--
|
|
63
122
|
# The agent that the function is associated with
|
|
64
123
|
_agent: Optional[Any] = None
|
|
124
|
+
# The team that the function is associated with
|
|
125
|
+
_team: Optional[Any] = None
|
|
126
|
+
# The run context that the function is associated with
|
|
127
|
+
_run_context: Optional[RunContext] = None
|
|
128
|
+
# The session state that the function is associated with
|
|
129
|
+
_session_state: Optional[Dict[str, Any]] = None
|
|
130
|
+
# The dependencies that the function is associated with
|
|
131
|
+
_dependencies: Optional[Dict[str, Any]] = None
|
|
132
|
+
|
|
133
|
+
# Media context that the function is associated with
|
|
134
|
+
_images: Optional[Sequence[Image]] = None
|
|
135
|
+
_videos: Optional[Sequence[Video]] = None
|
|
136
|
+
_audios: Optional[Sequence[Audio]] = None
|
|
137
|
+
_files: Optional[Sequence[File]] = None
|
|
65
138
|
|
|
66
139
|
def to_dict(self) -> Dict[str, Any]:
|
|
67
|
-
return self.model_dump(
|
|
140
|
+
return self.model_dump(
|
|
141
|
+
exclude_none=True,
|
|
142
|
+
include={"name", "description", "parameters", "strict", "requires_confirmation", "external_execution"},
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def model_copy(self, *, deep: bool = False) -> "Function":
|
|
146
|
+
"""
|
|
147
|
+
Override model_copy to handle callable fields that can't be deep copied (pickled).
|
|
148
|
+
Callables should always be shallow copied (referenced), not deep copied.
|
|
149
|
+
"""
|
|
150
|
+
# For deep copy, we need to handle callable fields specially
|
|
151
|
+
if deep:
|
|
152
|
+
# Fields that should NOT be deep copied (callables and complex objects)
|
|
153
|
+
shallow_fields = {
|
|
154
|
+
"entrypoint",
|
|
155
|
+
"pre_hook",
|
|
156
|
+
"post_hook",
|
|
157
|
+
"tool_hooks",
|
|
158
|
+
"_agent",
|
|
159
|
+
"_team",
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# Create a copy with shallow references to callable fields
|
|
163
|
+
copied_data = {}
|
|
164
|
+
for field_name, field_value in self.__dict__.items():
|
|
165
|
+
if field_name in shallow_fields:
|
|
166
|
+
# Shallow copy - just reference the same object
|
|
167
|
+
copied_data[field_name] = field_value
|
|
168
|
+
elif field_name == "parameters":
|
|
169
|
+
# Deep copy the parameters dict
|
|
170
|
+
from copy import deepcopy
|
|
171
|
+
|
|
172
|
+
copied_data[field_name] = deepcopy(field_value)
|
|
173
|
+
else:
|
|
174
|
+
# For simple types, just copy the value
|
|
175
|
+
copied_data[field_name] = field_value
|
|
176
|
+
|
|
177
|
+
# Create new instance with copied data
|
|
178
|
+
new_instance = self.__class__.model_construct(**copied_data)
|
|
179
|
+
|
|
180
|
+
return new_instance
|
|
181
|
+
else:
|
|
182
|
+
# For shallow copy, use the default Pydantic behavior
|
|
183
|
+
return super().model_copy(deep=False)
|
|
68
184
|
|
|
69
185
|
@classmethod
|
|
70
|
-
def from_callable(cls, c: Callable, strict: bool = False) -> "Function":
|
|
186
|
+
def from_callable(cls, c: Callable, name: Optional[str] = None, strict: bool = False) -> "Function":
|
|
71
187
|
from inspect import getdoc, signature
|
|
72
188
|
|
|
73
189
|
from agno.utils.json_schema import get_json_schema
|
|
74
190
|
|
|
75
|
-
function_name = c.__name__
|
|
191
|
+
function_name = name or c.__name__
|
|
76
192
|
parameters = {"type": "object", "properties": {}, "required": []}
|
|
77
193
|
try:
|
|
78
194
|
sig = signature(c)
|
|
79
195
|
type_hints = get_type_hints(c)
|
|
80
196
|
|
|
81
197
|
# If function has an the agent argument, remove the agent parameter from the type hints
|
|
82
|
-
if "agent" in sig.parameters:
|
|
198
|
+
if "agent" in sig.parameters and "agent" in type_hints:
|
|
83
199
|
del type_hints["agent"]
|
|
84
|
-
|
|
200
|
+
if "team" in sig.parameters and "team" in type_hints:
|
|
201
|
+
del type_hints["team"]
|
|
202
|
+
if "run_context" in sig.parameters and "run_context" in type_hints:
|
|
203
|
+
del type_hints["run_context"]
|
|
204
|
+
if "session_state" in sig.parameters and "session_state" in type_hints:
|
|
205
|
+
del type_hints["session_state"]
|
|
206
|
+
if "dependencies" in sig.parameters and "dependencies" in type_hints:
|
|
207
|
+
del type_hints["dependencies"]
|
|
208
|
+
|
|
209
|
+
# Remove media parameters from type hints as they are injected automatically
|
|
210
|
+
if "images" in sig.parameters and "images" in type_hints:
|
|
211
|
+
del type_hints["images"]
|
|
212
|
+
if "videos" in sig.parameters and "videos" in type_hints:
|
|
213
|
+
del type_hints["videos"]
|
|
214
|
+
if "audios" in sig.parameters and "audios" in type_hints:
|
|
215
|
+
del type_hints["audios"]
|
|
216
|
+
if "files" in sig.parameters and "files" in type_hints:
|
|
217
|
+
del type_hints["files"]
|
|
218
|
+
# log_info(f"Type hints for {function_name}: {type_hints}")
|
|
85
219
|
|
|
86
220
|
# Filter out return type and only process parameters
|
|
87
221
|
param_type_hints = {
|
|
88
|
-
name: type_hints.get(name)
|
|
222
|
+
name: type_hints.get(name)
|
|
223
|
+
for name in sig.parameters
|
|
224
|
+
if name != "return"
|
|
225
|
+
and name
|
|
226
|
+
not in [
|
|
227
|
+
"agent",
|
|
228
|
+
"team",
|
|
229
|
+
"run_context",
|
|
230
|
+
"session_state",
|
|
231
|
+
"dependencies",
|
|
232
|
+
"self",
|
|
233
|
+
"images",
|
|
234
|
+
"videos",
|
|
235
|
+
"audios",
|
|
236
|
+
"files",
|
|
237
|
+
]
|
|
89
238
|
}
|
|
90
239
|
|
|
91
240
|
# Parse docstring for parameters
|
|
92
|
-
param_descriptions = {}
|
|
241
|
+
param_descriptions: Dict[str, Any] = {}
|
|
93
242
|
if docstring := getdoc(c):
|
|
94
243
|
parsed_doc = parse(docstring)
|
|
95
244
|
param_docs = parsed_doc.params
|
|
@@ -98,8 +247,10 @@ class Function(BaseModel):
|
|
|
98
247
|
for param in param_docs:
|
|
99
248
|
param_name = param.arg_name
|
|
100
249
|
param_type = param.type_name
|
|
101
|
-
|
|
102
|
-
|
|
250
|
+
if param_type is None:
|
|
251
|
+
param_descriptions[param_name] = param.description
|
|
252
|
+
else:
|
|
253
|
+
param_descriptions[param_name] = f"({param_type}) {param.description}"
|
|
103
254
|
|
|
104
255
|
# Get JSON schema for parameters only
|
|
105
256
|
parameters = get_json_schema(
|
|
@@ -109,24 +260,55 @@ class Function(BaseModel):
|
|
|
109
260
|
# If strict=True mark all fields as required
|
|
110
261
|
# See: https://platform.openai.com/docs/guides/structured-outputs/supported-schemas#all-fields-must-be-required
|
|
111
262
|
if strict:
|
|
112
|
-
parameters["required"] = [
|
|
263
|
+
parameters["required"] = [
|
|
264
|
+
name
|
|
265
|
+
for name in parameters["properties"]
|
|
266
|
+
if name
|
|
267
|
+
not in [
|
|
268
|
+
"agent",
|
|
269
|
+
"team",
|
|
270
|
+
"run_context",
|
|
271
|
+
"session_state",
|
|
272
|
+
"dependencies",
|
|
273
|
+
"self",
|
|
274
|
+
"images",
|
|
275
|
+
"videos",
|
|
276
|
+
"audios",
|
|
277
|
+
"files",
|
|
278
|
+
]
|
|
279
|
+
]
|
|
113
280
|
else:
|
|
114
|
-
# Mark a field as required if it has no default value
|
|
281
|
+
# Mark a field as required if it has no default value (this would include optional fields)
|
|
115
282
|
parameters["required"] = [
|
|
116
283
|
name
|
|
117
284
|
for name, param in sig.parameters.items()
|
|
118
|
-
if param.default == param.empty
|
|
285
|
+
if param.default == param.empty
|
|
286
|
+
and name
|
|
287
|
+
not in [
|
|
288
|
+
"agent",
|
|
289
|
+
"team",
|
|
290
|
+
"run_context",
|
|
291
|
+
"session_state",
|
|
292
|
+
"dependencies",
|
|
293
|
+
"self",
|
|
294
|
+
"images",
|
|
295
|
+
"videos",
|
|
296
|
+
"audios",
|
|
297
|
+
"files",
|
|
298
|
+
]
|
|
119
299
|
]
|
|
120
300
|
|
|
121
|
-
#
|
|
301
|
+
# log_debug(f"JSON schema for {function_name}: {parameters}")
|
|
122
302
|
except Exception as e:
|
|
123
|
-
|
|
303
|
+
log_warning(f"Could not parse args for {function_name}: {e}", exc_info=True)
|
|
304
|
+
|
|
305
|
+
entrypoint = cls._wrap_callable(c)
|
|
124
306
|
|
|
125
307
|
return cls(
|
|
126
308
|
name=function_name,
|
|
127
309
|
description=get_entrypoint_docstring(entrypoint=c),
|
|
128
310
|
parameters=parameters,
|
|
129
|
-
entrypoint=
|
|
311
|
+
entrypoint=entrypoint,
|
|
130
312
|
)
|
|
131
313
|
|
|
132
314
|
def process_entrypoint(self, strict: bool = False):
|
|
@@ -135,6 +317,11 @@ class Function(BaseModel):
|
|
|
135
317
|
|
|
136
318
|
from agno.utils.json_schema import get_json_schema
|
|
137
319
|
|
|
320
|
+
if self.skip_entrypoint_processing:
|
|
321
|
+
if strict:
|
|
322
|
+
self.process_schema_for_strict()
|
|
323
|
+
return
|
|
324
|
+
|
|
138
325
|
if self.entrypoint is None:
|
|
139
326
|
return
|
|
140
327
|
|
|
@@ -145,22 +332,60 @@ class Function(BaseModel):
|
|
|
145
332
|
if self.parameters != parameters:
|
|
146
333
|
params_set_by_user = True
|
|
147
334
|
|
|
335
|
+
if self.requires_user_input:
|
|
336
|
+
self.user_input_schema = self.user_input_schema or []
|
|
337
|
+
|
|
148
338
|
try:
|
|
149
339
|
sig = signature(self.entrypoint)
|
|
150
340
|
type_hints = get_type_hints(self.entrypoint)
|
|
151
341
|
|
|
152
342
|
# If function has an the agent argument, remove the agent parameter from the type hints
|
|
153
|
-
if "agent" in sig.parameters:
|
|
343
|
+
if "agent" in sig.parameters and "agent" in type_hints:
|
|
154
344
|
del type_hints["agent"]
|
|
155
|
-
|
|
345
|
+
if "team" in sig.parameters and "team" in type_hints:
|
|
346
|
+
del type_hints["team"]
|
|
347
|
+
if "run_context" in sig.parameters and "run_context" in type_hints:
|
|
348
|
+
del type_hints["run_context"]
|
|
349
|
+
if "session_state" in sig.parameters and "session_state" in type_hints:
|
|
350
|
+
del type_hints["session_state"]
|
|
351
|
+
if "dependencies" in sig.parameters and "dependencies" in type_hints:
|
|
352
|
+
del type_hints["dependencies"]
|
|
353
|
+
if "images" in sig.parameters and "images" in type_hints:
|
|
354
|
+
del type_hints["images"]
|
|
355
|
+
if "videos" in sig.parameters and "videos" in type_hints:
|
|
356
|
+
del type_hints["videos"]
|
|
357
|
+
if "audios" in sig.parameters and "audios" in type_hints:
|
|
358
|
+
del type_hints["audios"]
|
|
359
|
+
if "files" in sig.parameters and "files" in type_hints:
|
|
360
|
+
del type_hints["files"]
|
|
361
|
+
# log_info(f"Type hints for {self.name}: {type_hints}")
|
|
156
362
|
|
|
157
363
|
# Filter out return type and only process parameters
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
364
|
+
excluded_params = [
|
|
365
|
+
"return",
|
|
366
|
+
"agent",
|
|
367
|
+
"team",
|
|
368
|
+
"run_context",
|
|
369
|
+
"session_state",
|
|
370
|
+
"dependencies",
|
|
371
|
+
"self",
|
|
372
|
+
"images",
|
|
373
|
+
"videos",
|
|
374
|
+
"audios",
|
|
375
|
+
"files",
|
|
376
|
+
]
|
|
377
|
+
if self.requires_user_input and self.user_input_fields:
|
|
378
|
+
if len(self.user_input_fields) == 0:
|
|
379
|
+
excluded_params.extend(list(type_hints.keys()))
|
|
380
|
+
else:
|
|
381
|
+
excluded_params.extend(self.user_input_fields)
|
|
382
|
+
|
|
383
|
+
# Get filtered list of parameter types
|
|
384
|
+
param_type_hints = {name: type_hints.get(name) for name in sig.parameters if name not in excluded_params}
|
|
161
385
|
|
|
162
386
|
# Parse docstring for parameters
|
|
163
387
|
param_descriptions = {}
|
|
388
|
+
param_descriptions_clean = {}
|
|
164
389
|
if docstring := getdoc(self.entrypoint):
|
|
165
390
|
parsed_doc = parse(docstring)
|
|
166
391
|
param_docs = parsed_doc.params
|
|
@@ -173,6 +398,18 @@ class Function(BaseModel):
|
|
|
173
398
|
# TODO: We should use type hints first, then map param types in docs to json schema types.
|
|
174
399
|
# This is temporary to not lose information
|
|
175
400
|
param_descriptions[param_name] = f"({param_type}) {param.description}"
|
|
401
|
+
param_descriptions_clean[param_name] = param.description
|
|
402
|
+
|
|
403
|
+
# If the function requires user input, we should set the user_input_schema to all parameters. The arguments provided by the model are filled in later.
|
|
404
|
+
if self.requires_user_input:
|
|
405
|
+
self.user_input_schema = [
|
|
406
|
+
UserInputField(
|
|
407
|
+
name=name,
|
|
408
|
+
description=param_descriptions_clean.get(name),
|
|
409
|
+
field_type=type_hints.get(name, str),
|
|
410
|
+
)
|
|
411
|
+
for name in sig.parameters
|
|
412
|
+
]
|
|
176
413
|
|
|
177
414
|
# Get JSON schema for parameters only
|
|
178
415
|
parameters = get_json_schema(
|
|
@@ -182,60 +419,229 @@ class Function(BaseModel):
|
|
|
182
419
|
# If strict=True mark all fields as required
|
|
183
420
|
# See: https://platform.openai.com/docs/guides/structured-outputs/supported-schemas#all-fields-must-be-required
|
|
184
421
|
if strict:
|
|
185
|
-
parameters["required"] = [name for name in parameters["properties"] if name
|
|
422
|
+
parameters["required"] = [name for name in parameters["properties"] if name not in excluded_params]
|
|
186
423
|
else:
|
|
187
424
|
# Mark a field as required if it has no default value
|
|
188
425
|
parameters["required"] = [
|
|
189
426
|
name
|
|
190
427
|
for name, param in sig.parameters.items()
|
|
191
|
-
if param.default == param.empty and name != "self" and name
|
|
428
|
+
if param.default == param.empty and name != "self" and name not in excluded_params
|
|
192
429
|
]
|
|
193
430
|
|
|
194
|
-
|
|
431
|
+
if params_set_by_user:
|
|
432
|
+
self.parameters["additionalProperties"] = False
|
|
433
|
+
if strict:
|
|
434
|
+
self.parameters["required"] = [
|
|
435
|
+
name for name in self.parameters["properties"] if name not in excluded_params
|
|
436
|
+
]
|
|
437
|
+
else:
|
|
438
|
+
# Mark a field as required if it has no default value
|
|
439
|
+
self.parameters["required"] = [
|
|
440
|
+
name
|
|
441
|
+
for name, param in sig.parameters.items()
|
|
442
|
+
if param.default == param.empty and name != "self" and name not in excluded_params
|
|
443
|
+
]
|
|
444
|
+
|
|
445
|
+
self.description = self.description or get_entrypoint_docstring(self.entrypoint)
|
|
446
|
+
|
|
447
|
+
# log_debug(f"JSON schema for {self.name}: {parameters}")
|
|
195
448
|
except Exception as e:
|
|
196
|
-
|
|
449
|
+
log_warning(f"Could not parse args for {self.name}: {e}", exc_info=True)
|
|
197
450
|
|
|
198
|
-
self.description = self.description or get_entrypoint_docstring(self.entrypoint)
|
|
199
451
|
if not params_set_by_user:
|
|
200
452
|
self.parameters = parameters
|
|
201
|
-
self.entrypoint = validate_call(self.entrypoint, config=dict(arbitrary_types_allowed=True)) # type: ignore
|
|
202
453
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if "list" in name or "dict" in name:
|
|
206
|
-
return name
|
|
207
|
-
else:
|
|
208
|
-
return t.__name__
|
|
454
|
+
if strict:
|
|
455
|
+
self.process_schema_for_strict()
|
|
209
456
|
|
|
210
|
-
|
|
211
|
-
|
|
457
|
+
try:
|
|
458
|
+
self.entrypoint = self._wrap_callable(self.entrypoint)
|
|
459
|
+
except Exception as e:
|
|
460
|
+
log_warning(f"Failed to add validate decorator to entrypoint: {e}")
|
|
212
461
|
|
|
213
|
-
|
|
214
|
-
|
|
462
|
+
@staticmethod
|
|
463
|
+
def _wrap_callable(func: Callable) -> Callable:
|
|
464
|
+
"""Wrap a callable with Pydantic's validate_call decorator, if relevant"""
|
|
465
|
+
from inspect import isasyncgenfunction, iscoroutinefunction, signature
|
|
215
466
|
|
|
216
|
-
|
|
217
|
-
return_type = type_hints.get("return", None)
|
|
218
|
-
returns = None
|
|
219
|
-
if return_type is not None:
|
|
220
|
-
returns = self.get_type_name(return_type)
|
|
467
|
+
pydantic_version = Version(version("pydantic"))
|
|
221
468
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
"arguments": self.parameters.get("properties", {}),
|
|
226
|
-
"returns": returns,
|
|
227
|
-
}
|
|
228
|
-
return function_info
|
|
469
|
+
# Don't wrap async generators validate_call
|
|
470
|
+
if isasyncgenfunction(func):
|
|
471
|
+
return func
|
|
229
472
|
|
|
230
|
-
|
|
231
|
-
|
|
473
|
+
# Don't wrap coroutines with validate_call if pydantic version is less than 2.10.0
|
|
474
|
+
if iscoroutinefunction(func) and pydantic_version < Version("2.10.0"):
|
|
475
|
+
log_debug(
|
|
476
|
+
f"Skipping validate_call for {func.__name__} because pydantic version is less than 2.10.0, please consider upgrading to pydantic 2.10.0 or higher"
|
|
477
|
+
)
|
|
478
|
+
return func
|
|
479
|
+
|
|
480
|
+
# Don't wrap callables that are already wrapped with validate_call
|
|
481
|
+
elif getattr(func, "_wrapped_for_validation", False):
|
|
482
|
+
return func
|
|
483
|
+
# Don't wrap functions with session_state parameter
|
|
484
|
+
# session_state needs to be passed by reference, not copied by pydantic's validation
|
|
485
|
+
elif "session_state" in signature(func).parameters:
|
|
486
|
+
return func
|
|
487
|
+
# Wrap the callable with validate_call
|
|
488
|
+
else:
|
|
489
|
+
wrapped = validate_call(func, config=dict(arbitrary_types_allowed=True)) # type: ignore
|
|
490
|
+
wrapped._wrapped_for_validation = True # Mark as wrapped to avoid infinite recursion
|
|
491
|
+
return wrapped
|
|
492
|
+
|
|
493
|
+
def process_schema_for_strict(self):
|
|
494
|
+
"""Process the schema to make it strict mode compliant."""
|
|
495
|
+
|
|
496
|
+
def make_nested_strict(schema):
|
|
497
|
+
"""Recursively ensure all object schemas have additionalProperties: false"""
|
|
498
|
+
if not isinstance(schema, dict):
|
|
499
|
+
return schema
|
|
500
|
+
|
|
501
|
+
# Make a copy to avoid modifying the original
|
|
502
|
+
result = schema.copy()
|
|
503
|
+
|
|
504
|
+
# If this is an object schema, ensure additionalProperties: false
|
|
505
|
+
if result.get("type") == "object" or "properties" in result:
|
|
506
|
+
result["additionalProperties"] = False
|
|
507
|
+
|
|
508
|
+
# If schema has no type but has other schema properties, give it a type
|
|
509
|
+
if "type" not in result:
|
|
510
|
+
if "properties" in result:
|
|
511
|
+
result["type"] = "object"
|
|
512
|
+
result["additionalProperties"] = False
|
|
513
|
+
elif result.get("title") and not any(
|
|
514
|
+
key in result for key in ["properties", "items", "anyOf", "oneOf", "allOf", "enum"]
|
|
515
|
+
):
|
|
516
|
+
result["type"] = "string"
|
|
517
|
+
|
|
518
|
+
# Recursively process nested schemas
|
|
519
|
+
for key, value in result.items():
|
|
520
|
+
if key == "properties" and isinstance(value, dict):
|
|
521
|
+
result[key] = {k: make_nested_strict(v) for k, v in value.items()}
|
|
522
|
+
elif key == "items" and isinstance(value, dict):
|
|
523
|
+
# This handles array items like List[KnowledgeFilter]
|
|
524
|
+
result[key] = make_nested_strict(value)
|
|
525
|
+
elif isinstance(value, dict):
|
|
526
|
+
result[key] = make_nested_strict(value)
|
|
527
|
+
|
|
528
|
+
return result
|
|
529
|
+
|
|
530
|
+
# Apply strict mode to the entire schema
|
|
531
|
+
self.parameters = make_nested_strict(self.parameters)
|
|
532
|
+
|
|
533
|
+
self.parameters["required"] = [
|
|
534
|
+
name
|
|
535
|
+
for name in self.parameters["properties"]
|
|
536
|
+
if name
|
|
537
|
+
not in [
|
|
538
|
+
"agent",
|
|
539
|
+
"team",
|
|
540
|
+
"run_context",
|
|
541
|
+
"session_state",
|
|
542
|
+
"dependencies",
|
|
543
|
+
"images",
|
|
544
|
+
"videos",
|
|
545
|
+
"audios",
|
|
546
|
+
"files",
|
|
547
|
+
"self",
|
|
548
|
+
]
|
|
549
|
+
]
|
|
550
|
+
|
|
551
|
+
def _get_cache_key(self, entrypoint_args: Dict[str, Any], call_args: Optional[Dict[str, Any]] = None) -> str:
|
|
552
|
+
"""Generate a cache key based on function name and arguments."""
|
|
553
|
+
import json
|
|
554
|
+
from hashlib import md5
|
|
555
|
+
|
|
556
|
+
copy_entrypoint_args = entrypoint_args.copy()
|
|
557
|
+
# Remove agent from entrypoint_args
|
|
558
|
+
if "agent" in copy_entrypoint_args:
|
|
559
|
+
del copy_entrypoint_args["agent"]
|
|
560
|
+
if "team" in copy_entrypoint_args:
|
|
561
|
+
del copy_entrypoint_args["team"]
|
|
562
|
+
if "run_context" in copy_entrypoint_args:
|
|
563
|
+
del copy_entrypoint_args["run_context"]
|
|
564
|
+
if "session_state" in copy_entrypoint_args:
|
|
565
|
+
del copy_entrypoint_args["session_state"]
|
|
566
|
+
if "dependencies" in copy_entrypoint_args:
|
|
567
|
+
del copy_entrypoint_args["dependencies"]
|
|
568
|
+
if "images" in copy_entrypoint_args:
|
|
569
|
+
del copy_entrypoint_args["images"]
|
|
570
|
+
if "videos" in copy_entrypoint_args:
|
|
571
|
+
del copy_entrypoint_args["videos"]
|
|
572
|
+
if "audios" in copy_entrypoint_args:
|
|
573
|
+
del copy_entrypoint_args["audios"]
|
|
574
|
+
if "files" in copy_entrypoint_args:
|
|
575
|
+
del copy_entrypoint_args["files"]
|
|
576
|
+
# Use json.dumps with sort_keys=True to ensure consistent ordering regardless of dict key order
|
|
577
|
+
args_str = json.dumps(copy_entrypoint_args, sort_keys=True, default=str)
|
|
578
|
+
|
|
579
|
+
kwargs_str = str(sorted((call_args or {}).items()))
|
|
580
|
+
key_str = f"{self.name}:{args_str}:{kwargs_str}"
|
|
581
|
+
return md5(key_str.encode()).hexdigest()
|
|
582
|
+
|
|
583
|
+
def _get_cache_file_path(self, cache_key: str) -> str:
|
|
584
|
+
"""Get the full path for the cache file."""
|
|
585
|
+
from pathlib import Path
|
|
586
|
+
from tempfile import gettempdir
|
|
587
|
+
|
|
588
|
+
base_cache_dir = self.cache_dir or Path(gettempdir()) / "agno_cache"
|
|
589
|
+
func_cache_dir = Path(base_cache_dir) / "functions" / self.name
|
|
590
|
+
func_cache_dir.mkdir(parents=True, exist_ok=True)
|
|
591
|
+
return str(func_cache_dir / f"{cache_key}.json")
|
|
592
|
+
|
|
593
|
+
def _get_cached_result(self, cache_file: str) -> Optional[Any]:
|
|
594
|
+
"""Retrieve cached result if valid."""
|
|
232
595
|
import json
|
|
596
|
+
from pathlib import Path
|
|
597
|
+
from time import time
|
|
598
|
+
|
|
599
|
+
cache_path = Path(cache_file)
|
|
600
|
+
if not cache_path.exists():
|
|
601
|
+
return None
|
|
602
|
+
|
|
603
|
+
try:
|
|
604
|
+
with cache_path.open("r") as f:
|
|
605
|
+
cache_data = json.load(f)
|
|
606
|
+
|
|
607
|
+
timestamp = cache_data.get("timestamp", 0)
|
|
608
|
+
result = cache_data.get("result")
|
|
609
|
+
|
|
610
|
+
if time() - timestamp <= self.cache_ttl:
|
|
611
|
+
return result
|
|
612
|
+
|
|
613
|
+
# Remove expired entry
|
|
614
|
+
cache_path.unlink()
|
|
615
|
+
except Exception as e:
|
|
616
|
+
log_error(f"Error reading cache: {e}")
|
|
233
617
|
|
|
234
|
-
function_info = self.get_definition_for_prompt_dict()
|
|
235
|
-
if function_info is not None:
|
|
236
|
-
return json.dumps(function_info, indent=2)
|
|
237
618
|
return None
|
|
238
619
|
|
|
620
|
+
def _save_to_cache(self, cache_file: str, result: Any):
|
|
621
|
+
"""Save result to cache."""
|
|
622
|
+
import json
|
|
623
|
+
from time import time
|
|
624
|
+
|
|
625
|
+
try:
|
|
626
|
+
with open(cache_file, "w") as f:
|
|
627
|
+
json.dump({"timestamp": time(), "result": result}, f)
|
|
628
|
+
except Exception as e:
|
|
629
|
+
log_error(f"Error writing cache: {e}")
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
class FunctionExecutionResult(BaseModel):
|
|
633
|
+
status: Literal["success", "failure"]
|
|
634
|
+
result: Optional[Any] = None
|
|
635
|
+
error: Optional[str] = None
|
|
636
|
+
|
|
637
|
+
updated_session_state: Optional[Dict[str, Any]] = None
|
|
638
|
+
|
|
639
|
+
# New fields for media artifacts
|
|
640
|
+
images: Optional[List[Image]] = None
|
|
641
|
+
videos: Optional[List[Video]] = None
|
|
642
|
+
audios: Optional[List[Audio]] = None
|
|
643
|
+
files: Optional[List[File]] = None
|
|
644
|
+
|
|
239
645
|
|
|
240
646
|
class FunctionCall(BaseModel):
|
|
241
647
|
"""Model for Function Calls"""
|
|
@@ -254,112 +660,544 @@ class FunctionCall(BaseModel):
|
|
|
254
660
|
|
|
255
661
|
def get_call_str(self) -> str:
|
|
256
662
|
"""Returns a string representation of the function call."""
|
|
663
|
+
import shutil
|
|
664
|
+
|
|
665
|
+
# Get terminal width, default to 80 if it can't be determined
|
|
666
|
+
term_width = shutil.get_terminal_size().columns or 80
|
|
667
|
+
max_arg_len = max(20, (term_width - len(self.function.name) - 4) // 2)
|
|
668
|
+
|
|
257
669
|
if self.arguments is None:
|
|
258
670
|
return f"{self.function.name}()"
|
|
259
671
|
|
|
260
672
|
trimmed_arguments = {}
|
|
261
673
|
for k, v in self.arguments.items():
|
|
262
|
-
if isinstance(v, str) and len(v) >
|
|
674
|
+
if isinstance(v, str) and len(str(v)) > max_arg_len:
|
|
263
675
|
trimmed_arguments[k] = "..."
|
|
264
676
|
else:
|
|
265
677
|
trimmed_arguments[k] = v
|
|
266
|
-
call_str = f"{self.function.name}({', '.join([f'{k}={v}' for k, v in trimmed_arguments.items()])})"
|
|
267
|
-
return call_str
|
|
268
|
-
|
|
269
|
-
def execute(self) -> bool:
|
|
270
|
-
"""Runs the function call.
|
|
271
678
|
|
|
272
|
-
|
|
273
|
-
The result of the function call is stored in self.result.
|
|
274
|
-
"""
|
|
275
|
-
from inspect import signature
|
|
679
|
+
call_str = f"{self.function.name}({', '.join([f'{k}={v}' for k, v in trimmed_arguments.items()])})"
|
|
276
680
|
|
|
277
|
-
|
|
278
|
-
|
|
681
|
+
# If call string is too long, truncate arguments
|
|
682
|
+
if len(call_str) > term_width:
|
|
683
|
+
return f"{self.function.name}(...)"
|
|
279
684
|
|
|
280
|
-
|
|
281
|
-
function_call_success = False
|
|
685
|
+
return call_str
|
|
282
686
|
|
|
283
|
-
|
|
687
|
+
def _handle_pre_hook(self):
|
|
688
|
+
"""Handles the pre-hook for the function call."""
|
|
284
689
|
if self.function.pre_hook is not None:
|
|
285
690
|
try:
|
|
691
|
+
from inspect import signature
|
|
692
|
+
|
|
286
693
|
pre_hook_args = {}
|
|
287
694
|
# Check if the pre-hook has and agent argument
|
|
288
695
|
if "agent" in signature(self.function.pre_hook).parameters:
|
|
289
696
|
pre_hook_args["agent"] = self.function._agent
|
|
697
|
+
# Check if the pre-hook has an team argument
|
|
698
|
+
if "team" in signature(self.function.pre_hook).parameters:
|
|
699
|
+
pre_hook_args["team"] = self.function._team
|
|
700
|
+
# Check if the pre-hook has an session_state argument
|
|
701
|
+
if "run_context" in signature(self.function.pre_hook).parameters:
|
|
702
|
+
pre_hook_args["run_context"] = self.function._run_context
|
|
703
|
+
# Check if the pre-hook has an session_state argument
|
|
704
|
+
if "session_state" in signature(self.function.pre_hook).parameters:
|
|
705
|
+
pre_hook_args["session_state"] = self.function._session_state
|
|
706
|
+
# Check if the pre-hook has an dependencies argument
|
|
707
|
+
if "dependencies" in signature(self.function.pre_hook).parameters:
|
|
708
|
+
pre_hook_args["dependencies"] = self.function._dependencies
|
|
290
709
|
# Check if the pre-hook has an fc argument
|
|
291
710
|
if "fc" in signature(self.function.pre_hook).parameters:
|
|
292
711
|
pre_hook_args["fc"] = self
|
|
293
712
|
self.function.pre_hook(**pre_hook_args)
|
|
294
713
|
except AgentRunException as e:
|
|
295
|
-
|
|
714
|
+
log_debug(f"{e.__class__.__name__}: {e}")
|
|
296
715
|
self.error = str(e)
|
|
297
716
|
raise
|
|
298
717
|
except Exception as e:
|
|
299
|
-
|
|
300
|
-
|
|
718
|
+
log_warning(f"Error in pre-hook callback: {e}")
|
|
719
|
+
log_exception(e)
|
|
301
720
|
|
|
302
|
-
|
|
303
|
-
|
|
721
|
+
def _handle_post_hook(self):
|
|
722
|
+
"""Handles the post-hook for the function call."""
|
|
723
|
+
if self.function.post_hook is not None:
|
|
304
724
|
try:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
725
|
+
from inspect import signature
|
|
726
|
+
|
|
727
|
+
post_hook_args = {}
|
|
728
|
+
# Check if the post-hook has and agent argument
|
|
729
|
+
if "agent" in signature(self.function.post_hook).parameters:
|
|
730
|
+
post_hook_args["agent"] = self.function._agent
|
|
731
|
+
# Check if the post-hook has an team argument
|
|
732
|
+
if "team" in signature(self.function.post_hook).parameters:
|
|
733
|
+
post_hook_args["team"] = self.function._team
|
|
734
|
+
# Check if the post-hook has an session_state argument
|
|
735
|
+
if "run_context" in signature(self.function.post_hook).parameters:
|
|
736
|
+
post_hook_args["run_context"] = self.function._run_context
|
|
737
|
+
# Check if the post-hook has an session_state argument
|
|
738
|
+
if "session_state" in signature(self.function.post_hook).parameters:
|
|
739
|
+
post_hook_args["session_state"] = self.function._session_state
|
|
740
|
+
# Check if the post-hook has an dependencies argument
|
|
741
|
+
if "dependencies" in signature(self.function.post_hook).parameters:
|
|
742
|
+
post_hook_args["dependencies"] = self.function._dependencies
|
|
743
|
+
# Check if the post-hook has an fc argument
|
|
744
|
+
if "fc" in signature(self.function.post_hook).parameters:
|
|
745
|
+
post_hook_args["fc"] = self
|
|
746
|
+
self.function.post_hook(**post_hook_args)
|
|
315
747
|
except AgentRunException as e:
|
|
316
|
-
|
|
748
|
+
log_debug(f"{e.__class__.__name__}: {e}")
|
|
317
749
|
self.error = str(e)
|
|
318
750
|
raise
|
|
319
751
|
except Exception as e:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
752
|
+
log_warning(f"Error in post-hook callback: {e}")
|
|
753
|
+
log_exception(e)
|
|
754
|
+
|
|
755
|
+
def _build_entrypoint_args(self) -> Dict[str, Any]:
|
|
756
|
+
"""Builds the arguments for the entrypoint."""
|
|
757
|
+
from inspect import signature
|
|
758
|
+
|
|
759
|
+
entrypoint_args = {}
|
|
760
|
+
# Check if the entrypoint has an agent argument
|
|
761
|
+
if "agent" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
762
|
+
entrypoint_args["agent"] = self.function._agent
|
|
763
|
+
# Check if the entrypoint has an team argument
|
|
764
|
+
if "team" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
765
|
+
entrypoint_args["team"] = self.function._team
|
|
766
|
+
# Check if the entrypoint has an run_context argument
|
|
767
|
+
if "run_context" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
768
|
+
entrypoint_args["run_context"] = self.function._run_context
|
|
769
|
+
# Check if the entrypoint has an session_state argument
|
|
770
|
+
if "session_state" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
771
|
+
entrypoint_args["session_state"] = self.function._session_state
|
|
772
|
+
# Check if the entrypoint has an dependencies argument
|
|
773
|
+
if "dependencies" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
774
|
+
entrypoint_args["dependencies"] = self.function._dependencies
|
|
775
|
+
# Check if the entrypoint has an fc argument
|
|
776
|
+
if "fc" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
777
|
+
entrypoint_args["fc"] = self
|
|
778
|
+
|
|
779
|
+
# Check if the entrypoint has media arguments
|
|
780
|
+
if "images" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
781
|
+
entrypoint_args["images"] = self.function._images
|
|
782
|
+
if "videos" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
783
|
+
entrypoint_args["videos"] = self.function._videos
|
|
784
|
+
if "audios" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
785
|
+
entrypoint_args["audios"] = self.function._audios
|
|
786
|
+
if "files" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
787
|
+
entrypoint_args["files"] = self.function._files
|
|
788
|
+
return entrypoint_args
|
|
789
|
+
|
|
790
|
+
def _build_hook_args(self, hook: Callable, name: str, func: Callable, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
791
|
+
"""Build the arguments for the hook."""
|
|
792
|
+
from inspect import signature
|
|
793
|
+
|
|
794
|
+
hook_args = {}
|
|
795
|
+
# Check if the hook has an agent argument
|
|
796
|
+
if "agent" in signature(hook).parameters:
|
|
797
|
+
hook_args["agent"] = self.function._agent
|
|
798
|
+
# Check if the hook has an team argument
|
|
799
|
+
if "team" in signature(hook).parameters:
|
|
800
|
+
hook_args["team"] = self.function._team
|
|
801
|
+
# Check if the hook has an run_context argument
|
|
802
|
+
if "run_context" in signature(hook).parameters:
|
|
803
|
+
hook_args["run_context"] = self.function._run_context
|
|
804
|
+
# Check if the hook has an session_state argument
|
|
805
|
+
if "session_state" in signature(hook).parameters:
|
|
806
|
+
hook_args["session_state"] = self.function._session_state
|
|
807
|
+
# Check if the hook has an dependencies argument
|
|
808
|
+
if "dependencies" in signature(hook).parameters:
|
|
809
|
+
hook_args["dependencies"] = self.function._dependencies
|
|
810
|
+
if "name" in signature(hook).parameters:
|
|
811
|
+
hook_args["name"] = name
|
|
812
|
+
if "function_name" in signature(hook).parameters:
|
|
813
|
+
hook_args["function_name"] = name
|
|
814
|
+
if "function" in signature(hook).parameters:
|
|
815
|
+
hook_args["function"] = func
|
|
816
|
+
if "func" in signature(hook).parameters:
|
|
817
|
+
hook_args["func"] = func
|
|
818
|
+
if "function_call" in signature(hook).parameters:
|
|
819
|
+
hook_args["function_call"] = func
|
|
820
|
+
if "args" in signature(hook).parameters:
|
|
821
|
+
hook_args["args"] = args
|
|
822
|
+
if "arguments" in signature(hook).parameters:
|
|
823
|
+
hook_args["arguments"] = args
|
|
824
|
+
return hook_args
|
|
825
|
+
|
|
826
|
+
def _build_nested_execution_chain(self, entrypoint_args: Dict[str, Any]):
|
|
827
|
+
"""Build a nested chain of hook executions with the entrypoint at the center.
|
|
828
|
+
|
|
829
|
+
This creates a chain where each hook wraps the next one, with the function call
|
|
830
|
+
at the innermost level. Returns bubble back up through each hook.
|
|
831
|
+
"""
|
|
832
|
+
from functools import reduce
|
|
833
|
+
from inspect import iscoroutinefunction
|
|
834
|
+
|
|
835
|
+
def execute_entrypoint(name, func, args):
|
|
836
|
+
"""Execute the entrypoint function."""
|
|
837
|
+
arguments = entrypoint_args.copy()
|
|
838
|
+
if self.arguments is not None:
|
|
839
|
+
arguments.update(self.arguments)
|
|
840
|
+
return self.function.entrypoint(**arguments) # type: ignore
|
|
841
|
+
|
|
842
|
+
# If no hooks, just return the entrypoint execution function
|
|
843
|
+
if not self.function.tool_hooks:
|
|
844
|
+
return execute_entrypoint
|
|
845
|
+
|
|
846
|
+
def create_hook_wrapper(inner_func, hook):
|
|
847
|
+
"""Create a nested wrapper for the hook."""
|
|
848
|
+
|
|
849
|
+
def wrapper(name, func, args):
|
|
850
|
+
# Pass the inner function as next_func to the hook
|
|
851
|
+
# The hook will call next_func to continue the chain
|
|
852
|
+
def next_func(**kwargs):
|
|
853
|
+
return inner_func(name, func, kwargs)
|
|
854
|
+
|
|
855
|
+
hook_args = self._build_hook_args(hook, name, next_func, args)
|
|
856
|
+
|
|
857
|
+
return hook(**hook_args)
|
|
858
|
+
|
|
859
|
+
return wrapper
|
|
860
|
+
|
|
861
|
+
# Remove coroutine hooks
|
|
862
|
+
final_hooks = []
|
|
863
|
+
for hook in self.function.tool_hooks:
|
|
864
|
+
if iscoroutinefunction(hook):
|
|
865
|
+
log_warning(f"Cannot use async hooks with sync function calls. Skipping hook: {hook.__name__}")
|
|
866
|
+
else:
|
|
867
|
+
final_hooks.append(hook)
|
|
868
|
+
|
|
869
|
+
# Build the chain from inside out - reverse the hooks to start from the innermost
|
|
870
|
+
hooks = list(reversed(final_hooks))
|
|
871
|
+
chain = reduce(create_hook_wrapper, hooks, execute_entrypoint)
|
|
872
|
+
return chain
|
|
873
|
+
|
|
874
|
+
def execute(self) -> FunctionExecutionResult:
|
|
875
|
+
"""Runs the function call."""
|
|
876
|
+
from inspect import isgenerator, isgeneratorfunction
|
|
877
|
+
|
|
878
|
+
if self.function.entrypoint is None:
|
|
879
|
+
return FunctionExecutionResult(status="failure", error="Entrypoint is not set")
|
|
880
|
+
|
|
881
|
+
log_debug(f"Running: {self.get_call_str()}")
|
|
882
|
+
|
|
883
|
+
# Execute pre-hook if it exists
|
|
884
|
+
self._handle_pre_hook()
|
|
885
|
+
|
|
886
|
+
entrypoint_args = self._build_entrypoint_args()
|
|
887
|
+
|
|
888
|
+
# Check cache if enabled and not a generator function
|
|
889
|
+
if self.function.cache_results and not isgeneratorfunction(self.function.entrypoint):
|
|
890
|
+
cache_key = self.function._get_cache_key(entrypoint_args, self.arguments)
|
|
891
|
+
cache_file = self.function._get_cache_file_path(cache_key)
|
|
892
|
+
cached_result = self.function._get_cached_result(cache_file)
|
|
893
|
+
|
|
894
|
+
if cached_result is not None:
|
|
895
|
+
log_debug(f"Cache hit for: {self.get_call_str()}")
|
|
896
|
+
self.result = cached_result
|
|
897
|
+
return FunctionExecutionResult(status="success", result=cached_result)
|
|
898
|
+
|
|
899
|
+
# Execute function
|
|
900
|
+
execution_result: FunctionExecutionResult
|
|
901
|
+
exception_to_raise = None
|
|
902
|
+
|
|
903
|
+
try:
|
|
904
|
+
# Build and execute the nested chain of hooks
|
|
905
|
+
if self.function.tool_hooks is not None:
|
|
906
|
+
execution_chain = self._build_nested_execution_chain(entrypoint_args=entrypoint_args)
|
|
907
|
+
result = execution_chain(self.function.name, self.function.entrypoint, self.arguments or {})
|
|
908
|
+
else:
|
|
909
|
+
result = self.function.entrypoint(**entrypoint_args, **self.arguments) # type: ignore
|
|
910
|
+
|
|
911
|
+
# Handle generator case
|
|
912
|
+
if isgenerator(result):
|
|
913
|
+
self.result = result # Store generator directly, can't cache
|
|
914
|
+
# For generators, don't capture updated_session_state yet -
|
|
915
|
+
# session_state is passed by reference, so mutations made during
|
|
916
|
+
# generator iteration are already reflected in the original dict.
|
|
917
|
+
# Returning None prevents stale state from being merged later.
|
|
918
|
+
execution_result = FunctionExecutionResult(
|
|
919
|
+
status="success", result=self.result, updated_session_state=None
|
|
920
|
+
)
|
|
921
|
+
else:
|
|
922
|
+
self.result = result
|
|
923
|
+
# Only cache non-generator results
|
|
924
|
+
if self.function.cache_results:
|
|
925
|
+
cache_key = self.function._get_cache_key(entrypoint_args, self.arguments)
|
|
926
|
+
cache_file = self.function._get_cache_file_path(cache_key)
|
|
927
|
+
self.function._save_to_cache(cache_file, self.result)
|
|
928
|
+
|
|
929
|
+
updated_session_state = None
|
|
930
|
+
if entrypoint_args.get("run_context") is not None:
|
|
931
|
+
run_context = entrypoint_args.get("run_context")
|
|
932
|
+
updated_session_state = (
|
|
933
|
+
run_context.session_state
|
|
934
|
+
if run_context is not None and run_context.session_state is not None
|
|
935
|
+
else None
|
|
936
|
+
)
|
|
937
|
+
else:
|
|
938
|
+
if self.function._session_state is not None:
|
|
939
|
+
updated_session_state = self.function._session_state
|
|
940
|
+
|
|
941
|
+
execution_result = FunctionExecutionResult(
|
|
942
|
+
status="success", result=self.result, updated_session_state=updated_session_state
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
except AgentRunException as e:
|
|
946
|
+
log_debug(f"{e.__class__.__name__}: {e}")
|
|
947
|
+
self.error = str(e)
|
|
948
|
+
exception_to_raise = e
|
|
949
|
+
execution_result = FunctionExecutionResult(status="failure", error=str(e))
|
|
950
|
+
except Exception as e:
|
|
951
|
+
log_warning(f"Could not run function {self.get_call_str()}")
|
|
952
|
+
log_exception(e)
|
|
953
|
+
self.error = str(e)
|
|
954
|
+
execution_result = FunctionExecutionResult(status="failure", error=str(e))
|
|
955
|
+
|
|
956
|
+
finally:
|
|
957
|
+
self._handle_post_hook()
|
|
958
|
+
|
|
959
|
+
if exception_to_raise is not None:
|
|
960
|
+
raise exception_to_raise
|
|
961
|
+
|
|
962
|
+
return execution_result
|
|
963
|
+
|
|
964
|
+
async def _handle_pre_hook_async(self):
|
|
965
|
+
"""Handles the async pre-hook for the function call."""
|
|
966
|
+
if self.function.pre_hook is not None:
|
|
325
967
|
try:
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
968
|
+
from inspect import signature
|
|
969
|
+
|
|
970
|
+
pre_hook_args = {}
|
|
971
|
+
# Check if the pre-hook has an agent argument
|
|
972
|
+
if "agent" in signature(self.function.pre_hook).parameters:
|
|
973
|
+
pre_hook_args["agent"] = self.function._agent
|
|
974
|
+
# Check if the pre-hook has an team argument
|
|
975
|
+
if "team" in signature(self.function.pre_hook).parameters:
|
|
976
|
+
pre_hook_args["team"] = self.function._team
|
|
977
|
+
# Check if the pre-hook has an run_context argument
|
|
978
|
+
if "run_context" in signature(self.function.pre_hook).parameters:
|
|
979
|
+
pre_hook_args["run_context"] = self.function._run_context
|
|
980
|
+
# Check if the pre-hook has an session_state argument
|
|
981
|
+
if "session_state" in signature(self.function.pre_hook).parameters:
|
|
982
|
+
pre_hook_args["session_state"] = self.function._session_state
|
|
983
|
+
# Check if the pre-hook has an dependencies argument
|
|
984
|
+
if "dependencies" in signature(self.function.pre_hook).parameters:
|
|
985
|
+
pre_hook_args["dependencies"] = self.function._dependencies
|
|
986
|
+
# Check if the pre-hook has an fc argument
|
|
987
|
+
if "fc" in signature(self.function.pre_hook).parameters:
|
|
988
|
+
pre_hook_args["fc"] = self
|
|
989
|
+
|
|
990
|
+
await self.function.pre_hook(**pre_hook_args)
|
|
336
991
|
except AgentRunException as e:
|
|
337
|
-
|
|
992
|
+
log_debug(f"{e.__class__.__name__}: {e}")
|
|
338
993
|
self.error = str(e)
|
|
339
994
|
raise
|
|
340
995
|
except Exception as e:
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
self.error = str(e)
|
|
344
|
-
return function_call_success
|
|
996
|
+
log_warning(f"Error in pre-hook callback: {e}")
|
|
997
|
+
log_exception(e)
|
|
345
998
|
|
|
346
|
-
|
|
999
|
+
async def _handle_post_hook_async(self):
|
|
1000
|
+
"""Handles the async post-hook for the function call."""
|
|
347
1001
|
if self.function.post_hook is not None:
|
|
348
1002
|
try:
|
|
1003
|
+
from inspect import signature
|
|
1004
|
+
|
|
349
1005
|
post_hook_args = {}
|
|
350
|
-
# Check if the post-hook has
|
|
1006
|
+
# Check if the post-hook has an agent argument
|
|
351
1007
|
if "agent" in signature(self.function.post_hook).parameters:
|
|
352
1008
|
post_hook_args["agent"] = self.function._agent
|
|
1009
|
+
# Check if the post-hook has an team argument
|
|
1010
|
+
if "team" in signature(self.function.post_hook).parameters:
|
|
1011
|
+
post_hook_args["team"] = self.function._team
|
|
1012
|
+
# Check if the post-hook has an run_context argument
|
|
1013
|
+
if "run_context" in signature(self.function.post_hook).parameters:
|
|
1014
|
+
post_hook_args["run_context"] = self.function._run_context
|
|
1015
|
+
# Check if the post-hook has an session_state argument
|
|
1016
|
+
if "session_state" in signature(self.function.post_hook).parameters:
|
|
1017
|
+
post_hook_args["session_state"] = self.function._session_state
|
|
1018
|
+
# Check if the post-hook has an dependencies argument
|
|
1019
|
+
if "dependencies" in signature(self.function.post_hook).parameters:
|
|
1020
|
+
post_hook_args["dependencies"] = self.function._dependencies
|
|
1021
|
+
|
|
353
1022
|
# Check if the post-hook has an fc argument
|
|
354
1023
|
if "fc" in signature(self.function.post_hook).parameters:
|
|
355
1024
|
post_hook_args["fc"] = self
|
|
356
|
-
|
|
1025
|
+
|
|
1026
|
+
await self.function.post_hook(**post_hook_args)
|
|
357
1027
|
except AgentRunException as e:
|
|
358
|
-
|
|
1028
|
+
log_debug(f"{e.__class__.__name__}: {e}")
|
|
359
1029
|
self.error = str(e)
|
|
360
1030
|
raise
|
|
361
1031
|
except Exception as e:
|
|
362
|
-
|
|
363
|
-
|
|
1032
|
+
log_warning(f"Error in post-hook callback: {e}")
|
|
1033
|
+
log_exception(e)
|
|
1034
|
+
|
|
1035
|
+
async def _build_nested_execution_chain_async(self, entrypoint_args: Dict[str, Any]):
|
|
1036
|
+
"""Build a nested chain of async hook executions with the entrypoint at the center.
|
|
1037
|
+
|
|
1038
|
+
Similar to _build_nested_execution_chain but for async execution.
|
|
1039
|
+
"""
|
|
1040
|
+
from functools import reduce
|
|
1041
|
+
from inspect import isasyncgenfunction, iscoroutinefunction
|
|
1042
|
+
|
|
1043
|
+
async def execute_entrypoint_async(name, func, args):
|
|
1044
|
+
"""Execute the entrypoint function asynchronously."""
|
|
1045
|
+
arguments = entrypoint_args.copy()
|
|
1046
|
+
if self.arguments is not None:
|
|
1047
|
+
arguments.update(self.arguments)
|
|
1048
|
+
|
|
1049
|
+
result = self.function.entrypoint(**arguments) # type: ignore
|
|
1050
|
+
if iscoroutinefunction(self.function.entrypoint) and not isasyncgenfunction(self.function.entrypoint):
|
|
1051
|
+
result = await result
|
|
1052
|
+
return result
|
|
1053
|
+
|
|
1054
|
+
def execute_entrypoint(name, func, args):
|
|
1055
|
+
"""Execute the entrypoint function synchronously."""
|
|
1056
|
+
arguments = entrypoint_args.copy()
|
|
1057
|
+
if self.arguments is not None:
|
|
1058
|
+
arguments.update(self.arguments)
|
|
1059
|
+
return self.function.entrypoint(**arguments) # type: ignore
|
|
1060
|
+
|
|
1061
|
+
# If no hooks, just return the entrypoint execution function
|
|
1062
|
+
if not self.function.tool_hooks:
|
|
1063
|
+
return execute_entrypoint
|
|
1064
|
+
|
|
1065
|
+
def create_hook_wrapper(inner_func, hook):
|
|
1066
|
+
"""Create a nested wrapper for the hook."""
|
|
1067
|
+
|
|
1068
|
+
async def wrapper(name, func, args):
|
|
1069
|
+
"""Create a nested wrapper for the hook."""
|
|
1070
|
+
|
|
1071
|
+
# Pass the inner function as next_func to the hook
|
|
1072
|
+
# The hook will call next_func to continue the chain
|
|
1073
|
+
async def next_func(**kwargs):
|
|
1074
|
+
if iscoroutinefunction(inner_func):
|
|
1075
|
+
return await inner_func(name, func, kwargs)
|
|
1076
|
+
else:
|
|
1077
|
+
return inner_func(name, func, kwargs)
|
|
1078
|
+
|
|
1079
|
+
hook_args = self._build_hook_args(hook, name, next_func, args)
|
|
1080
|
+
|
|
1081
|
+
if iscoroutinefunction(hook):
|
|
1082
|
+
return await hook(**hook_args)
|
|
1083
|
+
else:
|
|
1084
|
+
return hook(**hook_args)
|
|
1085
|
+
|
|
1086
|
+
return wrapper
|
|
1087
|
+
|
|
1088
|
+
# Build the chain from inside out - reverse the hooks to start from the innermost
|
|
1089
|
+
hooks = list(reversed(self.function.tool_hooks))
|
|
1090
|
+
|
|
1091
|
+
# Handle async and sync entrypoints
|
|
1092
|
+
if iscoroutinefunction(self.function.entrypoint):
|
|
1093
|
+
chain = reduce(create_hook_wrapper, hooks, execute_entrypoint_async)
|
|
1094
|
+
else:
|
|
1095
|
+
chain = reduce(create_hook_wrapper, hooks, execute_entrypoint)
|
|
1096
|
+
return chain
|
|
1097
|
+
|
|
1098
|
+
async def aexecute(self) -> FunctionExecutionResult:
|
|
1099
|
+
"""Runs the function call asynchronously."""
|
|
1100
|
+
from inspect import isasyncgen, isasyncgenfunction, iscoroutinefunction, isgenerator, isgeneratorfunction
|
|
1101
|
+
|
|
1102
|
+
if self.function.entrypoint is None:
|
|
1103
|
+
return FunctionExecutionResult(status="failure", error="Entrypoint is not set")
|
|
1104
|
+
|
|
1105
|
+
log_debug(f"Running: {self.get_call_str()}")
|
|
1106
|
+
|
|
1107
|
+
# Execute pre-hook if it exists
|
|
1108
|
+
if iscoroutinefunction(self.function.pre_hook):
|
|
1109
|
+
await self._handle_pre_hook_async()
|
|
1110
|
+
else:
|
|
1111
|
+
self._handle_pre_hook()
|
|
1112
|
+
|
|
1113
|
+
entrypoint_args = self._build_entrypoint_args()
|
|
1114
|
+
|
|
1115
|
+
# Check cache if enabled and not a generator function
|
|
1116
|
+
if self.function.cache_results and not (
|
|
1117
|
+
isasyncgenfunction(self.function.entrypoint) or isgeneratorfunction(self.function.entrypoint)
|
|
1118
|
+
):
|
|
1119
|
+
cache_key = self.function._get_cache_key(entrypoint_args, self.arguments)
|
|
1120
|
+
cache_file = self.function._get_cache_file_path(cache_key)
|
|
1121
|
+
cached_result = self.function._get_cached_result(cache_file)
|
|
1122
|
+
if cached_result is not None:
|
|
1123
|
+
log_debug(f"Cache hit for: {self.get_call_str()}")
|
|
1124
|
+
self.result = cached_result
|
|
1125
|
+
return FunctionExecutionResult(status="success", result=cached_result)
|
|
1126
|
+
|
|
1127
|
+
# Execute function
|
|
1128
|
+
execution_result: FunctionExecutionResult
|
|
1129
|
+
exception_to_raise = None
|
|
1130
|
+
|
|
1131
|
+
try:
|
|
1132
|
+
# Build and execute the nested chain of hooks
|
|
1133
|
+
if self.function.tool_hooks is not None:
|
|
1134
|
+
execution_chain = await self._build_nested_execution_chain_async(entrypoint_args)
|
|
1135
|
+
self.result = await execution_chain(self.function.name, self.function.entrypoint, self.arguments or {})
|
|
1136
|
+
else:
|
|
1137
|
+
if self.arguments is None or self.arguments == {}:
|
|
1138
|
+
result = self.function.entrypoint(**entrypoint_args)
|
|
1139
|
+
else:
|
|
1140
|
+
result = self.function.entrypoint(**entrypoint_args, **self.arguments)
|
|
1141
|
+
|
|
1142
|
+
if isasyncgenfunction(self.function.entrypoint):
|
|
1143
|
+
self.result = result # Store async generator directly
|
|
1144
|
+
else:
|
|
1145
|
+
self.result = await result
|
|
1146
|
+
|
|
1147
|
+
# Only cache if not a generator
|
|
1148
|
+
if self.function.cache_results and not (isgenerator(self.result) or isasyncgen(self.result)):
|
|
1149
|
+
cache_key = self.function._get_cache_key(entrypoint_args, self.arguments)
|
|
1150
|
+
cache_file = self.function._get_cache_file_path(cache_key)
|
|
1151
|
+
self.function._save_to_cache(cache_file, self.result)
|
|
1152
|
+
|
|
1153
|
+
# For generators, don't capture updated_session_state -
|
|
1154
|
+
# session_state is passed by reference, so mutations made during
|
|
1155
|
+
# generator iteration are already reflected in the original dict.
|
|
1156
|
+
# Returning None prevents stale state from being merged later.
|
|
1157
|
+
if isgenerator(self.result) or isasyncgen(self.result):
|
|
1158
|
+
updated_session_state = None
|
|
1159
|
+
else:
|
|
1160
|
+
updated_session_state = None
|
|
1161
|
+
if entrypoint_args.get("run_context") is not None:
|
|
1162
|
+
run_context = entrypoint_args.get("run_context")
|
|
1163
|
+
updated_session_state = (
|
|
1164
|
+
run_context.session_state
|
|
1165
|
+
if run_context is not None and run_context.session_state is not None
|
|
1166
|
+
else None
|
|
1167
|
+
)
|
|
1168
|
+
|
|
1169
|
+
execution_result = FunctionExecutionResult(
|
|
1170
|
+
status="success", result=self.result, updated_session_state=updated_session_state
|
|
1171
|
+
)
|
|
1172
|
+
|
|
1173
|
+
except AgentRunException as e:
|
|
1174
|
+
log_debug(f"{e.__class__.__name__}: {e}")
|
|
1175
|
+
self.error = str(e)
|
|
1176
|
+
exception_to_raise = e
|
|
1177
|
+
execution_result = FunctionExecutionResult(status="failure", error=str(e))
|
|
1178
|
+
except Exception as e:
|
|
1179
|
+
log_warning(f"Could not run function {self.get_call_str()}")
|
|
1180
|
+
log_exception(e)
|
|
1181
|
+
self.error = str(e)
|
|
1182
|
+
execution_result = FunctionExecutionResult(status="failure", error=str(e))
|
|
1183
|
+
|
|
1184
|
+
finally:
|
|
1185
|
+
if iscoroutinefunction(self.function.post_hook):
|
|
1186
|
+
await self._handle_post_hook_async()
|
|
1187
|
+
else:
|
|
1188
|
+
self._handle_post_hook()
|
|
1189
|
+
|
|
1190
|
+
if exception_to_raise is not None:
|
|
1191
|
+
raise exception_to_raise
|
|
1192
|
+
|
|
1193
|
+
return execution_result
|
|
1194
|
+
|
|
1195
|
+
|
|
1196
|
+
class ToolResult(BaseModel):
|
|
1197
|
+
"""Result from a tool that can include media artifacts."""
|
|
364
1198
|
|
|
365
|
-
|
|
1199
|
+
content: str
|
|
1200
|
+
images: Optional[List[Image]] = None
|
|
1201
|
+
videos: Optional[List[Video]] = None
|
|
1202
|
+
audios: Optional[List[Audio]] = None
|
|
1203
|
+
files: Optional[List[File]] = None
|