基于 LangGraph 实现具备自我反思能力的智能体——ReflectAgent 实战解析

在传统的问答式系统中,智能体往往只做一件事:根据输入给出单次回答。这种"单轮输出"模式在简单场景下勉强可用,但面对复杂问题时就显得力不从心:回答可能过于简略、抓不住重点、甚至犯低级错误,同时缺乏"自我修正"的能力。

本文为读者朋友们展示如何构建一个具有:

  • 初次尝试(Initial Attempt)
  • 自我反思(Self Reflection)
  • 基于反思的改进回答(Improved Answer)

能力的智能体 ------ ReflectAgent(反思智能体)

我们不会停留在概念层面,而是通过核心代码逐步拆解整个系统的设计与执行过程。


一、整体设计思路:把"反思回路"编排成一张图

核心设计思路非常清晰:

用 LangGraph 把 Agent 的行为拆成 3 个阶段节点:
初次回答 → 反思 → 改进

并在状态中记录整个过程中产生的所有中间产物和 Step 轨迹。

对应的图结构在 build_reflect_agent_graph() 中定义:

python 复制代码
graph = StateGraph(ReflectState)

graph.add_node("initial_attempt", initial_attempt_node)
graph.add_node("reflect", reflect_node)
graph.add_node("improve", improve_node)

graph.add_edge(START, "initial_attempt")
graph.add_edge("initial_attempt", "reflect")
graph.add_edge("reflect", "improve")
graph.add_edge("improve", END)

图非常简单,却涵盖了一个完整的"自我改进回路":

text 复制代码
START → initial_attempt → reflect → improve → END

整条链路运行在一个统一的 ReflectState 状态上,这也是 LangGraph 推荐的有状态工作流编排方式


二、状态建模:把 Agent 内部的所有"心智活动"结构化

用一个 TypedDict 把所有需要在图节点之间共享的数据统一封装成 ReflectState

python 复制代码
class ReflectState(TypedDict, total=False):
    # 输入
    query: str

    # 过程记录
    steps: List[Step]
    reflections: List[str]

    # 各阶段产物
    initial_response: Optional[str]
    reflection: Optional[str]
    reflection_struct: Optional[Dict[str, Any]]
    improved_response: Optional[str]

    # 工具使用情况/结果
    used_tools: bool
    analysis_result: Optional[str]
    eval_result: Optional[str]

