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
agno/models/groq/groq.py CHANGED
@@ -1,65 +1,32 @@
1
+ from collections.abc import AsyncIterator
1
2
  from dataclasses import dataclass
2
3
  from os import getenv
3
- from typing import Any, Dict, Iterator, List, Optional, Union
4
+ from typing import Any, Dict, Iterator, List, Optional, Type, Union
4
5
 
5
6
  import httpx
7
+ from pydantic import BaseModel
6
8
 
7
- from agno.models.base import Metrics as BaseMetrics
9
+ from agno.exceptions import ModelAuthenticationError, ModelProviderError
8
10
  from agno.models.base import Model
9
11
  from agno.models.message import Message
10
- from agno.models.response import ModelResponse, ModelResponseEvent
11
- from agno.tools.function import FunctionCall
12
- from agno.utils.log import logger
13
- from agno.utils.tools import get_function_call_for_tool_call
12
+ from agno.models.metrics import Metrics
13
+ from agno.models.response import ModelResponse
14
+ from agno.run.agent import RunOutput
15
+ from agno.utils.http import get_default_async_client, get_default_sync_client
16
+ from agno.utils.log import log_debug, log_error, log_warning
17
+ from agno.utils.openai import images_to_message
14
18
 
15
19
  try:
20
+ from groq import APIError, APIResponseValidationError, APIStatusError
16
21
  from groq import AsyncGroq as AsyncGroqClient
17
22
  from groq import Groq as GroqClient
18
- from groq.types.chat import ChatCompletion, ChatCompletionMessage
23
+ from groq.types.chat import ChatCompletion
19
24
  from groq.types.chat.chat_completion_chunk import ChatCompletionChunk, ChoiceDelta, ChoiceDeltaToolCall
20
25
  from groq.types.completion_usage import CompletionUsage
21
- except (ModuleNotFoundError, ImportError):
26
+ except ImportError:
22
27
  raise ImportError("`groq` not installed. Please install using `pip install groq`")
23
28
 
24
29
 
25
- @dataclass
26
- class Metrics(BaseMetrics):
27
- completion_time: Optional[float] = None
28
- prompt_time: Optional[float] = None
29
- queue_time: Optional[float] = None
30
- total_time: Optional[float] = None
31
-
32
- def log(self):
33
- metric_lines = []
34
- if self.time_to_first_token is not None:
35
- metric_lines.append(f"* Time to first token: {self.time_to_first_token:.4f}s")
36
- metric_lines.extend(
37
- [
38
- f"* Time to generate response: {self.response_timer.elapsed:.4f}s",
39
- f"* Tokens per second: {self.output_tokens / self.response_timer.elapsed:.4f} tokens/s",
40
- f"* Input tokens: {self.input_tokens or self.prompt_tokens}",
41
- f"* Output tokens: {self.output_tokens or self.completion_tokens}",
42
- f"* Total tokens: {self.total_tokens}",
43
- ]
44
- )
45
- if self.completion_time is not None:
46
- metric_lines.append(f"* Completion time: {self.completion_time:.4f}s")
47
- if self.prompt_time is not None:
48
- metric_lines.append(f"* Prompt time: {self.prompt_time:.4f}s")
49
- if self.queue_time is not None:
50
- metric_lines.append(f"* Queue time: {self.queue_time:.4f}s")
51
- if self.total_time is not None:
52
- metric_lines.append(f"* Total time: {self.total_time:.4f}s")
53
-
54
- self._log(metric_lines=metric_lines)
55
-
56
-
57
- @dataclass
58
- class StreamData:
59
- response_content: str = ""
60
- response_tool_calls: Optional[List[ChoiceDeltaToolCall]] = None
61
-
62
-
63
30
  @dataclass
64
31
  class Groq(Model):
65
32
  """
@@ -68,7 +35,7 @@ class Groq(Model):
68
35
  For more information, see: https://console.groq.com/docs/libraries
69
36
  """
70
37
 
71
- id: str = "llama3-groq-70b-8192-tool-use-preview"
38
+ id: str = "llama-3.3-70b-versatile"
72
39
  name: str = "Groq"
73
40
  provider: str = "Groq"
74
41
 
@@ -78,7 +45,6 @@ class Groq(Model):
78
45
  logprobs: Optional[bool] = None
79
46
  max_tokens: Optional[int] = None
80
47
  presence_penalty: Optional[float] = None
81
- response_format: Optional[Dict[str, Any]] = None
82
48
  seed: Optional[int] = None
83
49
  stop: Optional[Union[str, List[str]]] = None
84
50
  temperature: Optional[float] = None
@@ -96,115 +62,134 @@ class Groq(Model):
96
62
  max_retries: Optional[int] = None
97
63
  default_headers: Optional[Any] = None
98
64
  default_query: Optional[Any] = None
99
- http_client: Optional[httpx.Client] = None
65
+ http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
100
66
  client_params: Optional[Dict[str, Any]] = None
101
67
 
102
68
  # Groq clients
103
69
  client: Optional[GroqClient] = None
104
70
  async_client: Optional[AsyncGroqClient] = None
105
71
 
106
- def get_client_params(self) -> Dict[str, Any]:
107
- self.api_key = self.api_key or getenv("GROQ_API_KEY")
72
+ def _get_client_params(self) -> Dict[str, Any]:
73
+ # Fetch API key from env if not already set
108
74
  if not self.api_key:
109
- logger.error("GROQ_API_KEY not set. Please set the GROQ_API_KEY environment variable.")
110
-
111
- client_params: Dict[str, Any] = {}
112
- if self.api_key:
113
- client_params["api_key"] = self.api_key
114
- if self.base_url:
115
- client_params["base_url"] = self.base_url
116
- if self.timeout:
117
- client_params["timeout"] = self.timeout
118
- if self.max_retries:
119
- client_params["max_retries"] = self.max_retries
120
- if self.default_headers:
121
- client_params["default_headers"] = self.default_headers
122
- if self.default_query:
123
- client_params["default_query"] = self.default_query
75
+ self.api_key = getenv("GROQ_API_KEY")
76
+ if not self.api_key:
77
+ raise ModelAuthenticationError(
78
+ message="GROQ_API_KEY not set. Please set the GROQ_API_KEY environment variable.",
79
+ model_name=self.name,
80
+ )
81
+
82
+ # Define base client params
83
+ base_params = {
84
+ "api_key": self.api_key,
85
+ "base_url": self.base_url,
86
+ "timeout": self.timeout,
87
+ "max_retries": self.max_retries,
88
+ "default_headers": self.default_headers,
89
+ "default_query": self.default_query,
90
+ }
91
+ # Create client_params dict with non-None values
92
+ client_params = {k: v for k, v in base_params.items() if v is not None}
93
+ # Add additional client params if provided
124
94
  if self.client_params:
