跳转至

构建基础聊天机器人

翻译说明

本文档由墨攻网络安全大模型团队翻译

在本教程中,你将构建一个基础聊天机器人。这个聊天机器人是以下一系列教程的基础,你将逐步添加更复杂的功能,并在此过程中了解关键的 LangGraph 概念。让我们开始吧! 🌟

前提条件

在开始本教程之前,请确保你能访问支持工具调用功能的 LLM,例如 OpenAIAnthropicGoogle Gemini

1. 安装包

安装所需的包:

pip install -U langgraph langsmith
npm install @langchain/langgraph @langchain/core zod
yarn add @langchain/langgraph @langchain/core zod
pnpm add @langchain/langgraph @langchain/core zod
bun add @langchain/langgraph @langchain/core zod

Tip

注册 LangSmith 以快速发现问题并提高 LangGraph 项目的性能。LangSmith 允许你使用跟踪数据来调试、测试和监控使用 LangGraph 构建的 LLM 应用。有关如何开始的更多信息,请参阅 LangSmith 文档

2. 创建 StateGraph

现在你可以使用 LangGraph 创建一个基础聊天机器人。这个聊天机器人将直接响应用户消息。

首先创建一个 StateGraphStateGraph 对象将我们的聊天机器人的结构定义为"状态机"。我们将添加 nodes 来表示 llm 和聊天机器人可以调用的函数,并添加 edges 来指定机器人应如何在这些函数之间转换。

from typing import Annotated

from typing_extensions import TypedDict

from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages


class State(TypedDict):
    # Messages 的类型是 "list"。注解中的 `add_messages` 函数
    # 定义了这个状态键应该如何更新
    # (在这种情况下,它将消息追加到列表中,而不是覆盖它们)
    messages: Annotated[list, add_messages]


graph_builder = StateGraph(State)
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
import { z } from "zod";

const State = z.object({ messages: MessagesZodState.shape.messages });

const graph = new StateGraph(State).compile();

我们的图现在可以处理两个关键任务:

  1. 每个 node 可以接收当前 State 作为输入并输出对状态的更新。
  2. 由于预构建的归约器函数,对 messages 的更新将追加到现有列表而不是覆盖它。

概念

定义图时,第一步是定义其 StateState 包括图的 schema 和处理状态更新的归约器函数。在我们的示例中,State 是一个有一个键的 schema:messages。归约器函数用于将新消息追加到列表而不是覆盖它。没有归约器注解的键将覆盖以前的值。

要了解有关状态、归约器和相关概念的更多信息,请参阅 LangGraph 参考文档

3. 添加节点

接下来,添加一个 "chatbot" 节点。**节点**表示工作单元,通常是常规函数。

让我们首先选择一个聊天模型:

聊天模型配置

查看支持的聊天模型配置方法:聊天模型选项卡

支持的模型包括:OpenAI、Anthropic、Azure、Google Gemini 和 AWS Bedrock。

import { ChatOpenAI } from "@langchain/openai";
// or import { ChatAnthropic } from "@langchain/anthropic";

const llm = new ChatOpenAI({
  model: "gpt-4o",
  temperature: 0,
});

我们现在可以将聊天模型合并到一个简单的节点中:

def chatbot(state: State):
    return {"messages": [llm.invoke(state["messages"])]}


# 第一个参数是唯一的节点名称
# 第二个参数是每次使用节点时将被调用的函数或对象
graph_builder.add_node("chatbot", chatbot)
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
import { z } from "zod";

const State = z.object({ messages: MessagesZodState.shape.messages });

const graph = new StateGraph(State)
  .addNode("chatbot", async (state: z.infer<typeof State>) => {
    return { messages: [await llm.invoke(state.messages)] };
  })
  .compile();

注意 chatbot 节点函数如何将当前 State 作为输入并返回一个包含键 "messages" 下更新的 messages 列表的字典。这是所有 LangGraph 节点函数的基本模式。

我们 State 中的 add_messages 函数将把 LLM 的响应消息追加到状态中已有的任何消息。

MessagesZodState 中使用的 addMessages 函数将把 LLM 的响应消息追加到状态中已有的任何消息。

4. 添加 entry

添加一个 entry 点来告诉图**每次运行时从哪里开始工作**:

graph_builder.add_edge(START, "chatbot")
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
import { z } from "zod";

const State = z.object({ messages: MessagesZodState.shape.messages });

const graph = new StateGraph(State)
  .addNode("chatbot", async (state: z.infer<typeof State>) => {
    return { messages: [await llm.invoke(state.messages)] };
  })
  .addEdge(START, "chatbot")
  .compile();

5. 添加 exit

添加一个 exit 点来指示**图应该在哪里完成执行**。这对于更复杂的流程很有帮助,但即使在像这样的简单图中,添加结束节点也能提高清晰度。

graph_builder.add_edge("chatbot", END)
import { StateGraph, MessagesZodState, START, END } from "@langchain/langgraph";
import { z } from "zod";

const State = z.object({ messages: MessagesZodState.shape.messages });

const graph = new StateGraph(State)
  .addNode("chatbot", async (state: z.infer<typeof State>) => {
    return { messages: [await llm.invoke(state.messages)] };
  })
  .addEdge(START, "chatbot")
  .addEdge("chatbot", END)
  .compile();

这告诉图在运行 chatbot 节点后终止。

6. 编译图

