agno 2.0.1__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/tools/memory.py ADDED
@@ -0,0 +1,391 @@
1
+ import json
2
+ from textwrap import dedent
3
+ from typing import Any, Dict, List, Optional
4
+ from uuid import uuid4
5
+
6
+ from agno.db.base import BaseDb
7
+ from agno.db.schemas import UserMemory
8
+ from agno.tools import Toolkit
9
+ from agno.utils.log import log_debug, log_error
10
+
11
+
12
+ class MemoryTools(Toolkit):
13
+ def __init__(
14
+ self,
15
+ db: BaseDb,
16
+ enable_get_memories: bool = True,
17
+ enable_add_memory: bool = True,
18
+ enable_update_memory: bool = True,
19
+ enable_delete_memory: bool = True,
20
+ enable_analyze: bool = True,
21
+ enable_think: bool = True,
22
+ instructions: Optional[str] = None,
23
+ add_instructions: bool = True,
24
+ add_few_shot: bool = True,
25
+ few_shot_examples: Optional[str] = None,
26
+ all: bool = False,
27
+ **kwargs,
28
+ ):
29
+ # Add instructions for using this toolkit
30
+ if instructions is None:
31
+ self.instructions = self.DEFAULT_INSTRUCTIONS
32
+ if add_few_shot:
33
+ if few_shot_examples is not None:
34
+ self.instructions += "\n" + few_shot_examples
35
+ else:
36
+ self.instructions += "\n" + self.FEW_SHOT_EXAMPLES
37
+ else:
38
+ self.instructions = instructions
39
+
40
+ # The database to use for memory operations
41
+ self.db: BaseDb = db
42
+
43
+ tools: List[Any] = []
44
+ if enable_think or all:
45
+ tools.append(self.think)
46
+ if enable_get_memories or all:
47
+ tools.append(self.get_memories)
48
+ if enable_add_memory or all:
49
+ tools.append(self.add_memory)
50
+ if enable_update_memory or all:
51
+ tools.append(self.update_memory)
52
+ if enable_delete_memory or all:
53
+ tools.append(self.delete_memory)
54
+ if enable_analyze or all:
55
+ tools.append(self.analyze)
56
+
57
+ super().__init__(
58
+ name="memory_tools",
59
+ instructions=self.instructions,
60
+ add_instructions=add_instructions,
61
+ tools=tools,
62
+ **kwargs,
63
+ )
64
+
65
+ def think(self, session_state: Dict[str, Any], thought: str) -> str:
66
+ """Use this tool as a scratchpad to reason about memory operations, refine your approach, brainstorm memory content, or revise your plan.
67
+
68
+ Call `Think` whenever you need to figure out what to do next, analyze the user's requirements, plan memory operations, or decide on execution strategy.
69
+ You should use this tool as frequently as needed.
70
+
71
+ Args:
72
+ thought: Your thought process and reasoning about memory operations.
73
+ """
74
+ try:
75
+ log_debug(f"Memory Thought: {thought}")
76
+
77
+ # Add the thought to the session state
78
+ if session_state is None:
79
+ session_state = {}
80
+ if "memory_thoughts" not in session_state:
81
+ session_state["memory_thoughts"] = []
82
+ session_state["memory_thoughts"].append(thought)
83
+
84
+ # Return the full log of thoughts and the new thought
85
+ thoughts = "\n".join([f"- {t}" for t in session_state["memory_thoughts"]])
86
+ formatted_thoughts = dedent(
87
+ f"""Memory Thoughts:
88
+ {thoughts}
89
+ """
90
+ ).strip()
91
+ return formatted_thoughts
92
+ except Exception as e:
93
+ log_error(f"Error recording memory thought: {e}")
94
+ return f"Error recording memory thought: {e}"
95
+
96
+ def get_memories(self, session_state: Dict[str, Any]) -> str:
97
+ """
98
+ Use this tool to get a list of memories from the database.
99
+ """
100
+ try:
101
+ # Get user info from session state
102
+ user_id = session_state.get("current_user_id") if session_state else None
103
+
104
+ memories = self.db.get_user_memories(user_id=user_id)
105
+ return json.dumps([memory.to_dict() for memory in memories], indent=2) # type: ignore
106
+ except Exception as e:
107
+ log_error(f"Error getting memories: {e}")
108
+ return json.dumps({"error": str(e)}, indent=2)
109
+
110
+ def add_memory(
111
+ self,
112
+ session_state: Dict[str, Any],
113
+ memory: str,
114
+ topics: Optional[List[str]] = None,
115
+ ) -> str:
116
+ """Use this tool to add a new memory to the database.
117
+
118
+ Args:
119
+ memory: The memory content to store
120
+ topics: Optional list of topics associated with this memory
121
+
122
+ Returns:
123
+ str: JSON string containing the created memory information
124
+ """
125
+ try:
126
+ log_debug(f"Adding memory: {memory}")
127
+
128
+ # Get user and agent info from session state
129
+ user_id = session_state.get("current_user_id") if session_state else None
130
+
131
+ # Create UserMemory object
132
+ user_memory = UserMemory(
133
+ memory_id=str(uuid4()),
134
+ memory=memory,
135
+ topics=topics,
136
+ user_id=user_id,
137
+ )
138
+
139
+ # Add to database
140
+ created_memory = self.db.upsert_user_memory(user_memory)
141
+
142
+ # Store the result in session state for analysis
143
+ if session_state is None:
144
+ session_state = {}
145
+ if "memory_operations" not in session_state:
146
+ session_state["memory_operations"] = []
147
+
148
+ memory_dict = created_memory.to_dict() if created_memory else None # type: ignore
149
+
150
+ operation_result = {
151
+ "operation": "add_memory",
152
+ "success": created_memory is not None,
153
+ "memory": memory_dict,
154
+ "error": None,
155
+ }
156
+ session_state["memory_operations"].append(operation_result)
157
+
158
+ if created_memory:
159
+ return json.dumps({"success": True, "operation": "add_memory", "memory": memory_dict}, indent=2)
160
+ else:
161
+ return json.dumps(
162
+ {"success": False, "operation": "add_memory", "error": "Failed to create memory"}, indent=2
163
+ )
164
+
165
+ except Exception as e:
166
+ log_error(f"Error adding memory: {e}")
167
+ return json.dumps({"success": False, "operation": "add_memory", "error": str(e)}, indent=2)
168
+
169
+ def update_memory(
170
+ self,
171
+ session_state: Dict[str, Any],
172
+ memory_id: str,
173
+ memory: Optional[str] = None,
174
+ topics: Optional[List[str]] = None,
175
+ ) -> str:
176
+ """Use this tool to update an existing memory in the database.
177
+
178
+ Args:
179
+ memory_id: The ID of the memory to update
180
+ memory: Updated memory content (if provided)
181
+ topics: Updated list of topics (if provided)
182
+
183
+ Returns:
184
+ str: JSON string containing the updated memory information
185
+ """
186
+ try:
187
+ log_debug(f"Updating memory: {memory_id}")
188
+
189
+ # First get the existing memory
190
+ existing_memory = self.db.get_user_memory(memory_id)
191
+ if not existing_memory:
192
+ return json.dumps(
193
+ {"success": False, "operation": "update_memory", "error": f"Memory with ID {memory_id} not found"},
194
+ indent=2,
195
+ )
196
+
197
+ # Update fields if provided
198
+ updated_memory = UserMemory(
199
+ memory=memory if memory is not None else existing_memory.memory, # type: ignore
200
+ memory_id=memory_id,
201
+ topics=topics if topics is not None else existing_memory.topics, # type: ignore
202
+ user_id=existing_memory.user_id, # type: ignore
203
+ )
204
+
205
+ # Update in database
206
+ updated_result = self.db.upsert_user_memory(updated_memory)
207
+
208
+ # Store the result in session state for analysis
209
+ if session_state is None:
210
+ session_state = {}
211
+ if "memory_operations" not in session_state:
212
+ session_state["memory_operations"] = []
213
+
214
+ memory_dict = updated_result.to_dict() if updated_result else None # type: ignore
215
+
216
+ operation_result = {
217
+ "operation": "update_memory",
218
+ "success": updated_result is not None,
219
+ "memory": memory_dict,
220
+ "error": None,
221
+ }
222
+ session_state["memory_operations"].append(operation_result)
223
+
224
+ if updated_result:
225
+ return json.dumps({"success": True, "operation": "update_memory", "memory": memory_dict}, indent=2)
226
+ else:
227
+ return json.dumps(
228
+ {"success": False, "operation": "update_memory", "error": "Failed to update memory"}, indent=2
229
+ )
230
+
231
+ except Exception as e:
232
+ log_error(f"Error updating memory: {e}")
233
+ return json.dumps({"success": False, "operation": "update_memory", "error": str(e)}, indent=2)
234
+
235
+ def delete_memory(
236
+ self,
237
+ session_state: Dict[str, Any],
238
+ memory_id: str,
239
+ ) -> str:
240
+ """Use this tool to delete a memory from the database.
241
+
242
+ Args:
243
+ memory_id: The ID of the memory to delete
244
+
245
+ Returns:
246
+ str: JSON string containing the deletion result
247
+ """
248
+ try:
249
+ log_debug(f"Deleting memory: {memory_id}")
250
+
251
+ # Check if memory exists before deletion
252
+ existing_memory = self.db.get_user_memory(memory_id)
253
+ if not existing_memory:
254
+ return json.dumps(
255
+ {"success": False, "operation": "delete_memory", "error": f"Memory with ID {memory_id} not found"},
256
+ indent=2,
257
+ )
258
+
259
+ # Delete from database
260
+ self.db.delete_user_memory(memory_id)
261
+
262
+ # Store the result in session state for analysis
263
+ if session_state is None:
264
+ session_state = {}
265
+ if "memory_operations" not in session_state:
266
+ session_state["memory_operations"] = []
267
+
268
+ memory_dict = existing_memory.to_dict() if existing_memory else None # type: ignore
269
+
270
+ operation_result = {
271
+ "operation": "delete_memory",
272
+ "success": True,
273
+ "memory_id": memory_id,
274
+ "deleted_memory": memory_dict,
275
+ "error": None,
276
+ }
277
+ session_state["memory_operations"].append(operation_result)
278
+
279
+ return json.dumps(
280
+ {
281
+ "success": True,
282
+ "operation": "delete_memory",
283
+ "memory_id": memory_id,
284
+ "deleted_memory": memory_dict,
285
+ },
286
+ indent=2,
287
+ )
288
+
289
+ except Exception as e:
290
+ log_error(f"Error deleting memory: {e}")
291
+ return json.dumps({"success": False, "operation": "delete_memory", "error": str(e)}, indent=2)
292
+
293
+ def analyze(self, session_state: Dict[str, Any], analysis: str) -> str:
294
+ """Use this tool to evaluate whether the memory operations results are correct and sufficient.
295
+ If not, go back to "Think" or use memory operations with refined parameters.
296
+
297
+ Args:
298
+ analysis: Your analysis of the memory operations results.
299
+ """
300
+ try:
301
+ log_debug(f"Memory Analysis: {analysis}")
302
+
303
+ # Add the analysis to the session state
304
+ if session_state is None:
305
+ session_state = {}
306
+ if "memory_analysis" not in session_state:
307
+ session_state["memory_analysis"] = []
308
+ session_state["memory_analysis"].append(analysis)
309
+
310
+ # Return the full log of analysis and the new analysis
311
+ analysis_log = "\n".join([f"- {a}" for a in session_state["memory_analysis"]])
312
+ formatted_analysis = dedent(
313
+ f"""Memory Analysis:
314
+ {analysis_log}
315
+ """
316
+ ).strip()
317
+ return formatted_analysis
318
+ except Exception as e:
319
+ log_error(f"Error recording memory analysis: {e}")
320
+ return f"Error recording memory analysis: {e}"
321
+
322
+ DEFAULT_INSTRUCTIONS = dedent("""\
323
+ You have access to the Think, Add Memory, Update Memory, Delete Memory, and Analyze tools that will help you manage user memories and analyze their operations. Use these tools as frequently as needed to successfully complete memory management tasks.
324
+
325
+ ## How to use the Think, Memory Operations, and Analyze tools:
326
+
327
+ 1. **Think**
328
+ - Purpose: A scratchpad for planning memory operations, brainstorming memory content, and refining your approach. You never reveal your "Think" content to the user.
329
+ - Usage: Call `think` whenever you need to figure out what memory operations to perform, analyze requirements, or decide on strategy.
330
+
331
+ 2. **Add Memory**
332
+ - Purpose: Creates new memories in the database with specified content and metadata.
333
+ - Usage: Call `add_memory` with memory content and optional topics when you need to store new information.
334
+
335
+ 3. **Update Memory**
336
+ - Purpose: Modifies existing memories in the database by memory ID.
337
+ - Usage: Call `update_memory` with a memory ID and the fields you want to change. Only specify the fields that need updating.
338
+
339
+ 4. **Delete Memory**
340
+ - Purpose: Removes memories from the database by memory ID.
341
+ - Usage: Call `delete_memory` with a memory ID when a memory is no longer needed or requested to be removed.
342
+
343
+ 5. **Analyze**
344
+ - Purpose: Evaluate whether the memory operations results are correct and sufficient. If not, go back to "Think" or use memory operations with refined parameters.
345
+ - Usage: Call `analyze` after performing memory operations to verify:
346
+ - Success: Did the operation complete successfully?
347
+ - Accuracy: Is the memory content correct and well-formed?
348
+ - Completeness: Are all required fields populated appropriately?
349
+ - Errors: Were there any failures or unexpected behaviors?
350
+
351
+ **Important Guidelines**:
352
+ - Do not include your internal chain-of-thought in direct user responses.
353
+ - Use "Think" to reason internally. These notes are never exposed to the user.
354
+ - When you provide a final answer to the user, be clear, concise, and based on the memory operation results.
355
+ - If memory operations fail or produce unexpected results, acknowledge limitations and explain what went wrong.
356
+ - Always verify memory IDs exist before attempting updates or deletions.
357
+ - Use descriptive topics and clear memory content to make memories easily searchable and understandable.\
358
+ """)
359
+
360
+ FEW_SHOT_EXAMPLES = dedent("""\
361
+ You can refer to the examples below as guidance for how to use each tool.
362
+
363
+ ### Examples
364
+
365
+ #### Example 1: Adding User Preferences
366
+
367
+ User: I prefer vegetarian recipes and I'm allergic to nuts.
368
+ Think: I should store the user's dietary preferences. I should create a memory with this information and use relevant topics for easy retrieval.
369
+ Add Memory: memory="User prefers vegetarian recipes and is allergic to nuts", topics=["dietary_preferences", "allergies", "food"]
370
+ Analyze: Successfully created memory with dietary preferences. The topics are well-chosen for future retrieval. This should help with future food-related requests.
371
+
372
+ Final Answer: Noted. I've stored your dietary preferences. I'll remember that you prefer vegetarian recipes and have a nut allergy for future reference.
373
+
374
+ #### Example 2: Updating Existing Information
375
+
376
+ User: Actually, update my dietary info - I'm now eating fish too, so I'm pescatarian.
377
+ Think: The user wants to update their previous dietary preference from vegetarian to pescatarian. I need to find their existing dietary memory and update it.
378
+ Update Memory: memory_id="previous_memory_id", memory="User follows pescatarian diet (vegetarian + fish) and is allergic to nuts", topics=["dietary_preferences", "allergies", "food", "pescatarian"]
379
+ Analyze: Successfully updated the dietary preference memory. The content now accurately reflects pescatarian diet and maintains the nut allergy information.
380
+
381
+ Final Answer: I've updated your dietary preferences to reflect that you follow a pescatarian diet (vegetarian plus fish) while maintaining your nut allergy information.
382
+
383
+ #### Example 3: Removing Outdated Information
384
+
385
+ User: Please forget about my old work schedule - it's completely changed.
386
+ Think: The user wants me to delete their old work schedule memory since it's no longer relevant. I should find and remove that memory.
387
+ Delete Memory: memory_id="work_schedule_memory_id"
388
+ Analyze: Successfully deleted the outdated work schedule memory. The old information won't interfere with future scheduling requests.
389
+
390
+ Final Answer: I've removed your old work schedule information. Feel free to share your new schedule when you're ready, and I'll store the updated information.\
391
+ """)
agno/tools/workflow.py ADDED
@@ -0,0 +1,279 @@
1
+ import json
2
+ from textwrap import dedent
3
+ from typing import Any, Dict, Optional
4
+
5
+ from pydantic import BaseModel
6
+
7
+ from agno.tools import Toolkit
8
+ from agno.utils.log import log_debug, log_error
9
+ from agno.workflow.workflow import Workflow, WorkflowRunOutput
10
+
11
+
12
+ class RunWorkflowInput(BaseModel):
13
+ input_data: str
14
+ additional_data: Optional[Dict[str, Any]] = None
15
+
16
+
17
+ class WorkflowTools(Toolkit):
18
+ def __init__(
19
+ self,
20
+ workflow: Workflow,
21
+ enable_run_workflow: bool = True,
22
+ enable_think: bool = False,
23
+ enable_analyze: bool = False,
24
+ all: bool = False,
25
+ instructions: Optional[str] = None,
26
+ add_instructions: bool = True,
27
+ add_few_shot: bool = False,
28
+ few_shot_examples: Optional[str] = None,
29
+ async_mode: bool = False,
30
+ **kwargs,
31
+ ):
32
+ # Add instructions for using this toolkit
33
+ if instructions is None:
34
+ self.instructions = self.DEFAULT_INSTRUCTIONS
35
+ if add_few_shot:
36
+ if few_shot_examples is not None:
37
+ self.instructions += "\n" + few_shot_examples
38
+ else:
39
+ self.instructions = instructions
40
+
41
+ # The workflow to execute
42
+ self.workflow: Workflow = workflow
43
+
44
+ super().__init__(
45
+ name="workflow_tools",
46
+ instructions=self.instructions,
47
+ add_instructions=add_instructions,
48
+ auto_register=False,
49
+ **kwargs,
50
+ )
51
+
52
+ if enable_think or all:
53
+ if async_mode:
54
+ self.register(self.async_think, name="think")
55
+ else:
56
+ self.register(self.think, name="think")
57
+ if enable_run_workflow or all:
58
+ if async_mode:
59
+ self.register(self.async_run_workflow, name="run_workflow")
60
+ else:
61
+ self.register(self.run_workflow, name="run_workflow")
62
+ if enable_analyze or all:
63
+ if async_mode:
64
+ self.register(self.async_analyze, name="analyze")
65
+ else:
66
+ self.register(self.analyze, name="analyze")
67
+
68
+ def think(self, session_state: Dict[str, Any], thought: str) -> str:
69
+ """Use this tool as a scratchpad to reason about the workflow execution, refine your approach, brainstorm workflow inputs, or revise your plan.
70
+ Call `Think` whenever you need to figure out what to do next, analyze the user's requirements, plan workflow inputs, or decide on execution strategy.
71
+ You should use this tool as frequently as needed.
72
+ Args:
73
+ thought: Your thought process and reasoning about workflow execution.
74
+ """
75
+ try:
76
+ log_debug(f"Workflow Thought: {thought}")
77
+
78
+ # Add the thought to the session state
79
+ if session_state is None:
80
+ session_state = {}
81
+ if "workflow_thoughts" not in session_state:
82
+ session_state["workflow_thoughts"] = []
83
+ session_state["workflow_thoughts"].append(thought)
84
+
85
+ # Return the full log of thoughts and the new thought
86
+ thoughts = "\n".join([f"- {t}" for t in session_state["workflow_thoughts"]])
87
+ formatted_thoughts = dedent(
88
+ f"""Workflow Thoughts:
89
+ {thoughts}
90
+ """
91
+ ).strip()
92
+ return formatted_thoughts
93
+ except Exception as e:
94
+ log_error(f"Error recording workflow thought: {e}")
95
+ return f"Error recording workflow thought: {e}"
96
+
97
+ async def async_think(self, session_state: Dict[str, Any], thought: str) -> str:
98
+ """Use this tool as a scratchpad to reason about the workflow execution, refine your approach, brainstorm workflow inputs, or revise your plan.
99
+ Call `Think` whenever you need to figure out what to do next, analyze the user's requirements, plan workflow inputs, or decide on execution strategy.
100
+ You should use this tool as frequently as needed.
101
+ Args:
102
+ thought: Your thought process and reasoning about workflow execution.
103
+ """
104
+ try:
105
+ log_debug(f"Workflow Thought: {thought}")
106
+
107
+ # Add the thought to the session state
108
+ if session_state is None:
109
+ session_state = {}
110
+ if "workflow_thoughts" not in session_state:
111
+ session_state["workflow_thoughts"] = []
112
+ session_state["workflow_thoughts"].append(thought)
113
+
114
+ # Return the full log of thoughts and the new thought
115
+ thoughts = "\n".join([f"- {t}" for t in session_state["workflow_thoughts"]])
116
+ formatted_thoughts = dedent(
117
+ f"""Workflow Thoughts:
118
+ {thoughts}
119
+ """
120
+ ).strip()
121
+ return formatted_thoughts
122
+ except Exception as e:
123
+ log_error(f"Error recording workflow thought: {e}")
124
+ return f"Error recording workflow thought: {e}"
125
+
126
+ def run_workflow(
127
+ self,
128
+ session_state: Dict[str, Any],
129
+ input: RunWorkflowInput,
130
+ ) -> str:
131
+ """Use this tool to execute the workflow with the specified inputs and parameters.
132
+ After thinking through the requirements, use this tool to run the workflow with appropriate inputs.
133
+ Args:
134
+ input_data: The input data for the workflow (use a `str` for a simple input)
135
+ additional_data: The additional data for the workflow. This is a dictionary of key-value pairs that will be passed to the workflow. E.g. {"topic": "food", "style": "Humour"}
136
+ """
137
+ try:
138
+ log_debug(f"Running workflow with input: {input.input_data}")
139
+
140
+ user_id = session_state.get("current_user_id")
141
+ session_id = session_state.get("current_session_id")
142
+
143
+ # Execute the workflow
144
+ result: WorkflowRunOutput = self.workflow.run(
145
+ input=input.input_data,
146
+ user_id=user_id,
147
+ session_id=session_id,
148
+ session_state=session_state,
149
+ additional_data=input.additional_data,
150
+ )
151
+
152
+ if "workflow_results" not in session_state:
153
+ session_state["workflow_results"] = []
154
+
155
+ session_state["workflow_results"].append(result.to_dict())
156
+
157
+ return json.dumps(result.to_dict(), indent=2)
158
+
159
+ except Exception as e:
160
+ log_error(f"Error running workflow: {e}")
161
+ return f"Error running workflow: {e}"
162
+
163
+ async def async_run_workflow(
164
+ self,
165
+ session_state: Dict[str, Any],
166
+ input: RunWorkflowInput,
167
+ ) -> str:
168
+ """Use this tool to execute the workflow with the specified inputs and parameters.
169
+ After thinking through the requirements, use this tool to run the workflow with appropriate inputs.
170
+ Args:
171
+ input_data: The input data for the workflow (use a `str` for a simple input)
172
+ additional_data: The additional data for the workflow. This is a dictionary of key-value pairs that will be passed to the workflow. E.g. {"topic": "food", "style": "Humour"}
173
+ """
174
+ try:
175
+ log_debug(f"Running workflow with input: {input.input_data}")
176
+
177
+ user_id = session_state.get("current_user_id")
178
+ session_id = session_state.get("current_session_id")
179
+
180
+ # Execute the workflow
181
+ result: WorkflowRunOutput = await self.workflow.arun(
182
+ input=input.input_data,
183
+ user_id=user_id,
184
+ session_id=session_id,
185
+ session_state=session_state,
186
+ additional_data=input.additional_data,
187
+ )
188
+
189
+ if "workflow_results" not in session_state:
190
+ session_state["workflow_results"] = []
191
+
192
+ session_state["workflow_results"].append(result.to_dict())
193
+
194
+ return json.dumps(result.to_dict(), indent=2)
195
+
196
+ except Exception as e:
197
+ log_error(f"Error running workflow: {e}")
198
+ return f"Error running workflow: {e}"
199
+
200
+ def analyze(self, session_state: Dict[str, Any], analysis: str) -> str:
201
+ """Use this tool to evaluate whether the workflow execution results are correct and sufficient.
202
+ If not, go back to "Think" or "Run" with refined inputs or parameters.
203
+ Args:
204
+ analysis: Your analysis of the workflow execution results.
205
+ """
206
+ try:
207
+ log_debug(f"Workflow Analysis: {analysis}")
208
+
209
+ # Add the analysis to the session state
210
+ if session_state is None:
211
+ session_state = {}
212
+ if "workflow_analysis" not in session_state:
213
+ session_state["workflow_analysis"] = []
214
+ session_state["workflow_analysis"].append(analysis)
215
+
216
+ # Return the full log of analysis and the new analysis
217
+ analysis_log = "\n".join([f"- {a}" for a in session_state["workflow_analysis"]])
218
+ formatted_analysis = dedent(
219
+ f"""Workflow Analysis:
220
+ {analysis_log}
221
+ """
222
+ ).strip()
223
+ return formatted_analysis
224
+ except Exception as e:
225
+ log_error(f"Error recording workflow analysis: {e}")
226
+ return f"Error recording workflow analysis: {e}"
227
+
228
+ async def async_analyze(self, session_state: Dict[str, Any], analysis: str) -> str:
229
+ """Use this tool to evaluate whether the workflow execution results are correct and sufficient.
230
+ If not, go back to "Think" or "Run" with refined inputs or parameters.
231
+ Args:
232
+ analysis: Your analysis of the workflow execution results.
233
+ """
234
+ try:
235
+ log_debug(f"Workflow Analysis: {analysis}")
236
+
237
+ # Add the analysis to the session state
238
+ if session_state is None:
239
+ session_state = {}
240
+ if "workflow_analysis" not in session_state:
241
+ session_state["workflow_analysis"] = []
242
+ session_state["workflow_analysis"].append(analysis)
243
+
244
+ # Return the full log of analysis and the new analysis
245
+ analysis_log = "\n".join([f"- {a}" for a in session_state["workflow_analysis"]])
246
+ formatted_analysis = dedent(
247
+ f"""Workflow Analysis:
248
+ {analysis_log}
249
+ """
250
+ ).strip()
251
+ return formatted_analysis
252
+ except Exception as e:
253
+ log_error(f"Error recording workflow analysis: {e}")
254
+ return f"Error recording workflow analysis: {e}"
255
+
256
+ DEFAULT_INSTRUCTIONS = dedent("""\
257
+ You have access to the Think, Run Workflow, and Analyze tools that will help you execute workflows and analyze their results. Use these tools as frequently as needed to successfully complete workflow-based tasks.
258
+ ## How to use the Think, Run Workflow, and Analyze tools:
259
+
260
+ 1. **Think**
261
+ - Purpose: A scratchpad for planning workflow execution, brainstorming inputs, and refining your approach. You never reveal your "Think" content to the user.
262
+ - Usage: Call `think` whenever you need to figure out what workflow inputs to use, analyze requirements, or decide on execution strategy before (or after) you run the workflow.
263
+ 2. **Run Workflow**
264
+ - Purpose: Executes the workflow with specified inputs and parameters.
265
+ - Usage: Call `run_workflow` with appropriate input data whenever you want to execute the workflow.
266
+ - For all workflows, start with simple inputs and gradually increase complexity
267
+ 3. **Analyze**
268
+ - Purpose: Evaluate whether the workflow execution results are correct and sufficient. If not, go back to "Think" or "Run Workflow" with refined inputs.
269
+ - Usage: Call `analyze` after getting workflow results to verify the quality and correctness of the execution. Consider:
270
+ - Completeness: Did the workflow complete all expected steps?
271
+ - Quality: Are the results accurate and meet the requirements?
272
+ - Errors: Were there any failures or unexpected behaviors?
273
+ **Important Guidelines**:
274
+ - Do not include your internal chain-of-thought in direct user responses.
275
+ - Use "Think" to reason internally. These notes are never exposed to the user.
276
+ - When you provide a final answer to the user, be clear, concise, and based on the workflow results.
277
+ - If workflow execution fails or produces unexpected results, acknowledge limitations and explain what went wrong.
278
+ - Synthesize information from multiple workflow runs if you execute the workflow several times with different inputs.\
279
+ """)