debug-agent-py 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. debug_agent_py-0.2.1/PKG-INFO +160 -0
  2. debug_agent_py-0.2.1/README.md +123 -0
  3. debug_agent_py-0.2.1/pyproject.toml +44 -0
  4. debug_agent_py-0.2.1/setup.cfg +4 -0
  5. debug_agent_py-0.2.1/src/debug_agent/__init__.py +23 -0
  6. debug_agent_py-0.2.1/src/debug_agent/chat_session.py +45 -0
  7. debug_agent_py-0.2.1/src/debug_agent/config.py +48 -0
  8. debug_agent_py-0.2.1/src/debug_agent/context_compressor.py +157 -0
  9. debug_agent_py-0.2.1/src/debug_agent/engine.py +180 -0
  10. debug_agent_py-0.2.1/src/debug_agent/inspectors/__init__.py +13 -0
  11. debug_agent_py-0.2.1/src/debug_agent/inspectors/async_tasks.py +108 -0
  12. debug_agent_py-0.2.1/src/debug_agent/inspectors/database.py +129 -0
  13. debug_agent_py-0.2.1/src/debug_agent/inspectors/framework.py +133 -0
  14. debug_agent_py-0.2.1/src/debug_agent/inspectors/http_tracker.py +93 -0
  15. debug_agent_py-0.2.1/src/debug_agent/inspectors/memory.py +117 -0
  16. debug_agent_py-0.2.1/src/debug_agent/inspectors/modules.py +129 -0
  17. debug_agent_py-0.2.1/src/debug_agent/inspectors/runtime.py +132 -0
  18. debug_agent_py-0.2.1/src/debug_agent/inspectors/system.py +79 -0
  19. debug_agent_py-0.2.1/src/debug_agent/inspectors/threads.py +97 -0
  20. debug_agent_py-0.2.1/src/debug_agent/llm_client.py +195 -0
  21. debug_agent_py-0.2.1/src/debug_agent/middleware.py +199 -0
  22. debug_agent_py-0.2.1/src/debug_agent/system_prompt_builder.py +101 -0
  23. debug_agent_py-0.2.1/src/debug_agent/tool_registry.py +121 -0
  24. debug_agent_py-0.2.1/src/debug_agent/web/__init__.py +0 -0
  25. debug_agent_py-0.2.1/src/debug_agent/web/chat_page.py +582 -0
  26. debug_agent_py-0.2.1/src/debug_agent_py.egg-info/PKG-INFO +160 -0
  27. debug_agent_py-0.2.1/src/debug_agent_py.egg-info/SOURCES.txt +28 -0
  28. debug_agent_py-0.2.1/src/debug_agent_py.egg-info/dependency_links.txt +1 -0
  29. debug_agent_py-0.2.1/src/debug_agent_py.egg-info/requires.txt +18 -0
  30. debug_agent_py-0.2.1/src/debug_agent_py.egg-info/top_level.txt +1 -0
