agno 2.0.0rc1__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.
Files changed (49) hide show
  1. agno/agent/agent.py +32 -14
  2. agno/db/mongo/mongo.py +8 -3
  3. agno/eval/accuracy.py +12 -5
  4. agno/knowledge/chunking/strategy.py +14 -14
  5. agno/knowledge/knowledge.py +156 -120
  6. agno/knowledge/reader/arxiv_reader.py +5 -5
  7. agno/knowledge/reader/csv_reader.py +6 -77
  8. agno/knowledge/reader/docx_reader.py +5 -5
  9. agno/knowledge/reader/firecrawl_reader.py +5 -5
  10. agno/knowledge/reader/json_reader.py +5 -5
  11. agno/knowledge/reader/markdown_reader.py +31 -9
  12. agno/knowledge/reader/pdf_reader.py +10 -123
  13. agno/knowledge/reader/reader_factory.py +65 -72
  14. agno/knowledge/reader/s3_reader.py +44 -114
  15. agno/knowledge/reader/text_reader.py +5 -5
  16. agno/knowledge/reader/url_reader.py +75 -31
  17. agno/knowledge/reader/web_search_reader.py +6 -29
  18. agno/knowledge/reader/website_reader.py +5 -5
  19. agno/knowledge/reader/wikipedia_reader.py +5 -5
  20. agno/knowledge/reader/youtube_reader.py +6 -6
  21. agno/knowledge/utils.py +10 -10
  22. agno/models/aws/bedrock.py +3 -7
  23. agno/models/base.py +37 -6
  24. agno/os/app.py +32 -24
  25. agno/os/mcp.py +39 -59
  26. agno/os/router.py +547 -16
  27. agno/os/routers/evals/evals.py +197 -12
  28. agno/os/routers/knowledge/knowledge.py +428 -14
  29. agno/os/routers/memory/memory.py +250 -28
  30. agno/os/routers/metrics/metrics.py +125 -7
  31. agno/os/routers/session/session.py +393 -25
  32. agno/os/schema.py +55 -2
  33. agno/run/agent.py +9 -0
  34. agno/run/team.py +93 -2
  35. agno/run/workflow.py +25 -12
  36. agno/team/team.py +861 -1051
  37. agno/tools/mcp.py +1 -2
  38. agno/utils/log.py +52 -2
  39. agno/utils/mcp.py +55 -3
  40. agno/utils/models/claude.py +0 -8
  41. agno/utils/print_response/team.py +177 -73
  42. agno/utils/streamlit.py +27 -0
  43. agno/workflow/workflow.py +9 -0
  44. {agno-2.0.0rc1.dist-info → agno-2.0.0rc2.dist-info}/METADATA +1 -1
  45. {agno-2.0.0rc1.dist-info → agno-2.0.0rc2.dist-info}/RECORD +48 -49
  46. agno/knowledge/reader/gcs_reader.py +0 -67
  47. {agno-2.0.0rc1.dist-info → agno-2.0.0rc2.dist-info}/WHEEL +0 -0
  48. {agno-2.0.0rc1.dist-info → agno-2.0.0rc2.dist-info}/licenses/LICENSE +0 -0
  49. {agno-2.0.0rc1.dist-info → agno-2.0.0rc2.dist-info}/top_level.txt +0 -0
@@ -2,14 +2,15 @@ import asyncio
2
2
  from io import BytesIO
3
3
  from pathlib import Path
4
4
  from typing import List, Optional
5
- from uuid import uuid4
6
5
 
7
6
  from agno.knowledge.chunking.fixed import FixedSizeChunking
8
7
  from agno.knowledge.chunking.strategy import ChunkingStrategy, ChunkingStrategyType
9
8
  from agno.knowledge.document.base import Document
10
9
  from agno.knowledge.reader.base import Reader
10
+ from agno.knowledge.reader.pdf_reader import PDFReader
11
+ from agno.knowledge.reader.text_reader import TextReader
11
12
  from agno.knowledge.types import ContentType
12
- from agno.utils.log import log_debug, log_info, logger
13
+ from agno.utils.log import log_info, logger
13
14
 
14
15
  try:
15
16
  from agno.aws.resource.s3.object import S3Object # type: ignore
