agno 2.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.
Files changed (314) hide show
  1. agno/agent/agent.py +5540 -2273
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/compression/__init__.py +3 -0
  5. agno/compression/manager.py +247 -0
  6. agno/culture/__init__.py +3 -0
  7. agno/culture/manager.py +956 -0
  8. agno/db/async_postgres/__init__.py +3 -0
  9. agno/db/base.py +689 -6
  10. agno/db/dynamo/dynamo.py +933 -37
  11. agno/db/dynamo/schemas.py +174 -10
  12. agno/db/dynamo/utils.py +63 -4
  13. agno/db/firestore/firestore.py +831 -9
  14. agno/db/firestore/schemas.py +51 -0
  15. agno/db/firestore/utils.py +102 -4
  16. agno/db/gcs_json/gcs_json_db.py +660 -12
  17. agno/db/gcs_json/utils.py +60 -26
  18. agno/db/in_memory/in_memory_db.py +287 -14
  19. agno/db/in_memory/utils.py +60 -2
  20. agno/db/json/json_db.py +590 -14
  21. agno/db/json/utils.py +60 -26
  22. agno/db/migrations/manager.py +199 -0
  23. agno/db/migrations/v1_to_v2.py +43 -13
  24. agno/db/migrations/versions/__init__.py +0 -0
  25. agno/db/migrations/versions/v2_3_0.py +938 -0
  26. agno/db/mongo/__init__.py +15 -1
  27. agno/db/mongo/async_mongo.py +2760 -0
  28. agno/db/mongo/mongo.py +879 -11
  29. agno/db/mongo/schemas.py +42 -0
  30. agno/db/mongo/utils.py +80 -8
  31. agno/db/mysql/__init__.py +2 -1
  32. agno/db/mysql/async_mysql.py +2912 -0
  33. agno/db/mysql/mysql.py +946 -68
  34. agno/db/mysql/schemas.py +72 -10
  35. agno/db/mysql/utils.py +198 -7
  36. agno/db/postgres/__init__.py +2 -1
  37. agno/db/postgres/async_postgres.py +2579 -0
  38. agno/db/postgres/postgres.py +942 -57
  39. agno/db/postgres/schemas.py +81 -18
  40. agno/db/postgres/utils.py +164 -2
  41. agno/db/redis/redis.py +671 -7
  42. agno/db/redis/schemas.py +50 -0
  43. agno/db/redis/utils.py +65 -7
  44. agno/db/schemas/__init__.py +2 -1
  45. agno/db/schemas/culture.py +120 -0
  46. agno/db/schemas/evals.py +1 -0
  47. agno/db/schemas/memory.py +17 -2
  48. agno/db/singlestore/schemas.py +63 -0
  49. agno/db/singlestore/singlestore.py +949 -83
  50. agno/db/singlestore/utils.py +60 -2
  51. agno/db/sqlite/__init__.py +2 -1
  52. agno/db/sqlite/async_sqlite.py +2911 -0
  53. agno/db/sqlite/schemas.py +62 -0
  54. agno/db/sqlite/sqlite.py +965 -46
  55. agno/db/sqlite/utils.py +169 -8
  56. agno/db/surrealdb/__init__.py +3 -0
  57. agno/db/surrealdb/metrics.py +292 -0
  58. agno/db/surrealdb/models.py +334 -0
  59. agno/db/surrealdb/queries.py +71 -0
  60. agno/db/surrealdb/surrealdb.py +1908 -0
  61. agno/db/surrealdb/utils.py +147 -0
  62. agno/db/utils.py +2 -0
  63. agno/eval/__init__.py +10 -0
  64. agno/eval/accuracy.py +75 -55
  65. agno/eval/agent_as_judge.py +861 -0
  66. agno/eval/base.py +29 -0
  67. agno/eval/performance.py +16 -7
  68. agno/eval/reliability.py +28 -16
  69. agno/eval/utils.py +35 -17
  70. agno/exceptions.py +27 -2
  71. agno/filters.py +354 -0
  72. agno/guardrails/prompt_injection.py +1 -0
  73. agno/hooks/__init__.py +3 -0
  74. agno/hooks/decorator.py +164 -0
  75. agno/integrations/discord/client.py +1 -1
  76. agno/knowledge/chunking/agentic.py +13 -10
  77. agno/knowledge/chunking/fixed.py +4 -1
  78. agno/knowledge/chunking/semantic.py +9 -4
  79. agno/knowledge/chunking/strategy.py +59 -15
  80. agno/knowledge/embedder/fastembed.py +1 -1
  81. agno/knowledge/embedder/nebius.py +1 -1
  82. agno/knowledge/embedder/ollama.py +8 -0
  83. agno/knowledge/embedder/openai.py +8 -8
  84. agno/knowledge/embedder/sentence_transformer.py +6 -2
  85. agno/knowledge/embedder/vllm.py +262 -0
  86. agno/knowledge/knowledge.py +1618 -318
  87. agno/knowledge/reader/base.py +6 -2
  88. agno/knowledge/reader/csv_reader.py +8 -10
  89. agno/knowledge/reader/docx_reader.py +5 -6
  90. agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
  91. agno/knowledge/reader/json_reader.py +5 -4
  92. agno/knowledge/reader/markdown_reader.py +8 -8
  93. agno/knowledge/reader/pdf_reader.py +17 -19
  94. agno/knowledge/reader/pptx_reader.py +101 -0
  95. agno/knowledge/reader/reader_factory.py +32 -3
  96. agno/knowledge/reader/s3_reader.py +3 -3
  97. agno/knowledge/reader/tavily_reader.py +193 -0
  98. agno/knowledge/reader/text_reader.py +22 -10
  99. agno/knowledge/reader/web_search_reader.py +1 -48
  100. agno/knowledge/reader/website_reader.py +10 -10
  101. agno/knowledge/reader/wikipedia_reader.py +33 -1
  102. agno/knowledge/types.py +1 -0
  103. agno/knowledge/utils.py +72 -7
  104. agno/media.py +22 -6
  105. agno/memory/__init__.py +14 -1
  106. agno/memory/manager.py +544 -83
  107. agno/memory/strategies/__init__.py +15 -0
  108. agno/memory/strategies/base.py +66 -0
  109. agno/memory/strategies/summarize.py +196 -0
  110. agno/memory/strategies/types.py +37 -0
  111. agno/models/aimlapi/aimlapi.py +17 -0
  112. agno/models/anthropic/claude.py +515 -40
  113. agno/models/aws/bedrock.py +102 -21
  114. agno/models/aws/claude.py +131 -274
  115. agno/models/azure/ai_foundry.py +41 -19
  116. agno/models/azure/openai_chat.py +39 -8
  117. agno/models/base.py +1249 -525
  118. agno/models/cerebras/cerebras.py +91 -21
  119. agno/models/cerebras/cerebras_openai.py +21 -2
  120. agno/models/cohere/chat.py +40 -6
  121. agno/models/cometapi/cometapi.py +18 -1
  122. agno/models/dashscope/dashscope.py +2 -3
  123. agno/models/deepinfra/deepinfra.py +18 -1
  124. agno/models/deepseek/deepseek.py +69 -3
  125. agno/models/fireworks/fireworks.py +18 -1
  126. agno/models/google/gemini.py +877 -80
  127. agno/models/google/utils.py +22 -0
  128. agno/models/groq/groq.py +51 -18
  129. agno/models/huggingface/huggingface.py +17 -6
  130. agno/models/ibm/watsonx.py +16 -6
  131. agno/models/internlm/internlm.py +18 -1
  132. agno/models/langdb/langdb.py +13 -1
  133. agno/models/litellm/chat.py +44 -9
  134. agno/models/litellm/litellm_openai.py +18 -1
  135. agno/models/message.py +28 -5
  136. agno/models/meta/llama.py +47 -14
  137. agno/models/meta/llama_openai.py +22 -17
  138. agno/models/mistral/mistral.py +8 -4
  139. agno/models/nebius/nebius.py +6 -7
  140. agno/models/nvidia/nvidia.py +20 -3
  141. agno/models/ollama/chat.py +24 -8
  142. agno/models/openai/chat.py +104 -29
  143. agno/models/openai/responses.py +101 -81
  144. agno/models/openrouter/openrouter.py +60 -3
  145. agno/models/perplexity/perplexity.py +17 -1
  146. agno/models/portkey/portkey.py +7 -6
  147. agno/models/requesty/requesty.py +24 -4
  148. agno/models/response.py +73 -2
  149. agno/models/sambanova/sambanova.py +20 -3
  150. agno/models/siliconflow/siliconflow.py +19 -2
  151. agno/models/together/together.py +20 -3
  152. agno/models/utils.py +254 -8
  153. agno/models/vercel/v0.py +20 -3
  154. agno/models/vertexai/__init__.py +0 -0
  155. agno/models/vertexai/claude.py +190 -0
  156. agno/models/vllm/vllm.py +19 -14
  157. agno/models/xai/xai.py +19 -2
  158. agno/os/app.py +549 -152
  159. agno/os/auth.py +190 -3
  160. agno/os/config.py +23 -0
  161. agno/os/interfaces/a2a/router.py +8 -11
  162. agno/os/interfaces/a2a/utils.py +1 -1
  163. agno/os/interfaces/agui/router.py +18 -3
  164. agno/os/interfaces/agui/utils.py +152 -39
  165. agno/os/interfaces/slack/router.py +55 -37
  166. agno/os/interfaces/slack/slack.py +9 -1
  167. agno/os/interfaces/whatsapp/router.py +0 -1
  168. agno/os/interfaces/whatsapp/security.py +3 -1
  169. agno/os/mcp.py +110 -52
  170. agno/os/middleware/__init__.py +2 -0
  171. agno/os/middleware/jwt.py +676 -112
  172. agno/os/router.py +40 -1478
  173. agno/os/routers/agents/__init__.py +3 -0
  174. agno/os/routers/agents/router.py +599 -0
  175. agno/os/routers/agents/schema.py +261 -0
  176. agno/os/routers/evals/evals.py +96 -39
  177. agno/os/routers/evals/schemas.py +65 -33
  178. agno/os/routers/evals/utils.py +80 -10
  179. agno/os/routers/health.py +10 -4
  180. agno/os/routers/knowledge/knowledge.py +196 -38
  181. agno/os/routers/knowledge/schemas.py +82 -22
  182. agno/os/routers/memory/memory.py +279 -52
  183. agno/os/routers/memory/schemas.py +46 -17
  184. agno/os/routers/metrics/metrics.py +20 -8
  185. agno/os/routers/metrics/schemas.py +16 -16
  186. agno/os/routers/session/session.py +462 -34
  187. agno/os/routers/teams/__init__.py +3 -0
  188. agno/os/routers/teams/router.py +512 -0
  189. agno/os/routers/teams/schema.py +257 -0
  190. agno/os/routers/traces/__init__.py +3 -0
  191. agno/os/routers/traces/schemas.py +414 -0
  192. agno/os/routers/traces/traces.py +499 -0
  193. agno/os/routers/workflows/__init__.py +3 -0
  194. agno/os/routers/workflows/router.py +624 -0
  195. agno/os/routers/workflows/schema.py +75 -0
  196. agno/os/schema.py +256 -693
  197. agno/os/scopes.py +469 -0
  198. agno/os/utils.py +514 -36
  199. agno/reasoning/anthropic.py +80 -0
  200. agno/reasoning/gemini.py +73 -0
  201. agno/reasoning/openai.py +5 -0
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +155 -32
  205. agno/run/base.py +55 -3
  206. agno/run/requirement.py +181 -0
  207. agno/run/team.py +125 -38
  208. agno/run/workflow.py +72 -18
  209. agno/session/agent.py +102 -89
  210. agno/session/summary.py +56 -15
  211. agno/session/team.py +164 -90
  212. agno/session/workflow.py +405 -40
  213. agno/table.py +10 -0
  214. agno/team/team.py +3974 -1903
  215. agno/tools/dalle.py +2 -4
  216. agno/tools/eleven_labs.py +23 -25
  217. agno/tools/exa.py +21 -16
  218. agno/tools/file.py +153 -23
  219. agno/tools/file_generation.py +16 -10
  220. agno/tools/firecrawl.py +15 -7
  221. agno/tools/function.py +193 -38
  222. agno/tools/gmail.py +238 -14
  223. agno/tools/google_drive.py +271 -0
  224. agno/tools/googlecalendar.py +36 -8
  225. agno/tools/googlesheets.py +20 -5
  226. agno/tools/jira.py +20 -0
  227. agno/tools/mcp/__init__.py +10 -0
  228. agno/tools/mcp/mcp.py +331 -0
  229. agno/tools/mcp/multi_mcp.py +347 -0
  230. agno/tools/mcp/params.py +24 -0
  231. agno/tools/mcp_toolbox.py +3 -3
  232. agno/tools/models/nebius.py +5 -5
  233. agno/tools/models_labs.py +20 -10
  234. agno/tools/nano_banana.py +151 -0
  235. agno/tools/notion.py +204 -0
  236. agno/tools/parallel.py +314 -0
  237. agno/tools/postgres.py +76 -36
  238. agno/tools/redshift.py +406 -0
  239. agno/tools/scrapegraph.py +1 -1
  240. agno/tools/shopify.py +1519 -0
  241. agno/tools/slack.py +18 -3
  242. agno/tools/spotify.py +919 -0
  243. agno/tools/tavily.py +146 -0
  244. agno/tools/toolkit.py +25 -0
  245. agno/tools/workflow.py +8 -1
  246. agno/tools/yfinance.py +12 -11
  247. agno/tracing/__init__.py +12 -0
  248. agno/tracing/exporter.py +157 -0
  249. agno/tracing/schemas.py +276 -0
  250. agno/tracing/setup.py +111 -0
  251. agno/utils/agent.py +938 -0
  252. agno/utils/cryptography.py +22 -0
  253. agno/utils/dttm.py +33 -0
  254. agno/utils/events.py +151 -3
  255. agno/utils/gemini.py +15 -5
  256. agno/utils/hooks.py +118 -4
  257. agno/utils/http.py +113 -2
  258. agno/utils/knowledge.py +12 -5
  259. agno/utils/log.py +1 -0
  260. agno/utils/mcp.py +92 -2
  261. agno/utils/media.py +187 -1
  262. agno/utils/merge_dict.py +3 -3
  263. agno/utils/message.py +60 -0
  264. agno/utils/models/ai_foundry.py +9 -2
  265. agno/utils/models/claude.py +49 -14
  266. agno/utils/models/cohere.py +9 -2
  267. agno/utils/models/llama.py +9 -2
  268. agno/utils/models/mistral.py +4 -2
  269. agno/utils/print_response/agent.py +109 -16
  270. agno/utils/print_response/team.py +223 -30
  271. agno/utils/print_response/workflow.py +251 -34
  272. agno/utils/streamlit.py +1 -1
  273. agno/utils/team.py +98 -9
  274. agno/utils/tokens.py +657 -0
  275. agno/vectordb/base.py +39 -7
  276. agno/vectordb/cassandra/cassandra.py +21 -5
  277. agno/vectordb/chroma/chromadb.py +43 -12
  278. agno/vectordb/clickhouse/clickhousedb.py +21 -5
  279. agno/vectordb/couchbase/couchbase.py +29 -5
  280. agno/vectordb/lancedb/lance_db.py +92 -181
  281. agno/vectordb/langchaindb/langchaindb.py +24 -4
  282. agno/vectordb/lightrag/lightrag.py +17 -3
  283. agno/vectordb/llamaindex/llamaindexdb.py +25 -5
  284. agno/vectordb/milvus/milvus.py +50 -37
  285. agno/vectordb/mongodb/__init__.py +7 -1
  286. agno/vectordb/mongodb/mongodb.py +36 -30
  287. agno/vectordb/pgvector/pgvector.py +201 -77
  288. agno/vectordb/pineconedb/pineconedb.py +41 -23
  289. agno/vectordb/qdrant/qdrant.py +67 -54
  290. agno/vectordb/redis/__init__.py +9 -0
  291. agno/vectordb/redis/redisdb.py +682 -0
  292. agno/vectordb/singlestore/singlestore.py +50 -29
  293. agno/vectordb/surrealdb/surrealdb.py +31 -41
  294. agno/vectordb/upstashdb/upstashdb.py +34 -6
  295. agno/vectordb/weaviate/weaviate.py +53 -14
  296. agno/workflow/__init__.py +2 -0
  297. agno/workflow/agent.py +299 -0
  298. agno/workflow/condition.py +120 -18
  299. agno/workflow/loop.py +77 -10
  300. agno/workflow/parallel.py +231 -143
  301. agno/workflow/router.py +118 -17
  302. agno/workflow/step.py +609 -170
  303. agno/workflow/steps.py +73 -6
  304. agno/workflow/types.py +96 -21
  305. agno/workflow/workflow.py +2039 -262
  306. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
  307. agno-2.3.13.dist-info/RECORD +613 -0
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -679
  310. agno/tools/memori.py +0 -339
  311. agno-2.1.2.dist-info/RECORD +0 -543
  312. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
  313. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.1.2.dist-info → agno-2.3.13.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,6 +123,8 @@ 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
