deer-flow/backend/debug.py
He Wang 3a61126824
fix: keep debug.py interactive terminal free from background log noise (#2466)
* fix(debug): keep terminal clean by redirecting all logs to file

- Redirect all logs to debug.log file to prevent background task logs
  from interfering with interactive terminal prompts
- Honor AppConfig.log_level setting instead of hard-coding to INFO
- Make logging setup idempotent by clearing pre-existing handlers
- Defer deerflow imports until after logging is configured to ensure
  import-time side effects are captured in debug.log
- Display active log level in startup banner
- Add prompt_toolkit installation tip for enhanced readline support

Made-with: Cursor

* attaching the file handler before importing/calling get_app_config()

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-24 17:09:41 +08:00

157 lines
4.9 KiB
Python

#!/usr/bin/env python
"""
Debug script for lead_agent.
Run this file directly in VS Code with breakpoints.
Requirements:
Run with `uv run` from the backend/ directory so that the uv workspace
resolves deerflow-harness and app packages correctly:
cd backend && PYTHONPATH=. uv run python debug.py
Usage:
1. Set breakpoints in agent.py or other files
2. Press F5 or use "Run and Debug" panel
3. Input messages in the terminal to interact with the agent
"""
import asyncio
import logging
from dotenv import load_dotenv
try:
from prompt_toolkit import PromptSession
from prompt_toolkit.history import InMemoryHistory
_HAS_PROMPT_TOOLKIT = True
except ImportError:
_HAS_PROMPT_TOOLKIT = False
load_dotenv()
_LOG_FMT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
_LOG_DATEFMT = "%Y-%m-%d %H:%M:%S"
def _logging_level_from_config(name: str) -> int:
"""Map ``config.yaml`` ``log_level`` string to a ``logging`` level constant."""
mapping = logging.getLevelNamesMapping()
return mapping.get((name or "info").strip().upper(), logging.INFO)
def _setup_logging(log_level: str) -> None:
"""Send application logs to ``debug.log`` at *log_level*; do not print them on the console.
Idempotent: any pre-existing handlers on the root logger (e.g. installed by
``logging.basicConfig`` in transitively imported modules) are removed so the
debug session output only lands in ``debug.log``.
"""
level = _logging_level_from_config(log_level)
root = logging.root
for h in list(root.handlers):
root.removeHandler(h)
h.close()
root.setLevel(level)
file_handler = logging.FileHandler("debug.log", mode="a", encoding="utf-8")
file_handler.setLevel(level)
file_handler.setFormatter(logging.Formatter(_LOG_FMT, datefmt=_LOG_DATEFMT))
root.addHandler(file_handler)
def _update_logging_level(log_level: str) -> None:
"""Update the root logger and existing handlers to *log_level*."""
level = _logging_level_from_config(log_level)
root = logging.root
root.setLevel(level)
for handler in root.handlers:
handler.setLevel(level)
async def main():
# Install file logging first so warnings emitted while loading config do not
# leak onto the interactive terminal via Python's lastResort handler.
_setup_logging("info")
from deerflow.config import get_app_config
app_config = get_app_config()
_update_logging_level(app_config.log_level)
# Delay the rest of the deerflow imports until *after* logging is installed
# so that any import-time side effects (e.g. deerflow.agents starts a
# background skill-loader thread on import) emit logs to debug.log instead
# of leaking onto the interactive terminal via Python's lastResort handler.
from langchain_core.messages import HumanMessage
from langgraph.runtime import Runtime
from deerflow.agents import make_lead_agent
from deerflow.mcp import initialize_mcp_tools
# Initialize MCP tools at startup
try:
await initialize_mcp_tools()
except Exception as e:
print(f"Warning: Failed to initialize MCP tools: {e}")
# Create agent with default config
config = {
"configurable": {
"thread_id": "debug-thread-001",
"thinking_enabled": True,
"is_plan_mode": True,
# Uncomment to use a specific model
"model_name": "kimi-k2.5",
}
}
runtime = Runtime(context={"thread_id": config["configurable"]["thread_id"]})
config["configurable"]["__pregel_runtime"] = runtime
agent = make_lead_agent(config)
session = PromptSession(history=InMemoryHistory()) if _HAS_PROMPT_TOOLKIT else None
print("=" * 50)
print("Lead Agent Debug Mode")
print("Type 'quit' or 'exit' to stop")
print(f"Logs: debug.log (log_level={app_config.log_level})")
if not _HAS_PROMPT_TOOLKIT:
print("Tip: `uv sync --group dev` to enable arrow-key & history support")
print("=" * 50)
while True:
try:
if session:
user_input = (await session.prompt_async("\nYou: ")).strip()
else:
user_input = input("\nYou: ").strip()
if not user_input:
continue
if user_input.lower() in ("quit", "exit"):
print("Goodbye!")
break
# Invoke the agent
state = {"messages": [HumanMessage(content=user_input)]}
result = await agent.ainvoke(state, config=config)
# Print the response
if result.get("messages"):
last_message = result["messages"][-1]
print(f"\nAgent: {last_message.content}")
except (KeyboardInterrupt, EOFError):
print("\nGoodbye!")
break
except Exception as e:
print(f"\nError: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
asyncio.run(main())