agno 2.4.2__py3-none-any.whl → 2.4.4__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.
@@ -2,7 +2,7 @@ import asyncio
2
2
  import csv
3
3
  import io
4
4
  from pathlib import Path
5
- from typing import IO, Any, Iterable, List, Optional, Sequence, Tuple, Union
5
+ from typing import IO, Any, List, Optional, Union
6
6
 
7
7
  try:
8
8
  import aiofiles
@@ -12,12 +12,7 @@ except ImportError:
12
12
  from agno.knowledge.chunking.strategy import ChunkingStrategyType
13
13
  from agno.knowledge.document.base import Document
14
14
  from agno.knowledge.reader.base import Reader
15
- from agno.knowledge.reader.csv_reader import (
16
- _convert_xls_cell_value,
17
- _get_workbook_name,
18
- _infer_file_extension,
19
- _stringify_spreadsheet_cell_value,
20
- )
15
+ from agno.knowledge.reader.utils import stringify_cell_value
21
16
  from agno.knowledge.types import ContentType
22
17
  from agno.utils.log import log_debug, log_error, log_warning
23
18
 
@@ -47,7 +42,7 @@ class FieldLabeledCSVReader(Reader):
47
42
  @classmethod
48
43
  def get_supported_content_types(cls) -> List[ContentType]:
49
44
  """Get the list of supported content types."""
50
- return [ContentType.CSV, ContentType.XLSX, ContentType.XLS]
45
+ return [ContentType.CSV]
51
46
 
52
47
  def _format_field_name(self, field_name: str) -> str:
53
48
  """Format field name to be more readable."""
@@ -72,17 +67,7 @@ class FieldLabeledCSVReader(Reader):
72
67
  return None
73
68
 
74
69
  def _convert_row_to_labeled_text(self, headers: List[str], row: List[str], entry_index: int) -> str:
75
- """
76
- Convert a CSV row to field-labeled text format.
77
-
78
- Args:
79
- headers: Column headers
80
- row: Data row values
81
- entry_index: Index of this entry (for title rotation)
82
-
83
- Returns:
84
- Formatted text with field labels
85
- """
70
+ """Convert a CSV row to field-labeled text format."""
86
71
  lines = []
87
72
 
88
73
  title = self._get_title_for_entry(entry_index)
@@ -91,7 +76,7 @@ class FieldLabeledCSVReader(Reader):
91
76
 
92
77
  for i, (header, value) in enumerate(zip(headers, row)):
93
78
  # Normalize line endings before stripping to handle embedded newlines
94
- clean_value = _stringify_spreadsheet_cell_value(value).strip() if value else ""
79
+ clean_value = stringify_cell_value(value).strip() if value else ""
95
80
 
96
81
  if self.skip_empty_fields and not clean_value:
97
82
  continue
@@ -105,155 +90,11 @@ class FieldLabeledCSVReader(Reader):
105
90
 
106
91
  return "\n".join(lines)
107
92
 
