agno 2.2.1__py3-none-any.whl → 2.2.3__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 (69) hide show
  1. agno/agent/agent.py +735 -574
  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/__init__.py +15 -1
  12. agno/db/mongo/async_mongo.py +1999 -0
  13. agno/db/mongo/mongo.py +0 -2
  14. agno/db/mysql/mysql.py +0 -3
  15. agno/db/postgres/__init__.py +1 -1
  16. agno/db/{async_postgres → postgres}/async_postgres.py +19 -22
  17. agno/db/postgres/postgres.py +7 -10
  18. agno/db/postgres/utils.py +106 -2
  19. agno/db/redis/redis.py +0 -2
  20. agno/db/singlestore/singlestore.py +0 -3
  21. agno/db/sqlite/__init__.py +2 -1
  22. agno/db/sqlite/async_sqlite.py +2269 -0
  23. agno/db/sqlite/sqlite.py +0 -2
  24. agno/db/sqlite/utils.py +96 -0
  25. agno/db/surrealdb/surrealdb.py +0 -6
  26. agno/knowledge/knowledge.py +3 -3
  27. agno/knowledge/reader/reader_factory.py +16 -0
  28. agno/knowledge/reader/tavily_reader.py +194 -0
  29. agno/memory/manager.py +28 -25
  30. agno/models/anthropic/claude.py +63 -6
  31. agno/models/base.py +251 -32
  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 +25 -20
  37. agno/os/utils.py +9 -2
  38. agno/run/agent.py +23 -30
  39. agno/run/base.py +17 -1
  40. agno/run/team.py +23 -29
  41. agno/run/workflow.py +17 -12
  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 +599 -367
  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/print_response/workflow.py +17 -1
  59. agno/utils/team.py +89 -1
  60. agno/workflow/step.py +0 -1
  61. agno/workflow/types.py +10 -15
  62. {agno-2.2.1.dist-info → agno-2.2.3.dist-info}/METADATA +28 -25
  63. {agno-2.2.1.dist-info → agno-2.2.3.dist-info}/RECORD +66 -62
  64. agno/db/async_postgres/schemas.py +0 -139
  65. agno/db/async_postgres/utils.py +0 -347
  66. agno/tools/mcp.py +0 -679
  67. {agno-2.2.1.dist-info → agno-2.2.3.dist-info}/WHEEL +0 -0
  68. {agno-2.2.1.dist-info → agno-2.2.3.dist-info}/licenses/LICENSE +0 -0
  69. {agno-2.2.1.dist-info → agno-2.2.3.dist-info}/top_level.txt +0 -0
