Skip to content

Commit 69c00d0

Browse files
committed
Final changes
1 parent 1003a4f commit 69c00d0

4 files changed

Lines changed: 83 additions & 35 deletions

File tree

AntiPattern_Remediator/src/core/agents/explainer.py

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
"""
22
ExplainerAgent — minimal version
33
- Delegates state handling to create_graph.py
4-
- Only builds messages and parses JSON response
5-
- Keeps code minimal and focused
4+
- Uses PromptManager if available; otherwise a tiny inline fallback prompt
5+
- Always passes msgs; always returns a non-empty explanation_json
66
"""
77
from __future__ import annotations
88

99
from typing import Dict, Any
1010
import json
1111

1212
from langchain_core.language_models import BaseLanguageModel
13+
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
1314
from ..prompt import PromptManager
1415
from src.core.utils import extract_first_json
1516

@@ -32,33 +33,55 @@ def explain_antipattern(self, state: Dict[str, Any]) -> Dict[str, Any]:
3233
antipattern_name=state.get("antipattern_name", "Unknown antipattern"),
3334
antipattern_description=state.get("antipattern_description", ""),
3435
antipatterns_json=json.dumps(state.get("antipatterns_json", []), ensure_ascii=False),
36+
msgs=state.get("msgs", []), # ensure MessagesPlaceholder is satisfied
3537
)
3638

3739
messages = self._build_messages(**kwargs)
38-
response = self.llm.invoke(messages)
39-
raw = getattr(response, "content", None) or str(response)
40+
41+
try:
42+
response = self.llm.invoke(messages)
43+
raw = getattr(response, "content", None) or str(response)
44+
except Exception as e:
45+
raw = f"LLM error: {e}"
46+
4047
state["explanation_response_raw"] = raw
4148

42-
parsed = extract_first_json(raw)
43-
state["explanation_json"] = parsed if isinstance(parsed, dict) else {}
49+
# Robust parse: accept dict, wrap list, or emit a minimal fallback
50+
try:
51+
parsed = extract_first_json(raw)
52+
except Exception:
53+
parsed = None
54+
55+
if isinstance(parsed, dict):
56+
state["explanation_json"] = parsed
57+
elif isinstance(parsed, list):
58+
state["explanation_json"] = {"items": parsed}
59+
else:
60+
state["explanation_json"] = self._fallback_payload(state)
61+
4462
return state
4563

4664
def display_explanation(self, state: Dict[str, Any]) -> Dict[str, Any]:
4765
print("\n=== Explanation (raw) ===\n", state.get("explanation_response_raw", "N/A"))
4866
if state.get("explanation_json"):
49-
print("\n=== Explanation (JSON) ===\n", json.dumps(state["explanation_json"], indent=2, ensure_ascii=False))
67+
print("\n=== Explanation (JSON) ===\n",
68+
json.dumps(state["explanation_json"], indent=2, ensure_ascii=False))
5069
return state
5170

5271
def _build_messages(self, **kwargs) -> Any:
53-
try:
54-
getp = getattr(self.prompt_manager, "get_prompt", None)
55-
if callable(getp):
56-
prompt = getp(PROMPT_KEY)
57-
if prompt is not None:
58-
return prompt.format_messages(**kwargs)
59-
except Exception:
60-
pass
72+
# Always ensure msgs exists
73+
if "msgs" not in kwargs or kwargs["msgs"] is None:
74+
kwargs = {**kwargs, "msgs": []}
6175