125
95
  client_params.update(self.client_params)
126
96
  return client_params
127
97
 
128
98
  def get_client(self) -> GroqClient:
129
99
  """
130
- Returns a Groq client.
100
+ Returns a Groq client. Caches the client to avoid recreating it on every request.
131
101
 
132
102
  Returns:
133
103
  GroqClient: An instance of the Groq client.
134
104
  """
135
- if self.client:
105
+ if self.client and not self.client.is_closed():
136
106
  return self.client
137
107
 
138
- client_params: Dict[str, Any] = self.get_client_params()
108
+ client_params: Dict[str, Any] = self._get_client_params()
139
109
  if self.http_client is not None:
140
- client_params["http_client"] = self.http_client
141
- return GroqClient(**client_params)
110
+ if isinstance(self.http_client, httpx.Client):
111
+ client_params["http_client"] = self.http_client
112
+ else:
113
+ log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
114
+ # Use global sync client when user http_client is invalid
115
+ client_params["http_client"] = get_default_sync_client()
116
+ else:
117
+ # Use global sync client when no custom http_client is provided
118
+ client_params["http_client"] = get_default_sync_client()
119
+
120
+ self.client = GroqClient(**client_params)
121
+ return self.client
142
122
 
143
123
  def get_async_client(self) -> AsyncGroqClient:
144
124
  """
145
- Returns an asynchronous Groq client.
125
+ Returns an asynchronous Groq client. Caches the client to avoid recreating it on every request.
146
126
 
147
127
  Returns:
148
128
  AsyncGroqClient: An instance of the asynchronous Groq client.
149
129
  """
150
- if self.async_client:
130
+ if self.async_client and not self.async_client.is_closed():
151
131
  return self.async_client
152
132
 
153
- client_params: Dict[str, Any] = self.get_client_params()
133
+ client_params: Dict[str, Any] = self._get_client_params()
154
134
  if self.http_client:
155
- client_params["http_client"] = self.http_client
135
+ if isinstance(self.http_client, httpx.AsyncClient):
136
+ client_params["http_client"] = self.http_client
137
+ else:
138
+ log_warning(
139
+ "http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
140
+ )
141
+ # Use global async client when user http_client is invalid
142
+ client_params["http_client"] = get_default_async_client()
156
143
  else:
157
- # Create a new async HTTP client with custom limits
158
- client_params["http_client"] = httpx.AsyncClient(
159
- limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100)
160
- )
161
- return AsyncGroqClient(**client_params)
144
+ # Use global async client when no custom http_client is provided
145
+ client_params["http_client"] = get_default_async_client()
162
146
 
163
- @property
164
- def request_kwargs(self) -> Dict[str, Any]:
147
+ # Create and cache the client
148
+ self.async_client = AsyncGroqClient(**client_params)
149
+ return self.async_client
150
+
151
+ def get_request_params(
152
+ self,
153
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
154
+ tools: Optional[List[Dict[str, Any]]] = None,
155
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
156
+ ) -> Dict[str, Any]:
165
157
  """
166
158
  Returns keyword arguments for API requests.
167
159
 
168
160
  Returns:
169
161
  Dict[str, Any]: A dictionary of keyword arguments for API requests.
170
162
  """
171
- request_params: Dict[str, Any] = {}
172
- if self.frequency_penalty:
173
- request_params["frequency_penalty"] = self.frequency_penalty
174
- if self.logit_bias:
175
- request_params["logit_bias"] = self.logit_bias
176
- if self.logprobs:
177
- request_params["logprobs"] = self.logprobs
178
- if self.max_tokens:
179
- request_params["max_tokens"] = self.max_tokens
180
- if self.presence_penalty:
181
- request_params["presence_penalty"] = self.presence_penalty
182
- if self.response_format:
183
- request_params["response_format"] = self.response_format
184
- if self.seed:
185
- request_params["seed"] = self.seed
186
- if self.stop:
187
- request_params["stop"] = self.stop
188
- if self.temperature:
189
- request_params["temperature"] = self.temperature
190
- if self.top_logprobs:
191
- request_params["top_logprobs"] = self.top_logprobs
192
- if self.top_p:
193
- request_params["top_p"] = self.top_p
194
- if self.user:
195
- request_params["user"] = self.user
196
- if self.extra_headers:
197
- request_params["extra_headers"] = self.extra_headers
198
- if self.extra_query:
199
- request_params["extra_query"] = self.extra_query
200
- if self.tools:
201
- request_params["tools"] = self.tools
202
- if self.tool_choice is None:
203
- request_params["tool_choice"] = "auto"
204
- else:
205
- request_params["tool_choice"] = self.tool_choice
163
+ # Define base request parameters
164
+ base_params = {
165
+ "frequency_penalty": self.frequency_penalty,
166
+ "logit_bias": self.logit_bias,
167
+ "logprobs": self.logprobs,
168
+ "max_tokens": self.max_tokens,
169
+ "presence_penalty": self.presence_penalty,
170
+ "response_format": response_format,
171
+ "seed": self.seed,
172
+ "stop": self.stop,
173
+ "temperature": self.temperature,
174
+ "top_logprobs": self.top_logprobs,
175
+ "top_p": self.top_p,
176
+ "user": self.user,
177
+ "extra_headers": self.extra_headers,
178
+ "extra_query": self.extra_query,
179
+ }
180
+ # Filter out None values
181
+ request_params = {k: v for k, v in base_params.items() if v is not None}
182
+ # Add tools
183
+ if tools is not None:
184
+ request_params["tools"] = tools
185
+ if tool_choice is not None:
186
+ request_params["tool_choice"] = tool_choice
187
+ # Add additional request params if provided
206
188
  if self.request_params:
207
189
  request_params.update(self.request_params)
190
+
191
+ if request_params:
192
+ log_debug(f"Calling {self.provider} with request parameters: {request_params}", log_level=2)
208
193
  return request_params
209
194
 
210
195
  def to_dict(self) -> Dict[str, Any]:
@@ -214,15 +199,14 @@ class Groq(Model):
214
199
  Returns:
215
200
  Dict[str, Any]: The dictionary representation of the model.
