agno 2.0.2__py3-none-any.whl → 2.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. agno/agent/agent.py +164 -87
  2. agno/db/dynamo/dynamo.py +8 -0
  3. agno/db/firestore/firestore.py +8 -1
  4. agno/db/gcs_json/gcs_json_db.py +9 -0
  5. agno/db/json/json_db.py +8 -0
  6. agno/db/mongo/mongo.py +10 -1
  7. agno/db/mysql/mysql.py +10 -0
  8. agno/db/postgres/postgres.py +16 -8
  9. agno/db/redis/redis.py +6 -0
  10. agno/db/singlestore/schemas.py +1 -1
  11. agno/db/singlestore/singlestore.py +8 -1
  12. agno/db/sqlite/sqlite.py +9 -1
  13. agno/db/utils.py +14 -0
  14. agno/knowledge/chunking/fixed.py +1 -1
  15. agno/knowledge/knowledge.py +91 -65
  16. agno/knowledge/reader/base.py +3 -0
  17. agno/knowledge/reader/csv_reader.py +1 -1
  18. agno/knowledge/reader/json_reader.py +1 -1
  19. agno/knowledge/reader/markdown_reader.py +5 -5
  20. agno/knowledge/reader/s3_reader.py +0 -12
  21. agno/knowledge/reader/text_reader.py +5 -5
  22. agno/models/base.py +2 -2
  23. agno/models/cerebras/cerebras.py +5 -3
  24. agno/models/cerebras/cerebras_openai.py +5 -3
  25. agno/models/google/gemini.py +33 -11
  26. agno/models/litellm/chat.py +1 -1
  27. agno/models/openai/chat.py +3 -0
  28. agno/models/openai/responses.py +81 -40
  29. agno/models/response.py +5 -0
  30. agno/models/siliconflow/__init__.py +5 -0
  31. agno/models/siliconflow/siliconflow.py +25 -0
  32. agno/os/app.py +4 -1
  33. agno/os/auth.py +24 -14
  34. agno/os/interfaces/slack/router.py +1 -1
  35. agno/os/interfaces/whatsapp/router.py +2 -0
  36. agno/os/router.py +187 -76
  37. agno/os/routers/evals/utils.py +9 -9
  38. agno/os/routers/health.py +26 -0
  39. agno/os/routers/knowledge/knowledge.py +11 -11
  40. agno/os/routers/session/session.py +24 -8
  41. agno/os/schema.py +8 -2
  42. agno/run/agent.py +5 -2
  43. agno/run/base.py +6 -3
  44. agno/run/team.py +11 -3
  45. agno/run/workflow.py +69 -12
  46. agno/session/team.py +1 -0
  47. agno/team/team.py +196 -93
  48. agno/tools/mcp.py +1 -0
  49. agno/tools/mem0.py +11 -17
  50. agno/tools/memory.py +419 -0
  51. agno/tools/workflow.py +279 -0
  52. agno/utils/audio.py +27 -0
  53. agno/utils/common.py +90 -1
  54. agno/utils/print_response/agent.py +6 -2
  55. agno/utils/streamlit.py +14 -8
  56. agno/vectordb/chroma/chromadb.py +8 -2
  57. agno/workflow/step.py +111 -13
  58. agno/workflow/workflow.py +16 -13
  59. {agno-2.0.2.dist-info → agno-2.0.4.dist-info}/METADATA +1 -1
  60. {agno-2.0.2.dist-info → agno-2.0.4.dist-info}/RECORD +63 -58
  61. {agno-2.0.2.dist-info → agno-2.0.4.dist-info}/WHEEL +0 -0
  62. {agno-2.0.2.dist-info → agno-2.0.4.dist-info}/licenses/LICENSE +0 -0
  63. {agno-2.0.2.dist-info → agno-2.0.4.dist-info}/top_level.txt +0 -0
@@ -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
@@ -826,7 +826,8 @@ class WorkflowSessionDetailSchema(BaseModel):
826
826
 
827
827
  class RunSchema(BaseModel):
828
828
  run_id: str
829
- agent_session_id: Optional[str]
829
+ parent_run_id: Optional[str]
830
+ agent_id: Optional[str]
830
831
  user_id: Optional[str]
831
832
  run_input: Optional[str]
832
833
  content: Optional[Union[str, dict]]
@@ -844,7 +845,8 @@ class RunSchema(BaseModel):
844
845
  run_response_format = "text" if run_dict.get("content_type", "str") == "str" else "json"
