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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. agno/agent/agent.py +6015 -2823
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +594 -186
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +2 -8
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +72 -0
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +999 -519
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +103 -31
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +139 -0
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +59 -5
  142. agno/models/openai/chat.py +69 -29
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +77 -1
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -178
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +248 -94
  205. agno/run/base.py +44 -5
  206. agno/run/team.py +238 -97
  207. agno/run/workflow.py +144 -33
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1610
  213. agno/tools/dalle.py +2 -4
  214. agno/tools/decorator.py +4 -2
  215. agno/tools/duckduckgo.py +15 -11
  216. agno/tools/e2b.py +14 -7
  217. agno/tools/eleven_labs.py +23 -25
  218. agno/tools/exa.py +21 -16
  219. agno/tools/file.py +153 -23
  220. agno/tools/file_generation.py +350 -0
  221. agno/tools/firecrawl.py +4 -4
  222. agno/tools/function.py +250 -30
  223. agno/tools/gmail.py +238 -14
  224. agno/tools/google_drive.py +270 -0
  225. agno/tools/googlecalendar.py +36 -8
  226. agno/tools/googlesheets.py +20 -5
  227. agno/tools/jira.py +20 -0
  228. agno/tools/knowledge.py +3 -3
  229. agno/tools/mcp/__init__.py +10 -0
  230. agno/tools/mcp/mcp.py +331 -0
  231. agno/tools/mcp/multi_mcp.py +347 -0
  232. agno/tools/mcp/params.py +24 -0
  233. agno/tools/mcp_toolbox.py +284 -0
  234. agno/tools/mem0.py +11 -17
  235. agno/tools/memori.py +1 -53
  236. agno/tools/memory.py +419 -0
  237. agno/tools/models/nebius.py +5 -5
  238. agno/tools/models_labs.py +20 -10
  239. agno/tools/notion.py +204 -0
  240. agno/tools/parallel.py +314 -0
  241. agno/tools/scrapegraph.py +58 -31
  242. agno/tools/searxng.py +2 -2
  243. agno/tools/serper.py +2 -2
  244. agno/tools/slack.py +18 -3
  245. agno/tools/spider.py +2 -2
  246. agno/tools/tavily.py +146 -0
  247. agno/tools/whatsapp.py +1 -1
  248. agno/tools/workflow.py +278 -0
  249. agno/tools/yfinance.py +12 -11
  250. agno/utils/agent.py +820 -0
  251. agno/utils/audio.py +27 -0
  252. agno/utils/common.py +90 -1
  253. agno/utils/events.py +217 -2
  254. agno/utils/gemini.py +180 -22
  255. agno/utils/hooks.py +57 -0
  256. agno/utils/http.py +111 -0
  257. agno/utils/knowledge.py +12 -5
  258. agno/utils/log.py +1 -0
  259. agno/utils/mcp.py +92 -2
  260. agno/utils/media.py +188 -10
  261. agno/utils/merge_dict.py +22 -1
  262. agno/utils/message.py +60 -0
  263. agno/utils/models/claude.py +40 -11
  264. agno/utils/print_response/agent.py +105 -21
  265. agno/utils/print_response/team.py +103 -38
  266. agno/utils/print_response/workflow.py +251 -34
  267. agno/utils/reasoning.py +22 -1
  268. agno/utils/serialize.py +32 -0
  269. agno/utils/streamlit.py +16 -10
  270. agno/utils/string.py +41 -0
  271. agno/utils/team.py +98 -9
  272. agno/utils/tools.py +1 -1
  273. agno/vectordb/base.py +23 -4
  274. agno/vectordb/cassandra/cassandra.py +65 -9
  275. agno/vectordb/chroma/chromadb.py +182 -38
  276. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  277. agno/vectordb/couchbase/couchbase.py +105 -10
  278. agno/vectordb/lancedb/lance_db.py +124 -133
  279. agno/vectordb/langchaindb/langchaindb.py +25 -7
  280. agno/vectordb/lightrag/lightrag.py +17 -3
  281. agno/vectordb/llamaindex/__init__.py +3 -0
  282. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  283. agno/vectordb/milvus/milvus.py +126 -9
  284. agno/vectordb/mongodb/__init__.py +7 -1
  285. agno/vectordb/mongodb/mongodb.py +112 -7
  286. agno/vectordb/pgvector/pgvector.py +142 -21
  287. agno/vectordb/pineconedb/pineconedb.py +80 -8
  288. agno/vectordb/qdrant/qdrant.py +125 -39
  289. agno/vectordb/redis/__init__.py +9 -0
  290. agno/vectordb/redis/redisdb.py +694 -0
  291. agno/vectordb/singlestore/singlestore.py +111 -25
  292. agno/vectordb/surrealdb/surrealdb.py +31 -5
  293. agno/vectordb/upstashdb/upstashdb.py +76 -8
  294. agno/vectordb/weaviate/weaviate.py +86 -15
  295. agno/workflow/__init__.py +2 -0
  296. agno/workflow/agent.py +299 -0
  297. agno/workflow/condition.py +112 -18
  298. agno/workflow/loop.py +69 -10
  299. agno/workflow/parallel.py +266 -118
  300. agno/workflow/router.py +110 -17
  301. agno/workflow/step.py +638 -129
  302. agno/workflow/steps.py +65 -6
  303. agno/workflow/types.py +61 -23
  304. agno/workflow/workflow.py +2085 -272
  305. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
  306. agno-2.3.0.dist-info/RECORD +577 -0
  307. agno/knowledge/reader/url_reader.py +0 -128
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -610
  310. agno/utils/models/aws_claude.py +0 -170
  311. agno-2.0.1.dist-info/RECORD +0 -515
  312. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  313. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/models/groq/groq.py CHANGED
