mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-16 11:24:45 +08:00
* feat: add comprehensive debug logging for issue #477 hanging/freezing diagnosis - Add debug logging to src/server/app.py for event streaming and message chunk processing - Track graph event flow with thread IDs for correlation - Add detailed logging in interrupt event processing - Add debug logging to src/agents/tool_interceptor.py for tool execution and interrupt handling - Log interrupt decision flow and user feedback processing - Add debug logging to src/graph/nodes.py for agent node execution - Track step execution progress and agent coordination in research_team_node - Add debug logging to src/agents/agents.py for agent creation and tool wrapping - Update server.py to enable debug logging when --log-level debug is specified - Add thread ID correlation throughout for better diagnostics - Helps diagnose hanging/freezing issues during workflow execution * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -36,20 +36,42 @@ def create_agent(
|
||||
Returns:
|
||||
A configured agent graph
|
||||
"""
|
||||
logger.debug(
|
||||
f"Creating agent '{agent_name}' of type '{agent_type}' "
|
||||
f"with {len(tools)} tools and template '{prompt_template}'"
|
||||
)
|
||||
|
||||
# Wrap tools with interrupt logic if specified
|
||||
processed_tools = tools
|
||||
if interrupt_before_tools:
|
||||
logger.info(
|
||||
f"Creating agent '{agent_name}' with tool-specific interrupts: {interrupt_before_tools}"
|
||||
)
|
||||
logger.debug(f"Wrapping {len(tools)} tools for agent '{agent_name}'")
|
||||
processed_tools = wrap_tools_with_interceptor(tools, interrupt_before_tools)
|
||||
logger.debug(f"Agent '{agent_name}' tool wrapping completed")
|
||||
else:
|
||||
logger.debug(f"Agent '{agent_name}' has no interrupt-before-tools configured")
|
||||
|
||||
return create_react_agent(
|
||||
if agent_type not in AGENT_LLM_MAP:
|
||||
logger.warning(
|
||||
f"Agent type '{agent_type}' not found in AGENT_LLM_MAP. "
|
||||
f"Falling back to default LLM type 'basic' for agent '{agent_name}'. "
|
||||
"This may indicate a configuration issue."
|
||||
)
|
||||
llm_type = AGENT_LLM_MAP.get(agent_type, "basic")
|
||||
logger.debug(f"Agent '{agent_name}' using LLM type: {llm_type}")
|
||||
|
||||
logger.debug(f"Creating ReAct agent '{agent_name}'")
|
||||
agent = create_react_agent(
|
||||
name=agent_name,
|
||||
model=get_llm_by_type(AGENT_LLM_MAP[agent_type]),
|
||||
model=get_llm_by_type(llm_type),
|
||||
tools=processed_tools,
|
||||
prompt=lambda state: apply_prompt_template(
|
||||
prompt_template, state, locale=state.get("locale", "en-US")
|
||||
),
|
||||
pre_model_hook=pre_model_hook,
|
||||
)
|
||||
logger.info(f"Agent '{agent_name}' created successfully")
|
||||
|
||||
return agent
|
||||
|
||||
@@ -84,47 +84,69 @@ class ToolInterceptor:
|
||||
BaseTool: The wrapped tool with interrupt capability
|
||||
"""
|
||||
original_func = tool.func
|
||||
logger.debug(f"Wrapping tool '{tool.name}' with interrupt capability")
|
||||
|
||||
def intercepted_func(*args: Any, **kwargs: Any) -> Any:
|
||||
"""Execute the tool with interrupt check."""
|
||||
tool_name = tool.name
|
||||
logger.debug(f"[ToolInterceptor] Executing tool: {tool_name}")
|
||||
|
||||
# Format tool input for display
|
||||
tool_input = args[0] if args else kwargs
|
||||
tool_input_repr = ToolInterceptor._format_tool_input(tool_input)
|
||||
logger.debug(f"[ToolInterceptor] Tool input: {tool_input_repr[:200]}")
|
||||
|
||||
if interceptor.should_interrupt(tool_name):
|
||||
should_interrupt = interceptor.should_interrupt(tool_name)
|
||||
logger.debug(f"[ToolInterceptor] should_interrupt={should_interrupt} for tool '{tool_name}'")
|
||||
|
||||
if should_interrupt:
|
||||
logger.info(
|
||||
f"Interrupting before tool '{tool_name}' with input: {tool_input_repr}"
|
||||
f"[ToolInterceptor] Interrupting before tool '{tool_name}'"
|
||||
)
|
||||
logger.debug(
|
||||
f"[ToolInterceptor] Interrupt message: About to execute tool '{tool_name}' with input: {tool_input_repr[:100]}..."
|
||||
)
|
||||
|
||||
# Trigger interrupt and wait for user feedback
|
||||
feedback = interrupt(
|
||||
f"About to execute tool: '{tool_name}'\n\nInput:\n{tool_input_repr}\n\nApprove execution?"
|
||||
)
|
||||
|
||||
logger.info(f"Interrupt feedback for '{tool_name}': {feedback}")
|
||||
try:
|
||||
feedback = interrupt(
|
||||
f"About to execute tool: '{tool_name}'\n\nInput:\n{tool_input_repr}\n\nApprove execution?"
|
||||
)
|
||||
logger.debug(f"[ToolInterceptor] Interrupt returned with feedback: {f'{feedback[:100]}...' if feedback and len(feedback) > 100 else feedback if feedback else 'None'}")
|
||||
except Exception as e:
|
||||
logger.error(f"[ToolInterceptor] Error during interrupt: {str(e)}")
|
||||
raise
|
||||
|
||||
logger.debug(f"[ToolInterceptor] Processing feedback approval for '{tool_name}'")
|
||||
|
||||
# Check if user approved
|
||||
if not ToolInterceptor._parse_approval(feedback):
|
||||
logger.warning(f"User rejected execution of tool '{tool_name}'")
|
||||
is_approved = ToolInterceptor._parse_approval(feedback)
|
||||
logger.info(f"[ToolInterceptor] Tool '{tool_name}' approval decision: {is_approved}")
|
||||
|
||||
if not is_approved:
|
||||
logger.warning(f"[ToolInterceptor] User rejected execution of tool '{tool_name}'")
|
||||
return {
|
||||
"error": f"Tool execution rejected by user",
|
||||
"tool": tool_name,
|
||||
"status": "rejected",
|
||||
}
|
||||
|
||||
logger.info(f"User approved execution of tool '{tool_name}'")
|
||||
logger.info(f"[ToolInterceptor] User approved execution of tool '{tool_name}', proceeding")
|
||||
|
||||
# Execute the original tool
|
||||
try:
|
||||
logger.debug(f"[ToolInterceptor] Calling original function for tool '{tool_name}'")
|
||||
result = original_func(*args, **kwargs)
|
||||
logger.debug(f"Tool '{tool_name}' execution completed")
|
||||
logger.info(f"[ToolInterceptor] Tool '{tool_name}' execution completed successfully")
|
||||
logger.debug(f"[ToolInterceptor] Tool result length: {len(str(result))}")
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing tool '{tool_name}': {str(e)}")
|
||||
logger.error(f"[ToolInterceptor] Error executing tool '{tool_name}': {str(e)}")
|
||||
raise
|
||||
|
||||
# Replace the function and update the tool
|
||||
# Use object.__setattr__ to bypass Pydantic validation
|
||||
logger.debug(f"Attaching intercepted function to tool '{tool.name}'")
|
||||
object.__setattr__(tool, "func", intercepted_func)
|
||||
return tool
|
||||
|
||||
|
||||
Reference in New Issue
Block a user