项目一:企业级 AI 对话与多模型网关
以流式对话、会话上下文、模型适配、工具权限和可观测性为主线,整理一个可验证的 AI 应用前端项目。
项目一:企业级 AI 对话与多模型网关
这是对资料中“智能对话引擎”方向的工程化改写。资料列出了 Vue 3、TypeScript、Node.js、会话存储、流式打字机、多模型封装、Docker 和 CI/CD;本文补上生产项目必须回答的权限、协议、失败和验证边界。不要把接通某个模型 API 或课程演示直接表述成生产上线。
1. 背景与目标
企业内部常见的问答助手需要同时处理知识问答、文档检索、表单填充和工具调用。简单的聊天页面很快会遇到几个问题:
- 不同模型的鉴权、请求格式、流式协议和错误码不同,页面被供应商 API 绑死。
- 多轮会话需要保存消息、截断上下文和恢复未完成请求;刷新或断线后不能重复执行写操作。
- 模型输出不稳定,可能返回半截 JSON、越权参数或包含敏感信息,不能直接渲染 HTML 或执行代码。
- 生成过程较长,用户需要看到增量状态,同时可以取消、重试和切换模型。
项目目标可以定义为:浏览器只负责交互和受控渲染,服务端负责身份、模型密钥、会话和工具权限;用户能够创建/恢复会话,获得可重连的流式回答,并在模型或依赖不可用时得到可理解的降级结果。
成功标准不要写“像 ChatGPT 一样”,而应写成可测行为:
| 目标 | 可验证定义 |
|---|---|
| 流式体验 | 首个可展示事件的延迟、增量事件间隔、完成/取消状态可观测 |
| 会话一致性 | 同一 conversationId 的消息顺序稳定,刷新后可恢复,重复提交有幂等结果 |
| 模型可替换 | 页面只依赖统一事件协议,增加供应商不修改会话 UI |
| 安全 | 浏览器看不到模型密钥;工具按用户/租户重新授权;敏感操作需确认 |
| 可运营 | 每次请求可由 requestId/traceId 关联模型、工具、存储和渲染结果 |
2. 推荐架构
浏览器 UI
| HTTPS / SSE(只接收受控事件)
v
BFF / AI Gateway
|- 会话、租户和权限校验
|- 限流、配额、幂等和审计
|- 模型适配器(OpenAI-compatible / 其他供应商)
|- Agent 编排器(检索、工具、人工确认)
|- 事件规范化与脱敏
|\
| \-- 会话/消息存储、向量检索、缓存
\---- 模型供应商、内部业务 API、MCP 工具
前端可以使用 Vue 3 + TypeScript,服务端可以使用 Node.js/Express 或已有 BFF。技术选型不是项目亮点本身,亮点在于边界:
- 页面层:消息列表、输入框、流式状态、重试/取消、可访问性和国际化。
- 客户端会话层:维护当前请求状态和已确认消息,不保存密钥;对流事件去重并处理断线。
- 网关层:验证用户和租户,生成
requestId,调用适配器,统一错误和流事件。 - 模型适配层:把供应商请求/响应映射到内部协议;不向上层泄漏供应商 SDK 类型。
- 工具与检索层:工具白名单、参数 schema、资源权限、超时和审计;检索结果带来源和租户过滤。
- 发布层:版本化配置、灰度、监控、回滚和密钥轮换。
更完整的概念、Agent 循环和生成式 UI 约束见 AI 前端工程。
3. 统一模型适配协议
页面不应根据 provider === 'xxx' 分支渲染。网关先把不同模型转换成内部请求和事件:
type ChatRequest = {
conversationId: string
requestId: string
messages: Array<{ role: 'user' | 'assistant' | 'tool'; content: string }>
model?: string
tools?: string[]
signal?: AbortSignal
}
type ChatEventBase = {
version: 1
requestId: string
// sequence is global within one request, so every event can be replayed safely.
sequence: number
}
type ChatEvent =
| (ChatEventBase & { type: 'message-start'; messageId: string })
| (ChatEventBase & { type: 'text-delta'; messageId: string; text: string })
| (ChatEventBase & { type: 'tool-status'; toolCallId: string; tool: string; status: 'started' | 'finished' | 'failed' })
| (ChatEventBase & { type: 'usage'; inputTokens?: number; outputTokens?: number })
| (ChatEventBase & { type: 'done'; finishReason: string })
| (ChatEventBase & { type: 'error'; code: string; retryable: boolean })
interface ModelAdapter {
readonly name: string
chat(input: ChatRequest): AsyncIterable<ChatEvent>
}
适配器必须明确:
- 请求超时和取消如何传递到供应商 SDK。
- 供应商的流事件如何映射为单调递增的
sequence。 - 限流、余额不足、内容安全拒绝和网络错误分别映射为什么内部错误码。
- 哪些错误可重试,重试是否会重复计费或重复执行工具。
- 供应商返回的 usage、模型版本和 region 是否进入审计事件。
新增模型时先写适配器契约测试,用固定响应 fixture 验证文本、工具调用、半截事件和错误映射,再接入真实账号。不要在浏览器直接调用模型供应商,也不要把 Authorization 记录到日志。
4. 流式协议与前端状态机
SSE 适合服务端单向推送文本和状态;需要双向低延迟或已有长连接基础设施时可考虑 WebSocket。无论采用哪种通道,都要定义事件版本、请求 ID 和顺序;同一请求包含多个 assistant 消息或并行工具调用时,还必须用 messageId/toolCallId 关联增量和状态。
idle
-> submitting
-> streaming(message-start/text-delta/tool-status)
-> completed
-> cancelled
-> failed(retryable | terminal)
前端状态应按 requestId 隔离,不能只用一个 isLoading:
- 发送前生成客户端幂等键,服务端返回同一个
requestId。 - 收到旧请求的事件时丢弃;
sequence重复时去重,乱序时暂存或终止并重新拉取。 - 文本增量按
messageId追加到对应消息草稿,工具状态按toolCallId更新对应调用;done后才把草稿标记为最终消息。 AbortController取消本地读取并通知网关;取消不应被展示为业务失败。- 断线重连携带最后确认的
sequence或游标;服务端必须保证重放幂等。 - 收到不完整的结构化 UI 片段时先缓冲并校验,不能渲染半个组件树。
打字机效果只是展示层,不应通过 setInterval 伪造模型速度;真实事件到达后按帧批量更新,避免每个 token 触发一次昂贵渲染。长消息和代码块要做增量 Markdown 解析或完成后解析,并限制单条消息大小。
5. 会话、上下文与检索
会话存储至少包含:
conversationId, tenantId, ownerId, messageId, role,
content/reference, model, createdAt, parentMessageId, status
上下文组装要有预算:先保留系统策略和最近消息,再按任务需要加入摘要、检索片段和工具结果。历史消息不能无限拼接;超出 token 或字节上限时应返回可解释的截断标记。用户删除会话时要同步清理向量索引、缓存和审计中可删除的内容。
RAG 场景中,检索结果应带 sourceId、租户和权限元数据,服务端在召回和最终拼接时都做过滤。回答展示来源片段,不能把向量相似度当成事实准确率。评估集至少包含无答案、权限隔离、重复文档和提示注入样例。
6. Agent 与工具安全
Agent 是一个有上限的执行循环,不是把模型输出当作命令执行:
意图 -> 规划 -> schema 校验 -> 权限检查 -> 工具执行
-> 观察结果 -> 再规划/回答
每个工具注册以下元数据:名称、版本、读/写类型、参数 schema、资源范围、超时、幂等性和审计字段。执行前重新从服务端上下文取得 tenantId、用户角色和资源归属,不信任模型传入的用户 ID。
- 查询工具和写入工具分开;写入、发送、删除、付款等不可逆动作先生成待确认命令。
- 外部文档和工具返回值都视为不可信数据,提示中出现“忽略系统指令”不能改变策略。
- 设置最大循环步数、并发数、响应大小和 token 预算;只对幂等操作退避重试。
- 工具失败要区分用户可重试、需要人工处理和永久拒绝,避免模型无限重试。
- 工具调用日志只记录必要参数摘要,令牌、Cookie、完整用户输入和业务机密必须脱敏。
MCP 只解决能力描述和发现,不自动授予权限;MCP Client 仍应放在受控服务端,并用同一套工具注册/审计边界。
7. 鉴权、跨域和部署
资料中的 Authorization、JWT、Cookie、CORS 和反向代理可以串成一条可解释链路:
- 登录接口在服务端验证凭据,签发短期访问凭据和可轮换的刷新机制。
- 浏览器调用同源 BFF,或在明确的 CORS 白名单下携带凭据;
Access-Control-Allow-Credentials时不能使用*。 - 令牌优先放在受控 Cookie(
Secure、HttpOnly、合适的SameSite)或由 BFF 代持,避免暴露给模型和第三方脚本。 - Cookie 身份的状态修改请求仍需校验 CSRF token 和
Origin/Referer;CORS 不是 CSRF 防护本身。 - 每个会话、检索和工具请求都在服务端做最终授权;前端隐藏按钮不是安全边界。
- Nginx/CDN 负责 TLS、反向代理和静态资源缓存,BFF 负责动态鉴权和流连接,不把长流错误缓存。
Node 服务可以通过 Docker 固定运行时和依赖,CI 依次执行类型检查、Lint、契约测试、镜像扫描和部署前 smoke test。密钥来自运行时 secret,不写进镜像、前端产物或仓库。
相关原理:HTTP 版本、HTTPS 安全、JWT 鉴权、BFF 与 API 契约。
8. 观测、限流与成本控制
每个请求都生成 traceId,关联:网关接收、模型首 token、每个工具、检索、存储、流结束和前端渲染。建议分开记录以下指标,而不是只记“成功率”:
| 维度 | 指标示例 | 用途 |
|---|---|---|
| 体验 | 首事件延迟、首 token 延迟、总耗时、断线率 | 判断网络、模型还是前端渲染瓶颈 |
| 质量 | 任务完成率、工具选择/参数校验失败率、人工接管率 | 评估 Agent 是否完成目标 |
| 可靠性 | 5xx、超时、取消、重连成功率、重复请求率 | 发现依赖或协议故障 |
| 资源 | token、请求大小、模型配额、缓存命中率 | 控制成本和容量 |
| 安全 | 越权拦截、敏感操作确认、提示注入命中 | 检查治理边界 |
限流可以按租户、用户、模型和工具分别设置;队列满时返回明确的稍后重试事件。模型切换要有策略记录,不能为了降低延迟悄悄改变回答质量。缓存默认只用于明确可复用且不含用户私密内容的结果;个性化回答应禁用共享缓存。若确需缓存,键至少要包含租户/用户权限范围、规范化输入与上下文摘要、模型和提示配置版本、工具版本、区域及功能开关,不能只拼接模型名。
9. 失败边界与降级
| 故障 | 用户表现 | 处理 |
|---|---|---|
| 模型超时/限流 | 流中断或长时间无事件 | 发送可重试错误,保留已生成文本;必要时切换到明确配置的备用模型 |
| SSE 断线 | 消息停在半截 | 根据游标重连或重新获取最终消息;写工具不能盲目重放 |
| 工具权限不足 | 模型提出越权操作 | 拒绝执行并记录原因,提供只读替代或人工流程 |
| 返回非法 UI/JSON | 页面不能解析 | 丢弃该片段,显示普通文本/表单降级,不执行任意代码 |
| 会话存储不可用 | 无法恢复历史 | 当前请求可短暂继续,但明确标记未保存并禁止重复提交 |
| 浏览器不支持流 | 无增量展示 | 回退到普通请求或轮询,保留取消和超时 |
降级逻辑应有自动化测试和开关;“模型不可用时继续无限重试”会把单点故障放大成全站故障。
10. 面试表达与追问
三分钟版本
我们要把一个多轮问答能力接入内部业务,但供应商协议、权限和流式失败都不稳定。
我负责会话 UI、流事件协议和 BFF 的模型适配/观测边界,页面只消费统一 ChatEvent,密钥和工具授权留在服务端。
核心难点是断线与重复执行:用 requestId + sequence 去重,AbortController 取消,写工具需要幂等键和人工确认。
上线前用供应商 fixture、权限/提示注入样例和网络故障测试验证,线上按首 token、完成率、错误率和成本分层观察;模型失败时降级到普通问答或人工处理。
具体收益只引用我能从发布和监控记录证明的版本、样本和分位数。
高频追问
| 追问 | 回答要点 | 深入阅读 |
|---|---|---|
| 为什么不让前端直接调用模型? | 密钥、配额、租户授权、审计和供应商切换都需要服务端;前端直连还会放大 CORS 和泄露风险 | AI 前端工程 |
| SSE 和 WebSocket 怎么选? | SSE 是单向流且重连语义简单;双向协作或已有长连接基础设施才考虑 WebSocket;两者都要有 requestId、顺序、心跳和取消 | 跨端交互与发布治理 |
| 如何避免流式响应覆盖新请求? | 按 requestId 隔离状态,旧请求事件丢弃;提交新请求前取消旧请求或明确允许并行 | React Hooks |
| Agent 如何防提示注入? | 外部内容不可信、工具白名单、服务端授权、敏感操作确认、步数/超时上限;不是靠提示词一句话解决 | AI 前端工程 |
| 多模型适配的价值是什么? | 稳定内部契约、隔离供应商变化,统一错误/usage/流事件;代价是要维护能力矩阵和契约测试 | 前端项目架构设计与选型 |
| 如何证明项目有效? | 给出固定任务集、版本、设备/网络、指标分位数和失败样本;不能直接复述资料中的收益数字 | 技术误区与验证 |
11. 交付与验证清单
- 模型适配器有统一类型、fixture 和契约测试。
- 流事件包含版本、
requestId、序号和结束/错误语义。 - 取消、断线、乱序、重复事件和半截 JSON 有测试。
- 会话、检索和工具都做租户/资源授权,写操作有确认与幂等。
- 令牌、模型密钥和敏感输入不会进入浏览器日志、Source Map 或监控事件。
- 首 token、完成率、重试、成本和安全拦截能按版本追踪。
- CI 有类型/Lint/契约测试/镜像扫描,发布有灰度、开关和回滚。
来源:高级前端亮点项目.pdf 第 18-19 页(智能对话引擎的前后端、会话、部署和多模型封装);高频真题解析与9月考点预测上.pdf 第 3-9 页(CORS、Cookie、缓存、Token/JWT);结合项目知识库已有 AI 前端工程 进行安全、观测和验证补充。