agno 2.0.0rc2__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.
Files changed (331) hide show
  1. agno/agent/agent.py +6009 -2874
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +595 -187
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +3 -0
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +339 -266
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +1011 -566
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +110 -37
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +143 -4
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +60 -6
  142. agno/models/openai/chat.py +102 -43
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +81 -5
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -175
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +266 -112
  205. agno/run/base.py +53 -24
  206. agno/run/team.py +252 -111
  207. agno/run/workflow.py +156 -45
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1692
  213. agno/tools/brightdata.py +3 -3
  214. agno/tools/cartesia.py +3 -5
  215. agno/tools/dalle.py +9 -8
  216. agno/tools/decorator.py +4 -2
  217. agno/tools/desi_vocal.py +2 -2
  218. agno/tools/duckduckgo.py +15 -11
  219. agno/tools/e2b.py +20 -13
  220. agno/tools/eleven_labs.py +26 -28
  221. agno/tools/exa.py +21 -16
  222. agno/tools/fal.py +4 -4
  223. agno/tools/file.py +153 -23
  224. agno/tools/file_generation.py +350 -0
  225. agno/tools/firecrawl.py +4 -4
  226. agno/tools/function.py +257 -37
  227. agno/tools/giphy.py +2 -2
  228. agno/tools/gmail.py +238 -14
  229. agno/tools/google_drive.py +270 -0
  230. agno/tools/googlecalendar.py +36 -8
  231. agno/tools/googlesheets.py +20 -5
  232. agno/tools/jira.py +20 -0
  233. agno/tools/knowledge.py +3 -3
  234. agno/tools/lumalab.py +3 -3
  235. agno/tools/mcp/__init__.py +10 -0
  236. agno/tools/mcp/mcp.py +331 -0
  237. agno/tools/mcp/multi_mcp.py +347 -0
  238. agno/tools/mcp/params.py +24 -0
  239. agno/tools/mcp_toolbox.py +284 -0
  240. agno/tools/mem0.py +11 -17
  241. agno/tools/memori.py +1 -53
  242. agno/tools/memory.py +419 -0
  243. agno/tools/models/azure_openai.py +2 -2
  244. agno/tools/models/gemini.py +3 -3
  245. agno/tools/models/groq.py +3 -5
  246. agno/tools/models/nebius.py +7 -7
  247. agno/tools/models_labs.py +25 -15
  248. agno/tools/notion.py +204 -0
  249. agno/tools/openai.py +4 -9
  250. agno/tools/opencv.py +3 -3
  251. agno/tools/parallel.py +314 -0
  252. agno/tools/replicate.py +7 -7
  253. agno/tools/scrapegraph.py +58 -31
  254. agno/tools/searxng.py +2 -2
  255. agno/tools/serper.py +2 -2
  256. agno/tools/slack.py +18 -3
  257. agno/tools/spider.py +2 -2
  258. agno/tools/tavily.py +146 -0
  259. agno/tools/whatsapp.py +1 -1
  260. agno/tools/workflow.py +278 -0
  261. agno/tools/yfinance.py +12 -11
  262. agno/utils/agent.py +820 -0
  263. agno/utils/audio.py +27 -0
  264. agno/utils/common.py +90 -1
  265. agno/utils/events.py +222 -7
  266. agno/utils/gemini.py +181 -23
  267. agno/utils/hooks.py +57 -0
  268. agno/utils/http.py +111 -0
  269. agno/utils/knowledge.py +12 -5
  270. agno/utils/log.py +1 -0
  271. agno/utils/mcp.py +95 -5
  272. agno/utils/media.py +188 -10
  273. agno/utils/merge_dict.py +22 -1
  274. agno/utils/message.py +60 -0
  275. agno/utils/models/claude.py +40 -11
  276. agno/utils/models/cohere.py +1 -1
  277. agno/utils/models/watsonx.py +1 -1
  278. agno/utils/openai.py +1 -1
  279. agno/utils/print_response/agent.py +105 -21
  280. agno/utils/print_response/team.py +103 -38
  281. agno/utils/print_response/workflow.py +251 -34
  282. agno/utils/reasoning.py +22 -1
  283. agno/utils/serialize.py +32 -0
  284. agno/utils/streamlit.py +16 -10
  285. agno/utils/string.py +41 -0
  286. agno/utils/team.py +98 -9
  287. agno/utils/tools.py +1 -1
  288. agno/vectordb/base.py +23 -4
  289. agno/vectordb/cassandra/cassandra.py +65 -9
  290. agno/vectordb/chroma/chromadb.py +182 -38
  291. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  292. agno/vectordb/couchbase/couchbase.py +105 -10
  293. agno/vectordb/lancedb/lance_db.py +183 -135
  294. agno/vectordb/langchaindb/langchaindb.py +25 -7
  295. agno/vectordb/lightrag/lightrag.py +17 -3
  296. agno/vectordb/llamaindex/__init__.py +3 -0
  297. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  298. agno/vectordb/milvus/milvus.py +126 -9
  299. agno/vectordb/mongodb/__init__.py +7 -1
  300. agno/vectordb/mongodb/mongodb.py +112 -7
  301. agno/vectordb/pgvector/pgvector.py +142 -21
  302. agno/vectordb/pineconedb/pineconedb.py +80 -8
  303. agno/vectordb/qdrant/qdrant.py +125 -39
  304. agno/vectordb/redis/__init__.py +9 -0
  305. agno/vectordb/redis/redisdb.py +694 -0
  306. agno/vectordb/singlestore/singlestore.py +111 -25
  307. agno/vectordb/surrealdb/surrealdb.py +31 -5
  308. agno/vectordb/upstashdb/upstashdb.py +76 -8
  309. agno/vectordb/weaviate/weaviate.py +86 -15
  310. agno/workflow/__init__.py +2 -0
  311. agno/workflow/agent.py +299 -0
  312. agno/workflow/condition.py +112 -18
  313. agno/workflow/loop.py +69 -10
  314. agno/workflow/parallel.py +266 -118
  315. agno/workflow/router.py +110 -17
  316. agno/workflow/step.py +645 -136
  317. agno/workflow/steps.py +65 -6
  318. agno/workflow/types.py +71 -33
  319. agno/workflow/workflow.py +2113 -300
  320. agno-2.3.0.dist-info/METADATA +618 -0
  321. agno-2.3.0.dist-info/RECORD +577 -0
  322. agno-2.3.0.dist-info/licenses/LICENSE +201 -0
  323. agno/knowledge/reader/url_reader.py +0 -128
  324. agno/tools/googlesearch.py +0 -98
  325. agno/tools/mcp.py +0 -610
  326. agno/utils/models/aws_claude.py +0 -170
  327. agno-2.0.0rc2.dist-info/METADATA +0 -355
  328. agno-2.0.0rc2.dist-info/RECORD +0 -515
  329. agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
  330. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  331. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/tools/function.py CHANGED
