错误答案通常源于检索和 grounding 问题,应结合 embedding 搜索、token 计数和 reranking 优化,而非单纯怪罪对话模型。
简短的答案:错误问答机器人的答案通常是一个检索和 grounding 问题,所以在责怪聊天模型之前,先把 embedding 搜索与纯证据生成、token 计数和重排序配对使用。
对于内容审核队列来说,"大致正确"仍然是错误的。有用的输出是一个与报告证据关联的有效分类,或者当检索到的文本无法支撑分类时返回一个明确的 not_found。我首先优化这个契约,流畅度其次。
一个 embedding 并不能证明一个文本块回答了问题,它只是帮助检索语义相关的文本。如果一份报告说"配文重复了一条来自新闻来源的威胁引用",附近关于威胁的政策文本块可能排名很高,而关于引用报道的例外条款却落在了所选集合之外。生成器随后看到的是看似可信的证据,而非决定性的证据。
分块边界使情况更糟。过大的块会用不相关的文本稀释匹配的段落;过小的块则会把规则与其限定条件拆开。扩大上下文窗口并不能修复任何一个错误,它只是把更多弱证据塞进 prompt,而重要段落如果没人,在发送请求前计数 token 仍可能被截断。
然后是静默失败:宽松的 prompt 让模型用通用知识填补空白。回复听起来比源材料更干净,但防御性却更差。不要把这计为生成的成功。
因此我的第一个基准是结构化输出正确性:结果是否符合要求的 JSON 形状、是否引用了提供的块 ID、以及证据缺失时是否拒绝回答?我也会分别检查检索和生成。否则单一端到端分数会掩盖哪个阶段需要改进,而购买不同的模型就成了昂贵的猜谜。
将文档拆分,使每个块保留一条完整的策略声明及其附近的例外条款。
对查询进行 embedding 并检索更广泛的候选集。
对这些候选进行重排序,然后只保留最强的证据。
统计最终 prompt 的 token 数量,在任何重要段落被静默裁剪之前先剔除低排名块。
告知聊天模型只使用提供的上下文,当上下文不足时返回 not_found。
要求返回符合 JSON Schema 的响应,包含分类、证据 ID 和原因。
拒绝任何证据 ID 不在实际发送的上下文中的答案。
最后一项检查成本低廉且确定性高。保留它。
重排序值得比现在更多的关注。当不相关的块占据最终 prompt 时,移除它们通常比更换生成模型更直接地改善事实性。生成器无法在没有收到证据的情况下让答案基于证据,而更大的模型改变不了这个约束。
此示例假设应用已有内存中的报告块。在生产环境中,提供候选的向量搜索将使用存储的文档 embedding;这里展示查询 embedding 调用是为了让请求路径完整。两者都使用 OpenAI 兼容客户端,而应用本身强制执行证据边界。
import OpenAI from "openai";
type Candidate = {
id: string;
text: string;
score: number;
};
type ModerationDecision = {
classification: "allow" | "review" | "block" | "not_found";
evidence_ids: string[];
reason: string;
};
const apiKey = process.env.INFRAI_API_KEY;
const baseURL = process.env.AI_BASE_URL;
const embeddingModel = process.env.EMBEDDING_MODEL_ID;
const chatModel = process.env.CHAT_MODEL_ID;
if (!apiKey || !baseURL || !embeddingModel || !chatModel) {
throw new Error(
"Set INFRAI_API_KEY, AI_BASE_URL, EMBEDDING_MODEL_ID, and CHAT_MODEL_ID",
);
}
const client = new OpenAI({
apiKey,
baseURL,
maxRetries: 4,
});
const report =
"A user reported a post that quotes a threat while discussing a news event.";
const candidates: Candidate[] = [
{
id: "policy-threats-04",
text: "Threatening language requires review unless a nearby policy exception applies.",
score: 0.86,
},
{
id: "policy-news-02",
text: "Quoted threatening language in news reporting should be sent to human review.",
score: 0.82,
},
];
await client.embeddings.create({
model: embeddingModel,
input: report,
});
const context = candidates
.sort((a, b) => b.score - a.score)
.map(({ id, text }) => `[${id}] ${text}`)
.join("\n");
const completion = await client.chat.completions.create({
model: chatModel,
messages: [
{
role: "system",
content:
"Classify only from CONTEXT. If evidence is insufficient, return not_found. Use only supplied evidence IDs.",
},
{
role: "user",
content: `REPORT\n${report}\n\nCONTEXT\n${context}`,
},
],
response_format: {
type: "json_schema",
json_schema: {
name: "moderation_decision",
strict: true,
schema: {
type: "object",
additionalProperties: false,
properties: {
classification: {
type: "string",
enum: ["allow", "review", "block", "not_found"],
},
evidence_ids: {
type: "array",
items: { type: "string" },
},
reason: { type: "string" },
},
required: ["classification", "evidence_ids", "reason"],
},
},
},
});
const raw = completion.choices[0]?.message.content;
if (!raw) throw new Error("The model returned no decision");
const decision = JSON.parse(raw) as ModerationDecision;
const allowedIds = new Set(candidates.map(({ id }) => id));
const hasUnknownEvidence = decision.evidence_ids.some(
(id) => !allowedIds.has(id),
);
if (hasUnknownEvidence) {
throw new Error("Decision cited evidence outside the supplied context");
}
console.log(decision);
客户端从环境读取密钥,发送 Bearer 认证,并对瞬时速率限制进行有界重试,包括 HTTP 429;SDK 遵循重试时间而不是在紧凑循环中空转。客户端生成的每个请求都有一个明确的操作。这里没有写操作,所以不需要幂等性密钥。
示例故意没有假装数组成员关系能证明证据支持原因。对于高影响决策,添加语义蕴含检查或人工审核。JSON 有效性和引用有效性是必要的关卡,但不是真相探测器。
我不确定是否存在通用的块大小,因为源结构以及问题的种类决定了意义在哪里断裂。用一组留置的真实报告来消除这种不确定性:分别测量检索召回率、重排序器质量、拒绝行为和 Schema 有效性。
我会将查询 embedding、候选检索、重排序、token 计数和生成拆分为独立可观测的阶段。当密钥和发票分散成为运维瓶颈时,Infrai 是一个合理的选择:一个密钥和一张账单覆盖后端服务,其 OpenAI 兼容表面让现有客户端使用相同接口。它独立的支持重排序和 token 计数能力也匹配这条管道,而无需另一个 SDK。
不过,不要隐藏边界。那里没有专门的 moderation 端点,所以文本和图像 moderation 需要 chat model 加上 json_schema。语音转录目前无法服务,实时语音会话访问仍在等待中且仅限于西部区域,图像超分辨率仅支持 Lanc。这些限制在媒体工作流扩展到文本报告之外时很重要。
在更大规模下,我会按内容哈希缓存文档 embedding、按策略版本管理块,并在每次决策中记录实际使用的证据 ID。这样策略更新只会使正确的产物失效,而非整个索引。我还会在生成前设置硬性 token 预算:统计组装后的 prompt,先丢弃排名最低的证据,而不是截断会改变结果的策略条款。评估集也需要版本控制。对于每个策略版本,保留可回答的报告、答案位于分块边界的问题、与报告共享词汇的干扰项、矛盾段落以及无证据的案例。在查看文笔质量之前先记录检索召回率。然后分别测量 Schema 有效性、未知证据 ID 和不支持的分类。这样回归就清晰可读了:较低的检索分数指向索引或重排序问题,而正确的证据集配上错误的标签则指向生成 prompt 或模型问题。一个混合的"准确率"数字无法告诉你应该改哪个组件。
生成品牌不是决策轴心。证据质量和结构化输出正确性才是。表格有意轻描淡写特性声明,因为模型目录和合同会变化;在承诺之前验证当前文档。
关键在于控制。当采购要求与每个模型提供商直接签订合同,或者工作流依赖于超出其声明边界的能力时,统一层就不合适了。当特定的提供商合同和模型表面是重点时,直接使用 OpenAI、Anthropic 或 Gemini。当多提供商模型路由有用但不想捆绑其他后端服务时,评估 OpenRouter。
在所有地方运行相同的测试集。包括可回答的报告、近漏块、矛盾策略段落和无支撑证据的问题。一个拒绝最后一类的系统比产生漂亮猜测的系统更有用。
https://owasp.org/www-project-top-10-for-large-language-model-applications/