debug-agent-py 0.2.1__py3-none-any.whl

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.
@@ -0,0 +1,101 @@
1
+ """Dynamic system prompt builder — generates prompt from registered tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from debug_agent.tool_registry import registry
6
+
7
+ CATEGORY_MAP = {
8
+ "heap": "Memory & GC",
9
+ "gc": "Memory & GC",
10
+ "memory": "Memory & GC",
11
+ "tracemalloc": "Memory & GC",
12
+ "object_count": "Memory & GC",
13
+ "ref_cycle": "Memory & GC",
14
+ "process": "Process Info",
15
+ "runtime": "Runtime Info",
16
+ "thread": "Threads",
17
+ "system": "System Info",
18
+ "cpu": "System Info",
19
+ "disk": "System Info",
20
+ "uptime": "System Info",
21
+ "routes": "Framework",
22
+ "middleware": "Framework",
23
+ "recent": "HTTP Requests",
24
+ "slow": "HTTP Requests",
25
+ "error": "HTTP Requests",
26
+ "request": "HTTP Requests",
27
+ "module": "Modules",
28
+ "import": "Modules",
29
+ "environment": "Environment & Config",
30
+ "installed": "Dependencies",
31
+ "sqlalchemy": "Database",
32
+ "db_": "Database",
33
+ "async": "Async Tasks",
34
+ "event_loop": "Async Tasks",
35
+ "package": "Dependencies",
36
+ }
37
+
38
+
39
+ class SystemPromptBuilder:
40
+ """Builds system prompt dynamically from registered tools."""
41
+
42
+ def __init__(self, tool_registry=registry):
43
+ self.registry = tool_registry
44
+
45
+ def build(self) -> str:
46
+ categories = self._categorize_tools()
47
+
48
+ sb = "You are an expert Python runtime debugging assistant.\n"
49
+ sb += "You are running INSIDE the developer's Python application and have direct access\n"
50
+ sb += "to its runtime state through diagnostic tools.\n\n"
51
+ sb += "## Your Capabilities\n"
52
+ sb += "You can call tools to inspect the live application. Here are ALL available tools,\n"
53
+ sb += "grouped by category:\n\n"
54
+
55
+ for category in sorted(categories.keys()):
56
+ tools = categories[category]
57
+ sb += f"**{category}**\n"
58
+ for tool in tools:
59
+ sb += f"- `{tool['name']}`: {self._truncate(tool['description'])}\n"
60
+ sb += "\n"
61
+
62
+ sb += "## Workflow\n"
63
+ sb += "1. Understand the developer's problem description\n"
64
+ sb += "2. Proactively call the most relevant tools to gather diagnostic data\n"
65
+ sb += "3. Analyze the collected data to identify root causes\n"
66
+ sb += "4. Provide clear, actionable solutions with data evidence\n\n"
67
+ sb += "## Guidelines\n"
68
+ sb += "- Be proactive: gather data with tools before answering\n"
69
+ sb += "- Always present data in a readable format (tables, bullet points)\n"
70
+ sb += "- Respond in the same language the developer uses\n"
71
+ sb += "- When you find a problem, explain the root cause and give concrete fix suggestions\n"
72
+ sb += "- You can call multiple tools in parallel if they are independent\n"
73
+
74
+ return sb
75
+
76
+ def _categorize_tools(self) -> dict[str, list[dict]]:
77
+ categories: dict[str, list[dict]] = {}
78
+ for schema in self.registry.all_schemas():
79
+ fn = schema["function"]
80
+ name = fn["name"]
81
+ desc = fn["description"]
82
+ category = self._extract_category(name)
83
+ if category not in categories:
84
+ categories[category] = []
85
+ categories[category].append({"name": name, "description": desc})
86
+ return categories
87
+
88
+ def _extract_category(self, tool_name: str) -> str:
89
+ name_lower = tool_name.lower()
90
+ for keyword, category in CATEGORY_MAP.items():
91
+ if keyword in name_lower:
92
+ return category
93
+ return "Other Tools"
94
+
95
+ def _truncate(self, desc: str) -> str:
96
+ if not desc:
97
+ return ""
98
+ period = desc.find(".")
99
+ if 0 < period < 150:
100
+ return desc[: period + 1]
101
+ return desc[:120] + "..." if len(desc) > 120 else desc
@@ -0,0 +1,121 @@
1
+ """Tool framework: decorator-based registration and execution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect as pyinspect
6
+ import json
7
+ import typing
8
+ from dataclasses import dataclass, field
9
+ from typing import Any, Callable, get_type_hints
10
+
11
+
12
+ @dataclass
13
+ class ToolParam:
14
+ """Metadata for a tool parameter."""
15
+ description: str = ""
16
+ required: bool = True
17
+ default: Any = None
18
+
19
+
20
+ @dataclass
21
+ class ToolDefinition:
22
+ name: str
23
+ description: str
24
+ func: Callable
25
+ params: dict[str, ToolParam] = field(default_factory=dict)
26
+
27
+ def schema(self) -> dict:
28
+ """Generate OpenAI function-calling schema."""
29
+ hints = get_type_hints(self.func)
30
+ sig = pyinspect.signature(self.func)
31
+ properties = {}
32
+ required = []
33
+
34
+ for pname, param in sig.parameters.items():
35
+ if pname == "ctx":
36
+ continue
37
+ ptype = hints.get(pname, str)
38
+ json_type = _python_type_to_json(ptype)
39
+ tp = self.params.get(pname)
40
+ desc = tp.description if tp else ""
41
+ properties[pname] = {"type": json_type, "description": desc}
42
+
43
+ if param.default is pyinspect.Parameter.empty and (not tp or tp.required):
44
+ required.append(pname)
45
+
46
+ return {
47
+ "type": "function",
48
+ "function": {
49
+ "name": self.name,
50
+ "description": self.description,
51
+ "parameters": {
52
+ "type": "object",
53
+ "properties": properties,
54
+ "required": required,
55
+ },
56
+ },
57
+ }
58
+
59
+ def execute(self, **kwargs) -> Any:
60
+ """Call the underlying function."""
61
+ return self.func(**kwargs)
62
+
63
+
64
+ def _python_type_to_json(ptype) -> str:
65
+ mapping = {int: "integer", float: "number", bool: "boolean", str: "string", list: "array", dict: "object"}
66
+ return mapping.get(ptype, "string")
67
+
68
+
69
+ class ToolRegistry:
70
+ """Global registry for debug tools."""
71
+
72
+ def __init__(self):
73
+ self._tools: dict[str, ToolDefinition] = {}
74
+
75
+ def register(self, tool: ToolDefinition):
76
+ self._tools[tool.name] = tool
77
+
78
+ def get(self, name: str) -> ToolDefinition | None:
79
+ return self._tools.get(name)
80
+
81
+ def all_schemas(self) -> list[dict]:
82
+ return [t.schema() for t in self._tools.values()]
83
+
84
+ def execute(self, name: str, args: dict) -> Any:
85
+ tool = self._tools.get(name)
86
+ if not tool:
87
+ return {"error": f"Unknown tool: {name}"}
88
+ try:
89
+ result = tool.execute(**args)
90
+ return result
91
+ except Exception as e:
92
+ return {"error": str(e)}
93
+
94
+ def names(self) -> list[str]:
95
+ return list(self._tools.keys())
96
+
97
+
98
+ # Global singleton
99
+ registry = ToolRegistry()
100
+
101
+
102
+ def debug_tool(name: str, description: str, params: dict[str, ToolParam] | None = None):
103
+ """Decorate a function to register it as a debug tool.
104
+
105
+ Usage:
106
+ @debug_tool("get_memory", "Get memory stats", {"detailed": ToolParam("Include details", required=False)})
107
+ def get_memory(detailed: bool = False) -> dict:
108
+ ...
109
+ """
110
+
111
+ def decorator(func: Callable) -> Callable:
112
+ tool = ToolDefinition(
113
+ name=name,
114
+ description=description,
115
+ func=func,
116
+ params=params or {},
117
+ )
118
+ registry.register(tool)
119
+ return func
120
+
121
+ return decorator
File without changes