127
130
  # The dependencies that the function is associated with
@@ -139,6 +142,46 @@ class Function(BaseModel):
139
142
  include={"name", "description", "parameters", "strict", "requires_confirmation", "external_execution"},
140
143
  )
141
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
+
142
185
  @classmethod
143
186
  def from_callable(cls, c: Callable, name: Optional[str] = None, strict: bool = False) -> "Function":
144
187
  from inspect import getdoc, signature
@@ -156,8 +199,13 @@ class Function(BaseModel):
156
199
  del type_hints["agent"]
157
200
  if "team" in sig.parameters and "team" in type_hints:
158
201
  del type_hints["team"]
202
+ if "run_context" in sig.parameters and "run_context" in type_hints:
203
+ del type_hints["run_context"]
159
204
  if "session_state" in sig.parameters and "session_state" in type_hints:
160
205
  del type_hints["session_state"]
206
+ if "dependencies" in sig.parameters and "dependencies" in type_hints:
207
+ del type_hints["dependencies"]
208
+
161
209
  # Remove media parameters from type hints as they are injected automatically
162
210
  if "images" in sig.parameters and "images" in type_hints:
163
211
  del type_hints["images"]
@@ -167,8 +215,6 @@ class Function(BaseModel):
167
215
  del type_hints["audios"]
