ReAct 模式Reason推理生成一段文本输出描述当前情况的观察结果并制定解决任务的计划Action动作基于推理结果采取相应行动通过调用工具与环境交互或直接回复用户核心思想赋予 LLM 使用工具的能力使其可以与外部环境交互并让 LLM 以循环方式运行多智能体通信机制仅共享每次执行的最终结果通过 LangGraph 全局 State 状态做通信学生写完答案存入 state 的answer字段教授节点读取 state 里question、options、answer生成 critique存入 state 的response下一轮学生节点读取 state 里面的feedbackcritique拿到教授的批评作为输入继续优化答案不需要传递完整长长的全量消息历史只把上一轮最终产出answer、critique 反馈放进全局状态两个智能体读写同一个状态完成通信缩短消息列表节省 token本文所采用的方式全消息历史传递把所有对话、工具返回、全部消息塞进 messages 列表消息会越来越长token 消耗高反思反思有两种一种是自我反思用同一个 LLM 另一种是交叉反思用其他 LLM 评判 critique 当前 LLM 的结果交叉反思 cross reflection —— 推理用的大模型 和 反思批判用的大模型是两个不同 Agent不是同一个模型自己检查自己学生 AgentReAct 研究智能体做题、调用搜索工具ddg-search、wiki、arxiv输出答案教授 / 批判 AgentCritique 反思智能体拿到学生的答案不调用外部工具只做评判输出批评意见critiqueSTART→research_start学生 Agent 第一轮做题 →reflect教授 Agent 批判打分、写反馈→ 条件判断_should_end如果教授判定答案正确 / 达到最大迭代轮次 → END 结束如果教授指出答案有错、推理有漏洞 → 回到 research 节点学生 Agent 拿着教授的反馈再次调用工具修改答案循环迭代Pydantic 模型通过一个结构良好的规划该规划本身是 LLM 受控生成的结果所定义的 Pydantic 模型与 ReAct 智能体进行通信LLM 先输出一份结构化规划用 Pydantic 强制约束格式ReAct 智能体读取这份规划作为指令来干活Pydantic 就是通信协议「规划本身是 LLM 受控生成的结果」普通 LLM 输出自由文本想到哪说到哪。受控生成 LLM 必须严格按照 Pydantic 模型定义的字段输出不能随便乱写比如让 LLM 生成一个解题规划不能返回大段散文必须输出固定结构classPlan(BaseModel):task_steps:list[str]# 步骤列表need_search:bool# 是否需要调用工具检索goal:str# 本次目标LLM 调用with_structured_output(Plan)强制输出符合这个模型的 JSON这个 JSON 就是规划 plan这个规划不是手写硬编码是 LLM 自己生成的但格式被 Pydantic 锁住受控「结构良好的规划由 Pydantic 模型定义」Pydantic 在这里做两件事定义通信的 “数据协议 / 信封”规定规划里面必须包含哪些字段、类型是什么校验如果 LLM 输出的规划格式乱了、缺字段直接报错拒绝接收类比API 接口的 Request/Response JSON Schema两个程序通信必须约定好 JSON 长什么样Pydantic 就是这个约定「Pydantic 模型 和 ReAct 智能体进行通信」ReAct 智能体能调用工具、思考 行动不直接读自由自然语言文本而是读取这个 Pydantic 解析出来的结构化规划对象拿到任务步骤、目标、检索要求再去执行工具调用发送方规划 LLM输出 → 解析成 Pydantic Plan 对象接收方ReAct Agent读取这个 Plan 对象按步骤执行任务通信载体不是纯聊天文本 messages而是Pydantic 实例结构化对象代码示例# 1. 导入依赖包 fromdatasetsimportload_datasetfromlangchain.agentsimportload_toolsfromlanggraph.prebuiltimportcreate_react_agentfromlangchain_google_vertexaiimportChatVertexAIfromlangchain_core.promptsimportChatPromptTemplate,PromptTemplatefromlanggraph.graphimportStateGraph,START,ENDfromlanggraph.prebuilt.chat_agent_executorimportAgentStatefrompydanticimportBaseModel,FieldfromtypingimportAnnotated,Literal,TypedDictfromlangchain_core.runnables.configimportRunnableConfigfromoperatorimportadd# 2. 加载MMLU数据集书本示例题目 dsload_dataset(cais/mmlu,high_school_geography)ds_dictds[test].take(2).to_dict()# 打印问题print(ds_dict[question][0])# 打印选项print(ds_dict[choices][0])# 3. 初始化LLM与工具 【学生ReAct智能体】 # 书本用 gemini-2.0-flash-litellmChatVertexAI(modelgemini-2.0-flash-lite,temperature1.0)research_toolsload_tools(tool_names[ddg-search,arxiv,wikipedia],llmllm)# 学生智能体 系统提示词system_prompt(Youre a hard-working, curious and creative student. Youre preparing an answer to an exam question. Work hard, think step by step.Always provide an argumentation for your answer. Do not assume anything, use available tools to search for evidence and supporting statements.)# 学生提示模板raw_prompt_template(Answer the following multiple-choice question. \n\nQUESTION:\n{question}\n\nANSWER OPTIONS:\n{option}\n)promptChatPromptTemplate.from_messages([(system,system_prompt),(user,raw_prompt_template),(placeholder,{messages})])# 自定义学生智能体状态继承AgentStateclassMyAgentState(AgentState):question:stroptions:str# 创建学生ReAct智能体research_agentcreate_react_agent(modelllm,toolsresearch_tools,state_schemaMyAgentState,promptprompt)# 4. 【教授反思/批判Agent】critique 结构化输出定义 # Pydantic模型定义反思节点输出结构classResponse(BaseModel):A final response to the user.answer:str|NoneField(defaultNone,# description 是给 LLM 看的不是给 Python 代码做校验用的descriptionThe final answer. It should be empty if critique has been provided.,)critique:str|NoneField(defaultNone,# description 是给 LLM 看的不是给 Python 代码做校验用的descriptionA critique of the initial answer. If you think it might be incorrect, provide an actionable feedback,)# 教授反思提示词reflection_prompt(# 请审阅这份答案并给出反馈判断答案正确与否。如果你认为最终答案正确则直接给出该正确答案。# 仅当答案有误或者推理过程存在漏洞时才给出批判性意见。禁止自行脑补额外信息只能基于学生给出的推理判断其论据是否充分。# reasoning flaws 推理漏洞You are a university professor and youre supervising a student who is working on multiple-choice exam question. \nQUESTION: {question}.\nANSWER OPTIONS:\n{options}.\n\nSTUDENTS ANSWER:\n{answer}\n\nReflect on the answer and provide a feedback whether the answer is right or wrong. If you think the final answer is correct, reply with the final answer. Only provide critique if you think the answer might be incorrect or there are reasoning flaws. Do not assume anything, evaluate only the reasoning provided and whether there is enough evidence for their answer.)# 反思链模板 LLM 结构化输出reflection_chainPromptTemplate.from_template(reflection_prompt)|llm.with_structured_output(Response)# 5. 第二轮学生Agent携带critique反馈重新改进答案 raw_prompt_template_with_critique(You tried to answer the exam question and you get feedback from your professor. Work on improving your answer and incorporating the feedback.\n\nQUESTION:\n{question}\n\nANSWER OPTIONS:\n{options}\nINITIAL ANSWER:\n{answer}\n\nFEEDBACK:\n{feedback})prompt_with_critiqueChatPromptTemplate.from_messages([(system,system_prompt),(user,raw_prompt_template_with_critique),(placeholder,{messages})])# 带反馈的子图状态classReflectionState(ResearchState):answer:strfeedback:str# 创建【带批判反馈的ReAct智能体】research_agent_with_critiquecreate_react_agent(modelllm,toolsresearch_tools,state_schemaReflectionState,promptprompt_with_critique)# 6. 顶层图状态 ReflectionAgentState 【全局状态】 classReflectionAgentState(TypedDict):question:stroptions:stranswer:str# steps累加器每轮迭代1防止无限循环steps:Annotated[int,add]response:Response# 7. 节点函数定义 # 终止判断条件条件边def_should_end(state:ReflectionAgentState,config:RunnableConfig)-Literal[research,END]:max_reasoning_stepsconfig[configurable].get(max_reasoning_steps,10)# 情况1教授判定答案正确response.answer存在直接结束ifstate.get(response)andstate[response].answer:returnEND# 情况2迭代步数超过上限结束ifstate.get(steps,1)max_reasoning_steps:returnEND# 继续回到research节点重新做题returnresearch# 反思节点教授Agentdef_reflection_step(state):resultreflection_chain.invoke(state)# steps 1return{response:result,steps:1}# 第一轮研究节点def_research_start(state):answerresearch_agent.invoke(state)return{answer:answer[messages][-1].content}# 迭代阶段研究节点拿到critique反馈重新做题def_research(state):agent_state{answer:state[answer],question:state[question],options:state[options],feedback:state[response].critique}answerresearch_agent_with_critique.invoke(agent_state)return{answer:answer[messages][-1].content}# 8. 构建LangGraph图、边、条件路由 builderStateGraph(ReflectionAgentState)builder.add_node(research_start,_research_start)builder.add_node(research,_research)builder.add_node(reflect,_reflection_step)builder.add_edge(START,research_start)builder.add_edge(research_start,reflect)builder.add_edge(research,reflect)# 条件边reflect节点输出后判断走结束还是回到researchbuilder.add_conditional_edges(reflect,_should_end)graphbuilder.compile()# 绘制Mermaid图书本流程图# display(Image(graph.get_graph().draw_mermaid_png()))# 9. 运行图astream流式执行书本最后代码 questionds_dict[question][0]options\n.join([f{i1}.{a}fori,ainenumerate(ds_dict[choices][0])])asyncforeventingraph.astream({question:question,options:options},stream_modeupdates):print(event)附录placeholderplaceholder是 消息占位符用来动态插入一整个消息列表List[BaseMessage]比如[HumanMessage, AIMessage, ToolMessage]不是插入一段普通字符串普通{变量}只能填单个文本placeholder 专门用来放多条消息组成的消息数组promptChatPromptTemplate.from_messages([# 固定的 system 系统提示静态写死(system,system_prompt),# 固定的 user 用户提示模板静态(user,raw_prompt_template),# {messages}占位位置运行时塞一整串动态消息历史(placeholder,{messages})])create_react_agent底层会自动维护state[messages]里面存用户提问、模型回答、工具调用请求、工具返回结果、历史 AI 回复等多条消息当执行这个 prompt 时LangChain 会把 state 里完整的消息列表直接插入到 placeholder 这个位置为什么不能直接写(user, {messages})(user, xxx)代表单条 HumanMessage值只能是字符串。如果传消息数组会报错(placeholder, {messages})专门支持消息列表展开数组每一条Human/Ai/ToolMessage都作为独立消息放进最终发给LLM的消息序列假设messages [AIMessage(我需要搜索), ToolMessage(搜索结果xxx)]最后拼给 LLM 的消息队列变成[SystemMessage(system_prompt),HumanMessage(raw_prompt_template渲染后的文本),AIMessage(我需要搜索),ToolMessage(搜索结果xxx)]占位符把消息列表平铺展开追加在后面ReAct 智能体需要持续追加工具调用历史ToolMessage这些是多条消息必须靠 placeholder 注入模型服务商对复杂消息序列System、Human、AI、ToolMessage 交替的支持问题。placeholder 就是用来组装这种多类型消息序列占位符参数名messages{messages}名字必须和传入的变量 key 保持一致调用的时候类似prompt.invoke({question:xxx,option:xxx,messages:state[messages]# 这里传入消息列表映射到placeholder})LangGraph 超级步骤Super-step超步一个超级步骤 一轮执行周期所有本轮被激活的节点并行运行 → 全部节点跑完统一合并状态更新 → 确定下一轮要跑哪些节点同一个 super-step 内所有节点读取本轮开始那一刻的状态快照同一个超级步骤里 A 节点产生的状态变更同轮里 B 节点看不见必须等到本轮全部结束合并状态后下一个超级步骤才能读到。并行节点属于同一个超级步骤串行节点不属于同一个超级步骤超级步骤的 3 个阶段一轮完整循环计算阶段 Compute根据上一轮结束后的状态找出所有被触发、需要执行的节点并行执行所有节点都基于【本轮开始时的旧状态】来计算。同轮节点之间互相看不到对方在本轮产生的修改同步屏障 Barrier关卡等待本轮全部节点执行完毕没有节点提前进入下一轮状态合并 路由 Route收集本轮所有节点输出的状态更新合并到全局 State生成全新状态快照再根据边edge逻辑判断下一轮下一个 super-step哪些节点会被激活例子例子 1串行图START → LLM 节点 → Tool 节点 → ENDSuper-step 1执行 LLM 节点Super-step 2执行 Tool 节点串行链路每个超级步骤只跑 1 个节点每跑完 1 个节点就完成一轮 super-step并且自动生成一次 checkpoint 检查点这就是为什么 LangGraph 可以在 LLM 调用后暂停、做人工干预 human-in-the-loop断点一定落在 super-step 边界例子 2并行分支fan-out一个节点同时指向 A、B 两个节点上一步节点跑完同时触发 A、B 两个节点A、B在同一个超级步骤里面并行执行A、B 读取同样的初始状态互不感知对方输出等 A、B 全部跑完合并两者的更新进入下一轮超级步骤注意检查点checkpoint只在超级步骤边界生成不能在同一个 super-step 内部节点中间保存状态。中断恢复、时间旅行只能回到 super-step 结束后的状态快照节点什么时候属于哪一轮 super-step完全由图的边结构自动决定不需要手动配置终止逻辑每轮结束节点可以投票 halt当所有节点都休眠没有待处理消息整个图执行结束stream 和 astream 区别逻辑功能完全一样只是同步 / 异步的区别输出的流内容super-step 超级步骤的状态完全相同stream同步# 普通同步代码forchunkingraph.stream(inputs,stream_modevalues):print(chunk)循环执行时会阻塞主线程在等待 LLM / 工具 IO 期间整个程序卡住不能处理别的请求astream异步# 必须写在 async def 里面asyncforchunkingraph.astream(inputs,stream_modevalues):print(chunk)等待 LLM 网络 IO 时事件循环可以切去处理其他请求别的用户对话适合后端 Web 服务prompt 中的 messages 是如何注入的agent_state{answer:state[answer],question:state[question],options:state[options],feedback:state[response].critique}answerresearch_agent_with_critique.invoke(agent_state)research_agent_with_critique是create_react_agent创建的 ReAct 智能体messages是由create_react_agent在内部自动维护、自动填充的不是手动在agent_state里写进去的classMyAgentState(AgentState):question:stroptions:strAgentState来自langgraph.prebuilt.chat_agent_executor.AgentState自带内置字段messages: list[BaseMessage]这个是框架自带的不用自己声明messages不用传create_react_agent内部会初始化messages消息列表执行 ReAct 循环时自动追加LLM 思考、工具调用、工具返回结果、AI 回复到 messages 数组然后把messages塞进 prompt 里的(placeholder, {messages}){messages}这个占位符读取的是 ReAct 智能体内部 state 里自动维护的 messages不是外面顶层图ReflectionAgentState的 messages顶层大图 StateReflectionAgentState自己定义包含 question、options、answer、response、stepsReAct 子 Agent 自己内部的 State继承 AgentState自带 messagesresearch_agent_with_critique单独的小状态独立维护工具对话历史调用research_agent_with_critique.invoke(agent_state)时把agent_state里的question/options/feedback传给 ReAct 子 AgentReAct 子 Agent内部新建自己独立的 messages 列表在 ReAct 工具循环中不断追加 ToolMessage/AIMessage和外层图 state 隔离开为什么最后拿到answer[messages][-1].contentanswerresearch_agent_with_critique.invoke(agent_state)return{answer:answer[messages][-1].content}answer是ReAct 子 Agent 执行完成后返回的它自己内部完整 state 字典answer[messages]就是 ReAct 子 Agent 内部自动收集的全部对话 工具消息列表[-1]取最后一条 AI 输出作为本次修订后的答案再回写到顶层大图 state 的answer字段这里存在两层独立 state顶层 LangGraph 图的 state和 ReAct 子 Agent 内部独立 state各自隔离传入的agent_state只是业务参数question/feedback 等交给 ReAct 的 prompt 渲染工具交互的消息历史是 ReAct 内部自己生成维护如果是顶层图直接维护 messages那就要在顶层ReflectionAgentState里加上messages字段调用时手动传messagesstate[messages]placeholder 才会读取顶层的消息如果是把工具消息交给 ReAct 子 Agent 内部管理顶层大图只负责保存最终答案和评审 critique仅共享最终结果而不是传递完整消息历史
阅读完成 · 觉得有帮助?