前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
返回 AI 情报前线
All News · 全部资讯9275
  • Harness Engineering:打造 AI 编程助手的工作环境
  • 加向量数据库前,先用 NumPy 搞定 Embedding
  • 自实现令牌桶限流:比等 429 更优
  • 长时 AI 任务用后台任务队列:状态机比框架重要
  • asyncio 并发调用 40 个 LLM:代码少但坑多
  • AI 项目中 API 密钥的泄密风险与防护层级
  • 电话Agent工程实践:SIP/WebRTC音频路径全解析
  • pgvector索引调优:HNSW与IVFFlat成本精确分析
  • 多租户应用按客户计费:成本与可计费用量必须分开
  • PDF 解析难点深度剖析:字符间距阈值是万恶之源
  • Hugging Face开源OlmoEarth文本嵌入
  • 阿里开源 Qwen3.8:2.4T MoE、激活 95B、256K 上下文
  • MiniMax H3:单个 Transformer 替代视频生成完整流水线
  • DeepSeek V4 Pro 正式版发布:多项测试接近 Fable 5 水平
  • AI编程助手Lovable完成新一轮4亿美元融资,估值达133亿美元
  • MiniMax Music 3.0:开放权重生产级音乐生成模型
  • Microsoft 发布 MindTopo:VLMs 空间推理能力新基准
  • Grok 4.6 发布:剑指 GPT-5.6 Sol,主打长时间 Agent 任务
  • Grok 4.6 中文详解:训练数据、Agent 能力边界与定价
  • 市场份额报告:Google Gemini 份额从 12% 跌至 1.9%
  • 用Embeddings+Reranking+LLM构建可靠的内容分类流水线
  • 推理冷启动从10分钟降至秒级:容器镜像瘦身实战
  • Anthropic研究揭示:Claude Code旧版权限提示97%被机械通过
  • DeepSeek-V4-Pro-0813 悄然上线,支持思考与非思考模式
  • AI 生图 Prompt 审核实战:Node.js 调用 Chat JSON Schema 方案
  • 企业AI分析的隐藏陷阱:语义漂移问题深度剖析
  • AI 正在消除软件工程中层:代码看不懂、没人负责的团队困境
  • Qwen3.8-2.4T-A95B 模型发布
  • TraceMotive:本地优先的AI代理执行追踪调试工具
  • AI编程工具正在离开IDE:终端原生Agent工作流崛起
  • 个人开发者用AI编程的项目架构经验
  • FastAPI五个安全漏洞发现与修复全过程
  • 长文档AI审核的审计设计:Map-Reduce优于检索增强
  • AI Agent辅助发现SharePoint RCE漏洞链(CVSS 9.1)
  • 我用Claude Code将API的P99延迟降低一半
  • AI Agent读了你的secrets并删了生产数据库——PocketOS事故详解
  • 用聊天模型做金融内容审核:结构化输出设计实践
  • Prompt注入攻击原理与防御实践指南
  • 大规模漏洞扫描活动泛滥,攻击者冒充ClaudeBot等AI爬虫
  • 谷歌DeepMind发布手语转文本模型SL2T
  • LFM2.5-VL-3B:边缘设备高性能视觉语言模型发布
  • 通义千问3.8-27B发布,刷新开源大模型参数效率
  • 多Agent协作陷阱:个体测试全过,团队输出仍错误
  • Agent Plugins:Vercel/OpenAI/Microsoft 等联合推出 Agent 技能打包新标准
  • 企业实战:50+ AI Agent在UK主权云上的部署架构
  • ZeroGPU Router:让 AI Agent 用小模型处理例行任务
  • Solv Labs在AWS Bedrock上构建可审计的Agent支付系统
  • CodeBurn:让AI编程投入产出可见化
  • AWS SageMaker HyperPod分层KV缓存:LLM推理新范式
  • 2026年AI Agent安全开发指南
  • 从专有LLM API窃取推理痕迹研究
  • 已加载 51 / 9275
8.0
热点
AI SCORE
技术实践2026-08-12 23:42