168
216
  if "files" in sig.parameters and "files" in type_hints:
169
217
  del type_hints["files"]
170
- if "dependencies" in sig.parameters and "dependencies" in type_hints:
171
- del type_hints["dependencies"]
172
218
  # log_info(f"Type hints for {function_name}: {type_hints}")
173
219
 
174
220
  # Filter out return type and only process parameters
@@ -177,7 +223,18 @@ class Function(BaseModel):
177
223
  for name in sig.parameters
178
224
  if name != "return"
179
225
  and name
180
- not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files", "dependencies"]
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
+ ]
181
238
  }
182
239
 
183
240
  # Parse docstring for parameters
@@ -210,13 +267,14 @@ class Function(BaseModel):
210
267
  not in [
211
268
  "agent",
212
269
  "team",
270
+ "run_context",
213
271
  "session_state",
272
+ "dependencies",
214
273
  "self",
215
274
  "images",
216
275
  "videos",
217
276
  "audios",
218
277
  "files",
219
- "dependencies",
220
278
  ]
221
279
  ]
222
280
  else:
@@ -229,13 +287,14 @@ class Function(BaseModel):
229
287
  not in [
230
288
  "agent",
231
289
  "team",
290
+ "run_context",
232
291
  "session_state",
292
+ "dependencies",
233
293
  "self",
234
294
  "images",
235
295
  "videos",
236
296
  "audios",
237
297
  "files",
238
- "dependencies",
239
298
  ]
