核心定位:全链路项目实战 + 大厂AI前端面试题深度解析
关键产出:完整AI对话应用 + 面试知识图谱


全链路实战:构建生产级AI对话应用

开篇:从知识点到产品

前9个模块,我们逐一击破了AI前端的核心技术问题。但真正的能力不是"知道每个知识点",而是"能把它们组装成一个能跑的产品"。

这一期,我们要构建一个生产级AI对话应用,它不是Demo,不是玩具,而是一个可以作为作品集展示的全链路项目。


一、项目架构总览

┌──────────────────────────────────────────────────────────────┐
│                      AI Chat Application                      │
├──────────────────────────────────────────────────────────────┤
│                                                               │
│  ┌─────────────┐  ┌─────────────┐  ┌──────────────────────┐ │
│  │  对话界面    │  │  设置面板    │  │  Function Calling    │ │
│  │  ChatView   │  │  Settings   │  │  Tool Executor       │ │
│  └──────┬──────┘  └──────┬──────┘  └──────────┬───────────┘ │
│         │                │                     │              │
│  ┌──────┴────────────────┴─────────────────────┴───────────┐ │
│  │                     核心状态层                            │ │
│  │              ChatStore (Zustand/Pinia)                    │ │
│  └──────┬────────────────┬─────────────────────┬───────────┘ │
│         │                │                     │              │
│  ┌──────┴──────┐  ┌──────┴──────┐  ┌──────────┴───────────┐ │
│  │  SSE客户端   │  │  渲染引擎    │  │  安全中间件           │ │
│  │  (模块1)    │  │  (模块2)    │  │  (模块7)              │ │
│  └─────────────┘  └─────────────┘  └──────────────────────┘ │
│                                                               │
├──────────────────────────────────────────────────────────────┤
│  异步调度器(模块3) │ Prompt管理(模块4) │ 状态管理(模块5)      │
│  图片优化(模块8)   │ 架构模式(模块9)   │                      │
└──────────────────────────────────────────────────────────────┘

技术栈选型

层次 技术选择 来源模块
通信层 SSE客户端 + 自动重连 模块1
渲染层 流式Markdown渲染引擎 模块2
并发层 多模型调度器 模块3
Prompt层 模板管理 + 变量注入 模块4
状态层 Zustand/Pinia + CQRS 模块5
工具层 Function Calling执行器 模块6
安全层 注入检测 + 输出过滤 模块7
多模态 渐进式图片加载 模块8
架构 插件化 + 多租户 模块9

二、核心代码实现

2.1 应用入口:组装所有模块
// main.tsx - 应用入口,组装所有模块
import { SSEClient } from './modules/communication/sse-client';
import { StreamRendererEngine } from './modules/rendering/stream-renderer';
import { ConcurrencyScheduler } from './modules/async/scheduler';
import { PromptTemplateManager } from './modules/prompt/template-manager';
import { ChatStore } from './modules/state/chat-store';
import { FunctionCallExecutor } from './modules/function-calling/executor';
import { AISecurityPipeline } from './modules/security/pipeline';
import { ProgressiveImageLoader } from './modules/multimodal/progressive-loader';

class AIChatApplication {
  private sseClient: SSEClient;
  private renderer: StreamRendererEngine;
  private scheduler: ConcurrencyScheduler;
  private promptManager: PromptTemplateManager;
  private store: ChatStore;
  private toolExecutor: FunctionCallExecutor;
  private security: AISecurityPipeline;
  private imageLoader: ProgressiveImageLoader;

  constructor() {
    // 1. 初始化SSE客户端
    this.sseClient = new SSEClient({
      endpoint: '/api/chat/stream',
      reconnect: {
        maxRetries: 3,
        baseDelay: 1000,
        maxDelay: 30000,
      },
    });

    // 2. 初始化流式渲染引擎
    this.renderer = new StreamRendererEngine({
      container: document.getElementById('chat-messages')!,
      typingSpeed: 20,
      enableSyntaxHighlight: true,
      enableMathRendering: true,
    });

    // 3. 初始化并发调度器
    this.scheduler = new ConcurrencyScheduler({
      maxConcurrency: 3,
      retryConfig: { maxRetries: 2, backoffMs: 1000 },
    });

    // 4. 初始化Prompt模板管理
    this.promptManager = new PromptTemplateManager();

    // 5. 初始化状态管理
    this.store = new ChatStore();

    // 6. 初始化Function Calling执行器
    this.toolExecutor = new FunctionCallExecutor({
      tools: this.getRegisteredTools(),
      maxExecutionTime: 30000,
    });

    // 7. 初始化安全管线
    this.security = new AISecurityPipeline({
      inputFilters: [
        new PromptInjectionDetector(),
        new XSSFilter(),
        new PIIProtector(),
      ],
      outputFilters: [
        new HallucinationDetector(),
        new ContentSafetyFilter(),
      ],
    });

    // 8. 初始化图片加载器
    this.imageLoader = new ProgressiveImageLoader({
      maxSizeMB: 200,
    });

    // 9. 连接各模块的事件流
    this.wireModules();
  }

