agno 2.0.3__py3-none-any.whl → 2.0.5__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 (58) hide show
  1. agno/agent/agent.py +229 -164
  2. agno/db/dynamo/dynamo.py +8 -0
  3. agno/db/firestore/firestore.py +8 -0
  4. agno/db/gcs_json/gcs_json_db.py +9 -0
  5. agno/db/json/json_db.py +8 -0
  6. agno/db/migrations/v1_to_v2.py +191 -23
  7. agno/db/mongo/mongo.py +68 -0
  8. agno/db/mysql/mysql.py +13 -3
  9. agno/db/mysql/schemas.py +27 -27
  10. agno/db/postgres/postgres.py +19 -11
  11. agno/db/redis/redis.py +6 -0
  12. agno/db/singlestore/schemas.py +1 -1
  13. agno/db/singlestore/singlestore.py +8 -1
  14. agno/db/sqlite/sqlite.py +12 -3
  15. agno/integrations/discord/client.py +1 -0
  16. agno/knowledge/knowledge.py +92 -66
  17. agno/knowledge/reader/reader_factory.py +7 -3
  18. agno/knowledge/reader/web_search_reader.py +12 -6
  19. agno/models/base.py +2 -2
  20. agno/models/message.py +109 -0
  21. agno/models/openai/chat.py +3 -0
  22. agno/models/openai/responses.py +12 -0
  23. agno/models/response.py +5 -0
  24. agno/models/siliconflow/__init__.py +5 -0
  25. agno/models/siliconflow/siliconflow.py +25 -0
  26. agno/os/app.py +164 -41
  27. agno/os/auth.py +24 -14
  28. agno/os/interfaces/agui/utils.py +98 -134
  29. agno/os/router.py +128 -55
  30. agno/os/routers/evals/utils.py +9 -9
  31. agno/os/routers/health.py +25 -0
  32. agno/os/routers/home.py +52 -0
  33. agno/os/routers/knowledge/knowledge.py +11 -11
  34. agno/os/routers/session/session.py +24 -8
  35. agno/os/schema.py +29 -2
  36. agno/os/utils.py +0 -8
  37. agno/run/agent.py +3 -3
  38. agno/run/team.py +3 -3
  39. agno/run/workflow.py +64 -10
  40. agno/session/team.py +1 -0
  41. agno/team/team.py +189 -94
  42. agno/tools/duckduckgo.py +15 -11
  43. agno/tools/googlesearch.py +1 -1
  44. agno/tools/mem0.py +11 -17
  45. agno/tools/memory.py +34 -6
  46. agno/utils/common.py +90 -1
  47. agno/utils/streamlit.py +14 -8
  48. agno/utils/string.py +32 -0
  49. agno/utils/tools.py +1 -1
  50. agno/vectordb/chroma/chromadb.py +8 -2
  51. agno/workflow/step.py +115 -16
  52. agno/workflow/workflow.py +16 -13
  53. {agno-2.0.3.dist-info → agno-2.0.5.dist-info}/METADATA +6 -5
  54. {agno-2.0.3.dist-info → agno-2.0.5.dist-info}/RECORD +57 -54
  55. agno/knowledge/reader/url_reader.py +0 -128
  56. {agno-2.0.3.dist-info → agno-2.0.5.dist-info}/WHEEL +0 -0
  57. {agno-2.0.3.dist-info → agno-2.0.5.dist-info}/licenses/LICENSE +0 -0
  58. {agno-2.0.3.dist-info → agno-2.0.5.dist-info}/top_level.txt +0 -0
@@ -35,7 +35,7 @@ async def run_accuracy_eval(
35
35
  name=eval_run_input.name,
36
36
  )
37
37
 
38
- result = accuracy_eval.run(print_results=False, print_summary=False)
38
+ result = await accuracy_eval.arun(print_results=False, print_summary=False)
39
39
  if not result:
40
40
  raise HTTPException(status_code=500, detail="Failed to run accuracy evaluation")
41
41
 
