Commit
Β·
b40e9bd
1
Parent(s):
f988705
Reworked system of routing should_continue, also logging for better managing and testing
Browse files- src/config.py +66 -5
- src/nodes.py +201 -208
- src/prompts/prompts.py +2 -2
- src/schemas.py +14 -4
- src/utils/utils.py +4 -3
- src/workflow_test.ipynb +159 -17
src/config.py
CHANGED
|
@@ -3,6 +3,8 @@ from tools.tools import *
|
|
| 3 |
from tools.code_interpreter import safe_code_run
|
| 4 |
from langgraph.prebuilt import ToolNode
|
| 5 |
from schemas import PlannerPlan
|
|
|
|
|
|
|
| 6 |
|
| 7 |
config = {"configurable": {"thread_id": "1"}, "recursion_limit" : 50}
|
| 8 |
|
|
@@ -15,12 +17,71 @@ TOOLS = [download_file_from_url, web_search,
|
|
| 15 |
class DebuggingToolNode(ToolNode):
|
| 16 |
def __init__(self, tools):
|
| 17 |
super().__init__(tools)
|
| 18 |
-
|
| 19 |
def __call__(self, state):
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
|
| 26 |
TOOL_NODE = ToolNode(TOOLS)
|
|
|
|
| 3 |
from tools.code_interpreter import safe_code_run
|
| 4 |
from langgraph.prebuilt import ToolNode
|
| 5 |
from schemas import PlannerPlan
|
| 6 |
+
from utils.utils import log_stage
|
| 7 |
+
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, ToolMessage
|
| 8 |
|
| 9 |
config = {"configurable": {"thread_id": "1"}, "recursion_limit" : 50}
|
| 10 |
|
|
|
|
| 17 |
class DebuggingToolNode(ToolNode):
|
| 18 |
def __init__(self, tools):
|
| 19 |
super().__init__(tools)
|
| 20 |
+
|
| 21 |
def __call__(self, state):
|
| 22 |
+
log_stage("TOOL NODE", subtitle="Dispatching tool calls", icon="π οΈ")
|
| 23 |
+
|
| 24 |
+
messages = state.get("messages", [])
|
| 25 |
+
last_message = messages[-1] if messages else None
|
| 26 |
+
|
| 27 |
+
if not last_message or not hasattr(last_message, "tool_calls"):
|
| 28 |
+
log_stage("TOOL ERROR", subtitle="No tool calls found", icon="β")
|
| 29 |
+
return state
|
| 30 |
+
|
| 31 |
+
tool_calls = last_message.tool_calls
|
| 32 |
+
log_stage("TOOL DISPATCH", subtitle=f"Executing {len(tool_calls)} tool(s)", icon="π§")
|
| 33 |
+
for call in tool_calls:
|
| 34 |
+
print(f" - {call['name']}: {call['args']}")
|
| 35 |
+
|
| 36 |
+
try:
|
| 37 |
+
# ΠΡΠΏΠΎΠ»Π½ΡΠ΅ΠΌ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΡ
|
| 38 |
+
result = super().__call__(state)
|
| 39 |
+
|
| 40 |
+
# ΠΡΠΎΠ²Π΅ΡΡΠ΅ΠΌ ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΡ
|
| 41 |
+
new_messages = result.get("messages", [])
|
| 42 |
+
tool_messages = [msg for msg in new_messages[len(messages):]
|
| 43 |
+
if isinstance(msg, ToolMessage)]
|
| 44 |
+
|
| 45 |
+
log_stage("TOOL RESULTS", subtitle=f"Got {len(tool_messages)} responses", icon="π¨")
|
| 46 |
+
|
| 47 |
+
# ΠΠΎΠ³ΠΈΡΡΠ΅ΠΌ ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΡ
|
| 48 |
+
for msg in tool_messages:
|
| 49 |
+
content_preview = msg.content[:100] + "..." if len(msg.content) > 100 else msg.content
|
| 50 |
+
print(f" - {msg.name}: {content_preview}")
|
| 51 |
+
|
| 52 |
+
# ΠΠ²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ Π΄ΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ ΡΠΈΠ³Π½Π°Π» Π·Π°Π²Π΅ΡΡΠ΅Π½ΠΈΡ ΡΠ°Π³Π° ΠΏΠΎΡΠ»Π΅ ΡΡΠΏΠ΅ΡΠ½ΠΎΠ³ΠΎ Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΡ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΠΎΠ²
|
| 53 |
+
if tool_messages:
|
| 54 |
+
current_step = state.get("current_step", 0)
|
| 55 |
+
plan = state.get("plan")
|
| 56 |
+
|
| 57 |
+
if plan and current_step < len(plan.steps):
|
| 58 |
+
step_completion_msg = AIMessage(
|
| 59 |
+
content=f"STEP COMPLETE: Successfully executed {len(tool_messages)} tool(s) for step {plan.steps[current_step].id}"
|
| 60 |
+
)
|
| 61 |
+
result["messages"] = result["messages"] + [step_completion_msg]
|
| 62 |
+
log_stage("STEP COMPLETION", subtitle=f"Step {current_step + 1} marked complete", icon="β
")
|
| 63 |
+
|
| 64 |
+
# ΠΡΠΎΠ΄Π²ΠΈΠ³Π°Π΅ΠΌ ΠΊ ΡΠ»Π΅Π΄ΡΡΡΠ΅ΠΌΡ ΡΠ°Π³Ρ
|
| 65 |
+
result["current_step"] = current_step + 1
|
| 66 |
+
result["reasoning_done"] = False # Π‘Π±ΡΠΎΡ Π΄Π»Ρ ΡΠ»Π΅Π΄ΡΡΡΠ΅Π³ΠΎ ΡΠ°Π³Π°
|
| 67 |
+
|
| 68 |
+
return result
|
| 69 |
+
|
| 70 |
+
except Exception as exc:
|
| 71 |
+
log_stage("TOOL ERROR", subtitle=f"{type(exc).__name__}: {exc}", icon="β")
|
| 72 |
+
print(f"Full error: {repr(exc)}")
|
| 73 |
+
|
| 74 |
+
# Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ ToolMessage Π΄Π»Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ failed tool call
|
| 75 |
+
error_messages = []
|
| 76 |
+
for call in tool_calls:
|
| 77 |
+
error_msg = ToolMessage(
|
| 78 |
+
content=f"ERROR: {type(exc).__name__}: {exc}",
|
| 79 |
+
tool_call_id=call.get("id") or "unknown_call",
|
| 80 |
+
name=call.get("name", "unknown_tool"),
|
| 81 |
+
)
|
| 82 |
+
error_messages.append(error_msg)
|
| 83 |
+
|
| 84 |
+
return {"messages": messages + error_messages}
|
| 85 |
|
| 86 |
|
| 87 |
TOOL_NODE = ToolNode(TOOLS)
|
src/nodes.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import os
|
| 2 |
-
from typing import Optional
|
| 3 |
-
|
| 4 |
from state import AgentState
|
| 5 |
from tools.tools import preprocess_files
|
|
|
|
| 6 |
from langgraph.prebuilt import ToolNode
|
|
|
|
| 7 |
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, ToolMessage
|
| 8 |
|
| 9 |
from prompts.prompts import (
|
|
@@ -12,8 +12,10 @@ from prompts.prompts import (
|
|
| 12 |
COMPLEXITY_ASSESSOR_PROMPT,
|
| 13 |
CRITIC_PROMPT,
|
| 14 |
)
|
|
|
|
| 15 |
from config import llm, TOOLS, planner_llm, llm_with_tools
|
| 16 |
from schemas import PlannerPlan, ComplexityLevel, CritiqueFeedback, ExecutionReport, ToolExecution
|
|
|
|
| 17 |
from utils.utils import (
|
| 18 |
format_final_answer,
|
| 19 |
clean_message_history,
|
|
@@ -23,7 +25,6 @@ from utils.utils import (
|
|
| 23 |
format_plan_overview,
|
| 24 |
)
|
| 25 |
|
| 26 |
-
|
| 27 |
def _build_planner_prompt(state: AgentState, extra_context: Optional[str] = None) -> str:
|
| 28 |
tool_catalogue = ", ".join(sorted(tool.name for tool in TOOLS))
|
| 29 |
file_paths = state.get("files", [])
|
|
@@ -35,15 +36,18 @@ def _build_planner_prompt(state: AgentState, extra_context: Optional[str] = None
|
|
| 35 |
extra_context=extra,
|
| 36 |
).strip()
|
| 37 |
|
| 38 |
-
def query_input(state: AgentState) -> AgentState:
|
| 39 |
log_stage("USER QUERY", icon="π‘")
|
|
|
|
| 40 |
|
| 41 |
files = state.get("files", [])
|
| 42 |
if files:
|
|
|
|
| 43 |
log_stage("FILE PREPARATION", subtitle=f"Processing {len(files)} file(s)", icon="π")
|
| 44 |
file_info = preprocess_files(files)
|
| 45 |
-
|
| 46 |
for file_path, info in file_info.items():
|
|
|
|
| 47 |
log_key_values(
|
| 48 |
[
|
| 49 |
("path", file_path),
|
|
@@ -52,39 +56,42 @@ def query_input(state: AgentState) -> AgentState:
|
|
| 52 |
("suggested_tool", info["suggested_tool"]),
|
| 53 |
]
|
| 54 |
)
|
| 55 |
-
|
| 56 |
state["file_contents"] = file_info
|
| 57 |
file_context = "\n\n=== AVAILABLE FILES FOR ANALYSIS ===\n"
|
| 58 |
for file_path, info in file_info.items():
|
| 59 |
filename = os.path.basename(file_path)
|
| 60 |
file_context += f"File: {filename}\n"
|
| 61 |
-
file_context += f" - Type: {info['type']}\n"
|
| 62 |
file_context += f" - Size: {info['size']} bytes\n"
|
| 63 |
file_context += f" - Suggested tool: {info['suggested_tool']}\n"
|
| 64 |
if info.get("preview"):
|
| 65 |
file_context += f" - Preview: {info['preview']}\n"
|
| 66 |
file_context += "\n"
|
| 67 |
-
|
|
|
|
| 68 |
file_context += "IMPORTANT: Use the suggested tools to analyze these files before processing their data.\n"
|
| 69 |
file_context += "File paths are available in the agent state and can be passed directly to analysis tools.\n"
|
| 70 |
-
|
| 71 |
-
original_query = state.get("query", "")
|
| 72 |
-
state["query"] = original_query + file_context
|
| 73 |
else:
|
| 74 |
log_key_values([("files", "none provided")])
|
|
|
|
|
|
|
|
|
|
| 75 |
return state
|
| 76 |
|
| 77 |
|
| 78 |
-
def planner(state: AgentState) -> AgentState:
|
|
|
|
| 79 |
log_stage("PLANNING", icon="π§")
|
| 80 |
planner_prompt = _build_planner_prompt(state)
|
| 81 |
|
| 82 |
sys_stack = [
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
plan: PlannerPlan = planner_llm.invoke(sys_stack)
|
| 87 |
-
|
|
|
|
| 88 |
display_plan(plan)
|
| 89 |
return {
|
| 90 |
"messages": state["messages"] + sys_stack,
|
|
@@ -95,19 +102,40 @@ def planner(state: AgentState) -> AgentState:
|
|
| 95 |
|
| 96 |
|
| 97 |
def agent(state: AgentState) -> AgentState:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
current_step = state.get("current_step", 0)
|
| 99 |
reasoning_done = state.get("reasoning_done", False)
|
| 100 |
plan: Optional[PlannerPlan] = state.get("plan")
|
|
|
|
| 101 |
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
log_stage("PLAN VALIDATION", subtitle="Planner returned no actionable steps", icon="β οΈ")
|
| 104 |
warning = AIMessage(content="No valid plan available. <FINAL_ANSWER>")
|
| 105 |
return {
|
| 106 |
"messages": state["messages"] + [warning],
|
| 107 |
"reasoning_done": False,
|
| 108 |
}
|
| 109 |
-
|
| 110 |
steps = plan.steps
|
|
|
|
| 111 |
total_steps = len(steps)
|
| 112 |
|
| 113 |
if total_steps == 0:
|
|
@@ -127,6 +155,8 @@ def agent(state: AgentState) -> AgentState:
|
|
| 127 |
}
|
| 128 |
|
| 129 |
current_step_info = steps[current_step]
|
|
|
|
|
|
|
| 130 |
log_stage(
|
| 131 |
"EXECUTION",
|
| 132 |
subtitle=f"Step {current_step + 1}/{total_steps}: {current_step_info.goal}",
|
|
@@ -157,7 +187,9 @@ def agent(state: AgentState) -> AgentState:
|
|
| 157 |
).strip()
|
| 158 |
)
|
| 159 |
|
|
|
|
| 160 |
if not reasoning_done:
|
|
|
|
| 161 |
instruction = HumanMessage(
|
| 162 |
content=(
|
| 163 |
"Provide reasoning for this step inside <REASONING>...</REASONING>. "
|
|
@@ -169,65 +201,67 @@ def agent(state: AgentState) -> AgentState:
|
|
| 169 |
log_stage("REASONING", subtitle=f"{current_step_info.id}", icon="π§ ")
|
| 170 |
print(reasoning_response.content)
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
)
|
| 189 |
-
)
|
| 190 |
-
print(warning.content)
|
| 191 |
-
return {
|
| 192 |
-
"messages": state["messages"] + [warning],
|
| 193 |
-
"reasoning_done": False,
|
| 194 |
-
}
|
| 195 |
-
|
| 196 |
-
execution_instruction = HumanMessage(
|
| 197 |
-
content=(
|
| 198 |
-
"Execute the planned action now. If a tool is required, call it with the "
|
| 199 |
-
"correct arguments. After success, respond with STEP COMPLETE. If inputs are "
|
| 200 |
-
"missing, explain the issue in <REASONING> without new tool calls."
|
| 201 |
-
)
|
| 202 |
-
)
|
| 203 |
-
stack = [system_message] + state["messages"] + [execution_instruction]
|
| 204 |
-
execution_response = llm_with_tools.invoke(stack)
|
| 205 |
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
else:
|
| 211 |
-
log_stage("EXECUTION OUTPUT", subtitle=current_step_info.id, icon="π οΈ")
|
| 212 |
-
if execution_response.content:
|
| 213 |
-
print(execution_response.content)
|
| 214 |
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
advance = True
|
| 218 |
-
elif execution_response.content and (
|
| 219 |
-
"STEP COMPLETE" in execution_response.content or "<FINAL_ANSWER>" in execution_response.content
|
| 220 |
-
):
|
| 221 |
-
advance = True
|
| 222 |
|
| 223 |
-
|
|
|
|
| 224 |
|
| 225 |
-
|
| 226 |
-
"
|
| 227 |
-
|
| 228 |
-
"reasoning_done": False,
|
| 229 |
-
}
|
| 230 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
def should_continue(state : AgentState) -> bool:
|
| 232 |
|
| 233 |
last_message = state["messages"][-1]
|
|
@@ -235,67 +269,48 @@ def should_continue(state : AgentState) -> bool:
|
|
| 235 |
plan = state.get("plan", None)
|
| 236 |
current_step = state.get("current_step", 0)
|
| 237 |
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
|
|
|
| 242 |
if hasattr(last_message, "content") and "<FINAL_ANSWER>" in last_message.content:
|
| 243 |
return "final_answer"
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
# Reasoning Π²ΡΠΏΠΎΠ»Π½Π΅Π½, Π½ΠΎ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΡ Π΅ΡΠ΅ Π½Π΅ Π²ΡΠ·Π²Π°Π½Ρ
|
| 248 |
return "agent"
|
| 249 |
elif reasoning_done:
|
| 250 |
# Reasoning Π²ΡΠΏΠΎΠ»Π½Π΅Π½, ΡΠ΅ΠΏΠ΅ΡΡ Π½ΡΠΆΠ½ΠΎ Π²ΡΠ·Π²Π°ΡΡ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΡ
|
| 251 |
return "agent"
|
| 252 |
-
|
| 253 |
# ΠΡΠΆΠ½ΠΎ ΡΠ΄Π΅Π»Π°ΡΡ reasoning
|
| 254 |
return "agent"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
# 6. ΠΠΎΠ±Π°Π²ΠΈΡΡ ΠΎΡΠ»Π°Π΄ΠΎΡΠ½ΡΡ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ Π² TOOL_NODE
|
| 257 |
class DebuggingToolNode(ToolNode):
|
| 258 |
def __init__(self, tools):
|
| 259 |
super().__init__(tools)
|
| 260 |
-
|
| 261 |
def __call__(self, state):
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
return result
|
| 267 |
-
except Exception as exc:
|
| 268 |
-
log_stage("TOOL ERROR", subtitle=f"{type(exc).__name__}: {exc}", icon="β")
|
| 269 |
-
messages = state.get("messages", [])
|
| 270 |
-
last_message = messages[-1] if messages else None
|
| 271 |
-
tool_calls = getattr(last_message, "tool_calls", []) if last_message else []
|
| 272 |
-
|
| 273 |
-
error_messages = []
|
| 274 |
-
for call in tool_calls:
|
| 275 |
-
error_messages.append(
|
| 276 |
-
ToolMessage(
|
| 277 |
-
content=f"ERROR: {type(exc).__name__}: {exc}",
|
| 278 |
-
tool_call_id=call.get("id") or "unknown_call",
|
| 279 |
-
name=call.get("name"),
|
| 280 |
-
)
|
| 281 |
-
)
|
| 282 |
-
|
| 283 |
-
if not error_messages:
|
| 284 |
-
error_messages.append(
|
| 285 |
-
ToolMessage(
|
| 286 |
-
content=f"ERROR: {type(exc).__name__}: {exc}",
|
| 287 |
-
tool_call_id="unknown_call",
|
| 288 |
-
)
|
| 289 |
-
)
|
| 290 |
-
|
| 291 |
-
return {"messages": messages + error_messages}
|
| 292 |
|
| 293 |
|
| 294 |
-
|
| 295 |
def enhanced_finalizer(state: AgentState) -> AgentState:
|
| 296 |
"""Generate comprehensive execution report for critic evaluation."""
|
| 297 |
-
|
| 298 |
-
|
| 299 |
# Extract tool execution information
|
| 300 |
tools_executed = []
|
| 301 |
data_sources = []
|
|
@@ -320,22 +335,20 @@ def enhanced_finalizer(state: AgentState) -> AgentState:
|
|
| 320 |
plan = state.get("plan")
|
| 321 |
approach_used = "Direct execution"
|
| 322 |
assumptions_made = []
|
| 323 |
-
|
| 324 |
-
|
| 325 |
if plan:
|
| 326 |
-
approach_used = f"{plan.task_type}
|
| 327 |
assumptions_made = plan.assumptions
|
| 328 |
-
plan_overview = format_plan_overview(plan)
|
| 329 |
|
| 330 |
# Generate structured report (ΠΠΠ‘Π’Π«ΠΠ¬ ΠΠΠΠ‘Π¬!)
|
| 331 |
report_generator_prompt = f"""
|
| 332 |
Generate a comprehensive execution report for the following query processing:
|
| 333 |
|
| 334 |
ORIGINAL QUERY: {state['query']}
|
| 335 |
-
|
| 336 |
EXECUTION CONTEXT:
|
| 337 |
- Complexity Level: {state.get('complexity_assessment', {}).level}
|
| 338 |
-
- Plan Used: {
|
| 339 |
- Tools Executed: {tools_executed}
|
| 340 |
- Available Files: {list(state.get('file_contents', {}).keys())}
|
| 341 |
|
|
@@ -362,18 +375,13 @@ def enhanced_finalizer(state: AgentState) -> AgentState:
|
|
| 362 |
HumanMessage(content="Generate the execution report.")
|
| 363 |
])
|
| 364 |
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
("sources", str(len(execution_report.data_sources))),
|
| 370 |
-
]
|
| 371 |
-
)
|
| 372 |
-
|
| 373 |
# Format final answer for user
|
| 374 |
formatted_answer = format_final_answer(execution_report, state.get('complexity_assessment', {}))
|
| 375 |
-
|
| 376 |
-
print(formatted_answer)
|
| 377 |
return {
|
| 378 |
"execution_report": execution_report,
|
| 379 |
"final_answer": formatted_answer
|
|
@@ -382,25 +390,23 @@ def enhanced_finalizer(state: AgentState) -> AgentState:
|
|
| 382 |
|
| 383 |
def simple_executor(state: AgentState) -> AgentState:
|
| 384 |
"""Handle simple queries directly without planning."""
|
| 385 |
-
|
| 386 |
-
|
| 387 |
# For simple queries, use the LLM with tools directly
|
| 388 |
simple_prompt = f"""
|
| 389 |
Answer this simple query directly and efficiently: {state['query']}
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
|
|
|
| 394 |
"""
|
| 395 |
-
|
| 396 |
response = llm_with_tools.invoke([
|
| 397 |
SystemMessage(content=simple_prompt),
|
| 398 |
HumanMessage(content=state['query'])
|
| 399 |
])
|
| 400 |
|
| 401 |
-
log_stage("SIMPLE EXECUTION OUTPUT", icon="π¬")
|
| 402 |
-
print(response.content)
|
| 403 |
-
|
| 404 |
return {
|
| 405 |
"messages": state["messages"] + [response],
|
| 406 |
"final_answer": response.content
|
|
@@ -419,8 +425,8 @@ def should_use_planning(state: AgentState) -> str:
|
|
| 419 |
|
| 420 |
def critic_evaluator(state: AgentState) -> AgentState:
|
| 421 |
"""Enhanced critic that evaluates execution reports."""
|
| 422 |
-
|
| 423 |
-
|
| 424 |
report = state.get("execution_report")
|
| 425 |
critic_llm = llm.with_structured_output(CritiqueFeedback)
|
| 426 |
|
|
@@ -440,22 +446,15 @@ def critic_evaluator(state: AgentState) -> AgentState:
|
|
| 440 |
HumanMessage(content="Evaluate this execution report thoroughly.")
|
| 441 |
])
|
| 442 |
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
("accurate", str(critique.is_accurate)),
|
| 448 |
-
]
|
| 449 |
-
)
|
| 450 |
-
|
| 451 |
if critique.errors_found:
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
print(f" - {issue}")
|
| 455 |
-
|
| 456 |
if critique.needs_replanning:
|
| 457 |
-
|
| 458 |
-
print(critique.replan_instructions)
|
| 459 |
|
| 460 |
return {
|
| 461 |
"critique_feedback": critique,
|
|
@@ -469,63 +468,64 @@ def should_replan(state: AgentState) -> str:
|
|
| 469 |
critique = state.get("critique_feedback")
|
| 470 |
iteration_count = state.get("iteration_count", 0)
|
| 471 |
max_iterations = state.get("max_iterations", 3)
|
|
|
|
| 472 |
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
if critique
|
| 476 |
-
|
| 477 |
-
[
|
| 478 |
-
("quality", str(critique.quality_score)),
|
| 479 |
-
("needs_replanning", str(critique.needs_replanning)),
|
| 480 |
-
]
|
| 481 |
-
)
|
| 482 |
|
| 483 |
if not critique:
|
| 484 |
return "end"
|
| 485 |
-
|
| 486 |
# Stop if max iterations reached
|
| 487 |
if iteration_count >= max_iterations:
|
| 488 |
-
|
| 489 |
return "end"
|
| 490 |
-
|
| 491 |
# Accept if quality is good enough
|
| 492 |
if critique.quality_score >= 7 or not critique.needs_replanning:
|
| 493 |
-
|
| 494 |
return "end"
|
| 495 |
-
|
| 496 |
# Replan if quality is poor and we haven't exceeded max iterations
|
| 497 |
if critique.needs_replanning and iteration_count < max_iterations:
|
| 498 |
-
|
| 499 |
return "replan"
|
| 500 |
-
|
| 501 |
return "end"
|
| 502 |
|
| 503 |
def replanner(state: AgentState) -> AgentState:
|
| 504 |
"""Create a revised plan based on critic feedback."""
|
| 505 |
-
|
| 506 |
-
|
| 507 |
critique = state["critique_feedback"]
|
| 508 |
previous_plan = state.get("plan")
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 522 |
revised_plan = planner_llm.invoke([
|
| 523 |
SystemMessage(content=replan_prompt),
|
| 524 |
HumanMessage(content="Create a revised plan based on the feedback.")
|
| 525 |
])
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
# ΠΡΠΈΡΠ°Π΅ΠΌ ΠΈΡΡΠΎΡΠΈΡ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠΉ ΠΎΡ Π½Π΅ΠΏΠΎΠ»Π½ΡΡ
tool_calls
|
| 530 |
current_messages = state.get("messages", [])
|
| 531 |
cleaned_messages = clean_message_history(current_messages)
|
|
@@ -540,12 +540,8 @@ def replanner(state: AgentState) -> AgentState:
|
|
| 540 |
isinstance(msg, HumanMessage)):
|
| 541 |
essential_messages.append(msg)
|
| 542 |
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
subtitle=f"Cleaned history: {len(current_messages)} β {len(essential_messages)}",
|
| 546 |
-
icon="π§Ή",
|
| 547 |
-
)
|
| 548 |
-
|
| 549 |
return {
|
| 550 |
"plan": revised_plan,
|
| 551 |
"current_step": 0,
|
|
@@ -557,24 +553,21 @@ def replanner(state: AgentState) -> AgentState:
|
|
| 557 |
|
| 558 |
def complexity_assessor(state: AgentState) -> AgentState:
|
| 559 |
"""Assess query complexity and determine if planning is needed."""
|
| 560 |
-
|
| 561 |
-
|
| 562 |
complexity_llm = llm.with_structured_output(ComplexityLevel)
|
| 563 |
-
|
| 564 |
assessment_message = [
|
| 565 |
SystemMessage(content=COMPLEXITY_ASSESSOR_PROMPT.strip()),
|
| 566 |
HumanMessage(content=f"Query: {state['query']}")
|
| 567 |
]
|
| 568 |
-
|
| 569 |
assessment = complexity_llm.invoke(assessment_message)
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
]
|
| 576 |
-
)
|
| 577 |
-
|
| 578 |
return {
|
| 579 |
"complexity_assessment": assessment,
|
| 580 |
"messages": state["messages"] + assessment_message
|
|
|
|
| 1 |
import os
|
|
|
|
|
|
|
| 2 |
from state import AgentState
|
| 3 |
from tools.tools import preprocess_files
|
| 4 |
+
from typing import Optional
|
| 5 |
from langgraph.prebuilt import ToolNode
|
| 6 |
+
|
| 7 |
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, ToolMessage
|
| 8 |
|
| 9 |
from prompts.prompts import (
|
|
|
|
| 12 |
COMPLEXITY_ASSESSOR_PROMPT,
|
| 13 |
CRITIC_PROMPT,
|
| 14 |
)
|
| 15 |
+
|
| 16 |
from config import llm, TOOLS, planner_llm, llm_with_tools
|
| 17 |
from schemas import PlannerPlan, ComplexityLevel, CritiqueFeedback, ExecutionReport, ToolExecution
|
| 18 |
+
|
| 19 |
from utils.utils import (
|
| 20 |
format_final_answer,
|
| 21 |
clean_message_history,
|
|
|
|
| 25 |
format_plan_overview,
|
| 26 |
)
|
| 27 |
|
|
|
|
| 28 |
def _build_planner_prompt(state: AgentState, extra_context: Optional[str] = None) -> str:
|
| 29 |
tool_catalogue = ", ".join(sorted(tool.name for tool in TOOLS))
|
| 30 |
file_paths = state.get("files", [])
|
|
|
|
| 36 |
extra_context=extra,
|
| 37 |
).strip()
|
| 38 |
|
| 39 |
+
def query_input(state : AgentState) -> AgentState:
|
| 40 |
log_stage("USER QUERY", icon="π‘")
|
| 41 |
+
#print("=== USER QUERY TRANSFERED TO AGENT ===")
|
| 42 |
|
| 43 |
files = state.get("files", [])
|
| 44 |
if files:
|
| 45 |
+
print(f"Processing {len(files)} files:")
|
| 46 |
log_stage("FILE PREPARATION", subtitle=f"Processing {len(files)} file(s)", icon="π")
|
| 47 |
file_info = preprocess_files(files)
|
| 48 |
+
|
| 49 |
for file_path, info in file_info.items():
|
| 50 |
+
print(f" - {file_path}: {info['type']} ({info['size']} bytes) -> {info['suggested_tool']}")
|
| 51 |
log_key_values(
|
| 52 |
[
|
| 53 |
("path", file_path),
|
|
|
|
| 56 |
("suggested_tool", info["suggested_tool"]),
|
| 57 |
]
|
| 58 |
)
|
|
|
|
| 59 |
state["file_contents"] = file_info
|
| 60 |
file_context = "\n\n=== AVAILABLE FILES FOR ANALYSIS ===\n"
|
| 61 |
for file_path, info in file_info.items():
|
| 62 |
filename = os.path.basename(file_path)
|
| 63 |
file_context += f"File: {filename}\n"
|
| 64 |
+
file_context += f" - Type: {info['type']}\n"
|
| 65 |
file_context += f" - Size: {info['size']} bytes\n"
|
| 66 |
file_context += f" - Suggested tool: {info['suggested_tool']}\n"
|
| 67 |
if info.get("preview"):
|
| 68 |
file_context += f" - Preview: {info['preview']}\n"
|
| 69 |
file_context += "\n"
|
| 70 |
+
|
| 71 |
+
# ΠΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ ΠΈΠ½ΡΡΡΡΠΊΡΠΈΠΈ ΠΏΠΎ ΡΠ°Π±ΠΎΡΠ΅ Ρ ΡΠ°ΠΉΠ»Π°ΠΌΠΈ
|
| 72 |
file_context += "IMPORTANT: Use the suggested tools to analyze these files before processing their data.\n"
|
| 73 |
file_context += "File paths are available in the agent state and can be passed directly to analysis tools.\n"
|
| 74 |
+
|
|
|
|
|
|
|
| 75 |
else:
|
| 76 |
log_key_values([("files", "none provided")])
|
| 77 |
+
file_context = ""
|
| 78 |
+
original_query = state.get("query", "")
|
| 79 |
+
state["query"] = original_query + file_context
|
| 80 |
return state
|
| 81 |
|
| 82 |
|
| 83 |
+
def planner(state : AgentState) -> AgentState:
|
| 84 |
+
|
| 85 |
log_stage("PLANNING", icon="π§")
|
| 86 |
planner_prompt = _build_planner_prompt(state)
|
| 87 |
|
| 88 |
sys_stack = [
|
| 89 |
+
SystemMessage(content=planner_prompt),
|
| 90 |
+
HumanMessage(content=state["query"]),
|
| 91 |
+
]
|
| 92 |
plan: PlannerPlan = planner_llm.invoke(sys_stack)
|
| 93 |
+
|
| 94 |
+
#print("=== GENERATED PLAN ===")
|
| 95 |
display_plan(plan)
|
| 96 |
return {
|
| 97 |
"messages": state["messages"] + sys_stack,
|
|
|
|
| 102 |
|
| 103 |
|
| 104 |
def agent(state: AgentState) -> AgentState:
|
| 105 |
+
|
| 106 |
+
"""
|
| 107 |
+
sys_msg = SystemMessage(
|
| 108 |
+
content=SYSTEM_EXECUTOR_PROMPT.strip().format(
|
| 109 |
+
plan=json.dumps(state["plan"], indent=2)
|
| 110 |
+
)
|
| 111 |
+
)
|
| 112 |
+
"""
|
| 113 |
current_step = state.get("current_step", 0)
|
| 114 |
reasoning_done = state.get("reasoning_done", False)
|
| 115 |
plan: Optional[PlannerPlan] = state.get("plan")
|
| 116 |
+
#steps = state["plan"].steps
|
| 117 |
|
| 118 |
+
"""
|
| 119 |
+
print(f"=== AGENT DEBUG ===")
|
| 120 |
+
print(f"Current step: {current_step}")
|
| 121 |
+
print(f"Reasoning done: {reasoning_done}")
|
| 122 |
+
print(f"Plan exists: {plan is not None}")
|
| 123 |
+
print(f"Total steps in plan: {len(plan.steps) if plan else 'No plan'}")
|
| 124 |
+
|
| 125 |
+
if not plan or not hasattr(plan, 'steps') or not plan.steps:
|
| 126 |
+
print("ERROR: No valid plan found!")
|
| 127 |
+
"""
|
| 128 |
+
|
| 129 |
+
if not plan or not hasattr(plan, 'steps'):
|
| 130 |
log_stage("PLAN VALIDATION", subtitle="Planner returned no actionable steps", icon="β οΈ")
|
| 131 |
warning = AIMessage(content="No valid plan available. <FINAL_ANSWER>")
|
| 132 |
return {
|
| 133 |
"messages": state["messages"] + [warning],
|
| 134 |
"reasoning_done": False,
|
| 135 |
}
|
| 136 |
+
|
| 137 |
steps = plan.steps
|
| 138 |
+
|
| 139 |
total_steps = len(steps)
|
| 140 |
|
| 141 |
if total_steps == 0:
|
|
|
|
| 155 |
}
|
| 156 |
|
| 157 |
current_step_info = steps[current_step]
|
| 158 |
+
#print(f"Executing step {current_step + 1}: {current_step_info.description}")
|
| 159 |
+
|
| 160 |
log_stage(
|
| 161 |
"EXECUTION",
|
| 162 |
subtitle=f"Step {current_step + 1}/{total_steps}: {current_step_info.goal}",
|
|
|
|
| 187 |
).strip()
|
| 188 |
)
|
| 189 |
|
| 190 |
+
|
| 191 |
if not reasoning_done:
|
| 192 |
+
|
| 193 |
instruction = HumanMessage(
|
| 194 |
content=(
|
| 195 |
"Provide reasoning for this step inside <REASONING>...</REASONING>. "
|
|
|
|
| 201 |
log_stage("REASONING", subtitle=f"{current_step_info.id}", icon="π§ ")
|
| 202 |
print(reasoning_response.content)
|
| 203 |
|
| 204 |
+
# β
ΠΠΠΠΠΠΠΠΠ: Π‘ΠΏΠ΅ΡΠΈΠ°Π»ΡΠ½ΡΠΉ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ Π΄Π»Ρ ΡΠ°ΠΉΠ»ΠΎΠ²
|
| 205 |
+
file_context = ""
|
| 206 |
+
file_contents = state.get("file_contents", {})
|
| 207 |
+
if file_contents:
|
| 208 |
+
file_context = "\n\nAVAILABLE FILES IN CURRENT SESSION:\n"
|
| 209 |
+
for filepath, info in file_contents.items():
|
| 210 |
+
filename = os.path.basename(filepath)
|
| 211 |
+
file_context += f"- {filename}: {info['type']} file, suggested tool: {info['suggested_tool']}\n"
|
| 212 |
+
file_context += f" Path: {filepath}\n"
|
| 213 |
+
|
| 214 |
+
reasoning_prompt = f"""
|
| 215 |
+
{SYSTEM_EXECUTOR_PROMPT}
|
| 216 |
+
|
| 217 |
+
CURRENT TASK: You must perform reasoning for step {current_step + 1}.
|
| 218 |
+
|
| 219 |
+
STEP INFO: {current_step_info}\n\n
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
|
| 221 |
+
FILE CONTEXT: {file_contents}
|
| 222 |
+
|
| 223 |
+
CRITICAL: You MUST output your reasoning in <REASONING> tags, but DO NOT call any tools yet.
|
| 224 |
+
Explain what you need to do and why, then end your response.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
|
| 226 |
+
REASONING IS IMPERATIVE BEFORE ANY TOOL CALLS.
|
| 227 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
| 229 |
+
sys_msg = SystemMessage(content = reasoning_prompt)
|
| 230 |
+
stack = [sys_msg] + state["messages"]
|
| 231 |
|
| 232 |
+
step = llm.invoke(stack)
|
| 233 |
+
print("=== REASONING STEP ===")
|
| 234 |
+
print(step.content)
|
|
|
|
|
|
|
| 235 |
|
| 236 |
+
return {
|
| 237 |
+
"messages" : state["messages"] + [step],
|
| 238 |
+
"reasoning_done" : True
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
else:
|
| 242 |
+
tool_prompt = f"""
|
| 243 |
+
Now execute the tool for step {current_step + 1}.
|
| 244 |
+
|
| 245 |
+
You have already done the reasoning. Now call the appropriate tool with the correct parameters.
|
| 246 |
+
Available file paths: {list(state.get("file_contents", {}).keys())}\n
|
| 247 |
+
IMPORTANT NOTE: IF YOU DECIDED TO USE safe_code_run, MAKE SURE TO FINISH CALCULATIONS WITH print() or saving to a variable NAMED 'result' so that the output can be captured!
|
| 248 |
+
AVAILABLE TOOLS: {', '.join([tool.name for tool in TOOLS])}
|
| 249 |
+
"""
|
| 250 |
+
|
| 251 |
+
sys_msg = SystemMessage(content=tool_prompt)
|
| 252 |
+
stack = [sys_msg] + state["messages"] # ΠΠ΅ΡΠ΅ΠΌ ΠΏΠΎΡΠ»Π΅Π΄Π½ΠΈΠ΅ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ Π²ΠΊΠ»ΡΡΠ°Ρ reasoning
|
| 253 |
+
|
| 254 |
+
# ΠΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ ΠΌΠΎΠ΄Π΅Π»Ρ Π‘ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΠ°ΠΌΠΈ Π΄Π»Ρ Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΡ
|
| 255 |
+
step = llm_with_tools.invoke(stack)
|
| 256 |
+
print("=== TOOL EXECUTION ===")
|
| 257 |
+
print(f"Tool calls: {step.tool_calls}")
|
| 258 |
+
|
| 259 |
+
return {
|
| 260 |
+
"messages": state["messages"] + [step],
|
| 261 |
+
"current_step": current_step + 1 if step.tool_calls else current_step,
|
| 262 |
+
"reasoning_done": False # Π‘Π±ΡΠ°ΡΡΠ²Π°Π΅ΠΌ Π΄Π»Ρ ΡΠ»Π΅Π΄ΡΡΡΠ΅Π³ΠΎ ΡΠ°Π³Π°
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
def should_continue(state : AgentState) -> bool:
|
| 266 |
|
| 267 |
last_message = state["messages"][-1]
|
|
|
|
| 269 |
plan = state.get("plan", None)
|
| 270 |
current_step = state.get("current_step", 0)
|
| 271 |
|
| 272 |
+
#ΠΠ ΠΠΠ ΠΠ’ΠΠ’ 1: ΠΡΠ»ΠΈ Π΅ΡΡΡ tool_calls - Π²ΡΠΏΠΎΠ»Π½ΡΠ΅ΠΌ ΠΈΡ
|
| 273 |
+
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
|
| 274 |
+
return "tools"
|
| 275 |
+
|
| 276 |
+
# ΠΠ ΠΠΠ ΠΠ’ΠΠ’ 2: Π―Π²Π½ΡΠΉ ΡΠΈΠ³Π½Π°Π» Π·Π°Π²Π΅ΡΡΠ΅Π½ΠΈΡ
|
| 277 |
if hasattr(last_message, "content") and "<FINAL_ANSWER>" in last_message.content:
|
| 278 |
return "final_answer"
|
| 279 |
+
|
| 280 |
+
# ΠΠ ΠΠΠ ΠΠ’ΠΠ’ 3: ΠΠΎΠ³ΠΈΠΊΠ° reasoning/execution
|
| 281 |
+
if not reasoning_done and hasattr(last_message, 'content') and "<REASONING>" in last_message.content:
|
| 282 |
# Reasoning Π²ΡΠΏΠΎΠ»Π½Π΅Π½, Π½ΠΎ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΡ Π΅ΡΠ΅ Π½Π΅ Π²ΡΠ·Π²Π°Π½Ρ
|
| 283 |
return "agent"
|
| 284 |
elif reasoning_done:
|
| 285 |
# Reasoning Π²ΡΠΏΠΎΠ»Π½Π΅Π½, ΡΠ΅ΠΏΠ΅ΡΡ Π½ΡΠΆΠ½ΠΎ Π²ΡΠ·Π²Π°ΡΡ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΡ
|
| 286 |
return "agent"
|
| 287 |
+
elif not reasoning_done:
|
| 288 |
# ΠΡΠΆΠ½ΠΎ ΡΠ΄Π΅Π»Π°ΡΡ reasoning
|
| 289 |
return "agent"
|
| 290 |
+
|
| 291 |
+
# ΠΠ ΠΠΠ ΠΠ’ΠΠ’ 4: ΠΡΠΎΠ²Π΅ΡΡΠ΅ΠΌ Π·Π°Π²Π΅ΡΡΠ΅Π½ΠΈΠ΅ ΠΏΠ»Π°Π½Π° (ΡΠΎΠ»ΡΠΊΠΎ Π΅ΡΠ»ΠΈ Π½Π΅Ρ Π°ΠΊΡΠΈΠ²Π½ΡΡ
tool_calls)
|
| 292 |
+
if plan and current_step >= len(plan.steps):
|
| 293 |
+
return "final_answer"
|
| 294 |
+
|
| 295 |
+
# ΠΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ ΠΏΡΠΎΠ΄ΠΎΠ»ΠΆΠ°Π΅ΠΌ Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅
|
| 296 |
+
return "agent"
|
| 297 |
|
| 298 |
# 6. ΠΠΎΠ±Π°Π²ΠΈΡΡ ΠΎΡΠ»Π°Π΄ΠΎΡΠ½ΡΡ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ Π² TOOL_NODE
|
| 299 |
class DebuggingToolNode(ToolNode):
|
| 300 |
def __init__(self, tools):
|
| 301 |
super().__init__(tools)
|
| 302 |
+
|
| 303 |
def __call__(self, state):
|
| 304 |
+
print("=== TOOL EXECUTION STARTED ===")
|
| 305 |
+
result = super().__call__(state)
|
| 306 |
+
print("=== TOOL EXECUTION COMPLETED ===")
|
| 307 |
+
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
|
| 309 |
|
|
|
|
| 310 |
def enhanced_finalizer(state: AgentState) -> AgentState:
|
| 311 |
"""Generate comprehensive execution report for critic evaluation."""
|
| 312 |
+
print("=== GENERATING EXECUTION REPORT ===")
|
| 313 |
+
|
| 314 |
# Extract tool execution information
|
| 315 |
tools_executed = []
|
| 316 |
data_sources = []
|
|
|
|
| 335 |
plan = state.get("plan")
|
| 336 |
approach_used = "Direct execution"
|
| 337 |
assumptions_made = []
|
| 338 |
+
|
|
|
|
| 339 |
if plan:
|
| 340 |
+
approach_used = f"{plan.task_type} approach with {len(plan.steps)} steps"
|
| 341 |
assumptions_made = plan.assumptions
|
|
|
|
| 342 |
|
| 343 |
# Generate structured report (ΠΠΠ‘Π’Π«ΠΠ¬ ΠΠΠΠ‘Π¬!)
|
| 344 |
report_generator_prompt = f"""
|
| 345 |
Generate a comprehensive execution report for the following query processing:
|
| 346 |
|
| 347 |
ORIGINAL QUERY: {state['query']}
|
| 348 |
+
|
| 349 |
EXECUTION CONTEXT:
|
| 350 |
- Complexity Level: {state.get('complexity_assessment', {}).level}
|
| 351 |
+
- Plan Used: {plan if plan else {}}
|
| 352 |
- Tools Executed: {tools_executed}
|
| 353 |
- Available Files: {list(state.get('file_contents', {}).keys())}
|
| 354 |
|
|
|
|
| 375 |
HumanMessage(content="Generate the execution report.")
|
| 376 |
])
|
| 377 |
|
| 378 |
+
print(f"Report generated - Confidence: {execution_report.confidence_level}")
|
| 379 |
+
print(f"Key findings: {len(execution_report.key_findings)}")
|
| 380 |
+
print(f"Data sources: {len(execution_report.data_sources)}")
|
| 381 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
# Format final answer for user
|
| 383 |
formatted_answer = format_final_answer(execution_report, state.get('complexity_assessment', {}))
|
| 384 |
+
print(execution_report)
|
|
|
|
| 385 |
return {
|
| 386 |
"execution_report": execution_report,
|
| 387 |
"final_answer": formatted_answer
|
|
|
|
| 390 |
|
| 391 |
def simple_executor(state: AgentState) -> AgentState:
|
| 392 |
"""Handle simple queries directly without planning."""
|
| 393 |
+
print("=== SIMPLE EXECUTION ===")
|
| 394 |
+
|
| 395 |
# For simple queries, use the LLM with tools directly
|
| 396 |
simple_prompt = f"""
|
| 397 |
Answer this simple query directly and efficiently: {state['query']}
|
| 398 |
+
|
| 399 |
+
You have access to tools if needed, but try to answer directly when possible.
|
| 400 |
+
If you need files, they are available at: {list(state.get('file_contents', {}).keys())}
|
| 401 |
+
|
| 402 |
+
Provide a clear, concise answer.
|
| 403 |
"""
|
| 404 |
+
|
| 405 |
response = llm_with_tools.invoke([
|
| 406 |
SystemMessage(content=simple_prompt),
|
| 407 |
HumanMessage(content=state['query'])
|
| 408 |
])
|
| 409 |
|
|
|
|
|
|
|
|
|
|
| 410 |
return {
|
| 411 |
"messages": state["messages"] + [response],
|
| 412 |
"final_answer": response.content
|
|
|
|
| 425 |
|
| 426 |
def critic_evaluator(state: AgentState) -> AgentState:
|
| 427 |
"""Enhanced critic that evaluates execution reports."""
|
| 428 |
+
print("=== ENHANCED ANSWER CRITIQUE ===")
|
| 429 |
+
|
| 430 |
report = state.get("execution_report")
|
| 431 |
critic_llm = llm.with_structured_output(CritiqueFeedback)
|
| 432 |
|
|
|
|
| 446 |
HumanMessage(content="Evaluate this execution report thoroughly.")
|
| 447 |
])
|
| 448 |
|
| 449 |
+
print(f"Quality Score: {critique.quality_score}/10")
|
| 450 |
+
print(f"Complete: {critique.is_complete}")
|
| 451 |
+
print(f"Accurate: {critique.is_accurate}")
|
| 452 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 453 |
if critique.errors_found:
|
| 454 |
+
print(f"Issues found: {critique.errors_found}")
|
| 455 |
+
|
|
|
|
|
|
|
| 456 |
if critique.needs_replanning:
|
| 457 |
+
print(f"Replanning needed: {critique.replan_instructions}")
|
|
|
|
| 458 |
|
| 459 |
return {
|
| 460 |
"critique_feedback": critique,
|
|
|
|
| 468 |
critique = state.get("critique_feedback")
|
| 469 |
iteration_count = state.get("iteration_count", 0)
|
| 470 |
max_iterations = state.get("max_iterations", 3)
|
| 471 |
+
|
| 472 |
|
| 473 |
+
print(f"=== REPLAN DECISION ===")
|
| 474 |
+
print(f"Iteration: {iteration_count}/{max_iterations}")
|
| 475 |
+
print(f"Quality score: {critique.quality_score if critique else 'N/A'}")
|
| 476 |
+
print(f"Needs replanning: {critique.needs_replanning if critique else 'N/A'}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 477 |
|
| 478 |
if not critique:
|
| 479 |
return "end"
|
| 480 |
+
|
| 481 |
# Stop if max iterations reached
|
| 482 |
if iteration_count >= max_iterations:
|
| 483 |
+
print(f"Max iterations ({max_iterations}) reached. Accepting current answer.")
|
| 484 |
return "end"
|
| 485 |
+
|
| 486 |
# Accept if quality is good enough
|
| 487 |
if critique.quality_score >= 7 or not critique.needs_replanning:
|
| 488 |
+
print("Quality acceptable, ending execution")
|
| 489 |
return "end"
|
| 490 |
+
|
| 491 |
# Replan if quality is poor and we haven't exceeded max iterations
|
| 492 |
if critique.needs_replanning and iteration_count < max_iterations:
|
| 493 |
+
print("Replanning due to critic feedback...")
|
| 494 |
return "replan"
|
| 495 |
+
|
| 496 |
return "end"
|
| 497 |
|
| 498 |
def replanner(state: AgentState) -> AgentState:
|
| 499 |
"""Create a revised plan based on critic feedback."""
|
| 500 |
+
print("=== REPLANNING ===")
|
| 501 |
+
|
| 502 |
critique = state["critique_feedback"]
|
| 503 |
previous_plan = state.get("plan")
|
| 504 |
+
|
| 505 |
+
replan_prompt = f"""
|
| 506 |
+
{SYSTEM_PROMPT_PLANNER}
|
| 507 |
+
|
| 508 |
+
REPLANNING CONTEXT:
|
| 509 |
+
Original Query: {state['query']}
|
| 510 |
+
Previous Plan: {previous_plan if previous_plan else {}}
|
| 511 |
+
|
| 512 |
+
CRITIC FEEDBACK:
|
| 513 |
+
- Quality Score: {critique.quality_score}/10
|
| 514 |
+
- Issues Found: {critique.errors_found}
|
| 515 |
+
- Missing Elements: {critique.missing_elements}
|
| 516 |
+
- Improvement Suggestions: {critique.suggested_improvements}
|
| 517 |
+
- Specific Instructions: {critique.replan_instructions}
|
| 518 |
+
|
| 519 |
+
Create a REVISED plan that addresses these issues. Focus on fixing the identified problems.
|
| 520 |
+
"""
|
| 521 |
+
|
| 522 |
revised_plan = planner_llm.invoke([
|
| 523 |
SystemMessage(content=replan_prompt),
|
| 524 |
HumanMessage(content="Create a revised plan based on the feedback.")
|
| 525 |
])
|
| 526 |
+
|
| 527 |
+
print("Plan revised based on critic feedback")
|
| 528 |
+
|
| 529 |
# ΠΡΠΈΡΠ°Π΅ΠΌ ΠΈΡΡΠΎΡΠΈΡ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠΉ ΠΎΡ Π½Π΅ΠΏΠΎΠ»Π½ΡΡ
tool_calls
|
| 530 |
current_messages = state.get("messages", [])
|
| 531 |
cleaned_messages = clean_message_history(current_messages)
|
|
|
|
| 540 |
isinstance(msg, HumanMessage)):
|
| 541 |
essential_messages.append(msg)
|
| 542 |
|
| 543 |
+
print(f"Cleaned message history: {len(current_messages)} -> {len(essential_messages)} messages")
|
| 544 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 545 |
return {
|
| 546 |
"plan": revised_plan,
|
| 547 |
"current_step": 0,
|
|
|
|
| 553 |
|
| 554 |
def complexity_assessor(state: AgentState) -> AgentState:
|
| 555 |
"""Assess query complexity and determine if planning is needed."""
|
| 556 |
+
print("=== COMPLEXITY ASSESSMENT ===")
|
| 557 |
+
|
| 558 |
complexity_llm = llm.with_structured_output(ComplexityLevel)
|
| 559 |
+
|
| 560 |
assessment_message = [
|
| 561 |
SystemMessage(content=COMPLEXITY_ASSESSOR_PROMPT.strip()),
|
| 562 |
HumanMessage(content=f"Query: {state['query']}")
|
| 563 |
]
|
| 564 |
+
|
| 565 |
assessment = complexity_llm.invoke(assessment_message)
|
| 566 |
+
|
| 567 |
+
print(f"Complexity: {assessment.level}")
|
| 568 |
+
print(f"Needs planning: {assessment.needs_planning}")
|
| 569 |
+
print(f"Reasoning: {assessment.reasoning}")
|
| 570 |
+
|
|
|
|
|
|
|
|
|
|
| 571 |
return {
|
| 572 |
"complexity_assessment": assessment,
|
| 573 |
"messages": state["messages"] + assessment_message
|
src/prompts/prompts.py
CHANGED
|
@@ -24,7 +24,7 @@ Return a single JSON object with this structure:
|
|
| 24 |
}}
|
| 25 |
|
| 26 |
Ground rules:
|
| 27 |
-
- Prefer 1β3 steps. Only add a step if it changes the outcome.
|
| 28 |
- Use tool names exactly as listed. If no tool is needed, set "tool": null.
|
| 29 |
- Never assume files or URLs existβplan to search/download before analysing.
|
| 30 |
- Skip download steps when the required file is already provided.
|
|
@@ -130,5 +130,5 @@ Confidence: {confidence}
|
|
| 130 |
Limitations: {limitations}
|
| 131 |
Final Answer: {answer}
|
| 132 |
|
| 133 |
-
Provide detailed critique focusing on what works well and what could be improved.
|
| 134 |
"""
|
|
|
|
| 24 |
}}
|
| 25 |
|
| 26 |
Ground rules:
|
| 27 |
+
- Prefer 1β3 steps. Only add a step if it changes the outcome. For complex tasks, up to 5-7 steps is okay.
|
| 28 |
- Use tool names exactly as listed. If no tool is needed, set "tool": null.
|
| 29 |
- Never assume files or URLs existβplan to search/download before analysing.
|
| 30 |
- Skip download steps when the required file is already provided.
|
|
|
|
| 130 |
Limitations: {limitations}
|
| 131 |
Final Answer: {answer}
|
| 132 |
|
| 133 |
+
Provide detailed critique focusing on what works well and what could be improved. REMEMBER: if the task is enough simple, just say "NO CRITIC NEEDED".
|
| 134 |
"""
|
src/schemas.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
from typing import List, Optional, Literal
|
| 2 |
-
from pydantic import BaseModel, Field
|
| 3 |
-
|
| 4 |
|
| 5 |
class ComplexityLevel(BaseModel):
|
| 6 |
level: Literal["simple", "moderate", "complex"] = Field(description="Complexity level of the query")
|
|
@@ -18,8 +17,6 @@ class CritiqueFeedback(BaseModel):
|
|
| 18 |
needs_replanning: bool = Field(description="Whether the plan should be revised")
|
| 19 |
replan_instructions: Optional[str] = Field(default=None, description="Instructions for replanning")
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
TaskType = Literal["info", "calc", "table", "doc_qa", "image_qa", "multi_hop"]
|
| 24 |
|
| 25 |
class PlanStep(BaseModel):
|
|
@@ -30,6 +27,19 @@ class PlanStep(BaseModel):
|
|
| 30 |
expected_result: str = Field(description="How to confirm the step succeeded")
|
| 31 |
on_fail: str = Field(default="replan", description="Fallback action if the step fails (replan or stop)")
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
class PlannerPlan(BaseModel):
|
| 35 |
task_type: TaskType
|
|
|
|
| 1 |
from typing import List, Optional, Literal
|
| 2 |
+
from pydantic import BaseModel, Field, field_validator
|
|
|
|
| 3 |
|
| 4 |
class ComplexityLevel(BaseModel):
|
| 5 |
level: Literal["simple", "moderate", "complex"] = Field(description="Complexity level of the query")
|
|
|
|
| 17 |
needs_replanning: bool = Field(description="Whether the plan should be revised")
|
| 18 |
replan_instructions: Optional[str] = Field(default=None, description="Instructions for replanning")
|
| 19 |
|
|
|
|
|
|
|
| 20 |
TaskType = Literal["info", "calc", "table", "doc_qa", "image_qa", "multi_hop"]
|
| 21 |
|
| 22 |
class PlanStep(BaseModel):
|
|
|
|
| 27 |
expected_result: str = Field(description="How to confirm the step succeeded")
|
| 28 |
on_fail: str = Field(default="replan", description="Fallback action if the step fails (replan or stop)")
|
| 29 |
|
| 30 |
+
@field_validator("tool", mode="before")
|
| 31 |
+
@classmethod
|
| 32 |
+
def normalize_tool(cls, value: Optional[str]) -> Optional[str]:
|
| 33 |
+
"""Ensure blank or null-like values are interpreted as no tool."""
|
| 34 |
+
|
| 35 |
+
if value is None:
|
| 36 |
+
return None
|
| 37 |
+
if isinstance(value, str):
|
| 38 |
+
cleaned = value.strip()
|
| 39 |
+
if not cleaned or cleaned.lower() in {"null", "none"}:
|
| 40 |
+
return None
|
| 41 |
+
return cleaned
|
| 42 |
+
return value
|
| 43 |
|
| 44 |
class PlannerPlan(BaseModel):
|
| 45 |
task_type: TaskType
|
src/utils/utils.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
from typing import Iterable, Optional
|
| 2 |
-
|
| 3 |
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage
|
| 4 |
|
| 5 |
from schemas import ComplexityLevel, ExecutionReport, PlannerPlan
|
| 6 |
from prompts.prompts import COMPLEXITY_ASSESSOR_PROMPT
|
| 7 |
-
from config import llm
|
| 8 |
from state import AgentState
|
| 9 |
|
| 10 |
def log_stage(title: str, subtitle: Optional[str] = None, icon: str = "π") -> None:
|
|
@@ -52,6 +51,8 @@ def display_plan(plan: PlannerPlan) -> None:
|
|
| 52 |
print(f" {step.id} β {step.goal}")
|
| 53 |
if step.tool:
|
| 54 |
print(f" tool: {step.tool}")
|
|
|
|
|
|
|
| 55 |
if step.inputs:
|
| 56 |
print(f" inputs: {step.inputs}")
|
| 57 |
print(f" expected: {step.expected_result}")
|
|
@@ -138,7 +139,7 @@ def complexity_assessor(state: AgentState) -> AgentState:
|
|
| 138 |
"""Assess query complexity and determine if planning is needed."""
|
| 139 |
print("=== COMPLEXITY ASSESSMENT ===")
|
| 140 |
|
| 141 |
-
complexity_llm =
|
| 142 |
|
| 143 |
assessment_message = [
|
| 144 |
SystemMessage(content=COMPLEXITY_ASSESSOR_PROMPT.strip()),
|
|
|
|
| 1 |
from typing import Iterable, Optional
|
| 2 |
+
from langchain_openai import ChatOpenAI
|
| 3 |
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage
|
| 4 |
|
| 5 |
from schemas import ComplexityLevel, ExecutionReport, PlannerPlan
|
| 6 |
from prompts.prompts import COMPLEXITY_ASSESSOR_PROMPT
|
|
|
|
| 7 |
from state import AgentState
|
| 8 |
|
| 9 |
def log_stage(title: str, subtitle: Optional[str] = None, icon: str = "π") -> None:
|
|
|
|
| 51 |
print(f" {step.id} β {step.goal}")
|
| 52 |
if step.tool:
|
| 53 |
print(f" tool: {step.tool}")
|
| 54 |
+
else:
|
| 55 |
+
print(" tool: (none)")
|
| 56 |
if step.inputs:
|
| 57 |
print(f" inputs: {step.inputs}")
|
| 58 |
print(f" expected: {step.expected_result}")
|
|
|
|
| 139 |
"""Assess query complexity and determine if planning is needed."""
|
| 140 |
print("=== COMPLEXITY ASSESSMENT ===")
|
| 141 |
|
| 142 |
+
complexity_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.25).with_structured_output(ComplexityLevel)
|
| 143 |
|
| 144 |
assessment_message = [
|
| 145 |
SystemMessage(content=COMPLEXITY_ASSESSOR_PROMPT.strip()),
|
src/workflow_test.ipynb
CHANGED
|
@@ -2,14 +2,14 @@
|
|
| 2 |
"cells": [
|
| 3 |
{
|
| 4 |
"cell_type": "code",
|
| 5 |
-
"execution_count":
|
| 6 |
"metadata": {},
|
| 7 |
"outputs": [
|
| 8 |
{
|
| 9 |
"name": "stderr",
|
| 10 |
"output_type": "stream",
|
| 11 |
"text": [
|
| 12 |
-
"d:\\
|
| 13 |
" from .autonotebook import tqdm as notebook_tqdm\n"
|
| 14 |
]
|
| 15 |
}
|
|
@@ -21,7 +21,7 @@
|
|
| 21 |
},
|
| 22 |
{
|
| 23 |
"cell_type": "code",
|
| 24 |
-
"execution_count":
|
| 25 |
"metadata": {},
|
| 26 |
"outputs": [],
|
| 27 |
"source": [
|
|
@@ -30,46 +30,188 @@
|
|
| 30 |
},
|
| 31 |
{
|
| 32 |
"cell_type": "code",
|
| 33 |
-
"execution_count":
|
| 34 |
"metadata": {},
|
| 35 |
"outputs": [
|
| 36 |
{
|
| 37 |
"name": "stdout",
|
| 38 |
"output_type": "stream",
|
| 39 |
"text": [
|
| 40 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
"=== COMPLEXITY ASSESSMENT ===\n",
|
| 42 |
-
"Complexity:
|
| 43 |
-
"Needs planning:
|
| 44 |
-
"Reasoning: This query
|
| 45 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
"=== GENERATING EXECUTION REPORT ===\n",
|
| 47 |
"Report generated - Confidence: high\n",
|
| 48 |
"Key findings: 2\n",
|
| 49 |
-
"Data sources:
|
| 50 |
-
"query_summary='The user requested
|
| 51 |
"=== ENHANCED ANSWER CRITIQUE ===\n",
|
| 52 |
-
"Quality Score:
|
| 53 |
"Complete: True\n",
|
| 54 |
"Accurate: True\n",
|
| 55 |
"=== REPLAN DECISION ===\n",
|
| 56 |
"Iteration: 1/10\n",
|
| 57 |
-
"Quality score:
|
| 58 |
"Needs replanning: False\n",
|
| 59 |
"Quality acceptable, ending execution\n"
|
| 60 |
]
|
| 61 |
}
|
| 62 |
],
|
| 63 |
"source": [
|
| 64 |
-
"
|
|
|
|
| 65 |
]
|
| 66 |
},
|
| 67 |
{
|
| 68 |
"cell_type": "code",
|
| 69 |
-
"execution_count":
|
| 70 |
"metadata": {},
|
| 71 |
-
"outputs": [
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
}
|
| 74 |
],
|
| 75 |
"metadata": {
|
|
|
|
| 2 |
"cells": [
|
| 3 |
{
|
| 4 |
"cell_type": "code",
|
| 5 |
+
"execution_count": 1,
|
| 6 |
"metadata": {},
|
| 7 |
"outputs": [
|
| 8 |
{
|
| 9 |
"name": "stderr",
|
| 10 |
"output_type": "stream",
|
| 11 |
"text": [
|
| 12 |
+
"d:\\ankelodon_multiagent_system\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
|
| 13 |
" from .autonotebook import tqdm as notebook_tqdm\n"
|
| 14 |
]
|
| 15 |
}
|
|
|
|
| 21 |
},
|
| 22 |
{
|
| 23 |
"cell_type": "code",
|
| 24 |
+
"execution_count": 2,
|
| 25 |
"metadata": {},
|
| 26 |
"outputs": [],
|
| 27 |
"source": [
|
|
|
|
| 30 |
},
|
| 31 |
{
|
| 32 |
"cell_type": "code",
|
| 33 |
+
"execution_count": 3,
|
| 34 |
"metadata": {},
|
| 35 |
"outputs": [
|
| 36 |
{
|
| 37 |
"name": "stdout",
|
| 38 |
"output_type": "stream",
|
| 39 |
"text": [
|
| 40 |
+
"\n",
|
| 41 |
+
"π‘ ββββββββββββββββββββ\n",
|
| 42 |
+
"π‘ USER QUERY \n",
|
| 43 |
+
"π‘ ββββββββββββββββββββ\n",
|
| 44 |
+
" β’ files: none provided\n",
|
| 45 |
"=== COMPLEXITY ASSESSMENT ===\n",
|
| 46 |
+
"Complexity: complex\n",
|
| 47 |
+
"Needs planning: True\n",
|
| 48 |
+
"Reasoning: This query involves multiple steps: first, gathering information about Nikita Miroshnichenko, which may require searching through various sources; second, verifying his affiliation with UNIL and any working experience at EPFL; and third, synthesizing this information into a coherent summary. The need to cross-reference information adds to the complexity, as it requires careful reasoning to ensure accuracy.\n",
|
| 49 |
+
"\n",
|
| 50 |
+
"π§ ββββββββββββββββββββ\n",
|
| 51 |
+
"π§ PLANNING \n",
|
| 52 |
+
"π§ ββββββββββββββββββββ\n",
|
| 53 |
+
"\n",
|
| 54 |
+
"π§ ββββββββββββββββββββ\n",
|
| 55 |
+
"π§ PLANNER OUTPUT \n",
|
| 56 |
+
"π§ ββββββββββββββββββββ\n",
|
| 57 |
+
"Task type: info\n",
|
| 58 |
+
"Summary: I will perform a web search to gather information about Nikita Miroshnichenko, including his background as a student at UNIL and any working experience at EPFL.\n",
|
| 59 |
+
"Steps:\n",
|
| 60 |
+
" s1 β Search for information about Nikita Miroshnichenko to confirm his background and work experience.\n",
|
| 61 |
+
" tool: web_search\n",
|
| 62 |
+
" inputs: Nikita Miroshnichenko UNIL EPFL\n",
|
| 63 |
+
" expected: Find relevant information confirming his student status and any work experience at EPFL.\n",
|
| 64 |
+
" on_fail: replan\n",
|
| 65 |
+
"Answer guidelines: Provide a concise summary based on the information found, including citations if applicable.\n",
|
| 66 |
+
"\n",
|
| 67 |
+
"π€ ββββββββββββββββββββ\n",
|
| 68 |
+
"π€ EXECUTION \n",
|
| 69 |
+
"π€ ββββββββββββββββββββ\n",
|
| 70 |
+
"π€ Step 1/1: Search for information about Nikita Miroshnichenko to confirm his background and work experience.\n",
|
| 71 |
+
" β’ step_id: s1\n",
|
| 72 |
+
" β’ tool: web_search\n",
|
| 73 |
+
" β’ expected: Find relevant information confirming his student status and any work experience at EPFL.\n",
|
| 74 |
+
"\n",
|
| 75 |
+
"π§ ββββββββββββββββββββ\n",
|
| 76 |
+
"π§ REASONING \n",
|
| 77 |
+
"π§ ββββββββββββββββββββ\n",
|
| 78 |
+
"π§ s1\n",
|
| 79 |
+
"<REASONING> The query requires gathering information about Nikita Miroshnichenko, specifically his background as a student at UNIL and any work experience at EPFL. This involves performing a web search to find relevant details about him, which will help in writing a short summary. The first step will be to use the web_search tool to collect this information. The expected outcome is to obtain sufficient data to confirm his educational background and work experience, which will then allow for the creation of a summary. Since this is a research task that requires external information, it is classified as a moderate complexity task. </REASONING>\n",
|
| 80 |
+
"=== REASONING STEP ===\n",
|
| 81 |
+
"{\n",
|
| 82 |
+
" \"task_type\": \"info\",\n",
|
| 83 |
+
" \"summary\": \"The plan involves searching for information about Nikita Miroshnichenko to confirm his background as a student at UNIL and any work experience at EPFL.\",\n",
|
| 84 |
+
" \"assumptions\": [\"Nikita Miroshnichenko is a student at UNIL\", \"There may be publicly available information regarding his work experience at EPFL\"],\n",
|
| 85 |
+
" \"steps\": [\n",
|
| 86 |
+
" {\n",
|
| 87 |
+
" \"id\": \"s1\",\n",
|
| 88 |
+
" \"goal\": \"Search for information about Nikita Miroshnichenko to confirm his background and work experience.\",\n",
|
| 89 |
+
" \"tool\": \"web_search\",\n",
|
| 90 |
+
" \"inputs\": \"Nikita Miroshnichenko UNIL EPFL\",\n",
|
| 91 |
+
" \"expected_result\": \"Find relevant information confirming his student status and any work experience at EPFL.\",\n",
|
| 92 |
+
" \"on_fail\": \"replan\"\n",
|
| 93 |
+
" }\n",
|
| 94 |
+
" ],\n",
|
| 95 |
+
" \"answer_guidelines\": \"Provide a summary of the findings, including citations for any sources used.\"\n",
|
| 96 |
+
"}\n",
|
| 97 |
+
"\n",
|
| 98 |
+
"π€ ββββββββββββββββββββ\n",
|
| 99 |
+
"π€ EXECUTION \n",
|
| 100 |
+
"π€ ββββββββββββββββββββ\n",
|
| 101 |
+
"π€ Step 1/1: Search for information about Nikita Miroshnichenko to confirm his background and work experience.\n",
|
| 102 |
+
" β’ step_id: s1\n",
|
| 103 |
+
" β’ tool: web_search\n",
|
| 104 |
+
" β’ expected: Find relevant information confirming his student status and any work experience at EPFL.\n",
|
| 105 |
+
"=== TOOL EXECUTION ===\n",
|
| 106 |
+
"Tool calls: [{'name': 'web_search', 'args': {'query': 'Nikita Miroshnichenko UNIL EPFL'}, 'id': 'call_TJN5zTZWXac12m0so0FrKpOr', 'type': 'tool_call'}]\n"
|
| 107 |
+
]
|
| 108 |
+
},
|
| 109 |
+
{
|
| 110 |
+
"name": "stderr",
|
| 111 |
+
"output_type": "stream",
|
| 112 |
+
"text": [
|
| 113 |
+
"d:\\ankelodon_multiagent_system\\src\\tools\\tools.py:228: LangChainDeprecationWarning: The class `TavilySearchResults` was deprecated in LangChain 0.3.25 and will be removed in 1.0. An updated version of the class exists in the :class:`~langchain-tavily package and should be used instead. To use it run `pip install -U :class:`~langchain-tavily` and import as `from :class:`~langchain_tavily import TavilySearch``.\n",
|
| 114 |
+
" raw_results = TavilySearchResults(max_results=max_results).invoke(query)\n"
|
| 115 |
+
]
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"name": "stdout",
|
| 119 |
+
"output_type": "stream",
|
| 120 |
+
"text": [
|
| 121 |
+
"\n",
|
| 122 |
+
"β
ββββββββββββββββββββ\n",
|
| 123 |
+
"β
PLAN COMPLETE \n",
|
| 124 |
+
"β
ββββββββββββββββββββ\n",
|
| 125 |
+
"β
All steps executed\n",
|
| 126 |
"=== GENERATING EXECUTION REPORT ===\n",
|
| 127 |
"Report generated - Confidence: high\n",
|
| 128 |
"Key findings: 2\n",
|
| 129 |
+
"Data sources: 1\n",
|
| 130 |
+
"query_summary='The user requested information about Nikita Miroshnichenko, a student from UNIL, and inquired about his working experience at EPFL.' approach_used=\"A web search was conducted to gather relevant information regarding Nikita Miroshnichenko's background as a student at UNIL and any work experience he may have at EPFL.\" tools_executed=[ToolExecution(tool_name='web_search', arguments=\"{'query': 'Nikita Miroshnichenko UNIL EPFL'}\", call_id='call_TJN5zTZWXac12m0so0FrKpOr')] key_findings=['Nikita Miroshnichenko is a student at UNIL.', 'He has been associated with EPFL, confirming his work experience there.'] data_sources=['https://topline.com/people/nikita-miroshnichenko-182776498'] assumptions_made=[] confidence_level='high' limitations=['The information retrieved is based on available online sources, which may not be exhaustive or fully up-to-date.'] final_answer='Nikita Miroshnichenko is a student at UNIL and has confirmed working experience at EPFL.'\n",
|
| 131 |
"=== ENHANCED ANSWER CRITIQUE ===\n",
|
| 132 |
+
"Quality Score: 6/10\n",
|
| 133 |
"Complete: True\n",
|
| 134 |
"Accurate: True\n",
|
| 135 |
"=== REPLAN DECISION ===\n",
|
| 136 |
"Iteration: 1/10\n",
|
| 137 |
+
"Quality score: 6\n",
|
| 138 |
"Needs replanning: False\n",
|
| 139 |
"Quality acceptable, ending execution\n"
|
| 140 |
]
|
| 141 |
}
|
| 142 |
],
|
| 143 |
"source": [
|
| 144 |
+
"query = \"Find info about Nikita Miroshnichenko, its a student from UNIL, and write a short summary about him. Is it true that he has a working experience at EPFL?\"\n",
|
| 145 |
+
"result = graph.invoke({\"query\" : query, \"current_step\": 0, \"reasoning_done\": False, \"files\" : [], \"files_contents\" : {}, \"iteration_count\" : 0, \"max_iterations\" : 10, \"plan\" : None} , config = config)"
|
| 146 |
]
|
| 147 |
},
|
| 148 |
{
|
| 149 |
"cell_type": "code",
|
| 150 |
+
"execution_count": 7,
|
| 151 |
"metadata": {},
|
| 152 |
+
"outputs": [
|
| 153 |
+
{
|
| 154 |
+
"name": "stdout",
|
| 155 |
+
"output_type": "stream",
|
| 156 |
+
"text": [
|
| 157 |
+
"FINAL ANSWER: Nikita Miroshnichenko is a student at UNIL and has confirmed working experience at EPFL.\n",
|
| 158 |
+
"\n",
|
| 159 |
+
"SUMMARY:\n",
|
| 160 |
+
"The user requested information about Nikita Miroshnichenko, a student from UNIL, and inquired about his working experience at EPFL.\n",
|
| 161 |
+
"\n",
|
| 162 |
+
"KEY FINDINGS:\n",
|
| 163 |
+
"β’ Nikita Miroshnichenko is a student at UNIL.\n",
|
| 164 |
+
"β’ He has been associated with EPFL, confirming his work experience there.\n",
|
| 165 |
+
"\n",
|
| 166 |
+
"SOURCES:\n",
|
| 167 |
+
"β’ https://topline.com/people/nikita-miroshnichenko-182776498\n",
|
| 168 |
+
"\n",
|
| 169 |
+
"LIMITATIONS:\n",
|
| 170 |
+
"β’ The information retrieved is based on available online sources, which may not be exhaustive or fully up-to-date.\n"
|
| 171 |
+
]
|
| 172 |
+
}
|
| 173 |
+
],
|
| 174 |
+
"source": [
|
| 175 |
+
"print(result[\"final_answer\"])"
|
| 176 |
+
]
|
| 177 |
+
},
|
| 178 |
+
{
|
| 179 |
+
"cell_type": "code",
|
| 180 |
+
"execution_count": 8,
|
| 181 |
+
"metadata": {},
|
| 182 |
+
"outputs": [
|
| 183 |
+
{
|
| 184 |
+
"data": {
|
| 185 |
+
"text/plain": [
|
| 186 |
+
"{'messages': [SystemMessage(content='You are a COMPLEXITY ASSESSOR for a multi-tool agent system.\\nYour job is to analyze user queries and determine their complexity level and processing requirements.\\n\\nCOMPLEXITY LEVELS:\\n1. SIMPLE: Direct questions that can be answered immediately without tools or with single tool use\\n - Examples: \"What is 2+2?\", \"Define photosynthesis\", \"What\\'s the capital of France?\"\\n \\n2. MODERATE: Questions requiring 1-3 tool calls or basic analysis\\n - Examples: \"Search for recent news about AI\", \"Analyze this CSV file\", \"What\\'s the weather tomorrow?\"\\n \\n3. COMPLEX: Multi-step problems requiring planning, multiple tools, or sophisticated reasoning\\n - Examples: Research tasks, multi-file analysis, calculations with dependencies, creative projects\\n\\nASSESSMENT CRITERIA:\\n- Number of steps likely needed\\n- Tool complexity and dependencies\\n- Data processing requirements\\n- Need for intermediate reasoning\\n- Risk of failure without proper planning\\n\\nRULES:\\n- SIMPLE queries bypass planning entirely\\n- MODERATE queries may use lightweight planning\\n- COMPLEX queries require full planning with fallbacks\\n- When in doubt, err toward higher complexity\\n\\nAnalyze the query and respond with your assessment.', additional_kwargs={}, response_metadata={}, id='11b7e36b-63f4-4dab-b911-19a122ded253'),\n",
|
| 187 |
+
" HumanMessage(content='Query: Find info about Nikita Miroshnichenko, its a student from UNIL, and write a short summary about him. Is it true that he has a working experience at EPFL?', additional_kwargs={}, response_metadata={}, id='33ed5c6b-c4af-4080-8531-afa04709ae79'),\n",
|
| 188 |
+
" SystemMessage(content='You are the planner of a multi-tool agent. Build a short, realistic plan that the executor can follow.\\n\\nAvailable tools: add, analyze_csv_file, analyze_docx_file, analyze_excel_file, analyze_image_file, analyze_pdf_file, analyze_txt_file, arxiv_search, divide, download_file_from_url, multiply, power, safe_code_run, subtract, vision_qa_gemma, web_search, wiki_search\\nKnown local files: none provided\\nAdditional context: None\\n\\nReturn a single JSON object with this structure:\\n{\\n \"task_type\": \"info|calc|table|doc_qa|image_qa|multi_hop\",\\n \"summary\": \"One sentence on the chosen approach\",\\n \"assumptions\": [\"optional clarifications\"],\\n \"steps\": [\\n {\\n \"id\": \"s1\",\\n \"goal\": \"Action to take and why it helps\",\\n \"tool\": \"tool_name_or_null\",\\n \"inputs\": \"Key parameters or references (files, URLs, prior steps)\",\\n \"expected_result\": \"How you know the step succeeded\",\\n \"on_fail\": \"replan|stop\"\\n }\\n ],\\n \"answer_guidelines\": \"Reminders for the final response (citations, format, units, etc.)\"\\n}\\n\\nGround rules:\\n- Prefer 1β3 steps. Only add a step if it changes the outcome. For complex tasks, up to 5-7 steps is okay.\\n- Use tool names exactly as listed. If no tool is needed, set \"tool\": null.\\n- Never assume files or URLs existβplan to search/download before analysing.\\n- Skip download steps when the required file is already provided.\\n- Ensure later steps only depend on results created by earlier steps.\\n- If the query is trivial, return an empty steps list and explain the direct answer in \"summary\".', additional_kwargs={}, response_metadata={}, id='a2291408-86bd-4a5a-ad97-88ba7ca26f8a'),\n",
|
| 189 |
+
" HumanMessage(content='Find info about Nikita Miroshnichenko, its a student from UNIL, and write a short summary about him. Is it true that he has a working experience at EPFL?', additional_kwargs={}, response_metadata={}, id='02bdaf14-e770-4903-8bd2-ec4bce7070a0'),\n",
|
| 190 |
+
" AIMessage(content='{\\n \"task_type\": \"info\",\\n \"summary\": \"The plan involves searching for information about Nikita Miroshnichenko to confirm his background as a student at UNIL and any work experience at EPFL.\",\\n \"assumptions\": [\"Nikita Miroshnichenko is a student at UNIL\", \"There may be publicly available information regarding his work experience at EPFL\"],\\n \"steps\": [\\n {\\n \"id\": \"s1\",\\n \"goal\": \"Search for information about Nikita Miroshnichenko to confirm his background and work experience.\",\\n \"tool\": \"web_search\",\\n \"inputs\": \"Nikita Miroshnichenko UNIL EPFL\",\\n \"expected_result\": \"Find relevant information confirming his student status and any work experience at EPFL.\",\\n \"on_fail\": \"replan\"\\n }\\n ],\\n \"answer_guidelines\": \"Provide a summary of the findings, including citations for any sources used.\"\\n}', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 206, 'prompt_tokens': 1088, 'total_tokens': 1294, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_51db84afab', 'id': 'chatcmpl-CHEdmytbl8Nei62qo9Ti4se6AOc5O', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--bbddcd4a-f737-4033-b789-adcf6c3bacb5-0', usage_metadata={'input_tokens': 1088, 'output_tokens': 206, 'total_tokens': 1294, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}),\n",
|
| 191 |
+
" AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_TJN5zTZWXac12m0so0FrKpOr', 'function': {'arguments': '{\"query\":\"Nikita Miroshnichenko UNIL EPFL\"}', 'name': 'web_search'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 25, 'prompt_tokens': 2672, 'total_tokens': 2697, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 1920}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_560af6e559', 'id': 'chatcmpl-CHEdrHAP7W9cDsebqQu7iAIVGl2OF', 'service_tier': 'default', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--9d9e841f-b6eb-4df1-8695-3cb5d35e83b7-0', tool_calls=[{'name': 'web_search', 'args': {'query': 'Nikita Miroshnichenko UNIL EPFL'}, 'id': 'call_TJN5zTZWXac12m0so0FrKpOr', 'type': 'tool_call'}], usage_metadata={'input_tokens': 2672, 'output_tokens': 25, 'total_tokens': 2697, 'input_token_details': {'audio': 0, 'cache_read': 1920}, 'output_token_details': {'audio': 0, 'reasoning': 0}}),\n",
|
| 192 |
+
" ToolMessage(content='{\"query\": \"Nikita Miroshnichenko UNIL EPFL\", \"provider\": \"tavily\", \"items\": [{\"url\": \"https://topline.com/people/nikita-miroshnichenko-182776498\", \"title\": \"Nikita Miroshnichenko - Topline\", \"snippet\": \"###### The International Festival of Engineering Science and Technology in Tunisia I-FEST\\\\u00b2 (Silver medal)\\\\n\\\\n###### Molecular phylogenetic&bioinformatics course\\\\n\\\\n###### Molecular biology&genetics course\\\\n\\\\n###### Python programming course\\\\n\\\\n#### Experience\\\\n\\\\n##### University of Lausanne - UNIL\\\\n\\\\n##### Student Laboratory Assistant\\\\n\\\\n##### EPFL (\\\\u00c9cole polytechnique f\\\\u00e9d\\\\u00e9rale de Lausanne)\\\\n\\\\n##### Laboratory A\\\\u2026\", \"published\": null, \"source\": \"topline.com\"}, {\"url\": \"https://cdn5.f-cdn.com/files/download/223843566/Nikita_Miroshnichenko_technical_CV_2024.pdf\", \"title\": \"[PDF] Nikita Miroshnichenko\", \"snippet\": \"development in this field. If possible, I will be glad to apply my work experience in biotech/neurotech-oriented projects. NOVEMBER 2023 \\\\u2013 PRESENT Research Assistant. Computational Biology and Cancer Genomics Group Department of Computational Biology | UNIL. Lausanne \\\\u2022 Processing raw single-cell RNAseq data, building bioinformatics pipelines for oncology and genomic research. \\\\u2022 Development of a P\\\\u2026\", \"published\": null, \"source\": \"f-cdn.com\"}, {\"url\": \"https://ch.linkedin.com/in/nikita-miroshnichenko\", \"title\": \"Nikita Miroshnichenko \\\\u2013 AI Engineer | Biotech Enthusiast - LinkedIn\", \"snippet\": \"Nikita Miroshnichenko. AI Engineer | Biotech Enthusiast | Researcher | Entrepreneur. University of Lausanne - UNIL Taras Shevchenko National University of Kyiv\", \"published\": null, \"source\": \"linkedin.com\"}, {\"url\": \"https://www.transfermarkt.com/nikita-miroshnichenko/profil/spieler/561855\", \"title\": \"Nikita Miroshnichenko - Player profile 25/26 - Transfermarkt\", \"snippet\": \"Transfermarkt\\\\nUEFA Champions League\\\\nPremier League\\\\nLaLiga\\\\nSerie A\\\\nBundesliga\\\\nLigue 1\\\\n\\\\nNikita Miroshnichenko\\\\n\\\\n# #18 Nikita Miroshnichenko\\\\n\\\\nShinnik Yaroslavl\\\\n1.Division1.Division\\\\nRussiaSecond Tier\\\\nNikita Miroshnichenko\\\\n\\\\nfnl.pro\\\\n\\\\n+\\\\nRussia \\\\nSalavat, ... \\\\nRussia Russia\\\\nRussiaRussia U17\\\\n\\\\nLast update: 02.06.2025\\\\n\\\\n## Player data\\\\n\\\\nRussia\\\\nRussia Russia\\\\nShinnik Yaroslavl\\\\n\\\\n## Stats of Nikita Miroshnichenko\\\\n\\\\u2026\", \"published\": null, \"source\": \"transfermarkt.com\"}, {\"url\": \"http://arxiv.org/list/physics.optics/2019-12?skip=125&show=2000\", \"title\": \"Optics Dec 2019 - arXiv\", \"snippet\": \"Cornell University\\\\narxiv logo\\\\n\\\\nHelp | Advanced Search\\\\n\\\\narXiv logo\\\\nCornell University Logo\\\\n\\\\n## quick links\\\\n\\\\n# Optics\\\\n\\\\n## Authors and titles for December 2019\\\\n\\\\narXiv Operational Status \\\\nGet status notifications via\\\\nemail\\\\nor slack\", \"published\": null, \"source\": \"arxiv.org\"}]}', name='web_search', id='cd84b5bb-27ed-495b-942a-70de16013c44', tool_call_id='call_TJN5zTZWXac12m0so0FrKpOr'),\n",
|
| 193 |
+
" AIMessage(content='All plan steps completed. <FINAL_ANSWER>', additional_kwargs={}, response_metadata={}, id='3463b080-aa10-4906-bc0c-4d6e49bb8d8a')],\n",
|
| 194 |
+
" 'query': 'Find info about Nikita Miroshnichenko, its a student from UNIL, and write a short summary about him. Is it true that he has a working experience at EPFL?',\n",
|
| 195 |
+
" 'final_answer': 'FINAL ANSWER: Nikita Miroshnichenko is a student at UNIL and has confirmed working experience at EPFL.\\n\\nSUMMARY:\\nThe user requested information about Nikita Miroshnichenko, a student from UNIL, and inquired about his working experience at EPFL.\\n\\nKEY FINDINGS:\\nβ’ Nikita Miroshnichenko is a student at UNIL.\\nβ’ He has been associated with EPFL, confirming his work experience there.\\n\\nSOURCES:\\nβ’ https://topline.com/people/nikita-miroshnichenko-182776498\\n\\nLIMITATIONS:\\nβ’ The information retrieved is based on available online sources, which may not be exhaustive or fully up-to-date.',\n",
|
| 196 |
+
" 'plan': PlannerPlan(task_type='info', summary='I will perform a web search to gather information about Nikita Miroshnichenko, including his background as a student at UNIL and any working experience at EPFL.', assumptions=[], steps=[PlanStep(id='s1', goal='Search for information about Nikita Miroshnichenko to confirm his background and work experience.', tool='web_search', inputs='Nikita Miroshnichenko UNIL EPFL', expected_result='Find relevant information confirming his student status and any work experience at EPFL.', on_fail='replan')], answer_guidelines='Provide a concise summary based on the information found, including citations if applicable.'),\n",
|
| 197 |
+
" 'complexity_assessment': ComplexityLevel(level='complex', reasoning='This query involves multiple steps: first, gathering information about Nikita Miroshnichenko, which may require searching through various sources; second, verifying his affiliation with UNIL and any working experience at EPFL; and third, synthesizing this information into a coherent summary. The need to cross-reference information adds to the complexity, as it requires careful reasoning to ensure accuracy.', needs_planning=True, suggested_approach='Begin by searching for Nikita Miroshnichenko on academic and professional platforms to gather relevant information. Verify his student status at UNIL and check for any records of employment or internships at EPFL. Compile the findings into a concise summary.'),\n",
|
| 198 |
+
" 'current_step': 1,\n",
|
| 199 |
+
" 'reasoning_done': False,\n",
|
| 200 |
+
" 'files': [],\n",
|
| 201 |
+
" 'critique_feedback': CritiqueFeedback(quality_score=6, is_complete=True, is_accurate=True, missing_elements=['Details about the specific role or position held by Nikita Miroshnichenko at EPFL', 'Information on the duration of his work experience at EPFL', 'Any notable projects or contributions made during his time at EPFL'], errors_found=[], suggested_improvements=[\"Include more specific details about Nikita's role at EPFL to provide a clearer picture of his experience.\", 'Add information about the duration of his work experience to contextualize his involvement.', 'Mention any projects or contributions he made during his time at EPFL to enhance the depth of the report.'], needs_replanning=False, replan_instructions=None),\n",
|
| 202 |
+
" 'iteration_count': 1,\n",
|
| 203 |
+
" 'max_iterations': 10,\n",
|
| 204 |
+
" 'execution_report': ExecutionReport(query_summary='The user requested information about Nikita Miroshnichenko, a student from UNIL, and inquired about his working experience at EPFL.', approach_used=\"A web search was conducted to gather relevant information regarding Nikita Miroshnichenko's background as a student at UNIL and any work experience he may have at EPFL.\", tools_executed=[ToolExecution(tool_name='web_search', arguments=\"{'query': 'Nikita Miroshnichenko UNIL EPFL'}\", call_id='call_TJN5zTZWXac12m0so0FrKpOr')], key_findings=['Nikita Miroshnichenko is a student at UNIL.', 'He has been associated with EPFL, confirming his work experience there.'], data_sources=['https://topline.com/people/nikita-miroshnichenko-182776498'], assumptions_made=[], confidence_level='high', limitations=['The information retrieved is based on available online sources, which may not be exhaustive or fully up-to-date.'], final_answer='Nikita Miroshnichenko is a student at UNIL and has confirmed working experience at EPFL.')}"
|
| 205 |
+
]
|
| 206 |
+
},
|
| 207 |
+
"execution_count": 8,
|
| 208 |
+
"metadata": {},
|
| 209 |
+
"output_type": "execute_result"
|
| 210 |
+
}
|
| 211 |
+
],
|
| 212 |
+
"source": [
|
| 213 |
+
"result"
|
| 214 |
+
]
|
| 215 |
}
|
| 216 |
],
|
| 217 |
"metadata": {
|