Skip to content

第一次调用

要点

  • 第一次调用 LangChain,建议先建一个独立 playground,避免直接塞进业务项目里。
  • 把环境变量、依赖版本和脚本路径显式固定下来,可以减少后续排查问题的时间。
  • 直接调模型时,重点关注 invoke()stream() 的区别:前者一次性返回,后者边生成边返回。
  • 最小 Agent 的写法与直接调模型类似,核心变化是入口从 model.stream() 变成 agent.stream(),并增加 streamMode: 'messages' 配置。

1. 背景:先让调用跑通

学习 LangChain 时,最容易遇到的阻力不是概念复杂,而是环境没配好。模型密钥、base URL、依赖版本、脚本执行目录,任何一项没对齐,都会让示例跑不起来。所以第一步不是研究 Agent 或工具,而是先把一次最简单的模型调用跑通。

这一节按三个小步骤走:

  1. 直接调一次模型,确认连接正常。
  2. 把调用改成流式输出,理解 stream() 的行为。
  3. 换成最小 Agent,为后面加工具做准备。

这样走下来,后面再看消息、Prompt、Tool、Agent 时,不会觉得突然。

2. 目录与依赖

2.1 目录结构

第一次尝试 LangChain,建议先单独放一个 playground,而不是直接塞进业务项目。独立目录更容易定位问题,也更容易把示例沉淀下来。

一个最小 playground 的目录结构如下:

langchain-first-call/
├── .env.local          # 模型密钥和 base URL
├── .gitignore          # 忽略密钥和依赖
├── package.json        # 依赖和运行命令
├── tsconfig.json       # TypeScript 配置
└── scripts/
    ├── first-call.ts   # 第一次完整调用
    ├── first-stream.ts # 第一次流式输出
    └── first-agent.ts  # 第一次 Agent 流式调用

2.2 依赖配置

下面是一份最小可用的 package.json

json
{
  "name": "langchain-first-call-playground",
  "private": true,
  "type": "module",
  "packageManager": "yarn@4.12.0",
  "scripts": {
    "first-call": "tsx scripts/first-call.ts",
    "first-stream": "tsx scripts/first-stream.ts",
    "first-agent": "tsx scripts/first-agent.ts"
  },
  "dependencies": {
    "@langchain/core": "^1.1.36",
    "@langchain/openai": "^1.3.1",
    "dotenv": "^17.3.1",
    "langchain": "^1.2.37"
  },
  "devDependencies": {
    "tsx": "^4.21.0",
    "typescript": "^6.0.2"
  }
}

最常用的依赖说明:

  • @langchain/openai:负责接 OpenAI 兼容接口。即使使用 DeepSeek、OpenRouter 等第三方模型,只要走 OpenAI 兼容格式,就可以用这个包。
  • langchain:提供 createAgent() 等高层 API。
  • dotenv:读取 .env.local
  • tsx:直接运行 TypeScript 脚本,不用先编译。

3. 环境变量

当前 playground 需要三个变量:

shellscript
# .env.local
MODEL_API_KEY=sk-xxxxxxxxxxxxxxxx
MODEL_BASE_URL=https://api.deepseek.com/v1
MODEL_NAME=deepseek-chat

这三个字段分别对应:

  • MODEL_API_KEY:模型密钥。
  • MODEL_BASE_URL:OpenAI 兼容接口地址。
  • MODEL_NAME:模型名。

一个容易漏掉的细节是:MODEL_BASE_URL 要带 /v1。如果少了这一段,脚本会报错或返回空响应。不同厂商的 OpenAI 兼容接口可能要求不同的路径前缀,接入时先看清楚厂商文档。

4. 直接调模型:invoke

第一步先不要碰 Agent,直接调模型。这样做的目的是把环境变量、模型配置和调用方式这三件事先确认清楚。

typescript
// scripts/first-call.ts
import dotenv from "dotenv";
import { ChatOpenAI } from "@langchain/openai";

dotenv.config({ path: new URL("../.env.local", import.meta.url) });

const model = new ChatOpenAI({
  apiKey: process.env.MODEL_API_KEY,
  model: process.env.MODEL_NAME ?? "deepseek-chat",
  configuration: {
    baseURL: process.env.MODEL_BASE_URL ?? "https://api.deepseek.com/v1",
  },
});

