深入浅出 LangChain —— 第二章:环境搭建与快速上手饿

17 阅读15分钟

📖 本章学习目标

  • ✅ 配置完整的 LangChain.js 开发环境(Node.js + TypeScript)
  • ✅ 理解模块化包安装的设计理念和最佳实践
  • ✅ 创建并运行第一个可工作的 AI Agent
  • ✅ 接入 LangSmith 实现可视化调试
  • ✅ 掌握流式输出的实现方法
  • ✅ 识别并避免常见的配置陷阱

一、开发环境准备

在写第一行代码之前,先把开发工具配好。这就像厨师做菜前要准备好厨具和食材一样重要。

1、Node.js 与包管理器

LangChain.js 要求 Node.js 20+(推荐 LTS 版本)。为什么需要这么高的版本?因为 LangChain.js 大量使用了现代 JavaScript 特性(如 ES Modules、Top-level await),这些特性在旧版本中支持不完善。

(1)检查当前Node版本

node -v   # 应该输出 v20.x.x 或更高

如果版本过低,需要升级。不同操作系统有不同的管理工具:

macOS / Linux 用户: 推荐使用 nvm(Node Version Manager)

# 安装 nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# 重新加载 shell 配置
source ~/.bashrc  # 或 source ~/.zshrc

# 安装并使用 Node.js 20
nvm install 20
nvm use 20

Windows 用户: 推荐使用 fnm(Fast Node Manager),比 nvm for Windows 更稳定

# 使用 winget 安装 fnm
winget install Schniz.fnm

# 安装并使用 Node.js 20
fnm install 20
fnm use 20

(2)安装包管理器

包管理器推荐使用 pnpm,相比 npm 和 yarn,它有两大优势:

  • 更快:使用硬链接和符号链接,避免重复下载
  • 更节省磁盘空间:全局存储依赖,多个项目共享
# 通过 npm 安装 pnpm(全局安装)
npm install -g pnpm

# 验证安装成功
pnpm -v

💡 小贴士:如果你已经熟悉 npm 或 yarn,继续使用也完全没问题。本系列教程的命令会用 pnpm,你可以轻松转换为对应的 npm/yarn 命令。

2、TypeScript 配置

所有教程的代码使用 TypeScript,原因有三:

  1. 类型安全:在编码阶段就能发现很多错误
  2. IDE 支持:智能提示和自动补全更准确
  3. 生产实践:大型项目几乎都用 TypeScript

(3)初始化项目

# 创建项目目录并进入
mkdir my-langchain-agent && cd my-langchain-agent

# 初始化 package.json
pnpm init

# 安装 TypeScript 相关依赖(-D 表示开发依赖)
pnpm add -D typescript tsx @types/node

依赖说明:

  • typescript:TypeScript 编译器
  • tsx:直接运行 TypeScript 文件的工具(无需手动编译)
  • @types/node:Node.js 的类型定义

(4)创建 TypeScript 配置文件

在项目根目录创建 tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",           // 目标 JavaScript 版本
    "module": "ESNext",           // 模块系统
    "moduleResolution": "bundler", // 模块解析策略
    "strict": true,               // 启用严格类型检查
    "esModuleInterop": true,      // 允许 import CommonJS 模块
    "skipLibCheck": true,         // 跳过库文件的类型检查(加速编译)
    "outDir": "./dist",           // 编译输出目录
    "rootDir": "./src"            // 源代码目录
  },
  "include": ["src/**/*"]         // 包含的文件
}

关键配置解读:

  • "strict": true:开启所有严格类型检查选项,帮助你在编码阶段发现潜在问题
  • "moduleResolution": "bundler":现代化的模块解析策略,更适合打包工具
  • "esModuleInterop": true:让你可以用 import x from 'module' 的方式导入 CommonJS 模块

(5)配置运行脚本

package.json 中添加脚本,方便开发和构建:

{
  "scripts": {
    "dev": "tsx src/index.ts",  // 开发模式:直接运行 TS 文件
    "build": "tsc"              // 构建模式:编译为 JS
  },
  "type": "module"              // ⚠️ 重要:声明使用 ESM 模块系统
}