@@ -37,11 +38,11 @@ class S3Reader(Reader):
37
38
  def get_supported_chunking_strategies(self) -> List[ChunkingStrategyType]:
38
39
  """Get the list of supported chunking strategies for S3 readers."""
39
40
  return [
40
- ChunkingStrategyType.FIXED_SIZE_CHUNKING,
41
- ChunkingStrategyType.AGENTIC_CHUNKING,
42
- ChunkingStrategyType.DOCUMENT_CHUNKING,
43
- ChunkingStrategyType.RECURSIVE_CHUNKING,
44
- ChunkingStrategyType.SEMANTIC_CHUNKING,
41
+ ChunkingStrategyType.FIXED_SIZE_CHUNKER,
42
+ ChunkingStrategyType.AGENTIC_CHUNKER,
43
+ ChunkingStrategyType.DOCUMENT_CHUNKER,
44
+ ChunkingStrategyType.RECURSIVE_CHUNKER,
45
+ ChunkingStrategyType.SEMANTIC_CHUNKER,
45
46
  ]
46
47
 
47
48
  @classmethod
@@ -52,120 +53,49 @@ class S3Reader(Reader):
52
53
  try:
53
54
  log_info(f"Reading S3 file: {s3_object.uri}")
54
55
 
56
+ # Read PDF files
55
57
  if s3_object.uri.endswith(".pdf"):
56
- return S3PDFReader().read(name, s3_object)
58
+ object_resource = s3_object.get_resource()
59
+ object_body = object_resource.get()["Body"]
60
+ doc_name = (
61
+ s3_object.name.split("/")[-1].split(".")[0].replace("/", "_").replace(" ", "_")
62
+ if name is None
63
+ else name
64
+ )
65
+ return PDFReader().read(pdf=BytesIO(object_body.read()), name=doc_name)
66
+
67
+ # Read text files
57
68
  else:
58
- return S3TextReader().read(name, s3_object)
69
+ doc_name = (
70
+ s3_object.name.split("/")[-1].split(".")[0].replace("/", "_").replace(" ", "_")
71
+ if name is None
72
+ else name
73
+ )
74
+ obj_name = s3_object.name.split("/")[-1]
75
+ temporary_file = Path("storage").joinpath(obj_name)
76
+ s3_object.download(temporary_file)
77
+
78
+ # TODO: Before we were using textract here. Needed?
79
+ # s3_object.download(temporary_file)
80
+ # doc_content = textract.process(temporary_file)
81
+ # documents = [
82
+ # Document(
83
+ # name=doc_name,
84
+ # id=doc_name,
85
+ # content=doc_content.decode("utf-8"),
86
+ # )
87
+ # ]
88
+
89
+ documents = TextReader().read(file=temporary_file, name=doc_name)
90
+
91
+ temporary_file.unlink()
92
+ return documents
59
93
 
60
94
  except Exception as e:
61
95
  logger.error(f"Error reading: {s3_object.uri}: {e}")
62
- return []
63
-
64
- async def async_read(self, name: Optional[str], s3_object: S3Object) -> List[Document]:
65
- """Asynchronously read S3 files by running the synchronous read operation in a thread."""
66
- return await asyncio.to_thread(self.read, name, s3_object)
67
-
68
-
69
- class S3TextReader(Reader):
70
- """Reader for text files on S3"""
71
96
 
72
- def get_supported_chunking_strategies(self) -> List[ChunkingStrategyType]:
73
- """Get the list of supported chunking strategies for S3 text readers."""
74
- return [
75
- ChunkingStrategyType.AGENTIC_CHUNKING,
76
- ChunkingStrategyType.DOCUMENT_CHUNKING,
77
- ChunkingStrategyType.RECURSIVE_CHUNKING,
78
- ]
79
-
80
- def get_supported_content_types(self) -> List[ContentType]:
81
- return [ContentType.TEXT]
82
-
83
- def read(self, name: Optional[str], s3_object: S3Object) -> List[Document]:
84
- try:
85
- log_info(f"Reading text file: {s3_object.uri}")
86
-
87
- doc_name = s3_object.name.split("/")[-1].split(".")[0].replace("/", "_").replace(" ", "_")
88
- if name is not None:
89
- doc_name = name
90
- obj_name = s3_object.name.split("/")[-1]
91
- temporary_file = Path("storage").joinpath(obj_name)
92
- s3_object.download(temporary_file)
93
-
94
- log_info(f"Parsing: {temporary_file}")
95
- doc_content = textract.process(temporary_file)
96
- documents = [
97
- Document(
98
- name=doc_name,
99
- id=doc_name,
100
- content=doc_content.decode("utf-8"),
101
- )
102
- ]
103
- if self.chunk:
104
- chunked_documents = []
105
- for document in documents:
106
- chunked_documents.extend(self.chunk_document(document))
107
- return chunked_documents
108
-
109
- log_debug(f"Deleting: {temporary_file}")
110
- temporary_file.unlink()
111
- return documents
112
- except Exception as e:
113
- logger.error(f"Error reading: {s3_object.uri}: {e}")
114
97
  return []