const response = await model.invoke([
  {
    role: "system",
    content: "你是一名技术助手,回答要清楚、简短。",
  },
  {
    role: "user",
    content: "请用两句话确认 LangChain 与模型服务的连接已经正常。",
  },
]);

console.log("invoke result:");
console.log(response.text);

执行:

shellscript
yarn first-call

这一段最值得记住的是 invoke() 的感觉:把一份完整输入交给模型,等模型生成结束,再一次性拿回结果。它的调用方式是同步等待的,适合短回答、确定性输出或后续步骤依赖完整结果的场景。

5. 流式输出:stream

invoke() 改成 stream(),其他配置不变:

typescript
// scripts/first-stream.ts
import dotenv from "dotenv";
import { ChatOpenAI } from "@langchain/openai";

dotenv.config({ path: new URL("../.env.local", import.meta.url) });

const model = new ChatOpenAI({
  apiKey: process.env.MODEL_API_KEY,
  model: process.env.MODEL_NAME ?? "deepseek-chat",
  configuration: {
    baseURL: process.env.MODEL_BASE_URL ?? "https://api.deepseek.com/v1",
  },
});

const stream = await model.stream([
  {
    role: "system",
    content: "你是一名技术助手,回答要自然、简短。",
  },
  {
    role: "user",
    content: "请用一句话说明当前是流式输出验证。",
  },
]);

process.stdout.write("stream result:\n");
for await (const chunk of stream) {
  process.stdout.write(chunk.text);
}
process.stdout.write("\n");

执行:

shellscript
yarn first-stream

这里最大的变化只有一个:不再等完整结果,而是边生成边输出。可以把区别简单记成:

  • invoke():一次性拿结果。
  • stream():边生成边拿结果。

流式输出在实时交互场景里很有用,比如聊天界面、长文本生成。但它也会让下游代码更复杂,因为需要处理流的中断和拼接。

6. 最小 Agent:createAgent + stream

前面两段代码都还是直接调模型。现在再往前走一步,看看最小 Agent 是什么样。

typescript
// scripts/first-agent.ts
import dotenv from "dotenv";
import { createAgent } from "langchain";
import { ChatOpenAI } from "@langchain/openai";

dotenv.config({ path: new URL("../.env.local", import.meta.url) });

const model = new ChatOpenAI({
  apiKey: process.env.MODEL_API_KEY,
  model: process.env.MODEL_NAME ?? "deepseek-chat",
  configuration: {
    baseURL: process.env.MODEL_BASE_URL ?? "https://api.deepseek.com/v1",
  },
});

const agent = createAgent({
  model,
  tools: [],
  systemPrompt: "你是一名技术助手,回答要自然、简短。",
});

const stream = await agent.stream(
  {
    messages: [
      {
        role: "user",
        content: "请用一句话说明当前是 Agent 流式调用验证。",
      },
    ],
  },
  {
    streamMode: "messages",
  },
);

process.stdout.write("agent stream result:\n");
for await (const [messageChunk] of stream) {
  if (messageChunk.content) {
    process.stdout.write(messageChunk.text);
  }
}
process.stdout.write("\n");

执行:

shellscript
yarn first-agent

这段代码里有三个地方值得注意:

  1. 模型配置本身没有变。Agent 不是另一套模型初始化方式,它建立在同一个模型对象之上。
  2. 入口从 model.invoke() / model.stream() 变成了 agent.stream()
  3. createAgent() 让模型外面多了一层运行时包装。现在这个例子里还没有工具,所以它看起来像是绕了一层再调模型。但后面一旦把 tools 接进去,这层包装的价值就会很明显:模型可以自主决定调用哪个工具、如何组合工具结果。

7. 为什么需要一个最小 Agent 版本

first-call.tsfirst-stream.ts 的目标是确认底层模型调用正常。first-agent.ts 则是在为后面的 Agent 主线铺路。

后面这一章的核心问题不是「怎么调一个模型」,而是「单个 Agent 怎样在一轮请求里调用多个工具,把事情做完」。先放一个最小 Agent,有两个作用:

  • createAgent()agent.stream() 这些入口认熟。
  • 后面加工具时,不需要再突然切换思路。

基于 MIT 协议开源