资讯动态

三层架构思维:AI应用输入输出处理的工程实战

发布时间:2026/9/26 13:08:09 来源:尧图企业网站定制
1. 三层架构不是老古董而是一把万能尺1.1 从 MVC 到 AI同一个骨架换了三层皮做 Java 的老哥们应该都熟 MVC 那套——Controller 收请求、Service 写业务、DAO 碰数据库。这套东西被吐槽“过时”很多年了但你去翻招聘 JDJava 后端十个有九个还写着“熟悉三层架构”。为什么因为三层架构的本质是把一个系统的复杂度切成三段外面的人怎么给我东西、我内部怎么算、我给别人回什么。这三段只要边界清楚每一段都能独立替换、独立测试、独立出问题。有人问我为什么 Java 大多数项目用三层架构而不是六边形架构我的回答很直接因为大多数业务根本没有六边形那么复杂的端口需求。六边形架构想解决的是“同一套核心逻辑对接多种输入输出端口”的问题比如一套订单系统同时要接 REST、消息队列、命令行、批处理。听上去很美但代价是每一层都要多包一层适配器抽象层级和心智负担都上去了。现实里一个业务系统能稳定撑三年靠的不是架构多先进而是边界多清楚。三层架构恰恰是那个“够用且不容易翻车”的默认选项。这套思维放到 AI 大模型时代一点不过时。你去看现在所有的 AI 应用拆到骨子里也就是三层输入层用户/系统喂给模型的上下文、指令、数据、处理层模型推理本身、输出层模型吐出来的原始内容经过的流式、格式化、校验、后处理。所谓“所有 AI 新花样只在输入什么 / 输出怎么处理”说的就是这个——模型本身是“处理层”大家用的底座越来越趋同真正拉开产品差距的全在两边。1.2 数字孪生、信号链万物皆可三层你可能会觉得这有点强行套壳。我再举两个领域。第一个是数字孪生。数字孪生的标准三层是感知层传感器采集物理世界的状态、数据层建模、仿真、分析、应用层可视化、控制、决策。感知层就是输入数据层就是处理应用层就是输出。很多数字孪生项目落地失败不是建模算法不行而是感知层的数据质量太差——传感器点位没校准、上传频率不稳定、数据格式不统一。这不就是输入侧的锅吗第二个是嵌入式。做过 STM32 的人都知道一个完整的信号链是传感器采集输入捕获、MCU 处理算法、PWM/串口/DAC 输出驱动执行器。大家天天调的“输入捕获”和“PWM 输出驱动电阻”本质上也是在调试输入协议和输出协议。PWM 引脚上串一个电阻是为了减小振铃匹配接收端的阻抗这不就是“输出端要做阻抗匹配”吗跟 AI 产品里“输出要给前端做适配裁剪”是一个道理。所以说三层架构不是某个技术栈的专属名词它是一种观察系统的元框架。你带着这个框架去看技术圈的热搜词——输入尺寸、格式化输出、SSE 流式输出、输入捕获、输出驱动电阻——会发现它们其实都落在同一个坐标轴上。掌握了这个坐标轴你对新技术的理解速度会快一大截。2. 输入处理大模型的“食材”怎么备2.1 输入决定上限提示词只是冰山一角同一颗 GPU、同一个开源模型不同人用出来效果天差地别甚至让人怀疑是不是两个模型。这不是玄学是输入层的差异。很多人的输入只有一句“帮我写一篇文章”模型的输出自然就是一篇“正确的废话”。而懂输入工程的人会一次性把这几样东西塞进上下文里角色设定你是谁、服务谁、任务目标写给谁看、达成什么目的、背景材料产品信息、数据、案例、约束条件字数、语气、禁止说什么、参考样例给一段范文让它模仿、输出格式标题/正文/结尾怎么排。这套东西行业内叫提示词工程但输入层远不止提示词。现在主流的 RAG检索增强生成本质上就是在输入层做文章——用户提了一个问题系统先去知识库检索相关文档把检索到的段落拼进上下文再让模型基于这些材料回答。Agent智能体也是同理模型在回答之前先调用工具、拿到工具返回的结果把结果作为新的输入再决策下一步。你看模型本身的权重一个没变变化的全是输入侧的内容拼装。所以我把输入层称为“食材准备间”。模型就像一个厨艺固定的大厨你给它一堆烂菜叶它只能炒出一盘烂菜你给它新鲜食材、配好调料、甚至给一张成品照片它就能给你端出一桌像样的菜。所有决定输出质量的上限在输入那一刻就基本锁死了。2.2 输入过长的代价不是越长越好热搜词里有个“输入过长”这条在真实场景里太常见了。很多人以为上下文窗口越大越好什么 128K、1M 的窗口恨不得把整个知识库都灌进去。但你有两个现实问题。第一个是注意力稀释。模型对中间位置的内容注意力天然会下降业界叫“lost in the middle”。你把关键信息埋在一万字中间模型大概率会漏掉它。第二是成本。按 token 计费的话多喂一万个 token 就是真金白银即使本地部署也意味着更长的首字延迟——处理前序 token 也是要时间的。我自己常用的做法是先说结论再贴长文本。把最关键的指令和约束放在上下文的最前面背景材料往后放。如果长文档确实躲不掉先用摘要替代原文或者把文档切开只选取和当前问题最相关的几个段落。那些系统工具的描述、固定的角色设定如果这轮用不到就临时从 prompt 里挪走需要的时候再拼回来。这就像后端接口的请求体能瘦身就瘦身别一股脑往上游塞。2.3 输入校验要过三关前端、后端、模型侧再来看另一个热搜词“el-input 只能输入数字 1-99”。做前端的同学天天写这种限制oninput 里面正则一挡简单。但你真把系统做大会发现输入层的校验没那么简单。一条用户输入的标准旅程是这样的先过前端格式校验长度、字符类型再过后端语义校验这个值是否合法、用户是否有权限最后才轮到模型侧的使用。AI 场景还要加第三关——上下文合法性校验也就是检查这条输入放在整个对话历史里是否合理。比如用户上一轮刚问完“今天天气怎么样”这一轮突然来一句“那它呢”代词“它”指代什么模型侧就要结合对话历史去判断甚至主动追问澄清。这个“三遍校验”的思路很多人只做了一遍于是报错的时候根本不知道该信哪边。我见过真实的坑前端限制了输入后端没限制结果有人绕过前端直接调接口后端把非法值存进数据库AI 生成出来的内容就开始胡说八道。后来查了一个下午发现根因是后端的输入校验漏了一项。C 语言里那个经典问题“scanf 输入一个字符后的值”也是同一类问题。scanf 读数字之后输入缓冲区里还留着一个换行符你紧接着用 getchar 去读字符读到的不是用户输入的字母而是那个残留的换行符。很多刚学编程的人在这里被坑得很懵。这不就和 AI 多轮对话里“上文的脏数据污染下一轮输出”一模一样吗处理办法也类似要么清空缓冲区要么在输入协议里约定好分隔符要么每次读取前做一次状态检查。设计 AI 应用的会话管理时也要记得定期清理上下文里的无效信息防止脏上下文滚雪球。3. 输出处理体验与价值的真正战场3.1 模型吐字不等于产品输出如果说输入层决定质量上限那输出层就决定用户感知。模型吐出来一段文本那只是原始产物离一个能用的产品输出还差很远。一个完整的 AI 输出链路至少包含四道工序流式渲染字一个一个蹦出来而不是等十几秒一次性出现、结构化解析把文本按 JSON、Markdown 等格式拆开、业务后处理去重、截断、敏感词过滤、格式美化、契约校验检查输出是否符合约定的结构和语义。大多数人第一次接大模型 API都是直接把返回值整个渲染到页面上。模型响应快还好模型响应慢一点用户面对一片空白十秒钟早就关页面了。这就是为什么现在所有像样的 AI 聊天产品都做流式输出——用户看到第一个字出现心理等待时间就过去了后面哪怕生成慢一点体验也是顺滑的。这一层做到位产品的基本质感就出来了。3.2 流式输出与 abort 中断一个真实的坑流式输出现在的主流实现是 SSEServer-Sent Events前端用 fetch 获取流式响应拿到一段渲染一段。伪代码大致是这样const controller new AbortController(); const response await fetch(/api/chat, { method: POST, body: JSON.stringify({ messages }), signal: controller.signal }); const reader response.body.getReader(); const decoder new TextDecoder(); while (true) { const { done, value } await reader.read(); if (done) break; const chunk decoder.decode(value, { stream: true }); renderChunk(chunk); // 把增量文本追加到界面 }注意这里有一个关键点支持“停止生成”必须配 AbortController用户点击停止按钮时调用controller.abort()请求才会被中断。而且 abort 之后不能直接撒手不管还要清理状态、释放连接、把按钮恢复原样。我在一个实际项目里踩过一个坑用户快速连续点击两次“停止”前端状态就错乱了。第二次点击时第一次请求已经 abort但流里其实还残留着最后一块 chunk 没被处理完。这个残留块又被渲染上去了等于用户看到截断之后又补了一段非常诡异。排查到最后发现问题不出在后端而是前端 abort 后没有进入“哨兵状态”——没有忽略 abort 后仍然到达的增量数据。正确的做法是abort 之后置一个标志位后面所有 chunk 直接丢弃前端状态机明确区分“生成中、已停止、已完成”三个状态停止按钮只在“生成中”可点其余时间全部置灰。这个状态机写清楚这类问题就根治了。3.3 格式化输出从 qdebug 到 JSON Schema热搜词里有一条“qdebug 怎么输出不带双引号”。写过 Qt 的人应该知道qDebug()直接输出QString时默认会带引号想去掉就得用qDebug().noquote()或者转成const char*再打印。这背后的本质是“输出契约”——调试输出也有一套格式约定不按约定来日志看起来就费劲。大模型领域的“输出契约”更严格。你让模型返回一段 JSON它不是每次都能给到合法 JSON偶尔会多一句解释、多一个逗号、多一层嵌套。靠“请返回 JSON”这种自然语言约束稳定性是不够的。业界现在普遍的做法是结构化输出直接给模型一个 JSON Schema 约束比如{ type: object, properties: { sentiment: { type: string, enum: [positive, neutral, negative] }, reply: { type: string }, confidence: { type: number, minimum: 0, maximum: 1 } }, required: [sentiment, reply, confidence] }模型在推理阶段就按这个 Schema 生成从源头保证输出合法。然后你的业务代码只需要做一次轻量的字段校验不用再写一坨正则和异常兜底去解析乱七八糟的文本。我用这个方案之后AI 接口的解析报错率基本降到接近零。同样的逻辑也适用于写科研论文、做专利辅助这些场景。你给模型一个清晰的模板——摘要几段、背景几段、方法怎么描述、结论怎么收——输出质量会比你只丢一句“帮我写一段”强得多。输入输出侧的契约约定得越细结果就越可控。3.4 硬件世界的输出发了不等于完事再回到嵌入式那个例子。STM32 输出 PWM 信号电平本身是对的但如果你不看负载端的电气特性接上去可能驱动不了电机或者信号振铃严重、干扰其他电路。所以工程师要在输出引脚上串驱动电阻调整上升沿的斜率匹配线缆和负载的阻抗。这就是输出层的“后处理”。对应到 AI 产品里就是不同客户端对输出的要求不一样。网页端要 HTML 片段小程序端要纯文本或者特定的 JSONApp 端可能只要增量 diff。同一个模型输出要在服务端做不同裁剪、包装、适配再分发给各端。很多做 AI 应用的团队忽略这一层直接拿模型原生输出往客户端怼结果就是各端样式错乱、流量浪费、展示不一致。所以你看输出处理这一层无论在软件还是硬件世界都不是“发出去就完事”而是要根据接收方的需求做格式转换、信号整形和协议匹配。做 AI 产品谁把输出这层打磨得越细谁的用户体验就越扎实。4. 中间层不是黑盒大模型部署、微调与工程落地4.1 选模型就是给处理层选“口味”处理层是模型本身这一层的选择决定了一个 AI 系统的“口味”——同样的输入不同模型给出的输出风格、深度、严谨程度不一样。现在开源模型的生态已经非常丰富了从几 B 的小模型到几百 B 的大模型都有。选模型不是单纯比参数量而是看你的场景对推理质量、延迟、成本的要求。跑在用户手机上的端侧模型要的是小、快、省内存跑在服务器上的对话模型要的是上下文长、逻辑强、指令遵循好。热词里的“android app 集成 ai 大模型 gguf”这就是端侧部署的典型思路——把模型量化成 GGUF 格式用 llama.cpp 这类推理框架跑在手机上输入输出全走本机数据不出设备。GGUF 这个格式你只需要知道一件事它是把模型权重量化压缩之后的一种封装好处是体积小、加载快、CPU 也能跑。选什么量化等级很关键Q4 和 Q8 的推理质量有差距但体积和速度差距也明显。我自己的经验是能上 Q8 就上 Q8显存不够再退 Q5/Q4。永远不要为了省几百 MB 内存牺牲输出质量用户感知到的回答变笨比内存高点更伤产品口碑。4.2 本地部署显存、带宽、管线三件事热词里的“rx6750gre 训练大模型”挺有意思。这张卡 12GB 显存拿来训大模型确实紧张但拿来推理和微调是够用的。这里给个大概参考7B 模型 Q4 量化大约需要 5-6GB 显存推理时还要留出 KV Cache 的空间所以 12GB 显卡跑 7B Q4 是可以的跑 13B 就比较吃力了。如果你打算本地部署搞实验优先考虑 7B 级别的模型别一上来就盯 70B那不是消费级显卡能干的事。部署时有个容易被忽略的关键点显存带宽比显存大小更影响首字延迟。同一个模型跑在高带宽显卡上可能 1 秒出字跑在低带宽显卡上要 3 秒。因为生成是逐 token 进行的每个 token 都要把整个模型权重读一遍带宽越高每秒能生成的 token 数越多。所以买卡的时候别只看显存容量带宽和显存要一起看。如果你用的推理框架是 llama.cpp 或 Ollama对话模板、采样参数、上下文长度这些都要单独调。同一份 GGUF 在不同框架里的默认行为可能不一致输入预处理和输出采样也各有差异。这就要求部署人员对“输入管线、模型推理、输出管线”三层分别做测试而不是一把梭。我习惯的做法是先固定一组输入对比不同框架的输出结果是否一致再微调采样参数参数有一处不对输出的风格就飘。4.3 微调改的不是性格是输入输出的映射很多人对微调有误解以为微调能“教会”模型新知识。实际上微调更多是在调整模型在特定输入分布下的输出偏好。LoRA低秩适配是目前最主流的微调方式因为它是增量训练——不修改原始权重只训练一小部分额外的适配参数训练完合并成一个低成本的微调模型。你用一批高质量的 “输入-期望输出” 样本去跑 LoRA本质上就是在让模型记住这一类输入应该给出这一类输出。所以微调的效果高度依赖你的数据质量。几十条精心构造的数据效果经常好过几千条粗制滥造的数据。数据要覆盖边界情况比如用户输入带噪声、带口语、带重复期望输出仍然要稳定。这里说一个实操口径做微调之前先确认“提示词RAG”这套输入层方案已经用满了。输入层的工程手段改造成本低、见效快微调属于动处理层的手术成本高、周期长、效果也难以预测。只有输入层的方案确实到天花板了再考虑微调。这就像写代码先优化调用方的参数再考虑改底层函数。5. 常见问题与排查技巧输入输出环节的实战避坑5.1 三层定位排查表我这些年排障的一个心得遇到问题先别急着怀疑模型先定位问题发生在哪一层。这里给一张我在团队内部常用的排查表故障现象可能的层排查手段输出内容完全离题输入层/处理层检查 prompt 是否明确、上下文是否被污染、是否调错模型输出内容散乱不可用输出层检查是否有结构化约束、后处理是否做了解析和校验回答很慢、首字延迟高处理层/输出层检查显存带宽、模型体积、是否流式输出、前端是否积压任务输入提交报错输入层检查前端格式校验、后端语义校验、编码与序列化生成的 JSON 经常解析失败输出层启用 JSON Schema 结构化输出而不是靠自然语言请求这张表看上去简单但真的能省很多排查时间。因为团队里最常见的错误就是明明问题出在输入层的前端校验大家却在那里调半天模型的 temperature。5.2 流式中断、乱码与超时的排障实录流式输出最常见的三类问题我一一列一下。第一类是中断。现象是回答到一半突然停了前端没有报错。排查方向先看网络层是不是代理或网关超时把连接掐了再看后端是不是输出端到停止词或最大 token 上限最后看前端是不是 abort 逻辑误触发了。我遇到过一个奇葩案例后端代码里把“。”误配成了停止词导致模型每说到句号就停看起来像答了一半。第二类是乱码。现象是流式输出时出现“锟斤拷”之类的内容。这通常不是模型的问题而是编码不一致。前端用 UTF-8 解码后端按 GBK 发了字节流或者 SSE 传输时没有显式声明charsetutf-8。排查时先固定编码协议前端后端统一 UTF-8大概率就好了。这和 C 语言里输入字符后残留换行符导致的乱码问题本质是一类——协议两侧对数据格式的理解不一致。第三类是超时。现象是请求发出去很久没有响应。这要分成两种情况一是模型真的在慢慢推理这时应尽快接流式输出让用户看到进展二是服务端没把长连接配置好代理层的 idle timeout 设得太短把还没传完的流掐了。排查时直接看网关日志关注超时时间戳前后有没有 connection closed 的记录。5.3 输入无效类报错的统一排查逻辑热搜词里还有一堆输入无效报错“输入的文件夹似乎无效”“无法定位程序输入点 GetSystemTime 于动态链接库”“输入的序列号无效”等等。这些报错看着千奇百怪但排查逻辑是共通的——都是输入层校验没通过。第一步先验证输入本身是否真的有效。比如共享文件夹添加网络位置报“输入的文件夹似乎无效”你先手动 ping 一下服务器地址确认路径可访问序列号无效先确认它是不是当前版本的、有没有被使用过。第二步再检查输入没有被程序篡改。比如动态链接库的入口点报错多半是 DLL 版本不匹配程序从一个地方加载了函数另一个地方加载了库。这就像你把一个函数的入参搞错了版本自然对不上。第三步最后才怀疑“程序有 bug”。大多数这类报错的根因都在输入侧或者输入与程序版本的匹配侧程序本身只是忠实地告诉你“这东西不对”。把这个逻辑迁移到 AI 场景如果你的应用报“用户输入内容无效”先确认用户输入有没有被前端过滤掉关键字符、有没有被编码过程破坏、有没有超出长度限制。三关校验做全了这类问题的出现频率会直线下降。5.4 一条贯穿始终的排查心法最后分享一条我自己的排查心法也是这几年做 AI 应用踩坑踩出来的习惯遇到 AI 输出不对先问输入对不对输入没问题再看输出校验够不够输出也正常才轮到调模型和参数。这个顺序不能乱。很多人一上来就调 temperature把 model 换大甚至想重训结果问题根本不在那。有一次团队反馈“模型越用越笨”聊天机器人回答越来越迟钝。我查了一圈发现原因是把聊天记录全量堆进上下文几千轮没清理上下文里垃圾信息太多模型每轮都要处理一大堆无关 token注意力被稀释反应自然变慢。解决方式很简单——加一个会话压缩策略旧消息摘要化超长会话自动截断。处理层一动没动问题就解决了。这种“先两侧、后中间”的排查顺序其实就是三层架构思维最实际的价值。框架看着虚用起来是能实打实省时间的。你看那热搜词里“输入过长”“格式化输出”“流式输出”“abort”“本地部署”“微调实战”翻来覆去无非是三层架构里的这一侧或那一侧。把“输入层、处理层、输出层”这九个字刻进脑子里以后再看到任何 AI 新花样你都能一眼看出它到底在动哪一层也就能更快判断它值不值得跟、怎么落地。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