115
98
 
116
99
  async def async_read(self, name: Optional[str], s3_object: S3Object) -> List[Document]:
117
- """Asynchronously read text files from S3 by running the synchronous read operation in a thread.
118
-
119
- Args:
120
- s3_object (S3Object): The S3 object to read
121
-
122
- Returns:
123
- List[Document]: List of documents from the text file
124
- """
125
- return await asyncio.to_thread(self.read, name, s3_object)
126
-
127
-
128
- class S3PDFReader(Reader):
129
- """Reader for PDF files on S3"""
130
-
131
- def get_supported_content_types(self) -> List[ContentType]:
132
- return [ContentType.FILE]
133
-
134
- def read(self, name: Optional[str], s3_object: S3Object) -> List[Document]:
135
- try:
136
- log_info(f"Reading PDF file: {s3_object.uri}")
137
-
138
- object_resource = s3_object.get_resource()
139
- object_body = object_resource.get()["Body"]
140
- doc_name = s3_object.name.split("/")[-1].split(".")[0].replace("/", "_").replace(" ", "_")
141
- if name is not None:
142
- doc_name = name
143
- doc_reader = DocumentReader(BytesIO(object_body.read()))
144
- documents = [
145
- Document(
146
- name=doc_name,
147
- id=str(uuid4()),
148
- meta_data={"page": page_number},
149
- content=page.extract_text(),
150
- )
151
- for page_number, page in enumerate(doc_reader.pages, start=1)
152
- ]
153
- if self.chunk:
154
- chunked_documents = []
155
- for document in documents:
156
- chunked_documents.extend(self.chunk_document(document))
157
- return chunked_documents
158
- return documents
159
- except Exception:
160
- raise
161
-
162
- async def async_read(self, name: Optional[str], s3_object: S3Object) -> List[Document]:
163
- """Asynchronously read PDF files from S3 by running the synchronous read operation in a thread.
164
-
165
- Args:
166
- s3_object (S3Object): The S3 object to read
167
-
168
- Returns:
169
- List[Document]: List of documents from the PDF file
170
- """
100
+ """Asynchronously read S3 files by running the synchronous read operation in a thread."""
171
101
  return await asyncio.to_thread(self.read, name, s3_object)