@@ -12,6 +12,7 @@ from agno.models.message import Message
12
12
  from agno.models.metrics import Metrics
13
13
  from agno.models.response import ModelResponse
14
14
  from agno.run.agent import RunOutput
15
+ from agno.utils.http import get_default_async_client, get_default_sync_client
15
16
  from agno.utils.log import log_debug, log_error, log_warning
16
17
  from agno.utils.openai import images_to_message
17
18
 
@@ -61,7 +62,7 @@ class Groq(Model):
61
62
  max_retries: Optional[int] = None
62
63
  default_headers: Optional[Any] = None
63
64
  default_query: Optional[Any] = None
64
- http_client: Optional[httpx.Client] = None
65
+ http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
65
66
  client_params: Optional[Dict[str, Any]] = None
66
67
 
67
68
  # Groq clients
@@ -93,7 +94,7 @@ class Groq(Model):
93
94
 
94
95
  def get_client(self) -> GroqClient:
95
96
  """
96
- Returns a Groq client.
97
+ Returns a Groq client. Caches the client to avoid recreating it on every request.
97
98
 
98
99
  Returns:
99
100
  GroqClient: An instance of the Groq client.
@@ -103,30 +104,46 @@ class Groq(Model):
103
104
 
104
105
  client_params: Dict[str, Any] = self._get_client_params()
105
106
  if self.http_client is not None:
106
- client_params["http_client"] = self.http_client
107
+ if isinstance(self.http_client, httpx.Client):
108
+ client_params["http_client"] = self.http_client
109
+ else:
110
+ log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
111
+ # Use global sync client when user http_client is invalid
112
+ client_params["http_client"] = get_default_sync_client()
113
+ else:
114
+ # Use global sync client when no custom http_client is provided
115
+ client_params["http_client"] = get_default_sync_client()
107
116
 
108
117
  self.client = GroqClient(**client_params)
109
118
  return self.client
110
119
 
111
120
  def get_async_client(self) -> AsyncGroqClient:
112
121
  """
113
- Returns an asynchronous Groq client.
122
+ Returns an asynchronous Groq client. Caches the client to avoid recreating it on every request.
114
123
 
