30 行代码 langChain.js 开发你的第一个 Agent

7,127 阅读5分钟

大家好,我是双越。前百度 滴滴 资深前端工程师,慕课网金牌讲师,PMP。我的代表作有:

  • wangEditor 开源 web 富文本编辑器,GitHub 18k star,npm 周下载量 20k
  • 划水AI Node 全栈 AIGC 知识库,包括 AI 写作、多人协同编辑。复杂业务,真实上线。
  • 前端面试派 系统专业的面试导航,刷题,写简历,看面试技巧,内推工作。开源免费。

我最近整理了一些 AI Agent 开发相关的资料,有兴趣的同学可以加入分享和讨论

开始

LangChain 是一个开发 LLM 应用的框架,是目前 LLM 开发最流行的解决方案之一。最早是 Python 开发的,后来也出来 JS 语言的,现在已经更新到 v0.3 版本。

本文将使用 LangChain 开发一个简单的 AI Agent,可使用自然语言来查询天气。再使用 LangGraph 进行自定义流程。

开发第一个 Agent

首先创建一个 nodejs 项目,并安装 langchain 和 dotenv ,后面我们需要使用环境变量。

npm i langchain dotenv

使用 Tavily 搜索功能

Tavily 是一个搜索引擎,可以让你的 LLM 和 Agent 有联网搜索的能力,例如搜索天气。

你需要注册登录并创建一个 API key ,Tavily 可免费使用 1000 次,足够学习使用。

把 API key 放在代码库 .env 文件中

TAVILY_API_KEY=xxx

安装 langchain tavily 插件

npm i @langchain/tavily

然后创建一个 agent.js 文件,代码如下

import 'dotenv/config'
import { TavilySearch } from '@langchain/tavily'

// 定义 tools
const agentTools = [
    new TavilySearch({
        maxResults: 3 // 最多查询 3 个结果
    })
]

选择一个大模型

langChain 集成了有很多 LLM 可供选择 js.langchain.com/docs/integr…

它默认推荐的是 OpenAI 但是在国内我们没法直接调用它的 API ,所以我当前选择的是 DeepSeek 。

注册登录 DeepSeek 创建一个 API key 并把它放在 .env 文件中

DEEPSEEK_API_KEY=xxx

安装 langChain deepseek 插件

npm i @langchain/deepseek

然后继续写 agent.js 代码

import { ChatDeepSeek } from '@langchain/deepseek'


// 定义 llm
const agentModel = new ChatDeepSeek({ model: 'deepseek-chat', temperature: 0 })

使用 langGraph 创建 Agent

langGraph 是基于 langChain 的框架,用于构建可控制的 Agent ,是现代开发 Agent 最流行的解决方案之一。

安装 langGraph 必要插件

npm install @langchain/langgraph @langchain/core

然后继续写 agent.js 代码

import { MemorySaver } from '@langchain/langgraph'
import { createReactAgent } from '@langchain/langgraph/prebuilt'

// Initialize memory to persist state between graph runs
const agentCheckpoint = new MemorySaver()

// Create agent
const agent = createReactAgent({
  llm: agentModel, // 使用之前创建的 llm
  tools: agentTools, // 使用之前创建的 tools
  checkpointSaver: agentCheckpoint, // 记忆,保存状态数据
})

使用 createReactAgent 来创建一个 Re-Act Agent 智能体。Re-Act 即 Reason + Act 推理和执行,它可以使用 llm 自主推理并选择调用哪个 tool 获取结果。这也是 Agent 的特点之一。

image.png

agentCheckpoint 可以存储近期对话记录,让 llm 有记忆能力。否则你刚说自己叫张三,再问它我的名字,它就忘了。

调用 Agent

继续写 agent.js 代码,使用 agent.invoke 方法调用 agent ,传入 HumanMessage 即 prompt 提示词

import { HumanMessage } from '@langchain/core/messages'

// test1
const agentFinalState = await agent.invoke(
  { messages: [new HumanMessage('what is the current weather in sf')] },
  { configurable: { thread_id: '1' } }
)
console.log(
  agentFinalState.messages[agentFinalState.messages.length - 1].content
)

// test2
const agentNextState = await agent.invoke(
  { messages: [new HumanMessage('what about Beijing')] },
  { configurable: { thread_id: '1' } }
)
console.log(agentNextState.messages[agentNextState.messages.length - 1].content)

执行 node agent.js 可以看到控制台打印结果,可以查询到 San Francisco 和 Beijing 的天气。

image.png

