agno 2.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 (314) hide show
  1. agno/agent/agent.py +5540 -2273
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/compression/__init__.py +3 -0
  5. agno/compression/manager.py +247 -0
  6. agno/culture/__init__.py +3 -0
  7. agno/culture/manager.py +956 -0
  8. agno/db/async_postgres/__init__.py +3 -0
  9. agno/db/base.py +689 -6
  10. agno/db/dynamo/dynamo.py +933 -37
  11. agno/db/dynamo/schemas.py +174 -10
  12. agno/db/dynamo/utils.py +63 -4
  13. agno/db/firestore/firestore.py +831 -9
  14. agno/db/firestore/schemas.py +51 -0
  15. agno/db/firestore/utils.py +102 -4
  16. agno/db/gcs_json/gcs_json_db.py +660 -12
  17. agno/db/gcs_json/utils.py +60 -26
  18. agno/db/in_memory/in_memory_db.py +287 -14
  19. agno/db/in_memory/utils.py +60 -2
  20. agno/db/json/json_db.py +590 -14
  21. agno/db/json/utils.py +60 -26
  22. agno/db/migrations/manager.py +199 -0
  23. agno/db/migrations/v1_to_v2.py +43 -13
  24. agno/db/migrations/versions/__init__.py +0 -0
  25. agno/db/migrations/versions/v2_3_0.py +938 -0
  26. agno/db/mongo/__init__.py +15 -1
  27. agno/db/mongo/async_mongo.py +2760 -0
  28. agno/db/mongo/mongo.py +879 -11
  29. agno/db/mongo/schemas.py +42 -0
  30. agno/db/mongo/utils.py +80 -8
  31. agno/db/mysql/__init__.py +2 -1
  32. agno/db/mysql/async_mysql.py +2912 -0
  33. agno/db/mysql/mysql.py +946 -68
  34. agno/db/mysql/schemas.py +72 -10
  35. agno/db/mysql/utils.py +198 -7
  36. agno/db/postgres/__init__.py +2 -1
  37. agno/db/postgres/async_postgres.py +2579 -0
  38. agno/db/postgres/postgres.py +942 -57
  39. agno/db/postgres/schemas.py +81 -18
  40. agno/db/postgres/utils.py +164 -2
  41. agno/db/redis/redis.py +671 -7
  42. agno/db/redis/schemas.py +50 -0
  43. agno/db/redis/utils.py +65 -7
  44. agno/db/schemas/__init__.py +2 -1
  45. agno/db/schemas/culture.py +120 -0
  46. agno/db/schemas/evals.py +1 -0
  47. agno/db/schemas/memory.py +17 -2
  48. agno/db/singlestore/schemas.py +63 -0
  49. agno/db/singlestore/singlestore.py +949 -83
  50. agno/db/singlestore/utils.py +60 -2
  51. agno/db/sqlite/__init__.py +2 -1
  52. agno/db/sqlite/async_sqlite.py +2911 -0
  53. agno/db/sqlite/schemas.py +62 -0
  54. agno/db/sqlite/sqlite.py +965 -46
  55. agno/db/sqlite/utils.py +169 -8
  56. agno/db/surrealdb/__init__.py +3 -0
  57. agno/db/surrealdb/metrics.py +292 -0
  58. agno/db/surrealdb/models.py +334 -0
  59. agno/db/surrealdb/queries.py +71 -0
  60. agno/db/surrealdb/surrealdb.py +1908 -0
  61. agno/db/surrealdb/utils.py +147 -0
  62. agno/db/utils.py +2 -0
  63. agno/eval/__init__.py +10 -0
  64. agno/eval/accuracy.py +75 -55
  65. agno/eval/agent_as_judge.py +861 -0
  66. agno/eval/base.py +29 -0
  67. agno/eval/performance.py +16 -7
  68. agno/eval/reliability.py +28 -16
  69. agno/eval/utils.py +35 -17
  70. agno/exceptions.py +27 -2
  71. agno/filters.py +354 -0
  72. agno/guardrails/prompt_injection.py +1 -0
  73. agno/hooks/__init__.py +3 -0
  74. agno/hooks/decorator.py +164 -0
  75. agno/integrations/discord/client.py +1 -1
  76. agno/knowledge/chunking/agentic.py +13 -10
  77. agno/knowledge/chunking/fixed.py +4 -1
  78. agno/knowledge/chunking/semantic.py +9 -4
  79. agno/knowledge/chunking/strategy.py +59 -15
  80. agno/knowledge/embedder/fastembed.py +1 -1
  81. agno/knowledge/embedder/nebius.py +1 -1
  82. agno/knowledge/embedder/ollama.py +8 -0
  83. agno/knowledge/embedder/openai.py +8 -8
  84. agno/knowledge/embedder/sentence_transformer.py +6 -2
  85. agno/knowledge/embedder/vllm.py +262 -0
  86. agno/knowledge/knowledge.py +1618 -318
  87. agno/knowledge/reader/base.py +6 -2
  88. agno/knowledge/reader/csv_reader.py +8 -10
  89. agno/knowledge/reader/docx_reader.py +5 -6
  90. agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
  91. agno/knowledge/reader/json_reader.py +5 -4
  92. agno/knowledge/reader/markdown_reader.py +8 -8
  93. agno/knowledge/reader/pdf_reader.py +17 -19
  94. agno/knowledge/reader/pptx_reader.py +101 -0
  95. agno/knowledge/reader/reader_factory.py +32 -3
  96. agno/knowledge/reader/s3_reader.py +3 -3
  97. agno/knowledge/reader/tavily_reader.py +193 -0
  98. agno/knowledge/reader/text_reader.py +22 -10
  99. agno/knowledge/reader/web_search_reader.py +1 -48
  100. agno/knowledge/reader/website_reader.py +10 -10
  101. agno/knowledge/reader/wikipedia_reader.py +33 -1
  102. agno/knowledge/types.py +1 -0
  103. agno/knowledge/utils.py +72 -7
  104. agno/media.py +22 -6
  105. agno/memory/__init__.py +14 -1
  106. agno/memory/manager.py +544 -83
  107. agno/memory/strategies/__init__.py +15 -0
  108. agno/memory/strategies/base.py +66 -0
  109. agno/memory/strategies/summarize.py +196 -0
  110. agno/memory/strategies/types.py +37 -0
  111. agno/models/aimlapi/aimlapi.py +17 -0
  112. agno/models/anthropic/claude.py +515 -40
  113. agno/models/aws/bedrock.py +102 -21
  114. agno/models/aws/claude.py +131 -274
  115. agno/models/azure/ai_foundry.py +41 -19
  116. agno/models/azure/openai_chat.py +39 -8
  117. agno/models/base.py +1249 -525
  118. agno/models/cerebras/cerebras.py +91 -21
  119. agno/models/cerebras/cerebras_openai.py +21 -2
  120. agno/models/cohere/chat.py +40 -6
  121. agno/models/cometapi/cometapi.py +18 -1
  122. agno/models/dashscope/dashscope.py +2 -3
  123. agno/models/deepinfra/deepinfra.py +18 -1
  124. agno/models/deepseek/deepseek.py +69 -3
  125. agno/models/fireworks/fireworks.py +18 -1
  126. agno/models/google/gemini.py +877 -80
  127. agno/models/google/utils.py +22 -0
  128. agno/models/groq/groq.py +51 -18
  129. agno/models/huggingface/huggingface.py +17 -6
  130. agno/models/ibm/watsonx.py +16 -6
  131. agno/models/internlm/internlm.py +18 -1
  132. agno/models/langdb/langdb.py +13 -1
  133. agno/models/litellm/chat.py +44 -9
  134. agno/models/litellm/litellm_openai.py +18 -1
  135. agno/models/message.py +28 -5
  136. agno/models/meta/llama.py +47 -14
  137. agno/models/meta/llama_openai.py +22 -17
  138. agno/models/mistral/mistral.py +8 -4
  139. agno/models/nebius/nebius.py +6 -7
  140. agno/models/nvidia/nvidia.py +20 -3
  141. agno/models/ollama/chat.py +24 -8
  142. agno/models/openai/chat.py +104 -29
  143. agno/models/openai/responses.py +101 -81
  144. agno/models/openrouter/openrouter.py +60 -3
  145. agno/models/perplexity/perplexity.py +17 -1
  146. agno/models/portkey/portkey.py +7 -6
  147. agno/models/requesty/requesty.py +24 -4
  148. agno/models/response.py +73 -2
  149. agno/models/sambanova/sambanova.py +20 -3
  150. agno/models/siliconflow/siliconflow.py +19 -2
  151. agno/models/together/together.py +20 -3
  152. agno/models/utils.py +254 -8
  153. agno/models/vercel/v0.py +20 -3
  154. agno/models/vertexai/__init__.py +0 -0
  155. agno/models/vertexai/claude.py +190 -0
  156. agno/models/vllm/vllm.py +19 -14
  157. agno/models/xai/xai.py +19 -2
  158. agno/os/app.py +549 -152
  159. agno/os/auth.py +190 -3
  160. agno/os/config.py +23 -0
  161. agno/os/interfaces/a2a/router.py +8 -11
  162. agno/os/interfaces/a2a/utils.py +1 -1
  163. agno/os/interfaces/agui/router.py +18 -3
  164. agno/os/interfaces/agui/utils.py +152 -39
  165. agno/os/interfaces/slack/router.py +55 -37
  166. agno/os/interfaces/slack/slack.py +9 -1
  167. agno/os/interfaces/whatsapp/router.py +0 -1
  168. agno/os/interfaces/whatsapp/security.py +3 -1
  169. agno/os/mcp.py +110 -52
  170. agno/os/middleware/__init__.py +2 -0
  171. agno/os/middleware/jwt.py +676 -112
  172. agno/os/router.py +40 -1478
  173. agno/os/routers/agents/__init__.py +3 -0
  174. agno/os/routers/agents/router.py +599 -0
  175. agno/os/routers/agents/schema.py +261 -0
  176. agno/os/routers/evals/evals.py +96 -39
  177. agno/os/routers/evals/schemas.py +65 -33
  178. agno/os/routers/evals/utils.py +80 -10
  179. agno/os/routers/health.py +10 -4
  180. agno/os/routers/knowledge/knowledge.py +196 -38
  181. agno/os/routers/knowledge/schemas.py +82 -22
  182. agno/os/routers/memory/memory.py +279 -52
  183. agno/os/routers/memory/schemas.py +46 -17
  184. agno/os/routers/metrics/metrics.py +20 -8
  185. agno/os/routers/metrics/schemas.py +16 -16
  186. agno/os/routers/session/session.py +462 -34
  187. agno/os/routers/teams/__init__.py +3 -0
  188. agno/os/routers/teams/router.py +512 -0
  189. agno/os/routers/teams/schema.py +257 -0
  190. agno/os/routers/traces/__init__.py +3 -0
  191. agno/os/routers/traces/schemas.py +414 -0
  192. agno/os/routers/traces/traces.py +499 -0
  193. agno/os/routers/workflows/__init__.py +3 -0
  194. agno/os/routers/workflows/router.py +624 -0
  195. agno/os/routers/workflows/schema.py +75 -0
  196. agno/os/schema.py +256 -693
  197. agno/os/scopes.py +469 -0
  198. agno/os/utils.py +514 -36
  199. agno/reasoning/anthropic.py +80 -0
  200. agno/reasoning/gemini.py +73 -0
  201. agno/reasoning/openai.py +5 -0
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +155 -32
  205. agno/run/base.py +55 -3
  206. agno/run/requirement.py +181 -0
  207. agno/run/team.py +125 -38
  208. agno/run/workflow.py +72 -18
  209. agno/session/agent.py +102 -89
  210. agno/session/summary.py +56 -15
  211. agno/session/team.py +164 -90
  212. agno/session/workflow.py +405 -40
  213. agno/table.py +10 -0
  214. agno/team/team.py +3974 -1903
  215. agno/tools/dalle.py +2 -4
  216. agno/tools/eleven_labs.py +23 -25
  217. agno/tools/exa.py +21 -16
  218. agno/tools/file.py +153 -23
  219. agno/tools/file_generation.py +16 -10
  220. agno/tools/firecrawl.py +15 -7
  221. agno/tools/function.py +193 -38
  222. agno/tools/gmail.py +238 -14
  223. agno/tools/google_drive.py +271 -0
  224. agno/tools/googlecalendar.py +36 -8
  225. agno/tools/googlesheets.py +20 -5
  226. agno/tools/jira.py +20 -0
  227. agno/tools/mcp/__init__.py +10 -0
  228. agno/tools/mcp/mcp.py +331 -0
  229. agno/tools/mcp/multi_mcp.py +347 -0
  230. agno/tools/mcp/params.py +24 -0
  231. agno/tools/mcp_toolbox.py +3 -3
  232. agno/tools/models/nebius.py +5 -5
  233. agno/tools/models_labs.py +20 -10
  234. agno/tools/nano_banana.py +151 -0
  235. agno/tools/notion.py +204 -0
  236. agno/tools/parallel.py +314 -0
  237. agno/tools/postgres.py +76 -36
  238. agno/tools/redshift.py +406 -0
  239. agno/tools/scrapegraph.py +1 -1
  240. agno/tools/shopify.py +1519 -0
  241. agno/tools/slack.py +18 -3
  242. agno/tools/spotify.py +919 -0
  243. agno/tools/tavily.py +146 -0
  244. agno/tools/toolkit.py +25 -0
  245. agno/tools/workflow.py +8 -1
  246. agno/tools/yfinance.py +12 -11
  247. agno/tracing/__init__.py +12 -0
  248. agno/tracing/exporter.py +157 -0
  249. agno/tracing/schemas.py +276 -0
  250. agno/tracing/setup.py +111 -0
  251. agno/utils/agent.py +938 -0
  252. agno/utils/cryptography.py +22 -0
  253. agno/utils/dttm.py +33 -0
  254. agno/utils/events.py +151 -3
  255. agno/utils/gemini.py +15 -5
  256. agno/utils/hooks.py +118 -4
  257. agno/utils/http.py +113 -2
  258. agno/utils/knowledge.py +12 -5
  259. agno/utils/log.py +1 -0
  260. agno/utils/mcp.py +92 -2
  261. agno/utils/media.py +187 -1
  262. agno/utils/merge_dict.py +3 -3
  263. agno/utils/message.py +60 -0
  264. agno/utils/models/ai_foundry.py +9 -2
  265. agno/utils/models/claude.py +49 -14
  266. agno/utils/models/cohere.py +9 -2
  267. agno/utils/models/llama.py +9 -2
  268. agno/utils/models/mistral.py +4 -2
  269. agno/utils/print_response/agent.py +109 -16
  270. agno/utils/print_response/team.py +223 -30
  271. agno/utils/print_response/workflow.py +251 -34
  272. agno/utils/streamlit.py +1 -1
  273. agno/utils/team.py +98 -9
  274. agno/utils/tokens.py +657 -0
  275. agno/vectordb/base.py +39 -7
  276. agno/vectordb/cassandra/cassandra.py +21 -5
  277. agno/vectordb/chroma/chromadb.py +43 -12
  278. agno/vectordb/clickhouse/clickhousedb.py +21 -5
  279. agno/vectordb/couchbase/couchbase.py +29 -5
  280. agno/vectordb/lancedb/lance_db.py +92 -181
  281. agno/vectordb/langchaindb/langchaindb.py +24 -4
  282. agno/vectordb/lightrag/lightrag.py +17 -3
  283. agno/vectordb/llamaindex/llamaindexdb.py +25 -5
  284. agno/vectordb/milvus/milvus.py +50 -37
  285. agno/vectordb/mongodb/__init__.py +7 -1
  286. agno/vectordb/mongodb/mongodb.py +36 -30
  287. agno/vectordb/pgvector/pgvector.py +201 -77
  288. agno/vectordb/pineconedb/pineconedb.py +41 -23
  289. agno/vectordb/qdrant/qdrant.py +67 -54
  290. agno/vectordb/redis/__init__.py +9 -0
  291. agno/vectordb/redis/redisdb.py +682 -0
  292. agno/vectordb/singlestore/singlestore.py +50 -29
  293. agno/vectordb/surrealdb/surrealdb.py +31 -41
  294. agno/vectordb/upstashdb/upstashdb.py +34 -6
  295. agno/vectordb/weaviate/weaviate.py +53 -14
  296. agno/workflow/__init__.py +2 -0
  297. agno/workflow/agent.py +299 -0
  298. agno/workflow/condition.py +120 -18
  299. agno/workflow/loop.py +77 -10
  300. agno/workflow/parallel.py +231 -143
  301. agno/workflow/router.py +118 -17
  302. agno/workflow/step.py +609 -170
  303. agno/workflow/steps.py +73 -6
  304. agno/workflow/types.py +96 -21
  305. agno/workflow/workflow.py +2039 -262
  306. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
  307. agno-2.3.13.dist-info/RECORD +613 -0
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -679
  310. agno/tools/memori.py +0 -339
  311. agno-2.1.2.dist-info/RECORD +0 -543
  312. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
  313. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
@@ -6,16 +6,18 @@ import httpx
6
6
  from pydantic import BaseModel
7
7
  from typing_extensions import Literal
8
8
 
9
- from agno.exceptions import ModelProviderError
9
+ from agno.exceptions import ModelAuthenticationError, ModelProviderError
10
10
  from agno.media import File
11
- from agno.models.base import MessageData, Model
11
+ from agno.models.base import Model
12
12
  from agno.models.message import Citations, Message, UrlCitation
13
13
  from agno.models.metrics import Metrics
14
14
  from agno.models.response import ModelResponse
15
15
  from agno.run.agent import RunOutput
16
+ from agno.utils.http import get_default_async_client, get_default_sync_client
16
17
  from agno.utils.log import log_debug, log_error, log_warning
17
18
  from agno.utils.models.openai_responses import images_to_message
18
19
  from agno.utils.models.schema_utils import get_response_schema_for_provider
20
+ from agno.utils.tokens import count_schema_tokens
19
21
 
20
22
  try:
21
23
  from openai import APIConnectionError, APIStatusError, AsyncOpenAI, OpenAI, RateLimitError
@@ -53,6 +55,7 @@ class OpenAIResponses(Model):
53
55
  truncation: Optional[Literal["auto", "disabled"]] = None
54
56
  user: Optional[str] = None
55
57
  service_tier: Optional[Literal["auto", "default", "flex", "priority"]] = None
58
+ strict_output: bool = True # When True, guarantees schema adherence for structured outputs. When False, attempts to follow schema as a guide but may occasionally deviate
56
59
  extra_headers: Optional[Any] = None
57
60
  extra_query: Optional[Any] = None
58
61
  extra_body: Optional[Any] = None
@@ -66,7 +69,7 @@ class OpenAIResponses(Model):
66
69
  max_retries: Optional[int] = None
67
70
  default_headers: Optional[Dict[str, str]] = None
68
71
  default_query: Optional[Dict[str, str]] = None
69
- http_client: Optional[httpx.Client] = None
72
+ http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
70
73
  client_params: Optional[Dict[str, Any]] = None
71
74
 
72
75
  # Parameters affecting built-in tools
@@ -115,7 +118,10 @@ class OpenAIResponses(Model):
115
118
  if not self.api_key:
116
119
  self.api_key = getenv("OPENAI_API_KEY")
117
120
  if not self.api_key:
118
- log_error("OPENAI_API_KEY not set. Please set the OPENAI_API_KEY environment variable.")
121
+ raise ModelAuthenticationError(
122
+ message="OPENAI_API_KEY not set. Please set the OPENAI_API_KEY environment variable.",
123
+ model_name=self.name,
124
+ )
119
125
 
120
126
  # Define base client params
121
127
  base_params = {
@@ -139,7 +145,7 @@ class OpenAIResponses(Model):
139
145
 
140
146
  def get_client(self) -> OpenAI:
141
147
  """
142
- Returns an OpenAI client.
148
+ Returns an OpenAI client. Caches the client to avoid recreating it on every request.
143
149
 
144
150
  Returns:
145
151
  OpenAI: An instance of the OpenAI client.
@@ -150,28 +156,29 @@ class OpenAIResponses(Model):
150
156
  client_params: Dict[str, Any] = self._get_client_params()
151
157
  if self.http_client is not None:
152
158
  client_params["http_client"] = self.http_client
159
+ else:
160
+ # Use global sync client when no custom http_client is provided
161
+ client_params["http_client"] = get_default_sync_client()
153
162
 
154
163
  self.client = OpenAI(**client_params)
155
164
  return self.client
156
165
 
157
166
  def get_async_client(self) -> AsyncOpenAI:
158
167
  """
159
- Returns an asynchronous OpenAI client.
168
+ Returns an asynchronous OpenAI client. Caches the client to avoid recreating it on every request.
160
169
 
161
170
  Returns:
162
171
  AsyncOpenAI: An instance of the asynchronous OpenAI client.
163
172
  """
164
- if self.async_client:
173
+ if self.async_client and not self.async_client.is_closed():
165
174
  return self.async_client
166
175
 
167
176
  client_params: Dict[str, Any] = self._get_client_params()
168
- if self.http_client:
177
+ if self.http_client and isinstance(self.http_client, httpx.AsyncClient):
169
178
  client_params["http_client"] = self.http_client
170
179
  else:
171
- # Create a new async HTTP client with custom limits
172
- client_params["http_client"] = httpx.AsyncClient(
173
- limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100)
174
- )
180
+ # Use global async client when no custom http_client is provided
181
+ client_params["http_client"] = get_default_async_client()
175
182
 
176
183
  self.async_client = AsyncOpenAI(**client_params)
177
184
  return self.async_client
@@ -224,7 +231,7 @@ class OpenAIResponses(Model):
224
231
  "type": "json_schema",
225
232
  "name": response_format.__name__,
226
233
  "schema": schema,
227
- "strict": True,
234
+ "strict": self.strict_output,
228
235
  }
229
236
  else:
230
237
  # JSON mode
@@ -301,6 +308,8 @@ class OpenAIResponses(Model):
301
308
 
302
309
  def _upload_file(self, file: File) -> Optional[str]:
303
310
  """Upload a file to the OpenAI vector database."""
311
+ from pathlib import Path
312
+ from urllib.parse import urlparse
304
313
 
305
314
  if file.url is not None:
306
315
  file_content_tuple = file.file_url_content
@@ -308,13 +317,12 @@ class OpenAIResponses(Model):
308
317
  file_content = file_content_tuple[0]
309
318
  else:
310
319
  return None
311
- file_name = file.url.split("/")[-1]
320
+ file_name = Path(urlparse(file.url).path).name or "file"
312
321
  file_tuple = (file_name, file_content)
313
322
  result = self.get_client().files.create(file=file_tuple, purpose="assistants")
314
323
  return result.id
315
324
  elif file.filepath is not None:
316
325
  import mimetypes
317
- from pathlib import Path
318
326
 
319
327
  file_path = file.filepath if isinstance(file.filepath, Path) else Path(file.filepath)
320
328
  if file_path.exists() and file_path.is_file():
@@ -392,12 +400,15 @@ class OpenAIResponses(Model):
392
400
 
393
401
  return formatted_tools
394
402
 
395
- def _format_messages(self, messages: List[Message]) -> List[Union[Dict[str, Any], ResponseReasoningItem]]:
403
+ def _format_messages(
404
+ self, messages: List[Message], compress_tool_results: bool = False
405
+ ) -> List[Union[Dict[str, Any], ResponseReasoningItem]]:
396
406
  """
397
407
  Format a message into the format expected by OpenAI.
398
408
 
399
409
  Args:
400
410
  messages (List[Message]): The message to format.
411
+ compress_tool_results: Whether to compress tool results.
401
412
 
402
413
  Returns:
403
414
  Dict[str, Any]: The formatted message.
@@ -442,7 +453,7 @@ class OpenAIResponses(Model):
442
453
  if message.role in ["user", "system"]:
443
454
  message_dict: Dict[str, Any] = {
444
455
  "role": self.role_map[message.role],
445
- "content": message.content,
456
+ "content": message.get_content(use_compressed_content=compress_tool_results),
446
457
  }
447
458
  message_dict = {k: v for k, v in message_dict.items() if v is not None}
448
459
 
@@ -466,7 +477,9 @@ class OpenAIResponses(Model):
466
477
 
467
478
  # Tool call result
468
479
  elif message.role == "tool":
469
- if message.tool_call_id and message.content is not None:
480
+ tool_result = message.get_content(use_compressed_content=compress_tool_results)
481
+
482
+ if message.tool_call_id and tool_result is not None:
470
483
  function_call_id = message.tool_call_id
471
484
  # Normalize: if a fc_* id was provided, translate to its corresponding call_* id
472
485
  if isinstance(function_call_id, str) and function_call_id in fc_id_to_call_id:
@@ -474,7 +487,7 @@ class OpenAIResponses(Model):
474
487
  else:
475
488
  call_id_value = function_call_id
476
489
  formatted_messages.append(
477
- {"type": "function_call_output", "call_id": call_id_value, "output": message.content}
490
+ {"type": "function_call_output", "call_id": call_id_value, "output": tool_result}
478
491
  )
479
492
  # Tool Calls
480
493
  elif message.tool_calls is not None and len(message.tool_calls) > 0:
@@ -508,6 +521,49 @@ class OpenAIResponses(Model):
508
521
  formatted_messages.append(reasoning_output)
509
522
  return formatted_messages
510
523
 
524
+ def count_tokens(
525
+ self,
526
+ messages: List[Message],
527
+ tools: Optional[List[Dict[str, Any]]] = None,
528
+ output_schema: Optional[Union[Dict, Type[BaseModel]]] = None,
529
+ ) -> int:
530
+ try:
531
+ formatted_input = self._format_messages(messages, compress_tool_results=True)
532
+ formatted_tools = self._format_tool_params(messages, tools) if tools else None
533
+
534
+ response = self.get_client().responses.input_tokens.count(
535
+ model=self.id,
536
+ input=formatted_input, # type: ignore
537
+ instructions=self.instructions, # type: ignore
538
+ tools=formatted_tools, # type: ignore
539
+ )
540
+ return response.input_tokens + count_schema_tokens(output_schema, self.id)
541
+ except Exception as e:
542
+ log_warning(f"Failed to count tokens via API: {e}")
543
+ return super().count_tokens(messages, tools, output_schema)
544
+
545
+ async def acount_tokens(
546
+ self,
547
+ messages: List[Message],
548
+ tools: Optional[List[Dict[str, Any]]] = None,
549
+ output_schema: Optional[Union[Dict, Type[BaseModel]]] = None,
550
+ ) -> int:
551
+ """Async version of count_tokens using the async client."""
552
+ try:
553
+ formatted_input = self._format_messages(messages, compress_tool_results=True)
554
+ formatted_tools = self._format_tool_params(messages, tools) if tools else None
555
+
556
+ response = await self.get_async_client().responses.input_tokens.count(
557
+ model=self.id,
558
+ input=formatted_input, # type: ignore
559
+ instructions=self.instructions, # type: ignore
560
+ tools=formatted_tools, # type: ignore
561
+ )
562
+ return response.input_tokens + count_schema_tokens(output_schema, self.id)
563
+ except Exception as e:
564
+ log_warning(f"Failed to count tokens via API: {e}")
565
+ return await super().acount_tokens(messages, tools, output_schema)
566
+
511
567
  def invoke(
512
568
  self,
513
569
  messages: List[Message],
@@ -516,6 +572,7 @@ class OpenAIResponses(Model):
516
572
  tools: Optional[List[Dict[str, Any]]] = None,
517
573
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
518
574
  run_response: Optional[RunOutput] = None,
575
+ compress_tool_results: bool = False,
519
576
  ) -> ModelResponse:
520
577
  """
521
578
  Send a request to the OpenAI Responses API.
@@ -532,7 +589,7 @@ class OpenAIResponses(Model):
532
589
 
533
590
  provider_response = self.get_client().responses.create(
534
591
  model=self.id,
535
- input=self._format_messages(messages), # type: ignore
592
+ input=self._format_messages(messages, compress_tool_results), # type: ignore
536
593
  **request_params,
537
594
  )
538
595
 
@@ -573,6 +630,9 @@ class OpenAIResponses(Model):
573
630
  model_name=self.name,
574
631
  model_id=self.id,
575
632
  ) from exc
633
+ except ModelAuthenticationError as exc:
634
+ log_error(f"Model authentication error from OpenAI API: {exc}")
635
+ raise exc
576
636
  except Exception as exc:
577
637
  log_error(f"Error from OpenAI API: {exc}")
578
638
  raise ModelProviderError(message=str(exc), model_name=self.name, model_id=self.id) from exc
@@ -585,6 +645,7 @@ class OpenAIResponses(Model):
585
645
  tools: Optional[List[Dict[str, Any]]] = None,
586
646
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
587
647
  run_response: Optional[RunOutput] = None,
648
+ compress_tool_results: bool = False,
588
649
  ) -> ModelResponse:
589
650
  """
590
651
  Sends an asynchronous request to the OpenAI Responses API.
@@ -601,7 +662,7 @@ class OpenAIResponses(Model):
601
662
 
602
663
  provider_response = await self.get_async_client().responses.create(
603
664
  model=self.id,
604
- input=self._format_messages(messages), # type: ignore
665
+ input=self._format_messages(messages, compress_tool_results), # type: ignore
605
666
  **request_params,
606
667
  )
607
668
 
@@ -642,6 +703,9 @@ class OpenAIResponses(Model):
642
703
  model_name=self.name,
643
704
  model_id=self.id,
644
705
  ) from exc
706
+ except ModelAuthenticationError as exc:
707
+ log_error(f"Model authentication error from OpenAI API: {exc}")
708
+ raise exc
645
709
  except Exception as exc:
646
710
  log_error(f"Error from OpenAI API: {exc}")
647
711
  raise ModelProviderError(message=str(exc), model_name=self.name, model_id=self.id) from exc
@@ -654,6 +718,7 @@ class OpenAIResponses(Model):
654
718
  tools: Optional[List[Dict[str, Any]]] = None,
655
719
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
656
720
  run_response: Optional[RunOutput] = None,
721
+ compress_tool_results: bool = False,
657
722
  ) -> Iterator[ModelResponse]:
658
723
  """
659
724
  Send a streaming request to the OpenAI Responses API.
@@ -671,7 +736,7 @@ class OpenAIResponses(Model):
671
736
 
672
737
  for chunk in self.get_client().responses.create(
673
738
  model=self.id,
674
- input=self._format_messages(messages), # type: ignore
739
+ input=self._format_messages(messages, compress_tool_results), # type: ignore
675
740
  stream=True,
676
741
  **request_params,
677
742
  ):
@@ -715,6 +780,9 @@ class OpenAIResponses(Model):
715
780
  model_name=self.name,
716
781
  model_id=self.id,
717
782
  ) from exc
783
+ except ModelAuthenticationError as exc:
784
+ log_error(f"Model authentication error from OpenAI API: {exc}")
785
+ raise exc
718
786
  except Exception as exc:
719
787
  log_error(f"Error from OpenAI API: {exc}")
720
788
  raise ModelProviderError(message=str(exc), model_name=self.name, model_id=self.id) from exc
@@ -727,6 +795,7 @@ class OpenAIResponses(Model):
727
795
  tools: Optional[List[Dict[str, Any]]] = None,
728
796
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
729
797
  run_response: Optional[RunOutput] = None,
798
+ compress_tool_results: bool = False,
730
799
  ) -> AsyncIterator[ModelResponse]:
731
800
  """
732
801
  Sends an asynchronous streaming request to the OpenAI Responses API.
@@ -744,7 +813,7 @@ class OpenAIResponses(Model):
744
813
 
745
814
  async_stream = await self.get_async_client().responses.create(
746
815
  model=self.id,
747
- input=self._format_messages(messages), # type: ignore
816
+ input=self._format_messages(messages, compress_tool_results), # type: ignore
748
817
  stream=True,
749
818
  **request_params,
750
819
  )
@@ -785,12 +854,19 @@ class OpenAIResponses(Model):
785
854
  model_name=self.name,
786
855
  model_id=self.id,
787
856
  ) from exc
857
+ except ModelAuthenticationError as exc:
858
+ log_error(f"Model authentication error from OpenAI API: {exc}")
859
+ raise exc
788
860
  except Exception as exc:
789
861
  log_error(f"Error from OpenAI API: {exc}")
790
862
  raise ModelProviderError(message=str(exc), model_name=self.name, model_id=self.id) from exc
791
863
 
792
864
  def format_function_call_results(
793
- self, messages: List[Message], function_call_results: List[Message], tool_call_ids: List[str]
865
+ self,
866
+ messages: List[Message],
867
+ function_call_results: List[Message],
868
+ tool_call_ids: List[str],
869
+ compress_tool_results: bool = False,
794
870
  ) -> None:
795
871
  """
796
872
  Handle the results of function calls.
@@ -799,69 +875,13 @@ class OpenAIResponses(Model):
799
875
  messages (List[Message]): The list of conversation messages.
800
876
  function_call_results (List[Message]): The results of the function calls.
801
877
  tool_ids (List[str]): The tool ids.
878
+ compress_tool_results (bool): Whether to compress tool results.
802
879
  """
