跳到正文
曾亮懿 ZengLiangYi Blog

AI SDK 结构化笔记:统一调用、Schema 与业务校验

对照 ChatCrystal 的 AI SDK 7 调用,分清 provider 适配、输出类型与业务事实,用离线 Zod 练习验证提示描述的边界。

曾亮懿

曾亮懿

阅读约 4 分钟
目录 展开 收起

统一模型接口能减少接入代码,却不能消除不同服务的协议和能力差异;结构化输出能验证形状,也不能证明内容属实。本文从 ChatCrystal 固定版本的调用链出发,解释这两条边界。它不是模型排名,也不要求读者为练习调用付费服务。

当前摘要调用使用 AI SDK 7

server/package.json 固定 ai: 7.0.97,项目要求 Node 24+。摘要实现 使用 generateText 配合 Output.object,从结果读取 output;原稿的 generateObject/system/object 写法不描述这个版本。

下面是实际调用片段,依赖应用已有的模型工厂、Schema、提示词和转录,不能单独复制后运行:

const { output } = await generateText({
  model: getLanguageModel(),
  output: Output.object({ schema: SummarizeSchema }),
  instructions: SYSTEM_PROMPT,
  prompt: transcript,
  maxOutputTokens: 4096,
  maxRetries: 3,
});

instructions 与 prompt 分别传入任务要求和可见转录;五个字段是 title、summary、key_conclusions、code_snippets、tags。之后应用把 tags 转成小写,并把原输出 JSON 保存到 raw_response。官方 结构化数据说明 对 Output.object 的作用是输出结构验证,不能据此保证调用永远成功或生成的结论都有原文依据。

工厂统一类型,调用协议仍有区别

providers.ts 当前注册七种生成 provider:Ollama、OpenAI、OrcaRouter、Anthropic、Google、Azure 和 custom。Embedding 由其中五种支持:Ollama、OpenAI、Google、Azure 和 custom;并不是所有生成模型都可拿来做向量。

getLanguageModel 从 llm 配置选择工厂,Embedding 从独立的 embedding 配置选择工厂。Ollama 在清理尾部斜杠后追加 /v1,生成模型显式使用 .chat(model);OpenAI/custom 的工厂调用 openai(model),使用 SDK 的默认 Responses 路径。OpenAI 适配器文档 说明了不同模型工厂。因此“兼容 OpenAI”不能只检查 URL 看起来相似,还要核对端点、模型能力、鉴权和所用适配器版本。

配置切换与实际兼容性测试见 云端 LLM 配置,向量配置见 Embedding 模型配置。这里关注统一调用与校验的职责,不再重复各服务的设置流程。

describe 是期望,不是长度或数量约束

实际 SummarizeSchema 使用 z.string() 和 z.array(...),再用 describe 写“20 字以内”“3–5 个结论”等期望,没有对应的 max/min 数量限制。下面是缩减为三个字段的离线教学 schema,不是应用完整 Schema。在新的临时目录安装 Zod 4.6.2,保存为 lesson.mjs,用 Node 24+ 运行;不调用模型,不访问数据库。

import assert from 'node:assert/strict';
import { z } from 'zod';

const noteSchema = z.object({
  title: z.string().describe('A concise title within 20 characters'),
  summary: z.string(),
  key_conclusions: z.array(z.string()).describe('3–5 conclusions'),
});
const constructed = {
  title: 'x'.repeat(80),
  summary: 'Invented example: this code has not been checked against a source.',
  key_conclusions: [],
};
assert.equal(noteSchema.safeParse(constructed).success, true);
assert.equal(noteSchema.safeParse({ ...constructed, title: 123 }).success, false);
assert.equal(
  noteSchema.safeParse({ ...constructed, key_conclusions: 'one conclusion' }).success,
  false
);

const stricter = noteSchema.extend({
  title: z.string().max(20),
  key_conclusions: z.array(z.string()).min(3).max(5),
});
assert.equal(stricter.safeParse(constructed).success, false);

这份构造对象的长标题、空数组和无来源摘要都能通过原教学 schema:它们符合字段类型。添加 max/min 后,长度与数量才成为可执行限制;事实是否来自原文仍需另查。刻意给原教学 title 加上 max(20),第一个断言应失败;恢复 describe-only 后应通过。这不是要求直接修改应用的生产 Schema,而是用反例说明验证职责。

模型输出后还有业务筛选与持久化

关系发现 用 Output.array、八种关系枚举及 0–1 数值约束,随后只保留候选 ID 集合中的目标、confidence 至少 0.5,并截取最多五条。候选存在、业务阈值和返回数量并非仅靠提示词描述完成;confidence 也不是经过校准的正确概率。关系投影细节见 笔记关系与图谱。

摘要链路先查已有笔记,再做 experience gate;接受后生成摘要并保存笔记,之后才生成向量和发现关系。向量失败会标记 embedding failed,关系失败会记录错误,它们不会自动把已成功的摘要变成失败。继续点击同一会话,也不会覆盖已有笔记后重新评测模型,评测方法见 摘要质量评估。

重试次数不是输出正确性的保证

该摘要调用 maxRetries 为 3,关系调用为 2;generateText 参考 定义了重试参数。不能把它写成“任何 Schema 校验失败都会自动重新生成三次”。错误种类、适配器和失败阶段不同,输出解析错误与可重试的传输/API 错误需要分别处理。

应用任务队列还有启动间隔与部分 429 重试,但一个任务能发起门控、摘要、多次 embed 和关系调用,不能用“队列每秒一个任务”推导“全系统每秒一个模型请求”。验证接入时应分别记录调用协议、输出形状、来源事实、业务筛选、保存状态和失败类型;其中任何一项通过都不能替代其余项。

分享:

相关文章

实现 Embedding 服务:文本、分块与提交边界

对照 ChatCrystal 的真实 Embedding 流程,区分文本选择、软分块、向量校验与提交,并用离线反例验证边界。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #AI #Node.js

结构化 JSON Prompt:把输出字段与来源证据一起设计

以 ChatCrystal 摘要和关系发现为例,设计字段契约、可见输入与反例样本,并用离线 Zod 练习区分格式验证和证据验证。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #AI #Node.js

Vectra 本地搜索:用固定向量验证排序、过滤与持久化

在临时目录运行 Vectra 0.15.0 的确定性练习,检查余弦分数、元数据过滤、重新打开索引和取消更新。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #AI #Node.js

从文章到代码

到我的 GitHub 看看 ChatCrystal、开发者工具和日常学习记录,欢迎交流实现细节。