240
299
  ]
241
300
 
@@ -285,8 +344,12 @@ class Function(BaseModel):
285
344
  del type_hints["agent"]
286
345
  if "team" in sig.parameters and "team" in type_hints:
287
346
  del type_hints["team"]
347
+ if "run_context" in sig.parameters and "run_context" in type_hints:
348
+ del type_hints["run_context"]
288
349
  if "session_state" in sig.parameters and "session_state" in type_hints:
289
350
  del type_hints["session_state"]
351
+ if "dependencies" in sig.parameters and "dependencies" in type_hints:
352
+ del type_hints["dependencies"]
290
353
  if "images" in sig.parameters and "images" in type_hints:
291
354
  del type_hints["images"]
292
355
  if "videos" in sig.parameters and "videos" in type_hints:
@@ -295,8 +358,6 @@ class Function(BaseModel):
295
358
  del type_hints["audios"]
296
359
  if "files" in sig.parameters and "files" in type_hints:
297
360
  del type_hints["files"]
298
- if "dependencies" in sig.parameters and "dependencies" in type_hints:
299
- del type_hints["dependencies"]
300
361
  # log_info(f"Type hints for {self.name}: {type_hints}")
301
362
 
302
363
  # Filter out return type and only process parameters
@@ -304,13 +365,14 @@ class Function(BaseModel):
304
365
  "return",