115
124
  Returns:
116
125
  AsyncGroqClient: An instance of the asynchronous Groq client.
117
126
  """
118
- if self.async_client:
127
+ if self.async_client and not self.async_client.is_closed():
119
128
  return self.async_client
120
129
 
121
130
  client_params: Dict[str, Any] = self._get_client_params()
122
131
  if self.http_client:
123
- client_params["http_client"] = self.http_client
132
+ if isinstance(self.http_client, httpx.AsyncClient):
133
+ client_params["http_client"] = self.http_client
134
+ else:
135
+ log_warning(
136
+ "http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
137
+ )
138
+ # Use global async client when user http_client is invalid
139
+ client_params["http_client"] = get_default_async_client()
124
140
  else:
125
- # Create a new async HTTP client with custom limits
126
- client_params["http_client"] = httpx.AsyncClient(
127
- limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100)
128
- )
129
- return AsyncGroqClient(**client_params)
141
+ # Use global async client when no custom http_client is provided
142
+ client_params["http_client"] = get_default_async_client()
143
+
144
+ # Create and cache the client
145
+ self.async_client = AsyncGroqClient(**client_params)
146
+ return self.async_client
130
147
 
131
148
  def get_request_params(
132
149
  self,
@@ -382,7 +382,8 @@ class HuggingFace(Model):
382
382
  List[Dict[str, Any]]: The built tool calls.
383
383
  """
384
384
  tool_calls: List[Dict[str, Any]] = []
385
- for _tool_call in tool_calls_data:
385
+ for tool_call in tool_calls_data:
386
+ _tool_call = tool_call[0]
386
387
  _index = _tool_call.index
387
388
  _tool_call_id = _tool_call.id
388
389
  _tool_call_type = _tool_call.type
@@ -1,4 +1,4 @@
1
- from dataclasses import dataclass
1
+ from dataclasses import dataclass, field
2
2
  from os import getenv
3
3
  from typing import Optional
4
4
 
@@ -22,5 +22,5 @@ class InternLM(OpenAILike):
22
22
  name: str = "InternLM"
23
23
  provider: str = "InternLM"
24
24
 
25
- api_key: Optional[str] = getenv("INTERNLM_API_KEY")
25
+ api_key: Optional[str] = field(default_factory=lambda: getenv("INTERNLM_API_KEY"))
26
26
  base_url: Optional[str] = "https://internlm-chat.intern-ai.org.cn/puyu/api/v1/chat/completions"
@@ -1,4 +1,4 @@
1
- from dataclasses import dataclass
1
+ from dataclasses import dataclass, field
2
2
  from os import getenv
3
3
  from typing import Any, Dict, Optional
4
4
 
@@ -22,10 +22,10 @@ class LangDB(OpenAILike):
22
22
  name: str = "LangDB"
23
23
  provider: str = "LangDB"
24
24
 
25
- api_key: Optional[str] = getenv("LANGDB_API_KEY")
26
- project_id: Optional[str] = getenv("LANGDB_PROJECT_ID")
25
+ api_key: Optional[str] = field(default_factory=lambda: getenv("LANGDB_API_KEY"))
26
+ project_id: Optional[str] = field(default_factory=lambda: getenv("LANGDB_PROJECT_ID"))
27
27
 
28
- base_host_url: str = getenv("LANGDB_API_BASE_URL", "https://api.us-east-1.langdb.ai")
28
+ base_host_url: str = field(default_factory=lambda: getenv("LANGDB_API_BASE_URL", "https://api.us-east-1.langdb.ai"))
29
29
 
30
30
  base_url: Optional[str] = None
31
31
  label: Optional[str] = None
@@ -38,6 +38,10 @@ class LiteLLM(Model):
38
38
  max_tokens: Optional[int] = None
39
39
  temperature: float = 0.7
40
40
  top_p: float = 1.0
