1. 模型调用
LangChain.js 里,所有能"对话"的模型都叫 ChatModel(聊天模型)。DeepSeek 的接入类是 ChatDeepSeek:
import { ChatDeepSeek } from '@langchain/deepseek'
const model = new ChatDeepSeek({
model: 'deepseek-v4-flash', // 模型名
apiKey: process.env.DEEPSEEK_API_KEY, // 模型中的key
})
调用方式
invoke(同步等结果):
const response = await model.invoke('用一句话解释什么是 Node.js?')
console.log(response.content) // 字符串回答
stream(流式):
const stream = await model.stream('写一首关于秋天的诗')
for await (const chunk of stream) {
process.stdout.write(chunk.content as string)
}
和 ai SDK 对照
| ai SDK | LangChain.js |
|---|---|
generateText({ model, prompt }) |
model.invoke(prompt) |
streamText({ model, prompt }) |
model.stream(prompt) |
注意一个本质差异:ai SDK 的 generateText 是一个函数,模型只是参数;LangChain.js 的 model 本身就是 Runnable,直接 invoke。这是两种设计哲学——ai SDK 是"函数式",LangChain 是"对象式(OO)"。
完整示例:
import 'dotenv/config'
import { ChatDeepSeek } from '@langchain/deepseek'
// 实例化模型
const model = new ChatDeepSeek({
model: 'deepseek-v4-flash',
apiKey: process.env.DEEPSEEK_API_KEY,
})
async function main() {
console.log('=== 1. invoke:同步等待完整结果 ===\n')
const response = await model.invoke('用一句话解释什么是 Node.js?')
console.log('返回类型:', response.constructor.name) // AIMessage
console.log('内容:', response.content) // 字符串回答
console.log()
console.log('=== 2. stream:流式逐字输出 ===\n')
const stream = await model.stream('写一首关于秋天的诗,四行即可')
for await (const chunk of stream) {
process.stdout.write(chunk.content as string)
}
console.log('\n')
}
main().catch(err => {
console.error('运行失败:', err)
process.exit(1)
})
2. 消息与 Prompt
消息类型
LangChain 用不同的消息类表示对话中的不同角色:
import { HumanMessage, AIMessage, SystemMessage } from '@langchain/core/messages'
const messages = [
new SystemMessage('你是一个简洁的代码助手'),
new HumanMessage('什么是 Node.js?'),
new AIMessage('Node.js 是一个 JS 运行时'), // 历史对话
new HumanMessage('它和浏览器 JS 有什么区别?'),
]
const response = await model.invoke(messages)
SystemMessage:设定 AI 的角色/规则HumanMessage:用户说的话AIMessage:AI 之前说的话(多轮对话时用)
PromptTemplate
当你要把变量填进 prompt 时,用模板:
import { PromptTemplate } from '@langchain/core/prompts'
const prompt = PromptTemplate.fromTemplate(
'用一句话解释什么是 {topic}?'
)
// 注意这里是prompt.invoke(),不是模型的.invoke()
const formatted = await prompt.invoke({ topic: 'Docker' })
// → '用一句话解释什么是 Docker?'
ChatPromptTemplate(更常用)
聊天场景用 ChatPromptTemplate,它能一次生成多条消息:
import { ChatPromptTemplate } from '@langchain/core/prompts'
const chatPrompt = ChatPromptTemplate.fromMessages([
['system', '你是一个{role}'],
['human', '{question}'],
]);
// 注意这里是chatPrompt.invoke(),不是模型的.invoke()
const messages = await chatPrompt.invoke({
role: '代码助手',
question: '什么是 TypeScript?',
});
// → [SystemMessage, HumanMessage]
和 ai SDK 对照
| ai SDK | LangChain.js |
|---|---|
messages: [{role, content}] |
[new HumanMessage(...), ...] |
system 顶层参数 |
new SystemMessage(...) 或 ChatPromptTemplate |
ai SDK 用普通对象 {role, content} 表示消息;LangChain 用类实例 HumanMessage。本质一样,形式不同。
重点理解
- PromptTemplate 的核心价值是"变量填充",让 prompt 可复用。
ChatPromptTemplate是后面 LCEL 链的关键构件——它能和 model 用 pipe 拼起来
完整示例:
import 'dotenv/config'
import { ChatDeepSeek } from '@langchain/deepseek'
import { HumanMessage, AIMessage, SystemMessage } from '@langchain/core/messages'
import { PromptTemplate, ChatPromptTemplate } from '@langchain/core/prompts'
const model = new ChatDeepSeek({
model: 'deepseek-v4-flash',
apiKey: process.env.DEEPSEEK_API_KEY,
})
async function main() {
console.log('=== 1. 消息数组(多轮对话)===\n')
// 用消息类构造一段对话历史,让模型能"接上"上下文
const messages = [
new SystemMessage('你是一个简洁的代码助手,回答不超过两句话'),
new HumanMessage('什么是 Node.js?'),
new AIMessage('Node.js 是一个服务端 JavaScript 运行时。'),
new HumanMessage('它和浏览器里的 JS 有什么区别?'),
]
const response = await model.invoke(messages)
console.log('回答:', response.content)
console.log()
console.log('=== 2. PromptTemplate(单条文本模板)===\n')
const prompt = PromptTemplate.fromTemplate('用一句话解释什么是 {topic}?')
const formatted = await prompt.invoke({ topic: 'Docker' })
console.log('填充后:', formatted.value) // prompt.invoke 返回 StringPromptValue,用 .value 取字符串
console.log()
console.log('=== 3. ChatPromptTemplate(聊天模板,常用)===\n')
// 一次定义多条消息的模板,变量在运行时填充
const chatPrompt = ChatPromptTemplate.fromMessages([
['system', '你是一个{role},回答要简洁'],
['human', '{question}'],
])
const chatMessages = await chatPrompt.invoke({
role: '代码助手',
question: '什么是 TypeScript?',
})
console.log('生成的消息:')
console.log(chatMessages.toChatMessages())
// 直接喂给模型
const answer = await model.invoke(chatMessages)
console.log('回答:', answer.content)
}
main().catch(err => {
console.error('运行失败:', err)
process.exit(1)
})
3. 输出结构化
让模型输出 JSON 对象而不是自由文本,用 .withStructuredOutput:
import { z } from 'zod'
const schema = z.object({
name: z.string().describe('人名'),
age: z.number().describe('年龄'),
})
const structuredModel = model.withStructuredOutput(schema)
const result = await structuredModel.invoke('张三今年 25 岁')
// result 是 { name: '张三', age: 25 },直接是对象,不是字符串
console.log(result.name) // '张三'
console.log(result.age) // 25
.withStructuredOutput(schema)返回一个新的 model,它的invoke输出直接是对象(不是 AIMessage)。- schema 用 zod 定义。
- 字段的
.describe()很重要——它会被转成 JSON Schema 发给模型,帮助模型理解每个字段该填什么。
完整示例:
import 'dotenv/config'
import { ChatDeepSeek } from '@langchain/deepseek'
import { z } from 'zod'
const model = new ChatDeepSeek({
model: 'deepseek-chat',
apiKey: process.env.DEEPSEEK_API_KEY,
})
async function main() {
console.log('=== 1. 基础结构化输出 ===\n')
// 定义输出 schema:告诉模型"给我返回一个对象,有 name 和 age 两个字段"
const personSchema = z.object({
name: z.string().describe('人名'),
age: z.number().describe('年龄'),
occupation: z.string().describe('职业'),
})
// withStructuredOutput 返回一个新模型,invoke 直接返回对象(不是 AIMessage)
const structuredModel = model.withStructuredOutput(personSchema)
const result = await structuredModel.invoke('张三今年 25 岁,是一名前端工程师')
console.log('返回类型:', typeof result) // object
console.log('完整结果:', result)
console.log('name:', result.name) // 直接是字段,不是字符串
console.log('age:', result.age)
console.log()
console.log('=== 2. 更复杂的 schema(数组 + 枚举)===\n')
const bookListSchema = z.object({
topic: z.string().describe('主题'),
books: z.array(z.object({
title: z.string().describe('书名'),
reason: z.string().describe('推荐理由'),
})).describe('推荐的书单'),
difficulty: z.enum(['入门', '进阶', '专家']).describe('难度级别'),
})
const recommender = model.withStructuredOutput(bookListSchema)
const books = await recommender.invoke('推荐 2 本适合初学者的 TypeScript 书')
console.log('主题:', books.topic)
console.log('难度:', books.difficulty)
console.log('书单:')
books.books.forEach((b, i) => {
console.log(` ${i + 1}. 《${b.title}》- ${b.reason}`)
})
}
main().catch(err => {
console.error('运行失败:', err)
process.exit(1)
})
4. 工具定义
工具是 Agent 的"手脚"。LangChain 用 tool() 函数定义:
import { tool } from '@langchain/core/tools'
import { z } from 'zod'
const getWeather = tool(
async ({ city }) => {
return `今天 ${city} 晴,25 度`
},
{
name: 'get_weather',
description: '查询某个城市的天气',
schema: z.object({
city: z.string().describe('城市名'),
}),
}
)
注意参数顺序和 ai SDK 不同!
LangChain 的 tool() 第一个参数是 execute 函数,第二个参数是配置对象(含 name/description/schema)。ai SDK 是全部塞一个对象。这是两个库最容易搞混的地方。
// LangChain.js
tool(async (input) => { ... }, { name, description, schema })
// ai SDK(对照)
tool({ description, inputSchema, execute: async (input) => { ... } })
另外注意 schema 字段名:LangChain 叫 schema,ai SDK v6 叫 inputSchema。
工具怎么被模型调用?
工具定义好之后,有两种用法:
- 手动调用:
await getWeather.invoke({ city: '北京' })——直接执行 - 交给 Agent:传给
createReactAgent,让模型决定何时调用(第 8 章讲)
和 ai SDK 对照
| 维度 | ai SDK | LangChain.js |
|---|---|---|
| 参数顺序 | 全塞一个对象 | execute 在前,config 在后 |
| schema 字段名 | inputSchema |
schema |
| 工具本质 | 普通对象 | Runnable(可 invoke) |
LangChain 的工具本身是 Runnable,能直接 invoke 执行;ai SDK 的工具只是数据结构,执行靠 SDK 内部。
完整示例:
import 'dotenv/config'
import { ChatDeepSeek } from '@langchain/deepseek'
import { tool } from '@langchain/core/tools'
import { z } from 'zod'
const model = new ChatDeepSeek({
model: 'deepseek-v4-flash',
apiKey: process.env.DEEPSEEK_API_KEY,
})
// === 1. 定义工具 ===
// 注意 tool() 的参数顺序:第一个是 execute 函数,第二个是 config 对象
const getWeather = tool(
async ({ city }) => {
console.log(` [工具执行] 查询天气: ${city}`)
// 模拟数据,实际接天气 API
const mockData: Record<string, string> = {
'北京': '晴,25°C',
'上海': '多云,28°C',
'深圳': '雷阵雨,30°C',
}
return mockData[city] ?? `暂无 ${city} 的天气数据`
},
{
name: 'get_weather',
description: '查询某个城市的当前天气',
schema: z.object({
city: z.string().describe('城市名,如"北京"'),
}),
}
)
const getTime = tool(
async () => {
console.log(` [工具执行] 获取当前时间`)
return new Date().toLocaleString('zh-CN')
},
{
name: 'get_time',
description: '获取当前时间',
schema: z.object({}), // 无参数也要传空对象 schema
}
)
async function main() {
console.log('=== 1. 手动执行工具(不走模型)===\n')
// 工具本身是 Runnable,可以直接 invoke 执行
const direct = await getWeather.invoke({ city: '北京' })
console.log('直接执行结果:', direct)
console.log()
console.log('=== 2. bindTools 让模型调用工具 ===\n')
// bindTools 把工具"绑"到模型上,模型看到这些工具后能决定何时调用
const modelWithTools = model.bindTools([getWeather, getTime])
const response = await modelWithTools.invoke('北京今天天气怎么样?')
// 模型不会直接回答天气,而是返回 tool_calls(决定要调哪个工具)
console.log('模型返回的 tool_calls:')
console.log(response.tool_calls)
console.log()
// === 3. 手动执行模型要求的工具调用 ===
if (response.tool_calls && response.tool_calls.length > 0) {
const toolCall = response.tool_calls[0]
console.log(`模型想调工具: ${toolCall.name},参数: ${JSON.stringify(toolCall.args)}`)
console.log()
// 根据 toolCall.name 找到对应工具并执行
const toolMap: Record<string, typeof getWeather> = {
'get_weather': getWeather,
'get_time': getTime, // 这个我们没有去实现它
}
const result = await toolMap[toolCall.name].invoke(toolCall.args)
console.log('工具执行结果:', result)
console.log()
// 把工具结果喂回模型,让它基于结果给出最终回答
// 这一步是 Agent 循环的核心(第 8 章 createReactAgent 会自动做这个循环)
console.log('把结果喂回模型,让它基于工具结果回答:')
const { HumanMessage, AIMessage, ToolMessage } = await import('@langchain/core/messages')
const finalResponse = await model.invoke([
new HumanMessage('北京今天天气怎么样?'),
response, // 模型上一步的输出(含 tool_calls)
new ToolMessage({
content: String(result),
tool_call_id: toolCall.id!, // 靠这个 ID 把结果和调用配对
}),
])
console.log(finalResponse.content)
}
}
main().catch(err => {
console.error('运行失败:', err)
process.exit(1)
})
💬 评论 0
还没有评论,快来抢沙发吧~