@@ -0,0 +1,160 @@
1
+ Metadata-Version: 2.4
2
+ Name: debug-agent-py
3
+ Version: 0.2.1
4
+ Summary: AI-powered runtime debugging agent for Python web applications
5
+ Author-email: ggcode <noreply@ggcode.dev>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/topcheer/python-debug-agent
8
+ Project-URL: Repository, https://github.com/topcheer/python-debug-agent
9
+ Project-URL: Issues, https://github.com/topcheer/python-debug-agent/issues
10
+ Keywords: debug,debugging,ai,llm,runtime,diagnostics,flask,fastapi,django,agent,observability
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Debuggers
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: httpx>=0.27
24
+ Provides-Extra: fastapi
25
+ Requires-Dist: fastapi>=0.100; extra == "fastapi"
26
+ Requires-Dist: starlette>=0.27; extra == "fastapi"
27
+ Requires-Dist: uvicorn>=0.23; extra == "fastapi"
28
+ Provides-Extra: flask
29
+ Requires-Dist: flask>=2.3; extra == "flask"
30
+ Provides-Extra: django
31
+ Requires-Dist: django>=4.2; extra == "django"
32
+ Provides-Extra: dev
33
+ Requires-Dist: fastapi; extra == "dev"
34
+ Requires-Dist: uvicorn; extra == "dev"
35
+ Requires-Dist: flask; extra == "dev"
36
+ Requires-Dist: pytest; extra == "dev"
37
+
38
+ # Python Debug Agent
39
+
40
+ An AI-powered runtime debugging agent that embeds directly into your Python web application. Add one dependency, configure an LLM key, and chat with your live app at `/agent` to inspect memory, threads, routes, HTTP requests, GC stats, and more.
41
+
42
+ ## Quick Start
43
+
44
+ ### 1. Install
45
+
46
+ ```bash
47
+ pip install debug-agent[fastapi]
48
+ ```
49
+
50
+ ### 2. Integrate (FastAPI)
51
+
52
+ ```python
53
+ from fastapi import FastAPI
54
+ from debug_agent.middleware import create_fastapi_router
55
+
56
+ app = FastAPI()
57
+
58
+ # One line to integrate
59
+ app.include_router(create_fastapi_router())
60
+ ```
61
+
62
+ ### 3. Configure LLM
63
+
64
+ ```bash
65
+ export LLM_API_KEY=your-key
66
+ export LLM_BASE_URL=https://api.openai.com/v1 # optional
67
+ export LLM_MODEL=gpt-4o # optional
68
+ ```
69
+
70
+ ### 4. Run and open
71
+
72
+ ```
73
+ http://localhost:8000/agent
74
+ ```
75
+
76
+ ## Framework Integrations
77
+
78
+ ### FastAPI / Starlette
79
+
80
+ ```python
81
+ from debug_agent.middleware import create_fastapi_router
82
+ app.include_router(create_fastapi_router())
83
+ ```
84
+
85
+ ### Flask
86
+
87
+ ```python
88
+ from flask import Flask
89
+ from debug_agent.middleware import create_flask_blueprint
90
+ app = Flask(__name__)
91
+ app.register_blueprint(create_flask_blueprint())
92
+ ```
93
+
94
+ ### Any ASGI App (Starlette Mount)
95
+
96
+ ```python
97
+ from starlette.routing import Mount
98
+ from debug_agent.middleware import create_starlette_app
99
+ routes = [Mount("/agent", app=create_starlette_app())]
100
+ ```
101
+
102
+ ## Built-in Tools (18+)
103
+
104
+ | Tool | Description |
105
+ |------|-------------|
106
+ | `get_gc_stats` | GC collection counts per generation |
107
+ | `get_memory_summary` | RSS, object counts, top types |
108
+ | `trigger_gc` | Force GC and show before/after |
109
+ | `get_thread_summary` | Thread count, names, daemon status |
110
+ | `get_thread_dump` | Stack traces for all threads |
111
+ | `get_runtime_info` | Python version, platform, PID |
112
+ | `get_memory_allocations` | tracemalloc top allocations |
113
+ | `get_routes` | List all web routes/endpoints |
114
+ | `get_middleware` | List registered middleware |
115
+ | `get_installed_packages` | Installed pip packages |
116
+ | `get_environment_variables` | Environment variables (masked secrets) |
117
+ | `get_recent_requests` | HTTP request ring buffer |
118
+ | `get_slow_requests` | Slowest requests sorted by duration |
119
+ | `get_error_requests` | Error requests (4xx/5xx) |
120
+ | `get_request_stats` | P50/P95/P99 latency, error rate |
121
+ | `get_process_info` | PID, CPU time, container detection |
122
+ | `get_system_info` | OS, CPU cores, load average |
123
+ | `get_disk_usage` | Disk usage for working directory |
124
+
125
+ ## Custom Tools
126
+
127
+ ```python
128
+ from debug_agent import debug_tool, ToolParam
129
+
130
+ @debug_tool("check_db_pool", "Check database connection pool stats")
131
+ def check_db_pool() -> dict:
132
+ return {"active": 5, "idle": 10, "max": 20}
133
+ ```
134
+
135
+ That's it. The tool is auto-discovered and made available to the LLM.
136
+
137
+ ## Configuration
138
+
139
+ | Env Var | Default | Description |
140
+ |---------|---------|-------------|
141
+ | `DEBUG_AGENT_ENABLED` | `true` | Enable/disable |
142
+ | `DEBUG_AGENT_BASE_PATH` | `/agent` | URL path |
143
+ | `LLM_BASE_URL` | `https://api.openai.com/v1` | LLM endpoint |
144
+ | `LLM_API_KEY` | (required) | API key |
145
+ | `LLM_MODEL` | `gpt-4o` | Model name |
146
+ | `LLM_TEMPERATURE` | `0.3` | Sampling temp |
147
+ | `LLM_MAX_TOOL_ROUNDS` | `10` | Max tool rounds |
148
+
149
+ ## Run the Demo
150
+
151
+ ```bash
152
+ pip install -e ".[dev]"
153
+ export LLM_API_KEY=your-key
154
+ python demo/app.py
155
+ # Open http://localhost:8000/agent
156
+ ```
157
+
158
+ ## License
159
+
160
+ MIT
@@ -0,0 +1,123 @@
1
+ # Python Debug Agent
2
+
3
+ An AI-powered runtime debugging agent that embeds directly into your Python web application. Add one dependency, configure an LLM key, and chat with your live app at `/agent` to inspect memory, threads, routes, HTTP requests, GC stats, and more.
4
+
5
+ ## Quick Start
6
+
7
+ ### 1. Install
8
+
9
+ ```bash
10
+ pip install debug-agent[fastapi]
11
+ ```
12
+
13
+ ### 2. Integrate (FastAPI)
14
+
15
+ ```python
16
+ from fastapi import FastAPI
17
+ from debug_agent.middleware import create_fastapi_router
18
+
19
+ app = FastAPI()
20
+
21
+ # One line to integrate
22
+ app.include_router(create_fastapi_router())
23
+ ```
24
+
25
+ ### 3. Configure LLM
26
+
27
+ ```bash
28
+ export LLM_API_KEY=your-key
29
+ export LLM_BASE_URL=https://api.openai.com/v1 # optional
30
+ export LLM_MODEL=gpt-4o # optional
31
+ ```
32
+
33
+ ### 4. Run and open
34
+
35
+ ```
36
+ http://localhost:8000/agent
37
+ ```
38
+
39
+ ## Framework Integrations
40
+
41
+ ### FastAPI / Starlette
42
+
43
+ ```python
44
+ from debug_agent.middleware import create_fastapi_router
45
+ app.include_router(create_fastapi_router())
46
+ ```
47
+
48
+ ### Flask
49
+
50
+ ```python
51
+ from flask import Flask
52
+ from debug_agent.middleware import create_flask_blueprint
53
+ app = Flask(__name__)
54
+ app.register_blueprint(create_flask_blueprint())
55
+ ```
56
+
57
+ ### Any ASGI App (Starlette Mount)
58
+
59
+ ```python
60
+ from starlette.routing import Mount
61
+ from debug_agent.middleware import create_starlette_app
62
+ routes = [Mount("/agent", app=create_starlette_app())]
63
+ ```
64
+
65
+ ## Built-in Tools (18+)
66
+
67
+ | Tool | Description |
68
+ |------|-------------|
69
+ | `get_gc_stats` | GC collection counts per generation |
70
+ | `get_memory_summary` | RSS, object counts, top types |
71
+ | `trigger_gc` | Force GC and show before/after |
72
+ | `get_thread_summary` | Thread count, names, daemon status |
73
+ | `get_thread_dump` | Stack traces for all threads |
74
+ | `get_runtime_info` | Python version, platform, PID |
75
+ | `get_memory_allocations` | tracemalloc top allocations |
76
+ | `get_routes` | List all web routes/endpoints |
77
+ | `get_middleware` | List registered middleware |
78
+ | `get_installed_packages` | Installed pip packages |
79
+ | `get_environment_variables` | Environment variables (masked secrets) |
80
+ | `get_recent_requests` | HTTP request ring buffer |
81
+ | `get_slow_requests` | Slowest requests sorted by duration |
82
+ | `get_error_requests` | Error requests (4xx/5xx) |
83
+ | `get_request_stats` | P50/P95/P99 latency, error rate |
84
+ | `get_process_info` | PID, CPU time, container detection |
85
+ | `get_system_info` | OS, CPU cores, load average |
86
+ | `get_disk_usage` | Disk usage for working directory |
87
+
88
+ ## Custom Tools
89
+
90
+ ```python
91
+ from debug_agent import debug_tool, ToolParam
92
+
93
+ @debug_tool("check_db_pool", "Check database connection pool stats")
94
+ def check_db_pool() -> dict:
95
+ return {"active": 5, "idle": 10, "max": 20}
96
+ ```
97
+
98
+ That's it. The tool is auto-discovered and made available to the LLM.
99
+
100
+ ## Configuration
101
+
102
+ | Env Var | Default | Description |
103
+ |---------|---------|-------------|
104
+ | `DEBUG_AGENT_ENABLED` | `true` | Enable/disable |
105
+ | `DEBUG_AGENT_BASE_PATH` | `/agent` | URL path |
106
+ | `LLM_BASE_URL` | `https://api.openai.com/v1` | LLM endpoint |
107
+ | `LLM_API_KEY` | (required) | API key |
108
+ | `LLM_MODEL` | `gpt-4o` | Model name |
109
+ | `LLM_TEMPERATURE` | `0.3` | Sampling temp |
110
+ | `LLM_MAX_TOOL_ROUNDS` | `10` | Max tool rounds |
111
+
112
+ ## Run the Demo
113
+
114
+ ```bash
115
+ pip install -e ".[dev]"
116
+ export LLM_API_KEY=your-key
117
+ python demo/app.py
118
+ # Open http://localhost:8000/agent
119
+ ```
120
+
121
+ ## License
122
+
123
+ MIT
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "debug-agent-py"
7
+ version = "0.2.1"
8
+ description = "AI-powered runtime debugging agent for Python web applications"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ {name = "ggcode", email = "noreply@ggcode.dev"}
14
+ ]
15
+ keywords = ["debug", "debugging", "ai", "llm", "runtime", "diagnostics", "flask", "fastapi", "django", "agent", "observability"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Topic :: Software Development :: Debuggers",
27
+ ]
28
+ dependencies = [
29
+ "httpx>=0.27",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ fastapi = ["fastapi>=0.100", "starlette>=0.27", "uvicorn>=0.23"]
34
+ flask = ["flask>=2.3"]
35
+ django = ["django>=4.2"]
36
+ dev = ["fastapi", "uvicorn", "flask", "pytest"]
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/topcheer/python-debug-agent"
40
+ Repository = "https://github.com/topcheer/python-debug-agent"
41
+ Issues = "https://github.com/topcheer/python-debug-agent/issues"
42
+
43
+ [tool.setuptools.packages.find]
44
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,23 @@
1
+ """Debug Agent — AI-powered runtime debugging for Python applications."""
2
+
3
+ from debug_agent.config import AgentConfig
4
+ from debug_agent.tool_registry import debug_tool, ToolParam, registry
5
+ from debug_agent.engine import DebugEngine, ChatCallback
6
+ from debug_agent.chat_session import ChatSession
7
+ from debug_agent.system_prompt_builder import SystemPromptBuilder
8
+ from debug_agent.context_compressor import ContextCompressor, CompressionResult
9
+ from debug_agent.llm_client import LLMClient, StreamHandler
10
+
11
+ __version__ = "0.1.0"
12
+ __all__ = [
13
+ "AgentConfig", "debug_tool", "ToolParam", "registry", "DebugEngine", "ChatCallback",
14
+ "ChatSession", "SystemPromptBuilder", "ContextCompressor", "CompressionResult",
15
+ "LLMClient", "StreamHandler", "setup",
16
+ ]
17
+
18
+
19
+ def setup(config: AgentConfig | None = None) -> DebugEngine:
20
+ """Initialize the debug agent and return the engine instance."""
21
+ from debug_agent import inspectors # noqa: F401 — triggers registration
22
+ cfg = config or AgentConfig.from_env()
23
+ return DebugEngine(cfg)
@@ -0,0 +1,45 @@
1
+ """Chat session management with token tracking."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from typing import Any
7
+
8
+
9
+ class ChatSession:
10
+ """Manages conversation history and cumulative token usage."""
11
+
12
+ def __init__(self, session_id: str):
13
+ self.session_id = session_id
14
+ self.created_at = time.time()
15
+ self.messages: list[dict[str, Any]] = []
16
+ self.last_active_at = self.created_at
17
+
18
+ self.last_token_usage: dict | None = None
19
+ self.cumulative_prompt_tokens: int = 0
20
+ self.cumulative_completion_tokens: int = 0
21
+
22
+ def add_message(self, message: dict[str, Any]):
23
+ self.messages.append(message)
24
+ self.last_active_at = time.time()
25
+
26
+ def replace_messages(self, new_messages: list[dict[str, Any]]):
27
+ self.messages = new_messages
28
+ self.last_active_at = time.time()
29
+
30
+ def record_token_usage(self, usage: dict | None):
31
+ if not usage:
32
+ return
33
+ self.last_token_usage = usage
34
+ self.cumulative_prompt_tokens = usage.get("prompt_tokens", 0)
35
+ self.cumulative_completion_tokens += usage.get("completion_tokens", 0)
36
+
37
+ def get_current_context_tokens(self) -> int:
38
+ return self.cumulative_prompt_tokens
39
+
40
+ def clear(self):
41
+ self.messages = []
42
+ self.last_token_usage = None
43
+ self.cumulative_prompt_tokens = 0
44
+ self.cumulative_completion_tokens = 0
45
+ self.last_active_at = time.time()
@@ -0,0 +1,48 @@
1
+ """Configuration for Debug Agent."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from dataclasses import dataclass, field
7
+
8
+
9
+ @dataclass
10
+ class LLMConfig:
11
+ base_url: str = "https://open.bigmodel.cn/api/coding/paas/v4"
12
+ api_key: str = ""
13
+ model: str = "glm-5.2"
14
+ temperature: float = 0.3
15
+ max_tokens: int = 4096
16
+ max_tool_rounds: int = 25
17
+ timeout_seconds: int = 120
18
+ max_retries: int = 3
19
+ retry_base_delay_ms: int = 1000
20
+ retry_max_delay_ms: int = 30000
21
+ context_window_tokens: int = 100000
22
+
23
+
24
+ @dataclass
25
+ class AgentConfig:
26
+ enabled: bool = True
27
+ base_path: str = "/agent"
28
+ llm: LLMConfig = field(default_factory=LLMConfig)
29
+
30
+ @classmethod
31
+ def from_env(cls) -> AgentConfig:
32
+ return cls(
33
+ enabled=os.getenv("DEBUG_AGENT_ENABLED", "true").lower() == "true",
34
+ base_path=os.getenv("DEBUG_AGENT_BASE_PATH", "/agent"),
35
+ llm=LLMConfig(
36
+ base_url=os.getenv("LLM_BASE_URL", "https://open.bigmodel.cn/api/coding/paas/v4"),
37
+ api_key=os.getenv("LLM_API_KEY") or os.getenv("OPENAI_API_KEY", ""),
38
+ model=os.getenv("LLM_MODEL", "glm-5.2"),
39
+ temperature=float(os.getenv("LLM_TEMPERATURE", "0.3")),
40
+ max_tokens=int(os.getenv("LLM_MAX_TOKENS", "4096")),
41
+ max_tool_rounds=int(os.getenv("LLM_MAX_TOOL_ROUNDS", "25")),
42
+ timeout_seconds=int(os.getenv("LLM_TIMEOUT_SECONDS", "120")),
43
+ max_retries=int(os.getenv("LLM_MAX_RETRIES", "3")),
44
+ retry_base_delay_ms=int(os.getenv("LLM_RETRY_BASE_DELAY_MS", "1000")),
45
+ retry_max_delay_ms=int(os.getenv("LLM_RETRY_MAX_DELAY_MS", "30000")),
46
+ context_window_tokens=int(os.getenv("LLM_CONTEXT_WINDOW_TOKENS", "100000")),
47
+ ),
48
+ )
@@ -0,0 +1,157 @@
1
+ """Context compressor — summarizes older conversation rounds via LLM."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ from typing import Any
8
+
9
+ from debug_agent.chat_session import ChatSession
10
+ from debug_agent.llm_client import LLMClient
11
+
12
+ logger = logging.getLogger("debug_agent")
13
+
14
+
15
+ class CompressionResult:
16
+ def __init__(self, original_tokens: int, compressed_tokens: int, removed_rounds: int, strategy: str):
17
+ self.original_tokens = original_tokens
18
+ self.compressed_tokens = compressed_tokens
19
+ self.removed_rounds = removed_rounds
20
+ self.strategy = strategy
21
+
22
+
23
+ class ContextCompressor:
24
+ def __init__(self, llm: LLMClient, model: str, temperature: float, max_context_tokens: int, recent_rounds_to_keep: int = 3):
25
+ self.llm = llm
26
+ self.model = model
27
+ self.temperature = temperature
28
+ self.max_context_tokens = max_context_tokens
29
+ self.recent_rounds_to_keep = recent_rounds_to_keep
30
+
31
+ def needs_compression(self, current_tokens: int) -> bool:
32
+ return current_tokens > self.max_context_tokens * 0.75
33
+
34
+ def compress(self, session: ChatSession) -> CompressionResult | None:
35
+ original_tokens = session.get_current_context_tokens()
36
+ if not self.needs_compression(original_tokens):
37
+ return None
38
+
39
+ rounds = self._identify_rounds(session.messages)
40
+
41
+ keep_count = min(self.recent_rounds_to_keep, len(rounds) - 1)
42
+ if keep_count < 1:
43
+ return None
44
+
45
+ summarize_count = len(rounds) - keep_count
46
+
47
+ to_summarize = []
48
+ for i in range(summarize_count):
49
+ to_summarize.extend(rounds[i])
50
+
51
+ to_keep = []
52
+ for i in range(summarize_count, len(rounds)):
53
+ to_keep.extend(rounds[i])
54
+
55
+ try:
56
+ summary = self._summarize_with_llm(to_summarize)
57
+ except Exception as e:
58
+ logger.warning(f"LLM summarization failed: {e}")
59
+ summary = self._fallback_truncate(to_summarize)
60
+
61
+ compressed = [
62
+ {"role": "system", "content": f"[Previous conversation summary — {summarize_count} rounds compressed]\n\n{summary}"},
63
+ *to_keep,
64
+ ]
65
+ compressed_tokens = self._estimate_tokens(compressed)
66
+ session.replace_messages(compressed)
67
+
68
+ return CompressionResult(original_tokens, compressed_tokens, summarize_count, f"LLM summarized {summarize_count} rounds")
69
+
70
+ def _summarize_with_llm(self, old_messages: list[dict]) -> str:
71
+ conversation_text = ""
72
+ for msg in old_messages:
73
+ role = msg.get("role", "")
74
+ content = msg.get("content", "")
75
+ if role == "user":
76
+ conversation_text += f"[User] {content}\n\n"
77
+ elif role == "assistant":
78
+ if content:
79
+ conversation_text += f"[Assistant] {content}\n\n"
80
+ for tc in msg.get("tool_calls", []):
81
+ fn = tc.get("function", {})
82
+ conversation_text += f"[Tool Call] {fn.get('name', '')}({fn.get('arguments', '')})\n\n"
83
+ elif role == "tool":
84
+ if len(content) > 2000:
85
+ content = content[:2000] + "...[truncated]"
86
+ conversation_text += f"[Tool Result] {content}\n\n"
87
+
88
+ prompt = """You are a conversation summarizer for a Python debugging assistant.
89
+ Summarize the KEY diagnostic findings from the conversation below concisely.
90
+
91
+ Focus on preserving:
92
+ - Problems investigated and their root causes (if found)
93
+ - Key tool results: actual numbers, statuses, error messages, configuration values
94
+ - Recommendations or fixes already suggested
95
+ - Any unresolved issues or follow-up actions pending
96
+
97
+ Rules:
98
+ - Be concise but preserve ALL important data points
99
+ - Use bullet points
100
+ - Do NOT include full JSON dumps
101
+ - Keep it under 600 words"""
102
+
103
+ response = self.llm.chat(
104
+ [
105
+ {"role": "system", "content": prompt},
106
+ {"role": "user", "content": f"Conversation to summarize:\n\n{conversation_text}"},
107
+ ],
108
+ tools=None,
109
+ )
110
+ return response["choices"][0]["message"]["content"]
111
+
112
+ def _fallback_truncate(self, messages: list[dict]) -> str:
113
+ sb = "Previous conversation summary (fallback):\n\n"
114
+ for msg in messages:
115
+ if msg.get("role") == "user" and msg.get("content"):
116
+ q = msg["content"][:100] + "..." if len(msg["content"]) > 100 else msg["content"]
117
+ sb += f"- User asked: {q}\n"
118
+ if msg.get("role") == "assistant" and msg.get("tool_calls"):
119
+ for tc in msg["tool_calls"]:
120
+ sb += f"- Called tool: {tc.get('function', {}).get('name', '')}\n"
121
+ return sb
122
+
123
+ def _identify_rounds(self, messages: list[dict]) -> list[list[dict]]:
124
+ rounds = []
125
+ current = []
126
+ has_assistant = False
127
+
128
+ for msg in messages:
129
+ role = msg.get("role", "")
130
+ if role == "user":
131
+ if current:
132
+ rounds.append(current)
133
+ current = []
134
+ has_assistant = False
135
+ current.append(msg)
136
+ elif role == "assistant":
137
+ if has_assistant:
138
+ rounds.append(current)
139
+ current = []
140
+ has_assistant = False
141
+ current.append(msg)
142
+ has_assistant = True
143
+ else:
144
+ current.append(msg)
145
+
146
+ if current:
147
+ rounds.append(current)
148
+ return rounds
149
+
150
+ def _estimate_tokens(self, messages: list[dict]) -> int:
151
+ chars = 0
152
+ for msg in messages:
153
+ chars += len(msg.get("content", "") or "")
154
+ for tc in msg.get("tool_calls", []):
155
+ fn = tc.get("function", {})
156
+ chars += len(fn.get("name", "")) + len(fn.get("arguments", ""))
157
+ return chars // 4