845
846
  return cls(
846
847
  run_id=run_dict.get("run_id", ""),
847
- agent_session_id=run_dict.get("session_id", ""),
848
+ parent_run_id=run_dict.get("parent_run_id", ""),
849
+ agent_id=run_dict.get("agent_id", ""),
848
850
  user_id=run_dict.get("user_id", ""),
849
851
  run_input=run_input,
850
852
  content=run_dict.get("content", ""),
@@ -863,6 +865,7 @@ class RunSchema(BaseModel):
863
865
  class TeamRunSchema(BaseModel):
864
866
  run_id: str
865
867
  parent_run_id: Optional[str]
868
+ team_id: Optional[str]
866
869
  content: Optional[Union[str, dict]]
867
870
  reasoning_content: Optional[str]
868
871
  run_input: Optional[str]
@@ -880,6 +883,7 @@ class TeamRunSchema(BaseModel):
880
883
  return cls(
881
884
  run_id=run_dict.get("run_id", ""),
882
885
  parent_run_id=run_dict.get("parent_run_id", ""),
886
+ team_id=run_dict.get("team_id", ""),
883
887
  run_input=run_input,
884
888
  content=run_dict.get("content", ""),
885
889
  run_response_format=run_response_format,
@@ -897,6 +901,7 @@ class TeamRunSchema(BaseModel):
897
901
  class WorkflowRunSchema(BaseModel):
898
902
  run_id: str
899
903
  run_input: Optional[str]
904
+ workflow_id: Optional[str]
900
905
  user_id: Optional[str]
901
906
  content: Optional[Union[str, dict]]
902
907
  content_type: Optional[str]
@@ -912,6 +917,7 @@ class WorkflowRunSchema(BaseModel):
912
917
  return cls(
913
918
  run_id=run_response.get("run_id", ""),
914
919
  run_input=run_input,
920
+ workflow_id=run_response.get("workflow_id", ""),
915
921
  user_id=run_response.get("user_id", ""),
916
922
  content=run_response.get("content", ""),
917
923
  content_type=run_response.get("content_type", ""),
agno/run/agent.py CHANGED
@@ -536,7 +536,7 @@ class RunOutput:
536
536
 
537
537
  return _dict
538
538
 
539
- def to_json(self) -> str:
539
+ def to_json(self, separators=(", ", ": "), indent: Optional[int] = 2) -> str:
540
540
  import json
541
541
 
542
542
  try:
@@ -545,7 +545,10 @@ class RunOutput:
545
545
  logger.error("Failed to convert response to json", exc_info=True)
546
546
  raise
547
547
 
548
- return json.dumps(_dict, indent=2)
548
+ if indent is None:
549
+ return json.dumps(_dict, separators=separators)
550
+ else:
551
+ return json.dumps(_dict, indent=indent, separators=separators)
549
552
 
550
553
  @classmethod
551
554
  def from_dict(cls, data: Dict[str, Any]) -> "RunOutput":
agno/run/base.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from dataclasses import asdict, dataclass
2
2
  from enum import Enum
3
- from typing import Any, Dict
3
+ from typing import Any, Dict, Optional
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
@@ -115,7 +115,7 @@ class BaseRunOutputEvent:
115
115
 
116
116
  return _dict
117
117
 
118
- def to_json(self) -> str:
118
+ def to_json(self, separators=(", ", ": "), indent: Optional[int] = 2) -> str:
119
119
  import json
120
120
 
121
121
  try:
@@ -124,7 +124,10 @@ class BaseRunOutputEvent:
124
124
  log_error("Failed to convert response event to json", exc_info=True)
125
125
  raise
126
126
 
127
- return json.dumps(_dict, indent=2)
127
+ if indent is None:
128
+ return json.dumps(_dict, separators=separators)
129
+ else:
130
+ return json.dumps(_dict, indent=indent, separators=separators)
128
131
 
129
132
  @classmethod
130
133
  def from_dict(cls, data: Dict[str, Any]):
agno/run/team.py CHANGED
@@ -12,6 +12,7 @@ from agno.models.response import ToolExecution
12
12
  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
+ from agno.utils.log import log_error
15
16
 
16
17
 
17
18
  class TeamRunEvent(str, Enum):
@@ -488,12 +489,19 @@ class TeamRunOutput:
488
489
 
489
490
  return _dict
490
491
 
491
- def to_json(self) -> str:
492
+ def to_json(self, separators=(", ", ": "), indent: Optional[int] = 2) -> str:
492
493
  import json
493
494
 
494
- _dict = self.to_dict()
495
+ try:
496
+ _dict = self.to_dict()
497
+ except Exception:
498
+ log_error("Failed to convert response to json", exc_info=True)
499
+ raise
495
500
 
496
- return json.dumps(_dict, indent=2)
501
+ if indent is None:
502
+ return json.dumps(_dict, separators=separators)
503
+ else:
504
+ return json.dumps(_dict, indent=indent, separators=separators)
497
505
 
498
506
  @classmethod
499
507
  def from_dict(cls, data: Dict[str, Any]) -> "TeamRunOutput":
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,23 +75,27 @@ 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
 
94
- def to_json(self) -> str:
98
+ def to_json(self, separators=(", ", ": "), indent: Optional[int] = 2) -> str:
95
99
  import json
96
100
 
97
101
  try:
@@ -100,7 +104,10 @@ class BaseWorkflowRunOutputEvent:
100
104
  log_error("Failed to convert response to json", exc_info=True)
101
105
  raise
102
106
 
103
- return json.dumps(_dict, indent=2)
107
+ if indent is None:
108
+ return json.dumps(_dict, separators=separators)
109
+ else:
110
+ return json.dumps(_dict, indent=indent, separators=separators)
104
111
 
105
112
  @property
106
113
  def is_cancelled(self):
@@ -417,6 +424,39 @@ WorkflowRunOutputEvent = Union[
417
424
  CustomEvent,
418
425
  ]
419
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
+
420
460
 
421
461
  @dataclass
422
462
  class WorkflowRunOutput:
@@ -565,7 +605,24 @@ class WorkflowRunOutput:
565
605
  response_audio = data.pop("response_audio", None)
566
606
  response_audio = Audio.model_validate(response_audio) if response_audio else None
567
607
 
568
- 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
569
626
 
570
627
  return cls(
571
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