108
- def _excel_rows_to_field_labeled_documents(
109
- self,
110
- *,
111
- workbook_name: str,
112
- sheets: Iterable[Tuple[str, Iterable[Sequence[Any]]]],
113
- ) -> List[Document]:
114
- """Convert Excel rows to field-labeled documents (one document per data row).
115
-
116
- For each sheet: first row = headers, subsequent rows = data.
117
- Each data row becomes a Document with field-labeled content.
118
- """
119
- documents = []
120
- global_row_index = 0
121
-
122
- for sheet_index, (sheet_name, rows) in enumerate(sheets, start=1):
123
- rows_list = list(rows)
124
-
125
- if not rows_list:
126
- log_debug(f"Sheet '{sheet_name}' is empty, skipping")
127
- continue
128
-
129
- # First row is headers
130
- headers = [_stringify_spreadsheet_cell_value(h).strip() for h in rows_list[0]]
131
- if not any(headers):
132
- log_debug(f"Sheet '{sheet_name}' has no valid headers, skipping")
133
- continue
134
-
135
- data_rows = rows_list[1:]
136
- if not data_rows:
137
- log_debug(f"Sheet '{sheet_name}' has only headers, skipping")
138
- continue
139
-
140
- log_debug(f"Processing sheet '{sheet_name}' with {len(headers)} headers and {len(data_rows)} rows")
141
-
142
- for row_in_sheet, row in enumerate(data_rows):
143
- # Convert cell values to strings
144
- str_row = [_stringify_spreadsheet_cell_value(v) for v in row]
145
-
146
- # Normalize row length
147
- normalized_row = str_row[: len(headers)]
148
- while len(normalized_row) < len(headers):
149
- normalized_row.append("")
150
-
151
- # Skip entirely empty rows
152
- if not any(v.strip() for v in normalized_row):
153
- continue
154
-
155
- labeled_text = self._convert_row_to_labeled_text(headers, normalized_row, global_row_index)
156
-
157
- if labeled_text.strip():
158
- doc_id = f"{workbook_name}_{sheet_name}_row_{row_in_sheet + 1}"
159
- documents.append(
160
- Document(
161
- id=doc_id,
162
- name=workbook_name,
163
- meta_data={
164
- "sheet_name": sheet_name,
165
- "sheet_index": sheet_index,
166
- "row_index": row_in_sheet,
167
- "headers": headers,
168
- "source": "field_labeled_csv_reader",
169
- },
170
- content=labeled_text,
171
- )
172
- )
173
- global_row_index += 1
174
-
175
- return documents
176
-
177
- def _read_xlsx(self, file: Union[Path, IO[Any]], *, workbook_name: str) -> List[Document]:
178
- """Read .xlsx file and convert rows to field-labeled documents."""
179
- try:
180
- import openpyxl # type: ignore
181
- except ImportError as e:
182
- raise ImportError(
183
- "`openpyxl` not installed. Please install it via `pip install agno[csv]` or `pip install openpyxl`."
184
- ) from e
185
-
186
- if isinstance(file, Path):
187
- workbook = openpyxl.load_workbook(filename=str(file), read_only=True, data_only=True)
188
- else:
189
- file.seek(0)
190
- raw = file.read()
191
- if isinstance(raw, str):
192
- raw = raw.encode("utf-8", errors="replace")
193
- workbook = openpyxl.load_workbook(filename=io.BytesIO(raw), read_only=True, data_only=True)
194
-
195
- try:
196
- return self._excel_rows_to_field_labeled_documents(
197
- workbook_name=workbook_name,
198
- sheets=[(worksheet.title, worksheet.iter_rows(values_only=True)) for worksheet in workbook.worksheets],
199
- )
200
- finally:
201
- workbook.close()
202
-
203
- def _read_xls(self, file: Union[Path, IO[Any]], *, workbook_name: str) -> List[Document]:
204
- """Read .xls file and convert rows to field-labeled documents."""
205
- try:
206
- import xlrd # type: ignore
207
- except ImportError as e:
208
- raise ImportError(
209
- "`xlrd` not installed. Please install it via `pip install agno[csv]` or `pip install xlrd`."
210
- ) from e
211
-
212
- if isinstance(file, Path):
213
- workbook = xlrd.open_workbook(filename=str(file))
214
- else:
215
- file.seek(0)
216
- raw = file.read()
217
- if isinstance(raw, str):
218
- raw = raw.encode("utf-8", errors="replace")
219
- workbook = xlrd.open_workbook(file_contents=raw)
220
-
221
- sheets: List[Tuple[str, Iterable[Sequence[Any]]]] = []
222
- for sheet_index in range(workbook.nsheets):
223
- sheet = workbook.sheet_by_index(sheet_index)
224
-
225
- def _iter_sheet_rows(_sheet: Any = sheet, _datemode: int = workbook.datemode) -> Iterable[Sequence[Any]]:
226
- for row_index in range(_sheet.nrows):
227
- yield [
228
- _convert_xls_cell_value(
229
- _sheet.cell_value(row_index, col_index),
230
- _sheet.cell_type(row_index, col_index),
231
- _datemode,
232
- )
233
- for col_index in range(_sheet.ncols)
234
- ]
235
-
236
- sheets.append((sheet.name, _iter_sheet_rows()))
237
-
238
- return self._excel_rows_to_field_labeled_documents(workbook_name=workbook_name, sheets=sheets)
239
-
240
93
  def read(
241
94
  self, file: Union[Path, IO[Any]], delimiter: str = ",", quotechar: str = '"', name: Optional[str] = None
242
95
  ) -> List[Document]:
96
+ """Read a CSV file and convert each row to a field-labeled document."""
243
97
  try:
244
- file_extension = _infer_file_extension(file, name)
245
-
246
- # Handle Excel files
247
- if file_extension in {ContentType.XLSX, ContentType.XLS}:
248
- workbook_name = _get_workbook_name(file, name)
249
- log_debug(f"Reading Excel file: {workbook_name}{file_extension}")
250
-
251
- if file_extension == ContentType.XLSX:
252
- return self._read_xlsx(file, workbook_name=workbook_name)
253
- else:
254
- return self._read_xls(file, workbook_name=workbook_name)
255
-
256
- # Handle CSV files
257
98
  if isinstance(file, Path):
258
99
  if not file.exists():
259
100
  raise FileNotFoundError(f"Could not find file: {file}")
@@ -318,6 +159,8 @@ class FieldLabeledCSVReader(Reader):
318
159
  log_debug(f"Successfully created {len(documents)} labeled documents from CSV")
319
160
  return documents
320
161
 
162
+ except FileNotFoundError:
163
+ raise
321
164
  except Exception as e:
322
165
  log_error(f"Error reading: {getattr(file, 'name', str(file)) if isinstance(file, IO) else file}: {e}")
323
166
  return []
@@ -330,20 +173,8 @@ class FieldLabeledCSVReader(Reader):
330
173
  page_size: int = 1000,
331
174
  name: Optional[str] = None,
332
175
  ) -> List[Document]:
176
+ """Read a CSV file asynchronously and convert each row to a field-labeled document."""
333
177
  try:
334
- file_extension = _infer_file_extension(file, name)
335
-
336
- # Handle Excel files (use asyncio.to_thread for sync openpyxl/xlrd)
337
- if file_extension in {ContentType.XLSX, ContentType.XLS}:
338
- workbook_name = _get_workbook_name(file, name)
339
- log_debug(f"Reading Excel file async: {workbook_name}{file_extension}")
340
-
341
- if file_extension == ContentType.XLSX:
342
- return await asyncio.to_thread(self._read_xlsx, file, workbook_name=workbook_name)
343
- else:
344
- return await asyncio.to_thread(self._read_xls, file, workbook_name=workbook_name)
345
-
346
- # Handle CSV files
347
178
  if isinstance(file, Path):
348
179
  if not file.exists():
349
180
  raise FileNotFoundError(f"Could not find file: {file}")
@@ -399,12 +230,13 @@ class FieldLabeledCSVReader(Reader):
399
230
  )
400
231
  documents.append(document)
401
232
  else:
233
+ # Large files: paginate and process in parallel
402
234
  pages = []
403
235
  for i in range(0, total_rows, page_size):
404
236
  pages.append(data_rows[i : i + page_size])
405
237
 
406
238
  async def _process_page(page_number: int, page_rows: List[List[str]]) -> List[Document]:
407
- """Process a page of rows into documents"""
239
+ """Process a page of rows into documents."""
408
240
  page_documents = []
409
241
  start_row_index = (page_number - 1) * page_size
410
242
 
@@ -443,6 +275,8 @@ class FieldLabeledCSVReader(Reader):
443
275
  log_debug(f"Successfully created {len(documents)} labeled documents from CSV")
444
276
  return documents
445
277
 
278
+ except FileNotFoundError:
279
+ raise
446
280
  except Exception as e:
447
281
  log_error(f"Error reading async: {getattr(file, 'name', str(file)) if isinstance(file, IO) else file}: {e}")
448
282
  return []
@@ -18,7 +18,11 @@ class ReaderFactory:
18
18
  },
19
19
  "csv": {
20
20
  "name": "CsvReader",
21
- "description": "Parses CSV, XLSX, and XLS files with custom delimiter support",
21
+ "description": "Parses CSV files with custom delimiter support",
22
+ },
23
+ "excel": {
24
+ "name": "ExcelReader",
25
+ "description": "Processes Excel workbooks (.xlsx and .xls) with sheet filtering and row-based chunking",
22
26
  },