305
366
  "agent",
306
367
  "team",
368
+ "run_context",
307
369
  "session_state",
370
+ "dependencies",
308
371
  "self",
309
372
  "images",
310
373
  "videos",
311
374
  "audios",
312
375
  "files",
313
- "dependencies",
314
376
  ]
315
377
  if self.requires_user_input and self.user_input_fields:
316
378
  if len(self.user_input_fields) == 0:
@@ -400,7 +462,7 @@ class Function(BaseModel):
400
462
  @staticmethod
401
463
  def _wrap_callable(func: Callable) -> Callable:
402
464
  """Wrap a callable with Pydantic's validate_call decorator, if relevant"""
403
- from inspect import isasyncgenfunction, iscoroutinefunction
465
+ from inspect import isasyncgenfunction, iscoroutinefunction, signature
404
466
 
405
467
  pydantic_version = Version(version("pydantic"))
406
468
 
@@ -418,6 +480,10 @@ class Function(BaseModel):
418
480
  # Don't wrap callables that are already wrapped with validate_call
419
481
  elif getattr(func, "_wrapped_for_validation", False):
420
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
421
487
  # Wrap the callable with validate_call
422
488
  else:
423
489
  wrapped = validate_call(func, config=dict(arbitrary_types_allowed=True)) # type: ignore
