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.
- agno/agent/agent.py +197 -110
- agno/api/api.py +2 -0
- agno/db/base.py +26 -0
- agno/db/dynamo/dynamo.py +8 -0
- agno/db/dynamo/schemas.py +1 -0
- agno/db/firestore/firestore.py +8 -0
- agno/db/firestore/schemas.py +1 -0
- agno/db/gcs_json/gcs_json_db.py +8 -0
- agno/db/in_memory/in_memory_db.py +8 -1
- agno/db/json/json_db.py +8 -0
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/async_mongo.py +16 -6
- agno/db/mongo/mongo.py +11 -0
- agno/db/mongo/schemas.py +3 -0
- agno/db/mongo/utils.py +17 -0
- agno/db/mysql/mysql.py +76 -3
- agno/db/mysql/schemas.py +20 -10
- agno/db/postgres/async_postgres.py +99 -25
- agno/db/postgres/postgres.py +75 -6
- agno/db/postgres/schemas.py +30 -20
- agno/db/redis/redis.py +15 -2
- agno/db/redis/schemas.py +4 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +11 -0
- agno/db/singlestore/singlestore.py +79 -5
- agno/db/sqlite/async_sqlite.py +97 -19
- agno/db/sqlite/schemas.py +10 -0
- agno/db/sqlite/sqlite.py +79 -2
- agno/db/surrealdb/surrealdb.py +8 -0
- agno/knowledge/chunking/semantic.py +7 -2
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/knowledge.py +57 -86
- agno/knowledge/reader/csv_reader.py +7 -9
- agno/knowledge/reader/docx_reader.py +5 -5
- agno/knowledge/reader/field_labeled_csv_reader.py +16 -18
- agno/knowledge/reader/json_reader.py +5 -4
- agno/knowledge/reader/markdown_reader.py +8 -8
- agno/knowledge/reader/pdf_reader.py +11 -11
- agno/knowledge/reader/pptx_reader.py +5 -5
- agno/knowledge/reader/s3_reader.py +3 -3
- agno/knowledge/reader/text_reader.py +8 -8
- agno/knowledge/reader/web_search_reader.py +1 -48
- agno/knowledge/reader/website_reader.py +10 -10
- agno/models/anthropic/claude.py +319 -28
- agno/models/aws/claude.py +32 -0
- agno/models/azure/openai_chat.py +19 -10
- agno/models/base.py +612 -545
- agno/models/cerebras/cerebras.py +8 -11
- agno/models/cohere/chat.py +27 -1
- agno/models/google/gemini.py +39 -7
- agno/models/groq/groq.py +25 -11
- agno/models/meta/llama.py +20 -9
- agno/models/meta/llama_openai.py +3 -19
- agno/models/nebius/nebius.py +4 -4
- agno/models/openai/chat.py +30 -14
- agno/models/openai/responses.py +10 -13
- agno/models/response.py +1 -0
- agno/models/vertexai/claude.py +26 -0
- agno/os/app.py +8 -19
- agno/os/router.py +54 -0
- agno/os/routers/knowledge/knowledge.py +2 -2
- agno/os/schema.py +2 -2
- agno/session/agent.py +57 -92
- agno/session/summary.py +1 -1
- agno/session/team.py +62 -112
- agno/session/workflow.py +353 -57
- agno/team/team.py +227 -125
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/http.py +111 -0
- agno/utils/media.py +11 -0
- agno/utils/models/claude.py +8 -0
- agno/utils/print_response/agent.py +33 -12
- agno/utils/print_response/team.py +22 -12
- agno/vectordb/couchbase/couchbase.py +6 -2
- agno/workflow/condition.py +13 -0
- agno/workflow/loop.py +13 -0
- agno/workflow/parallel.py +13 -0
- agno/workflow/router.py +13 -0
- agno/workflow/step.py +120 -20
- agno/workflow/steps.py +13 -0
- agno/workflow/workflow.py +76 -63
- {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/METADATA +6 -2
- {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/RECORD +91 -88
- agno/tools/googlesearch.py +0 -98
- {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/WHEEL +0 -0
- {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/top_level.txt +0 -0
agno/models/cerebras/cerebras.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
|
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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
|
|
agno/models/cohere/chat.py
CHANGED
|
@@ -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.
|
|
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
|
|
agno/models/google/gemini.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
123
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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
|
|
127
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
agno/models/meta/llama_openai.py
CHANGED
|
@@ -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
|
agno/models/nebius/nebius.py
CHANGED
|
@@ -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
|
|
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.
|
|
19
|
+
base_url (str): The base URL. Defaults to "https://api.tokenfactory.nebius.com/v1".
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
id: str = "
|
|
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.
|
|
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:
|
agno/models/openai/chat.py
CHANGED
|
@@ -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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
157
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
agno/models/openai/responses.py
CHANGED
|
@@ -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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
176
|
-
|
|
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
agno/models/vertexai/claude.py
CHANGED
|
@@ -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
|
|
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 =
|
|
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
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if
|
|
339
|
-
self.mcp_tools
|
|
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
|
|