agno 2.0.1__py3-none-any.whl → 2.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. agno/agent/agent.py +6015 -2823
  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 +594 -186
  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 +2 -8
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +72 -0
  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 +999 -519
  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 +103 -31
  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 +139 -0
  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 +59 -5
  142. agno/models/openai/chat.py +69 -29
  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 +77 -1
  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 -178
  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 +248 -94
  205. agno/run/base.py +44 -5
  206. agno/run/team.py +238 -97
  207. agno/run/workflow.py +144 -33
  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 -1610
  213. agno/tools/dalle.py +2 -4
  214. agno/tools/decorator.py +4 -2
  215. agno/tools/duckduckgo.py +15 -11
  216. agno/tools/e2b.py +14 -7
  217. agno/tools/eleven_labs.py +23 -25
  218. agno/tools/exa.py +21 -16
  219. agno/tools/file.py +153 -23
  220. agno/tools/file_generation.py +350 -0
  221. agno/tools/firecrawl.py +4 -4
  222. agno/tools/function.py +250 -30
  223. agno/tools/gmail.py +238 -14
  224. agno/tools/google_drive.py +270 -0
  225. agno/tools/googlecalendar.py +36 -8
  226. agno/tools/googlesheets.py +20 -5
  227. agno/tools/jira.py +20 -0
  228. agno/tools/knowledge.py +3 -3
  229. agno/tools/mcp/__init__.py +10 -0
  230. agno/tools/mcp/mcp.py +331 -0
  231. agno/tools/mcp/multi_mcp.py +347 -0
  232. agno/tools/mcp/params.py +24 -0
  233. agno/tools/mcp_toolbox.py +284 -0
  234. agno/tools/mem0.py +11 -17
  235. agno/tools/memori.py +1 -53
  236. agno/tools/memory.py +419 -0
  237. agno/tools/models/nebius.py +5 -5
  238. agno/tools/models_labs.py +20 -10
  239. agno/tools/notion.py +204 -0
  240. agno/tools/parallel.py +314 -0
  241. agno/tools/scrapegraph.py +58 -31
  242. agno/tools/searxng.py +2 -2
  243. agno/tools/serper.py +2 -2
  244. agno/tools/slack.py +18 -3
  245. agno/tools/spider.py +2 -2
  246. agno/tools/tavily.py +146 -0
  247. agno/tools/whatsapp.py +1 -1
  248. agno/tools/workflow.py +278 -0
  249. agno/tools/yfinance.py +12 -11
  250. agno/utils/agent.py +820 -0
  251. agno/utils/audio.py +27 -0
  252. agno/utils/common.py +90 -1
  253. agno/utils/events.py +217 -2
  254. agno/utils/gemini.py +180 -22
  255. agno/utils/hooks.py +57 -0
  256. agno/utils/http.py +111 -0
  257. agno/utils/knowledge.py +12 -5
  258. agno/utils/log.py +1 -0
  259. agno/utils/mcp.py +92 -2
  260. agno/utils/media.py +188 -10
  261. agno/utils/merge_dict.py +22 -1
  262. agno/utils/message.py +60 -0
  263. agno/utils/models/claude.py +40 -11
  264. agno/utils/print_response/agent.py +105 -21
  265. agno/utils/print_response/team.py +103 -38
  266. agno/utils/print_response/workflow.py +251 -34
  267. agno/utils/reasoning.py +22 -1
  268. agno/utils/serialize.py +32 -0
  269. agno/utils/streamlit.py +16 -10
  270. agno/utils/string.py +41 -0
  271. agno/utils/team.py +98 -9
  272. agno/utils/tools.py +1 -1
  273. agno/vectordb/base.py +23 -4
  274. agno/vectordb/cassandra/cassandra.py +65 -9
  275. agno/vectordb/chroma/chromadb.py +182 -38
  276. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  277. agno/vectordb/couchbase/couchbase.py +105 -10
  278. agno/vectordb/lancedb/lance_db.py +124 -133
  279. agno/vectordb/langchaindb/langchaindb.py +25 -7
  280. agno/vectordb/lightrag/lightrag.py +17 -3
  281. agno/vectordb/llamaindex/__init__.py +3 -0
  282. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  283. agno/vectordb/milvus/milvus.py +126 -9
  284. agno/vectordb/mongodb/__init__.py +7 -1
  285. agno/vectordb/mongodb/mongodb.py +112 -7
  286. agno/vectordb/pgvector/pgvector.py +142 -21
  287. agno/vectordb/pineconedb/pineconedb.py +80 -8
  288. agno/vectordb/qdrant/qdrant.py +125 -39
  289. agno/vectordb/redis/__init__.py +9 -0
  290. agno/vectordb/redis/redisdb.py +694 -0
  291. agno/vectordb/singlestore/singlestore.py +111 -25
  292. agno/vectordb/surrealdb/surrealdb.py +31 -5
  293. agno/vectordb/upstashdb/upstashdb.py +76 -8
  294. agno/vectordb/weaviate/weaviate.py +86 -15
  295. agno/workflow/__init__.py +2 -0
  296. agno/workflow/agent.py +299 -0
  297. agno/workflow/condition.py +112 -18
  298. agno/workflow/loop.py +69 -10
  299. agno/workflow/parallel.py +266 -118
  300. agno/workflow/router.py +110 -17
  301. agno/workflow/step.py +638 -129
  302. agno/workflow/steps.py +65 -6
  303. agno/workflow/types.py +61 -23
  304. agno/workflow/workflow.py +2085 -272
  305. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
  306. agno-2.3.0.dist-info/RECORD +577 -0
  307. agno/knowledge/reader/url_reader.py +0 -128
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -610
  310. agno/utils/models/aws_claude.py +0 -170
  311. agno-2.0.1.dist-info/RECORD +0 -515
  312. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  313. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,7 @@ from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Tuple, Ty