  /** 连接各模块事件流 */
  private wireModules(): void {
    // SSE → 渲染
    this.sseClient.on('token', (token) => {
      this.renderer.appendToken(token);
    });

    // SSE → 状态
    this.sseClient.on('message_complete', (message) => {
      this.store.addAssistantMessage(message);
    });

    // SSE → Function Calling
    this.sseClient.on('function_call', async (call) => {
      const result = await this.toolExecutor.execute(call);
      // 将工具执行结果发回服务端
      this.sseClient.sendToolResult(call.id, result);
    });

    // 安全管线拦截
    this.sseClient.on('raw_input', (input) => {
      const filtered = this.security.filterInput(input);
      if (filtered.blocked) {
        this.store.addSystemMessage('输入被安全策略拦截:' + filtered.reason);
        return;
      }
    });

    this.sseClient.on('raw_output', (output) => {
      const filtered = this.security.filterOutput(output);
      if (filtered.warning) {
        this.renderer.appendWarning(filtered.warning);
      }
    });
  }

  /** 发送消息:完整流程 */
  async sendMessage(content: string): Promise<void> {
    // 1. 安全检查
    const inputCheck = this.security.filterInput(content);
    if (inputCheck.blocked) {
      this.store.addSystemMessage('输入被安全策略拦截:' + inputCheck.reason);
      return;
    }

    // 2. 添加用户消息到状态
    const userMsgId = this.store.addUserMessage(content);

    // 3. 构建Prompt(模板 + 上下文)
    const prompt = this.promptManager.buildPrompt('default_chat', {
      userMessage: content,
      conversationHistory: this.store.getRecentMessages(10),
      systemInstruction: this.store.getSystemPrompt(),
    });

    // 4. 创建AI消息占位符
    const aiMsgId = this.store.addAssistantPlaceholder();

    // 5. 发起SSE请求
    try {
      await this.sseClient.connect({
        body: {
          messages: prompt.messages,
          model: this.store.getSelectedModel(),
          tools: this.toolExecutor.getToolDefinitions(),
          stream: true,
        },
      });
    } catch (error) {
      this.store.updateMessage(aiMsgId, {
        status: 'error',
        content: '生成失败,点击重试',
      });
    }
  }

  /** 获取已注册的工具 */
  private getRegisteredTools() {
    return [
      {
        type: 'function',
        function: {
          name: 'web_search',
          description: '搜索互联网获取最新信息',
          parameters: {
            type: 'object',
            properties: {
              query: { type: 'string', description: '搜索关键词' },
            },
            required: ['query'],
          },
        },
      },
      {
        type: 'function',
        function: {
          name: 'code_execute',
          description: '执行代码并返回结果',
          parameters: {
            type: 'object',
            properties: {
              language: { type: 'string', enum: ['python', 'javascript'] },
              code: { type: 'string', description: '要执行的代码' },
            },
            required: ['language', 'code'],
          },
        },
      },
      {
        type: 'function',
        function: {
          name: 'generate_image',
          description: '根据文字描述生成图片',
          parameters: {
            type: 'object',
            properties: {
              prompt: { type: 'string', description: '图片描述' },
              style: { type: 'string', enum: ['photorealistic', 'anime', 'oil-painting'] },
              size: { type: 'string', enum: ['1024x1024', '1792x1024', '1024x1792'] },
            },
            required: ['prompt'],
          },
        },
      },
    ];
  }
}
2.2 ChatStore:对话状态管理(模块5实战)
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
import { persist } from 'zustand/middleware';

interface ChatMessage {
  id: string;
  role: 'user' | 'assistant' | 'system' | 'tool';
  content: string;
  status: 'pending' | 'streaming' | 'complete' | 'error';
  model?: string;
  toolCalls?: ToolCall[];
  toolResults?: ToolResult[];
  createdAt: number;
  metadata?: {
    tokens?: { prompt: number; completion: number; total: number };
    latency?: number;
    finishReason?: string;
  };
}

interface ChatSession {
  id: string;
  title: string;
  messages: ChatMessage[];
  createdAt: number;
  updatedAt: number;
  model: string;
  systemPrompt: string;
}

interface ChatStoreState {
  // 数据
  sessions: ChatSession[];
  activeSessionId: string | null;

  // 计算属性
  activeSession: () => ChatSession | null;
  recentMessages: (count: number) => ChatMessage[];

