简要回答:在生成之前对完整图像请求进行分类,返回一个小的JSON决策,并将分类器和图像调用附加到同一个租户账本上。有用的设计选择是成本边界,而不是特定的审核端点。
对于物流产品来说,这个账本很重要。承运商、仓库运营方和内部支持团队可能都通过同一个图像功能发送提示词,但他们的审核率和提示词大小各不相同。如果分类是一个不可见的辅助调用,团队就无法解释为什么某个租户的月度AI支出发生了变化。我更喜欢一条从笔记本到生产的路径,使策略契约、租户键和评估记录从第一个示例起就明确可见。
闸门在图像任务进入队列之前运行。它接收将发送给图像模型的精确文本:用户的描述、选定的样式、负向提示词以及任何模板字段。它输出allow、review或block。只有allow才能创建图像任务。
这条边界是不可协商的。
将分类器视为策略组件,而不是安全预言机。应用程序拥有策略类别以及针对每个结果所采取的操作。JSON模式使边界易于验证,但仅凭有效的JSON并不能证明策略是有用的。
以下示例使用纯HTTP形状的配置,以便应用程序可以指向其选定的聊天分类器和图像服务。这里使用Python是因为我的生产示例需要保持与评估框架的紧密关联;相同的请求体也适用于Node.js HTTP客户端。URL是配置项,而非推荐项。
import json
import os
import urllib.request
import uuid
DECISIONS = {"allow", "review", "block"}
def post_json(url, payload, headers):
request = urllib.request.Request(
url,
data=json.dumps(payload).encode("utf-8"),
headers={**headers, "Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(request, timeout=60) as response:
return json.loads(response.read().decode("utf-8"))
def moderate_and_queue(tenant_id, prompt, style, negative_prompt=""):
candidate = {
"prompt": prompt,
"style": style,
"negative_prompt": negative_prompt,
}
headers = {"Authorization": f"Bearer {os.environ['AI_API_KEY']}"}
result = post_json(
os.environ["CLASSIFIER_URL"],
{
"input": candidate,
"response_schema": {
"type": "object",
"properties": {
"decision": {
"type": "string",
"enum": ["allow", "review", "block"],
},
"reason": {"type": "string"},
},
"required": ["decision", "reason"],
"additionalProperties": False,
},
},
headers,
)
decision = result.get("decision")
if decision not in DECISIONS:
return {"decision": "review", "reason": "Invalid policy result"}
if decision != "allow":
return result
job_id = str(uuid.uuid4())
job = post_json(
os.environ["IMAGE_URL"],
{"job_id": job_id, "prompt": candidate},
{**headers, "Idempotency-Key": job_id},
)
return {"decision": "allow", "job": job}
print(moderate_and_queue(
tenant_id="warehouse-west",
prompt="A clean loading dock diagram at sunrise",
style="technical illustration",
))
有一个细节故意显得平淡无奇:格式错误的结果会变成 review,绝不会变成 allow。网络故障应在所在的 worker 中走同样的非生成路径,并附带持久化的原因和重试策略。图片请求携带幂等键,因为重试绝不能意外创建两个任务;HTTP 重试行为和幂等性是应用层面的关注点,应当依据 RFC 9110 中的语义来设计,而不是从客户端库中猜测。
示例还将 tenant_id 传入函数,尽管远程载荷并不需要它。这提醒你:要在本地记录归属信息。在分类之前,创建一个包含租户、请求 ID、策略版本和提示词 token 估算值的账目记录。每次调用后,在可用时追加服务报告的用量。不要将分类器和图片用量合并成一个数字。
第一个失败是输入不完整。分类器看到的是主提示词,却看不到模板的样式字段,因此一个看起来无害的请求可能在后续获得新的含义。构建一个规范的候选对象,将该对象序列化用于分类,并使用同一对象进行图片生成。这消除了一类极其常见的策略漂移。
第二个失败是结果不明确。“大概没问题”不是一种生产状态。保持契约精简,拒绝未知的枚举值,并将 review 发送到带足够裁决上下文的人工队列。对请求方而言,拦截响应应当是通用的;详细的策略推理应放在受限的操作日志中。
第三个失败是成本归属。即使所有图片都被拦截,提示词较长的租户也可能消耗更多分类器 token。这是真实的成本,也是一个有用的信号。分别跟踪分类器输入大小、图片输入大小、结果、延迟和重试次数。按租户的视图应同时回答“这个请求花费了多少?”和“它在哪里停止了?”
以下是我会放在队列设计旁边的决策表:
| 边界选择 | 适用场景 | 需接受的成本或风险 |
|---|---|---|
| 分类每个完整请求 | 模板和租户策略各不相同 | 每次尝试都会增加分类器工作量,包括被拦截的请求 |
| 将不确定的案例发送到审核 | 误放行比延迟更具破坏性 | 审核人力和队列延迟成为产品的一部分 |
| 按租户和阶段记录用量 | 财务和工程部门需要可解释的账单 | 提示词保留和标识符设计需要明确的控制措施 |
| 仅重试传输安全的工作 | Worker 可能重启或断开连接 | 重试策略无法修复不明确的策略结果 |
这张表虽然小,却能防止一个常见的设计错误:将安全和记账视为独立的中间件。它们观察的是同一个请求,因此它们应共享同一个请求 ID。
考虑一个租户提交了由路径描述、仓库预设和用户选择的样式组装而成的长提示词。分类器可能返回 block,因此不会生成图片,但分类尝试仍然消耗了时间和 token。如果账本只记录成功的图片,租户会看到其活动仪表板与账单之间存在无法解释的差距。如果账本只记录原始文本,运营团队就在试图解决财务问题的同时制造了一个隐私问题。有用的记录应更窄:租户 ID、请求 ID、策略版本、阶段、测量到的用量、决策和短期指纹。该记录支持对账,而无需将每份成本报告变成提示词存储的副本。
从一组评估集开始,将其分为普通创意请求、明确的策略违规、不明确的案例,以及在可编辑字段中隐藏指令的尝试。存储预期动作,而不仅仅是标签。当策略提示词、分类器模型、JSON schema 或图片模板发生变化时,重放该评估集。我不确定是否有任何单一阈值适用于所有物流工作流;审核队列的裁决结果才是解决这个问题的关键。
按租户细分衡量误放行和误拦截。仓库图表工具可能需要与公开创意功能不同的审核阈值,但这种差异应存在于版本化的策略配置中,而不是未被跟踪的提示词编辑。在产品和合规要求允许的范围内,尽量缩短原始文本的保留时间,并在日常成本报告中使用指纹。
重试需要边界。当服务器提供了重试延迟时,要遵守它;限制尝试次数,并区分可重试的传输响应与策略决策。RFC 9110 是思考方法语义和重试安全性的有用基准。对于图片任务,幂等键加持久化请求 ID 可以让 worker 在恢复时不会静默地重复工作;但这并不会让每个失败都可重试。
当你需要自定义审核状态、完整的输入覆盖,以及按租户解释分类器和生成花费时,可以使用这种架构。它与面向标准的 HTTP 边界和小型应用自有 schema 配合良好。
问题在于策略所有权。当托管审核工作流、固定安全分类法或特定提供商的审计界面是硬性要求时,这种架构并不合适;当托管路径的运维保障比可移植契约更重要时,请选择托管路径。如果没有人能标注审核案例或维护评估集,它也不太合适。共享接口无法弥补策略管理的缺失。
上线前,用文字逐条验证五件事:每个用户可编辑的字段都能到达关卡;只有经过验证的 allow 才能将图片加入队列;格式错误或不可用的分类器响应必须故障关闭并进入审核;重试有界且具备幂等性;账本分别记录租户、策略版本、分类器用量、图片用量和结果。然后在 CI 中运行评估集,并对线上审核案例进行抽样。保持实现简洁。简洁更容易审计。
——
一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。