agno 0.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 (723) hide show
  1. agno/__init__.py +8 -0
  2. agno/agent/__init__.py +44 -5
  3. agno/agent/agent.py +10531 -2975
  4. agno/api/agent.py +14 -53
  5. agno/api/api.py +7 -46
  6. agno/api/evals.py +22 -0
  7. agno/api/os.py +17 -0
  8. agno/api/routes.py +6 -25
  9. agno/api/schemas/__init__.py +9 -0
  10. agno/api/schemas/agent.py +6 -9
  11. agno/api/schemas/evals.py +16 -0
  12. agno/api/schemas/os.py +14 -0
  13. agno/api/schemas/team.py +10 -10
  14. agno/api/schemas/utils.py +21 -0
  15. agno/api/schemas/workflows.py +16 -0
  16. agno/api/settings.py +53 -0
  17. agno/api/team.py +22 -26
  18. agno/api/workflow.py +28 -0
  19. agno/cloud/aws/base.py +214 -0
  20. agno/cloud/aws/s3/__init__.py +2 -0
  21. agno/cloud/aws/s3/api_client.py +43 -0
  22. agno/cloud/aws/s3/bucket.py +195 -0
  23. agno/cloud/aws/s3/object.py +57 -0
  24. agno/compression/__init__.py +3 -0
  25. agno/compression/manager.py +247 -0
  26. agno/culture/__init__.py +3 -0
  27. agno/culture/manager.py +956 -0
  28. agno/db/__init__.py +24 -0
  29. agno/db/async_postgres/__init__.py +3 -0
  30. agno/db/base.py +946 -0
  31. agno/db/dynamo/__init__.py +3 -0
  32. agno/db/dynamo/dynamo.py +2781 -0
  33. agno/db/dynamo/schemas.py +442 -0
  34. agno/db/dynamo/utils.py +743 -0
  35. agno/db/firestore/__init__.py +3 -0
  36. agno/db/firestore/firestore.py +2379 -0
  37. agno/db/firestore/schemas.py +181 -0
  38. agno/db/firestore/utils.py +376 -0
  39. agno/db/gcs_json/__init__.py +3 -0
  40. agno/db/gcs_json/gcs_json_db.py +1791 -0
  41. agno/db/gcs_json/utils.py +228 -0
  42. agno/db/in_memory/__init__.py +3 -0
  43. agno/db/in_memory/in_memory_db.py +1312 -0
  44. agno/db/in_memory/utils.py +230 -0
  45. agno/db/json/__init__.py +3 -0
  46. agno/db/json/json_db.py +1777 -0
  47. agno/db/json/utils.py +230 -0
  48. agno/db/migrations/manager.py +199 -0
  49. agno/db/migrations/v1_to_v2.py +635 -0
  50. agno/db/migrations/versions/v2_3_0.py +938 -0
  51. agno/db/mongo/__init__.py +17 -0
  52. agno/db/mongo/async_mongo.py +2760 -0
  53. agno/db/mongo/mongo.py +2597 -0
  54. agno/db/mongo/schemas.py +119 -0
  55. agno/db/mongo/utils.py +276 -0
  56. agno/db/mysql/__init__.py +4 -0
  57. agno/db/mysql/async_mysql.py +2912 -0
  58. agno/db/mysql/mysql.py +2923 -0
  59. agno/db/mysql/schemas.py +186 -0
  60. agno/db/mysql/utils.py +488 -0
  61. agno/db/postgres/__init__.py +4 -0
  62. agno/db/postgres/async_postgres.py +2579 -0
  63. agno/db/postgres/postgres.py +2870 -0
  64. agno/db/postgres/schemas.py +187 -0
  65. agno/db/postgres/utils.py +442 -0
  66. agno/db/redis/__init__.py +3 -0
  67. agno/db/redis/redis.py +2141 -0
  68. agno/db/redis/schemas.py +159 -0
  69. agno/db/redis/utils.py +346 -0
  70. agno/db/schemas/__init__.py +4 -0
  71. agno/db/schemas/culture.py +120 -0
  72. agno/db/schemas/evals.py +34 -0
  73. agno/db/schemas/knowledge.py +40 -0
  74. agno/db/schemas/memory.py +61 -0
  75. agno/db/singlestore/__init__.py +3 -0
  76. agno/db/singlestore/schemas.py +179 -0
  77. agno/db/singlestore/singlestore.py +2877 -0
  78. agno/db/singlestore/utils.py +384 -0
  79. agno/db/sqlite/__init__.py +4 -0
  80. agno/db/sqlite/async_sqlite.py +2911 -0
  81. agno/db/sqlite/schemas.py +181 -0
  82. agno/db/sqlite/sqlite.py +2908 -0
  83. agno/db/sqlite/utils.py +429 -0
  84. agno/db/surrealdb/__init__.py +3 -0
  85. agno/db/surrealdb/metrics.py +292 -0
  86. agno/db/surrealdb/models.py +334 -0
  87. agno/db/surrealdb/queries.py +71 -0
  88. agno/db/surrealdb/surrealdb.py +1908 -0
  89. agno/db/surrealdb/utils.py +147 -0
  90. agno/db/utils.py +118 -0
  91. agno/eval/__init__.py +24 -0
  92. agno/eval/accuracy.py +666 -276
  93. agno/eval/agent_as_judge.py +861 -0
  94. agno/eval/base.py +29 -0
  95. agno/eval/performance.py +779 -0
  96. agno/eval/reliability.py +241 -62
  97. agno/eval/utils.py +120 -0
  98. agno/exceptions.py +143 -1
  99. agno/filters.py +354 -0
  100. agno/guardrails/__init__.py +6 -0
  101. agno/guardrails/base.py +19 -0
  102. agno/guardrails/openai.py +144 -0
  103. agno/guardrails/pii.py +94 -0
  104. agno/guardrails/prompt_injection.py +52 -0
  105. agno/hooks/__init__.py +3 -0
  106. agno/hooks/decorator.py +164 -0
  107. agno/integrations/discord/__init__.py +3 -0
  108. agno/integrations/discord/client.py +203 -0
  109. agno/knowledge/__init__.py +5 -1
  110. agno/{document → knowledge}/chunking/agentic.py +22 -14
  111. agno/{document → knowledge}/chunking/document.py +2 -2
  112. agno/{document → knowledge}/chunking/fixed.py +7 -6
  113. agno/knowledge/chunking/markdown.py +151 -0
  114. agno/{document → knowledge}/chunking/recursive.py +15 -3
  115. agno/knowledge/chunking/row.py +39 -0
  116. agno/knowledge/chunking/semantic.py +91 -0
  117. agno/knowledge/chunking/strategy.py +165 -0
  118. agno/knowledge/content.py +74 -0
  119. agno/knowledge/document/__init__.py +5 -0
  120. agno/{document → knowledge/document}/base.py +12 -2
  121. agno/knowledge/embedder/__init__.py +5 -0
  122. agno/knowledge/embedder/aws_bedrock.py +343 -0
  123. agno/knowledge/embedder/azure_openai.py +210 -0
  124. agno/{embedder → knowledge/embedder}/base.py +8 -0
  125. agno/knowledge/embedder/cohere.py +323 -0
  126. agno/knowledge/embedder/fastembed.py +62 -0
  127. agno/{embedder → knowledge/embedder}/fireworks.py +1 -1
  128. agno/knowledge/embedder/google.py +258 -0
  129. agno/knowledge/embedder/huggingface.py +94 -0
  130. agno/knowledge/embedder/jina.py +182 -0
  131. agno/knowledge/embedder/langdb.py +22 -0
  132. agno/knowledge/embedder/mistral.py +206 -0
  133. agno/knowledge/embedder/nebius.py +13 -0
  134. agno/knowledge/embedder/ollama.py +154 -0
  135. agno/knowledge/embedder/openai.py +195 -0
  136. agno/knowledge/embedder/sentence_transformer.py +63 -0
  137. agno/{embedder → knowledge/embedder}/together.py +1 -1
  138. agno/knowledge/embedder/vllm.py +262 -0
  139. agno/knowledge/embedder/voyageai.py +165 -0
  140. agno/knowledge/knowledge.py +3006 -0
  141. agno/knowledge/reader/__init__.py +7 -0
  142. agno/knowledge/reader/arxiv_reader.py +81 -0
  143. agno/knowledge/reader/base.py +95 -0
  144. agno/knowledge/reader/csv_reader.py +164 -0
  145. agno/knowledge/reader/docx_reader.py +82 -0
  146. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  147. agno/knowledge/reader/firecrawl_reader.py +201 -0
  148. agno/knowledge/reader/json_reader.py +88 -0
  149. agno/knowledge/reader/markdown_reader.py +137 -0
  150. agno/knowledge/reader/pdf_reader.py +431 -0
  151. agno/knowledge/reader/pptx_reader.py +101 -0
  152. agno/knowledge/reader/reader_factory.py +313 -0
  153. agno/knowledge/reader/s3_reader.py +89 -0
  154. agno/knowledge/reader/tavily_reader.py +193 -0
  155. agno/knowledge/reader/text_reader.py +127 -0
  156. agno/knowledge/reader/web_search_reader.py +325 -0
  157. agno/knowledge/reader/website_reader.py +455 -0
  158. agno/knowledge/reader/wikipedia_reader.py +91 -0
  159. agno/knowledge/reader/youtube_reader.py +78 -0
  160. agno/knowledge/remote_content/remote_content.py +88 -0
  161. agno/knowledge/reranker/__init__.py +3 -0
  162. agno/{reranker → knowledge/reranker}/base.py +1 -1
  163. agno/{reranker → knowledge/reranker}/cohere.py +2 -2
  164. agno/knowledge/reranker/infinity.py +195 -0
  165. agno/knowledge/reranker/sentence_transformer.py +54 -0
  166. agno/knowledge/types.py +39 -0
  167. agno/knowledge/utils.py +234 -0
  168. agno/media.py +439 -95
  169. agno/memory/__init__.py +16 -3
  170. agno/memory/manager.py +1474 -123
  171. agno/memory/strategies/__init__.py +15 -0
  172. agno/memory/strategies/base.py +66 -0
  173. agno/memory/strategies/summarize.py +196 -0
  174. agno/memory/strategies/types.py +37 -0
  175. agno/models/aimlapi/__init__.py +5 -0
  176. agno/models/aimlapi/aimlapi.py +62 -0
  177. agno/models/anthropic/__init__.py +4 -0
  178. agno/models/anthropic/claude.py +960 -496
  179. agno/models/aws/__init__.py +15 -0
  180. agno/models/aws/bedrock.py +686 -451
  181. agno/models/aws/claude.py +190 -183
  182. agno/models/azure/__init__.py +18 -1
  183. agno/models/azure/ai_foundry.py +489 -0
  184. agno/models/azure/openai_chat.py +89 -40
  185. agno/models/base.py +2477 -550
  186. agno/models/cerebras/__init__.py +12 -0
  187. agno/models/cerebras/cerebras.py +565 -0
  188. agno/models/cerebras/cerebras_openai.py +131 -0
  189. agno/models/cohere/__init__.py +4 -0
  190. agno/models/cohere/chat.py +306 -492
  191. agno/models/cometapi/__init__.py +5 -0
  192. agno/models/cometapi/cometapi.py +74 -0
  193. agno/models/dashscope/__init__.py +5 -0
  194. agno/models/dashscope/dashscope.py +90 -0
  195. agno/models/deepinfra/__init__.py +5 -0
  196. agno/models/deepinfra/deepinfra.py +45 -0
  197. agno/models/deepseek/__init__.py +4 -0
  198. agno/models/deepseek/deepseek.py +110 -9
  199. agno/models/fireworks/__init__.py +4 -0
  200. agno/models/fireworks/fireworks.py +19 -22
  201. agno/models/google/__init__.py +3 -7
  202. agno/models/google/gemini.py +1717 -662
  203. agno/models/google/utils.py +22 -0
  204. agno/models/groq/__init__.py +4 -0
  205. agno/models/groq/groq.py +391 -666
  206. agno/models/huggingface/__init__.py +4 -0
  207. agno/models/huggingface/huggingface.py +266 -538
  208. agno/models/ibm/__init__.py +5 -0
  209. agno/models/ibm/watsonx.py +432 -0
  210. agno/models/internlm/__init__.py +3 -0
  211. agno/models/internlm/internlm.py +20 -3
  212. agno/models/langdb/__init__.py +1 -0
  213. agno/models/langdb/langdb.py +60 -0
  214. agno/models/litellm/__init__.py +14 -0
  215. agno/models/litellm/chat.py +503 -0
  216. agno/models/litellm/litellm_openai.py +42 -0
  217. agno/models/llama_cpp/__init__.py +5 -0
  218. agno/models/llama_cpp/llama_cpp.py +22 -0
  219. agno/models/lmstudio/__init__.py +5 -0
  220. agno/models/lmstudio/lmstudio.py +25 -0
  221. agno/models/message.py +361 -39
  222. agno/models/meta/__init__.py +12 -0
  223. agno/models/meta/llama.py +502 -0
  224. agno/models/meta/llama_openai.py +79 -0
  225. agno/models/metrics.py +120 -0
  226. agno/models/mistral/__init__.py +4 -0
  227. agno/models/mistral/mistral.py +293 -393
  228. agno/models/nebius/__init__.py +3 -0
  229. agno/models/nebius/nebius.py +53 -0
  230. agno/models/nexus/__init__.py +3 -0
  231. agno/models/nexus/nexus.py +22 -0
  232. agno/models/nvidia/__init__.py +4 -0
  233. agno/models/nvidia/nvidia.py +22 -3
  234. agno/models/ollama/__init__.py +4 -2
  235. agno/models/ollama/chat.py +257 -492
  236. agno/models/openai/__init__.py +7 -0
  237. agno/models/openai/chat.py +725 -770
  238. agno/models/openai/like.py +16 -2
  239. agno/models/openai/responses.py +1121 -0
  240. agno/models/openrouter/__init__.py +4 -0
  241. agno/models/openrouter/openrouter.py +62 -5
  242. agno/models/perplexity/__init__.py +5 -0
  243. agno/models/perplexity/perplexity.py +203 -0
  244. agno/models/portkey/__init__.py +3 -0
  245. agno/models/portkey/portkey.py +82 -0
  246. agno/models/requesty/__init__.py +5 -0
  247. agno/models/requesty/requesty.py +69 -0
  248. agno/models/response.py +177 -7
  249. agno/models/sambanova/__init__.py +4 -0
  250. agno/models/sambanova/sambanova.py +23 -4
  251. agno/models/siliconflow/__init__.py +5 -0
  252. agno/models/siliconflow/siliconflow.py +42 -0
  253. agno/models/together/__init__.py +4 -0
  254. agno/models/together/together.py +21 -164
  255. agno/models/utils.py +266 -0
  256. agno/models/vercel/__init__.py +3 -0
  257. agno/models/vercel/v0.py +43 -0
  258. agno/models/vertexai/__init__.py +0 -1
  259. agno/models/vertexai/claude.py +190 -0
  260. agno/models/vllm/__init__.py +3 -0
  261. agno/models/vllm/vllm.py +83 -0
  262. agno/models/xai/__init__.py +2 -0
  263. agno/models/xai/xai.py +111 -7
  264. agno/os/__init__.py +3 -0
  265. agno/os/app.py +1027 -0
  266. agno/os/auth.py +244 -0
  267. agno/os/config.py +126 -0
  268. agno/os/interfaces/__init__.py +1 -0
  269. agno/os/interfaces/a2a/__init__.py +3 -0
  270. agno/os/interfaces/a2a/a2a.py +42 -0
  271. agno/os/interfaces/a2a/router.py +249 -0
  272. agno/os/interfaces/a2a/utils.py +924 -0
  273. agno/os/interfaces/agui/__init__.py +3 -0
  274. agno/os/interfaces/agui/agui.py +47 -0
  275. agno/os/interfaces/agui/router.py +147 -0
  276. agno/os/interfaces/agui/utils.py +574 -0
  277. agno/os/interfaces/base.py +25 -0
  278. agno/os/interfaces/slack/__init__.py +3 -0
  279. agno/os/interfaces/slack/router.py +148 -0
  280. agno/os/interfaces/slack/security.py +30 -0
  281. agno/os/interfaces/slack/slack.py +47 -0
  282. agno/os/interfaces/whatsapp/__init__.py +3 -0
  283. agno/os/interfaces/whatsapp/router.py +210 -0
  284. agno/os/interfaces/whatsapp/security.py +55 -0
  285. agno/os/interfaces/whatsapp/whatsapp.py +36 -0
  286. agno/os/mcp.py +293 -0
  287. agno/os/middleware/__init__.py +9 -0
  288. agno/os/middleware/jwt.py +797 -0
  289. agno/os/router.py +258 -0
  290. agno/os/routers/__init__.py +3 -0
  291. agno/os/routers/agents/__init__.py +3 -0
  292. agno/os/routers/agents/router.py +599 -0
  293. agno/os/routers/agents/schema.py +261 -0
  294. agno/os/routers/evals/__init__.py +3 -0
  295. agno/os/routers/evals/evals.py +450 -0
  296. agno/os/routers/evals/schemas.py +174 -0
  297. agno/os/routers/evals/utils.py +231 -0
  298. agno/os/routers/health.py +31 -0
  299. agno/os/routers/home.py +52 -0
  300. agno/os/routers/knowledge/__init__.py +3 -0
  301. agno/os/routers/knowledge/knowledge.py +1008 -0
  302. agno/os/routers/knowledge/schemas.py +178 -0
  303. agno/os/routers/memory/__init__.py +3 -0
  304. agno/os/routers/memory/memory.py +661 -0
  305. agno/os/routers/memory/schemas.py +88 -0
  306. agno/os/routers/metrics/__init__.py +3 -0
  307. agno/os/routers/metrics/metrics.py +190 -0
  308. agno/os/routers/metrics/schemas.py +47 -0
  309. agno/os/routers/session/__init__.py +3 -0
  310. agno/os/routers/session/session.py +997 -0
  311. agno/os/routers/teams/__init__.py +3 -0
  312. agno/os/routers/teams/router.py +512 -0
  313. agno/os/routers/teams/schema.py +257 -0
  314. agno/os/routers/traces/__init__.py +3 -0
  315. agno/os/routers/traces/schemas.py +414 -0
  316. agno/os/routers/traces/traces.py +499 -0
  317. agno/os/routers/workflows/__init__.py +3 -0
  318. agno/os/routers/workflows/router.py +624 -0
  319. agno/os/routers/workflows/schema.py +75 -0
  320. agno/os/schema.py +534 -0
  321. agno/os/scopes.py +469 -0
  322. agno/{playground → os}/settings.py +7 -15
  323. agno/os/utils.py +973 -0
  324. agno/reasoning/anthropic.py +80 -0
  325. agno/reasoning/azure_ai_foundry.py +67 -0
  326. agno/reasoning/deepseek.py +63 -0
  327. agno/reasoning/default.py +97 -0
  328. agno/reasoning/gemini.py +73 -0
  329. agno/reasoning/groq.py +71 -0
  330. agno/reasoning/helpers.py +24 -1
  331. agno/reasoning/ollama.py +67 -0
  332. agno/reasoning/openai.py +86 -0
  333. agno/reasoning/step.py +2 -1
  334. agno/reasoning/vertexai.py +76 -0
  335. agno/run/__init__.py +6 -0
  336. agno/run/agent.py +822 -0
  337. agno/run/base.py +247 -0
  338. agno/run/cancel.py +81 -0
  339. agno/run/requirement.py +181 -0
  340. agno/run/team.py +767 -0
  341. agno/run/workflow.py +708 -0
  342. agno/session/__init__.py +10 -0
  343. agno/session/agent.py +260 -0
  344. agno/session/summary.py +265 -0
  345. agno/session/team.py +342 -0
  346. agno/session/workflow.py +501 -0
  347. agno/table.py +10 -0
  348. agno/team/__init__.py +37 -0
  349. agno/team/team.py +9536 -0
  350. agno/tools/__init__.py +7 -0
  351. agno/tools/agentql.py +120 -0
  352. agno/tools/airflow.py +22 -12
  353. agno/tools/api.py +122 -0
  354. agno/tools/apify.py +276 -83
  355. agno/tools/{arxiv_toolkit.py → arxiv.py} +20 -12
  356. agno/tools/aws_lambda.py +28 -7
  357. agno/tools/aws_ses.py +66 -0
  358. agno/tools/baidusearch.py +11 -4
  359. agno/tools/bitbucket.py +292 -0
  360. agno/tools/brandfetch.py +213 -0
  361. agno/tools/bravesearch.py +106 -0
  362. agno/tools/brightdata.py +367 -0
  363. agno/tools/browserbase.py +209 -0
  364. agno/tools/calcom.py +32 -23
  365. agno/tools/calculator.py +24 -37
  366. agno/tools/cartesia.py +187 -0
  367. agno/tools/{clickup_tool.py → clickup.py} +17 -28
  368. agno/tools/confluence.py +91 -26
  369. agno/tools/crawl4ai.py +139 -43
  370. agno/tools/csv_toolkit.py +28 -22
  371. agno/tools/dalle.py +36 -22
  372. agno/tools/daytona.py +475 -0
  373. agno/tools/decorator.py +169 -14
  374. agno/tools/desi_vocal.py +23 -11
  375. agno/tools/discord.py +32 -29
  376. agno/tools/docker.py +716 -0
  377. agno/tools/duckdb.py +76 -81
  378. agno/tools/duckduckgo.py +43 -40
  379. agno/tools/e2b.py +703 -0
  380. agno/tools/eleven_labs.py +65 -54
  381. agno/tools/email.py +13 -5
  382. agno/tools/evm.py +129 -0
  383. agno/tools/exa.py +324 -42
  384. agno/tools/fal.py +39 -35
  385. agno/tools/file.py +196 -30
  386. agno/tools/file_generation.py +356 -0
  387. agno/tools/financial_datasets.py +288 -0
  388. agno/tools/firecrawl.py +108 -33
  389. agno/tools/function.py +960 -122
  390. agno/tools/giphy.py +34 -12
  391. agno/tools/github.py +1294 -97
  392. agno/tools/gmail.py +922 -0
  393. agno/tools/google_bigquery.py +117 -0
  394. agno/tools/google_drive.py +271 -0
  395. agno/tools/google_maps.py +253 -0
  396. agno/tools/googlecalendar.py +607 -107
  397. agno/tools/googlesheets.py +377 -0
  398. agno/tools/hackernews.py +20 -12
  399. agno/tools/jina.py +24 -14
  400. agno/tools/jira.py +48 -19
  401. agno/tools/knowledge.py +218 -0
  402. agno/tools/linear.py +82 -43
  403. agno/tools/linkup.py +58 -0
  404. agno/tools/local_file_system.py +15 -7
  405. agno/tools/lumalab.py +41 -26
  406. agno/tools/mcp/__init__.py +10 -0
  407. agno/tools/mcp/mcp.py +331 -0
  408. agno/tools/mcp/multi_mcp.py +347 -0
  409. agno/tools/mcp/params.py +24 -0
  410. agno/tools/mcp_toolbox.py +284 -0
  411. agno/tools/mem0.py +193 -0
  412. agno/tools/memory.py +419 -0
  413. agno/tools/mlx_transcribe.py +11 -9
  414. agno/tools/models/azure_openai.py +190 -0
  415. agno/tools/models/gemini.py +203 -0
  416. agno/tools/models/groq.py +158 -0
  417. agno/tools/models/morph.py +186 -0
  418. agno/tools/models/nebius.py +124 -0
  419. agno/tools/models_labs.py +163 -82
  420. agno/tools/moviepy_video.py +18 -13
  421. agno/tools/nano_banana.py +151 -0
  422. agno/tools/neo4j.py +134 -0
  423. agno/tools/newspaper.py +15 -4
  424. agno/tools/newspaper4k.py +19 -6
  425. agno/tools/notion.py +204 -0
  426. agno/tools/openai.py +181 -17
  427. agno/tools/openbb.py +27 -20
  428. agno/tools/opencv.py +321 -0
  429. agno/tools/openweather.py +233 -0
  430. agno/tools/oxylabs.py +385 -0
  431. agno/tools/pandas.py +25 -15
  432. agno/tools/parallel.py +314 -0
  433. agno/tools/postgres.py +238 -185
  434. agno/tools/pubmed.py +125 -13
  435. agno/tools/python.py +48 -35
  436. agno/tools/reasoning.py +283 -0
  437. agno/tools/reddit.py +207 -29
  438. agno/tools/redshift.py +406 -0
  439. agno/tools/replicate.py +69 -26
  440. agno/tools/resend.py +11 -6
  441. agno/tools/scrapegraph.py +179 -19
  442. agno/tools/searxng.py +23 -31
  443. agno/tools/serpapi.py +15 -10
  444. agno/tools/serper.py +255 -0
  445. agno/tools/shell.py +23 -12
  446. agno/tools/shopify.py +1519 -0
  447. agno/tools/slack.py +56 -14
  448. agno/tools/sleep.py +8 -6
  449. agno/tools/spider.py +35 -11
  450. agno/tools/spotify.py +919 -0
  451. agno/tools/sql.py +34 -19
  452. agno/tools/tavily.py +158 -8
  453. agno/tools/telegram.py +18 -8
  454. agno/tools/todoist.py +218 -0
  455. agno/tools/toolkit.py +134 -9
  456. agno/tools/trafilatura.py +388 -0
  457. agno/tools/trello.py +25 -28
  458. agno/tools/twilio.py +18 -9
  459. agno/tools/user_control_flow.py +78 -0
  460. agno/tools/valyu.py +228 -0
  461. agno/tools/visualization.py +467 -0
  462. agno/tools/webbrowser.py +28 -0
  463. agno/tools/webex.py +76 -0
  464. agno/tools/website.py +23 -19
  465. agno/tools/webtools.py +45 -0
  466. agno/tools/whatsapp.py +286 -0
  467. agno/tools/wikipedia.py +28 -19
  468. agno/tools/workflow.py +285 -0
  469. agno/tools/{twitter.py → x.py} +142 -46
  470. agno/tools/yfinance.py +41 -39
  471. agno/tools/youtube.py +34 -17
  472. agno/tools/zendesk.py +15 -5
  473. agno/tools/zep.py +454 -0
  474. agno/tools/zoom.py +86 -37
  475. agno/tracing/__init__.py +12 -0
  476. agno/tracing/exporter.py +157 -0
  477. agno/tracing/schemas.py +276 -0
  478. agno/tracing/setup.py +111 -0
  479. agno/utils/agent.py +938 -0
  480. agno/utils/audio.py +37 -1
  481. agno/utils/certs.py +27 -0
  482. agno/utils/code_execution.py +11 -0
  483. agno/utils/common.py +103 -20
  484. agno/utils/cryptography.py +22 -0
  485. agno/utils/dttm.py +33 -0
  486. agno/utils/events.py +700 -0
  487. agno/utils/functions.py +107 -37
  488. agno/utils/gemini.py +426 -0
  489. agno/utils/hooks.py +171 -0
  490. agno/utils/http.py +185 -0
  491. agno/utils/json_schema.py +159 -37
  492. agno/utils/knowledge.py +36 -0
  493. agno/utils/location.py +19 -0
  494. agno/utils/log.py +221 -8
  495. agno/utils/mcp.py +214 -0
  496. agno/utils/media.py +335 -14
  497. agno/utils/merge_dict.py +22 -1
  498. agno/utils/message.py +77 -2
  499. agno/utils/models/ai_foundry.py +50 -0
  500. agno/utils/models/claude.py +373 -0
  501. agno/utils/models/cohere.py +94 -0
  502. agno/utils/models/llama.py +85 -0
  503. agno/utils/models/mistral.py +100 -0
  504. agno/utils/models/openai_responses.py +140 -0
  505. agno/utils/models/schema_utils.py +153 -0
  506. agno/utils/models/watsonx.py +41 -0
  507. agno/utils/openai.py +257 -0
  508. agno/utils/pickle.py +1 -1
  509. agno/utils/pprint.py +124 -8
  510. agno/utils/print_response/agent.py +930 -0
  511. agno/utils/print_response/team.py +1914 -0
  512. agno/utils/print_response/workflow.py +1668 -0
  513. agno/utils/prompts.py +111 -0
  514. agno/utils/reasoning.py +108 -0
  515. agno/utils/response.py +163 -0
  516. agno/utils/serialize.py +32 -0
  517. agno/utils/shell.py +4 -4
  518. agno/utils/streamlit.py +487 -0
  519. agno/utils/string.py +204 -51
  520. agno/utils/team.py +139 -0
  521. agno/utils/timer.py +9 -2
  522. agno/utils/tokens.py +657 -0
  523. agno/utils/tools.py +19 -1
  524. agno/utils/whatsapp.py +305 -0
  525. agno/utils/yaml_io.py +3 -3
  526. agno/vectordb/__init__.py +2 -0
  527. agno/vectordb/base.py +87 -9
  528. agno/vectordb/cassandra/__init__.py +5 -1
  529. agno/vectordb/cassandra/cassandra.py +383 -27
  530. agno/vectordb/chroma/__init__.py +4 -0
  531. agno/vectordb/chroma/chromadb.py +748 -83
  532. agno/vectordb/clickhouse/__init__.py +7 -1
  533. agno/vectordb/clickhouse/clickhousedb.py +554 -53
  534. agno/vectordb/couchbase/__init__.py +3 -0
  535. agno/vectordb/couchbase/couchbase.py +1446 -0
  536. agno/vectordb/lancedb/__init__.py +5 -0
  537. agno/vectordb/lancedb/lance_db.py +730 -98
  538. agno/vectordb/langchaindb/__init__.py +5 -0
  539. agno/vectordb/langchaindb/langchaindb.py +163 -0
  540. agno/vectordb/lightrag/__init__.py +5 -0
  541. agno/vectordb/lightrag/lightrag.py +388 -0
  542. agno/vectordb/llamaindex/__init__.py +3 -0
  543. agno/vectordb/llamaindex/llamaindexdb.py +166 -0
  544. agno/vectordb/milvus/__init__.py +3 -0
  545. agno/vectordb/milvus/milvus.py +966 -78
  546. agno/vectordb/mongodb/__init__.py +9 -1
  547. agno/vectordb/mongodb/mongodb.py +1175 -172
  548. agno/vectordb/pgvector/__init__.py +8 -0
  549. agno/vectordb/pgvector/pgvector.py +599 -115
  550. agno/vectordb/pineconedb/__init__.py +5 -1
  551. agno/vectordb/pineconedb/pineconedb.py +406 -43
  552. agno/vectordb/qdrant/__init__.py +4 -0
  553. agno/vectordb/qdrant/qdrant.py +914 -61
  554. agno/vectordb/redis/__init__.py +9 -0
  555. agno/vectordb/redis/redisdb.py +682 -0
  556. agno/vectordb/singlestore/__init__.py +8 -1
  557. agno/vectordb/singlestore/singlestore.py +771 -0
  558. agno/vectordb/surrealdb/__init__.py +3 -0
  559. agno/vectordb/surrealdb/surrealdb.py +663 -0
  560. agno/vectordb/upstashdb/__init__.py +5 -0
  561. agno/vectordb/upstashdb/upstashdb.py +718 -0
  562. agno/vectordb/weaviate/__init__.py +8 -0
  563. agno/vectordb/weaviate/index.py +15 -0
  564. agno/vectordb/weaviate/weaviate.py +1009 -0
  565. agno/workflow/__init__.py +23 -1
  566. agno/workflow/agent.py +299 -0
  567. agno/workflow/condition.py +759 -0
  568. agno/workflow/loop.py +756 -0
  569. agno/workflow/parallel.py +853 -0
  570. agno/workflow/router.py +723 -0
  571. agno/workflow/step.py +1564 -0
  572. agno/workflow/steps.py +613 -0
  573. agno/workflow/types.py +556 -0
  574. agno/workflow/workflow.py +4327 -514
  575. agno-2.3.13.dist-info/METADATA +639 -0
  576. agno-2.3.13.dist-info/RECORD +613 -0
  577. {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +1 -1
  578. agno-2.3.13.dist-info/licenses/LICENSE +201 -0
  579. agno/api/playground.py +0 -91
  580. agno/api/schemas/playground.py +0 -22
  581. agno/api/schemas/user.py +0 -22
  582. agno/api/schemas/workspace.py +0 -46
  583. agno/api/user.py +0 -160
  584. agno/api/workspace.py +0 -151
  585. agno/cli/auth_server.py +0 -118
  586. agno/cli/config.py +0 -275
  587. agno/cli/console.py +0 -88
  588. agno/cli/credentials.py +0 -23
  589. agno/cli/entrypoint.py +0 -571
  590. agno/cli/operator.py +0 -355
  591. agno/cli/settings.py +0 -85
  592. agno/cli/ws/ws_cli.py +0 -817
  593. agno/constants.py +0 -13
  594. agno/document/__init__.py +0 -1
  595. agno/document/chunking/semantic.py +0 -47
  596. agno/document/chunking/strategy.py +0 -31
  597. agno/document/reader/__init__.py +0 -1
  598. agno/document/reader/arxiv_reader.py +0 -41
  599. agno/document/reader/base.py +0 -22
  600. agno/document/reader/csv_reader.py +0 -84
  601. agno/document/reader/docx_reader.py +0 -46
  602. agno/document/reader/firecrawl_reader.py +0 -99
  603. agno/document/reader/json_reader.py +0 -43
  604. agno/document/reader/pdf_reader.py +0 -219
  605. agno/document/reader/s3/pdf_reader.py +0 -46
  606. agno/document/reader/s3/text_reader.py +0 -51
  607. agno/document/reader/text_reader.py +0 -41
  608. agno/document/reader/website_reader.py +0 -175
  609. agno/document/reader/youtube_reader.py +0 -50
  610. agno/embedder/__init__.py +0 -1
  611. agno/embedder/azure_openai.py +0 -86
  612. agno/embedder/cohere.py +0 -72
  613. agno/embedder/fastembed.py +0 -37
  614. agno/embedder/google.py +0 -73
  615. agno/embedder/huggingface.py +0 -54
  616. agno/embedder/mistral.py +0 -80
  617. agno/embedder/ollama.py +0 -57
  618. agno/embedder/openai.py +0 -74
  619. agno/embedder/sentence_transformer.py +0 -38
  620. agno/embedder/voyageai.py +0 -64
  621. agno/eval/perf.py +0 -201
  622. agno/file/__init__.py +0 -1
  623. agno/file/file.py +0 -16
  624. agno/file/local/csv.py +0 -32
  625. agno/file/local/txt.py +0 -19
  626. agno/infra/app.py +0 -240
  627. agno/infra/base.py +0 -144
  628. agno/infra/context.py +0 -20
  629. agno/infra/db_app.py +0 -52
  630. agno/infra/resource.py +0 -205
  631. agno/infra/resources.py +0 -55
  632. agno/knowledge/agent.py +0 -230
  633. agno/knowledge/arxiv.py +0 -22
  634. agno/knowledge/combined.py +0 -22
  635. agno/knowledge/csv.py +0 -28
  636. agno/knowledge/csv_url.py +0 -19
  637. agno/knowledge/document.py +0 -20
  638. agno/knowledge/docx.py +0 -30
  639. agno/knowledge/json.py +0 -28
  640. agno/knowledge/langchain.py +0 -71
  641. agno/knowledge/llamaindex.py +0 -66
  642. agno/knowledge/pdf.py +0 -28
  643. agno/knowledge/pdf_url.py +0 -26
  644. agno/knowledge/s3/base.py +0 -60
  645. agno/knowledge/s3/pdf.py +0 -21
  646. agno/knowledge/s3/text.py +0 -23
  647. agno/knowledge/text.py +0 -30
  648. agno/knowledge/website.py +0 -88
  649. agno/knowledge/wikipedia.py +0 -31
  650. agno/knowledge/youtube.py +0 -22
  651. agno/memory/agent.py +0 -392
  652. agno/memory/classifier.py +0 -104
  653. agno/memory/db/__init__.py +0 -1
  654. agno/memory/db/base.py +0 -42
  655. agno/memory/db/mongodb.py +0 -189
  656. agno/memory/db/postgres.py +0 -203
  657. agno/memory/db/sqlite.py +0 -193
  658. agno/memory/memory.py +0 -15
  659. agno/memory/row.py +0 -36
  660. agno/memory/summarizer.py +0 -192
  661. agno/memory/summary.py +0 -19
  662. agno/memory/workflow.py +0 -38
  663. agno/models/google/gemini_openai.py +0 -26
  664. agno/models/ollama/hermes.py +0 -221
  665. agno/models/ollama/tools.py +0 -362
  666. agno/models/vertexai/gemini.py +0 -595
  667. agno/playground/__init__.py +0 -3
  668. agno/playground/async_router.py +0 -421
  669. agno/playground/deploy.py +0 -249
  670. agno/playground/operator.py +0 -92
  671. agno/playground/playground.py +0 -91
  672. agno/playground/schemas.py +0 -76
  673. agno/playground/serve.py +0 -55
  674. agno/playground/sync_router.py +0 -405
  675. agno/reasoning/agent.py +0 -68
  676. agno/run/response.py +0 -112
  677. agno/storage/agent/__init__.py +0 -0
  678. agno/storage/agent/base.py +0 -38
  679. agno/storage/agent/dynamodb.py +0 -350
  680. agno/storage/agent/json.py +0 -92
  681. agno/storage/agent/mongodb.py +0 -228
  682. agno/storage/agent/postgres.py +0 -367
  683. agno/storage/agent/session.py +0 -79
  684. agno/storage/agent/singlestore.py +0 -303
  685. agno/storage/agent/sqlite.py +0 -357
  686. agno/storage/agent/yaml.py +0 -93
  687. agno/storage/workflow/__init__.py +0 -0
  688. agno/storage/workflow/base.py +0 -40
  689. agno/storage/workflow/mongodb.py +0 -233
  690. agno/storage/workflow/postgres.py +0 -366
  691. agno/storage/workflow/session.py +0 -60
  692. agno/storage/workflow/sqlite.py +0 -359
  693. agno/tools/googlesearch.py +0 -88
  694. agno/utils/defaults.py +0 -57
  695. agno/utils/filesystem.py +0 -39
  696. agno/utils/git.py +0 -52
  697. agno/utils/json_io.py +0 -30
  698. agno/utils/load_env.py +0 -19
  699. agno/utils/py_io.py +0 -19
  700. agno/utils/pyproject.py +0 -18
  701. agno/utils/resource_filter.py +0 -31
  702. agno/vectordb/singlestore/s2vectordb.py +0 -390
  703. agno/vectordb/singlestore/s2vectordb2.py +0 -355
  704. agno/workspace/__init__.py +0 -0
  705. agno/workspace/config.py +0 -325
  706. agno/workspace/enums.py +0 -6
  707. agno/workspace/helpers.py +0 -48
  708. agno/workspace/operator.py +0 -758
  709. agno/workspace/settings.py +0 -63
  710. agno-0.1.2.dist-info/LICENSE +0 -375
  711. agno-0.1.2.dist-info/METADATA +0 -502
  712. agno-0.1.2.dist-info/RECORD +0 -352
  713. agno-0.1.2.dist-info/entry_points.txt +0 -3
  714. /agno/{cli → db/migrations}/__init__.py +0 -0
  715. /agno/{cli/ws → db/migrations/versions}/__init__.py +0 -0
  716. /agno/{document/chunking/__init__.py → db/schemas/metrics.py} +0 -0
  717. /agno/{document/reader/s3 → integrations}/__init__.py +0 -0
  718. /agno/{file/local → knowledge/chunking}/__init__.py +0 -0
  719. /agno/{infra → knowledge/remote_content}/__init__.py +0 -0
  720. /agno/{knowledge/s3 → tools/models}/__init__.py +0 -0
  721. /agno/{reranker → utils/models}/__init__.py +0 -0
  722. /agno/{storage → utils/print_response}/__init__.py +0 -0
  723. {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
@@ -1,22 +1,38 @@
1
1
  import json
2
2
  from dataclasses import dataclass
3
- from typing import Any, Dict, Iterator, List, Optional, Tuple
3
+ from os import getenv
4
+ from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Tuple, Type, Union
4
5
 
5
- from agno.aws.api_client import AwsApiClient # type: ignore
6
- from agno.models.base import Model, StreamData
6
+ from pydantic import BaseModel
7
+
8
+ from agno.exceptions import ModelProviderError
9
+ from agno.models.base import Model
7
10
  from agno.models.message import Message
8
- from agno.models.response import ModelResponse, ModelResponseEvent
9
- from agno.utils.log import logger
10
- from agno.utils.timer import Timer
11
- from agno.utils.tools import (
12
- get_function_call_for_tool_call,
13
- )
11
+ from agno.models.metrics import Metrics
12
+ from agno.models.response import ModelResponse
13
+ from agno.run.agent import RunOutput
14
+ from agno.utils.log import log_debug, log_error, log_warning
15
+ from agno.utils.tokens import count_schema_tokens
14
16
 
15
17
  try:
16
- from boto3 import session # noqa: F401
18
+ from boto3 import client as AwsClient
19
+ from boto3.session import Session
20
+ from botocore.exceptions import ClientError
17
21
  except ImportError:
18
- logger.error("`boto3` not installed")
19
- raise
22
+ raise ImportError("`boto3` not installed. Please install using `pip install boto3`")
23
+
24
+ try:
25
+ import aioboto3
26
+
27
+ AIOBOTO3_AVAILABLE = True
28
+ except ImportError:
29
+ aioboto3 = None
30
+ AIOBOTO3_AVAILABLE = False
31
+
32
+
33
+ BEDROCK_SUPPORTED_IMAGE_FORMATS = ["png", "jpeg", "webp", "gif"]
34
+ BEDROCK_SUPPORTED_VIDEO_FORMATS = ["mp4", "mov", "mkv", "webm", "flv", "mpeg", "mpg", "wmv", "three_gp"]
35
+ BEDROCK_SUPPORTED_FILE_FORMATS = ["pdf", "csv", "doc", "docx", "xls", "xlsx", "html", "txt", "md"]
20
36
 
21
37
 
22
38
  @dataclass
@@ -24,524 +40,743 @@ class AwsBedrock(Model):
24
40
  """
25
41
  AWS Bedrock model.
26
42
 
43
+ To use this model, you need to either:
44
+ 1. Set the following environment variables:
45
+ - AWS_ACCESS_KEY_ID
46
+ - AWS_SECRET_ACCESS_KEY
47
+ - AWS_REGION
48
+ 2. Or provide a boto3 Session object
49
+
50
+ For async support, you also need aioboto3 installed:
51
+ pip install aioboto3
52
+
53
+ Not all Bedrock models support all features. See this documentation for more information: https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-supported-models-features.html
54
+
27
55
  Args:
28
56
  aws_region (Optional[str]): The AWS region to use.
29
- aws_profile (Optional[str]): The AWS profile to use.
30
- aws_client (Optional[AwsApiClient]): The AWS client to use.
31
- _bedrock_client (Optional[Any]): The Bedrock client to use.
32
- _bedrock_runtime_client (Optional[Any]): The Bedrock runtime client to use.
57
+ aws_access_key_id (Optional[str]): The AWS access key ID to use.
58
+ aws_secret_access_key (Optional[str]): The AWS secret access key to use.
59
+ aws_sso_auth (Optional[str]): Removes the need for an access and secret access key by leveraging the current profile's authentication
60
+ session (Optional[Session]): A boto3 Session object to use for authentication.
33
61
  """
34
62
 
35
- aws_region: Optional[str] = None
36
- aws_profile: Optional[str] = None
37
- aws_client: Optional[AwsApiClient] = None
63
+ id: str = "mistral.mistral-small-2402-v1:0"
64
+ name: str = "AwsBedrock"
65
+ provider: str = "AwsBedrock"
38
66
 
39
- _bedrock_client: Optional[Any] = None
40
- _bedrock_runtime_client: Optional[Any] = None
67
+ aws_sso_auth: Optional[bool] = False
68
+ aws_region: Optional[str] = None
69
+ aws_access_key_id: Optional[str] = None
70
+ aws_secret_access_key: Optional[str] = None
71
+ session: Optional[Session] = None
72
+
73
+ # Request parameters
74
+ max_tokens: Optional[int] = None
75
+ temperature: Optional[float] = None
76
+ top_p: Optional[float] = None
77
+ stop_sequences: Optional[List[str]] = None
78
+ request_params: Optional[Dict[str, Any]] = None
79
+
80
+ client: Optional[AwsClient] = None
81
+ async_client: Optional[Any] = None
82
+ async_session: Optional[Any] = None
83
+
84
+ def get_client(self) -> AwsClient:
85
+ """
86
+ Get the Bedrock client.
41
87
 
42
- def get_aws_region(self) -> Optional[str]:
43
- # Priority 1: Use aws_region from model
44
- if self.aws_region is not None:
45
- return self.aws_region
88
+ Returns:
89
+ AwsClient: The Bedrock client.
90
+ """
91
+ if self.client is not None:
92
+ return self.client
93
+
94
+ if self.session:
95
+ self.client = self.session.client("bedrock-runtime")
96
+ return self.client
97
+
98
+ self.aws_access_key_id = self.aws_access_key_id or getenv("AWS_ACCESS_KEY_ID")
99
+ self.aws_secret_access_key = self.aws_secret_access_key or getenv("AWS_SECRET_ACCESS_KEY")
100
+ self.aws_region = self.aws_region or getenv("AWS_REGION")
101
+
102
+ if self.aws_sso_auth:
103
+ self.client = AwsClient(service_name="bedrock-runtime", region_name=self.aws_region)
104
+ else:
105
+ if not self.aws_access_key_id or not self.aws_secret_access_key:
106
+ log_error(
107
+ "AWS credentials not found. Please set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables or provide a boto3 session."
108
+ )
46
109
 
47
- # Priority 2: Get aws_region from env
48
- from os import getenv
110
+ self.client = AwsClient(
111
+ service_name="bedrock-runtime",
112
+ region_name=self.aws_region,
113
+ aws_access_key_id=self.aws_access_key_id,
114
+ aws_secret_access_key=self.aws_secret_access_key,
115
+ )
116
+ return self.client
49
117
 
50
- from agno.constants import AWS_REGION_ENV_VAR
118
+ def get_async_client(self):
119
+ """
120
+ Get the async Bedrock client context manager.
51
121
 
52
- aws_region_env = getenv(AWS_REGION_ENV_VAR)
53
- if aws_region_env is not None:
54
- self.aws_region = aws_region_env
55
- return self.aws_region
122
+ Returns:
123
+ The async Bedrock client context manager.
124
+ """
125
+ if not AIOBOTO3_AVAILABLE:
126
+ raise ImportError(
127
+ "`aioboto3` not installed. Please install using `pip install aioboto3` for async support."
128
+ )
56
129
 
57
- def get_aws_profile(self) -> Optional[str]:
58
- # Priority 1: Use aws_region from resource
59
- if self.aws_profile is not None:
60
- return self.aws_profile
130
+ if self.async_session is None:
131
+ self.aws_access_key_id = self.aws_access_key_id or getenv("AWS_ACCESS_KEY_ID")
132
+ self.aws_secret_access_key = self.aws_secret_access_key or getenv("AWS_SECRET_ACCESS_KEY")
133
+ self.aws_region = self.aws_region or getenv("AWS_REGION")
61
134
 
62
- # Priority 2: Get aws_profile from env
63
- from os import getenv
135
+ self.async_session = aioboto3.Session()
64
136
 
65
- from agno.constants import AWS_PROFILE_ENV_VAR
137
+ client_kwargs = {
138
+ "service_name": "bedrock-runtime",
139
+ "region_name": self.aws_region,
140
+ }
66
141
 
67
- aws_profile_env = getenv(AWS_PROFILE_ENV_VAR)
68
- if aws_profile_env is not None:
69
- self.aws_profile = aws_profile_env
70
- return self.aws_profile
142
+ if self.aws_sso_auth:
143
+ pass
144
+ else:
145
+ if not self.aws_access_key_id or not self.aws_secret_access_key:
146
+ import os
147
+
148
+ env_access_key = os.environ.get("AWS_ACCESS_KEY_ID")
149
+ env_secret_key = os.environ.get("AWS_SECRET_ACCESS_KEY")
150
+ env_region = os.environ.get("AWS_REGION")
151
+
152
+ if env_access_key and env_secret_key:
153
+ self.aws_access_key_id = env_access_key
154
+ self.aws_secret_access_key = env_secret_key
155
+ if env_region:
156
+ self.aws_region = env_region
157
+ client_kwargs["region_name"] = self.aws_region
158
+
159
+ if self.aws_access_key_id and self.aws_secret_access_key:
160
+ client_kwargs.update(
161
+ {
162
+ "aws_access_key_id": self.aws_access_key_id,
163
+ "aws_secret_access_key": self.aws_secret_access_key,
164
+ }
165
+ )
71
166
 
72
- def get_aws_client(self) -> AwsApiClient:
73
- if self.aws_client is not None:
74
- return self.aws_client
167
+ return self.async_session.client(**client_kwargs)
75
168
 
76
- self.aws_client = AwsApiClient(aws_region=self.get_aws_region(), aws_profile=self.get_aws_profile())
77
- return self.aws_client
169
+ def _format_tools_for_request(self, tools: Optional[List[Dict[str, Any]]]) -> List[Dict[str, Any]]:
170
+ """
171
+ Format the tools for the request.
78
172
 
79
- @property
80
- def bedrock_runtime_client(self):
81
- if self._bedrock_runtime_client is not None:
82
- return self._bedrock_runtime_client
173
+ Returns:
174
+ List[Dict[str, Any]]: The formatted tools.
175
+ """
176
+ parsed_tools = []
177
+ if tools is not None:
178
+ for tool_def in tools:
179
+ func_def = tool_def.get("function", {})
180
+ properties = {}
181
+ required = []
182
+
183
+ for param_name, param_info in func_def.get("parameters", {}).get("properties", {}).items():
184
+ properties[param_name] = param_info.copy()
185
+
186
+ if "description" not in properties[param_name]:
187
+ properties[param_name]["description"] = ""
188
+
189
+ if "null" not in (
190
+ param_info.get("type") if isinstance(param_info.get("type"), list) else [param_info.get("type")]
191
+ ):
192
+ required.append(param_name)
193
+
194
+ parsed_tools.append(
195
+ {
196
+ "toolSpec": {
197
+ "name": func_def.get("name") or "",
198
+ "description": func_def.get("description") or "",
199
+ "inputSchema": {"json": {"type": "object", "properties": properties, "required": required}},
200
+ }
201
+ }
202
+ )
83
203
 
84
- boto3_session: session = self.get_aws_client().boto3_session
85
- self._bedrock_runtime_client = boto3_session.client(service_name="bedrock-runtime")
86
- return self._bedrock_runtime_client
204
+ return parsed_tools
87
205
 
88
- def invoke(self, body: Dict[str, Any]) -> Dict[str, Any]:
206
+ def _get_inference_config(self) -> Dict[str, Any]:
89
207
  """
90
- Invoke the Bedrock API.
91
-
92
- Args:
93
- body (Dict[str, Any]): The request body.
208
+ Get the inference config.
94
209
 
95
210
  Returns:
96
- Dict[str, Any]: The response from the Bedrock API.
211
+ Dict[str, Any]: The inference config.
97
212
  """
98
- return self.bedrock_runtime_client.converse(**body)
99
-
100
- def invoke_stream(self, body: Dict[str, Any]) -> Iterator[Dict[str, Any]]:
213
+ request_kwargs = {
214
+ "maxTokens": self.max_tokens,
215
+ "temperature": self.temperature,
216
+ "topP": self.top_p,
217
+ "stopSequences": self.stop_sequences,
218
+ }
219
+
220
+ return {k: v for k, v in request_kwargs.items() if v is not None}
221
+
222
+ def _format_messages(
223
+ self, messages: List[Message], compress_tool_results: bool = False
224
+ ) -> Tuple[List[Dict[str, Any]], Optional[List[Dict[str, Any]]]]:
101
225
  """
102
- Invoke the Bedrock API with streaming.
226
+ Format the messages for the request.
103
227
 
104
228
  Args:
105
- body (Dict[str, Any]): The request body.
229
+ messages: List of messages to format
230
+ compress_tool_results: Whether to compress tool results
106
231
 
107
232
  Returns:
108
- Iterator[Dict[str, Any]]: The streamed response.
233
+ Tuple[List[Dict[str, Any]], Optional[List[Dict[str, Any]]]]: The formatted messages.
109
234
  """
110
- response = self.bedrock_runtime_client.converse_stream(**body)
111
- stream = response.get("stream")
112
- if stream:
113
- for event in stream:
114
- yield event
115
-
116
- def create_assistant_message(self, request_body: Dict[str, Any]) -> Message:
117
- raise NotImplementedError("Please use a subclass of AwsBedrock")
118
-
119
- def get_request_body(self, messages: List[Message]) -> Dict[str, Any]:
120
- raise NotImplementedError("Please use a subclass of AwsBedrock")
121
-
122
- def parse_response_message(self, response: Dict[str, Any]) -> Dict[str, Any]:
123
- raise NotImplementedError("Please use a subclass of AwsBedrock")
124
-
125
- def _create_tool_calls(
126
- self, stop_reason: str, parsed_response: Dict[str, Any]
127
- ) -> Tuple[List[str], List[Dict[str, Any]]]:
128
- tool_ids: List[str] = []
129
- tool_calls: List[Dict[str, Any]] = []
130
-
131
- if stop_reason == "tool_use":
132
- tool_requests = parsed_response.get("tool_requests")
133
- if tool_requests:
134
- for tool in tool_requests:
135
- if "toolUse" in tool:
136
- tool_use = tool["toolUse"]
137
- tool_id = tool_use["toolUseId"]
138
- tool_name = tool_use["name"]
139
- tool_args = tool_use["input"]
140
235
 
141
- tool_ids.append(tool_id)
142
- tool_calls.append(
236
+ formatted_messages: List[Dict[str, Any]] = []
237
+ system_message = None
238
+ for message in messages:
239
+ if message.role == "system":
240
+ system_message = [{"text": message.content}]
241
+ elif message.role == "tool":
242
+ content = message.get_content(use_compressed_content=compress_tool_results)
243
+ tool_result = {
244
+ "toolUseId": message.tool_call_id,
245
+ "content": [{"json": {"result": content}}],
246
+ }
247
+ formatted_message: Dict[str, Any] = {"role": "user", "content": [{"toolResult": tool_result}]}
248
+ formatted_messages.append(formatted_message)
249
+ else:
250
+ formatted_message = {"role": message.role, "content": []}
251
+ if isinstance(message.content, list):
252
+ formatted_message["content"].extend(message.content)
253
+ elif message.tool_calls:
254
+ tool_use_content = []
255
+ for tool_call in message.tool_calls:
256
+ try:
257
+ # Parse arguments with error handling for empty or invalid JSON
258
+ arguments = tool_call["function"]["arguments"]
259
+ if not arguments or arguments.strip() == "":
260
+ tool_input = {}
261
+ else:
262
+ tool_input = json.loads(arguments)
263
+ except (json.JSONDecodeError, KeyError) as e:
264
+ log_warning(f"Failed to parse tool call arguments: {e}")
265
+ tool_input = {}
266
+
267
+ tool_use_content.append(
143
268
  {
144
- "type": "function",
145
- "function": {
146
- "name": tool_name,
147
- "arguments": json.dumps(tool_args),
148
- },
269
+ "toolUse": {
270
+ "toolUseId": tool_call["id"],
271
+ "name": tool_call["function"]["name"],
272
+ "input": tool_input,
273
+ }
274
+ }
275
+ )
276
+ formatted_message["content"].extend(tool_use_content)
277
+ else:
278
+ formatted_message["content"].append({"text": message.content})
279
+
280
+ if message.images:
281
+ for image in message.images:
282
+ if not image.content:
283
+ raise ValueError("Image content is required for AWS Bedrock.")
284
+ if not image.format:
285
+ raise ValueError("Image format is required for AWS Bedrock.")
286
+
287
+ if image.format not in BEDROCK_SUPPORTED_IMAGE_FORMATS:
288
+ raise ValueError(
289
+ f"Unsupported image format: {image.format}. "
290
+ f"Supported formats: {BEDROCK_SUPPORTED_IMAGE_FORMATS}"
291
+ )
292
+
293
+ formatted_message["content"].append(
294
+ {
295
+ "image": {
296
+ "format": image.format,
297
+ "source": {
298
+ "bytes": image.content,
299
+ },
300
+ }
301
+ }
302
+ )
303
+ if message.audio:
304
+ log_warning("Audio input is currently unsupported.")
305
+
306
+ if message.videos:
307
+ for video in message.videos:
308
+ if not video.content:
309
+ raise ValueError("Video content is required for AWS Bedrock.")
310
+ if not video.format:
311
+ raise ValueError("Video format is required for AWS Bedrock.")
312
+
313
+ if video.format not in BEDROCK_SUPPORTED_VIDEO_FORMATS:
314
+ raise ValueError(
315
+ f"Unsupported video format: {video.format}. "
316
+ f"Supported formats: {BEDROCK_SUPPORTED_VIDEO_FORMATS}"
317
+ )
318
+
319
+ formatted_message["content"].append(
320
+ {
321
+ "video": {
322
+ "format": video.format,
323
+ "source": {
324
+ "bytes": video.content,
325
+ },
326
+ }
149
327
  }
150
328
  )
151
329
 
152
- return tool_ids, tool_calls
330
+ if message.files:
331
+ for file in message.files:
332
+ if not file.content:
333
+ raise ValueError("File content is required for AWS Bedrock document input.")
334
+ if not file.format:
335
+ raise ValueError("File format is required for AWS Bedrock document input.")
336
+ if not file.name:
337
+ raise ValueError("File name is required for AWS Bedrock document input.")
338
+
339
+ if file.format not in BEDROCK_SUPPORTED_FILE_FORMATS:
340
+ raise ValueError(
341
+ f"Unsupported file format: {file.format}. "
342
+ f"Supported formats: {BEDROCK_SUPPORTED_FILE_FORMATS}"
343
+ )
344
+
345
+ formatted_message["content"].append(
346
+ {
347
+ "document": {
348
+ "format": file.format,
349
+ "name": file.name,
350
+ "source": {
351
+ "bytes": file.content,
352
+ },
353
+ }
354
+ }
355
+ )
153
356
 
154
- def _handle_tool_calls(
155
- self, assistant_message: Message, messages: List[Message], model_response: ModelResponse, tool_ids
156
- ) -> Optional[ModelResponse]:
357
+ formatted_messages.append(formatted_message)
358
+ # TODO: Add caching: https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-call.html
359
+ return formatted_messages, system_message
360
+
361
+ def count_tokens(
362
+ self,
363
+ messages: List[Message],
364
+ tools: Optional[List[Dict[str, Any]]] = None,
365
+ output_schema: Optional[Union[Dict, Type[BaseModel]]] = None,
366
+ ) -> int:
367
+ try:
368
+ formatted_messages, system_message = self._format_messages(messages, compress_tool_results=True)
369
+ converse_input: Dict[str, Any] = {"messages": formatted_messages}
370
+ if system_message:
371
+ converse_input["system"] = system_message
372
+
373
+ response = self.get_client().count_tokens(modelId=self.id, input={"converse": converse_input})
374
+ tokens = response.get("inputTokens", 0)
375
+
376
+ # Count tool tokens
377
+ if tools:
378
+ from agno.utils.tokens import count_tool_tokens
379
+
380
+ tokens += count_tool_tokens(tools, self.id)
381
+
382
+ # Count schema tokens
383
+ tokens += count_schema_tokens(output_schema, self.id)
384
+
385
+ return tokens
386
+ except Exception as e:
387
+ log_warning(f"Failed to count tokens via Bedrock API: {e}")
388
+ return super().count_tokens(messages, tools, output_schema)
389
+
390
+ async def acount_tokens(
391
+ self,
392
+ messages: List[Message],
393
+ tools: Optional[List[Dict[str, Any]]] = None,
394
+ output_schema: Optional[Union[Dict, Type[BaseModel]]] = None,
395
+ ) -> int:
396
+ try:
397
+ formatted_messages, system_message = self._format_messages(messages, compress_tool_results=True)
398
+ converse_input: Dict[str, Any] = {"messages": formatted_messages}
399
+ if system_message:
400
+ converse_input["system"] = system_message
401
+
402
+ async with self.get_async_client() as client:
403
+ response = await client.count_tokens(modelId=self.id, input={"converse": converse_input})
404
+ tokens = response.get("inputTokens", 0)
405
+
406
+ # Count tool tokens
407
+ if tools:
408
+ from agno.utils.tokens import count_tool_tokens
409
+
410
+ tokens += count_tool_tokens(tools, self.id)
411
+
412
+ # Count schema tokens
413
+ tokens += count_schema_tokens(output_schema, self.id)
414
+
415
+ return tokens
416
+ except Exception as e:
417
+ log_warning(f"Failed to count tokens via Bedrock API: {e}")
418
+ return await super().acount_tokens(messages, tools, output_schema)
419
+
420
+ def invoke(
421
+ self,
422
+ messages: List[Message],
423
+ assistant_message: Message,
424
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
425
+ tools: Optional[List[Dict[str, Any]]] = None,
426
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
427
+ run_response: Optional[RunOutput] = None,
428
+ compress_tool_results: bool = False,
429
+ ) -> ModelResponse:
430
+ """
431
+ Invoke the Bedrock API.
157
432
  """
158
- Handle tool calls in the assistant message.
433
+ try:
434
+ formatted_messages, system_message = self._format_messages(messages, compress_tool_results)
159
435
 
160
- Args:
161
- assistant_message (Message): The assistant message.
162
- messages (List[Message]): The list of messages.
163
- model_response (ModelResponse): The model response.
436
+ tool_config = None
437
+ if tools:
438
+ tool_config = {"tools": self._format_tools_for_request(tools)}
164
439
 
165
- Returns:
166
- Optional[ModelResponse]: The model response after handling tool calls.
167
- """
168
- # -*- Parse and run function call
169
- if assistant_message.tool_calls is not None:
170
- if model_response.tool_calls is None:
171
- model_response.tool_calls = []
440
+ body = {
441
+ "system": system_message,
442
+ "toolConfig": tool_config,
443
+ "inferenceConfig": self._get_inference_config(),
444
+ }
445
+ body = {k: v for k, v in body.items() if v is not None}
172
446
 
173
- # Remove the tool call from the response content
174
- model_response.content = ""
175
- tool_role: str = "tool"
176
- function_calls_to_run: List[Any] = []
177
- function_call_results: List[Message] = []
178
- for tool_call in assistant_message.tool_calls:
179
- _tool_call_id = tool_call.get("id")
180
- _function_call = get_function_call_for_tool_call(tool_call, self._functions)
181
- if _function_call is None:
182
- messages.append(
183
- Message(
184
- role="tool",
185
- tool_call_id=_tool_call_id,
186
- content="Could not find function to call.",
187
- )
188
- )
189
- continue
190
- if _function_call.error is not None:
191
- messages.append(
192
- Message(
193
- role="tool",
194
- tool_call_id=_tool_call_id,
195
- content=_function_call.error,
196
- )
197
- )
198
- continue
199
- function_calls_to_run.append(_function_call)
200
-
201
- if self.show_tool_calls:
202
- model_response.content += "\nRunning:"
203
- for _f in function_calls_to_run:
204
- model_response.content += f"\n - {_f.get_call_str()}"
205
- model_response.content += "\n\n"
206
-
207
- for function_call_response in self.run_function_calls(
208
- function_calls=function_calls_to_run, function_call_results=function_call_results, tool_role=tool_role
209
- ):
210
- if (
211
- function_call_response.event == ModelResponseEvent.tool_call_completed.value
212
- and function_call_response.tool_calls is not None
213
- ):
214
- model_response.tool_calls.extend(function_call_response.tool_calls)
215
-
216
- if len(function_call_results) > 0:
217
- fc_responses: List = []
218
-
219
- for _fc_message_index, _fc_message in enumerate(function_call_results):
220
- tool_result = {
221
- "toolUseId": tool_ids[_fc_message_index],
222
- "content": [{"json": json.dumps(_fc_message.content)}],
223
- }
224
- tool_result_message = {"role": "user", "content": json.dumps([{"toolResult": tool_result}])}
225
- fc_responses.append(tool_result_message)
447
+ if self.request_params:
448
+ log_debug(f"Calling {self.provider} with request parameters: {self.request_params}", log_level=2)
449
+ body.update(**self.request_params)
226
450
 
227
- logger.debug(f"Tool call responses: {fc_responses}")
228
- messages.append(Message(role="user", content=json.dumps(fc_responses)))
451
+ if run_response and run_response.metrics:
452
+ run_response.metrics.set_time_to_first_token()
453
+
454
+ assistant_message.metrics.start_timer()
455
+ response = self.get_client().converse(modelId=self.id, messages=formatted_messages, **body)
456
+ assistant_message.metrics.stop_timer()
457
+
458
+ model_response = self._parse_provider_response(response, response_format=response_format)
229
459
 
230
460
  return model_response
231
- return None
232
461
 
233
- def _update_metrics(self, assistant_message, parsed_response, response_timer):
462
+ except ClientError as e:
463
+ log_error(f"Unexpected error calling Bedrock API: {str(e)}")
464
+ raise ModelProviderError(message=str(e.response), model_name=self.name, model_id=self.id) from e
465
+ except Exception as e:
466
+ log_error(f"Unexpected error calling Bedrock API: {str(e)}")
467
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
468
+
469
+ def invoke_stream(
470
+ self,
471
+ messages: List[Message],
472
+ assistant_message: Message,
473
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
474
+ tools: Optional[List[Dict[str, Any]]] = None,
475
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
476
+ run_response: Optional[RunOutput] = None,
477
+ compress_tool_results: bool = False,
478
+ ) -> Iterator[ModelResponse]:
234
479
  """
235
- Update usage metrics in assistant_message and self.metrics based on the parsed_response.
236
-
237
- Args:
238
- assistant_message: The assistant's message object where individual metrics are stored.
239
- parsed_response: The parsed response containing usage metrics.
240
- response_timer: Timer object that has the elapsed time of the response.
480
+ Invoke the Bedrock API with streaming.
241
481
  """
242
- # Add response time to metrics
243
- assistant_message.metrics["time"] = response_timer.elapsed
244
- if "response_times" not in self.metrics:
245
- self.metrics["response_times"] = []
246
- self.metrics["response_times"].append(response_timer.elapsed)
247
-
248
- # Add token usage to metrics
249
- usage = parsed_response.get("usage", {})
250
- prompt_tokens = usage.get("inputTokens")
251
- completion_tokens = usage.get("outputTokens")
252
- total_tokens = usage.get("totalTokens")
253
-
254
- if prompt_tokens is not None:
255
- assistant_message.metrics["prompt_tokens"] = prompt_tokens
256
- self.metrics["prompt_tokens"] = self.metrics.get("prompt_tokens", 0) + prompt_tokens
257
-
258
- if completion_tokens is not None:
259
- assistant_message.metrics["completion_tokens"] = completion_tokens
260
- self.metrics["completion_tokens"] = self.metrics.get("completion_tokens", 0) + completion_tokens
261
-
262
- if total_tokens is not None:
263
- assistant_message.metrics["total_tokens"] = total_tokens
264
- self.metrics["total_tokens"] = self.metrics.get("total_tokens", 0) + total_tokens
265
-
266
- def response(self, messages: List[Message]) -> ModelResponse:
482
+ try:
483
+ formatted_messages, system_message = self._format_messages(messages, compress_tool_results)
484
+
485
+ tool_config = None
486
+ if tools:
487
+ tool_config = {"tools": self._format_tools_for_request(tools)}
488
+
489
+ body = {
490
+ "system": system_message,
491
+ "toolConfig": tool_config,
492
+ "inferenceConfig": self._get_inference_config(),
493
+ }
494
+ body = {k: v for k, v in body.items() if v is not None}
495
+
496
+ if self.request_params:
497
+ body.update(**self.request_params)
498
+
499
+ if run_response and run_response.metrics:
500
+ run_response.metrics.set_time_to_first_token()
501
+
502
+ assistant_message.metrics.start_timer()
503
+
504
+ # Track current tool being built across chunks
505
+ current_tool: Dict[str, Any] = {}
506
+
507
+ for chunk in self.get_client().converse_stream(modelId=self.id, messages=formatted_messages, **body)[
508
+ "stream"
509
+ ]:
510
+ model_response, current_tool = self._parse_provider_response_delta(chunk, current_tool)
511
+ yield model_response
512
+
513
+ assistant_message.metrics.stop_timer()
514
+
515
+ except ClientError as e:
516
+ log_error(f"Unexpected error calling Bedrock API: {str(e)}")
517
+ raise ModelProviderError(message=str(e.response), model_name=self.name, model_id=self.id) from e
518
+ except Exception as e:
519
+ log_error(f"Unexpected error calling Bedrock API: {str(e)}")
520
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
521
+
522
+ async def ainvoke(
523
+ self,
524
+ messages: List[Message],
525
+ assistant_message: Message,
526
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
527
+ tools: Optional[List[Dict[str, Any]]] = None,
528
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
529
+ run_response: Optional[RunOutput] = None,
530
+ compress_tool_results: bool = False,
531
+ ) -> ModelResponse:
267
532
  """
268
- Generate a response from the Bedrock API.
269
-
270
- Args:
271
- messages (List[Message]): The messages to include in the request.
272
-
273
- Returns:
274
- ModelResponse: The response from the Bedrock API.
533
+ Async invoke the Bedrock API.
275
534
  """
276
- logger.debug("---------- Bedrock Response Start ----------")
277
- self._log_messages(messages)
278
- model_response = ModelResponse()
535
+ try:
536
+ formatted_messages, system_message = self._format_messages(messages, compress_tool_results)
279
537
 
280
- # Invoke the Bedrock API
281
- response_timer = Timer()
282
- response_timer.start()
283
- body = self.get_request_body(messages)
284
- response: Dict[str, Any] = self.invoke(body=body)
285
- response_timer.stop()
286
-
287
- # Parse response
288
- parsed_response = self.parse_response_message(response)
289
- logger.debug(f"Parsed response: {parsed_response}")
290
- stop_reason = parsed_response["stop_reason"]
291
-
292
- # Create assistant message
293
- assistant_message = self.create_assistant_message(parsed_response)
294
-
295
- # Update usage metrics using the new function
296
- self._update_metrics(assistant_message, parsed_response, response_timer)
297
-
298
- # Add assistant message to messages
299
- messages.append(assistant_message)
300
- assistant_message.log()
301
-
302
- # Create tool calls if needed
303
- tool_ids, tool_calls = self._create_tool_calls(stop_reason, parsed_response)
304
-
305
- # Handle tool calls
306
- if stop_reason == "tool_use" and tool_calls:
307
- assistant_message.content = parsed_response["tool_requests"][0]["text"]
308
- assistant_message.tool_calls = tool_calls
309
-
310
- # Run tool calls
311
- if self._handle_tool_calls(assistant_message, messages, model_response, tool_ids):
312
- response_after_tool_calls = self.response(messages=messages)
313
- if response_after_tool_calls.content is not None:
314
- if model_response.content is None:
315
- model_response.content = ""
316
- model_response.content += response_after_tool_calls.content
317
- return model_response
538
+ tool_config = None
539
+ if tools:
540
+ tool_config = {"tools": self._format_tools_for_request(tools)}
318
541
 
319
- # Add assistant message content to model response
320
- if assistant_message.content is not None:
321
- model_response.content = assistant_message.get_content_string()
542
+ body = {
543
+ "system": system_message,
544
+ "toolConfig": tool_config,
545
+ "inferenceConfig": self._get_inference_config(),
546
+ }
547
+ body = {k: v for k, v in body.items() if v is not None}
322
548
 
323
- logger.debug("---------- AWS Response End ----------")
324
- return model_response
549
+ if self.request_params:
550
+ log_debug(f"Calling {self.provider} with request parameters: {self.request_params}", log_level=2)
551
+ body.update(**self.request_params)
325
552
 
326
- def _handle_stream_tool_calls(self, assistant_message: Message, messages: List[Message], tool_ids: List[str]):
327
- """
328
- Handle tool calls in the assistant message.
553
+ if run_response and run_response.metrics:
554
+ run_response.metrics.set_time_to_first_token()
329
555
 
330
- Args:
331
- assistant_message (Message): The assistant message.
332
- messages (List[Message]): The list of messages.
333
- tool_ids (List[str]): The list of tool IDs.
334
- """
335
- tool_role: str = "tool"
336
- function_calls_to_run: List[Any] = []
337
- function_call_results: List[Message] = []
338
- for tool_call in assistant_message.tool_calls or []:
339
- _tool_call_id = tool_call.get("id")
340
- _function_call = get_function_call_for_tool_call(tool_call, self._functions)
341
- if _function_call is None:
342
- messages.append(
343
- Message(
344
- role="tool",
345
- tool_call_id=_tool_call_id,
346
- content="Could not find function to call.",
347
- )
348
- )
349
- continue
350
- if _function_call.error is not None:
351
- messages.append(
352
- Message(
353
- role="tool",
354
- tool_call_id=_tool_call_id,
355
- content=_function_call.error,
356
- )
357
- )
358
- continue
359
- function_calls_to_run.append(_function_call)
360
-
361
- if self.show_tool_calls:
362
- yield ModelResponse(content="\nRunning:")
363
- for _f in function_calls_to_run:
364
- yield ModelResponse(content=f"\n - {_f.get_call_str()}")
365
- yield ModelResponse(content="\n\n")
366
-
367
- for _ in self.run_function_calls(
368
- function_calls=function_calls_to_run, function_call_results=function_call_results, tool_role=tool_role
369
- ):
370
- pass
556
+ assistant_message.metrics.start_timer()
371
557
 
372
- if len(function_call_results) > 0:
373
- fc_responses: List = []
558
+ async with self.get_async_client() as client:
559
+ response = await client.converse(modelId=self.id, messages=formatted_messages, **body)
374
560
 
375
- for _fc_message_index, _fc_message in enumerate(function_call_results):
376
- tool_result = {
377
- "toolUseId": tool_ids[_fc_message_index],
378
- "content": [{"json": json.dumps(_fc_message.content)}],
379
- }
380
- tool_result_message = {"role": "user", "content": json.dumps([{"toolResult": tool_result}])}
381
- fc_responses.append(tool_result_message)
561
+ assistant_message.metrics.stop_timer()
382
562
 
383
- logger.debug(f"Tool call responses: {fc_responses}")
384
- messages.append(Message(role="user", content=json.dumps(fc_responses)))
563
+ model_response = self._parse_provider_response(response, response_format=response_format)
385
564
 
386
- def _update_stream_metrics(self, stream_data: StreamData, assistant_message: Message):
565
+ return model_response
566
+
567
+ except ClientError as e:
568
+ log_error(f"Unexpected error calling Bedrock API: {str(e)}")
569
+ raise ModelProviderError(message=str(e.response), model_name=self.name, model_id=self.id) from e
570
+ except Exception as e:
571
+ log_error(f"Unexpected error calling Bedrock API: {str(e)}")
572
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
573
+
574
+ async def ainvoke_stream(
575
+ self,
576
+ messages: List[Message],
577
+ assistant_message: Message,
578
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
579
+ tools: Optional[List[Dict[str, Any]]] = None,
580
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
581
+ run_response: Optional[RunOutput] = None,
582
+ compress_tool_results: bool = False,
583
+ ) -> AsyncIterator[ModelResponse]:
584
+ """
585
+ Async invoke the Bedrock API with streaming.
387
586
  """
388
- Update the metrics for the streaming response.
587
+ try:
588
+ formatted_messages, system_message = self._format_messages(messages, compress_tool_results)
589
+
590
+ tool_config = None
591
+ if tools:
592
+ tool_config = {"tools": self._format_tools_for_request(tools)}
593
+
594
+ body = {
595
+ "system": system_message,
596
+ "toolConfig": tool_config,
597
+ "inferenceConfig": self._get_inference_config(),
598
+ }
599
+ body = {k: v for k, v in body.items() if v is not None}
600
+
601
+ if self.request_params:
602
+ body.update(**self.request_params)
603
+
604
+ if run_response and run_response.metrics:
605
+ run_response.metrics.set_time_to_first_token()
606
+
607
+ assistant_message.metrics.start_timer()
608
+
609
+ # Track current tool being built across chunks
610
+ current_tool: Dict[str, Any] = {}
611
+
612
+ async with self.get_async_client() as client:
613
+ response = await client.converse_stream(modelId=self.id, messages=formatted_messages, **body)
614
+ async for chunk in response["stream"]:
615
+ model_response, current_tool = self._parse_provider_response_delta(chunk, current_tool)
616
+ yield model_response
617
+
618
+ assistant_message.metrics.stop_timer()
619
+
620
+ except ClientError as e:
621
+ log_error(f"Unexpected error calling Bedrock API: {str(e)}")
622
+ raise ModelProviderError(message=str(e.response), model_name=self.name, model_id=self.id) from e
623
+ except Exception as e:
624
+ log_error(f"Unexpected error calling Bedrock API: {str(e)}")
625
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
626
+
627
+ # Overwrite the default from the base model
628
+ def format_function_call_results(
629
+ self,
630
+ messages: List[Message],
631
+ function_call_results: List[Message],
632
+ compress_tool_results: bool = False,
633
+ **kwargs,
634
+ ) -> None:
635
+ """
636
+ Handle the results of function calls for Bedrock.
637
+ Uses compressed_content if compress_tool_results is True.
389
638
 
390
639
  Args:
391
- stream_data (StreamData): The streaming data
392
- assistant_message (Message): The assistant message.
640
+ messages (List[Message]): The list of conversation messages.
641
+ function_call_results (List[Message]): The results of the function calls.
642
+ compress_tool_results: Whether to compress tool results.
643
+ **kwargs: Additional arguments including tool_ids.
393
644
  """
394
- assistant_message.metrics["time"] = stream_data.response_timer.elapsed
395
- if stream_data.time_to_first_token is not None:
396
- assistant_message.metrics["time_to_first_token"] = stream_data.time_to_first_token
397
-
398
- if "response_times" not in self.metrics:
399
- self.metrics["response_times"] = []
400
- self.metrics["response_times"].append(stream_data.response_timer.elapsed)
401
- if stream_data.time_to_first_token is not None:
402
- if "time_to_first_token" not in self.metrics:
403
- self.metrics["time_to_first_token"] = []
404
- self.metrics["time_to_first_token"].append(stream_data.time_to_first_token)
405
- if stream_data.completion_tokens > 0:
406
- if "tokens_per_second" not in self.metrics:
407
- self.metrics["tokens_per_second"] = []
408
- self.metrics["tokens_per_second"].append(
409
- f"{stream_data.completion_tokens / stream_data.response_timer.elapsed:.4f}"
410
- )
411
645
 
412
- assistant_message.metrics["prompt_tokens"] = stream_data.response_prompt_tokens
413
- assistant_message.metrics["input_tokens"] = stream_data.response_prompt_tokens
414
- self.metrics["prompt_tokens"] = self.metrics.get("prompt_tokens", 0) + stream_data.response_prompt_tokens
415
- self.metrics["input_tokens"] = self.metrics.get("input_tokens", 0) + stream_data.response_prompt_tokens
646
+ if function_call_results:
647
+ tool_ids = kwargs.get("tool_ids", [])
416
648
 
417
- assistant_message.metrics["completion_tokens"] = stream_data.response_completion_tokens
418
- assistant_message.metrics["output_tokens"] = stream_data.response_completion_tokens
419
- self.metrics["completion_tokens"] = (
420
- self.metrics.get("completion_tokens", 0) + stream_data.response_completion_tokens
421
- )
422
- self.metrics["output_tokens"] = self.metrics.get("output_tokens", 0) + stream_data.response_completion_tokens
649
+ for _fc_message_index, _fc_message in enumerate(function_call_results):
650
+ # Use tool_call_id from message if tool_ids list is insufficient
651
+ tool_id = tool_ids[_fc_message_index] if _fc_message_index < len(tool_ids) else _fc_message.tool_call_id
652
+ if not _fc_message.tool_call_id:
653
+ _fc_message.tool_call_id = tool_id
423
654
 
424
- assistant_message.metrics["total_tokens"] = stream_data.response_total_tokens
425
- self.metrics["total_tokens"] = self.metrics.get("total_tokens", 0) + stream_data.response_total_tokens
655
+ # Append as standard role="tool" message
656
+ messages.append(_fc_message)
426
657
 
427
- def response_stream(self, messages: List[Message]) -> Iterator[ModelResponse]:
658
+ def _parse_provider_response(self, response: Dict[str, Any], **kwargs) -> ModelResponse:
428
659
  """
429
- Stream the response from the Bedrock API.
660
+ Parse the provider response.
430
661
 
431
662
  Args:
432
- messages (List[Message]): The messages to include in the request.
663
+ response (Dict[str, Any]): The response from the provider.
433
664
 
434
665
  Returns:
435
- Iterator[str]: The streamed response.
666
+ ModelResponse: The parsed response.
436
667
  """
437
- logger.debug("---------- Bedrock Response Start ----------")
438
- self._log_messages(messages)
439
-
440
- stream_data: StreamData = StreamData()
441
- stream_data.response_timer.start()
442
-
443
- tool_use: Dict[str, Any] = {}
444
- tool_ids: List[str] = []
445
- tool_calls: List[Dict[str, Any]] = []
446
- stop_reason: Optional[str] = None
447
- content: List[Dict[str, Any]] = []
448
-
449
- request_body = self.get_request_body(messages)
450
- response = self.invoke_stream(body=request_body)
451
-
452
- # Process the streaming response
453
- for chunk in response:
454
- if "contentBlockStart" in chunk:
455
- tool = chunk["contentBlockStart"]["start"].get("toolUse")
456
- if tool:
457
- tool_use["toolUseId"] = tool["toolUseId"]
458
- tool_use["name"] = tool["name"]
459
-
460
- elif "contentBlockDelta" in chunk:
461
- delta = chunk["contentBlockDelta"]["delta"]
462
- if "toolUse" in delta:
463
- if "input" not in tool_use:
464
- tool_use["input"] = ""
465
- tool_use["input"] += delta["toolUse"]["input"]
466
- elif "text" in delta:
467
- stream_data.response_content += delta["text"]
468
- stream_data.completion_tokens += 1
469
- if stream_data.completion_tokens == 1:
470
- stream_data.time_to_first_token = stream_data.response_timer.elapsed
471
- logger.debug(f"Time to first token: {stream_data.time_to_first_token:.4f}s")
472
- yield ModelResponse(content=delta["text"]) # Yield text content as it's received
473
-
474
- elif "contentBlockStop" in chunk:
475
- if "input" in tool_use:
476
- # Finish collecting tool use input
477
- try:
478
- tool_use["input"] = json.loads(tool_use["input"])
479
- except json.JSONDecodeError as e:
480
- logger.error(f"Failed to parse tool input as JSON: {e}")
481
- tool_use["input"] = {}
482
- content.append({"toolUse": tool_use})
483
- tool_ids.append(tool_use["toolUseId"])
484
- # Prepare the tool call
485
- tool_call = {
486
- "type": "function",
487
- "function": {
488
- "name": tool_use["name"],
489
- "arguments": json.dumps(tool_use["input"]),
490
- },
491
- }
492
- tool_calls.append(tool_call)
493
- tool_use = {}
494
- else:
495
- # Finish collecting text content
496
- content.append({"text": stream_data.response_content})
668
+ model_response = ModelResponse()
497
669
 
498
- elif "messageStop" in chunk:
499
- stop_reason = chunk["messageStop"]["stopReason"]
500
- logger.debug(f"Stop reason: {stop_reason}")
670
+ if "output" in response and "message" in response["output"]:
671
+ message = response["output"]["message"]
672
+ # Set the role of the message
673
+ model_response.role = message["role"]
501
674
 
502
- elif "metadata" in chunk:
503
- metadata = chunk["metadata"]
504
- if "usage" in metadata:
505
- stream_data.response_prompt_tokens = metadata["usage"]["inputTokens"]
506
- stream_data.response_total_tokens = metadata["usage"]["totalTokens"]
507
- stream_data.completion_tokens = metadata["usage"]["outputTokens"]
675
+ # Get the content of the message
676
+ content = message["content"]
508
677
 
509
- stream_data.response_timer.stop()
678
+ # Tools
679
+ if "stopReason" in response and response["stopReason"] == "tool_use":
680
+ model_response.tool_calls = []
681
+ model_response.extra = model_response.extra or {}
682
+ model_response.extra["tool_ids"] = []
683
+ for tool in content:
684
+ if "toolUse" in tool:
685
+ model_response.extra["tool_ids"].append(tool["toolUse"]["toolUseId"])
686
+ model_response.tool_calls.append(
687
+ {
688
+ "id": tool["toolUse"]["toolUseId"],
689
+ "type": "function",
690
+ "function": {
691
+ "name": tool["toolUse"]["name"],
692
+ "arguments": json.dumps(tool["toolUse"]["input"]),
693
+ },
694
+ }
695
+ )
510
696
 
511
- # Create assistant message
512
- if stream_data.response_content != "":
513
- assistant_message = Message(role="assistant", content=stream_data.response_content, tool_calls=tool_calls)
697
+ # Extract text content if it's a list of dictionaries
698
+ if isinstance(content, list) and content and isinstance(content[0], dict):
699
+ content = [item.get("text", "") for item in content if "text" in item]
700
+ content = "\n".join(content) # Join multiple text items if present
514
701
 
515
- if stream_data.completion_tokens > 0:
516
- logger.debug(
517
- f"Time per output token: {stream_data.response_timer.elapsed / stream_data.completion_tokens:.4f}s"
518
- )
519
- logger.debug(
520
- f"Throughput: {stream_data.completion_tokens / stream_data.response_timer.elapsed:.4f} tokens/s"
521
- )
702
+ model_response.content = content
522
703
 
523
- # Update metrics
524
- self._update_stream_metrics(stream_data, assistant_message)
704
+ if "usage" in response:
705
+ model_response.response_usage = self._get_metrics(response["usage"])
525
706
 
526
- # Add assistant message to messages
527
- messages.append(assistant_message)
528
- assistant_message.log()
707
+ return model_response
529
708
 
530
- # Handle tool calls if any
531
- if tool_calls:
532
- yield from self._handle_stream_tool_calls(assistant_message, messages, tool_ids)
533
- yield from self.response_stream(messages=messages)
709
+ def _parse_provider_response_delta(
710
+ self, response_delta: Dict[str, Any], current_tool: Dict[str, Any]
711
+ ) -> Tuple[ModelResponse, Dict[str, Any]]:
712
+ """Parse the provider response delta for streaming.
534
713
 
535
- logger.debug("---------- Bedrock Response End ----------")
714
+ Args:
715
+ response_delta: The streaming response delta from AWS Bedrock
716
+ current_tool: The current tool being built across chunks
536
717
 
537
- async def ainvoke(self, *args, **kwargs) -> Any:
538
- raise NotImplementedError(f"Async not supported on {self.name}.")
718
+ Returns:
719
+ Tuple[ModelResponse, Dict[str, Any]]: The parsed model response delta and updated current_tool
720
+ """
721
+ model_response = ModelResponse(role="assistant")
722
+
723
+ # Handle contentBlockStart - tool use start
724
+ if "contentBlockStart" in response_delta:
725
+ start = response_delta["contentBlockStart"]["start"]
726
+ if "toolUse" in start:
727
+ # Start a new tool
728
+ tool_use_data = start["toolUse"]
729
+ current_tool = {
730
+ "id": tool_use_data.get("toolUseId", ""),
731
+ "type": "function",
732
+ "function": {
733
+ "name": tool_use_data.get("name", ""),
734
+ "arguments": "", # Will be filled in subsequent deltas
735
+ },
736
+ }
539
737
 
540
- async def ainvoke_stream(self, *args, **kwargs) -> Any:
541
- raise NotImplementedError(f"Async not supported on {self.name}.")
738
+ # Handle contentBlockDelta - text content or tool input
739
+ elif "contentBlockDelta" in response_delta:
740
+ delta = response_delta["contentBlockDelta"]["delta"]
741
+ if "text" in delta:
742
+ model_response.content = delta["text"]
743
+ elif "toolUse" in delta and current_tool:
744
+ # Accumulate tool input
745
+ tool_input = delta["toolUse"].get("input", "")
746
+ if tool_input:
747
+ current_tool["function"]["arguments"] += tool_input
748
+
749
+ # Handle contentBlockStop - tool use complete
750
+ elif "contentBlockStop" in response_delta and current_tool:
751
+ # Tool is complete, add it to model response
752
+ model_response.tool_calls = [current_tool]
753
+ # Track tool_id in extra for format_function_call_results
754
+ model_response.extra = {"tool_ids": [current_tool["id"]]}
755
+ # Reset current_tool for next tool
756
+ current_tool = {}
757
+
758
+ # Handle metadata/usage information
759
+ elif "metadata" in response_delta or "messageStop" in response_delta:
760
+ body = response_delta.get("metadata") or response_delta.get("messageStop") or {}
761
+ if "usage" in body:
762
+ model_response.response_usage = self._get_metrics(body["usage"])
763
+
764
+ return model_response, current_tool
765
+
766
+ def _get_metrics(self, response_usage: Dict[str, Any]) -> Metrics:
767
+ """
768
+ Parse the given AWS Bedrock usage into an Agno Metrics object.
769
+
770
+ Args:
771
+ response_usage: Usage data from AWS Bedrock
772
+
773
+ Returns:
774
+ Metrics: Parsed metrics data
775
+ """
776
+ metrics = Metrics()
542
777
 
543
- async def aresponse(self, messages: List[Message]) -> ModelResponse:
544
- raise NotImplementedError(f"Async not supported on {self.name}.")
778
+ metrics.input_tokens = response_usage.get("inputTokens", 0) or 0
779
+ metrics.output_tokens = response_usage.get("outputTokens", 0) or 0
780
+ metrics.total_tokens = metrics.input_tokens + metrics.output_tokens
545
781
 
546
- async def aresponse_stream(self, messages: List[Message]) -> ModelResponse:
547
- raise NotImplementedError(f"Async not supported on {self.name}.")
782
+ return metrics