拓冰建站拓冰建站
首页 / 资讯中心 / 正文

AI软件工厂设计模式:构建可替换、可观察的AI应用

AI软件工厂设计模式这个话题最近在技术社区里讨论明显变多。我把它拆成一条主线AI并没有让设计模式失效反而让设计模式重新回到工程实践的中心。很多人误以为设计模式是Java时代的产物既然大模型能直接生成代码还要不要设计模式就无所谓了。实际只要开始把大模型接入业务系统、把AI Agent编排成工作流、把提示词和工具调用标准化就会立刻遇到对象创建、模型路由、缓存代理、状态流转、消息通知这些老问题。这些问题如果不用设计模式约束代码会很快变成一堆复制粘贴的特例。这篇文章面向正在做AI应用开发、想把多个模型统一接入、或者打算把AI能力做成内部技术平台的人。我不假设你研究过设计模式但希望你已经接触过大模型API。后面会按我自己的实践路径来写先想清楚为什么需要再选哪些模式然后搭一个最小框架最后聊批量任务和排查顺序。1. 先想清楚AI软件工厂要解决的是“可替换”和“可观察”1.1 为什么不是把模型塞进业务代码里AI软件工厂不是一个标准产品更像一种工程方法用大模型、Agent、自动化流水线来辅助软件生产让生成、路由、调用、缓存、日志、重试这些环节都变成可替换模块。这个概念听起来大但落到代码上最先撞上的问题就是“模型调用点散落”。一开始接一个外部模型API业务代码里直接client.chat(...)看起来最直接。等到想换一个模型或者同一个场景需要不同模型做兜底问题就来了调用点分布在十几个文件里每个地方都要改入参格式和出参解析想加缓存不知道从哪个入口统一加想统计耗时只能到处打点。更麻烦的是某个模型API临时不可用想切到本地模型或者另一个服务需要改业务代码业务团队就被模型供应商绑死了。这其实是典型的“重复出现的变化点”。设计模式在AI软件工厂里最核心的价值就是把变化点隔离起来。模型、参数、输入输出格式、缓存策略、失败重试这些都会变。如果它们散落在业务代码里每变一次就要牵动一片如果把它们封装在独立模块后面变化就只影响局部。1.2 设计模式在AI工程里的真实作用传统业务系统里设计模式解决的是对象创建、算法切换、职责扩展、状态流转、消息通知这些常见问题。AI软件工厂里这些问题一个都不少只是对象从订单、用户变成了模型客户端、Agent任务、工具调用。我见过一个很典型的反例团队做了一个“智能写作助手”一开始只接一家模型API所有逻辑都在一个服务里后来要接入另一个模型做长文总结代码里开始出现大量if model a的分支。再后来要支持多个部门调用又加了一堆配置项。三个月后没人敢改那段代码因为改动的影响面已经无法判断。设计模式的作用不是让代码看起来“专业”而是让需求变化时改动范围可控。判断一个模式用得好不好不是看用了多少个类而是看新增一个模型、切换一个策略、增加一个缓存机制时需要改动多少个文件。如果只需要改注册配置和新增一个实现类调用层完全不动这就算立住了。2. 哪些设计模式在AI软件工厂里最常用2.1 工厂模式统一创建模型客户端工厂模式解决的是对象创建问题。在AI软件工厂里最常见的需求是根据配置创建不同的模型客户端比如一个OpenAI兼容客户端、一个本地部署模型客户端、一个内部封装模型服务。如果不做工厂业务层会写成if model_provider openai_compatible: client OpenAICompatClient(api_key..., base_url...) elif model_provider local: client LocalClient(model_path...)这种if-elif本身没问题问题在于它会不断膨胀而且每个调用模型的地方都需要知道如何创建客户端。如果创建逻辑变了比如某个供应商需要传新的认证参数所有分支都要跟着改。用工厂模式可以把创建逻辑收拢到一个地方。新增模型时只要注册一个新的构造函数业务层继续依赖统一接口不需要关心具体实现。我一般会把工厂做成一个注册表而不是一个巨大switch。模式解决的核心问题AI应用中的典型位置示例工厂模式对象创建和替换模型客户端、Embedding客户端根据配置创建不同模型客户端策略模式算法或行为动态切换模型路由、评分策略、答案抽取按任务类型选择模型代理模式控制访问、增强职责缓存、鉴权、日志、限流调用模型前检查缓存观察者模式一对多通知解耦任务事件、日志流、指标上报任务完成时通知存储和监控状态机模式复杂状态流转Agent执行流程待执行、运行中、等待工具结果、完成或失败2.2 策略模式模型路由和参数切换策略模式适合处理“同一套流程里不同场景用不同策略”的情况。在AI软件工厂里最典型的是模型路由。例如一句话术识别任务简单问答复用便宜的小模型长文档总结用大上下文模型代码生成用代码能力更强的模型。如果把这些逻辑写成一堆嵌套if后面每加一个策略就要改一遍调用主体。策略模式的做法是定义一个路由策略接口每个策略返回一个模型名称或策略配置调用主体只依赖策略接口不关心具体选哪个模型。这样新加策略时主流程不用动。更进阶的用法是参数策略。不同模型对温度、最大token、超时时间的要求不同。策略模式可以负责组装这些参数避免调用方写一堆“模型名参数”组合的特例。2.3 代理模式缓存、鉴权、日志与限流代理模式在处理公共逻辑时非常好用。调用大模型前后通常有这些公共动作检查缓存、鉴权、记录日志、做限流、统一异常处理。我想强调一点这些逻辑不应该散落在业务代码里也不应该每个模型实现里写一遍。用代理模式包一层可以让模型客户端只负责真正的模型调用公共逻辑放在代理里。一个简单的调用链可以是代理先查缓存命中就直接返回没有命中再调用真实的模型客户端拿到结果后写缓存记录耗时和token消耗。后面如果要加限流只要在代理里加一个计数逻辑不需要改模型客户端和业务层。这有点像 AOP但比动态代理更容易理解。它把“模型调用”和“围绕调用的控制逻辑”解耦了。2.4 状态机模式管理Agent的任务生命周期做AI Agent相关工作的人很快会遇到状态管理问题。一个Agent任务可能经过待执行、运行中、等待工具结果、生成完成、失败、超时。如果你用一堆布尔变量去控制很快就会乱。状态机模式要求把状态和转移条件显式定义清楚。什么条件下从“等待工具结果”到“生成完成”什么条件下进入“失败”都写在一个地方。这样状态流转可预测也方便做任务恢复。不是所有AI应用都需要完整的状态机。如果只是简单的一问一答不需要。一旦开始做多步任务编排、工具调用、人工审核状态机就值得引入。3. 搭一个最小AI软件工厂从接口到路由3.1 先定义统一输入输出再写实现真正动手时我建议不要急着写任何模型接入代码。先定义一个统一接口让上层只依赖这个接口。from abc import ABC, abstractmethod class LLMClient(ABC): abstractmethod def chat(self, messages, **kwargs): 输入一组消息返回补全结果。 pass这个接口很朴素但很重要。它把“模型是什么”隐藏了。调用方只需要知道我传入messages拿到一个字符串结果。至于背后是OpenAI兼容接口、本地模型、还是内部网关调用方不需要关心。如果你需要更完整的输出可以在返回结构里加元信息比如模型名、耗时、token数、原始响应。统一返回结构以后上层统计和处理会方便很多。不要一开始就把所有模型参数都往接口里塞。这个阶段只需要稳定住最常用的交互方式具体参数可以放到实现类里。3.2 用工厂和策略组合出可替换的调用链有了接口下一步是创建客户端。我用一个注册式工厂来管理class LLMClientFactory: _registry {} classmethod def register(cls, name, client_cls): cls._registry[name] client_cls classmethod def create(cls, name, config): client_cls cls._registry.get(name) if not client_cls: raise ValueError(funknown client: {name}) return client_cls(**config)新增一个模型时只需要注册一个名字和它对应的实现类然后通过配置文件指定name。业务层永远调用LLMClientFactory.create(model_name, config)不会散落if。再配合一个最简单的路由策略class ModelRouter: def __init__(self, default_model): self.default_model default_model def route(self, request): if request.get(task_type) long_text: return long-context-model return self.default_model实际使用的时候路由策略可能会根据用户、部门、任务类型、成本预算返回不同的模型名。这里的关键不是路由逻辑复杂而是主流程只调用router.route(request)策略替换不影响主流程。3.3 加上日志代理让调用过程可观测一个只有工厂和路由的系统还不太适合生产因为你不知道每次模型调用到底花了多久、消耗了多少token、是否报错。我一般会在模型客户端外面加一层日志代理。import time class LoggingProxy(LLMClient): def __init__(self, client: LLMClient): self._client client def chat(self, messages, **kwargs): start time.time() try: result self._client.chat(messages, **kwargs) elapsed time.time() - start # 这里做结构化日志方便后续统计 print(fmodel call elapsed: {elapsed:.2f}s) return result except Exception as e: print(fmodel call failed: {e}) raise这个代理类本身不做模型调用而是把调用过程包一层。好处是日志逻辑只写一次所有客户端增强都走同一套逻辑。之后要加缓存可以再写一个CachedClientProxy通过组合方式叠加。不要一开始就把缓存、日志、限流、重试全部堆到一个代理里。那样代理会变成一个上帝类。每加一个职责就写一个新的代理然后按需组合。4. 从单任务走向批量设计模式怎么兜底4.1 批量任务不是循环调用而是队列加幂等单条任务跑通之后很多人会想当然地写一个for循环把输入列表跑一遍。这样做的问题在于如果跑了 100 条跑到第 50 条时网络中断前面 49 条的状态怎么记录已经生成的输出会不会重复批量任务的核心不是“循环”而是“队列加状态”。每次任务都应该有一个唯一ID任务状态要能持久化。处理流程是从队列里取一个任务更新状态为“运行中”执行模型调用写完输出更新状态为“完成”如果失败记录错误按策略重试或跳过。这里“幂等”很重要。同一任务重复执行结果如果一致那重复执行就没问题如果不一致就要设计好“重复执行是否覆盖旧输出”的策略。4.2 输出命名、失败重试与断点续跑批量的输出目录和命名规则最好在任务设计时就定好。我常用的命名方式是任务ID 输入哈希 扩展名。这样同一个输入重复执行时输出文件不会互相覆盖也方便判断这个结果来自哪条输入。失败重试要控制节奏。不要失败后立即重试尤其不要无限制重试。一个相对稳妥的做法是指数退避第一次重试等几秒第二次等更久最多重试三次。重试逻辑要写在任务层而不是业务调用层。断点续跑是批量任务里最容易被低估的功能。如果一个批量任务需要两个小时中途因为网络波动失败重新跑一遍整个任务会浪费大量时间。比较好的做法是把每个任务的执行状态记录到本地文件或数据库里重新启动时先读取状态只处理未完成的任务。4.3 接口化时并发控制要放在代理层如果你要把AI软件工厂暴露成内部接口让多个业务方调用并发控制和超时管理就不能放在业务方了。这两个职责同样适合放在代理层。假设你有一个ModelClientProxy它可以在真实调用之前做并发限制。比如同一个模型同时最多只允许 5 个请求超过的请求排队或直接拒绝。这个逻辑放到代理后业务方不需要关心底层模型有多少并发配额。接口返回结构也要稳定。调用方只需要知道请求成功返回什么失败返回什么错误码。不要因为换了模型而改变返回结构。稳定性比“灵活”更重要。5. AI辅助开发中的设计模式实践5.1 AI生成代码后设计方案要先行现在AI编程工具很流行很多人拿到需求就直接让AI生成代码。这么做对简单任务没问题但对AI软件工厂这类多模块系统直接生成很容易得到一堆“看起来能用但改不动”的代码。我的习惯是先花十几分钟把接口和关键模式定下来再让AI填充具体实现。比如先定好LLMClient接口告诉AI“新增一个模型客户端实现这个接口”生成的结果会比直接说“帮我写一个AI接口”稳定得多。设计模式在AI辅助开发里的作用更像是给AI一个“约束框架”。没有约束时AI会随机发挥有了约束后生成结果更一致评审也更有抓手。5.2 提示词里如何约束代码结构如果使用AI编程可以在提示词里明确指定设计约束而不只是说功能。例如“使用工厂模式创建模型客户端不要在业务代码中直接实例化具体客户端。”“新增模型时只需要新增一个实现类和注册配置不需要修改调用方。”“所有模型调用都通过代理层记录日志业务层不直接调用SDK。”“模型路由使用策略接口不要用多层if-else写死。”这样生成出来的代码不会一开始就让设计走样。你甚至可以要求AI先生成接口和目录结构再逐层实现。对于偏经验不足的开发者这种提示词能保证大方向不错。5.3 评审时重点看哪些点代码评审时我一般会重点看这几个点新建一个模型需要改几个文件。如果超过两个说明耦合度高了。是不是有地方直接用具体类实例而不是走工厂。日志、超时、重试是不是散落在业务代码里。路由策略是不是变厚了是否还能轻松替换。Agent状态流转是不是只通过状态机更新而不是在多个地方随便赋值。这些检查点本质上都是在回答一个问题下一次变化发生时系统能不能承受住。如果每次加模型、换模型都要动一遍核心业务代码设计模式的引入就是失败的。6. 我踩过的坑和排查顺序6.1 不要为了设计模式而设计模式设计模式的最大坑是过度设计。如果整个系统只接一个模型只有一条调用链路一套工厂加代理加观察者可能会让你的代码变得比业务还复杂。我自己比较建议“按变化点引入模式”。当前只有一个模型就先定义接口当出现第二个模型时再引入工厂当需要频繁切换模型时再加策略当调用量上来时再加日志代理和缓存。模式不是开局就堆满而是跟着复杂度走。6.2 报错排查顺序输入、环境、网络、参数、代码AI软件工厂的报错经常让人无从下手。报错一多很多人会怀疑模型质量或代码但实际很多时候是环境问题。我的一般排查顺序是先看输入。输入格式是不是符合模型要求消息结构是否合法文本长度是否超限。再看环境。依赖版本是否一致模型路径是否存在配置密钥是否正确。接着看网络。网络超时、DNS解析、接口地址是否可达是不是需要走网关。然后看参数。温度、最大token、超时时间、并发数是否设置合理。最后才看代码。是不是最近的改动被缓存了是不是分支写错。如果出现“偶发失败”不要急着改代码。先看日志里的耗时分布和失败规律。很多偶发失败都是因为并发数超了模型服务上限而代码本身没有错。6.3 落地时最值得先做的小改造如果你现在有一堆直接调用模型API的代码不要想着一次重构完。我建议按三步走第一步抽一个统一接口哪怕只有chat方法先把所有直接调用改成通过这个接口。第二步给统一接口加一层日志代理先解决可观测问题让每次调用都能看到耗时和错误。第三步引入工厂把模型客户端的创建收拢到注册表为后续新增模型做准备。做完这三步你已经具备一个最小AI软件工厂的雏形。后面再加缓存、限流、任务队列、状态机都是在这个基础上扩展不会伤筋动骨。我个人更建议先把单任务跑稳再考虑批量和接口。AI软件工厂设计模式的最终效果不是看用了多少个模式而是看换模型、加缓存、加限流、排查问题时要改动多大范围。如果每次新增一个模型只需要注册一行配置调用层完全不动这个工厂才算真正立住了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门