agno 2.2.0__py3-none-any.whl → 2.2.2__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 (71) hide show
  1. agno/agent/agent.py +751 -575
  2. agno/culture/manager.py +22 -24
  3. agno/db/async_postgres/__init__.py +1 -1
  4. agno/db/dynamo/dynamo.py +0 -2
  5. agno/db/firestore/firestore.py +0 -2
  6. agno/db/gcs_json/gcs_json_db.py +0 -4
  7. agno/db/gcs_json/utils.py +0 -24
  8. agno/db/in_memory/in_memory_db.py +0 -3
  9. agno/db/json/json_db.py +4 -10
  10. agno/db/json/utils.py +0 -24
  11. agno/db/mongo/mongo.py +0 -2
  12. agno/db/mysql/mysql.py +0 -3
  13. agno/db/postgres/__init__.py +1 -1
  14. agno/db/{async_postgres → postgres}/async_postgres.py +19 -22
  15. agno/db/postgres/postgres.py +7 -10
  16. agno/db/postgres/utils.py +106 -2
  17. agno/db/redis/redis.py +0 -2
  18. agno/db/singlestore/singlestore.py +0 -3
  19. agno/db/sqlite/__init__.py +2 -1
  20. agno/db/sqlite/async_sqlite.py +2269 -0
  21. agno/db/sqlite/sqlite.py +0 -2
  22. agno/db/sqlite/utils.py +96 -0
  23. agno/db/surrealdb/surrealdb.py +0 -6
  24. agno/knowledge/knowledge.py +14 -3
  25. agno/knowledge/reader/pptx_reader.py +101 -0
  26. agno/knowledge/reader/reader_factory.py +30 -0
  27. agno/knowledge/reader/tavily_reader.py +194 -0
  28. agno/knowledge/types.py +1 -0
  29. agno/memory/manager.py +28 -25
  30. agno/models/anthropic/claude.py +63 -6
  31. agno/models/base.py +255 -36
  32. agno/models/response.py +69 -0
  33. agno/os/router.py +7 -5
  34. agno/os/routers/memory/memory.py +2 -1
  35. agno/os/routers/memory/schemas.py +5 -2
  36. agno/os/schema.py +26 -20
  37. agno/os/utils.py +9 -2
  38. agno/run/agent.py +28 -30
  39. agno/run/base.py +17 -1
  40. agno/run/team.py +28 -29
  41. agno/run/workflow.py +32 -17
  42. agno/session/agent.py +3 -0
  43. agno/session/summary.py +4 -1
  44. agno/session/team.py +1 -1
  45. agno/team/team.py +620 -374
  46. agno/tools/dalle.py +2 -4
  47. agno/tools/eleven_labs.py +23 -25
  48. agno/tools/function.py +40 -0
  49. agno/tools/mcp/__init__.py +10 -0
  50. agno/tools/mcp/mcp.py +324 -0
  51. agno/tools/mcp/multi_mcp.py +347 -0
  52. agno/tools/mcp/params.py +24 -0
  53. agno/tools/slack.py +18 -3
  54. agno/tools/tavily.py +146 -0
  55. agno/utils/agent.py +366 -1
  56. agno/utils/mcp.py +92 -2
  57. agno/utils/media.py +166 -1
  58. agno/utils/message.py +60 -0
  59. agno/utils/print_response/workflow.py +17 -1
  60. agno/utils/team.py +89 -1
  61. agno/workflow/step.py +0 -1
  62. agno/workflow/types.py +10 -15
  63. agno/workflow/workflow.py +86 -1
  64. {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/METADATA +31 -25
  65. {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/RECORD +68 -64
  66. agno/db/async_postgres/schemas.py +0 -139
  67. agno/db/async_postgres/utils.py +0 -347
  68. agno/tools/mcp.py +0 -679
  69. {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/WHEEL +0 -0
  70. {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/licenses/LICENSE +0 -0
  71. {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/top_level.txt +0 -0
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:
@@ -7,7 +7,7 @@ from rich.markdown import Markdown
7
7
  from rich.status import Status
8
8
  from rich.text import Text
9
9
 
10
- from agno.media import Audio, Image, Video
10
+ from agno.media import Audio, File, Image, Video
11
11
  from agno.models.message import Message
12
12
  from agno.run.workflow import (
13
13
  ConditionExecutionCompletedEvent,
@@ -48,6 +48,7 @@ def print_response(
48
48
  audio: Optional[List[Audio]] = None,
49
49
  images: Optional[List[Image]] = None,
50
50
  videos: Optional[List[Video]] = None,
51
+ files: Optional[List[File]] = None,
51
52
  markdown: bool = True,
52
53
  show_time: bool = True,
53
54
  show_step_details: bool = True,
@@ -76,6 +77,8 @@ def print_response(
76
77
  media_info.append(f"Images: {len(images)}")
77
78
  if videos:
78
79
  media_info.append(f"Videos: {len(videos)}")
80
+ if files:
81
+ media_info.append(f"Files: {len(files)}")
79
82
 
80
83
  workflow_info = f"""**Workflow:** {workflow.name}"""
81
84
  if workflow.description:
@@ -126,6 +129,7 @@ def print_response(
126
129
  audio=audio,
127
130
  images=images,
128
131
  videos=videos,
132
+ files=files,
129
133
  **kwargs,
130
134
  ) # type: ignore
131
135
 
@@ -186,6 +190,7 @@ def print_response_stream(
186
190
  audio: Optional[List[Audio]] = None,
187
191
  images: Optional[List[Image]] = None,
188
192
  videos: Optional[List[Video]] = None,
193
+ files: Optional[List[File]] = None,
189
194
  stream_events: bool = False,
190
195
  stream_intermediate_steps: bool = False,
191
196
  markdown: bool = True,
@@ -210,6 +215,8 @@ def print_response_stream(
210
215
  media_info.append(f"Images: {len(images)}")
211
216
  if videos:
212
217
  media_info.append(f"Videos: {len(videos)}")
218
+ if files:
219
+ media_info.append(f"Files: {len(files)}")
213
220
 
214
221
  workflow_info = f"""**Workflow:** {workflow.name}"""
215
222
  if workflow.description:
@@ -314,6 +321,7 @@ def print_response_stream(
314
321
  audio=audio,
315
322
  images=images,
316
323
  videos=videos,
324
+ files=files,
317
325
  stream=True,
318
326
  stream_events=stream_events,
319
327
  **kwargs,
@@ -832,6 +840,7 @@ async def aprint_response(
832
840
  audio: Optional[List[Audio]] = None,
833
841
  images: Optional[List[Image]] = None,
834
842
  videos: Optional[List[Video]] = None,
843
+ files: Optional[List[File]] = None,
835
844
  markdown: bool = True,
836
845
  show_time: bool = True,
837
846
  show_step_details: bool = True,
@@ -860,6 +869,8 @@ async def aprint_response(
860
869
  media_info.append(f"Images: {len(images)}")
861
870
  if videos:
862
871
  media_info.append(f"Videos: {len(videos)}")
872
+ if files:
873
+ media_info.append(f"Files: {len(files)}")
863
874
 
864
875
  workflow_info = f"""**Workflow:** {workflow.name}"""
865
876
  if workflow.description:
@@ -910,6 +921,7 @@ async def aprint_response(
910
921
  audio=audio,
911
922
  images=images,
912
923
  videos=videos,
924
+ files=files,
913
925
  **kwargs,
914
926
  ) # type: ignore
915
927
 
@@ -970,6 +982,7 @@ async def aprint_response_stream(
970
982
  audio: Optional[List[Audio]] = None,
971
983
  images: Optional[List[Image]] = None,
972
984
  videos: Optional[List[Video]] = None,
985
+ files: Optional[List[File]] = None,
973
986
  stream_events: bool = False,
974
987
  stream_intermediate_steps: bool = False,
975
988
  markdown: bool = True,
@@ -994,6 +1007,8 @@ async def aprint_response_stream(
994
1007
  media_info.append(f"Images: {len(images)}")
995
1008
  if videos:
996
1009
  media_info.append(f"Videos: {len(videos)}")
1010
+ if files:
1011
+ media_info.append(f"Files: {len(files)}")
997
1012
 
998
1013
  workflow_info = f"""**Workflow:** {workflow.name}"""
999
1014
  if workflow.description:
@@ -1098,6 +1113,7 @@ async def aprint_response_stream(
1098
1113
  audio=audio,
1099
1114
  images=images,
1100
1115
  videos=videos,
1116
+ files=files,
1101
1117
  stream=True,
1102
1118
  stream_events=stream_events,
1103
1119
  **kwargs,
agno/utils/team.py CHANGED
@@ -1,6 +1,10 @@
1
- from typing import TYPE_CHECKING, Optional, Union
1
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
2
2
 
3
3
  from agno.agent import Agent
4
+ from agno.media import Audio, File, Image, Video
5
+ from agno.run.agent import RunOutput
6
+ from agno.run.team import TeamRunOutput
7
+ from agno.utils.log import log_debug
4
8
  from agno.utils.string import is_valid_uuid, url_safe_string
5
9
 
6
10
  if TYPE_CHECKING:
@@ -49,3 +53,87 @@ def get_member_id(member: Union[Agent, "Team"]) -> str:
49
53
  else:
50
54
  url_safe_member_id = None
51
55
  return url_safe_member_id
56
+
57
+
58
+ def add_interaction_to_team_run_context(
59
+ team_run_context: Dict[str, Any],
60
+ member_name: str,
61
+ task: str,
62
+ run_response: Union[RunOutput, TeamRunOutput],
63
+ ) -> None:
64
+ if "member_responses" not in team_run_context:
65
+ team_run_context["member_responses"] = []
66
+ team_run_context["member_responses"].append(
67
+ {
68
+ "member_name": member_name,
69
+ "task": task,
70
+ "run_response": run_response,
71
+ }
72
+ )
73
+ log_debug(f"Updated team run context with member name: {member_name}")
74
+
75
+
76
+ def get_team_member_interactions_str(team_run_context: Dict[str, Any]) -> str:
77
+ if not team_run_context:
78
+ return ""
79
+ team_member_interactions_str = ""
80
+ if "member_responses" in team_run_context:
81
+ team_member_interactions_str += "<member_interaction_context>\nSee below interactions wit other team members.\n"
82
+
83
+ for interaction in team_run_context["member_responses"]:
84
+ response_dict = interaction["run_response"].to_dict()
85
+ response_content = (
86
+ response_dict.get("content")
87
+ or ",".join([tool.get("content", "") for tool in response_dict.get("tools", [])])
88
+ or ""
89
+ )
90
+ team_member_interactions_str += f"Member: {interaction['member_name']}\n"
91
+ team_member_interactions_str += f"Task: {interaction['task']}\n"
92
+ team_member_interactions_str += f"Response: {response_content}\n"
93
+ team_member_interactions_str += "\n"
94
+ team_member_interactions_str += "</member_interaction_context>\n"
95
+ return team_member_interactions_str
96
+
97
+
98
+ def get_team_run_context_images(team_run_context: Dict[str, Any]) -> List[Image]:
99
+ if not team_run_context:
100
+ return []
101
+ images = []
102
+ if "member_responses" in team_run_context:
103
+ for interaction in team_run_context["member_responses"]:
104
+ if interaction["run_response"].images:
105
+ images.extend(interaction["run_response"].images)
106
+ return images
107
+
108
+
109
+ def get_team_run_context_videos(team_run_context: Dict[str, Any]) -> List[Video]:
110
+ if not team_run_context:
111
+ return []
112
+ videos = []
113
+ if "member_responses" in team_run_context:
114
+ for interaction in team_run_context["member_responses"]:
115
+ if interaction["run_response"].videos:
116
+ videos.extend(interaction["run_response"].videos)
117
+ return videos
118
+
119
+
120
+ def get_team_run_context_audio(team_run_context: Dict[str, Any]) -> List[Audio]:
121
+ if not team_run_context:
122
+ return []
123
+ audio = []
124
+ if "member_responses" in team_run_context:
125
+ for interaction in team_run_context["member_responses"]:
126
+ if interaction["run_response"].audio:
127
+ audio.extend(interaction["run_response"].audio)
128
+ return audio
129
+
130
+
131
+ def get_team_run_context_files(team_run_context: Dict[str, Any]) -> List[File]:
132
+ if not team_run_context:
133
+ return []
134
+ files = []
135
+ if "member_responses" in team_run_context:
136
+ for interaction in team_run_context["member_responses"]:
137
+ if interaction["run_response"].files:
138
+ files.extend(interaction["run_response"].files)
139
+ return files
agno/workflow/step.py CHANGED
@@ -382,7 +382,6 @@ class Step:
382
382
  """Enrich event with step and workflow context information"""
383
383
  if workflow_run_response is None:
384
384
  return event
385
-
386
385
  if hasattr(event, "workflow_id"):
387
386
  event.workflow_id = workflow_run_response.workflow_id
388
387
  if hasattr(event, "workflow_run_id"):
agno/workflow/types.py CHANGED
@@ -10,6 +10,12 @@ from agno.media import Audio, File, Image, Video
10
10
  from agno.models.metrics import Metrics
11
11
  from agno.session.workflow import WorkflowSession
12
12
  from agno.utils.log import log_warning
13
+ from agno.utils.media import (
14
+ reconstruct_audio_list,
15
+ reconstruct_files,
16
+ reconstruct_images,
17
+ reconstruct_videos,
18
+ )
13
19
  from agno.utils.serialize import json_serializer
14
20
 
15
21
 
@@ -308,21 +314,10 @@ class StepOutput:
308
314
  def from_dict(cls, data: Dict[str, Any]) -> "StepOutput":
309
315
  """Create StepOutput from dictionary"""
310
316
  # Reconstruct media artifacts
311
- images = data.get("images")
312
- if images:
313
- images = [Image.model_validate(img) for img in images]
314
-
315
- videos = data.get("videos")
316
- if videos:
317
- videos = [Video.model_validate(vid) for vid in videos]
318
-
319
- audio = data.get("audio")
320
- if audio:
321
- audio = [Audio.model_validate(aud) for aud in audio]
322
-
323
- files = data.get("files")
324
- if files:
325
- files = [File.model_validate(file) for file in files]
317
+ images = reconstruct_images(data.get("images"))
318
+ videos = reconstruct_videos(data.get("videos"))
319
+ audio = reconstruct_audio_list(data.get("audio"))
320
+ files = reconstruct_files(data.get("files"))
326
321
 
327
322
  metrics_data = data.get("metrics")
328
323
  metrics = None
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.2
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
@@ -135,6 +135,8 @@ Provides-Extra: fal
135
135
  Requires-Dist: fal_client; extra == "fal"
136
136
  Provides-Extra: firecrawl
137
137
  Requires-Dist: firecrawl-py; extra == "firecrawl"
138
+ Provides-Extra: tavily
139
+ Requires-Dist: tavily-python; extra == "tavily"
138
140
  Provides-Extra: crawl4ai
139
141
  Requires-Dist: crawl4ai; extra == "crawl4ai"
140
142
  Provides-Extra: github
@@ -238,6 +240,8 @@ Requires-Dist: pypdf; extra == "pdf"
238
240
  Requires-Dist: rapidocr_onnxruntime; extra == "pdf"
239
241
  Provides-Extra: docx
240
242
  Requires-Dist: python-docx; extra == "docx"
243
+ Provides-Extra: pptx
244
+ Requires-Dist: python-pptx; extra == "pptx"
241
245
  Provides-Extra: text
242
246
  Requires-Dist: aiofiles; extra == "text"
243
247
  Provides-Extra: csv
@@ -294,6 +298,7 @@ Requires-Dist: agno[duckdb]; extra == "tools"
294
298
  Requires-Dist: agno[newspaper]; extra == "tools"
295
299
  Requires-Dist: agno[youtube]; extra == "tools"
296
300
  Requires-Dist: agno[firecrawl]; extra == "tools"
301
+ Requires-Dist: agno[tavily]; extra == "tools"
297
302
  Requires-Dist: agno[crawl4ai]; extra == "tools"
298
303
  Requires-Dist: agno[github]; extra == "tools"
299
304
  Requires-Dist: agno[gmail]; extra == "tools"
@@ -346,6 +351,7 @@ Requires-Dist: agno[upstash]; extra == "vectordbs"
346
351
  Provides-Extra: knowledge
347
352
  Requires-Dist: agno[pdf]; extra == "knowledge"
348
353
  Requires-Dist: agno[docx]; extra == "knowledge"
354
+ Requires-Dist: agno[pptx]; extra == "knowledge"
349
355
  Requires-Dist: agno[text]; extra == "knowledge"
350
356
  Requires-Dist: agno[csv]; extra == "knowledge"
351
357
  Requires-Dist: agno[markdown]; extra == "knowledge"
@@ -446,6 +452,30 @@ if __name__ == "__main__":
446
452
  agent_os.serve(app="agno_agent:app", reload=True)
447
453
  ```
448
454
 
455
+ ## AgentOS - Production Runtime for Multi-Agent Systems
456
+
457
+ AgentOS is Agno's high-performance runtime for serving multi-agent systems in production. Key features include:
458
+
459
+ 1. **Pre-built FastAPI Runtime**: AgentOS ships with a ready-to-use FastAPI app for orchestrating your agents, teams, and workflows. This provides a major head start when building an AI product.
460
+
461
+ 2. **Integrated Control Plane**: The [AgentOS UI](https://os.agno.com) connects directly to your runtime, letting you test, monitor, and manage your system in real time. This gives you unmatched visibility and control over your system.
462
+
463
+ 3. **Private by Design**: AgentOS runs entirely in your cloud, ensuring complete data privacy. No data ever leaves your system. This is ideal for security-conscious enterprises.
464
+
465
+ Here's what the [AgentOS UI](https://os.agno.com) looks like in action:
466
+
467
+ https://github.com/user-attachments/assets/feb23db8-15cc-4e88-be7c-01a21a03ebf6
468
+
469
+ ## The Complete Agentic Solution
470
+
471
+ For companies building agents, Agno provides the complete agentic solution:
472
+
473
+ - The fastest framework for building agents, multi-agent teams and agentic workflows.
474
+ - A ready-to-use FastAPI app that gets you building AI products on day one.
475
+ - A control plane for testing, monitoring and managing your system.
476
+
477
+ Agno brings a novel architecture that no other framework provides, your AgentOS runs securely in your cloud, and the control plane connects directly to it from your browser. You don't need to send data to any external services or pay retention costs, you get complete privacy and control.
478
+
449
479
  ## Designed for Agent Engineering
450
480
 
451
481
  Agno is a remarkably feature-rich framework, purpose-built for large-scale multi-agent deployments.
@@ -474,30 +504,6 @@ Agno is a remarkably feature-rich framework, purpose-built for large-scale multi
474
504
 
475
505
  Every part of Agno is built for real-world deployment — where developer experience meets production performance.
476
506
 
477
- ## AgentOS - Production Runtime for Multi-Agent Systems
478
-
479
- AgentOS is Agno's high-performance runtime for serving multi-agent systems in production. Key features include:
480
-
481
- 1. **Pre-built FastAPI Runtime**: AgentOS ships with a ready-to-use FastAPI app for orchestrating your agents, teams, and workflows. This provides a major head start when building an AI product.
482
-
483
- 2. **Integrated Control Plane**: The [AgentOS UI](https://os.agno.com) connects directly to your runtime, letting you test, monitor, and manage your system in real time. This gives you unmatched visibility and control over your system.
484
-
485
- 3. **Private by Design**: AgentOS runs entirely in your cloud, ensuring complete data privacy. No data ever leaves your system. This is ideal for security-conscious enterprises.
486
-
487
- Here's what the [AgentOS UI](https://os.agno.com) looks like in action:
488
-
489
- https://github.com/user-attachments/assets/feb23db8-15cc-4e88-be7c-01a21a03ebf6
490
-
491
- ## The Complete Agentic Solution
492
-
493
- For companies building agents, Agno provides the complete agentic solution:
494
-
495
- - The fastest framework for building agents, multi-agent teams and agentic workflows.
496
- - A ready-to-use FastAPI app that gets you building AI products on day one.
497
- - A control plane for testing, monitoring and managing your system.
498
-
499
- Agno brings a novel architecture that no other framework provides, your AgentOS runs securely in your cloud, and the control plane connects directly to it from your browser. You don't need to send data to any external services or pay retention costs, you get complete privacy and control.
500
-
501
507
  ## Setup Your Coding Agent to Use Agno
502
508
 
503
509
  For LLMs and AI assistants to understand and navigate Agno's documentation, we provide an [llms.txt](https://docs.agno.com/llms.txt) or [llms-full.txt](https://docs.agno.com/llms-full.txt) file. This file is built for AI systems to efficiently parse and reference our documentation.