23
27
  "field_labeled_csv": {
24
28
  "name": "FieldLabeledCsvReader",
@@ -93,11 +97,23 @@ class ReaderFactory:
93
97
 
94
98
  config: Dict[str, Any] = {
95
99
  "name": "CSV Reader",
96
- "description": "Parses CSV, XLSX, and XLS files with custom delimiter support",
100
+ "description": "Parses CSV files with custom delimiter support",
97
101
  }
98
102
  config.update(kwargs)
99
103
  return CSVReader(**config)
100
104
 
105
+ @classmethod
106
+ def _get_excel_reader(cls, **kwargs) -> Reader:
107
+ """Get Excel reader instance."""
108
+ from agno.knowledge.reader.excel_reader import ExcelReader
109
+
110
+ config: Dict[str, Any] = {
111
+ "name": "Excel Reader",
112
+ "description": "Processes Excel workbooks (.xlsx and .xls) with sheet filtering and row-based chunking",
113
+ }
114
+ config.update(kwargs)
115
+ return ExcelReader(**config)
116
+
101
117
  @classmethod
102
118
  def _get_field_labeled_csv_reader(cls, **kwargs) -> Reader:
103
119
  """Get Field Labeled CSV reader instance."""
@@ -288,6 +304,7 @@ class ReaderFactory:
288
304
  reader_class_map: Dict[str, tuple] = {
289
305
  "pdf": ("agno.knowledge.reader.pdf_reader", "PDFReader"),
290
306
  "csv": ("agno.knowledge.reader.csv_reader", "CSVReader"),
307
+ "excel": ("agno.knowledge.reader.excel_reader", "ExcelReader"),
291
308
  "field_labeled_csv": ("agno.knowledge.reader.field_labeled_csv_reader", "FieldLabeledCSVReader"),
292
309
  "docx": ("agno.knowledge.reader.docx_reader", "DocxReader"),
293
310
  "pptx": ("agno.knowledge.reader.pptx_reader", "PPTXReader"),
@@ -335,15 +352,15 @@ class ReaderFactory:
335
352
 
336
353
  if extension in [".pdf", "application/pdf"]:
337
354
  return cls.create_reader("pdf")
355
+ elif extension in [".csv", "text/csv"]:
356
+ return cls.create_reader("csv")
338
357
  elif extension in [
339
- ".csv",
340
358
  ".xlsx",
341
359
  ".xls",
342
- "text/csv",
343
360
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
344
361
  "application/vnd.ms-excel",
345
362
  ]:
346
- return cls.create_reader("csv")
363
+ return cls.create_reader("excel")
347
364
  elif extension in [".docx", ".doc", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"]:
348
365
  return cls.create_reader("docx")
349
366
  elif extension == ".pptx":
@@ -0,0 +1,17 @@
1
+ from agno.knowledge.reader.utils.spreadsheet import (
2
+ convert_xls_cell_value,
3
+ excel_rows_to_documents,
4
+ get_workbook_name,
5
+ infer_file_extension,
6
+ row_to_csv_line,
7
+ stringify_cell_value,
8
+ )
9
+
10
+ __all__ = [
11
+ "convert_xls_cell_value",
12
+ "excel_rows_to_documents",
13
+ "get_workbook_name",
14
+ "infer_file_extension",
15
+ "row_to_csv_line",
16
+ "stringify_cell_value",
17
+ ]
@@ -0,0 +1,114 @@
1
+ from datetime import date, datetime
2
+ from pathlib import Path
3
+ from typing import IO, Any, Iterable, List, Optional, Sequence, Tuple, Union
4
+ from uuid import uuid4
5
+
6
+ from agno.knowledge.document.base import Document
7
+ from agno.utils.log import log_debug
8
+
9
+
10
+ def stringify_cell_value(value: Any) -> str:
11
+ """Convert cell value to string, normalizing dates and line endings."""
12
+ if value is None:
13
+ return ""
14
+
15
+ if isinstance(value, datetime):
16
+ return value.isoformat()
17
+ if isinstance(value, date):
18
+ return value.isoformat()
19
+
20
+ if isinstance(value, float) and value.is_integer():
21
+ return str(int(value))
22
+
23
+ result = str(value)
24
+ # Normalize all line endings to space to preserve row integrity in CSV-like output
25
+ # Must handle CRLF first before individual CR/LF to avoid double-spacing
26
+ result = result.replace("\r\n", " ") # Windows (CRLF)
27
+ result = result.replace("\r", " ") # Old Mac (CR)
28
+ result = result.replace("\n", " ") # Unix (LF)
29
+ return result
30
+
31
+
32
+ def get_workbook_name(file: Union[Path, IO[Any]], name: Optional[str]) -> str:
33
+ """Extract workbook name from file path or name parameter."""
34
+ if name:
35
+ return Path(name).stem
36
+ if isinstance(file, Path):
37
+ return file.stem
38
+ # getattr returns None when attribute exists but is None, so check explicitly
39
+ file_name = getattr(file, "name", None)
40
+ if file_name:
41
+ return Path(file_name).stem
42
+ return "workbook"
43
+
44
+
45
+ def infer_file_extension(file: Union[Path, IO[Any]], name: Optional[str]) -> str:
46
+ """Infer file extension from Path, IO object, or explicit name."""
47
+ if isinstance(file, Path):
48
+ return file.suffix.lower()
49
+
50
+ file_name = getattr(file, "name", None)
51
+ if isinstance(file_name, str) and file_name:
52
+ return Path(file_name).suffix.lower()
53
+
54
+ if name:
55
+ return Path(name).suffix.lower()
56
+
57
+ return ""
58
+
59
+
60
+ def convert_xls_cell_value(cell_value: Any, cell_type: int, datemode: int) -> Any:
61
+ """Convert xlrd cell value to Python type (dates and booleans need conversion)."""
62
+ try:
63
+ import xlrd
64
+ except ImportError:
65
+ return cell_value
66
+
67
+ if cell_type == xlrd.XL_CELL_DATE:
68
+ try:
69
+ date_tuple = xlrd.xldate_as_tuple(cell_value, datemode)
70
+ return datetime(*date_tuple)
71
+ except Exception:
72
+ return cell_value
73
+ if cell_type == xlrd.XL_CELL_BOOLEAN:
74
+ return bool(cell_value)
75
+ return cell_value
76
+
77
+
78
+ def row_to_csv_line(row_values: Sequence[Any]) -> str:
79
+ """Convert row values to CSV-like string, trimming trailing empty cells."""
80
+ values = [stringify_cell_value(v) for v in row_values]
81
+ while values and values[-1] == "":
82
+ values.pop()
83
+
84
+ return ", ".join(values)
85
+
86
+
87
+ def excel_rows_to_documents(
88
+ *,
89
+ workbook_name: str,
90
+ sheets: Iterable[Tuple[str, int, Iterable[Sequence[Any]]]],
91
+ ) -> List[Document]:
92
+ """Convert Excel sheet rows to Documents (one per sheet)."""
93
+ documents = []
94
+ for sheet_name, sheet_index, rows in sheets:
95
+ lines = []
96
+ for row in rows:
97
+ line = row_to_csv_line(row)
98
+ if line:
99
+ lines.append(line)
100
+
101
+ if not lines:
102
+ log_debug(f"Sheet '{sheet_name}' is empty, skipping")
103
+ continue
104
+
105
+ documents.append(
106
+ Document(
107
+ name=workbook_name,
108
+ id=str(uuid4()),
109
+ meta_data={"sheet_name": sheet_name, "sheet_index": sheet_index},
110
+ content="\n".join(lines),
111
+ )
112
+ )
113
+
114
+ return documents
agno/models/base.py CHANGED
@@ -2109,6 +2109,7 @@ class Model(ABC):
2109
2109
  tool_name=fc.function.name,
2110
2110
  tool_args=fc.arguments,
2111
2111
  requires_confirmation=True,
2112
+ external_execution_silent=fc.function.external_execution_silent,
2112
2113
  )
2113
2114
  )
2114
2115
 
@@ -2128,6 +2129,7 @@ class Model(ABC):
2128
2129
  tool_args=fc.arguments,
2129
2130
  requires_user_input=True,
2130
2131
  user_input_schema=user_input_schema,
2132
+ external_execution_silent=fc.function.external_execution_silent,
2131
2133
  )
