主题
第一次调用
要点
- 第一次调用 LangChain,建议先建一个独立 playground,避免直接塞进业务项目里。
- 把环境变量、依赖版本和脚本路径显式固定下来,可以减少后续排查问题的时间。
- 直接调模型时,重点关注
invoke()和stream()的区别:前者一次性返回,后者边生成边返回。 - 最小 Agent 的写法与直接调模型类似,核心变化是入口从
model.stream()变成agent.stream(),并增加streamMode: 'messages'配置。
1. 背景:先让调用跑通
学习 LangChain 时,最容易遇到的阻力不是概念复杂,而是环境没配好。模型密钥、base URL、依赖版本、脚本执行目录,任何一项没对齐,都会让示例跑不起来。所以第一步不是研究 Agent 或工具,而是先把一次最简单的模型调用跑通。
这一节按三个小步骤走:
- 直接调一次模型,确认连接正常。
- 把调用改成流式输出,理解
stream()的行为。 - 换成最小 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": "[email protected]",
"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这段代码里有三个地方值得注意:
- 模型配置本身没有变。Agent 不是另一套模型初始化方式,它建立在同一个模型对象之上。
- 入口从
model.invoke()/model.stream()变成了agent.stream()。 createAgent()让模型外面多了一层运行时包装。现在这个例子里还没有工具,所以它看起来像是绕了一层再调模型。但后面一旦把 tools 接进去,这层包装的价值就会很明显:模型可以自主决定调用哪个工具、如何组合工具结果。
7. 为什么需要一个最小 Agent 版本
first-call.ts 和 first-stream.ts 的目标是确认底层模型调用正常。first-agent.ts 则是在为后面的 Agent 主线铺路。
后面这一章的核心问题不是「怎么调一个模型」,而是「单个 Agent 怎样在一轮请求里调用多个工具,把事情做完」。先放一个最小 Agent,有两个作用:
- 把
createAgent()和agent.stream()这些入口认熟。 - 后面加工具时,不需要再突然切换思路。