这两年我做AI应用最大的感受是:AI不是在替代写代码这件事,而是在重塑整个Web应用从立项到上线的节奏。以前做一个带智能能力的Web产品,先得攒团队、定方案、跑模型,折腾几个月才能看到东西;现在一个人用AI辅助开发,加上现成的大模型API,几天就能把核心流程跑通,剩下的时间基本都花在打磨体验、压成本和填坑上。
这篇文章把我自己落地“AI + Web应用”这套东西的完整思路梳理了一遍:需求怎么拆、技术栈怎么选、模型怎么接、细节怎么抠、上线后怎么测怎么稳。适合正在做AI应用开发的工程师,也适合打算用AI重构现有Web产品的技术负责人。内容不绕弯子,都是可以直接抄作业的东西。
1. AI进入Web应用的三种形态,以及我为什么这么拆分
很多人一提“用AI打造Web应用”,第一反应是“接个大模型API不就行了”。真做过几个项目之后你会发现,AI在Web应用里其实以三种完全不同的形态存在,每种形态对应的工程复杂度、技术选型和坑都不一样,混在一起谈只会越谈越乱。
1.1 AI辅助开发:把大模型当成结对编程搭子
这是门槛最低也最容易被低估的一种形态。说白了就是开发阶段用AI编程工具干活,比如GitHub Copilot、Cursor、通义灵码这类,让它帮你写业务代码、补测试、查bug。产品本身可能没引入任何AI能力,但研发效率翻了一倍不止。
我现在的习惯是:接需求后先用AI把数据模型和接口定义拉出来,再让它按模块生成初版代码,最后我自己做代码审查和逻辑修正。这里有个关键认知——AI写代码不是“一键生成”,而是“任务拆解 + 上下文管理”。你把需求拆得越细、给到的约束越明确,它写出来的东西就越靠谱。我见过很多人在这一步偷懒,甩一句“帮我写个登录功能”,得到的代码基本是拼拼凑凑、不可维护的垃圾。
实操中有个经验分享给你:把项目里沉淀下来的架构规范、命名规范、代码风格写成一个AGENTS.md或CODING_GUIDE.md放在仓库根目录,每次让AI干活之前先让它读一遍这个文件。它生成的代码会明显更贴合项目风格,Review成本直线下降。
1.2 AI能力内嵌:让业务长出一个“会思考”的接口
这是目前绝大多数AI Web应用的核心形态:把大模型的能力封装成服务接口,嵌入到业务流程里。典型场景包括智能客服、文档问答、内容生成、知识库检索、Agent工作流等。这个形态下,AI不是一个飘在云端的“灵魂”,它就是后端的一个微服务,接收请求、处理上下文、调用工具、返回结果。
用生活化类比理解:传统Web应用的接口是“收到请求—查数据库—返回数据”的确定性流程,而AI接口是“收到请求—理解意图—可能调用多个外部工具—生成回复”的非确定性流程。你没法用传统接口的思维去设计它,必须引入Agent、RAG、Function Calling这类技术,把“模型会猜”这件事用工程手段约束住。
我在实际项目里见过太多翻车案例,都是把大模型直接裸接进生产环境:用户问一句不在知识范围内的话,模型就一本正经地编答案;上下文一长,响应慢得用户直接关页面;模型情绪化输出,好好的客服变“吐槽机器人”。这些问题不是模型本身的错,是缺少工程化封装。
1.3 AI原生基础设施:决定Demo和产品分界线的隐形赛场
第三种形态最容易被忽略,但它恰恰是“高品质”三个字的关键。当你的AI应用需要真正面向用户稳定运行时,你得配套一套AI原生基础设施,包括向量数据库、模型网关、Prompt版本管理、评测集、可观测性、成本配额、限流降级策略等。
我自己的判断标准很简单:能跑通的Demo只证明“模型可以做到”,而能上线的基础设施才决定“产品能不能持续做到”。举个最实际的例子,你的应用用了A家大模型,上线第三天A家API抽风,限流、超时一起来,如果没有模型网关做多路切换和优雅降级,用户就会在评论区骂你三天。这事我经历过一次之后就老实了。
所以下面整篇文章,我会沿着这条主线展开:先聊技术选型,再讲核心细节,然后跑一个完整实操记录,最后把测试、稳定性、成本这些“产品化必修课”讲透。
2. 技术选型怎么定——从框架到模型的决策链路
技术选型是AI Web应用最容易纠结的环节。网上信息鱼龙混杂,今天吹这个框架,明天吹那个模型,你要没有清晰的决策链路,很容易在这上面耗掉大量时间。
2.1 后端框架:Flask、Django5、Spring AI怎么选
从热搜词里能看到,大家关心的后端框架集中在Flask、Django5、Spring AI这几个上。它们没有绝对的优劣,选型完全取决于你的团队情况和项目规模。
| 框架 | 适用场景 | AI生态 | 学习曲线 | 性能与扩展 |
|---|---|---|---|---|
| Flask | 轻量API、快速原型、小微应用 | 中等,可通过LangChain等补齐 | 低,简单直接 | 灵活,适合单服务快速部署 |
| FastAPI | 中大型API、对流式交互敏感的应用 | 较好,原生支持异步,配合SSE方便 | 中等 | 高,异步天然适合流式输出 |
| Django5 | 企业级Web应用、后台管理复杂 | 中等,偏重业务系统集成 | 中高 | 高,自带ORM/Admin/Auth |
| Spring AI | Java技术栈、现有Spring生态 | 较好,官方支持主流模型 | 高 | 高,适合Java团队 |
如果你做的是内部工具、原型验证,Flask够用了;如果做面向用户的AI应用,我强烈建议上FastAPI——它原生支持async/await,做SSE流式输出的时候写起来特别顺,这是AI交互场景的刚需。如果你的公司是Java技术栈,历史系统都在Spring体系里,那Spring AI是融入现有架构最平滑的方案。Django5则适合那种“AI只是其中一个模块,整体还是传统业务系统”的企业级应用,比如你要在后台管理系统里加一个智能工单分类功能。
2.2 模型接入:直接调API还是私有化部署
模型接入层的选型,本质上是在成本、效果、数据安全三者之间做取舍。
直接调用大模型API(通义千问、豆包、DeepSeek、智谱等)的优势是速度快、效果有保障、不用管GPU,适合绝大多数创新业务。私有化部署(用vLLM、Ollama等跑开源模型)的优势是数据不出内网、可控性高、长期成本低,但你需要技术团队运维模型服务、处理负载伸缩和模型更新。
我个人的实践建议是分层混用:常规场景用API快速验证,核心数据和敏感业务走私有化部署的开源模型,比如Qwen系列、DeepSeek系列。很多团队会先上API把产品跑起来,等用户量和调用量上来了,再把高频路径上的模型替换成自部署。整个过程通过模型网关做路由切换,对上层业务透明,切换成本可以压缩到一次配置发布。
2.3 引入AI基础设施:LLM网关和模型路由是必需品吗
说实话,早期我做过很多“裸奔”项目——代码里直接写死一家模型供应商的SDK,结果每次模型升级、价格变动、API出问题,都要改代码重新上线,痛苦得刻骨铭心。现在我的所有项目,不管大小,都会在应用和模型供应商之间加一层统一的LLM网关。
这层网关解决的不只是“切换模型”的问题,还承担了四件事:统一接口协议、做模型路由和灰度、配额与成本控制、缓存与降级。举个配置示例,你要让简单问题走便宜的小模型、复杂问题才调用旗舰模型,可以在网关层根据问题长度和关键词路由:
{ "routes": [ { "name": "quick", "match": { "max_tokens_usage": 500, "topics": ["greeting", "faq"] }, "model": "deepseek-chat", "temperature": 0.3 }, { "name": "default", "match": { "topics": ["*"] }, "model": "qwen-plus", "temperature": 0.5 } ], "fallback": "qwen-turbo" }模型供应商的SDK经常变,但网关的接口可以保持稳定,业务方只需要对接网关,底层模型随便换。这就把AI基础设施的复杂度收口了,业务代码干净很多。
3. 核心细节解析与实操要点——决定品质的不是模型,而是细节
很多团队做出来的AI应用,Demo演示很惊艳,一上生产就拉胯,问题往往不在模型能力,而在细节工程化没做到位。这一章我挑几个绕不开的核心细节展开讲。
3.1 上下文工程:从RAG到长文本的结构化组织
RAG是现阶段AI Web应用落地最实的技术路线,它的本质很简单:先从知识库里找到和用户问题相关的内容,再把这些内容作为参考材料交给大模型生成回答。这个链条里有四个关键环节:分块、向量化、召回、重排。
分块是最容易被忽视的环节。很多人把整个文档一股脑丢给向量模型,结果召回质量惨不忍睹。我现在的做法是根据文档结构来分块,标题、段落、表格都独立成块,块之间保留层级关系。一个经验参数:普通产品文档按200到500字一块,代码文档按函数或类分块,长表格单独处理。分块之后,每块要带着上下文元数据,比如“属于哪一章、哪个版本”,这样召回的时候可以按元数据过滤。
重排环节很多人直接跳过,但实测下来效果差异非常大。初次召回通常拿Top 20,让重排模型(比如BGE-Reranker)重新打分后只保留Top 5,生成的答案质量会有肉眼可见的提升。代价就是多一次模型调用,延迟增加几十毫秒,这笔账是划算的。
3.2 Prompt与结构化输出:让模型稳定返回可解析的数据
你做Web应用,模型输出不能只是“看起来像人话”,必须是机器可以解析的结构化数据。比如AI客服要返回“意图分类、实体抽取、回复内容、是否转人工”这些字段,少一个你的业务逻辑就跑不通。
我的做法是三步走:第一步,定义输出Schema,用Pydantic或者JSON Schema写清楚每个字段的类型、枚举和约束;第二步,把Schema和示例一起写进System Prompt,明确要求模型“只输出符合Schema的JSON,不要多余解释”;第三步,在代码里做严格校验和重试,解析失败就带着错误信息重试一次。
一个实际可用的Pydantic结构示例:
from pydantic import BaseModel, Field from typing import Literal class SupportResponse(BaseModel): intent: Literal["refund", "shipping", "product", "other"] sentiment: Literal["positive", "neutral", "negative"] should_escalate: bool = False reply: str = Field(description="回复用户的话术") confidence: float = Field(ge=0.0, le=1.0)在Prompt里带上这个Schema定义,并要求模型输出JSON之后,再用这段代码做健壮性兜底:
import json from pydantic import ValidationError def safe_parse(model_output: str) -> SupportResponse | None: # 先尝试直接解析,不满足再尝试提取```json```块 candidates = [model_output] if "```json" in model_output: candidates.append(model_output.split("```json")[1].split("```")[0]) for text in candidates: try: data = json.loads(text, strict=False) return SupportResponse(**data) except (json.JSONDecodeError, ValidationError): continue return None把模型当“初级工程师”管理就对了:给规范、给例子、给校验,而不是放任它自由发挥。高品质的AI应用,Prompt不是写在代码里的字符串,而是有版本、有评审、有回归测试的资产。
3.3 流式输出与前端体验:高品质的感知全在交互细节
用户体验上,同样一段长回答,等10秒一次性弹出来和1秒后逐字流式输出,用户的耐心和满意度是完全不同的。后者给人“它在思考”的实时感,前者只会让人觉得系统卡死了。
后端的关键技术是SSE(Server-Sent Events)。FastAPI里配合异步生成器可以轻松实现。核心逻辑是先把整个Prompt计算好,然后通过StreamingResponse把模型的增量输出逐段推给前端:
from fastapi import FastAPI from fastapi.responses import StreamingResponse import json app = FastAPI() async def event_stream(query: str): # 这里以OpenAI兼容接口为例,接入你的LLM网关 # 简化起见直接模拟增量输出 async def generate(): for chunk in ["你好", ",我是", "你的AI助手"]: yield f"data: {json.dumps({'delta': chunk}, ensure_ascii=False)}\n\n" await asyncio.sleep(0.1) return StreamingResponse(generate(), media_type="text/event-stream") @app.post("/api/chat") async def chat(request: dict): return await event_stream(request["query"])前端这边用fetch加ReadableStream逐字解析,边接收边把文本渲染到页面上。这里有三个细节必须处理到位:一是用户点击“停止生成”时要中断流,避免无效消耗;二是网络中断后要给出友好的重试提示,而不是页面卡死;三是不能让流式渲染阻塞用户滚动,要用“追加渲染”而不是“整体替换”的方式更新DOM。
我踩过最亏的坑是移动端的流式实现,一开始直接用了WebSocket全双工,后来发现SSE完全够用且实现简单得多。能用SSE的场合没必要上WebSocket,少一条连接就少一类问题。
3.4 幻觉治理与安全护栏:高品质的底线
AI应用最怕的是什么?不是答得不好,而是不懂装懂、一本正经地胡编。你做的如果是客服或知识问答类应用,一次严重幻觉就足以透支用户对整个产品的信任。
治理幻觉的核心是“给模型说我不知道的权利”。我的做法是:Prompt里明确写“如果知识库中没有相关信息,必须回答‘我暂时无法回答这个问题’,禁止编造”;同时在RAG召回环节设定相关性阈值,召回内容与问题的相似度低于阈值时,直接拦截拒绝生成。不要把这个判断交给模型自觉,要在工程链路上硬性拦截。
从产品安全角度,任何面向用户的AI应用,输入输出两侧都要挂内容安全服务,对恶意输入、诱导Prompt注入、违规生成内容做过滤。上线前要准备合规的免责声明和用户协议,明确AI生成内容仅供参考。这些看起来是“非技术工作”,但没有哪家高品质产品的AI功能敢在这上面省事。
4. 从零到一落地一个AI问答助手——完整实操记录
理论讲再多,不如跑一个完整项目。这一章我会用当前最主流的技术栈,搭建一个最小可用的AI问答助手,重点展示工程落地时的关键决策和代码细节。项目形态很简单:用户输入一个问题,应用从知识库中检索相关内容,交给大模型生成带引用的回答,前端流式展示。
4.1 需求定义与Prompt基座
动手写代码前,先定义清楚几个问题:目标用户是谁?回答范围是什么?如果知识库查不到该怎么表现?回答是否需要展示引用?是否需要多轮对话能力?这些决定了你的Prompt基座和检索策略。
我把初始的System Prompt设计为:
你是一个专业的产品知识助手,回答基于提供的知识库内容。 要求: 1. 只依据检索到的参考资料回答,不编造不存在的信息。 2. 回答结构:先给出结论,再补充说明。 3. 引用来源:回答末尾标注内容对应的参考文档编号,格式为【来源:文档名】。 4. 如果参考资料不包含答案,直接回复:“根据现有资料,我暂时无法回答这个问题,请咨询人工客服。” 5. 回答使用简体中文,语气专业简洁。这个Prompt的每一句话都有明确目的:第1条治幻觉、第2条保体验、第3条给可追溯性、第4条给兜底、第5条保证一致性。初版Prompt可能不完美,但建议先把框架立起来,后续通过评测集迭代优化,而不是一上来就追求“一版封神”。
4.2 知识库构建与检索链路
知识库用的是最常用的RAG方案:文档解析、分块、向量化、存入向量库。
我习惯用FastAPI + Qdrant + BGE-M3这套组合,原因很简单:Qdrant部署轻量(一个Docker容器搞定),BGE-M3的中文效果稳定且支持稀疏+稠密混合检索。分块逻辑对Markdown文档做了结构化处理,保持了标题层级和代码块的完整性:
from langchain.text_splitter import MarkdownHeaderTextSplitter headers_to_split_on = [ ("#", "H1"), ("##", "H2"), ("###", "H3"), ] splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on) chunks = splitter.split_text(markdown_document)每个分块落库时带上文档来源、标题路径等元数据,后面展示引用来源时直接从元数据里取,不用再反查原文档。向量化选用BGE-M3,embedding维度1024,实测中文检索效果比很多同体积模型都好。检索的时候做两级召回:先用向量检索Top 20,再用重排模型精排取Top 5,这部分之前已经讲过了,这里不再展开。
4.3 核心流程:检索增强生成(RAG)实现
核心生成逻辑集中在一个函数里:接收用户问题→检索知识库→组装上下文→调用模型流式生成。这里直接把组装上下文和调用的关键代码贴出来:
from openai import AsyncOpenAI from qdrant_client import AsyncQdrantClient client = AsyncOpenAI(base_url="你的模型网关地址", api_key="sk-xxx") qdrant = AsyncQdrantClient(url="http://localhost:6333") async def rag_answer(question: str): # 1. 召回相关文档 hits = await qdrant.search( collection_name="product_docs", query_vector=await embed(question), limit=20, ) docs = [hit.payload["content"] for hit in hits[:5]] # 2. 组装上下文 context = "\n\n".join(f"[文档{i+1}] {doc}" for i, doc in enumerate(docs)) messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": f"知识库内容:\n{context}\n\n用户问题:{question}"}, ] # 3. 流式调用模型 stream = await client.chat.completions.create( model="qwen-plus", messages=messages, temperature=0.3, stream=True, ) # 4. 逐块产出 async for chunk in stream: delta = chunk.choices[0].delta.content if delta: yield delta这里有个很容易忽略但影响很大的细节:temperature参数。知识问答场景需要事实性回答,温度必须压低,我一般设0.2到0.4;如果是创意写作场景再调高。很多人在所有场景都用同一个温度参数,结果问答场景的答案飘得没法看。
4.4 Prompt与参数调优:第一批badcase的沉淀
系统联调完成后,我做的第一件事不是写前后端对接,而是找来20个真实问题跑一遍,把效果不好的案例收集起来,逐一分析原因。第一批badcase往往能暴露三类问题:知识库缺内容、检索召回不准、Prompt指令不够明确。
比如用户问“退货周期是多久”,知识库里有“签收后7天内可申请无理由退货”,但这句话分布在文档中段,召回时排到了第8位,没进Top 5,模型就只能瞎猜。这个case直接推动了两个优化:一是把退货相关的高频问题写了单独的摘要文档,提高检索命中率;二是Prompt里追加了一句“如果问题涉及时效、金额、条件等关键信息,必须从知识库中找到明确依据才能回答”。优化的效果直接反映在评测集通过率上,我从一开始就把这些案例沉淀成了回归测试集,以后每次改Prompt或换模型都要重新跑一遍,防止“修了东墙漏了西墙”。
4.5 前端流式接入与交互体验
前端我用的Vue 3,核心逻辑是把SSE流逐段渲染到对话区。完整接入方案如下:
async function sendMessage() { const response = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ question: input.value }), }); const reader = response.body.getReader(); const decoder = new TextDecoder("utf-8"); let buffer = ""; let answer = ""; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop(); // 保留未完成的数据 for (const line of lines) { if (line.startsWith("data: ")) { const data = JSON.parse(line.slice(6)); answer += data.delta; renderAnswer(answer); } } } }体验层面的细节很琐碎但极影响观感:回答生成过程中要有光标闪烁的“正在思考”状态、用户消息和AI消息要用不同气泡区分、答案里的引用编号要可以点击跳转对应文档。这些做好了,用户才会觉得这是“专业产品”,而不是“AI玩具”。另外记得处理滚动容器的高度变化,避免回答太长时用户看不到最新内容。
5. AI应用测试、稳定性和成本控制
很多人做到上面那一步,觉得功能能跑通了,就准备上线。但我见过太多产品死于上线后的第二天:模型供应商限流、回答质量不稳定、单用户对话成本飙到几块钱。这一章讲的就是上线前的“产品化必修课”。
5.1 AI测试为什么难,怎么补
传统Web应用的测试是确定性的:输入一组参数、断言一组返回值。AI应用的输出本质上是概率性的,同样的问题模型可能给出不同说法。这让很多团队直接放弃测试,让AI应用“裸奔”上线。
我的做法是把测试拆成两个层次:第一层是确定性强的校验,如返回结果必须是合法JSON、必填字段不能缺失、流式连接不能中断、接口响应时间必须在阈值内。这些完全可以用自动化测试来保证。第二层是语义层面的校验,比如回答是否基于给定知识库、是否回避了敏感话题、语气是否专业,这类测试我用“评测集 + 人工抽检”的方式。
具体操作是:维护一个评测集,里面放50到200条典型问题,每条标注期望行为,按场景分组,覆盖常规问题、边界问题、误导性问题、恶意输入等。每次升级Prompt、切换模型或调整检索策略时,全量跑一遍评测集,把通过率变化记录下来。没有这个评测集,你的“优化”就是凭感觉,根本不知道是在进步还是倒退。
5.2 稳定性:限流、降级与重试
接第三方模型服务,就要做好它随时可能出问题的准备。超时、限流、返回非标准内容,这些是家常便饭。工程上要做三层防护:超时控制、重试策略、熔断降级。
我的惯例配置是:单次请求超时30秒(流式场景可以放宽,但要设空闲超时);连接错误重试2次,指数退避;连续失败达到阈值后触发熔断,快速失败直接返回兜底话术,同时告警通知值班人员。这里贴一套我用在网关层的重试和熔断参数,可以直接抄:
retry: max_attempts: 3 initial_backoff_ms: 500 backoff_multiplier: 2 retryable_status_codes: [429, 500, 502, 503, 504] circuit_breaker: failure_threshold: 10 cooldown_seconds: 60 fallback_message: "服务繁忙,请稍后再试"用户侧还要做兜底UI,而不是让异常堆栈直接暴露在浏览器里。一款高品质AI应用,用户看不到的地方远比看得到的地方复杂得多。
5.3 成本控制:Token的每一分钱都该花在哪
模型调用的成本结构和传统服务器成本完全不同——它不是按时间计费的,而是按Token消耗量计费的。这意味着成本优化主要围绕三个方向:减少Token浪费、优化模型选型、引入缓存。
减少Token浪费最有效的手段是控制上下文长度。很多开发喜欢把整个对话历史无脑塞给模型,结果上下文越长,费用越高、响应越慢、效果还越差。我的习惯是滑动窗口策略,只保留最近5轮对话摘要和关键背景信息。必要的时候先调用一次轻量模型做历史对话压缩,再带着压缩后的摘要去调用主模型。
模型分级路由是成本优化的大头。不是所有请求都需要旗舰模型,我实测过,问候、闲聊、简单FAQ这些问题用小参数模型(比如qwen-turbo、deepseek-chat)完全够用,只有复杂推理和长文生成才需要调用旗舰版。通过模型网关按问题特征路由,整体成本能下降40%到60%。还可以对相似请求做语义缓存,比如产品介绍这类高频问题,第一次生成后缓存结果,后续直接命中,基本零成本。
我平时看成本报表,重点盯三个指标:单次会话平均Token消耗、单次会话平均成本、以及缓存命中率。一旦发现某个入口的成本明显异常,大概率是某个场景的Prompt把不需要的上下文也塞进去了,值得排查。
6. 常见问题与排查技巧实录
最后这部分是我在不同项目里反复踩过的坑,整理成一张速查表,供你遇到问题时对照排查。
| 问题 | 常见原因 | 排查思路 | 解决方案 |
|---|---|---|---|
| 模型返回JSON解析失败 | 输出格式未约束或上下文被截断 | 查看原始输出是否被截断 | 加JSON Schema约束;提高max_tokens;解析失败自动重试 |
| 流式输出中断 | 代理超时、网络抖动、服务端异常退出 | 检查网关日志和连接状态 | 设置空闲超时;前端实现断线重连;服务端增加心跳 |
| 回答内容偏离知识库 | 检索召回不准或上下文不重要 | 检查召回结果的相关性得分 | 优化分块策略;加入重排模型;提高相关性阈值 |
| 上下文过长导致成本失控 | 未做历史消息裁剪 | 查看请求体的Token消耗明细 | 滑动窗口策略;对话摘要压缩;限制单轮输入长度 |
| 模型供应商限流(429) | QPS超过套餐配额 | 查看网关的限流日志 | 增加重试退避;多模型切换;降级到兜底话术 |
| 同样的问题回答不稳定 | 温度参数过高或Prompt缺乏约束 | 对比多次输出差异 | 降低temperature;加入few-shot示例;固定随机种子 |
| 评测集通过率忽高忽低 | 评测样本不够或标注不准 | 检查评测集质量 | 扩充样本数量;多人交叉标注;用LLM辅助打分再加人工复核 |
其中有几个问题值得单独提醒:
关于JSON解析失败,我见过最典型的场景是模型输出在长文本中间被截断,恰好断在JSON字符串中间,导致整个JSON不合法。这通常是max_tokens设得太低。如果生成内容确实较长,优先把max_tokens调大;如果不想调大,就让模型先输出结构化字段、再输出自然语言字段,确保关键数据在截断前就已经完整返回。
关于流式输出中断,常见的是用户端和服务端之间存在网关代理,代理有默认超时时间(比如Nginx默认60秒),流式响应如果长时间没有数据,连接会被强行断开。解决方法是调大代理的超时配置,或者在服务端加“心跳包”定期发送注释行让连接保持活跃。
关于评测集稳定性,每次跑评测时模型版本变化可能导致结果波动,这是正常的。关键是区分“模型本身波动”和“你的系统是否有改进”,我的做法是每个改动跑三遍评测取平均值,通过率提升5个百分点以上才算有效优化。
7. 最后一个建议
我自己现在做AI应用,最大的体会是:不要被新模型、新框架的营销节奏带着走。今天出来一个号称“吊打所有”的模型,明天又出来一个新框架,如果你每次都跟着换,你的系统永远在重构的路上,永远没有时间打磨真正的体验。反过来,把评测集、模型网关、监控告警、成本报表这套工程底座建好,以后不管是换模型、调Prompt还是加新功能,都只是配置和迭代,不需要伤筋动骨。
再分享一个小技巧:从第一天开始,就把每次模型输出的badcase沉淀下来,录成带截图和错误分析的文档。这些badcase是你最宝贵的数据资产,比任何模型都要值钱。每次迭代前翻一遍,你会发现很多问题根本不用换大模型就能解决——调整分块策略、优化Prompt、增加上下文约束,效果立竿见影。
AI还在快速变化,但工程化的方法论是稳定的。把地基打好,以不变应万变,这才是做高品质AI Web应用的正确姿势。