@@ -468,11 +534,23 @@ class Function(BaseModel):
468
534
  name
469
535
  for name in self.parameters["properties"]
470
536
  if name
471
- not in ["agent", "team", "session_state", "images", "videos", "audios", "files", "self", "dependencies"]
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
+ ]
472
549
  ]
473
550
 
474
551
  def _get_cache_key(self, entrypoint_args: Dict[str, Any], call_args: Optional[Dict[str, Any]] = None) -> str:
475
552
  """Generate a cache key based on function name and arguments."""
553
+ import json
476
554
  from hashlib import md5
477
555
 
478
556
  copy_entrypoint_args = entrypoint_args.copy()
@@ -481,8 +559,12 @@ class Function(BaseModel):
481
559
  del copy_entrypoint_args["agent"]
482
560
  if "team" in copy_entrypoint_args:
483
561
  del copy_entrypoint_args["team"]
562
+ if "run_context" in copy_entrypoint_args:
563
+ del copy_entrypoint_args["run_context"]
484
564
  if "session_state" in copy_entrypoint_args:
485
565
  del copy_entrypoint_args["session_state"]
566
+ if "dependencies" in copy_entrypoint_args:
567
+ del copy_entrypoint_args["dependencies"]
486
568
  if "images" in copy_entrypoint_args:
487
569
  del copy_entrypoint_args["images"]
488
570
  if "videos" in copy_entrypoint_args:
@@ -491,9 +573,8 @@ class Function(BaseModel):
491
573
  del copy_entrypoint_args["audios"]