@@ -60,16 +60,16 @@ async def run_performance_eval(
60
60
  """Run a performance evaluation for the given agent or team"""
61
61
  if agent:
62
62
 
63
- def run_component(): # type: ignore
64
- return agent.run(eval_run_input.input)
63
+ async def run_component(): # type: ignore
64
+ return await agent.arun(eval_run_input.input)
65
65
 
66
66
  model_id = agent.model.id if agent and agent.model else None
67
67
  model_provider = agent.model.provider if agent and agent.model else None
68
68
 
69
69
  elif team:
70
70
 
71
- def run_component():
72
- return team.run(eval_run_input.input)
71
+ async def run_component():
72
+ return await team.arun(eval_run_input.input)
73
73
 
74
74
  model_id = team.model.id if team and team.model else None
75
75
  model_provider = team.model.provider if team and team.model else None
@@ -85,7 +85,7 @@ async def run_performance_eval(
85
85
  model_id=model_id,
86
86
  model_provider=model_provider,
87
87
  )
88
- result = performance_eval.run(print_results=False, print_summary=False)
88
+ result = await performance_eval.arun(print_results=False, print_summary=False)
89
89
  if not result:
90
90
  raise HTTPException(status_code=500, detail="Failed to run performance evaluation")
91
91
 
@@ -119,7 +119,7 @@ async def run_reliability_eval(
119
119
  raise HTTPException(status_code=400, detail="expected_tool_calls is required for reliability evaluations")
120
120
 
121
121
  if agent:
122
- agent_response = agent.run(eval_run_input.input)
122
+ agent_response = await agent.arun(eval_run_input.input)
123
123
  reliability_eval = ReliabilityEval(
124
124
  db=db,
125
125
  name=eval_run_input.name,
@@ -130,7 +130,7 @@ async def run_reliability_eval(
130
130
  model_provider = agent.model.provider if agent and agent.model else None
131
131
 
132
132
  elif team:
133
- team_response = team.run(eval_run_input.input)
133
+ team_response = await team.arun(eval_run_input.input)
134
134
  reliability_eval = ReliabilityEval(
135
135
  db=db,
136
136
  name=eval_run_input.name,
@@ -140,7 +140,7 @@ async def run_reliability_eval(
140
140
  model_id = team.model.id if team and team.model else None
141
141
  model_provider = team.model.provider if team and team.model else None
142
142
 
143
- result = reliability_eval.run(print_results=False)
143
+ result = await reliability_eval.arun(print_results=False)
144
144
  if not result:
145
145
  raise HTTPException(status_code=500, detail="Failed to run reliability evaluation")
146
146
 
@@ -0,0 +1,25 @@
1
+ from fastapi import APIRouter
2
+
3
+ from agno.os.schema import HealthResponse
4
+
5
+
6
+ def get_health_router() -> APIRouter:
7
+ router = APIRouter(tags=["Health"])
8
+
9
+ @router.get(
10
+ "/health",
11
+ operation_id="health_check",
12
+ summary="Health Check",
13
+ description="Check the health status of the AgentOS API. Returns a simple status indicator.",
14
+ response_model=HealthResponse,
15
+ responses={
16
+ 200: {
17
+ "description": "API is healthy and operational",
18
+ "content": {"application/json": {"example": {"status": "ok"}}},
19
+ }
20
+ },
21
+ )
22
+ async def health_check() -> HealthResponse:
23
+ return HealthResponse(status="ok")
24
+
25
+ return router
@@ -0,0 +1,52 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from fastapi import APIRouter
4
+
5
+ if TYPE_CHECKING:
6
+ from agno.os.app import AgentOS
7
+
8
+
9
+ def get_home_router(os: "AgentOS") -> APIRouter:
10
+ router = APIRouter(tags=["Home"])
11
+
12
+ @router.get(
13
+ "/",
14
+ operation_id="get_api_info",
15
+ summary="API Information",
16
+ description=(
17
+ "Get basic information about this AgentOS API instance, including:\n\n"
18
+ "- API metadata and version\n"
19
+ "- Available capabilities overview\n"
20
+ "- Links to key endpoints and documentation"
21
+ ),
22
+ responses={
23
+ 200: {
24
+ "description": "API information retrieved successfully",
25
+ "content": {
26
+ "application/json": {
27
+ "examples": {
28
+ "home": {
29
+ "summary": "Example home response",
30
+ "value": {
31
+ "name": "AgentOS API",
32
+ "description": "AI Agent Operating System API",
33
+ "os_id": "demo-os",
34
+ "version": "1.0.0",
35
+ },
36
+ }
37
+ }
38
+ }
39
+ },
40
+ }
41
+ },
42
+ )
43
+ async def get_api_info():
44
+ """Get basic API information and available capabilities"""
45
+ return {
46
+ "name": "AgentOS API",
47
+ "description": os.description or "AI Agent Operating System API",
48
+ "os_id": os.os_id or "agno-agentos",
49
+ "version": os.version or "1.0.0",
50
+ }
51
+
52
+ return router
@@ -2,7 +2,6 @@ import json
2
2
  import logging
3
3
  import math
4
4
  from typing import Dict, List, Optional
5
- from uuid import uuid4
6
5
 
7
6
  from fastapi import APIRouter, BackgroundTasks, Depends, File, Form, HTTPException, Path, Query, UploadFile
8
7
 
@@ -34,6 +33,7 @@ from agno.os.schema import (
34
33
  from agno.os.settings import AgnoAPISettings
35
34
  from agno.os.utils import get_knowledge_instance_by_db_id
36
35
  from agno.utils.log import log_debug, log_info
36
+ from agno.utils.string import generate_id
37
37
 
38
38
  logger = logging.getLogger(__name__)
39
39
 
@@ -102,8 +102,7 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
102
102
  db_id: Optional[str] = Query(default=None, description="Database ID to use for content storage"),
103
103
  ):
104
104
  knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
105
- content_id = str(uuid4())
106
- log_info(f"Adding content: {name}, {description}, {url}, {metadata} with ID: {content_id}")
105
+ log_info(f"Adding content: {name}, {description}, {url}, {metadata}")
107
106
 
108
107
  parsed_metadata = None
109
108
  if metadata:
@@ -166,10 +165,14 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
166
165
  file_data=file_data,
167
166
  size=file.size if file else None if text_content else None,
168
167
  )
169
- background_tasks.add_task(process_content, knowledge, content_id, content, reader_id, chunker)
168
+ content_hash = knowledge._build_content_hash(content)
169
+ content.content_hash = content_hash
170
+ content.id = generate_id(content_hash)
171
+
172
+ background_tasks.add_task(process_content, knowledge, content, reader_id, chunker)
170
173
 
171
174
  response = ContentResponseSchema(
172
- id=content_id,
175
+ id=content.id,
173
176
  name=name,
174
177
  description=description,
175
178
  metadata=parsed_metadata,
@@ -802,15 +805,13 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
802
805
 
803
806
  async def process_content(
804
807
  knowledge: Knowledge,
805
- content_id: str,
806
808
  content: Content,
807
809
  reader_id: Optional[str] = None,
808
810
  chunker: Optional[str] = None,
809
811
  ):
810
812
  """Background task to process the content"""
811
- log_info(f"Processing content {content_id}")
813
+
812
814
  try:
813
- content.id = content_id
814
815
  if reader_id:
815
816
  reader = None
816
817
  if knowledge.readers and reader_id in knowledge.readers:
@@ -834,16 +835,15 @@ async def process_content(
834
835
 
835
836
  log_debug(f"Using reader: {content.reader.__class__.__name__}")
836
837
  await knowledge._load_content(content, upsert=False, skip_if_exists=True)
837
- log_info(f"Content {content_id} processed successfully")
838
+ log_info(f"Content {content.id} processed successfully")
838
839
  except Exception as e:
839
- log_info(f"Error processing content {content_id}: {e}")
840
+ log_info(f"Error processing content: {e}")
840
841
  # Mark content as failed in the contents DB
841
842
  try:
842
843
  from agno.knowledge.content import ContentStatus as KnowledgeContentStatus
843
844
 
844
845
  content.status = KnowledgeContentStatus.FAILED
845
846
  content.status_message = str(e)
846
- content.id = content_id
847
847
  knowledge.patch_content(content)
848
848
  except Exception:
849
849
  # Swallow any secondary errors to avoid crashing the background task
@@ -222,7 +222,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
222
222
  db = get_db(dbs, db_id)
223
223
  session = db.get_session(session_id=session_id, session_type=session_type)
224
224
  if not session:
225
- raise HTTPException(status_code=404, detail=f"Session with id '{session_id}' not found")
225
+ raise HTTPException(
226
+ status_code=404, detail=f"{session_type.value.title()} Session with id '{session_id}' not found"
227
+ )
226
228
 
227
229
  if session_type == SessionType.AGENT:
228
230
  return AgentSessionDetailSchema.from_session(session) # type: ignore
@@ -233,7 +235,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
233
235
 
234
236
  @router.get(
235
237
  "/sessions/{session_id}/runs",
236
- response_model=Union[List[RunSchema], List[TeamRunSchema], List[WorkflowRunSchema]],
238
+ response_model=List[Union[RunSchema, TeamRunSchema, WorkflowRunSchema]],
237
239
  status_code=200,
238
240
  operation_id="get_session_runs",
239
241
  summary="Get Session Runs",
@@ -251,7 +253,8 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
251
253
  "summary": "Example completed run",
252
254
  "value": {
253
255
  "run_id": "fcdf50f0-7c32-4593-b2ef-68a558774340",
254
- "agent_session_id": "80056af0-c7a5-4d69-b6a2-c3eba9f040e0",
256
+ "parent_run_id": "80056af0-c7a5-4d69-b6a2-c3eba9f040e0",
257
+ "agent_id": "basic-agent",
255
258
  "user_id": "",
256
259
  "run_input": "Which tools do you have access to?",
257
260
  "content": "I don't have access to external tools or the internet. However, I can assist you with a wide range of topics by providing information, answering questions, and offering suggestions based on the knowledge I've been trained on. If there's anything specific you need help with, feel free to ask!",
@@ -351,7 +354,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
351
354
  default=SessionType.AGENT, description="Session type (agent, team, or workflow)", alias="type"
352
355
  ),
353
356
  db_id: Optional[str] = Query(default=None, description="Database ID to query runs from"),
354
- ) -> Union[List[RunSchema], List[TeamRunSchema], List[WorkflowRunSchema]]:
357
+ ) -> List[Union[RunSchema, TeamRunSchema, WorkflowRunSchema]]:
355
358
  db = get_db(dbs, db_id)
356
359
  session = db.get_session(session_id=session_id, session_type=session_type, deserialize=False)
357
360
  if not session:
@@ -365,13 +368,26 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
365
368
  return [RunSchema.from_dict(run) for run in runs]
366
369
 
367
370
  elif session_type == SessionType.TEAM:
368
- return [TeamRunSchema.from_dict(run) for run in runs]
371
+ run_responses: List[Union[RunSchema, TeamRunSchema, WorkflowRunSchema]] = []
372
+ for run in runs:
373
+ if run.get("agent_id") is not None:
374
+ run_responses.append(RunSchema.from_dict(run))
375
+ elif run.get("team_id") is not None:
376
+ run_responses.append(TeamRunSchema.from_dict(run))
377
+ return run_responses
369
378
 
370
379
  elif session_type == SessionType.WORKFLOW:
371
- return [WorkflowRunSchema.from_dict(run) for run in runs]
372
-
380
+ run_responses: List[Union[RunSchema, TeamRunSchema, WorkflowRunSchema]] = [] # type: ignore
381
+ for run in runs:
382
+ if run.get("workflow_id") is not None:
383
+ run_responses.append(WorkflowRunSchema.from_dict(run))
384
+ elif run.get("team_id") is not None:
385
+ run_responses.append(TeamRunSchema.from_dict(run))
386
+ else:
387
+ run_responses.append(RunSchema.from_dict(run))
388
+ return run_responses
373
389
  else:
374
- return [RunSchema.from_dict(run) for run in runs]
390
+ raise HTTPException(status_code=400, detail=f"Invalid session type: {session_type}")
375
391
 
376
392
  @router.delete(
377
393
  "/sessions/{session_id}",
agno/os/schema.py CHANGED
@@ -765,6 +765,7 @@ class TeamSessionDetailSchema(BaseModel):
765
765
  team_data: Optional[dict]
766
766
  created_at: Optional[datetime]
767
767
  updated_at: Optional[datetime]
768
+ total_tokens: Optional[int]
768
769
 
769
770
  @classmethod
770
771
  def from_session(cls, session: TeamSession) -> "TeamSessionDetailSchema":
@@ -826,17 +827,21 @@ class WorkflowSessionDetailSchema(BaseModel):
826
827
 
827
828
  class RunSchema(BaseModel):
828
829
  run_id: str
829
- agent_session_id: Optional[str]
830
+ parent_run_id: Optional[str]
831
+ agent_id: Optional[str]
830
832
  user_id: Optional[str]
831
833
  run_input: Optional[str]
832
834
  content: Optional[Union[str, dict]]
833
835
  run_response_format: Optional[str]
834
836
  reasoning_content: Optional[str]
837
+ reasoning_steps: Optional[List[dict]]
835
838
  metrics: Optional[dict]
836
839
  messages: Optional[List[dict]]
837
840
  tools: Optional[List[dict]]
838
841
  events: Optional[List[dict]]
839
842
  created_at: Optional[datetime]
843
+ references: Optional[List[dict]]
844
+ reasoning_messages: Optional[List[dict]]
840
845
 
841
846
  @classmethod
842
847
  def from_dict(cls, run_dict: Dict[str, Any]) -> "RunSchema":
@@ -844,16 +849,20 @@ class RunSchema(BaseModel):
844
849
  run_response_format = "text" if run_dict.get("content_type", "str") == "str" else "json"
845
850
  return cls(
846
851
  run_id=run_dict.get("run_id", ""),
847
- agent_session_id=run_dict.get("session_id", ""),
852
+ parent_run_id=run_dict.get("parent_run_id", ""),
853
+ agent_id=run_dict.get("agent_id", ""),
848
854
  user_id=run_dict.get("user_id", ""),
849
855
  run_input=run_input,
850
856
  content=run_dict.get("content", ""),
851
857
  run_response_format=run_response_format,
852
858
  reasoning_content=run_dict.get("reasoning_content", ""),
859
+ reasoning_steps=run_dict.get("reasoning_steps", []),
853
860
  metrics=run_dict.get("metrics", {}),
854
861
  messages=[message for message in run_dict.get("messages", [])] if run_dict.get("messages") else None,
855
862
  tools=[tool for tool in run_dict.get("tools", [])] if run_dict.get("tools") else None,
856
863
  events=[event for event in run_dict["events"]] if run_dict.get("events") else None,
864
+ references=run_dict.get("references", []),
865
+ reasoning_messages=run_dict.get("reasoning_messages", []),
857
866
  created_at=datetime.fromtimestamp(run_dict.get("created_at", 0), tz=timezone.utc)
858
867
  if run_dict.get("created_at") is not None
859
868
  else None,
@@ -863,8 +872,10 @@ class RunSchema(BaseModel):
863
872
  class TeamRunSchema(BaseModel):
864
873
  run_id: str
865
874
  parent_run_id: Optional[str]
875
+ team_id: Optional[str]
866
876
  content: Optional[Union[str, dict]]
867
877
  reasoning_content: Optional[str]
878
+ reasoning_steps: Optional[List[dict]]
868
879
  run_input: Optional[str]
869
880
  run_response_format: Optional[str]
870
881
  metrics: Optional[dict]
@@ -872,6 +883,8 @@ class TeamRunSchema(BaseModel):
872
883
  messages: Optional[List[dict]]
873
884
  events: Optional[List[dict]]
874
885
  created_at: Optional[datetime]
886
+ references: Optional[List[dict]]
887
+ reasoning_messages: Optional[List[dict]]
875
888
 
876
889
  @classmethod
877
890
  def from_dict(cls, run_dict: Dict[str, Any]) -> "TeamRunSchema":
@@ -880,10 +893,12 @@ class TeamRunSchema(BaseModel):
880
893
  return cls(
881
894
  run_id=run_dict.get("run_id", ""),
882
895
  parent_run_id=run_dict.get("parent_run_id", ""),
896
+ team_id=run_dict.get("team_id", ""),
883
897
  run_input=run_input,
884
898
  content=run_dict.get("content", ""),
885
899
  run_response_format=run_response_format,
886
900
  reasoning_content=run_dict.get("reasoning_content", ""),
901
+ reasoning_steps=run_dict.get("reasoning_steps", []),
887
902
  metrics=run_dict.get("metrics", {}),
888
903
  messages=[message for message in run_dict.get("messages", [])] if run_dict.get("messages") else None,
889
904
  tools=[tool for tool in run_dict.get("tools", [])] if run_dict.get("tools") else None,
@@ -891,12 +906,15 @@ class TeamRunSchema(BaseModel):
891
906
  created_at=datetime.fromtimestamp(run_dict.get("created_at", 0), tz=timezone.utc)
892
907
  if run_dict.get("created_at") is not None
893
908
  else None,
909
+ references=run_dict.get("references", []),
910
+ reasoning_messages=run_dict.get("reasoning_messages", []),
894
911
  )
895
912
 
896
913
 
897
914
  class WorkflowRunSchema(BaseModel):
898
915
  run_id: str
899
916
  run_input: Optional[str]
917
+ workflow_id: Optional[str]
900
918
  user_id: Optional[str]
901
919
  content: Optional[Union[str, dict]]
902
920
  content_type: Optional[str]
@@ -905,6 +923,10 @@ class WorkflowRunSchema(BaseModel):
905
923
  step_executor_runs: Optional[list[dict]]
906
924
  metrics: Optional[dict]
907
925
  created_at: Optional[int]
926
+ reasoning_content: Optional[str]
927
+ reasoning_steps: Optional[List[dict]]
928
+ references: Optional[List[dict]]
929
+ reasoning_messages: Optional[List[dict]]
908
930
 
909
931
  @classmethod
910
932
  def from_dict(cls, run_response: Dict[str, Any]) -> "WorkflowRunSchema":
@@ -912,6 +934,7 @@ class WorkflowRunSchema(BaseModel):
912
934
  return cls(
913
935
  run_id=run_response.get("run_id", ""),
914
936
  run_input=run_input,
937
+ workflow_id=run_response.get("workflow_id", ""),
915
938
  user_id=run_response.get("user_id", ""),
916
939
  content=run_response.get("content", ""),
917
940
  content_type=run_response.get("content_type", ""),
@@ -920,6 +943,10 @@ class WorkflowRunSchema(BaseModel):
920
943
  step_results=run_response.get("step_results", []),
921
944
  step_executor_runs=run_response.get("step_executor_runs", []),
922
945
  created_at=run_response["created_at"],
946
+ reasoning_content=run_response.get("reasoning_content", ""),
947
+ reasoning_steps=run_response.get("reasoning_steps", []),
948
+ references=run_response.get("references", []),
949
+ reasoning_messages=run_response.get("reasoning_messages", []),
923
950
  )
924
951
 
925
952
 
agno/os/utils.py CHANGED
@@ -1,5 +1,4 @@
1
1
  from typing import Any, Callable, Dict, List, Optional, Union
2
- from uuid import uuid4
3
2
 
4
3
  from fastapi import HTTPException, UploadFile
5
4
 
@@ -261,10 +260,3 @@ def _generate_schema_from_params(params: Dict[str, Any]) -> Dict[str, Any]:
261
260
  schema["required"] = required
262
261
 
263
262
  return schema
264
-
265
-
266
- def generate_id(name: Optional[str] = None) -> str:
267
- if name:
268
- return name.lower().replace(" ", "-").replace("_", "-")
269
- else:
270
- return str(uuid4())
agno/run/agent.py CHANGED
@@ -559,7 +559,7 @@ class RunOutput:
559
559
  events = [run_output_event_from_dict(event) for event in events] if events else None
560
560
 
561
561
  messages = data.pop("messages", None)
562
- messages = [Message.model_validate(message) for message in messages] if messages else None
562
+ messages = [Message.from_dict(message) for message in messages] if messages else None
563
563
 
564
564
  citations = data.pop("citations", None)
565
565
  citations = Citations.model_validate(citations) if citations else None
@@ -591,7 +591,7 @@ class RunOutput:
591
591
  additional_input = data.pop("additional_input", None)
592
592
 
593
593
  if additional_input is not None:
594
- additional_input = [Message.model_validate(message) for message in additional_input]
594
+ additional_input = [Message.from_dict(message) for message in additional_input]
595
595
 
596
596
  reasoning_steps = data.pop("reasoning_steps", None)
597
597
  if reasoning_steps is not None:
@@ -599,7 +599,7 @@ class RunOutput:
599
599
 
600
600
  reasoning_messages = data.pop("reasoning_messages", None)
601
601
  if reasoning_messages is not None:
602
- reasoning_messages = [Message.model_validate(message) for message in reasoning_messages]
602
+ reasoning_messages = [Message.from_dict(message) for message in reasoning_messages]
603
603
 
604
604
  references = data.pop("references", None)
605
605
  if references is not None:
agno/run/team.py CHANGED
@@ -519,7 +519,7 @@ class TeamRunOutput:
519
519
  events = final_events
520
520
 
521
521
  messages = data.pop("messages", None)
522
- messages = [Message.model_validate(message) for message in messages] if messages else None
522
+ messages = [Message.from_dict(message) for message in messages] if messages else None
523
523
 
524
524
  member_responses = data.pop("member_responses", [])
525
525
  parsed_member_responses: List[Union["TeamRunOutput", RunOutput]] = []
@@ -532,7 +532,7 @@ class TeamRunOutput:
532
532
 
533
533
  additional_input = data.pop("additional_input", None)
534
534
  if additional_input is not None:
535
- additional_input = [Message.model_validate(message) for message in additional_input]
535
+ additional_input = [Message.from_dict(message) for message in additional_input]
536
536
 
537
537
  reasoning_steps = data.pop("reasoning_steps", None)
538
538
  if reasoning_steps is not None:
@@ -540,7 +540,7 @@ class TeamRunOutput:
540
540
 
541
541
  reasoning_messages = data.pop("reasoning_messages", None)
542
542
  if reasoning_messages is not None:
543
- reasoning_messages = [Message.model_validate(message) for message in reasoning_messages]
543
+ reasoning_messages = [Message.from_dict(message) for message in reasoning_messages]
544
544
 
545
545
  references = data.pop("references", None)
546
546
  if references is not None:
agno/run/workflow.py CHANGED
@@ -7,7 +7,7 @@ from pydantic import BaseModel
7
7
 
8
8
  from agno.media import Audio, Image, Video
9
9
  from agno.run.agent import RunOutput
10
- from agno.run.base import RunStatus
10
+ from agno.run.base import BaseRunOutputEvent, RunStatus
11
11
  from agno.run.team import TeamRunOutput
12
12
  from agno.utils.log import log_error
13
13
 
@@ -53,7 +53,7 @@ class WorkflowRunEvent(str, Enum):
53
53
 
54
54
 
55
55
  @dataclass
56
- class BaseWorkflowRunOutputEvent:
56
+ class BaseWorkflowRunOutputEvent(BaseRunOutputEvent):
57
57
  """Base class for all workflow run response events"""
58
58
 
59
59
  created_at: int = field(default_factory=lambda: int(time()))
@@ -75,19 +75,23 @@ class BaseWorkflowRunOutputEvent:
75
75
 
76
76
  # Handle StepOutput fields that contain Message objects
77
77
  if hasattr(self, "step_results") and self.step_results is not None:
78
- _dict["step_results"] = [step.to_dict() for step in self.step_results]
78
+ _dict["step_results"] = [step.to_dict() if hasattr(step, "to_dict") else step for step in self.step_results]
79
79
 
80
80
  if hasattr(self, "step_response") and self.step_response is not None:
81
- _dict["step_response"] = self.step_response.to_dict()
81
+ _dict["step_response"] = (
82
+ self.step_response.to_dict() if hasattr(self.step_response, "to_dict") else self.step_response
83
+ )
82
84
 
83
85
  if hasattr(self, "iteration_results") and self.iteration_results is not None:
84
- _dict["iteration_results"] = [step.to_dict() for step in self.iteration_results]
86
+ _dict["iteration_results"] = [
87
+ step.to_dict() if hasattr(step, "to_dict") else step for step in self.iteration_results
88
+ ]
85
89
 
86
90
  if hasattr(self, "all_results") and self.all_results is not None:
87
- _dict["all_results"] = [[step.to_dict() for step in iteration] for iteration in self.all_results]
88
-
89
- if hasattr(self, "step_results") and self.step_results is not None:
90
- _dict["step_results"] = [step.to_dict() for step in self.step_results]
91
+ _dict["all_results"] = [
92
+ [step.to_dict() if hasattr(step, "to_dict") else step for step in iteration]
93
+ for iteration in self.all_results
94
+ ]
91
95
 
92
96
  return _dict
93
97
 
@@ -420,6 +424,39 @@ WorkflowRunOutputEvent = Union[
420
424
  CustomEvent,
421
425
  ]
422
426
 
427
+ # Map event string to dataclass for workflow events
428
+ WORKFLOW_RUN_EVENT_TYPE_REGISTRY = {
429
+ WorkflowRunEvent.workflow_started.value: WorkflowStartedEvent,
430
+ WorkflowRunEvent.workflow_completed.value: WorkflowCompletedEvent,
431
+ WorkflowRunEvent.workflow_cancelled.value: WorkflowCancelledEvent,
432
+ WorkflowRunEvent.workflow_error.value: WorkflowErrorEvent,
433
+ WorkflowRunEvent.step_started.value: StepStartedEvent,
434
+ WorkflowRunEvent.step_completed.value: StepCompletedEvent,
435
+ WorkflowRunEvent.step_error.value: StepErrorEvent,
436
+ WorkflowRunEvent.loop_execution_started.value: LoopExecutionStartedEvent,
437
+ WorkflowRunEvent.loop_iteration_started.value: LoopIterationStartedEvent,
438
+ WorkflowRunEvent.loop_iteration_completed.value: LoopIterationCompletedEvent,
439
+ WorkflowRunEvent.loop_execution_completed.value: LoopExecutionCompletedEvent,
440
+ WorkflowRunEvent.parallel_execution_started.value: ParallelExecutionStartedEvent,
441
+ WorkflowRunEvent.parallel_execution_completed.value: ParallelExecutionCompletedEvent,
442
+ WorkflowRunEvent.condition_execution_started.value: ConditionExecutionStartedEvent,
443
+ WorkflowRunEvent.condition_execution_completed.value: ConditionExecutionCompletedEvent,
444
+ WorkflowRunEvent.router_execution_started.value: RouterExecutionStartedEvent,
445
+ WorkflowRunEvent.router_execution_completed.value: RouterExecutionCompletedEvent,
446
+ WorkflowRunEvent.steps_execution_started.value: StepsExecutionStartedEvent,
447
+ WorkflowRunEvent.steps_execution_completed.value: StepsExecutionCompletedEvent,
448
+ WorkflowRunEvent.step_output.value: StepOutputEvent,
449
+ WorkflowRunEvent.custom_event.value: CustomEvent,
450
+ }
451
+
452
+
453
+ def workflow_run_output_event_from_dict(data: dict) -> BaseWorkflowRunOutputEvent:
454
+ event_type = data.get("event", "")
455
+ cls = WORKFLOW_RUN_EVENT_TYPE_REGISTRY.get(event_type)
456
+ if not cls:
457
+ raise ValueError(f"Unknown workflow event type: {event_type}")
458
+ return cls.from_dict(data) # type: ignore
459
+
423
460
 
424
461
  @dataclass
425
462
  class WorkflowRunOutput:
@@ -568,7 +605,24 @@ class WorkflowRunOutput:
568
605
  response_audio = data.pop("response_audio", None)
569
606
  response_audio = Audio.model_validate(response_audio) if response_audio else None
570
607
 
571
- events = data.pop("events", [])
608
+ events_data = data.pop("events", [])
609
+ final_events = []
610
+ for event in events_data or []:
611
+ if "agent_id" in event:
612
+ # Agent event from agent step
613
+ from agno.run.agent import run_output_event_from_dict
614
+
615
+ event = run_output_event_from_dict(event)
616
+ elif "team_id" in event:
617
+ # Team event from team step
618
+ from agno.run.team import team_run_output_event_from_dict
619
+
620
+ event = team_run_output_event_from_dict(event)
621
+ else:
622
+ # Pure workflow event
623
+ event = workflow_run_output_event_from_dict(event)
624
+ final_events.append(event)
625
+ events = final_events
572
626
 
573
627
  return cls(
574
628
  step_results=parsed_step_results,
agno/session/team.py CHANGED
@@ -92,6 +92,7 @@ class TeamSession:
92
92
  if messages is None:
93
93
  return
94
94
 
95
+ # Make message duration None
95
96
  for m in messages or []:
96
97
  if m.metrics is not None:
97
98
  m.metrics.duration = None