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/agent/agent.py +2 -1
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/reader/base.py +3 -0
- agno/knowledge/reader/csv_reader.py +1 -1
- agno/knowledge/reader/json_reader.py +1 -1
- agno/knowledge/reader/markdown_reader.py +5 -5
- agno/knowledge/reader/s3_reader.py +0 -12
- agno/knowledge/reader/text_reader.py +5 -5
- agno/models/cerebras/cerebras.py +5 -3
- agno/models/cerebras/cerebras_openai.py +5 -3
- agno/models/google/gemini.py +33 -11
- agno/models/litellm/chat.py +1 -1
- agno/models/openai/responses.py +75 -40
- agno/os/interfaces/slack/router.py +1 -1
- agno/os/interfaces/whatsapp/router.py +2 -0
- agno/os/router.py +60 -22
- agno/run/agent.py +5 -2
- agno/run/base.py +6 -3
- agno/run/team.py +11 -3
- agno/run/workflow.py +5 -2
- agno/team/team.py +4 -1
- agno/tools/mcp.py +1 -0
- agno/tools/memory.py +391 -0
- agno/tools/workflow.py +279 -0
- agno/utils/audio.py +27 -0
- agno/utils/print_response/agent.py +6 -2
- {agno-2.0.2.dist-info → agno-2.0.3.dist-info}/METADATA +1 -1
- {agno-2.0.2.dist-info → agno-2.0.3.dist-info}/RECORD +31 -29
- {agno-2.0.2.dist-info → agno-2.0.3.dist-info}/WHEEL +0 -0
- {agno-2.0.2.dist-info → agno-2.0.3.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.2.dist-info → agno-2.0.3.dist-info}/top_level.txt +0 -0
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|