⚠️ 注意"type": "module" 这一行非常重要!它告诉 Node.js 你的项目使用 ES Modules(而不是传统的 CommonJS)。缺少这一行会导致 import 语句报错。

3、安装 LangChain.js

LangChain.js 采用模块化设计,核心包和各个 Provider 集成包分开安装。

(1)为什么要模块化?

想象一下,如果你只需要 OpenAI 的模型,却要安装 Anthropic、Google、Ollama 等所有 Provider 的依赖,会造成:

  • 安装包体积巨大(可能超过 100MB)
  • 安装时间长
  • 潜在的依赖冲突

模块化设计让你只安装需要的部分

(2)安装核心包

# LangChain 核心包(必须)
pnpm add langchain

(3) 安装 Provider 集成包

根据你的需求选择安装(至少选一个):

# OpenAI(GPT-4o、GPT-4o-mini 等)
pnpm add @langchain/openai

# Anthropic(Claude Opus、Sonnet、Haiku 等)
pnpm add @langchain/anthropic

# Google(Gemini 系列)
pnpm add @langchain/google-genai

# Ollama(本地运行的开源模型)
pnpm add @langchain/ollama

(5)安装 LangGraph

# LangGraph(底层运行时,Agent 的基础)
pnpm add @langchain/langgraph

💡 提示:虽然 LangGraph 是底层依赖,但建议显式安装,确保版本可控。

4. 安装类型校验库Zod

Zod 是一个 TypeScript 类型验证库,用于确保输入数据符合定义的格式。

pnpm add zod

5、配置 API Key

大多数 LLM Provider 都需要 API Key 才能使用。我们需要安全地存储这些密钥。

(1)创建环境变量文件

在项目根目录创建 .env 文件:

# .env 文件(存储敏感信息,不要提交到 Git)

# OpenAI API Key(从 https://platform.openai.com/api-keys 获取)
OPENAI_API_KEY=sk-your-openai-key-here

# Anthropic API Key(可选,如果使用 Claude 模型)
ANTHROPIC_API_KEY=sk-ant-your-anthropic-key-here

# LangSmith 配置(可选,但强烈推荐用于调试)
LANGSMITH_TRACING=true
LANGSMITH_API_KEY=ls-your-langsmith-key-here
LANGSMITH_PROJECT=my-first-agent

(2)创建 .gitignore 文件

为了防止敏感信息泄露,必须把 .env 加入 .gitignore

# .gitignore
.env
node_modules/
dist/

(3)安装 dotenv 包

dotenv 是一个轻量级的库,用于在应用启动时加载 .env 文件中的环境变量:

pnpm add dotenv

最终的项目结构

my-langchain-agent/
├── src/
│   └── index.ts          # 主入口文件
├── .env                  # API Keys(不提交到 Git)
├── .gitignore            # Git 忽略配置
├── package.json          # 项目配置和依赖
└── tsconfig.json         # TypeScript 配置

二、第一个 Agent:天气查询示例

环境配好了,现在来写第一个能真正运行的 Agent。我们从一个简单的场景开始:查询天气。

1、最简版本:20 行代码

这个示例展示如何用最少代码创建一个可用的 Agent。

(1)完整代码

// src/index.ts

// 第一步:加载环境变量(必须在最顶部)
import "dotenv/config";

// 第二步:导入 LangChain 核心功能
import { createAgent, tool } from "langchain";
import { z } from "zod";

// 第三步:定义天气查询工具
const getWeather = tool(
  ({ city }) => `${city} 今日天气:晴,气温 22°C,湿度 60%`,
  {
    name: "get_weather",
    description: "查询指定城市的当前天气",
    schema: z.object({
      city: z.string().describe("要查询天气的城市名称"),
    }),
  }
);

// 第四步:创建 Agent
const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [getWeather],
});

// 第五步:调用 Agent 并输出结果
const result = await agent.invoke({
  messages: [{ role: "user", content: "北京和上海今天天气怎么样?" }],
});

console.log(result.messages.at(-1)?.content);

(2)运行代码

pnpm dev

(3)预期输出

北京今日天气:晴,气温 22°C,湿度 60%
上海今日天气:晴,气温 22°C,湿度 60%

两个城市今天天气都不错,都是晴天,气温适中。如果你计划出行,不用担心雨天哦!

