ai-logmaster 1.0.0__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.
- ai_logmaster/__init__.py +10 -0
- ai_logmaster/agent.py +356 -0
- ai_logmaster/analyzer.py +343 -0
- ai_logmaster/cli.py +214 -0
- ai_logmaster/config/__init__.py +137 -0
- ai_logmaster/config/config.yaml.example +89 -0
- ai_logmaster-1.0.0.dist-info/METADATA +340 -0
- ai_logmaster-1.0.0.dist-info/RECORD +12 -0
- ai_logmaster-1.0.0.dist-info/WHEEL +5 -0
- ai_logmaster-1.0.0.dist-info/entry_points.txt +2 -0
- ai_logmaster-1.0.0.dist-info/licenses/LICENSE +21 -0
- ai_logmaster-1.0.0.dist-info/top_level.txt +1 -0
ai_logmaster/__init__.py
ADDED
ai_logmaster/agent.py
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LangGraph Agent for Smart Error Analysis
|
|
3
|
+
- Decides when to fetch documentation vs use cached knowledge
|
|
4
|
+
- Optimizes API usage to stay within free quotas
|
|
5
|
+
- Uses DuckDuckGo search for documentation when needed
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
from typing import TypedDict, Annotated, Literal
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
|
|
11
|
+
load_dotenv()
|
|
12
|
+
|
|
13
|
+
# State definition
|
|
14
|
+
class AgentState(TypedDict):
|
|
15
|
+
error_context: str
|
|
16
|
+
error_type: str
|
|
17
|
+
needs_docs: bool
|
|
18
|
+
documentation: str
|
|
19
|
+
analysis: dict
|
|
20
|
+
api_calls: int
|
|
21
|
+
|
|
22
|
+
def create_agent():
|
|
23
|
+
"""Create the LangGraph agent"""
|
|
24
|
+
try:
|
|
25
|
+
from langgraph.graph import StateGraph, END
|
|
26
|
+
from langchain_openai import ChatOpenAI
|
|
27
|
+
from langchain_community.tools import DuckDuckGoSearchRun
|
|
28
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
29
|
+
|
|
30
|
+
# Initialize LLM (we'll use it sparingly)
|
|
31
|
+
llm = ChatOpenAI(
|
|
32
|
+
model="mistralai/mistral-small-3.1-24b-instruct-2503",
|
|
33
|
+
base_url="https://integrate.api.nvidia.com/v1",
|
|
34
|
+
api_key=os.environ.get("NVIDIA_API_KEY", ""),
|
|
35
|
+
temperature=0.1, # Lower temperature for consistency
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Initialize search tool
|
|
39
|
+
search = DuckDuckGoSearchRun()
|
|
40
|
+
|
|
41
|
+
# Node 1: Classify Error (Pattern-based, no API call)
|
|
42
|
+
def classify_error(state: AgentState) -> AgentState:
|
|
43
|
+
"""Classify error type using pattern matching (free)"""
|
|
44
|
+
print("[AGENT] Classifying error type...")
|
|
45
|
+
|
|
46
|
+
context = state["error_context"]
|
|
47
|
+
error_type = "unknown"
|
|
48
|
+
needs_docs = False
|
|
49
|
+
|
|
50
|
+
# Pattern-based classification (no API needed)
|
|
51
|
+
patterns = {
|
|
52
|
+
"connection": ["ConnectionRefused", "Connection refused", "ECONNREFUSED"],
|
|
53
|
+
"import": ["ModuleNotFoundError", "ImportError", "No module named"],
|
|
54
|
+
"memory": ["MemoryError", "Out of memory", "OOM"],
|
|
55
|
+
"timeout": ["TimeoutError", "timed out", "Read timed out"],
|
|
56
|
+
"permission": ["PermissionError", "Permission denied"],
|
|
57
|
+
"syntax": ["SyntaxError", "IndentationError"],
|
|
58
|
+
"type": ["TypeError", "AttributeError"],
|
|
59
|
+
"value": ["ValueError", "KeyError"],
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for err_type, keywords in patterns.items():
|
|
63
|
+
if any(kw in context for kw in keywords):
|
|
64
|
+
error_type = err_type
|
|
65
|
+
# Only fetch docs for complex/uncommon errors
|
|
66
|
+
needs_docs = err_type in ["syntax", "type", "value", "unknown"]
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
state["error_type"] = error_type
|
|
70
|
+
state["needs_docs"] = needs_docs
|
|
71
|
+
state["api_calls"] = 0
|
|
72
|
+
|
|
73
|
+
print(f"[AGENT] Error type: {error_type}, Needs docs: {needs_docs}")
|
|
74
|
+
return state
|
|
75
|
+
|
|
76
|
+
# Node 2: Fetch Documentation (conditional)
|
|
77
|
+
def fetch_documentation(state: AgentState) -> AgentState:
|
|
78
|
+
"""Fetch relevant documentation if needed"""
|
|
79
|
+
if not state["needs_docs"]:
|
|
80
|
+
print("[AGENT] Using cached knowledge, skipping doc fetch")
|
|
81
|
+
state["documentation"] = ""
|
|
82
|
+
return state
|
|
83
|
+
|
|
84
|
+
print("[AGENT] Fetching documentation from web...")
|
|
85
|
+
|
|
86
|
+
# Extract error message for search
|
|
87
|
+
lines = state["error_context"].split('\n')
|
|
88
|
+
error_msg = ""
|
|
89
|
+
for line in lines:
|
|
90
|
+
if "Error:" in line or "Exception:" in line:
|
|
91
|
+
error_msg = line.strip()
|
|
92
|
+
break
|
|
93
|
+
|
|
94
|
+
if not error_msg:
|
|
95
|
+
error_msg = f"{state['error_type']} error python"
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
# Search for documentation
|
|
99
|
+
query = f"{error_msg} official documentation solution"
|
|
100
|
+
docs = search.run(query)
|
|
101
|
+
state["documentation"] = docs[:1000] # Limit to 1000 chars
|
|
102
|
+
print(f"[AGENT] ✓ Fetched {len(docs)} chars of documentation")
|
|
103
|
+
except Exception as e:
|
|
104
|
+
print(f"[AGENT] ✗ Doc fetch failed: {e}")
|
|
105
|
+
state["documentation"] = ""
|
|
106
|
+
|
|
107
|
+
return state
|
|
108
|
+
|
|
109
|
+
# Node 3: Analyze with AI (only if needed)
|
|
110
|
+
def analyze_with_ai(state: AgentState) -> AgentState:
|
|
111
|
+
"""Use AI for analysis (costs API calls)"""
|
|
112
|
+
print("[AGENT] Analyzing with AI...")
|
|
113
|
+
|
|
114
|
+
# Build context-aware prompt
|
|
115
|
+
if state["documentation"]:
|
|
116
|
+
prompt_template = """You are a debugging expert. Analyze this error using the documentation.
|
|
117
|
+
|
|
118
|
+
Documentation Context:
|
|
119
|
+
{docs}
|
|
120
|
+
|
|
121
|
+
Error:
|
|
122
|
+
{error}
|
|
123
|
+
|
|
124
|
+
Provide in this EXACT format:
|
|
125
|
+
TYPE: <error type>
|
|
126
|
+
CAUSE: <root cause>
|
|
127
|
+
FIX1: <specific fix>
|
|
128
|
+
FIX2: <alternative fix>
|
|
129
|
+
FIX3: <prevention tip>"""
|
|
130
|
+
|
|
131
|
+
prompt = ChatPromptTemplate.from_template(prompt_template)
|
|
132
|
+
chain = prompt | llm
|
|
133
|
+
|
|
134
|
+
response = chain.invoke({
|
|
135
|
+
"docs": state["documentation"],
|
|
136
|
+
"error": state["error_context"]
|
|
137
|
+
})
|
|
138
|
+
else:
|
|
139
|
+
# Simpler prompt for known errors
|
|
140
|
+
prompt_template = """Analyze this {error_type} error and provide fixes.
|
|
141
|
+
|
|
142
|
+
Error:
|
|
143
|
+
{error}
|
|
144
|
+
|
|
145
|
+
Format:
|
|
146
|
+
TYPE: <type>
|
|
147
|
+
CAUSE: <cause>
|
|
148
|
+
FIX1: <fix>
|
|
149
|
+
FIX2: <fix>
|
|
150
|
+
FIX3: <fix>"""
|
|
151
|
+
|
|
152
|
+
prompt = ChatPromptTemplate.from_template(prompt_template)
|
|
153
|
+
chain = prompt | llm
|
|
154
|
+
|
|
155
|
+
response = chain.invoke({
|
|
156
|
+
"error_type": state["error_type"],
|
|
157
|
+
"error": state["error_context"][:500] # Limit context to save tokens
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
# Parse response
|
|
161
|
+
content = response.content
|
|
162
|
+
analysis = parse_response(content)
|
|
163
|
+
analysis["method"] = "AI + Docs" if state["documentation"] else "AI"
|
|
164
|
+
analysis["confidence"] = 0.90 if state["documentation"] else 0.75
|
|
165
|
+
|
|
166
|
+
state["analysis"] = analysis
|
|
167
|
+
state["api_calls"] += 1
|
|
168
|
+
|
|
169
|
+
print(f"[AGENT] ✓ Analysis complete (API calls: {state['api_calls']})")
|
|
170
|
+
return state
|
|
171
|
+
|
|
172
|
+
# Node 4: Use Cached Solution (no API call)
|
|
173
|
+
def use_cached_solution(state: AgentState) -> AgentState:
|
|
174
|
+
"""Use pre-defined solutions for common errors"""
|
|
175
|
+
print("[AGENT] Using cached solution...")
|
|
176
|
+
|
|
177
|
+
solutions = {
|
|
178
|
+
"connection": {
|
|
179
|
+
"type": "Connection Error",
|
|
180
|
+
"cause": "Service is not running or unreachable",
|
|
181
|
+
"fixes": [
|
|
182
|
+
"Check if the service is running",
|
|
183
|
+
"Verify host and port configuration",
|
|
184
|
+
"Check firewall and network settings"
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
"import": {
|
|
188
|
+
"type": "Import Error",
|
|
189
|
+
"cause": "Required package is not installed",
|
|
190
|
+
"fixes": [
|
|
191
|
+
"Install package: pip install <package-name>",
|
|
192
|
+
"Activate virtual environment",
|
|
193
|
+
"Check package name spelling"
|
|
194
|
+
]
|
|
195
|
+
},
|
|
196
|
+
"memory": {
|
|
197
|
+
"type": "Memory Error",
|
|
198
|
+
"cause": "Insufficient memory available",
|
|
199
|
+
"fixes": [
|
|
200
|
+
"Reduce batch size or data volume",
|
|
201
|
+
"Use generators instead of lists",
|
|
202
|
+
"Increase system memory"
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
"timeout": {
|
|
206
|
+
"type": "Timeout Error",
|
|
207
|
+
"cause": "Operation took too long",
|
|
208
|
+
"fixes": [
|
|
209
|
+
"Increase timeout value",
|
|
210
|
+
"Optimize slow operations",
|
|
211
|
+
"Check network latency"
|
|
212
|
+
]
|
|
213
|
+
},
|
|
214
|
+
"permission": {
|
|
215
|
+
"type": "Permission Error",
|
|
216
|
+
"cause": "Insufficient file/resource permissions",
|
|
217
|
+
"fixes": [
|
|
218
|
+
"Check file permissions: ls -la",
|
|
219
|
+
"Run with appropriate privileges",
|
|
220
|
+
"Verify user access rights"
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
error_type = state["error_type"]
|
|
226
|
+
if error_type in solutions:
|
|
227
|
+
state["analysis"] = {
|
|
228
|
+
**solutions[error_type],
|
|
229
|
+
"method": "Cached",
|
|
230
|
+
"confidence": 0.80
|
|
231
|
+
}
|
|
232
|
+
else:
|
|
233
|
+
# Fallback
|
|
234
|
+
state["analysis"] = {
|
|
235
|
+
"type": "Unknown Error",
|
|
236
|
+
"cause": "Unable to classify",
|
|
237
|
+
"fixes": [
|
|
238
|
+
"Read error message carefully",
|
|
239
|
+
"Search error online",
|
|
240
|
+
"Check recent code changes"
|
|
241
|
+
],
|
|
242
|
+
"method": "Generic",
|
|
243
|
+
"confidence": 0.40
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
print(f"[AGENT] ✓ Using cached solution for {error_type}")
|
|
247
|
+
return state
|
|
248
|
+
|
|
249
|
+
# Decision function: Should we use AI?
|
|
250
|
+
def should_use_ai(state: AgentState) -> Literal["ai", "cached"]:
|
|
251
|
+
"""Decide whether to use AI or cached solution"""
|
|
252
|
+
# Use AI only for complex/unknown errors or when docs are available
|
|
253
|
+
if state["needs_docs"] or state["error_type"] == "unknown":
|
|
254
|
+
return "ai"
|
|
255
|
+
return "cached"
|
|
256
|
+
|
|
257
|
+
# Build the graph
|
|
258
|
+
workflow = StateGraph(AgentState)
|
|
259
|
+
|
|
260
|
+
# Add nodes
|
|
261
|
+
workflow.add_node("classify", classify_error)
|
|
262
|
+
workflow.add_node("fetch_docs", fetch_documentation)
|
|
263
|
+
workflow.add_node("ai_analysis", analyze_with_ai)
|
|
264
|
+
workflow.add_node("cached_solution", use_cached_solution)
|
|
265
|
+
|
|
266
|
+
# Add edges
|
|
267
|
+
workflow.set_entry_point("classify")
|
|
268
|
+
workflow.add_edge("classify", "fetch_docs")
|
|
269
|
+
workflow.add_conditional_edges(
|
|
270
|
+
"fetch_docs",
|
|
271
|
+
should_use_ai,
|
|
272
|
+
{
|
|
273
|
+
"ai": "ai_analysis",
|
|
274
|
+
"cached": "cached_solution"
|
|
275
|
+
}
|
|
276
|
+
)
|
|
277
|
+
workflow.add_edge("ai_analysis", END)
|
|
278
|
+
workflow.add_edge("cached_solution", END)
|
|
279
|
+
|
|
280
|
+
# Compile
|
|
281
|
+
app = workflow.compile()
|
|
282
|
+
|
|
283
|
+
print("[AGENT] ✓ LangGraph agent initialized")
|
|
284
|
+
return app
|
|
285
|
+
|
|
286
|
+
except Exception as e:
|
|
287
|
+
print(f"[AGENT] Failed to create agent: {e}")
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
def parse_response(content: str) -> dict:
|
|
291
|
+
"""Parse AI response"""
|
|
292
|
+
import re
|
|
293
|
+
|
|
294
|
+
result = {
|
|
295
|
+
"type": "Unknown",
|
|
296
|
+
"cause": "Not determined",
|
|
297
|
+
"fixes": []
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
for line in content.split('\n'):
|
|
301
|
+
line = line.strip()
|
|
302
|
+
if line.startswith('TYPE:'):
|
|
303
|
+
result["type"] = line.replace('TYPE:', '').strip()
|
|
304
|
+
elif line.startswith('CAUSE:'):
|
|
305
|
+
result["cause"] = line.replace('CAUSE:', '').strip()
|
|
306
|
+
elif line.startswith('FIX'):
|
|
307
|
+
fix = re.sub(r'^FIX\d+:\s*', '', line)
|
|
308
|
+
if fix:
|
|
309
|
+
result["fixes"].append(fix)
|
|
310
|
+
|
|
311
|
+
return result
|
|
312
|
+
|
|
313
|
+
# Global agent instance
|
|
314
|
+
_agent = None
|
|
315
|
+
|
|
316
|
+
def analyze_with_agent(error_context: str) -> dict:
|
|
317
|
+
"""Analyze error using LangGraph agent"""
|
|
318
|
+
global _agent
|
|
319
|
+
|
|
320
|
+
if _agent is None:
|
|
321
|
+
_agent = create_agent()
|
|
322
|
+
|
|
323
|
+
if _agent is None:
|
|
324
|
+
# Fallback if agent creation failed
|
|
325
|
+
return {
|
|
326
|
+
"type": "Error",
|
|
327
|
+
"cause": "Agent initialization failed",
|
|
328
|
+
"fixes": ["Check dependencies", "Review error logs"],
|
|
329
|
+
"method": "Fallback",
|
|
330
|
+
"confidence": 0.30
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# Run the agent
|
|
334
|
+
initial_state = {
|
|
335
|
+
"error_context": error_context,
|
|
336
|
+
"error_type": "",
|
|
337
|
+
"needs_docs": False,
|
|
338
|
+
"documentation": "",
|
|
339
|
+
"analysis": {},
|
|
340
|
+
"api_calls": 0
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
result = _agent.invoke(initial_state)
|
|
345
|
+
analysis = result["analysis"]
|
|
346
|
+
analysis["api_calls_used"] = result["api_calls"]
|
|
347
|
+
return analysis
|
|
348
|
+
except Exception as e:
|
|
349
|
+
print(f"[AGENT] Execution failed: {e}")
|
|
350
|
+
return {
|
|
351
|
+
"type": "Analysis Error",
|
|
352
|
+
"cause": str(e),
|
|
353
|
+
"fixes": ["Check agent configuration"],
|
|
354
|
+
"method": "Error",
|
|
355
|
+
"confidence": 0.20
|
|
356
|
+
}
|