492
574
  if "files" in copy_entrypoint_args:
493
575
  del copy_entrypoint_args["files"]
494
- if "dependencies" in copy_entrypoint_args:
495
- del copy_entrypoint_args["dependencies"]
496
- 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)
497
578
 
498
579
  kwargs_str = str(sorted((call_args or {}).items()))
499
580
  key_str = f"{self.name}:{args_str}:{kwargs_str}"
@@ -617,8 +698,14 @@ class FunctionCall(BaseModel):
617
698
  if "team" in signature(self.function.pre_hook).parameters:
618
699
  pre_hook_args["team"] = self.function._team
619
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
620
704
  if "session_state" in signature(self.function.pre_hook).parameters:
621
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
622
709
  # Check if the pre-hook has an fc argument
623
710
  if "fc" in signature(self.function.pre_hook).parameters:
624
711
  pre_hook_args["fc"] = self
@@ -645,8 +732,14 @@ class FunctionCall(BaseModel):
645
732
  if "team" in signature(self.function.post_hook).parameters:
646
733
  post_hook_args["team"] = self.function._team
647
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
648
738
  if "session_state" in signature(self.function.post_hook).parameters:
649
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
650
743
  # Check if the post-hook has an fc argument
651
744
  if "fc" in signature(self.function.post_hook).parameters:
652
745
  post_hook_args["fc"] = self
@@ -670,6 +763,9 @@ class FunctionCall(BaseModel):
670
763
  # Check if the entrypoint has an team argument
671
764
  if "team" in signature(self.function.entrypoint).parameters: # type: ignore
672
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
673
769
  # Check if the entrypoint has an session_state argument
674
770
  if "session_state" in signature(self.function.entrypoint).parameters: # type: ignore
675
771
  entrypoint_args["session_state"] = self.function._session_state
@@ -702,13 +798,15 @@ class FunctionCall(BaseModel):
702
798
  # Check if the hook has an team argument
703
799
  if "team" in signature(hook).parameters:
704
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
705
804
  # Check if the hook has an session_state argument
706
805
  if "session_state" in signature(hook).parameters:
707
806
  hook_args["session_state"] = self.function._session_state
708
807
  # Check if the hook has an dependencies argument
709
808
  if "dependencies" in signature(hook).parameters:
710
809
  hook_args["dependencies"] = self.function._dependencies
711
-
712
810
  if "name" in signature(hook).parameters:
713
811
  hook_args["name"] = name
714
812
  if "function_name" in signature(hook).parameters:
@@ -799,6 +897,9 @@ class FunctionCall(BaseModel):
799
897
  return FunctionExecutionResult(status="success", result=cached_result)
800
898
 
801
899
  # Execute function
900
+ execution_result: FunctionExecutionResult
901
+ exception_to_raise = None
902
+
802
903
  try:
803
904
  # Build and execute the nested chain of hooks
804
905
  if self.function.tool_hooks is not None:
@@ -807,13 +908,16 @@ class FunctionCall(BaseModel):
807
908
  else:
808
909
  result = self.function.entrypoint(**entrypoint_args, **self.arguments) # type: ignore
809
910
 
810
- updated_session_state = None
811
- if entrypoint_args.get("session_state") is not None:
812
- updated_session_state = entrypoint_args.get("session_state")
813
-
814
911
  # Handle generator case
815
912
  if isgenerator(result):
816
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
+ )
817
921
  else:
818
922
  self.result = result
819
923
  # Only cache non-generator results
@@ -822,22 +926,40 @@ class FunctionCall(BaseModel):
822
926
  cache_file = self.function._get_cache_file_path(cache_key)
823
927
  self.function._save_to_cache(cache_file, self.result)
824
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
+
825
945
  except AgentRunException as e:
826
946
  log_debug(f"{e.__class__.__name__}: {e}")
827
947
  self.error = str(e)
828
- raise
948
+ exception_to_raise = e
949
+ execution_result = FunctionExecutionResult(status="failure", error=str(e))
829
950
  except Exception as e:
830
951
  log_warning(f"Could not run function {self.get_call_str()}")