41
+ metadata: Optional[Dict[str, Any]] = None
42
+ extra_headers: Optional[Dict[str, Any]] = None
43
+ extra_query: Optional[Dict[str, Any]] = None
44
+ extra_body: Optional[Dict[str, Any]] = None
41
45
  request_params: Optional[Dict[str, Any]] = None
42
46
 
43
47
  client: Optional[Any] = None
@@ -47,7 +51,7 @@ class LiteLLM(Model):
47
51
  super().__post_init__()
48
52
 
49
53
  # Set up API key from environment variable if not already set
50
- if not self.api_key:
54
+ if not self.client and not self.api_key:
51
55
  self.api_key = getenv("LITELLM_API_KEY")
52
56
  if not self.api_key:
53
57
  # Check for other present valid keys, e.g. OPENAI_API_KEY if self.id is an OpenAI model
@@ -148,10 +152,23 @@ class LiteLLM(Model):
148
152
  base_params["api_key"] = self.api_key
149
153
  if self.api_base:
150
154
  base_params["api_base"] = self.api_base
155
+ if self.extra_headers:
156
+ base_params["extra_headers"] = self.extra_headers
157
+ if self.extra_query:
158
+ base_params["extra_query"] = self.extra_query
151
159
  if tools:
152
160
  base_params["tools"] = tools
153
161
  base_params["tool_choice"] = "auto"
154
162
 
163
+ # Handle metadata via extra_body as per LiteLLM docs
164
+ if self.metadata:
165
+ if self.extra_body:
166
+ base_params["extra_body"] = {**self.extra_body, "metadata": self.metadata}
167
+ else:
168
+ base_params["extra_body"] = {"metadata": self.metadata}
169
+ elif self.extra_body:
170
+ base_params["extra_body"] = self.extra_body
171
+
155
172
  # Add additional request params if provided
156
173
  request_params: Dict[str, Any] = {k: v for k, v in base_params.items() if v is not None}
157
174
  if self.request_params:
@@ -1,4 +1,4 @@
1
- from dataclasses import dataclass
1
+ from dataclasses import dataclass, field
2
2
  from os import getenv
3
3
  from typing import Optional
4
4
 
@@ -21,5 +21,5 @@ class LiteLLMOpenAI(OpenAILike):
21
21
  name: str = "LiteLLM"
22
22
  provider: str = "LiteLLM"
23
23
 
24
- api_key: Optional[str] = getenv("LITELLM_API_KEY")
24
+ api_key: Optional[str] = field(default_factory=lambda: getenv("LITELLM_API_KEY"))
25
25
  base_url: str = "http://0.0.0.0:4000"
@@ -0,0 +1,5 @@
1
+ from agno.models.llama_cpp.llama_cpp import LlamaCpp
2
+
3
+ __all__ = [
4
+ "LlamaCpp",
5
+ ]
@@ -0,0 +1,22 @@
1
+ from dataclasses import dataclass
2
+
3
+ from agno.models.openai.like import OpenAILike
4
+
5
+
6
+ @dataclass
7
+ class LlamaCpp(OpenAILike):
8
+ """
9
+ A class for interacting with LLMs using Llama CPP.
10
+
11
+ Attributes:
12
+ id (str): The id of the Llama CPP model. Default is "ggml-org/gpt-oss-20b-GGUF".
13
+ name (str): The name of this chat model instance. Default is "LlamaCpp".
14
+ provider (str): The provider of the model. Default is "LlamaCpp".
15
+ base_url (str): The base url to which the requests are sent.
16
+ """
17
+
18
+ id: str = "ggml-org/gpt-oss-20b-GGUF"
19
+ name: str = "LlamaCpp"
20
+ provider: str = "LlamaCpp"
21
+
22
+ base_url: str = "http://127.0.0.1:8080/v1"
agno/models/message.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  from time import time
3
3
  from typing import Any, Dict, List, Optional, Sequence, Union
4
+ from uuid import uuid4
4
5
 
5
6
  from pydantic import BaseModel, ConfigDict, Field
6
7
 
