在传统的问答式系统中,智能体往往只做一件事:根据输入给出单次回答。这种"单轮输出"模式在简单场景下勉强可用,但面对复杂问题时就显得力不从心:回答可能过于简略、抓不住重点、甚至犯低级错误,同时缺乏"自我修正"的能力。
本文为读者朋友们展示如何构建一个具有:
- 初次尝试(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_tools、analysis_result、eval_result
这种状态设计有以下几个优点:
- 可观测:任何时刻都能看到 Agent 目前处于什么阶段、做过什么动作;
- 可调试:每一条 Step 都是一个可追踪的"日志事件";
- 可拓展 :后续若增加新工具、新阶段,只需往
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_attemptaction(工具调用)observation(工具观察结果)initial_responsereflectionimprovementfinal_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] 最终反思:已根据自我反思和工具评估生成优化后的回答。
九、总结
上述项目实现了以下功能:
- 用 LangGraph 把 Agent 行为显式建模为图(StateGraph)
- 用 TypedDict 状态集中管理所有中间结果与上下文
- 用本地 Tool(analyze / evaluate)给 LLM 提供结构化信号
- 用 LLM 自己生成结构化 JSON 反思(reflection_struct),而不是只有自然语言
- 基于反思结果驱动工具调用与最终回答改写
- 用 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']}")