@@ -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.FIXED_SIZE_CHUNKING,
25
- ChunkingStrategyType.AGENTIC_CHUNKING,
26
- ChunkingStrategyType.DOCUMENT_CHUNKING,
27
- ChunkingStrategyType.RECURSIVE_CHUNKING,
28
- ChunkingStrategyType.SEMANTIC_CHUNKING,
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.FIXED_SIZE_CHUNKING,
29
- ChunkingStrategyType.AGENTIC_CHUNKING,
30
- ChunkingStrategyType.DOCUMENT_CHUNKING,
31
- ChunkingStrategyType.RECURSIVE_CHUNKING,
32
- ChunkingStrategyType.SEMANTIC_CHUNKING,
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(self, url: str, id: Optional[str] = None, name: Optional[str] = None) -> List[Document]:
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
- document = self._create_document(url, response.text, id, name)
48
- if self.chunk:
49
- return self.chunk_document(document)
50
- return [document]
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(self, url: str, id: Optional[str] = None, name: Optional[str] = None) -> List[Document]:
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
- document = self._create_document(url, response.text, id, name)
63
- if self.chunk:
64
- return await self.chunk_documents_async([document])
65
- return [document]
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 _create_document(
68
- self, url: str, content: str, id: Optional[str] = None, name: Optional[str] = None
69
- ) -> Document:
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
- doc_name = name or parsed_url.path.strip("/").replace("/", "_").replace(" ", "_")
73
- if not doc_name:
74
- doc_name = parsed_url.netloc
75
- if not doc_name:
76
- doc_name = url
77
-
78
- return Document(
79
- name=doc_name,
80
- id=id or doc_name,
81
- meta_data={"url": url},
82
- content=content,
83
- size=len(content),
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.AGENTIC_CHUNKING,
66
- ChunkingStrategyType.DOCUMENT_CHUNKING,
67
- ChunkingStrategyType.RECURSIVE_CHUNKING,
68
- ChunkingStrategyType.SEMANTIC_CHUNKING,
69
- ChunkingStrategyType.FIXED_SIZE_CHUNKING,
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.URL, ContentType.TEXT]
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.AGENTIC_CHUNKING,
56
- ChunkingStrategyType.DOCUMENT_CHUNKING,
57
- ChunkingStrategyType.RECURSIVE_CHUNKING,
58
- ChunkingStrategyType.SEMANTIC_CHUNKING,
59
- ChunkingStrategyType.FIXED_SIZE_CHUNKING,
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.FIXED_SIZE_CHUNKING,
30
- ChunkingStrategyType.AGENTIC_CHUNKING,
31
- ChunkingStrategyType.DOCUMENT_CHUNKING,
32
- ChunkingStrategyType.RECURSIVE_CHUNKING,
33
- ChunkingStrategyType.SEMANTIC_CHUNKING,
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.RECURSIVE_CHUNKING,
30
- ChunkingStrategyType.AGENTIC_CHUNKING,
31
- ChunkingStrategyType.DOCUMENT_CHUNKING,
32
- ChunkingStrategyType.SEMANTIC_CHUNKING,
33
- ChunkingStrategyType.FIXED_SIZE_CHUNKING,
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.URL, ContentType.YOUTUBE]
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.AGENTIC_CHUNKING: lambda: _import_class(
14
+ ChunkingStrategyType.AGENTIC_CHUNKER: lambda: _import_class(
15
15
  "agno.knowledge.chunking.agentic", "AgenticChunking"
16
16
  ),
17
- ChunkingStrategyType.DOCUMENT_CHUNKING: lambda: _import_class(
17
+ ChunkingStrategyType.DOCUMENT_CHUNKER: lambda: _import_class(
18
18
  "agno.knowledge.chunking.document", "DocumentChunking"
19
19
  ),
20
- ChunkingStrategyType.RECURSIVE_CHUNKING: lambda: _import_class(
20
+ ChunkingStrategyType.RECURSIVE_CHUNKER: lambda: _import_class(
21
21
  "agno.knowledge.chunking.recursive", "RecursiveChunking"
22
22
  ),
23
- ChunkingStrategyType.SEMANTIC_CHUNKING: lambda: _import_class(
23
+ ChunkingStrategyType.SEMANTIC_CHUNKER: lambda: _import_class(
24
24
  "agno.knowledge.chunking.semantic", "SemanticChunking"
25
25
  ),
26
- ChunkingStrategyType.FIXED_SIZE_CHUNKING: lambda: _import_class(
26
+ ChunkingStrategyType.FIXED_SIZE_CHUNKER: lambda: _import_class(
27
27
  "agno.knowledge.chunking.fixed", "FixedSizeChunking"
28
28
  ),
29
- ChunkingStrategyType.ROW_CHUNKING: lambda: _import_class("agno.knowledge.chunking.row", "RowChunking"),
30
- ChunkingStrategyType.MARKDOWN_CHUNKING: lambda: _import_class(
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.replace("_", " ").title() + " Reader",
65
- "description": f"Reads {reader_key} files",
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": class_name.replace("Chunking", "").replace("Strategy", ""),
135
+ "name": chunker_key,
136
136
  "description": docstring.strip(),
137
137
  "strategy_type": strategy_type.value,
138
138
  }
@@ -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
- param_type = param_info.get("type")
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
- "type": param_type or "string",
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, Optional[Dict[str, Any]]]:
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.updated_session_state
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, updated_session_state = result
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
- function_call_output = str(function_call.result)
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, success=function_call_success, output=function_call_output, timer=function_call_timer
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