216
201
  """
217
- _dict = super().to_dict()
218
- _dict.update(
202
+ model_dict = super().to_dict()
203
+ model_dict.update(
219
204
  {
220
205
  "frequency_penalty": self.frequency_penalty,
221
206
  "logit_bias": self.logit_bias,
222
207
  "logprobs": self.logprobs,
223
208
  "max_tokens": self.max_tokens,
224
209
  "presence_penalty": self.presence_penalty,
225
- "response_format": self.response_format,
226
210
  "seed": self.seed,
227
211
  "stop": self.stop,
228
212
  "temperature": self.temperature,
@@ -231,631 +215,372 @@ class Groq(Model):
231
215
  "user": self.user,
232
216
  "extra_headers": self.extra_headers,
233
217
  "extra_query": self.extra_query,
234
- "tools": self.tools,
235
- "tool_choice": self.tool_choice
236
- if (self.tools is not None and self.tool_choice is not None)
237
- else "auto",
238
218
  }
239
219
  )
240
- cleaned_dict = {k: v for k, v in _dict.items() if v is not None}
220
+ cleaned_dict = {k: v for k, v in model_dict.items() if v is not None}
241
221
  return cleaned_dict
242
222
 
243
- def format_message(self, message: Message) -> Dict[str, Any]:
223
+ def format_message(
224
+ self,
225
+ message: Message,
226
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
227
+ compress_tool_results: bool = False,
228
+ ) -> Dict[str, Any]:
244
229
  """
245
- Format a message into the format expected by OpenAI.
230
+ Format a message into the format expected by Groq.
246
231
 
247
232
  Args:
248
233
  message (Message): The message to format.
234
+ response_format: Optional response format specification.
235
+ compress_tool_results: Whether to compress tool results.
249
236
 
250
237
  Returns:
251
238
  Dict[str, Any]: The formatted message.
252
239
  """
253
- if message.role == "user":
254
- if message.images is not None:
255
- message = self.add_images_to_message(message=message, images=message.images)
256
- # TODO: Add audio support
257
- # if message.audio is not None:
258
- # message = self.add_audio_to_message(message=message, audio=message.audio)
240
+ # Use compressed content for tool messages if compression is active
241
+ if message.role == "tool":
242
+ content = message.get_content(use_compressed_content=compress_tool_results)
243
+ else:
244
+ content = message.content
259
245
 
260
- return message.to_dict()
246
+ message_dict: Dict[str, Any] = {
247
+ "role": message.role,
248
+ "content": content,
249
+ "name": message.name,
250
+ "tool_call_id": message.tool_call_id,
251
+ "tool_calls": message.tool_calls,
252
+ }
253
+ message_dict = {k: v for k, v in message_dict.items() if v is not None}
261
254
 
262
- def invoke(self, messages: List[Message]) -> ChatCompletion:
263
- """
264
- Send a chat completion request to the Groq API.
255
+ if (
256
+ message.role == "system"
257
+ and isinstance(message.content, str)
258
+ and response_format is not None
259
+ and isinstance(response_format, dict)
260
+ and response_format.get("type") == "json_object"
261
+ ):
262
+ # This is required by Groq to ensure the model outputs in the correct format
263
+ message.content += "\n\nYour output should be in JSON format."
265
264
 
266
- Args:
267
- messages (List[Message]): A list of messages to send to the model.
265
+ if message.images is not None and len(message.images) > 0:
266
+ # Ignore non-string message content
267
+ # because we assume that the images/audio are already added to the message
268
+ if isinstance(message.content, str):
269
+ message_dict["content"] = [{"type": "text", "text": message.content}]
270
+ message_dict["content"].extend(images_to_message(images=message.images))
268
271
 
269
- Returns:
270
- ChatCompletion: The chat completion response from the API.
271
- """
272
- return self.get_client().chat.completions.create(
273
- model=self.id,
274
- messages=[self.format_message(m) for m in messages], # type: ignore
275
- **self.request_kwargs,
276
- )
272
+ if message.files is not None and len(message.files) > 0:
273
+ log_warning("File input is currently unsupported.")
277
274
 
278
- async def ainvoke(self, messages: List[Message]) -> ChatCompletion:
279
- """
280
- Sends an asynchronous chat completion request to the Groq API.
275
+ if message.audio is not None and len(message.audio) > 0:
276
+ log_warning("Audio input is currently unsupported.")
281
277
 
282
- Args:
283
- messages (List[Message]): A list of messages to send to the model.
278
+ if message.videos is not None and len(message.videos) > 0:
279
+ log_warning("Video input is currently unsupported.")
284
280
 
285
- Returns:
286
- ChatCompletion: The chat completion response from the API.
287
- """
288
- return await self.get_async_client().chat.completions.create(
289
- model=self.id,
290
- messages=[self.format_message(m) for m in messages], # type: ignore
291
- **self.request_kwargs,
292
- )
281
+ return message_dict
293
282
 
294
- def invoke_stream(self, messages: List[Message]) -> Iterator[ChatCompletionChunk]:
283
+ def invoke(
284
+ self,
285
+ messages: List[Message],
286
+ assistant_message: Message,
287
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
288
+ tools: Optional[List[Dict[str, Any]]] = None,
289
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
290
+ run_response: Optional[RunOutput] = None,
291
+ compress_tool_results: bool = False,
292
+ ) -> ModelResponse:
295
293
  """
296
- Send a streaming chat completion request to the Groq API.
297
-
298
- Args:
299
- messages (List[Message]): A list of messages to send to the model.
300
-
301
- Returns:
302
- Iterator[ChatCompletionChunk]: An iterator of chat completion chunks.
294
+ Send a chat completion request to the Groq API.
303
295
  """
304
- yield from self.get_client().chat.completions.create(
305
- model=self.id,
306
- messages=[self.format_message(m) for m in messages], # type: ignore
307
- stream=True,
308
- **self.request_kwargs,
309
- )
296
+ try:
297
+ if run_response and run_response.metrics:
298
+ run_response.metrics.set_time_to_first_token()
310
299
 
311
- async def ainvoke_stream(self, messages: List[Message]) -> Any:
312
- """
313
- Sends an asynchronous streaming chat completion request to the Groq API.
300
+ assistant_message.metrics.start_timer()
301
+ provider_response = self.get_client().chat.completions.create(
302
+ model=self.id,
303
+ messages=[self.format_message(m, response_format, compress_tool_results) for m in messages], # type: ignore
304
+ **self.get_request_params(response_format=response_format, tools=tools, tool_choice=tool_choice),
305
+ )
306
+ assistant_message.metrics.stop_timer()
314
307
 
315
- Args:
316
- messages (List[Message]): A list of messages to send to the model.
308
+ model_response = self._parse_provider_response(provider_response, response_format=response_format)
317
309
 