✨ 神奇之处:你只定义了一个查询单个城市天气的工具,但 Agent 自动判断需要查询两个城市,连续调用了两次工具,最后生成了汇总回答。这就是 Agent 的智能之处!

2、代码逐段解析

让我们深入理解每一部分的作用。

(1)导入依赖

// 加载环境变量(让 process.env.OPENAI_API_KEY 可用)
import "dotenv/config";

// 导入 LangChain 核心功能
import { createAgent, tool } from "langchain";

// 导入 Zod(用于定义参数校验规则)
import { z } from "zod";

关键点:

  • dotenv/config 必须在最顶部导入,确保在其他代码执行前加载环境变量
  • createAgent:创建 Agent 的核心函数
  • tool:将普通函数包装成 Agent 可调用的工具
  • zod:类型安全的参数校验库(LangChain.js 内置依赖)

(2)定义工具

const getWeather = tool(
  // 参数 1:工具的执行函数
  // 接收解构的参数对象,返回字符串结果
  ({ city }) => `${city} 今日天气:晴,气温 22°C,湿度 60%`,

  // 参数 2:工具的元数据(告诉 LLM 如何使用这个工具)
  {
    name: "get_weather",                    // 工具的唯一标识符
    description: "查询指定城市的当前天气",     // 工具的功能描述
    schema: z.object({                      // 参数校验规则
      city: z.string().describe("要查询天气的城市名称"),
    }),
  }
);

工具定义的三个关键要素:

要素作用重要性
nameLLM 用来引用这个工具⭐⭐⭐ 必须唯一
descriptionLLM 根据描述决定是否调用⭐⭐⭐⭐⭐ 最关键
schema定义参数的类型和约束⭐⭐⭐⭐ 保证类型安全

💡 最佳实践description 写得越详细(明确而不是字多),Agent 的表现越好。例如:

description: "查询指定城市的当前天气情况,包括温度、湿度、天气状况。当用户询问某个地方的天气时使用此工具。"

(3)创建 Agent

const agent = createAgent({
  model: "openai:gpt-4o",  // 使用的模型(Provider:模型名格式)
  tools: [getWeather],      // 注册给 Agent 的工具列表
});

支持的模型格式:

// OpenAI 系列
model: "openai:gpt-4o"
model: "openai:gpt-4o-mini"
model: "openai:o1-mini"

// Anthropic 系列
model: "anthropic:claude-opus-4-5"
model: "anthropic:claude-sonnet-4-6"

// Google 系列
model: "google:gemini-2.0-flash"

(4)调用 Agent

const result = await agent.invoke({
  messages: [{ role: "user", content: "北京和上海今天天气怎么样?" }],
});

返回值结构:

result = {
  messages: [
    { role: "user", content: "北京和上海今天天气怎么样?" },
    { role: "assistant", content: "", tool_calls: [...] },  // LLM 决定调用工具
    { role: "tool", content: "北京 今日天气:晴...", name: "get_weather" },
    { role: "assistant", content: "", tool_calls: [...] },  // 再次调用工具
    { role: "tool", content: "上海 今日天气:晴...", name: "get_weather" },
    { role: "assistant", content: "两个城市今天天气都不错..." }  // 最终回答
  ]
}

使用 result.messages.at(-1)?.content 获取最后一条消息(即最终回答)。

3、Agent 执行流程可视化

为了帮助你理解 Agent 的内部工作流程,我们用序列图展示:

sequenceDiagram
    participant U as 用户
    participant A as Agent
    participant L as GPT-4o (LLM)
    participant T as get_weather 工具

    U->>A: "北京和上海今天天气怎么样?"
    A->>L: 传入用户消息 + 工具定义
    Note over L: LLM 分析任务,<br/>决定需要查天气
    L-->>A: tool_call(get_weather, {city: "北京"})
    A->>T: 执行工具调用
    T-->>A: "北京 今日天气:晴..."
    A->>L: 传入工具返回结果
    Note over L: LLM 发现还需要查上海
    L-->>A: tool_call(get_weather, {city: "上海"})
    A->>T: 执行工具调用
    T-->>A: "上海 今日天气:晴..."
    A->>L: 传入全部结果
    Note over L: LLM 生成汇总回答
    L-->>A: "两个城市今天天气都不错..."
    A-->>U: 返回最终回答

