agno 2.2.4__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.
Files changed (41) hide show
  1. agno/agent/agent.py +82 -19
  2. agno/culture/manager.py +3 -4
  3. agno/knowledge/chunking/agentic.py +6 -2
  4. agno/memory/manager.py +9 -4
  5. agno/models/anthropic/claude.py +1 -2
  6. agno/models/azure/ai_foundry.py +31 -14
  7. agno/models/azure/openai_chat.py +12 -4
  8. agno/models/base.py +44 -11
  9. agno/models/cerebras/cerebras.py +11 -6
  10. agno/models/groq/groq.py +7 -4
  11. agno/models/meta/llama.py +12 -6
  12. agno/models/meta/llama_openai.py +5 -1
  13. agno/models/openai/chat.py +20 -12
  14. agno/models/openai/responses.py +10 -5
  15. agno/models/utils.py +254 -8
  16. agno/models/vertexai/claude.py +9 -13
  17. agno/os/app.py +48 -21
  18. agno/os/routers/evals/evals.py +8 -8
  19. agno/os/routers/evals/utils.py +1 -0
  20. agno/os/schema.py +48 -33
  21. agno/os/utils.py +27 -0
  22. agno/run/agent.py +5 -0
  23. agno/run/team.py +2 -0
  24. agno/run/workflow.py +39 -0
  25. agno/session/summary.py +8 -2
  26. agno/session/workflow.py +4 -3
  27. agno/team/team.py +50 -14
  28. agno/tools/file.py +153 -25
  29. agno/tools/function.py +5 -1
  30. agno/tools/notion.py +201 -0
  31. agno/utils/events.py +2 -0
  32. agno/utils/print_response/workflow.py +115 -16
  33. agno/vectordb/milvus/milvus.py +5 -0
  34. agno/workflow/__init__.py +2 -0
  35. agno/workflow/agent.py +298 -0
  36. agno/workflow/workflow.py +929 -64
  37. {agno-2.2.4.dist-info → agno-2.2.6.dist-info}/METADATA +4 -1
  38. {agno-2.2.4.dist-info → agno-2.2.6.dist-info}/RECORD +41 -39
  39. {agno-2.2.4.dist-info → agno-2.2.6.dist-info}/WHEEL +0 -0
  40. {agno-2.2.4.dist-info → agno-2.2.6.dist-info}/licenses/LICENSE +0 -0
  41. {agno-2.2.4.dist-info → agno-2.2.6.dist-info}/top_level.txt +0 -0
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