codebase-brain 0.3.2__py3-none-any.whl → 0.3.4__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.
- brain_parser/codebase_walker.py +1 -1
- brain_parser/llm_router.py +79 -31
- brain_parser/root_cause.py +34 -8
- {codebase_brain-0.3.2.dist-info → codebase_brain-0.3.4.dist-info}/METADATA +1 -1
- {codebase_brain-0.3.2.dist-info → codebase_brain-0.3.4.dist-info}/RECORD +8 -8
- {codebase_brain-0.3.2.dist-info → codebase_brain-0.3.4.dist-info}/WHEEL +1 -1
- {codebase_brain-0.3.2.dist-info → codebase_brain-0.3.4.dist-info}/entry_points.txt +0 -0
- {codebase_brain-0.3.2.dist-info → codebase_brain-0.3.4.dist-info}/top_level.txt +0 -0
brain_parser/codebase_walker.py
CHANGED
brain_parser/llm_router.py
CHANGED
|
@@ -8,11 +8,9 @@ def get_config():
|
|
|
8
8
|
if os.path.exists(local_env):
|
|
9
9
|
load_dotenv(local_env, override=True)
|
|
10
10
|
|
|
11
|
-
# check if local .env actually had Brain's keys
|
|
12
11
|
has_key = os.getenv("LLM_API_KEY") or os.getenv("GROQ_API_KEY")
|
|
13
12
|
|
|
14
13
|
if not has_key:
|
|
15
|
-
# local .env didn't have Brain's config — fall back to global
|
|
16
14
|
global_config = os.path.expanduser("~/.codebase-brain/config.env")
|
|
17
15
|
if os.path.exists(global_config):
|
|
18
16
|
load_dotenv(global_config, override=True)
|
|
@@ -20,7 +18,7 @@ def get_config():
|
|
|
20
18
|
return {
|
|
21
19
|
'provider': os.getenv("LLM_PROVIDER", "groq").lower(),
|
|
22
20
|
'api_key': os.getenv("LLM_API_KEY") or os.getenv("GROQ_API_KEY"),
|
|
23
|
-
'model': os.getenv("LLM_MODEL") or os.getenv("GROQ_MODEL", "
|
|
21
|
+
'model': os.getenv("LLM_MODEL") or os.getenv("GROQ_MODEL", "openai/gpt-oss-120b")
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
|
|
@@ -44,53 +42,103 @@ def call_llm(prompt, max_tokens=500):
|
|
|
44
42
|
return _call_groq(api_key, model, prompt, max_tokens)
|
|
45
43
|
|
|
46
44
|
|
|
45
|
+
def _try_models(model, fallback_list, call_fn):
|
|
46
|
+
"""Generic fallback runner — tries the configured model first, then known-stable alternatives."""
|
|
47
|
+
models_to_try = [model] + [m for m in fallback_list if m != model]
|
|
48
|
+
seen = set()
|
|
49
|
+
models_to_try = [m for m in models_to_try if not (m in seen or seen.add(m))]
|
|
50
|
+
|
|
51
|
+
last_error = None
|
|
52
|
+
for try_model in models_to_try:
|
|
53
|
+
try:
|
|
54
|
+
return call_fn(try_model)
|
|
55
|
+
except Exception as e:
|
|
56
|
+
last_error = e
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
return f"Could not get AI response — all models unavailable: {str(last_error)}"
|
|
60
|
+
|
|
61
|
+
|
|
47
62
|
def _call_groq(api_key, model, prompt, max_tokens):
|
|
48
63
|
from groq import Groq
|
|
49
64
|
client = Groq(api_key=api_key)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
|
|
66
|
+
fallback_list = ["openai/gpt-oss-120b", "llama-3.1-8b-instant", "qwen3.6-27b"]
|
|
67
|
+
|
|
68
|
+
def call_fn(m):
|
|
69
|
+
response = client.chat.completions.create(
|
|
70
|
+
model=m,
|
|
71
|
+
messages=[{"role": "user", "content": prompt}],
|
|
72
|
+
max_tokens=max_tokens,
|
|
73
|
+
temperature=0
|
|
74
|
+
)
|
|
75
|
+
return response.choices[0].message.content
|
|
76
|
+
|
|
77
|
+
return _try_models(model, fallback_list, call_fn)
|
|
57
78
|
|
|
58
79
|
|
|
59
80
|
def _call_openai(api_key, model, prompt, max_tokens):
|
|
60
81
|
from openai import OpenAI
|
|
61
82
|
client = OpenAI(api_key=api_key)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
83
|
+
|
|
84
|
+
fallback_list = ["gpt-4o-mini", "gpt-4o", "gpt-3.5-turbo"]
|
|
85
|
+
|
|
86
|
+
def call_fn(m):
|
|
87
|
+
response = client.chat.completions.create(
|
|
88
|
+
model=m,
|
|
89
|
+
messages=[{"role": "user", "content": prompt}],
|
|
90
|
+
max_tokens=max_tokens,
|
|
91
|
+
temperature=0
|
|
92
|
+
)
|
|
93
|
+
return response.choices[0].message.content
|
|
94
|
+
|
|
95
|
+
return _try_models(model, fallback_list, call_fn)
|
|
69
96
|
|
|
70
97
|
|
|
71
98
|
def _call_anthropic(api_key, model, prompt, max_tokens):
|
|
72
99
|
import anthropic
|
|
73
100
|
client = anthropic.Anthropic(api_key=api_key)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
101
|
+
|
|
102
|
+
fallback_list = ["claude-3-5-haiku-20241022", "claude-3-5-sonnet-20241022", "claude-3-haiku-20240307"]
|
|
103
|
+
|
|
104
|
+
def call_fn(m):
|
|
105
|
+
response = client.messages.create(
|
|
106
|
+
model=m,
|
|
107
|
+
max_tokens=max_tokens,
|
|
108
|
+
messages=[{"role": "user", "content": prompt}]
|
|
109
|
+
)
|
|
110
|
+
return response.content[0].text
|
|
111
|
+
|
|
112
|
+
return _try_models(model, fallback_list, call_fn)
|
|
80
113
|
|
|
81
114
|
|
|
82
115
|
def _call_google(api_key, model, prompt, max_tokens):
|
|
83
116
|
import google.generativeai as genai
|
|
84
117
|
genai.configure(api_key=api_key)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
118
|
+
|
|
119
|
+
fallback_list = ["gemini-1.5-flash", "gemini-1.5-pro", "gemini-2.0-flash"]
|
|
120
|
+
|
|
121
|
+
def call_fn(m):
|
|
122
|
+
gemini = genai.GenerativeModel(m)
|
|
123
|
+
response = gemini.generate_content(prompt)
|
|
124
|
+
return response.text
|
|
125
|
+
|
|
126
|
+
return _try_models(model, fallback_list, call_fn)
|
|
88
127
|
|
|
89
128
|
|
|
90
129
|
def _call_ollama(model, prompt, max_tokens):
|
|
91
130
|
import requests
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
)
|
|
96
|
-
|
|
131
|
+
|
|
132
|
+
fallback_list = ["llama3.2", "codellama", "deepseek-coder"]
|
|
133
|
+
|
|
134
|
+
def call_fn(m):
|
|
135
|
+
response = requests.post(
|
|
136
|
+
"http://localhost:11434/api/generate",
|
|
137
|
+
json={"model": m, "prompt": prompt, "stream": False}
|
|
138
|
+
)
|
|
139
|
+
result = response.json().get("response", "")
|
|
140
|
+
if not result:
|
|
141
|
+
raise ValueError(f"Empty response from Ollama model {m}")
|
|
142
|
+
return result
|
|
143
|
+
|
|
144
|
+
return _try_models(model, fallback_list, call_fn)
|
brain_parser/root_cause.py
CHANGED
|
@@ -37,21 +37,47 @@ def extract_frames(stacktrace):
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def match_frame_to_graph(frame_file, G):
|
|
40
|
-
"""Match
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
"""Match any path format from stacktrace to full path in graph — universal matching."""
|
|
41
|
+
|
|
42
|
+
def normalize(path):
|
|
43
|
+
p = path.replace('\\', '/').lower()
|
|
44
|
+
if len(p) > 1 and p[1] == ':':
|
|
45
|
+
p = p[2:]
|
|
46
|
+
return p.lstrip('/')
|
|
47
|
+
|
|
48
|
+
frame_norm = normalize(frame_file)
|
|
49
|
+
frame_parts = frame_norm.split('/')
|
|
43
50
|
|
|
51
|
+
candidates = []
|
|
44
52
|
for node in G.nodes():
|
|
45
|
-
node_norm = node
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
node_norm = normalize(node)
|
|
54
|
+
node_parts = node_norm.split('/')
|
|
55
|
+
|
|
56
|
+
min_len = min(len(frame_parts), len(node_parts))
|
|
57
|
+
if min_len == 0:
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
if frame_parts[-min_len:] == node_parts[-min_len:]:
|
|
61
|
+
candidates.append(node)
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
if frame_parts[-1] == node_parts[-1]:
|
|
48
65
|
candidates.append(node)
|
|
49
66
|
|
|
50
67
|
if not candidates:
|
|
51
68
|
return None
|
|
52
|
-
# prefer shortest path — most specific match
|
|
53
|
-
return min(candidates, key=len)
|
|
54
69
|
|
|
70
|
+
def match_score(node):
|
|
71
|
+
node_parts = normalize(node).split('/')
|
|
72
|
+
score = 0
|
|
73
|
+
for a, b in zip(reversed(frame_parts), reversed(node_parts)):
|
|
74
|
+
if a == b:
|
|
75
|
+
score += 1
|
|
76
|
+
else:
|
|
77
|
+
break
|
|
78
|
+
return score
|
|
79
|
+
|
|
80
|
+
return max(candidates, key=match_score)
|
|
55
81
|
|
|
56
82
|
def find_root_cause(stacktrace):
|
|
57
83
|
"""Main function — paste stacktrace, get root cause analysis."""
|
|
@@ -2,15 +2,15 @@ brain_cli.py,sha256=uYpVxWpM7_fVM-Z4JSgWj0fqU9TGc4Zhc9uu6ritbZA,14576
|
|
|
2
2
|
brain_parser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
brain_parser/ast_parser.py,sha256=3Vy1XXyInjgA2xn6IBYv4K1juEdtFwAo58upqukCHXI,1799
|
|
4
4
|
brain_parser/bug_detector.py,sha256=IbEpyXfatL0r1cjaIZ7LXzYPg7z0molyRjg2ULu4828,7653
|
|
5
|
-
brain_parser/codebase_walker.py,sha256=
|
|
5
|
+
brain_parser/codebase_walker.py,sha256=VqzHcVerp-CEnSsGvHf_13RjT1YBne9iMeAc8kHVLuY,3716
|
|
6
6
|
brain_parser/file_watcher.py,sha256=cXsXxrSPvVhSzVmbGsLu5u-jd98VUHvTs8-W0QkhHf4,2582
|
|
7
7
|
brain_parser/graph_builder.py,sha256=gOXII_SwEs_sJfVFBk81ftedT3fxYN2gz1_aopbpTUU,8741
|
|
8
|
-
brain_parser/llm_router.py,sha256=
|
|
8
|
+
brain_parser/llm_router.py,sha256=D0RD71xVNqy9PCPuDUsSCYmlSaWtczDkERUnLslHGQ8,4733
|
|
9
9
|
brain_parser/query_engine.py,sha256=Lunb_-9z1Lk2VYOE7f_-jF8X2AQtby1tPFXgFUjqnpA,3671
|
|
10
|
-
brain_parser/root_cause.py,sha256=
|
|
10
|
+
brain_parser/root_cause.py,sha256=bgBVjP6rlDlcOUrjM5mUi4Fa-ERQvrhgFRqbMfgWZpw,5974
|
|
11
11
|
brain_parser/universal_parser.py,sha256=47StXj-GPGJb4g0WzM5VYgM3tIc1tOzwEa8mcL_q9AY,6694
|
|
12
|
-
codebase_brain-0.3.
|
|
13
|
-
codebase_brain-0.3.
|
|
14
|
-
codebase_brain-0.3.
|
|
15
|
-
codebase_brain-0.3.
|
|
16
|
-
codebase_brain-0.3.
|
|
12
|
+
codebase_brain-0.3.4.dist-info/METADATA,sha256=UrO6il1XfJ_OeEw8uoO6qM7h2XIOC9jmZsdcG8lKkAM,531
|
|
13
|
+
codebase_brain-0.3.4.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
14
|
+
codebase_brain-0.3.4.dist-info/entry_points.txt,sha256=O9yHP4zy_uoVPxmewGbdNjNksmxRYUv34MQJDRXXodc,41
|
|
15
|
+
codebase_brain-0.3.4.dist-info/top_level.txt,sha256=RmSvznU63-vmm5zLSKAlYYsFzQ8X3RyPrB-m0q0uQfI,23
|
|
16
|
+
codebase_brain-0.3.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|