6
6
  from pydantic import BaseModel
7
7
 
8
8
  from agno.exceptions import AgnoError, ModelProviderError
9
- from agno.models.base import MessageData, Model
9
+ from agno.models.base import Model
10
10
  from agno.models.message import Message
11
11
  from agno.models.metrics import Metrics
12
12
  from agno.models.response import ModelResponse
@@ -360,7 +360,7 @@ class AwsBedrock(Model):
360
360
  formatted_messages, system_message = self._format_messages(messages)
361
361
 
362
362
  tool_config = None
363
- if tools is not None and tools:
363
+ if tools:
364
364
  tool_config = {"tools": self._format_tools_for_request(tools)}
365
365
 
366
366
  body = {
@@ -408,7 +408,7 @@ class AwsBedrock(Model):
408
408
  formatted_messages, system_message = self._format_messages(messages)
409
409
 
410
410
  tool_config = None
411
- if tools is not None and tools:
411
+ if tools:
412
412
  tool_config = {"tools": self._format_tools_for_request(tools)}
413
413
 
414
414
  body = {
@@ -426,10 +426,14 @@ class AwsBedrock(Model):
426
426
 
427
427
  assistant_message.metrics.start_timer()
428
428
 
429
+ # Track current tool being built across chunks
430
+ current_tool: Dict[str, Any] = {}
431
+
429
432
  for chunk in self.get_client().converse_stream(modelId=self.id, messages=formatted_messages, **body)[
430
433
  "stream"
431
434
  ]:
432
- yield self._parse_provider_response_delta(chunk)
435
+ model_response, current_tool = self._parse_provider_response_delta(chunk, current_tool)
436
+ yield model_response
433
437
 
434
438
  assistant_message.metrics.stop_timer()
435
439
 
@@ -456,7 +460,7 @@ class AwsBedrock(Model):
456
460
  formatted_messages, system_message = self._format_messages(messages)
457
461
 
458
462
  tool_config = None
459
- if tools is not None and tools:
463
+ if tools:
460
464
  tool_config = {"tools": self._format_tools_for_request(tools)}
461
465
 
462
466
  body = {
@@ -507,7 +511,7 @@ class AwsBedrock(Model):
507
511
  formatted_messages, system_message = self._format_messages(messages)
508
512
 
509
513
  tool_config = None
510
- if tools is not None and tools:
514
+ if tools:
511
515
  tool_config = {"tools": self._format_tools_for_request(tools)}
512
516
 
513
517
  body = {
@@ -525,10 +529,14 @@ class AwsBedrock(Model):
525
529
 
526
530
  assistant_message.metrics.start_timer()
527
531
 
532
+ # Track current tool being built across chunks
533
+ current_tool: Dict[str, Any] = {}
534
+
528
535
  async with self.get_async_client() as client:
529
536
  response = await client.converse_stream(modelId=self.id, messages=formatted_messages, **body)
530
537
  async for chunk in response["stream"]:
531
- yield self._parse_provider_response_delta(chunk)
538
+ model_response, current_tool = self._parse_provider_response_delta(chunk, current_tool)
539
+ yield model_response
532
540
 
533
541
  assistant_message.metrics.stop_timer()
534
542
 
@@ -617,122 +625,54 @@ class AwsBedrock(Model):
617
625
 
618
626
  return model_response
619
627
 
620
- def process_response_stream(
621
- self,
622
- messages: List[Message],
623
- assistant_message: Message,
624
- stream_data: MessageData,
625
- response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
626
- tools: Optional[List[Dict[str, Any]]] = None,
627
- tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
628
- run_response: Optional[RunOutput] = None,
629
- ) -> Iterator[ModelResponse]:
630
- """
631
- Process the synchronous response stream.
632
-
633
- Args:
634
- messages (List[Message]): The messages to include in the request.
635
- assistant_message (Message): The assistant message.
636
- stream_data (MessageData): The stream data.
637
- """
638
- for response_delta in self.invoke_stream(
639
- messages=messages,
640
- assistant_message=assistant_message,
641
- response_format=response_format,
642
- tools=tools,
643
- tool_choice=tool_choice,
644
- run_response=run_response,
645
- ):
646
- should_yield = False
647
-
648
- if response_delta.content:
649
- stream_data.response_content += response_delta.content
650
- should_yield = True
651
-
652
- if response_delta.tool_calls:
653
- if stream_data.response_tool_calls is None:
654
- stream_data.response_tool_calls = []
655
- stream_data.response_tool_calls.extend(response_delta.tool_calls)
656
- should_yield = True
657
-
658
- if should_yield:
659
- yield response_delta
660
-
661
- async def aprocess_response_stream(
662
- self,
663
- messages: List[Message],
664
- assistant_message: Message,
665
- stream_data: MessageData,
666
- response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
667
- tools: Optional[List[Dict[str, Any]]] = None,
668
- tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
669
- run_response: Optional[RunOutput] = None,
670
- ) -> AsyncIterator[ModelResponse]:
671
- """
672
- Process the asynchronous response stream.
673
-
674
- Args:
675
- messages (List[Message]): The messages to include in the request.
676
- assistant_message (Message): The assistant message.
677
- stream_data (MessageData): The stream data.
678
- """
679
- async for response_delta in self.ainvoke_stream(
680
- messages=messages,
681
- assistant_message=assistant_message,
682
- response_format=response_format,
683
- tools=tools,
684
- tool_choice=tool_choice,
685
- run_response=run_response,
686
- ):
687
- should_yield = False
688
-
689
- if response_delta.content:
690
- stream_data.response_content += response_delta.content
691
- should_yield = True
692
-
693
- if response_delta.tool_calls:
694
- if stream_data.response_tool_calls is None:
695
- stream_data.response_tool_calls = []
696
- stream_data.response_tool_calls.extend(response_delta.tool_calls)
697
- should_yield = True
698
-
699
- if should_yield:
700
- yield response_delta
701
-
702
- self._populate_assistant_message(assistant_message=assistant_message, provider_response=response_delta)
703
-
704
- def _parse_provider_response_delta(self, response_delta: Dict[str, Any]) -> ModelResponse: # type: ignore
628
+ def _parse_provider_response_delta(
629
+ self, response_delta: Dict[str, Any], current_tool: Dict[str, Any]
630
+ ) -> Tuple[ModelResponse, Dict[str, Any]]:
705
631
  """Parse the provider response delta for streaming.
706
632
 
707
633
  Args:
708
634
  response_delta: The streaming response delta from AWS Bedrock
635
+ current_tool: The current tool being built across chunks
709
636
 
710
637
  Returns:
711
- ModelResponse: The parsed model response delta
638
+ Tuple[ModelResponse, Dict[str, Any]]: The parsed model response delta and updated current_tool
712
639
  """
713
640
  model_response = ModelResponse(role="assistant")
714
641
 
715
- # Handle contentBlockDelta - text content
716
- if "contentBlockDelta" in response_delta:
717
- delta = response_delta["contentBlockDelta"]["delta"]
718
- if "text" in delta:
719
- model_response.content = delta["text"]
720
-
721
642
  # Handle contentBlockStart - tool use start
722
- elif "contentBlockStart" in response_delta:
643
+ if "contentBlockStart" in response_delta:
723
644
  start = response_delta["contentBlockStart"]["start"]
724
645
  if "toolUse" in start:
725
- tool_use = start["toolUse"]
726
- model_response.tool_calls = [
727
- {
728
- "id": tool_use.get("toolUseId", ""),
729
- "type": "function",
730
- "function": {
731
- "name": tool_use.get("name", ""),
732
- "arguments": "", # Will be filled in subsequent deltas
733
- },
734
- }
735
- ]
646
+ # Start a new tool
647
+ tool_use_data = start["toolUse"]
648
+ current_tool = {
649
+ "id": tool_use_data.get("toolUseId", ""),
650
+ "type": "function",
651
+ "function": {
652
+ "name": tool_use_data.get("name", ""),
653
+ "arguments": "", # Will be filled in subsequent deltas
654
+ },
655
+ }
656
+
657
+ # Handle contentBlockDelta - text content or tool input
658
+ elif "contentBlockDelta" in response_delta:
659
+ delta = response_delta["contentBlockDelta"]["delta"]
660
+ if "text" in delta:
661
+ model_response.content = delta["text"]
662
+ elif "toolUse" in delta and current_tool:
663
+ # Accumulate tool input
664
+ tool_input = delta["toolUse"].get("input", "")
665
+ if tool_input:
666
+ current_tool["function"]["arguments"] += tool_input
667
+
668
+ # Handle contentBlockStop - tool use complete
669
+ elif "contentBlockStop" in response_delta and current_tool:
670
+ # Tool is complete, add it to model response
671
+ model_response.tool_calls = [current_tool]
672
+ # Track tool_id in extra for format_function_call_results
673
+ model_response.extra = {"tool_ids": [current_tool["id"]]}
674
+ # Reset current_tool for next tool
675
+ current_tool = {}
736
676
 
737
677
  # Handle metadata/usage information
738
678
  elif "metadata" in response_delta or "messageStop" in response_delta:
@@ -740,7 +680,7 @@ class AwsBedrock(Model):
740
680
  if "usage" in body:
741
681
  model_response.response_usage = self._get_metrics(body["usage"])
742
682
 
743
- return model_response
683
+ return model_response, current_tool
744
684
 
745
685
  def _get_metrics(self, response_usage: Dict[str, Any]) -> Metrics:
746
686
  """
agno/models/aws/claude.py CHANGED
@@ -2,6 +2,7 @@ from dataclasses import dataclass
2
2
  from os import getenv
3
3
  from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Type, Union
4
4
 
5
+ import httpx
5
6
  from pydantic import BaseModel
6
7
 
7
8
  from agno.exceptions import ModelProviderError, ModelRateLimitError
@@ -9,8 +10,9 @@ from agno.models.anthropic import Claude as AnthropicClaude
9
10
  from agno.models.message import Message
10
11
  from agno.models.response import ModelResponse
11
12
  from agno.run.agent import RunOutput
13
+ from agno.utils.http import get_default_async_client, get_default_sync_client
12
14
  from agno.utils.log import log_debug, log_error, log_warning
13
- from agno.utils.models.aws_claude import format_messages
15
+ from agno.utils.models.claude import format_messages
14
16
 
15
17
  try:
16
18
  from anthropic import AnthropicBedrock, APIConnectionError, APIStatusError, AsyncAnthropicBedrock, RateLimitError
@@ -99,9 +101,23 @@ class Claude(AnthropicClaude):
99
101
  "aws_region": self.aws_region,
100
102
  }
101
103
 
104
+ if self.timeout is not None:
105
+ client_params["timeout"] = self.timeout
106
+
102
107
  if self.client_params:
103
108
  client_params.update(self.client_params)
104
109
 
110
+ if self.http_client:
111
+ if isinstance(self.http_client, httpx.Client):
112
+ client_params["http_client"] = self.http_client
113
+ else:
114
+ log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
115
+ # Use global sync client when user http_client is invalid
116
+ client_params["http_client"] = get_default_sync_client()
117
+ else:
118
+ # Use global sync client when no custom http_client is provided
119
+ client_params["http_client"] = get_default_sync_client()
120
+
105
121
  self.client = AnthropicBedrock(
106
122
  **client_params, # type: ignore
107
123
  )
@@ -132,9 +148,25 @@ class Claude(AnthropicClaude):
132
148
  "aws_region": self.aws_region,
133
149
  }
134
150
 
151
+ if self.timeout is not None:
152
+ client_params["timeout"] = self.timeout
153
+
135
154
  if self.client_params:
136
155
  client_params.update(self.client_params)
137
156
 
157
+ if self.http_client:
158
+ if isinstance(self.http_client, httpx.AsyncClient):
159
+ client_params["http_client"] = self.http_client
160
+ else:
161
+ log_warning(
162
+ "http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
163
+ )
164
+ # Use global async client when user http_client is invalid
165
+ client_params["http_client"] = get_default_async_client()
166
+ else:
167
+ # Use global async client when no custom http_client is provided
168
+ client_params["http_client"] = get_default_async_client()
169
+
138
170
  self.async_client = AsyncAnthropicBedrock(
139
171
  **client_params, # type: ignore
140
172
  )
@@ -60,6 +60,7 @@ class AzureAIFoundry(Model):
60
60
  stop: Optional[Union[str, List[str]]] = None
61
61
  seed: Optional[int] = None
62
62
  model_extras: Optional[Dict[str, Any]] = None
63
+ strict_output: bool = True # When True, guarantees schema adherence for structured outputs. When False, attempts to follow schema as a guide but may occasionally deviate
63
64
  request_params: Optional[Dict[str, Any]] = None
64
65
  # Client parameters
65
66
  api_key: Optional[str] = None
@@ -116,7 +117,7 @@ class AzureAIFoundry(Model):
116
117
  name=response_format.__name__,
117
118
  schema=response_format.model_json_schema(), # type: ignore
118
119
  description=response_format.__doc__,
119
- strict=True,
120
+ strict=self.strict_output,
120
121
  ),
121
122
  )
122
123
 
@@ -160,7 +161,9 @@ class AzureAIFoundry(Model):
160
161
  Returns:
161
162
  ChatCompletionsClient: An instance of the Azure AI client.
162
163
  """
163
- if self.client:
164
+ # Check if client exists and is not closed
165
+ # Azure's client doesn't have is_closed(), so we check if _client exists
166
+ if self.client and hasattr(self.client, "_client"):
164
167
  return self.client
165
168
 
166
169
  client_params = self._get_client_params()
@@ -174,11 +177,28 @@ class AzureAIFoundry(Model):
174
177
  Returns:
175
178
  AsyncChatCompletionsClient: An instance of the asynchronous Azure AI client.
176
179
  """
180
+ # Check if client exists and is not closed
181
+ # Azure's async client doesn't have is_closed(), so we check if _client exists
182
+ if self.async_client and hasattr(self.async_client, "_client"):
183
+ return self.async_client
184
+
177
185
  client_params = self._get_client_params()
178
186
 
179
187
  self.async_client = AsyncChatCompletionsClient(**client_params)
180
188
  return self.async_client
181
189
 
190
+ def close(self) -> None:
191
+ """Close the synchronous client and clean up resources."""
192
+ if self.client:
193
+ self.client.close()
194
+ self.client = None
195
+
196
+ async def aclose(self) -> None:
197
+ """Close the asynchronous client and clean up resources."""
198
+ if self.async_client:
199
+ await self.async_client.close()
200
+ self.async_client = None
201
+
182
202
  def invoke(
183
203
  self,
184
204
  messages: List[Message],
@@ -236,11 +256,10 @@ class AzureAIFoundry(Model):
236
256
  run_response.metrics.set_time_to_first_token()
237
257
 
238
258
  assistant_message.metrics.start_timer()
239
- async with self.get_async_client() as client:
240
- provider_response = await client.complete(
241
- messages=[format_message(m) for m in messages],
242
- **self.get_request_params(tools=tools, response_format=response_format, tool_choice=tool_choice),
243
- )
259
+ provider_response = await self.get_async_client().complete(
260
+ messages=[format_message(m) for m in messages],
261
+ **self.get_request_params(tools=tools, response_format=response_format, tool_choice=tool_choice),
262
+ )
244
263
  assistant_message.metrics.stop_timer()
245
264
 
246
265
  model_response = self._parse_provider_response(provider_response, response_format=response_format) # type: ignore
@@ -316,14 +335,13 @@ class AzureAIFoundry(Model):
316
335
 
317
336
  assistant_message.metrics.start_timer()
318
337
 
319
- async with self.get_async_client() as client:
320
- async_stream = await client.complete(
321
- messages=[format_message(m) for m in messages],
322
- stream=True,
323
- **self.get_request_params(tools=tools, response_format=response_format, tool_choice=tool_choice),
324
- )
325
- async for chunk in async_stream: # type: ignore
326
- yield self._parse_provider_response_delta(chunk)
338
+ async_stream = await self.get_async_client().complete(
339
+ messages=[format_message(m) for m in messages],
340
+ stream=True,
341
+ **self.get_request_params(tools=tools, response_format=response_format, tool_choice=tool_choice),
342
+ )
343
+ async for chunk in async_stream: # type: ignore
344
+ yield self._parse_provider_response_delta(chunk)
327
345
 
328
346
  assistant_message.metrics.stop_timer()
329
347
 
@@ -5,6 +5,8 @@ from typing import Any, Dict, Optional
5
5
  import httpx
6
6
 
7
7
  from agno.models.openai.like import OpenAILike
8
+ from agno.utils.http import get_default_async_client, get_default_sync_client
9
+ from agno.utils.log import log_warning
8
10
 
9
11
  try:
10
12
  from openai import AsyncAzureOpenAI as AsyncAzureOpenAIClient
@@ -70,7 +72,6 @@ class AzureOpenAI(OpenAILike):
70
72
  "base_url": self.base_url,
71
73
  "azure_ad_token": self.azure_ad_token,
72
74
  "azure_ad_token_provider": self.azure_ad_token_provider,
73
- "http_client": self.http_client,
74
75
  }
75
76
  if self.default_headers is not None:
76
77
  _client_params["default_headers"] = self.default_headers
@@ -95,7 +96,18 @@ class AzureOpenAI(OpenAILike):
95
96
 
96
97
  _client_params: Dict[str, Any] = self._get_client_params()
97
98
 
98
- # -*- Create client
99
+ if self.http_client:
100
+ if isinstance(self.http_client, httpx.Client):
101
+ _client_params["http_client"] = self.http_client
102
+ else:
103
+ log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
104
+ # Use global sync client when user http_client is invalid
105
+ _client_params["http_client"] = get_default_sync_client()
106
+ else:
107
+ # Use global sync client when no custom http_client is provided
108
+ _client_params["http_client"] = get_default_sync_client()
109
+
110
+ # Create client
99
111
  self.client = AzureOpenAIClient(**_client_params)
100
112
  return self.client
101
113
 
@@ -106,18 +118,23 @@ class AzureOpenAI(OpenAILike):
106
118
  Returns:
107
119
  AsyncAzureOpenAIClient: An instance of the asynchronous OpenAI client.
108
120
  """
109
- if self.async_client:
121
+ if self.async_client and not self.async_client.is_closed():
110
122
  return self.async_client
111
123
 
112
124
  _client_params: Dict[str, Any] = self._get_client_params()
113
125
 
114
126
  if self.http_client:
115
- _client_params["http_client"] = self.http_client
127
+ if isinstance(self.http_client, httpx.AsyncClient):
128
+ _client_params["http_client"] = self.http_client
129
+ else:
130
+ log_warning(
131
+ "http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
132
+ )
133
+ # Use global async client when user http_client is invalid
134
+ _client_params["http_client"] = get_default_async_client()
116
135
  else:
117
- # Create a new async HTTP client with custom limits
118
- _client_params["http_client"] = httpx.AsyncClient(
119
- limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100)
120
- )
136
+ # Use global async client when no custom http_client is provided
137
+ _client_params["http_client"] = get_default_async_client()
121
138
 
122
139
  self.async_client = AsyncAzureOpenAIClient(**_client_params)
123
140
  return self.async_client