agno 2.2.13__py3-none-any.whl → 2.4.3__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 (383) hide show
  1. agno/agent/__init__.py +6 -0
  2. agno/agent/agent.py +5252 -3145
  3. agno/agent/remote.py +525 -0
  4. agno/api/api.py +2 -0
  5. agno/client/__init__.py +3 -0
  6. agno/client/a2a/__init__.py +10 -0
  7. agno/client/a2a/client.py +554 -0
  8. agno/client/a2a/schemas.py +112 -0
  9. agno/client/a2a/utils.py +369 -0
  10. agno/client/os.py +2669 -0
  11. agno/compression/__init__.py +3 -0
  12. agno/compression/manager.py +247 -0
  13. agno/culture/manager.py +2 -2
  14. agno/db/base.py +927 -6
  15. agno/db/dynamo/dynamo.py +788 -2
  16. agno/db/dynamo/schemas.py +128 -0
  17. agno/db/dynamo/utils.py +26 -3
  18. agno/db/firestore/firestore.py +674 -50
  19. agno/db/firestore/schemas.py +41 -0
  20. agno/db/firestore/utils.py +25 -10
  21. agno/db/gcs_json/gcs_json_db.py +506 -3
  22. agno/db/gcs_json/utils.py +14 -2
  23. agno/db/in_memory/in_memory_db.py +203 -4
  24. agno/db/in_memory/utils.py +14 -2
  25. agno/db/json/json_db.py +498 -2
  26. agno/db/json/utils.py +14 -2
  27. agno/db/migrations/manager.py +199 -0
  28. agno/db/migrations/utils.py +19 -0
  29. agno/db/migrations/v1_to_v2.py +54 -16
  30. agno/db/migrations/versions/__init__.py +0 -0
  31. agno/db/migrations/versions/v2_3_0.py +977 -0
  32. agno/db/mongo/async_mongo.py +1013 -39
  33. agno/db/mongo/mongo.py +684 -4
  34. agno/db/mongo/schemas.py +48 -0
  35. agno/db/mongo/utils.py +17 -0
  36. agno/db/mysql/__init__.py +2 -1
  37. agno/db/mysql/async_mysql.py +2958 -0
  38. agno/db/mysql/mysql.py +722 -53
  39. agno/db/mysql/schemas.py +77 -11
  40. agno/db/mysql/utils.py +151 -8
  41. agno/db/postgres/async_postgres.py +1254 -137
  42. agno/db/postgres/postgres.py +2316 -93
  43. agno/db/postgres/schemas.py +153 -21
  44. agno/db/postgres/utils.py +22 -7
  45. agno/db/redis/redis.py +531 -3
  46. agno/db/redis/schemas.py +36 -0
  47. agno/db/redis/utils.py +31 -15
  48. agno/db/schemas/evals.py +1 -0
  49. agno/db/schemas/memory.py +20 -9
  50. agno/db/singlestore/schemas.py +70 -1
  51. agno/db/singlestore/singlestore.py +737 -74
  52. agno/db/singlestore/utils.py +13 -3
  53. agno/db/sqlite/async_sqlite.py +1069 -89
  54. agno/db/sqlite/schemas.py +133 -1
  55. agno/db/sqlite/sqlite.py +2203 -165
  56. agno/db/sqlite/utils.py +21 -11
  57. agno/db/surrealdb/models.py +25 -0
  58. agno/db/surrealdb/surrealdb.py +603 -1
  59. agno/db/utils.py +60 -0
  60. agno/eval/__init__.py +26 -3
  61. agno/eval/accuracy.py +25 -12
  62. agno/eval/agent_as_judge.py +871 -0
  63. agno/eval/base.py +29 -0
  64. agno/eval/performance.py +10 -4
  65. agno/eval/reliability.py +22 -13
  66. agno/eval/utils.py +2 -1
  67. agno/exceptions.py +42 -0
  68. agno/hooks/__init__.py +3 -0
  69. agno/hooks/decorator.py +164 -0
  70. agno/integrations/discord/client.py +13 -2
  71. agno/knowledge/__init__.py +4 -0
  72. agno/knowledge/chunking/code.py +90 -0
  73. agno/knowledge/chunking/document.py +65 -4
  74. agno/knowledge/chunking/fixed.py +4 -1
  75. agno/knowledge/chunking/markdown.py +102 -11
  76. agno/knowledge/chunking/recursive.py +2 -2
  77. agno/knowledge/chunking/semantic.py +130 -48
  78. agno/knowledge/chunking/strategy.py +18 -0
  79. agno/knowledge/embedder/azure_openai.py +0 -1
  80. agno/knowledge/embedder/google.py +1 -1
  81. agno/knowledge/embedder/mistral.py +1 -1
  82. agno/knowledge/embedder/nebius.py +1 -1
  83. agno/knowledge/embedder/openai.py +16 -12
  84. agno/knowledge/filesystem.py +412 -0
  85. agno/knowledge/knowledge.py +4261 -1199
  86. agno/knowledge/protocol.py +134 -0
  87. agno/knowledge/reader/arxiv_reader.py +3 -2
  88. agno/knowledge/reader/base.py +9 -7
  89. agno/knowledge/reader/csv_reader.py +91 -42
  90. agno/knowledge/reader/docx_reader.py +9 -10
  91. agno/knowledge/reader/excel_reader.py +225 -0
  92. agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
  93. agno/knowledge/reader/firecrawl_reader.py +3 -2
  94. agno/knowledge/reader/json_reader.py +16 -22
  95. agno/knowledge/reader/markdown_reader.py +15 -14
  96. agno/knowledge/reader/pdf_reader.py +33 -28
  97. agno/knowledge/reader/pptx_reader.py +9 -10
  98. agno/knowledge/reader/reader_factory.py +135 -1
  99. agno/knowledge/reader/s3_reader.py +8 -16
  100. agno/knowledge/reader/tavily_reader.py +3 -3
  101. agno/knowledge/reader/text_reader.py +15 -14
  102. agno/knowledge/reader/utils/__init__.py +17 -0
  103. agno/knowledge/reader/utils/spreadsheet.py +114 -0
  104. agno/knowledge/reader/web_search_reader.py +8 -65
  105. agno/knowledge/reader/website_reader.py +16 -13
  106. agno/knowledge/reader/wikipedia_reader.py +36 -3
  107. agno/knowledge/reader/youtube_reader.py +3 -2
  108. agno/knowledge/remote_content/__init__.py +33 -0
  109. agno/knowledge/remote_content/config.py +266 -0
  110. agno/knowledge/remote_content/remote_content.py +105 -17
  111. agno/knowledge/utils.py +76 -22
  112. agno/learn/__init__.py +71 -0
  113. agno/learn/config.py +463 -0
  114. agno/learn/curate.py +185 -0
  115. agno/learn/machine.py +725 -0
  116. agno/learn/schemas.py +1114 -0
  117. agno/learn/stores/__init__.py +38 -0
  118. agno/learn/stores/decision_log.py +1156 -0
  119. agno/learn/stores/entity_memory.py +3275 -0
  120. agno/learn/stores/learned_knowledge.py +1583 -0
  121. agno/learn/stores/protocol.py +117 -0
  122. agno/learn/stores/session_context.py +1217 -0
  123. agno/learn/stores/user_memory.py +1495 -0
  124. agno/learn/stores/user_profile.py +1220 -0
  125. agno/learn/utils.py +209 -0
  126. agno/media.py +22 -6
  127. agno/memory/__init__.py +14 -1
  128. agno/memory/manager.py +223 -8
  129. agno/memory/strategies/__init__.py +15 -0
  130. agno/memory/strategies/base.py +66 -0
  131. agno/memory/strategies/summarize.py +196 -0
  132. agno/memory/strategies/types.py +37 -0
  133. agno/models/aimlapi/aimlapi.py +17 -0
  134. agno/models/anthropic/claude.py +434 -59
  135. agno/models/aws/bedrock.py +121 -20
  136. agno/models/aws/claude.py +131 -274
  137. agno/models/azure/ai_foundry.py +10 -6
  138. agno/models/azure/openai_chat.py +33 -10
  139. agno/models/base.py +1162 -561
  140. agno/models/cerebras/cerebras.py +120 -24
  141. agno/models/cerebras/cerebras_openai.py +21 -2
  142. agno/models/cohere/chat.py +65 -6
  143. agno/models/cometapi/cometapi.py +18 -1
  144. agno/models/dashscope/dashscope.py +2 -3
  145. agno/models/deepinfra/deepinfra.py +18 -1
  146. agno/models/deepseek/deepseek.py +69 -3
  147. agno/models/fireworks/fireworks.py +18 -1
  148. agno/models/google/gemini.py +959 -89
  149. agno/models/google/utils.py +22 -0
  150. agno/models/groq/groq.py +48 -18
  151. agno/models/huggingface/huggingface.py +17 -6
  152. agno/models/ibm/watsonx.py +16 -6
  153. agno/models/internlm/internlm.py +18 -1
  154. agno/models/langdb/langdb.py +13 -1
  155. agno/models/litellm/chat.py +88 -9
  156. agno/models/litellm/litellm_openai.py +18 -1
  157. agno/models/message.py +24 -5
  158. agno/models/meta/llama.py +40 -13
  159. agno/models/meta/llama_openai.py +22 -21
  160. agno/models/metrics.py +12 -0
  161. agno/models/mistral/mistral.py +8 -4
  162. agno/models/n1n/__init__.py +3 -0
  163. agno/models/n1n/n1n.py +57 -0
  164. agno/models/nebius/nebius.py +6 -7
  165. agno/models/nvidia/nvidia.py +20 -3
  166. agno/models/ollama/__init__.py +2 -0
  167. agno/models/ollama/chat.py +17 -6
  168. agno/models/ollama/responses.py +100 -0
  169. agno/models/openai/__init__.py +2 -0
  170. agno/models/openai/chat.py +117 -26
  171. agno/models/openai/open_responses.py +46 -0
  172. agno/models/openai/responses.py +110 -32
  173. agno/models/openrouter/__init__.py +2 -0
  174. agno/models/openrouter/openrouter.py +67 -2
  175. agno/models/openrouter/responses.py +146 -0
  176. agno/models/perplexity/perplexity.py +19 -1
  177. agno/models/portkey/portkey.py +7 -6
  178. agno/models/requesty/requesty.py +19 -2
  179. agno/models/response.py +20 -2
  180. agno/models/sambanova/sambanova.py +20 -3
  181. agno/models/siliconflow/siliconflow.py +19 -2
  182. agno/models/together/together.py +20 -3
  183. agno/models/vercel/v0.py +20 -3
  184. agno/models/vertexai/claude.py +124 -4
  185. agno/models/vllm/vllm.py +19 -14
  186. agno/models/xai/xai.py +19 -2
  187. agno/os/app.py +467 -137
  188. agno/os/auth.py +253 -5
  189. agno/os/config.py +22 -0
  190. agno/os/interfaces/a2a/a2a.py +7 -6
  191. agno/os/interfaces/a2a/router.py +635 -26
  192. agno/os/interfaces/a2a/utils.py +32 -33
  193. agno/os/interfaces/agui/agui.py +5 -3
  194. agno/os/interfaces/agui/router.py +26 -16
  195. agno/os/interfaces/agui/utils.py +97 -57
  196. agno/os/interfaces/base.py +7 -7
  197. agno/os/interfaces/slack/router.py +16 -7
  198. agno/os/interfaces/slack/slack.py +7 -7
  199. agno/os/interfaces/whatsapp/router.py +35 -7
  200. agno/os/interfaces/whatsapp/security.py +3 -1
  201. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  202. agno/os/managers.py +326 -0
  203. agno/os/mcp.py +652 -79
  204. agno/os/middleware/__init__.py +4 -0
  205. agno/os/middleware/jwt.py +718 -115
  206. agno/os/middleware/trailing_slash.py +27 -0
  207. agno/os/router.py +105 -1558
  208. agno/os/routers/agents/__init__.py +3 -0
  209. agno/os/routers/agents/router.py +655 -0
  210. agno/os/routers/agents/schema.py +288 -0
  211. agno/os/routers/components/__init__.py +3 -0
  212. agno/os/routers/components/components.py +475 -0
  213. agno/os/routers/database.py +155 -0
  214. agno/os/routers/evals/evals.py +111 -18
  215. agno/os/routers/evals/schemas.py +38 -5
  216. agno/os/routers/evals/utils.py +80 -11
  217. agno/os/routers/health.py +3 -3
  218. agno/os/routers/knowledge/knowledge.py +284 -35
  219. agno/os/routers/knowledge/schemas.py +14 -2
  220. agno/os/routers/memory/memory.py +274 -11
  221. agno/os/routers/memory/schemas.py +44 -3
  222. agno/os/routers/metrics/metrics.py +30 -15
  223. agno/os/routers/metrics/schemas.py +10 -6
  224. agno/os/routers/registry/__init__.py +3 -0
  225. agno/os/routers/registry/registry.py +337 -0
  226. agno/os/routers/session/session.py +143 -14
  227. agno/os/routers/teams/__init__.py +3 -0
  228. agno/os/routers/teams/router.py +550 -0
  229. agno/os/routers/teams/schema.py +280 -0
  230. agno/os/routers/traces/__init__.py +3 -0
  231. agno/os/routers/traces/schemas.py +414 -0
  232. agno/os/routers/traces/traces.py +549 -0
  233. agno/os/routers/workflows/__init__.py +3 -0
  234. agno/os/routers/workflows/router.py +757 -0
  235. agno/os/routers/workflows/schema.py +139 -0
  236. agno/os/schema.py +157 -584
  237. agno/os/scopes.py +469 -0
  238. agno/os/settings.py +3 -0
  239. agno/os/utils.py +574 -185
  240. agno/reasoning/anthropic.py +85 -1
  241. agno/reasoning/azure_ai_foundry.py +93 -1
  242. agno/reasoning/deepseek.py +102 -2
  243. agno/reasoning/default.py +6 -7
  244. agno/reasoning/gemini.py +87 -3
  245. agno/reasoning/groq.py +109 -2
  246. agno/reasoning/helpers.py +6 -7
  247. agno/reasoning/manager.py +1238 -0
  248. agno/reasoning/ollama.py +93 -1
  249. agno/reasoning/openai.py +115 -1
  250. agno/reasoning/vertexai.py +85 -1
  251. agno/registry/__init__.py +3 -0
  252. agno/registry/registry.py +68 -0
  253. agno/remote/__init__.py +3 -0
  254. agno/remote/base.py +581 -0
  255. agno/run/__init__.py +2 -4
  256. agno/run/agent.py +134 -19
  257. agno/run/base.py +49 -1
  258. agno/run/cancel.py +65 -52
  259. agno/run/cancellation_management/__init__.py +9 -0
  260. agno/run/cancellation_management/base.py +78 -0
  261. agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
  262. agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
  263. agno/run/requirement.py +181 -0
  264. agno/run/team.py +111 -19
  265. agno/run/workflow.py +2 -1
  266. agno/session/agent.py +57 -92
  267. agno/session/summary.py +1 -1
  268. agno/session/team.py +62 -115
  269. agno/session/workflow.py +353 -57
  270. agno/skills/__init__.py +17 -0
  271. agno/skills/agent_skills.py +377 -0
  272. agno/skills/errors.py +32 -0
  273. agno/skills/loaders/__init__.py +4 -0
  274. agno/skills/loaders/base.py +27 -0
  275. agno/skills/loaders/local.py +216 -0
  276. agno/skills/skill.py +65 -0
  277. agno/skills/utils.py +107 -0
  278. agno/skills/validator.py +277 -0
  279. agno/table.py +10 -0
  280. agno/team/__init__.py +5 -1
  281. agno/team/remote.py +447 -0
  282. agno/team/team.py +3769 -2202
  283. agno/tools/brandfetch.py +27 -18
  284. agno/tools/browserbase.py +225 -16
  285. agno/tools/crawl4ai.py +3 -0
  286. agno/tools/duckduckgo.py +25 -71
  287. agno/tools/exa.py +0 -21
  288. agno/tools/file.py +14 -13
  289. agno/tools/file_generation.py +12 -6
  290. agno/tools/firecrawl.py +15 -7
  291. agno/tools/function.py +94 -113
  292. agno/tools/google_bigquery.py +11 -2
  293. agno/tools/google_drive.py +4 -3
  294. agno/tools/knowledge.py +9 -4
  295. agno/tools/mcp/mcp.py +301 -18
  296. agno/tools/mcp/multi_mcp.py +269 -14
  297. agno/tools/mem0.py +11 -10
  298. agno/tools/memory.py +47 -46
  299. agno/tools/mlx_transcribe.py +10 -7
  300. agno/tools/models/nebius.py +5 -5
  301. agno/tools/models_labs.py +20 -10
  302. agno/tools/nano_banana.py +151 -0
  303. agno/tools/parallel.py +0 -7
  304. agno/tools/postgres.py +76 -36
  305. agno/tools/python.py +14 -6
  306. agno/tools/reasoning.py +30 -23
  307. agno/tools/redshift.py +406 -0
  308. agno/tools/shopify.py +1519 -0
  309. agno/tools/spotify.py +919 -0
  310. agno/tools/tavily.py +4 -1
  311. agno/tools/toolkit.py +253 -18
  312. agno/tools/websearch.py +93 -0
  313. agno/tools/website.py +1 -1
  314. agno/tools/wikipedia.py +1 -1
  315. agno/tools/workflow.py +56 -48
  316. agno/tools/yfinance.py +12 -11
  317. agno/tracing/__init__.py +12 -0
  318. agno/tracing/exporter.py +161 -0
  319. agno/tracing/schemas.py +276 -0
  320. agno/tracing/setup.py +112 -0
  321. agno/utils/agent.py +251 -10
  322. agno/utils/cryptography.py +22 -0
  323. agno/utils/dttm.py +33 -0
  324. agno/utils/events.py +264 -7
  325. agno/utils/hooks.py +111 -3
  326. agno/utils/http.py +161 -2
  327. agno/utils/mcp.py +49 -8
  328. agno/utils/media.py +22 -1
  329. agno/utils/models/ai_foundry.py +9 -2
  330. agno/utils/models/claude.py +20 -5
  331. agno/utils/models/cohere.py +9 -2
  332. agno/utils/models/llama.py +9 -2
  333. agno/utils/models/mistral.py +4 -2
  334. agno/utils/os.py +0 -0
  335. agno/utils/print_response/agent.py +99 -16
  336. agno/utils/print_response/team.py +223 -24
  337. agno/utils/print_response/workflow.py +0 -2
  338. agno/utils/prompts.py +8 -6
  339. agno/utils/remote.py +23 -0
  340. agno/utils/response.py +1 -13
  341. agno/utils/string.py +91 -2
  342. agno/utils/team.py +62 -12
  343. agno/utils/tokens.py +657 -0
  344. agno/vectordb/base.py +15 -2
  345. agno/vectordb/cassandra/cassandra.py +1 -1
  346. agno/vectordb/chroma/__init__.py +2 -1
  347. agno/vectordb/chroma/chromadb.py +468 -23
  348. agno/vectordb/clickhouse/clickhousedb.py +1 -1
  349. agno/vectordb/couchbase/couchbase.py +6 -2
  350. agno/vectordb/lancedb/lance_db.py +7 -38
  351. agno/vectordb/lightrag/lightrag.py +7 -6
  352. agno/vectordb/milvus/milvus.py +118 -84
  353. agno/vectordb/mongodb/__init__.py +2 -1
  354. agno/vectordb/mongodb/mongodb.py +14 -31
  355. agno/vectordb/pgvector/pgvector.py +120 -66
  356. agno/vectordb/pineconedb/pineconedb.py +2 -19
  357. agno/vectordb/qdrant/__init__.py +2 -1
  358. agno/vectordb/qdrant/qdrant.py +33 -56
  359. agno/vectordb/redis/__init__.py +2 -1
  360. agno/vectordb/redis/redisdb.py +19 -31
  361. agno/vectordb/singlestore/singlestore.py +17 -9
  362. agno/vectordb/surrealdb/surrealdb.py +2 -38
  363. agno/vectordb/weaviate/__init__.py +2 -1
  364. agno/vectordb/weaviate/weaviate.py +7 -3
  365. agno/workflow/__init__.py +5 -1
  366. agno/workflow/agent.py +2 -2
  367. agno/workflow/condition.py +12 -10
  368. agno/workflow/loop.py +28 -9
  369. agno/workflow/parallel.py +21 -13
  370. agno/workflow/remote.py +362 -0
  371. agno/workflow/router.py +12 -9
  372. agno/workflow/step.py +261 -36
  373. agno/workflow/steps.py +12 -8
  374. agno/workflow/types.py +40 -77
  375. agno/workflow/workflow.py +939 -213
  376. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
  377. agno-2.4.3.dist-info/RECORD +677 -0
  378. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
  379. agno/tools/googlesearch.py +0 -98
  380. agno/tools/memori.py +0 -339
  381. agno-2.2.13.dist-info/RECORD +0 -575
  382. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
  383. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