在运行图之前,我们需要编译它。我们可以通过在图构建器上调用 compile() 来实现。这将创建一个 CompiledGraph,我们可以在状态上调用它。

graph = graph_builder.compile()
import { StateGraph, MessagesZodState, START, END } from "@langchain/langgraph";
import { z } from "zod";

const State = z.object({ messages: MessagesZodState.shape.messages });

const graph = new StateGraph(State)
  .addNode("chatbot", async (state: z.infer<typeof State>) => {
    return { messages: [await llm.invoke(state.messages)] };
  })
  .addEdge(START, "chatbot")
  .addEdge("chatbot", END)
  .compile();

7. 可视化图(可选)

你可以使用 get_graph 方法和其中一种"绘制"方法(如 draw_asciidraw_png)来可视化图。draw 方法各需要额外的依赖项。

from IPython.display import Image, display

try:
    display(Image(graph.get_graph().draw_mermaid_png()))
except Exception:
    # 这需要一些额外的依赖项并且是可选的
    pass

你可以使用 getGraph 方法可视化图,并使用 drawMermaidPng 方法渲染图。

import * as fs from "node:fs/promises";

const drawableGraph = await graph.getGraphAsync();
const image = await drawableGraph.drawMermaidPng();
const imageBuffer = new Uint8Array(await image.arrayBuffer());

await fs.writeFile("basic-chatbot.png", imageBuffer);

basic chatbot diagram

8. 运行聊天机器人

现在运行聊天机器人!

Tip

你可以随时通过输入 quitexitq 退出聊天循环。

def stream_graph_updates(user_input: str):
    for event in graph.stream({"messages": [{"role": "user", "content": user_input}]}):
        for value in event.values():
            print("Assistant:", value["messages"][-1].content)


while True:
    try:
        user_input = input("User: ")
        if user_input.lower() in ["quit", "exit", "q"]:
            print("Goodbye!")
            break
        stream_graph_updates(user_input)
    except:
        # 如果 input() 不可用时的后备方案
        user_input = "What do you know about LangGraph?"
        print("User: " + user_input)
        stream_graph_updates(user_input)
        break
import { HumanMessage } from "@langchain/core/messages";

async function streamGraphUpdates(userInput: string) {
  const stream = await graph.stream({
    messages: [new HumanMessage(userInput)],
  });

import * as readline from "node:readline/promises";
import { StateGraph, MessagesZodState, START, END } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";

const llm = new ChatOpenAI({ model: "gpt-4o-mini" });

const State = z.object({ messages: MessagesZodState.shape.messages });

const graph = new StateGraph(State)
  .addNode("chatbot", async (state: z.infer<typeof State>) => {
    return { messages: [await llm.invoke(state.messages)] };
  })
  .addEdge(START, "chatbot")
  .addEdge("chatbot", END)
  .compile();

async function generateText(content: string) {
  const stream = await graph.stream(
    { messages: [{ type: "human", content }] },
    { streamMode: "values" }
  );

  for await (const event of stream) {
    for (const value of Object.values(event)) {
      console.log(
        "Assistant:",
        value.messages[value.messages.length - 1].content
      );
    const lastMessage = event.messages.at(-1);
    if (lastMessage?.getType() === "ai") {
      console.log(`Assistant: ${lastMessage.text}`);
    }
  }
}

const prompt = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

while (true) {
  const human = await prompt.question("User: ");
  if (["quit", "exit", "q"].includes(human.trim())) break;
  await generateText(human || "What do you know about LangGraph?");
}

prompt.close();
Assistant: LangGraph is a library designed to help build stateful multi-agent applications using language models. It provides tools for creating workflows and state machines to coordinate multiple AI agents or language model interactions. LangGraph is built on top of LangChain, leveraging its components while adding graph-based coordination capabilities. It's particularly useful for developing more complex, stateful AI applications that go beyond simple query-response interactions.
Goodbye!

恭喜! 你已经使用 LangGraph 构建了第一个聊天机器人。这个机器人可以通过接收用户输入并使用 LLM 生成响应来进行基本对话。你可以查看上面调用的 LangSmith Trace

以下是本教程的完整代码:

from typing import Annotated

from langchain.chat_models import init_chat_model
from typing_extensions import TypedDict

from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages


class State(TypedDict):
    messages: Annotated[list, add_messages]


graph_builder = StateGraph(State)


llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")


def chatbot(state: State):
    return {"messages": [llm.invoke(state["messages"])]}


# 第一个参数是唯一的节点名称
# 第二个参数是每次使用节点时将被调用的函数或对象
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_edge(START, "chatbot")
graph_builder.add_edge("chatbot", END)
graph = graph_builder.compile()
import { StateGraph, START, END, MessagesZodState } from "@langchain/langgraph";
import { z } from "zod";
import { ChatOpenAI } from "@langchain/openai";

const llm = new ChatOpenAI({
  model: "gpt-4o",
  temperature: 0,
});

const State = z.object({ messages: MessagesZodState.shape.messages });

const graph = new StateGraph(State);
  // 第一个参数是唯一的节点名称
  // 第二个参数是每次使用节点时将被调用的函数或对象
  .addNode("chatbot", async (state) => {
    return { messages: [await llm.invoke(state.messages)] };
  });
  .addEdge(START, "chatbot");
  .addEdge("chatbot", END)
  .compile();

下一步

你可能已经注意到,机器人的知识仅限于其训练数据中的内容。在下一部分中,我们将添加一个网络搜索工具来扩展机器人的知识并使其更强大。