self_check_analysis_graph.py 4.0 KB

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