@@ -8,7 +8,8 @@ from packaging.version import Version
8
8
  from pydantic import BaseModel, Field, validate_call
9
9
 
10
10
  from agno.exceptions import AgentRunException
11
- from agno.media import Audio, AudioArtifact, File, Image, ImageArtifact, Video, VideoArtifact
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 not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files"]
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 not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files"]
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 not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files"]
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
- self.parameters["additionalProperties"] = False
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 not in ["agent", "team", "session_state", "images", "videos", "audios", "files", "self"]
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
- args_str = str(copy_entrypoint_args)
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}"
@@ -482,9 +637,10 @@ class FunctionExecutionResult(BaseModel):
482
637
  updated_session_state: Optional[Dict[str, Any]] = None
483
638
 
484
639
  # New fields for media artifacts
485
- images: Optional[List[ImageArtifact]] = None
486
- videos: Optional[List[VideoArtifact]] = None
487
- audios: Optional[List[AudioArtifact]] = None
640
+ images: Optional[List[Image]] = None
641
+ videos: Optional[List[Video]] = None
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("session_state") is not None:
732
- updated_session_state = entrypoint_args.get("session_state")
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
- raise
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
- return FunctionExecutionResult(status="failure", error=str(e))
946
+ execution_result = FunctionExecutionResult(status="failure", error=str(e))
947
+
948
+ finally:
949
+ self._handle_post_hook()
754
950
 
755
- # Execute post-hook if it exists
756
- self._handle_post_hook()
951
+ if exception_to_raise is not None:
952
+ raise exception_to_raise
757
953
 
758
- return FunctionExecutionResult(
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,34 +1143,45 @@ 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("session_state") is not None:
938
- updated_session_state = entrypoint_args.get("session_state")
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
- raise
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
- return FunctionExecutionResult(status="failure", error=str(e))
1166
+ execution_result = FunctionExecutionResult(status="failure", error=str(e))
949
1167
 
950
- # Execute post-hook if it exists
951
- if iscoroutinefunction(self.function.post_hook):
952
- await self._handle_post_hook_async()
953
- else:
954
- self._handle_post_hook()
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
- return FunctionExecutionResult(
957
- status="success", result=self.result, updated_session_state=updated_session_state
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):
962
1181
  """Result from a tool that can include media artifacts."""
963
1182
 
964
1183
  content: str
965
- images: Optional[List[ImageArtifact]] = None
966
- videos: Optional[List[VideoArtifact]] = None
967
- audios: Optional[List[AudioArtifact]] = None
1184
+ images: Optional[List[Image]] = None
1185
+ videos: Optional[List[Video]] = None
1186
+ audios: Optional[List[Audio]] = None
1187
+ files: Optional[List[File]] = None
agno/tools/giphy.py CHANGED
@@ -5,7 +5,7 @@ from typing import Any, List, Optional, Union
5
5
  import httpx
6
6
 
7
7
  from agno.agent import Agent
8
- from agno.media import ImageArtifact
8
+ from agno.media import Image
9
9
  from agno.team.team import Team
10
10
  from agno.tools import Toolkit
11
11
  from agno.tools.function import ToolResult
@@ -77,7 +77,7 @@ class GiphyTools(Toolkit):
77
77
  gif_urls.append(gif_url)
78
78
 
79
79
  # Create ImageArtifact for the GIF
80
- image_artifact = ImageArtifact(id=media_id, url=gif_url, alt_text=alt_text, revised_prompt=query)
80
+ image_artifact = Image(id=media_id, url=gif_url, alt_text=alt_text, revised_prompt=query)
81
81
  image_artifacts.append(image_artifact)
82
82
 
83
83
  if image_artifacts: