agno 2.2.13__py3-none-any.whl → 2.3.1__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 (92) hide show
  1. agno/agent/agent.py +197 -110
  2. agno/api/api.py +2 -0
  3. agno/db/base.py +26 -0
  4. agno/db/dynamo/dynamo.py +8 -0
  5. agno/db/dynamo/schemas.py +1 -0
  6. agno/db/firestore/firestore.py +8 -0
  7. agno/db/firestore/schemas.py +1 -0
  8. agno/db/gcs_json/gcs_json_db.py +8 -0
  9. agno/db/in_memory/in_memory_db.py +8 -1
  10. agno/db/json/json_db.py +8 -0
  11. agno/db/migrations/manager.py +199 -0
  12. agno/db/migrations/versions/__init__.py +0 -0
  13. agno/db/migrations/versions/v2_3_0.py +938 -0
  14. agno/db/mongo/async_mongo.py +16 -6
  15. agno/db/mongo/mongo.py +11 -0
  16. agno/db/mongo/schemas.py +3 -0
  17. agno/db/mongo/utils.py +17 -0
  18. agno/db/mysql/mysql.py +76 -3
  19. agno/db/mysql/schemas.py +20 -10
  20. agno/db/postgres/async_postgres.py +99 -25
  21. agno/db/postgres/postgres.py +75 -6
  22. agno/db/postgres/schemas.py +30 -20
  23. agno/db/redis/redis.py +15 -2
  24. agno/db/redis/schemas.py +4 -0
  25. agno/db/schemas/memory.py +13 -0
  26. agno/db/singlestore/schemas.py +11 -0
  27. agno/db/singlestore/singlestore.py +79 -5
  28. agno/db/sqlite/async_sqlite.py +97 -19
  29. agno/db/sqlite/schemas.py +10 -0
  30. agno/db/sqlite/sqlite.py +79 -2
  31. agno/db/surrealdb/surrealdb.py +8 -0
  32. agno/knowledge/chunking/semantic.py +7 -2
  33. agno/knowledge/embedder/nebius.py +1 -1
  34. agno/knowledge/knowledge.py +57 -86
  35. agno/knowledge/reader/csv_reader.py +7 -9
  36. agno/knowledge/reader/docx_reader.py +5 -5
  37. agno/knowledge/reader/field_labeled_csv_reader.py +16 -18
  38. agno/knowledge/reader/json_reader.py +5 -4
  39. agno/knowledge/reader/markdown_reader.py +8 -8
  40. agno/knowledge/reader/pdf_reader.py +11 -11
  41. agno/knowledge/reader/pptx_reader.py +5 -5
  42. agno/knowledge/reader/s3_reader.py +3 -3
  43. agno/knowledge/reader/text_reader.py +8 -8
  44. agno/knowledge/reader/web_search_reader.py +1 -48
  45. agno/knowledge/reader/website_reader.py +10 -10
  46. agno/models/anthropic/claude.py +319 -28
  47. agno/models/aws/claude.py +32 -0
  48. agno/models/azure/openai_chat.py +19 -10
  49. agno/models/base.py +612 -545
  50. agno/models/cerebras/cerebras.py +8 -11
  51. agno/models/cohere/chat.py +27 -1
  52. agno/models/google/gemini.py +39 -7
  53. agno/models/groq/groq.py +25 -11
  54. agno/models/meta/llama.py +20 -9
  55. agno/models/meta/llama_openai.py +3 -19
  56. agno/models/nebius/nebius.py +4 -4
  57. agno/models/openai/chat.py +30 -14
  58. agno/models/openai/responses.py +10 -13
  59. agno/models/response.py +1 -0
  60. agno/models/vertexai/claude.py +26 -0
  61. agno/os/app.py +8 -19
  62. agno/os/router.py +54 -0
  63. agno/os/routers/knowledge/knowledge.py +2 -2
  64. agno/os/schema.py +2 -2
  65. agno/session/agent.py +57 -92
  66. agno/session/summary.py +1 -1
  67. agno/session/team.py +62 -112
  68. agno/session/workflow.py +353 -57
  69. agno/team/team.py +227 -125
  70. agno/tools/models/nebius.py +5 -5
  71. agno/tools/models_labs.py +20 -10
  72. agno/tools/nano_banana.py +151 -0
  73. agno/tools/yfinance.py +12 -11
  74. agno/utils/http.py +111 -0
  75. agno/utils/media.py +11 -0
  76. agno/utils/models/claude.py +8 -0
  77. agno/utils/print_response/agent.py +33 -12
  78. agno/utils/print_response/team.py +22 -12
  79. agno/vectordb/couchbase/couchbase.py +6 -2
  80. agno/workflow/condition.py +13 -0
  81. agno/workflow/loop.py +13 -0
  82. agno/workflow/parallel.py +13 -0
  83. agno/workflow/router.py +13 -0
  84. agno/workflow/step.py +120 -20
  85. agno/workflow/steps.py +13 -0
  86. agno/workflow/workflow.py +76 -63
  87. {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/METADATA +6 -2
  88. {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/RECORD +91 -88
  89. agno/tools/googlesearch.py +0 -98
  90. {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/WHEEL +0 -0
  91. {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/licenses/LICENSE +0 -0
  92. {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/top_level.txt +0 -0
@@ -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
 
17
18
  try:
@@ -107,11 +108,11 @@ class Cerebras(Model):
107
108
  return self.client
108
109
 
109
110
  client_params: Dict[str, Any] = self._get_client_params()
110
- if self.http_client:
111
- if isinstance(self.http_client, httpx.Client):
112
- client_params["http_client"] = self.http_client
113
- else:
114
- log_debug("http_client is not an instance of httpx.Client.")
111
+ if self.http_client is not None:
112
+ client_params["http_client"] = self.http_client
113
+ else:
114
+ # Use global sync client when no custom http_client is provided
115
+ client_params["http_client"] = get_default_sync_client()
115
116
  self.client = CerebrasClient(**client_params)
116
117
  return self.client
117
118
 
@@ -129,12 +130,8 @@ class Cerebras(Model):
129
130
  if self.http_client and isinstance(self.http_client, httpx.AsyncClient):
130
131
  client_params["http_client"] = self.http_client
131
132
  else:
132
- if self.http_client:
133
- log_debug("The current http_client is not async. A default httpx.AsyncClient will be used instead.")
134
- # Create a new async HTTP client with custom limits
135
- client_params["http_client"] = httpx.AsyncClient(
136
- limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100)
137
- )
133
+ # Use global async client when no custom http_client is provided
134
+ client_params["http_client"] = get_default_async_client()
138
135
  self.async_client = AsyncCerebrasClient(**client_params)
139
136
  return self.async_client
140
137
 
@@ -2,6 +2,7 @@ from dataclasses import dataclass
2
2
  from os import getenv
3
3
  from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Tuple, Type, Union
4
4
 
5
+ import httpx
5
6
  from pydantic import BaseModel
6
7
 
7
8
  from agno.exceptions import ModelProviderError
@@ -10,7 +11,8 @@ from agno.models.message import Message
10
11
  from agno.models.metrics import Metrics
11
12
  from agno.models.response import ModelResponse
12
13
  from agno.run.agent import RunOutput
13
- from agno.utils.log import log_debug, log_error
14
+ from agno.utils.http import get_default_async_client, get_default_sync_client
15
+ from agno.utils.log import log_debug, log_error, log_warning
14
16
  from agno.utils.models.cohere import format_messages
15
17
 
16
18
  try:
@@ -50,6 +52,7 @@ class Cohere(Model):
50
52
  # -*- Client parameters
51
53
  api_key: Optional[str] = None
52
54
  client_params: Optional[Dict[str, Any]] = None
55
+ http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
53
56
  # -*- Provide the Cohere client manually
54
57
  client: Optional[CohereClient] = None
55
58
  async_client: Optional[CohereAsyncClient] = None
@@ -66,6 +69,17 @@ class Cohere(Model):
66
69
 
67
70
  _client_params["api_key"] = self.api_key
68
71
 
72
+ if self.http_client:
73
+ if isinstance(self.http_client, httpx.Client):
74
+ _client_params["httpx_client"] = self.http_client
75
+ else:
76
+ log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
77
+ # Use global sync client when user http_client is invalid
78
+ _client_params["httpx_client"] = get_default_sync_client()
79
+ else:
80
+ # Use global sync client when no custom http_client is provided
81
+ _client_params["httpx_client"] = get_default_sync_client()
82
+
69
83
  self.client = CohereClient(**_client_params)
70
84
  return self.client # type: ignore
71
85
 
@@ -82,6 +96,18 @@ class Cohere(Model):
82
96
 
83
97
  _client_params["api_key"] = self.api_key
84
98
 
99
+ if self.http_client:
100
+ if isinstance(self.http_client, httpx.AsyncClient):
101
+ _client_params["httpx_client"] = self.http_client
102
+ else:
103
+ log_warning(
104
+ "http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
105
+ )
106
+ # Use global async client when user http_client is invalid
107
+ _client_params["httpx_client"] = get_default_async_client()
108
+ else:
109
+ # Use global async client when no custom http_client is provided
110
+ _client_params["httpx_client"] = get_default_async_client()
85
111
  self.async_client = CohereAsyncClient(**_client_params)
86
112
  return self.async_client # type: ignore
87
113
 
@@ -1,3 +1,4 @@
1
+ import base64
1
2
  import json
2
3
  import time
3
4
  from collections.abc import AsyncIterator
@@ -480,14 +481,18 @@ class Gemini(Model):
480
481
  if role == "model" and message.tool_calls is not None and len(message.tool_calls) > 0:
481
482
  if content is not None:
482
483
  content_str = content if isinstance(content, str) else str(content)
483
- message_parts.append(Part.from_text(text=content_str))
484
+ part = Part.from_text(text=content_str)
485
+ if message.provider_data and "thought_signature" in message.provider_data:
486
+ part.thought_signature = base64.b64decode(message.provider_data["thought_signature"])
487
+ message_parts.append(part)
484
488
  for tool_call in message.tool_calls:
485
- message_parts.append(
486
- Part.from_function_call(
487
- name=tool_call["function"]["name"],
488
- args=json.loads(tool_call["function"]["arguments"]),
489
- )
489
+ part = Part.from_function_call(
490
+ name=tool_call["function"]["name"],
491
+ args=json.loads(tool_call["function"]["arguments"]),
490
492
  )
493
+ if "thought_signature" in tool_call:
494
+ part.thought_signature = base64.b64decode(tool_call["thought_signature"])
495
+ message_parts.append(part)
491
496
  # Function call results
492
497
  elif message.tool_calls is not None and len(message.tool_calls) > 0:
493
498
  for tool_call in message.tool_calls:
@@ -499,7 +504,10 @@ class Gemini(Model):
499
504
  # Regular text content
500
505
  else:
501
506
  if isinstance(content, str):
502
- message_parts = [Part.from_text(text=content)]
507
+ part = Part.from_text(text=content)
508
+ if message.provider_data and "thought_signature" in message.provider_data:
509
+ part.thought_signature = base64.b64decode(message.provider_data["thought_signature"])
510
+ message_parts = [part]
503
511
 
504
512
  if role == "user" and message.tool_calls is None:
505
513
  # Add images to the message for the model
@@ -834,6 +842,14 @@ class Gemini(Model):
834
842
  else:
835
843
  model_response.content += content_str
836
844
 
845
+ # Capture thought signature for text parts
846
+ if hasattr(part, "thought_signature") and part.thought_signature:
847
+ if model_response.provider_data is None:
848
+ model_response.provider_data = {}
849
+ model_response.provider_data["thought_signature"] = base64.b64encode(
850
+ part.thought_signature
851
+ ).decode("ascii")
852
+
837
853
  if hasattr(part, "inline_data") and part.inline_data is not None:
838
854
  # Handle audio responses (for TTS models)
839
855
  if part.inline_data.mime_type and part.inline_data.mime_type.startswith("audio/"):
@@ -865,6 +881,10 @@ class Gemini(Model):
865
881
  },
866
882
  }
867
883
 
884
+ # Capture thought signature for function calls
885
+ if hasattr(part, "thought_signature") and part.thought_signature:
886
+ tool_call["thought_signature"] = base64.b64encode(part.thought_signature).decode("ascii")
887
+
868
888
  model_response.tool_calls.append(tool_call)
869
889
 
870
890
  citations = Citations()
@@ -956,6 +976,14 @@ class Gemini(Model):
956
976
  else:
957
977
  model_response.content += text_content
958
978
 
979
+ # Capture thought signature for text parts
980
+ if hasattr(part, "thought_signature") and part.thought_signature:
981
+ if model_response.provider_data is None:
982
+ model_response.provider_data = {}
983
+ model_response.provider_data["thought_signature"] = base64.b64encode(
984
+ part.thought_signature
985
+ ).decode("ascii")
986
+
959
987
  if hasattr(part, "inline_data") and part.inline_data is not None:
960
988
  # Audio responses
961
989
  if part.inline_data.mime_type and part.inline_data.mime_type.startswith("audio/"):
@@ -989,6 +1017,10 @@ class Gemini(Model):
989
1017
  },
990
1018
  }
991
1019
 
1020
+ # Capture thought signature for function calls
1021
+ if hasattr(part, "thought_signature") and part.thought_signature:
1022
+ tool_call["thought_signature"] = base64.b64encode(part.thought_signature).decode("ascii")
1023
+
992
1024
  model_response.tool_calls.append(tool_call)
993
1025
 
994
1026
  if response_delta.candidates[0].grounding_metadata is not None:
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
 
@@ -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,14 +104,22 @@ 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.
@@ -119,15 +128,20 @@ class Groq(Model):
119
128
  return self.async_client
120
129
 
121
130
  client_params: Dict[str, Any] = self._get_client_params()
122
- if self.http_client and isinstance(self.http_client, httpx.AsyncClient):
123
- client_params["http_client"] = self.http_client
131
+ if 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
- if self.http_client:
126
- log_debug("The current http_client is not async. A default httpx.AsyncClient will be used instead.")
127
- # Create a new async HTTP client with custom limits
128
- client_params["http_client"] = httpx.AsyncClient(
129
- limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100)
130
- )
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
131
145
  self.async_client = AsyncGroqClient(**client_params)
132
146
  return self.async_client
133
147
 
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
 
@@ -108,7 +109,12 @@ class Llama(Model):
108
109
  if isinstance(self.http_client, httpx.Client):
109
110
  client_params["http_client"] = self.http_client
110
111
  else:
111
- log_debug("http_client is not an instance of httpx.Client.")
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()
112
118
  self.client = LlamaAPIClient(**client_params)
113
119
  return self.client
114
120
 
@@ -123,15 +129,20 @@ class Llama(Model):
123
129
  return self.async_client
124
130
 
125
131
  client_params: Dict[str, Any] = self._get_client_params()
126
- if self.http_client and isinstance(self.http_client, httpx.AsyncClient):
127
- client_params["http_client"] = self.http_client
132
+ if 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()
128
141
  else:
129
- if self.http_client:
130
- log_debug("The current http_client is not async. A default httpx.AsyncClient will be used instead.")
131
- # Create a new async HTTP client with custom limits
132
- client_params["http_client"] = httpx.AsyncClient(
133
- limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100)
134
- )
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
135
146
  self.async_client = AsyncLlamaAPIClient(**client_params)
136
147
  return self.async_client
137
148
 
@@ -2,8 +2,6 @@ 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:
@@ -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,20 +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
- if self.async_client and not self.async_client.is_closed():
66
- return self.async_client
67
-
68
- client_params = self._get_client_params()
69
-
70
- # Llama gives a 307 redirect error, so we need to set up a custom client to allow redirects
71
- client_params["http_client"] = httpx.AsyncClient(
72
- limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100),
73
- follow_redirects=True,
74
- timeout=httpx.Timeout(30.0),
75
- )
76
-
77
- self.async_client = AsyncOpenAIClient(**client_params)
78
- return self.async_client
@@ -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
26
  api_key: Optional[str] = field(default_factory=lambda: getenv("NEBIUS_API_KEY"))
27
- base_url: str = "https://api.studio.nebius.com/v1/"
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:
@@ -15,6 +15,7 @@ from agno.models.metrics import Metrics
15
15
  from agno.models.response import ModelResponse
16
16
  from agno.run.agent import RunOutput
17
17
  from agno.run.team import TeamRunOutput
18
+ from agno.utils.http import get_default_async_client, get_default_sync_client
18
19
  from agno.utils.log import log_debug, log_error, log_warning
19
20
  from agno.utils.openai import _format_file_for_message, audio_to_message, images_to_message
20
21
  from agno.utils.reasoning import extract_thinking_content
@@ -83,7 +84,7 @@ class OpenAIChat(Model):
83
84
  http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
84
85
  client_params: Optional[Dict[str, Any]] = None
85
86
 
86
- # OpenAI clients
87
+ # Cached clients to avoid recreating them on every request
87
88
  client: Optional[OpenAIClient] = None
88
89
  async_client: Optional[AsyncOpenAIClient] = None
89
90
 
@@ -124,44 +125,59 @@ class OpenAIChat(Model):
124
125
 
125
126
  def get_client(self) -> OpenAIClient:
126
127
  """
127
- Returns an OpenAI client.
128
+ Returns an OpenAI client. Caches the client to avoid recreating it on every request.
128
129
 
129
130
  Returns:
130
131
  OpenAIClient: An instance of the OpenAI client.
131
132
  """
132
- if self.client and not self.client.is_closed():
133
+ # Return cached client if it exists and is not closed
134
+ if self.client is not None and not self.client.is_closed():
133
135
  return self.client
134
136
 
137
+ log_debug(f"Creating new sync OpenAI client for model {self.id}")
135
138
  client_params: Dict[str, Any] = self._get_client_params()
136
139
  if self.http_client:
137
140
  if isinstance(self.http_client, httpx.Client):
138
141
  client_params["http_client"] = self.http_client
139
142
  else:
140
- log_debug("http_client is not an instance of httpx.Client.")
143
+ log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
144
+ # Use global sync client when user http_client is invalid
145
+ client_params["http_client"] = get_default_sync_client()
146
+ else:
147
+ # Use global sync client when no custom http_client is provided
148
+ client_params["http_client"] = get_default_sync_client()
141
149
 
150
+ # Create and cache the client
142
151
  self.client = OpenAIClient(**client_params)
143
152
  return self.client
144
153
 
145
154
  def get_async_client(self) -> AsyncOpenAIClient:
146
155
  """
147
- Returns an asynchronous OpenAI client.
156
+ Returns an asynchronous OpenAI client. Caches the client to avoid recreating it on every request.
148
157
 
149
158
  Returns:
150
159
  AsyncOpenAIClient: An instance of the asynchronous OpenAI client.
151
160
  """
152
- if self.async_client and not self.async_client.is_closed():
161
+ # Return cached client if it exists and is not closed
162
+ if self.async_client is not None and not self.async_client.is_closed():
153
163
  return self.async_client
154
164
 
165
+ log_debug(f"Creating new async OpenAI client for model {self.id}")
155
166
  client_params: Dict[str, Any] = self._get_client_params()
156
- if self.http_client and isinstance(self.http_client, httpx.AsyncClient):
157
- client_params["http_client"] = self.http_client
167
+ if self.http_client:
168
+ if isinstance(self.http_client, httpx.AsyncClient):
169
+ client_params["http_client"] = self.http_client
170
+ else:
171
+ log_warning(
172
+ "http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
173
+ )
174
+ # Use global async client when user http_client is invalid
175
+ client_params["http_client"] = get_default_async_client()
158
176
  else:
159
- if self.http_client:
160
- log_debug("The current http_client is not async. A default httpx.AsyncClient will be used instead.")
161
- # Create a new async HTTP client with custom limits
162
- client_params["http_client"] = httpx.AsyncClient(
163
- limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100)
164
- )
177
+ # Use global async client when no custom http_client is provided
178
+ client_params["http_client"] = get_default_async_client()
179
+
180
+ # Create and cache the client
165
181
  self.async_client = AsyncOpenAIClient(**client_params)
166
182
  return self.async_client
167
183
 
@@ -13,6 +13,7 @@ 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
@@ -140,7 +141,7 @@ class OpenAIResponses(Model):
140
141
 
141
142
  def get_client(self) -> OpenAI:
142
143
  """
143
- Returns an OpenAI client.
144
+ Returns an OpenAI client. Caches the client to avoid recreating it on every request.
144
145
 
145
146
  Returns:
146
147
  OpenAI: An instance of the OpenAI client.
@@ -149,18 +150,18 @@ class OpenAIResponses(Model):
149
150
  return self.client
150
151
 
151
152
  client_params: Dict[str, Any] = self._get_client_params()
152
- if self.http_client:
153
- if isinstance(self.http_client, httpx.Client):
154
- client_params["http_client"] = self.http_client
155
- else:
156
- log_debug("http_client is not an instance of httpx.Client.")
153
+ if self.http_client is not None:
154
+ client_params["http_client"] = self.http_client
155
+ else:
156
+ # Use global sync client when no custom http_client is provided
157
+ client_params["http_client"] = get_default_sync_client()
157
158
 
158
159
  self.client = OpenAI(**client_params)
159
160
  return self.client
160
161
 
161
162
  def get_async_client(self) -> AsyncOpenAI:
162
163
  """
163
- Returns an asynchronous OpenAI client.
164
+ Returns an asynchronous OpenAI client. Caches the client to avoid recreating it on every request.
164
165
 
165
166
  Returns:
166
167
  AsyncOpenAI: An instance of the asynchronous OpenAI client.
@@ -172,12 +173,8 @@ class OpenAIResponses(Model):
172
173
  if self.http_client and isinstance(self.http_client, httpx.AsyncClient):
173
174
  client_params["http_client"] = self.http_client
174
175
  else:
175
- if self.http_client:
176
- log_debug("The current http_client is not async. A default httpx.AsyncClient will be used instead.")
177
- # Create a new async HTTP client with custom limits
178
- client_params["http_client"] = httpx.AsyncClient(
179
- limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100)
180
- )
176
+ # Use global async client when no custom http_client is provided
177
+ client_params["http_client"] = get_default_async_client()
181
178
 
182
179
  self.async_client = AsyncOpenAI(**client_params)
183
180
  return self.async_client
agno/models/response.py CHANGED
@@ -197,3 +197,4 @@ class FileType(str, Enum):
197
197
  MP4 = "mp4"
198
198
  GIF = "gif"
199
199
  MP3 = "mp3"
200
+ WAV = "wav"
@@ -2,7 +2,11 @@ from dataclasses import dataclass
2
2
  from os import getenv
3
3
  from typing import Any, Dict, Optional
4
4
 
5
+ import httpx
6
+
5
7
  from agno.models.anthropic import Claude as AnthropicClaude
8
+ from agno.utils.http import get_default_async_client, get_default_sync_client
9
+ from agno.utils.log import log_warning
6
10
 
7
11
  try:
8
12
  from anthropic import AnthropicVertex, AsyncAnthropicVertex
@@ -55,6 +59,16 @@ class Claude(AnthropicClaude):
55
59
  return self.client
56
60
 
57
61
  _client_params = self._get_client_params()
62
+ if self.http_client:
63
+ if isinstance(self.http_client, httpx.Client):
64
+ _client_params["http_client"] = self.http_client
65
+ else:
66
+ log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
67
+ # Use global sync client when user http_client is invalid
68
+ _client_params["http_client"] = get_default_sync_client()
69
+ else:
70
+ # Use global sync client when no custom http_client is provided
71
+ _client_params["http_client"] = get_default_sync_client()
58
72
  self.client = AnthropicVertex(**_client_params)
59
73
  return self.client
60
74
 
@@ -66,5 +80,17 @@ class Claude(AnthropicClaude):
66
80
  return self.async_client
67
81
 
68
82
  _client_params = self._get_client_params()
83
+ if self.http_client:
84
+ if isinstance(self.http_client, httpx.AsyncClient):
85
+ _client_params["http_client"] = self.http_client
86
+ else:
87
+ log_warning(
88
+ "http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
89
+ )
90
+ # Use global async client when user http_client is invalid
91
+ _client_params["http_client"] = get_default_async_client()
92
+ else:
93
+ # Use global async client when no custom http_client is provided
94
+ _client_params["http_client"] = get_default_async_client()
69
95
  self.async_client = AsyncAnthropicVertex(**_client_params)
70
96
  return self.async_client
agno/os/app.py CHANGED
@@ -110,10 +110,6 @@ class AgentOS:
110
110
  on_route_conflict: Literal["preserve_agentos", "preserve_base_app", "error"] = "preserve_agentos",
111
111
  telemetry: bool = True,
112
112
  auto_provision_dbs: bool = True,
113
- os_id: Optional[str] = None, # Deprecated
114
- enable_mcp: bool = False, # Deprecated
115
- fastapi_app: Optional[FastAPI] = None, # Deprecated
116
- replace_routes: Optional[bool] = None, # Deprecated
117
113
  ):
118
114
  """Initialize AgentOS.
119
115
 
@@ -156,13 +152,6 @@ class AgentOS:
156
152
  self.base_app: Optional[FastAPI] = base_app
157
153
  self._app_set = True
158
154
  self.on_route_conflict = on_route_conflict
159
- elif fastapi_app:
160
- self.base_app = fastapi_app
161
- self._app_set = True
162
- if replace_routes is not None:
163
- self.on_route_conflict = "preserve_agentos" if replace_routes else "preserve_base_app"
164
- else:
165
- self.on_route_conflict = on_route_conflict
166
155
  else:
167
156
  self.base_app = None
168
157
  self._app_set = False
@@ -172,7 +161,7 @@ class AgentOS:
172
161
 
173
162
  self.name = name
174
163
 
175
- self.id = id or os_id
164
+ self.id = id
176
165
  if not self.id:
177
166
  self.id = generate_id(self.name) if self.name else str(uuid4())
178
167
 
@@ -181,7 +170,7 @@ class AgentOS:
181
170
 
182
171
  self.telemetry = telemetry
183
172
 
184
- self.enable_mcp_server = enable_mcp or enable_mcp_server
173
+ self.enable_mcp_server = enable_mcp_server
185
174
  self.lifespan = lifespan
186
175
 
187
176
  # List of all MCP tools used inside the AgentOS
@@ -327,16 +316,16 @@ class AgentOS:
327
316
  """Initialize and configure all agents for AgentOS usage."""
328
317
  if not self.agents:
329
318
  return
330
-
331
319
  for agent in self.agents:
332
320
  # Track all MCP tools to later handle their connection
333
321
  if agent.tools:
334
322
  for tool in agent.tools:
335
- # Checking if the tool is a MCPTools or MultiMCPTools instance
336
- type_name = type(tool).__name__
337
- if type_name in ("MCPTools", "MultiMCPTools"):
338
- if tool not in self.mcp_tools:
339
- self.mcp_tools.append(tool)
323
+ # Checking if the tool is an instance of MCPTools, MultiMCPTools, or a subclass of those
324
+ if hasattr(type(tool), "__mro__"):
325
+ mro_names = {cls.__name__ for cls in type(tool).__mro__}
326
+ if mro_names & {"MCPTools", "MultiMCPTools"}:
327
+ if tool not in self.mcp_tools:
328
+ self.mcp_tools.append(tool)
340
329
 
341
330
  agent.initialize_agent()
342
331