LPXian/Graph-News.AI
0
1from langchain_core.messages import HumanMessage, SystemMessage2from app.core.llm import get_llm3from app.graph.state import GraphState4import re5 6def critic_node(state: GraphState) -> GraphState:7 """8 Evaluates the quality of the drafted article.9 """10 print("--- CRITIC NODE ---")11 draft = state.get("final_draft", "")12 iteration = state.get("iteration_count", 0)13 14 # Basic word count validation15 word_count = len(re.findall(r'\w+', draft))16 print(f"Current Draft Word Count: {word_count}")17 18 # LLM-based evaluation19 llm = get_llm()20 eval_prompt = f"""21 Evaluate the following AI news draft based on these criteria:22 1. Length: Is it between 150 and 800 words? (Current: {word_count})23 2. Structure: Does it have a title, exec summary, 4-5 topics (each with Brief, Detail, Sources), and conclusion?24 3. Clarity: Is it written in a natural, human-like tone for AI engineers/students?25 26 Draft:27 ---28 {draft}29 ---30 31 Respond in JSON format with two fields:32 "is_approved": boolean,33 "feedback": string (specific improvements if not approved, or "Excellent" if approved)34 """35 36 # Use a structured output approach or just parse JSON from content37 # For simplicity in MVP, we'll parse JSON from response38 try:39 response = llm.invoke([SystemMessage(content="You are a strict editor evaluating AI news quality. Respond ONLY with valid JSON."), HumanMessage(content=eval_prompt)])40 # Basic JSON extraction (Gemini often wraps in ```json)41 json_str = re.search(r'\{.*\}', response.content, re.DOTALL).group(0)42 import json43 evaluation = json.loads(json_str)44 45 is_approved = evaluation.get("is_approved", False)46 feedback = evaluation.get("feedback", "Needs improvement")47 except Exception as e:48 print(f"Critical Error in evaluation parsing: {e}")49 # Fallback if LLM fails50 is_approved = 150 <= word_count <= 50051 feedback = "Check word count and structure."52 53 # Logic: Approve if LLM says yes, or if we have already retried once.54 # We enforce "max 1 retry" here.55 final_approval = is_approved or (iteration >= 1)56 57 if final_approval:58 # Prepare the final payload for the database59 lines = draft.strip().split('\n')60 title = lines[0].replace('#', '').strip() if lines else "Daily AI News"61 62 # Collect source links63 source_links = [{"title": a["title"], "link": a["link"]} for a in state.get("clean_articles", [])[:10]]64 65 # New: Tracking metrics66 tokens = response.usage_metadata.get("total_tokens", 0) if hasattr(response, "usage_metadata") else 067 total_tokens = state.get("total_tokens", 0) + tokens68 69 # Calculate a mock processing load based on cleaning ratio70 raw_count = len(state.get("raw_articles", []))71 clean_count = len(state.get("clean_articles", []))72 load = round((clean_count / raw_count * 100), 1) if raw_count > 0 else 073 74 final_payload = {75 "title": title,76 "summary": draft[:200] + "...",77 "content": draft,78 "source_links": source_links,79 "metadata": {80 "total_tokens": total_tokens,81 "processing_load": load82 }83 }84 85 return {86 "is_approved": True,87 "final_payload": final_payload,88 "total_tokens": total_tokens,89 "iteration_count": iteration + 190 }91 else:92 print(f"Draft rejected. Feedback: {feedback}. Moving to retry 1.")93 return {94 "is_approved": False,95 "critic_feedback": feedback,96 "iteration_count": iteration + 197 }98 