318
- Returns:
319
- Any: An asynchronous iterator of chat completion chunks.
320
- """
321
- async_stream = await self.get_async_client().chat.completions.create(
322
- model=self.id,
323
- messages=[self.format_message(m) for m in messages], # type: ignore
324
- stream=True,
325
- **self.request_kwargs,
326
- )
327
- async for chunk in async_stream: # type: ignore
328
- yield chunk
310
+ return model_response
329
311
 
330
- def handle_tool_calls(
312
+ except (APIResponseValidationError, APIStatusError) as e:
313
+ log_error(f"Error calling Groq API: {str(e)}")
314
+ raise ModelProviderError(
315
+ message=e.response.text, status_code=e.response.status_code, model_name=self.name, model_id=self.id
316
+ ) from e
317
+ except APIError as e:
318
+ log_error(f"Error calling Groq API: {str(e)}")
319
+ raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
320
+ except Exception as e:
321
+ log_error(f"Unexpected error calling Groq API: {str(e)}")
322
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
323
+
324
+ async def ainvoke(
331
325
  self,
332
- assistant_message: Message,
333
326
  messages: List[Message],
334
- model_response: ModelResponse,
335
- tool_role: str = "tool",
336
- ) -> Optional[ModelResponse]:
327
+ assistant_message: Message,
328
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
329
+ tools: Optional[List[Dict[str, Any]]] = None,
330
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
331
+ run_response: Optional[RunOutput] = None,
332
+ compress_tool_results: bool = False,
333
+ ) -> ModelResponse:
337
334
  """
338
- Handle tool calls in the assistant message.
339
-
340
- Args:
341
- assistant_message (Message): The assistant message.
342
- messages (List[Message]): The list of messages.
343
- model_response (ModelResponse): The model response.
344
- tool_role (str): The role of the tool call. Defaults to "tool".
345
-
346
- Returns:
347
- Optional[ModelResponse]: The model response after handling tool calls.
335
+ Sends an asynchronous chat completion request to the Groq API.
348
336
  """
349
- if assistant_message.tool_calls is not None and len(assistant_message.tool_calls) > 0:
350
- if model_response.content is None:
351
- model_response.content = ""
352
- if model_response.tool_calls is None:
353
- model_response.tool_calls = []
354
- function_call_results: List[Message] = []
355
- function_calls_to_run: List[FunctionCall] = []
356
- for tool_call in assistant_message.tool_calls:
357
- _tool_call_id = tool_call.get("id")
358
- _function_call = get_function_call_for_tool_call(tool_call, self._functions)
359
- if _function_call is None:
360
- messages.append(
361
- Message(
362
- role="tool",
363
- tool_call_id=_tool_call_id,
364
- content="Could not find function to call.",
365
- )
366
- )
367
- continue
368
- if _function_call.error is not None:
369
- messages.append(
370
- Message(
371
- role="tool",
372
- tool_call_id=_tool_call_id,
373
- content=_function_call.error,
374
- )
375
- )
376
- continue
377
- function_calls_to_run.append(_function_call)
378
-
379
- if self.show_tool_calls:
380
- model_response.content += "\nRunning:"
381
- for _f in function_calls_to_run:
382
- model_response.content += f"\n - {_f.get_call_str()}"
383
- model_response.content += "\n\n"
384
-
385
- for function_call_response in self.run_function_calls(
386
- function_calls=function_calls_to_run, function_call_results=function_call_results, tool_role=tool_role
387
- ):
388
- if (
389
- function_call_response.event == ModelResponseEvent.tool_call_completed.value
390
- and function_call_response.tool_calls is not None
391
- ):
392
- model_response.tool_calls.extend(function_call_response.tool_calls)
337
+ try:
338
+ if run_response and run_response.metrics:
339
+ run_response.metrics.set_time_to_first_token()
340
+
341
+ assistant_message.metrics.start_timer()
342
+ response = await self.get_async_client().chat.completions.create(
343
+ model=self.id,
344
+ messages=[self.format_message(m, response_format, compress_tool_results) for m in messages], # type: ignore
345
+ **self.get_request_params(response_format=response_format, tools=tools, tool_choice=tool_choice),
346
+ )
347
+ assistant_message.metrics.stop_timer()
393
348
 
394
- if len(function_call_results) > 0:
395
- messages.extend(function_call_results)
349
+ model_response = self._parse_provider_response(response, response_format=response_format)
396
350
 
397
351
  return model_response
398
- return None
399
-
400
- def update_usage_metrics(
401
- self, assistant_message: Message, metrics: Metrics, response_usage: Optional[CompletionUsage]
402
- ) -> None:
403
- """
404
- Update the usage metrics for the assistant message and the model.
405
352
 
406
- Args:
407
- assistant_message (Message): The assistant message.
408
- metrics (Metrics): The metrics.
409
- response_usage (Optional[CompletionUsage]): The response usage.
410
- """
411
- # Update time taken to generate response
412
- assistant_message.metrics["time"] = metrics.response_timer.elapsed
413
- self.metrics.setdefault("response_times", []).append(metrics.response_timer.elapsed)
414
- if response_usage:
415
- prompt_tokens = response_usage.prompt_tokens
416
- completion_tokens = response_usage.completion_tokens
417
- total_tokens = response_usage.total_tokens
418
-
419
- if prompt_tokens is not None:
420
- metrics.input_tokens = prompt_tokens
421
- metrics.prompt_tokens = prompt_tokens
422
- assistant_message.metrics["input_tokens"] = prompt_tokens
423
- assistant_message.metrics["prompt_tokens"] = prompt_tokens
424
- self.metrics["input_tokens"] = self.metrics.get("input_tokens", 0) + prompt_tokens
425
- self.metrics["prompt_tokens"] = self.metrics.get("prompt_tokens", 0) + prompt_tokens
426
- if completion_tokens is not None:
427
- metrics.output_tokens = completion_tokens
428
- metrics.completion_tokens = completion_tokens
429
- assistant_message.metrics["output_tokens"] = completion_tokens
430
- assistant_message.metrics["completion_tokens"] = completion_tokens
431
- self.metrics["output_tokens"] = self.metrics.get("output_tokens", 0) + completion_tokens
432
- self.metrics["completion_tokens"] = self.metrics.get("completion_tokens", 0) + completion_tokens
433
- if total_tokens is not None:
434
- metrics.total_tokens = total_tokens
435
- assistant_message.metrics["total_tokens"] = total_tokens
436
- self.metrics["total_tokens"] = self.metrics.get("total_tokens", 0) + total_tokens
437
- if response_usage.completion_time is not None:
438
- metrics.completion_time = response_usage.completion_time
439
- assistant_message.metrics["completion_time"] = response_usage.completion_time
440
- self.metrics["completion_time"] = (
441
- self.metrics.get("completion_time", 0) + response_usage.completion_time
442
- )
443
- if response_usage.prompt_time is not None:
444
- metrics.prompt_time = response_usage.prompt_time
445
- assistant_message.metrics["prompt_time"] = response_usage.prompt_time
446
- self.metrics["prompt_time"] = self.metrics.get("prompt_time", 0) + response_usage.prompt_time
447
- if response_usage.queue_time is not None:
448
- metrics.queue_time = response_usage.queue_time
449
- assistant_message.metrics["queue_time"] = response_usage.queue_time
450
- self.metrics["queue_time"] = self.metrics.get("queue_time", 0) + response_usage.queue_time
451
- if response_usage.total_time is not None:
452
- metrics.total_time = response_usage.total_time
453
- assistant_message.metrics["total_time"] = response_usage.total_time
454
- self.metrics["total_time"] = self.metrics.get("total_time", 0) + response_usage.total_time
455
-
456
- def create_assistant_message(
353
+ except (APIResponseValidationError, APIStatusError) as e:
354
+ log_error(f"Error calling Groq API: {str(e)}")
355
+ raise ModelProviderError(
356
+ message=e.response.text, status_code=e.response.status_code, model_name=self.name, model_id=self.id
357
+ ) from e
358
+ except APIError as e:
359
+ log_error(f"Error calling Groq API: {str(e)}")
360
+ raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
361
+ except Exception as e:
362
+ log_error(f"Unexpected error calling Groq API: {str(e)}")
363
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
364
+
365
+ def invoke_stream(
457
366
  self,
458
- response_message: ChatCompletionMessage,
459
- metrics: Metrics,
460
- response_usage: Optional[CompletionUsage],
461
- ) -> Message:
367
+ messages: List[Message],
368
+ assistant_message: Message,
369
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
370
+ tools: Optional[List[Dict[str, Any]]] = None,
371
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
372
+ run_response: Optional[RunOutput] = None,
373
+ compress_tool_results: bool = False,
374
+ ) -> Iterator[ModelResponse]:
462
375
  """