2132
2134
  )
2133
2135
 
@@ -2176,6 +2178,7 @@ class Model(ABC):
2176
2178
  tool_name=fc.function.name,
2177
2179
  tool_args=fc.arguments,
2178
2180
  external_execution_required=True,
2181
+ external_execution_silent=fc.function.external_execution_silent,
2179
2182
  )
2180
2183
  )
2181
2184
 
@@ -2270,6 +2273,7 @@ class Model(ABC):
2270
2273
  tool_name=fc.function.name,
2271
2274
  tool_args=fc.arguments,
2272
2275
  requires_confirmation=True,
2276
+ external_execution_silent=fc.function.external_execution_silent,
2273
2277
  )
2274
2278
  )
2275
2279
  # If the function requires user input, we yield a message to the user
@@ -2288,6 +2292,7 @@ class Model(ABC):
2288
2292
  tool_args=fc.arguments,
2289
2293
  requires_user_input=True,
2290
2294
  user_input_schema=user_input_schema,
2295
+ external_execution_silent=fc.function.external_execution_silent,
2291
2296
  )
2292
2297
  )
2293
2298
  # If the function is from the user control flow tools, we handle it here
@@ -2340,6 +2345,7 @@ class Model(ABC):
2340
2345
  tool_name=fc.function.name,