这里的 { thread_id: '1' } 是 Memory 存储记忆的索引, thread_id 相同的才是同一个对话记录,才能共享记忆。例如这里第二次调用时 prompt 是 'what about Beijing' 并没有问天气,但是 llm 也能根据上一次对话判断出这里是问天气,所以才能给出正确答案。

还可以使用 agent.stream 方式流式输出,配合前端可实现打字效果

// streaming
const stream = await agent.stream(
  { messages: [new HumanMessage('what is the current weather in sf')] },
  { configurable: { thread_id: '1' }, streamMode: 'updates' }
)
for await (const step of stream) {
  console.log(JSON.stringify(step))
}

以上就是一个最基本的 AI ReAct Agent,核心代码不到 30 行。它包含三个要素:llm + tools + memory ,可以根据自然语言自主推理并调用 tool 获取结果,最后组合为自然语言,返回给用户。

自定义 Agent 行为

ReAct Agent 可以自主推理并执行,但如果你想自己控制这个流程呢? LangGraph 可以让你自定义 Agent 行为。

定义 toolNode

继续修改你的 agent.js代码。定义一个 toolNode ,并且在创建 model 时绑定 tools 。

import { ToolNode } from '@langchain/langgraph/prebuilt'

// Define the tools for the agent to use
const tools = [new TavilySearch({ maxResults: 3 })]
const toolNode = new ToolNode(tools)

// Create a model and give it access to the tools
const model = new ChatDeepSeek({
  model: 'deepseek-chat',
  temperature: 0,
}).bindTools(tools)

定义节点函数

然后定义一个函数 shouldContinue 用于判断 llm 是否要发起一个 tool 调用?

// Define the function that determines whether to continue or not
function shouldContinue({ messages }) {
  const lastMessage = messages[messages.length - 1]

  // If the LLM makes a tool call, then we route to the "tools" node
  if (lastMessage.tool_calls?.length) {
    return 'tools'
  }
  // Otherwise, we stop (reply to the user) using the special "__end__" node
  return '__end__'
}

再定义一个函数 callModel 用于调用 llm 并返回结果

// Define the function that calls the model
async function callModel(state) {
  const response = await model.invoke(state.messages)

  // We return a list, because this will get added to the existing list
  return { messages: [response] }
}

定义工作流

最后定义工作流 workflow ,并编译为一个 app 。这个 app 可以被触发执行。

// Define a new graph
const workflow = new StateGraph(MessagesAnnotation)
  .addNode('agent', callModel) // 添加节点 agent ,对应 callModel 函数
  .addEdge('__start__', 'agent') // 添加“边”,流程开始就指向 agent 节点
  .addNode('tools', toolNode) // 添加节点 tools ,对应 toolNode
  .addEdge('tools', 'agent') // 添加边,tools 节点指向 agent 节点
  .addConditionalEdges('agent', shouldContinue) // 添加条件边,根据 shouldContinue 函数里的逻辑,有 tool_calls 返回 'tools' 即指向 tools 节点,否则返回 '__end__' 即流程结束

// Finally, we compile it into a LangChain Runnable.
const app = workflow.compile()

工作流的定义过程看以上代码的注释,整体的流程图如下:开始,先执行 agent,再执行 shouldContinue 判断,然后要么结束、要么去执行 tools ,tools 再到 agent 。

/* workflow diagram
           ┌──────────┐
           │ __start__│
               ↓
          ┌──────────┐
          │  agent   │ ←────────┐
          └────┬─────┘          │
               ↓                │
        ┌───────────  ─┐        │
        │shouldContinue│── tools│
        └────┬───────  ┘        │
             ↓                  ↓
         __end__        ┌──────────┐
                        │  tools   │
                        └──────────┘

*/

调用工作流

同样可以使用 invoke 方法触发工作流,传入 HumanMessage 即用户 prompt 信息

const finalState = await app.invoke({
  messages: [new HumanMessage('what is the weather in sf')],
})
console.log(finalState.messages[finalState.messages.length - 1].content)

const nextState = await app.invoke({
  // Including the messages from the previous run gives the LLM context.
  // This way it knows we're asking about the weather in NY
  messages: [...finalState.messages, new HumanMessage('what about ny')],
})
console.log(nextState.messages[nextState.messages.length - 1].content)

控制台执行 node agent.js 返回结果如下

image.png

最后

使用 ReAct Agent 会更加灵活,使用 StateGraph 可以自定义控制 Agent 行为,langChain 提供了强大的 LLM 开发能力。

最后,前端想学全栈 + AI 开发,可以看看我做的 划水AI 项目,AI 写作、多人协同编辑。复杂业务,真实上线,可注册试用。