AI 应用后端工程化:从原型到可交付系统 · 第 03 篇 · 第一章 · 服务基础
把提示词从字符串提升为可组合流程,并理解并行执行、调用追踪和浏览器访问边界分别解决什么问题。
把 Prompt 写在字符串里并没有错。问题出现在它开始承担越来越多职责:拼接用户资料、并行生成几个候选答案、记录调用成本、从浏览器访问接口。此时继续把所有内容堆进一个模板,会让“为什么得到这个结果”变得无法回答。
Prompt 工程化并不神秘,可以沿用普通数据处理的思路:明确输入,拆分步骤,保留中间结果,观察耗时,并在系统边界上控制访问。
一条提示词背后其实有四层
flowchart LR
Input[结构化输入] --> Compose[Prompt 组合]
Compose --> BranchA[任务 A]
Compose --> BranchB[任务 B]
BranchA --> Merge[结果合并]
BranchB --> Merge
Merge --> Output[结构化输出]
Trace[调用追踪] -.记录.-> BranchA
Trace -.记录.-> BranchB
Browser[浏览器] --> Boundary[CORS / API 边界]
Boundary --> Input
第一层是输入契约。例如摘要任务需要正文和目标长度,而不是一个随意命名的字典。第二层是组合方式:系统规则、用户内容和运行时上下文分别放在哪里。第三层是执行图:哪些步骤串行,哪些步骤可以并行。第四层才是可观察性和访问边界,它们让这条链在真实服务中能够被调用和排查。
并行只适合彼此独立的工作
假设要同时生成标题、摘要和关键词,三项都只依赖原文,完全可以并行。若关键词依赖摘要结果,就必须保持先后顺序。判断依据是数据依赖,不是“并行看起来更快”。
from concurrent.futures import ThreadPoolExecutor
def enrich_article(text: str, model) -> dict[str, object]:
tasks = {
"title": lambda: model.complete(f"为下文拟标题:\n{text}"),
"summary": lambda: model.complete(f"用三句话摘要:\n{text}"),
"keywords": lambda: model.complete(f"提取五个关键词:\n{text}"),
}
with ThreadPoolExecutor(max_workers=3) as pool:
futures = {name: pool.submit(task) for name, task in tasks.items()}
return {name: future.result() for name, future in futures.items()}
并行之后,失败语义也要重新定义:标题生成失败时,摘要是否仍然返回?任一任务超时是否取消其他任务?三个调用的总 Token 如何统计?这些问题必须由产品需求决定。底层并行组件只能提供机制,不能替你选择“一起成功”还是“部分成功”。
追踪不是把 Prompt 全部打印出来
调用追踪最有价值的信息通常包括请求 ID、模板版本、模型、延迟、Token 用量、重试次数和输出解析状态。直接把完整 Prompt、用户附件和模型回复写进公共日志,虽然排查方便,却可能把个人信息与商业数据暴露给不该看到的人。
更合理的方法是分层:普通指标保存数量和耗时;受控 trace 保存经过脱敏的输入摘要;只有在明确授权的调试环境中保留完整内容,并设置较短留存时间。可观测性也需要权限设计。
追踪还能帮助判断优化方向。若模型生成耗时占 95%,在 Python 字符串拼接上省一毫秒没有意义。若解析失败率突然上升,应比较模板版本和模型版本,而不是只看服务器 CPU。
CORS 不是报错消除开关
浏览器提示跨域失败时,最省事的做法是允许所有来源、所有方法和所有请求头。但 CORS 描述的是“哪些网页可以代表用户调用这个接口”,它是一条信任声明,不是开发期警告开关。
对于携带 Cookie 或浏览器凭证的接口,来源应该使用明确名单;预检请求要与真实方法一致;服务端仍必须做认证和授权。CORS 只能限制浏览器环境,脚本和服务端请求根本不受它保护,因此不能把它当安全体系。
一次典型的失控
团队为了改善答案,在 Prompt 中增加了用户画像、历史对话、知识片段和五个示例。质量偶尔提高,但成本翻倍、响应变慢,某些输入还超过上下文窗口。由于没有记录各部分 Token 和模板版本,没人知道哪一段真正有用。
修复顺序不应该是继续改措辞,而是先把输入拆成可测量区块,为每次调用记录版本、用量和评估结果。然后逐项移除内容做对照实验。Prompt 一旦进入服务,就应像代码一样有版本、有输入契约、有回归样本。
迁移到其他任务编排
并行、追踪和边界并非 AI 专属。价格聚合服务会并行请求多个渠道,图片处理会并行生成不同尺寸,报表系统会组合多个查询。它们同样需要数据依赖、局部失败策略和统一 trace。
可以挑一条现有模型调用,画出输入字段、并行分支、合并点和错误路径。随后记录五次请求的耗时与 Token,删除一段上下文再比较结果。练习的产物不是“感觉更快”,而是一张能说明取舍的数据表。
四个演进节点
55dbcc9:开始提炼 Prompt,用结构表达模型输入。e7599cc:引入并行 Runnable,执行从单链条变成可组合图。69950f5:加入调用追踪,让延迟和结果拥有观察入口。4986e90:处理 CORS,模型能力开始面对浏览器访问边界。
这四步分别触及输入、执行、观测和网络边界。把它们放在一起看,会发现 Prompt 的“工程化”从来不只是如何写一句更聪明的指令。