831
952
  log_exception(e)
832
953
  self.error = str(e)
833
- return FunctionExecutionResult(status="failure", error=str(e))
954
+ execution_result = FunctionExecutionResult(status="failure", error=str(e))
955
+
956
+ finally:
957
+ self._handle_post_hook()
834
958
 
835
- # Execute post-hook if it exists
836
- self._handle_post_hook()
959
+ if exception_to_raise is not None:
960
+ raise exception_to_raise
837
961
 
838
- return FunctionExecutionResult(
839
- status="success", result=self.result, updated_session_state=updated_session_state
840
- )
962
+ return execution_result
841
963
 
842
964
  async def _handle_pre_hook_async(self):
843
965
  """Handles the async pre-hook for the function call."""
@@ -852,9 +974,15 @@ class FunctionCall(BaseModel):
852
974
  # Check if the pre-hook has an team argument
853
975
  if "team" in signature(self.function.pre_hook).parameters:
854
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
855
980
  # Check if the pre-hook has an session_state argument
856
981
  if "session_state" in signature(self.function.pre_hook).parameters:
857
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
858
986
  # Check if the pre-hook has an fc argument
859
987
  if "fc" in signature(self.function.pre_hook).parameters:
860
988
  pre_hook_args["fc"] = self
@@ -881,9 +1009,15 @@ class FunctionCall(BaseModel):
881
1009
  # Check if the post-hook has an team argument
882
1010
  if "team" in signature(self.function.post_hook).parameters:
883
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
884
1015
  # Check if the post-hook has an session_state argument
885
1016
  if "session_state" in signature(self.function.post_hook).parameters:
886
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
887
1021
 
888
1022
  # Check if the post-hook has an fc argument
889
1023
  if "fc" in signature(self.function.post_hook).parameters:
@@ -991,6 +1125,9 @@ class FunctionCall(BaseModel):
991
1125
  return FunctionExecutionResult(status="success", result=cached_result)
992
1126
 
993
1127
  # Execute function
1128
+ execution_result: FunctionExecutionResult
1129
+ exception_to_raise = None
1130
+
994
1131
  try:
995
1132
  # Build and execute the nested chain of hooks
996
1133
  if self.function.tool_hooks is not None:
@@ -1013,29 +1150,47 @@ class FunctionCall(BaseModel):
1013
1150
  cache_file = self.function._get_cache_file_path(cache_key)
1014
1151
  self.function._save_to_cache(cache_file, self.result)
1015
1152
 
1016
- updated_session_state = None
1017
- if entrypoint_args.get("session_state") is not None:
1018
- updated_session_state = entrypoint_args.get("session_state")
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
+ )
1019
1172
 
1020
1173
  except AgentRunException as e:
1021
1174
  log_debug(f"{e.__class__.__name__}: {e}")
1022
1175
  self.error = str(e)
1023
- raise
1176
+ exception_to_raise = e
1177
+ execution_result = FunctionExecutionResult(status="failure", error=str(e))
1024
1178
  except Exception as e:
1025
1179
  log_warning(f"Could not run function {self.get_call_str()}")
1026
1180
  log_exception(e)
1027
1181
  self.error = str(e)
1028
- return FunctionExecutionResult(status="failure", error=str(e))
1182
+ execution_result = FunctionExecutionResult(status="failure", error=str(e))
1029
1183
 
1030
- # Execute post-hook if it exists
1031
- if iscoroutinefunction(self.function.post_hook):
1032
- await self._handle_post_hook_async()
1033
- else:
1034
- self._handle_post_hook()
1184
+ finally:
1185
+ if iscoroutinefunction(self.function.post_hook):
1186
+ await self._handle_post_hook_async()
1187
+ else:
1188
+ self._handle_post_hook()
1035
1189
 
1036
- return FunctionExecutionResult(
1037
- status="success", result=self.result, updated_session_state=updated_session_state
1038
- )
1190
+ if exception_to_raise is not None:
1191
+ raise exception_to_raise
1192
+
1193
+ return execution_result
1039
1194
 
1040
1195
 
1041
1196
  class ToolResult(BaseModel):