76+
# 1) Try preloaded template from PromptManager
77+
prompt = None
78+
getp = getattr(self.prompt_manager, "get_prompt", None)
79+
if callable(getp):
80+
prompt = getp(PROMPT_KEY)
81+
if prompt is not None:
82+
return prompt.format_messages(**kwargs)
83+
84+
# 2) Minimal inline fallback
6285
schema = {
6386
"items": [{
6487
"antipattern_name": "",
@@ -76,11 +99,33 @@ def _build_messages(self, **kwargs) -> Any:
7699
"closing_summary": ""
77100
}
78101
content = (
79-
"Given inputs (JSON):\n" + json.dumps(kwargs, ensure_ascii=False) +
102+
"Given inputs (JSON):\n" + json.dumps({k: v for k, v in kwargs.items() if k != "msgs"}, ensure_ascii=False) +
80103
"\nRespond with STRICT JSON using exactly this schema:\n" +
81104
json.dumps(schema, ensure_ascii=False)
82105
)
83-
return [
84-
{"role": "system", "content": "Return STRICT JSON only. No commentary."},
85-
{"role": "user", "content": content},
86-
]
106+
fallback = ChatPromptTemplate.from_messages([
107+
("system", "Return STRICT JSON only. No commentary."),
108+
("user", content),
109+
MessagesPlaceholder("msgs"),
110+
])
111+
return fallback.format_messages(**kwargs)
112+
113+
@staticmethod
114+
def _fallback_payload(state: Dict[str, Any]) -> Dict[str, Any]:
115+
"""Tiny fallback so downstream never breaks if parsing fails."""
116+
return {
117+
"items": [{
118+
"antipattern_name": state.get("antipattern_name", "Unknown antipattern"),
119+
"antipattern_description": state.get("antipattern_description", ""),
120+
"impact": "",
121+
"why_it_is_bad": "",
122+
"how_we_fixed_it": state.get("refactor_rationale", ""),
123+
"refactored_code": state.get("refactored_code", ""),
124+
"summary": "Auto-generated minimal explanation (parser fallback)."
125+
}],
126+
"what_changed": [],
127+
"why_better": [],
128+
"principles_applied": [],
129+
"trade_offs": [],
130+
"closing_summary": ""
131+
}

AntiPattern_Remediator/src/core/state.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
class AgentState(TypedDict):
99
"""State definition for passing data through the workflow"""
1010
code: str # Code to be analyzed
11+
language: Optional[str] # Language of the code (used by ExplainerAgent)
1112
context: Optional[str] # Context retrieved from knowledge base (scanner)
1213
trove_context: Optional[str] # Context retrieved from the Anti-Pattern Trove (TinyDB/Chroma)
1314
antipatterns_scanner_results: Optional[str]
@@ -20,3 +21,6 @@ class AgentState(TypedDict):
2021
explanation_response_raw: Optional[str] # Raw LLM output from explainer
2122
explanation_json: Optional[Dict[str, Any]] # Parsed JSON explanation
2223
current_file_path: Optional[str] # Path to the current file being processed
24+
25+
explanation_response_raw: Optional[str] # Raw LLM output from explainer
26+
explanation_json: Optional[Dict[str, Any]] # Parsed JSON explanation
Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
explainer:
2-
template: |
3-
You are a senior software reviewer.
4-
Your job is to explain detected anti-patterns and the applied refactor in a clear, structured way.
5-
Output STRICT JSON only — no commentary outside JSON.
2+
system: |
3+
You are a senior software reviewer. Output STRICT JSON only — no commentary outside the JSON object.
64
5+
user: |
76
=== Inputs ===
87
Language: {language}
98
Context: {context}
@@ -15,28 +14,28 @@ explainer:
1514
Refactor Rationale:
1615
{refactor_rationale}
1716
18-
=== Required Output Schema ===
19-
{
17+
=== Return EXACTLY this JSON structure ===
18+
{{
2019
"items": [
21-
{
20+
{{
2221
"antipattern_name": "",
2322
"antipattern_description": "",
2423
"impact": "",
2524
"why_it_is_bad": "",
2625
"how_we_fixed_it": "",
2726
"refactored_code": "",
2827
"summary": ""
29-
}
28+
}}
3029
],
3130
"what_changed": [],
3231
"why_better": [],
3332
"principles_applied": [],
3433
"trade_offs": [],
3534
"closing_summary": ""
36-
}
35+
}}
3736
38-
Notes:
39-
- Always return valid JSON.
40-
- Use multiple entries under "items" if more than one antipattern is relevant.
41-
- Keep `refactored_code` short (or truncated if needed).
42-
- Fill all fields, even if briefly.
37+
Rules:
38+
- Return valid JSON only (no code fences, no prose).
39+
- If multiple antipatterns apply, include multiple entries in "items".
40+
- Keep "refactored_code" concise; truncate if needed but keep it valid.
41+
- Populate all fields; use brief placeholders if unsure.

AntiPattern_Remediator/test/unit_test/prompt/test_prompt_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ def temp_prompt_files(self):
583583
},
584584
'explainer.yaml': {
585585
'explainer': {
586-
'system': 'You are a senior software reviewer.',
586+
'system': 'You are a senior software code explainer.',
587587
'user': 'Explain: {code}\nLang: {language}\nCtx: {context}'
588588
}
589589
}

0 commit comments

Comments
 (0)