Spaces:
Sleeping
Sleeping
New approach: langgraph only
Browse files- agent_builder.py +0 -321
- agent_builder_template/design_puml.txt +0 -15
- agent_builder_template/design_yaml.txt +0 -11
- agent_builder_template/graph.txt +0 -21
- agent_builder_template/graph_builder.txt +0 -24
- agent_builder_template/test.txt +0 -2
- solver.py → agents.py +45 -73
- agents/universal_solver/design.puml +0 -32
- agents/universal_solver/design.yaml +0 -21
- agents/universal_solver/graph.py +0 -74
- agents/universal_solver/test.py +0 -20
- alfred.py +6 -208
- args.py +3 -3
- design.puml +87 -0
- design.yaml +66 -0
- diagrams/architecture.puml +0 -147
- diagrams/diagram.puml +0 -87
- diagrams/style.puml +0 -7
- graph.py +142 -0
- graph_builder.py +78 -0
- logger.py +40 -0
- management.py +0 -106
- system_prompts/{02_manager.txt → 01_manager.txt} +0 -0
- system_prompts/{01_assistant.txt → 02_auditor.txt} +0 -0
- test.py +339 -0
- test_agents.py +0 -17
- toolbox.py +0 -2
agent_builder.py
DELETED
@@ -1,321 +0,0 @@
|
|
1 |
-
from pathlib import Path
|
2 |
-
import yaml
|
3 |
-
from typing import Dict, List, Any
|
4 |
-
|
5 |
-
|
6 |
-
class AgentBuilder:
|
7 |
-
@staticmethod
|
8 |
-
def initialize(agent_name: str):
|
9 |
-
"""Create agent folder and an empty 'yaml' file
|
10 |
-
|
11 |
-
Args:
|
12 |
-
agent_name: Name of the agent to create
|
13 |
-
"""
|
14 |
-
# Create base agents directory if it doesn't exist
|
15 |
-
agents_base_dir = Path("agents")
|
16 |
-
agents_base_dir.mkdir(exist_ok=True)
|
17 |
-
|
18 |
-
# Create agent-specific directory
|
19 |
-
agent_dir = agents_base_dir / agent_name
|
20 |
-
agent_dir.mkdir(exist_ok=True)
|
21 |
-
|
22 |
-
# Create YAML file with initial content
|
23 |
-
yaml_file = agent_dir / f"design.yaml"
|
24 |
-
|
25 |
-
# Initial YAML content with nodes list and example node
|
26 |
-
initial_content = {
|
27 |
-
"nodes": [
|
28 |
-
{
|
29 |
-
"name": "START",
|
30 |
-
"connections": ["example_node"],
|
31 |
-
"description": "This is the mandatory initial node `START` !"
|
32 |
-
},
|
33 |
-
{
|
34 |
-
"name": "example_node",
|
35 |
-
"connections": [],
|
36 |
-
"description": "This is an example node"
|
37 |
-
}
|
38 |
-
]
|
39 |
-
}
|
40 |
-
|
41 |
-
# Write the YAML content to the file
|
42 |
-
with open(yaml_file, "w") as f:
|
43 |
-
yaml.dump(initial_content, f, default_flow_style=False, sort_keys=False)
|
44 |
-
|
45 |
-
@classmethod
|
46 |
-
def setup(cls, agent_name: str):
|
47 |
-
"""Create the graph and the test python files as well as a 'puml' diagram
|
48 |
-
|
49 |
-
Args:
|
50 |
-
agent_name: Name of the agent to set up
|
51 |
-
"""
|
52 |
-
design_data = cls._validate_design(agent_name)
|
53 |
-
|
54 |
-
cls._create_graph_file(agent_name, design_data)
|
55 |
-
cls._create_test_file(agent_name)
|
56 |
-
cls._create_puml_file(agent_name, design_data)
|
57 |
-
|
58 |
-
@classmethod
|
59 |
-
def _validate_design(cls, agent_name: str) -> Dict[str, List[Dict[str, Any]]]:
|
60 |
-
"""Validate the design.yaml file structure
|
61 |
-
|
62 |
-
Args:
|
63 |
-
agent_name: Name of the agent to validate
|
64 |
-
|
65 |
-
Returns:
|
66 |
-
The parsed design data if valid
|
67 |
-
|
68 |
-
Raises:
|
69 |
-
ValueError: If design file is invalid or missing required elements
|
70 |
-
"""
|
71 |
-
yaml_path = Path(f"agents/{agent_name}/design.yaml")
|
72 |
-
|
73 |
-
if not yaml_path.exists():
|
74 |
-
raise ValueError(f"Design file not found at {yaml_path}")
|
75 |
-
|
76 |
-
with open(yaml_path, 'r') as f:
|
77 |
-
design_data = yaml.safe_load(f)
|
78 |
-
|
79 |
-
# Check if nodes list exists
|
80 |
-
if not design_data or 'nodes' not in design_data or not isinstance(design_data['nodes'], list):
|
81 |
-
raise ValueError("Design file must contain a 'nodes' list")
|
82 |
-
|
83 |
-
# Check if START node is defined
|
84 |
-
start_node_exists = any(node.get('name') == "START" for node in design_data['nodes'])
|
85 |
-
if not start_node_exists:
|
86 |
-
raise ValueError("Design file must contain a 'START' node")
|
87 |
-
|
88 |
-
# Validate each node
|
89 |
-
for i, node in enumerate(design_data['nodes']):
|
90 |
-
if not isinstance(node, dict):
|
91 |
-
raise ValueError(f"Node at index {i} must be a dictionary")
|
92 |
-
|
93 |
-
if 'name' not in node:
|
94 |
-
raise ValueError(f"Node at index {i} is missing a 'name' field")
|
95 |
-
|
96 |
-
if 'description' not in node:
|
97 |
-
raise ValueError(f"Node '{node.get('name', f'at index {i}')}' is missing a 'description' field")
|
98 |
-
|
99 |
-
if 'connections' not in node or not isinstance(node['connections'], list):
|
100 |
-
raise ValueError(f"Node '{node.get('name')}' must have a 'connections' list")
|
101 |
-
|
102 |
-
return design_data
|
103 |
-
|
104 |
-
@classmethod
|
105 |
-
def _create_graph_file(cls, agent_name: str, design_data: Dict[str, List[Dict[str, Any]]]):
|
106 |
-
"""Create the graph.py file with the necessary classes
|
107 |
-
|
108 |
-
Args:
|
109 |
-
agent_name: Name of the agent
|
110 |
-
design_data: The validated design data
|
111 |
-
"""
|
112 |
-
nodes = design_data['nodes']
|
113 |
-
|
114 |
-
# Prepare node methods and conditional edge methods
|
115 |
-
node_methods = []
|
116 |
-
edge_methods = []
|
117 |
-
|
118 |
-
# Generate node method for each node
|
119 |
-
for node in nodes:
|
120 |
-
node_name = node['name']
|
121 |
-
node_desc = node['description']
|
122 |
-
|
123 |
-
node_method = f'''
|
124 |
-
def {node_name}_node(self, state):
|
125 |
-
"""
|
126 |
-
{node_desc}
|
127 |
-
"""
|
128 |
-
# TODO: To implement...
|
129 |
-
pass
|
130 |
-
'''
|
131 |
-
if node_name != "START":
|
132 |
-
node_methods.append(node_method)
|
133 |
-
|
134 |
-
# Check if this node has more than one connection (needs conditional edge)
|
135 |
-
if len(node['connections']) > 1:
|
136 |
-
connections_str = ", ".join([f'"{conn}"' for conn in node['connections']])
|
137 |
-
edge_method = f'''
|
138 |
-
def {node_name}_edge(self, state):
|
139 |
-
"""
|
140 |
-
Conditional edge for {node_name} node.
|
141 |
-
Returns one of: {connections_str}
|
142 |
-
"""
|
143 |
-
# TODO: To implement...
|
144 |
-
pass
|
145 |
-
'''
|
146 |
-
edge_methods.append(edge_method)
|
147 |
-
|
148 |
-
# Build the file content
|
149 |
-
file_content = f'''from typing import Dict, Any
|
150 |
-
from langgraph.graph import StateGraph, END, START
|
151 |
-
from langgraph.graph.state import CompiledStateGraph
|
152 |
-
|
153 |
-
|
154 |
-
class State:
|
155 |
-
"""
|
156 |
-
State class for the agent graph.
|
157 |
-
"""
|
158 |
-
# TODO: Define state structure
|
159 |
-
pass
|
160 |
-
|
161 |
-
|
162 |
-
class Nodes:
|
163 |
-
"""
|
164 |
-
Collection of node functions for the agent graph.
|
165 |
-
"""
|
166 |
-
{"".join(node_methods)}
|
167 |
-
|
168 |
-
|
169 |
-
class Edges:
|
170 |
-
"""
|
171 |
-
Collection of conditional edge functions for the agent graph.
|
172 |
-
"""
|
173 |
-
{"".join(edge_methods)}
|
174 |
-
|
175 |
-
|
176 |
-
class GraphBuilder:
|
177 |
-
def __init__(self):
|
178 |
-
"""
|
179 |
-
Initializes the GraphBuilder.
|
180 |
-
"""
|
181 |
-
self.nodes = Nodes()
|
182 |
-
self.edges = Edges()
|
183 |
-
# TODO: Implement the desired constructor.
|
184 |
-
pass
|
185 |
-
|
186 |
-
def build_agent_graph(self) -> CompiledStateGraph:
|
187 |
-
"""Build and return the agent graph."""
|
188 |
-
graph = StateGraph(State)
|
189 |
-
|
190 |
-
# Add all nodes
|
191 |
-
{cls._generate_add_nodes_code(nodes)}
|
192 |
-
|
193 |
-
# Add edges
|
194 |
-
{cls._generate_regular_edges_code(nodes)}
|
195 |
-
{cls._generate_conditional_edges_code(nodes)}
|
196 |
-
return graph.compile()
|
197 |
-
'''
|
198 |
-
# Write to file
|
199 |
-
graph_file_path = Path(f"agents/{agent_name}/graph.py")
|
200 |
-
with open(graph_file_path, 'w') as f:
|
201 |
-
f.write(file_content)
|
202 |
-
|
203 |
-
@staticmethod
|
204 |
-
def _generate_add_nodes_code(nodes):
|
205 |
-
"""Generate code for adding nodes to the graph"""
|
206 |
-
code_lines = []
|
207 |
-
for node in nodes:
|
208 |
-
if node["name"] != "START":
|
209 |
-
code_lines.append(f' graph.add_node("{node["name"]}", self.nodes.{node["name"]}_node)')
|
210 |
-
return "\n".join(code_lines)
|
211 |
-
|
212 |
-
@staticmethod
|
213 |
-
def _generate_conditional_edges_code(nodes):
|
214 |
-
"""Generate code for adding conditional edges to the graph"""
|
215 |
-
code_lines = []
|
216 |
-
for node in nodes:
|
217 |
-
if len(node['connections']) > 1:
|
218 |
-
destinations = ", ".join([f'{conn}: {conn}' if conn in ["START", "END"] else f'"{conn}": "{conn}"' for conn in node['connections']])
|
219 |
-
code_lines.append(f''' graph.add_conditional_edges(
|
220 |
-
"{node["name"]}",
|
221 |
-
self.edges.{node["name"]}_edge,
|
222 |
-
{{
|
223 |
-
{destinations}
|
224 |
-
}}
|
225 |
-
)''')
|
226 |
-
return "\n".join(code_lines) if code_lines else ""
|
227 |
-
|
228 |
-
@staticmethod
|
229 |
-
def _generate_regular_edges_code(nodes):
|
230 |
-
"""Generate code for adding regular edges to the graph"""
|
231 |
-
code_lines = []
|
232 |
-
for node in nodes:
|
233 |
-
if len(node['connections']) == 1:
|
234 |
-
start_key = node["name"] if node["name"] in ["START", "END"] else f'"{node["name"]}"'
|
235 |
-
end_key = node["connections"][0] if node["connections"][0] in ["START", "END"] else f'"{node["connections"][0]}"'
|
236 |
-
code_lines.append(f' graph.add_edge({start_key}, {end_key})')
|
237 |
-
return "\n".join(code_lines) if code_lines else ""
|
238 |
-
|
239 |
-
@staticmethod
|
240 |
-
def _create_test_file(agent_name: str):
|
241 |
-
"""Create the test.py file
|
242 |
-
|
243 |
-
Args:
|
244 |
-
agent_name: Name of the agent
|
245 |
-
"""
|
246 |
-
test_file_content = f'''# Test file for {agent_name} agent
|
247 |
-
from graph import GraphBuilder
|
248 |
-
|
249 |
-
def test_agent():
|
250 |
-
"""
|
251 |
-
Test the {agent_name} agent functionality.
|
252 |
-
"""
|
253 |
-
# Create the graph
|
254 |
-
builder = GraphBuilder()
|
255 |
-
graph = builder.build_agent_graph()
|
256 |
-
|
257 |
-
# TODO: Add test code here
|
258 |
-
print("Testing {agent_name} agent...")
|
259 |
-
|
260 |
-
# Example test
|
261 |
-
# result = graph.invoke({{"input": "Test input"}})
|
262 |
-
# print(f"Result: {{result}}")
|
263 |
-
|
264 |
-
if __name__ == "__main__":
|
265 |
-
test_agent()
|
266 |
-
'''
|
267 |
-
|
268 |
-
# Write to file
|
269 |
-
test_file_path = Path(f"agents/{agent_name}/test.py")
|
270 |
-
with open(test_file_path, 'w') as f:
|
271 |
-
f.write(test_file_content)
|
272 |
-
|
273 |
-
@staticmethod
|
274 |
-
def _create_puml_file(agent_name: str, design_data: Dict[str, List[Dict[str, Any]]]):
|
275 |
-
"""Create the design.puml file for diagram visualization
|
276 |
-
|
277 |
-
Args:
|
278 |
-
agent_name: Name of the agent
|
279 |
-
design_data: The validated design data
|
280 |
-
"""
|
281 |
-
nodes = design_data['nodes']
|
282 |
-
|
283 |
-
# Start the PlantUML content
|
284 |
-
puml_content = f'''@startuml {agent_name}
|
285 |
-
!define NOT_IMPLEMENTED_NODE_COLOR #IndianRed
|
286 |
-
!define IMPLEMENTED_NODE_COLOR #Gold
|
287 |
-
!define TESTED_NODE_COLOR #LawnGreen
|
288 |
-
!define TERMINAL_NODE_COLOR #DodgerBlue
|
289 |
-
|
290 |
-
'''
|
291 |
-
|
292 |
-
# Add node descriptions
|
293 |
-
for node in nodes:
|
294 |
-
puml_content += f'node {node["name"]} {node["status"]}_NODE_COLOR[\n {node["description"]}\n]\n\n'
|
295 |
-
puml_content += f'node END TERMINAL_NODE_COLOR[\n This is the final Node !\n]\n\n'
|
296 |
-
|
297 |
-
# Add connections
|
298 |
-
for node in nodes:
|
299 |
-
node_name = node["name"]
|
300 |
-
for connection in node["connections"]:
|
301 |
-
if connection == "END":
|
302 |
-
puml_content += f'{node_name} --> END\n'
|
303 |
-
else:
|
304 |
-
puml_content += f'{node_name} --> {connection}\n'
|
305 |
-
|
306 |
-
# End the PlantUML content
|
307 |
-
puml_content += '\n@enduml'
|
308 |
-
|
309 |
-
# Write to file
|
310 |
-
puml_file_path = Path(f"agents/{agent_name}/design.puml")
|
311 |
-
with open(puml_file_path, 'w') as f:
|
312 |
-
f.write(puml_content)
|
313 |
-
|
314 |
-
@staticmethod
|
315 |
-
def validate(agent_name: str):
|
316 |
-
pass
|
317 |
-
|
318 |
-
|
319 |
-
if __name__ == "__main__":
|
320 |
-
# AgentBuilder.initialize("universal_solver")
|
321 |
-
AgentBuilder.setup("universal_solver")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agent_builder_template/design_puml.txt
DELETED
@@ -1,15 +0,0 @@
|
|
1 |
-
@startuml universal_solver
|
2 |
-
!define NOT_IMPLEMENTED_NODE_COLOR #IndianRed
|
3 |
-
!define IMPLEMENTED_NODE_COLOR #Gold
|
4 |
-
!define TESTED_NODE_COLOR #LawnGreen
|
5 |
-
!define TERMINAL_NODE_COLOR #DodgerBlue
|
6 |
-
|
7 |
-
>>>>>NODES<<<<<
|
8 |
-
|
9 |
-
node END TERMINAL_NODE_COLOR[
|
10 |
-
This is the final Node !
|
11 |
-
]
|
12 |
-
|
13 |
-
>>>>>EDGES<<<<<
|
14 |
-
|
15 |
-
@enduml
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agent_builder_template/design_yaml.txt
DELETED
@@ -1,11 +0,0 @@
|
|
1 |
-
nodes:
|
2 |
-
- name: START
|
3 |
-
connections:
|
4 |
-
- example_node
|
5 |
-
description: This is the mandatory initial node `START` !
|
6 |
-
status: TERMINAL
|
7 |
-
|
8 |
-
- name: example_node
|
9 |
-
connections: [END]
|
10 |
-
description: This is an example node
|
11 |
-
status: NOT_IMPLEMENTED
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agent_builder_template/graph.txt
DELETED
@@ -1,21 +0,0 @@
|
|
1 |
-
class State:
|
2 |
-
"""
|
3 |
-
State class for the agent graph.
|
4 |
-
"""
|
5 |
-
# TODO: Define state structure
|
6 |
-
pass
|
7 |
-
|
8 |
-
|
9 |
-
class Nodes:
|
10 |
-
"""
|
11 |
-
Collection of node functions for the agent graph.
|
12 |
-
"""
|
13 |
-
>>>>>NODE_METHODS<<<<<
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
class Edges:
|
18 |
-
"""
|
19 |
-
Collection of conditional edge functions for the agent graph.
|
20 |
-
"""
|
21 |
-
>>>>>CONDITIONAL_EDGE_METHODS<<<<<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agent_builder_template/graph_builder.txt
DELETED
@@ -1,24 +0,0 @@
|
|
1 |
-
from typing import Dict, Any
|
2 |
-
from langgraph.graph import StateGraph, END, START
|
3 |
-
from langgraph.graph.state import CompiledStateGraph
|
4 |
-
|
5 |
-
|
6 |
-
class GraphBuilder:
|
7 |
-
def __init__(self):
|
8 |
-
"""
|
9 |
-
Initializes the GraphBuilder.
|
10 |
-
"""
|
11 |
-
self.nodes = Nodes()
|
12 |
-
self.edges = Edges()
|
13 |
-
# TODO: Implement the desired constructor.
|
14 |
-
pass
|
15 |
-
|
16 |
-
def build_agent_graph(self) -> CompiledStateGraph:
|
17 |
-
"""Build and return the agent graph."""
|
18 |
-
graph = StateGraph(State)
|
19 |
-
|
20 |
-
>>>>>NODES<<<<<
|
21 |
-
|
22 |
-
>>>>>EDGES<<<<<
|
23 |
-
|
24 |
-
return graph.compile()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agent_builder_template/test.txt
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
from graph import State, Nodes, Edges
|
2 |
-
from graph_builder import GraphBuilder
|
|
|
|
|
|
solver.py → agents.py
RENAMED
@@ -1,110 +1,82 @@
|
|
1 |
-
from
|
|
|
2 |
|
3 |
-
from typing import List
|
4 |
|
5 |
-
|
6 |
-
|
7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
|
10 |
class Summarizer(IAgent):
|
|
|
|
|
|
|
11 |
def __init__(self, temperature, max_tokens):
|
12 |
super().__init__(temperature, max_tokens, "04_summarizer.txt", Args.primary_llm_interface)
|
13 |
|
14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
class Researcher(IAgent):
|
|
|
|
|
|
|
16 |
def __init__(self, temperature, max_tokens):
|
17 |
super().__init__(temperature, max_tokens, "05_researcher.txt", Args.primary_llm_interface)
|
18 |
|
19 |
-
def setup_tools(self) -> List[FunctionTool]:
|
20 |
-
return [
|
21 |
-
Toolbox.web_search.duckduckgo_text_search,
|
22 |
-
Toolbox.web_search.duckduckgo_images_search,
|
23 |
-
Toolbox.web_search.duckduckgo_videos_search
|
24 |
-
]
|
25 |
-
|
26 |
|
27 |
class EncryptionExpert(IAgent):
|
|
|
|
|
|
|
28 |
def __init__(self, temperature, max_tokens):
|
29 |
super().__init__(temperature, max_tokens, "06_encryption_expert.txt", Args.primary_llm_interface)
|
30 |
|
31 |
-
def setup_tools(self) -> List[FunctionTool]:
|
32 |
-
return [
|
33 |
-
Toolbox.encryption.ascii_encode,
|
34 |
-
Toolbox.encryption.ascii_decode,
|
35 |
-
Toolbox.encryption.base64_encode,
|
36 |
-
Toolbox.encryption.base64_decode,
|
37 |
-
Toolbox.encryption.caesar_cipher_encode,
|
38 |
-
Toolbox.encryption.caesar_cipher_decode,
|
39 |
-
Toolbox.encryption.caesar_cipher_brute_force,
|
40 |
-
Toolbox.encryption.reverse_string,
|
41 |
-
Toolbox.math.unit_converter
|
42 |
-
]
|
43 |
-
|
44 |
-
def setup_slaves(self) -> List:
|
45 |
-
reasoner = Reasoner(self.temperature, self.max_tokens)
|
46 |
-
return [reasoner]
|
47 |
-
|
48 |
|
49 |
class MathExpert(IAgent):
|
|
|
|
|
|
|
50 |
def __init__(self, temperature, max_tokens):
|
51 |
super().__init__(temperature, max_tokens, "07_math_expert.txt", Args.primary_llm_interface)
|
52 |
|
53 |
-
def setup_tools(self) -> List[FunctionTool]:
|
54 |
-
return [
|
55 |
-
Toolbox.math.symbolic_calc,
|
56 |
-
Toolbox.math.unit_converter,
|
57 |
-
]
|
58 |
-
|
59 |
-
def setup_slaves(self) -> List:
|
60 |
-
reasoner = Reasoner(self.temperature, self.max_tokens)
|
61 |
-
return [reasoner]
|
62 |
-
|
63 |
|
64 |
class Reasoner(IAgent):
|
|
|
|
|
|
|
65 |
def __init__(self, temperature, max_tokens):
|
66 |
super().__init__(temperature, max_tokens, "08_reasoner.txt", Args.primary_llm_interface)
|
67 |
|
68 |
|
69 |
class ImageHandler(IAgent):
|
|
|
|
|
|
|
70 |
def __init__(self, temperature, max_tokens):
|
71 |
super().__init__(temperature, max_tokens, "09_image_handler.txt", Args.vlm_interface)
|
72 |
|
73 |
-
async def query(self, question: str, has_context=True) -> str:
|
74 |
-
return "Image Handler is not available due to maintainance !"
|
75 |
-
|
76 |
|
77 |
class VideoHandler(IAgent):
|
|
|
|
|
|
|
78 |
def __init__(self, temperature, max_tokens):
|
79 |
super().__init__(temperature, max_tokens, "10_video_handler.txt", Args.vlm_interface)
|
80 |
-
|
81 |
-
async def query(self, question: str, has_context=True) -> str:
|
82 |
-
return "Video Handler is not available due to maintainance !"
|
83 |
-
|
84 |
-
|
85 |
-
class Solver(IAgent):
|
86 |
-
def __init__(self, temperature, max_tokens):
|
87 |
-
super().__init__(temperature, max_tokens, "03_solver.txt", Args.primary_llm_interface)
|
88 |
-
|
89 |
-
def setup_slaves(self) -> List:
|
90 |
-
summarizer = Summarizer(self.temperature, self.max_tokens)
|
91 |
-
researcher = Researcher(self.temperature, self.max_tokens)
|
92 |
-
encryption_expert = EncryptionExpert(self.temperature, self.max_tokens)
|
93 |
-
math_expert = MathExpert(self.temperature, self.max_tokens)
|
94 |
-
reasoner = Reasoner(self.temperature, self.max_tokens)
|
95 |
-
image_handler = ImageHandler(self.temperature, self.max_tokens)
|
96 |
-
video_handler = VideoHandler(self.temperature, self.max_tokens)
|
97 |
-
|
98 |
-
return [
|
99 |
-
summarizer,
|
100 |
-
researcher,
|
101 |
-
encryption_expert,
|
102 |
-
math_expert,
|
103 |
-
reasoner,
|
104 |
-
image_handler,
|
105 |
-
video_handler
|
106 |
-
]
|
107 |
-
|
108 |
-
|
109 |
-
# if __name__ == "__main__":
|
110 |
-
# pass
|
|
|
1 |
+
from args import Args
|
2 |
+
from itf_agent import IAgent
|
3 |
|
|
|
4 |
|
5 |
+
class Manager(IAgent):
|
6 |
+
"""
|
7 |
+
Orchestrates the workflow by delegating tasks to specialized nodes and integrating their outputs
|
8 |
+
"""
|
9 |
+
def __init__(self, temperature, max_tokens):
|
10 |
+
super().__init__(temperature, max_tokens, "01_manager.txt", Args.primary_llm_interface)
|
11 |
+
|
12 |
+
|
13 |
+
class Auditor(IAgent):
|
14 |
+
"""
|
15 |
+
Reviews manager's outputs for accuracy, safety, and quality
|
16 |
+
"""
|
17 |
+
def __init__(self, temperature, max_tokens):
|
18 |
+
super().__init__(temperature, max_tokens, "02_auditor.txt", Args.primary_llm_interface)
|
19 |
|
20 |
|
21 |
class Summarizer(IAgent):
|
22 |
+
"""
|
23 |
+
Generates concise summaries of conversations or passages.
|
24 |
+
"""
|
25 |
def __init__(self, temperature, max_tokens):
|
26 |
super().__init__(temperature, max_tokens, "04_summarizer.txt", Args.primary_llm_interface)
|
27 |
|
28 |
|
29 |
+
class Solver(IAgent):
|
30 |
+
"""
|
31 |
+
Central problem-solving node that coordinates with specialized experts based on task requirements
|
32 |
+
"""
|
33 |
+
def __init__(self, temperature, max_tokens):
|
34 |
+
super().__init__(temperature, max_tokens, "03_solver.txt", Args.primary_llm_interface)
|
35 |
+
|
36 |
+
|
37 |
class Researcher(IAgent):
|
38 |
+
"""
|
39 |
+
Retrieves and synthesizes information from various sources to answer knowledge-based questions
|
40 |
+
"""
|
41 |
def __init__(self, temperature, max_tokens):
|
42 |
super().__init__(temperature, max_tokens, "05_researcher.txt", Args.primary_llm_interface)
|
43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
|
45 |
class EncryptionExpert(IAgent):
|
46 |
+
"""
|
47 |
+
Handles encryption/decryption tasks and encoding/decoding operations
|
48 |
+
"""
|
49 |
def __init__(self, temperature, max_tokens):
|
50 |
super().__init__(temperature, max_tokens, "06_encryption_expert.txt", Args.primary_llm_interface)
|
51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
|
53 |
class MathExpert(IAgent):
|
54 |
+
"""
|
55 |
+
Performs mathematical calculations and solves numerical problems
|
56 |
+
"""
|
57 |
def __init__(self, temperature, max_tokens):
|
58 |
super().__init__(temperature, max_tokens, "07_math_expert.txt", Args.primary_llm_interface)
|
59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
|
61 |
class Reasoner(IAgent):
|
62 |
+
"""
|
63 |
+
Performs logical reasoning, inference, and step-by-step problem-solving
|
64 |
+
"""
|
65 |
def __init__(self, temperature, max_tokens):
|
66 |
super().__init__(temperature, max_tokens, "08_reasoner.txt", Args.primary_llm_interface)
|
67 |
|
68 |
|
69 |
class ImageHandler(IAgent):
|
70 |
+
"""
|
71 |
+
Processes, analyzes, and generates information related to images
|
72 |
+
"""
|
73 |
def __init__(self, temperature, max_tokens):
|
74 |
super().__init__(temperature, max_tokens, "09_image_handler.txt", Args.vlm_interface)
|
75 |
|
|
|
|
|
|
|
76 |
|
77 |
class VideoHandler(IAgent):
|
78 |
+
"""
|
79 |
+
Processes, analyzes, and generates information related to videos
|
80 |
+
"""
|
81 |
def __init__(self, temperature, max_tokens):
|
82 |
super().__init__(temperature, max_tokens, "10_video_handler.txt", Args.vlm_interface)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/universal_solver/design.puml
DELETED
@@ -1,32 +0,0 @@
|
|
1 |
-
@startuml universal_solver
|
2 |
-
!define NOT_IMPLEMENTED_NODE_COLOR #IndianRed
|
3 |
-
!define IMPLEMENTED_NODE_COLOR #Gold
|
4 |
-
!define TESTED_NODE_COLOR #LawnGreen
|
5 |
-
!define TERMINAL_NODE_COLOR #DodgerBlue
|
6 |
-
|
7 |
-
node START TERMINAL_NODE_COLOR[
|
8 |
-
This is the mandatory initial node `START` !
|
9 |
-
]
|
10 |
-
|
11 |
-
node example_node_1 NOT_IMPLEMENTED_NODE_COLOR[
|
12 |
-
This is an example node
|
13 |
-
]
|
14 |
-
|
15 |
-
node example_node_2 IMPLEMENTED_NODE_COLOR[
|
16 |
-
This is another example node
|
17 |
-
]
|
18 |
-
|
19 |
-
node example_node_3 TESTED_NODE_COLOR[
|
20 |
-
This is yet another example node
|
21 |
-
]
|
22 |
-
|
23 |
-
node END TERMINAL_NODE_COLOR[
|
24 |
-
This is the final Node !
|
25 |
-
]
|
26 |
-
|
27 |
-
START --> example_node_1
|
28 |
-
example_node_1 --> example_node_2
|
29 |
-
example_node_2 --> example_node_3
|
30 |
-
example_node_3 --> END
|
31 |
-
|
32 |
-
@enduml
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/universal_solver/design.yaml
DELETED
@@ -1,21 +0,0 @@
|
|
1 |
-
nodes:
|
2 |
-
- name: START
|
3 |
-
connections:
|
4 |
-
- example_node_1
|
5 |
-
description: This is the mandatory initial node `START` !
|
6 |
-
status: TERMINAL
|
7 |
-
|
8 |
-
- name: example_node_1
|
9 |
-
connections: [example_node_2]
|
10 |
-
description: This is an example node
|
11 |
-
status: NOT_IMPLEMENTED
|
12 |
-
|
13 |
-
- name: example_node_2
|
14 |
-
connections: [example_node_3]
|
15 |
-
description: This is another example node
|
16 |
-
status: IMPLEMENTED
|
17 |
-
|
18 |
-
- name: example_node_3
|
19 |
-
connections: [END]
|
20 |
-
description: This is yet another example node
|
21 |
-
status: TESTED
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/universal_solver/graph.py
DELETED
@@ -1,74 +0,0 @@
|
|
1 |
-
from typing import Dict, Any
|
2 |
-
from langgraph.graph import StateGraph, END, START
|
3 |
-
from langgraph.graph.state import CompiledStateGraph
|
4 |
-
|
5 |
-
|
6 |
-
class State:
|
7 |
-
"""
|
8 |
-
State class for the agent graph.
|
9 |
-
"""
|
10 |
-
# TODO: Define state structure
|
11 |
-
pass
|
12 |
-
|
13 |
-
|
14 |
-
class Nodes:
|
15 |
-
"""
|
16 |
-
Collection of node functions for the agent graph.
|
17 |
-
"""
|
18 |
-
|
19 |
-
def example_node_1_node(self, state):
|
20 |
-
"""
|
21 |
-
This is an example node
|
22 |
-
"""
|
23 |
-
# TODO: To implement...
|
24 |
-
pass
|
25 |
-
|
26 |
-
def example_node_2_node(self, state):
|
27 |
-
"""
|
28 |
-
This is another example node
|
29 |
-
"""
|
30 |
-
# TODO: To implement...
|
31 |
-
pass
|
32 |
-
|
33 |
-
def example_node_3_node(self, state):
|
34 |
-
"""
|
35 |
-
This is yet another example node
|
36 |
-
"""
|
37 |
-
# TODO: To implement...
|
38 |
-
pass
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
class Edges:
|
43 |
-
"""
|
44 |
-
Collection of conditional edge functions for the agent graph.
|
45 |
-
"""
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
class GraphBuilder:
|
50 |
-
def __init__(self):
|
51 |
-
"""
|
52 |
-
Initializes the GraphBuilder.
|
53 |
-
"""
|
54 |
-
self.nodes = Nodes()
|
55 |
-
self.edges = Edges()
|
56 |
-
# TODO: Implement the desired constructor.
|
57 |
-
pass
|
58 |
-
|
59 |
-
def build_agent_graph(self) -> CompiledStateGraph:
|
60 |
-
"""Build and return the agent graph."""
|
61 |
-
graph = StateGraph(State)
|
62 |
-
|
63 |
-
# Add all nodes
|
64 |
-
graph.add_node("example_node_1", self.nodes.example_node_1_node)
|
65 |
-
graph.add_node("example_node_2", self.nodes.example_node_2_node)
|
66 |
-
graph.add_node("example_node_3", self.nodes.example_node_3_node)
|
67 |
-
|
68 |
-
# Add edges
|
69 |
-
graph.add_edge(START, "example_node_1")
|
70 |
-
graph.add_edge("example_node_1", "example_node_2")
|
71 |
-
graph.add_edge("example_node_2", "example_node_3")
|
72 |
-
graph.add_edge("example_node_3", END)
|
73 |
-
|
74 |
-
return graph.compile()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/universal_solver/test.py
DELETED
@@ -1,20 +0,0 @@
|
|
1 |
-
# Test file for universal_solver agent
|
2 |
-
from graph import GraphBuilder
|
3 |
-
|
4 |
-
def test_agent():
|
5 |
-
"""
|
6 |
-
Test the universal_solver agent functionality.
|
7 |
-
"""
|
8 |
-
# Create the graph
|
9 |
-
builder = GraphBuilder()
|
10 |
-
graph = builder.build_agent_graph()
|
11 |
-
|
12 |
-
# TODO: Add test code here
|
13 |
-
print("Testing universal_solver agent...")
|
14 |
-
|
15 |
-
# Example test
|
16 |
-
# result = graph.invoke({"input": "Test input"})
|
17 |
-
# print(f"Result: {result}")
|
18 |
-
|
19 |
-
if __name__ == "__main__":
|
20 |
-
test_agent()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
alfred.py
CHANGED
@@ -1,13 +1,8 @@
|
|
1 |
-
from
|
2 |
-
from langgraph.graph.state import CompiledStateGraph
|
3 |
-
|
4 |
-
from typing import Dict, Any, TypedDict, Literal, Optional
|
5 |
-
import logging
|
6 |
-
import datetime
|
7 |
-
from pathlib import Path
|
8 |
|
9 |
from args import Args
|
10 |
-
from
|
|
|
11 |
|
12 |
|
13 |
# Maximum number of interactions between Assistant and Manager
|
@@ -19,173 +14,11 @@ TEMPERATURE = 0.7
|
|
19 |
MAX_TOKENS = 2000
|
20 |
|
21 |
|
22 |
-
class State(TypedDict):
|
23 |
-
"""State for the agent graph."""
|
24 |
-
initial_query: str
|
25 |
-
current_message: Optional[str]
|
26 |
-
nr_interactions: int
|
27 |
-
final_response: Optional[str]
|
28 |
-
|
29 |
-
|
30 |
-
class GraphBuilder:
|
31 |
-
def __init__(self):
|
32 |
-
"""
|
33 |
-
Initializes the GraphBuilder.
|
34 |
-
"""
|
35 |
-
self.assistant_agent = Assistant(TEMPERATURE, MAX_TOKENS)
|
36 |
-
self.manager_agent = Manager(TEMPERATURE, MAX_TOKENS, MAX_DEPTH)
|
37 |
-
self.final_answer_hint = "Final answer:"
|
38 |
-
|
39 |
-
def clear_chat_history(self):
|
40 |
-
self.assistant_agent.clear_context()
|
41 |
-
self.manager_agent.clear_context()
|
42 |
-
|
43 |
-
async def assistant_node(self, state: State) -> State:
|
44 |
-
"""
|
45 |
-
Assistant agent that evaluates the query and decides whether to give a final answer
|
46 |
-
or continue the conversation with the Manager.
|
47 |
-
|
48 |
-
Uses the existing Assistant implementation.
|
49 |
-
"""
|
50 |
-
if Args.LOGGER is None:
|
51 |
-
raise RuntimeError("LOGGER must be defined before running the assistant_node.")
|
52 |
-
|
53 |
-
Args.LOGGER.log(logging.INFO, "********** assistant_node **********")
|
54 |
-
|
55 |
-
if state["current_message"] is None:
|
56 |
-
# First time, just forward the query to the manager
|
57 |
-
response = state["initial_query"]
|
58 |
-
else:
|
59 |
-
assistent_input = f"This is the initial user query:\n\n{state["initial_query"]}\n\nThe manager provided the following answer:\n\n{state["current_message"]}\n"
|
60 |
-
response = await self.assistant_agent.query(assistent_input)
|
61 |
-
|
62 |
-
# Check if this is a final answer
|
63 |
-
if self.final_answer_hint in response:
|
64 |
-
# Extract the text after final answer hint
|
65 |
-
final_response = response.split(self.final_answer_hint)[-1]
|
66 |
-
final_response = final_response.strip()
|
67 |
-
state["final_response"] = final_response
|
68 |
-
|
69 |
-
state["current_message"] = response
|
70 |
-
state["nr_interactions"] += 1
|
71 |
-
|
72 |
-
return state
|
73 |
-
|
74 |
-
async def manager_node(self, state: State) -> State:
|
75 |
-
"""
|
76 |
-
Manager agent that handles the queries from the Assistant and provides responses.
|
77 |
-
|
78 |
-
Uses the existing Manager implementation.
|
79 |
-
"""
|
80 |
-
if Args.LOGGER is None:
|
81 |
-
raise RuntimeError("LOGGER must be defined before running the manager_node.")
|
82 |
-
|
83 |
-
Args.LOGGER.log(logging.INFO, "********** manager_node **********")
|
84 |
-
|
85 |
-
if state["current_message"] is None:
|
86 |
-
raise ValueError("manager_node called with no current_message in state")
|
87 |
-
|
88 |
-
response = await self.manager_agent.query(state["current_message"])
|
89 |
-
|
90 |
-
state["current_message"] = response
|
91 |
-
|
92 |
-
return state
|
93 |
-
|
94 |
-
async def final_answer_node(self, state: State) -> State:
|
95 |
-
"""
|
96 |
-
Final answer node that formats and returns the final response.
|
97 |
-
|
98 |
-
If there's already a final answer in the state, it uses that.
|
99 |
-
Otherwise, it asks the assistant to formulate a final answer.
|
100 |
-
"""
|
101 |
-
if Args.LOGGER is None:
|
102 |
-
raise RuntimeError("LOGGER must be defined before running the final_answer_node.")
|
103 |
-
|
104 |
-
Args.LOGGER.log(logging.INFO, "********** final_answer_node **********")
|
105 |
-
|
106 |
-
# If we already have a final answer, use it
|
107 |
-
final_response = state.get("final_response")
|
108 |
-
if final_response is not None:
|
109 |
-
Args.LOGGER.log(logging.INFO, f"==========\nFinal response:\n{final_response}\n==========")
|
110 |
-
return state
|
111 |
-
|
112 |
-
# Otherwise, have the assistant formulate a final answer
|
113 |
-
prompt = f"Based on the conversation so far, provide a final answer to the original query:\n\n{state['initial_query']}"
|
114 |
-
state["current_message"] = prompt
|
115 |
-
response = await self.assistant_agent.query(prompt)
|
116 |
-
|
117 |
-
# Format the response
|
118 |
-
if self.final_answer_hint not in response:
|
119 |
-
Args.LOGGER.log(logging.WARNING, f"Final_answer_hint '{self.final_answer_hint}' not in response !")
|
120 |
-
response = f"{self.final_answer_hint}{response}"
|
121 |
-
|
122 |
-
# Extract the text after final answer hint
|
123 |
-
final_response = response.split(self.final_answer_hint)[-1]
|
124 |
-
final_response = final_response.strip()
|
125 |
-
state["final_response"] = final_response
|
126 |
-
Args.LOGGER.log(logging.INFO, f"==========\nFinal response:\n{final_response}\n==========")
|
127 |
-
|
128 |
-
return state
|
129 |
-
|
130 |
-
def should_continue(self, state: State) -> Literal["manager", "final_answer"]:
|
131 |
-
"""
|
132 |
-
Decides whether to continue to the Manager or to provide a final answer.
|
133 |
-
|
134 |
-
Returns:
|
135 |
-
"manager": If the Assistant has decided to continue the conversation
|
136 |
-
"final_answer": If the Assistant has decided to provide a final answer
|
137 |
-
"""
|
138 |
-
if Args.LOGGER is None:
|
139 |
-
raise RuntimeError("LOGGER must be defined before running the should_continue edge.")
|
140 |
-
|
141 |
-
Args.LOGGER.log(logging.INFO, "++++++++++ should_continue edge ++++++++++")
|
142 |
-
|
143 |
-
if state["current_message"] is None:
|
144 |
-
raise ValueError("should_continue conditional edge was reached with no current_message in state")
|
145 |
-
|
146 |
-
message = state["current_message"]
|
147 |
-
|
148 |
-
if state["nr_interactions"] >= MAX_INTERACTIONS or self.final_answer_hint in message:
|
149 |
-
Args.LOGGER.log(logging.INFO, "++++++++++ should_continue edge decision: final_answer ++++++++++")
|
150 |
-
return "final_answer"
|
151 |
-
Args.LOGGER.log(logging.INFO, "++++++++++ should_continue edge decision: manager ++++++++++")
|
152 |
-
return "manager"
|
153 |
-
|
154 |
-
def build_agent_graph(self) -> CompiledStateGraph:
|
155 |
-
"""Build and return the agent graph."""
|
156 |
-
graph = StateGraph(State)
|
157 |
-
|
158 |
-
# Add the nodes with sync wrappers
|
159 |
-
graph.add_node("assistant", self.assistant_node)
|
160 |
-
graph.add_node("manager", self.manager_node)
|
161 |
-
graph.add_node("final_answer", self.final_answer_node)
|
162 |
-
|
163 |
-
# Add the edges
|
164 |
-
graph.add_edge(START, "assistant")
|
165 |
-
|
166 |
-
graph.add_conditional_edges(
|
167 |
-
"assistant",
|
168 |
-
self.should_continue,
|
169 |
-
{
|
170 |
-
"manager": "manager",
|
171 |
-
"final_answer": "final_answer"
|
172 |
-
}
|
173 |
-
)
|
174 |
-
|
175 |
-
graph.add_edge("manager", "assistant")
|
176 |
-
|
177 |
-
graph.add_edge("final_answer", END)
|
178 |
-
|
179 |
-
return graph.compile()
|
180 |
-
|
181 |
-
|
182 |
class Alfred:
|
183 |
|
184 |
def __init__(self):
|
185 |
print("Agent initialized.")
|
186 |
|
187 |
-
Args.LOGGER = self.set_logger()
|
188 |
-
|
189 |
self.graph_builder = GraphBuilder()
|
190 |
self.agent_graph = self.graph_builder.build_agent_graph()
|
191 |
|
@@ -200,54 +33,19 @@ class Alfred:
|
|
200 |
async def process_query(self, query: str) -> Dict[str, Any]:
|
201 |
"""
|
202 |
Process a query through the agent graph.
|
203 |
-
|
204 |
Args:
|
205 |
query: The initial query to process
|
206 |
-
|
207 |
Returns:
|
208 |
The final state of the graph execution
|
209 |
"""
|
210 |
initial_state: State = {
|
211 |
"initial_query": query,
|
212 |
-
"
|
213 |
"nr_interactions": 0,
|
214 |
"final_response": None
|
215 |
}
|
216 |
-
self.graph_builder.clear_chat_history()
|
217 |
|
218 |
result = await self.agent_graph.ainvoke(initial_state)
|
219 |
return result
|
220 |
-
|
221 |
-
def set_logger(self) -> logging.Logger:
|
222 |
-
"""
|
223 |
-
Configure and return a logger with a file handler that writes to logs/<current date-time>.txt
|
224 |
-
|
225 |
-
Returns:
|
226 |
-
logging.Logger: Configured logger instance
|
227 |
-
"""
|
228 |
-
# Create logger
|
229 |
-
logger = logging.getLogger("Alfred")
|
230 |
-
logger.setLevel(logging.INFO)
|
231 |
-
|
232 |
-
# Create formatter
|
233 |
-
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
234 |
-
|
235 |
-
# Create logs directory if it doesn't exist
|
236 |
-
logs_dir = Path('logs')
|
237 |
-
logs_dir.mkdir(exist_ok=True)
|
238 |
-
|
239 |
-
# Generate log filename with current date-time
|
240 |
-
current_time = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
241 |
-
log_filename = f"{current_time}.txt"
|
242 |
-
log_filepath = logs_dir / log_filename
|
243 |
-
|
244 |
-
# Create file handler with UTF-8 encoding to handle all Unicode characters
|
245 |
-
file_handler = logging.FileHandler(log_filepath, encoding='utf-8')
|
246 |
-
file_handler.setLevel(logging.INFO)
|
247 |
-
file_handler.setFormatter(formatter)
|
248 |
-
|
249 |
-
# Add handler to logger
|
250 |
-
logger.addHandler(file_handler)
|
251 |
-
|
252 |
-
logger.info(f"Logging started at {current_time}")
|
253 |
-
return logger
|
|
|
1 |
+
from typing import Dict, Any
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
|
3 |
from args import Args
|
4 |
+
from graph import State
|
5 |
+
from graph_builder import GraphBuilder
|
6 |
|
7 |
|
8 |
# Maximum number of interactions between Assistant and Manager
|
|
|
14 |
MAX_TOKENS = 2000
|
15 |
|
16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
class Alfred:
|
18 |
|
19 |
def __init__(self):
|
20 |
print("Agent initialized.")
|
21 |
|
|
|
|
|
22 |
self.graph_builder = GraphBuilder()
|
23 |
self.agent_graph = self.graph_builder.build_agent_graph()
|
24 |
|
|
|
33 |
async def process_query(self, query: str) -> Dict[str, Any]:
|
34 |
"""
|
35 |
Process a query through the agent graph.
|
36 |
+
|
37 |
Args:
|
38 |
query: The initial query to process
|
39 |
+
|
40 |
Returns:
|
41 |
The final state of the graph execution
|
42 |
"""
|
43 |
initial_state: State = {
|
44 |
"initial_query": query,
|
45 |
+
"messages": [],
|
46 |
"nr_interactions": 0,
|
47 |
"final_response": None
|
48 |
}
|
|
|
49 |
|
50 |
result = await self.agent_graph.ainvoke(initial_state)
|
51 |
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
args.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
|
2 |
from enum import Enum
|
3 |
-
|
4 |
-
from
|
5 |
|
6 |
|
7 |
class LLMInterface(Enum):
|
@@ -12,7 +12,7 @@ class LLMInterface(Enum):
|
|
12 |
|
13 |
|
14 |
class Args:
|
15 |
-
LOGGER
|
16 |
primary_llm_interface=LLMInterface.OPENAILIKE
|
17 |
# secondary_llm_interface=LLMInterface.HUGGINGFACE
|
18 |
vlm_interface=LLMInterface.HUGGINGFACE
|
|
|
1 |
|
2 |
from enum import Enum
|
3 |
+
|
4 |
+
from logger import Logger
|
5 |
|
6 |
|
7 |
class LLMInterface(Enum):
|
|
|
12 |
|
13 |
|
14 |
class Args:
|
15 |
+
LOGGER = Logger.set_logger()
|
16 |
primary_llm_interface=LLMInterface.OPENAILIKE
|
17 |
# secondary_llm_interface=LLMInterface.HUGGINGFACE
|
18 |
vlm_interface=LLMInterface.HUGGINGFACE
|
design.puml
ADDED
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@startuml alfred
|
2 |
+
!define NOT_IMPLEMENTED_NODE_COLOR #IndianRed
|
3 |
+
!define IMPLEMENTED_NODE_COLOR #Gold
|
4 |
+
!define TESTED_NODE_COLOR #LawnGreen
|
5 |
+
!define TERMINAL_NODE_COLOR #DodgerBlue
|
6 |
+
|
7 |
+
node START TERMINAL_NODE_COLOR[
|
8 |
+
START
|
9 |
+
]
|
10 |
+
|
11 |
+
node manager NOT_IMPLEMENTED_NODE_COLOR[
|
12 |
+
manager
|
13 |
+
]
|
14 |
+
|
15 |
+
node final_answer NOT_IMPLEMENTED_NODE_COLOR[
|
16 |
+
final_answer
|
17 |
+
]
|
18 |
+
|
19 |
+
node auditor NOT_IMPLEMENTED_NODE_COLOR[
|
20 |
+
auditor
|
21 |
+
]
|
22 |
+
|
23 |
+
node solver NOT_IMPLEMENTED_NODE_COLOR[
|
24 |
+
solver
|
25 |
+
]
|
26 |
+
|
27 |
+
node researcher NOT_IMPLEMENTED_NODE_COLOR[
|
28 |
+
researcher
|
29 |
+
]
|
30 |
+
|
31 |
+
node encryption_expert NOT_IMPLEMENTED_NODE_COLOR[
|
32 |
+
encryption_expert
|
33 |
+
]
|
34 |
+
|
35 |
+
node encryption_advisor NOT_IMPLEMENTED_NODE_COLOR[
|
36 |
+
encryption_advisor
|
37 |
+
]
|
38 |
+
|
39 |
+
node math_expert NOT_IMPLEMENTED_NODE_COLOR[
|
40 |
+
math_expert
|
41 |
+
]
|
42 |
+
|
43 |
+
node math_advisor NOT_IMPLEMENTED_NODE_COLOR[
|
44 |
+
math_advisor
|
45 |
+
]
|
46 |
+
|
47 |
+
node reasoner NOT_IMPLEMENTED_NODE_COLOR[
|
48 |
+
reasoner
|
49 |
+
]
|
50 |
+
|
51 |
+
node image_handler NOT_IMPLEMENTED_NODE_COLOR[
|
52 |
+
image_handler
|
53 |
+
]
|
54 |
+
|
55 |
+
node video_handler NOT_IMPLEMENTED_NODE_COLOR[
|
56 |
+
video_handler
|
57 |
+
]
|
58 |
+
|
59 |
+
node END TERMINAL_NODE_COLOR[
|
60 |
+
END
|
61 |
+
]
|
62 |
+
|
63 |
+
START --> manager
|
64 |
+
manager --> solver
|
65 |
+
manager --> auditor
|
66 |
+
manager --> final_answer
|
67 |
+
final_answer --> END
|
68 |
+
auditor --> manager
|
69 |
+
solver --> manager
|
70 |
+
solver --> researcher
|
71 |
+
solver --> encryption_expert
|
72 |
+
solver --> math_expert
|
73 |
+
solver --> reasoner
|
74 |
+
solver --> image_handler
|
75 |
+
solver --> video_handler
|
76 |
+
researcher --> solver
|
77 |
+
encryption_expert --> solver
|
78 |
+
encryption_expert --> encryption_advisor
|
79 |
+
encryption_advisor --> encryption_expert
|
80 |
+
math_expert --> solver
|
81 |
+
math_expert --> math_advisor
|
82 |
+
math_advisor --> math_expert
|
83 |
+
reasoner --> solver
|
84 |
+
image_handler --> solver
|
85 |
+
video_handler --> solver
|
86 |
+
|
87 |
+
@enduml
|
design.yaml
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
nodes:
|
2 |
+
- name: START
|
3 |
+
connections:
|
4 |
+
- manager
|
5 |
+
description: This is the mandatory initial node `START` !
|
6 |
+
status: TERMINAL
|
7 |
+
|
8 |
+
- name: manager
|
9 |
+
connections: [solver, auditor, final_answer]
|
10 |
+
description: Orchestrates the workflow by delegating tasks to specialized nodes and integrating their outputs
|
11 |
+
status: NOT_IMPLEMENTED
|
12 |
+
|
13 |
+
- name: final_answer
|
14 |
+
connections: [END]
|
15 |
+
description: Formats and delivers the final response to the user
|
16 |
+
status: NOT_IMPLEMENTED
|
17 |
+
|
18 |
+
- name: auditor
|
19 |
+
connections: [manager]
|
20 |
+
description: Reviews manager's outputs for accuracy, safety, and quality
|
21 |
+
status: NOT_IMPLEMENTED
|
22 |
+
|
23 |
+
- name: solver
|
24 |
+
connections: [manager, researcher, encryption_expert, math_expert, reasoner, image_handler, video_handler]
|
25 |
+
description: Central problem-solving node that coordinates with specialized experts based on task requirements
|
26 |
+
status: NOT_IMPLEMENTED
|
27 |
+
|
28 |
+
- name: researcher
|
29 |
+
connections: [solver]
|
30 |
+
description: Retrieves and synthesizes information from various sources to answer knowledge-based questions
|
31 |
+
status: NOT_IMPLEMENTED
|
32 |
+
|
33 |
+
- name: encryption_expert
|
34 |
+
connections: [solver, encryption_advisor]
|
35 |
+
description: Handles encryption/decryption tasks and encoding/decoding operations
|
36 |
+
status: NOT_IMPLEMENTED
|
37 |
+
|
38 |
+
- name: encryption_advisor
|
39 |
+
connections: [encryption_expert]
|
40 |
+
description: Provides specialized guidance on complex encryption problems to the encryption expert
|
41 |
+
status: NOT_IMPLEMENTED
|
42 |
+
|
43 |
+
- name: math_expert
|
44 |
+
connections: [solver, math_advisor]
|
45 |
+
description: Performs mathematical calculations and solves numerical problems
|
46 |
+
status: NOT_IMPLEMENTED
|
47 |
+
|
48 |
+
- name: math_advisor
|
49 |
+
connections: [math_expert]
|
50 |
+
description: Provides specialized guidance on complex mathematical problems to the math expert
|
51 |
+
status: NOT_IMPLEMENTED
|
52 |
+
|
53 |
+
- name: reasoner
|
54 |
+
connections: [solver]
|
55 |
+
description: Performs logical reasoning, inference, and step-by-step problem-solving
|
56 |
+
status: NOT_IMPLEMENTED
|
57 |
+
|
58 |
+
- name: image_handler
|
59 |
+
connections: [solver]
|
60 |
+
description: Processes, analyzes, and generates information related to images
|
61 |
+
status: NOT_IMPLEMENTED
|
62 |
+
|
63 |
+
- name: video_handler
|
64 |
+
connections: [solver]
|
65 |
+
description: Processes, analyzes, and generates information related to videos
|
66 |
+
status: NOT_IMPLEMENTED
|
diagrams/architecture.puml
DELETED
@@ -1,147 +0,0 @@
|
|
1 |
-
@startuml Architecture Diagram
|
2 |
-
|
3 |
-
' Define color variables
|
4 |
-
!define CANVAS_COLOR #FAEBD7
|
5 |
-
!define DEFAULT_NODE_COLOR #Gold
|
6 |
-
!define TODO_COLOR #FireBrick
|
7 |
-
!define ARROW_COLOR #666666
|
8 |
-
!define BORDER_COLOR #999999
|
9 |
-
!define TOOLBOX_MATH_COLOR #LightBlue
|
10 |
-
!define TOOLBOX_WEBSEARCH_COLOR #LightGreen
|
11 |
-
!define TOOLBOX_ENCRYPTION_COLOR #PaleVioletRed
|
12 |
-
!define POWERS_ARROW_COLOR #4285F4
|
13 |
-
!define TRIGGER_ARROW_COLOR #OrangeRed
|
14 |
-
!define FINAL_ANSWER_COLOR #Orange
|
15 |
-
|
16 |
-
' Style settings
|
17 |
-
skinparam handwritten true
|
18 |
-
skinparam backgroundColor white
|
19 |
-
skinparam componentStyle uml2
|
20 |
-
skinparam defaultFontName Arial
|
21 |
-
skinparam arrowColor ARROW_COLOR
|
22 |
-
skinparam componentBorderColor BORDER_COLOR
|
23 |
-
skinparam component {
|
24 |
-
BackgroundColor DEFAULT_NODE_COLOR
|
25 |
-
}
|
26 |
-
|
27 |
-
' Custom style for toolbox functions
|
28 |
-
skinparam label {
|
29 |
-
FontSize 10
|
30 |
-
FontColor black
|
31 |
-
}
|
32 |
-
|
33 |
-
' Components
|
34 |
-
package "app.py" {
|
35 |
-
[UI] as UI
|
36 |
-
[Application] as Application
|
37 |
-
}
|
38 |
-
|
39 |
-
package "alfred.py" {
|
40 |
-
[Alfred] as Alfred
|
41 |
-
[GraphBuilder] as GraphBuilder
|
42 |
-
}
|
43 |
-
|
44 |
-
package "management.py" {
|
45 |
-
[Assistant] as Assistant
|
46 |
-
[Manager] as Manager
|
47 |
-
}
|
48 |
-
|
49 |
-
' Relationships
|
50 |
-
UI -d-> Application
|
51 |
-
Application -d-> Alfred
|
52 |
-
Alfred -d-> GraphBuilder
|
53 |
-
GraphBuilder -d-> Assistant
|
54 |
-
GraphBuilder -d-> Manager
|
55 |
-
|
56 |
-
' LangGraph Flow and LlamaIndex Flow positioning
|
57 |
-
component "LangGraph Flow" as LangGraphFlow CANVAS_COLOR {
|
58 |
-
[Query] as Query
|
59 |
-
[Assistant Node] as AssistantNode
|
60 |
-
[Manager Node] as ManagerNode
|
61 |
-
[<b>Final Answer</b>] as FinalAnswer FINAL_ANSWER_COLOR
|
62 |
-
|
63 |
-
Query -r-> AssistantNode
|
64 |
-
AssistantNode -r-> ManagerNode : requests
|
65 |
-
ManagerNode -l-> AssistantNode : solution
|
66 |
-
ManagerNode -d-> break_up_recursion
|
67 |
-
break_up_recursion -u-> ManagerNode
|
68 |
-
AssistantNode -d-> FinalAnswer : problem solved
|
69 |
-
}
|
70 |
-
|
71 |
-
' Position LlamaIndex Flow with solver.py and toolbox.py - more compact
|
72 |
-
component "LlamaIndex Flow" as LlamaIndexFlow CANVAS_COLOR {
|
73 |
-
package "solver.py" {
|
74 |
-
[Solver] as Solver TODO_COLOR
|
75 |
-
[Summarizer] as Summarizer
|
76 |
-
[Researcher] as Researcher TODO_COLOR
|
77 |
-
[EncryptionExpert] as EncryptionExpert TODO_COLOR
|
78 |
-
[MathExpert] as MathExpert TODO_COLOR
|
79 |
-
[Reasoner] as Reasoner TODO_COLOR
|
80 |
-
[ImageHandler] as ImageHandler TODO_COLOR
|
81 |
-
[VideoHandler] as VideoHandler TODO_COLOR
|
82 |
-
}
|
83 |
-
|
84 |
-
package "toolbox.py" {
|
85 |
-
node Math TOOLBOX_MATH_COLOR [
|
86 |
-
<b>Math</b>
|
87 |
-
----
|
88 |
-
symbolic_calc
|
89 |
-
____
|
90 |
-
unit_converter
|
91 |
-
]
|
92 |
-
node WebSearch TOOLBOX_WEBSEARCH_COLOR [
|
93 |
-
<b>WebSearch</b>
|
94 |
-
----
|
95 |
-
duckduckgo_text_search
|
96 |
-
____
|
97 |
-
duckduckgo_images_search
|
98 |
-
____
|
99 |
-
duckduckgo_videos_search
|
100 |
-
]
|
101 |
-
node Encryption TOOLBOX_ENCRYPTION_COLOR [
|
102 |
-
<b>Encryption</b>
|
103 |
-
----
|
104 |
-
<font color=FireBrick><b>ascii_encode/decode</b></font>
|
105 |
-
____
|
106 |
-
<font color=FireBrick><b>chr_to_int/int_to_chr</b></font>
|
107 |
-
____
|
108 |
-
base64_encode/decode
|
109 |
-
____
|
110 |
-
caesar_cipher_encode/decode/<font color=FireBrick><b>brute_force</b></font>
|
111 |
-
____
|
112 |
-
reverse_string
|
113 |
-
]
|
114 |
-
}
|
115 |
-
|
116 |
-
Solver -r-> Summarizer
|
117 |
-
Solver -d-> Researcher
|
118 |
-
Solver -d-> EncryptionExpert
|
119 |
-
Solver -d-> MathExpert
|
120 |
-
Solver -d-> Reasoner
|
121 |
-
Solver -d-> ImageHandler
|
122 |
-
Solver -d-> VideoHandler
|
123 |
-
|
124 |
-
Researcher -d-> WebSearch
|
125 |
-
MathExpert -d-> Math
|
126 |
-
EncryptionExpert -d-> Encryption
|
127 |
-
}
|
128 |
-
|
129 |
-
' Adjust the label position to avoid overlap
|
130 |
-
GraphBuilder -d-> LangGraphFlow : builds
|
131 |
-
|
132 |
-
' Curved connections from Manager to Solver/Summarizer
|
133 |
-
Manager -d-> Solver
|
134 |
-
Manager -d-> Summarizer
|
135 |
-
|
136 |
-
' Bidirectional connection between ManagerNode and Solver
|
137 |
-
ManagerNode -r-> Solver : requests
|
138 |
-
Solver -l-> ManagerNode : provides solution
|
139 |
-
|
140 |
-
' Agents powering the Nodes (dashed, blue color)
|
141 |
-
Assistant -[POWERS_ARROW_COLOR,dashed]d-> AssistantNode : powers
|
142 |
-
Manager -[POWERS_ARROW_COLOR,dashed]d-> ManagerNode : powers
|
143 |
-
|
144 |
-
' Adding a direct connection from Alfred to Query
|
145 |
-
Alfred -[TRIGGER_ARROW_COLOR]d..> Query
|
146 |
-
|
147 |
-
@enduml
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
diagrams/diagram.puml
DELETED
@@ -1,87 +0,0 @@
|
|
1 |
-
@startuml
|
2 |
-
|
3 |
-
!include style.puml
|
4 |
-
|
5 |
-
' left to right direction
|
6 |
-
|
7 |
-
' Hide the class indicators (C)
|
8 |
-
hide circle
|
9 |
-
hide empty members
|
10 |
-
|
11 |
-
' Agent Definitions (Use class notation for agents with tools as fields)
|
12 |
-
|
13 |
-
|
14 |
-
class "Assistant" as Assistant <<M>> {
|
15 |
-
}
|
16 |
-
|
17 |
-
class "Manager" as Manager <<M>> {
|
18 |
-
- Solver() {trivial task}
|
19 |
-
- Manager() {complex task}
|
20 |
-
}
|
21 |
-
|
22 |
-
class "Solver" as Solver {
|
23 |
-
- Researcher()
|
24 |
-
- EncryptionExpert()
|
25 |
-
- MathExpert()
|
26 |
-
- Reasoner()
|
27 |
-
- ImageHandler()
|
28 |
-
- VideoHandler()
|
29 |
-
}
|
30 |
-
|
31 |
-
class "Researcher" as Researcher {
|
32 |
-
+ DuckDuckGoSearchToolSpec
|
33 |
-
}
|
34 |
-
|
35 |
-
class "EncryptionExpert" as EncryptionExpert {
|
36 |
-
+ ASCII Encode
|
37 |
-
+ ASCII Decode
|
38 |
-
+ ChrToInt Encode
|
39 |
-
+ ChrToInt Decode
|
40 |
-
+ Base64 Encode
|
41 |
-
+ Base64 Decode
|
42 |
-
+ Caesar Cipher Encode
|
43 |
-
+ Caesar Cipher Decode
|
44 |
-
+ Caesar Cipher Brute Force
|
45 |
-
+ Reverse String
|
46 |
-
- MathExpert()
|
47 |
-
- Reasoner()
|
48 |
-
}
|
49 |
-
|
50 |
-
class "MathExpert" as MathExpert {
|
51 |
-
+ Symbolic Math Calculator
|
52 |
-
+ Unit Converter
|
53 |
-
- Reasoner()
|
54 |
-
}
|
55 |
-
|
56 |
-
class "Reasoner" as Reasoner {
|
57 |
-
}
|
58 |
-
|
59 |
-
class "ImageHandler" as ImageHandler {
|
60 |
-
}
|
61 |
-
|
62 |
-
class "VideoHandler" as VideoHandler {
|
63 |
-
}
|
64 |
-
|
65 |
-
' Agent-to-Agent Connections
|
66 |
-
Query --> Assistant
|
67 |
-
Assistant --> Manager : request
|
68 |
-
Manager --> Assistant : solution
|
69 |
-
Assistant --> Final_Answer : problem solved
|
70 |
-
|
71 |
-
Manager ..> Manager : complex task
|
72 |
-
Manager ..> Solver : trivial task
|
73 |
-
Solver ..> Manager : solution
|
74 |
-
|
75 |
-
Solver .. Researcher : query
|
76 |
-
Solver .. EncryptionExpert : query
|
77 |
-
Solver .. MathExpert : query
|
78 |
-
Solver .. Reasoner : query
|
79 |
-
Solver .. ImageHandler : query
|
80 |
-
Solver .. VideoHandler : query
|
81 |
-
|
82 |
-
EncryptionExpert .. MathExpert : query
|
83 |
-
EncryptionExpert .. Reasoner : query
|
84 |
-
MathExpert .. Reasoner : query
|
85 |
-
|
86 |
-
|
87 |
-
@enduml
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
diagrams/style.puml
DELETED
@@ -1,7 +0,0 @@
|
|
1 |
-
skinparam class {
|
2 |
-
BackgroundColor #E0F7FA
|
3 |
-
BorderColor #D84315
|
4 |
-
FontColor #E65100
|
5 |
-
FontStyle bold
|
6 |
-
HeaderBackgroundColor #FFE0B2
|
7 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
graph.py
ADDED
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_core.messages import AIMessage, HumanMessage
|
2 |
+
from langgraph.graph import START, END, StateGraph
|
3 |
+
|
4 |
+
from typing import Any, Dict, List, Literal, Optional, TypedDict
|
5 |
+
import logging
|
6 |
+
from pathlib import Path
|
7 |
+
|
8 |
+
from args import Args
|
9 |
+
|
10 |
+
|
11 |
+
class State(TypedDict):
|
12 |
+
"""State class for the agent graph."""
|
13 |
+
initial_query: str
|
14 |
+
messages: List[Dict[str, Any]]
|
15 |
+
nr_interactions: int
|
16 |
+
final_response: Optional[str]
|
17 |
+
|
18 |
+
|
19 |
+
class Nodes:
|
20 |
+
"""
|
21 |
+
Collection of node functions for the agent graph.
|
22 |
+
"""
|
23 |
+
def manager_node(self, state: State) -> State:
|
24 |
+
"""
|
25 |
+
Orchestrates the workflow by delegating tasks to specialized nodes and integrating their outputs
|
26 |
+
"""
|
27 |
+
# TODO: To implement...
|
28 |
+
pass
|
29 |
+
|
30 |
+
def final_answer_node(self, state: State) -> State:
|
31 |
+
"""
|
32 |
+
Formats and delivers the final response to the user
|
33 |
+
"""
|
34 |
+
# TODO: To implement...
|
35 |
+
pass
|
36 |
+
|
37 |
+
def auditor_node(self, state: State) -> State:
|
38 |
+
"""
|
39 |
+
Reviews manager's outputs for accuracy, safety, and quality
|
40 |
+
"""
|
41 |
+
# TODO: To implement...
|
42 |
+
pass
|
43 |
+
|
44 |
+
def solver_node(self, state: State) -> State:
|
45 |
+
"""
|
46 |
+
Central problem-solving node that coordinates with specialized experts based on task requirements
|
47 |
+
"""
|
48 |
+
# TODO: To implement...
|
49 |
+
pass
|
50 |
+
|
51 |
+
def researcher_node(self, state: State) -> State:
|
52 |
+
"""
|
53 |
+
Retrieves and synthesizes information from various sources to answer knowledge-based questions
|
54 |
+
"""
|
55 |
+
# TODO: To implement...
|
56 |
+
pass
|
57 |
+
|
58 |
+
def encryption_expert_node(self, state: State) -> State:
|
59 |
+
"""
|
60 |
+
Handles encryption/decryption tasks and encoding/decoding operations
|
61 |
+
"""
|
62 |
+
# TODO: To implement...
|
63 |
+
pass
|
64 |
+
|
65 |
+
def encryption_advisor_node(self, state: State) -> State:
|
66 |
+
"""
|
67 |
+
Provides specialized guidance on complex encryption problems to the encryption expert
|
68 |
+
"""
|
69 |
+
# TODO: To implement...
|
70 |
+
pass
|
71 |
+
|
72 |
+
def math_expert_node(self, state: State) -> State:
|
73 |
+
"""
|
74 |
+
Performs mathematical calculations and solves numerical problems
|
75 |
+
"""
|
76 |
+
# TODO: To implement...
|
77 |
+
pass
|
78 |
+
|
79 |
+
def math_advisor_node(self, state: State) -> State:
|
80 |
+
"""
|
81 |
+
Provides specialized guidance on complex mathematical problems to the math expert
|
82 |
+
"""
|
83 |
+
# TODO: To implement...
|
84 |
+
pass
|
85 |
+
|
86 |
+
def reasoner_node(self, state: State) -> State:
|
87 |
+
"""
|
88 |
+
Performs logical reasoning, inference, and step-by-step problem-solving
|
89 |
+
"""
|
90 |
+
# TODO: To implement...
|
91 |
+
pass
|
92 |
+
|
93 |
+
def image_handler_node(self, state: State) -> State:
|
94 |
+
"""
|
95 |
+
Processes, analyzes, and generates information related to images
|
96 |
+
"""
|
97 |
+
# TODO: To implement...
|
98 |
+
pass
|
99 |
+
|
100 |
+
def video_handler_node(self, state: State) -> State:
|
101 |
+
"""
|
102 |
+
Processes, analyzes, and generates information related to videos
|
103 |
+
"""
|
104 |
+
# TODO: To implement...
|
105 |
+
pass
|
106 |
+
|
107 |
+
|
108 |
+
class Edges:
|
109 |
+
"""
|
110 |
+
Collection of conditional edge functions for the agent graph.
|
111 |
+
"""
|
112 |
+
def manager_edge(self, state: State) -> Literal["solver", "auditor", "final_answer"]:
|
113 |
+
"""
|
114 |
+
Conditional edge for manager node.
|
115 |
+
Returns one of: "solver", "auditor", "final_answer"
|
116 |
+
"""
|
117 |
+
# TODO: To implement...
|
118 |
+
pass
|
119 |
+
|
120 |
+
def solver_edge(self, state: State) -> Literal["manager", "researcher", "encryption_expert", "math_expert", "reasoner", "image_handler", "video_handler"]:
|
121 |
+
"""
|
122 |
+
Conditional edge for solver node.
|
123 |
+
Returns one of: "manager", "researcher", "encryption_expert", "math_expert", "reasoner", "image_handler", "video_handler"
|
124 |
+
"""
|
125 |
+
# TODO: To implement...
|
126 |
+
pass
|
127 |
+
|
128 |
+
def encryption_expert_edge(self, state: State) -> Literal["solver", "encryption_advisor"]:
|
129 |
+
"""
|
130 |
+
Conditional edge for encryption_expert node.
|
131 |
+
Returns one of: "solver", "encryption_advisor"
|
132 |
+
"""
|
133 |
+
# TODO: To implement...
|
134 |
+
pass
|
135 |
+
|
136 |
+
def math_expert_edge(self, state: State) -> Literal["solver", "math_advisor"]:
|
137 |
+
"""
|
138 |
+
Conditional edge for math_expert node.
|
139 |
+
Returns one of: "solver", "math_advisor"
|
140 |
+
"""
|
141 |
+
# TODO: To implement...
|
142 |
+
pass
|
graph_builder.py
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from IPython.display import Image, display
|
2 |
+
from langgraph.graph import StateGraph, END, START
|
3 |
+
from langgraph.graph.state import CompiledStateGraph
|
4 |
+
from graph import State, Nodes, Edges
|
5 |
+
|
6 |
+
|
7 |
+
class GraphBuilder:
|
8 |
+
def __init__(self):
|
9 |
+
"""
|
10 |
+
Initializes the GraphBuilder.
|
11 |
+
"""
|
12 |
+
self.nodes = Nodes()
|
13 |
+
self.edges = Edges()
|
14 |
+
self.compiled_graph = None
|
15 |
+
|
16 |
+
def build_agent_graph(self) -> CompiledStateGraph:
|
17 |
+
"""Build and return the agent graph."""
|
18 |
+
graph = StateGraph(State)
|
19 |
+
|
20 |
+
graph.add_node("manager", self.nodes.manager_node)
|
21 |
+
graph.add_node("final_answer", self.nodes.final_answer_node)
|
22 |
+
graph.add_node("auditor", self.nodes.auditor_node)
|
23 |
+
graph.add_node("solver", self.nodes.solver_node)
|
24 |
+
graph.add_node("researcher", self.nodes.researcher_node)
|
25 |
+
graph.add_node("encryption_expert", self.nodes.encryption_expert_node)
|
26 |
+
graph.add_node("encryption_advisor", self.nodes.encryption_advisor_node)
|
27 |
+
graph.add_node("math_expert", self.nodes.math_expert_node)
|
28 |
+
graph.add_node("math_advisor", self.nodes.math_advisor_node)
|
29 |
+
graph.add_node("reasoner", self.nodes.reasoner_node)
|
30 |
+
graph.add_node("image_handler", self.nodes.image_handler_node)
|
31 |
+
graph.add_node("video_handler", self.nodes.video_handler_node)
|
32 |
+
|
33 |
+
graph.add_edge(START, "manager")
|
34 |
+
graph.add_edge("final_answer", END)
|
35 |
+
graph.add_edge("auditor", "manager")
|
36 |
+
graph.add_edge("researcher", "solver")
|
37 |
+
graph.add_edge("encryption_advisor", "encryption_expert")
|
38 |
+
graph.add_edge("math_advisor", "math_expert")
|
39 |
+
graph.add_edge("reasoner", "solver")
|
40 |
+
graph.add_edge("image_handler", "solver")
|
41 |
+
graph.add_edge("video_handler", "solver")
|
42 |
+
|
43 |
+
graph.add_conditional_edges(
|
44 |
+
"manager",
|
45 |
+
self.edges.manager_edge,
|
46 |
+
{
|
47 |
+
"solver": "solver", "auditor": "auditor", "final_answer": "final_answer"
|
48 |
+
}
|
49 |
+
)
|
50 |
+
graph.add_conditional_edges(
|
51 |
+
"solver",
|
52 |
+
self.edges.solver_edge,
|
53 |
+
{
|
54 |
+
"manager": "manager", "researcher": "researcher", "encryption_expert": "encryption_expert", "math_expert": "math_expert", "reasoner": "reasoner", "image_handler": "image_handler", "video_handler": "video_handler"
|
55 |
+
}
|
56 |
+
)
|
57 |
+
graph.add_conditional_edges(
|
58 |
+
"encryption_expert",
|
59 |
+
self.edges.encryption_expert_edge,
|
60 |
+
{
|
61 |
+
"solver": "solver", "encryption_advisor": "encryption_advisor"
|
62 |
+
}
|
63 |
+
)
|
64 |
+
graph.add_conditional_edges(
|
65 |
+
"math_expert",
|
66 |
+
self.edges.math_expert_edge,
|
67 |
+
{
|
68 |
+
"solver": "solver", "math_advisor": "math_advisor"
|
69 |
+
}
|
70 |
+
)
|
71 |
+
|
72 |
+
return graph.compile()
|
73 |
+
|
74 |
+
def display_graph(self):
|
75 |
+
if self.compiled_graph is None:
|
76 |
+
raise ValueError("Graph has not been compiled yet. Call build_agent_graph() first.")
|
77 |
+
|
78 |
+
display(Image(self.compiled_graph.get_graph().draw_mermaid_png()))
|
logger.py
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging
|
2 |
+
import datetime
|
3 |
+
from pathlib import Path
|
4 |
+
|
5 |
+
|
6 |
+
class Logger:
|
7 |
+
@staticmethod
|
8 |
+
def set_logger() -> logging.Logger:
|
9 |
+
"""
|
10 |
+
Configure and return a logger with a file handler that writes to logs/<current date-time>.txt
|
11 |
+
|
12 |
+
Returns:
|
13 |
+
logging.Logger: Configured logger instance
|
14 |
+
"""
|
15 |
+
# Create logger
|
16 |
+
logger = logging.getLogger("Alfred")
|
17 |
+
logger.setLevel(logging.INFO)
|
18 |
+
|
19 |
+
# Create formatter
|
20 |
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
21 |
+
|
22 |
+
# Create logs directory if it doesn't exist
|
23 |
+
logs_dir = Path('logs')
|
24 |
+
logs_dir.mkdir(exist_ok=True)
|
25 |
+
|
26 |
+
# Generate log filename with current date-time
|
27 |
+
current_time = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
28 |
+
log_filename = f"{current_time}.txt"
|
29 |
+
log_filepath = logs_dir / log_filename
|
30 |
+
|
31 |
+
# Create file handler with UTF-8 encoding to handle all Unicode characters
|
32 |
+
file_handler = logging.FileHandler(log_filepath, encoding='utf-8')
|
33 |
+
file_handler.setLevel(logging.INFO)
|
34 |
+
file_handler.setFormatter(formatter)
|
35 |
+
|
36 |
+
# Add handler to logger
|
37 |
+
logger.addHandler(file_handler)
|
38 |
+
|
39 |
+
logger.info(f"Logging started at {current_time}")
|
40 |
+
return logger
|
management.py
DELETED
@@ -1,106 +0,0 @@
|
|
1 |
-
from llama_index.core.tools import FunctionTool
|
2 |
-
|
3 |
-
from typing import List
|
4 |
-
|
5 |
-
from itf_agent import IAgent
|
6 |
-
from solver import Solver, Summarizer
|
7 |
-
from args import Args
|
8 |
-
|
9 |
-
|
10 |
-
class Assistant(IAgent):
|
11 |
-
def __init__(self, temperature, max_tokens):
|
12 |
-
super().__init__(temperature, max_tokens, "01_assistant.txt", Args.primary_llm_interface)
|
13 |
-
|
14 |
-
class Manager(IAgent):
|
15 |
-
def __init__(self, temperature, max_tokens, max_depth):
|
16 |
-
super().__init__(temperature, max_tokens, "02_manager.txt", Args.primary_llm_interface)
|
17 |
-
|
18 |
-
self.max_depth = max_depth
|
19 |
-
self.current_depth = 0
|
20 |
-
|
21 |
-
# We track the current query to forward it to the team when needed.
|
22 |
-
self.current_query = ""
|
23 |
-
|
24 |
-
self.solver = Solver(temperature, max_tokens)
|
25 |
-
self.summarizer = Summarizer(temperature, max_tokens)
|
26 |
-
|
27 |
-
def setup_tools(self) -> List:
|
28 |
-
return [
|
29 |
-
FunctionTool.from_defaults(
|
30 |
-
name="require_break_up",
|
31 |
-
description="Break a complex task into simpler subtasks. Use when a task needs to be divided into manageable parts.",
|
32 |
-
fn=self.require_break_up
|
33 |
-
),
|
34 |
-
FunctionTool.from_defaults(
|
35 |
-
name="require_solution",
|
36 |
-
description="Request direct solutions for specific tasks. Use when a task is simple enough to be solved directly.",
|
37 |
-
fn=self.require_solution
|
38 |
-
),
|
39 |
-
FunctionTool.from_defaults(
|
40 |
-
name="forward_query",
|
41 |
-
description="Request direct solutions for the current query. Use as a first attempt and to make the team aware of the task's context.",
|
42 |
-
fn=self.forward_query
|
43 |
-
)
|
44 |
-
]
|
45 |
-
|
46 |
-
async def query(self, question: str, has_context=True) -> str:
|
47 |
-
self.current_query = question
|
48 |
-
return await super().query(question, has_context)
|
49 |
-
|
50 |
-
async def require_break_up(self, tasks: List[str], try_solving = False) -> str:
|
51 |
-
"""
|
52 |
-
Break down complex tasks into simpler subtasks recursively up to max_depth.
|
53 |
-
|
54 |
-
Args:
|
55 |
-
tasks: List of tasks to break down
|
56 |
-
try_solving: Whether to attempt solving tasks at max depth (default: False)
|
57 |
-
|
58 |
-
Returns:
|
59 |
-
Summarized report of the task breakdown
|
60 |
-
"""
|
61 |
-
print(f"-> require_break_up tool used (input: {tasks}) !")
|
62 |
-
if not tasks:
|
63 |
-
return "Error: No tasks provided to break up. Please provide at least one task."
|
64 |
-
|
65 |
-
self.current_depth += 1
|
66 |
-
|
67 |
-
observation = ""
|
68 |
-
if self.current_depth < self.max_depth:
|
69 |
-
for task in tasks:
|
70 |
-
solution = await self.query(task, has_context=False)
|
71 |
-
response = f"For task:\n\n{task}\n\nThe following break up has been provided:\n\n{solution}\n\n"
|
72 |
-
observation += response
|
73 |
-
elif try_solving:
|
74 |
-
for task in tasks:
|
75 |
-
response = await self.solver.query(task)
|
76 |
-
else:
|
77 |
-
observation = "Maximum depth for `break_up` tool has been reached ! At this point, you may try to break up the task yourself or try `require_solution`."
|
78 |
-
|
79 |
-
self.current_depth -= 1
|
80 |
-
report = await self.summarizer.query(observation.strip())
|
81 |
-
return report
|
82 |
-
|
83 |
-
async def require_solution(self, tasks: List[str]) -> str:
|
84 |
-
"""
|
85 |
-
Request direct solutions for the provided tasks using the Solver.
|
86 |
-
|
87 |
-
Args:
|
88 |
-
tasks: List of tasks to solve
|
89 |
-
|
90 |
-
Returns:
|
91 |
-
Summarized report of solutions for all tasks
|
92 |
-
"""
|
93 |
-
print(f"-> require_solution tool used with input: {tasks} !")
|
94 |
-
if not tasks:
|
95 |
-
return "Error: No tasks provided to solve. Please provide at least one task."
|
96 |
-
|
97 |
-
observation = ""
|
98 |
-
for task in tasks:
|
99 |
-
solution = await self.solver.query(task)
|
100 |
-
observation += solution
|
101 |
-
|
102 |
-
report = await self.summarizer.query(observation.strip())
|
103 |
-
return report
|
104 |
-
|
105 |
-
async def forward_query(self) -> str:
|
106 |
-
return await self.require_solution([self.current_query])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
system_prompts/{02_manager.txt → 01_manager.txt}
RENAMED
File without changes
|
system_prompts/{01_assistant.txt → 02_auditor.txt}
RENAMED
File without changes
|
test.py
ADDED
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from graph import State, Nodes, Edges
|
2 |
+
from graph_builder import GraphBuilder
|
3 |
+
|
4 |
+
import unittest
|
5 |
+
|
6 |
+
|
7 |
+
class TestAlfredAgent(unittest.TestCase):
|
8 |
+
"""Test suite for the Alfred agent"""
|
9 |
+
|
10 |
+
def setUp(self):
|
11 |
+
"""Set up test fixtures"""
|
12 |
+
self.nodes = Nodes()
|
13 |
+
self.edges = Edges()
|
14 |
+
self.builder = GraphBuilder()
|
15 |
+
self.graph = self.builder.build_agent_graph()
|
16 |
+
|
17 |
+
def test_manager_node(self):
|
18 |
+
"""
|
19 |
+
Test the manager node functionality.
|
20 |
+
|
21 |
+
Orchestrates the workflow by delegating tasks to specialized nodes and integrating their outputs
|
22 |
+
"""
|
23 |
+
# Create an instance of Nodes class
|
24 |
+
nodes = Nodes()
|
25 |
+
|
26 |
+
# Create a test state
|
27 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
28 |
+
|
29 |
+
# Test the node function
|
30 |
+
print(f"Testing 'manager' node...")
|
31 |
+
nodes.manager_node(test_state)
|
32 |
+
|
33 |
+
# TODO: Add assertions to verify the state changes
|
34 |
+
print(f"State after node execution: {test_state}")
|
35 |
+
|
36 |
+
def test_final_answer_node(self):
|
37 |
+
"""
|
38 |
+
Test the final_answer node functionality.
|
39 |
+
|
40 |
+
Formats and delivers the final response to the user
|
41 |
+
"""
|
42 |
+
# Create an instance of Nodes class
|
43 |
+
nodes = Nodes()
|
44 |
+
|
45 |
+
# Create a test state
|
46 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
47 |
+
|
48 |
+
# Test the node function
|
49 |
+
print(f"Testing 'final_answer' node...")
|
50 |
+
nodes.final_answer_node(test_state)
|
51 |
+
|
52 |
+
# TODO: Add assertions to verify the state changes
|
53 |
+
print(f"State after node execution: {test_state}")
|
54 |
+
|
55 |
+
def test_auditor_node(self):
|
56 |
+
"""
|
57 |
+
Test the auditor node functionality.
|
58 |
+
|
59 |
+
Reviews manager's outputs for accuracy, safety, and quality
|
60 |
+
"""
|
61 |
+
# Create an instance of Nodes class
|
62 |
+
nodes = Nodes()
|
63 |
+
|
64 |
+
# Create a test state
|
65 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
66 |
+
|
67 |
+
# Test the node function
|
68 |
+
print(f"Testing 'auditor' node...")
|
69 |
+
nodes.auditor_node(test_state)
|
70 |
+
|
71 |
+
# TODO: Add assertions to verify the state changes
|
72 |
+
print(f"State after node execution: {test_state}")
|
73 |
+
|
74 |
+
def test_solver_node(self):
|
75 |
+
"""
|
76 |
+
Test the solver node functionality.
|
77 |
+
|
78 |
+
Central problem-solving node that coordinates with specialized experts based on task requirements
|
79 |
+
"""
|
80 |
+
# Create an instance of Nodes class
|
81 |
+
nodes = Nodes()
|
82 |
+
|
83 |
+
# Create a test state
|
84 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
85 |
+
|
86 |
+
# Test the node function
|
87 |
+
print(f"Testing 'solver' node...")
|
88 |
+
nodes.solver_node(test_state)
|
89 |
+
|
90 |
+
# TODO: Add assertions to verify the state changes
|
91 |
+
print(f"State after node execution: {test_state}")
|
92 |
+
|
93 |
+
def test_researcher_node(self):
|
94 |
+
"""
|
95 |
+
Test the researcher node functionality.
|
96 |
+
|
97 |
+
Retrieves and synthesizes information from various sources to answer knowledge-based questions
|
98 |
+
"""
|
99 |
+
# Create an instance of Nodes class
|
100 |
+
nodes = Nodes()
|
101 |
+
|
102 |
+
# Create a test state
|
103 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
104 |
+
|
105 |
+
# Test the node function
|
106 |
+
print(f"Testing 'researcher' node...")
|
107 |
+
nodes.researcher_node(test_state)
|
108 |
+
|
109 |
+
# TODO: Add assertions to verify the state changes
|
110 |
+
print(f"State after node execution: {test_state}")
|
111 |
+
|
112 |
+
def test_encryption_expert_node(self):
|
113 |
+
"""
|
114 |
+
Test the encryption_expert node functionality.
|
115 |
+
|
116 |
+
Handles encryption/decryption tasks and encoding/decoding operations
|
117 |
+
"""
|
118 |
+
# Create an instance of Nodes class
|
119 |
+
nodes = Nodes()
|
120 |
+
|
121 |
+
# Create a test state
|
122 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
123 |
+
|
124 |
+
# Test the node function
|
125 |
+
print(f"Testing 'encryption_expert' node...")
|
126 |
+
nodes.encryption_expert_node(test_state)
|
127 |
+
|
128 |
+
# TODO: Add assertions to verify the state changes
|
129 |
+
print(f"State after node execution: {test_state}")
|
130 |
+
|
131 |
+
def test_encryption_advisor_node(self):
|
132 |
+
"""
|
133 |
+
Test the encryption_advisor node functionality.
|
134 |
+
|
135 |
+
Provides specialized guidance on complex encryption problems to the encryption expert
|
136 |
+
"""
|
137 |
+
# Create an instance of Nodes class
|
138 |
+
nodes = Nodes()
|
139 |
+
|
140 |
+
# Create a test state
|
141 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
142 |
+
|
143 |
+
# Test the node function
|
144 |
+
print(f"Testing 'encryption_advisor' node...")
|
145 |
+
nodes.encryption_advisor_node(test_state)
|
146 |
+
|
147 |
+
# TODO: Add assertions to verify the state changes
|
148 |
+
print(f"State after node execution: {test_state}")
|
149 |
+
|
150 |
+
def test_math_expert_node(self):
|
151 |
+
"""
|
152 |
+
Test the math_expert node functionality.
|
153 |
+
|
154 |
+
Performs mathematical calculations and solves numerical problems
|
155 |
+
"""
|
156 |
+
# Create an instance of Nodes class
|
157 |
+
nodes = Nodes()
|
158 |
+
|
159 |
+
# Create a test state
|
160 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
161 |
+
|
162 |
+
# Test the node function
|
163 |
+
print(f"Testing 'math_expert' node...")
|
164 |
+
nodes.math_expert_node(test_state)
|
165 |
+
|
166 |
+
# TODO: Add assertions to verify the state changes
|
167 |
+
print(f"State after node execution: {test_state}")
|
168 |
+
|
169 |
+
def test_math_advisor_node(self):
|
170 |
+
"""
|
171 |
+
Test the math_advisor node functionality.
|
172 |
+
|
173 |
+
Provides specialized guidance on complex mathematical problems to the math expert
|
174 |
+
"""
|
175 |
+
# Create an instance of Nodes class
|
176 |
+
nodes = Nodes()
|
177 |
+
|
178 |
+
# Create a test state
|
179 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
180 |
+
|
181 |
+
# Test the node function
|
182 |
+
print(f"Testing 'math_advisor' node...")
|
183 |
+
nodes.math_advisor_node(test_state)
|
184 |
+
|
185 |
+
# TODO: Add assertions to verify the state changes
|
186 |
+
print(f"State after node execution: {test_state}")
|
187 |
+
|
188 |
+
def test_reasoner_node(self):
|
189 |
+
"""
|
190 |
+
Test the reasoner node functionality.
|
191 |
+
|
192 |
+
Performs logical reasoning, inference, and step-by-step problem-solving
|
193 |
+
"""
|
194 |
+
# Create an instance of Nodes class
|
195 |
+
nodes = Nodes()
|
196 |
+
|
197 |
+
# Create a test state
|
198 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
199 |
+
|
200 |
+
# Test the node function
|
201 |
+
print(f"Testing 'reasoner' node...")
|
202 |
+
nodes.reasoner_node(test_state)
|
203 |
+
|
204 |
+
# TODO: Add assertions to verify the state changes
|
205 |
+
print(f"State after node execution: {test_state}")
|
206 |
+
|
207 |
+
def test_image_handler_node(self):
|
208 |
+
"""
|
209 |
+
Test the image_handler node functionality.
|
210 |
+
|
211 |
+
Processes, analyzes, and generates information related to images
|
212 |
+
"""
|
213 |
+
# Create an instance of Nodes class
|
214 |
+
nodes = Nodes()
|
215 |
+
|
216 |
+
# Create a test state
|
217 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
218 |
+
|
219 |
+
# Test the node function
|
220 |
+
print(f"Testing 'image_handler' node...")
|
221 |
+
nodes.image_handler_node(test_state)
|
222 |
+
|
223 |
+
# TODO: Add assertions to verify the state changes
|
224 |
+
print(f"State after node execution: {test_state}")
|
225 |
+
|
226 |
+
def test_video_handler_node(self):
|
227 |
+
"""
|
228 |
+
Test the video_handler node functionality.
|
229 |
+
|
230 |
+
Processes, analyzes, and generates information related to videos
|
231 |
+
"""
|
232 |
+
# Create an instance of Nodes class
|
233 |
+
nodes = Nodes()
|
234 |
+
|
235 |
+
# Create a test state
|
236 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
237 |
+
|
238 |
+
# Test the node function
|
239 |
+
print(f"Testing 'video_handler' node...")
|
240 |
+
nodes.video_handler_node(test_state)
|
241 |
+
|
242 |
+
# TODO: Add assertions to verify the state changes
|
243 |
+
print(f"State after node execution: {test_state}")
|
244 |
+
|
245 |
+
def test_manager_edge(self):
|
246 |
+
"""
|
247 |
+
Test the conditional edge for manager node.
|
248 |
+
|
249 |
+
This edge should return one of: "solver", "auditor", "final_answer"
|
250 |
+
"""
|
251 |
+
# Create an instance of Edges class
|
252 |
+
edges = Edges()
|
253 |
+
|
254 |
+
# Create a test state
|
255 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
256 |
+
|
257 |
+
# Test the edge function
|
258 |
+
print(f"Testing 'manager' conditional edge...")
|
259 |
+
result = edges.manager_edge(test_state)
|
260 |
+
|
261 |
+
# TODO: Add assertions to verify the result
|
262 |
+
print(f"Edge decision: {result}")
|
263 |
+
assert result in ["solver", "auditor", "final_answer"], f"Edge result '{result}' not in expected values"
|
264 |
+
|
265 |
+
def test_solver_edge(self):
|
266 |
+
"""
|
267 |
+
Test the conditional edge for solver node.
|
268 |
+
|
269 |
+
This edge should return one of: "manager", "researcher", "encryption_expert", "math_expert", "reasoner", "image_handler", "video_handler"
|
270 |
+
"""
|
271 |
+
# Create an instance of Edges class
|
272 |
+
edges = Edges()
|
273 |
+
|
274 |
+
# Create a test state
|
275 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
276 |
+
|
277 |
+
# Test the edge function
|
278 |
+
print(f"Testing 'solver' conditional edge...")
|
279 |
+
result = edges.solver_edge(test_state)
|
280 |
+
|
281 |
+
# TODO: Add assertions to verify the result
|
282 |
+
print(f"Edge decision: {result}")
|
283 |
+
assert result in ["manager", "researcher", "encryption_expert", "math_expert", "reasoner", "image_handler", "video_handler"], f"Edge result '{result}' not in expected values"
|
284 |
+
|
285 |
+
def test_encryption_expert_edge(self):
|
286 |
+
"""
|
287 |
+
Test the conditional edge for encryption_expert node.
|
288 |
+
|
289 |
+
This edge should return one of: "solver", "encryption_advisor"
|
290 |
+
"""
|
291 |
+
# Create an instance of Edges class
|
292 |
+
edges = Edges()
|
293 |
+
|
294 |
+
# Create a test state
|
295 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
296 |
+
|
297 |
+
# Test the edge function
|
298 |
+
print(f"Testing 'encryption_expert' conditional edge...")
|
299 |
+
result = edges.encryption_expert_edge(test_state)
|
300 |
+
|
301 |
+
# TODO: Add assertions to verify the result
|
302 |
+
print(f"Edge decision: {result}")
|
303 |
+
assert result in ["solver", "encryption_advisor"], f"Edge result '{result}' not in expected values"
|
304 |
+
|
305 |
+
def test_math_expert_edge(self):
|
306 |
+
"""
|
307 |
+
Test the conditional edge for math_expert node.
|
308 |
+
|
309 |
+
This edge should return one of: "solver", "math_advisor"
|
310 |
+
"""
|
311 |
+
# Create an instance of Edges class
|
312 |
+
edges = Edges()
|
313 |
+
|
314 |
+
# Create a test state
|
315 |
+
test_state = {} # TODO: Initialize with appropriate test data
|
316 |
+
|
317 |
+
# Test the edge function
|
318 |
+
print(f"Testing 'math_expert' conditional edge...")
|
319 |
+
result = edges.math_expert_edge(test_state)
|
320 |
+
|
321 |
+
# TODO: Add assertions to verify the result
|
322 |
+
print(f"Edge decision: {result}")
|
323 |
+
assert result in ["solver", "math_advisor"], f"Edge result '{result}' not in expected values"
|
324 |
+
|
325 |
+
def test_full_workflow(self):
|
326 |
+
"""
|
327 |
+
Test the Alfred agent full workflow.
|
328 |
+
"""
|
329 |
+
# TODO: Add test code here
|
330 |
+
print("Testing Alfred complete workflow...")
|
331 |
+
|
332 |
+
# Example test
|
333 |
+
# result = self.graph.invoke({"input": "Test input"})
|
334 |
+
# self.assertIsNotNone(result)
|
335 |
+
# print(f"Workflow result: {result}")
|
336 |
+
|
337 |
+
|
338 |
+
if __name__ == "__main__":
|
339 |
+
unittest.main()
|
test_agents.py
DELETED
@@ -1,17 +0,0 @@
|
|
1 |
-
# OUTDATED !
|
2 |
-
from agents_stage_2 import MathAgent, MainAgent
|
3 |
-
import asyncio
|
4 |
-
|
5 |
-
def test_math_agent():
|
6 |
-
math_agent = MathAgent(temperature=0.7, max_tokens=100)
|
7 |
-
print(math_agent.get_system_prompt())
|
8 |
-
asyncio.run(math_agent.query("What is 345 times 281?"))
|
9 |
-
|
10 |
-
def test_main_agent():
|
11 |
-
main_agent = MainAgent(temperature=0.7, max_tokens=100)
|
12 |
-
print(main_agent.get_system_prompt())
|
13 |
-
asyncio.run(main_agent.query("What is 345 times 281?"))
|
14 |
-
|
15 |
-
if __name__ == "__main__":
|
16 |
-
# test_math_agent()
|
17 |
-
test_main_agent()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
toolbox.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from llama_index.core.tools import FunctionTool
|
2 |
-
# from llama_index.tools.duckduckgo import DuckDuckGoSearchToolSpec # I encountered some issues... Sometimes it doesn't work !
|
3 |
|
4 |
|
5 |
from duckduckgo_search import DDGS
|
@@ -136,7 +135,6 @@ class _WebSearch:
|
|
136 |
|
137 |
|
138 |
class _WebSearchToolbox:
|
139 |
-
# duckduckgo_tools = DuckDuckGoSearchToolSpec().to_tool_list() # I encountered some issues... Sometimes it doesn't work !
|
140 |
duckduckgo_text_search = FunctionTool.from_defaults(
|
141 |
name="duckduckgo_text_search",
|
142 |
description="DuckDuckGo text search",
|
|
|
1 |
from llama_index.core.tools import FunctionTool
|
|
|
2 |
|
3 |
|
4 |
from duckduckgo_search import DDGS
|
|
|
135 |
|
136 |
|
137 |
class _WebSearchToolbox:
|
|
|
138 |
duckduckgo_text_search = FunctionTool.from_defaults(
|
139 |
name="duckduckgo_text_search",
|
140 |
description="DuckDuckGo text search",
|