02-Chat Models & Basic Interactions 章节总结
章节概述
本章教授如何与 AI 模型进行自然对话的艺术。学习如何维护多轮对话的上下文、实时流式响应以提升用户体验、优雅地处理错误和重试逻辑,以及控制 AI 创造力的关键参数(如 temperature)和理解 token 使用以优化成本。
学习目标
完成本章后,你应该能够:
- ✅ 与 AI 进行多轮对话
- ✅ 流式传输响应以获得更好的用户体验
- ✅ 优雅地处理错误
- ✅ 使用参数控制模型行为
- ✅ 理解 token 使用
主要要点
1. 知识渊博的朋友类比
想象你正在和一位知识渊博的朋友喝咖啡。
当你们交谈时:
- 💬 你们有来回对话(不仅仅是一个问题)
- 🧠 他们记得你之前说的话(对话上下文)
- 🗣️ 他们边思考边说话(流式响应)
- 😊 根据你的偏好调整语气(模型参数)
- ⚠️ 有时他们需要澄清(错误处理)
聊天模型也是如此!
与简单的一次性问题不同,聊天模型擅长:
- 多轮对话
- 维护上下文
- 实时流式响应
- 适应行为
2. 多轮对话
对话历史如何工作
聊天模型实际上并不"记住"之前的消息。相反,你在每次发送新消息时都会发送整个对话历史。
就像这样:每次发送消息时,你都在向 AI 展示到目前为止的完整对话线程。
LangChain 中的消息类型
LangChain 提供三种核心消息类型用于构建对话:
| 类型 | 用途 | 示例 |
|---|---|---|
| SystemMessage | 设置 AI 行为和个性 | new SystemMessage("You are a helpful coding tutor") |
| HumanMessage | 用户输入和问题 | new HumanMessage("What is TypeScript?") |
| AIMessage | 带有元数据的 AI 响应 | 由 model.invoke() 返回,包含 content、usage_metadata、id |
创建消息
本课程使用消息类来创建消息。这种方法明确且对初学者友好:
import { SystemMessage, HumanMessage, AIMessage } from "langchain";
const messages = [
new SystemMessage("You are a helpful assistant"),
new HumanMessage("Hello!")
];
为什么使用消息类?
- ✅ 清晰明确 - 容易理解每条消息代表什么
- ✅ 类型安全 - TypeScript 在运行前捕获错误
- ✅ 更好的自动完成 - 编辑器帮助你更快编写代码
- ✅ 一致的模式 - 整个课程使用相同的方法
Code 示例 1: 01-multi-turn.ts
功能:演示如何维护对话上下文
关键代码:
// 构建对话数组
const messages: BaseMessage[] = [
new SystemMessage("You are a helpful coding tutor who gives clear, concise explanations."),
new HumanMessage("What is TypeScript?"),
];
// 获取 AI 响应并添加到历史记录
const response1 = await model.invoke(messages);
messages.push(new AIMessage(String(response1.content)));
// 继续对话 - AI 记住上下文
messages.push(new HumanMessage("Can you show me a simple example?"));
const response2 = await model.invoke(messages);
工作原理:
- Messages 数组保存整个对话 - 创建一个数组存储所有消息(系统、人类和 AI)
- 每个响应都添加到历史记录中 - 获取响应后,将其推送到 messages 数组
- AI 可以引用之前的消息 - 当问"Can you show me a simple example?"时,AI 知道我们在谈论第一次交流中的 TypeScript
- 每次都发送完整历史记录 - 每次调用
invoke()时,发送完整的对话历史
为什么这很重要:AI 实际上不"记住"任何东西。它只知道你发送的 messages 数组中的内容。这就是为什么维护对话历史对于多轮对话至关重要。
3. 流式响应
当你问一个复杂问题时,等待整个响应可能会感觉很慢。流式传输在生成响应时逐字发送。
就像看着朋友边思考边说话,而不是等待他们完成整个想法。
Code 示例 2: 02-streaming.ts
功能:比较流式和非流式响应,演示流式响应的即时反馈效果
关键代码:
// 流式传输响应而不是一次性等待
const stream = await model.stream("Explain how the internet works in 3 paragraphs.");
// 在每个块到达时循环处理
for await (const chunk of stream) {
process.stdout.write(chunk.content); // 立即显示而不换行
}
代码结构:
nonStreamingExample(): 传统的非流式方式,等待完整响应streamingExample(): 流式方式,逐块显示响应- 比较两者的时间和用户体验
工作原理:
- 调用
model.stream()而不是model.invoke() - 返回一个异步可迭代对象,在生成时产生块
- 使用
for await...of循环遍历每个块 - 每个块包含响应的一部分(通常是几个词)
- 使用
process.stdout.write()显示文本而不换行,创建流式效果
流式传输的好处:
- 更好的用户体验(即时反馈)
- 感觉更响应 - 用户立即看到进度
- 用户可以在 AI 生成其余部分时开始阅读
- 即使总时间相同,感知性能也会提高
何时使用:
- ✅ 长响应(文章、解释、代码)
- ✅ 面向用户的聊天机器人和交互式应用程序
- ✅ 当你想向用户显示进度时
- ❌ 当你需要先获得完整响应时(解析、验证、后处理)
4. 模型参数
你可以通过调整参数来控制 AI 的响应方式。这些参数可能因提供商/模型而异,因此请始终查看文档。
Temperature (0.0 - 2.0)
Temperature 控制随机性和创造力:
- 0.0 = 确定性:相同问题 → 相同答案
- 用于:代码生成、事实性答案
- 1.0 = 平衡(默认):一致性和多样性的混合
- 用于:一般对话
- 2.0 = 创造性:某些模型支持高达 2.0 以获得更随机和创造性的响应,但通常不太可预测
- 用于:创意写作、头脑风暴
提供商和模型差异:
- GitHub Models (OpenAI):大多数模型支持 0.0 到 2.0
- Microsoft Foundry:根据模型通常限制 temperature 为 0.0-1.0
- 某些模型(如 gpt-5-mini):可能只支持默认 temperature 值 (1) 并拒绝其他值
Max Tokens
什么是 token? Token 是 AI 模型处理的基本文本单位。将它们视为单词片段 - 大约 1 token ≈ 4 个字符或 ¾ 个单词。例如,"Hello world!" 大约是 3 个 token。
限制响应长度:
- 控制响应可以有多长
- 设置
maxTokens: 100将响应限制到大约 75 个单词 - 通过限制输出长度防止成本失控
Code 示例 3: 03-parameters.ts
功能:演示 temperature 和 maxTokens 参数的效果
关键代码:
const temperatures = [0, 1, 2];
for (const temp of temperatures) {
// 创建具有特定 temperature 的模型
const model = new ChatOpenAI({
model: process.env.AI_MODEL,
temperature: temp, // 控制随机性/创造力
// ... 其他配置
});
// 两次尝试相同的提示以查看变化
const response = await model.invoke(prompt);
}
代码结构:
temperatureComparison(): 比较不同 temperature 值(0, 1, 2)的效果maxTokensExample(): 比较不同 token 限制(50, 150, 500)的效果- 包含错误处理以跳过不支持的参数值
工作原理:
-
Temperature 参数:
- 使用三个不同的 temperature 设置(0, 1, 2)使用相同的提示
- 代码将每个模型调用包装在 try-catch 中以处理不支持的 temperature 值
- 如果模型不支持特定的 temperature,显示警告并继续到下一个值
- Temperature 0 产生最可预测的响应(当支持时)
- Temperature 1(默认)平衡一致性和创造力
- Temperature 2 产生更不寻常和创造性的变化
-
Max Tokens 参数:
- 脚本测试三个不同的 token 限制:50, 150, 500
- 每个限制控制 AI 可以在其响应中使用多少 token(单词片段)
- 较低的限制(50)通常导致不完整、截断的响应
- 中等限制(150)提供部分解释
- 较高的限制(500)允许完整、详细的响应
- 字符数显示 token 和实际文本长度之间的关系
成本优化策略:
- 使用 maxTokens 限制响应长度
- 修剪对话历史 - 只保留最近的消息以减少输入 token
5. 错误处理与内置重试
API 调用可能因速率限制、网络问题或临时服务问题而失败。LangChain 提供内置的重试逻辑和指数退避。
常见错误
- 429 Too Many Requests:超过速率限制(免费层最常见)
- 401 Unauthorized:无效的 API 密钥
- 500 Server Error:临时提供商问题
- Network timeout:连接问题
Code 示例 5: 05-error-handling.ts
功能:演示如何使用 LangChain 的内置重试逻辑处理错误
关键代码:
const model = new ChatOpenAI({
model: process.env.AI_MODEL,
// ... 其他配置
});
// 添加自动重试逻辑和指数退避
const modelWithRetry = model.withRetry({
stopAfterAttempt: 3, // 将重试最多 3 次
});
// 像普通模型一样使用它 - 重试自动发生
const response = await modelWithRetry.invoke("What is LangChain.js?");
代码结构:
robustCall(): 使用withRetry()的健壮调用函数errorExamples(): 演示不同的错误场景- 无效 API 密钥示例
- 模拟瞬态失败的重试演示
- 正常 withRetry() 使用
- 错误类型分类
showBestPractices(): 错误处理最佳实践
工作原理:
withRetry()用重试逻辑包装模型- 如果请求失败(429, 500, 超时),它自动重试
- 指数退避增加重试之间的等待时间
- 达到最大尝试次数后,它抛出错误供你处理
内置重试的好处:
- ✅ 自动指数退避:每次重试之间等待更长时间(1s, 2s, 4s...)
- ✅ 适用于所有 LangChain 组件:与代理、RAG 和链兼容
- ✅ 优雅地处理 429 错误:自动重试速率限制错误
- ✅ 更少的代码:无需手动重试循环
错误处理最佳实践:
- ✅ 始终将 API 调用包装在 try-catch 中
- ✅ 使用内置重试逻辑
withRetry() - ✅ 处理特定错误类型(429, 401, 超时)
- ✅ 记录错误以进行调试
- ✅ 向用户提供有用的错误消息
- ✅ 有回退行为(缓存响应或默认响应)
- ✅ 在生产环境中监控错误率
已知限制:withRetry() 目前在流式传输(.stream())方面存在问题。重试逻辑与 .invoke() 正确工作,但可能无法与 .stream() 一起执行。对于需要重试逻辑的关键操作,使用 .invoke() 而不是 .stream()。
6. Token 跟踪和成本
Token 驱动 AI 模型,它们直接影响成本和性能。
Code 示例 6: 06-token-tracking.ts
功能:演示如何跟踪 token 使用以进行成本估算和监控
关键代码:
// 发出请求
const response = await model.invoke("Explain what TypeScript is in 2 sentences.");
// 从响应元数据中提取 token 使用情况
const usage = response.usage_metadata;
console.log(` Prompt tokens: ${usage.input_tokens}`); // 你的输入
console.log(` Completion tokens: ${usage.output_tokens}`); // AI 的响应
console.log(` Total tokens: ${usage.total_tokens}`); // 总成本基础
工作原理:
- 发出 API 调用:向模型发送提示
- 提取元数据:获取
response.usage_metadata - 计算成本:将 token 乘以提供商费率
- 跟踪支出:监控每个请求的成本
关键见解:
- Prompt tokens:你的输入(问题 + 对话历史)
- Completion tokens:AI 的输出(响应)
- Total tokens:两者的总和(你支付的费用)
为什么跟踪 token?
- 💰 成本监控:了解你的 API 支出
- ⚡ 性能:更多 token = 更慢的响应
- 📊 优化:识别昂贵的查询
- 🎯 预算编制:预测生产环境的成本
7. 提供商无关的初始化(附录)
Code 示例 4: 04-init-chat-model.ts
功能:演示 initChatModel() 用于提供商无关的初始化(本课程的替代方案)
关键代码:
import { initChatModel } from "langchain/chat_models/universal";
// 在不同提供商类型之间切换(概念性)
const openaiModel = await initChatModel("gpt-5-mini", {
modelProvider: "openai",
apiKey: process.env.OPENAI_API_KEY,
});
const anthropicModel = await initChatModel("claude-3-5-sonnet-20241022", {
modelProvider: "anthropic",
apiKey: process.env.ANTHROPIC_API_KEY,
});
// 本课程推荐(GitHub Models/Azure)
const model = new ChatOpenAI({
model: process.env.AI_MODEL,
configuration: { baseURL: process.env.AI_ENDPOINT },
apiKey: process.env.AI_API_KEY
});
何时使用 initChatModel():
- 🔄 多种提供商类型:在 OpenAI、Anthropic、Google 等之间切换
- 🏗️ 框架构建:创建支持许多提供商的库
- 🎯 提供商无关代码:编写一次,适用于任何标准提供商
何时使用 ChatOpenAI(本课程):
- ✅ GitHub Models:自定义端点需要特定配置
- ✅ Azure OpenAI:非标准 API 路径与 ChatOpenAI 配合得更好
- ✅ 学习:更明确且更容易理解
- ✅ 单一提供商:当你主要使用一个提供商时
比较:
| 功能 | ChatOpenAI(推荐) | initChatModel() |
|---|---|---|
| 自定义端点 | ✅ 优秀 | ⚠️ 有限 |
| 类型安全 | ✅ 优秀 | ✅ 良好 |
| 学习曲线 | ✅ 更容易 | 🔄 中等 |
| 用例 | 单一提供商或自定义端点 | 多种标准提供商 |
对于本课程:坚持使用 ChatOpenAI。它更明确,并且最适用于 GitHub Models 和 Azure OpenAI。
关键要点
- 多轮对话:每次调用都发送完整的消息历史
- 流式传输:在生成时显示响应以获得更好的 UX
- Temperature:控制随机性(0 = 一致,2 = 创造性)
- 错误处理:始终使用 try-catch 并实现重试
- Token 跟踪:监控使用情况并估算成本以进行预算编制
- 成本优化:选择合适的模型,限制响应,缓存结果
- Token:影响成本和限制(1 token ≈ 4 个字符)
- 上下文窗口:模型只能处理有限的对话历史
实际应用
- 聊天机器人和虚拟助手:使用模型、内存和系统消息来维护有用的对话
- 内容生成工具:使用提示和模板创建一致、高质量的内容
- 代码助手:使用工具和代理搜索文档、运行测试并建议改进
- 客户支持系统:使用消息类型设置语气,使用内存在对话之间维护上下文
Code 示例总结
本章节包含 6 个代码示例:
- 01-multi-turn.ts - 多轮对话,演示如何维护对话历史
- 02-streaming.ts - 流式响应,比较流式和非流式方式
- 03-parameters.ts - 模型参数,演示 temperature 和 maxTokens 的效果
- 04-init-chat-model.ts - 提供商无关初始化(附录,本课程推荐使用 ChatOpenAI)
- 05-error-handling.ts - 错误处理,演示内置重试逻辑和错误分类
- 06-token-tracking.ts - Token 跟踪,演示如何监控 token 使用和成本
下一步
你已经学会了如何与 AI 聊天模型交互——从基本调用到处理带有消息历史的对话。现在你可以与 AI 进行来回对话!
下一步:学习如何使用提示控制这些对话并获得可靠的、结构化的输出,让你的代码可以依赖!