2341
2346
  tool_args=fc.arguments,
2342
2347
  external_execution_required=True,
2348
+ external_execution_silent=fc.function.external_execution_silent,
2343
2349
  )
2344
2350
  )
2345
2351
 
@@ -0,0 +1,3 @@
1
+ from agno.models.moonshot.moonshot import MoonShot
2
+
3
+ __all__ = ["MoonShot"]
@@ -0,0 +1,57 @@
1
+ from dataclasses import dataclass, field
2
+ from os import getenv
3
+ from typing import Any, Dict, Optional
4
+
5
+ from agno.exceptions import ModelAuthenticationError
6
+ from agno.models.openai.like import OpenAILike
7
+
8
+
9
+ @dataclass
10
+ class MoonShot(OpenAILike):
11
+ """
12
+ A class for interacting with MoonShot models.
13
+
14
+ Attributes:
15
+ id (str): The model id. Defaults to "kimi-k2-thinking".
16
+ name (str): The model name. Defaults to "Moonshot".
17
+ provider (str): The provider name. Defaults to "Moonshot".
18
+ api_key (Optional[str]): The API key.
19
+ base_url (str): The base URL. Defaults to "https://api.moonshot.ai/v1".
20
+ """
21
+
22
+ id: str = "kimi-k2-thinking"
23
+ name: str = "Moonshot"
24
+ provider: str = "Moonshot"
25
+
26
+ api_key: Optional[str] = field(default_factory=lambda: getenv("MOONSHOT_API_KEY"))
27
+ base_url: str = "https://api.moonshot.ai/v1"
28
+
29
+ def _get_client_params(self) -> Dict[str, Any]:
30
+ # Fetch API key from env if not already set
31
+ if not self.api_key:
32
+ self.api_key = getenv("MOONSHOT_API_KEY")
33
+ if not self.api_key:
34
+ # Raise error immediately if key is missing
35
+ raise ModelAuthenticationError(
36
+ message="MOONSHOT_API_KEY not set. Please set the MOONSHOT_API_KEY environment variable.",
37
+ model_name=self.name,
38
+ )
39
+
40
+ # Define base client params
41
+ base_params = {
42
+ "api_key": self.api_key,
43
+ "organization": self.organization,
44
+ "base_url": self.base_url,
45
+ "timeout": self.timeout,
46
+ "max_retries": self.max_retries,
47
+ "default_headers": self.default_headers,
48
+ "default_query": self.default_query,
49
+ }
50
+
51
+ # Create client_params dict with non-None values
52
+ client_params = {k: v for k, v in base_params.items() if v is not None}
53
+
54
+ # Add additional client params if provided
55
+ if self.client_params:
56
+ client_params.update(self.client_params)
57
+ return client_params
@@ -5,8 +5,8 @@ from typing import Any, Dict, List, Optional, Type, Union
5
5
  from pydantic import BaseModel
6
6
 
7
7
  from agno.exceptions import ModelAuthenticationError
8
- from agno.models.openai.open_responses import OpenResponses
9
8
  from agno.models.message import Message
9
+ from agno.models.openai.open_responses import OpenResponses
10
10
 
11
11
 
12
12
  @dataclass
@@ -143,4 +143,4 @@ class OpenRouterResponses(OpenResponses):
143
143
  # Check for OpenAI reasoning models hosted on OpenRouter
144
144
  if self.id.startswith("openai/o3") or self.id.startswith("openai/o4"):
145
145
  return True
