agno 2.0.0a1__py3-none-any.whl → 2.0.0rc2__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 +416 -41
- agno/api/agent.py +2 -2
- agno/api/evals.py +2 -2
- agno/api/os.py +1 -1
- agno/api/settings.py +2 -2
- agno/api/team.py +2 -2
- agno/db/dynamo/dynamo.py +0 -6
- agno/db/firestore/firestore.py +0 -6
- agno/db/in_memory/in_memory_db.py +0 -6
- agno/db/json/json_db.py +0 -6
- agno/db/mongo/mongo.py +8 -9
- agno/db/mysql/utils.py +0 -1
- agno/db/postgres/postgres.py +0 -10
- agno/db/postgres/utils.py +0 -1
- agno/db/redis/redis.py +0 -4
- agno/db/singlestore/singlestore.py +0 -10
- agno/db/singlestore/utils.py +0 -1
- agno/db/sqlite/sqlite.py +0 -4
- agno/db/sqlite/utils.py +0 -1
- agno/eval/accuracy.py +12 -5
- agno/integrations/discord/client.py +5 -1
- agno/knowledge/chunking/strategy.py +14 -14
- agno/knowledge/embedder/aws_bedrock.py +2 -2
- agno/knowledge/knowledge.py +156 -120
- agno/knowledge/reader/arxiv_reader.py +5 -5
- agno/knowledge/reader/csv_reader.py +6 -77
- agno/knowledge/reader/docx_reader.py +5 -5
- agno/knowledge/reader/firecrawl_reader.py +5 -5
- agno/knowledge/reader/json_reader.py +5 -5
- agno/knowledge/reader/markdown_reader.py +31 -9
- agno/knowledge/reader/pdf_reader.py +10 -123
- agno/knowledge/reader/reader_factory.py +65 -72
- agno/knowledge/reader/s3_reader.py +44 -114
- agno/knowledge/reader/text_reader.py +5 -5
- agno/knowledge/reader/url_reader.py +75 -31
- agno/knowledge/reader/web_search_reader.py +6 -29
- agno/knowledge/reader/website_reader.py +5 -5
- agno/knowledge/reader/wikipedia_reader.py +5 -5
- agno/knowledge/reader/youtube_reader.py +6 -6
- agno/knowledge/utils.py +10 -10
- agno/models/anthropic/claude.py +2 -49
- agno/models/aws/bedrock.py +3 -7
- agno/models/base.py +37 -6
- agno/models/message.py +7 -6
- agno/os/app.py +168 -64
- agno/os/interfaces/agui/agui.py +1 -1
- agno/os/interfaces/agui/utils.py +16 -9
- agno/os/interfaces/slack/slack.py +2 -3
- agno/os/interfaces/whatsapp/whatsapp.py +2 -3
- agno/os/mcp.py +235 -0
- agno/os/router.py +576 -19
- agno/os/routers/evals/evals.py +201 -12
- agno/os/routers/knowledge/knowledge.py +455 -18
- agno/os/routers/memory/memory.py +260 -29
- agno/os/routers/metrics/metrics.py +127 -7
- agno/os/routers/session/session.py +398 -25
- agno/os/schema.py +55 -2
- agno/os/settings.py +0 -1
- agno/run/agent.py +96 -2
- agno/run/cancel.py +0 -2
- agno/run/team.py +93 -2
- agno/run/workflow.py +25 -12
- agno/team/team.py +863 -1053
- agno/tools/function.py +65 -7
- agno/tools/linear.py +1 -1
- agno/tools/mcp.py +1 -2
- agno/utils/gemini.py +31 -1
- agno/utils/log.py +52 -2
- agno/utils/mcp.py +55 -3
- agno/utils/models/claude.py +41 -0
- agno/utils/print_response/team.py +177 -73
- agno/utils/streamlit.py +481 -0
- agno/workflow/workflow.py +17 -1
- {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/METADATA +1 -1
- {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/RECORD +78 -77
- agno/knowledge/reader/gcs_reader.py +0 -67
- {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/WHEEL +0 -0
- {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/top_level.txt +0 -0
|
@@ -21,11 +21,11 @@ class TextReader(Reader):
|
|
|
21
21
|
def get_supported_chunking_strategies(self) -> List[ChunkingStrategyType]:
|
|
22
22
|
"""Get the list of supported chunking strategies for Text readers."""
|
|
23
23
|
return [
|
|
24
|
-
ChunkingStrategyType.
|
|
25
|
-
ChunkingStrategyType.
|
|
26
|
-
ChunkingStrategyType.
|
|
27
|
-
ChunkingStrategyType.
|
|
28
|
-
ChunkingStrategyType.
|
|
24
|
+
ChunkingStrategyType.FIXED_SIZE_CHUNKER,
|
|
25
|
+
ChunkingStrategyType.AGENTIC_CHUNKER,
|
|
26
|
+
ChunkingStrategyType.DOCUMENT_CHUNKER,
|
|
27
|
+
ChunkingStrategyType.RECURSIVE_CHUNKER,
|
|
28
|
+
ChunkingStrategyType.SEMANTIC_CHUNKER,
|
|
29
29
|
]
|
|
30
30
|
|
|
31
31
|
@classmethod
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
from io import BytesIO
|
|
2
|
+
from os.path import basename
|
|
3
|
+
from pathlib import Path
|
|
1
4
|
from typing import List, Optional
|
|
2
5
|
from urllib.parse import urlparse
|
|
3
6
|
|
|
@@ -7,6 +10,8 @@ from agno.knowledge.chunking.fixed import FixedSizeChunking
|
|
|
7
10
|
from agno.knowledge.chunking.strategy import ChunkingStrategy, ChunkingStrategyType
|
|
8
11
|
from agno.knowledge.document.base import Document
|
|
9
12
|
from agno.knowledge.reader.base import Reader
|
|
13
|
+
from agno.knowledge.reader.csv_reader import CSVReader
|
|
14
|
+
from agno.knowledge.reader.pdf_reader import PDFReader
|
|
10
15
|
from agno.knowledge.types import ContentType
|
|
11
16
|
from agno.utils.http import async_fetch_with_retry, fetch_with_retry
|
|
12
17
|
from agno.utils.log import log_debug
|
|
@@ -25,31 +30,43 @@ class URLReader(Reader):
|
|
|
25
30
|
def get_supported_chunking_strategies(self) -> List[ChunkingStrategyType]:
|
|
26
31
|
"""Get the list of supported chunking strategies for URL readers."""
|
|
27
32
|
return [
|
|
28
|
-
ChunkingStrategyType.
|
|
29
|
-
ChunkingStrategyType.
|
|
30
|
-
ChunkingStrategyType.
|
|
31
|
-
ChunkingStrategyType.
|
|
32
|
-
ChunkingStrategyType.
|
|
33
|
+
ChunkingStrategyType.FIXED_SIZE_CHUNKER,
|
|
34
|
+
ChunkingStrategyType.AGENTIC_CHUNKER,
|
|
35
|
+
ChunkingStrategyType.DOCUMENT_CHUNKER,
|
|
36
|
+
ChunkingStrategyType.RECURSIVE_CHUNKER,
|
|
37
|
+
ChunkingStrategyType.SEMANTIC_CHUNKER,
|
|
33
38
|
]
|
|
34
39
|
|
|
35
40
|
@classmethod
|
|
36
41
|
def get_supported_content_types(self) -> List[ContentType]:
|
|
37
42
|
return [ContentType.URL]
|
|
38
43
|
|
|
39
|
-
def read(
|
|
44
|
+
def read(
|
|
45
|
+
self, url: str, id: Optional[str] = None, name: Optional[str] = None, password: Optional[str] = None
|
|
46
|
+
) -> List[Document]:
|
|
40
47
|
if not url:
|
|
41
48
|
raise ValueError("No url provided")
|
|
42
49
|
|
|
43
50
|
log_debug(f"Reading: {url}")
|
|
51
|
+
|
|
44
52
|
# Retry the request up to 3 times with exponential backoff
|
|
45
53
|
response = fetch_with_retry(url, proxy=self.proxy)
|
|
46
54
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
documents = self._create_documents(
|
|
56
|
+
url=url, text=response.text, content=response.content, id=id, name=name, password=password
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if not self.chunk:
|
|
60
|
+
return documents
|
|
61
|
+
|
|
62
|
+
chunked_documents = []
|
|
63
|
+
for document in documents:
|
|
64
|
+
chunked_documents.append(self.chunk_document(document))
|
|
65
|
+
return [doc for sublist in chunked_documents for doc in sublist]
|
|
51
66
|
|
|
52
|
-
async def async_read(
|
|
67
|
+
async def async_read(
|
|
68
|
+
self, url: str, id: Optional[str] = None, name: Optional[str] = None, password: Optional[str] = None
|
|
69
|
+
) -> List[Document]:
|
|
53
70
|
"""Async version of read method"""
|
|
54
71
|
if not url:
|
|
55
72
|
raise ValueError("No url provided")
|
|
@@ -59,26 +76,53 @@ class URLReader(Reader):
|
|
|
59
76
|
async with httpx.AsyncClient(**client_args) as client: # type: ignore
|
|
60
77
|
response = await async_fetch_with_retry(url, client=client)
|
|
61
78
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
79
|
+
documents = self._create_documents(
|
|
80
|
+
url=url, text=response.text, content=response.content, id=id, name=name, password=password
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if not self.chunk:
|
|
84
|
+
return documents
|
|
85
|
+
|
|
86
|
+
return await self.chunk_documents_async(documents)
|
|
66
87
|
|
|
67
|
-
def
|
|
68
|
-
self,
|
|
69
|
-
|
|
88
|
+
def _create_documents(
|
|
89
|
+
self,
|
|
90
|
+
url: str,
|
|
91
|
+
text: str,
|
|
92
|
+
content: bytes,
|
|
93
|
+
id: Optional[str] = None,
|
|
94
|
+
name: Optional[str] = None,
|
|
95
|
+
password: Optional[str] = None,
|
|
96
|
+
) -> List[Document]:
|
|
70
97
|
"""Helper method to create a document from URL content"""
|
|
98
|
+
|
|
99
|
+
# Determine file extension from URL
|
|
71
100
|
parsed_url = urlparse(url)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
101
|
+
url_path = Path(parsed_url.path) # type: ignore
|
|
102
|
+
file_extension = url_path.suffix.lower()
|
|
103
|
+
|
|
104
|
+
# Read the document using the appropriate reader
|
|
105
|
+
if file_extension == ".csv":
|
|
106
|
+
filename = basename(parsed_url.path) or "data.csv"
|
|
107
|
+
return CSVReader().read(file=BytesIO(content), name=filename)
|
|
108
|
+
elif file_extension == ".pdf":
|
|
109
|
+
if password:
|
|
110
|
+
return PDFReader().read(pdf=BytesIO(content), name=name, password=password)
|
|
111
|
+
else:
|
|
112
|
+
return PDFReader().read(pdf=BytesIO(content), name=name)
|
|
113
|
+
else:
|
|
114
|
+
doc_name = name or parsed_url.path.strip("/").replace("/", "_").replace(" ", "_")
|
|
115
|
+
if not doc_name:
|
|
116
|
+
doc_name = parsed_url.netloc
|
|
117
|
+
if not doc_name:
|
|
118
|
+
doc_name = url
|
|
119
|
+
|
|
120
|
+
return [
|
|
121
|
+
Document(
|
|
122
|
+
name=doc_name,
|
|
123
|
+
id=id or doc_name,
|
|
124
|
+
meta_data={"url": url},
|
|
125
|
+
content=text,
|
|
126
|
+
size=len(text),
|
|
127
|
+
)
|
|
128
|
+
]
|
|
@@ -11,7 +11,6 @@ from agno.knowledge.chunking.semantic import SemanticChunking
|
|
|
11
11
|
from agno.knowledge.chunking.strategy import ChunkingStrategy, ChunkingStrategyType
|
|
12
12
|
from agno.knowledge.document.base import Document
|
|
13
13
|
from agno.knowledge.reader.base import Reader
|
|
14
|
-
from agno.knowledge.reader.url_reader import URLReader
|
|
15
14
|
from agno.knowledge.types import ContentType
|
|
16
15
|
from agno.utils.log import log_debug, logger
|
|
17
16
|
|
|
@@ -48,30 +47,25 @@ class WebSearchReader(Reader):
|
|
|
48
47
|
|
|
49
48
|
# Internal state
|
|
50
49
|
_visited_urls: Set[str] = field(default_factory=set)
|
|
51
|
-
_url_reader: Optional[URLReader] = None
|
|
52
50
|
_last_search_time: float = field(default=0.0, init=False)
|
|
53
51
|
|
|
54
52
|
# Override default chunking strategy
|
|
55
53
|
chunking_strategy: Optional[ChunkingStrategy] = SemanticChunking()
|
|
56
54
|
|
|
57
|
-
def __post_init__(self):
|
|
58
|
-
"""Initialize the URL reader and chunking strategy after dataclass initialization"""
|
|
59
|
-
self._url_reader = URLReader()
|
|
60
|
-
|
|
61
55
|
@classmethod
|
|
62
56
|
def get_supported_chunking_strategies(self) -> List[ChunkingStrategyType]:
|
|
63
57
|
"""Get the list of supported chunking strategies for Web Search readers."""
|
|
64
58
|
return [
|
|
65
|
-
ChunkingStrategyType.
|
|
66
|
-
ChunkingStrategyType.
|
|
67
|
-
ChunkingStrategyType.
|
|
68
|
-
ChunkingStrategyType.
|
|
69
|
-
ChunkingStrategyType.
|
|
59
|
+
ChunkingStrategyType.AGENTIC_CHUNKER,
|
|
60
|
+
ChunkingStrategyType.DOCUMENT_CHUNKER,
|
|
61
|
+
ChunkingStrategyType.RECURSIVE_CHUNKER,
|
|
62
|
+
ChunkingStrategyType.SEMANTIC_CHUNKER,
|
|
63
|
+
ChunkingStrategyType.FIXED_SIZE_CHUNKER,
|
|
70
64
|
]
|
|
71
65
|
|
|
72
66
|
@classmethod
|
|
73
67
|
def get_supported_content_types(self) -> List[ContentType]:
|
|
74
|
-
return [ContentType.
|
|
68
|
+
return [ContentType.TOPIC]
|
|
75
69
|
|
|
76
70
|
def _respect_rate_limits(self):
|
|
77
71
|
"""Ensure we don't exceed rate limits"""
|
|
@@ -328,23 +322,6 @@ class WebSearchReader(Reader):
|
|
|
328
322
|
self._visited_urls.add(url)
|
|
329
323
|
|
|
330
324
|
try:
|
|
331
|
-
# Use the URL reader for async fetching
|
|
332
|
-
if self._url_reader:
|
|
333
|
-
docs = await self._url_reader.async_read(url)
|
|
334
|
-
if docs:
|
|
335
|
-
# Use the first document and add search metadata
|
|
336
|
-
doc = docs[0]
|
|
337
|
-
doc.meta_data.update(
|
|
338
|
-
{
|
|
339
|
-
"search_title": result.get("title", ""),
|
|
340
|
-
"search_description": result.get("description", ""),
|
|
341
|
-
"source": "web_search",
|
|
342
|
-
"search_engine": self.search_engine,
|
|
343
|
-
}
|
|
344
|
-
)
|
|
345
|
-
return doc
|
|
346
|
-
|
|
347
|
-
# Fallback to manual async fetching
|
|
348
325
|
headers = {"User-Agent": self.user_agent}
|
|
349
326
|
async with httpx.AsyncClient(timeout=self.request_timeout) as client:
|
|
350
327
|
response = await client.get(url, headers=headers, follow_redirects=True)
|
|
@@ -52,11 +52,11 @@ class WebsiteReader(Reader):
|
|
|
52
52
|
def get_supported_chunking_strategies(self) -> List[ChunkingStrategyType]:
|
|
53
53
|
"""Get the list of supported chunking strategies for Website readers."""
|
|
54
54
|
return [
|
|
55
|
-
ChunkingStrategyType.
|
|
56
|
-
ChunkingStrategyType.
|
|
57
|
-
ChunkingStrategyType.
|
|
58
|
-
ChunkingStrategyType.
|
|
59
|
-
ChunkingStrategyType.
|
|
55
|
+
ChunkingStrategyType.AGENTIC_CHUNKER,
|
|
56
|
+
ChunkingStrategyType.DOCUMENT_CHUNKER,
|
|
57
|
+
ChunkingStrategyType.RECURSIVE_CHUNKER,
|
|
58
|
+
ChunkingStrategyType.SEMANTIC_CHUNKER,
|
|
59
|
+
ChunkingStrategyType.FIXED_SIZE_CHUNKER,
|
|
60
60
|
]
|
|
61
61
|
|
|
62
62
|
@classmethod
|
|
@@ -26,11 +26,11 @@ class WikipediaReader(Reader):
|
|
|
26
26
|
def get_supported_chunking_strategies(self) -> List[ChunkingStrategyType]:
|
|
27
27
|
"""Get the list of supported chunking strategies for Wikipedia readers."""
|
|
28
28
|
return [
|
|
29
|
-
ChunkingStrategyType.
|
|
30
|
-
ChunkingStrategyType.
|
|
31
|
-
ChunkingStrategyType.
|
|
32
|
-
ChunkingStrategyType.
|
|
33
|
-
ChunkingStrategyType.
|
|
29
|
+
ChunkingStrategyType.FIXED_SIZE_CHUNKER,
|
|
30
|
+
ChunkingStrategyType.AGENTIC_CHUNKER,
|
|
31
|
+
ChunkingStrategyType.DOCUMENT_CHUNKER,
|
|
32
|
+
ChunkingStrategyType.RECURSIVE_CHUNKER,
|
|
33
|
+
ChunkingStrategyType.SEMANTIC_CHUNKER,
|
|
34
34
|
]
|
|
35
35
|
|
|
36
36
|
@classmethod
|
|
@@ -26,16 +26,16 @@ class YouTubeReader(Reader):
|
|
|
26
26
|
def get_supported_chunking_strategies(self) -> List[ChunkingStrategyType]:
|
|
27
27
|
"""Get the list of supported chunking strategies for YouTube readers."""
|
|
28
28
|
return [
|
|
29
|
-
ChunkingStrategyType.
|
|
30
|
-
ChunkingStrategyType.
|
|
31
|
-
ChunkingStrategyType.
|
|
32
|
-
ChunkingStrategyType.
|
|
33
|
-
ChunkingStrategyType.
|
|
29
|
+
ChunkingStrategyType.RECURSIVE_CHUNKER,
|
|
30
|
+
ChunkingStrategyType.AGENTIC_CHUNKER,
|
|
31
|
+
ChunkingStrategyType.DOCUMENT_CHUNKER,
|
|
32
|
+
ChunkingStrategyType.SEMANTIC_CHUNKER,
|
|
33
|
+
ChunkingStrategyType.FIXED_SIZE_CHUNKER,
|
|
34
34
|
]
|
|
35
35
|
|
|
36
36
|
@classmethod
|
|
37
37
|
def get_supported_content_types(self) -> List[ContentType]:
|
|
38
|
-
return [ContentType.
|
|
38
|
+
return [ContentType.YOUTUBE]
|
|
39
39
|
|
|
40
40
|
def read(self, url: str, name: Optional[str] = None) -> List[Document]:
|
|
41
41
|
try:
|
agno/knowledge/utils.py
CHANGED
|
@@ -11,23 +11,23 @@ def _get_chunker_class(strategy_type):
|
|
|
11
11
|
|
|
12
12
|
# Map strategy types to their corresponding classes
|
|
13
13
|
strategy_class_mapping = {
|
|
14
|
-
ChunkingStrategyType.
|
|
14
|
+
ChunkingStrategyType.AGENTIC_CHUNKER: lambda: _import_class(
|
|
15
15
|
"agno.knowledge.chunking.agentic", "AgenticChunking"
|
|
16
16
|
),
|
|
17
|
-
ChunkingStrategyType.
|
|
17
|
+
ChunkingStrategyType.DOCUMENT_CHUNKER: lambda: _import_class(
|
|
18
18
|
"agno.knowledge.chunking.document", "DocumentChunking"
|
|
19
19
|
),
|
|
20
|
-
ChunkingStrategyType.
|
|
20
|
+
ChunkingStrategyType.RECURSIVE_CHUNKER: lambda: _import_class(
|
|
21
21
|
"agno.knowledge.chunking.recursive", "RecursiveChunking"
|
|
22
22
|
),
|
|
23
|
-
ChunkingStrategyType.
|
|
23
|
+
ChunkingStrategyType.SEMANTIC_CHUNKER: lambda: _import_class(
|
|
24
24
|
"agno.knowledge.chunking.semantic", "SemanticChunking"
|
|
25
25
|
),
|
|
26
|
-
ChunkingStrategyType.
|
|
26
|
+
ChunkingStrategyType.FIXED_SIZE_CHUNKER: lambda: _import_class(
|
|
27
27
|
"agno.knowledge.chunking.fixed", "FixedSizeChunking"
|
|
28
28
|
),
|
|
29
|
-
ChunkingStrategyType.
|
|
30
|
-
ChunkingStrategyType.
|
|
29
|
+
ChunkingStrategyType.ROW_CHUNKER: lambda: _import_class("agno.knowledge.chunking.row", "RowChunking"),
|
|
30
|
+
ChunkingStrategyType.MARKDOWN_CHUNKER: lambda: _import_class(
|
|
31
31
|
"agno.knowledge.chunking.markdown", "MarkdownChunking"
|
|
32
32
|
),
|
|
33
33
|
}
|
|
@@ -61,8 +61,8 @@ def get_reader_info(reader_key: str) -> Dict:
|
|
|
61
61
|
|
|
62
62
|
return {
|
|
63
63
|
"id": reader_key,
|
|
64
|
-
"name": reader_key.
|
|
65
|
-
"description":
|
|
64
|
+
"name": "".join(word.capitalize() for word in reader_key.split("_")) + "Reader",
|
|
65
|
+
"description": reader_instance.description,
|
|
66
66
|
"chunking_strategies": [
|
|
67
67
|
strategy.value for strategy in supported_strategies
|
|
68
68
|
], # Convert enums to string values
|
|
@@ -132,7 +132,7 @@ def get_chunker_info(chunker_key: str) -> Dict:
|
|
|
132
132
|
return {
|
|
133
133
|
"key": chunker_key,
|
|
134
134
|
"class_name": class_name,
|
|
135
|
-
"name":
|
|
135
|
+
"name": chunker_key,
|
|
136
136
|
"description": docstring.strip(),
|
|
137
137
|
"strategy_type": strategy_type.value,
|
|
138
138
|
}
|
agno/models/anthropic/claude.py
CHANGED
|
@@ -13,7 +13,7 @@ from agno.models.metrics import Metrics
|
|
|
13
13
|
from agno.models.response import ModelResponse
|
|
14
14
|
from agno.run.agent import RunOutput
|
|
15
15
|
from agno.utils.log import log_debug, log_error, log_warning
|
|
16
|
-
from agno.utils.models.claude import MCPServerConfiguration, format_messages
|
|
16
|
+
from agno.utils.models.claude import MCPServerConfiguration, format_messages, format_tools_for_model
|
|
17
17
|
|
|
18
18
|
try:
|
|
19
19
|
from anthropic import Anthropic as AnthropicClient
|
|
@@ -174,59 +174,12 @@ class Claude(Model):
|
|
|
174
174
|
request_kwargs["system"] = [{"text": system_message, "type": "text"}]
|
|
175
175
|
|
|
176
176
|
if tools:
|
|
177
|
-
request_kwargs["tools"] =
|
|
177
|
+
request_kwargs["tools"] = format_tools_for_model(tools)
|
|
178
178
|
|
|
179
179
|
if request_kwargs:
|
|
180
180
|
log_debug(f"Calling {self.provider} with request parameters: {request_kwargs}", log_level=2)
|
|
181
181
|
return request_kwargs
|
|
182
182
|
|
|
183
|
-
def _format_tools_for_model(self, tools: Optional[List[Dict[str, Any]]] = None) -> Optional[List[Dict[str, Any]]]:
|
|
184
|
-
"""
|
|
185
|
-
Transforms function definitions into a format accepted by the Anthropic API.
|
|
186
|
-
"""
|
|
187
|
-
if not tools:
|
|
188
|
-
return None
|
|
189
|
-
|
|
190
|
-
parsed_tools: List[Dict[str, Any]] = []
|
|
191
|
-
for tool_def in tools:
|
|
192
|
-
if tool_def.get("type", "") != "function":
|
|
193
|
-
parsed_tools.append(tool_def)
|
|
194
|
-
continue
|
|
195
|
-
|
|
196
|
-
func_def = tool_def.get("function", {})
|
|
197
|
-
parameters: Dict[str, Any] = func_def.get("parameters", {})
|
|
198
|
-
properties: Dict[str, Any] = parameters.get("properties", {})
|
|
199
|
-
required_params: List[str] = []
|
|
200
|
-
|
|
201
|
-
for param_name, param_info in properties.items():
|
|
202
|
-
param_type = param_info.get("type", "")
|
|
203
|
-
param_type_list: List[str] = [param_type] if isinstance(param_type, str) else param_type or []
|
|
204
|
-
|
|
205
|
-
if "null" not in param_type_list:
|
|
206
|
-
required_params.append(param_name)
|
|
207
|
-
|
|
208
|
-
input_properties: Dict[str, Dict[str, Union[str, List[str]]]] = {}
|
|
209
|
-
for param_name, param_info in properties.items():
|
|
210
|
-
input_properties[param_name] = {
|
|
211
|
-
"description": param_info.get("description", ""),
|
|
212
|
-
}
|
|
213
|
-
if "type" not in param_info and "anyOf" in param_info:
|
|
214
|
-
input_properties[param_name]["anyOf"] = param_info["anyOf"]
|
|
215
|
-
else:
|
|
216
|
-
input_properties[param_name]["type"] = param_info.get("type", "")
|
|
217
|
-
|
|
218
|
-
tool = {
|
|
219
|
-
"name": func_def.get("name") or "",
|
|
220
|
-
"description": func_def.get("description") or "",
|
|
221
|
-
"input_schema": {
|
|
222
|
-
"type": parameters.get("type", "object"),
|
|
223
|
-
"properties": input_properties,
|
|
224
|
-
"required": required_params,
|
|
225
|
-
},
|
|
226
|
-
}
|
|
227
|
-
parsed_tools.append(tool)
|
|
228
|
-
return parsed_tools
|
|
229
|
-
|
|
230
183
|
def invoke(
|
|
231
184
|
self,
|
|
232
185
|
messages: List[Message],
|
agno/models/aws/bedrock.py
CHANGED
|
@@ -181,14 +181,10 @@ class AwsBedrock(Model):
|
|
|
181
181
|
required = []
|
|
182
182
|
|
|
183
183
|
for param_name, param_info in func_def.get("parameters", {}).get("properties", {}).items():
|
|
184
|
-
|
|
185
|
-
if isinstance(param_type, list):
|
|
186
|
-
param_type = [t for t in param_type if t != "null"][0]
|
|
184
|
+
properties[param_name] = param_info.copy()
|
|
187
185
|
|
|
188
|
-
properties[param_name]
|
|
189
|
-
"
|
|
190
|
-
"description": param_info.get("description") or "",
|
|
191
|
-
}
|
|
186
|
+
if "description" not in properties[param_name]:
|
|
187
|
+
properties[param_name]["description"] = ""
|
|
192
188
|
|
|
193
189
|
if "null" not in (
|
|
194
190
|
param_info.get("type") if isinstance(param_info.get("type"), list) else [param_info.get("type")]
|
agno/models/base.py
CHANGED
|
@@ -25,7 +25,7 @@ from agno.media import Audio, AudioArtifact, AudioResponse, Image, ImageArtifact
|
|
|
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
|
|
28
|
-
from agno.run.agent import RunContentEvent, RunOutput, RunOutputEvent
|
|
28
|
+
from agno.run.agent import CustomEvent, RunContentEvent, RunOutput, RunOutputEvent
|
|
29
29
|
from agno.run.team import RunContentEvent as TeamRunContentEvent
|
|
30
30
|
from agno.run.team import TeamRunOutputEvent
|
|
31
31
|
from agno.tools.function import Function, FunctionCall, FunctionExecutionResult, UserInputField
|
|
@@ -1223,6 +1223,9 @@ class Model(ABC):
|
|
|
1223
1223
|
if function_call.function.show_result:
|
|
1224
1224
|
yield ModelResponse(content=item.content)
|
|
1225
1225
|
|
|
1226
|
+
if isinstance(item, CustomEvent):
|
|
1227
|
+
function_call_output += str(item)
|
|
1228
|
+
|
|
1226
1229
|
# Yield the event itself to bubble it up
|
|
1227
1230
|
yield item
|
|
1228
1231
|
|
|
@@ -1389,7 +1392,7 @@ class Model(ABC):
|
|
|
1389
1392
|
async def arun_function_call(
|
|
1390
1393
|
self,
|
|
1391
1394
|
function_call: FunctionCall,
|
|
1392
|
-
) -> Tuple[Union[bool, AgentRunException], Timer, FunctionCall,
|
|
1395
|
+
) -> Tuple[Union[bool, AgentRunException], Timer, FunctionCall, FunctionExecutionResult]:
|
|
1393
1396
|
"""Run a single function call and return its success status, timer, and the FunctionCall object."""
|
|
1394
1397
|
from inspect import isasyncgenfunction, iscoroutine, iscoroutinefunction
|
|
1395
1398
|
|
|
@@ -1423,7 +1426,7 @@ class Model(ABC):
|
|
|
1423
1426
|
raise e
|
|
1424
1427
|
|
|
1425
1428
|
function_call_timer.stop()
|
|
1426
|
-
return success, function_call_timer, function_call, result
|
|
1429
|
+
return success, function_call_timer, function_call, result
|
|
1427
1430
|
|
|
1428
1431
|
async def arun_function_calls(
|
|
1429
1432
|
self,
|
|
@@ -1569,7 +1572,9 @@ class Model(ABC):
|
|
|
1569
1572
|
raise result
|
|
1570
1573
|
|
|
1571
1574
|
# Unpack result
|
|
1572
|
-
function_call_success, function_call_timer, function_call,
|
|
1575
|
+
function_call_success, function_call_timer, function_call, function_execution_result = result
|
|
1576
|
+
|
|
1577
|
+
updated_session_state = function_execution_result.updated_session_state
|
|
1573
1578
|
|
|
1574
1579
|
# Handle AgentRunException
|
|
1575
1580
|
if isinstance(function_call_success, AgentRunException):
|
|
@@ -1623,20 +1628,43 @@ class Model(ABC):
|
|
|
1623
1628
|
yield ModelResponse(content=item.content)
|
|
1624
1629
|
continue
|
|
1625
1630
|
|
|
1631
|
+
if isinstance(item, CustomEvent):
|
|
1632
|
+
function_call_output += str(item)
|
|
1633
|
+
|
|
1626
1634
|
# Yield the event itself to bubble it up
|
|
1627
1635
|
yield item
|
|
1636
|
+
|
|
1637
|
+
# Yield custom events emitted by the tool
|
|
1628
1638
|
else:
|
|
1629
1639
|
function_call_output += str(item)
|
|
1630
1640
|
if function_call.function.show_result:
|
|
1631
1641
|
yield ModelResponse(content=str(item))
|
|
1632
1642
|
else:
|
|
1633
|
-
|
|
1643
|
+
from agno.tools.function import ToolResult
|
|
1644
|
+
|
|
1645
|
+
if isinstance(function_execution_result.result, ToolResult):
|
|
1646
|
+
tool_result = function_execution_result.result
|
|
1647
|
+
function_call_output = tool_result.content
|
|
1648
|
+
|
|
1649
|
+
if tool_result.images:
|
|
1650
|
+
function_execution_result.images = tool_result.images
|
|
1651
|
+
if tool_result.videos:
|
|
1652
|
+
function_execution_result.videos = tool_result.videos
|
|
1653
|
+
if tool_result.audios:
|
|
1654
|
+
function_execution_result.audios = tool_result.audios
|
|
1655
|
+
else:
|
|
1656
|
+
function_call_output = str(function_call.result)
|
|
1657
|
+
|
|
1634
1658
|
if function_call.function.show_result:
|
|
1635
1659
|
yield ModelResponse(content=function_call_output)
|
|
1636
1660
|
|
|
1637
1661
|
# Create and yield function call result
|
|
1638
1662
|
function_call_result = self.create_function_call_result(
|
|
1639
|
-
function_call,
|
|
1663
|
+
function_call,
|
|
1664
|
+
success=function_call_success,
|
|
1665
|
+
output=function_call_output,
|
|
1666
|
+
timer=function_call_timer,
|
|
1667
|
+
function_execution_result=function_execution_result,
|
|
1640
1668
|
)
|
|
1641
1669
|
yield ModelResponse(
|
|
1642
1670
|
content=f"{function_call.get_call_str()} completed in {function_call_timer.elapsed:.4f}s.",
|
|
@@ -1653,6 +1681,9 @@ class Model(ABC):
|
|
|
1653
1681
|
],
|
|
1654
1682
|
event=ModelResponseEvent.tool_call_completed.value,
|
|
1655
1683
|
updated_session_state=updated_session_state,
|
|
1684
|
+
images=function_execution_result.images,
|
|
1685
|
+
videos=function_execution_result.videos,
|
|
1686
|
+
audios=function_execution_result.audios,
|
|
1656
1687
|
)
|
|
1657
1688
|
|
|
1658
1689
|
# Add function call result to function call results
|
agno/models/message.py
CHANGED
|
@@ -228,12 +228,13 @@ class Message(BaseModel):
|
|
|
228
228
|
if isinstance(tool_call_arguments, dict)
|
|
229
229
|
else json.loads(tool_call_arguments)
|
|
230
230
|
)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
231
|
+
if tool_call_args:
|
|
232
|
+
# Ensure tool_call_args is a dictionary before calling .items()
|
|
233
|
+
if isinstance(tool_call_args, dict):
|
|
234
|
+
arguments = ", ".join(f"{k}: {v}" for k, v in tool_call_args.items())
|
|
235
|
+
tool_calls_list.append(f" Arguments: '{arguments}'")
|
|
236
|
+
else:
|
|
237
|
+
tool_calls_list.append(f" Arguments: '{tool_call_args}'")
|
|
237
238
|
except json.JSONDecodeError:
|
|
238
239
|
tool_calls_list.append(" Arguments: 'Invalid JSON format'")
|
|
239
240
|
tool_calls_str = "\n".join(tool_calls_list)
|