AI Agent: From Prototype to Production

构建可投入生产的人工智能应用。编写评估程序以衡量大型语言模型(LLM)和工具的准确性。实现检索增强生成(RAG)管道,并探索结构化输出如何为大型语言模型的响应提供可预测的模式。通过适当的上下文内存管理,负责任地控制成本和令牌限制。采用“人在回路”的最佳实践,在系统中建立更完善的防护机制。

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 的点赞/点踩功能,用于收集用户反馈数据。

评估指标类型

  1. 响应质量指标:连贯性、相关性、毒性分类
  2. 任务完成验证:确保代理完成整个工作流程
  3. 工具准确性:检查是否选择正确工具并正确解释输出

数据集创建

  1. 合成数据:人工创建测试数据
  2. 真实用户数据:从实际使用中收集数据

挑战

  • 工具数量增加会降低 LLM 选择正确工具的能力
  • 需要考虑多种策略:多个 LLM 分组、意图分类器等
  • 即使工具选择正确,LLM 仍可能给出错误答案

3-what-to-measure-with-evals

测量指标的重要性

选择正确的评估指标是 Evals 中最有价值但也最困难的部分。

三种测量类型

1. LLM 基础测量

  • 使用 LLM 作为评判者(AI 作为法官)
  • 例如:语义相似性评分、实体检查
  • 通过多个 LLM 评估不同维度的质量

2. 实施策略

  • 离线评估:本地运行评估
  • 持续集成评估:在 GitHub Actions 中运行
  • 生产环境评估:基于用户反馈的实时评估

3. 数据收集

  • 黄金数据集:已标注的输入-期望输出对
  • 用户反馈:免费但难以获取的高质量数据
  • 合成数据:人工生成的测试案例

成本管理

  • 评估成本无法避免,需要测试真实用户使用的系统
  • 大规模公司通常将 20-30%时间用于评估
  • 成本与用户规模存在一定相关性

改进流程

  1. 快速原型开发(1-2 天)
  2. 发布给内测用户收集反馈
  3. 编写评估系统
  4. 基于反馈改进系统
  5. 达到阈值后扩大用户范围
  6. 持续优化直到生产就绪

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";

改进流程

  1. 运行评估发现问题
  2. 分析失败原因
  3. 优化工具名称和描述
  4. 重新运行验证改进

生成图像评估示例

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 系统需要四个核心组件:

  1. 向量数据库: 存储数值化数据
  2. 嵌入模型: 将文本转换为向量
  3. 索引系统: 组织和存储数据
  4. 查询机制: 检索相关信息

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 登录。

创建向量索引

  1. 登录后点击 Vector
  2. 点击 Create Index
  3. 设置参数:
    • 名称:任意命名
    • 区域:选择最近区域
    • 嵌入模型:选择 mixedbread(1024 维度,不要选 Custom)
    • 度量算法:COSINE
  4. 选择免费套餐(每日 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({
  // 环境变量会自动加载
});

数据处理策略

  1. 文本字段选择:标题 + 类型 + 描述用于向量化
  2. 元数据转换:年份、评分、投票数转为数字类型
  3. 幂等性:使用标题作为 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";
  }
};

集成步骤

  1. 添加到tools/index.ts的工具数组
  2. toolRunner.ts中添加执行逻辑
  3. 清空聊天历史测试

测试结果

查询"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)
  • 中风险: 单人审批(如发送邮件)
  • 高风险: 多人审批(如数据库操作)

设计考虑

关键问题

  1. 检测机制: 如何识别需要审批的操作
  2. 对话连续性: 确保 AI 理解审批概念
  3. 状态管理: 处理审批等待状态
  4. 用户体验: 自然的审批响应解析

技术挑战

  • 消息类型正确性(工具响应 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 方案

将历史消息存入向量数据库,提供查询历史工具。

生产环境方案

作者使用的三层策略:

  1. 消息窗口限制
  2. 事实提取工具:识别并保存用户事实
  3. 对话摘要:定期摘要并存入系统提示

用户体验考虑

  • 用户界面显示完整历史
  • 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 系统需要:

  1. 评估优先:是产品核心,不是附加功能
  2. 持续优化:更好的 RAG、更多评估、成本优化
  3. 用户反馈:建立有效的数据收集机制
  4. 技能投资:LLM 技能将成为基础开发能力