agno 2.2.5__py3-none-any.whl → 2.2.6__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 +82 -19
- agno/culture/manager.py +3 -4
- agno/knowledge/chunking/agentic.py +6 -2
- agno/memory/manager.py +9 -4
- agno/models/anthropic/claude.py +1 -2
- agno/models/azure/ai_foundry.py +31 -14
- agno/models/azure/openai_chat.py +12 -4
- agno/models/base.py +44 -11
- agno/models/cerebras/cerebras.py +11 -6
- agno/models/groq/groq.py +7 -4
- agno/models/meta/llama.py +12 -6
- agno/models/meta/llama_openai.py +5 -1
- agno/models/openai/chat.py +20 -12
- agno/models/openai/responses.py +10 -5
- agno/models/utils.py +254 -8
- agno/models/vertexai/claude.py +9 -13
- agno/os/routers/evals/evals.py +8 -8
- agno/os/routers/evals/utils.py +1 -0
- agno/os/schema.py +48 -33
- agno/os/utils.py +27 -0
- agno/run/agent.py +5 -0
- agno/run/team.py +2 -0
- agno/run/workflow.py +39 -0
- agno/session/summary.py +8 -2
- agno/session/workflow.py +4 -3
- agno/team/team.py +50 -14
- agno/tools/file.py +153 -25
- agno/tools/function.py +5 -1
- agno/tools/notion.py +201 -0
- agno/utils/events.py +2 -0
- agno/utils/print_response/workflow.py +115 -16
- agno/vectordb/milvus/milvus.py +5 -0
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +298 -0
- agno/workflow/workflow.py +929 -64
- {agno-2.2.5.dist-info → agno-2.2.6.dist-info}/METADATA +4 -1
- {agno-2.2.5.dist-info → agno-2.2.6.dist-info}/RECORD +40 -38
- {agno-2.2.5.dist-info → agno-2.2.6.dist-info}/WHEEL +0 -0
- {agno-2.2.5.dist-info → agno-2.2.6.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.5.dist-info → agno-2.2.6.dist-info}/top_level.txt +0 -0
|
@@ -25,6 +25,8 @@ from agno.run.workflow import (
|
|
|
25
25
|
StepsExecutionCompletedEvent,
|
|
26
26
|
StepsExecutionStartedEvent,
|
|
27
27
|
StepStartedEvent,
|
|
28
|
+
WorkflowAgentCompletedEvent,
|
|
29
|
+
WorkflowAgentStartedEvent,
|
|
28
30
|
WorkflowCompletedEvent,
|
|
29
31
|
WorkflowErrorEvent,
|
|
30
32
|
WorkflowRunOutput,
|
|
@@ -135,7 +137,16 @@ def print_response(
|
|
|
135
137
|
|
|
136
138
|
response_timer.stop()
|
|
137
139
|
|
|
138
|
-
if
|
|
140
|
+
# Check if this is a workflow agent direct response
|
|
141
|
+
if workflow_response.workflow_agent_run is not None and not workflow_response.workflow_agent_run.tools:
|
|
142
|
+
# Agent answered directly from history without executing workflow
|
|
143
|
+
agent_response_panel = create_panel(
|
|
144
|
+
content=Markdown(str(workflow_response.content)) if markdown else str(workflow_response.content),
|
|
145
|
+
title="Workflow Agent Response",
|
|
146
|
+
border_style="green",
|
|
147
|
+
)
|
|
148
|
+
console.print(agent_response_panel) # type: ignore
|
|
149
|
+
elif show_step_details and workflow_response.step_results:
|
|
139
150
|
for i, step_output in enumerate(workflow_response.step_results):
|
|
140
151
|
print_step_output_recursive(step_output, i + 1, markdown, console) # type: ignore
|
|
141
152
|
|
|
@@ -260,6 +271,8 @@ def print_response_stream(
|
|
|
260
271
|
step_results = []
|
|
261
272
|
step_started_printed = False
|
|
262
273
|
is_callable_function = callable(workflow.steps)
|
|
274
|
+
workflow_started = False # Track if workflow has actually started
|
|
275
|
+
is_workflow_agent_response = False # Track if this is a workflow agent direct response
|
|
263
276
|
|
|
264
277
|
# Smart step hierarchy tracking
|
|
265
278
|
current_primitive_context = None # Current primitive being executed (parallel, loop, etc.)
|
|
@@ -328,12 +341,25 @@ def print_response_stream(
|
|
|
328
341
|
): # type: ignore
|
|
329
342
|
# Handle the new event types
|
|
330
343
|
if isinstance(response, WorkflowStartedEvent):
|
|
344
|
+
workflow_started = True
|
|
331
345
|
status.update("Workflow started...")
|
|
332
346
|
if is_callable_function:
|
|
333
347
|
current_step_name = "Custom Function"
|
|
334
348
|
current_step_index = 0
|
|
335
349
|
live_log.update(status)
|
|
336
350
|
|
|
351
|
+
elif isinstance(response, WorkflowAgentStartedEvent):
|
|
352
|
+
# Workflow agent is starting to process
|
|
353
|
+
status.update("Workflow agent processing...")
|
|
354
|
+
live_log.update(status)
|
|
355
|
+
continue
|
|
356
|
+
|
|
357
|
+
elif isinstance(response, WorkflowAgentCompletedEvent):
|
|
358
|
+
# Workflow agent has completed
|
|
359
|
+
status.update("Workflow agent completed")
|
|
360
|
+
live_log.update(status)
|
|
361
|
+
continue
|
|
362
|
+
|
|
337
363
|
elif isinstance(response, StepStartedEvent):
|
|
338
364
|
step_name = response.step_name or "Unknown"
|
|
339
365
|
step_index = response.step_index or 0 # type: ignore
|
|
@@ -646,8 +672,23 @@ def print_response_stream(
|
|
|
646
672
|
elif isinstance(response, WorkflowCompletedEvent):
|
|
647
673
|
status.update("Workflow completed!")
|
|
648
674
|
|
|
675
|
+
# Check if this is an agent direct response
|
|
676
|
+
if response.metadata and response.metadata.get("agent_direct_response"):
|
|
677
|
+
is_workflow_agent_response = True
|
|
678
|
+
# Print the agent's direct response from history
|
|
679
|
+
if show_step_details:
|
|
680
|
+
live_log.update(status, refresh=True)
|
|
681
|
+
agent_response_panel = create_panel(
|
|
682
|
+
content=Markdown(str(response.content)) if markdown else str(response.content),
|
|
683
|
+
title="Workflow Agent Response",
|
|
684
|
+
border_style="green",
|
|
685
|
+
)
|
|
686
|
+
console.print(agent_response_panel) # type: ignore
|
|
687
|
+
step_started_printed = True
|
|
649
688
|
# For callable functions, print the final content block here since there are no step events
|
|
650
|
-
|
|
689
|
+
elif (
|
|
690
|
+
is_callable_function and show_step_details and current_step_content and not step_started_printed
|
|
691
|
+
):
|
|
651
692
|
final_step_panel = create_panel(
|
|
652
693
|
content=Markdown(current_step_content) if markdown else current_step_content,
|
|
653
694
|
title="Custom Function (Completed)",
|
|
@@ -658,8 +699,8 @@ def print_response_stream(
|
|
|
658
699
|
|
|
659
700
|
live_log.update(status, refresh=True)
|
|
660
701
|
|
|
661
|
-
# Show final summary
|
|
662
|
-
if response.metadata:
|
|
702
|
+
# Show final summary (skip for agent responses)
|
|
703
|
+
if response.metadata and not is_workflow_agent_response:
|
|
663
704
|
status = response.status
|
|
664
705
|
summary_content = ""
|
|
665
706
|
summary_content += f"""\n\n**Status:** {status}"""
|
|
@@ -710,8 +751,16 @@ def print_response_stream(
|
|
|
710
751
|
and response.content_type != ""
|
|
711
752
|
)
|
|
712
753
|
response_str = response.content # type: ignore
|
|
754
|
+
|
|
755
|
+
if isinstance(response, RunContentEvent) and not workflow_started:
|
|
756
|
+
is_workflow_agent_response = True
|
|
757
|
+
continue
|
|
758
|
+
|
|
713
759
|
elif isinstance(response, RunContentEvent) and current_step_executor_type != "team":
|
|
714
760
|
response_str = response.content # type: ignore
|
|
761
|
+
# If we get RunContentEvent BEFORE workflow starts, it's an agent direct response
|
|
762
|
+
if not workflow_started and not is_workflow_agent_response:
|
|
763
|
+
is_workflow_agent_response = True
|
|
715
764
|
else:
|
|
716
765
|
continue
|
|
717
766
|
|
|
@@ -734,8 +783,8 @@ def print_response_stream(
|
|
|
734
783
|
else:
|
|
735
784
|
current_step_content += response_str
|
|
736
785
|
|
|
737
|
-
# Live update the step panel with streaming content
|
|
738
|
-
if show_step_details and not step_started_printed:
|
|
786
|
+
# Live update the step panel with streaming content (skip for workflow agent responses)
|
|
787
|
+
if show_step_details and not step_started_printed and not is_workflow_agent_response:
|
|
739
788
|
# Generate smart step number for streaming title (will use cached value)
|
|
740
789
|
step_display = get_step_display_number(current_step_index, current_step_name)
|
|
741
790
|
title = f"{step_display}: {current_step_name} (Streaming...)"
|
|
@@ -757,8 +806,8 @@ def print_response_stream(
|
|
|
757
806
|
|
|
758
807
|
live_log.update("")
|
|
759
808
|
|
|
760
|
-
# Final completion message
|
|
761
|
-
if show_time:
|
|
809
|
+
# Final completion message (skip for agent responses)
|
|
810
|
+
if show_time and not is_workflow_agent_response:
|
|
762
811
|
completion_text = Text(f"Completed in {response_timer.elapsed:.1f}s", style="bold green")
|
|
763
812
|
console.print(completion_text) # type: ignore
|
|
764
813
|
|
|
@@ -927,7 +976,16 @@ async def aprint_response(
|
|
|
927
976
|
|
|
928
977
|
response_timer.stop()
|
|
929
978
|
|
|
930
|
-
if
|
|
979
|
+
# Check if this is a workflow agent direct response
|
|
980
|
+
if workflow_response.workflow_agent_run is not None and not workflow_response.workflow_agent_run.tools:
|
|
981
|
+
# Agent answered directly from history without executing workflow
|
|
982
|
+
agent_response_panel = create_panel(
|
|
983
|
+
content=Markdown(str(workflow_response.content)) if markdown else str(workflow_response.content),
|
|
984
|
+
title="Workflow Agent Response",
|
|
985
|
+
border_style="green",
|
|
986
|
+
)
|
|
987
|
+
console.print(agent_response_panel) # type: ignore
|
|
988
|
+
elif show_step_details and workflow_response.step_results:
|
|
931
989
|
for i, step_output in enumerate(workflow_response.step_results):
|
|
932
990
|
print_step_output_recursive(step_output, i + 1, markdown, console) # type: ignore
|
|
933
991
|
|
|
@@ -1052,6 +1110,8 @@ async def aprint_response_stream(
|
|
|
1052
1110
|
step_results = []
|
|
1053
1111
|
step_started_printed = False
|
|
1054
1112
|
is_callable_function = callable(workflow.steps)
|
|
1113
|
+
workflow_started = False # Track if workflow has actually started
|
|
1114
|
+
is_workflow_agent_response = False # Track if this is a workflow agent direct response
|
|
1055
1115
|
|
|
1056
1116
|
# Smart step hierarchy tracking
|
|
1057
1117
|
current_primitive_context = None # Current primitive being executed (parallel, loop, etc.)
|
|
@@ -1120,13 +1180,30 @@ async def aprint_response_stream(
|
|
|
1120
1180
|
): # type: ignore
|
|
1121
1181
|
# Handle the new event types
|
|
1122
1182
|
if isinstance(response, WorkflowStartedEvent):
|
|
1183
|
+
workflow_started = True
|
|
1123
1184
|
status.update("Workflow started...")
|
|
1124
1185
|
if is_callable_function:
|
|
1125
1186
|
current_step_name = "Custom Function"
|
|
1126
1187
|
current_step_index = 0
|
|
1127
1188
|
live_log.update(status)
|
|
1128
1189
|
|
|
1190
|
+
elif isinstance(response, WorkflowAgentStartedEvent):
|
|
1191
|
+
# Workflow agent is starting to process
|
|
1192
|
+
status.update("Workflow agent processing...")
|
|
1193
|
+
live_log.update(status)
|
|
1194
|
+
continue
|
|
1195
|
+
|
|
1196
|
+
elif isinstance(response, WorkflowAgentCompletedEvent):
|
|
1197
|
+
# Workflow agent has completed
|
|
1198
|
+
status.update("Workflow agent completed")
|
|
1199
|
+
live_log.update(status)
|
|
1200
|
+
continue
|
|
1201
|
+
|
|
1129
1202
|
elif isinstance(response, StepStartedEvent):
|
|
1203
|
+
# Skip step events if workflow hasn't started (agent direct response)
|
|
1204
|
+
if not workflow_started:
|
|
1205
|
+
continue
|
|
1206
|
+
|
|
1130
1207
|
step_name = response.step_name or "Unknown"
|
|
1131
1208
|
step_index = response.step_index or 0 # type: ignore
|
|
1132
1209
|
|
|
@@ -1438,8 +1515,23 @@ async def aprint_response_stream(
|
|
|
1438
1515
|
elif isinstance(response, WorkflowCompletedEvent):
|
|
1439
1516
|
status.update("Workflow completed!")
|
|
1440
1517
|
|
|
1518
|
+
# Check if this is an agent direct response
|
|
1519
|
+
if response.metadata and response.metadata.get("agent_direct_response"):
|
|
1520
|
+
is_workflow_agent_response = True
|
|
1521
|
+
# Print the agent's direct response from history
|
|
1522
|
+
if show_step_details:
|
|
1523
|
+
live_log.update(status, refresh=True)
|
|
1524
|
+
agent_response_panel = create_panel(
|
|
1525
|
+
content=Markdown(str(response.content)) if markdown else str(response.content),
|
|
1526
|
+
title="Workflow Agent Response",
|
|
1527
|
+
border_style="green",
|
|
1528
|
+
)
|
|
1529
|
+
console.print(agent_response_panel) # type: ignore
|
|
1530
|
+
step_started_printed = True
|
|
1441
1531
|
# For callable functions, print the final content block here since there are no step events
|
|
1442
|
-
|
|
1532
|
+
elif (
|
|
1533
|
+
is_callable_function and show_step_details and current_step_content and not step_started_printed
|
|
1534
|
+
):
|
|
1443
1535
|
final_step_panel = create_panel(
|
|
1444
1536
|
content=Markdown(current_step_content) if markdown else current_step_content,
|
|
1445
1537
|
title="Custom Function (Completed)",
|
|
@@ -1450,8 +1542,8 @@ async def aprint_response_stream(
|
|
|
1450
1542
|
|
|
1451
1543
|
live_log.update(status, refresh=True)
|
|
1452
1544
|
|
|
1453
|
-
# Show final summary
|
|
1454
|
-
if response.metadata:
|
|
1545
|
+
# Show final summary (skip for agent responses)
|
|
1546
|
+
if response.metadata and not is_workflow_agent_response:
|
|
1455
1547
|
status = response.status
|
|
1456
1548
|
summary_content = ""
|
|
1457
1549
|
summary_content += f"""\n\n**Status:** {status}"""
|
|
@@ -1499,6 +1591,11 @@ async def aprint_response_stream(
|
|
|
1499
1591
|
# Extract the content from the streaming event
|
|
1500
1592
|
response_str = response.content # type: ignore
|
|
1501
1593
|
|
|
1594
|
+
# If we get RunContentEvent BEFORE workflow starts, it's an agent direct response
|
|
1595
|
+
if isinstance(response, RunContentEvent) and not workflow_started:
|
|
1596
|
+
is_workflow_agent_response = True
|
|
1597
|
+
continue # Skip ALL agent direct response content
|
|
1598
|
+
|
|
1502
1599
|
# Check if this is a team's final structured output
|
|
1503
1600
|
is_structured_output = (
|
|
1504
1601
|
isinstance(response, TeamRunContentEvent)
|
|
@@ -1508,6 +1605,9 @@ async def aprint_response_stream(
|
|
|
1508
1605
|
)
|
|
1509
1606
|
elif isinstance(response, RunContentEvent) and current_step_executor_type != "team":
|
|
1510
1607
|
response_str = response.content # type: ignore
|
|
1608
|
+
# If we get RunContentEvent BEFORE workflow starts, it's an agent direct response
|
|
1609
|
+
if not workflow_started and not is_workflow_agent_response:
|
|
1610
|
+
is_workflow_agent_response = True
|
|
1511
1611
|
else:
|
|
1512
1612
|
continue
|
|
1513
1613
|
|
|
@@ -1530,8 +1630,8 @@ async def aprint_response_stream(
|
|
|
1530
1630
|
else:
|
|
1531
1631
|
current_step_content += response_str
|
|
1532
1632
|
|
|
1533
|
-
# Live update the step panel with streaming content
|
|
1534
|
-
if show_step_details and not step_started_printed:
|
|
1633
|
+
# Live update the step panel with streaming content (skip for workflow agent responses)
|
|
1634
|
+
if show_step_details and not step_started_printed and not is_workflow_agent_response:
|
|
1535
1635
|
# Generate smart step number for streaming title (will use cached value)
|
|
1536
1636
|
step_display = get_step_display_number(current_step_index, current_step_name)
|
|
1537
1637
|
title = f"{step_display}: {current_step_name} (Streaming...)"
|
|
@@ -1553,8 +1653,7 @@ async def aprint_response_stream(
|
|
|
1553
1653
|
|
|
1554
1654
|
live_log.update("")
|
|
1555
1655
|
|
|
1556
|
-
|
|
1557
|
-
if show_time:
|
|
1656
|
+
if show_time and not is_workflow_agent_response:
|
|
1558
1657
|
completion_text = Text(f"Completed in {response_timer.elapsed:.1f}s", style="bold green")
|
|
1559
1658
|
console.print(completion_text) # type: ignore
|
|
1560
1659
|
|
agno/vectordb/milvus/milvus.py
CHANGED
|
@@ -719,6 +719,11 @@ class Milvus(VectorDb):
|
|
|
719
719
|
)
|
|
720
720
|
)
|
|
721
721
|
|
|
722
|
+
# Apply reranker if available
|
|
723
|
+
if self.reranker and search_results:
|
|
724
|
+
search_results = self.reranker.rerank(query=query, documents=search_results)
|
|
725
|
+
search_results = search_results[:limit]
|
|
726
|
+
|
|
722
727
|
log_info(f"Found {len(search_results)} documents")
|
|
723
728
|
return search_results
|
|
724
729
|
|
agno/workflow/__init__.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from agno.workflow.agent import WorkflowAgent
|
|
1
2
|
from agno.workflow.condition import Condition
|
|
2
3
|
from agno.workflow.loop import Loop
|
|
3
4
|
from agno.workflow.parallel import Parallel
|
|
@@ -9,6 +10,7 @@ from agno.workflow.workflow import Workflow
|
|
|
9
10
|
|
|
10
11
|
__all__ = [
|
|
11
12
|
"Workflow",
|
|
13
|
+
"WorkflowAgent",
|
|
12
14
|
"Steps",
|
|
13
15
|
"Step",
|
|
14
16
|
"Loop",
|
agno/workflow/agent.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""WorkflowAgent - A restricted Agent for workflow orchestration"""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from agno.agent import Agent
|
|
6
|
+
from agno.models.base import Model
|
|
7
|
+
from agno.workflow.types import WebSocketHandler
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from agno.session.workflow import WorkflowSession
|
|
11
|
+
from agno.workflow.types import WorkflowExecutionInput
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WorkflowAgent(Agent):
|
|
15
|
+
"""
|
|
16
|
+
A restricted Agent class specifically designed for workflow orchestration.
|
|
17
|
+
This agent can:
|
|
18
|
+
1. Decide whether to run the workflow or answer directly from history
|
|
19
|
+
2. Call the workflow execution tool when needed
|
|
20
|
+
3. Access workflow session history for context
|
|
21
|
+
Restrictions:
|
|
22
|
+
- Only model configuration allowed
|
|
23
|
+
- No custom tools (tools are set by workflow)
|
|
24
|
+
- No knowledge base
|
|
25
|
+
- Limited configuration options
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
model: Model,
|
|
31
|
+
instructions: Optional[str] = None,
|
|
32
|
+
add_workflow_history: bool = True,
|
|
33
|
+
num_history_runs: int = 5,
|
|
34
|
+
):
|
|
35
|
+
"""
|
|
36
|
+
Initialize WorkflowAgent with restricted parameters.
|
|
37
|
+
Args:
|
|
38
|
+
model: The model to use for the agent (required)
|
|
39
|
+
instructions: Custom instructions (will be combined with workflow context)
|
|
40
|
+
add_workflow_history: Whether to add workflow history to context (default: True)
|
|
41
|
+
num_history_runs: Number of previous workflow runs to include in context (default: 5)
|
|
42
|
+
"""
|
|
43
|
+
self.add_workflow_history = add_workflow_history
|
|
44
|
+
|
|
45
|
+
default_instructions = """You are a workflow orchestration agent. Your job is to help users by either:
|
|
46
|
+
1. **Answering directly** from the workflow history context if the question can be answered from previous runs
|
|
47
|
+
2. **Running the workflow** by calling the run_workflow tool ONCE when you need to process a new query
|
|
48
|
+
|
|
49
|
+
Guidelines:
|
|
50
|
+
- ALWAYS check the workflow history first before calling the tool
|
|
51
|
+
- Answer directly from history if:
|
|
52
|
+
* The user asks about something already in history
|
|
53
|
+
* The user asks for comparisons/analysis of things in history (e.g., "compare X and Y")
|
|
54
|
+
* The user asks follow-up questions about previous results
|
|
55
|
+
- Only call the run_workflow tool for NEW topics not covered in history
|
|
56
|
+
- IMPORTANT: Do NOT call the tool multiple times. Call it once and use the result.
|
|
57
|
+
- Keep your responses concise and helpful
|
|
58
|
+
- When you must call the workflow, pass a clear and concise query
|
|
59
|
+
|
|
60
|
+
{workflow_context}
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
if instructions:
|
|
64
|
+
if "{workflow_context}" not in instructions:
|
|
65
|
+
# Add the workflow context placeholder
|
|
66
|
+
final_instructions = f"{instructions}\n\n{{workflow_context}}"
|
|
67
|
+
else:
|
|
68
|
+
final_instructions = instructions
|
|
69
|
+
else:
|
|
70
|
+
final_instructions = default_instructions
|
|
71
|
+
|
|
72
|
+
super().__init__(
|
|
73
|
+
model=model,
|
|
74
|
+
instructions=final_instructions,
|
|
75
|
+
resolve_in_context=True,
|
|
76
|
+
num_history_runs=num_history_runs,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def create_workflow_tool(
|
|
80
|
+
self,
|
|
81
|
+
workflow: "Any", # Workflow type
|
|
82
|
+
session: "WorkflowSession",
|
|
83
|
+
execution_input: "WorkflowExecutionInput",
|
|
84
|
+
session_state: Optional[Dict[str, Any]],
|
|
85
|
+
stream: bool = False,
|
|
86
|
+
) -> Callable:
|
|
87
|
+
"""
|
|
88
|
+
Create the workflow execution tool that this agent can call.
|
|
89
|
+
This is similar to how Agent has search_knowledge_base() method.
|
|
90
|
+
Args:
|
|
91
|
+
workflow: The workflow instance
|
|
92
|
+
session: The workflow session
|
|
93
|
+
execution_input: The execution input
|
|
94
|
+
session_state: The session state
|
|
95
|
+
stream: Whether to stream the workflow execution
|
|
96
|
+
Returns:
|
|
97
|
+
Callable tool function
|
|
98
|
+
"""
|
|
99
|
+
from datetime import datetime
|
|
100
|
+
from uuid import uuid4
|
|
101
|
+
|
|
102
|
+
from pydantic import BaseModel
|
|
103
|
+
|
|
104
|
+
from agno.run.workflow import WorkflowRunOutput
|
|
105
|
+
from agno.utils.log import log_debug
|
|
106
|
+
from agno.workflow.types import WorkflowExecutionInput
|
|
107
|
+
|
|
108
|
+
def run_workflow(query: str):
|
|
109
|
+
"""
|
|
110
|
+
Execute the complete workflow with the given query.
|
|
111
|
+
Use this tool when you need to run the workflow to answer the user's question.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
query: The input query/question to process through the workflow
|
|
115
|
+
Returns:
|
|
116
|
+
The workflow execution result (str in non-streaming, generator in streaming)
|
|
117
|
+
"""
|
|
118
|
+
# Reload session to get latest data from database
|
|
119
|
+
# This ensures we don't overwrite any updates made after the tool was created
|
|
120
|
+
session_from_db = workflow.get_session(session_id=session.session_id)
|
|
121
|
+
if session_from_db is None:
|
|
122
|
+
session_from_db = session # Fallback to closure session if reload fails
|
|
123
|
+
log_debug(f"Fallback to closure session: {len(session_from_db.runs or [])} runs")
|
|
124
|
+
else:
|
|
125
|
+
log_debug(f"Reloaded session before tool execution: {len(session_from_db.runs or [])} runs")
|
|
126
|
+
|
|
127
|
+
# Create a new run ID for this execution
|
|
128
|
+
run_id = str(uuid4())
|
|
129
|
+
|
|
130
|
+
workflow_run_response = WorkflowRunOutput(
|
|
131
|
+
run_id=run_id,
|
|
132
|
+
input=execution_input.input, # Use original user input
|
|
133
|
+
session_id=session_from_db.session_id,
|
|
134
|
+
workflow_id=workflow.id,
|
|
135
|
+
workflow_name=workflow.name,
|
|
136
|
+
created_at=int(datetime.now().timestamp()),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
workflow_execution_input = WorkflowExecutionInput(
|
|
140
|
+
input=query, # Agent's refined query for execution
|
|
141
|
+
additional_data=execution_input.additional_data,
|
|
142
|
+
audio=execution_input.audio,
|
|
143
|
+
images=execution_input.images,
|
|
144
|
+
videos=execution_input.videos,
|
|
145
|
+
files=execution_input.files,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# ===== EXECUTION LOGIC (Based on streaming mode) =====
|
|
149
|
+
if stream:
|
|
150
|
+
final_content = ""
|
|
151
|
+
for event in workflow._execute_stream(
|
|
152
|
+
session=session_from_db,
|
|
153
|
+
execution_input=workflow_execution_input,
|
|
154
|
+
workflow_run_response=workflow_run_response,
|
|
155
|
+
session_state=session_state,
|
|
156
|
+
stream_events=True,
|
|
157
|
+
):
|
|
158
|
+
yield event
|
|
159
|
+
|
|
160
|
+
# Capture final content from WorkflowCompletedEvent
|
|
161
|
+
from agno.run.workflow import WorkflowCompletedEvent
|
|
162
|
+
|
|
163
|
+
if isinstance(event, WorkflowCompletedEvent):
|
|
164
|
+
final_content = str(event.content) if event.content else ""
|
|
165
|
+
|
|
166
|
+
return final_content
|
|
167
|
+
else:
|
|
168
|
+
# NON-STREAMING MODE: Execute synchronously
|
|
169
|
+
result = workflow._execute(
|
|
170
|
+
session=session_from_db,
|
|
171
|
+
execution_input=workflow_execution_input,
|
|
172
|
+
workflow_run_response=workflow_run_response,
|
|
173
|
+
session_state=session_state,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if isinstance(result.content, str):
|
|
177
|
+
return result.content
|
|
178
|
+
elif isinstance(result.content, BaseModel):
|
|
179
|
+
return result.content.model_dump_json(exclude_none=True)
|
|
180
|
+
else:
|
|
181
|
+
return str(result.content)
|
|
182
|
+
|
|
183
|
+
return run_workflow
|
|
184
|
+
|
|
185
|
+
def async_create_workflow_tool(
|
|
186
|
+
self,
|
|
187
|
+
workflow: "Any", # Workflow type
|
|
188
|
+
session: "WorkflowSession",
|
|
189
|
+
execution_input: "WorkflowExecutionInput",
|
|
190
|
+
session_state: Optional[Dict[str, Any]],
|
|
191
|
+
stream: bool = False,
|
|
192
|
+
websocket_handler: Optional[WebSocketHandler] = None,
|
|
193
|
+
) -> Callable:
|
|
194
|
+
"""
|
|
195
|
+
Create the async workflow execution tool that this agent can call.
|
|
196
|
+
This is the async counterpart of create_workflow_tool.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
workflow: The workflow instance
|
|
200
|
+
session: The workflow session
|
|
201
|
+
execution_input: The execution input
|
|
202
|
+
session_state: The session state
|
|
203
|
+
stream: Whether to stream the workflow execution
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Async callable tool function
|
|
207
|
+
"""
|
|
208
|
+
from datetime import datetime
|
|
209
|
+
from uuid import uuid4
|
|
210
|
+
|
|
211
|
+
from pydantic import BaseModel
|
|
212
|
+
|
|
213
|
+
from agno.run.workflow import WorkflowRunOutput
|
|
214
|
+
from agno.utils.log import log_debug
|
|
215
|
+
from agno.workflow.types import WorkflowExecutionInput
|
|
216
|
+
|
|
217
|
+
async def run_workflow(query: str):
|
|
218
|
+
"""
|
|
219
|
+
Execute the complete workflow with the given query asynchronously.
|
|
220
|
+
Use this tool when you need to run the workflow to answer the user's question.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
query: The input query/question to process through the workflow
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
The workflow execution result (str in non-streaming, async generator in streaming)
|
|
227
|
+
"""
|
|
228
|
+
# Reload session to get latest data from database
|
|
229
|
+
# This ensures we don't overwrite any updates made after the tool was created
|
|
230
|
+
# Use async or sync method based on database type
|
|
231
|
+
if workflow._has_async_db():
|
|
232
|
+
session_from_db = await workflow.aget_session(session_id=session.session_id)
|
|
233
|
+
else:
|
|
234
|
+
session_from_db = workflow.get_session(session_id=session.session_id)
|
|
235
|
+
|
|
236
|
+
if session_from_db is None:
|
|
237
|
+
session_from_db = session # Fallback to closure session if reload fails
|
|
238
|
+
log_debug(f"Fallback to closure session: {len(session_from_db.runs or [])} runs")
|
|
239
|
+
else:
|
|
240
|
+
log_debug(f"Reloaded session before async tool execution: {len(session_from_db.runs or [])} runs")
|
|
241
|
+
|
|
242
|
+
# Create a new run ID for this execution
|
|
243
|
+
run_id = str(uuid4())
|
|
244
|
+
|
|
245
|
+
workflow_run_response = WorkflowRunOutput(
|
|
246
|
+
run_id=run_id,
|
|
247
|
+
input=execution_input.input, # Use original user input
|
|
248
|
+
session_id=session_from_db.session_id,
|
|
249
|
+
workflow_id=workflow.id,
|
|
250
|
+
workflow_name=workflow.name,
|
|
251
|
+
created_at=int(datetime.now().timestamp()),
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
workflow_execution_input = WorkflowExecutionInput(
|
|
255
|
+
input=query, # Agent's refined query for execution
|
|
256
|
+
additional_data=execution_input.additional_data,
|
|
257
|
+
audio=execution_input.audio,
|
|
258
|
+
images=execution_input.images,
|
|
259
|
+
videos=execution_input.videos,
|
|
260
|
+
files=execution_input.files,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
if stream:
|
|
264
|
+
final_content = ""
|
|
265
|
+
async for event in workflow._aexecute_stream(
|
|
266
|
+
session_id=session_from_db.session_id,
|
|
267
|
+
user_id=session_from_db.user_id,
|
|
268
|
+
execution_input=workflow_execution_input,
|
|
269
|
+
workflow_run_response=workflow_run_response,
|
|
270
|
+
session_state=session_state,
|
|
271
|
+
stream_events=True,
|
|
272
|
+
websocket_handler=websocket_handler,
|
|
273
|
+
):
|
|
274
|
+
yield event
|
|
275
|
+
|
|
276
|
+
from agno.run.workflow import WorkflowCompletedEvent
|
|
277
|
+
|
|
278
|
+
if isinstance(event, WorkflowCompletedEvent):
|
|
279
|
+
final_content = str(event.content) if event.content else ""
|
|
280
|
+
|
|
281
|
+
yield final_content
|
|
282
|
+
else:
|
|
283
|
+
result = await workflow._aexecute(
|
|
284
|
+
session_id=session_from_db.session_id,
|
|
285
|
+
user_id=session_from_db.user_id,
|
|
286
|
+
execution_input=workflow_execution_input,
|
|
287
|
+
workflow_run_response=workflow_run_response,
|
|
288
|
+
session_state=session_state,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
if isinstance(result.content, str):
|
|
292
|
+
yield result.content
|
|
293
|
+
elif isinstance(result.content, BaseModel):
|
|
294
|
+
yield result.content.model_dump_json(exclude_none=True)
|
|
295
|
+
else:
|
|
296
|
+
yield str(result.content)
|
|
297
|
+
|
|
298
|
+
return run_workflow
|