803
880
  if len(function_call_results) > 0:
804
881
  for _fc_message_index, _fc_message in enumerate(function_call_results):
805
882
  _fc_message.tool_call_id = tool_call_ids[_fc_message_index]
806
883
  messages.append(_fc_message)
807
884
 
808
- def process_response_stream(
809
- self,
810
- messages: List[Message],
811
- assistant_message: Message,
812
- stream_data: MessageData,
813
- response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
814
- tools: Optional[List[Dict[str, Any]]] = None,
815
- tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
816
- run_response: Optional[RunOutput] = None,
817
- ) -> Iterator[ModelResponse]:
818
- """Process the synchronous response stream."""
819
- for model_response_delta in self.invoke_stream(
820
- messages=messages,
821
- assistant_message=assistant_message,
822
- tools=tools,
823
- response_format=response_format,
824
- tool_choice=tool_choice,
825
- run_response=run_response,
826
- ):
827
- yield from self._populate_stream_data_and_assistant_message(
828
- stream_data=stream_data,
829
- assistant_message=assistant_message,
830
- model_response_delta=model_response_delta,
831
- )
832
-
833
- # Add final metrics to assistant message
834
- self._populate_assistant_message(assistant_message=assistant_message, provider_response=model_response_delta)
835
-
836
- async def aprocess_response_stream(
837
- self,
838
- messages: List[Message],
839
- assistant_message: Message,
840
- stream_data: MessageData,
841
- response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
842
- tools: Optional[List[Dict[str, Any]]] = None,
843
- tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
844
- run_response: Optional[RunOutput] = None,
845
- ) -> AsyncIterator[ModelResponse]:
846
- """Process the asynchronous response stream."""
847
- async for model_response_delta in self.ainvoke_stream(
848
- messages=messages,
849
- assistant_message=assistant_message,
850
- tools=tools,
851
- response_format=response_format,
852
- tool_choice=tool_choice,
853
- run_response=run_response,
854
- ):
855
- for model_response in self._populate_stream_data_and_assistant_message(
856
- stream_data=stream_data,
857
- assistant_message=assistant_message,
858
- model_response_delta=model_response_delta,
859
- ):
860
- yield model_response
861
-
862
- # Add final metrics to assistant message
863
- self._populate_assistant_message(assistant_message=assistant_message, provider_response=model_response_delta)
864
-
865
885
  def _parse_provider_response(self, response: Response, **kwargs) -> ModelResponse:
866
886
  """
867
887
  Parse the OpenAI response into a ModelResponse.
@@ -1,8 +1,12 @@
1
- from dataclasses import dataclass, field
1
+ from dataclasses import dataclass
2
2
  from os import getenv
3
- from typing import Optional
3
+ from typing import Any, Dict, List, Optional, Type, Union
4
4
 
5
+ from pydantic import BaseModel
6
+
7
+ from agno.exceptions import ModelAuthenticationError
5
8
  from agno.models.openai.like import OpenAILike
9
+ from agno.run.agent import RunOutput
6
10
 
7
11
 
8
12
  @dataclass
@@ -17,12 +21,65 @@ class OpenRouter(OpenAILike):
17
21
  api_key (Optional[str]): The API key.
18
22
  base_url (str): The base URL. Defaults to "https://openrouter.ai/api/v1".
19
23
  max_tokens (int): The maximum number of tokens. Defaults to 1024.
24
+ fallback_models (Optional[List[str]]): List of fallback model IDs to use if the primary model
25
+ fails due to rate limits, timeouts, or unavailability. OpenRouter will automatically try
26
+ these models in order. Example: ["anthropic/claude-sonnet-4", "deepseek/deepseek-r1"]
20
27
  """
21
28
 
22
29
  id: str = "gpt-4o"
23
30
  name: str = "OpenRouter"
24
31
  provider: str = "OpenRouter"
25
32
 
26
- api_key: Optional[str] = field(default_factory=lambda: getenv("OPENROUTER_API_KEY"))
33
+ api_key: Optional[str] = None
27
34
  base_url: str = "https://openrouter.ai/api/v1"
28
35
  max_tokens: int = 1024
36
+ models: Optional[List[str]] = None # Dynamic model routing https://openrouter.ai/docs/features/model-routing
37
+
38
+ def _get_client_params(self) -> Dict[str, Any]:
39
+ """
40
+ Returns client parameters for API requests, checking for OPENROUTER_API_KEY.
41
+
42
+ Returns:
43
+ Dict[str, Any]: A dictionary of client parameters for API requests.
44
+ """
45
+ # Fetch API key from env if not already set
46
+ if not self.api_key:
47
+ self.api_key = getenv("OPENROUTER_API_KEY")
48
+ if not self.api_key:
49
+ raise ModelAuthenticationError(
50
+ message="OPENROUTER_API_KEY not set. Please set the OPENROUTER_API_KEY environment variable.",
51
+ model_name=self.name,
52
+ )
53
+
54
+ return super()._get_client_params()
55
+
56
+ def get_request_params(
57
+ self,
58
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
59
+ tools: Optional[List[Dict[str, Any]]] = None,
60
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
61
+ run_response: Optional[RunOutput] = None,
62
+ ) -> Dict[str, Any]:
63
+ """
64
+ Returns keyword arguments for API requests, including fallback models configuration.
65
+
66
+ Returns:
67
+ Dict[str, Any]: A dictionary of keyword arguments for API requests.
68
+ """
69
+ # Get base request params from parent class
70
+ request_params = super().get_request_params(
71
+ response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
72
+ )
73
+
74
+ # Add fallback models to extra_body if specified
75
+ if self.models:
76
+ # Get existing extra_body or create new dict
77
+ extra_body = request_params.get("extra_body") or {}
78
+
79
+ # Merge fallback models into extra_body
80
+ extra_body["models"] = self.models
81
+
82
+ # Update request params
83
+ request_params["extra_body"] = extra_body
84
+
85
+ return request_params
@@ -4,7 +4,7 @@ from typing import Any, Dict, Optional, Type, Union
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
7
- from agno.exceptions import ModelProviderError
7
+ from agno.exceptions import ModelAuthenticationError, ModelProviderError
8
8
  from agno.models.message import Citations, UrlCitation
