agno 2.2.0__py3-none-any.whl → 2.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
agno/agent/agent.py CHANGED
@@ -117,7 +117,7 @@ from agno.utils.log import (
117
117
  set_log_level_to_info,
118
118
  )
119
119
  from agno.utils.merge_dict import merge_dictionaries
120
- from agno.utils.message import get_text_from_message
120
+ from agno.utils.message import filter_tool_calls, get_text_from_message
121
121
  from agno.utils.print_response.agent import (
122
122
  aprint_response,
123
123
  aprint_response_stream,
@@ -203,6 +203,8 @@ class Agent:
203
203
  add_history_to_context: bool = False
204
204
  # Number of historical runs to include in the messages
205
205
  num_history_runs: int = 3
206
+ # Maximum number of tool calls to include from history (None = no limit)
207
+ max_tool_calls_from_history: Optional[int] = None
206
208
 
207
209
  # --- Knowledge ---
208
210
  knowledge: Optional[Knowledge] = None
@@ -419,6 +421,7 @@ class Agent:
419
421
  session_summary_manager: Optional[SessionSummaryManager] = None,
420
422
  add_history_to_context: bool = False,
421
423
  num_history_runs: int = 3,
424
+ max_tool_calls_from_history: Optional[int] = None,
422
425
  store_media: bool = True,
423
426
  store_tool_messages: bool = True,
424
427
  store_history_messages: bool = True,
@@ -520,6 +523,7 @@ class Agent:
520
523
 
521
524
  self.add_history_to_context = add_history_to_context
522
525
  self.num_history_runs = num_history_runs
526
+ self.max_tool_calls_from_history = max_tool_calls_from_history
523
527
 
524
528
  self.store_media = store_media
525
529
  self.store_tool_messages = store_tool_messages
@@ -1995,6 +1999,9 @@ class Agent:
1995
1999
  run_response=run_response,
1996
2000
  run_input=run_input,
1997
2001
  session=agent_session,
2002
+ session_state=session_state,
2003
+ dependencies=dependencies,
2004
+ metadata=metadata,
1998
2005
  user_id=user_id,
1999
2006
  debug_mode=debug_mode,
2000
2007
  **kwargs,
@@ -7321,6 +7328,10 @@ class Agent:
7321
7328
  for _msg in history_copy:
7322
7329
  _msg.from_history = True
7323
7330
 
7331
+ # Filter tool calls from history if limit is set (before adding to run_messages)
7332
+ if self.max_tool_calls_from_history is not None:
7333
+ filter_tool_calls(history_copy, self.max_tool_calls_from_history)
7334
+
7324
7335
  log_debug(f"Adding {len(history_copy)} messages from history")
7325
7336
 
7326
7337
  run_messages.messages += history_copy
@@ -7514,6 +7525,10 @@ class Agent:
7514
7525
  for _msg in history_copy:
7515
7526
  _msg.from_history = True
7516
7527
 
7528
+ # Filter tool calls from history if limit is set (before adding to run_messages)
7529
+ if self.max_tool_calls_from_history is not None:
7530
+ filter_tool_calls(history_copy, self.max_tool_calls_from_history)
7531
+
7517
7532
  log_debug(f"Adding {len(history_copy)} messages from history")
7518
7533
 
7519
7534
  run_messages.messages += history_copy
@@ -547,6 +547,8 @@ class Knowledge:
547
547
  reader = self.pdf_reader
548
548
  elif file_extension == ".docx":
549
549
  reader = self.docx_reader
550
+ elif file_extension == ".pptx":
551
+ reader = self.pptx_reader
550
552
  elif file_extension == ".json":
551
553
  reader = self.json_reader
552
554
  elif file_extension == ".markdown":
@@ -835,6 +837,8 @@ class Knowledge:
835
837
  reader = self.csv_reader
836
838
  elif s3_object.uri.endswith(".docx"):
837
839
  reader = self.docx_reader
840
+ elif s3_object.uri.endswith(".pptx"):
841
+ reader = self.pptx_reader
838
842
  elif s3_object.uri.endswith(".json"):
839
843
  reader = self.json_reader
840
844
  elif s3_object.uri.endswith(".markdown"):
@@ -917,6 +921,8 @@ class Knowledge:
917
921
  reader = self.csv_reader
918
922
  elif gcs_object.name.endswith(".docx"):
919
923
  reader = self.docx_reader
924
+ elif gcs_object.name.endswith(".pptx"):
925
+ reader = self.pptx_reader
920
926
  elif gcs_object.name.endswith(".json"):
921
927
  reader = self.json_reader
922
928
  elif gcs_object.name.endswith(".markdown"):
@@ -1893,6 +1899,11 @@ class Knowledge:
1893
1899
  """Docx reader - lazy loaded via factory."""
1894
1900
  return self._get_reader("docx")
1895
1901
 
1902
+ @property
1903
+ def pptx_reader(self) -> Optional[Reader]:
1904
+ """PPTX reader - lazy loaded via factory."""
1905
+ return self._get_reader("pptx")
1906
+
1896
1907
  @property
1897
1908
  def json_reader(self) -> Optional[Reader]:
1898
1909
  """JSON reader - lazy loaded via factory."""
@@ -0,0 +1,101 @@
1
+ import asyncio
2
+ from pathlib import Path
3
+ from typing import IO, Any, List, Optional, Union
4
+ from uuid import uuid4
5
+
6
+ from agno.knowledge.chunking.document import DocumentChunking
7
+ from agno.knowledge.chunking.strategy import ChunkingStrategy, ChunkingStrategyType
8
+ from agno.knowledge.document.base import Document
9
+ from agno.knowledge.reader.base import Reader
10
+ from agno.knowledge.types import ContentType
11
+ from agno.utils.log import log_info, logger
12
+
13
+ try:
14
+ from pptx import Presentation # type: ignore
15
+ except ImportError:
16
+ raise ImportError("The `python-pptx` package is not installed. Please install it via `pip install python-pptx`.")
17
+
18
+
19
+ class PPTXReader(Reader):
20
+ """Reader for PPTX files"""
21
+
22
+ def __init__(self, chunking_strategy: Optional[ChunkingStrategy] = DocumentChunking(), **kwargs):
23
+ super().__init__(chunking_strategy=chunking_strategy, **kwargs)
24
+
25
+ @classmethod
26
+ def get_supported_chunking_strategies(self) -> List[ChunkingStrategyType]:
27
+ """Get the list of supported chunking strategies for PPTX readers."""
28
+ return [
29
+ ChunkingStrategyType.DOCUMENT_CHUNKER,
30
+ ChunkingStrategyType.FIXED_SIZE_CHUNKER,
31
+ ChunkingStrategyType.SEMANTIC_CHUNKER,
32
+ ChunkingStrategyType.AGENTIC_CHUNKER,
33
+ ChunkingStrategyType.RECURSIVE_CHUNKER,
34
+ ]
35
+
36
+ @classmethod
37
+ def get_supported_content_types(self) -> List[ContentType]:
38
+ return [ContentType.PPTX]
39
+
40
+ def read(self, file: Union[Path, IO[Any]], name: Optional[str] = None) -> List[Document]:
41
+ """Read a pptx file and return a list of documents"""
42
+ try:
43
+ if isinstance(file, Path):
44
+ if not file.exists():
45
+ raise FileNotFoundError(f"Could not find file: {file}")
46
+ log_info(f"Reading: {file}")
47
+ presentation = Presentation(str(file))
48
+ doc_name = name or file.stem
49
+ else:
50
+ log_info(f"Reading uploaded file: {getattr(file, 'name', 'pptx_file')}")
51
+ presentation = Presentation(file)
52
+ doc_name = name or (
53
+ getattr(file, "name", "pptx_file").split(".")[0] if hasattr(file, "name") else "pptx_file"
54
+ )
55
+
56
+ # Extract text from all slides
57
+ slide_texts = []
58
+ for slide_number, slide in enumerate(presentation.slides, 1):
59
+ slide_text = f"Slide {slide_number}:\n"
60
+
61
+ # Extract text from shapes that contain text
62
+ text_content = []
63
+ for shape in slide.shapes:
64
+ if hasattr(shape, "text") and shape.text.strip():
65
+ text_content.append(shape.text.strip())
66
+
67
+ if text_content:
68
+ slide_text += "\n".join(text_content)
69
+ else:
70
+ slide_text += "(No text content)"
71
+
72
+ slide_texts.append(slide_text)
73
+
74
+ doc_content = "\n\n".join(slide_texts)
75
+
76
+ documents = [
77
+ Document(
78
+ name=doc_name,
79
+ id=str(uuid4()),
80
+ content=doc_content,
81
+ )
82
+ ]
83
+
84
+ if self.chunk:
85
+ chunked_documents = []
86
+ for document in documents:
87
+ chunked_documents.extend(self.chunk_document(document))
88
+ return chunked_documents
89
+ return documents
90
+
91
+ except Exception as e:
92
+ logger.error(f"Error reading file: {e}")
93
+ return []
94
+
95
+ async def async_read(self, file: Union[Path, IO[Any]], name: Optional[str] = None) -> List[Document]:
96
+ """Asynchronously read a pptx file and return a list of documents"""
97
+ try:
98
+ return await asyncio.to_thread(self.read, file, name)
99
+ except Exception as e:
100
+ logger.error(f"Error reading file asynchronously: {e}")
101
+ return []
@@ -58,6 +58,18 @@ class ReaderFactory:
58
58
  config.update(kwargs)
59
59
  return DocxReader(**config)
60
60
 
61
+ @classmethod
62
+ def _get_pptx_reader(cls, **kwargs) -> Reader:
63
+ """Get PPTX reader instance."""
64
+ from agno.knowledge.reader.pptx_reader import PPTXReader
65
+
66
+ config: Dict[str, Any] = {
67
+ "name": "PPTX Reader",
68
+ "description": "Extracts text content from Microsoft PowerPoint presentations (.pptx format)",
69
+ }
70
+ config.update(kwargs)
71
+ return PPTXReader(**config)
72
+
61
73
  @classmethod
62
74
  def _get_json_reader(cls, **kwargs) -> Reader:
63
75
  """Get JSON reader instance."""
@@ -202,6 +214,8 @@ class ReaderFactory:
202
214
  return cls.create_reader("csv")
203
215
  elif extension in [".docx", ".doc", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"]:
204
216
  return cls.create_reader("docx")
217
+ elif extension == ".pptx":
218
+ return cls.create_reader("pptx")
205
219
  elif extension == ".json":
206
220
  return cls.create_reader("json")
207
221
  elif extension in [".md", ".markdown"]:
agno/knowledge/types.py CHANGED
@@ -20,6 +20,7 @@ class ContentType(str, Enum):
20
20
  MARKDOWN = ".md"
21
21
  DOCX = ".docx"
22
22
  DOC = ".doc"
23
+ PPTX = ".pptx"
23
24
  JSON = ".json"
24
25
 
25
26
  # Spreadsheet file extensions
agno/models/base.py CHANGED
@@ -1237,8 +1237,8 @@ class Model(ABC):
1237
1237
  if function_call.function.show_result:
1238
1238
  yield ModelResponse(content=item.content)
1239
1239
 
1240
- if isinstance(item, CustomEvent):
1241
- function_call_output += str(item)
1240
+ if isinstance(item, CustomEvent):
1241
+ function_call_output += str(item)
1242
1242
 
1243
1243
  # Yield the event itself to bubble it up
1244
1244
  yield item
@@ -1626,8 +1626,8 @@ class Model(ABC):
1626
1626
  await event_queue.put(ModelResponse(content=item.content))
1627
1627
  continue
1628
1628
 
1629
- if isinstance(item, CustomEvent):
1630
- function_call_output += str(item)
1629
+ if isinstance(item, CustomEvent):
1630
+ function_call_output += str(item)
1631
1631
 
1632
1632
  # Put the event into the queue to be yielded
1633
1633
  await event_queue.put(item)
agno/run/agent.py CHANGED
@@ -385,6 +385,11 @@ class OutputModelResponseCompletedEvent(BaseAgentRunEvent):
385
385
  class CustomEvent(BaseAgentRunEvent):
386
386
  event: str = RunEvent.custom_event.value
387
387
 
388
+ def __init__(self, **kwargs):
389
+ # Store arbitrary attributes directly on the instance
390
+ for key, value in kwargs.items():
391
+ setattr(self, key, value)
392
+
388
393
 
389
394
  RunOutputEvent = Union[
390
395
  RunStartedEvent,
agno/run/team.py CHANGED
@@ -368,6 +368,11 @@ class OutputModelResponseCompletedEvent(BaseTeamRunEvent):
368
368
  class CustomEvent(BaseTeamRunEvent):
369
369
  event: str = TeamRunEvent.custom_event.value
370
370
 
371
+ def __init__(self, **kwargs):
372
+ # Store arbitrary attributes directly on the instance
373
+ for key, value in kwargs.items():
374
+ setattr(self, key, value)
375
+
371
376
 
372
377
  TeamRunOutputEvent = Union[
373
378
  RunStartedEvent,
agno/run/workflow.py CHANGED
@@ -6,9 +6,9 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
6
6
  from pydantic import BaseModel
7
7
 
8
8
  from agno.media import Audio, Image, Video
9
- from agno.run.agent import RunOutput
9
+ from agno.run.agent import RunEvent, RunOutput, run_output_event_from_dict
10
10
  from agno.run.base import BaseRunOutputEvent, RunStatus
11
- from agno.run.team import TeamRunOutput
11
+ from agno.run.team import TeamRunEvent, TeamRunOutput, team_run_output_event_from_dict
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from agno.workflow.types import StepOutput, WorkflowMetrics
@@ -388,6 +388,11 @@ class CustomEvent(BaseWorkflowRunOutputEvent):
388
388
 
389
389
  event: str = WorkflowRunEvent.custom_event.value
390
390
 
391
+ def __init__(self, **kwargs):
392
+ # Store arbitrary attributes directly on the instance
393
+ for key, value in kwargs.items():
394
+ setattr(self, key, value)
395
+
391
396
 
392
397
  # Union type for all workflow run response events
393
398
  WorkflowRunOutputEvent = Union[
@@ -442,10 +447,15 @@ WORKFLOW_RUN_EVENT_TYPE_REGISTRY = {
442
447
 
443
448
  def workflow_run_output_event_from_dict(data: dict) -> BaseWorkflowRunOutputEvent:
444
449
  event_type = data.get("event", "")
445
- cls = WORKFLOW_RUN_EVENT_TYPE_REGISTRY.get(event_type)
446
- if not cls:
450
+ if event_type in {e.value for e in RunEvent}:
451
+ return run_output_event_from_dict(data) # type: ignore
452
+ elif event_type in {e.value for e in TeamRunEvent}:
453
+ return team_run_output_event_from_dict(data) # type: ignore
454
+ else:
455
+ event_class = WORKFLOW_RUN_EVENT_TYPE_REGISTRY.get(event_type)
456
+ if not event_class:
447
457
  raise ValueError(f"Unknown workflow event type: {event_type}")
448
- return cls.from_dict(data) # type: ignore
458
+ return event_class.from_dict(data) # type: ignore
449
459
 
450
460
 
451
461
  @dataclass
agno/team/team.py CHANGED
@@ -111,7 +111,7 @@ from agno.utils.log import (
111
111
  use_team_logger,
112
112
  )
113
113
  from agno.utils.merge_dict import merge_dictionaries
114
- from agno.utils.message import get_text_from_message
114
+ from agno.utils.message import filter_tool_calls, get_text_from_message
115
115
  from agno.utils.print_response.team import (
116
116
  aprint_response,
117
117
  aprint_response_stream,
@@ -189,12 +189,6 @@ class Team:
189
189
  # If True, cache the current Team session in memory for faster access
190
190
  cache_session: bool = False
191
191
 
192
- # --- Team history settings ---
193
- # add_history_to_context=true adds messages from the chat history to the messages list sent to the Model. This only applies to the team leader, not the members.
194
- add_history_to_context: bool = False
195
- # Number of historical runs to include in the messages
196
- num_history_runs: int = 3
197
-
198
192
  # Add this flag to control if the workflow should send the team history to the members. This means sending the team-level history to the members, not the agent-level history.
199
193
  add_team_history_to_members: bool = False
200
194
  # Number of historical runs to include in the messages sent to the members
@@ -354,6 +348,14 @@ class Team:
354
348
  # If True, the team adds session summaries to the context
355
349
  add_session_summary_to_context: Optional[bool] = None
356
350
 
351
+ # --- Team History ---
352
+ # add_history_to_context=true adds messages from the chat history to the messages list sent to the Model.
353
+ add_history_to_context: bool = False
354
+ # Number of historical runs to include in the messages
355
+ num_history_runs: int = 3
356
+ # Maximum number of tool calls to include from history (None = no limit)
357
+ max_tool_calls_from_history: Optional[int] = None
358
+
357
359
  # --- Team Storage ---
358
360
  # Metadata stored with this team
359
361
  metadata: Optional[Dict[str, Any]] = None
@@ -421,8 +423,6 @@ class Team:
421
423
  overwrite_db_session_state: bool = False,
422
424
  resolve_in_context: bool = True,
423
425
  cache_session: bool = False,
424
- add_history_to_context: bool = False,
425
- num_history_runs: int = 3,
426
426
  add_team_history_to_members: bool = False,
427
427
  num_team_history_runs: int = 3,
428
428
  search_session_history: Optional[bool] = False,
@@ -458,6 +458,8 @@ class Team:
458
458
  store_tool_messages: bool = True,
459
459
  store_history_messages: bool = True,
460
460
  send_media_to_model: bool = True,
461
+ add_history_to_context: bool = False,
462
+ num_history_runs: int = 3,
461
463
  tools: Optional[List[Union[Toolkit, Callable, Function, Dict]]] = None,
462
464
  tool_call_limit: Optional[int] = None,
463
465
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
@@ -480,6 +482,7 @@ class Team:
480
482
  enable_session_summaries: bool = False,
481
483
  session_summary_manager: Optional[SessionSummaryManager] = None,
482
484
  add_session_summary_to_context: Optional[bool] = None,
485
+ max_tool_calls_from_history: Optional[int] = None,
483
486
  metadata: Optional[Dict[str, Any]] = None,
484
487
  reasoning: bool = False,
485
488
  reasoning_model: Optional[Model] = None,
@@ -591,6 +594,9 @@ class Team:
591
594
  self.enable_session_summaries = enable_session_summaries
592
595
  self.session_summary_manager = session_summary_manager
593
596
  self.add_session_summary_to_context = add_session_summary_to_context
597
+ self.add_history_to_context = add_history_to_context
598
+ self.num_history_runs = num_history_runs
599
+ self.max_tool_calls_from_history = max_tool_calls_from_history
594
600
  self.metadata = metadata
595
601
 
596
602
  self.reasoning = reasoning
@@ -1320,6 +1326,9 @@ class Team:
1320
1326
  hooks=self.post_hooks, # type: ignore
1321
1327
  run_output=run_response,
1322
1328
  session=session,
1329
+ session_state=session_state,
1330
+ dependencies=dependencies,
1331
+ metadata=metadata,
1323
1332
  user_id=user_id,
1324
1333
  debug_mode=debug_mode,
1325
1334
  **kwargs,
@@ -2133,6 +2142,9 @@ class Team:
2133
2142
  hooks=self.post_hooks, # type: ignore
2134
2143
  run_output=run_response,
2135
2144
  session=team_session,
2145
+ session_state=session_state,
2146
+ dependencies=dependencies,
2147
+ metadata=metadata,
2136
2148
  user_id=user_id,
2137
2149
  debug_mode=debug_mode,
2138
2150
  **kwargs,
@@ -5824,6 +5836,10 @@ class Team:
5824
5836
  for _msg in history_copy:
5825
5837
  _msg.from_history = True
5826
5838
 
5839
+ # Filter tool calls from history messages
5840
+ if self.max_tool_calls_from_history is not None:
5841
+ filter_tool_calls(history_copy, self.max_tool_calls_from_history)
5842
+
5827
5843
  log_debug(f"Adding {len(history_copy)} messages from history")
5828
5844
 
5829
5845
  # Extend the messages with the history
@@ -5951,6 +5967,10 @@ class Team:
5951
5967
  for _msg in history_copy:
5952
5968
  _msg.from_history = True
5953
5969
 
5970
+ # Filter tool calls from history messages
5971
+ if self.max_tool_calls_from_history is not None:
5972
+ filter_tool_calls(history_copy, self.max_tool_calls_from_history)
5973
+
5954
5974
  log_debug(f"Adding {len(history_copy)} messages from history")
5955
5975
 
5956
5976
  # Extend the messages with the history
agno/utils/message.py CHANGED
@@ -1,8 +1,68 @@
1
+ from copy import deepcopy
1
2
  from typing import Dict, List, Union
2
3
 
3
4
  from pydantic import BaseModel
4
5
 
5
6
  from agno.models.message import Message
7
+ from agno.utils.log import log_debug
8
+
9
+
10
+ def filter_tool_calls(messages: List[Message], max_tool_calls: int) -> None:
11
+ """
12
+ Filter messages (in-place) to keep only the most recent N tool calls.
13
+
14
+ Args:
15
+ messages: List of messages to filter (modified in-place)
16
+ max_tool_calls: Number of recent tool calls to keep
17
+ """
18
+ # Count total tool calls
19
+ tool_call_count = sum(1 for m in messages if m.role == "tool")
20
+
21
+ # No filtering needed
22
+ if tool_call_count <= max_tool_calls:
23
+ return
24
+
25
+ # Collect tool_call_ids to keep (most recent N)
26
+ tool_call_ids_list: List[str] = []
27
+ for msg in reversed(messages):
28
+ if msg.role == "tool" and len(tool_call_ids_list) < max_tool_calls:
29
+ if msg.tool_call_id:
30
+ tool_call_ids_list.append(msg.tool_call_id)
31
+
32
+ tool_call_ids_to_keep: set[str] = set(tool_call_ids_list)
33
+
34
+ # Filter messages in-place
35
+ filtered_messages = []
36
+ for msg in messages:
37
+ if msg.role == "tool":
38
+ # Keep only tool results in our window
39
+ if msg.tool_call_id in tool_call_ids_to_keep:
40
+ filtered_messages.append(msg)
41
+ elif msg.role == "assistant" and msg.tool_calls:
42
+ # Filter tool_calls within the assistant message
43
+ # Use deepcopy to ensure complete isolation of the filtered message
44
+ filtered_msg = deepcopy(msg)
45
+ # Filter tool_calls
46
+ if filtered_msg.tool_calls is not None:
47
+ filtered_msg.tool_calls = [
48
+ tc for tc in filtered_msg.tool_calls if tc.get("id") in tool_call_ids_to_keep
49
+ ]
50
+
51
+ if filtered_msg.tool_calls:
52
+ # Has tool_calls remaining, keep it
53
+ filtered_messages.append(filtered_msg)
54
+ # skip empty messages
55
+ elif filtered_msg.content:
56
+ filtered_msg.tool_calls = None
57
+ filtered_messages.append(filtered_msg)
58
+ else:
59
+ filtered_messages.append(msg)
60
+
61
+ messages[:] = filtered_messages
62
+
63
+ # Log filtering information
64
+ num_filtered = tool_call_count - len(tool_call_ids_to_keep)
65
+ log_debug(f"Filtered {num_filtered} tool calls, kept {len(tool_call_ids_to_keep)}")
6
66
 
7
67
 
8
68
  def get_text_from_message(message: Union[List, Dict, str, Message, BaseModel]) -> str:
agno/workflow/workflow.py CHANGED
@@ -29,7 +29,7 @@ from agno.exceptions import InputCheckError, OutputCheckError, RunCancelledExcep
29
29
  from agno.media import Audio, File, Image, Video
30
30
  from agno.models.message import Message
31
31
  from agno.models.metrics import Metrics
32
- from agno.run.agent import RunEvent
32
+ from agno.run.agent import RunContentEvent, RunEvent
33
33
  from agno.run.base import RunStatus
34
34
  from agno.run.cancel import (
35
35
  cancel_run as cancel_run_global,
@@ -39,6 +39,7 @@ from agno.run.cancel import (
39
39
  raise_if_cancelled,
40
40
  register_run,
41
41
  )
42
+ from agno.run.team import RunContentEvent as TeamRunContentEvent
42
43
  from agno.run.team import TeamRunEvent
43
44
  from agno.run.workflow import (
44
45
  StepOutputEvent,
@@ -1177,6 +1178,15 @@ class Workflow:
1177
1178
  logger.error(f"Function signature inspection failed: {e}. Falling back to original calling convention.")
1178
1179
  return func(**kwargs)
1179
1180
 
1181
+ def _accumulate_partial_step_data(
1182
+ self, event: Union[RunContentEvent, TeamRunContentEvent], partial_step_content: str
1183
+ ) -> str:
1184
+ """Accumulate partial step data from streaming events"""
1185
+ if isinstance(event, (RunContentEvent, TeamRunContentEvent)) and event.content:
1186
+ if isinstance(event.content, str):
1187
+ partial_step_content += event.content
1188
+ return partial_step_content
1189
+
1180
1190
  def _execute(
1181
1191
  self,
1182
1192
  session: WorkflowSession,
@@ -1400,11 +1410,22 @@ class Workflow:
1400
1410
 
1401
1411
  early_termination = False
1402
1412
 
1413
+ # Track partial step data in case of cancellation
1414
+ current_step_name = ""
1415
+ current_step = None
1416
+ partial_step_content = ""
1417
+
1403
1418
  for i, step in enumerate(self.steps): # type: ignore[arg-type]
1404
1419
  raise_if_cancelled(workflow_run_response.run_id) # type: ignore
1405
1420
  step_name = getattr(step, "name", f"step_{i + 1}")
1406
1421
  log_debug(f"Streaming step {i + 1}/{self._get_step_count()}: {step_name}")
1407
1422
 
1423
+ # Track current step for cancellation handler
1424
+ current_step_name = step_name
1425
+ current_step = step
1426
+ # Reset partial data for this step
1427
+ partial_step_content = ""
1428
+
1408
1429
  # Create enhanced StepInput
1409
1430
  step_input = self._create_step_input(
1410
1431
  execution_input=execution_input,
@@ -1433,6 +1454,10 @@ class Workflow:
1433
1454
  num_history_runs=self.num_history_runs,
1434
1455
  ):
1435
1456
  raise_if_cancelled(workflow_run_response.run_id) # type: ignore
1457
+
1458
+ # Accumulate partial data from streaming events
1459
+ partial_step_content = self._accumulate_partial_step_data(event, partial_step_content) # type: ignore
1460
+
1436
1461
  # Handle events
1437
1462
  if isinstance(event, StepOutput):
1438
1463
  step_output = event
@@ -1547,6 +1572,29 @@ class Workflow:
1547
1572
  logger.info(f"Workflow run {workflow_run_response.run_id} was cancelled during streaming")
1548
1573
  workflow_run_response.status = RunStatus.cancelled
1549
1574
  workflow_run_response.content = str(e)
1575
+
1576
+ # Capture partial progress from the step that was cancelled mid-stream
1577
+ if partial_step_content:
1578
+ logger.info(
1579
+ f"Step with name '{current_step_name}' was cancelled. Setting its partial progress as step output."
1580
+ )
1581
+ partial_step_output = StepOutput(
1582
+ step_name=current_step_name,
1583
+ step_id=getattr(current_step, "step_id", None) if current_step else None,
1584
+ step_type=StepType.STEP,
1585
+ executor_type=getattr(current_step, "executor_type", None) if current_step else None,
1586
+ executor_name=getattr(current_step, "executor_name", None) if current_step else None,
1587
+ content=partial_step_content,
1588
+ success=False,
1589
+ error="Cancelled during execution",
1590
+ )
1591
+ collected_step_outputs.append(partial_step_output)
1592
+
1593
+ # Preserve all progress (completed steps + partial step) before cancellation
1594
+ if collected_step_outputs:
1595
+ workflow_run_response.step_results = collected_step_outputs
1596
+ workflow_run_response.metrics = self._aggregate_workflow_metrics(collected_step_outputs)
1597
+
1550
1598
  cancelled_event = WorkflowCancelledEvent(
1551
1599
  run_id=workflow_run_response.run_id or "",
1552
1600
  workflow_id=self.id,
@@ -1922,12 +1970,22 @@ class Workflow:
1922
1970
 
1923
1971
  early_termination = False
1924
1972
 
1973
+ # Track partial step data in case of cancellation
1974
+ current_step_name = ""
1975
+ current_step = None
1976
+ partial_step_content = ""
1977
+
1925
1978
  for i, step in enumerate(self.steps): # type: ignore[arg-type]
1926
1979
  if workflow_run_response.run_id:
1927
1980
  raise_if_cancelled(workflow_run_response.run_id)
1928
1981
  step_name = getattr(step, "name", f"step_{i + 1}")
1929
1982
  log_debug(f"Async streaming step {i + 1}/{self._get_step_count()}: {step_name}")
1930
1983
 
1984
+ current_step_name = step_name
1985
+ current_step = step
1986
+ # Reset partial data for this step
1987
+ partial_step_content = ""
1988
+
1931
1989
  # Create enhanced StepInput
1932
1990
  step_input = self._create_step_input(
1933
1991
  execution_input=execution_input,
@@ -1957,6 +2015,10 @@ class Workflow:
1957
2015
  ):
1958
2016
  if workflow_run_response.run_id:
1959
2017
  raise_if_cancelled(workflow_run_response.run_id)
2018
+
2019
+ # Accumulate partial data from streaming events
2020
+ partial_step_content = self._accumulate_partial_step_data(event, partial_step_content) # type: ignore
2021
+
1960
2022
  if isinstance(event, StepOutput):
1961
2023
  step_output = event
1962
2024
  collected_step_outputs.append(step_output)
@@ -2073,6 +2135,29 @@ class Workflow:
2073
2135
  logger.info(f"Workflow run {workflow_run_response.run_id} was cancelled during streaming")
2074
2136
  workflow_run_response.status = RunStatus.cancelled
2075
2137
  workflow_run_response.content = str(e)
2138
+
2139
+ # Capture partial progress from the step that was cancelled mid-stream
2140
+ if partial_step_content:
2141
+ logger.info(
2142
+ f"Step with name '{current_step_name}' was cancelled. Setting its partial progress as step output."
2143
+ )
2144
+ partial_step_output = StepOutput(
2145
+ step_name=current_step_name,
2146
+ step_id=getattr(current_step, "step_id", None) if current_step else None,
2147
+ step_type=StepType.STEP,
2148
+ executor_type=getattr(current_step, "executor_type", None) if current_step else None,
2149
+ executor_name=getattr(current_step, "executor_name", None) if current_step else None,
2150
+ content=partial_step_content,
2151
+ success=False,
2152
+ error="Cancelled during execution",
2153
+ )
2154
+ collected_step_outputs.append(partial_step_output)
2155
+
2156
+ # Preserve all progress (completed steps + partial step) before cancellation
2157
+ if collected_step_outputs:
2158
+ workflow_run_response.step_results = collected_step_outputs
2159
+ workflow_run_response.metrics = self._aggregate_workflow_metrics(collected_step_outputs)
2160
+
2076
2161
  cancelled_event = WorkflowCancelledEvent(
2077
2162
  run_id=workflow_run_response.run_id or "",
2078
2163
  workflow_id=self.id,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agno
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: Agno: a lightweight library for building Multi-Agent Systems
5
5
  Author-email: Ashpreet Bedi <ashpreet@agno.com>
6
6
  Project-URL: homepage, https://agno.com
@@ -238,6 +238,8 @@ Requires-Dist: pypdf; extra == "pdf"
238
238
  Requires-Dist: rapidocr_onnxruntime; extra == "pdf"
239
239
  Provides-Extra: docx
240
240
  Requires-Dist: python-docx; extra == "docx"
241
+ Provides-Extra: pptx
242
+ Requires-Dist: python-pptx; extra == "pptx"
241
243
  Provides-Extra: text
242
244
  Requires-Dist: aiofiles; extra == "text"
243
245
  Provides-Extra: csv
@@ -346,6 +348,7 @@ Requires-Dist: agno[upstash]; extra == "vectordbs"
346
348
  Provides-Extra: knowledge
347
349
  Requires-Dist: agno[pdf]; extra == "knowledge"
348
350
  Requires-Dist: agno[docx]; extra == "knowledge"
351
+ Requires-Dist: agno[pptx]; extra == "knowledge"
349
352
  Requires-Dist: agno[text]; extra == "knowledge"
350
353
  Requires-Dist: agno[csv]; extra == "knowledge"
351
354
  Requires-Dist: agno[markdown]; extra == "knowledge"
@@ -4,7 +4,7 @@ agno/exceptions.py,sha256=7xqLur8sWHugnViIJz4PvPKSHljSiVKNAqaKQOJgZiU,4982
4
4
  agno/media.py,sha256=eTfYb_pwhX_PCIVPSrW4VYRqmoxKABEF1aZClrVvQ30,16500
5
5
  agno/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  agno/agent/__init__.py,sha256=s7S3FgsjZxuaabzi8L5n4aSH8IZAiZ7XaNNcySGR-EQ,1051
7
- agno/agent/agent.py,sha256=Qmxp1_fmOWt4qpO9oTDBCmiYF1IIR6NSjUN4LPM8pLk,448928
7
+ agno/agent/agent.py,sha256=YcP5yKxsxLb4KoxNr7H4SDGu27dwtqsXQ9clDzZl3bw,449829
8
8
  agno/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  agno/api/agent.py,sha256=fKlQ62E_C9Rjd7Zus3Gs3R1RG-IhzFV-ICpkb6SLqYc,932
10
10
  agno/api/api.py,sha256=Z7iWbrjheJcGLeeDYrtTCWiKTVqjH0uJI35UNWOtAXw,973
@@ -106,8 +106,8 @@ agno/integrations/discord/__init__.py,sha256=MS08QSnegGgpDZd9LyLjK4pWIk9dXlbzgD7
106
106
  agno/integrations/discord/client.py,sha256=2IWkA-kCDsDXByKOGq2QJG6MtFsbouzNN105rsXn95A,8375
107
107
  agno/knowledge/__init__.py,sha256=PJCt-AGKGFztzR--Ok2TNKW5QEqllZzqiI_7f8-1u80,79
108
108
  agno/knowledge/content.py,sha256=q2bjcjDhfge_UrQAcygrv5R9ZTk7vozzKnQpatDQWRo,2295
109
- agno/knowledge/knowledge.py,sha256=P3-aEys4nYbLWs-vCoMQ1vf5A3s6wiKNp4yqscH-wGg,78103
110
- agno/knowledge/types.py,sha256=ciwDLK9MXwi_W_g4nUChEmK6meDQyqTqGK2Ad-wM1os,754
109
+ agno/knowledge/knowledge.py,sha256=-VhXpz2SuKysPiANF6lff3v61P5HJKr-HKc17WwBOC0,78546
110
+ agno/knowledge/types.py,sha256=4NnkL_h2W-7GQnHW-yIqMNPUCWLzo5qBXF99gfsMe08,773
111
111
  agno/knowledge/utils.py,sha256=_uhEFtz4p7-cIKffdj5UZ__c-u96rs2UWP6dv5HpOMk,6490
112
112
  agno/knowledge/chunking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
113
113
  agno/knowledge/chunking/agentic.py,sha256=OksjCXySqu0vRseQsFgsOdfD0lXqXHP_SGJ6G8Tecbc,3059
@@ -148,7 +148,8 @@ agno/knowledge/reader/firecrawl_reader.py,sha256=SCbcRLkMrUb2WiC6SPuAoZULtHITa2V
148
148
  agno/knowledge/reader/json_reader.py,sha256=KfDgHno_XHORIGaoA1Q2Y5M7nkcTFM9IUzWXkjVjYKQ,3245
149
149
  agno/knowledge/reader/markdown_reader.py,sha256=HoSEkX2Svl2tEMDyzAmRNmwxsterb3i3-1-EQR6N4IU,5272
150
150
  agno/knowledge/reader/pdf_reader.py,sha256=F1Zx5i5jTUbuuKEPEnlnzMgCAweJMehYY0igRFHuHf4,17206
151
- agno/knowledge/reader/reader_factory.py,sha256=4rQ2MbdCdDOquFJWMi_o0eYzxsaGSTDcu6dQez1LHGs,10275
151
+ agno/knowledge/reader/pptx_reader.py,sha256=q0VAszrLry1ZhTOYXFRt6TVMAw12pAK64frYRvMNACo,3929
152
+ agno/knowledge/reader/reader_factory.py,sha256=18H8FQHIZf4SECZls1AwpMAzrS1FSOEuTkgJqbv0-HE,10783
152
153
  agno/knowledge/reader/s3_reader.py,sha256=j_xBDWV9l5Uhsr88glQ0dCMS-ijvqw8gdMc1Bzfm394,3547
153
154
  agno/knowledge/reader/text_reader.py,sha256=Vkk3mWhjyazPv77Z9PGO1nRVk8cmVkUFIWZFCVqTkOI,4444
154
155
  agno/knowledge/reader/web_search_reader.py,sha256=GMc59AxwS1zTyBEmnwzJCEbPmK4ut1eWy8EV5XXTRSk,14585
@@ -165,7 +166,7 @@ agno/knowledge/reranker/sentence_transformer.py,sha256=ZN4SqnMZsUhg5G7AzlONM1_Uj
165
166
  agno/memory/__init__.py,sha256=XWKJU5SJObYZqEKMZ2XYwgH8-YeuWUoSRfT4dEI5HnY,101
166
167
  agno/memory/manager.py,sha256=IWMnFxRF604zLY1jQArOggMwLjUUKXiPWt7RtLlpHUk,51907
167
168
  agno/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
168
- agno/models/base.py,sha256=bjP0Xt5-Jlk1I_7wHmuv8lhUPE75WzZ_iF3U-d81TUI,85214
169
+ agno/models/base.py,sha256=Tifu_fMGiWgwGMMoB6ww_BjrZ7cOb73V1oqZCU9PXJk,85198
169
170
  agno/models/defaults.py,sha256=1_fe4-ZbNriE8BgqxVRVi4KGzEYxYKYsz4hn6CZNEEM,40
170
171
  agno/models/message.py,sha256=HMlY71ZCDWCUrNTqIbRezWZK5FfWcvcs5eg_hhjbuqo,19023
171
172
  agno/models/metrics.py,sha256=81IILXZwGmOTiWK003bi5mg4bM1f4LCWbwyamjFzp18,4500
@@ -314,19 +315,19 @@ agno/reasoning/openai.py,sha256=JYk-mR9cMf1ibprX3MdL8oeCEDyQ3XaJw9PAIYvWeGk,3234
314
315
  agno/reasoning/step.py,sha256=6DaOb_0DJRz9Yh1w_mxcRaOSVzIQDrj3lQ6rzHLdIwA,1220
315
316
  agno/reasoning/vertexai.py,sha256=O9ntvalkIY2jLmWviEH1DnecMskqTL-mRZQBZohoHiU,2974
316
317
  agno/run/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
317
- agno/run/agent.py,sha256=Nj22h_EFgaspc_YLgJrlY-sVYGkubAOiwrUIfy9o_5Y,25454
318
+ agno/run/agent.py,sha256=0Je28vN3tFW5YdhcU-YAXZHT16wFSWKbE5zkAu9E3bo,25631
318
319
  agno/run/base.py,sha256=x37ecdhKdb3dbW23d8t2JPBZwrwIC93lRvT1-76CK0c,7059
319
320
  agno/run/cancel.py,sha256=yoSj3fnx8D7Gf-fSngVIgd3GOp3tRaDhHH_4QeHDoAk,2667
320
321
  agno/run/messages.py,sha256=rAC4CLW-xBA6qFS1BOvcjJ9j_qYf0a7sX1mcdY04zMU,1126
321
- agno/run/team.py,sha256=kYcFH58EZbE17o338cEVOzchulVrz4-NVEYCvBjzf4E,25905
322
- agno/run/workflow.py,sha256=sgYN4VeiDzXP-w16pE68uGWUlZSVCa3UCE98tHWSEqY,22662
322
+ agno/run/team.py,sha256=_6MhyeDvP2pO6dqA0U8yxqxwpmX6q0eBfhLjrsOY1Fc,26082
323
+ agno/run/workflow.py,sha256=DHnRDjtWlMXwoG0jGkk332hFbhqRKav3zUlb7dYrps4,23201
323
324
  agno/session/__init__.py,sha256=p6eqzWcLSHiMex2yZvkwv2yrFUNdGs21TGMS49xrEC4,376
324
325
  agno/session/agent.py,sha256=vut3IwCivUu0vvuht_1c3rUhW61NZee6whxDQUsEkUM,9756
325
326
  agno/session/summary.py,sha256=5lKMepDMh3bp1Uo8EJMvHHQRwLiB14eF53iMkg0wumM,10012
326
327
  agno/session/team.py,sha256=e1GagwJOwmhjvijUiCitOu0YCuf_BadAtG2KC4n34ic,13390
327
328
  agno/session/workflow.py,sha256=tluE_3ERMBYJtffpwlrhdETWlzJk6Xw2x06FZ1Y3fXg,7397
328
329
  agno/team/__init__.py,sha256=toHidBOo5M3n_TIVtIKHgcDbLL9HR-_U-YQYuIt_XtE,847
329
- agno/team/team.py,sha256=JPe66RR2g7KZubr-vCYUAaJMTVLAybEVhn2GexKqqsY,390424
330
+ agno/team/team.py,sha256=RNOMWDWHaOPt7MBT2oVuhGis1M5aKiRgvpzXASWYHx4,391440
330
331
  agno/tools/__init__.py,sha256=jNll2sELhPPbqm5nPeT4_uyzRO2_KRTW-8Or60kioS0,210
331
332
  agno/tools/agentql.py,sha256=S82Z9aTNr-E5wnA4fbFs76COljJtiQIjf2grjz3CkHU,4104
332
333
  agno/tools/airflow.py,sha256=uf2rOzZpSU64l_qRJ5Raku-R3Gky-uewmYkh6W0-oxg,2610
@@ -471,7 +472,7 @@ agno/utils/log.py,sha256=Ycg4_MEkXyOz25OkY272JHWxIZP1nB4OScwJ-r8BMGk,7791
471
472
  agno/utils/mcp.py,sha256=v5juISDNtdQaUK1DmJnAAGuNiSp0491TDwErgjEdhF8,5113
472
473
  agno/utils/media.py,sha256=Lk0sbSHmoKlQi3hLKorowCKGF74xdeMfqIj6Gt21Zek,5891
473
474
  agno/utils/merge_dict.py,sha256=K_dZdwi46gjDFn7wgqZ9_fycRIZGJCnUxFVKCrQ6PYc,1490
474
- agno/utils/message.py,sha256=HcTDVdgZOfwqGL0YW5QNEeNoSLUvTawCo6_USJzsd6M,2423
475
+ agno/utils/message.py,sha256=jIcSe79WmfpbbYowy0C57ZzRBd9ENj-hyj3TQw_9vsg,4684
475
476
  agno/utils/openai.py,sha256=quhHsggz0uSZc9Lt-aVsveHSS1LT2r1fMgC0IpG43Tg,10300
476
477
  agno/utils/pickle.py,sha256=Ip6CPNxAWF3MVrUhMTW7e3IcpKhYG0IA-NKbdUYr94c,1008
477
478
  agno/utils/pprint.py,sha256=aTg3gfib3x3VK3E-xJrbBeufK7s77ebTVtWoa2VrYgU,7762
@@ -555,9 +556,9 @@ agno/workflow/router.py,sha256=q2nuFMEAUQwVTZGncFCwlOsdOw-4nHt5QNJQgEdhv6I,29707
555
556
  agno/workflow/step.py,sha256=gD9Nw3jYk6jsTTko3Ab0pRmMZ2ReOhcCzCgHZA7EcSc,60533
556
557
  agno/workflow/steps.py,sha256=O3lbKw56ziSnuJndAGm8hjlEwdTh2jQXf1s0587Va3M,25671
557
558
  agno/workflow/types.py,sha256=DutB4UkEppJoWRiNaGEnPk6xFNpg0oCBwOb7VJ8T_xE,18646
558
- agno/workflow/workflow.py,sha256=uOy27YcIwbOY-N-FwrMMS6JBflvcxfByclEZC8fcBoA,142896
559
- agno-2.2.0.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
560
- agno-2.2.0.dist-info/METADATA,sha256=D4DRUYBuhP3dZm_aqFrPICOhCpZoU7gPqvvztY8B2kY,27963
561
- agno-2.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
562
- agno-2.2.0.dist-info/top_level.txt,sha256=MKyeuVesTyOKIXUhc-d_tPa2Hrh0oTA4LM0izowpx70,5
563
- agno-2.2.0.dist-info/RECORD,,
559
+ agno/workflow/workflow.py,sha256=LBy_jVjvglRE4sOLAITZGaOJctJYbP59oV0HvuiHopA,147346
560
+ agno-2.2.1.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
561
+ agno-2.2.1.dist-info/METADATA,sha256=C28Hn25iN0DTL3dfdB_4qm3pY0ygTuXVWQqzyQFuyJ0,28076
562
+ agno-2.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
563
+ agno-2.2.1.dist-info/top_level.txt,sha256=MKyeuVesTyOKIXUhc-d_tPa2Hrh0oTA4LM0izowpx70,5
564
+ agno-2.2.1.dist-info/RECORD,,
File without changes