agno 2.0.1__py3-none-any.whl → 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agno/agent/agent.py +6015 -2823
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +385 -6
- agno/db/dynamo/dynamo.py +388 -81
- agno/db/dynamo/schemas.py +47 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +435 -64
- agno/db/firestore/schemas.py +11 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +384 -42
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +351 -66
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +339 -48
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +510 -37
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2036 -0
- agno/db/mongo/mongo.py +653 -76
- agno/db/mongo/schemas.py +13 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/mysql.py +687 -25
- agno/db/mysql/schemas.py +61 -37
- agno/db/mysql/utils.py +60 -2
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2001 -0
- agno/db/postgres/postgres.py +676 -57
- agno/db/postgres/schemas.py +43 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +344 -38
- agno/db/redis/schemas.py +18 -0
- agno/db/redis/utils.py +60 -2
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +26 -1
- agno/db/singlestore/singlestore.py +687 -53
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2371 -0
- agno/db/sqlite/schemas.py +24 -0
- agno/db/sqlite/sqlite.py +774 -85
- agno/db/sqlite/utils.py +168 -5
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1361 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +50 -22
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +68 -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/integrations/discord/client.py +1 -0
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/chunking/semantic.py +40 -8
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/aws_bedrock.py +9 -4
- agno/knowledge/embedder/azure_openai.py +54 -0
- agno/knowledge/embedder/base.py +2 -0
- agno/knowledge/embedder/cohere.py +184 -5
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/google.py +79 -1
- agno/knowledge/embedder/huggingface.py +9 -4
- agno/knowledge/embedder/jina.py +63 -0
- agno/knowledge/embedder/mistral.py +78 -11
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +13 -0
- agno/knowledge/embedder/openai.py +37 -65
- agno/knowledge/embedder/sentence_transformer.py +8 -4
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +594 -186
- agno/knowledge/reader/base.py +9 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
- agno/knowledge/reader/json_reader.py +6 -5
- agno/knowledge/reader/markdown_reader.py +13 -13
- agno/knowledge/reader/pdf_reader.py +43 -68
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +51 -6
- agno/knowledge/reader/s3_reader.py +3 -15
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +13 -13
- agno/knowledge/reader/web_search_reader.py +2 -43
- agno/knowledge/reader/website_reader.py +43 -25
- agno/knowledge/reranker/__init__.py +2 -8
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +72 -0
- agno/memory/manager.py +336 -82
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/anthropic/claude.py +183 -37
- agno/models/aws/bedrock.py +52 -112
- agno/models/aws/claude.py +33 -1
- agno/models/azure/ai_foundry.py +33 -15
- agno/models/azure/openai_chat.py +25 -8
- agno/models/base.py +999 -519
- agno/models/cerebras/cerebras.py +19 -13
- agno/models/cerebras/cerebras_openai.py +8 -5
- agno/models/cohere/chat.py +27 -1
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/google/gemini.py +103 -31
- agno/models/groq/groq.py +28 -11
- agno/models/huggingface/huggingface.py +2 -1
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/chat.py +18 -1
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/message.py +139 -0
- agno/models/meta/llama.py +27 -10
- agno/models/meta/llama_openai.py +5 -17
- agno/models/nebius/nebius.py +6 -6
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/ollama/chat.py +59 -5
- agno/models/openai/chat.py +69 -29
- agno/models/openai/responses.py +103 -106
- agno/models/openrouter/openrouter.py +41 -3
- agno/models/perplexity/perplexity.py +4 -5
- agno/models/portkey/portkey.py +3 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +77 -1
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/together.py +2 -2
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +2 -2
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +96 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +3 -2
- agno/os/app.py +543 -178
- agno/os/auth.py +24 -14
- agno/os/config.py +1 -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 +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/agui.py +23 -7
- agno/os/interfaces/agui/router.py +27 -3
- agno/os/interfaces/agui/utils.py +242 -142
- agno/os/interfaces/base.py +6 -2
- agno/os/interfaces/slack/router.py +81 -23
- agno/os/interfaces/slack/slack.py +29 -14
- agno/os/interfaces/whatsapp/router.py +11 -4
- agno/os/interfaces/whatsapp/whatsapp.py +14 -7
- agno/os/mcp.py +111 -54
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +556 -139
- agno/os/routers/evals/evals.py +71 -34
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/evals/utils.py +6 -5
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +185 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +158 -53
- agno/os/routers/memory/schemas.py +20 -16
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +499 -38
- agno/os/schema.py +308 -198
- agno/os/utils.py +401 -41
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +3 -1
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +2 -2
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +7 -2
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +248 -94
- agno/run/base.py +44 -5
- agno/run/team.py +238 -97
- agno/run/workflow.py +144 -33
- agno/session/agent.py +105 -89
- agno/session/summary.py +65 -25
- agno/session/team.py +176 -96
- agno/session/workflow.py +406 -40
- agno/team/team.py +3854 -1610
- agno/tools/dalle.py +2 -4
- agno/tools/decorator.py +4 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +14 -7
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +250 -30
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +270 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/knowledge.py +3 -3
- 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 +11 -17
- agno/tools/memori.py +1 -53
- agno/tools/memory.py +419 -0
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/scrapegraph.py +58 -31
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/slack.py +18 -3
- agno/tools/spider.py +2 -2
- agno/tools/tavily.py +146 -0
- agno/tools/whatsapp.py +1 -1
- agno/tools/workflow.py +278 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +27 -0
- agno/utils/common.py +90 -1
- agno/utils/events.py +217 -2
- agno/utils/gemini.py +180 -22
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +111 -0
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +188 -10
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +60 -0
- agno/utils/models/claude.py +40 -11
- agno/utils/print_response/agent.py +105 -21
- agno/utils/print_response/team.py +103 -38
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/reasoning.py +22 -1
- agno/utils/serialize.py +32 -0
- agno/utils/streamlit.py +16 -10
- agno/utils/string.py +41 -0
- agno/utils/team.py +98 -9
- agno/utils/tools.py +1 -1
- agno/vectordb/base.py +23 -4
- agno/vectordb/cassandra/cassandra.py +65 -9
- agno/vectordb/chroma/chromadb.py +182 -38
- agno/vectordb/clickhouse/clickhousedb.py +64 -11
- agno/vectordb/couchbase/couchbase.py +105 -10
- agno/vectordb/lancedb/lance_db.py +124 -133
- agno/vectordb/langchaindb/langchaindb.py +25 -7
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +46 -7
- agno/vectordb/milvus/milvus.py +126 -9
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +112 -7
- agno/vectordb/pgvector/pgvector.py +142 -21
- agno/vectordb/pineconedb/pineconedb.py +80 -8
- agno/vectordb/qdrant/qdrant.py +125 -39
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/singlestore/singlestore.py +111 -25
- agno/vectordb/surrealdb/surrealdb.py +31 -5
- agno/vectordb/upstashdb/upstashdb.py +76 -8
- agno/vectordb/weaviate/weaviate.py +86 -15
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +112 -18
- agno/workflow/loop.py +69 -10
- agno/workflow/parallel.py +266 -118
- agno/workflow/router.py +110 -17
- agno/workflow/step.py +638 -129
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +61 -23
- agno/workflow/workflow.py +2085 -272
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
- agno-2.3.0.dist-info/RECORD +577 -0
- agno/knowledge/reader/url_reader.py +0 -128
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -610
- agno/utils/models/aws_claude.py +0 -170
- agno-2.0.1.dist-info/RECORD +0 -515
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/tools/function.py
CHANGED
|
@@ -9,6 +9,7 @@ from pydantic import BaseModel, Field, validate_call
|
|
|
9
9
|
|
|
10
10
|
from agno.exceptions import AgentRunException
|
|
11
11
|
from agno.media import Audio, File, Image, Video
|
|
12
|
+
from agno.run import RunContext
|
|
12
13
|
from agno.utils.log import log_debug, log_error, log_exception, log_warning
|
|
13
14
|
|
|
14
15
|
T = TypeVar("T")
|
|
@@ -122,8 +123,12 @@ class Function(BaseModel):
|
|
|
122
123
|
_agent: Optional[Any] = None
|
|
123
124
|
# The team that the function is associated with
|
|
124
125
|
_team: Optional[Any] = None
|
|
126
|
+
# The run context that the function is associated with
|
|
127
|
+
_run_context: Optional[RunContext] = None
|
|
125
128
|
# The session state that the function is associated with
|
|
126
129
|
_session_state: Optional[Dict[str, Any]] = None
|
|
130
|
+
# The dependencies that the function is associated with
|
|
131
|
+
_dependencies: Optional[Dict[str, Any]] = None
|
|
127
132
|
|
|
128
133
|
# Media context that the function is associated with
|
|
129
134
|
_images: Optional[Sequence[Image]] = None
|
|
@@ -137,6 +142,46 @@ class Function(BaseModel):
|
|
|
137
142
|
include={"name", "description", "parameters", "strict", "requires_confirmation", "external_execution"},
|
|
138
143
|
)
|
|
139
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)
|
|
184
|
+
|
|
140
185
|
@classmethod
|
|
141
186
|
def from_callable(cls, c: Callable, name: Optional[str] = None, strict: bool = False) -> "Function":
|
|
142
187
|
from inspect import getdoc, signature
|
|
@@ -154,8 +199,13 @@ class Function(BaseModel):
|
|
|
154
199
|
del type_hints["agent"]
|
|
155
200
|
if "team" in sig.parameters and "team" in type_hints:
|
|
156
201
|
del type_hints["team"]
|
|
202
|
+
if "run_context" in sig.parameters and "run_context" in type_hints:
|
|
203
|
+
del type_hints["run_context"]
|
|
157
204
|
if "session_state" in sig.parameters and "session_state" in type_hints:
|
|
158
205
|
del type_hints["session_state"]
|
|
206
|
+
if "dependencies" in sig.parameters and "dependencies" in type_hints:
|
|
207
|
+
del type_hints["dependencies"]
|
|
208
|
+
|
|
159
209
|
# Remove media parameters from type hints as they are injected automatically
|
|
160
210
|
if "images" in sig.parameters and "images" in type_hints:
|
|
161
211
|
del type_hints["images"]
|
|
@@ -172,7 +222,19 @@ class Function(BaseModel):
|
|
|
172
222
|
name: type_hints.get(name)
|
|
173
223
|
for name in sig.parameters
|
|
174
224
|
if name != "return"
|
|
175
|
-
and name
|
|
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
|
+
]
|
|
176
238
|
}
|
|
177
239
|
|
|
178
240
|
# Parse docstring for parameters
|
|
@@ -201,7 +263,19 @@ class Function(BaseModel):
|
|
|
201
263
|
parameters["required"] = [
|
|
202
264
|
name
|
|
203
265
|
for name in parameters["properties"]
|
|
204
|
-
if name
|
|
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
|
+
]
|
|
205
279
|
]
|
|
206
280
|
else:
|
|
207
281
|
# Mark a field as required if it has no default value (this would include optional fields)
|
|
@@ -209,7 +283,19 @@ class Function(BaseModel):
|
|
|
209
283
|
name
|
|
210
284
|
for name, param in sig.parameters.items()
|
|
211
285
|
if param.default == param.empty
|
|
212
|
-
and name
|
|
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
|
+
]
|
|
213
299
|
]
|
|
214
300
|
|
|
215
301
|
# log_debug(f"JSON schema for {function_name}: {parameters}")
|
|
@@ -258,8 +344,12 @@ class Function(BaseModel):
|
|
|
258
344
|
del type_hints["agent"]
|
|
259
345
|
if "team" in sig.parameters and "team" in type_hints:
|
|
260
346
|
del type_hints["team"]
|
|
347
|
+
if "run_context" in sig.parameters and "run_context" in type_hints:
|
|
348
|
+
del type_hints["run_context"]
|
|
261
349
|
if "session_state" in sig.parameters and "session_state" in type_hints:
|
|
262
350
|
del type_hints["session_state"]
|
|
351
|
+
if "dependencies" in sig.parameters and "dependencies" in type_hints:
|
|
352
|
+
del type_hints["dependencies"]
|
|
263
353
|
if "images" in sig.parameters and "images" in type_hints:
|
|
264
354
|
del type_hints["images"]
|
|
265
355
|
if "videos" in sig.parameters and "videos" in type_hints:
|
|
@@ -275,7 +365,9 @@ class Function(BaseModel):
|
|
|
275
365
|
"return",
|
|
276
366
|
"agent",
|
|
277
367
|
"team",
|
|
368
|
+
"run_context",
|
|
278
369
|
"session_state",
|
|
370
|
+
"dependencies",
|
|
279
371
|
"self",
|
|
280
372
|
"images",
|
|
281
373
|
"videos",
|
|
@@ -359,6 +451,9 @@ class Function(BaseModel):
|
|
|
359
451
|
if not params_set_by_user:
|
|
360
452
|
self.parameters = parameters
|
|
361
453
|
|
|
454
|
+
if strict:
|
|
455
|
+
self.process_schema_for_strict()
|
|
456
|
+
|
|
362
457
|
try:
|
|
363
458
|
self.entrypoint = self._wrap_callable(self.entrypoint)
|
|
364
459
|
except Exception as e:
|
|
@@ -367,7 +462,7 @@ class Function(BaseModel):
|
|
|
367
462
|
@staticmethod
|
|
368
463
|
def _wrap_callable(func: Callable) -> Callable:
|
|
369
464
|
"""Wrap a callable with Pydantic's validate_call decorator, if relevant"""
|
|
370
|
-
from inspect import isasyncgenfunction, iscoroutinefunction
|
|
465
|
+
from inspect import isasyncgenfunction, iscoroutinefunction, signature
|
|
371
466
|
|
|
372
467
|
pydantic_version = Version(version("pydantic"))
|
|
373
468
|
|
|
@@ -385,6 +480,10 @@ class Function(BaseModel):
|
|
|
385
480
|
# Don't wrap callables that are already wrapped with validate_call
|
|
386
481
|
elif getattr(func, "_wrapped_for_validation", False):
|
|
387
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
|
|
388
487
|
# Wrap the callable with validate_call
|
|
389
488
|
else:
|
|
390
489
|
wrapped = validate_call(func, config=dict(arbitrary_types_allowed=True)) # type: ignore
|
|
@@ -392,15 +491,66 @@ class Function(BaseModel):
|
|
|
392
491
|
return wrapped
|
|
393
492
|
|
|
394
493
|
def process_schema_for_strict(self):
|
|
395
|
-
|
|
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
|
+
|
|
396
533
|
self.parameters["required"] = [
|
|
397
534
|
name
|
|
398
535
|
for name in self.parameters["properties"]
|
|
399
|
-
if name
|
|
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
|
+
]
|
|
400
549
|
]
|
|
401
550
|
|
|
402
551
|
def _get_cache_key(self, entrypoint_args: Dict[str, Any], call_args: Optional[Dict[str, Any]] = None) -> str:
|
|
403
552
|
"""Generate a cache key based on function name and arguments."""
|
|
553
|
+
import json
|
|
404
554
|
from hashlib import md5
|
|
405
555
|
|
|
406
556
|
copy_entrypoint_args = entrypoint_args.copy()
|
|
@@ -409,8 +559,12 @@ class Function(BaseModel):
|
|
|
409
559
|
del copy_entrypoint_args["agent"]
|
|
410
560
|
if "team" in copy_entrypoint_args:
|
|
411
561
|
del copy_entrypoint_args["team"]
|
|
562
|
+
if "run_context" in copy_entrypoint_args:
|
|
563
|
+
del copy_entrypoint_args["run_context"]
|
|
412
564
|
if "session_state" in copy_entrypoint_args:
|
|
413
565
|
del copy_entrypoint_args["session_state"]
|
|
566
|
+
if "dependencies" in copy_entrypoint_args:
|
|
567
|
+
del copy_entrypoint_args["dependencies"]
|
|
414
568
|
if "images" in copy_entrypoint_args:
|
|
415
569
|
del copy_entrypoint_args["images"]
|
|
416
570
|
if "videos" in copy_entrypoint_args:
|
|
@@ -419,7 +573,8 @@ class Function(BaseModel):
|
|
|
419
573
|
del copy_entrypoint_args["audios"]
|
|
420
574
|
if "files" in copy_entrypoint_args:
|
|
421
575
|
del copy_entrypoint_args["files"]
|
|
422
|
-
|
|
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)
|
|
423
578
|
|
|
424
579
|
kwargs_str = str(sorted((call_args or {}).items()))
|
|
425
580
|
key_str = f"{self.name}:{args_str}:{kwargs_str}"
|
|
@@ -485,6 +640,7 @@ class FunctionExecutionResult(BaseModel):
|
|
|
485
640
|
images: Optional[List[Image]] = None
|
|
486
641
|
videos: Optional[List[Video]] = None
|
|
487
642
|
audios: Optional[List[Audio]] = None
|
|
643
|
+
files: Optional[List[File]] = None
|
|
488
644
|
|
|
489
645
|
|
|
490
646
|
class FunctionCall(BaseModel):
|
|
@@ -542,8 +698,14 @@ class FunctionCall(BaseModel):
|
|
|
542
698
|
if "team" in signature(self.function.pre_hook).parameters:
|
|
543
699
|
pre_hook_args["team"] = self.function._team
|
|
544
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
|
|
545
704
|
if "session_state" in signature(self.function.pre_hook).parameters:
|
|
546
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
|
|
547
709
|
# Check if the pre-hook has an fc argument
|
|
548
710
|
if "fc" in signature(self.function.pre_hook).parameters:
|
|
549
711
|
pre_hook_args["fc"] = self
|
|
@@ -570,8 +732,14 @@ class FunctionCall(BaseModel):
|
|
|
570
732
|
if "team" in signature(self.function.post_hook).parameters:
|
|
571
733
|
post_hook_args["team"] = self.function._team
|
|
572
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
|
|
573
738
|
if "session_state" in signature(self.function.post_hook).parameters:
|
|
574
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
|
|
575
743
|
# Check if the post-hook has an fc argument
|
|
576
744
|
if "fc" in signature(self.function.post_hook).parameters:
|
|
577
745
|
post_hook_args["fc"] = self
|
|
@@ -595,9 +763,15 @@ class FunctionCall(BaseModel):
|
|
|
595
763
|
# Check if the entrypoint has an team argument
|
|
596
764
|
if "team" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
597
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
|
|
598
769
|
# Check if the entrypoint has an session_state argument
|
|
599
770
|
if "session_state" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
600
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
|
|
601
775
|
# Check if the entrypoint has an fc argument
|
|
602
776
|
if "fc" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
603
777
|
entrypoint_args["fc"] = self
|
|
@@ -611,7 +785,6 @@ class FunctionCall(BaseModel):
|
|
|
611
785
|
entrypoint_args["audios"] = self.function._audios
|
|
612
786
|
if "files" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
613
787
|
entrypoint_args["files"] = self.function._files
|
|
614
|
-
|
|
615
788
|
return entrypoint_args
|
|
616
789
|
|
|
617
790
|
def _build_hook_args(self, hook: Callable, name: str, func: Callable, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
@@ -625,10 +798,15 @@ class FunctionCall(BaseModel):
|
|
|
625
798
|
# Check if the hook has an team argument
|
|
626
799
|
if "team" in signature(hook).parameters:
|
|
627
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
|
|
628
804
|
# Check if the hook has an session_state argument
|
|
629
805
|
if "session_state" in signature(hook).parameters:
|
|
630
806
|
hook_args["session_state"] = self.function._session_state
|
|
631
|
-
|
|
807
|
+
# Check if the hook has an dependencies argument
|
|
808
|
+
if "dependencies" in signature(hook).parameters:
|
|
809
|
+
hook_args["dependencies"] = self.function._dependencies
|
|
632
810
|
if "name" in signature(hook).parameters:
|
|
633
811
|
hook_args["name"] = name
|
|
634
812
|
if "function_name" in signature(hook).parameters:
|
|
@@ -719,6 +897,9 @@ class FunctionCall(BaseModel):
|
|
|
719
897
|
return FunctionExecutionResult(status="success", result=cached_result)
|
|
720
898
|
|
|
721
899
|
# Execute function
|
|
900
|
+
execution_result = None
|
|
901
|
+
exception_to_raise = None
|
|
902
|
+
|
|
722
903
|
try:
|
|
723
904
|
# Build and execute the nested chain of hooks
|
|
724
905
|
if self.function.tool_hooks is not None:
|
|
@@ -728,8 +909,16 @@ class FunctionCall(BaseModel):
|
|
|
728
909
|
result = self.function.entrypoint(**entrypoint_args, **self.arguments) # type: ignore
|
|
729
910
|
|
|
730
911
|
updated_session_state = None
|
|
731
|
-
if entrypoint_args.get("
|
|
732
|
-
|
|
912
|
+
if entrypoint_args.get("run_context") is not None:
|
|
913
|
+
run_context = entrypoint_args.get("run_context")
|
|
914
|
+
updated_session_state = (
|
|
915
|
+
run_context.session_state
|
|
916
|
+
if run_context is not None and run_context.session_state is not None
|
|
917
|
+
else None
|
|
918
|
+
)
|
|
919
|
+
else:
|
|
920
|
+
if self.function._session_state is not None:
|
|
921
|
+
updated_session_state = self.function._session_state
|
|
733
922
|
|
|
734
923
|
# Handle generator case
|
|
735
924
|
if isgenerator(result):
|
|
@@ -742,22 +931,27 @@ class FunctionCall(BaseModel):
|
|
|
742
931
|
cache_file = self.function._get_cache_file_path(cache_key)
|
|
743
932
|
self.function._save_to_cache(cache_file, self.result)
|
|
744
933
|
|
|
934
|
+
execution_result = FunctionExecutionResult(
|
|
935
|
+
status="success", result=self.result, updated_session_state=updated_session_state
|
|
936
|
+
)
|
|
937
|
+
|
|
745
938
|
except AgentRunException as e:
|
|
746
939
|
log_debug(f"{e.__class__.__name__}: {e}")
|
|
747
940
|
self.error = str(e)
|
|
748
|
-
|
|
941
|
+
exception_to_raise = e
|
|
749
942
|
except Exception as e:
|
|
750
943
|
log_warning(f"Could not run function {self.get_call_str()}")
|
|
751
944
|
log_exception(e)
|
|
752
945
|
self.error = str(e)
|
|
753
|
-
|
|
946
|
+
execution_result = FunctionExecutionResult(status="failure", error=str(e))
|
|
947
|
+
|
|
948
|
+
finally:
|
|
949
|
+
self._handle_post_hook()
|
|
754
950
|
|
|
755
|
-
|
|
756
|
-
|
|
951
|
+
if exception_to_raise is not None:
|
|
952
|
+
raise exception_to_raise
|
|
757
953
|
|
|
758
|
-
|
|
759
|
-
status="success", result=self.result, updated_session_state=updated_session_state
|
|
760
|
-
)
|
|
954
|
+
return execution_result # type: ignore[return-value]
|
|
761
955
|
|
|
762
956
|
async def _handle_pre_hook_async(self):
|
|
763
957
|
"""Handles the async pre-hook for the function call."""
|
|
@@ -772,9 +966,15 @@ class FunctionCall(BaseModel):
|
|
|
772
966
|
# Check if the pre-hook has an team argument
|
|
773
967
|
if "team" in signature(self.function.pre_hook).parameters:
|
|
774
968
|
pre_hook_args["team"] = self.function._team
|
|
969
|
+
# Check if the pre-hook has an run_context argument
|
|
970
|
+
if "run_context" in signature(self.function.pre_hook).parameters:
|
|
971
|
+
pre_hook_args["run_context"] = self.function._run_context
|
|
775
972
|
# Check if the pre-hook has an session_state argument
|
|
776
973
|
if "session_state" in signature(self.function.pre_hook).parameters:
|
|
777
974
|
pre_hook_args["session_state"] = self.function._session_state
|
|
975
|
+
# Check if the pre-hook has an dependencies argument
|
|
976
|
+
if "dependencies" in signature(self.function.pre_hook).parameters:
|
|
977
|
+
pre_hook_args["dependencies"] = self.function._dependencies
|
|
778
978
|
# Check if the pre-hook has an fc argument
|
|
779
979
|
if "fc" in signature(self.function.pre_hook).parameters:
|
|
780
980
|
pre_hook_args["fc"] = self
|
|
@@ -801,9 +1001,15 @@ class FunctionCall(BaseModel):
|
|
|
801
1001
|
# Check if the post-hook has an team argument
|
|
802
1002
|
if "team" in signature(self.function.post_hook).parameters:
|
|
803
1003
|
post_hook_args["team"] = self.function._team
|
|
1004
|
+
# Check if the post-hook has an run_context argument
|
|
1005
|
+
if "run_context" in signature(self.function.post_hook).parameters:
|
|
1006
|
+
post_hook_args["run_context"] = self.function._run_context
|
|
804
1007
|
# Check if the post-hook has an session_state argument
|
|
805
1008
|
if "session_state" in signature(self.function.post_hook).parameters:
|
|
806
1009
|
post_hook_args["session_state"] = self.function._session_state
|
|
1010
|
+
# Check if the post-hook has an dependencies argument
|
|
1011
|
+
if "dependencies" in signature(self.function.post_hook).parameters:
|
|
1012
|
+
post_hook_args["dependencies"] = self.function._dependencies
|
|
807
1013
|
|
|
808
1014
|
# Check if the post-hook has an fc argument
|
|
809
1015
|
if "fc" in signature(self.function.post_hook).parameters:
|
|
@@ -911,6 +1117,9 @@ class FunctionCall(BaseModel):
|
|
|
911
1117
|
return FunctionExecutionResult(status="success", result=cached_result)
|
|
912
1118
|
|
|
913
1119
|
# Execute function
|
|
1120
|
+
execution_result = None
|
|
1121
|
+
exception_to_raise = None
|
|
1122
|
+
|
|
914
1123
|
try:
|
|
915
1124
|
# Build and execute the nested chain of hooks
|
|
916
1125
|
if self.function.tool_hooks is not None:
|
|
@@ -934,28 +1143,38 @@ class FunctionCall(BaseModel):
|
|
|
934
1143
|
self.function._save_to_cache(cache_file, self.result)
|
|
935
1144
|
|
|
936
1145
|
updated_session_state = None
|
|
937
|
-
if entrypoint_args.get("
|
|
938
|
-
|
|
1146
|
+
if entrypoint_args.get("run_context") is not None:
|
|
1147
|
+
run_context = entrypoint_args.get("run_context")
|
|
1148
|
+
updated_session_state = (
|
|
1149
|
+
run_context.session_state
|
|
1150
|
+
if run_context is not None and run_context.session_state is not None
|
|
1151
|
+
else None
|
|
1152
|
+
)
|
|
1153
|
+
|
|
1154
|
+
execution_result = FunctionExecutionResult(
|
|
1155
|
+
status="success", result=self.result, updated_session_state=updated_session_state
|
|
1156
|
+
)
|
|
939
1157
|
|
|
940
1158
|
except AgentRunException as e:
|
|
941
1159
|
log_debug(f"{e.__class__.__name__}: {e}")
|
|
942
1160
|
self.error = str(e)
|
|
943
|
-
|
|
1161
|
+
exception_to_raise = e
|
|
944
1162
|
except Exception as e:
|
|
945
1163
|
log_warning(f"Could not run function {self.get_call_str()}")
|
|
946
1164
|
log_exception(e)
|
|
947
1165
|
self.error = str(e)
|
|
948
|
-
|
|
1166
|
+
execution_result = FunctionExecutionResult(status="failure", error=str(e))
|
|
949
1167
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1168
|
+
finally:
|
|
1169
|
+
if iscoroutinefunction(self.function.post_hook):
|
|
1170
|
+
await self._handle_post_hook_async()
|
|
1171
|
+
else:
|
|
1172
|
+
self._handle_post_hook()
|
|
955
1173
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1174
|
+
if exception_to_raise is not None:
|
|
1175
|
+
raise exception_to_raise
|
|
1176
|
+
|
|
1177
|
+
return execution_result # type: ignore[return-value]
|
|
959
1178
|
|
|
960
1179
|
|
|
961
1180
|
class ToolResult(BaseModel):
|
|
@@ -965,3 +1184,4 @@ class ToolResult(BaseModel):
|
|
|
965
1184
|
images: Optional[List[Image]] = None
|
|
966
1185
|
videos: Optional[List[Video]] = None
|
|
967
1186
|
audios: Optional[List[Audio]] = None
|
|
1187
|
+
files: Optional[List[File]] = None
|