9
9
  from agno.models.metrics import Metrics
10
10
  from agno.models.response import ModelResponse
@@ -50,6 +50,22 @@ class Perplexity(OpenAILike):
50
50
  supports_native_structured_outputs: bool = False
51
51
  supports_json_schema_outputs: bool = True
52
52
 
53
+ def _get_client_params(self) -> Dict[str, Any]:
54
+ """
55
+ Returns client parameters for API requests, checking for PERPLEXITY_API_KEY.
56
+
57
+ Returns:
58
+ Dict[str, Any]: A dictionary of client parameters for API requests.
59
+ """
60
+ if not self.api_key:
61
+ self.api_key = getenv("PERPLEXITY_API_KEY")
62
+ if not self.api_key:
63
+ raise ModelAuthenticationError(
64
+ message="PERPLEXITY_API_KEY not set. Please set the PERPLEXITY_API_KEY environment variable.",
65
+ model_name=self.name,
66
+ )
67
+ return super()._get_client_params()
68
+
53
69
  def get_request_params(
54
70
  self,
55
71
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
@@ -1,8 +1,8 @@
1
- from dataclasses import dataclass, field
1
+ from dataclasses import dataclass
2
2
  from os import getenv
3
3
  from typing import Any, Dict, Optional, cast
4
4
 
5
- from agno.exceptions import ModelProviderError
5
+ from agno.exceptions import ModelAuthenticationError
6
6
  from agno.models.openai.like import OpenAILike
7
7
 
8
8
  try:
@@ -30,20 +30,21 @@ class Portkey(OpenAILike):
30
30
  name: str = "Portkey"
31
31
  provider: str = "Portkey"
32
32
 
33
- portkey_api_key: Optional[str] = field(default_factory=lambda: getenv("PORTKEY_API_KEY"))
34
- virtual_key: Optional[str] = field(default_factory=lambda: getenv("PORTKEY_VIRTUAL_KEY"))
33
+ portkey_api_key: Optional[str] = None
34
+ virtual_key: Optional[str] = None
35
35
  config: Optional[Dict[str, Any]] = None
36
36
  base_url: str = PORTKEY_GATEWAY_URL
37
37
 
38
38
  def _get_client_params(self) -> Dict[str, Any]:
39
39
  # Check for required keys
40
40
  if not self.portkey_api_key:
41
- raise ModelProviderError(
41
+ raise ModelAuthenticationError(
42
42
  message="PORTKEY_API_KEY not set. Please set the PORTKEY_API_KEY environment variable.",
43
43
  model_name=self.name,
44
- model_id=self.id,
45
44
  )
46
45
 
46
+ self.virtual_key = self.virtual_key or getenv("PORTKEY_VIRTUAL_KEY")
47
+
47
48
  # Create headers using Portkey's createHeaders function
48
49
  header_params: Dict[str, Any] = {
49
50
  "api_key": self.portkey_api_key,
@@ -1,11 +1,13 @@
1
- from dataclasses import dataclass, field
1
+ from dataclasses import dataclass
2
2
  from os import getenv
3
3
  from typing import Any, Dict, List, Optional, Type, Union
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
7
+ from agno.exceptions import ModelAuthenticationError
7
8
  from agno.models.openai.like import OpenAILike
8
9
  from agno.run.agent import RunOutput
10
+ from agno.run.team import TeamRunOutput
9
11
 
10
12
 
11
13
  @dataclass
@@ -25,18 +27,36 @@ class Requesty(OpenAILike):
25
27
  name: str = "Requesty"
26
28
  provider: str = "Requesty"
27
29
 
28
- api_key: Optional[str] = field(default_factory=lambda: getenv("REQUESTY_API_KEY"))
30
+ api_key: Optional[str] = None
29
31
  base_url: str = "https://router.requesty.ai/v1"
30
32
  max_tokens: int = 1024
31
33
 
34
+ def _get_client_params(self) -> Dict[str, Any]:
35
+ """
36
+ Returns client parameters for API requests, checking for REQUESTY_API_KEY.
37
+
38
+ Returns:
39
+ Dict[str, Any]: A dictionary of client parameters for API requests.
40
+ """
41
+ if not self.api_key:
42
+ self.api_key = getenv("REQUESTY_API_KEY")
43
+ if not self.api_key:
44
+ raise ModelAuthenticationError(
45
+ message="REQUESTY_API_KEY not set. Please set the REQUESTY_API_KEY environment variable.",
46
+ model_name=self.name,
47
+ )
48
+ return super()._get_client_params()
49
+
32
50
  def get_request_params(
33
51
  self,
34
52
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
35
53
  tools: Optional[List[Dict[str, Any]]] = None,
36
54
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
37
- run_response: Optional[RunOutput] = None,
55
+ run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
38
56
  ) -> Dict[str, Any]:
39
- params = super().get_request_params(response_format=response_format, tools=tools, tool_choice=tool_choice)
57
+ params = super().get_request_params(
58
+ response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
59
+ )
40
60
 
41
61
  if "extra_body" not in params:
42
62
  params["extra_body"] = {}