0-introduction
本课程主要教授如何将 LLM 代理原型部署到生产环境。
环境要求
- Node.js 最新版本(20 推荐)
- OpenAI 账户(需要添加信用卡和最少 5 美元)
- Upstash 账户(稍后设置,无需信用卡)
先决条件
- 完成"从零构建代理"课程
- JavaScript 基础(TypeScript 可选)
- API 使用经验
- LLM 基础知识
课程内容
构建一个具有以下功能的代理:
- 访问 Reddit
- 生成随机爸爸笑话
- 生成图像
主要模块
1. Evals(评估)
- 类似测试的概念,用于检测 LLM 是否正确执行任务
- 检查代理是否选择正确的工具
- 提供可视化界面查看评估结果
2. RAG(检索增强生成)
- 为 AI 提供未训练过的数据索引
- 基于 IMDB 电影数据集实现电影搜索功能
- 能够根据描述推荐电影
3. Human-in-the-loop(人机协作)
- 在 AI 执行某些操作前需要获得用户批准
- 主要用于破坏性或变更性操作
- 例如图像生成前的审批流程
1-llm-agents-review
LLM 基础概念
大语言模型(LLM)本质上是一个基于权重和平衡的统计系统,用于预测下一个词。关键创新在于注意力机制,能够记住整个句子和之前的所有内容,而不仅仅是前一个字符。
代理 vs LLM
- 单轮对话:类似 Cursor 或 VS Code Copilot 的事务性 LLM 调用
- 多轮对话:代理通过多次 LLM 调用实现复杂任务
代理特点
- 循环调用 LLM
- 具备工具访问能力
- 拥有对话记忆功能
- 能够执行复杂的工作流程
2-evals
Evals 定义
评估(Evals)是 LLM 系统的测试方法,但与传统测试不同,因为 LLM 输出具有非确定性和随机性。
Evals 的重要性
- 测试系统输出的准确性和质量
- 评估系统各个组成部分
- 是当前 AI 研究的重点领域
- 将成为未来就业市场的核心技能
评估类型
自动化评估
类似 ChatGPT 的点赞/点踩功能,用于收集用户反馈数据。
评估指标类型
- 响应质量指标:连贯性、相关性、毒性分类
- 任务完成验证:确保代理完成整个工作流程
- 工具准确性:检查是否选择正确工具并正确解释输出
数据集创建
- 合成数据:人工创建测试数据
- 真实用户数据:从实际使用中收集数据
挑战
- 工具数量增加会降低 LLM 选择正确工具的能力
- 需要考虑多种策略:多个 LLM 分组、意图分类器等
- 即使工具选择正确,LLM 仍可能给出错误答案
3-what-to-measure-with-evals
测量指标的重要性
选择正确的评估指标是 Evals 中最有价值但也最困难的部分。
三种测量类型
1. LLM 基础测量
- 使用 LLM 作为评判者(AI 作为法官)
- 例如:语义相似性评分、实体检查
- 通过多个 LLM 评估不同维度的质量
2. 实施策略
- 离线评估:本地运行评估
- 持续集成评估:在 GitHub Actions 中运行
- 生产环境评估:基于用户反馈的实时评估
3. 数据收集
- 黄金数据集:已标注的输入-期望输出对
- 用户反馈:免费但难以获取的高质量数据
- 合成数据:人工生成的测试案例
成本管理
- 评估成本无法避免,需要测试真实用户使用的系统
- 大规模公司通常将 20-30%时间用于评估
- 成本与用户规模存在一定相关性
改进流程
- 快速原型开发(1-2 天)
- 发布给内测用户收集反馈
- 编写评估系统
- 基于反馈改进系统
- 达到阈值后扩大用户范围
- 持续优化直到生产就绪
4-setting-up-an-eval-framework
自定义评分器
创建一个工具调用匹配评分器,检查 LLM 是否选择了正确的工具。
评估框架结构
位于evals文件夹,包含:
evalTools.js:评估框架核心run.js:命令行运行脚本scores.js:自定义评分器定义
框架组件
- 实验:具有唯一名称的测试单元,用于跟踪历史改进
- 任务:执行 AI 系统的异步函数
- 数据:输入和期望输出的数组
- 评分器:评估指标的数组
ToolCallMatch 评分器实现
export const ToolCallMatch = {
name: "ToolCallMatch",
scorer: (input, output, expected) => {
// 检查输出是否为assistant角色的消息
if (output.role !== "assistant") return { name: "ToolCallMatch", score: 0 };
// 检查是否包含工具调用
if (!output.tool_calls || output.tool_calls.length === 0) {
return { name: "ToolCallMatch", score: 0 };
}
// 比较工具名称
const actualTool = output.tool_calls[0].function.name;
const expectedTool = expected.tool_calls[0].function.name;
return {
name: "ToolCallMatch",
score: actualTool === expectedTool ? 1 : 0,
};
},
};
运行命令
npm run start:执行代理npm run eval [filename]:运行指定评估或全部评估
5-creating-an-eval
Reddit 评估创建
在experiments文件夹中创建reddit.eval.js文件。
评估结构
runEval("reddit", {
task: async (input) => {
return runLLM({
messages: [{ role: "user", content: input }],
tools: [redditTool],
});
},
data: [
{
input: "find me something interesting on reddit",
expected: createToolCallMessage(redditTool.name),
},
],
scorers: [ToolCallMatch],
});
关键要点
- 任务函数接收输入并返回 LLM 响应
- 数据包含输入和期望输出
- 期望输出格式必须与任务返回格式匹配
- 每个数据项都会执行一次任务
- 最终分数是所有运行的平均值
辅助函数
createToolCallMessage函数用于创建标准的工具调用消息格式:
const createToolCallMessage = (toolName) => ({
role: "assistant",
tool_calls: [
{
type: "function",
function: { name: toolName },
},
],
});
运行评估
使用npm run eval reddit命令运行评估,系统会显示:
- 之前的分数
- 当前分数
- 分数差异
6-viewing-eval-results
可视化仪表板
通过仪表板查看评估结果的变化趋势,显示系统性能是改善还是退化。
运行仪表板
cd dashboard
npm run dev
仪表板功能
- 图表显示分数变化趋势
- 实时查看评估结果
- 历史数据对比
故意测试退化
添加不相关的数据项测试系统退化:
data: [
{
input: "find me something interesting on reddit",
expected: createToolCallMessage(redditTool.name),
},
{
input: "hi", // 不相关输入
expected: createToolCallMessage(redditTool.name),
},
];
Windows 兼容性
Windows 用户可使用以下命令直接运行:
npx tsx evals/experiments/reddit.eval.ts
注意:需要在文件顶部添加环境变量导入:
import "dotenv/config";
数据存储
结果存储在results.json中,可构建自定义仪表板或直接查看 JSON 数据。
7-handling-evals-on-subjective-inputs
创建 Dad Jokes 评估
创建dadjoke.eval.ts文件,测试爸爸笑话工具的选择。
runEval("dadJoke", {
task: async (input) => {
return runLLM({
messages: [{ role: "user", content: input }],
tools: [dadJokeToolDefinition],
});
},
data: [
{
input: "tell me a funny dad joke",
expected: createToolCallMessage(dadJokeToolDefinition.name),
},
],
scorers: [ToolCallMatch],
});
故意破坏测试
通过修改工具描述测试系统鲁棒性:
// 错误描述
description: "use this tool to get the weather";
// 或错误名称
name: "weatherTool";
改进流程
- 运行评估发现问题
- 分析失败原因
- 优化工具名称和描述
- 重新运行验证改进
生成图像评估示例
data: [
{
input: "generate an image of a sunset",
expected: createToolCallMessage(generateImageToolDefinition.name),
},
{
input: "take a photo of the sunset", // 语义相似但不同表述
expected: createToolCallMessage(generateImageToolDefinition.name),
},
];
描述优化
通过改进工具描述处理语义相似的不同表述:
description: "use this tool with a prompt to generate or take a photo of anything";
持续改进
- 收集真实用户输入
- 分析失败案例
- 优化提示词和工具描述
- 建立版本化实验跟踪
8-eval-multiple-tools
多工具评估
创建allTools.eval.ts测试代理在多个工具中选择正确工具的能力。
const allTools = [
redditToolDefinition,
dadJokeToolDefinition,
generateImageToolDefinition,
];
runEval("allTools", {
task: async (input) => {
return runLLM({
messages: [{ role: "user", content: input }],
tools: allTools,
});
},
data: [
{
input: "tell me a funny dad joke",
expected: createToolCallMessage(dadJokeToolDefinition.name),
},
{
input: "take a photo of mars",
expected: createToolCallMessage(generateImageToolDefinition.name),
},
{
input: "what is the most upvoted post on reddit",
expected: createToolCallMessage(redditToolDefinition.name),
},
],
scorers: [ToolCallMatch],
});
调试失败案例
通过查看results.json分析具体哪个工具调用失败:
- 分数为 1 表示成功
- 分数为 0 表示失败
- 总分为平均值
工具描述优化
针对失败案例改进工具描述:
// 原描述
description: "generates an image";
// 优化后描述
description: "use this tool with a prompt to generate or take a photo of anything";
真实场景评估框架
介绍 AutoEvals 框架提供的高级评估指标:
- Battle: 比较两个系统输出质量
- ClosedQA: 测试 LLM 内置知识
- Factuality: 检查事实准确性
- Moderation: 内容审核
- SQL: SQL 查询有效性
RAG 专用评估
推荐 Ragas 框架用于 RAG 系统评估:
- Context Precision: 检索精确度
- Context Recall: 检索召回率
- Faithfulness: 答案忠实度
- Answer Relevancy: 答案相关性
行业标准
- SWE-bench: 代码生成代理标准
- 白皮书: 每日更新的评估指标研究
- 专业团队: 评估是全职工作,需要专门团队
9-rag-overview
RAG 定义
检索增强生成(RAG)解决 LLM 知识局限性问题。LLM 不知道训练数据之外的信息,如最新 NBA 比赛结果。
传统解决方案的问题
系统提示方案
直接在系统提示中添加信息:
- 优点: 简单直接
- 缺点:
- 超出上下文窗口限制
- 成本高(每次请求都计费)
- 响应速度慢
- 中间信息遗忘问题
微调方案
- 成本极高(数百万美元)
- 需要大量数据和时间
- 不适合频繁更新
RAG 解决方案
RAG 只检索回答问题所需的最小信息集:
- 更快: 处理更少 tokens
- 更便宜: 只为必需信息付费
- 更准确: 避免信息遗忘问题
RAG 的挑战
RAG 实施极其困难,是当前 AI 领域最大挑战之一:
- 与 Evals 并列为最具挑战性的问题
- 大量公司和研究人员专门研究 RAG
- 90%的当前 AI 研究集中在 RAG 和 Evals 上
核心技术要求
RAG 系统需要四个核心组件:
- 向量数据库: 存储数值化数据
- 嵌入模型: 将文本转换为向量
- 索引系统: 组织和存储数据
- 查询机制: 检索相关信息
10-rag-pipeline
RAG 管道组件
RAG 包含四个核心组件:向量数据库、嵌入向量、索引和查询。
文档处理阶段
1. 分块策略(Chunking)
文档必须分块以避免超出 token 限制:
- 固定 token 限制: 如 100 个 token 每块
- 重叠策略: 块间重叠 20 个 token 保持上下文连续性
- 复杂性: 邮件等关系型数据需考虑线程、发送者、接收者等因素
2. 文本提取
多种数据格式需要转换为文本:
- JSON、XML 格式转换
- 图像、视频、音频需要专门模型处理
- 已有成熟模型解决大部分格式问题
3. 嵌入生成
将文本块转换为数值向量:
- 向量: 0-1 之间的数字数组
- 维度: 根据嵌入模型确定(通常 700-1500 维)
- 一致性: 相同文本每次产生相同向量
存储阶段
向量数据库只接受数值,不接受文本:
- 静态数据: 一次性离线处理
- 动态数据: 像常规数据库一样实时更新
- 成本考虑: 频繁更新成本较高
检索阶段
1. 用户查询向量化
用户输入也转换为嵌入向量进行数学计算。
2. 相似度搜索
- 余弦相似度: 最常用算法
- 元数据过滤: 按导演、年份等条件过滤
- 高维计算: 在 1500 维空间中计算距离
增强阶段
检索结果需要额外处理才能给 LLM:
1. Token 计数检查
避免重新引入原始问题(token 过多)
2. 重新排序(Re-ranking)
向量数据库擅长语义匹配但不擅长相关性判断:
- 例子:搜索"1989-2000 年间电影"可能返回标题含"1989"和"2000"的无关电影
- 需要额外模型评估实际相关性
3. 内容总结
进一步压缩信息以适应 LLM 处理
生成阶段
LLM 基于检索到的信息生成答案:
- 引用来源(如 Perplexity、Claude)
- 保持答案与检索内容的关联性
高级优化
上下文检索
Anthropic 的方法:
- 为每个块生成与整个文档关系的描述
- 将上下文化的块存入向量数据库
- 帮助 AI 理解块与整体文档的关系
11-create-an-upstash-vector-database
Upstash 账户设置
在 upstash.com 创建免费账户,无需信用卡,支持 GitHub/Google 登录。
创建向量索引
- 登录后点击 Vector
- 点击 Create Index
- 设置参数:
- 名称:任意命名
- 区域:选择最近区域
- 嵌入模型:选择 mixedbread(1024 维度,不要选 Custom)
- 度量算法:COSINE
- 选择免费套餐(每日 10,000 次更新和查询)
环境变量配置
点击 env 按钮复制两个环境变量到.env 文件。
向量数据库功能演示
- 1000 部电影数据索引
- 语义搜索:查询"movies about space"返回《重力》、《星际穿越》、《瓦力》等
- 每个向量包含 1024 个 0-1 之间的小数
- 内置 RAG 聊天功能(需 OpenAI API 密钥)
12-ingesting-data-into-vector-db
数据源
使用 Kaggle 的 IMDB 电影数据集,包含电影元数据的 CSV 文件。
数据摄取脚本
位于rag/ingest.js,执行一次性离线索引。
核心导入
import "dotenv/config";
import { Index as UpstashIndex } from "@upstash/vector";
import { parse } from "csv-parse/sync";
import fs from "fs";
import path from "path";
import ora from "ora";
索引初始化
const index = new UpstashIndex({
// 环境变量会自动加载
});
数据处理策略
- 文本字段选择:标题 + 类型 + 描述用于向量化
- 元数据转换:年份、评分、投票数转为数字类型
- 幂等性:使用标题作为 ID 避免重复索引
批量上传
for (const record of records) {
const text = `${record.Title}. ${record.Genre}. ${record.Description}`;
await index.upsert({
id: record.Title,
data: text,
metadata: {
year: Number(record.Year),
rating: Number(record.Rating),
votes: Number(record.Votes),
// ... 其他字段
},
});
}
执行命令
npm run ingest
验证结果
在 Upstash 仪表板查看 999 条记录,测试语义搜索和元数据过滤功能。
13-create-a-movies-query
查询函数设计
创建queryMovies函数,接受三个参数:
- query: 搜索查询字符串
- filters: 元数据过滤对象(可选)
- topK: 返回结果数量(默认 5)
基础实现
export const queryMovies = async (query, filters, topK = 5) => {
return index.query({
data: query,
topK,
includeData: true,
includeMetadata: true,
});
};
元数据过滤
Upstash 需要 SQL 风格的过滤字符串:
// 对象转SQL字符串
{ year: 2020, rating: { gt: 8 } }
// 转换为 "year = 2020 AND rating > 8"
命名空间支持
// 用户隔离
const userIndex = index.namespace(userId);
// 或使用元数据过滤
metadata: {
userId: "user123";
}
搜索算法
- COSINE: 余弦相似度(推荐)
- EUCLIDEAN: 欧几里得距离
- DOT_PRODUCT: 点积
相关性 vs 语义匹配
向量搜索擅长语义匹配但可能缺乏相关性判断,生产环境通常需要重新排序步骤。
14-create-a-movie-search-tool
工具定义
创建movieSearch.js工具,包含定义和执行函数。
export const movieSearchToolDefinition = {
name: "movieSearch",
parameters: z.object({
query: z.string().describe("query used for vector search on movies"),
}),
description:
"use this tool to find movies or answer questions about movies and their metadata like score, rating, cast, director, actors, and more",
};
工具实现
export const movieSearch: ToolFn<Args> = async ({ toolArgs }) => {
try {
const results = await queryMovies(toolArgs.query);
const formattedResults = results.map((result) => ({
...result.metadata,
description: result.data,
}));
return JSON.stringify(formattedResults, null, 2);
} catch (error) {
console.error(error);
return "Could not query the db to get movies";
}
};
集成步骤
- 添加到
tools/index.ts的工具数组 - 在
toolRunner.ts中添加执行逻辑 - 清空聊天历史测试
测试结果
查询"Find me a scary movie about ghosts"成功返回《安娜贝尔》等恐怖片,展示 RAG 系统的有效性。
RAG 评估挑战
RAG 系统评估需要多个维度:
- 检索精度: 是否找到正确文档
- 答案准确性: 给定正确上下文的回答质量
- 工具选择: 是否选择正确工具
- 整体表现: 端到端用户体验
15-using-structured-outputs
结构化输出概念
LLM 返回预定义 JSON 格式而非自由文本,确保输出格式一致性。
核心优势
- 格式保证: 严格匹配预定义架构
- 必需字段: 不会丢失必需字段
- 无解析错误: 避免 JSON 解析失败
- 更好错误处理: 可预测的边界情况
- 开发效率: 类似 schema 数据库的确定性
OpenAI 实现
使用openai.beta.chat.completions.parse而非标准 completion API:
const response = await openai.beta.chat.completions.parse({
model: "gpt-4",
messages: [...],
response_format: zodResponseFormat(schema, "response_name")
})
const result = response.choices[0].message.parsed
Zod 架构示例
const schema = z.object({
title: z.string(),
categories: z.array(z.string()),
confidence: z.number(),
suggestions: z.array(
z.object({
text: z.string(),
score: z.number(),
})
),
});
限制条件
- 根对象必须是 object 类型
- 不支持 Zod 的默认值和强制转换
- 不支持复杂验证(如数值范围)
- 存在嵌套深度限制
模型拒绝处理
if (response.choices[0].message.refusal) {
// 处理安全或政策拒绝
console.log("Model refused:", response.choices[0].message.refusal);
} else {
const data = response.choices[0].message.parsed;
}
生成式 UI 应用
结构化输出的强大用例:
组件级生成
const uiSchema = z.object({
componentType: z.enum(["weather", "chart", "calendar"]),
props: z.object({
title: z.string(),
data: z.array(z.any()),
}),
});
原子级 UI 生成
AI 生成完整 DOM 树结构:
const atomicSchema = z.object({
type: z.enum(["div", "button", "header", "section"]),
attributes: z.record(z.string()),
children: z.array(z.lazy(() => atomicSchema)).optional(),
});
递归架构支持
支持嵌套和递归结构,实现复杂 UI 组件树的动态生成。
实际应用场景
- 动态 UI 生成
- 个性化界面适配
- 数据结构化提取
- API 响应标准化
- 复杂工作流输出
16-limitations-of-structured-outputs
流媒体限制
结构化输出无法实现 token 级流媒体:
- JSON 格式需要完整才能解析
- 无法渲染部分 JSON 内容
- 失去实时打字效果的用户体验
替代方案
工具调用作为结构化输出
在结构化输出出现前,常用工具调用强制获得结构化响应:
- 创建包含期望输出格式的工具
- 强制 AI 必须调用该工具
- 获取工具参数作为结构化数据
实现细节
// 强制调用特定工具
function_call: {
name: "specific_tool_name";
}
// 或使用auto模式但只提供一个工具
技术实现
OpenAI 通过微调实现结构化输出保证,可能结合:
- 模型微调
- 输出验证和重试机制
- 大模型监督小模型输出
社区普遍认为内部使用类似 LangChain 的验证重试逻辑。
17-using-human-in-the-loop
HITL 概念
Human-in-the-Loop 作为 AI 安全机制,防止破坏性操作。
核心原则
CRUD 操作安全性
- 读取(Read): 非破坏性,无需审批
- 创建/更新/删除: 破坏性操作,需要人工审批
行业趋势
从完全自动化转向人机协作,强调人类控制和 AI 辅助。
实现模式
1. 同步审批(课程实现)
- 执行完全暂停
- 下一个输入必须是审批响应
- 无超时限制
- 流程:检测保护工具 → 暂停 → 审批 → 执行或拒绝
2. 异步队列
- 非阻塞用户交互
- 独立审批流程
- 适用于文档签署等长时间流程
- 多人审批支持
3. 分层审批
基于风险等级的不同审批策略:
- 低风险: 自动执行(如写入 Notion)
- 中风险: 单人审批(如发送邮件)
- 高风险: 多人审批(如数据库操作)
设计考虑
关键问题
- 检测机制: 如何识别需要审批的操作
- 对话连续性: 确保 AI 理解审批概念
- 状态管理: 处理审批等待状态
- 用户体验: 自然的审批响应解析
技术挑战
- 消息类型正确性(工具响应 vs 用户消息)
- 自然语言审批解析
- 审批状态恢复
- 错误处理和恢复
18-interpreting-approvals-using-llms
审批解析函数
使用 LLM 解析用户审批意图,因为用户可能用多种方式表达同意/拒绝。
实现细节
export const runApprovalCheck = async (userMessage: string) => {
const result = await openai.beta.chat.completions.parse({
model: "gpt-4o-mini",
temperature: 0.1, // 减少随机性
messages: [
{
role: "system",
content:
"Determine if the user approved the image generation. If you are not sure, then it is not approved.",
},
{
role: "user",
content: userMessage,
},
],
response_format: zodResponseFormat(
z.object({
approved: z
.boolean()
.describe("did the user approve the action or not"),
}),
"approval"
),
});
return result.choices[0].message.parsed.approved;
};
设计原则
保守策略
"如果不确定,则未批准" - 避免误执行破坏性操作。
上下文特定性
系统提示包含具体操作类型(图像生成),提高解析准确性。
结构化输出
使用 zod schema 确保返回布尔值,避免解析错误。
扩展考虑
对于多工具系统,需要:
- 动态工具名称列表
- 通用审批解析逻辑
- 操作类型识别
19-adding-approvals-to-agent
审批流程集成
将审批逻辑集成到代理主循环中。
核心函数实现
const handleImageApprovalFlow = async (
history: AIMessage[],
userMessage: string
) => {
const lastMessage = history.at(-1);
const toolCall = lastMessage?.tool_calls?.[0];
// 检查是否需要审批
if (
!toolCall ||
toolCall.function.name !== generateImageToolDefinition.name
) {
return false; // 无需审批
}
const approved = await runApprovalCheck(userMessage);
if (approved) {
// 执行工具并保存响应
const toolResponse = await runTool(toolCall, userMessage);
await saveToolResponse(toolCall.id, toolResponse);
} else {
// 保存拒绝响应
await saveToolResponse(
toolCall.id,
"User did not approve image generation"
);
}
return true; // 处理了审批
};
主循环修改
const history = await getMessages();
const isApproval = await handleImageApprovalFlow(history, userMessage);
if (!isApproval) {
// 正常流程:保存用户消息
await saveUserMessage(userMessage);
}
工具执行拦截
在工具执行点添加审批检查:
if (toolCall.function.name === generateImageToolDefinition.name) {
updateLoader("Need user approval");
loader.stop();
return getMessages(); // 返回消息等待审批
}
状态管理关键点
消息类型正确性
- 审批响应必须保存为工具响应消息
- 避免创建用户消息破坏对话流
对话连续性
- AI 理解审批概念通过工具响应内容
- 提供清晰的拒绝原因
错误恢复
处理异常状态,确保系统健壮性。
20-history-management-strategies
内存管理挑战
随着对话增长,token 数量会超过模型限制,导致系统无法继续运行。
管理策略
1. 窗口切片
最基础的方法:
- 只保留最近 N 条消息或 T 个 tokens
- 优点:简单实现
- 缺点:丢失长期记忆
2. 基于摘要的管理
结合窗口和摘要:
- 保留最近消息窗口
- 定期摘要旧消息并放入系统提示
- 平衡短期和长期记忆
3. 分层摘要
不同时间层级的摘要:
- 即时摘要:刚讨论的内容
- 中期摘要:最近几次对话
- 永久事实:用户个人信息、偏好
4. 基于主题的分段
按对话主题组织和保留信息。
5. 重要性评分
- 评估消息重要性
- 优先保留高分消息
- 渐进式淘汰低分内容
6. RAG 方案
将历史消息存入向量数据库,提供查询历史工具。
生产环境方案
作者使用的三层策略:
- 消息窗口限制
- 事实提取工具:识别并保存用户事实
- 对话摘要:定期摘要并存入系统提示
用户体验考虑
- 用户界面显示完整历史
- AI 只看到处理后的窗口
- 区分存储和计算需求
这种内存管理对生产级 AI 系统至关重要,需要根据具体用例选择合适策略组合。
21-summarizing-messages
消息摘要策略
实现窗口切片和简单摘要的内存管理方案。
核心实现
窗口限制:保留最近 5 条消息 摘要触发:每 10 条消息创建一次摘要 状态检查:避免在工具响应上截断(会破坏 LLM 状态)
数据结构更新
// 添加summary字段
type Database = {
messages: AIMessage[];
summary: string;
};
// 默认值
const defaultData = {
messages: [],
summary: "",
};
消息添加逻辑
// memory.js - addMessages函数
if (db.data.messages.length >= 10) {
const oldestMessages = db.data.messages.slice(0, 5);
const summary = await summarizeMessages(oldestMessages);
db.data.summary = summary;
}
获取消息窗口
const getMessages = async () => {
const db = await getDb();
let lastFive = db.data.messages.slice(-5);
// 避免在工具响应上开始(会导致LLM错误)
if (lastFive[0]?.role === "tool") {
lastFive = db.data.messages.slice(-6);
}
return lastFive;
};
摘要生成
const summarizeMessages = async (messages) => {
const response = await runLLM({
messages,
systemPrompt:
"You're job is to summarize the given messages to be used in another LLM's system prompt. Summarize it play by play.",
});
return response.content || "";
};
系统提示集成
// LLM.js - runLLM函数
const summary = await getSummary();
const systemPrompt = `${baseSystemPrompt}
Conversation so far: ${summary}`;
测试结果
成功实现消息摘要和窗口管理,AI 能够:
- 获取爸爸笑话
- 处理审批流程(使用 ✓ 表情符号作为同意)
- 生成相关图像
22-advanced-rag-fine-tuning
进阶方向
多代理系统
使用 OpenAI Swarm 框架:
- 编排代理管理其他专门代理
- 每个代理绑定特定功能集
- 提高系统模块化和专业化
评估至关重要
Greg Brockman(OpenAI 联合创始人):"Evals are surprisingly all you need"
评估不是事后考虑,而是产品核心组成部分。没有评估无法构建优质 AI 产品。
混合搜索
结合向量搜索和元数据过滤:
- GUI 场景:用户选择过滤器效果很好
- 聊天场景:从消息推断过滤器困难(通常过强或过弱)
- 单一语料库:如 PDF 聊天不需要元数据过滤
- 多数据类型:歌曲、电影、邮件等需要混合搜索
上下文处理技术
推荐研究领域:
- Self-RAG: 自我改进检索
- Context-faithful prompting: 上下文忠实提示
- Hypothetical document embeddings: 假设文档嵌入
- RAG-Fusion: RAG 融合技术
- Contextual retrieval: 上下文检索(Anthropic 方法)
微调限制
微调类似"最后一公里"优化:
- 用途:改变输出格式/语调,不增加知识
- 成本优化:减少冗余词汇降低 token 成本
- 迁移路径:从 GPT 迁移到开源模型(如 Llama)
- 知识获取:仍需 RAG,微调无法添加领域知识
生成式 UI
Schema 驱动的 UI 生成:
- 定义组件库和布局规则
- AI 根据内容需求选择合适组件
- 支持网格、flexbox 等复杂布局
- 实现动态 UI 适配
23-wrapping-up
核心研究方向
重新排序
向量数据库返回语义相似结果,但缺乏相关性判断。需要额外模型根据原始查询重新排序结果。
反馈循环
在应用中集成用户反馈机制获取免费标注数据:
- 传统点赞/点踩参与率低
- 考虑游戏化激励机制
- 定期推送准确性回顾
- 达到准确率目标给予奖励
技能发展趋势
LLM 技能普及化
预测 LLM 技能将从专业技能变为通用技能:
- 类似 2012 年前后端分离到全栈普及
- LLM 技术栈将如 Postgres 般普遍
- 从专家技能演变为基础开发技能
工具选择建议
SDK 推荐
OpenAI SDK:推荐使用原生 OpenAI SDK
- 成为行业标准(如 Tesla 充电器)
- 易于切换不同模型提供商
- 避免过度抽象
Vercel AI SDK:Next.js 环境下值得考虑
- 支持流式 React 组件
- 服务器组件集成良好
LangChain:不推荐
- 抽象过度复杂
- JavaScript 实现质量问题
评估数据收集
用户反馈策略:
- 准确性评分显示
- 周期性工作回顾
- 滑动界面快速标注
- 游戏化激励机制
- 准确率奖励系统
总结
构建生产级 AI 系统需要:
- 评估优先:是产品核心,不是附加功能
- 持续优化:更好的 RAG、更多评估、成本优化
- 用户反馈:建立有效的数据收集机制
- 技能投资:LLM 技能将成为基础开发能力