  // 操作
  createSession: () => string;
  deleteSession: (id: string) => void;
  switchSession: (id: string) => void;

  addUserMessage: (content: string) => string;
  addAssistantPlaceholder: () => string;
  updateMessage: (id: string, updates: Partial<ChatMessage>) => void;
  appendToMessage: (id: string, token: string) => void;

  setSystemPrompt: (prompt: string) => void;
  setSelectedModel: (model: string) => void;

  // 导出
  exportSession: (id: string) => string;
}

export const useChatStore = create<ChatStoreState>()(
  persist(
    immer((set, get) => ({
      sessions: [],
      activeSessionId: null,

      activeSession: () => {
        const state = get();
        return state.sessions.find(s => s.id === state.activeSessionId) ?? null;
      },

      recentMessages: (count: number) => {
        const session = get().activeSession();
        if (!session) return [];
        return session.messages.slice(-count);
      },

      createSession: () => {
        const id = crypto.randomUUID();
        set(state => {
          state.sessions.unshift({
            id,
            title: '新对话',
            messages: [],
            createdAt: Date.now(),
            updatedAt: Date.now(),
            model: 'gpt-4',
            systemPrompt: '',
          });
          state.activeSessionId = id;
        });
        return id;
      },

      deleteSession: (id: string) => {
        set(state => {
          state.sessions = state.sessions.filter(s => s.id !== id);
          if (state.activeSessionId === id) {
            state.activeSessionId = state.sessions[0]?.id ?? null;
          }
        });
      },

      switchSession: (id: string) => {
        set(state => { state.activeSessionId = id; });
      },

      addUserMessage: (content: string) => {
        const id = crypto.randomUUID();
        set(state => {
          const session = state.sessions.find(s => s.id === state.activeSessionId);
          if (!session) return;

          session.messages.push({
            id,
            role: 'user',
            content,
            status: 'complete',
            createdAt: Date.now(),
          });

          session.updatedAt = Date.now();

          // 自动生成会话标题(取用户第一条消息的前20个字)
          if (session.messages.filter(m => m.role === 'user').length === 1) {
            session.title = content.slice(0, 20) + (content.length > 20 ? '...' : '');
          }
        });
        return id;
      },

      addAssistantPlaceholder: () => {
        const id = crypto.randomUUID();
        set(state => {
          const session = state.sessions.find(s => s.id === state.activeSessionId);
          if (!session) return;

          session.messages.push({
            id,
            role: 'assistant',
            content: '',
            status: 'streaming',
            model: session.model,
            createdAt: Date.now(),
          });
          session.updatedAt = Date.now();
        });
        return id;
      },

      updateMessage: (id: string, updates: Partial<ChatMessage>) => {
        set(state => {
          const session = state.sessions.find(s => s.id === state.activeSessionId);
          if (!session) return;

          const msg = session.messages.find(m => m.id === id);
          if (msg) {
            Object.assign(msg, updates);
          }
        });
      },

      appendToMessage: (id: string, token: string) => {
        set(state => {
          const session = state.sessions.find(s => s.id === state.activeSessionId);
          if (!session) return;

          const msg = session.messages.find(m => m.id === id);
          if (msg) {
            msg.content += token;
          }
        });
      },

      setSystemPrompt: (prompt: string) => {
        set(state => {
          const session = state.sessions.find(s => s.id === state.activeSessionId);
          if (session) session.systemPrompt = prompt;
        });
      },

      setSelectedModel: (model: string) => {
        set(state => {
          const session = state.sessions.find(s => s.id === state.activeSessionId);
          if (session) session.model = model;
        });
      },

      exportSession: (id: string) => {
        const session = get().sessions.find(s => s.id === id);
        if (!session) return '';
        return JSON.stringify(session, null, 2);
      },
    })),
    {
      name: 'ai-chat-store',
      partialize: (state) => ({
        sessions: state.sessions.map(s => ({
          ...s,
          messages: s.messages.filter(m => m.status === 'complete'),
        })),
        activeSessionId: state.activeSessionId,
      }),
    }
  )
);

interface ToolCall {
  id: string;
  name: string;
  arguments: string;
}

interface ToolResult {
  toolCallId: string;
  result: string;
  isError: boolean;
}
2.3 安全管线:集成所有防御模块
class AISecurityPipeline {
  private inputFilters: SecurityFilter[];
  private outputFilters: SecurityFilter[];

  constructor(config: {
    inputFilters: SecurityFilter[];
    outputFilters: SecurityFilter[];
  }) {
    this.inputFilters = config.inputFilters;
    this.outputFilters = config.outputFilters;
  }