关键点:

  • Agent 会自主决策调用几次工具
  • 每次工具调用后,结果都会回传给 LLM
  • LLM 基于所有历史信息决定下一步行动
  • 整个过程是循环迭代的,直到 LLM 认为任务完成

三、接入 LangSmith:让每一步都可见

上面的示例跑通了,但你有没有想过:

  • Agent 在执行过程中,LLM 具体传了什么 Prompt?
  • 工具调用的参数是什么?
  • 每一步花了多少时间?
  • 用了多少 Token,成本是多少?

在传统软件开发里,你可以加 console.log 来调试。但在 Agent 开发中,调用链路可能有十几步,而且 LLM 的每次调用都有延迟,手动打日志效率很低。

LangSmith 就是为这个场景设计的可观测性平台。

1、注册并获取 API Key

步骤 1:访问官网

前往 LangSmith 官网

步骤 2:注册账号

可以使用 GitHub 账号直接登录,或者用邮箱注册。

步骤 3:创建项目

登录后,点击 "Create Project",输入项目名称(如 my-first-agent)。

步骤 4:获取 API Key

在项目设置页面,找到 "API Keys" 标签页,复制你的 API Key。

2、开启追踪

.env 文件中添加以下配置:

# .env
LANGSMITH_TRACING=true
LANGSMITH_API_KEY=ls_你的API_Key
LANGSMITH_PROJECT=my-first-agent  # 项目名,可以自定义

就这样!不需要修改任何业务代码。

3、查看执行追踪

再次运行你的 Agent:

pnpm dev

然后打开 LangSmith 控制台(smith.langchain.com),你会看到类似这样的完整执行追踪:

Run: "北京和上海今天天气怎么样?"
├── 🤖 LLM Call 1(决策阶段)
│   ├── Input: [system prompt] + [user message] + [工具定义]
│   ├── Output: tool_call(get_weather, {city: "北京"})
│   ├── Latency: 1.2s
│   └── Tokens: 312 (prompt: 280, completion: 32)
│
├── 🔧 Tool Call: get_weather
│   ├── Input: {city: "北京"}
│   └── Output: "北京 今日天气:晴..."
│
├── 🤖 LLM Call 2(再次决策)
│   ├── Input: [...历史消息] + [工具结果]
│   ├── Output: tool_call(get_weather, {city: "上海"})
│   ├── Latency: 0.9s
│   └── Tokens: 198
│
├── 🔧 Tool Call: get_weather
│   ├── Input: {city: "上海"}
│   └── Output: "上海 今日天气:晴..."
│
└── 🤖 LLM Call 3(生成最终回答)
    ├── Input: [...历史消息] + [所有工具结果]
    ├── Output: "两个城市今天天气都不错..."
    ├── Latency: 1.5s
    └── Tokens: 267

你可以在 LangSmith 中看到:

  • ✅ 每次 LLM 调用的完整输入输出
  • ✅ 工具调用的参数和返回值
  • ✅ 每一步的耗时和 Token 消耗
  • ✅ 整个执行链路的可视化流程图

🔍 为什么可观测性在 AI 开发中尤为重要?

传统软件的 bug 往往是确定性的——同样的输入,永远产生同样的错误。LLM 应用则不同:

  • 模型输出具有随机性(即使 temperature=0)
  • 同一个 Prompt 在不同时刻可能给出不同的工具调用决策
  • Agent 的行为依赖于多轮交互的累积状态

如果你不追踪每次执行的完整链路,遇到 Agent 行为异常时几乎无从下手。

建议:从第一天就开启 LangSmith,养成在 LangSmith 里调试的习惯,而不是只看终端输出。


四、流式输出:让 Agent 实时响应

上面的示例使用 invoke(),需要等 Agent 完成全部推理才返回结果。对于耗时较长的任务(比如需要调用多次工具),用户体验不好(用户只能盯着空白屏幕等待 5-10 秒甚至更长)。

流式输出(Streaming) 可以让 Agent 的中间过程和最终输出实时传输,实现类似 ChatGPT 的"打字机效果"。

1、基础流式示例