agno/utils/string.py CHANGED
@@ -2,13 +2,15 @@ import hashlib
2
2
  import json
3
3
  import re
4
4
  import uuid
5
- from typing import Optional, Type
5
+ from typing import Any, Optional, Type, Union
6
6
  from uuid import uuid4
7
7
 
8
8
  from pydantic import BaseModel, ValidationError
9
9
 
10
10
  from agno.utils.log import logger
11
11
 
12
+ POSTGRES_INVALID_CHARS_REGEX = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\ufffe\uffff]")
13
+
12
14
 
13
15
  def is_valid_uuid(uuid_str: str) -> bool:
14
16
  """
@@ -87,7 +89,8 @@ def _clean_json_content(content: str) -> str:
87
89
  if "```json" in content:
88
90
  content = content.split("```json")[-1].strip()
89
91
  parts = content.split("```")
90
- parts.pop(-1)
92
+ if len(parts) > 1:
93
+ parts.pop(-1)
91
94
  content = "".join(parts)
92
95
  elif "```" in content:
93
96
  content = content.split("```")[1].strip()
@@ -201,6 +204,52 @@ def parse_response_model_str(content: str, output_schema: Type[BaseModel]) -> Op
201
204
  return structured_output
202
205
 
203
206
 
207
+ def parse_response_dict_str(content: str) -> Optional[dict]:
208
+ """Parse dict from string content, extracting JSON if needed"""
209
+ from agno.utils.reasoning import extract_thinking_content
210
+
211
+ # Handle thinking content b/w <think> tags
212
+ if "</think>" in content:
213
+ reasoning_content, output_content = extract_thinking_content(content)
214
+ if reasoning_content:
215
+ content = output_content
216
+
217
+ # Clean content first to simplify all parsing attempts
218
+ cleaned_content = _clean_json_content(content)
219
+
220
+ try:
221
+ # First attempt: direct JSON parsing on cleaned content
222
+ return json.loads(cleaned_content)
223
+ except json.JSONDecodeError as e:
224
+ logger.warning(f"Failed to parse cleaned JSON: {e}")
225
+
226
+ # Second attempt: Extract individual JSON objects
227
+ candidate_jsons = _extract_json_objects(cleaned_content)
228
+
229
+ if len(candidate_jsons) == 1:
230
+ # Single JSON object - try to parse it directly
231
+ try:
232
+ return json.loads(candidate_jsons[0])
233
+ except json.JSONDecodeError:
234
+ pass
235
+
236
+ if len(candidate_jsons) > 1:
237
+ # Final attempt: Merge multiple JSON objects
238
+ merged_data: dict = {}
239
+ for candidate in candidate_jsons:
240
+ try:
241
+ obj = json.loads(candidate)
242
+ if isinstance(obj, dict):
243
+ merged_data.update(obj)
244
+ except json.JSONDecodeError:
245
+ continue
246
+ if merged_data:
247
+ return merged_data
248
+
249
+ logger.warning("All parsing attempts failed.")
250
+ return None
251
+
252
+
204
253
  def generate_id(seed: Optional[str] = None) -> str:
205
254
  """