用Embeddings+Reranking+LLM构建可靠的内容分类流水线

dev.to · AI#RAG#Embeddings#LLM架构
Editor brief · 编辑速览

通过 embedding 检索候选类目、Rerank 优化证据排序、LLM 最终分类并输出 schema 校验 JSON 的三阶段架构设计,强调标签 schema 是稳定契约而非模型技巧。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

简而言之:用 Embeddings 检索 Taxonomy 段落,对这个小集合进行 Rerank,然后用 LLM 根据最好的段落对审核报告进行分类,返回一个经过 Schema 验证的 JSON 标签供人工审核。

这是一个架构决策,不是三模型 trick。稳定的契约是标签 Schema。Retrieval 保持业务定义实时更新,Reranking 改进放在分类器面前的证据,Validation 阻止一个看起来合理但实际不符合期望 Topic ID 字段的段落进入系统。对于审核报告,应该优先优化结构化输出的正确性,而不是模型的新颖性或原始响应速度。

语义搜索、Embeddings、Rerank 和 LLM 分类器应该保留什么?

第一个不变量平淡无奇但具有决定性:每个被接受的结果恰好包含一个已知 Topic、一个在允许范围内的置信度值,以及所用引导段落的 ID。未知键会被拒绝。一份报告可以是不明确的,但其 Wire Format 不能是不明确的。

第二个不变量是:策略文本始终是证据,而非可执行权威。将标签定义和示例存储为 Embedding 文档,为报告检索候选内容,然后在分类前对这些候选内容进行 Rerank。不要将整个 Taxonomy 手册粘贴到每个 Prompt 中。那样会把不相关的定义塞进上下文,让判断哪个措辞驱动了决策变得更加困难。

失败边界应该在人工审核队列之前。无效的 JSON、未知 Topic、缺失证据或 HTTP 429 不能悄无声息地变成默认标签。我把 429 视为背压——当存在 Retry-After 时尊重它,否则使用指数退避——而结构无效的答案获得一次有界的修复尝试,然后进入明确的未分类状态。这是一个小区别,但有很大的运营效果:传输重试不应重写业务含义。

还有一个合规边界。检索到的段落可能包含指示、示例或引用的滥用内容。它们是不可信的数据。对它们进行定界,告诉分类器只将它们用作标签指导,并保留段落 ID 以便审核员可以重建该项目被路由的原因。我不让模型产生的置信度分数绕过人工审核;它是一个路由提示,不是证明。

在选择供应商之前画出失败边界

考虑一份普通报告:反复发送未经请求的推广内容给维护者。Retrieval 发现了一个垃圾邮件定义、一个隐私定义(因为报告提到了一个人),以及一个骚扰示例(因为发送者重复了这种行为)。Reranking 应该将垃圾邮件定义移到顶部,但分类器仍然需要返回一个允许的 Topic,并且只能引用它实际收到的段落 ID。如果它返回 marketing_abuse、编造 tax-spam-99、在 JSON 外面包裹评论,或省略证据,应用程序会在队列发布之前拒绝该答案。这个有效的例子比一个精心打磨的成功路径更重要,因为每个阶段在本地看起来都合理,而组合结果可能违反审核契约。保留原始报告、有序的证据 ID、Taxonomy 版本、Schema 版本和最终标签作为不同的字段;否则审核员无法区分是检索失误还是分类失误。同样的纪律也适用于重试:被限流的读取可以在有界退避后再次运行,但发布审核任务需要一个基于报告和 Taxonomy 版本的幂等键。一条重复的审核内容可能看起来无害。但在规模上,重复项会扭曲审核员的工作量以及任何后续的质量分析。

将关键路径放在严格的 Schema 后面

应用程序仍然应该拥有验证边界。下面的可运行示例通过纯 Python HTTP 调用 OpenAI 兼容的 Chat 接口,请求结构化 JSON,用 Retry-After 或指数延迟处理 429,检查每个响应状态,并在返回的标签进入人工审核之前对其进行验证。它使用一条经过验证的路由,没有提供商特定的 SDK;Retrieval 和 Reranking 在这最后一步之前发生,只有最上面的引导片段被传入。

