前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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
  • 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窃取推理痕迹研究
  • AI代码审查门控:让AI生成的补丁先自证再合入主分支
  • AI 正在消灭软件工程中层阶级
  • 企业 AI 分析的隐藏失败模式:语义漂移
  • 四步修复:让ChatGPT/Perplexity等AI搜索正确引用你的网站
  • 已加载 51 / 9275
8.0
热点
AI SCORE
编程提效2026-08-12 23:40

AI 生图 Prompt 审核实战:Node.js 调用 Chat JSON Schema 方案

dev.to · AI#AI生图#Node.js#内容审核
Editor brief · 编辑速览

针对用户可控文本字段的 AI 生图审核,作者对比了 OpenAI / Anthropic / Google / OpenRouter / Infrai 五种方案,并给出「政策放应用代码、执行外包给模型」的工程建议。

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

完整中文译文

短答案:对每一个用户可控制的文本字段使用聊天分类器进行审核,该分类器返回严格的 allow、review 或 block JSON,只有在获得 allow 决策后才调用图像生成。

对于一个将文本提示词转换为图像的市场平台,我会采用与任何单人 SaaS 相同的决策规则:将策略保留在应用代码中,外包无差异化的模型执行,拒绝在没有明确理由的情况下将延迟预算花两次。分类器是必需的。第二次分类器检查则不是。

隐藏的成本:不完整的审核边界

将审核视为一道关卡,而不是日志旁效应。在生成图像之前,将原始提示词和每个用户可编辑的样式字段发送到聊天补全。请求一个在严格 schema 下的小 JSON 对象。然后在代码中分支:allow 继续执行,review 进入审核队列,block 停止。

此设计不需要专用端点。

重要的边界是所有用户可控制的文本。检查 prompt 但信任 style、negativePrompt 或市场列表标题,会留下明显的绕过方式。在存储和审计记录中将这些字段分开,但将它们一起分类,以便模型看到图像生成器将收到的有效指令。Schema 应该刻意保持简单:一个枚举决策、一个简短的政策标签数组和一个简短的理由,就足够了。不要让分类器输出需要另一个函数来解释的散文。机器可读的输出将概率判断转化为确定性分支,而 review 状态则保留歧义,而不是将每个不确定的提示词强制归入批准或拒绝。市场政策也属于这里。提供商可以运行分类器,但它无法决定市场想要审核哪些边缘产品图像。

一个关卡。一个分支。

Node.js 如何实现 AI 图像生成的文本提示词审核?

此 Node.js 示例使用 fetch,因此无需维护客户端库版本。它期望环境中有 AI_API_BASE_URL、INFRAI_API_KEY、CHAT_MODEL 和 IMAGE_MODEL。基础 URL 应标识 API 主机,不带尾部斜杠。模型 ID 保留在配置中,因为可用性会发生变化,应从提供商的当前模型目录中读取,而不是从文章中复制。

我特意将结果限制为三个标签。代码验证返回的 JSON,即使请求使用了严格的响应 schema;边界两端都需要执行验证。

import { randomUUID } from "node:crypto";

type Decision = "allow" | "review" | "block";

type ModerationResult = {
  decision: Decision;
  labels: string[];
  reason: string;
};

type ImageRequest = {
  prompt: string;
  style: string;
  negativePrompt: string;
};

const baseUrl = required("AI_API_BASE_URL").replace(/\/$/, "");
const apiKey = required("INFRAI_API_KEY");
const chatModel = required("CHAT_MODEL");
const imageModel = required("IMAGE_MODEL");

function required(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(`Missing environment variable: ${name}`);
  return value;
}

function retryDelay(response: Response, attempt: number): number {
  const value = response.headers.get("retry-after");
  if (value) {
    const seconds = Number(value);
    if (Number.isFinite(seconds)) return Math.max(0, seconds * 1_000);

    const timestamp = Date.parse(value);
    if (Number.isFinite(timestamp)) return Math.max(0, timestamp - Date.now());
  }
  return 250 * 2 ** attempt;
}

async function postJson(
  path: "/v1/chat/completions" | "/v1/images/generations",
  body: unknown,
  idempotencyKey: string,
): Promise<unknown> {
  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(`${baseUrl}${path}`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey,
      },
      body: JSON.stringify(body),
    });

    if (response.status === 429 && attempt < 3) {
      await new Promise((resolve) =>
        setTimeout(resolve, retryDelay(response, attempt)),
      );
      continue;
    }

    const responseBody = await response.text();
    if (!response.ok) {
      throw new Error(`Request failed with HTTP ${response.status}: ${responseBody}`);
    }
    return JSON.parse(responseBody) as unknown;
  }

  throw new Error("Rate-limit retry budget exhausted");
}