206
255
  Generate a deterministic UUID5 based on a seed string.
@@ -229,3 +278,43 @@ def generate_id_from_name(name: Optional[str] = None) -> str:
229
278
  return name.lower().replace(" ", "-").replace("_", "-")
230
279
  else:
231
280
  return str(uuid4())
281
+
282
+
283
+ def sanitize_postgres_string(value: Optional[str]) -> Optional[str]:
284
+ """Remove illegal chars from string values to prevent PostgreSQL encoding errors.
285
+
286
+ This function all chars illegal in Postgres UTF-8 text fields.
287
+ Useful to prevent CharacterNotInRepertoireError when storing strings.
288
+
289
+ Args:
290
+ value: The string value to sanitize.
291
+
292
+ Returns:
293
+ The sanitized string with illegal chars removed, or None if input was None.
294
+ """
295
+ if value is None:
296
+ return None
297
+ if isinstance(value, str):
298
+ return POSTGRES_INVALID_CHARS_REGEX.sub("", value)
299
+
300
+
301
+ def sanitize_postgres_strings(data: Union[dict, list, str, Any]) -> Union[dict, list, str, Any]:
302
+ """Recursively sanitize all string values in a dictionary or JSON structure.
303
+
304
+ This function traverses dictionaries, lists, and nested structures to find
305
+ and sanitize all string values, removing null bytes that PostgreSQL cannot handle.
306
+
307
+ Args:
308
+ data: The data structure to sanitize (dict, list, str or any other type).
309
+
310
+ Returns:
311
+ The sanitized data structure with all strings cleaned of null bytes.
312
+ """
313
+ if isinstance(data, dict):
314
+ return {key: sanitize_postgres_strings(value) for key, value in data.items()}
315
+ elif isinstance(data, list):
316
+ return [sanitize_postgres_strings(item) for item in data]
317
+ elif isinstance(data, str):
318
+ return sanitize_postgres_string(data)
319
+ else:
320
+ return data
agno/utils/team.py CHANGED
@@ -59,7 +59,7 @@ def add_interaction_to_team_run_context(
59
59
  team_run_context: Dict[str, Any],
60
60
  member_name: str,
61
61
  task: str,
62
- run_response: Union[RunOutput, TeamRunOutput],
62
+ run_response: Optional[Union[RunOutput, TeamRunOutput]],
63
63
  ) -> None:
64
64
  if "member_responses" not in team_run_context:
65
65
  team_run_context["member_responses"] = []
@@ -73,14 +73,40 @@ def add_interaction_to_team_run_context(
73
73
  log_debug(f"Updated team run context with member name: {member_name}")
74
74
 
75
75
 
76
- def get_team_member_interactions_str(team_run_context: Dict[str, Any]) -> str:
76
+ def get_team_member_interactions_str(
77
+ team_run_context: Dict[str, Any],
78
+ max_interactions: Optional[int] = None,
79
+ ) -> str:
80
+ """
81
+ Build a string representation of member interactions from the team run context.
82
+
83
+ Args:
84
+ team_run_context: The context containing member responses
85
+ max_interactions: Maximum number of recent interactions to include.
86
+ None means include all interactions.
87
+ If set, only the most recent N interactions are included.
88
+
89
+ Returns:
90
+ A formatted string with member interactions
91
+ """
77
92
  if not team_run_context:
78
93
  return ""
79
94
  team_member_interactions_str = ""
80
95
  if "member_responses" in team_run_context:
81
- team_member_interactions_str += "<member_interaction_context>\nSee below interactions wit other team members.\n"
96
+ member_responses = team_run_context["member_responses"]
97
+
98
+ # If max_interactions is set, only include the most recent N interactions
99
+ if max_interactions is not None and len(member_responses) > max_interactions:
100
+ member_responses = member_responses[-max_interactions:]
82
101
 
83
- for interaction in team_run_context["member_responses"]:
102
+ if not member_responses:
103
+ return ""
104
+
105
+ team_member_interactions_str += (
106
+ "<member_interaction_context>\nSee below interactions with other team members.\n"
107
+ )
108
+
109
+ for interaction in member_responses:
84
110
  response_dict = interaction["run_response"].to_dict()
85
111
  response_content = (
86
112
  response_dict.get("content")
@@ -95,45 +121,69 @@ def get_team_member_interactions_str(team_run_context: Dict[str, Any]) -> str:
95
121
  return team_member_interactions_str
96
122
 
97
123
 
98
- def get_team_run_context_images(team_run_context: Dict[str, Any]) -> List[Image]:
124
+ def get_team_run_context_images(
125
+ team_run_context: Dict[str, Any],
126
+ max_interactions: Optional[int] = None,
127
+ ) -> List[Image]:
99
128
  if not team_run_context:
100
129
  return []
101
130
  images = []
102
131
  if "member_responses" in team_run_context:
103
- for interaction in team_run_context["member_responses"]:
132
+ member_responses = team_run_context["member_responses"]
133
+ if max_interactions is not None and len(member_responses) > max_interactions:
134
+ member_responses = member_responses[-max_interactions:]
135
+ for interaction in member_responses:
104
136
  if interaction["run_response"].images:
105
137
  images.extend(interaction["run_response"].images)
106
138
  return images
107
139
 
108
140
 
109
- def get_team_run_context_videos(team_run_context: Dict[str, Any]) -> List[Video]:
141
+ def get_team_run_context_videos(
142
+ team_run_context: Dict[str, Any],
143
+ max_interactions: Optional[int] = None,
144
+ ) -> List[Video]:
110
145
  if not team_run_context:
111
146
  return []
112
147
  videos = []
113
148
  if "member_responses" in team_run_context:
114
- for interaction in team_run_context["member_responses"]:
149
+ member_responses = team_run_context["member_responses"]
150
+ if max_interactions is not None and len(member_responses) > max_interactions:
151
+ member_responses = member_responses[-max_interactions:]
152
+ for interaction in member_responses:
115
153
  if interaction["run_response"].videos:
116
154
  videos.extend(interaction["run_response"].videos)
117
155
  return videos
118
156
 
119
157
 
120
- def get_team_run_context_audio(team_run_context: Dict[str, Any]) -> List[Audio]:
158
+ def get_team_run_context_audio(
159
+ team_run_context: Dict[str, Any],
160
+ max_interactions: Optional[int] = None,
161
+ ) -> List[Audio]:
121
162
  if not team_run_context:
122
163
  return []
123
164
  audio = []
124
165
  if "member_responses" in team_run_context:
125
- for interaction in team_run_context["member_responses"]:
166
+ member_responses = team_run_context["member_responses"]
167
+ if max_interactions is not None and len(member_responses) > max_interactions:
168
+ member_responses = member_responses[-max_interactions:]
169
+ for interaction in member_responses:
126
170
  if interaction["run_response"].audio:
127
171
  audio.extend(interaction["run_response"].audio)
128
172
  return audio
129
173
 
130
174
 
131
- def get_team_run_context_files(team_run_context: Dict[str, Any]) -> List[File]:
175
+ def get_team_run_context_files(
176
+ team_run_context: Dict[str, Any],
177
+ max_interactions: Optional[int] = None,
178
+ ) -> List[File]:
132
179
  if not team_run_context:
133
180
  return []
134
181
  files = []
135
182
  if "member_responses" in team_run_context:
136
- for interaction in team_run_context["member_responses"]:
183
+ member_responses = team_run_context["member_responses"]
184
+ if max_interactions is not None and len(member_responses) > max_interactions:
185
+ member_responses = member_responses[-max_interactions:]
186
+ for interaction in member_responses:
137
187
  if interaction["run_response"].files:
138
188
  files.extend(interaction["run_response"].files)
139
189
  return files