from __future__ import annotations

import json
import os
import time
import urllib.error
import urllib.request
from dataclasses import dataclass
from typing import Any

API_URL = os.environ["INFRAI_BASE_URL"].rstrip("/") + "/v1/chat/completions"
ALLOWED_TOPICS = {"spam", "harassment", "privacy", "other"}

@dataclass(frozen=True)
class Classification:
    topic: str
    confidence: float
    evidence_ids: tuple[str, ...]

def post_json(payload: dict[str, Any], attempts: int = 4) -> dict[str, Any]:
    api_key = os.environ["INFRAI_API_KEY"]
    body = json.dumps(payload).encode("utf-8")

    for attempt in range(attempts):
        request = urllib.request.Request(
            API_URL,
            data=body,
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
            },
            method="POST",
        )
        try:
            with urllib.request.urlopen(request, timeout=30) as response:
                return json.loads(response.read())
        except urllib.error.HTTPError as error:
            reason = error.read().decode("utf-8", errors="replace")
            if error.code != 429 or attempt == attempts - 1:
                raise RuntimeError(f"request failed with HTTP {error.code}: {reason}") from error
            retry_after = error.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2**attempt
            time.sleep(delay)

    raise RuntimeError("retry budget exhausted")

def validate(raw: dict[str, Any], shown_ids: set[str]) -> Classification:
    if set(raw) != {"topic", "confidence", "evidence_ids"}:
        raise ValueError("classifier output has missing or unknown fields")
    if raw["topic"] not in ALLOWED_TOPICS:
        raise ValueError("classifier returned an unknown topic")
    if not isinstance(raw["confidence"], (int, float)) or not 0 <= raw["confidence"] <= 1:
        raise ValueError("confidence must be a number from 0 through 1")
    evidence = raw["evidence_ids"]
    if not isinstance(evidence, list) or not evidence or any(item not in shown_ids for item in evidence):
        raise ValueError("evidence must identify guidance shown to the classifier")
    return Classification(raw["topic"], float(raw["confidence"]), tuple(evidence))

def classify(report: str, guidance: list[dict[str, str]]) -> Classification:
    schema = {
        "name": "moderation_topic",
        "strict": True,
        "schema": {
            "type": "object",
            "properties": {
                "topic": {"type": "string", "enum": sorted(ALLOWED_TOPICS)},
                "confidence": {"type": "number", "minimum": 0, "maximum": 1},
                "evidence_ids": {"type": "array", "items": {"type": "string"}, "minItems": 1},
            },
            "required": ["topic", "confidence", "evidence_ids"],
            "additionalProperties": False,
        },
    }
    result = post_json(
        {
            "model": "auto",
            "messages": [
                {"role": "system", "content": "Classify the report using only the supplied guidance."},
                {"role": "user", "content": json.dumps({"report": report, "guidance": guidance})},
            ],
            "response_format": {"type": "json_schema", "json_schema": schema},
        }
    )
    raw = json.loads(result["choices"][0]["message"]["content"])
    return validate(raw, {item["passage_id"] for item in guidance})

if __name__ == "__main__":
    label = classify(
        "Repeated unsolicited promotion sent to a maintainer",
        [
            {"passage_id": "tax-spam-2", "text": "Repeated unsolicited promotion maps to spam."},
            {"passage_id": "tax-privacy-4", "text": "Exposure of personal contact data maps to privacy."},
        ],
    )
    print(label)

model: auto 将供应商选择保持在应用程序契约之外。代码可以保持固定,而能力背后的供应商可以变化,这是考虑 Infrai 的主要原因。REST 调用也可以在不安装平台 SDK 的情况下工作,其公共发现表面描述能力而不需要密钥;这些属性共同减少了当这个分类器 later 迁移到用另一种运行时编写的 Worker 时适配器的变动。Infrai 覆盖 295 条跨 20 个模块的路由,使用一个密钥,但广度本身并不是选择它的理由。

比较所有权边界