@@ -51,6 +52,8 @@ class Citations(BaseModel):
51
52
  class Message(BaseModel):
52
53
  """Message sent to the Model"""
53
54
 
55
+ id: str = Field(default_factory=lambda: str(uuid4()))
56
+
54
57
  # The role of the message author.
55
58
  # One of system, user, assistant, or tool.
56
59
  role: str
@@ -74,6 +77,7 @@ class Message(BaseModel):
74
77
  audio_output: Optional[Audio] = None
75
78
  image_output: Optional[Image] = None
76
79
  video_output: Optional[Video] = None
80
+ file_output: Optional[File] = None
77
81
 
78
82
  # The thinking content from the model
79
83
  redacted_reasoning_content: Optional[str] = None
@@ -121,11 +125,144 @@ class Message(BaseModel):
121
125
 
122
126
  @classmethod
123
127
  def from_dict(cls, data: Dict[str, Any]) -> "Message":
128
+ # Handle image reconstruction properly
129
+ if "images" in data and data["images"]:
130
+ reconstructed_images = []
131
+ for i, img_data in enumerate(data["images"]):
132
+ if isinstance(img_data, dict):
133
+ # If content is base64, decode it back to bytes
134
+ if "content" in img_data and isinstance(img_data["content"], str):
135
+ reconstructed_images.append(
136
+ Image.from_base64(
137
+ img_data["content"],
138
+ id=img_data.get("id"),
139
+ mime_type=img_data.get("mime_type"),
140
+ format=img_data.get("format"),
141
+ )
142
+ )
143
+ else:
144
+ # Regular image (filepath/url)
145
+ reconstructed_images.append(Image(**img_data))
146
+ else:
147
+ reconstructed_images.append(img_data)
148
+ data["images"] = reconstructed_images
149
+
150
+ # Handle audio reconstruction properly
151
+ if "audio" in data and data["audio"]:
152
+ reconstructed_audio = []
153
+ for i, aud_data in enumerate(data["audio"]):
154
+ if isinstance(aud_data, dict):
155
+ # If content is base64, decode it back to bytes
156
+ if "content" in aud_data and isinstance(aud_data["content"], str):
157
+ reconstructed_audio.append(
158
+ Audio.from_base64(
159
+ aud_data["content"],
160
+ id=aud_data.get("id"),
161
+ mime_type=aud_data.get("mime_type"),
162
+ transcript=aud_data.get("transcript"),
163
+ expires_at=aud_data.get("expires_at"),
164
+ sample_rate=aud_data.get("sample_rate", 24000),
165
+ channels=aud_data.get("channels", 1),
166
+ )
167
+ )
168
+ else:
169
+ reconstructed_audio.append(Audio(**aud_data))
170
+ else:
171
+ reconstructed_audio.append(aud_data)
172
+ data["audio"] = reconstructed_audio
173
+
174
+ # Handle video reconstruction properly
175
+ if "videos" in data and data["videos"]:
176
+ reconstructed_videos = []
177
+ for i, vid_data in enumerate(data["videos"]):
178
+ if isinstance(vid_data, dict):
179
+ # If content is base64, decode it back to bytes
180
+ if "content" in vid_data and isinstance(vid_data["content"], str):
181
+ reconstructed_videos.append(
182
+ Video.from_base64(
183
+ vid_data["content"],
184
+ id=vid_data.get("id"),
185
+ mime_type=vid_data.get("mime_type"),
186
+ format=vid_data.get("format"),
187
+ )
188
+ )
189
+ else:
190
+ reconstructed_videos.append(Video(**vid_data))
191
+ else:
192
+ reconstructed_videos.append(vid_data)
193
+ data["videos"] = reconstructed_videos
194
+
195
+ # Handle file reconstruction properly
196
+ if "files" in data and data["files"]:
197
+ reconstructed_files = []
198
+ for i, file_data in enumerate(data["files"]):
199
+ if isinstance(file_data, dict):
200
+ # If content is base64, decode it back to bytes
201
+ if "content" in file_data and isinstance(file_data["content"], str):
202
+ reconstructed_files.append(
203
+ File.from_base64(
204
+ file_data["content"],
205
+ id=file_data.get("id"),
206
+ mime_type=file_data.get("mime_type"),
207
+ filename=file_data.get("filename"),
208
+ name=file_data.get("name"),
209
+ format=file_data.get("format"),
210
+ )
211
+ )
212
+ else:
213
+ reconstructed_files.append(File(**file_data))
214
+ else:
215
+ reconstructed_files.append(file_data)
216
+ data["files"] = reconstructed_files
217
+
218
+ if "audio_output" in data and data["audio_output"]:
219
+ aud_data = data["audio_output"]
220
+ if isinstance(aud_data, dict):
221
+ if "content" in aud_data and isinstance(aud_data["content"], str):
222
+ data["audio_output"] = Audio.from_base64(
223
+ aud_data["content"],
224
+ id=aud_data.get("id"),
225
+ mime_type=aud_data.get("mime_type"),
226
+ transcript=aud_data.get("transcript"),
227
+ expires_at=aud_data.get("expires_at"),
228
+ sample_rate=aud_data.get("sample_rate", 24000),
229
+ channels=aud_data.get("channels", 1),
230
+ )
231
+ else:
232
+ data["audio_output"] = Audio(**aud_data)
233
+
234
+ if "image_output" in data and data["image_output"]:
235
+ img_data = data["image_output"]
236
+ if isinstance(img_data, dict):
237
+ if "content" in img_data and isinstance(img_data["content"], str):
238
+ data["image_output"] = Image.from_base64(
239
+ img_data["content"],
240
+ id=img_data.get("id"),
241
+ mime_type=img_data.get("mime_type"),
242
+ format=img_data.get("format"),
243
+ )
244
+ else:
245
+ data["image_output"] = Image(**img_data)
246
+
247
+ if "video_output" in data and data["video_output"]:
248
+ vid_data = data["video_output"]
249
+ if isinstance(vid_data, dict):
250
+ if "content" in vid_data and isinstance(vid_data["content"], str):
251
+ data["video_output"] = Video.from_base64(
252
+ vid_data["content"],
253
+ id=vid_data.get("id"),
254
+ mime_type=vid_data.get("mime_type"),
255
+ format=vid_data.get("format"),
256
+ )
257
+ else:
258
+ data["video_output"] = Video(**vid_data)
259
+
124
260
  return cls(**data)