agno/os/router.py CHANGED
@@ -1082,7 +1082,8 @@ def get_base_router(
1082
1082
 
1083
1083
  agents = []
1084
1084
  for agent in os.agents:
1085
- agents.append(AgentResponse.from_agent(agent=agent))
1085
+ agent_response = await AgentResponse.from_agent(agent=agent)
1086
+ agents.append(agent_response)
1086
1087
 
1087
1088
  return agents
1088
1089
 
@@ -1129,7 +1130,7 @@ def get_base_router(
1129
1130
  if agent is None:
1130
1131
  raise HTTPException(status_code=404, detail="Agent not found")
1131
1132
 
1132
- return AgentResponse.from_agent(agent)
1133
+ return await AgentResponse.from_agent(agent)
1133
1134
 
1134
1135
  # -- Team routes ---
1135
1136
 
@@ -1414,7 +1415,8 @@ def get_base_router(
1414
1415
 
1415
1416
  teams = []
1416
1417
  for team in os.teams:
1417
- teams.append(TeamResponse.from_team(team=team))
1418
+ team_response = await TeamResponse.from_team(team=team)
1419
+ teams.append(team_response)
1418
1420
 
1419
1421
  return teams
1420
1422
 
@@ -1507,7 +1509,7 @@ def get_base_router(
1507
1509
  if team is None:
1508
1510
  raise HTTPException(status_code=404, detail="Team not found")
1509
1511
 
1510
- return TeamResponse.from_team(team)
1512
+ return await TeamResponse.from_team(team)
1511
1513
 
1512
1514
  # -- Workflow routes ---
1513
1515
 
@@ -1580,7 +1582,7 @@ def get_base_router(
1580
1582
  if workflow is None:
1581
1583
  raise HTTPException(status_code=404, detail="Workflow not found")
1582
1584
 
1583
- return WorkflowResponse.from_workflow(workflow)
1585
+ return await WorkflowResponse.from_workflow(workflow)
1584
1586
 
1585
1587
  @router.post(
1586
1588
  "/workflows/{workflow_id}/runs",
@@ -251,8 +251,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
251
251
  deserialize=False,
252
252
  )
253
253
 
254
+ memories = [UserMemorySchema.from_dict(user_memory) for user_memory in user_memories] # type: ignore
254
255
  return PaginatedResponse(
255
- data=[UserMemorySchema.from_dict(user_memory) for user_memory in user_memories], # type: ignore
256
+ data=[memory for memory in memories if memory is not None],
256
257
  meta=PaginationInfo(
257
258
  page=page,
258
259
  limit=limit,
@@ -11,7 +11,7 @@ class DeleteMemoriesRequest(BaseModel):
11
11
 
12
12
  class UserMemorySchema(BaseModel):
13
13
  memory_id: str = Field(..., description="Unique identifier for the memory")
14
- memory: str = Field(..., description="Memory content text", min_length=1)
14
+ memory: str = Field(..., description="Memory content text")
15
15
  topics: Optional[List[str]] = Field(None, description="Topics or tags associated with the memory")
16
16
 
17
17
  agent_id: Optional[str] = Field(None, description="Agent ID associated with this memory")
@@ -21,7 +21,10 @@ class UserMemorySchema(BaseModel):
21
21
  updated_at: Optional[datetime] = Field(None, description="Timestamp when memory was last updated")
22
22
 
23
23
  @classmethod
24
- def from_dict(cls, memory_dict: Dict[str, Any]) -> "UserMemorySchema":
24
+ def from_dict(cls, memory_dict: Dict[str, Any]) -> Optional["UserMemorySchema"]:
25
+ if memory_dict["memory"] == "":
26
+ return None
27
+
25
28
  return cls(
26
29
  memory_id=memory_dict["memory_id"],
27
30
  user_id=str(memory_dict["user_id"]),
agno/os/schema.py CHANGED
@@ -183,7 +183,7 @@ class AgentResponse(BaseModel):
183
183
  metadata: Optional[Dict[str, Any]] = Field(None, description="Additional metadata")
184
184
 
185
185
  @classmethod
186
- def from_agent(cls, agent: Agent) -> "AgentResponse":
186
+ async def from_agent(cls, agent: Agent) -> "AgentResponse":
187
187
  def filter_meaningful_config(d: Dict[str, Any], defaults: Dict[str, Any]) -> Optional[Dict[str, Any]]:
188
188
  """Filter out fields that match their default values, keeping only meaningful user configurations"""
189
189
  filtered = {}
@@ -243,10 +243,10 @@ class AgentResponse(BaseModel):
243
243
  "stream_intermediate_steps": False,
244
244
  }
245
245
 
246
- agent_tools = agent.get_tools(
246
+ agent_tools = await agent.aget_tools(
247
247
  session=AgentSession(session_id=str(uuid4()), session_data={}),
248
248
  run_response=RunOutput(run_id=str(uuid4())),
249
- async_mode=True,
249
+ check_mcp_tools=False,
250
250
  )
251
251
  formatted_tools = format_tools(agent_tools) if agent_tools else None
252
252
 
@@ -415,7 +415,7 @@ class TeamResponse(BaseModel):
415
415
  metadata: Optional[Dict[str, Any]] = Field(None, description="Additional metadata")
416
416
 
417
417
  @classmethod
418
- def from_team(cls, team: Team) -> "TeamResponse":
418
+ async def from_team(cls, team: Team) -> "TeamResponse":
419
419
  def filter_meaningful_config(d: Dict[str, Any], defaults: Dict[str, Any]) -> Optional[Dict[str, Any]]:
420
420
  """Filter out fields that match their default values, keeping only meaningful user configurations"""
421
421
  filtered = {}
@@ -466,15 +466,16 @@ class TeamResponse(BaseModel):
466
466
  "stream_member_events": False,
467
467
  }
468
468
 
469
- team.determine_tools_for_model(
469
+ _tools = team._determine_tools_for_model(
470
470
  model=team.model, # type: ignore
471
471
  session=TeamSession(session_id=str(uuid4()), session_data={}),
472
472
  run_response=TeamRunOutput(run_id=str(uuid4())),
473
473
  async_mode=True,
474
474
  session_state={},
475
475
  team_run_context={},
476
+ check_mcp_tools=False,
476
477
  )
477
- team_tools = list(team._functions_for_model.values()) if team._functions_for_model else []
478
+ team_tools = _tools
478
479
  formatted_tools = format_team_tools(team_tools) if team_tools else None
479
480
 
480
481
  model_name = team.model.name or team.model.__class__.__name__ if team.model else None
@@ -595,6 +596,15 @@ class TeamResponse(BaseModel):
595
596
  if team.model and team.model.provider is not None:
596
597
  _team_model_data["provider"] = team.model.provider
597
598
 
599
+ members: List[Union[AgentResponse, TeamResponse]] = []
600
+ for member in team.members:
601
+ if isinstance(member, Agent):
602
+ agent_response = await AgentResponse.from_agent(member)
603
+ members.append(agent_response)
604
+ if isinstance(member, Team):
605
+ team_response = await TeamResponse.from_team(member)
606
+ members.append(team_response)
607
+
598
608
  return TeamResponse(
599
609
  id=team.id,
600
610
  name=team.name,
@@ -609,14 +619,7 @@ class TeamResponse(BaseModel):
609
619
  system_message=filter_meaningful_config(system_message_info, team_defaults),
610
620
  response_settings=filter_meaningful_config(response_settings_info, team_defaults),
611
621
  streaming=filter_meaningful_config(streaming_info, team_defaults),
612
- members=[ # type: ignore
613
- AgentResponse.from_agent(member)
614
- if isinstance(member, Agent)
615
- else TeamResponse.from_team(member)
616
- if isinstance(member, Team)
617
- else None
618
- for member in team.members
619
- ],
622
+ members=members if members else None,
620
623
  metadata=team.metadata,
621
624
  )
622
625
 
@@ -633,7 +636,7 @@ class WorkflowResponse(BaseModel):
633
636
  metadata: Optional[Dict[str, Any]] = Field(None, description="Additional metadata")
634
637
 
635
638
  @classmethod
636
- def _resolve_agents_and_teams_recursively(cls, steps: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
639
+ async def _resolve_agents_and_teams_recursively(cls, steps: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
637
640
  """Parse Agents and Teams into AgentResponse and TeamResponse objects.
638
641
 
639
642
  If the given steps have nested steps, recursively work on those."""
@@ -651,13 +654,15 @@ class WorkflowResponse(BaseModel):
651
654
  for idx, step in enumerate(steps):
652
655
  if step.get("agent"):
653
656
  # Convert to dict and exclude fields that are None
654
- step["agent"] = AgentResponse.from_agent(step["agent"]).model_dump(exclude_none=True)
657
+ agent_response = await AgentResponse.from_agent(step.get("agent")) # type: ignore
658
+ step["agent"] = agent_response.model_dump(exclude_none=True)
655
659
 
656
660
  if step.get("team"):
657
- step["team"] = TeamResponse.from_team(step["team"]).model_dump(exclude_none=True)
661
+ team_response = await TeamResponse.from_team(step.get("team")) # type: ignore
662
+ step["team"] = team_response.model_dump(exclude_none=True)
658
663
 
659
664
  if step.get("steps"):
660
- step["steps"] = cls._resolve_agents_and_teams_recursively(step["steps"])
665
+ step["steps"] = await cls._resolve_agents_and_teams_recursively(step["steps"])
661
666
 
662
667
  # Prune None values in the entire step
663
668
  steps[idx] = _prune_none(step)
@@ -665,12 +670,12 @@ class WorkflowResponse(BaseModel):
665
670
  return steps
666
671
 
667
672
  @classmethod
668
- def from_workflow(cls, workflow: Workflow) -> "WorkflowResponse":
673
+ async def from_workflow(cls, workflow: Workflow) -> "WorkflowResponse":
669
674
  workflow_dict = workflow.to_dict()
670
675
  steps = workflow_dict.get("steps")
671
676
 
672
677
  if steps:
673
- steps = cls._resolve_agents_and_teams_recursively(steps)
678
+ steps = await cls._resolve_agents_and_teams_recursively(steps)
674
679
 
675
680
  return cls(
676
681
  id=workflow.id,
agno/os/utils.py CHANGED
@@ -233,8 +233,15 @@ def format_tools(agent_tools: List[Union[Dict[str, Any], Toolkit, Function, Call
233
233
  return formatted_tools
234
234
 
235
235
 
236
- def format_team_tools(team_tools: List[Function]):
237
- return [tool.to_dict() for tool in team_tools]
236
+ def format_team_tools(team_tools: List[Union[Function, dict]]):
237
+ formatted_tools: List[Dict] = []
238
+ if team_tools is not None:
239
+ for tool in team_tools:
240
+ if isinstance(tool, dict):
241
+ formatted_tools.append(tool)
242
+ elif isinstance(tool, Function):
243
+ formatted_tools.append(tool.to_dict())
244
+ return formatted_tools
238
245
 
239
246
 
240
247
  def get_agent_by_id(agent_id: str, agents: Optional[List[Agent]] = None) -> Optional[Agent]:
agno/run/agent.py CHANGED
@@ -12,6 +12,13 @@ from agno.models.response import ToolExecution
12
12
  from agno.reasoning.step import ReasoningStep
13
13
  from agno.run.base import BaseRunOutputEvent, MessageReferences, RunStatus
14
14
  from agno.utils.log import logger
15
+ from agno.utils.media import (
16
+ reconstruct_audio_list,
17
+ reconstruct_files,
18
+ reconstruct_images,
19
+ reconstruct_response_audio,
20
+ reconstruct_videos,
21
+ )
15
22
 
16
23
  if TYPE_CHECKING:
17
24
  from agno.session.summary import SessionSummary
@@ -86,21 +93,10 @@ class RunInput:
86
93
  @classmethod
87
94
  def from_dict(cls, data: Dict[str, Any]) -> "RunInput":
88
95
  """Create RunInput from dictionary"""
89
- images = None
90
- if data.get("images"):
91
- images = [Image.model_validate(img_data) for img_data in data["images"]]
92
-
93
- videos = None
94
- if data.get("videos"):
95
- videos = [Video.model_validate(vid_data) for vid_data in data["videos"]]
96
-
97
- audios = None
98
- if data.get("audios"):
99
- audios = [Audio.model_validate(aud_data) for aud_data in data["audios"]]
100
-
101
- files = None
102
- if data.get("files"):
103
- files = [File.model_validate(file_data) for file_data in data["files"]]
96
+ images = reconstruct_images(data.get("images"))
97
+ videos = reconstruct_videos(data.get("videos"))
98
+ audios = reconstruct_audio_list(data.get("audios"))
99
+ files = reconstruct_files(data.get("files"))
104
100
 
105
101
  return cls(
106
102
  input_content=data.get("input_content", ""), images=images, videos=videos, audios=audios, files=files
@@ -679,20 +675,11 @@ class RunOutput:
679
675
  tools = data.pop("tools", [])
680
676
  tools = [ToolExecution.from_dict(tool) for tool in tools] if tools else None
681
677
 
682
- images = data.pop("images", [])
683
- images = [Image.model_validate(image) for image in images] if images else None
684
-
685
- videos = data.pop("videos", [])
686
- videos = [Video.model_validate(video) for video in videos] if videos else None
687
-
688
- audio = data.pop("audio", [])
689
- audio = [Audio.model_validate(audio) for audio in audio] if audio else None
690
-
691
- files = data.pop("files", [])
692
- files = [File.model_validate(file) for file in files] if files else None
693
-
694
- response_audio = data.pop("response_audio", None)
695
- response_audio = Audio.model_validate(response_audio) if response_audio else None
678
+ images = reconstruct_images(data.pop("images", []))
679
+ videos = reconstruct_videos(data.pop("videos", []))
680
+ audio = reconstruct_audio_list(data.pop("audio", []))
681
+ files = reconstruct_files(data.pop("files", []))
682
+ response_audio = reconstruct_response_audio(data.pop("response_audio", None))
696
683
 
697
684
  input_data = data.pop("input", None)
698
685
  input_obj = None
@@ -720,6 +707,12 @@ class RunOutput:
720
707
  if references is not None:
721
708
  references = [MessageReferences.model_validate(reference) for reference in references]
722
709
 
710
+ # Filter data to only include fields that are actually defined in the RunOutput dataclass
711
+ from dataclasses import fields
712
+
713
+ supported_fields = {f.name for f in fields(cls)}
714
+ filtered_data = {k: v for k, v in data.items() if k in supported_fields}
715
+
723
716
  return cls(
724
717
  messages=messages,
725
718
  metrics=metrics,
@@ -736,7 +729,7 @@ class RunOutput:
736
729
  reasoning_steps=reasoning_steps,
737
730
  reasoning_messages=reasoning_messages,
738
731
  references=references,
739
- **data,
732
+ **filtered_data,
740
733
  )
741
734
 
742
735
  def get_content_as_string(self, **kwargs) -> str:
agno/run/base.py CHANGED
@@ -35,6 +35,7 @@ class BaseRunOutputEvent:
35
35
  "reasoning_steps",
36
36
  "references",
37
37
  "additional_input",
38
+ "session_summary",
38
39
  "metrics",
39
40
  ]
40
41
  }
@@ -113,6 +114,9 @@ class BaseRunOutputEvent:
113
114
  if hasattr(self, "metrics") and self.metrics is not None:
114
115
  _dict["metrics"] = self.metrics.to_dict()
115
116
 
117
+ if hasattr(self, "session_summary") and self.session_summary is not None:
118
+ _dict["session_summary"] = self.session_summary.to_dict()
119
+
116
120
  return _dict
117
121
 
118
122
  def to_json(self, separators=(", ", ": "), indent: Optional[int] = 2) -> str:
@@ -173,7 +177,19 @@ class BaseRunOutputEvent:
173
177
  if metrics:
174
178
  data["metrics"] = Metrics(**metrics)
175
179
 
176
- return cls(**data)
180
+ session_summary = data.pop("session_summary", None)
181
+ if session_summary:
182
+ from agno.session.summary import SessionSummary
183
+
184
+ data["session_summary"] = SessionSummary.from_dict(session_summary)
185
+
186
+ # Filter data to only include fields that are actually defined in the target class
187
+ from dataclasses import fields
188
+
189
+ supported_fields = {f.name for f in fields(cls)}
190
+ filtered_data = {k: v for k, v in data.items() if k in supported_fields}
191
+
192
+ return cls(**filtered_data)
177
193
 
178
194
  @property
179
195
  def is_paused(self):
agno/run/team.py CHANGED
@@ -13,6 +13,13 @@ from agno.reasoning.step import ReasoningStep
13
13
  from agno.run.agent import RunEvent, RunOutput, RunOutputEvent, run_output_event_from_dict
14
14
  from agno.run.base import BaseRunOutputEvent, MessageReferences, RunStatus
15
15
  from agno.utils.log import log_error
16
+ from agno.utils.media import (
17
+ reconstruct_audio_list,
18
+ reconstruct_files,
19
+ reconstruct_images,
20
+ reconstruct_response_audio,
21
+ reconstruct_videos,
22
+ )
16
23
 
17
24
 
18
25
  @dataclass
@@ -82,21 +89,10 @@ class TeamRunInput:
82
89
  @classmethod
83
90
  def from_dict(cls, data: Dict[str, Any]) -> "TeamRunInput":
84
91
  """Create TeamRunInput from dictionary"""
85
- images = None
86
- if data.get("images"):
87
- images = [Image.model_validate(img_data) for img_data in data["images"]]
88
-
89
- videos = None
90
- if data.get("videos"):
91
- videos = [Video.model_validate(vid_data) for vid_data in data["videos"]]
92
-
93
- audios = None
94
- if data.get("audios"):
95
- audios = [Audio.model_validate(aud_data) for aud_data in data["audios"]]
96
-
97
- files = None
98
- if data.get("files"):
99
- files = [File.model_validate(file_data) for file_data in data["files"]]
92
+ images = reconstruct_images(data.get("images"))
93
+ videos = reconstruct_videos(data.get("videos"))
94
+ audios = reconstruct_audio_list(data.get("audios"))
95
+ files = reconstruct_files(data.get("files"))
100
96
 
101
97
  return cls(
102
98
  input_content=data.get("input_content", ""), images=images, videos=videos, audios=audios, files=files
@@ -646,23 +642,15 @@ class TeamRunOutput:
646
642
  if references is not None:
647
643
  references = [MessageReferences.model_validate(reference) for reference in references]
648
644
 
649
- images = data.pop("images", [])
650
- images = [Image.model_validate(image) for image in images] if images else None
651
-
652
- videos = data.pop("videos", [])
653
- videos = [Video.model_validate(video) for video in videos] if videos else None
654
-
655
- audio = data.pop("audio", [])
656
- audio = [Audio.model_validate(audio) for audio in audio] if audio else None
657
-
658
- files = data.pop("files", [])
659
- files = [File.model_validate(file) for file in files] if files else None
645
+ images = reconstruct_images(data.pop("images", []))
646
+ videos = reconstruct_videos(data.pop("videos", []))
647
+ audio = reconstruct_audio_list(data.pop("audio", []))
648
+ files = reconstruct_files(data.pop("files", []))
660
649
 
661
650
  tools = data.pop("tools", [])
662
651
  tools = [ToolExecution.from_dict(tool) for tool in tools] if tools else None
663
652
 
664
- response_audio = data.pop("response_audio", None)
665
- response_audio = Audio.model_validate(response_audio) if response_audio else None
653
+ response_audio = reconstruct_response_audio(data.pop("response_audio", None))
666
654
 
667
655
  input_data = data.pop("input", None)
668
656
  input_obj = None
@@ -676,6 +664,12 @@ class TeamRunOutput:
676
664
  citations = data.pop("citations", None)
677
665
  citations = Citations.model_validate(citations) if citations else None
678
666
 
667
+ # Filter data to only include fields that are actually defined in the TeamRunOutput dataclass
668
+ from dataclasses import fields
669
+
670
+ supported_fields = {f.name for f in fields(cls)}
671
+ filtered_data = {k: v for k, v in data.items() if k in supported_fields}
672
+
679
673
  return cls(
680
674
  messages=messages,
681
675
  metrics=metrics,
@@ -693,7 +687,7 @@ class TeamRunOutput:
693
687
  citations=citations,
694
688
  tools=tools,
695
689
  events=events,
696
- **data,
690
+ **filtered_data,
697
691
  )
698
692
 
699
693
  def get_content_as_string(self, **kwargs) -> str:
agno/run/workflow.py CHANGED
@@ -9,6 +9,12 @@ from agno.media import Audio, Image, Video
9
9
  from agno.run.agent import RunEvent, RunOutput, run_output_event_from_dict
10
10
  from agno.run.base import BaseRunOutputEvent, RunStatus
11
11
  from agno.run.team import TeamRunEvent, TeamRunOutput, team_run_output_event_from_dict
12
+ from agno.utils.media import (
13
+ reconstruct_audio_list,
14
+ reconstruct_images,
15
+ reconstruct_response_audio,
16
+ reconstruct_videos,
17
+ )
12
18
 
13
19
  if TYPE_CHECKING:
14
20
  from agno.workflow.types import StepOutput, WorkflowMetrics
@@ -600,17 +606,10 @@ class WorkflowRunOutput:
600
606
 
601
607
  metadata = data.pop("metadata", None)
602
608
 
603
- images = data.pop("images", [])
604
- images = [Image.model_validate(image) for image in images] if images else None
605
-
606
- videos = data.pop("videos", [])
607
- videos = [Video.model_validate(video) for video in videos] if videos else None
608
-
609
- audio = data.pop("audio", [])
610
- audio = [Audio.model_validate(audio) for audio in audio] if audio else None
611
-
612
- response_audio = data.pop("response_audio", None)
613
- response_audio = Audio.model_validate(response_audio) if response_audio else None
609
+ images = reconstruct_images(data.pop("images", []))
610
+ videos = reconstruct_videos(data.pop("videos", []))
611
+ audio = reconstruct_audio_list(data.pop("audio", []))
612
+ response_audio = reconstruct_response_audio(data.pop("response_audio", None))
614
613
 
615
614
  events_data = data.pop("events", [])
616
615
  final_events = []
@@ -633,6 +632,12 @@ class WorkflowRunOutput:
633
632
 
634
633
  input_data = data.pop("input", None)
635
634
 
635
+ # Filter data to only include fields that are actually defined in the WorkflowRunOutput dataclass
636
+ from dataclasses import fields
637
+
638
+ supported_fields = {f.name for f in fields(cls)}
639
+ filtered_data = {k: v for k, v in data.items() if k in supported_fields}
640
+
636
641
  return cls(
637
642
  step_results=parsed_step_results,
638
643
  metadata=metadata,
@@ -644,7 +649,7 @@ class WorkflowRunOutput:
644
649
  metrics=workflow_metrics,
645
650
  step_executor_runs=step_executor_runs,
646
651
  input=input_data,
647
- **data,
652
+ **filtered_data,
648
653
  )
649
654
 
650
655
  def get_content_as_string(self, **kwargs) -> str:
agno/session/agent.py CHANGED
@@ -140,6 +140,9 @@ class AgentSession:
140
140
  if team_id:
141
141
  session_runs = [run for run in session_runs if hasattr(run, "team_id") and run.team_id == team_id] # type: ignore
142
142
 
143
+ # Skip any messages that might be part of members of teams (for session re-use)
144
+ session_runs = [run for run in session_runs if run.parent_run_id is None] # type: ignore
145
+
143
146
  # Filter by status
144
147
  session_runs = [run for run in session_runs if hasattr(run, "status") and run.status not in skip_status] # type: ignore
145
148
 
agno/session/summary.py CHANGED
@@ -66,6 +66,9 @@ class SessionSummaryManager:
66
66
  # Prompt used for session summary generation
67
67
  session_summary_prompt: Optional[str] = None
68
68
 
69
+ # User message prompt for requesting the summary
70
+ summary_request_message: str = "Provide the summary of the conversation."
71
+
69
72
  # Whether session summaries were created in the last run
70
73
  summaries_updated: bool = False
71
74
 
@@ -152,7 +155,7 @@ class SessionSummaryManager:
152
155
 
153
156
  return [
154
157
  system_message,
155
- Message(role="user", content="Provide the summary of the conversation."),
158
+ Message(role="user", content=self.summary_request_message),
156
159
  ]
157
160
 
158
161
  def _process_summary_response(self, summary_response, session_summary_model: "Model") -> Optional[SessionSummary]: # type: ignore
agno/session/team.py CHANGED
@@ -150,7 +150,7 @@ class TeamSession:
150
150
  session_runs = [run for run in session_runs if hasattr(run, "team_id") and run.team_id == team_id] # type: ignore
151
151
 
152
152
  if not member_runs:
153
- # Filter for the main team runs
153
+ # Filter for the top-level runs (main team runs or agent runs when sharing session)
154
154
  session_runs = [run for run in session_runs if run.parent_run_id is None] # type: ignore
155
155
  # Filter by status
156
156
  session_runs = [run for run in session_runs if hasattr(run, "status") and run.status not in skip_status] # type: ignore