agno 2.0.5__py3-none-any.whl → 2.0.6__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 +53 -17
- agno/db/dynamo/dynamo.py +7 -5
- agno/db/firestore/firestore.py +4 -2
- agno/db/gcs_json/gcs_json_db.py +4 -2
- agno/db/json/json_db.py +8 -4
- agno/db/mongo/mongo.py +6 -4
- agno/db/mysql/mysql.py +2 -1
- agno/db/postgres/postgres.py +2 -1
- agno/db/redis/redis.py +1 -1
- agno/db/singlestore/singlestore.py +2 -2
- agno/db/sqlite/sqlite.py +1 -1
- agno/knowledge/embedder/openai.py +19 -11
- agno/knowledge/knowledge.py +4 -3
- agno/knowledge/reader/website_reader.py +33 -16
- agno/media.py +70 -0
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/base.py +31 -4
- agno/models/cerebras/cerebras_openai.py +2 -2
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/message.py +26 -0
- agno/models/meta/llama_openai.py +2 -2
- agno/models/nebius/nebius.py +2 -2
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +25 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/openrouter/openrouter.py +2 -2
- agno/models/perplexity/perplexity.py +2 -2
- agno/models/portkey/portkey.py +3 -3
- agno/models/response.py +2 -1
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/together/together.py +2 -2
- agno/models/vercel/v0.py +2 -2
- agno/models/xai/xai.py +2 -2
- agno/os/router.py +3 -1
- agno/os/utils.py +1 -1
- agno/run/agent.py +16 -0
- agno/run/team.py +15 -0
- agno/run/workflow.py +10 -0
- agno/team/team.py +37 -7
- agno/tools/e2b.py +14 -7
- agno/tools/file_generation.py +350 -0
- agno/tools/function.py +2 -0
- agno/utils/gemini.py +24 -4
- agno/vectordb/chroma/chromadb.py +66 -25
- agno/vectordb/lancedb/lance_db.py +15 -4
- agno/vectordb/milvus/milvus.py +6 -0
- agno/workflow/workflow.py +4 -0
- {agno-2.0.5.dist-info → agno-2.0.6.dist-info}/METADATA +4 -1
- {agno-2.0.5.dist-info → agno-2.0.6.dist-info}/RECORD +57 -54
- {agno-2.0.5.dist-info → agno-2.0.6.dist-info}/WHEEL +0 -0
- {agno-2.0.5.dist-info → agno-2.0.6.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.5.dist-info → agno-2.0.6.dist-info}/top_level.txt +0 -0
agno/media.py
CHANGED
|
@@ -334,11 +334,16 @@ class Video(BaseModel):
|
|
|
334
334
|
|
|
335
335
|
|
|
336
336
|
class File(BaseModel):
|
|
337
|
+
id: Optional[str] = None
|
|
337
338
|
url: Optional[str] = None
|
|
338
339
|
filepath: Optional[Union[Path, str]] = None
|
|
339
340
|
# Raw bytes content of a file
|
|
340
341
|
content: Optional[Any] = None
|
|
341
342
|
mime_type: Optional[str] = None
|
|
343
|
+
|
|
344
|
+
file_type: Optional[str] = None
|
|
345
|
+
filename: Optional[str] = None
|
|
346
|
+
size: Optional[int] = None
|
|
342
347
|
# External file object (e.g. GeminiFile, must be a valid object as expected by the model you are using)
|
|
343
348
|
external: Optional[Any] = None
|
|
344
349
|
format: Optional[str] = None # E.g. `pdf`, `txt`, `csv`, `xml`, etc.
|
|
@@ -364,6 +369,7 @@ class File(BaseModel):
|
|
|
364
369
|
def valid_mime_types(cls) -> List[str]:
|
|
365
370
|
return [
|
|
366
371
|
"application/pdf",
|
|
372
|
+
"application/json",
|
|
367
373
|
"application/x-javascript",
|
|
368
374
|
"text/javascript",
|
|
369
375
|
"application/x-python",
|
|
@@ -377,6 +383,29 @@ class File(BaseModel):
|
|
|
377
383
|
"text/rtf",
|
|
378
384
|
]
|
|
379
385
|
|
|
386
|
+
@classmethod
|
|
387
|
+
def from_base64(
|
|
388
|
+
cls,
|
|
389
|
+
base64_content: str,
|
|
390
|
+
id: Optional[str] = None,
|
|
391
|
+
mime_type: Optional[str] = None,
|
|
392
|
+
filename: Optional[str] = None,
|
|
393
|
+
name: Optional[str] = None,
|
|
394
|
+
format: Optional[str] = None,
|
|
395
|
+
) -> "File":
|
|
396
|
+
"""Create File from base64 encoded content"""
|
|
397
|
+
import base64
|
|
398
|
+
|
|
399
|
+
content_bytes = base64.b64decode(base64_content)
|
|
400
|
+
return cls(
|
|
401
|
+
content=content_bytes,
|
|
402
|
+
id=id,
|
|
403
|
+
mime_type=mime_type,
|
|
404
|
+
filename=filename,
|
|
405
|
+
name=name,
|
|
406
|
+
format=format,
|
|
407
|
+
)
|
|
408
|
+
|
|
380
409
|
@property
|
|
381
410
|
def file_url_content(self) -> Optional[Tuple[bytes, str]]:
|
|
382
411
|
import httpx
|
|
@@ -388,3 +417,44 @@ class File(BaseModel):
|
|
|
388
417
|
return content, mime_type
|
|
389
418
|
else:
|
|
390
419
|
return None
|
|
420
|
+
|
|
421
|
+
def _normalise_content(self) -> Optional[Union[str, bytes]]:
|
|
422
|
+
if self.content is None:
|
|
423
|
+
return None
|
|
424
|
+
content_normalised: Union[str, bytes] = self.content
|
|
425
|
+
if content_normalised and isinstance(content_normalised, bytes):
|
|
426
|
+
from base64 import b64encode
|
|
427
|
+
|
|
428
|
+
try:
|
|
429
|
+
if self.mime_type and self.mime_type.startswith("text/"):
|
|
430
|
+
content_normalised = content_normalised.decode("utf-8")
|
|
431
|
+
else:
|
|
432
|
+
content_normalised = b64encode(content_normalised).decode("utf-8")
|
|
433
|
+
except UnicodeDecodeError:
|
|
434
|
+
if isinstance(self.content, bytes):
|
|
435
|
+
content_normalised = b64encode(self.content).decode("utf-8")
|
|
436
|
+
except Exception:
|
|
437
|
+
try:
|
|
438
|
+
if isinstance(self.content, bytes):
|
|
439
|
+
content_normalised = b64encode(self.content).decode("utf-8")
|
|
440
|
+
except Exception:
|
|
441
|
+
pass
|
|
442
|
+
return content_normalised
|
|
443
|
+
|
|
444
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
445
|
+
content_normalised = self._normalise_content()
|
|
446
|
+
|
|
447
|
+
response_dict = {
|
|
448
|
+
"id": self.id,
|
|
449
|
+
"url": self.url,
|
|
450
|
+
"filepath": str(self.filepath) if self.filepath else None,
|
|
451
|
+
"content": content_normalised,
|
|
452
|
+
"mime_type": self.mime_type,
|
|
453
|
+
"file_type": self.file_type,
|
|
454
|
+
"filename": self.filename,
|
|
455
|
+
"size": self.size,
|
|
456
|
+
"external": self.external,
|
|
457
|
+
"format": self.format,
|
|
458
|
+
"name": self.name,
|
|
459
|
+
}
|
|
460
|
+
return {k: v for k, v in response_dict.items() if v is not None}
|
agno/models/aimlapi/aimlapi.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Any, Dict, Optional
|
|
4
4
|
|
|
@@ -24,7 +24,7 @@ class AIMLAPI(OpenAILike):
|
|
|
24
24
|
name: str = "AIMLAPI"
|
|
25
25
|
provider: str = "AIMLAPI"
|
|
26
26
|
|
|
27
|
-
api_key: Optional[str] = getenv("AIMLAPI_API_KEY")
|
|
27
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("AIMLAPI_API_KEY"))
|
|
28
28
|
base_url: str = "https://api.aimlapi.com/v1"
|
|
29
29
|
max_tokens: int = 4096
|
|
30
30
|
|
agno/models/base.py
CHANGED
|
@@ -21,7 +21,7 @@ from uuid import uuid4
|
|
|
21
21
|
from pydantic import BaseModel
|
|
22
22
|
|
|
23
23
|
from agno.exceptions import AgentRunException
|
|
24
|
-
from agno.media import Audio, Image, Video
|
|
24
|
+
from agno.media import Audio, File, Image, Video
|
|
25
25
|
from agno.models.message import Citations, Message
|
|
26
26
|
from agno.models.metrics import Metrics
|
|
27
27
|
from agno.models.response import ModelResponse, ModelResponseEvent, ToolExecution
|
|
@@ -46,6 +46,7 @@ class MessageData:
|
|
|
46
46
|
response_audio: Optional[Audio] = None
|
|
47
47
|
response_image: Optional[Image] = None
|
|
48
48
|
response_video: Optional[Video] = None
|
|
49
|
+
response_file: Optional[File] = None
|
|
49
50
|
|
|
50
51
|
# Data from the provider that we might need on subsequent messages
|
|
51
52
|
response_provider_data: Optional[Dict[str, Any]] = None
|
|
@@ -266,6 +267,11 @@ class Model(ABC):
|
|
|
266
267
|
model_response.videos = []
|
|
267
268
|
model_response.videos.extend(function_call_response.videos)
|
|
268
269
|
|
|
270
|
+
if function_call_response.files is not None:
|
|
271
|
+
if model_response.files is None:
|
|
272
|
+
model_response.files = []
|
|
273
|
+
model_response.files.extend(function_call_response.files)
|
|
274
|
+
|
|
269
275
|
if (
|
|
270
276
|
function_call_response.event
|
|
271
277
|
in [
|
|
@@ -293,7 +299,7 @@ class Model(ABC):
|
|
|
293
299
|
messages=messages, function_call_results=function_call_results, **model_response.extra or {}
|
|
294
300
|
)
|
|
295
301
|
|
|
296
|
-
if any(msg.images or msg.videos or msg.audio for msg in function_call_results):
|
|
302
|
+
if any(msg.images or msg.videos or msg.audio or msg.files for msg in function_call_results):
|
|
297
303
|
# Handle function call media
|
|
298
304
|
self._handle_function_call_media(messages=messages, function_call_results=function_call_results)
|
|
299
305
|
|
|
@@ -402,6 +408,11 @@ class Model(ABC):
|
|
|
402
408
|
model_response.videos = []
|
|
403
409
|
model_response.videos.extend(function_call_response.videos)
|
|
404
410
|
|
|
411
|
+
if function_call_response.files is not None:
|
|
412
|
+
if model_response.files is None:
|
|
413
|
+
model_response.files = []
|
|
414
|
+
model_response.files.extend(function_call_response.files)
|
|
415
|
+
|
|
405
416
|
if (
|
|
406
417
|
function_call_response.event
|
|
407
418
|
in [
|
|
@@ -428,7 +439,7 @@ class Model(ABC):
|
|
|
428
439
|
messages=messages, function_call_results=function_call_results, **model_response.extra or {}
|
|
429
440
|
)
|
|
430
441
|
|
|
431
|
-
if any(msg.images or msg.videos or msg.audio for msg in function_call_results):
|
|
442
|
+
if any(msg.images or msg.videos or msg.audio or msg.files for msg in function_call_results):
|
|
432
443
|
# Handle function call media
|
|
433
444
|
self._handle_function_call_media(messages=messages, function_call_results=function_call_results)
|
|
434
445
|
|
|
@@ -607,6 +618,10 @@ class Model(ABC):
|
|
|
607
618
|
if provider_response.videos:
|
|
608
619
|
assistant_message.video_output = provider_response.videos[-1] # Taking last (most recent) video
|
|
609
620
|
|
|
621
|
+
if provider_response.files is not None:
|
|
622
|
+
if provider_response.files:
|
|
623
|
+
assistant_message.file_output = provider_response.files[-1] # Taking last (most recent) file
|
|
624
|
+
|
|
610
625
|
if provider_response.audios is not None:
|
|
611
626
|
if provider_response.audios:
|
|
612
627
|
assistant_message.audio_output = provider_response.audios[-1] # Taking last (most recent) audio
|
|
@@ -1213,6 +1228,8 @@ class Model(ABC):
|
|
|
1213
1228
|
function_execution_result.videos = tool_result.videos
|
|
1214
1229
|
if tool_result.audios:
|
|
1215
1230
|
function_execution_result.audios = tool_result.audios
|
|
1231
|
+
if tool_result.files:
|
|
1232
|
+
function_execution_result.files = tool_result.files
|
|
1216
1233
|
else:
|
|
1217
1234
|
function_call_output = str(function_execution_result.result) if function_execution_result.result else ""
|
|
1218
1235
|
|
|
@@ -1246,6 +1263,7 @@ class Model(ABC):
|
|
|
1246
1263
|
images=function_execution_result.images,
|
|
1247
1264
|
videos=function_execution_result.videos,
|
|
1248
1265
|
audios=function_execution_result.audios,
|
|
1266
|
+
files=function_execution_result.files,
|
|
1249
1267
|
)
|
|
1250
1268
|
|
|
1251
1269
|
# Add function call to function call results
|
|
@@ -1617,6 +1635,8 @@ class Model(ABC):
|
|
|
1617
1635
|
function_execution_result.videos = tool_result.videos
|
|
1618
1636
|
if tool_result.audios:
|
|
1619
1637
|
function_execution_result.audios = tool_result.audios
|
|
1638
|
+
if tool_result.files:
|
|
1639
|
+
function_execution_result.files = tool_result.files
|
|
1620
1640
|
else:
|
|
1621
1641
|
function_call_output = str(function_call.result)
|
|
1622
1642
|
|
|
@@ -1649,6 +1669,7 @@ class Model(ABC):
|
|
|
1649
1669
|
images=function_execution_result.images,
|
|
1650
1670
|
videos=function_execution_result.videos,
|
|
1651
1671
|
audios=function_execution_result.audios,
|
|
1672
|
+
files=function_execution_result.files,
|
|
1652
1673
|
)
|
|
1653
1674
|
|
|
1654
1675
|
# Add function call result to function call results
|
|
@@ -1698,6 +1719,7 @@ class Model(ABC):
|
|
|
1698
1719
|
all_images: List[Image] = []
|
|
1699
1720
|
all_videos: List[Video] = []
|
|
1700
1721
|
all_audio: List[Audio] = []
|
|
1722
|
+
all_files: List[File] = []
|
|
1701
1723
|
|
|
1702
1724
|
for result_message in function_call_results:
|
|
1703
1725
|
if result_message.images:
|
|
@@ -1713,15 +1735,20 @@ class Model(ABC):
|
|
|
1713
1735
|
all_audio.extend(result_message.audio)
|
|
1714
1736
|
result_message.audio = None
|
|
1715
1737
|
|
|
1738
|
+
if result_message.files:
|
|
1739
|
+
all_files.extend(result_message.files)
|
|
1740
|
+
result_message.files = None
|
|
1741
|
+
|
|
1716
1742
|
# If we have media artifacts, add a follow-up "user" message instead of a "tool"
|
|
1717
1743
|
# message with the media artifacts which throws error for some models
|
|
1718
|
-
if all_images or all_videos or all_audio:
|
|
1744
|
+
if all_images or all_videos or all_audio or all_files:
|
|
1719
1745
|
media_message = Message(
|
|
1720
1746
|
role="user",
|
|
1721
1747
|
content="Take note of the following content",
|
|
1722
1748
|
images=all_images if all_images else None,
|
|
1723
1749
|
videos=all_videos if all_videos else None,
|
|
1724
1750
|
audio=all_audio if all_audio else None,
|
|
1751
|
+
files=all_files if all_files else None,
|
|
1725
1752
|
)
|
|
1726
1753
|
messages.append(media_message)
|
|
1727
1754
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from dataclasses import dataclass
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
3
|
from os import getenv
|
|
4
4
|
from typing import Any, Dict, List, Optional, Type, Union
|
|
5
5
|
|
|
@@ -18,7 +18,7 @@ class CerebrasOpenAI(OpenAILike):
|
|
|
18
18
|
|
|
19
19
|
parallel_tool_calls: Optional[bool] = None
|
|
20
20
|
base_url: str = "https://api.cerebras.ai/v1"
|
|
21
|
-
api_key: Optional[str] = getenv("CEREBRAS_API_KEY", None)
|
|
21
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("CEREBRAS_API_KEY", None))
|
|
22
22
|
|
|
23
23
|
def get_request_params(
|
|
24
24
|
self,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
@@ -22,7 +22,7 @@ class DeepInfra(OpenAILike):
|
|
|
22
22
|
name: str = "DeepInfra"
|
|
23
23
|
provider: str = "DeepInfra"
|
|
24
24
|
|
|
25
|
-
api_key: Optional[str] = getenv("DEEPINFRA_API_KEY")
|
|
25
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("DEEPINFRA_API_KEY"))
|
|
26
26
|
base_url: str = "https://api.deepinfra.com/v1/openai"
|
|
27
27
|
|
|
28
28
|
supports_native_structured_outputs: bool = False
|
agno/models/deepseek/deepseek.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Any, Dict, Optional
|
|
4
4
|
|
|
@@ -23,7 +23,7 @@ class DeepSeek(OpenAILike):
|
|
|
23
23
|
name: str = "DeepSeek"
|
|
24
24
|
provider: str = "DeepSeek"
|
|
25
25
|
|
|
26
|
-
api_key: Optional[str] = getenv("DEEPSEEK_API_KEY")
|
|
26
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("DEEPSEEK_API_KEY"))
|
|
27
27
|
base_url: str = "https://api.deepseek.com"
|
|
28
28
|
|
|
29
29
|
# Their support for structured outputs is currently broken
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
@@ -22,5 +22,5 @@ class Fireworks(OpenAILike):
|
|
|
22
22
|
name: str = "Fireworks"
|
|
23
23
|
provider: str = "Fireworks"
|
|
24
24
|
|
|
25
|
-
api_key: Optional[str] = getenv("FIREWORKS_API_KEY")
|
|
25
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("FIREWORKS_API_KEY"))
|
|
26
26
|
base_url: str = "https://api.fireworks.ai/inference/v1"
|
agno/models/internlm/internlm.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
@@ -22,5 +22,5 @@ class InternLM(OpenAILike):
|
|
|
22
22
|
name: str = "InternLM"
|
|
23
23
|
provider: str = "InternLM"
|
|
24
24
|
|
|
25
|
-
api_key: Optional[str] = getenv("INTERNLM_API_KEY")
|
|
25
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("INTERNLM_API_KEY"))
|
|
26
26
|
base_url: Optional[str] = "https://internlm-chat.intern-ai.org.cn/puyu/api/v1/chat/completions"
|
agno/models/langdb/langdb.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Any, Dict, Optional
|
|
4
4
|
|
|
@@ -22,10 +22,10 @@ class LangDB(OpenAILike):
|
|
|
22
22
|
name: str = "LangDB"
|
|
23
23
|
provider: str = "LangDB"
|
|
24
24
|
|
|
25
|
-
api_key: Optional[str] = getenv("LANGDB_API_KEY")
|
|
26
|
-
project_id: Optional[str] = getenv("LANGDB_PROJECT_ID")
|
|
25
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("LANGDB_API_KEY"))
|
|
26
|
+
project_id: Optional[str] = field(default_factory=lambda: getenv("LANGDB_PROJECT_ID"))
|
|
27
27
|
|
|
28
|
-
base_host_url: str = getenv("LANGDB_API_BASE_URL", "https://api.us-east-1.langdb.ai")
|
|
28
|
+
base_host_url: str = field(default_factory=lambda: getenv("LANGDB_API_BASE_URL", "https://api.us-east-1.langdb.ai"))
|
|
29
29
|
|
|
30
30
|
base_url: Optional[str] = None
|
|
31
31
|
label: Optional[str] = None
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
@@ -21,5 +21,5 @@ class LiteLLMOpenAI(OpenAILike):
|
|
|
21
21
|
name: str = "LiteLLM"
|
|
22
22
|
provider: str = "LiteLLM"
|
|
23
23
|
|
|
24
|
-
api_key: Optional[str] = getenv("LITELLM_API_KEY")
|
|
24
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("LITELLM_API_KEY"))
|
|
25
25
|
base_url: str = "http://0.0.0.0:4000"
|
agno/models/message.py
CHANGED
|
@@ -74,6 +74,7 @@ class Message(BaseModel):
|
|
|
74
74
|
audio_output: Optional[Audio] = None
|
|
75
75
|
image_output: Optional[Image] = None
|
|
76
76
|
video_output: Optional[Video] = None
|
|
77
|
+
file_output: Optional[File] = None
|
|
77
78
|
|
|
78
79
|
# The thinking content from the model
|
|
79
80
|
redacted_reasoning_content: Optional[str] = None
|
|
@@ -188,6 +189,29 @@ class Message(BaseModel):
|
|
|
188
189
|
reconstructed_videos.append(vid_data)
|
|
189
190
|
data["videos"] = reconstructed_videos
|
|
190
191
|
|
|
192
|
+
# Handle file reconstruction properly
|
|
193
|
+
if "files" in data and data["files"]:
|
|
194
|
+
reconstructed_files = []
|
|
195
|
+
for i, file_data in enumerate(data["files"]):
|
|
196
|
+
if isinstance(file_data, dict):
|
|
197
|
+
# If content is base64, decode it back to bytes
|
|
198
|
+
if "content" in file_data and isinstance(file_data["content"], str):
|
|
199
|
+
reconstructed_files.append(
|
|
200
|
+
File.from_base64(
|
|
201
|
+
file_data["content"],
|
|
202
|
+
id=file_data.get("id"),
|
|
203
|
+
mime_type=file_data.get("mime_type"),
|
|
204
|
+
filename=file_data.get("filename"),
|
|
205
|
+
name=file_data.get("name"),
|
|
206
|
+
format=file_data.get("format"),
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
reconstructed_files.append(File(**file_data))
|
|
211
|
+
else:
|
|
212
|
+
reconstructed_files.append(file_data)
|
|
213
|
+
data["files"] = reconstructed_files
|
|
214
|
+
|
|
191
215
|
if "audio_output" in data and data["audio_output"]:
|
|
192
216
|
aud_data = data["audio_output"]
|
|
193
217
|
if isinstance(aud_data, dict):
|
|
@@ -261,6 +285,8 @@ class Message(BaseModel):
|
|
|
261
285
|
message_dict["audio"] = [aud.to_dict() for aud in self.audio]
|
|
262
286
|
if self.videos:
|
|
263
287
|
message_dict["videos"] = [vid.to_dict() for vid in self.videos]
|
|
288
|
+
if self.files:
|
|
289
|
+
message_dict["files"] = [file.to_dict() for file in self.files]
|
|
264
290
|
if self.audio_output:
|
|
265
291
|
message_dict["audio_output"] = self.audio_output.to_dict()
|
|
266
292
|
|
agno/models/meta/llama_openai.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Any, Dict, Optional
|
|
4
4
|
|
|
@@ -31,7 +31,7 @@ class LlamaOpenAI(OpenAILike):
|
|
|
31
31
|
name: str = "LlamaOpenAI"
|
|
32
32
|
provider: str = "LlamaOpenAI"
|
|
33
33
|
|
|
34
|
-
api_key: Optional[str] = getenv("LLAMA_API_KEY")
|
|
34
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("LLAMA_API_KEY"))
|
|
35
35
|
base_url: Optional[str] = "https://api.llama.com/compat/v1/"
|
|
36
36
|
|
|
37
37
|
# Request parameters
|
agno/models/nebius/nebius.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Any, Dict, Optional
|
|
4
4
|
|
|
@@ -23,7 +23,7 @@ class Nebius(OpenAILike):
|
|
|
23
23
|
name: str = "Nebius"
|
|
24
24
|
provider: str = "Nebius"
|
|
25
25
|
|
|
26
|
-
api_key: Optional[str] = getenv("NEBIUS_API_KEY")
|
|
26
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("NEBIUS_API_KEY"))
|
|
27
27
|
base_url: str = "https://api.studio.nebius.com/v1/"
|
|
28
28
|
|
|
29
29
|
def _get_client_params(self) -> Dict[str, Any]:
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from agno.models.openai.like import OpenAILike
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class Nexus(OpenAILike):
|
|
8
|
+
"""
|
|
9
|
+
A class for interacting with Nvidia models.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
id (str): The id of the Nexus model to use. Default is "nvidia/llama-3.1-nemotron-70b-instruct".
|
|
13
|
+
name (str): The name of this chat model instance. Default is "Nexus"
|
|
14
|
+
provider (str): The provider of the model. Default is "Nexus".
|
|
15
|
+
api_key (str): The api key to authorize request to Nexus.
|
|
16
|
+
base_url (str): The base url to which the requests are sent.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
id: str = "openai/gpt-4"
|
|
20
|
+
name: str = "Nexus"
|
|
21
|
+
provider: str = "Nexus"
|
|
22
|
+
|
|
23
|
+
base_url: str = "http://localhost:8000/llm/v1/"
|
|
24
|
+
|
|
25
|
+
supports_native_structured_outputs: bool = False
|
agno/models/nvidia/nvidia.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
@@ -22,7 +22,7 @@ class Nvidia(OpenAILike):
|
|
|
22
22
|
name: str = "Nvidia"
|
|
23
23
|
provider: str = "Nvidia"
|
|
24
24
|
|
|
25
|
-
api_key: Optional[str] = getenv("NVIDIA_API_KEY")
|
|
25
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("NVIDIA_API_KEY"))
|
|
26
26
|
base_url: str = "https://integrate.api.nvidia.com/v1"
|
|
27
27
|
|
|
28
28
|
supports_native_structured_outputs: bool = False
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
@@ -23,6 +23,6 @@ class OpenRouter(OpenAILike):
|
|
|
23
23
|
name: str = "OpenRouter"
|
|
24
24
|
provider: str = "OpenRouter"
|
|
25
25
|
|
|
26
|
-
api_key: Optional[str] = getenv("OPENROUTER_API_KEY")
|
|
26
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("OPENROUTER_API_KEY"))
|
|
27
27
|
base_url: str = "https://openrouter.ai/api/v1"
|
|
28
28
|
max_tokens: int = 1024
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Any, Dict, List, Optional, Type, Union
|
|
4
4
|
|
|
@@ -42,7 +42,7 @@ class Perplexity(OpenAILike):
|
|
|
42
42
|
name: str = "Perplexity"
|
|
43
43
|
provider: str = "Perplexity"
|
|
44
44
|
|
|
45
|
-
api_key: Optional[str] = getenv("PERPLEXITY_API_KEY")
|
|
45
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("PERPLEXITY_API_KEY"))
|
|
46
46
|
base_url: str = "https://api.perplexity.ai/"
|
|
47
47
|
max_tokens: int = 1024
|
|
48
48
|
top_k: Optional[float] = None
|
agno/models/portkey/portkey.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Any, Dict, Optional, cast
|
|
4
4
|
|
|
@@ -30,8 +30,8 @@ class Portkey(OpenAILike):
|
|
|
30
30
|
name: str = "Portkey"
|
|
31
31
|
provider: str = "Portkey"
|
|
32
32
|
|
|
33
|
-
portkey_api_key: Optional[str] = getenv("PORTKEY_API_KEY")
|
|
34
|
-
virtual_key: Optional[str] = getenv("PORTKEY_VIRTUAL_KEY")
|
|
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"))
|
|
35
35
|
config: Optional[Dict[str, Any]] = None
|
|
36
36
|
base_url: str = PORTKEY_GATEWAY_URL
|
|
37
37
|
|
agno/models/response.py
CHANGED
|
@@ -3,7 +3,7 @@ from enum import Enum
|
|
|
3
3
|
from time import time
|
|
4
4
|
from typing import Any, Dict, List, Optional
|
|
5
5
|
|
|
6
|
-
from agno.media import Audio, Image, Video
|
|
6
|
+
from agno.media import Audio, File, Image, Video
|
|
7
7
|
from agno.models.message import Citations
|
|
8
8
|
from agno.models.metrics import Metrics
|
|
9
9
|
from agno.tools.function import UserInputField
|
|
@@ -98,6 +98,7 @@ class ModelResponse:
|
|
|
98
98
|
images: Optional[List[Image]] = None
|
|
99
99
|
videos: Optional[List[Video]] = None
|
|
100
100
|
audios: Optional[List[Audio]] = None
|
|
101
|
+
files: Optional[List[File]] = None
|
|
101
102
|
|
|
102
103
|
# Model tool calls
|
|
103
104
|
tool_calls: List[Dict[str, Any]] = field(default_factory=list)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
@@ -22,7 +22,7 @@ class Sambanova(OpenAILike):
|
|
|
22
22
|
name: str = "Sambanova"
|
|
23
23
|
provider: str = "Sambanova"
|
|
24
24
|
|
|
25
|
-
api_key: Optional[str] = getenv("SAMBANOVA_API_KEY")
|
|
25
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("SAMBANOVA_API_KEY"))
|
|
26
26
|
base_url: str = "https://api.sambanova.ai/v1"
|
|
27
27
|
|
|
28
28
|
supports_native_structured_outputs: bool = False
|
agno/models/together/together.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
@@ -21,5 +21,5 @@ class Together(OpenAILike):
|
|
|
21
21
|
id: str = "mistralai/Mixtral-8x7B-Instruct-v0.1"
|
|
22
22
|
name: str = "Together"
|
|
23
23
|
provider: str = "Together"
|
|
24
|
-
api_key: Optional[str] = getenv("TOGETHER_API_KEY")
|
|
24
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("TOGETHER_API_KEY"))
|
|
25
25
|
base_url: str = "https://api.together.xyz/v1"
|
agno/models/vercel/v0.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
@@ -22,5 +22,5 @@ class V0(OpenAILike):
|
|
|
22
22
|
name: str = "v0"
|
|
23
23
|
provider: str = "Vercel"
|
|
24
24
|
|
|
25
|
-
api_key: Optional[str] = getenv("V0_API_KEY")
|
|
25
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("V0_API_KEY"))
|
|
26
26
|
base_url: str = "https://api.v0.dev/v1/"
|
agno/models/xai/xai.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Any, Dict, List, Optional, Type, Union
|
|
4
4
|
|
|
@@ -34,7 +34,7 @@ class xAI(OpenAILike):
|
|
|
34
34
|
name: str = "xAI"
|
|
35
35
|
provider: str = "xAI"
|
|
36
36
|
|
|
37
|
-
api_key: Optional[str] = getenv("XAI_API_KEY")
|
|
37
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("XAI_API_KEY"))
|
|
38
38
|
base_url: str = "https://api.x.ai/v1"
|
|
39
39
|
|
|
40
40
|
search_parameters: Optional[Dict[str, Any]] = None
|
agno/os/router.py
CHANGED
|
@@ -732,7 +732,9 @@ def get_base_router(
|
|
|
732
732
|
# Process document files
|
|
733
733
|
try:
|
|
734
734
|
file_content = await file.read()
|
|
735
|
-
input_files.append(
|
|
735
|
+
input_files.append(
|
|
736
|
+
FileMedia(content=file_content, filename=file.filename, mime_type=file.content_type)
|
|
737
|
+
)
|
|
736
738
|
except Exception as e:
|
|
737
739
|
log_error(f"Error processing file {file.filename}: {e}")
|
|
738
740
|
continue
|
agno/os/utils.py
CHANGED
|
@@ -138,7 +138,7 @@ def process_document(file: UploadFile) -> Optional[FileMedia]:
|
|
|
138
138
|
if not content:
|
|
139
139
|
raise HTTPException(status_code=400, detail="Empty file")
|
|
140
140
|
|
|
141
|
-
return FileMedia(content=content)
|
|
141
|
+
return FileMedia(content=content, filename=file.filename, mime_type=file.content_type)
|
|
142
142
|
except Exception as e:
|
|
143
143
|
logger.error(f"Error processing document {file.filename}: {e}")
|
|
144
144
|
return None
|