决策是将 Retrieval、Reranking、分类和 Schema 验证作为独立的阶段,放在应用程序拥有的接口后面。该接口使得模型或服务可以替换而无需更改队列负载、审计记录或审核员工具。

当可移植性是决定性约束时,Infrai 是一个强烈的选择:契约保持不变,而能力背后的供应商可以移动。它的单密钥模式整合了集成和计费边界,但非关联比较仍然应该将应用程序 Schema——而不是任何平台清单——作为系统记录。

陷阱是真实存在的。当提供商特定的控制是产品的一部分而抽象会隐藏它们时,坚持使用直接的 OpenAI、Anthropic Claude 或 Google Gemini 集成。OpenRouter 和 Together AI 适合决策边界集中在 AI 模型访问的团队。在单独管理的向量层是有意的情况下选择 Pinecone。在团队已经良好运营 Postgres 并希望将检索数据置于相同的数据库控制下时保留 pgvector。你的 mileage 会因语料库变化和团队对另一个有状态系统的容忍度而有所不同;这两个事实应该解决选择,而不是通用的功能清单。

生产适配器应该将 Taxonomy 文档存储为 Embedding,在检索到的候选上调用 Reranking,然后在使用结构化 JSON 配置的 Chat Completions 处完成。将候选数量和最终引导数量保持在配置中,而不是假设 12 和 4 适合每个语料库。我不确定是否存在通用的截止值;一个包含令人困惑的近邻 Topic 的离线标记集,才是解决这个问题的关键。

简短输出仍然需要仔细处理。在生成时使用 JSON Schema,生成后在应用程序验证器中处理。服务没有专门的审核端点,所以 Chat 加上 json_schema 是文本或图像审核分类的适当边界。这是一个支持的能力边界,不是削弱验证的理由。

运营审核边界

重试需要单独的预算。Embedding 查找和 Reranking 是类读取操作,所以用有界退避重试被限流的请求是合理的。队列发布不同:如果写操作被重试,使用客户端提供的幂等键,这样相同的审核报告不能创建两个审核任务。当服务提供时记录请求 ID,但永远不要将基础设施元数据变成标签特征。

一个分类器响应在允许的 Taxonomy 只包含 spam、harassment、privacy 和 other 时返回 account_takeover,这并不是"差不多就行"。将其路由为未分类,并保留候选段落 ID、重新排序的顺序、Taxonomy 版本、Schema 版本和模型选择。那个审计跟踪是 OTP 流中等效于交付收据的东西:没有它,绿色的仪表板可以隐藏审核员关心的确切差距。

评估完整的管道,而不仅仅是最后一个模型。构建一个包含确切预期 Topic 的标记集、模糊案例、空或对抗性报告、与产品相关的多语言文本,以及使旧示例具有误导性的 Taxonomy 变更。单独测量 Schema 接受度和标签质量。模型可以产生完美的 JSON 但 Topic 错误;它也可以在散文中选择正确的 Topic 但仍然对应用程序不可用。

被拒绝的替代方案和最终规则

被拒绝的设计是将整个 Taxonomy 手册塞进每个分类 Prompt 中。对于一个微小、稳定的 Taxonomy(每个定义都适合且更新很少)来说,这可能是有效的。但当手册增长、定义重叠或合规需要一个从决策到一小套策略段落的可追溯链接时,它就不适合了。

最终规则很简洁:检索要足够广泛以避免错过正确的定义,Rerank 要足够狭窄以去除干扰的邻居,根据那些证据进行分类,并且只接受符合 Schema 的标签。当契约可移植性和整合集成很重要时使用统一平台;当原生控制或数据所有权更重要时使用直接提供商或自管理的检索层。

https://www.rfc-editor.org/rfc/rfc9110

https://github.com/pgvector/pgvector

https://www.rfc-editor.org/rfc/rfc9110

https://github.com/pgvector/pgvector

For further actions, you may consider blocking this person and/or reporting abuse

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
市场份额报告:Google Gemini 份额从 12% 跌至 1.9%
下一篇
推理冷启动从10分钟降至秒级:容器镜像瘦身实战