agno 2.2.5__py3-none-any.whl → 2.2.7__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 (57) hide show
  1. agno/agent/agent.py +500 -423
  2. agno/api/os.py +1 -1
  3. agno/culture/manager.py +12 -8
  4. agno/guardrails/prompt_injection.py +1 -0
  5. agno/knowledge/chunking/agentic.py +6 -2
  6. agno/knowledge/embedder/vllm.py +262 -0
  7. agno/knowledge/knowledge.py +37 -5
  8. agno/memory/manager.py +9 -4
  9. agno/models/anthropic/claude.py +1 -2
  10. agno/models/azure/ai_foundry.py +31 -14
  11. agno/models/azure/openai_chat.py +12 -4
  12. agno/models/base.py +106 -65
  13. agno/models/cerebras/cerebras.py +11 -6
  14. agno/models/groq/groq.py +7 -4
  15. agno/models/meta/llama.py +12 -6
  16. agno/models/meta/llama_openai.py +5 -1
  17. agno/models/openai/chat.py +26 -17
  18. agno/models/openai/responses.py +11 -63
  19. agno/models/requesty/requesty.py +5 -2
  20. agno/models/utils.py +254 -8
  21. agno/models/vertexai/claude.py +9 -13
  22. agno/os/app.py +13 -12
  23. agno/os/routers/evals/evals.py +8 -8
  24. agno/os/routers/evals/utils.py +1 -0
  25. agno/os/schema.py +56 -38
  26. agno/os/utils.py +27 -0
  27. agno/run/__init__.py +6 -0
  28. agno/run/agent.py +5 -0
  29. agno/run/base.py +18 -1
  30. agno/run/team.py +13 -9
  31. agno/run/workflow.py +39 -0
  32. agno/session/summary.py +8 -2
  33. agno/session/workflow.py +4 -3
  34. agno/team/team.py +302 -369
  35. agno/tools/exa.py +21 -16
  36. agno/tools/file.py +153 -25
  37. agno/tools/function.py +98 -17
  38. agno/tools/mcp/mcp.py +8 -1
  39. agno/tools/notion.py +204 -0
  40. agno/utils/agent.py +78 -0
  41. agno/utils/events.py +2 -0
  42. agno/utils/hooks.py +1 -1
  43. agno/utils/models/claude.py +25 -8
  44. agno/utils/print_response/workflow.py +115 -16
  45. agno/vectordb/__init__.py +2 -1
  46. agno/vectordb/milvus/milvus.py +5 -0
  47. agno/vectordb/redis/__init__.py +5 -0
  48. agno/vectordb/redis/redisdb.py +687 -0
  49. agno/workflow/__init__.py +2 -0
  50. agno/workflow/agent.py +299 -0
  51. agno/workflow/step.py +13 -2
  52. agno/workflow/workflow.py +969 -72
  53. {agno-2.2.5.dist-info → agno-2.2.7.dist-info}/METADATA +10 -3
  54. {agno-2.2.5.dist-info → agno-2.2.7.dist-info}/RECORD +57 -52
  55. {agno-2.2.5.dist-info → agno-2.2.7.dist-info}/WHEEL +0 -0
  56. {agno-2.2.5.dist-info → agno-2.2.7.dist-info}/licenses/LICENSE +0 -0
  57. {agno-2.2.5.dist-info → agno-2.2.7.dist-info}/top_level.txt +0 -0