463
- Create an assistant message from the response.
464
-
465
- Args:
466
- response_message (ChatCompletionMessage): The response message.
467
- metrics (Metrics): The metrics.
468
- response_usage (Optional[CompletionUsage]): The response usage.
469
-
470
- Returns:
471
- Message: The assistant message.
376
+ Send a streaming chat completion request to the Groq API.
472
377
  """
473
- assistant_message = Message(
474
- role=response_message.role or "assistant",
475
- content=response_message.content,
476
- )
477
- if response_message.tool_calls is not None and len(response_message.tool_calls) > 0:
478
- try:
479
- assistant_message.tool_calls = [t.model_dump() for t in response_message.tool_calls]
480
- except Exception as e:
481
- logger.warning(f"Error processing tool calls: {e}")
378
+ try:
379
+ if run_response and run_response.metrics:
380
+ run_response.metrics.set_time_to_first_token()
482
381
 
483
- # Update metrics
484
- self.update_usage_metrics(assistant_message, metrics, response_usage)
485
- return assistant_message
382
+ assistant_message.metrics.start_timer()
486
383
 
487
- def response(self, messages: List[Message]) -> ModelResponse:
384
+ for chunk in self.get_client().chat.completions.create(
385
+ model=self.id,
386
+ messages=[self.format_message(m, response_format, compress_tool_results) for m in messages], # type: ignore
387
+ stream=True,
388
+ **self.get_request_params(response_format=response_format, tools=tools, tool_choice=tool_choice),
389
+ ):
390
+ yield self._parse_provider_response_delta(chunk) # type: ignore
391
+
392
+ assistant_message.metrics.stop_timer()
393
+
394
+ except (APIResponseValidationError, APIStatusError) as e:
395
+ log_error(f"Error calling Groq API: {str(e)}")
396
+ raise ModelProviderError(
397
+ message=e.response.text, status_code=e.response.status_code, model_name=self.name, model_id=self.id
398
+ ) from e
399
+ except APIError as e:
400
+ log_error(f"Error calling Groq API: {str(e)}")
401
+ raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
402
+ except Exception as e:
403
+ log_error(f"Unexpected error calling Groq API: {str(e)}")
404
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
405
+
406
+ async def ainvoke_stream(
407
+ self,
408
+ messages: List[Message],
409
+ assistant_message: Message,
410
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
411
+ tools: Optional[List[Dict[str, Any]]] = None,
412
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
413
+ run_response: Optional[RunOutput] = None,
414
+ compress_tool_results: bool = False,
415
+ ) -> AsyncIterator[ModelResponse]:
488
416
  """
489
- Generate a response from Groq.
490
-
491
- Args:
492
- messages (List[Message]): A list of messages.
493
-
494
- Returns:
495
- ModelResponse: The model response.
417
+ Sends an asynchronous streaming chat completion request to the Groq API.
496
418
  """
497
- logger.debug("---------- Groq Response Start ----------")
498
- self._log_messages(messages)
499
- model_response = ModelResponse()
500
- metrics = Metrics()
501
419
 
502
- # -*- Generate response
503
- metrics.start_response_timer()
504
- response: ChatCompletion = self.invoke(messages=messages)
505
- metrics.stop_response_timer()
420
+ try:
421
+ if run_response and run_response.metrics:
422
+ run_response.metrics.set_time_to_first_token()
506
423
 
507
- # -*- Parse response
508
- response_message: ChatCompletionMessage = response.choices[0].message
509
- response_usage: Optional[CompletionUsage] = response.usage
424
+ assistant_message.metrics.start_timer()
510
425
 
511
- # -*- Create assistant message
512
- assistant_message = self.create_assistant_message(
513
- response_message=response_message, metrics=metrics, response_usage=response_usage
514
- )
426
+ async_stream = await self.get_async_client().chat.completions.create(
427
+ model=self.id,
428
+ messages=[self.format_message(m, response_format, compress_tool_results) for m in messages], # type: ignore
429
+ stream=True,
430
+ **self.get_request_params(response_format=response_format, tools=tools, tool_choice=tool_choice),
431
+ )
432
+ async for chunk in async_stream: # type: ignore
433
+ yield self._parse_provider_response_delta(chunk) # type: ignore
515
434
 
516
- # -*- Add assistant message to messages
517
- messages.append(assistant_message)
435
+ assistant_message.metrics.stop_timer()
518
436
 
519
- # -*- Log response and metrics
520
- assistant_message.log()
521
- metrics.log()
437
+ except (APIResponseValidationError, APIStatusError) as e:
438
+ log_error(f"Error calling Groq API: {str(e)}")
439
+ raise ModelProviderError(
440
+ message=e.response.text, status_code=e.response.status_code, model_name=self.name, model_id=self.id
441
+ ) from e
442
+ except APIError as e:
443
+ log_error(f"Error calling Groq API: {str(e)}")
444
+ raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
445
+ except Exception as e:
446
+ log_error(f"Unexpected error calling Groq API: {str(e)}")
447
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
522
448
 
523
- # -*- Update model response with assistant message content and audio
524
- if assistant_message.content is not None:
525
- # add the content to the model response
526
- model_response.content = assistant_message.get_content_string()
449
+ # Override base method
450
+ @staticmethod
451
+ def parse_tool_calls(tool_calls_data: List[ChoiceDeltaToolCall]) -> List[Dict[str, Any]]:
452
+ """
453
+ Build tool calls from streamed tool call data.
527
454
 