// src/streaming.ts
import "dotenv/config";
import { createAgent, tool } from "langchain";
import { z } from "zod";

// 定义工具(和之前一样)
const getWeather = tool(
  ({ city }) => `${city} 今日天气:晴,气温 22°C,湿度 60%`,
  {
    name: "get_weather",
    description: "查询指定城市的当前天气",
    schema: z.object({ city: z.string() }),
  }
);

// 创建 Agent(和之前一样)
const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [getWeather],
});

// 使用 stream() 替代 invoke()
const stream = await agent.stream(
  { messages: [{ role: "user", content: "北京今天天气怎么样?" }] },
  { streamMode: "values" }  // 流式返回每次状态更新
);

// 遍历流式数据
for await (const chunk of stream) {
  const lastMessage = chunk.messages.at(-1);
  if (lastMessage?.role === "assistant") {
    process.stdout.write(lastMessage.content as string);
  }
}

代码解读:

  • 第 19 行:使用 stream() 方法替代 invoke()
  • 第 21 行:streamMode: "values" 表示返回每次状态更新的完整快照
  • 第 24-28 行:使用 for await...of 异步遍历流式数据
  • 第 26-27 行:只输出 assistant 角色的消息内容

2、streamMode 的三种模式

模式返回内容适用场景
"values"每次状态更新后的完整状态需要展示中间步骤,了解 Agent 的思考过程
"updates"每次状态变化的增量部分只关心变化部分,减少数据传输
"messages"逐 Token 流式输出对话界面的打字机效果,用户体验最佳

3、逐 Token 流式输出(推荐用于聊天界面)

如果你想实现类似 ChatGPT 的效果(文字逐个出现),使用 "messages" 模式:

const stream = await agent.stream(
  { messages: [{ role: "user", content: "北京今天天气怎么样?" }] },
  { streamMode: "messages" }  // 逐 Token 流式输出
);

for await (const [message, metadata] of stream) {
  if (message.role === "assistant") {
    // 逐字符输出,实现打字机效果
    process.stdout.write(message.content);
  }
}

效果:

北→京→今→日→天→气→:→晴→,→气→温→ →2→2→°→C...

💡 实际应用场景

  • Web 应用:使用 Server-Sent Events (SSE)WebSocket 将流式数据推送到前端
  • CLI 工具:直接在终端逐字输出,提升交互体验
  • 移动端:分块渲染,减少首屏等待时间

五、完整的项目结构

至此,一个基本的 LangChain.js 项目结构如下:

my-langchain-agent/
├── src/
│   ├── tools/              # 工具定义目录
│   │   └── weather.ts      # 天气查询工具
│   ├── agents/             # Agent 配置目录
│   │   └── main.ts         # 主 Agent 创建逻辑
│   └── index.ts            # 入口文件
├── .env                    # API Keys(不提交 Git)
├── .env.example            # 环境变量模板(提交 Git)
├── .gitignore              # Git 忽略配置
├── package.json            # 项目配置
└── tsconfig.json           # TypeScript 配置

添加.env.example 文件

这是一个模板文件,提交到代码仓库,告诉其他开发者需要配置哪些环境变量:

# .env.example(提交到 Git)

# OpenAI API Key(必填)
# 从 https://platform.openai.com/api-keys 获取
OPENAI_API_KEY=

# Anthropic API Key(可选)
# 从 https://console.anthropic.com/settings/keys 获取
ANTHROPIC_API_KEY=

# LangSmith 配置(推荐)
# 从 https://smith.langchain.com 获取
LANGSMITH_TRACING=true
LANGSMITH_API_KEY=
LANGSMITH_PROJECT=my-first-agent

使用方法:

# 克隆项目后,复制模板并填写真实值
cp .env.example .env
# 然后用编辑器打开 .env 填入真实的 API Key

六、常见问题与踩坑指南

在实际开发中,你可能会遇到以下问题。这里整理了最常见的几个坑和解决方案。

⚠️ 踩坑 1:Cannot use import statement in a module 错误

错误现象:

SyntaxError: Cannot use import statement outside a module

原因: Node.js 默认使用 CommonJS 模块系统(require/module.exports),而 LangChain.js 使用 ES Modules(import/export)。

解决方案:package.json 中添加 "type": "module"

