11"""
22ExplainerAgent — 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"""
77from __future__ import annotations
88
99from typing import Dict , Any
1010import json
1111
1212from langchain_core .language_models import BaseLanguageModel
13+ from langchain .prompts import ChatPromptTemplate , MessagesPlaceholder
1314from ..prompt import PromptManager
1415from 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 "\n Respond 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+ }
0 commit comments