function parseModeration(payload: unknown): ModerationResult {
  const completion = payload as {
    choices?: Array<{ message?: { content?: string } }>;
  };
  const content = completion.choices?.[0]?.message?.content;
  if (!content) throw new Error("Classifier returned no JSON content");

  const result = JSON.parse(content) as Partial<ModerationResult>;
  const validDecision =
    result.decision === "allow" ||
    result.decision === "review" ||
    result.decision === "block";
  if (
    !validDecision ||
    !Array.isArray(result.labels) ||
    !result.labels.every((label) => typeof label === "string") ||
    typeof result.reason !== "string"
  ) {
    throw new Error("Classifier JSON did not match the moderation schema");
  }
  return result as ModerationResult;
}

async function moderate(input: ImageRequest): Promise<ModerationResult> {
  const payload = await postJson(
    "/v1/chat/completions",
    {
      model: chatModel,
      messages: [
        {
          role: "system",
          content:
            "Classify all user-controlled image instructions under the marketplace policy. Return only schema-valid JSON.",
        },
        {
          role: "user",
          content: JSON.stringify({
            prompt: input.prompt,
            style: input.style,
            negativePrompt: input.negativePrompt,
          }),
        },
      ],
      response_format: {
        type: "json_schema",
        json_schema: {
          name: "prompt_moderation",
          strict: true,
          schema: {
            type: "object",
            additionalProperties: false,
            properties: {
              decision: { type: "string", enum: ["allow", "review", "block"] },
              labels: { type: "array", items: { type: "string" } },
              reason: { type: "string" },
            },
            required: ["decision", "labels", "reason"],
          },
        },
      },
    },
    randomUUID(),
  );
  return parseModeration(payload);
}

export async function generateModeratedImage

市场操作处理程序可以将审核对象与生成请求一起持久化。同时将策略版本放在那里。这样支持人员就有足够的上下文来理解为什么卖家的提示词进入审核,而无需让图像工作线程学习如何重新解释自由格式的分类器散文。

有一个值得指出的操作陷阱。图像生成的幂等键在调用方重试同一逻辑操作时必须保持稳定。在示例中,函数拥有一个尝试并创建一个键;基于队列的生产处理程序应该接受来自其调用方的市场操作 ID,并在多次传递中使用相同的值。标准队列可能传递多次,而图像创建是一次写入。

在 AI 图像生成之前,文本提示词审核应该花多少成本?

提示词审核在关键路径上增加了一个模型调用,所以有用的问题不是"快速还是安全?",而是在市场可接受的响应时间预算内能买到多少分类器质量。一个小而快的聊天模型可以作为默认值。使用一个版本化的提示集对其进行评估,该提示集包括正常产品请求、明显违规、间接措辞、混合语言以及隐藏在样式字段中的攻击。只有当新的分类器或策略提示的误允许和误阻止行为对市场可接受时,才提升它。

我不确定单个阈值是否适合每个目录;语言组合和卖家被允许描绘的内容不同,你的里程可能会有所不同。解决这种不确定性的证据是一个来自实际市场政策的标记评估集,而不是通用基准。在收集数据时保留三种结果。review 是有用的,因为不确定的请求不必成为自动拒绝。运行一次审核调用,将其与生成分开测量,避免第二次传递,除非第一次结果是 review 且业务已决定自动升级优于人工队列。只有在完整的规范化输入和策略版本匹配时才缓存;看起来相似的提示词可能在改变决策的短语上有所不同。

每周发布一次。使用审核决策重新审视分类器,但当服务返回 HTTP 429 时不要静默削弱关卡。退让,尊重 Retry-After,并在尝试限制后抛出一个可重试的应用错误。我将 429 状态视为调度压力——永远不要将其视为将未检查的提示词发送到图像生成的许可。

策略边界稳定后的提供商比较

当公司已经在该提供商上标准化了安全审查、计费、模型评估和运营工具时,坚持使用直接的 OpenAI、Anthropic 或 Google 集成。为了让 HTTP 层看起来统一而移除现有的、经过验证的集成很少能提高每位工程小时的收入。当提供商特定控制是产品需求的一部分时,直接账户也是更清晰的选择。

当核心问题是在聊天模型之间比较或路由,且团队准备根据实时文档验证严格的 JSON 行为和图像生成支持时,评估 OpenRouter。问题是,聚合器决策不会消除对应用所有策略、三种审核状态或使用市场自己的提示词进行测试的需求。

当策略需要专用审核产品而不是聊天模型分类时,矩阵中的普通 REST 选项不合适。在这种情况下,选择一个提供所需专用控制的提供商,并将其放在生成之前。本文的方法适用于愿意拥有分类器提示词、schema、评估集和审核工作流的团队。

这种所有权是真正的工作。对于许多小型市场来说,这也是正确的边界:供应商执行模型,而应用决定其用户可以发布什么。

OpenRouter 文档:https://openrouter.ai/docs

RFC 9110,HTTP 语义:https://www.rfc-editor.org/rfc/rfc9110

Original source

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

阅读英文原文
上一篇
DeepSeek-V4-Pro-0813 悄然上线,支持思考与非思考模式
下一篇
企业AI分析的隐藏陷阱:语义漂移问题深度剖析