fake_chat fake_chat 是 LangGraph 官方写好的模拟 LLM,可以让我们快速学习模型接口。
在文件开始,导入了正则表达式、迭代器、异步等功能包以及 LangChain 的核心。接下来创建了一个继承了 GenericFakeChatModel 的类,名为 FakeChatModel。成员变量有:
1 2 3 4 5 messages: list [BaseMessage] i: int = 0
bind_tools 函数表示绑定工具,Fake 模型不支持工具所以直接返回自己。
1 2 def bind_tools (self, functions: list ): return self
_generate() 函数是 ChatModel 最核心的方法,当外部调用 model.invoke() 时,最终会调用 _generate()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def _generate ( self, messages: list [BaseMessage], stop: list [str ] | None = None , run_manager: CallbackManagerForLLMRun | None = None , **kwargs: Any , ) -> ChatResult: """Top Level call""" if self .i >= len (self .messages): self .i = 0 message = self .messages[self .i] self .i += 1 if isinstance (message, str ): message_ = AIMessage(content=message) else : if hasattr (message, "model_copy" ): message_ = message.model_copy() else : message_ = message.copy() generation = ChatGeneration(message=message_) return ChatResult(generations=[generation])
这里只是从消息列表中依次读取消息,然后判断是否为字符串,然后转换为 LangChain 能接收的 Message 类型。最终通过 ChatGeneration() 函数生成结果。
_stream() 用于流式输出,对应的是 model.stream() 函数。但其本质上是先调用 generate 函数拿到完整答案,然后获取 AIMessage,再获取文本。再通过正则表达式拆分文本:
1 2 3 4 content_chunks = re.split( r"(\s)" , content )
接下来创建 chunk,将拆分好的文本存入不同的 chunk。通过 run_manager.on_llm_new_token() 函数通知前端新 Token 来了,方便前端显示。整个函数完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 def _stream ( self, messages: list [BaseMessage], stop: list [str ] | None = None , run_manager: CallbackManagerForLLMRun | None = None , **kwargs: Any , ) -> Iterator[ChatGenerationChunk]: """Stream the output of the model.""" chat_result = self ._generate( messages, stop=stop, run_manager=run_manager, **kwargs ) if not isinstance (chat_result, ChatResult): raise ValueError( f"Expected generate to return a ChatResult, " f"but got {type (chat_result)} instead." ) message = chat_result.generations[0 ].message if not isinstance (message, AIMessage): raise ValueError( f"Expected invoke to return an AIMessage, " f"but got {type (message)} instead." ) content = message.content if content: assert isinstance (content, str ) content_chunks = cast(list [str ], re.split(r"(\s)" , content)) for i, token in enumerate (content_chunks): if i == len (content_chunks) - 1 : chunk = ChatGenerationChunk( message=AIMessageChunk( content=token, id =message.id , chunk_position="last" ) ) else : chunk = ChatGenerationChunk( message=AIMessageChunk(content=token, id =message.id ) ) if run_manager: run_manager.on_llm_new_token(token, chunk=chunk) yield chunk else : args = message.__dict__ args.pop("type" ) chunk = ChatGenerationChunk( message=AIMessageChunk(**args, chunk_position="last" ) ) if run_manager: run_manager.on_llm_new_token("" , chunk=chunk) yield chunk
AgentState AgentState 是整个 LangGraph 的核心,Agent 运行过程中的所有信息都存在这个字典里:
1 2 3 4 5 6 class AgentState (TypedDict ): """The state of the agent.""" messages: Annotated[Sequence [BaseMessage], add_messages] remaining_steps: NotRequired[RemainingSteps]
messages:完整对话历史。Annotated[…, add_messages] 是重点 —— add_messages 是 reducer,规定”新值如何合并进旧值”。因为没有 reducer 时,新消息会覆盖旧消息;有了它,新消息会 append 追加到历史里。 remaining_steps:剩余步数上限(防死循环)。
create_react_agent 这个函数是 LangGraph 里的核心函数之一,它的作用是根据一个大模型 + 工具集合,自动创建一个 ReAct Agent 的状态图(StateGraph),最后返回一个可以执行的 Agent。其函数入口如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 def create_react_agent ( model: str | LanguageModelLike | Callable [[StateSchema, Runtime[ContextT]], BaseChatModel] | Callable [[StateSchema, Runtime[ContextT]], Awaitable[BaseChatModel]] | Callable [ [StateSchema, Runtime[ContextT]], Runnable[LanguageModelInput, BaseMessage] ] | Callable [ [StateSchema, Runtime[ContextT]], Awaitable[Runnable[LanguageModelInput, BaseMessage]], ], tools: Sequence [BaseTool | Callable | dict [str , Any ]] | ToolNode, *, prompt: Prompt | None = None , response_format: StructuredResponseSchema | tuple [str , StructuredResponseSchema] | None = None , pre_model_hook: RunnableLike | None = None , post_model_hook: RunnableLike | None = None , state_schema: StateSchemaType | None = None , context_schema: type [Any ] | None = None , checkpointer: Checkpointer | None = None , store: BaseStore | None = None , interrupt_before: list [str ] | None = None , interrupt_after: list [str ] | None = None , debug: bool = False , version: Literal ["v1" , "v2" ] = "v2" , name: str | None = None , **deprecated_kwargs: Any , ) -> CompiledStateGraph:
其中有三个关键部分:model、tools、返回值。
model 在这个函数中,model 可以是很多种形式: 1、字符串,LangGraph 内部根据字符串帮你创建模型。 2、直接传模型对象,任何满足 LangChain Runnable 接口的模型都可以。 3、Callable 模型。例如:
1 2 3 4 5 6 7 Callable [[ StateSchema, Runtime[ContextT] ], BaseChatModel ]
意为 model 也可以是一个函数。其具体语法如下:
1 2 3 4 5 6 7 8 Callable [[int , int ], int ]def add ( a:int , b:int )->int : return a+b
这样做可以让 Agent 动态选择模型,简单问题和复杂问题用不同的模型回答。
第二个参数 tools 表示工具,它也可以是很多种类型。 1、BaseTool,LangChain 标准工具。例如:
1 2 3 @tool def search (query:str ): return result
2、普通函数。例如:
1 2 def add (a,b ): return a+b
3、dict,工具描述,例如
1 2 3 4 5 { "name" :"search" ,"description" :"搜索网页" ,"parameters" :{}}
4、ToolNode,是 LangGraph 的工具节点。
返回值 CompiledStateGraph 这里返回的并不是 Agent 对象,而是编译好的状态图,之后可以直接使用 invoke 函数。
其他参数
prompt:Agent 的系统提示词
response_format:输出格式
pre_model_hook:模型调用之前执行…
post_model_hook:模型调用之后执行…
state_schema:定义 Agent 状态
context_schema:上下文
checkpointer:检查点,用于保存 Agent 状态
store:用于长期储存
interrupt_before:执行前暂停
interrupt_after:执行后暂停
工具归一化 在 create_react_agent 函数中,会将各种形式的 tools 统一成两种:
ict 形式 → llm_builtin_tools(模型内置工具 schema,直接传给模型用)
其他 (函数 / basetool) → 包进一个 ToolNode,从它提取 tool_classes
1 2 3 4 5 6 7 if isinstance (tools, ToolNode): tool_classes = list (tools.tools_by_name.values()) tool_node = tools else : llm_builtin_tools = [t for t in tools if isinstance (t, dict )] tool_node = ToolNode([t for t in tools if not isinstance (t, dict )]) tool_classes = list (tool_node.tools_by_name.values())
call_model 在 LangGraph 工作流中,这个函数负责调用 LLM,并把 AI 回复写回 State。其整体流程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def call_model (state,runtime,config ): input = get_messages(state) if dynamic: model = choose_model(state,runtime) response = model.invoke(input ) response.name=name if too_many_steps: return error_message return { "messages" :[response] }
注意最后一行,节点并不直接修改 state,而是返回一个 dict 表示对 state 的更新。LangGraph 会拿这个 dict 和 旧 state 合并。
should_continue 这个函数负责判断 LLM 下一步应该干什么:结束?调用工具?结构化输出?进入后处理?其核心判断代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 def should_continue (state: StateSchema ) -> str | list [Send]: messages = _get_state_value(state, "messages" ) last_message = messages[-1 ] if not isinstance (last_message, AIMessage) or not last_message.tool_calls: if post_model_hook is not None : return "post_model_hook" elif response_format is not None : return "generate_structured_response" else : return END else : if version == "v1" : return "tools" elif version == "v2" : if post_model_hook is not None : return "post_model_hook" return [ Send( "tools" , ToolCallWithContext( __type ="tool_call_with_context" , tool_call=call, state=state, ), ) for call in last_message.tool_calls ]
这段代码检查最后一条 AIMessage 有没有 tool_calls,没有就结束,有的话就将每个工具调用单独发给一个 tools 节点(并行)。在判断完成后就会创建新的图,其核心代码如下:
1 2 3 4 5 6 7 workflow = StateGraph(state_schema=state_schema, context_schema=context_schema) workflow.add_node("agent" , RunnableCallable(call_model, acall_model), input_schema=input_schema) workflow.add_node("tools" , tool_node) workflow.set_entry_point("agent" ) workflow.add_conditional_edges("agent" , should_continue, path_map=agent_paths) workflow.add_edge("tools" , entrypoint) return workflow.compile (checkpointer=..., store=...)
其整体逻辑如下图。
1 2 3 4 5 6 ┌──────────► agent (调 LLM) ──────────┐ │ │ │ │ │ should_continue │ (无 tool_calls → END) │ ▼ ▼ │ "tools" (执行工具) END └──────────────┘
这就是 ReAct 循环:agent → 有工具调用吗?→ 有则执行 tools → 回到 agent → 直到没有工具调用为止。
StateGraph 打开 state.py,可以看到 StateGraph 类下的注释:
节点签名是 State -> Partial:输入整个状态,输出部分状态更新。
每个 key 可以用 reducer 注解,签名 (Value, Value) -> Value。
StateGraph 是 builder,不能直接执行,必须 .compile()。
.compile() 之后支持 invoke() / stream() / ainvoke() / astream()。
其核心属性如下:
edges: set[tuple[str, str]] # 静态边: {(来源, 目标)}
nodes: dict[str, StateNodeSpec] # 节点: {名字: 节点规范}
branches: defaultdict[str, dict[str, BranchSpec]] # 条件边: {节点: {分支名: 分支}}
channels: dict[str, BaseChannel] # 状态通道
schemas: dict[type, dict[str, …]] # 每个 schema 的通道
这就是”图纸”的完整数据模型:节点 + 边 + 条件边,全部存起来,compile 时才变成可执行结构。
tools_condition 是一个路由函数,用于判断 LLM 是否产生了模型调用,其核心判断逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 def tools_condition (state, messages_key="messages" ) -> Literal ["tools" , "__end__" ]: if isinstance (state, list ): ai_message = state[-1 ] elif (isinstance (state, dict ) and (messages := state.get(messages_key, []))) or \ (messages := getattr (state, messages_key, [])): ai_message = messages[-1 ] else : raise ValueError(f"No messages found in input state to tool_edge: {state} " ) if hasattr (ai_message, "tool_calls" ) and len (ai_message.tool_calls) > 0 : return "tools" return "__end__"
它兼容三种格式状态:list / dict / BaseModel,检查最后一句话是否有 tool_calls。其实这个函数与 should_continue 是同一模式,只是这个函数更加精简。
ToolNode 是一个大类,其输入可以有三种格式:①图状态(有 messages 键);②消息列表;③直接的工具调用列表。输出是 {“messages”: [ToolMessage(…)]} 或 [ToolMessage(…)]。
不同工具的结果通过 _combine_tool_outputs 函数汇总,多个工具并行执行完,把结果收集成一个 list。普通 ToolMessage 直接包成 {messages: […]} 返回(配合 add_messages reducer 追加进历史)。有 Command 时走复杂路径:合并 Command(goto=…)
小结:ToolNode 负责”执行工具 + 把结果转成 ToolMessage 塞回状态”。tools_condition 负责”要不要执行工具”。两者配合就是完整的 ReAct 闭环。
练习 目标 使用今天看过的东西复现假 ReAct 模型。理解 ReAct 的循环。
代码 完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from langgraph.prebuilt.chat_agent_executor import create_react_agentfrom langchain_core.messages import HumanMessage, AIMessage, ToolMessage, BaseMessagefrom langchain_core.language_models.fake_chat_models import GenericFakeChatModelfrom langchain_core.tools import toolclass MyFakeChatModel (GenericFakeChatModel ): def bind_tools (self, functions: list ): return self model = MyFakeChatModel(messages=iter ([ AIMessage(content="" , tool_calls=[{"id" : "1" , "name" : "search_weather" , "args" : {"city" : "sf" }}]), AIMessage(content="天气很好" ), ])) @tool def search_weather (city: str ): """查询城市天气""" return f"result for {city} " client = create_react_agent(model, tools=[search_weather]) result = client.invoke({"messages" :[HumanMessage(content="你好" )]}) print ("=== 最终对话历史 ===" )for m in result["messages" ]: print (f" {type (m).__name__} : content={m.content!r} tool_calls={getattr (m, 'tool_calls' , None )!r} " ) print (f"=== 消息条数: {len (result['messages' ])} ===" )
最终输出如下:
1 2 3 4 5 6 === 最终对话历史 === HumanMessage: content='你好' tool_calls=None AIMessage: content='' tool_calls=[{'name': 'search_weather', 'args': {'city': 'sf'}, 'id': '1', 'type': 'tool_call'}] ToolMessage: content='result for sf' tool_calls=None AIMessage: content='天气很好' tool_calls=[] === 消息条数: 4 ===