可以看到,智能体在整个生命周期里的"思考轨迹"都显式记录下来了:

  • 用户输入:query

  • 推理过程轨迹:steps(每个 Step 包含 step_type + content + metadata

  • 中间思考结果:

    • 初始回答:initial_response
    • 反思文本:reflection
    • 结构化反思 JSON:reflection_struct
    • 改进后的最终回答:improved_response
  • 工具使用情况:used_toolsanalysis_resulteval_result

这种状态设计有以下几个优点:

  1. 可观测:任何时刻都能看到 Agent 目前处于什么阶段、做过什么动作;
  2. 可调试:每一条 Step 都是一个可追踪的"日志事件";
  3. 可拓展 :后续若增加新工具、新阶段,只需往 ReflectState 中加字段,不会破坏现有逻辑。

三、工具层:将本地逻辑能力封装成 Tool

虽然这是一个"带 LLM 的智能体",但我们并没有把所有逻辑都交给大模型,而是保留了两类本地工具:

1. 文本分析工具 analyze_tool

python 复制代码
def analyze_tool(args: Dict[str, Any]) -> str:
    text = args.get("text", "")
    ...
    analysis.append(f"文本长度: {len(text)} 字符")
    analysis.append(f"词汇数量: {len(text.split())} 个")
    has_question = "是" if ("?" in text or "?" in text) else "否"
    analysis.append(f"包含问号: {has_question}")
    ...
    analysis.append(f"情感倾向: {sentiment}")
    return "分析结果: " + "; ".join(analysis)

该工具的功能包括:

  • 文本长度统计
  • 词数统计
  • 是否含疑问符
  • 简单情感分析(正向/负向/中性)

它的定位非常明确:为 LLM 提供结构化的"观察信号",而不是抢 LLM 的活。

2. 回答质量评估工具 evaluate_tool

python 复制代码
def evaluate_tool(args: Dict[str, Any]) -> str:
    criteria = args.get("criteria") or "通用质量"
    scores: Dict[str, int] = {
        "准确性": 7 + rng.randint(0, 3),
        "完整性": 6 + rng.randint(0, 3),
        "清晰度": 8 + rng.randint(0, 2),
    }
    return f"评估结果({criteria}): " + "; ".join(parts)

该工具模拟一个"外部评审者"的打分过程,用于在反思阶段之后辅助改进回答。

该工具不依赖 LLM,意味着其:

  • 可重复、可预期、可控;
  • 作为"硬规则评估信号",可以补足 LLM 一些不稳定的判断。

四、Step 机制:把整个思考过程串成一条可审计的轨迹

python 复制代码
def add_step(state: ReflectState, step_type: str, content: str,
             metadata: Optional[Dict[str, Any]] = None) -> ReflectState:
    if "steps" not in state or state["steps"] is None:
        state["steps"] = []
    step: Step = {
        "step_type": step_type,
        "content": content,
        "metadata": metadata or {},
    }
    state["steps"].append(step)
    return state

用于记录三个节点产生的中间信息,例如:

  • initial_attempt
  • action(工具调用)
  • observation(工具观察结果)
  • initial_response
  • reflection
  • improvement
  • final_reflection

记录中间信息是非常必要的,因为:

这条 steps 其实就是一个"可回放的思考链(Trace)",天然适合接到前端做可视化,也适合用于日志审计和后续训练数据采集。


五、节点一:初次尝试 initial_attempt_node

这是整个链条的起点,负责生产 初始回答

核心逻辑

python 复制代码
def initial_attempt_node(state: ReflectState) -> ReflectState:
    query = state["query"]
    add_step(state, "initial_attempt", f"开始处理查询: {query}")

    if "分析" in query:
        # 1)先用 analyze_tool 做结构化分析
        ...
        # 2)再把"用户问题 + 分析结果"交给 LLM,让它生成初始回答
        messages = [...]
        resp = llm.invoke(messages)
        initial_response = resp.content.strip()
        ...
    else:
        # 不带分析的直接初次 LLM 回答
        messages = [...]
        resp = llm.invoke(messages)
        initial_response = resp.content.strip()
        ...

把"是否调用 analyze_tool"作为一个简单规则分支:

  • 若用户问题中包含"分析"
    → 先通过 analyze_tool 得到一个结构化"分析结果"
    → 再把这个分析结果作为 LLM 的上下文之一
    → 生成一个更有信息密度的初次回答
  • 否则
    → 直接交给 LLM 产出一个简要初始回答即可

这里体现的是一个很典型的"工具增强 + LLM "模式:LLM 不直接对原始文本"生吃硬算",而是先看一眼结构化的 analysis_result,再来组织语言。


六、节点二:反思 reflect_node ------ 让 LLM 输出结构化 JSON 反思

反思目标

希望 LLM 对自己的初始回答做一个结构化自评,内容包括:

  • too_short:回答是否过短,影响完整性
  • low_relevance:与问题的关键点是否不够贴合
  • need_tools:是否建议使用工具来辅助改进
  • comments:自然语言总结

提示(Prompt)设计

python 复制代码
system_prompt = (
        "你是一个对自己回答进行自我反思的智能体。"
        "请严格按照 JSON 格式输出反思结果,不要输出任何多余文字。"
        "JSON 字段含义:\n"
        "  - too_short: 回答是否过于简短,影响完整性。\n"
        "  - low_relevance: 回答是否与问题的关键点不够贴合。\n"
        "  - need_tools: 是否建议使用工具(例如外部评估工具)来辅助改进回答。\n"
        "  - comments: 用简洁中文总结你的反思结论。\n"
    )
python 复制代码
user_prompt = (
        f"用户问题:{query}\n\n"
        f"你的初始回答:{initial_response}\n\n"
        f"本轮初始回答是否使用过工具:{'是' if used_tools_flag else '否'}\n\n"
        "请从完整性、相关性、是否需要工具三方面进行反思,"
        "并输出 JSON,例如:\n"
        "{\n"
        '  \"too_short\": true,\n'
        '  \"low_relevance\": false,\n'
        '  \"need_tools\": true,\n'
        '  \"comments\": \"回答有点短,但大体相关,建议用工具评估一下逻辑严谨性。\"\n'
        "}\n"
    )

然后调用 LLM:

python 复制代码
resp = llm.invoke([SystemMessage(content=system_prompt),
                   HumanMessage(content=user_prompt)])
raw = resp.content.strip()

反序列化 + 容错机制

使用 json.loads(raw) 对结果进行解析,并加了一层健壮性兜底:

python 复制代码
try:
    parsed = json.loads(raw)
    # 做一层容错
    reflection_struct["too_short"] = bool(parsed.get("too_short", False))
    reflection_struct["low_relevance"] = bool(parsed.get("low_relevance", False))
    reflection_struct["need_tools"] = bool(parsed.get("need_tools", False))
    reflection_struct["comments"] = str(parsed.get("comments", "")).strip() or "(无详细说明)"
except Exception:
    # 如果 JSON 解析失败,可以降级:简单根据长度和关键词做一点规则判断
    reflection_struct["too_short"] = len(initial_response) < 50
    reflection_struct["low_relevance"] = False  # 简化处理
    reflection_struct["need_tools"] = not used_tools_flag
    reflection_struct["comments"] = "LLM 输出非 JSON 格式,已使用降级规则推断反思结果。"

最终把反思结果:

  • 转成自然语言一条:reflection_text
  • 存入 state["reflection"]state["reflection_struct"]
  • 并用 add_step(..., "reflection", ...) 写入步骤轨迹
python 复制代码
reflection_text = (
        f"LLM 反思结果:{reflection_struct['comments']} "
        f"(too_short={reflection_struct['too_short']}, "
        f"low_relevance={reflection_struct['low_relevance']}, "
        f"need_tools={reflection_struct['need_tools']})"
)
add_step(state, "reflection", reflection_text, {"raw": raw, "struct": reflection_struct})

# 写入状态
reflections = state.get("reflections") or []
reflections.append(reflection_text)
state["reflections"] = reflections
state["reflection"] = reflection_text
state["reflection_struct"] = reflection_struct

这一步非常关键,它把 LLM 的"自我评价"转成了结构化决策信号,为下一步"是否调用评估工具、如何改写回答"提供清晰输入。


七、节点三:改进 improve_node ------ 反思驱动的二次生成

最后一个节点负责产出 改进后的最终回答

决策逻辑

python 复制代码
reflection_struct = state.get("reflection_struct") or {...}

if reflection_struct.get("too_short"):
    improvements_notes.append("根据反思:初始回答偏短,需要补充细节。")

if reflection_struct.get("low_relevance"):
    improvements_notes.append("根据反思:需要让回答更聚焦于问题核心。")

if reflection_struct.get("need_tools"):
    # 调用 evaluate_tool 做质量评估,并写入 step
    args = {"criteria": "回答质量"}
    eval_result = evaluate_tool(args)
    ...
    improvements_notes.append(f"根据反思:已使用评估工具辅助改进。评估结果:{eval_result}")

通过 reflection_struct 中的布尔信号,让"是否调用工具、需要从哪些维度改进回答"变成了一个可控、可解释的决策流程,而不是让 LLM 完全黑箱决定。

最终 LLM 改写

然后把所有信息打包成一个 Prompt,让 LLM 输出"改进后的最终回答":

python 复制代码
system_prompt = (
    "你是一个会根据自我反思来改进答案的智能体。"
    "现在你已经有一个初始回答、一份自我反思、以及(可选的)工具评估结果。"
    "请给出一份改进后的、更加完整和相关的最终回答。"
    "注意:回答要用自然中文,不要输出任何 JSON 或额外解释。"
)

user_parts = [
    f"用户问题:{query}",
    f"初始回答:{initial_response}",
    f"自我反思(摘要):{reflection_struct.get('comments', '')}",
]
if eval_result:
    user_parts.append(f"工具评估结果:{eval_result}")
if improvements_notes:
    user_parts.append("需要改进的要点:" + ";".join(improvements_notes))

user_prompt = "\n\n".join(user_parts)
resp = llm.invoke([...])
improved_response = resp.content.strip()

并记录:

python 复制代码
add_step(state, "improvement", improved_response, {"from": "llm"})
add_step(state, "final_reflection", "最终反思:已根据自我反思和工具评估生成优化后的回答。")

至此,一个完整的"回答 → 反思 → 工具辅助 → 改进"闭环就跑通了。


八、运行结果

__main__ 中,进行简单的测试:

python 复制代码
if __name__ == "__main__":
    app = build_reflect_agent_graph()

    query = "请帮我分析这段用户反馈是否积极,并给出改进建议:\"这个产品还不错,就是细节上还有提升空间\""

    init_state: ReflectState = {
        "query": query,
        "steps": [],
        "reflections": [],
    }

    final_state = app.invoke(init_state)

    print("=== 最终改进后的回答(LLM 生成) ===")
    print(final_state.get("improved_response", ""))

    print("\n=== 过程步骤(steps) ===")
    for i, step in enumerate(final_state.get("steps", []), start=1):
        print(f"{i}. [{step['step_type']}] {step['content']}")

运行结果为:

复制代码
=== 最终改进后的回答(LLM 生成) ===
这段用户反馈是一段典型的混合情感反馈,整体倾向积极,并包含了极具价值的建设性意见。

**情感分析:**

1.  **积极肯定(核心价值):** "这个产品还不错"是整个反馈的基石。用户明确表达了对产品核心功能和价值的认可,说明产品已经满足了其基本需求,并带来了正向体验。这是维持用户黏性的根本。
2.  **建设性意见(优化方向):** "就是细节上还有提升空间"是反馈的核心价值所在。这里的"就是"并非强烈的转折,而是一种补充和期望,表明用户对产品有更高的期待和参与感。不满意的用户通常会直接离开或给出负面评价,而愿意指出"细节"问题的用户,往往是产品的忠实用户或潜在忠实用户,他们希望产品能变得更好。

综合来看,这不是消极反馈,而是一个来自"友好批评者"的宝贵信号。情感可以概括为**"满意,但期待更高"**。

**改进建议:**

针对这类反馈,建议采取短期、中期和长期三步走的策略:

1.  **短期行动:跟进与深挖(针对该用户)**
    *   **及时响应与感谢:** 第一时间联系该用户,感谢他提出的宝贵意见,让他感受到被重视。
    *   **开放式提问引导:** 尝试用更具体的问题引导他说出细节所在。例如:"非常感谢您的反馈!为了让我们更好地改进,您能具体说说您指的是哪些方面吗?比如是界面的某个操作、按钮的摆放位置,还是文字提示呢?" 这样可以有效获取到可执行的优化点。

2.  **中期行动:全面审视与定位(针对产品本身)**
    *   **细节专项审查:** 将"细节"作为主题,组织团队对产品进行一次全面的细节审查。可以分模块进行,如UI/UX一致性、交互流畅度、文案准确性、性能加载速度、异常提示等,排查潜在的优化点。
    *   **数据分析佐证:** 结合后台数据,寻找可能印证"细节问题"的线索。例如,某个功能按钮的点击率低、某个页面的跳出率高等,都可能和细节体验不佳有关。

3.  **长期行动:建立反馈文化(针对未来)**
    *   **鼓励精细化反馈:** 在产品内设置便捷的反馈通道,并主动引导用户从"好用/不好用"的评价,转向"哪里好用/哪里可以更好"的具体描述。
    *   **建立用户反馈分级处理机制:** 将此类"建设性反馈"设为高优先级,形成从收集、分析、落实到反馈的闭环,从而持续驱动产品优化,培养更多忠实的"产品共建者"。

=== 过程步骤(steps) ===
1. [initial_attempt] 开始处理查询: 请帮我分析这段用户反馈是否积极,并给出改进建议:"这个产品还不错,就是细节上还有提升空间"
2. [action] 调用分析工具 analyze
3. [observation] 分析结果: 文本长度: 45 字符; 词汇数量: 1 个; 包含问号: 否; 情感倾向: 中性
4. [initial_response] 这段用户反馈整体情感倾向为中性。用户用"还不错"对产品给予了肯定,同时也明确指出了改进方向:"细节上还有提升空间"。

**改进建议**:建议团队关注产品细节,并可以尝试与该用户进一步沟通,了解其具体所指的细节,从而进行精准优化。
5. [reflection] LLM 反思结果:回答简洁且切题,但为增强分析的客观性和深度,建议使用工具进行量化分析。 (too_short=False, low_relevance=False, need_tools=True)
6. [action] 调用评估工具 evaluate
7. [observation] 评估结果(回答质量): 准确性:10/10; 完整性:9/10; 清晰度:9/10
8. [improvement] 这段用户反馈是一段典型的混合情感反馈,整体倾向积极,并包含了极具价值的建设性意见。

**情感分析:**

1.  **积极肯定(核心价值):** "这个产品还不错"是整个反馈的基石。用户明确表达了对产品核心功能和价值的认可,说明产品已经满足了其基本需求,并带来了正向体验。这是维持用户黏性的根本。
2.  **建设性意见(优化方向):** "就是细节上还有提升空间"是反馈的核心价值所在。这里的"就是"并非强烈的转折,而是一种补充和期望,表明用户对产品有更高的期待和参与感。不满意的用户通常会直接离开或给出负面评价,而愿意指出"细节"问题的用户,往往是产品的忠实用户或潜在忠实用户,他们希望产品能变得更好。

综合来看,这不是消极反馈,而是一个来自"友好批评者"的宝贵信号。情感可以概括为**"满意,但期待更高"**。

**改进建议:**

针对这类反馈,建议采取短期、中期和长期三步走的策略:

1.  **短期行动:跟进与深挖(针对该用户)**
    *   **及时响应与感谢:** 第一时间联系该用户,感谢他提出的宝贵意见,让他感受到被重视。
    *   **开放式提问引导:** 尝试用更具体的问题引导他说出细节所在。例如:"非常感谢您的反馈!为了让我们更好地改进,您能具体说说您指的是哪些方面吗?比如是界面的某个操作、按钮的摆放位置,还是文字提示呢?" 这样可以有效获取到可执行的优化点。

2.  **中期行动:全面审视与定位(针对产品本身)**
    *   **细节专项审查:** 将"细节"作为主题,组织团队对产品进行一次全面的细节审查。可以分模块进行,如UI/UX一致性、交互流畅度、文案准确性、性能加载速度、异常提示等,排查潜在的优化点。
    *   **数据分析佐证:** 结合后台数据,寻找可能印证"细节问题"的线索。例如,某个功能按钮的点击率低、某个页面的跳出率高等,都可能和细节体验不佳有关。

3.  **长期行动:建立反馈文化(针对未来)**
    *   **鼓励精细化反馈:** 在产品内设置便捷的反馈通道,并主动引导用户从"好用/不好用"的评价,转向"哪里好用/哪里可以更好"的具体描述。
    *   **建立用户反馈分级处理机制:** 将此类"建设性反馈"设为高优先级,形成从收集、分析、落实到反馈的闭环,从而持续驱动产品优化,培养更多忠实的"产品共建者"。
9. [final_reflection] 最终反思:已根据自我反思和工具评估生成优化后的回答。

九、总结

上述项目实现了以下功能:

  1. 用 LangGraph 把 Agent 行为显式建模为图(StateGraph)
  2. 用 TypedDict 状态集中管理所有中间结果与上下文
  3. 用本地 Tool(analyze / evaluate)给 LLM 提供结构化信号
  4. 用 LLM 自己生成结构化 JSON 反思(reflection_struct),而不是只有自然语言
  5. 基于反思结果驱动工具调用与最终回答改写
  6. 用 Step 机制沉淀可观测、可审计、可调试的思考轨迹

附:完整项目代码

python 复制代码
from __future__ import annotations

import json
from typing import TypedDict, List, Dict, Any, Optional

from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage

# ================================
# 0. 全局 LLM 客户端
# ================================
llm = ChatOpenAI(
    model="glm-4.6",
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    api_key="<your_secret_key>"
)


# ================================
# 1. 状态结构定义
# ================================

class Step(TypedDict):
    step_type: str
    content: str
    metadata: Dict[str, Any]


class ReflectState(TypedDict, total=False):
    """LangGraph 中流转的状态"""

    # 输入
    query: str

    # 过程记录
    steps: List[Step]
    reflections: List[str]

    # 各阶段产物
    initial_response: Optional[str]
    reflection: Optional[str]
    reflection_struct: Optional[Dict[str, Any]]
    improved_response: Optional[str]

    # 工具使用情况/结果
    used_tools: bool
    analysis_result: Optional[str]
    eval_result: Optional[str]


# ================================
# 2. 工具实现( analyzeTool / evaluateTool)
# ================================

def analyze_tool(args: Dict[str, Any]) -> str:
    text = args.get("text", "")
    if not text:
        return "分析结果: 文本为空"

    analysis: List[str] = []
    analysis.append(f"文本长度: {len(text)} 字符")
    analysis.append(f"词汇数量: {len(text.split())} 个")
    has_question = "是" if ("?" in text or "?" in text) else "否"
    analysis.append(f"包含问号: {has_question}")

    sentiment = "中性"
    positive_keywords = ["好", "棒", "优秀", "太好了", "很棒"]
    negative_keywords = ["差", "糟糕", "不好", "失败"]

    if any(k in text for k in positive_keywords):
        sentiment = "积极"
    elif any(k in text for k in negative_keywords):
        sentiment = "消极"

    analysis.append(f"情感倾向: {sentiment}")
    return "分析结果: " + "; ".join(analysis)


import random


def evaluate_tool(args: Dict[str, Any]) -> str:
    criteria = args.get("criteria") or "通用质量"
    rng = random.Random()

    scores: Dict[str, int] = {
        "准确性": 7 + rng.randint(0, 3),  # 7-10
        "完整性": 6 + rng.randint(0, 3),  # 6-9
        "清晰度": 8 + rng.randint(0, 2),  # 8-10
    }
    parts = [f"{k}:{v}/10" for k, v in scores.items()]
    return f"评估结果({criteria}): " + "; ".join(parts)


# ================================
# 3. Step 辅助函数
# ================================

def add_step(state: ReflectState, step_type: str, content: str,
             metadata: Optional[Dict[str, Any]] = None) -> ReflectState:
    if "steps" not in state or state["steps"] is None:
        state["steps"] = []
    step: Step = {
        "step_type": step_type,
        "content": content,
        "metadata": metadata or {},
    }
    state["steps"].append(step)
    return state


# ================================
# 4. 节点 1:初次尝试(带 LLM)
# ================================

def initial_attempt_node(state: ReflectState) -> ReflectState:
    """
    - 如果 query 里包含"分析",先调用 analyze_tool,再让 LLM 基于分析结果生成有信息量的初次回答。
    - 否则直接把 query 交给 LLM,请它给出简要初始回答。
    """
    query = state["query"]
    add_step(state, "initial_attempt", f"开始处理查询: {query}")

    if "分析" in query:
        # 先用本地 analyze 工具做结构化分析
        args = {"text": query}
        add_step(state, "action", "调用分析工具 analyze", {"tool": "analyze", "args": args})
        analysis_result = analyze_tool(args)
        add_step(state, "observation", analysis_result, {"tool": "analyze"})
        state["analysis_result"] = analysis_result
        state["used_tools"] = True

        # 再让 LLM 基于"原始问题 + 工具分析结果"生成初次回答
        messages = [
            SystemMessage(
                content=(
                    "你是一个负责分析用户问题的智能体。"
                    "已为你提供一段针对用户问题的结构化分析结果。"
                    "请基于这些分析,用简洁自然的中文回答用户的问题,"
                    "这是一个初步回答,后续还会有反思和改进。"
                )
            ),
            HumanMessage(
                content=(
                    f"用户问题:{query}\n\n"
                    f"分析结果如下:\n{analysis_result}\n\n"
                    "请给出一个初步的、简要的回答。"
                )
            ),
        ]
        resp = llm.invoke(messages)
        initial_response = resp.content.strip()

        add_step(state, "initial_response", initial_response, {"from": "llm+analyze"})
        state["initial_response"] = initial_response

    else:
        # 纯 LLM 初步回答
        messages = [
            SystemMessage(
                content=(
                    "你是一个谨慎的智能体,请对用户问题给出一个简要的初始回答。"
                    "这个回答不需要面面俱到,因为后续还有反思和改进环节。"
                )
            ),
            HumanMessage(content=f"用户问题:{query}")
        ]
        resp = llm.invoke(messages)
        initial_response = resp.content.strip()

        add_step(state, "initial_response", initial_response, {"from": "llm"})
        state["initial_response"] = initial_response
        state["used_tools"] = False

    return state


# ================================
# 5. 节点 2:反思(由 LLM 输出结构化 JSON)
# ================================

def reflect_node(state: ReflectState) -> ReflectState:
    """
    对应 Java ReflectAgent.reflect(initialResponse, query)
    差异:
    - 这里让 LLM 生成结构化 JSON 反思结果:
      {
        "too_short": bool,
        "low_relevance": bool,
        "need_tools": bool,
        "comments": "自然语言的反思说明"
      }
    """
    query = state["query"]
    initial_response = state.get("initial_response") or ""
    used_tools_flag = bool(state.get("used_tools"))

    # 构造提示,让 LLM 自己做自我评价
    system_prompt = (
        "你是一个对自己回答进行自我反思的智能体。"
        "请严格按照 JSON 格式输出反思结果,不要输出任何多余文字。"
        "JSON 字段含义:\n"
        "  - too_short: 回答是否过于简短,影响完整性。\n"
        "  - low_relevance: 回答是否与问题的关键点不够贴合。\n"
        "  - need_tools: 是否建议使用工具(例如外部评估工具)来辅助改进回答。\n"
        "  - comments: 用简洁中文总结你的反思结论。\n"
    )

    user_prompt = (
        f"用户问题:{query}\n\n"
        f"你的初始回答:{initial_response}\n\n"
        f"本轮初始回答是否使用过工具:{'是' if used_tools_flag else '否'}\n\n"
        "请从完整性、相关性、是否需要工具三方面进行反思,"
        "并输出 JSON,例如:\n"
        "{\n"
        '  \"too_short\": true,\n'
        '  \"low_relevance\": false,\n'
        '  \"need_tools\": true,\n'
        '  \"comments\": \"回答有点短,但大体相关,建议用工具评估一下逻辑严谨性。\"\n'
        "}\n"
    )

    resp = llm.invoke([SystemMessage(content=system_prompt),
                       HumanMessage(content=user_prompt)])
    raw = resp.content.strip()

    reflection_struct: Dict[str, Any] = {
        "too_short": False,
        "low_relevance": False,
        "need_tools": False,
        "comments": "LLM 反思信息解析失败,使用默认反思。"
    }

    try:
        parsed = json.loads(raw)
        # 做一层容错
        reflection_struct["too_short"] = bool(parsed.get("too_short", False))
        reflection_struct["low_relevance"] = bool(parsed.get("low_relevance", False))
        reflection_struct["need_tools"] = bool(parsed.get("need_tools", False))
        reflection_struct["comments"] = str(parsed.get("comments", "")).strip() or "(无详细说明)"
    except Exception:
        # 如果 JSON 解析失败,可以降级:简单根据长度和关键词做一点规则判断
        reflection_struct["too_short"] = len(initial_response) < 50
        reflection_struct["low_relevance"] = False  # 简化处理
        reflection_struct["need_tools"] = not used_tools_flag
        reflection_struct["comments"] = "LLM 输出非 JSON 格式,已使用降级规则推断反思结果。"

    # 记录 Step
    reflection_text = (
        f"LLM 反思结果:{reflection_struct['comments']} "
        f"(too_short={reflection_struct['too_short']}, "
        f"low_relevance={reflection_struct['low_relevance']}, "
        f"need_tools={reflection_struct['need_tools']})"
    )
    add_step(state, "reflection", reflection_text, {"raw": raw, "struct": reflection_struct})

    # 写入状态
    reflections = state.get("reflections") or []
    reflections.append(reflection_text)
    state["reflections"] = reflections
    state["reflection"] = reflection_text
    state["reflection_struct"] = reflection_struct

    return state


# ================================
# 6. 节点 3:改进(由 LLM 生成优化后回答)
# ================================

def improve_node(state: ReflectState) -> ReflectState:
    """
    对应 Java ReflectAgent.improve(...)
    与 Java 的差异:
    - 是否调用 evaluate_tool 由 LLM 反思结果中的 need_tools 决定
    - 最终"改进后的回答"由 LLM 生成,而不是简单字符串拼接
    """
    query = state["query"]
    initial_response = state.get("initial_response") or ""
    reflection_struct = state.get("reflection_struct") or {
        "too_short": False,
        "low_relevance": False,
        "need_tools": False,
        "comments": "",
    }

    improvements_notes: List[str] = []

    if reflection_struct.get("too_short"):
        improvements_notes.append("根据反思:初始回答偏短,需要补充细节。")

    if reflection_struct.get("low_relevance"):
        improvements_notes.append("根据反思:需要让回答更聚焦于问题核心。")

    eval_result = None
    if reflection_struct.get("need_tools"):
        # 调用本地评估工具
        args = {"criteria": "回答质量"}
        add_step(state, "action", "调用评估工具 evaluate", {"tool": "evaluate", "args": args})
        eval_result = evaluate_tool(args)
        add_step(state, "observation", eval_result, {"tool": "evaluate"})
        state["eval_result"] = eval_result
        state["used_tools"] = True
        improvements_notes.append(f"根据反思:已使用评估工具辅助改进。评估结果:{eval_result}")

    # 组织一个提示,让 LLM 生成"改进后的最终回答"
    system_prompt = (
        "你是一个会根据自我反思来改进答案的智能体。"
        "现在你已经有一个初始回答、一份自我反思、以及(可选的)工具评估结果。"
        "请给出一份改进后的、更加完整和相关的最终回答。"
        "注意:回答要用自然中文,不要输出任何 JSON 或额外解释。"
    )

    user_parts = [
        f"用户问题:{query}",
        f"初始回答:{initial_response}",
        f"自我反思(摘要):{reflection_struct.get('comments', '')}",
    ]
    if eval_result:
        user_parts.append(f"工具评估结果:{eval_result}")
    if improvements_notes:
        user_parts.append("需要改进的要点:" + ";".join(improvements_notes))
    user_prompt = "\n\n".join(user_parts)

    resp = llm.invoke(
        [SystemMessage(content=system_prompt),
         HumanMessage(content=user_prompt)]
    )
    improved_response = resp.content.strip()

    add_step(state, "improvement", improved_response, {"from": "llm"})
    final_reflection_text = "最终反思:已根据自我反思和工具评估生成优化后的回答。"
    add_step(state, "final_reflection", final_reflection_text)

    state["improved_response"] = improved_response
    return state


# ================================
# 7. 构建 LangGraph 图
# ================================

def build_reflect_agent_graph():
    """
    图结构:
    START -> initial_attempt -> reflect -> improve -> END
    """
    graph = StateGraph(ReflectState)

    graph.add_node("initial_attempt", initial_attempt_node)
    graph.add_node("reflect", reflect_node)
    graph.add_node("improve", improve_node)

    graph.add_edge(START, "initial_attempt")
    graph.add_edge("initial_attempt", "reflect")
    graph.add_edge("reflect", "improve")
    graph.add_edge("improve", END)

    app = graph.compile()
    return app


# ================================
# 8. 简单运行示例
# ================================

if __name__ == "__main__":
    app = build_reflect_agent_graph()

    query = "请帮我分析这段用户反馈是否积极,并给出改进建议:\"这个产品还不错,就是细节上还有提升空间\""

    init_state: ReflectState = {
        "query": query,
        "steps": [],
        "reflections": [],
    }

    final_state = app.invoke(init_state)

    print("=== 最终改进后的回答(LLM 生成) ===")
    print(final_state.get("improved_response", ""))

    print("\n=== 过程步骤(steps) ===")
    for i, step in enumerate(final_state.get("steps", []), start=1):
        print(f"{i}. [{step['step_type']}] {step['content']}")
相关推荐
安思派Anspire5 小时前
麦肯锡刚刚发布了他们的2025年AI报告。以下是TLDR
aigc·openai·agent
神州问学7 小时前
使用大语言模型从零构建知识图谱(下)
人工智能·llm
沛沛老爹9 小时前
ightRAG 系列 4:核心技术解析——检索模块详解(上)
llm·rag·lightrag·ai入门·向量化原理·向量化流程
大模型教程10 小时前
下一个时代属于AI Agent!5分钟讲明白什么是Agent?
程序员·llm·agent
大模型教程10 小时前
5小时整理60页《Google Agent指南》,不懂Agent的包教包会
程序员·llm·agent
Ma04071310 小时前
【论文阅读21】-基于大语言模型与领域知识图谱集成的CNC智能故障诊断
llm·知识图谱·故障诊断·cnc·rag·人在回路
AI大模型11 小时前
2025年AI智能体开发完全指南:10个GitHub顶级教程资源助你从入门到精通
程序员·llm·agent
AndrewHZ11 小时前
【AI算法工程师面试指北】以qwen3-next为例,阐述如何提升模型推理的tps?
人工智能·算法·面试·大模型·llm·阿里·qwen3-next