528
- # -*- Handle tool calls
529
- tool_role = "tool"
530
- if (
531
- self.handle_tool_calls(
532
- assistant_message=assistant_message,
533
- messages=messages,
534
- model_response=model_response,
535
- tool_role=tool_role,
536
- )
537
- is not None
538
- ):
539
- return self.handle_post_tool_call_messages(messages=messages, model_response=model_response)
540
- logger.debug("---------- Groq Response End ----------")
541
- return model_response
455
+ Args:
456
+ tool_calls_data (List[ChoiceDeltaToolCall]): The tool call data to build from.
542
457
 
543
- async def aresponse(self, messages: List[Message]) -> ModelResponse:
458
+ Returns:
459
+ List[Dict[str, Any]]: The built tool calls.
544
460
  """
545
- Generate an asynchronous response from Groq.
461
+ tool_calls: List[Dict[str, Any]] = []
462
+ for _tool_call in tool_calls_data:
463
+ _index = _tool_call.index
464
+ _tool_call_id = _tool_call.id
465
+ _tool_call_type = _tool_call.type
466
+ _function_name = _tool_call.function.name if _tool_call.function else None
467
+ _function_arguments = _tool_call.function.arguments if _tool_call.function else None
468
+
469
+ if len(tool_calls) <= _index:
470
+ tool_calls.extend([{}] * (_index - len(tool_calls) + 1))
471
+ tool_call_entry = tool_calls[_index]
472
+ if not tool_call_entry:
473
+ tool_call_entry["id"] = _tool_call_id
474
+ tool_call_entry["type"] = _tool_call_type
475
+ tool_call_entry["function"] = {
476
+ "name": _function_name or "",
477
+ "arguments": _function_arguments or "",
478
+ }
479
+ else:
480
+ if _function_name:
481
+ tool_call_entry["function"]["name"] += _function_name
482
+ if _function_arguments:
483
+ tool_call_entry["function"]["arguments"] += _function_arguments
484
+ if _tool_call_id:
485
+ tool_call_entry["id"] = _tool_call_id
486
+ if _tool_call_type:
487
+ tool_call_entry["type"] = _tool_call_type
488
+ return tool_calls
489
+
490
+ def _parse_provider_response(self, response: ChatCompletion, **kwargs) -> ModelResponse:
491
+ """
492
+ Parse the Groq response into a ModelResponse.
546
493
 
547
494
  Args:
548
- messages (List[Message]): A list of messages.
495
+ response: Raw response from Groq
549
496
 
550
497
  Returns:
551
- ModelResponse: The model response from the API.
498
+ ModelResponse: Parsed response data
552
499
  """
553
- logger.debug("---------- Groq Async Response Start ----------")
554
- self._log_messages(messages)
555
500
  model_response = ModelResponse()
556
- metrics = Metrics()
557
501
 
558
- # -*- Generate response
559
- metrics.start_response_timer()
560
- response: ChatCompletion = await self.ainvoke(messages=messages)
561
- metrics.stop_response_timer()
502
+ # Get response message
503
+ response_message = response.choices[0].message
562
504
 
563
- # -*- Parse response
564
- response_message: ChatCompletionMessage = response.choices[0].message
565
- response_usage: Optional[CompletionUsage] = response.usage
505
+ # Add role
506
+ if response_message.role is not None:
507
+ model_response.role = response_message.role
566
508
 
567
- # -*- Create assistant message
568
- assistant_message = self.create_assistant_message(
569
- response_message=response_message, metrics=metrics, response_usage=response_usage
570
- )
509
+ # Add content
510
+ if response_message.content is not None:
511
+ model_response.content = response_message.content
571
512
 
572
- # -*- Add assistant message to messages
573
- messages.append(assistant_message)
574
-
575
- # -*- Log response and metrics
576
- assistant_message.log()
577
- metrics.log()
513
+ # Add tool calls
514
+ if response_message.tool_calls is not None and len(response_message.tool_calls) > 0:
515
+ try:
516
+ model_response.tool_calls = [t.model_dump() for t in response_message.tool_calls]
517
+ except Exception as e:
518
+ log_warning(f"Error processing tool calls: {e}")
578
519
 
579
- # -*- Update model response with assistant message content and audio
580
- if assistant_message.content is not None:
581
- # add the content to the model response
582
- model_response.content = assistant_message.get_content_string()
520
+ # Add usage metrics if present
521
+ if response.usage is not None:
522
+ model_response.response_usage = self._get_metrics(response.usage)
583
523
 
584
- # -*- Handle tool calls
585
- tool_role = "tool"
586
- if (
587
- self.handle_tool_calls(
588
- assistant_message=assistant_message,
589
- messages=messages,
590
- model_response=model_response,
591
- tool_role=tool_role,
592
- )
593
- is not None
594
- ):
595
- return await self.ahandle_post_tool_call_messages(messages=messages, model_response=model_response)
596
-
597
- logger.debug("---------- Groq Async Response End ----------")
598
524
  return model_response
599
525
 
600
- def update_stream_metrics(self, assistant_message: Message, metrics: Metrics):
601
- """
602
- Update the usage metrics for the assistant message and the model.
603
-
604
- Args:
605
- assistant_message (Message): The assistant message.
606
- metrics (Metrics): The metrics.
526
+ def _parse_provider_response_delta(self, response: ChatCompletionChunk) -> ModelResponse:
607
527
  """
608
- # Update time taken to generate response
609
- assistant_message.metrics["time"] = metrics.response_timer.elapsed
610
- self.metrics.setdefault("response_times", []).append(metrics.response_timer.elapsed)
611
-
612
- if metrics.time_to_first_token is not None:
613
- assistant_message.metrics["time_to_first_token"] = metrics.time_to_first_token
614
- self.metrics.setdefault("time_to_first_token", []).append(metrics.time_to_first_token)
615
-
616
- if metrics.input_tokens is not None:
617
- assistant_message.metrics["input_tokens"] = metrics.input_tokens
618
- self.metrics["input_tokens"] = self.metrics.get("input_tokens", 0) + metrics.input_tokens
619
- if metrics.output_tokens is not None:
620
- assistant_message.metrics["output_tokens"] = metrics.output_tokens
621
- self.metrics["output_tokens"] = self.metrics.get("output_tokens", 0) + metrics.output_tokens
622
- if metrics.prompt_tokens is not None:
623
- assistant_message.metrics["prompt_tokens"] = metrics.prompt_tokens
624
- self.metrics["prompt_tokens"] = self.metrics.get("prompt_tokens", 0) + metrics.prompt_tokens
625
- if metrics.completion_tokens is not None:
626
- assistant_message.metrics["completion_tokens"] = metrics.completion_tokens
627
- self.metrics["completion_tokens"] = self.metrics.get("completion_tokens", 0) + metrics.completion_tokens
628
- if metrics.total_tokens is not None:
629
- assistant_message.metrics["total_tokens"] = metrics.total_tokens
630
- self.metrics["total_tokens"] = self.metrics.get("total_tokens", 0) + metrics.total_tokens
631
- if metrics.completion_time is not None:
632
- assistant_message.metrics["completion_time"] = metrics.completion_time
633
- self.metrics["completion_time"] = self.metrics.get("completion_time", 0) + metrics.completion_time
634
- if metrics.prompt_time is not None:
635
- assistant_message.metrics["prompt_time"] = metrics.prompt_time
636
- self.metrics["prompt_time"] = self.metrics.get("prompt_time", 0) + metrics.prompt_time
637
- if metrics.queue_time is not None:
638
- assistant_message.metrics["queue_time"] = metrics.queue_time
639
- self.metrics["queue_time"] = self.metrics.get("queue_time", 0) + metrics.queue_time
640
- if metrics.total_time is not None:
641
- assistant_message.metrics["total_time"] = metrics.total_time
642
- self.metrics["total_time"] = self.metrics.get("total_time", 0) + metrics.total_time
643
-
644
- def add_response_usage_to_metrics(self, metrics: Metrics, response_usage: CompletionUsage):
645
- metrics.input_tokens = response_usage.prompt_tokens
646
- metrics.prompt_tokens = response_usage.prompt_tokens
647
- metrics.output_tokens = response_usage.completion_tokens
648
- metrics.completion_tokens = response_usage.completion_tokens
649
- metrics.total_tokens = response_usage.total_tokens
650
- metrics.completion_time = response_usage.completion_time
651
- metrics.prompt_time = response_usage.prompt_time
652
- metrics.queue_time = response_usage.queue_time
653
- metrics.total_time = response_usage.total_time
654
-
655
- def handle_stream_tool_calls(
656
- self,
657
- assistant_message: Message,
658
- messages: List[Message],
659
- tool_role: str = "tool",
660
- ) -> Iterator[ModelResponse]:
661
- """
662
- Handle tool calls for response stream.
528
+ Parse the Groq streaming response into ModelResponse objects.
663
529
 