  /** 过滤用户输入 */
  filterInput(input: string): SecurityResult {
    const result: SecurityResult = { blocked: false, filtered: input, warnings: [], reason: '' };

    for (const filter of this.inputFilters) {
      const filterResult = filter.check(input);
      if (filterResult.blocked) {
        result.blocked = true;
        result.reason = filterResult.reason;
        return result;
      }
      if (filterResult.filtered !== input) {
        result.filtered = filterResult.filtered;
      }
      if (filterResult.warning) {
        result.warnings.push(filterResult.warning);
      }
    }

    return result;
  }

  /** 过滤AI输出 */
  filterOutput(output: string): SecurityResult {
    const result: SecurityResult = { blocked: false, filtered: output, warnings: [], reason: '' };

    for (const filter of this.outputFilters) {
      const filterResult = filter.check(output);
      if (filterResult.warning) {
        result.warnings.push(filterResult.warning);
      }
      if (filterResult.filtered !== output) {
        result.filtered = filterResult.filtered;
      }
    }

    return result;
  }
}

interface SecurityFilter {
  check(input: string): FilterResult;
}

interface FilterResult {
  blocked: boolean;
  filtered: string;
  reason: string;
  warning?: string;
}

interface SecurityResult {
  blocked: boolean;
  filtered: string;
  warnings: string[];
  reason: string;
}

三、项目验收标准

□ 通信层
  □ SSE流式通信,支持自动重连
  □ 请求取消(用户切换对话时中断前一次请求)

□ 渲染层
  □ 打字机效果,60fps流畅渲染
  □ Markdown渲染:标题/列表/代码块/表格/数学公式
  □ 代码块语法高亮 + 复制按钮
  □ 防御性渲染:不闭合代码块自动修复

□ 状态层
  □ 多会话管理:创建/切换/删除
  □ 长对话虚拟滚动
  □ 本地持久化(localStorage)
  □ 会话导出(JSON/Markdown)

□ 功能层
  □ Function Calling:web_search / code_execute / generate_image
  □ 工具调用过程可视化("正在搜索..." / "正在执行代码...")
  □ 图片生成后渐进式加载展示

□ 安全层
  □ Prompt Injection检测与拦截
  □ XSS过滤
  □ PII脱敏(手机号/身份证号/邮箱)
  □ 幻觉检测 + 警告标记

□ 性能层
  □ TTFT < 1秒
  □ 骨架屏加载状态
  □ 乐观更新(消息发送/收藏)
  □ 智能建议(对话结束后推荐下一步操作)

□ 体验层
  □ 响应式布局(桌面/平板/手机)
  □ 深色/浅色主题切换
  □ 快捷键支持(Ctrl+Enter发送等)
  □ 生成中的停止按钮
  □ 重新生成按钮

实践任务

任务:基于以上架构和代码,构建完整的AI对话应用,满足上述所有验收标准。

提交物

  1. 完整的源代码仓库(含README)
  2. 在线Demo(部署到Vercel/Netlify)
  3. 架构设计文档(含模块关系图)
  4. 性能测试报告(Lighthouse + 自定义指标)

10.2 大厂AI前端面试题深度解析

开篇:面试不是背诵,是理解

回到一切的起点——那张同程旅行前端AI方向面试题。我们现在已经有了9个模块的深度积累,是时候把每一道面试题都打穿了。

面试题不是用来背答案的,是用来展示思考深度的。同一道题,初级工程师背模板,高级工程师讲架构,架构师讲取舍。


一面:技术基础(6题深度解析)


题目1:SSE和WebSocket的区别,为什么AI聊天场景通常选择SSE?

初级回答:SSE是单向的,WebSocket是双向的。AI聊天只需要服务器推送,所以选SSE。

高级回答