agno/workflow/agent.py ADDED
@@ -0,0 +1,299 @@
1
+ """WorkflowAgent - A restricted Agent for workflow orchestration"""
2
+
3
+ from typing import TYPE_CHECKING, Any, Callable, Optional
4
+
5
+ from agno.agent import Agent
6
+ from agno.models.base import Model
7
+ from agno.run import RunContext
8
+ from agno.workflow.types import WebSocketHandler
9
+
10
+ if TYPE_CHECKING:
11
+ from agno.session.workflow import WorkflowSession
12
+ from agno.workflow.types import WorkflowExecutionInput
13
+
14
+
15
+ class WorkflowAgent(Agent):
16
+ """
17
+ A restricted Agent class specifically designed for workflow orchestration.
18
+ This agent can:
19
+ 1. Decide whether to run the workflow or answer directly from history
20
+ 2. Call the workflow execution tool when needed
21
+ 3. Access workflow session history for context
22
+ Restrictions:
23
+ - Only model configuration allowed
24
+ - No custom tools (tools are set by workflow)
25
+ - No knowledge base
26
+ - Limited configuration options
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ model: Model,
32
+ instructions: Optional[str] = None,
33
+ add_workflow_history: bool = True,
34
+ num_history_runs: int = 5,
35
+ ):
36
+ """
37
+ Initialize WorkflowAgent with restricted parameters.
38
+ Args:
39
+ model: The model to use for the agent (required)
40
+ instructions: Custom instructions (will be combined with workflow context)
41
+ add_workflow_history: Whether to add workflow history to context (default: True)
42
+ num_history_runs: Number of previous workflow runs to include in context (default: 5)
43
+ """
44
+ self.add_workflow_history = add_workflow_history
45
+
46
+ default_instructions = """You are a workflow orchestration agent. Your job is to help users by either:
47
+ 1. **Answering directly** from the workflow history context if the question can be answered from previous runs
48
+ 2. **Running the workflow** by calling the run_workflow tool ONCE when you need to process a new query
49
+
50
+ Guidelines:
51
+ - ALWAYS check the workflow history first before calling the tool
52
+ - Answer directly from history if:
53
+ * The user asks about something already in history
54
+ * The user asks for comparisons/analysis of things in history (e.g., "compare X and Y")
55
+ * The user asks follow-up questions about previous results
56
+ - Only call the run_workflow tool for NEW topics not covered in history
57
+ - IMPORTANT: Do NOT call the tool multiple times. Call it once and use the result.
58
+ - Keep your responses concise and helpful
59
+ - When you must call the workflow, pass a clear and concise query
60
+
61
+ {workflow_context}
62
+ """
63
+
64
+ if instructions:
65
+ if "{workflow_context}" not in instructions:
66
+ # Add the workflow context placeholder
67
+ final_instructions = f"{instructions}\n\n{{workflow_context}}"
68
+ else:
69
+ final_instructions = instructions
70
+ else:
71
+ final_instructions = default_instructions
72
+
73
+ super().__init__(
74
+ model=model,
75
+ instructions=final_instructions,
76
+ resolve_in_context=True,
77
+ num_history_runs=num_history_runs,
78
+ )
79
+
80
+ def create_workflow_tool(
81
+ self,
82
+ workflow: "Any", # Workflow type
83
+ session: "WorkflowSession",
84
+ execution_input: "WorkflowExecutionInput",
85
+ run_context: RunContext,
86
+ stream: bool = False,
87
+ ) -> Callable:
88
+ """
89
+ Create the workflow execution tool that this agent can call.
90
+ This is similar to how Agent has search_knowledge_base() method.
91
+ Args:
92
+ workflow: The workflow instance
93
+ session: The workflow session
94
+ execution_input: The execution input
95
+ run_context: The run context
96
+ stream: Whether to stream the workflow execution
97
+ Returns:
98
+ Callable tool function
99
+ """
100
+ from datetime import datetime
101
+ from uuid import uuid4
102
+
103
+ from pydantic import BaseModel
104
+
105
+ from agno.run.workflow import WorkflowRunOutput
106
+ from agno.utils.log import log_debug
107
+ from agno.workflow.types import WorkflowExecutionInput
108
+
109
+ def run_workflow(query: str):
110
+ """
111
+ Execute the complete workflow with the given query.
112
+ Use this tool when you need to run the workflow to answer the user's question.
113
+
114
+ Args:
115
+ query: The input query/question to process through the workflow
116
+ Returns:
117
+ The workflow execution result (str in non-streaming, generator in streaming)
118
+ """
119
+ # Reload session to get latest data from database
120
+ # This ensures we don't overwrite any updates made after the tool was created
121
+ session_from_db = workflow.get_session(session_id=session.session_id)
122
+ if session_from_db is None:
123
+ session_from_db = session # Fallback to closure session if reload fails
124
+ log_debug(f"Fallback to closure session: {len(session_from_db.runs or [])} runs")
125
+ else:
126
+ log_debug(f"Reloaded session before tool execution: {len(session_from_db.runs or [])} runs")
127
+
128
+ # Create a new run ID for this execution
129
+ run_id = str(uuid4())
130
+
131
+ workflow_run_response = WorkflowRunOutput(
132
+ run_id=run_id,
133
+ input=execution_input.input, # Use original user input
134
+ session_id=session_from_db.session_id,
135
+ workflow_id=workflow.id,
136
+ workflow_name=workflow.name,
137
+ created_at=int(datetime.now().timestamp()),
138
+ )
139
+
140
+ workflow_execution_input = WorkflowExecutionInput(
141
+ input=query, # Agent's refined query for execution
142
+ additional_data=execution_input.additional_data,
143
+ audio=execution_input.audio,
144
+ images=execution_input.images,
145
+ videos=execution_input.videos,
146
+ files=execution_input.files,
147
+ )
148
+
149
+ # ===== EXECUTION LOGIC (Based on streaming mode) =====
150
+ if stream:
151
+ final_content = ""
152
+ for event in workflow._execute_stream(
153
+ session=session_from_db,
154
+ run_context=run_context,
155
+ execution_input=workflow_execution_input,
156
+ workflow_run_response=workflow_run_response,
157
+ stream_events=True,
158
+ ):
159
+ yield event
160
+
161
+ # Capture final content from WorkflowCompletedEvent
162
+ from agno.run.workflow import WorkflowCompletedEvent
163
+
164
+ if isinstance(event, WorkflowCompletedEvent):
165
+ final_content = str(event.content) if event.content else ""
166
+
167
+ return final_content
168
+ else:
169
+ # NON-STREAMING MODE: Execute synchronously
170
+ result = workflow._execute(
171
+ session=session_from_db,
172
+ execution_input=workflow_execution_input,
173
+ workflow_run_response=workflow_run_response,
174
+ run_context=run_context,
175
+ )
176
+
177
+ if isinstance(result.content, str):
178
+ return result.content
179
+ elif isinstance(result.content, BaseModel):
180
+ return result.content.model_dump_json(exclude_none=True)
181
+ else:
182
+ return str(result.content)
183
+
184
+ return run_workflow
185
+
186
+ def async_create_workflow_tool(
187
+ self,
188
+ workflow: "Any", # Workflow type
189
+ session: "WorkflowSession",
190
+ execution_input: "WorkflowExecutionInput",
191
+ run_context: RunContext,
192
+ stream: bool = False,
193
+ websocket_handler: Optional[WebSocketHandler] = None,
194
+ ) -> Callable:
195
+ """
196
+ Create the async workflow execution tool that this agent can call.
197
+ This is the async counterpart of create_workflow_tool.
198
+
199
+ Args:
200
+ workflow: The workflow instance
201
+ session: The workflow session
202
+ execution_input: The execution input
203
+ run_context: The run context
204
+ stream: Whether to stream the workflow execution
205
+
206
+ Returns:
207
+ Async callable tool function
208
+ """
209
+ from datetime import datetime
210
+ from uuid import uuid4
211
+
212
+ from pydantic import BaseModel
213
+
214
+ from agno.run.workflow import WorkflowRunOutput
215
+ from agno.utils.log import log_debug
216
+ from agno.workflow.types import WorkflowExecutionInput
217
+
218
+ async def run_workflow(query: str):
219
+ """
220
+ Execute the complete workflow with the given query asynchronously.
221
+ Use this tool when you need to run the workflow to answer the user's question.
222
+
223
+ Args:
224
+ query: The input query/question to process through the workflow
225
+
226
+ Returns:
227
+ The workflow execution result (str in non-streaming, async generator in streaming)
228
+ """
229
+ # Reload session to get latest data from database
230
+ # This ensures we don't overwrite any updates made after the tool was created
231
+ # Use async or sync method based on database type
232
+ if workflow._has_async_db():
233
+ session_from_db = await workflow.aget_session(session_id=session.session_id)
234
+ else:
235
+ session_from_db = workflow.get_session(session_id=session.session_id)
236
+
237
+ if session_from_db is None:
238
+ session_from_db = session # Fallback to closure session if reload fails
239
+ log_debug(f"Fallback to closure session: {len(session_from_db.runs or [])} runs")
240
+ else:
241
+ log_debug(f"Reloaded session before async tool execution: {len(session_from_db.runs or [])} runs")
242
+
243
+ # Create a new run ID for this execution
244
+ run_id = str(uuid4())
245
+
246
+ workflow_run_response = WorkflowRunOutput(
247
+ run_id=run_id,
248
+ input=execution_input.input, # Use original user input
249
+ session_id=session_from_db.session_id,
250
+ workflow_id=workflow.id,
251
+ workflow_name=workflow.name,
252
+ created_at=int(datetime.now().timestamp()),
253
+ )
254
+
255
+ workflow_execution_input = WorkflowExecutionInput(
256
+ input=query, # Agent's refined query for execution
257
+ additional_data=execution_input.additional_data,
258
+ audio=execution_input.audio,
259
+ images=execution_input.images,
260
+ videos=execution_input.videos,
261
+ files=execution_input.files,
262
+ )
263
+
264
+ if stream:
265
+ final_content = ""
266
+ async for event in workflow._aexecute_stream(
267
+ session_id=session_from_db.session_id,
268
+ user_id=session_from_db.user_id,
269
+ execution_input=workflow_execution_input,
270
+ workflow_run_response=workflow_run_response,
271
+ session_state=run_context.session_state,
272
+ stream_events=True,
273
+ websocket_handler=websocket_handler,
274
+ ):
275
+ yield event
276
+
277
+ from agno.run.workflow import WorkflowCompletedEvent
278
+
279
+ if isinstance(event, WorkflowCompletedEvent):
280
+ final_content = str(event.content) if event.content else ""
281
+
282
+ yield final_content
283
+ else:
284
+ result = await workflow._aexecute(
285
+ session_id=session_from_db.session_id,
286
+ user_id=session_from_db.user_id,
287
+ execution_input=workflow_execution_input,
288
+ workflow_run_response=workflow_run_response,
289
+ session_state=run_context.session_state,
290
+ )
291
+
292
+ if isinstance(result.content, str):
293
+ yield result.content
294
+ elif isinstance(result.content, BaseModel):
295
+ yield result.content.model_dump_json(exclude_none=True)
296
+ else:
297
+ yield str(result.content)
298
+
299
+ return run_workflow
agno/workflow/step.py CHANGED
@@ -10,6 +10,7 @@ from typing_extensions import TypeGuard
10
10
  from agno.agent import Agent
11
11
  from agno.media import Audio, Image, Video
12
12
  from agno.models.metrics import Metrics
13
+ from agno.run import RunContext
13
14
  from agno.run.agent import RunOutput
14
15
  from agno.run.team import TeamRunOutput
15
16
  from agno.run.workflow import (
@@ -210,6 +211,7 @@ class Step:
210
211
  session_id: Optional[str] = None,
211
212
  user_id: Optional[str] = None,
212
213
  workflow_run_response: Optional["WorkflowRunOutput"] = None,
214
+ run_context: Optional[RunContext] = None,
213
215
  session_state: Optional[Dict[str, Any]] = None,
214
216
  store_executor_outputs: bool = True,
215
217
  workflow_session: Optional[WorkflowSession] = None,
@@ -224,7 +226,11 @@ class Step:
224
226
 
225
227
  if workflow_session:
226
228
  step_input.workflow_session = workflow_session
227
- session_state_copy = copy(session_state) if session_state is not None else {}
229
+
230
+ if run_context is not None and run_context.session_state is not None:
231
+ session_state_copy = copy(run_context.session_state)
232
+ else:
233
+ session_state_copy = copy(session_state) if session_state is not None else {}
228
234
 
229
235
  # Execute with retries
230
236
  for attempt in range(self.max_retries + 1):
@@ -407,6 +413,7 @@ class Step:
407
413
  stream_intermediate_steps: bool = False,
408
414
  stream_executor_events: bool = True,
409
415
  workflow_run_response: Optional["WorkflowRunOutput"] = None,
416
+ run_context: Optional[RunContext] = None,
410
417
  session_state: Optional[Dict[str, Any]] = None,
411
418
  step_index: Optional[Union[int, tuple]] = None,
412
419
  store_executor_outputs: bool = True,
@@ -422,8 +429,12 @@ class Step:
422
429
 
423
430
  if workflow_session:
424
431
  step_input.workflow_session = workflow_session
432
+
425
433
  # Create session_state copy once to avoid duplication
426
- session_state_copy = copy(session_state) if session_state is not None else {}
434
+ if run_context is not None and run_context.session_state is not None:
435
+ session_state_copy = copy(run_context.session_state)
436
+ else:
437
+ session_state_copy = copy(session_state) if session_state is not None else {}
427
438
 
428
439
  # Considering both stream_events and stream_intermediate_steps (deprecated)
429
440
  stream_events = stream_events or stream_intermediate_steps