664
530
  Args:
665
- assistant_message (Message): The assistant message.
666
- messages (List[Message]): The list of messages.
667
- tool_role (str): The role of the tool call. Defaults to "tool".
531
+ response: Raw response chunk from Groq
668
532
 
669
533
  Returns:
670
- Iterator[ModelResponse]: An iterator of the model response.
534
+ ModelResponse: Iterator of parsed response data
671
535
  """
672
- if assistant_message.tool_calls is not None and len(assistant_message.tool_calls) > 0:
673
- function_calls_to_run: List[FunctionCall] = []
674
- function_call_results: List[Message] = []
675
- for tool_call in assistant_message.tool_calls:
676
- _tool_call_id = tool_call.get("id")
677
- _function_call = get_function_call_for_tool_call(tool_call, self._functions)
678
- if _function_call is None:
679
- messages.append(
680
- Message(
681
- role=tool_role,
682
- tool_call_id=_tool_call_id,
683
- content="Could not find function to call.",
684
- )
685
- )
686
- continue
687
- if _function_call.error is not None:
688
- messages.append(
689
- Message(
690
- role=tool_role,
691
- tool_call_id=_tool_call_id,
692
- content=_function_call.error,
693
- )
694
- )
695
- continue
696
- function_calls_to_run.append(_function_call)
697
-
698
- if self.show_tool_calls:
699
- yield ModelResponse(content="\nRunning:")
700
- for _f in function_calls_to_run:
701
- yield ModelResponse(content=f"\n - {_f.get_call_str()}")
702
- yield ModelResponse(content="\n\n")
703
-
704
- for function_call_response in self.run_function_calls(
705
- function_calls=function_calls_to_run, function_call_results=function_call_results, tool_role=tool_role
706
- ):
707
- yield function_call_response
708
-
709
- if len(function_call_results) > 0:
710
- messages.extend(function_call_results)
536
+ model_response = ModelResponse()
711
537
 
712
- def response_stream(self, messages: List[Message]) -> Iterator[ModelResponse]:
713
- """
714
- Generate a streaming response from Groq.
538
+ if len(response.choices) > 0:
539
+ choice_delta: ChoiceDelta = response.choices[0].delta
715
540
 
716
- Args:
717
- messages (List[Message]): A list of messages.
541
+ if choice_delta:
542
+ # Add content
543
+ if choice_delta.content is not None:
544
+ model_response.content = choice_delta.content
718
545
 