{
  "name": "my-langchain-agent",
  "version": "1.0.0",
  "type": "module",  // ← 添加这一行
  "scripts": {
    "dev": "tsx src/index.ts"
  }
}

⚠️ 踩坑 2:API Key 没有生效

错误现象:

AuthenticationError: Invalid API key

原因: dotenv/config 需要在代码最顶部导入,且必须在其他 LangChain 导入之前。

解决方案:

// ✅ 正确:dotenv 必须第一行
import "dotenv/config";
import { createAgent } from "langchain";

// ❌ 错误:dotenv 在后面,环境变量可能已经被读取过
import { createAgent } from "langchain";
import "dotenv/config";

验证方法:

import "dotenv/config";
console.log("API Key:", process.env.OPENAI_API_KEY?.substring(0, 10) + "...");
// 应该输出:API Key: sk-xxxxx...

⚠️ 踩坑 3:工具返回值类型错误

错误现象:

TypeError: Tool output must be a string

原因: 工具函数的返回值必须是字符串(或者能序列化为字符串的内容)。

解决方案: 如果工具返回的是对象或数组,手动序列化:

const searchTool = tool(
  async ({ query }) => {
    const results = await fetchSearchResults(query);
    
    // ❌ 错误:返回对象
    // return results;
    
    // ✅ 正确:序列化为 JSON 字符串
    return JSON.stringify(results);
  },
  { 
    name: "search", 
    description: "搜索互联网", 
    schema: z.object({ query: z.string() }) 
  }
);

⚠️ 踩坑 4:model 字符串格式错误

错误现象:

Error: Invalid model identifier

原因: v1.x 的 model 参数使用 "provider:model-name" 格式,而不是直接传模型实例。

解决方案:

// ✅ 正确:使用字符串标识符(推荐)
createAgent({ model: "openai:gpt-4o" })

// ✅ 也可以:显式实例化(需要精细配置时)
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0.7 });
createAgent({ model })

// ❌ 错误:v0.x 的旧写法(已废弃)
createAgent({ llm: new ChatOpenAI(...) })

⚠️ 踩坑 5:Zod Schema 定义不完整

错误现象: Agent 调用工具时传入了错误的参数类型,但没有报错。

原因: Zod Schema 定义不够严格,缺少必要的校验规则。

解决方案: 为每个参数添加详细的描述和校验:

// ❌ 不够严格
schema: z.object({
  city: z.string(),
})

// ✅ 更严格的定义
schema: z.object({
  city: z.string()
    .min(1, "城市名称不能为空")
    .max(50, "城市名称过长")
    .describe("要查询天气的城市名称,如:北京、上海、广州"),
})

七、本章小结

恭喜你完成了第一章的实战练习!这一章我们完成了三件重要的事情:

✅ 学习成果回顾

  1. 搭好了开发环境

    • Node.js 20+ 和 pnpm 包管理器
    • TypeScript 配置和 tsx 运行工具
    • LangChain.js 模块化安装策略
  2. 跑通了第一个 Agent

    • 理解了 tool() 定义工具的三要素(name、description、schema)
    • 掌握了 createAgent() 创建 Agent 的方法
    • 学会了 invoke() 同步调用和 stream() 流式输出
  3. 接入了 LangSmith

    • 用两个环境变量就开启了完整的执行链路追踪
    • 理解了可观测性在 AI 开发中的重要性

🎯 动手练习

尝试完成以下练习,巩固所学知识:

练习 1:扩展天气工具 修改 getWeather 工具,让它返回更真实的天气数据(可以调用免费的天气 API,如 OpenWeatherMap)。

练习 2:添加新工具 创建一个 getTime 工具,返回当前时间。测试 Agent 能否正确回答"现在几点了?"

练习 3:对比不同模型 将模型从 "openai:gpt-4o" 改为 "openai:gpt-4o-mini",观察响应速度和质量的差异。

练习 4:探索 LangSmith 在 LangSmith 控制台中查看你的 Agent 执行轨迹,找出:

  • 总共调用了几次 LLM?
  • 每次调用的 Token 消耗是多少?
  • 工具调用的参数是否正确?

📚 延伸阅读


下一章:第三章 —— 模型抽象层(Models & Messages)