agno 2.0.2__py3-none-any.whl → 2.0.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.
agno/os/router.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import json
2
- from typing import TYPE_CHECKING, Any, AsyncGenerator, Dict, List, Optional, Union, cast
2
+ from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Dict, List, Optional, Union, cast
3
3
  from uuid import uuid4
4
4
 
5
5
  from fastapi import (
@@ -8,6 +8,7 @@ from fastapi import (
8
8
  File,
9
9
  Form,
10
10
  HTTPException,
11
+ Request,
11
12
  UploadFile,
12
13
  WebSocket,
13
14
  )
@@ -45,9 +46,10 @@ from agno.os.utils import (
45
46
  process_image,
46
47
  process_video,
47
48
  )
48
- from agno.run.agent import RunErrorEvent, RunOutput
49
+ from agno.run.agent import RunErrorEvent, RunOutput, RunOutputEvent
49
50
  from agno.run.team import RunErrorEvent as TeamRunErrorEvent
50
- from agno.run.workflow import WorkflowErrorEvent
51
+ from agno.run.team import TeamRunOutputEvent
52
+ from agno.run.workflow import WorkflowErrorEvent, WorkflowRunOutputEvent
51
53
  from agno.team.team import Team
52
54
  from agno.utils.log import log_debug, log_error, log_warning, logger
53
55
  from agno.workflow.workflow import Workflow
@@ -56,11 +58,29 @@ if TYPE_CHECKING:
56
58
  from agno.os.app import AgentOS
57
59
 
58
60
 
59
- def format_sse_event(json_data: str) -> str:
61
+ async def _get_request_kwargs(request: Request, endpoint_func: Callable) -> Dict[str, Any]:
62
+ """Given a Request and an endpoint function, return a dictionary with all extra form data fields.
63
+ Args:
64
+ request: The FastAPI Request object
65
+ endpoint_func: The function exposing the endpoint that received the request
66
+
67
+ Returns:
68
+ A dictionary of kwargs
69
+ """
70
+ import inspect
71
+
72
+ form_data = await request.form()
73
+ sig = inspect.signature(endpoint_func)
74
+ known_fields = set(sig.parameters.keys())
75
+ kwargs = {key: value for key, value in form_data.items() if key not in known_fields}
76
+ return kwargs
77
+
78
+
79
+ def format_sse_event(event: Union[RunOutputEvent, TeamRunOutputEvent, WorkflowRunOutputEvent]) -> str:
60
80
  """Parse JSON data into SSE-compliant format.
61
81
 
62
82
  Args:
63
- json_data: JSON string containing the event data
83
+ event_dict: Dictionary containing the event data
64
84
 
65
85
  Returns:
66
86
  SSE-formatted response:
@@ -75,14 +95,15 @@ def format_sse_event(json_data: str) -> str:
75
95
  """
76
96
  try:
77
97
  # Parse the JSON to extract the event type
78
- data = json.loads(json_data)
79
- event_type = data.get("event", "message")
98
+ event_type = event.event or "message"
99
+
100
+ # Serialize to valid JSON with double quotes and no newlines
101
+ clean_json = event.to_json(separators=(",", ":"), indent=None)
80
102
 
81
- # Format as SSE: event: <event_type>\ndata: <json_data>\n\n
82
- return f"event: {event_type}\ndata: {json_data}\n\n"
83
- except (json.JSONDecodeError, KeyError):
84
- # Fallback to generic message event if parsing fails
85
- return f"event: message\ndata: {json_data}\n\n"
103
+ return f"event: {event_type}\ndata: {clean_json}\n\n"
104
+ except json.JSONDecodeError:
105
+ clean_json = event.to_json(separators=(",", ":"), indent=None)
106
+ return f"event: message\ndata: {clean_json}\n\n"
86
107
 
87
108
 
88
109
  class WebSocketManager:
@@ -143,6 +164,7 @@ async def agent_response_streamer(
143
164
  audio: Optional[List[Audio]] = None,
144
165
  videos: Optional[List[Video]] = None,
145
166
  files: Optional[List[FileMedia]] = None,
167
+ **kwargs: Any,
146
168
  ) -> AsyncGenerator:
147
169
  try:
148
170
  run_response = agent.arun(
@@ -155,9 +177,10 @@ async def agent_response_streamer(
155
177
  files=files,
156
178
  stream=True,
157
179
  stream_intermediate_steps=True,
180
+ **kwargs,
158
181
  )
159
182
  async for run_response_chunk in run_response:
160
- yield format_sse_event(run_response_chunk.to_json())
183
+ yield format_sse_event(run_response_chunk) # type: ignore
161
184
 
162
185
  except Exception as e:
163
186
  import traceback
@@ -166,7 +189,7 @@ async def agent_response_streamer(
166
189
  error_response = RunErrorEvent(
167
190
  content=str(e),
168
191
  )
169
- yield format_sse_event(error_response.to_json())
192
+ yield format_sse_event(error_response)
170
193
 
171
194
 
172
195
  async def agent_continue_response_streamer(
@@ -186,7 +209,7 @@ async def agent_continue_response_streamer(
186
209
  stream_intermediate_steps=True,
187
210
  )
188
211
  async for run_response_chunk in continue_response:
189
- yield format_sse_event(run_response_chunk.to_json())
212
+ yield format_sse_event(run_response_chunk) # type: ignore
190
213
 
191
214
  except Exception as e:
192
215
  import traceback
@@ -195,7 +218,7 @@ async def agent_continue_response_streamer(
195
218
  error_response = RunErrorEvent(
196
219
  content=str(e),
197
220
  )
198
- yield format_sse_event(error_response.to_json())
221
+ yield format_sse_event(error_response)
199
222
  return
200
223
 
201
224
 
@@ -208,6 +231,7 @@ async def team_response_streamer(
208
231
  audio: Optional[List[Audio]] = None,
209
232
  videos: Optional[List[Video]] = None,
210
233
  files: Optional[List[FileMedia]] = None,
234
+ **kwargs: Any,
211
235
  ) -> AsyncGenerator:
212
236
  """Run the given team asynchronously and yield its response"""
213
237
  try:
@@ -221,9 +245,10 @@ async def team_response_streamer(
221
245
  files=files,
222
246
  stream=True,
223
247
  stream_intermediate_steps=True,
248
+ **kwargs,
224
249
  )
225
250
  async for run_response_chunk in run_response:
226
- yield format_sse_event(run_response_chunk.to_json())
251
+ yield format_sse_event(run_response_chunk) # type: ignore
227
252
 
228
253
  except Exception as e:
229
254
  import traceback
@@ -232,7 +257,7 @@ async def team_response_streamer(
232
257
  error_response = TeamRunErrorEvent(
233
258
  content=str(e),
234
259
  )
235
- yield format_sse_event(error_response.to_json())
260
+ yield format_sse_event(error_response)
236
261
  return
237
262
 
238
263
 
@@ -296,7 +321,7 @@ async def workflow_response_streamer(
296
321
  )
297
322
 
298
323
  async for run_response_chunk in run_response:
299
- yield format_sse_event(run_response_chunk.to_json())
324
+ yield format_sse_event(run_response_chunk) # type: ignore
300
325
 
301
326
  except Exception as e:
302
327
  import traceback
@@ -305,7 +330,7 @@ async def workflow_response_streamer(
305
330
  error_response = WorkflowErrorEvent(
306
331
  error=str(e),
307
332
  )
308
- yield format_sse_event(error_response.to_json())
333
+ yield format_sse_event(error_response)
309
334
  return
310
335
 
311
336
 
@@ -538,12 +563,15 @@ def get_base_router(
538
563
  )
539
564
  async def create_agent_run(
540
565
  agent_id: str,
566
+ request: Request,
541
567
  message: str = Form(...),
542
568
  stream: bool = Form(False),
543
569
  session_id: Optional[str] = Form(None),
544
570
  user_id: Optional[str] = Form(None),
545
571
  files: Optional[List[UploadFile]] = File(None),
546
572
  ):
573
+ kwargs = await _get_request_kwargs(request, create_agent_run)
574
+
547
575
  agent = get_agent_by_id(agent_id, os.agents)
548
576
  if agent is None:
549
577
  raise HTTPException(status_code=404, detail="Agent not found")
@@ -620,6 +648,7 @@ def get_base_router(
620
648
  audio=base64_audios if base64_audios else None,
621
649
  videos=base64_videos if base64_videos else None,
622
650
  files=input_files if input_files else None,
651
+ **kwargs,
623
652
  ),
624
653
  media_type="text/event-stream",
625
654
  )
@@ -635,6 +664,7 @@ def get_base_router(
635
664
  videos=base64_videos if base64_videos else None,
636
665
  files=input_files if input_files else None,
637
666
  stream=False,
667
+ **kwargs,
638
668
  ),
639
669
  )
640
670
  return run_response.to_dict()
@@ -880,6 +910,7 @@ def get_base_router(
880
910
  )
881
911
  async def create_team_run(
882
912
  team_id: str,
913
+ request: Request,
883
914
  message: str = Form(...),
884
915
  stream: bool = Form(True),
885
916
  monitor: bool = Form(True),
@@ -887,7 +918,10 @@ def get_base_router(
887
918
  user_id: Optional[str] = Form(None),
888
919
  files: Optional[List[UploadFile]] = File(None),
889
920
  ):
890
- logger.debug(f"Creating team run: {message} {session_id} {monitor} {user_id} {team_id} {files}")
921
+ kwargs = await _get_request_kwargs(request, create_team_run)
922
+
923
+ logger.debug(f"Creating team run: {message=} {session_id=} {monitor=} {user_id=} {team_id=} {files=} {kwargs=}")
924
+
891
925
  team = get_team_by_id(team_id, os.teams)
892
926
  if team is None:
893
927
  raise HTTPException(status_code=404, detail="Team not found")
@@ -962,6 +996,7 @@ def get_base_router(
962
996
  audio=base64_audios if base64_audios else None,
963
997
  videos=base64_videos if base64_videos else None,
964
998
  files=document_files if document_files else None,
999
+ **kwargs,
965
1000
  ),
966
1001
  media_type="text/event-stream",
967
1002
  )
@@ -975,6 +1010,7 @@ def get_base_router(
975
1010
  videos=base64_videos if base64_videos else None,
976
1011
  files=document_files if document_files else None,
977
1012
  stream=False,
1013
+ **kwargs,
978
1014
  )
979
1015
  return run_response.to_dict()
980
1016
 
@@ -1328,12 +1364,14 @@ def get_base_router(
1328
1364
  )
1329
1365
  async def create_workflow_run(
1330
1366
  workflow_id: str,
1367
+ request: Request,
1331
1368
  message: str = Form(...),
1332
1369
  stream: bool = Form(True),
1333
1370
  session_id: Optional[str] = Form(None),
1334
1371
  user_id: Optional[str] = Form(None),
1335
- **kwargs: Any,
1336
1372
  ):
1373
+ kwargs = await _get_request_kwargs(request, create_workflow_run)
1374
+
1337
1375
  # Retrieve the workflow by ID
1338
1376
  workflow = get_workflow_by_id(workflow_id, os.workflows)
1339
1377
  if workflow is None:
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
@@ -91,7 +91,7 @@ class BaseWorkflowRunOutputEvent:
91
91
 
92
92
  return _dict
93
93
 
94
- def to_json(self) -> str:
94
+ def to_json(self, separators=(", ", ": "), indent: Optional[int] = 2) -> str:
95
95
  import json
96
96
 
97
97
  try:
@@ -100,7 +100,10 @@ class BaseWorkflowRunOutputEvent:
100
100
  log_error("Failed to convert response to json", exc_info=True)
101
101
  raise
102
102
 
103
- return json.dumps(_dict, indent=2)
103
+ if indent is None:
104
+ return json.dumps(_dict, separators=separators)
105
+ else:
106
+ return json.dumps(_dict, indent=indent, separators=separators)
104
107
 
105
108
  @property
106
109
  def is_cancelled(self):
agno/team/team.py CHANGED
@@ -408,6 +408,7 @@ class Team:
408
408
  enable_agentic_memory: bool = False,
409
409
  enable_user_memories: bool = False,
410
410
  add_memories_to_context: Optional[bool] = None,
411
+ memory_manager: Optional[MemoryManager] = None,
411
412
  enable_session_summaries: bool = False,
412
413
  session_summary_manager: Optional[SessionSummaryManager] = None,
413
414
  add_session_summary_to_context: Optional[bool] = None,
@@ -505,6 +506,7 @@ class Team:
505
506
  self.enable_agentic_memory = enable_agentic_memory
506
507
  self.enable_user_memories = enable_user_memories
507
508
  self.add_memories_to_context = add_memories_to_context
509
+ self.memory_manager = memory_manager
508
510
  self.enable_session_summaries = enable_session_summaries
509
511
  self.session_summary_manager = session_summary_manager
510
512
  self.add_session_summary_to_context = add_session_summary_to_context
@@ -6040,7 +6042,8 @@ class Team:
6040
6042
  session = self.get_session(session_id=session_id) # type: ignore
6041
6043
 
6042
6044
  if session is None:
6043
- raise Exception("Session not found")
6045
+ log_warning(f"Session {session_id} not found")
6046
+ return []
6044
6047
 
6045
6048
  # Only filter by agent_id if this is part of a team
6046
6049
  return session.get_messages_from_last_n_runs(
agno/tools/mcp.py CHANGED
@@ -51,6 +51,7 @@ def _prepare_command(command: str) -> list[str]:
51
51
  "deno",
52
52
  "java",
53
53
  "ruby",
54
+ "docker",
54
55
  }
55
56
 
56
57
  executable = parts[0].split("/")[-1]