719
- Returns:
720
- Iterator[ModelResponse]: An iterator of model responses.
721
- """
722
- logger.debug("---------- Groq Response Start ----------")
723
- self._log_messages(messages)
724
- stream_data: StreamData = StreamData()
725
- metrics: Metrics = Metrics()
726
-
727
- # -*- Generate response
728
- metrics.start_response_timer()
729
- for response in self.invoke_stream(messages=messages):
730
- if len(response.choices) > 0:
731
- metrics.completion_tokens += 1
732
- if metrics.completion_tokens == 1:
733
- metrics.time_to_first_token = metrics.response_timer.elapsed
734
-
735
- response_delta: ChoiceDelta = response.choices[0].delta
736
- response_content: Optional[str] = response_delta.content
737
- response_tool_calls: Optional[List[ChoiceDeltaToolCall]] = response_delta.tool_calls
738
-
739
- if response_content is not None:
740
- stream_data.response_content += response_content
741
- yield ModelResponse(content=response_content)
742
-
743
- if response_tool_calls is not None:
744
- if stream_data.response_tool_calls is None:
745
- stream_data.response_tool_calls = []
746
- stream_data.response_tool_calls.extend(response_tool_calls)
747
-
748
- if response.usage is not None:
749
- self.add_response_usage_to_metrics(metrics=metrics, response_usage=response.usage)
750
- metrics.stop_response_timer()
751
-
752
- # -*- Create assistant message
753
- assistant_message = Message(role="assistant")
754
- if stream_data.response_content != "":
755
- assistant_message.content = stream_data.response_content
756
-
757
- if stream_data.response_tool_calls is not None:
758
- _tool_calls = self.build_tool_calls(stream_data.response_tool_calls)
759
- if len(_tool_calls) > 0:
760
- assistant_message.tool_calls = _tool_calls
761
-
762
- # -*- Update usage metrics
763
- self.update_stream_metrics(assistant_message=assistant_message, metrics=metrics)
764
-
765
- # -*- Add assistant message to messages
766
- messages.append(assistant_message)
767
-
768
- # -*- Log response and metrics
769
- assistant_message.log()
770
- metrics.log()
771
-
772
- # -*- Handle tool calls
773
- if assistant_message.tool_calls is not None and len(assistant_message.tool_calls) > 0:
774
- tool_role = "tool"
775
- yield from self.handle_stream_tool_calls(
776
- assistant_message=assistant_message, messages=messages, tool_role=tool_role
777
- )
778
- yield from self.handle_post_tool_call_messages_stream(messages=messages)
779
- logger.debug("---------- Groq Response End ----------")
780
-
781
- async def aresponse_stream(self, messages: List[Message]) -> Any:
782
- """
783
- Generate an asynchronous streaming response from Groq.
546
+ # Add tool calls
547
+ if choice_delta.tool_calls is not None:
548
+ model_response.tool_calls = choice_delta.tool_calls # type: ignore
784
549
 
785
- Args:
786
- messages (List[Message]): A list of messages.
550
+ # Add usage metrics if present
551
+ if response.x_groq is not None and response.x_groq.usage is not None:
552
+ model_response.response_usage = self._get_metrics(response.x_groq.usage)
787
553
 
788
- Returns:
789
- Any: An asynchronous iterator of model responses.
790
- """
791
- logger.debug("---------- Groq Async Response Start ----------")
792
- self._log_messages(messages)
793
- stream_data: StreamData = StreamData()
794
- metrics: Metrics = Metrics()
795
-
796
- # -*- Generate response
797
- metrics.start_response_timer()
798
- async for response in self.ainvoke_stream(messages=messages):
799
- if len(response.choices) > 0:
800
- metrics.completion_tokens += 1
801
- if metrics.completion_tokens == 1:
802
- metrics.time_to_first_token = metrics.response_timer.elapsed
803
-
804
- response_delta: ChoiceDelta = response.choices[0].delta
805
- response_content = response_delta.content
806
- response_tool_calls = response_delta.tool_calls
807
-
808
- if response_content is not None:
809
- stream_data.response_content += response_content
810
- yield ModelResponse(content=response_content)
811
-
812
- if response_tool_calls is not None:
813
- if stream_data.response_tool_calls is None:
814
- stream_data.response_tool_calls = []
815
- stream_data.response_tool_calls.extend(response_tool_calls)
816
-
817
- if response.usage is not None:
818
- self.add_response_usage_to_metrics(metrics=metrics, response_usage=response.usage)
819
- metrics.stop_response_timer()
820
-
821
- # -*- Create assistant message
822
- assistant_message = Message(role="assistant")
823
- if stream_data.response_content != "":
824
- assistant_message.content = stream_data.response_content
825
-
826
- if stream_data.response_tool_calls is not None:
827
- _tool_calls = self.build_tool_calls(stream_data.response_tool_calls)
828
- if len(_tool_calls) > 0:
829
- assistant_message.tool_calls = _tool_calls
830
-
831
- self.update_stream_metrics(assistant_message=assistant_message, metrics=metrics)
832
-
833
- # -*- Add assistant message to messages
834
- messages.append(assistant_message)
835
-
836
- # -*- Log response and metrics
837
- assistant_message.log()
838
- metrics.log()
839
-
840
- # -*- Handle tool calls
841
- if assistant_message.tool_calls is not None and len(assistant_message.tool_calls) > 0:
842
- tool_role = "tool"
843
- for tool_call_response in self.handle_stream_tool_calls(
844
- assistant_message=assistant_message, messages=messages, tool_role=tool_role
845
- ):
846
- yield tool_call_response
847
- async for post_tool_call_response in self.ahandle_post_tool_call_messages_stream(messages=messages):
848
- yield post_tool_call_response
849
- logger.debug("---------- Groq Async Response End ----------")
554
+ return model_response
850
555
 
851
- def build_tool_calls(self, tool_calls_data: List[ChoiceDeltaToolCall]) -> List[Dict[str, Any]]:
556
+ def _get_metrics(self, response_usage: CompletionUsage) -> Metrics:
852
557
  """
853
- Build tool calls from tool call data.
558
+ Parse the given Groq usage into an Agno Metrics object.
854
559
 
855
560
  Args:
856
- tool_calls_data (List[ChoiceDeltaToolCall]): The tool call data to build from.
561
+ response_usage: Usage data from Groq
857
562
 
858
563
  Returns:
859
- List[Dict[str, Any]]: The built tool calls.
564
+ Metrics: Parsed metrics data
860
565
  """
861
- return self._build_tool_calls(tool_calls_data)
566
+ metrics = Metrics()
567
+
568
+ metrics.input_tokens = response_usage.prompt_tokens or 0
569
+ metrics.output_tokens = response_usage.completion_tokens or 0
570
+ metrics.total_tokens = metrics.input_tokens + metrics.output_tokens
571
+
572
+ # Additional time metrics offered by Groq
573
+ if completion_time := response_usage.completion_time:
574
+ metrics.provider_metrics = metrics.provider_metrics or {}
575
+ metrics.provider_metrics["completion_time"] = completion_time
576
+ if prompt_time := response_usage.prompt_time:
577
+ metrics.provider_metrics = metrics.provider_metrics or {}
578
+ metrics.provider_metrics["prompt_time"] = prompt_time
579
+ if queue_time := response_usage.queue_time:
580
+ metrics.provider_metrics = metrics.provider_metrics or {}
581
+ metrics.provider_metrics["queue_time"] = queue_time
582
+ if total_time := response_usage.total_time:
583
+ metrics.provider_metrics = metrics.provider_metrics or {}
584
+ metrics.provider_metrics["total_time"] = total_time
585
+
586
+ return metrics