agno 1.7.3__py3-none-any.whl → 1.7.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.
- agno/agent/agent.py +113 -31
- agno/api/schemas/agent.py +1 -0
- agno/api/schemas/team.py +1 -0
- agno/app/fastapi/app.py +1 -1
- agno/app/fastapi/async_router.py +67 -16
- agno/app/fastapi/sync_router.py +80 -14
- agno/app/playground/app.py +2 -1
- agno/app/playground/async_router.py +97 -28
- agno/app/playground/operator.py +25 -19
- agno/app/playground/schemas.py +1 -0
- agno/app/playground/sync_router.py +93 -26
- agno/knowledge/agent.py +39 -2
- agno/knowledge/combined.py +1 -1
- agno/run/base.py +2 -0
- agno/run/response.py +4 -4
- agno/run/team.py +6 -6
- agno/run/v2/__init__.py +0 -0
- agno/run/v2/workflow.py +563 -0
- agno/storage/base.py +4 -4
- agno/storage/dynamodb.py +74 -10
- agno/storage/firestore.py +6 -1
- agno/storage/gcs_json.py +8 -2
- agno/storage/json.py +20 -5
- agno/storage/mongodb.py +14 -5
- agno/storage/mysql.py +56 -17
- agno/storage/postgres.py +55 -13
- agno/storage/redis.py +25 -5
- agno/storage/session/__init__.py +3 -1
- agno/storage/session/agent.py +3 -0
- agno/storage/session/team.py +3 -0
- agno/storage/session/v2/__init__.py +5 -0
- agno/storage/session/v2/workflow.py +89 -0
- agno/storage/singlestore.py +74 -12
- agno/storage/sqlite.py +64 -18
- agno/storage/yaml.py +26 -6
- agno/team/team.py +105 -21
- agno/tools/decorator.py +45 -2
- agno/tools/function.py +16 -12
- agno/utils/log.py +12 -0
- agno/utils/message.py +5 -1
- agno/utils/openai.py +20 -5
- agno/utils/pprint.py +34 -8
- agno/vectordb/surrealdb/__init__.py +3 -0
- agno/vectordb/surrealdb/surrealdb.py +493 -0
- agno/workflow/v2/__init__.py +21 -0
- agno/workflow/v2/condition.py +554 -0
- agno/workflow/v2/loop.py +602 -0
- agno/workflow/v2/parallel.py +659 -0
- agno/workflow/v2/router.py +521 -0
- agno/workflow/v2/step.py +861 -0
- agno/workflow/v2/steps.py +465 -0
- agno/workflow/v2/types.py +347 -0
- agno/workflow/v2/workflow.py +3132 -0
- {agno-1.7.3.dist-info → agno-1.7.5.dist-info}/METADATA +4 -1
- {agno-1.7.3.dist-info → agno-1.7.5.dist-info}/RECORD +59 -44
- {agno-1.7.3.dist-info → agno-1.7.5.dist-info}/WHEEL +0 -0
- {agno-1.7.3.dist-info → agno-1.7.5.dist-info}/entry_points.txt +0 -0
- {agno-1.7.3.dist-info → agno-1.7.5.dist-info}/licenses/LICENSE +0 -0
- {agno-1.7.3.dist-info → agno-1.7.5.dist-info}/top_level.txt +0 -0
agno/app/fastapi/sync_router.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from dataclasses import asdict
|
|
3
3
|
from io import BytesIO
|
|
4
|
-
from typing import Any, Dict, Generator, List, Optional, cast
|
|
4
|
+
from typing import Any, Dict, Generator, List, Optional, Union, cast
|
|
5
5
|
from uuid import uuid4
|
|
6
6
|
|
|
7
7
|
from fastapi import APIRouter, File, Form, HTTPException, Query, UploadFile
|
|
@@ -15,8 +15,10 @@ from agno.run.base import RunStatus
|
|
|
15
15
|
from agno.run.response import RunResponseEvent
|
|
16
16
|
from agno.run.team import RunResponseErrorEvent as TeamRunResponseErrorEvent
|
|
17
17
|
from agno.run.team import TeamRunResponseEvent
|
|
18
|
+
from agno.run.v2.workflow import WorkflowErrorEvent
|
|
18
19
|
from agno.team.team import Team
|
|
19
20
|
from agno.utils.log import logger
|
|
21
|
+
from agno.workflow.v2.workflow import Workflow as WorkflowV2
|
|
20
22
|
from agno.workflow.workflow import Workflow
|
|
21
23
|
|
|
22
24
|
|
|
@@ -82,6 +84,42 @@ def team_chat_response_streamer(
|
|
|
82
84
|
return
|
|
83
85
|
|
|
84
86
|
|
|
87
|
+
def workflow_response_streamer(
|
|
88
|
+
workflow: WorkflowV2,
|
|
89
|
+
body: Union[Dict[str, Any], str],
|
|
90
|
+
session_id: Optional[str] = None,
|
|
91
|
+
user_id: Optional[str] = None,
|
|
92
|
+
) -> Generator:
|
|
93
|
+
try:
|
|
94
|
+
if isinstance(body, dict):
|
|
95
|
+
run_response = workflow.run(
|
|
96
|
+
**body,
|
|
97
|
+
user_id=user_id,
|
|
98
|
+
session_id=session_id,
|
|
99
|
+
stream=True,
|
|
100
|
+
stream_intermediate_steps=True,
|
|
101
|
+
)
|
|
102
|
+
else:
|
|
103
|
+
run_response = workflow.run(
|
|
104
|
+
body,
|
|
105
|
+
user_id=user_id,
|
|
106
|
+
session_id=session_id,
|
|
107
|
+
stream=True,
|
|
108
|
+
stream_intermediate_steps=True,
|
|
109
|
+
)
|
|
110
|
+
for run_response_chunk in run_response:
|
|
111
|
+
yield run_response_chunk.to_json()
|
|
112
|
+
except Exception as e:
|
|
113
|
+
import traceback
|
|
114
|
+
|
|
115
|
+
traceback.print_exc(limit=3)
|
|
116
|
+
error_response = WorkflowErrorEvent(
|
|
117
|
+
error=str(e),
|
|
118
|
+
)
|
|
119
|
+
yield error_response.to_json()
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
|
|
85
123
|
def get_sync_router(
|
|
86
124
|
agents: Optional[List[Agent]] = None, teams: Optional[List[Team]] = None, workflows: Optional[List[Workflow]] = None
|
|
87
125
|
) -> APIRouter:
|
|
@@ -251,12 +289,12 @@ def get_sync_router(
|
|
|
251
289
|
@router.post("/runs")
|
|
252
290
|
def run_agent_or_team_or_workflow(
|
|
253
291
|
message: str = Form(None),
|
|
254
|
-
stream: bool = Form(
|
|
292
|
+
stream: bool = Form(False),
|
|
255
293
|
monitor: bool = Form(False),
|
|
256
294
|
agent_id: Optional[str] = Query(None),
|
|
257
295
|
team_id: Optional[str] = Query(None),
|
|
258
296
|
workflow_id: Optional[str] = Query(None),
|
|
259
|
-
workflow_input: Optional[
|
|
297
|
+
workflow_input: Optional[str] = Form(None),
|
|
260
298
|
session_id: Optional[str] = Form(None),
|
|
261
299
|
user_id: Optional[str] = Form(None),
|
|
262
300
|
files: Optional[List[UploadFile]] = File(None),
|
|
@@ -297,6 +335,13 @@ def get_sync_router(
|
|
|
297
335
|
if not workflow_input:
|
|
298
336
|
raise HTTPException(status_code=400, detail="Workflow input is required")
|
|
299
337
|
|
|
338
|
+
# Parse workflow_input into a dict if it is a valid JSON
|
|
339
|
+
try:
|
|
340
|
+
parsed_workflow_input = json.loads(workflow_input)
|
|
341
|
+
workflow_input = parsed_workflow_input
|
|
342
|
+
except json.JSONDecodeError:
|
|
343
|
+
pass
|
|
344
|
+
|
|
300
345
|
if agent:
|
|
301
346
|
agent.monitoring = bool(monitor)
|
|
302
347
|
elif team:
|
|
@@ -339,13 +384,25 @@ def get_sync_router(
|
|
|
339
384
|
media_type="text/event-stream",
|
|
340
385
|
)
|
|
341
386
|
elif workflow:
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
387
|
+
if isinstance(workflow, Workflow):
|
|
388
|
+
workflow_instance = workflow.deep_copy(update={"workflow_id": workflow_id})
|
|
389
|
+
workflow_instance.user_id = user_id
|
|
390
|
+
workflow_instance.session_name = None
|
|
391
|
+
if isinstance(workflow_input, dict):
|
|
392
|
+
return StreamingResponse(
|
|
393
|
+
(json.dumps(asdict(result)) for result in workflow_instance.run(**workflow_input)),
|
|
394
|
+
media_type="text/event-stream",
|
|
395
|
+
)
|
|
396
|
+
else:
|
|
397
|
+
return StreamingResponse(
|
|
398
|
+
(json.dumps(asdict(result)) for result in workflow_instance.run(workflow_input)), # type: ignore
|
|
399
|
+
media_type="text/event-stream",
|
|
400
|
+
)
|
|
401
|
+
else:
|
|
402
|
+
return StreamingResponse(
|
|
403
|
+
workflow_response_streamer(workflow, workflow_input, session_id=session_id, user_id=user_id),
|
|
404
|
+
media_type="text/event-stream",
|
|
405
|
+
)
|
|
349
406
|
else:
|
|
350
407
|
if agent:
|
|
351
408
|
run_response = cast(
|
|
@@ -374,9 +431,18 @@ def get_sync_router(
|
|
|
374
431
|
)
|
|
375
432
|
return team_run_response.to_dict()
|
|
376
433
|
elif workflow:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
434
|
+
if isinstance(workflow, Workflow):
|
|
435
|
+
workflow_instance = workflow.deep_copy(update={"workflow_id": workflow_id})
|
|
436
|
+
workflow_instance.user_id = user_id
|
|
437
|
+
workflow_instance.session_name = None
|
|
438
|
+
if isinstance(workflow_input, dict):
|
|
439
|
+
return workflow_instance.run(**workflow_input).to_dict()
|
|
440
|
+
else:
|
|
441
|
+
return workflow_instance.run(workflow_input).to_dict() # type: ignore
|
|
442
|
+
else:
|
|
443
|
+
if isinstance(workflow_input, dict):
|
|
444
|
+
return workflow.run(**workflow_input, session_id=session_id, user_id=user_id).to_dict()
|
|
445
|
+
else:
|
|
446
|
+
return workflow.run(workflow_input, session_id=session_id, user_id=user_id).to_dict()
|
|
381
447
|
|
|
382
448
|
return router
|
agno/app/playground/app.py
CHANGED
|
@@ -83,7 +83,7 @@ class Playground:
|
|
|
83
83
|
|
|
84
84
|
if self.workflows:
|
|
85
85
|
for workflow in self.workflows:
|
|
86
|
-
if not workflow.app_id:
|
|
86
|
+
if hasattr(workflow, "app_id") and not workflow.app_id:
|
|
87
87
|
workflow.app_id = self.app_id
|
|
88
88
|
if not workflow.workflow_id:
|
|
89
89
|
workflow.workflow_id = generate_id(workflow.name)
|
|
@@ -197,6 +197,7 @@ class Playground:
|
|
|
197
197
|
# Print the panel
|
|
198
198
|
console.print(panel)
|
|
199
199
|
self.set_app_id()
|
|
200
|
+
|
|
200
201
|
self.register_app_on_platform()
|
|
201
202
|
|
|
202
203
|
uvicorn.run(app=app, host=host, port=port, reload=reload, **kwargs)
|
|
@@ -38,11 +38,13 @@ from agno.memory.agent import AgentMemory
|
|
|
38
38
|
from agno.memory.v2 import Memory
|
|
39
39
|
from agno.run.response import RunResponseErrorEvent, RunResponseEvent
|
|
40
40
|
from agno.run.team import RunResponseErrorEvent as TeamRunResponseErrorEvent
|
|
41
|
+
from agno.run.v2.workflow import WorkflowErrorEvent
|
|
41
42
|
from agno.storage.session.agent import AgentSession
|
|
42
43
|
from agno.storage.session.team import TeamSession
|
|
43
44
|
from agno.storage.session.workflow import WorkflowSession
|
|
44
45
|
from agno.team.team import Team
|
|
45
46
|
from agno.utils.log import logger
|
|
47
|
+
from agno.workflow.v2.workflow import Workflow as WorkflowV2
|
|
46
48
|
from agno.workflow.workflow import Workflow
|
|
47
49
|
|
|
48
50
|
|
|
@@ -146,6 +148,31 @@ async def team_chat_response_streamer(
|
|
|
146
148
|
return
|
|
147
149
|
|
|
148
150
|
|
|
151
|
+
async def workflow_response_streamer(
|
|
152
|
+
workflow: WorkflowV2,
|
|
153
|
+
body: WorkflowRunRequest,
|
|
154
|
+
) -> AsyncGenerator:
|
|
155
|
+
try:
|
|
156
|
+
run_response = await workflow.arun(
|
|
157
|
+
**body.input,
|
|
158
|
+
user_id=body.user_id,
|
|
159
|
+
session_id=body.session_id or str(uuid4()),
|
|
160
|
+
stream=True,
|
|
161
|
+
stream_intermediate_steps=True,
|
|
162
|
+
)
|
|
163
|
+
async for run_response_chunk in run_response:
|
|
164
|
+
yield run_response_chunk.to_json()
|
|
165
|
+
except Exception as e:
|
|
166
|
+
import traceback
|
|
167
|
+
|
|
168
|
+
traceback.print_exc(limit=3)
|
|
169
|
+
error_response = WorkflowErrorEvent(
|
|
170
|
+
error=str(e),
|
|
171
|
+
)
|
|
172
|
+
yield error_response.to_json()
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
|
|
149
176
|
def get_async_playground_router(
|
|
150
177
|
agents: Optional[List[Agent]] = None,
|
|
151
178
|
workflows: Optional[List[Workflow]] = None,
|
|
@@ -616,13 +643,22 @@ def get_async_playground_router(
|
|
|
616
643
|
if workflow is None:
|
|
617
644
|
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
618
645
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
646
|
+
if isinstance(workflow, Workflow):
|
|
647
|
+
return WorkflowGetResponse(
|
|
648
|
+
workflow_id=workflow.workflow_id,
|
|
649
|
+
name=workflow.name,
|
|
650
|
+
description=workflow.description,
|
|
651
|
+
parameters=workflow._run_parameters or {},
|
|
652
|
+
storage=workflow.storage.__class__.__name__ if workflow.storage else None,
|
|
653
|
+
)
|
|
654
|
+
else:
|
|
655
|
+
return WorkflowGetResponse(
|
|
656
|
+
workflow_id=workflow.workflow_id,
|
|
657
|
+
name=workflow.name,
|
|
658
|
+
description=workflow.description,
|
|
659
|
+
parameters=workflow.run_parameters,
|
|
660
|
+
storage=workflow.storage.__class__.__name__ if workflow.storage else None,
|
|
661
|
+
)
|
|
626
662
|
|
|
627
663
|
@playground_router.post("/workflows/{workflow_id}/runs")
|
|
628
664
|
async def create_workflow_run(workflow_id: str, body: WorkflowRunRequest):
|
|
@@ -637,24 +673,54 @@ def get_async_playground_router(
|
|
|
637
673
|
logger.debug("Creating new session")
|
|
638
674
|
|
|
639
675
|
# Create a new instance of this workflow
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
676
|
+
if isinstance(workflow, Workflow):
|
|
677
|
+
new_workflow_instance = workflow.deep_copy(
|
|
678
|
+
update={"workflow_id": workflow_id, "session_id": body.session_id}
|
|
679
|
+
)
|
|
680
|
+
new_workflow_instance.user_id = body.user_id
|
|
681
|
+
new_workflow_instance.session_name = None
|
|
643
682
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
683
|
+
# Return based on the response type
|
|
684
|
+
try:
|
|
685
|
+
if new_workflow_instance._run_return_type == "RunResponse":
|
|
686
|
+
# Return as a normal response
|
|
687
|
+
return new_workflow_instance.run(**body.input)
|
|
688
|
+
else:
|
|
689
|
+
# Return as a streaming response
|
|
690
|
+
return StreamingResponse(
|
|
691
|
+
(result.to_json() for result in new_workflow_instance.run(**body.input)),
|
|
692
|
+
media_type="text/event-stream",
|
|
693
|
+
headers={
|
|
694
|
+
"Access-Control-Allow-Origin": "*",
|
|
695
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
696
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
697
|
+
},
|
|
698
|
+
)
|
|
699
|
+
except Exception as e:
|
|
700
|
+
# Handle unexpected runtime errors
|
|
701
|
+
raise HTTPException(status_code=500, detail=f"Error running workflow: {str(e)}")
|
|
702
|
+
else:
|
|
703
|
+
# Return based on the response type
|
|
704
|
+
try:
|
|
705
|
+
if body.stream:
|
|
706
|
+
# Return as a streaming response
|
|
707
|
+
return StreamingResponse(
|
|
708
|
+
workflow_response_streamer(workflow, body),
|
|
709
|
+
media_type="text/event-stream",
|
|
710
|
+
headers={
|
|
711
|
+
"Access-Control-Allow-Origin": "*",
|
|
712
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
713
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
714
|
+
},
|
|
715
|
+
)
|
|
716
|
+
else:
|
|
717
|
+
# Return as a normal response
|
|
718
|
+
return await workflow.arun(
|
|
719
|
+
**body.input, session_id=body.session_id or str(uuid4()), user_id=body.user_id
|
|
720
|
+
)
|
|
721
|
+
except Exception as e:
|
|
722
|
+
# Handle unexpected runtime errors
|
|
723
|
+
raise HTTPException(status_code=500, detail=f"Error running workflow: {str(e)}")
|
|
658
724
|
|
|
659
725
|
@playground_router.get("/workflows/{workflow_id}/sessions")
|
|
660
726
|
async def get_all_workflow_sessions(workflow_id: str, user_id: Optional[str] = Query(None, min_length=1)):
|
|
@@ -689,7 +755,7 @@ def get_async_playground_router(
|
|
|
689
755
|
)
|
|
690
756
|
return workflow_sessions
|
|
691
757
|
|
|
692
|
-
@playground_router.get("/workflows/{workflow_id}/sessions/{session_id}"
|
|
758
|
+
@playground_router.get("/workflows/{workflow_id}/sessions/{session_id}")
|
|
693
759
|
async def get_workflow_session(
|
|
694
760
|
workflow_id: str, session_id: str, user_id: Optional[str] = Query(None, min_length=1)
|
|
695
761
|
):
|
|
@@ -704,15 +770,18 @@ def get_async_playground_router(
|
|
|
704
770
|
|
|
705
771
|
# Retrieve the specific session
|
|
706
772
|
try:
|
|
707
|
-
workflow_session
|
|
773
|
+
workflow_session = workflow.storage.read(session_id, user_id) # type: ignore
|
|
708
774
|
except Exception as e:
|
|
709
775
|
raise HTTPException(status_code=500, detail=f"Error retrieving session: {str(e)}")
|
|
710
776
|
|
|
711
777
|
if not workflow_session:
|
|
712
778
|
raise HTTPException(status_code=404, detail="Session not found")
|
|
713
779
|
|
|
714
|
-
|
|
715
|
-
|
|
780
|
+
workflow_session_dict = workflow_session.to_dict()
|
|
781
|
+
if "memory" not in workflow_session_dict:
|
|
782
|
+
workflow_session_dict["memory"] = {"runs": workflow_session_dict.pop("runs", [])}
|
|
783
|
+
|
|
784
|
+
return JSONResponse(content=workflow_session_dict)
|
|
716
785
|
|
|
717
786
|
@playground_router.post("/workflows/{workflow_id}/sessions/{session_id}/rename")
|
|
718
787
|
async def rename_workflow_session(workflow_id: str, session_id: str, body: WorkflowRenameRequest):
|
agno/app/playground/operator.py
CHANGED
|
@@ -92,30 +92,36 @@ def get_session_title_from_workflow_session(workflow_session: WorkflowSession) -
|
|
|
92
92
|
)
|
|
93
93
|
if session_name is not None:
|
|
94
94
|
return session_name
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
# Split content by newlines and take first line, but limit to 100 chars
|
|
105
|
-
first_line = content.split("\n")[0]
|
|
106
|
-
return first_line[:100] + "..." if len(first_line) > 100 else first_line
|
|
107
|
-
|
|
108
|
-
# Fallback to response.content structure (if it exists)
|
|
109
|
-
response = _run.get("response")
|
|
110
|
-
if response:
|
|
111
|
-
content = response.get("content")
|
|
95
|
+
if hasattr(workflow_session, "memory"):
|
|
96
|
+
memory = workflow_session.memory
|
|
97
|
+
if memory is not None:
|
|
98
|
+
runs = memory.get("runs")
|
|
99
|
+
runs = cast(List[Any], runs)
|
|
100
|
+
for _run in runs:
|
|
101
|
+
try:
|
|
102
|
+
# Try to get content directly from the run first (workflow structure)
|
|
103
|
+
content = _run.get("content")
|
|
112
104
|
if content:
|
|
113
105
|
# Split content by newlines and take first line, but limit to 100 chars
|
|
114
106
|
first_line = content.split("\n")[0]
|
|
115
107
|
return first_line[:100] + "..." if len(first_line) > 100 else first_line
|
|
116
108
|
|
|
117
|
-
|
|
118
|
-
|
|
109
|
+
# Fallback to response.content structure (if it exists)
|
|
110
|
+
response = _run.get("response")
|
|
111
|
+
if response:
|
|
112
|
+
content = response.get("content")
|
|
113
|
+
if content:
|
|
114
|
+
# Split content by newlines and take first line, but limit to 100 chars
|
|
115
|
+
first_line = content.split("\n")[0]
|
|
116
|
+
return first_line[:100] + "..." if len(first_line) > 100 else first_line
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.error(f"Error parsing workflow session: {e}")
|
|
120
|
+
if hasattr(workflow_session, "runs"):
|
|
121
|
+
if workflow_session.runs is not None and len(workflow_session.runs) > 0:
|
|
122
|
+
for _run in workflow_session.runs:
|
|
123
|
+
if _run.content:
|
|
124
|
+
return _run.content[:100] + "..." if len(_run.content) > 100 else _run.content
|
|
119
125
|
return "Unnamed session"
|
|
120
126
|
|
|
121
127
|
|
agno/app/playground/schemas.py
CHANGED
|
@@ -38,11 +38,13 @@ from agno.memory.agent import AgentMemory
|
|
|
38
38
|
from agno.memory.v2 import Memory
|
|
39
39
|
from agno.run.response import RunResponseErrorEvent, RunResponseEvent
|
|
40
40
|
from agno.run.team import RunResponseErrorEvent as TeamRunResponseErrorEvent
|
|
41
|
+
from agno.run.v2.workflow import WorkflowErrorEvent
|
|
41
42
|
from agno.storage.session.agent import AgentSession
|
|
42
43
|
from agno.storage.session.team import TeamSession
|
|
43
44
|
from agno.storage.session.workflow import WorkflowSession
|
|
44
45
|
from agno.team.team import Team
|
|
45
46
|
from agno.utils.log import logger
|
|
47
|
+
from agno.workflow.v2.workflow import Workflow as WorkflowV2
|
|
46
48
|
from agno.workflow.workflow import Workflow
|
|
47
49
|
|
|
48
50
|
|
|
@@ -147,6 +149,31 @@ def team_chat_response_streamer(
|
|
|
147
149
|
return
|
|
148
150
|
|
|
149
151
|
|
|
152
|
+
def workflow_response_streamer(
|
|
153
|
+
workflow: WorkflowV2,
|
|
154
|
+
body: WorkflowRunRequest,
|
|
155
|
+
) -> Generator:
|
|
156
|
+
try:
|
|
157
|
+
run_response = workflow.run(
|
|
158
|
+
**body.input,
|
|
159
|
+
user_id=body.user_id,
|
|
160
|
+
session_id=body.session_id or str(uuid4()),
|
|
161
|
+
stream=True,
|
|
162
|
+
stream_intermediate_steps=True,
|
|
163
|
+
)
|
|
164
|
+
for run_response_chunk in run_response:
|
|
165
|
+
yield run_response_chunk.to_json()
|
|
166
|
+
except Exception as e:
|
|
167
|
+
import traceback
|
|
168
|
+
|
|
169
|
+
traceback.print_exc(limit=3)
|
|
170
|
+
error_response = WorkflowErrorEvent(
|
|
171
|
+
error=str(e),
|
|
172
|
+
)
|
|
173
|
+
yield error_response.to_json()
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
|
|
150
177
|
def get_sync_playground_router(
|
|
151
178
|
agents: Optional[List[Agent]] = None,
|
|
152
179
|
workflows: Optional[List[Workflow]] = None,
|
|
@@ -615,13 +642,22 @@ def get_sync_playground_router(
|
|
|
615
642
|
if workflow is None:
|
|
616
643
|
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
617
644
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
645
|
+
if isinstance(workflow, Workflow):
|
|
646
|
+
return WorkflowGetResponse(
|
|
647
|
+
workflow_id=workflow.workflow_id,
|
|
648
|
+
name=workflow.name,
|
|
649
|
+
description=workflow.description,
|
|
650
|
+
parameters=workflow._run_parameters or {},
|
|
651
|
+
storage=workflow.storage.__class__.__name__ if workflow.storage else None,
|
|
652
|
+
)
|
|
653
|
+
else:
|
|
654
|
+
return WorkflowGetResponse(
|
|
655
|
+
workflow_id=workflow.workflow_id,
|
|
656
|
+
name=workflow.name,
|
|
657
|
+
description=workflow.description,
|
|
658
|
+
parameters=workflow.run_parameters,
|
|
659
|
+
storage=workflow.storage.__class__.__name__ if workflow.storage else None,
|
|
660
|
+
)
|
|
625
661
|
|
|
626
662
|
@playground_router.post("/workflows/{workflow_id}/runs")
|
|
627
663
|
def create_workflow_run(workflow_id: str, body: WorkflowRunRequest):
|
|
@@ -631,24 +667,52 @@ def get_sync_playground_router(
|
|
|
631
667
|
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
632
668
|
|
|
633
669
|
# Create a new instance of this workflow
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
670
|
+
if isinstance(workflow, Workflow):
|
|
671
|
+
new_workflow_instance = workflow.deep_copy(
|
|
672
|
+
update={"workflow_id": workflow_id, "session_id": body.session_id}
|
|
673
|
+
)
|
|
674
|
+
new_workflow_instance.user_id = body.user_id
|
|
675
|
+
new_workflow_instance.session_name = None
|
|
637
676
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
677
|
+
# Return based on the response type
|
|
678
|
+
try:
|
|
679
|
+
if new_workflow_instance._run_return_type == "RunResponse":
|
|
680
|
+
# Return as a normal response
|
|
681
|
+
return new_workflow_instance.run(**body.input)
|
|
682
|
+
else:
|
|
683
|
+
# Return as a streaming response
|
|
684
|
+
return StreamingResponse(
|
|
685
|
+
(result.to_json() for result in new_workflow_instance.run(**body.input)),
|
|
686
|
+
media_type="text/event-stream",
|
|
687
|
+
headers={
|
|
688
|
+
"Access-Control-Allow-Origin": "*",
|
|
689
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
690
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
691
|
+
},
|
|
692
|
+
)
|
|
693
|
+
except Exception as e:
|
|
694
|
+
# Handle unexpected runtime errors
|
|
695
|
+
raise HTTPException(status_code=500, detail=f"Error running workflow: {str(e)}")
|
|
696
|
+
else:
|
|
697
|
+
# Return based on the response type
|
|
698
|
+
try:
|
|
699
|
+
if body.stream:
|
|
700
|
+
# Return as a streaming response
|
|
701
|
+
return StreamingResponse(
|
|
702
|
+
workflow_response_streamer(workflow, body),
|
|
703
|
+
media_type="text/event-stream",
|
|
704
|
+
headers={
|
|
705
|
+
"Access-Control-Allow-Origin": "*",
|
|
706
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
707
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
708
|
+
},
|
|
709
|
+
)
|
|
710
|
+
else:
|
|
711
|
+
# Return as a normal response
|
|
712
|
+
return workflow.arun(**body.input, session_id=body.session_id or str(uuid4()), user_id=body.user_id)
|
|
713
|
+
except Exception as e:
|
|
714
|
+
# Handle unexpected runtime errors
|
|
715
|
+
raise HTTPException(status_code=500, detail=f"Error running workflow: {str(e)}")
|
|
652
716
|
|
|
653
717
|
@playground_router.get("/workflows/{workflow_id}/sessions")
|
|
654
718
|
def get_all_workflow_sessions(workflow_id: str, user_id: Optional[str] = Query(None, min_length=1)):
|
|
@@ -703,8 +767,11 @@ def get_sync_playground_router(
|
|
|
703
767
|
if not workflow_session:
|
|
704
768
|
raise HTTPException(status_code=404, detail="Session not found")
|
|
705
769
|
|
|
706
|
-
|
|
707
|
-
|
|
770
|
+
workflow_session_dict = workflow_session.to_dict()
|
|
771
|
+
if "memory" not in workflow_session_dict:
|
|
772
|
+
workflow_session_dict["memory"] = {"runs": workflow_session_dict.pop("runs", [])}
|
|
773
|
+
|
|
774
|
+
return JSONResponse(content=workflow_session_dict)
|
|
708
775
|
|
|
709
776
|
@playground_router.post("/workflows/{workflow_id}/sessions/{session_id}/rename")
|
|
710
777
|
def rename_workflow_session(
|
agno/knowledge/agent.py
CHANGED
|
@@ -184,7 +184,7 @@ class AgentKnowledge(BaseModel):
|
|
|
184
184
|
# Filter out documents which already exist in the vector db
|
|
185
185
|
if skip_existing:
|
|
186
186
|
log_debug("Filtering out existing documents before insertion.")
|
|
187
|
-
documents_to_load = self.
|
|
187
|
+
documents_to_load = await self.async_filter_existing_documents(document_list)
|
|
188
188
|
|
|
189
189
|
if documents_to_load:
|
|
190
190
|
for doc in documents_to_load:
|
|
@@ -439,6 +439,43 @@ class AgentKnowledge(BaseModel):
|
|
|
439
439
|
|
|
440
440
|
return filtered_documents
|
|
441
441
|
|
|
442
|
+
async def async_filter_existing_documents(self, documents: List[Document]) -> List[Document]:
|
|
443
|
+
"""Filter out documents that already exist in the vector database.
|
|
444
|
+
|
|
445
|
+
This helper method is used across various knowledge base implementations
|
|
446
|
+
to avoid inserting duplicate documents.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
documents (List[Document]): List of documents to filter
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
List[Document]: Filtered list of documents that don't exist in the database
|
|
453
|
+
"""
|
|
454
|
+
from agno.utils.log import log_debug, log_info
|
|
455
|
+
|
|
456
|
+
if not self.vector_db:
|
|
457
|
+
log_debug("No vector database configured, skipping document filtering")
|
|
458
|
+
return documents
|
|
459
|
+
|
|
460
|
+
# Use set for O(1) lookups
|
|
461
|
+
seen_content = set()
|
|
462
|
+
original_count = len(documents)
|
|
463
|
+
filtered_documents = []
|
|
464
|
+
|
|
465
|
+
for doc in documents:
|
|
466
|
+
# Check hash and existence in DB
|
|
467
|
+
content_hash = doc.content # Assuming doc.content is reliable hash key
|
|
468
|
+
if content_hash not in seen_content and not await self.vector_db.async_doc_exists(doc):
|
|
469
|
+
seen_content.add(content_hash)
|
|
470
|
+
filtered_documents.append(doc)
|
|
471
|
+
else:
|
|
472
|
+
log_debug(f"Skipping existing document: {doc.name} (or duplicate content)")
|
|
473
|
+
|
|
474
|
+
if len(filtered_documents) < original_count:
|
|
475
|
+
log_info(f"Skipped {original_count - len(filtered_documents)} existing/duplicate documents.")
|
|
476
|
+
|
|
477
|
+
return filtered_documents
|
|
478
|
+
|
|
442
479
|
def _track_metadata_structure(self, metadata: Optional[Dict[str, Any]]) -> None:
|
|
443
480
|
"""Track metadata structure to enable filter extraction from queries
|
|
444
481
|
|
|
@@ -655,7 +692,7 @@ class AgentKnowledge(BaseModel):
|
|
|
655
692
|
documents_to_insert = documents
|
|
656
693
|
if skip_existing:
|
|
657
694
|
log_debug("Filtering out existing documents before insertion.")
|
|
658
|
-
documents_to_insert = self.
|
|
695
|
+
documents_to_insert = await self.async_filter_existing_documents(documents)
|
|
659
696
|
|
|
660
697
|
if documents_to_insert: # type: ignore
|
|
661
698
|
log_debug(f"Inserting {len(documents_to_insert)} new documents.")
|
agno/knowledge/combined.py
CHANGED
|
@@ -32,5 +32,5 @@ class CombinedKnowledgeBase(AgentKnowledge):
|
|
|
32
32
|
|
|
33
33
|
for kb in self.sources:
|
|
34
34
|
log_debug(f"Loading documents from {kb.__class__.__name__}")
|
|
35
|
-
async for document in
|
|
35
|
+
async for document in kb.async_document_lists: # type: ignore
|
|
36
36
|
yield document
|
agno/run/base.py
CHANGED
|
@@ -206,7 +206,9 @@ class RunResponseExtraData:
|
|
|
206
206
|
class RunStatus(str, Enum):
|
|
207
207
|
"""State of the main run response"""
|
|
208
208
|
|
|
209
|
+
pending = "PENDING"
|
|
209
210
|
running = "RUNNING"
|
|
211
|
+
completed = "COMPLETED"
|
|
210
212
|
paused = "PAUSED"
|
|
211
213
|
cancelled = "CANCELLED"
|
|
212
214
|
error = "ERROR"
|