维度 SSE WebSocket
通信方向 服务端→客户端(单向) 双向
协议 基于HTTP 独立协议(ws://)
断线重连 浏览器自动重连 + Last-Event-ID 需手动实现
消息格式 纯文本 二进制/文本
代理/CDN 天然兼容 需要特殊配置
连接开销 复用HTTP连接 需TCP+TLS握手
浏览器支持 原生EventSource 原生

AI聊天选SSE的根本原因

  1. 请求-响应模式:AI聊天的本质是"用户发一个问题,模型流式返回答案",天然是请求-响应模式,不需要服务端主动推送能力
  2. 基础设施兼容性:SSE基于HTTP,可以无缝通过CDN、Nginx、负载均衡器,WebSocket需要特殊配置(Upgrade头、超时保活)
  3. 断线恢复:SSE的Last-Event-ID让断线后可以从断点续传,WebSocket断线后需要重新建立连接+重新协商状态
  4. 成本:SSE复用HTTP连接,一个请求一个响应;WebSocket需要维护长连接的心跳,服务端资源消耗更大
  5. ** simplicity**:不需要额外的连接管理、心跳、状态同步代码

何时选WebSocket:多用户实时协作(如AI画板多人同时编辑)、AI Agent需要主动推送(如后台Agent完成长时间任务后主动通知用户)


题目2:处理AI长文本流时,前端如何保证渲染性能?

核心策略

  1. 增量渲染:每次只处理新增的token,不重新渲染整个消息
  2. DOM差异化更新:跟踪当前正在渲染的DOM节点,只append新内容
  3. Markdown分块解析:不等到完整Markdown再解析,而是逐块解析渲染
  4. 代码块延迟高亮:代码块正在生成时使用纯文本,生成完毕后一次性高亮
  5. 虚拟滚动:长对话场景下,只渲染可视区域的消息
  6. requestAnimationFrame节流:token到达频率可能远超16ms,用rAF合并渲染
  7. OffscreenCanvas:图片解码放到Worker中,不阻塞主线程

关键代码模式

// 高频token → rAF合并 → 低频DOM更新
let pendingTokens: string[] = [];
let rafId: number | null = null;

function onToken(token: string) {
  pendingTokens.push(token);

  if (!rafId) {
    rafId = requestAnimationFrame(() => {
      const combined = pendingTokens.join('');
      pendingTokens = [];
      rafId = null;
      renderer.append(combined);
    });
  }
}

题目3:Promise.all和Promise.allSettled的区别,调用多个AI模型接口时选哪个?
维度 Promise.all Promise.allSettled
失败处理 一个失败,全部失败 每个独立,互不影响
返回值 成功值的数组 {status, value/reason}数组
短路行为 有(第一个reject就短路) 无(等所有完成)

多模型调用场景的选型

  • 选Promise.allSettled:并行调用3个模型生成回答,展示所有结果让用户选择。任何一个模型失败不应影响其他模型的结果展示
  • 选Promise.all:并行调用多个RAG检索接口,所有结果都需要合并后才有效。任何一个检索失败,合并结果就不完整,此时快速失败比等待更好

实际场景的混合策略

// 场景:并行调用3个模型,至少需要1个成功
async function callMultipleModels(
  prompts: string[],
  models: string[]
): Promise<AIMessage[]> {
  const results = await Promise.allSettled(
    models.map(model => callModel(model, prompts))
  );

  const successful = results
    .filter((r): r is PromiseFulfilledResult<AIMessage> => r.status === 'fulfilled')
    .map(r => r.value);

  if (successful.length === 0) {
    throw new Error('所有模型调用均失败');
  }

  return successful;
}

题目4:对Prompt Engineering的理解,前端在其中扮演什么角色?

Prompt Engineering的本质:通过精心设计输入指令,引导LLM产生期望输出的技术。不是"写提示词",而是"设计人与AI的交互协议"。

前端在Prompt Engineering中的独特角色

  1. 上下文组装者:前端负责将对话历史、系统指令、工具定义、用户输入组装成完整的Prompt。这是Prompt Engineering的"工程化"部分
  2. 模板管理者:System Prompt模板的版本管理、A/B测试、动态注入——这些都是前端可以做的
  3. 反馈闭环:用户对AI回答的"重新生成"“点赞/踩”"修改提示词"等操作,都是Prompt优化的反馈信号
  4. 可视化编辑器:让非技术用户也能通过GUI编辑Prompt模板,是前端的独有能力
  5. 安全守门人:Prompt Injection的检测和防御,前端是第一道防线

代码示例

// 前端组装完整Prompt
function buildChatPrompt(context: {
  systemInstruction: string;
  conversationHistory: ChatMessage[];
  userMessage: string;
  tools?: ToolDefinition[];
  ragContext?: string;
}): ChatCompletionMessage[] {
  const messages: ChatCompletionMessage[] = [];

  // 1. 系统指令
  let systemContent = context.systemInstruction;
  if (context.ragContext) {
    systemContent += `\n\n参考知识:\n${context.ragContext}`;
  }
  messages.push({ role: 'system', content: systemContent });

  // 2. 对话历史
  for (const msg of context.conversationHistory) {
    messages.push({ role: msg.role, content: msg.content });
  }

  // 3. 当前用户消息
  messages.push({ role: 'user', content: context.userMessage });

  return messages;
}

题目5:手写带立即执行选项的防抖函数
function debounce<T extends (...args: any[]) => any>(
  fn: T,
  delay: number,
  options: { leading?: boolean } = {}
): T & { cancel: () => void; flush: () => void } {
  const { leading = false } = options;

  let timer: ReturnType<typeof setTimeout> | null = null;
  let lastCallArgs: any[] | null = null;

  function debounced(...args: any[]) {
    lastCallArgs = args;

    // 立即执行模式:第一次调用时立刻执行
    if (leading && timer === null) {
      fn(...args);
    }

    // 清除上一次的定时器
    if (timer !== null) {
      clearTimeout(timer);
    }

    // 设置新的定时器
    timer = setTimeout(() => {
      timer = null;

      // 非立即执行模式:延迟结束后才执行
      if (!leading && lastCallArgs !== null) {
        fn(...lastCallArgs);
      }

      lastCallArgs = null;
    }, delay);
  }

  // 取消防抖
  debounced.cancel = () => {
    if (timer !== null) {
      clearTimeout(timer);
      timer = null;
    }
    lastCallArgs = null;
  };

  // 立即执行待处理的调用
  debounced.flush = () => {
    if (timer !== null) {
      clearTimeout(timer);
      timer = null;
      if (lastCallArgs !== null) {
        fn(...lastCallArgs);
        lastCallArgs = null;
      }
    }
  };

  return debounced as T & { cancel: () => void; flush: () => void };
}

// 使用场景:AI聊天输入框
// leading模式:用户第一次按键时立即触发"正在输入"状态
// delay:500ms内没有新输入才发送"停止输入"事件
const handleInput = debounce(
  (value: string) => {
    sendTypingStatus('stopped');
  },
  500,
  { leading: true }
);

二面:深入原理与项目(7题深度解析)


题目1:React/Vue中如何优雅地管理AI长对话的状态?

核心挑战

  • 消息量大(几百条)→ 渲染性能
  • 流式更新(每秒数十次token)→ 状态更新频率
  • 多会话切换 → 状态隔离
  • 本地持久化 → 读写性能

React方案(Zustand + Immer)

// 使用Immer的不可变更新,避免深拷贝开销
// 使用Zustand的selector精确订阅,避免不必要的重渲染
const useMessageList = () => useChatStore(state => state.activeSession()?.messages);
const useLastMessage = () => useChatStore(state => {
  const msgs = state.activeSession()?.messages;
  return msgs?.[msgs.length - 1];
});

Vue方案(Pinia + shallowRef)

// 使用shallowRef避免深层响应式追踪
// 只有消息列表的引用变化才触发更新
const messages = shallowRef<ChatMessage[]>([]);

// 添加新消息时替换整个数组
function addMessage(msg: ChatMessage) {
  messages.value = [...messages.value, msg];
}

虚拟滚动

// 只渲染可视区域的消息,DOM节点数恒定
import { useVirtualList } from '@vueuse/core';

const { list, containerProps, wrapperProps } = useVirtualList(
  messages,
  { itemHeight: 80, overscan: 5 }
);

题目2:前端如何处理LLM的Function Calling流程?

完整流程

1. 用户发送消息
     ↓
2. 前端将消息 + 工具定义发给LLM
     ↓
3. LLM决定调用工具(返回function_call)
     ↓
4. 前端解析function_call,提取工具名和参数
     ↓
5. 前端执行工具(本地函数/API调用)
     ↓
6. 前端将工具结果发回LLM(tool message)
     ↓
7. LLM基于工具结果生成最终回答
     ↓
8. 前端渲染最终回答

前端的核心职责:步骤4-6是前端独有的——LLM本身不能执行代码、不能调API,它只是"决定要调什么",真正的执行在前端/后端。

UI设计关键:用户需要知道"AI正在使用工具",否则AI突然暂停再回答会让用户困惑。


题目3:RAG在前端侧可以做哪些优化?

前端侧的RAG优化(不是后端的向量检索优化,是前端能做的事):

  1. 查询预处理:用户输入的查询先经过前端处理——去除无关词、提取关键实体、纠错——再发给后端检索
  2. 上下文窗口管理:RAG返回的检索结果可能很长,前端需要裁剪到模型上下文窗口内,优先保留相关性最高的片段
  3. 检索结果可视化:展示AI回答的引用来源,让用户知道"AI是基于哪些资料回答的"
  4. 增量检索:用户追问时,前端可以在新查询中带上之前的检索结果ID,避免重复检索
  5. 缓存:相似查询的检索结果可以前端缓存,减少后端压力
  6. 混合检索策略:前端可以同时发起关键词检索和语义检索,合并结果后排序

题目4:实现类似ChatGPT的"打字机"效果,确保不同网络环境下平滑

关键挑战

  • 网络好:token到达频率>60fps → 需要合并
  • 网络差:token到达频率<1fps → 需要缓存后匀速播放
  • 网络抖动:时快时慢 → 需要自适应节奏

解决方案

class AdaptiveTypingEngine {
  private buffer: string[] = [];
  private isPlaying = false;
  private baseSpeed = 30; // 基础速度:30ms/字
  private lastTokenTime = 0;
  private tokenInterval = 0; // token到达间隔

  /** 收到新token */
  onToken(token: string): void {
    const now = Date.now();
    this.tokenInterval = now - this.lastTokenTime;
    this.lastTokenTime = now;

    this.buffer.push(token);

    if (!this.isPlaying) {
      this.startPlaying();
    }

    // 自适应调整播放速度
    this.adaptSpeed();
  }

  /** 自适应速度 */
  private adaptSpeed(): void {
    if (this.tokenInterval < 20) {
      // token来得太快,加快播放速度以追上
      this.baseSpeed = Math.max(10, this.baseSpeed - 5);
    } else if (this.tokenInterval > 500) {
      // token来得太慢,放慢播放速度以节省buffer
      this.baseSpeed = Math.min(60, this.baseSpeed + 5);
    } else {
      // 正常节奏,逐步回归默认速度
      this.baseSpeed += (30 - this.baseSpeed) * 0.1;
    }
  }

  /** 播放buffer中的内容 */
  private async startPlaying(): Promise<void> {
    this.isPlaying = true;

    while (this.buffer.length > 0) {
      const token = this.buffer.shift()!;
      this.renderToken(token);
      await this.delay(this.baseSpeed);
    }

    this.isPlaying = false;
  }

  private renderToken(token: string): void {
    // 直接追加到当前渲染节点
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

题目5:AI应用中如何优化前端的大图加载和交互?

(此题已在模块8详细解答,此处给出面试精简版)

三层优化

  1. 加载优化:三级渐进式加载(缩略图→低质量→全尺寸)+ IntersectionObserver懒加载
  2. 渲染优化:OffscreenCanvas + WebWorker解码 + GPU加速(transform/will-change)
  3. 内存优化:LRU缓存 + ImageBitmap.close()主动释放 + 不可见区域自动回收

题目6:前端如何预防Prompt Injection攻击?

(此题已在模块7详细解答,此处给出面试精简版)

纵深防御四层

  1. 输入检测:规则引擎检测已知注入模式(忽略指令、角色覆盖、分隔符注入)
  2. 输入净化:用户输入包裹分隔符 + 转义特殊标记
  3. 输出过滤:AI输出不直接渲染HTML,检测是否泄露系统指令
  4. 最小权限:Function Calling的参数严格校验,工具执行结果脱敏

题目7:项目中如何衡量AI前端页面的性能指标?

AI场景特有的性能指标

指标 定义 目标
TTFT Time to First Token < 1秒
TPS Tokens Per Second(渲染速率) > 30 tokens/s
TTLC Time to Last Character 取决于回答长度
首次可交互时间 用户发送后多久可以操作界面 < 100ms
流式渲染帧率 打字机效果的FPS ≥ 60fps
消息列表滚动帧率 长对话滚动FPS ≥ 60fps
内存峰值 同时展示N条消息时的内存 < 200MB

通用Web指标:LCP、FID、CLS仍然适用,但AI场景中TTFT是最核心的差异化指标。


三面:架构综合(5题深度解析)


题目1:设计企业级AI Agent平台前端架构,核心模块有哪些?

(此题已在模块9详细解答,此处给出面试框架版)

5大核心模块 + 1个基础设施层

  1. 对话引擎:SSE通信 + 流式渲染 + 状态管理,复用模块1-5技术栈
  2. 工具市场:Agent能力插件化注册、权限控制、使用统计
  3. 工作流编排:DAG可视化编辑器(React Flow/Vue Flow),节点类型:Agent/Tool/Condition/Parallel
  4. 知识库:文档上传→切片→向量化→检索测试,前端侧做查询预处理和结果可视化
  5. 监控面板:Token用量、成本、延迟、成功率、错误热力图

基础设施层:CQRS状态管理 + 插件体系 + 多租户隔离 + 安全纵深

关键洞察:Agent平台的前端本质是"能力编排层"——不是功能堆叠,而是让各种能力可以灵活组合。


题目2:AI时代,前端工程师的核心竞争力会发生怎样的变化?

我的判断

变化中的不变:前端工程师的核心价值始终是"用户体验"。但AI时代"体验"的定义变了——从"交互流畅"变成"智能可信"。

能力迁移

传统前端核心竞争力 AI前端核心竞争力
CSS/布局/动画 流式渲染/Markdown引擎
REST API调用 SSE/WebSocket + Function Calling
状态管理 长对话状态 + AI输出不确定性管理
性能优化(首屏/渲染) 性能优化(TTFT/流式渲染/大图)
组件库/设计系统 Prompt模板系统/安全中间件
单页应用架构 Agent平台架构(插件化/多租户)

新增的核心竞争力

  1. AI输出质量控制:不是调模型参数,而是在前端层做防御性渲染、幻觉检测、Schema校验
  2. 人机协作设计:设计"人→AI→人"的工作流,而不只是"人→系统"的交互流
  3. 安全意识:Prompt Injection是AI前端独有的安全威胁
  4. 产品思维:前端工程师更接近用户,需要理解AI的能力边界,设计合理的用户预期

不会变的核心竞争力:工程化能力(架构、性能、可维护性)和用户同理心。


题目3:如何解决AI输出的不确定性对前端UI稳定性的挑战?

(此题已在模块9.2详细解答,此处给出面试精简版)

核心策略

  1. 防御性渲染:主渲染器 + 纯文本降级,任何输出都能显示
  2. 自动修复:不闭合的Markdown/HTML/代码块自动补全
  3. 内容裁剪:超长输出折叠展示
  4. Schema校验:Function Calling输出用Zod严格校验
  5. 优雅降级:错误页面有重试,不是白屏

题目4:过去一年遇到的最难的AI前端技术问题及解决方案

这道题没有标准答案,但可以按以下框架组织你的故事

1. 问题描述(具体场景 + 具体指标)
   "在实现ChatGPT风格的流式渲染时,当回答包含Markdown代码块时,
    代码块闭合前的短暂期间会导致整个消息重新渲染,造成页面闪烁。"

2. 根因分析(深入到原理层面)
   "根因是Markdown解析器在流式输入时无法确定代码块是否闭合,
    每次新token到达都可能导致整个AST重建。"

3. 解决方案(多方案对比 + 最终选择 + 权衡)
   "方案A:延迟解析(等代码块闭合后再渲染)→ 牺牲实时性
    方案B:增量解析(只重新解析最后一个未闭合的块)→ 实现复杂
    方案C:混合策略(文本增量渲染 + 代码块延迟高亮)→ 最终选择"

4. 效果验证(量化指标)
   "渲染帧率从32fps提升到58fps,用户感知卡顿率从15%降到2%"

题目5:从前端技术角度,如何提高AI产品的用户留存?

(此题已在模块9.3详细解答,此处给出面试精简版)

6大维度

  1. 首屏体验:TTFT<1秒、骨架屏、预检请求
  2. 交互流畅度:乐观更新、预测性渲染、智能建议
  3. 性能可感知:分阶段进度反馈、渐进式展示
  4. 个性化:偏好学习、风格适配
  5. 错误容忍:友好错误页面、一键重试
  6. 持续引导:新手引导、功能发现、成就感设计

核心洞察:AI产品的留存本质上取决于"信任"——前端是构建信任的第一触点。


面试知识图谱

将所有面试题按知识域组织,形成可检索的知识图谱:

AI前端面试知识图谱
│
├── 通信层
│   ├── SSE vs WebSocket
│   ├── SSE断线重连机制
│   └── HTTP/2 Server Push
│
├── 渲染层
│   ├── 流式Markdown渲染
│   ├── 打字机效果(自适应节奏)
│   ├── 代码块语法高亮
│   ├── 虚拟滚动
│   └── 防御性渲染
│
├── 并发层
│   ├── Promise.all vs allSettled
│   ├── 多模型调度器
│   └── AbortController取消请求
│
├── Prompt层
│   ├── Prompt Engineering理解
│   ├── 前端在Prompt中的角色
│   ├── 模板管理
│   └── Prompt Injection防御
│
├── 状态层
│   ├── 长对话状态管理
│   ├── CQRS模式
│   ├── 本地持久化
│   └── 多会话管理
│
├── 工具层
│   ├── Function Calling流程
│   ├── 工具执行器
│   ├── RAG前端优化
│   └── Schema校验
│
├── 安全层
│   ├── Prompt Injection防御
│   ├── XSS过滤
│   ├── PII脱敏
│   └── 幻觉检测
│
├── 多模态层
│   ├── 渐进式图片加载
│   ├── 大图渲染优化
│   └── 图片对比交互
│
├── 架构层
│   ├── Agent平台架构
│   ├── 插件体系
│   ├── 多租户隔离
│   └── AI输出不确定性管理
│
└── 产品层
    ├── 性能指标(TTFT/TPS)
    ├── 留存优化
    ├── 前端工程师竞争力
    └── 用户体验设计

至此,《前端AI工程化实战指南》全部10个模块、26期内容已完整落地。

从SSE通信协议到Agent平台架构,从流式渲染引擎到面试知识图谱——这不是一本"知识点合集",而是一条从"会用AI的前端"到"能造AI前端"的完整进阶路径。

学完本专栏,你应该能:

  1. 从零搭建一个生产级AI对话应用
  2. 设计企业级AI Agent平台的前端架构
  3. 应对大厂AI前端方向的任何面试题
  4. 在AI时代定义前端工程师的核心竞争力

个人名片

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