146
- return False
146
+ return False
agno/models/response.py CHANGED
@@ -52,6 +52,9 @@ class ToolExecution:
52
52
 
53
53
  external_execution_required: Optional[bool] = None
54
54
 
55
+ # If True (and external_execution_required=True), suppresses verbose paused messages
56
+ external_execution_silent: Optional[bool] = None
57
+
55
58
  @property
56
59
  def is_paused(self) -> bool:
57
60
  return bool(self.requires_confirmation or self.requires_user_input or self.external_execution_required)
@@ -84,6 +87,7 @@ class ToolExecution:
84
87
  if "user_input_schema" in data
85
88
  else None,
86
89
  external_execution_required=data.get("external_execution_required"),
90
+ external_execution_silent=data.get("external_execution_silent"),
87
91
  metrics=Metrics(**(data.get("metrics", {}) or {})),
88
92
  **{"created_at": data["created_at"]} if "created_at" in data else {},
89
93
  )
agno/models/utils.py CHANGED
@@ -139,6 +139,11 @@ def _get_model_class(model_id: str, model_provider: str) -> Model:
139
139
 
140
140
  return MistralChat(id=model_id)
141
141
 
142
+ elif model_provider == "moonshot":
143
+ from agno.models.moonshot import MoonShot
144
+
145
+ return MoonShot(id=model_id)
146
+
142
147
  elif model_provider == "nebius":
143
148
  from agno.models.nebius import Nebius
144
149
 
@@ -989,9 +989,11 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Union[Knowledge,
989
989
  "text": ["web_search"],
990
990
  "topic": ["arxiv"],
991
991
  "file": ["csv", "gcs"],
992
- ".csv": ["csv"],
993
- ".xlsx": ["csv"],
994
- ".xls": ["csv"],
992
+ ".csv": ["csv", "field_labeled_csv"],
993
+ ".xlsx": ["excel"],
994
+ ".xls": ["excel"],
995
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ["excel"],
996
+ "application/vnd.ms-excel": ["excel"],
995
997
  ".docx": ["docx"],
996
998
  ".doc": ["docx"],
997
999
  ".json": ["json"],
agno/run/base.py CHANGED
@@ -246,6 +246,10 @@ class BaseRunOutputEvent:
246
246
  data["requirements"] = requirements_list if requirements_list else None
247
247
 
248
248
  # Filter data to only include fields that are actually defined in the target class
249
+ # CustomEvent accepts arbitrary fields, so skip filtering for it
250
+ if cls.__name__ == "CustomEvent":
251
+ return cls(**data)
252
+
249
253
  from dataclasses import fields
250
254
 
251
255
  supported_fields = {f.name for f in fields(cls)}
agno/tools/decorator.py CHANGED
@@ -70,6 +70,7 @@ def tool(
70
70
  requires_user_input: Optional[bool] = None,
71
71
  user_input_fields: Optional[List[str]] = None,
72
72
  external_execution: Optional[bool] = None,
73
+ external_execution_silent: Optional[bool] = None,
73
74
  pre_hook: Optional[Callable] = None,
74
75
  post_hook: Optional[Callable] = None,
75
76
  tool_hooks: Optional[List[Callable]] = None,
@@ -98,6 +99,7 @@ def tool(*args, **kwargs) -> Union[Function, Callable[[F], Function]]:
98
99
  requires_user_input: Optional[bool] - If True, the function will require user input before execution
99
100
  user_input_fields: Optional[List[str]] - List of fields that will be provided to the function as user input
100
101
  external_execution: Optional[bool] - If True, the function will be executed outside of the agent's context
102
+ external_execution_silent: Optional[bool] - If True (and external_execution=True), suppresses verbose paused messages (e.g., "I have tools to execute...")
101
103
  pre_hook: Optional[Callable] - Hook that runs before the function is executed.
102
104
  post_hook: Optional[Callable] - Hook that runs after the function is executed.
103
105
  tool_hooks: Optional[List[Callable]] - List of hooks that run before and after the function is executed.
@@ -135,6 +137,7 @@ def tool(*args, **kwargs) -> Union[Function, Callable[[F], Function]]:
135
137
  "requires_user_input",
136
138
  "user_input_fields",
137
139
  "external_execution",
140
+ "external_execution_silent",
138
141
  "pre_hook",
139
142
  "post_hook",
140
143
  "tool_hooks",