1. 引言
目录
- [1. 引言](#1. 引言)
- [2. 为什么 AI 项目需要工程化设计模式](#2. 为什么 AI 项目需要工程化设计模式)
- [3. 异步流水线模式](#3. 异步流水线模式)
- [3.1 模式概述](#3.1 模式概述)
- [3.2 核心设计:基于 asyncio.Queue 的 Stage 模式](#3.2 核心设计:基于 asyncio.Queue 的 Stage 模式)
- [3.3 代码实现:基础流水线骨架](#3.3 代码实现:基础流水线骨架)
- [3.4 实战:LLM 预处理流水线](#3.4 实战:LLM 预处理流水线)
- [4. 策略模式](#4. 策略模式)
- [4.1 模式概述](#4.1 模式概述)
- [4.2 在 AI 项目中的应用场景](#4.2 在 AI 项目中的应用场景)
- [4.3 实现方式](#4.3 实现方式)
- [4.4 实战:可插拔的 Prompt 模板策略](#4.4 实战:可插拔的 Prompt 模板策略)
- [5. 模式结合:构建 AI 内容生成引擎](#5. 模式结合:构建 AI 内容生成引擎)
- [5.1 整体架构设计](#5.1 整体架构设计)
- [5.2 消息载体设计](#5.2 消息载体设计)
- [5.3 各阶段实现](#5.3 各阶段实现)
- [5.4 完整代码示例](#5.4 完整代码示例)
- [6. 总结与展望](#6. 总结与展望)
现在的 AI 项目很少再是单文件脚本。当我们在生产环境中接入大模型、构建 RAG 问答、实现多 Agent 协作时,往往会面对复杂的数据处理链路和多变的业务规则。如果再加上异步 I/O、高并发请求,代码很容易陷入「回调地狱」或「if-else 海洋」。
这篇文章会结合 AI 项目里的真实场景,拆解两种经典的设计模式:
- 异步流水线(Pipeline):把一连串的数据处理步骤组织成清晰、可扩展的异步节点。
- 策略模式(Strategy):让算法、规则可以动态切换,告别复杂的条件分支。
我们会从模式原理讲起,然后给出可运行的 Python 示例,最后把它们组合成一个轻量级的 AI 内容生成引擎。读完以后,你可以直接把这些模式用到自己的项目中。
2. 为什么 AI 项目需要工程化设计模式
在搭第一个 AI 原型时,你可能会写出这样的代码:
python
def process(text):
text = clean(text)
text = chunk(text)
embeddings = embed(text)
results = search(embeddings)
context = merge(results)
prompt = build_prompt(context, text)
answer = llm.generate(prompt)
return answer
它看起来还行,但问题会随着需求迭代慢慢暴露:
- 步骤耦合:想要在清洗之后加一个脱敏步骤?你得在 function 中间插一行,牵一发动全身。
- 硬编码规则:不同的场景要换不同的分块策略、不同的 LLM 模型,只能用 if-else 判断。
- 同步阻塞:embed、search、generate 都是耗时操作,顺序执行会让接口响应变慢。
- 难以测试:每一步的结果都混在一起,单元测试时很难单独验证某一环节。
异步流水线模式能解决 1 和 3,策略模式能解决 2。两者结合,就能把 AI 应用的基础架构大幅提升一个档次。
3. 异步流水线模式
3.1 模式概述
异步流水线把一次请求的处理拆成多个阶段(Stage),每个阶段是一个异步任务。数据像在工厂流水线上一样,从上一个阶段产出后,立刻流入下一个阶段。各阶段之间通过队列或管道解耦,既能并行处理,又保持逻辑清晰。
在 AI 项目中,典型的流水线可以是:
原始输入 → 清洗 → 分块 → 向量化 → 检索 → 组装上下文 → LLM 调用 → 返回
每一「→」都是一个可替换的管道节点。
3.2 核心设计:基于 asyncio.Queue 的 Stage 模式
我们用 asyncio.Queue 连接各个 Stage。每个 Stage 是一个协程,从输入队列读取消息,处理完后放到输出队列。多个 Stage 可以串联,也可以并行(例如同一阶段启动多个 worker)。
这种设计的优点:
- 天然异步:不会阻塞事件循环,适合 I/O 密集型任务。
- 背压控制:通过队列最大容量可以限制上游速度。
- 易扩展:想加一个新步骤就在链中插入一个 Stage,不影响其他节点。
3.3 代码实现:基础流水线骨架
python
import asyncio
from typing import Any, Callable, Awaitable
async def pipeline_stage(
name: str,
in_queue: asyncio.Queue,
out_queue: asyncio.Queue,
transform: Callable[[Any], Awaitable[Any]],
workers: int = 1
):
async def worker():
while True:
item = await in_queue.get()
if item is None: # 结束信号
in_queue.task_done()
break
try:
result = await transform(item)
if result is not None:
await out_queue.put(result)
except Exception as e:
print(f"[{name}] 处理异常: {e}")
finally:
in_queue.task_done()
tasks = [asyncio.create_task(worker()) for _ in range(workers)]
await asyncio.gather(*tasks)
async def run_pipeline(
items: list,
stages: list[tuple[str, Callable[[Any], Awaitable[Any]], int]],
queue_size: int = 100
):
# 构建队列链
queues = [asyncio.Queue(maxsize=queue_size) for _ in range(len(stages) + 1)]
# 启动所有 stage
stage_tasks = []
for i, (name, func, workers) in enumerate(stages):
task = asyncio.create_task(
pipeline_stage(name, queues[i], queues[i+1], func, workers)
)
stage_tasks.append(task)
# 放入初始数据
for item in items:
await queues[0].put(item)
# 所有数据放入后发送结束信号(每个 stage 一个 None)
for q in queues[:-1]:
await q.put(None)
# 等待所有 stage 结束
await asyncio.gather(*stage_tasks)
# 收集最终输出
results = []
while not queues[-1].empty():
results.append(await queues[-1].get())
return results
3.4 实战:LLM 预处理流水线
假设我们要对用户问题做预处理:清洗特殊字符 → 提取关键词 → 向量化。我们把三个函数定义为异步协程,丢进流水线。
python
import re
async def clean_text(text: str) -> str:
await asyncio.sleep(0.01) # 模拟耗时
return re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9\s]", "", text).strip()
async def extract_keywords(text: str) -> list[str]:
await asyncio.sleep(0.02)
# 模拟关键词提取
keywords = [w for w in text.split() if len(w) > 1][:5]
return keywords
async def embed_keywords(keywords: list[str]) -> list[float]:
await asyncio.sleep(0.05) # 实际会调用嵌入模型
# 模拟一个固定维度的向量
return [hash(kw) % 100 / 100.0 for kw in keywords]
async def main():
questions = ["如何使用 asyncio 队列?", "Python 中的异步上下文管理器"]
stages = [
("clean", clean_text, 2),
("keywords", extract_keywords, 2),
("embed", embed_keywords, 2),
]
results = await run_pipeline(questions, stages)
for q, emb in zip(questions, results):
print(f"问题: {q}\n向量: {emb}\n")
if __name__ == "__main__":
asyncio.run(main())
运行这段代码,你会看到并行处理的效果,即使我们故意加了 sleep 模拟耗时。
4. 策略模式
4.1 模式概述
策略模式定义一系列算法,把它们封装成独立的类或函数,并让它们可以互相替换。客户端只需要依赖一个通用的接口,而不需要知道具体使用了哪种算法。
在 Python 中,策略模式不必像 Java 那样强制生成一堆类文件。Python 天然支持一等函数,策略可以是一个函数对象、一个 lambda,或者一个实现了特定接口的类。为了让系统更可配置,我们通常使用注册表的方式管理策略。
4.2 在 AI 项目中的应用场景
AI 项目里,策略模式无处不在:
- Prompt 模板:根据用户意图(闲聊、技术问答、代码生成)选择不同的 prompt。
- 文本分块:按句子、按固定长度、按语义切分,不同场景用不同分块器。
- 嵌入模型:OpenAI text-embedding-ada-002、本地 BGE、高维向量,随时切换。
- 大模型选择:gpt-4o、Claude、本地 Qwen,通过策略统一调用。
这些如果不用策略模式,最终会变成一长串的 if-elif-else,甚至散落在业务代码各处。
4.3 实现方式
方式一:函数字典(最简单)
python
async def strategy_a(input: str) -> str:
...
async def strategy_b(input: str) -> str:
...
strategies = {
"option_a": strategy_a,
"option_b": strategy_b,
}
async def process(mode: str, input: str):
if mode not in strategies:
raise ValueError(f"未知策略: {mode}")
return await strategies[mode](input)
方式二:类封装(更面向对象)
python
from abc import ABC, abstractmethod
class ChunkStrategy(ABC):
@abstractmethod
async def chunk(self, text: str) -> list[str]:
...
class SentenceChunker(ChunkStrategy):
async def chunk(self, text: str) -> list[str]:
# 按句子切分
...
class FixedLengthChunker(ChunkStrategy):
def __init__(self, length=100):
self.length = length
async def chunk(self, text: str) -> list[str]:
# 按固定长度切分
...
方式三:注册表(推荐工程化)
python
class StrategyRegistry:
def __init__(self):
self._strategies = {}
def register(self, name: str, strategy):
self._strategies[name] = strategy
def get(self, name: str):
if name not in self._strategies:
raise KeyError(f"策略 '{name}' 未注册")
return self._strategies[name]
# 使用装饰器注册
chunk_registry = StrategyRegistry()
@chunk_registry.register("sentence")
class SentenceChunker(ChunkStrategy):
...
注册表的好处是,新增策略只需写一个新类并注册,不需要改动调用方代码。
4.4 实战:可插拔的 Prompt 模板策略
我们定义一个 Prompt 策略接口,三种模板:技术问答、闲聊、翻译。
python
from abc import ABC, abstractmethod
class PromptStrategy(ABC):
@abstractmethod
def build_prompt(self, user_input: str, context: str = "") -> str:
...
class TechnicalQAPrompt(PromptStrategy):
def build_prompt(self, user_input: str, context: str = "") -> str:
return f"""你是一名资深软件工程师。请基于以下上下文回答问题。
上下文: {context}
问题: {user_input}
答案:"""
class ChatPrompt(PromptStrategy):
def build_prompt(self, user_input: str, context: str = "") -> str:
return f"你是一个友好的 AI 助手,请用口语化方式回答: {user_input}"
class TranslatePrompt(PromptStrategy):
def build_prompt(self, user_input: str, context: str = "") -> str:
return f"将以下内容翻译成英文: {user_input}"
# 注册表
prompt_registry = StrategyRegistry()
prompt_registry.register("tech_qa", TechnicalQAPrompt())
prompt_registry.register("chat", ChatPrompt())
prompt_registry.register("translate", TranslatePrompt())
# 使用
async def generate_answer(mode: str, input_text: str, context: str = ""):
prompt_builder = prompt_registry.get(mode)
prompt = prompt_builder.build_prompt(input_text, context)
# 调用 LLM...
return f"模拟回答: 已处理 '{mode}' 策略"
5. 模式结合:构建 AI 内容生成引擎
现在我们把两个模式融合,写一个完整的例子:一个简单的 AI 内容生成服务。它接收一条用户指令,经过异步流水线处理,最终生成结果。
流水线结构如下:
用户输入 → 意图识别 → 策略选择 → 数据预处理 → LLM 调用 → 后处理 → 返回
其中「意图识别」和「LLM 调用」的不同实现通过策略模式切换。
5.1 整体架构设计
我们用框架图展示整体流程:
#mermaid-svg-wTj4X7tBpvUMMWPS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wTj4X7tBpvUMMWPS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wTj4X7tBpvUMMWPS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wTj4X7tBpvUMMWPS .error-icon{fill:#552222;}#mermaid-svg-wTj4X7tBpvUMMWPS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wTj4X7tBpvUMMWPS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wTj4X7tBpvUMMWPS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wTj4X7tBpvUMMWPS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wTj4X7tBpvUMMWPS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wTj4X7tBpvUMMWPS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wTj4X7tBpvUMMWPS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wTj4X7tBpvUMMWPS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wTj4X7tBpvUMMWPS .marker.cross{stroke:#333333;}#mermaid-svg-wTj4X7tBpvUMMWPS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wTj4X7tBpvUMMWPS p{margin:0;}#mermaid-svg-wTj4X7tBpvUMMWPS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wTj4X7tBpvUMMWPS .cluster-label text{fill:#333;}#mermaid-svg-wTj4X7tBpvUMMWPS .cluster-label span{color:#333;}#mermaid-svg-wTj4X7tBpvUMMWPS .cluster-label span p{background-color:transparent;}#mermaid-svg-wTj4X7tBpvUMMWPS .label text,#mermaid-svg-wTj4X7tBpvUMMWPS span{fill:#333;color:#333;}#mermaid-svg-wTj4X7tBpvUMMWPS .node rect,#mermaid-svg-wTj4X7tBpvUMMWPS .node circle,#mermaid-svg-wTj4X7tBpvUMMWPS .node ellipse,#mermaid-svg-wTj4X7tBpvUMMWPS .node polygon,#mermaid-svg-wTj4X7tBpvUMMWPS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wTj4X7tBpvUMMWPS .rough-node .label text,#mermaid-svg-wTj4X7tBpvUMMWPS .node .label text,#mermaid-svg-wTj4X7tBpvUMMWPS .image-shape .label,#mermaid-svg-wTj4X7tBpvUMMWPS .icon-shape .label{text-anchor:middle;}#mermaid-svg-wTj4X7tBpvUMMWPS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wTj4X7tBpvUMMWPS .rough-node .label,#mermaid-svg-wTj4X7tBpvUMMWPS .node .label,#mermaid-svg-wTj4X7tBpvUMMWPS .image-shape .label,#mermaid-svg-wTj4X7tBpvUMMWPS .icon-shape .label{text-align:center;}#mermaid-svg-wTj4X7tBpvUMMWPS .node.clickable{cursor:pointer;}#mermaid-svg-wTj4X7tBpvUMMWPS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wTj4X7tBpvUMMWPS .arrowheadPath{fill:#333333;}#mermaid-svg-wTj4X7tBpvUMMWPS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wTj4X7tBpvUMMWPS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wTj4X7tBpvUMMWPS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wTj4X7tBpvUMMWPS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wTj4X7tBpvUMMWPS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wTj4X7tBpvUMMWPS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wTj4X7tBpvUMMWPS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wTj4X7tBpvUMMWPS .cluster text{fill:#333;}#mermaid-svg-wTj4X7tBpvUMMWPS .cluster span{color:#333;}#mermaid-svg-wTj4X7tBpvUMMWPS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wTj4X7tBpvUMMWPS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wTj4X7tBpvUMMWPS rect.text{fill:none;stroke-width:0;}#mermaid-svg-wTj4X7tBpvUMMWPS .icon-shape,#mermaid-svg-wTj4X7tBpvUMMWPS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wTj4X7tBpvUMMWPS .icon-shape p,#mermaid-svg-wTj4X7tBpvUMMWPS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wTj4X7tBpvUMMWPS .icon-shape .label rect,#mermaid-svg-wTj4X7tBpvUMMWPS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wTj4X7tBpvUMMWPS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wTj4X7tBpvUMMWPS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wTj4X7tBpvUMMWPS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} tech_qa
chat
translate
用户输入
意图识别 Stage
策略注册表
技术问答 Prompt
闲聊 Prompt
翻译 Prompt
数据预处理 Stage
LLM 调用 Stage
后处理 Stage
返回结果
实际代码中,意图识别本身也是一个 Stage,它会把识别出的mode附加到消息对象上,后续 Stage 从注册表获取对应策略。
5.2 消息载体设计
为了让流水线传递的信息不只是原始字符串,我们定义一个简单的消息体:
python
from dataclasses import dataclass, field
@dataclass
class Message:
text: str
mode: str = "chat" # 意图分类结果
processed_text: str = "" # 预处理后
prompt: str = "" # 最终 prompt
response: str = "" # LLM 回复
5.3 各阶段实现
意图识别 Stage(简化版,用关键词匹配替代模型):
python
async def intent_detect(msg: Message) -> Message:
text = msg.text
if "代码" in text or "错误" in text or "bug" in text:
msg.mode = "tech_qa"
elif "翻译" in text:
msg.mode = "translate"
else:
msg.mode = "chat"
return msg
构建 Prompt Stage:
python
async def build_prompt_stage(msg: Message) -> Message:
builder = prompt_registry.get(msg.mode)
msg.prompt = builder.build_prompt(msg.text, context="")
return msg
LLM 调用 Stage(模拟):
python
async def llm_call(msg: Message) -> Message:
await asyncio.sleep(0.3) # 模拟网络延迟
msg.response = f"[{msg.mode} 模式] 这是对「{msg.text}」的回复"
return msg
后处理 Stage:
python
async def post_process(msg: Message) -> Message:
# 可以加一些清洗、格式化
msg.response = msg.response.strip()
return msg
5.4 完整代码示例
python
import asyncio
from typing import Any
# ---------- 策略注册 ----------
prompt_registry = StrategyRegistry()
prompt_registry.register("tech_qa", TechnicalQAPrompt())
prompt_registry.register("chat", ChatPrompt())
prompt_registry.register("translate", TranslatePrompt())
# ---------- Stage 定义 ----------
async def intent_detect(msg: Message) -> Message:
text = msg.text
if "代码" in text or "bug" in text or "错误" in text:
msg.mode = "tech_qa"
elif "翻译" in text:
msg.mode = "translate"
else:
msg.mode = "chat"
return msg
async def build_prompt_stage(msg: Message) -> Message:
builder = prompt_registry.get(msg.mode)
msg.prompt = builder.build_prompt(msg.text)
return msg
async def llm_call(msg: Message) -> Message:
await asyncio.sleep(0.2)
msg.response = f"[{msg.mode}] 这是对「{msg.text}」的回复"
return msg
async def post_process(msg: Message) -> Message:
msg.response = msg.response.strip()
return msg
async def run_engine(queries: list[str]):
msgs = [Message(text=q) for q in queries]
stages = [
("intent", intent_detect, 2),
("prompt", build_prompt_stage, 2),
("llm", llm_call, 2),
("post", post_process, 2),
]
results = await run_pipeline(msgs, stages)
for res in results:
print(f"输入: {res.text}\n意图: {res.mode}\n回复: {res.response}\n")
if __name__ == "__main__":
test_queries = ["Python 代码缩进错误怎么办?", "帮我翻译:你好世界", "今天心情真好!"]
asyncio.run(run_engine(test_queries))
运行后你会看到三条输入被并行处理,不同意图走了不同的 prompt 策略,且整条链路清晰可测。
6. 总结与展望
这篇博客我们通过代码实战,展示了在 AI 项目中落地两种关键工程化模式的方法:
- 异步流水线 用
asyncio.Queue把数据处理拆成独立 Stage,天然支持并行和背压,非常适合多步骤 I/O 密集型任务。 - 策略模式通过注册表解耦算法选择,让 prompt、分块、模型切换变得灵活,避免代码膨胀。
在实际项目中,你还可以进一步扩展:
- 给流水线加入监控和日志,用 OpenTelemetry 追踪每个 Stage 的耗时。
- 策略模式结合配置中心,实现热切换策略,不重启服务。
- 引入工厂模式 或建造者模式,动态构建不同的流水线组合。
- 使用 FastAPI 等框架将流水线暴露为 REST API,配合
async获得高并发。
设计模式不是教条,而是前人总结的通用语言。当你的 AI 应用从实验室走向生产环境时,这些模式能帮你构建出可维护、可扩展的代码。
希望这篇实践指南对你有帮助,欢迎在评论里分享你的工程化经验!