跳到正文
前端知识库
项目亮点

项目一:企业级 AI 对话与多模型网关

以流式对话、会话上下文、模型适配、工具权限和可观测性为主线,整理一个可验证的 AI 应用前端项目。

10 分钟AI · LLM · Agent · SSE · WebSocket · Node.js · 鉴权 · RAG · 可观测性

项目一:企业级 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>
}

适配器必须明确:

  1. 请求超时和取消如何传递到供应商 SDK。
  2. 供应商的流事件如何映射为单调递增的 sequence
  3. 限流、余额不足、内容安全拒绝和网络错误分别映射为什么内部错误码。
  4. 哪些错误可重试,重试是否会重复计费或重复执行工具。
  5. 供应商返回的 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 和反向代理可以串成一条可解释链路:

  1. 登录接口在服务端验证凭据,签发短期访问凭据和可轮换的刷新机制。
  2. 浏览器调用同源 BFF,或在明确的 CORS 白名单下携带凭据;Access-Control-Allow-Credentials 时不能使用 *
  3. 令牌优先放在受控 Cookie(SecureHttpOnly、合适的 SameSite)或由 BFF 代持,避免暴露给模型和第三方脚本。
  4. Cookie 身份的状态修改请求仍需校验 CSRF token 和 Origin/Referer;CORS 不是 CSRF 防护本身。
  5. 每个会话、检索和工具请求都在服务端做最终授权;前端隐藏按钮不是安全边界。
  6. 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 前端工程 进行安全、观测和验证补充。