125
261
 
126
262
  def to_dict(self) -> Dict[str, Any]:
127
263
  """Returns the message as a dictionary."""
128
264
  message_dict = {
265
+ "id": self.id,
129
266
  "content": self.content,
130
267
  "reasoning_content": self.reasoning_content,
131
268
  "from_history": self.from_history,
@@ -152,6 +289,8 @@ class Message(BaseModel):
152
289
  message_dict["audio"] = [aud.to_dict() for aud in self.audio]
153
290
  if self.videos:
154
291
  message_dict["videos"] = [vid.to_dict() for vid in self.videos]
292
+ if self.files:
293
+ message_dict["files"] = [file.to_dict() for file in self.files]
155
294
  if self.audio_output:
156
295
  message_dict["audio_output"] = self.audio_output.to_dict()
157
296
 
agno/models/meta/llama.py CHANGED
@@ -12,6 +12,7 @@ from agno.models.message import Message
12
12
  from agno.models.metrics import Metrics
13
13
  from agno.models.response import ModelResponse
14
14
  from agno.run.agent import RunOutput
15
+ from agno.utils.http import get_default_async_client, get_default_sync_client
15
16
  from agno.utils.log import log_debug, log_error, log_warning
16
17
  from agno.utils.models.llama import format_message
17
18
 
@@ -61,7 +62,7 @@ class Llama(Model):
61
62
  max_retries: Optional[int] = None
62
63
  default_headers: Optional[Any] = None
63
64
  default_query: Optional[Any] = None
64
- http_client: Optional[httpx.Client] = None
65
+ http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
65
66
  client_params: Optional[Dict[str, Any]] = None
66
67
 
67
68
  # OpenAI clients
@@ -104,8 +105,16 @@ class Llama(Model):
104
105
  return self.client
105
106
 
106
107
  client_params: Dict[str, Any] = self._get_client_params()
107
- if self.http_client is not None:
108
- client_params["http_client"] = self.http_client
108
+ if self.http_client:
109
+ if isinstance(self.http_client, httpx.Client):
110
+ client_params["http_client"] = self.http_client
111
+ else:
112
+ log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
113
+ # Use global sync client when user http_client is invalid
114
+ client_params["http_client"] = get_default_sync_client()
115
+ else:
116
+ # Use global sync client when no custom http_client is provided
117
+ client_params["http_client"] = get_default_sync_client()
109
118
  self.client = LlamaAPIClient(**client_params)
110
119
  return self.client
111
120
 
@@ -116,18 +125,26 @@ class Llama(Model):
116
125
  Returns:
117
126
  AsyncLlamaAPIClient: An instance of the asynchronous Llama client.
118
127
  """
119
- if self.async_client:
128
+ if self.async_client and not self.async_client.is_closed():
120
129
  return self.async_client
121
130
 
122
131
  client_params: Dict[str, Any] = self._get_client_params()
123
132
  if self.http_client:
124
- client_params["http_client"] = self.http_client
133
+ if isinstance(self.http_client, httpx.AsyncClient):
134
+ client_params["http_client"] = self.http_client
135
+ else:
136
+ log_warning(
137
+ "http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
138
+ )
139
+ # Use global async client when user http_client is invalid
140
+ client_params["http_client"] = get_default_async_client()
125
141
  else:
126
- # Create a new async HTTP client with custom limits
127
- client_params["http_client"] = httpx.AsyncClient(
128
- limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100)
129
- )
130
- return AsyncLlamaAPIClient(**client_params)
142
+ # Use global async client when no custom http_client is provided
143
+ client_params["http_client"] = get_default_async_client()
144
+
145
+ # Create and cache the client
146
+ self.async_client = AsyncLlamaAPIClient(**client_params)
147
+ return self.async_client
131
148
 
132
149
  def get_request_params(
133
150
  self,
@@ -1,9 +1,7 @@
1
- from dataclasses import dataclass
1
+ from dataclasses import dataclass, field
2
2
  from os import getenv
3
3
  from typing import Any, Dict, Optional
4
4
 
5
- import httpx
6
-
7
5
  try:
8
6
  from openai import AsyncOpenAI as AsyncOpenAIClient
9
7
  except ImportError:
@@ -31,7 +29,7 @@ class LlamaOpenAI(OpenAILike):
31
29
  name: str = "LlamaOpenAI"
32
30
  provider: str = "LlamaOpenAI"
33
31
 
34
- api_key: Optional[str] = getenv("LLAMA_API_KEY")
32
+ api_key: Optional[str] = field(default_factory=lambda: getenv("LLAMA_API_KEY"))
35
33
  base_url: Optional[str] = "https://api.llama.com/compat/v1/"
36
34
 
37
35
  # Request parameters
@@ -48,6 +46,9 @@ class LlamaOpenAI(OpenAILike):
48
46
  supports_native_structured_outputs: bool = False
49
47
  supports_json_schema_outputs: bool = True
50
48
 
49
+ # Cached async client
50
+ openai_async_client: Optional[AsyncOpenAIClient] = None
51
+
51
52
  def _format_message(self, message: Message) -> Dict[str, Any]:
52
53
  """
53
54
  Format a message into the format expected by Llama API.
@@ -59,16 +60,3 @@ class LlamaOpenAI(OpenAILike):
59
60
  Dict[str, Any]: The formatted message.
60
61
  """
61
62
  return format_message(message, openai_like=True)
62
-
63
- def get_async_client(self):
64
- """Override to provide custom httpx client that properly handles redirects"""
65
- client_params = self._get_client_params()
66
-
67
- # Llama gives a 307 redirect error, so we need to set up a custom client to allow redirects
68
- client_params["http_client"] = httpx.AsyncClient(
69
- limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100),
70
- follow_redirects=True,
71
- timeout=httpx.Timeout(30.0),
72
- )
73
-
74
- return AsyncOpenAIClient(**client_params)
@@ -1,4 +1,4 @@
1
- from dataclasses import dataclass
1
+ from dataclasses import dataclass, field
2
2
  from os import getenv
3
3
  from typing import Any, Dict, Optional
4
4
 
@@ -9,22 +9,22 @@ from agno.models.openai.like import OpenAILike
9
9
  @dataclass
10
10
  class Nebius(OpenAILike):
11
11
  """
12
- A class for interacting with Nebius AI Studio models.
12
+ A class for interacting with Nebius Token Factory models.
13
13
 
14
14
  Attributes:
15
15
  id (str): The model id. Defaults to "Qwen/Qwen3-235B-A22B"".
16
16
  name (str): The model name. Defaults to "Nebius".
17
17
  provider (str): The provider name. Defaults to "Nebius".
18
18
  api_key (Optional[str]): The API key.
19
- base_url (str): The base URL. Defaults to "https://api.studio.nebius.com/v1".
19
+ base_url (str): The base URL. Defaults to "https://api.tokenfactory.nebius.com/v1".
20
20
  """
21
21
 
22
- id: str = "Qwen/Qwen3-4B-fast" # Default model for chat
22
+ id: str = "openai/gpt-oss-20b" # Default model for chat
23
23
  name: str = "Nebius"
24
24
  provider: str = "Nebius"
25
25
 
26
- api_key: Optional[str] = getenv("NEBIUS_API_KEY")
27
- base_url: str = "https://api.studio.nebius.com/v1/"
26
+ api_key: Optional[str] = field(default_factory=lambda: getenv("NEBIUS_API_KEY"))
27
+ base_url: str = "https://api.tokenfactory.nebius.com/v1/"
28
28
 
29
29
  def _get_client_params(self) -> Dict[str, Any]:
30
30
  if not self.api_key:
@@ -0,0 +1,3 @@
1
+ from agno.models.nexus.nexus import Nexus
2
+
3
+ __all__ = ["Nexus"]
@@ -0,0 +1,22 @@
1
+ from dataclasses import dataclass
2
+
3
+ from agno.models.openai.like import OpenAILike
4
+
5
+
6
+ @dataclass
7
+ class Nexus(OpenAILike):
8
+ """
9
+ A class for interacting with LLMs using Nexus.
10
+
11
+ Attributes:
12
+ id (str): The id of the Nexus model to use. Default is "openai/gpt-4".
13
+ name (str): The name of this chat model instance. Default is "Nexus"
14
+ provider (str): The provider of the model. Default is "Nexus".
15
+ base_url (str): The base url to which the requests are sent.
16
+ """
17
+
18
+ id: str = "openai/gpt-4"
19
+ name: str = "Nexus"
20
+ provider: str = "Nexus"
21
+
22
+ base_url: str = "http://localhost:8000/llm/v1/"
@@ -1,4 +1,4 @@
1
- from dataclasses import dataclass
1
+ from dataclasses import dataclass, field
2
2
  from os import getenv
3
3
  from typing import Optional
4
4
 
@@ -22,7 +22,7 @@ class Nvidia(OpenAILike):
22
22
  name: str = "Nvidia"
23
23
  provider: str = "Nvidia"
24
24
 
25
- api_key: Optional[str] = getenv("NVIDIA_API_KEY")
25
+ api_key: Optional[str] = field(default_factory=lambda: getenv("NVIDIA_API_KEY"))
26
26
  base_url: str = "https://integrate.api.nvidia.com/v1"
27
27
 
28
28
  supports_native_structured_outputs: bool = False