self_check_analysis_graph.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import json
  2. import logging
  3. from typing import TypedDict, Optional
  4. from langgraph.graph import StateGraph, START, END
  5. from langchain_core.messages import SystemMessage, HumanMessage
  6. from app.llm.client import get_llm
  7. from app.monitoring import monitor_agent
  8. from app.prompt_service import get_prompt
  9. logger = logging.getLogger(__name__)
  10. SYSTEM_PROMPT = """你是一位家庭健康顾问,基于五维自检结果(身·智·富·行·心,每维0-9分,满分45)给出个性化建议。
  11. 要求:
  12. 1. 对每个低分维度(≤6分)给出1-2句解读和2-3个具体可执行的微行动
  13. 2. 如有历史数据,简要对比趋势(改善/下滑)
  14. 3. 语气温暖口语化,每条解读不超过80字
  15. 4. 最后给出1句家庭整体洞察(30字以内)
  16. 返回 JSON(严格格式,不要额外文字):
  17. {
  18. "advice": [
  19. {
  20. "dimension": "mind",
  21. "dimensionName": "心",
  22. "interpretation": "你的情绪能量偏低,可能最近压力较大,建议...",
  23. "microActions": ["今晚睡前做10分钟深呼吸", "和伴侣约定每周一次夜谈"],
  24. "fallbackUsed": false
  25. }
  26. ],
  27. "familyInsight": "建议从行动维度入手,关系顺畅了内心才能安定"
  28. }
  29. """
  30. class GraphState(TypedDict):
  31. scores: dict
  32. question_ids: list
  33. user_id: int
  34. recent_history: list
  35. advice: Optional[dict]
  36. error: Optional[str]
  37. class SelfCheckAnalysisAgent:
  38. def __init__(self):
  39. self.llm = get_llm()
  40. @monitor_agent("self_check_analysis")
  41. async def run(self, scores: dict, question_ids: list, user_id: int, recent_history: list) -> dict:
  42. try:
  43. score_summary = "\n".join(
  44. f"{v.get('dimensionName', k)}({k}): {v.get('score', 0)}分"
  45. for k, v in scores.items()
  46. )
  47. history_summary = ""
  48. if recent_history:
  49. history_summary = "历史趋势:\n" + "\n".join(
  50. f"- {h.get('createdAt', '')}: 总分{h.get('totalScore', 0)}分"
  51. for h in recent_history[:3]
  52. )
  53. messages = [
  54. SystemMessage(content=await get_prompt("self_check_analysis") or SYSTEM_PROMPT),
  55. HumanMessage(content=f"用户ID: {user_id}\n当前自检得分:\n{score_summary}\n{history_summary}"),
  56. ]
  57. response = await self.llm.ainvoke(messages)
  58. text = response.content.strip()
  59. if "```json" in text:
  60. text = text.split("```json")[1].split("```")[0].strip()
  61. elif "```" in text:
  62. text = text.split("```")[1].split("```")[0].strip()
  63. data = json.loads(text)
  64. advice_list = data.get("advice", [])
  65. for item in advice_list:
  66. item.setdefault("fallbackUsed", False)
  67. if advice_list:
  68. advice_list[0]["familyInsight"] = data.get("familyInsight", "")
  69. return {"advice": {"advice_json": json.dumps(advice_list, ensure_ascii=False), "fallback_used": False}}
  70. except Exception as e:
  71. logger.warning("自检建议生成失败: %s", e)
  72. return {"advice": {"advice_json": None, "fallback_used": True}}
  73. def build_graph():
  74. agent = SelfCheckAnalysisAgent()
  75. def parse_input(state: GraphState) -> GraphState:
  76. return state
  77. async def call_llm(state: GraphState) -> dict:
  78. return await agent.run(state["scores"], state["question_ids"], state["user_id"], state["recent_history"])
  79. def validate(state: GraphState) -> GraphState:
  80. adv = state.get("advice")
  81. if adv is None or adv.get("advice_json") is None:
  82. return {**state, "error": "AI 建议生成失败"}
  83. return state
  84. graph = StateGraph(GraphState)
  85. graph.add_node("parse", parse_input)
  86. graph.add_node("llm", call_llm)
  87. graph.add_node("validate", validate)
  88. graph.add_edge(START, "parse")
  89. graph.add_edge("parse", "llm")
  90. graph.add_edge("llm", "validate")
  91. graph.add_edge("validate", END)
  92. return graph.compile()
  93. _graph = None
  94. def get_graph():
  95. global _graph
  96. if _graph is None:
  97. _graph = build_graph()
  98. return _graph