← 返回题库
AI Agent·模拟面试

面试模拟题库(AI Agent 全栈 · 两周冲刺)

项目深挖、Python、AI/LLM 原理、AI 工程落地、后端数据库、系统设计、行为面试、性能优化与高并发,逐题追问式答案。

项目深挖PythonLLM系统设计高并发

面试模拟题库(AI Agent 全栈方向 · 两周冲刺版)

用法:每题先自己答一遍(出声说),再看参考答案。被问住/答错/卡壳的题标记 ⚠️,当晚回补。 目录:A 项目深挖 → B Python → C AI/LLM 原理 → D AI 工程 → E 后端/数据库 → F 系统设计 → G 前端保底 → H 行为面试 → I 性能优化与高并发(高频必考)J 前端工程化架构(微前端 / 自研组件库)


A. 项目深挖(必考,占面试 50%+)

A1. 介绍下你主导的 AI 小灵项目

答题框架(STAR 压缩版)

  1. 背景:钱大妈是"日清模式"生鲜(当日到货当日清、不卖隔夜菜),门店每天要产出今日鲜文案、晚市清货倒计时、社群内容等高频营销素材,人工创作效率低、质量不稳定
  2. 方案:从 0 到 1 主导构建多 Agent 内容生产平台——用户提交需求(brief),平台编排"研究→撰写→审核→发布"四个角色 Agent 协作完成,审核不合格自动回写重写,发布前人工确认
  3. 我的角色:全栈主导(前端三端 + B 端后台 + Python 后端编排引擎),负责架构决策
  4. 结果:覆盖 29 个生鲜场景,内容生产周期 4h→30min(↓87.5%),1000+ 门店并发,日均 5w+ 任务

A2. 为什么用 LangGraph,而不是 LangChain Agent 或自研状态机?

答案要点

  • 项目有 29 种场景,但图的节点集合是固定的(supervisor/research/write/review/publish/review_escalate/video_gen 等);每个场景只是在已知节点上声明一条 pipeline 序列(如"直播脚本:write→review→publish→人工确认")。LangGraph 把流程建模成显式状态图(节点 + 边),29 种场景 = 29 条配置而非 29 套代码——流程可预测、可调试、可回放
  • LangChain Agent 是循环式"思考→行动"结构,路径由模型自由发挥、不可控;我们的场景虽多,但每条流程都是预先声明、按序执行的,不是模型临场决定路径——不适合"错不得、要留痕"的内容生产
  • 自研状态机要自己处理 checkpoint、中断恢复、并发,LangGraph 原生支持(Checkpointer + interrupt),省掉大量基建
  • 加分话术:"我选型时对比过:场景有 29 个,但图节点是固定的,每个场景只是节点序列的声明组合。所以核心判断是用状态图把'流程编排'和'节点实现'解耦——加场景只加配置、零改图;而 LangChain Agent 的循环结构适合路径不定的自由任务,不适合我们这种要留痕、要审批的强编排场景"

A3. 为什么不用 Coze / Dify 等低代码平台,而选择自研?(试过 Dify)

答题框架:先肯定(不是没对比,是真试过)→ 再讲 4-5 个具体原因(按项目特性排序)→ 收束结论(不是平台不好,是不匹配这个场景)。

答案要点

  • 开场:"我们实际试过 Dify,还搭了 Demo 跑流程,最后放弃自研,核心原因是这个项目的定位和平台的定位不匹配。"
  • 原因 1:编排粒度不够——我们的核心是"审核循环回写(≤3 轮)+ 条件分支 + 超限转人工",Dify 的工作流偏节点拼装,对"循环回写 + 动态分流 + 中断恢复"这类控制流支持弱,硬要做会很别扭
  • 原因 2:黑盒不可观测——项目要求 append-only 事件日志、任务全链路可回放("模型可见必可重建"),平台内部状态不可见、不可导出,满足不了"可观测性即产品"的诉求
  • 原因 3:HITL 审批难定制——发布前人工确认、审批通过后中断恢复(进程重启也能续跑)这种能力,平台不开放底层,我们接不进去
  • 原因 4:数据与密钥合规——门店业务数据、MCP 密钥要私有化不出内网(Fernet 加密落库),平台托管模式有数据合规风险,私有化部署版本成本又高
  • 原因 5:深度集成——要接自有三端(Web/App/小程序)+ B 端 + 企业知识库 + 自有 MCP 工具,平台 API 相对封闭,集成成本反而高于自研
  • 收束话术:"Coze 偏 C 端 bot 搭建、Dify 偏通用工作流,我们是'内容生产引擎 + 强编排 + 强留痕',这个差异化需求正好落在平台的薄弱区,自研反而更快。"

A4. Supervisor 动态分流是怎么设计的?"新场景零改图"指什么?

答案要点

  • 平台注册了任务类型注册表(TaskType):每种类型声明 target(入口)、roles(角色序列)、pipeline(节点执行序列)
  • Supervisor 节点拿到用户选择的 target 后,查注册表 → 解析出对应 pipeline → 通用路由按序执行
  • 零改图:图本身只有 supervisor + 通用节点,新增场景只需在注册表加一条配置(如"直播脚本:write→review→publish"),不用改图代码、不用重新部署图
  • 运行时真相源是数据库表,B 端配置即时生效

工程落地(项目实例)

  • TaskType 定义(frozen dataclass):name / type_label / targets(frozenset) / roles(tuple) / pipeline(tuple) / role_tools / knowledge
  • 注册期真防御__post_init__ 校验 pipeline 节点 ∈ 已知图节点集合(_KNOWN_GRAPH_NODES),引用了未注册节点直接 ValueError——防"空流程/悬空节点"在注册期就暴露,而不是运行时炸
  • Supervisor 节点用 resolve_type(target) 遍历注册表匹配 target → 返回 TaskType → 通用路由按 pipeline 顺序执行;查不到 target 直接拒绝(防未知输入)
  • 三档内置类型:content(研究→撰写→审核→发布)direct_answer(写→审→发)video_short(写→视频生成→审→发)SEED_TARGETS 提供 target 中文名兜底
  • 新增场景 = seed_data/scenario_seed 里加一条 dict(name/type_label/targets/roles/pipeline)→ 落库 → 运行时生效,零改图

A5. 审核循环 ≤3 轮怎么实现的?为什么是 3 轮?超限后怎么办?

答案要点

  • 图的条件边:review 节点输出 score,条件边判断——passed → 走 publish;未过且轮次 < 3 → 回写 write 重写;未过且轮次 ≥ 3 → 走 review_escalate(转人工)
  • 轮次存在 state(PipelineState)里,每回写 +1
  • 为什么 3 轮:经验值——多数问题 1-2 轮能修完,3 轮以上大概率是需求理解偏差,再让模型重写边际收益低,转人工更合理(避免死循环 + 避免无限烧 token)
  • 超限:转人工审核(HITL),人工看稿 + 修改意见,人工确认后放行

工程落地(项目实例)

  • 审核节点返回 JSON {"score": 0-100, "comments": "修改意见", "passed": bool}通过 Pydantic/JSON 解析强约束格式,非法输出走规则降级(不崩溃)
  • 条件边在 LangGraph 用 add_conditional_edges:passed → publish / 未过且轮次<3 → write / 超限 → review_escalate
  • 轮次存在 PipelineState(TypedDict)的 revision 字段,write 节点每次读取判断剩余次数
  • 审核节点同时做引用校验:核对正文 来源N 序号是否在素材范围内、被引内容与素材是否一致——这是防幻觉落到代码层的校验逻辑
  • 审核意见(comments)会随回写一并传给 write 节点,让重写有针对性(不是盲改)

审核循环的图结构图解

flowchart TD
    Start(["任务开始"]) --> Research["research 节点<br/>检索素材"]
    Research --> Write["write 节点<br/>生成 / 修改草稿"]
    Write --> Review["review 节点<br/>审核质量"]
    Review --> Judge{"审核结论?<br/>(读 State.revision)"}

    Judge -->|"passed = true"| Publish["publish 节点<br/>发布输出"]
    Judge -->|"未通过 且 revision < 3"| Inc["revision += 1"]
    Inc --> Write
    Judge -->|"未通过 且 revision >= 3"| Escalate["review_escalate 节点<br/>转人工(HITL)"]

    Publish --> End(["结束"])
    Escalate --> End

    style Judge fill:#ffe6cc,stroke:#d79b00
    style Escalate fill:#f8cecc,stroke:#b85450
    style Publish fill:#d5e8d4,stroke:#82b366

读图要点:这是 add_conditional_edges 的典型用法——条件边读 State.revision 决定流向。 为什么是 3 轮:经验值,兼顾"给模型足够机会自我修正"与"避免无限循环烧 token / 延迟失控"。 ⚠️ 实现细节(易被追问):条件边必须显式声明 default 分支或兜底到 END,否则当条件都未命中时图会报"找不到下一节点"——这是 LangGraph 实战的高频坑。 超限后不是直接失败,而是降级到人工审核,保证任务最终有结果——这是"生产可用"与"玩具 Demo"的分界线。

A6. HITL 人工确认是怎么做的?interrupt 后状态怎么恢复?

答案要点

  • 发布节点执行前调用 LangGraph 的 interrupt 机制,图暂停,生成一个待审批记录(含草稿内容)
  • 前端审批页展示待审批项,用户点"通过/拒绝"
  • 通过 → 恢复执行(resume),调用发布工具;拒绝 → 走降级或结束
  • 关键点:interrupt + checkpointer 配合,图状态已持久化,进程重启也能恢复;这就是选 LangGraph 的重要理由

工程落地(项目实例)

  • 审批 APIPOST /api/v1/approvals/{id}/approve|reject——审批对象含草稿内容 + 任务 ID,前端审批页拉取待审批列表
  • 恢复链路:approve → 调图接口 resume(带着审批结果继续执行剩余节点 → 发布工具);reject → 更新任务状态为"已拒绝"并写事件,不再继续发布
  • checkpoint 双后端:memory(MemorySaver)dev 兜底 / MySQL(PyMySQLSaver,异步装配 AIOMySQLSaver)——异步 saver 需要运行中的事件循环,所以在 FastAPI lifespan 里装配(attach_async_checkpointer)再重编译图,否则直接 ainvoke 会 NotImplementedError(踩过的坑)
  • 高风险动作统一走 interrupt:发布确认是 HITL 的第一落地场景,后续工具调用审批(MCP autoApprove 豁免)复用同一机制

A7. MCP 密钥 Fernet 加密,密钥存在哪?怎么轮换?

答案要点

  • 架构:opaque at rest / plaintext at use——数据库里存 Fernet 密文,运行时解密注入给 MCP 客户端
  • 主密钥 CRED_KEY 来自环境变量(.env),不进数据库
  • 轮换:换 CRED_KEY + 全量重加密存量密钥(提供迁移脚本);或者引入 KMS(云上)做密钥托管
  • 加分话术:"这是把安全边界从'运维配环境变量'下沉到'应用层加密落库',密钥不落明文、不依赖运维手工操作"

工程落地(项目实例)

  • 加密security/crypto.py 用 Fernet 对称加密 + mask_value(脱敏展示);主密钥 CRED_KEY 来自 .env(SecretStr)
  • 落库mcp_server_config 表存密文;load_all_view() 返回脱敏视图给 B 端列表(不泄露密文),get_decrypted_env() 运行时解密
  • 注入McpDispatcher 调用远端工具前解密并注入子进程环境变量;兼容旧的 {{ENV:VAR}} 引用机制(向后兼容)
  • 信任边界enabled=False 时运行时不可调用(配置了也不生效);auto_approve 白名单中的工具免审批直通
  • 12 个单测覆盖:加解密往返、mask 不泄露原文、解密注入正确性

A8. append-only 事件日志怎么保证?并发写怎么办?

答案要点

  • 事件日志只有 INSERT 没有 UPDATE/DELETE,每条事件带 stage/ts/任务 ID,满足"模型可见必可重建"
  • 并发写:MySQL 后端用连接池(多连接借还)。append-only 的并发安全并不靠"单连接串行化"(连接池本身就是多连接并行),而是靠三条:① 每条事件是一个独立的原子 INSERT——没有读改写、没有跨语句长事务,天然免锁;② 表上建唯一约束 / 主键防止重复写入;③ 连接池保证每条连接同一时刻只被一个使用者持有,避免单连接被多线程并发复用导致的协议错乱(这属于"连接复用安全",与"写入串行化"是两个不同层面的问题)。内存后端则用线程安全存储。
  • 事件配对:agent/request 先于 agent/response,靠事件类型枚举 + 时间戳,前端按 stage 分组渲染

工程落地(项目实例)

  • 事件类型枚举(六域):task/agent/tool/system/review/hitl,各有 request/response/success/error 变体——用一个枚举模块统一,注册新事件不散落
  • 表结构event_log 表(任务 ID / stage / 类型 / 载荷 JSON / 时间戳),只 INSERT
  • Tracker 封装tracker.emit(task_id, event_type, payload) 统一入口,节点不求"怎么记"只调 emit——记录与业务解耦
  • 事件流 API + SSEGET /api/v1/tasks/{id}/events 流式返回,前端按 stage 分组渲染成时间线 + 阶段产物卡片
  • 防并发:MySQL 后端用连接池,写操作借连接执行;内存后端线程锁 + 线程安全存储
  • 测试:专门测"agent/request 先于 response、stage 配对、事件完整可重建"

A9. 1000+ 门店并发怎么支撑的?瓶颈在哪?

答案要点

  • 业务存储用 MySQL 连接池(多连接真并行);图的 checkpoint 用异步 saver(AIOMySQLSaver,事件循环内装配)
  • 任务创建做了幂等(防重复提交);LLM 瞬时错误做重试 + 指数退避
  • 瓶颈坦诚说:真实模型模式下每条流水线要多次 LLM 调用,单任务耗时长(分钟级),吞吐瓶颈在 LLM API 的 QPS 和成本,不在应用层
  • 缓解:模型档位(fast/strong)区分轻重任务、mock 降级、异步化任务队列(已规划)

工程落地(项目实例)

  • 连接池MySQLConnectionPool(lambda 建连 + max_size)——业务 store(事件日志/任务表/各配置表)共用,FastAPI 同步端点跑在线程池、每条请求借一条连接,单连接同一时刻只有一个使用者(修复了单连接并发协议错乱 InterfaceError 的坑)
  • 幂等IdempotencyStore(内存/MySQL 双实现)——任务创建带幂等键,重复请求返回首次结果
  • 重试降级:LLM 客户端 retries + 指数退避,仅对可重试错误(连接/超时/429/5xx)重试,4xx 不重试;节点层可埋 system/retry 事件;工具不可用降级 Mock(搜索/视频)
  • 异步装配:图执行走 ainvoke,MySQL checkpoint 用 AIOMySQLSaver(生命周期内装配后重编译)

A10. 如果任务量翻 10 倍,架构哪里先挂?怎么改?

答案要点(系统设计题,主动讲思路):

  1. LLM API 限流/成本 → 多模型路由降级 + 缓存相似请求结果
  2. 同步执行链路阻塞 → 全量异步化(任务队列:入队即返回,worker 消费)
  3. 数据库压力 → 读写分离、事件日志归档分表、Redis 缓存热点配置
  4. 前端事件推送 → SSE 连接数优化(网关聚合、按门店分组推送)

A11. 你在这个项目里最大的技术挑战是什么?

建议选题(选一个讲透):

  • 动态分流零改图(架构设计能力)
  • HITL 中断恢复(对 LangGraph 的理解深度)
  • 密钥安全落库(安全设计)
  • 三端一套代码基座(前端工程能力) 讲法:难点是什么 → 我做了什么决策 → 为什么这么选 → 结果/踩了什么坑

B. Python(前端转后端必被问)

B1. GIL 是什么?影响什么?多线程/多进程/asyncio 怎么选?

答案

  • GIL 是 CPython 的全局解释器锁,同一时刻只有一个线程执行 Python 字节码
  • 影响:CPU 密集任务多线程无加速(甚至有反效果);IO 密集任务多线程有用(等待 IO 时释放 GIL)
  • 选型:CPU 密集 → 多进程(multiprocessing)或 C 扩展;IO 密集 → asyncio(单线程协程)或线程池;两者混合 → 多进程 + 进程内 asyncio
  • 对照前端话术(讲清楚版)
    • JS = 无锁的串行:JS 主线程永远只有一个执行流,不存在"两个线程同时改变量"的竞态 → 天然不需要锁;异步并发靠事件循环交错,是"假并发",但不是因为锁,而是因为单线程
    • Python 多线程 = 有锁的受限并行threading 创建的是真 OS 线程(多个执行流真实存在),但 GIL 强制同一时刻只有一个能执行 Python 字节码 → CPU 密集时多线程被锁串行化、无加速;IO 等待时 GIL 释放 → 多线程对 IO 密集有效
    • asyncio ≈ 浏览器事件循环:Python 的 asyncio 是"单线程 + 事件循环"模型(循环取协程 → await 挂起 → 结果好了恢复),和浏览器事件循环同思路——asyncio 是 Python 里最像 JS 的并发模型,前端转 Python 优先吃透它
    • 一句话记忆:JS 无锁因为根本没有多执行流;Python 有锁因为多线程是真存在但被 GIL 限速;想 CPU 并行必须换多进程;想写异步就学 asyncio(≈ JS 事件循环)

工程落地(项目实例)

  • FastAPI 同步端点跑在线程池(GIL 在 IO 等待时释放)→ 业务 store 借连接执行 SQL
  • FastAPI async 端点跑在事件循环async def)→ 图执行 ainvoke、LLM 异步调用
  • 异步 saver 必须在事件循环内装配AIOMySQLSaver.__init__ 内部 asyncio.get_running_loop(),所以在 FastAPI lifespan 里 attach_async_checkpointer 再重编译图——这就是"同步/异步边界"在项目里的真实分界
  • 判断标准:CPU 密集(如文档解析/embedding 计算)才需要多进程;本项目 IO 密集为主(LLM 调用/MySQL/SSE),asyncio + 线程池足够

B2. async/await 原理?

答案

  • asyncio 是单线程事件循环:一个循环不断从任务队列取协程执行,遇到 await 挂起,IO 完成回调再恢复
  • await 表达式:把控制权交回事件循环,等结果回来继续
  • 对比 JS:事件循环机制一致(宏任务/微任务),但 Python 靠 await 显式让出,JS 靠 Promise 链

工程落地(项目实例)

  • LangGraph 图执行走 ainvoke(async invoke),每个节点内部 await LLM 调用/工具调用——异步贯穿编排层
  • 事件流 API 用 StreamingResponse + async 生成器逐条 yield 事件(SSE 推送),不阻塞事件循环
  • 记忆点:同步 def vs async def 在 FastAPI 里的行为差异(线程池 vs 事件循环)是面试官爱挖的点,能讲清"为什么 async 端点不能做 CPU 密集计算"就稳了

B3. 装饰器是什么?带参数的装饰器怎么写?

答案:函数接收函数返回函数;@decorator 等价于 f = decorator(f)。带参数装饰器 = 三层嵌套。

import functools, time

def retry(times, base_delay=0.5):
    """带指数退避的重试装饰器"""
    def deco(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            last_exc = None
            for attempt in range(times):
                try:
                    return fn(*args, **kwargs)
                except Exception as e:
                    last_exc = e
                    if attempt < times - 1:                       # 最后一轮不再等待
                        time.sleep(base_delay * (2 ** attempt))   # 指数退避:0.5s → 1s → 2s
            raise last_exc      # ⚠️ 重试耗尽后必须抛出,绝不能静默返回 None
        return wrapper
    return deco

⚠️ 注意上面 raise last_exc 这一行:很多网上抄来的重试装饰器写成 except: pass,结果是重试全部失败后函数静默返回 None——调用方拿到 None 却以为成功,这是生产事故的经典来源。正确实现必须:① 耗尽后抛出原始异常(保留 traceback 用 raise ... from last_exc);② 加指数退避避免打爆下游;③ 生产里还应只对可重试错误重试(超时/429/5xx),4xx 这类业务错误直接抛出。

工程落地(项目实例)

  • LLM 重试就是装饰器思想实现(项目里做成了显式循环):瞬时错误(连接/超时/429/5xx)重试 + 指数退避,4xx 不重试;每次重试前回调可埋 system/retry 事件(可观测)
  • FastAPI 的 Depends(require_admin) 依赖注入本质也是"装饰":函数执行前先过权限校验
  • 路由装饰器 @router.post(...) 本身就是一个装饰器应用

B4. 生成器 vs 迭代器?yield 的作用?

答案:迭代器是实现了 __iter__/__next__ 的对象;生成器是含 yield 的函数,调用返回生成器对象。yield 暂停函数保存状态,next() 恢复。惰性求值——大数据流处理省内存。你项目里分块器(chunker)就是生成器思想。

工程落地(项目实例)

  • SSE 事件流:async 生成器逐条 yield 事件(StreamingResponse)——不需要一次性把全部事件读进内存,边查边推,前端逐条渲染
  • 文档分块:chunker 按段落聚合,本质是"流式处理长文本",超长段落边切边出块
  • 对比列表推导:[x for x in big] 全量进内存 vs (x for x in big) 惰性迭代——海量事件/大文档场景必须用惰性

B5. == vs is?深拷贝 vs 浅拷贝?

答案== 比值,is 比身份(内存地址)。小整数/短字符串有缓存池可能 is 为 True,不可依赖。浅拷贝只复制外层,深拷贝递归复制全部(copy.deepcopy)。

B6. *args / **kwargs 是什么?

答案*args 收位置参数为元组,**kwargs 收关键字参数为字典;函数调用时 f(*list) 解包。

B7. Pydantic 是干什么的?FastAPI 为什么用它?

答案:数据校验 + 序列化库,基于类型注解定义 schema,自动校验/转换/生成 OpenAPI 文档。FastAPI 用它做请求体验证、响应模型、依赖注入的类型安全。对照前端:类似 zod + joi 的结合,但和类型系统原生集成。

工程落地(项目实例)

  • 配置模型全 PydanticAgentUpsertRequest / ScenarioUpsertRequest / TargetRoleUpsertRequest / McpServerUpsertRequest——B 端每个写接口都带 min_length/max_length/Literal 约束,非法请求在路由层就被 422 拦下
  • 运行时校验:agent_configs 表加载后用 Pydantic 强约束字段(model_tier ∈ {fast, strong}、system_prompt 长度上限 4000)——配置错误在装配期暴露而非运行期
  • 对比裸 dict:改 schema → 改一处模型,自动同步校验 + OpenAPI 文档,前后端契约不会漂移

B8. FastAPI vs Flask vs Django?

答案

  • FastAPI:异步原生、Pydantic 校验、自动 OpenAPI 文档、性能高(对标 Node 的 Fastify)
  • Flask:轻量同步、生态老、适合小服务
  • Django:全家桶(ORM/Admin/认证)、重、适合内容型站点
  • 话术:"我选 FastAPI 因为它和 LangGraph 异步生态契合(图执行走 ainvoke),且 Pydantic 保证配置 schema 的运行时校验"

工程落地(项目实例)

  • 路由注册:按域分模块(tasks/approvals/admin/chat/auth/targets),register_xxx_routes(app, deps) 注入依赖,装配时统一挂载到 /api/v1 前缀
  • 依赖注入AppDeps dataclass 持有全部 store/registry/llm/graph,路由用 Depends 拿当前用户 + 权限校验(require_admin
  • lifespanattach_async_checkpointer(换异步 saver 重编译图)+ aclose(释放连接池)——启动/关闭钩子
  • CORS/限流/健康检查:中间件 + RateLimiter(每用户限流)+ /health 探活

手写题(务必默写):

  1. 线程安全单例
  2. 装饰器(带参 + functools.wraps)
  3. 生成器实现斐波那契
  4. 异步版生产者消费者
  5. 手写 LRU Cache(OrderedDict)

C. AI/LLM 原理

C1. Tokenizer 是什么?为什么中文 token 更贵?

答案:LLM 输入输出的最小单位。BPE 等算法把文本切成子词。中文单字信息密度高,一个汉字常被切成 1-2 个 token(英文一个词可能 1-2 token),同样的中文内容 token 数更多 → 更贵更慢。

工程落地(项目实例)

  • 成本意识:模型档位设计(fast/strong)本质就是 token 成本管理——高频轻任务(research/publish)走便宜档,质量关键任务(write/review)才用好模型
  • 上下文预算:注入 prompt 时按 token 预算截断低分素材,防止上下文撑爆(token 超限 = 请求失败 + 费钱)
  • 中文场景的 token 经济性→自己写的 chunker 用 800 字符 ≈ 控制单条注入的 token 上限,就是"每 token 都要花得值"的工程取舍

C2. Embedding 是什么?相似度怎么算?

答案:把文本映射成高维向量,语义相近的文本向量距离近。相似度用余弦相似度(cosine):向量夹角越小越相似。RAG 里 query 和文档块都转向量,检索取 top-K。

工程落地(项目实例)

  • Phase 1 没直接上 embedding——用中文 2-gram + 英文单词的命中计数做近似检索(零依赖,离线可测)。这恰恰是能讲出工程判断的地方:"数据量小 + 术语固定(日清/损耗/临期)时,关键词检索够用;语义检索的价值随文档规模上升才显现,所以放 Phase 2"
  • 若上向量库:chunk 先过 embedding 模型 → 存向量(Qdrant/pgvector)→ query 同模型向量化 → 余弦近邻 top-K
  • 两阶段混合检索(Phase 2 目标):关键词召回 + 向量召回合并 → 重排——兼顾精确命中与语义扩展

C3. Transformer 注意力机制?讲 Q/K/V

答案:注意力让模型在生成每个词时"关注"输入的其他词。Q(Query 查询)、K(Key 键)、V(Value 值):Q 与所有 K 算相似度(点积)→ softmax 得权重 → 加权求和 V。多头注意力 = 多组 Q/K/V 并行捕捉不同关系。

C4. temperature / top_p 是什么?

答案:控制随机性。temperature 高 → 分布更平 → 更随机/更有创造性;低 → 更确定。top_p(核采样):只从累计概率 p 的 token 里采样。写代码用低温度,创意生成用高温度。

工程落地(项目实例)

  • LLM 客户端默认 temperature=0.7按节点覆盖:write(创意内容)用较高、review(打分审核)用较低——审核要"稳定判分"不能飘
  • mock 模式确定性输出(LLM_MODE=mock 时直接返回引用最后一条消息的假文本)——无 key 也能跑通全流程,CI 测试不依赖真实模型
  • 面试可讲:"temperature 是质量与创造性的旋钮,我们按角色分配(审核用低随机性保一致性),而不是全局一套参数"

C5. 什么是幻觉(Hallucination)?怎么防?

答案:模型生成看似合理但错误/编造的内容。防法(你项目里都有,系统化讲):

  1. RAG 引用溯源:检索真实资料,标注 来源N,只允许引用素材内事实
  2. 审核循环:LLM 审核 + 规则校验(引用序号是否越界、关键事实是否有引用)
  3. 提示约束:素材未覆盖的信息必须标注"存疑/待核实"
  4. HITL:发布前人工确认兜底
  5. 评估集 + LLM-as-judge 持续回归

C6. RAG 完整链路?每一步的工程落地?(深度题,加分重点)

答题框架:先给链路总览 → 再按 8 步逐段讲"概念 → 项目里怎么做 → 工程决策/踩坑"。讲到自己项目里真实写过的代码时画龙点睛。

链路总览:解析 → 清洗 → 分块 → 向量化/存储 → 检索 → 重排 → 注入 prompt → 生成 + 引用溯源。

① 文档解析(Parser)

  • 概念:把 PDF/Word/HTML/扫描件等非结构化文档转成可处理的文本。
  • 项目落地:解析器做成 Seam 三层(spec + impl)——接口 parse(data, filename) -> str,实现类可插拔:
    • PlainTextParser:UTF-8 解码兜底(.txt/.md 直通)
    • MinerUParser:PDF/Word/扫描件 → Markdown(可选增强,未安装自动降级回 PlainText)
  • 工程决策:用工厂模式build_document_parser(prefer_mineru))——MinerU 是重依赖(要下模型),没装也能跑通,测试不依赖外部重依赖。这就是"核心链路不阻塞于可选增强"的解耦思路。

② 清洗

  • 概念:去噪声——页眉页脚、乱码、重复空行、HTML 标签、超链接、表格残留。
  • 项目落地:解析后文本统一 strip + 过滤空段落(text.split("\n")p.strip() 非空才保留)——chunker 真实的预处理就是这一行。
  • 踩坑:扫描件 OCR 结果常带页码/页眉,直接切块会污染每个 chunk 的相关度打分,优先在解析层清洗而不是检索层。

完整清洗管线(能默写,面试画这个)

def clean_text(raw: str) -> list[str]:
    """清洗 → 返回干净段落列表(链接着给 chunker 用)"""
    import re
    text = raw.replace("\r\n", "\n").replace("\r", "\n")   # 统一换行符
    text = re.sub(r"[ \t]+", " ", text)                     # 合并多余空格/制表符
    text = re.sub(r"\n{3,}", "\n\n", text)                  # 压缩连续空行(保留段落分隔)
    text = re.sub(r"<[^>]+>", "", text)                     # 去 HTML 标签(解析器残留)
    text = re.sub(r"https?://\S+", "", text)                # 去 URL(噪声 + 可能带恶意链接)
    text = re.sub(r"^\s*(第\s*[0-9一二三四五六七八九十]+\s*页|[0-9]+\s*/\s*[0-9]+)\s*$",
                  "", text, flags=re.M)                     # 去"第X页 / X/Y"页码行
    # 页眉页脚(按公司文档特征):识别重复出现的固定行
    lines = [ln.strip() for ln in text.split("\n")]
    from collections import Counter
    freq = Counter(lines)
    boilerplate = {ln for ln, n in freq.items() if n >= 3 and len(ln) <= 30}
    lines = [ln for ln in lines if ln not in boilerplate]   # 出现≥3次的短行=页眉页脚/水印
    return [ln for ln in lines if ln]                        # 去空行,得到干净段落
  • 关键规则(面试讲这 3 条):① 页码/页眉是"短行 + 高频重复"(用 Counter 统计出现 ≥3 次且 ≤30 字符的行,当作固定版式删除);② 空行压缩但不能全删——保留 \n\n 作为段落边界给 chunker 用;③ URL/HTML 标签直接正则删,避免污染检索相关度。

为什么在解析层清:清洗是一次性、确定性的(同文档每次结果一样);如果留在检索层做,每次 query 都要重复处理全部文档、且不好测试——清洗作为"入库预处理"放进解析→切块链路,chunker 只认干净段落。

③ 分块(Chunker)——面试官最爱展开的地方

  • 概念:把长文档切成语义完整的片段,太小丢上下文、太大噪声多。
  • 项目落地(真实实现,chunk_size=800 字符 / overlap=100):
    先按段落切(保留段落边界)→ 再按 size 聚合:
    - 段落优先:能停在段落边界就停在段落边界(语义完整)
    - 超长段落强制切:while len(para) > size → 切片 + overlap 回退
    - overlap=100:保留上一块尾部 100 字符,防止跨块的上下文断裂
    
  • 工程决策段落边界优先而非纯字符切——字符硬切会把"如果…那么…"切断,检索召回后模型读不懂因果;overlap 是"上下文连续性"和"存储膨胀"的折中。
  • 参数怎么定:本项目中英混合按 800 字符 + 100 overlap(约等于 200-300 token);检索单元太小(<200 字)容易答非所问,太大(>2000 字)噪声淹没关键信息。要用评测集试不同 size 看命中率,不能拍脑袋。

④ 向量化 / 存储(Embedding + 向量库)

  • 概念:chunk 转向量存向量库(Qdrant/Milvus/pgvector),query 也向量化后找最近邻。
  • 项目落地分阶段演进——
    • Phase 1(已落地)零依赖关键词检索(不引向量库)——中文 2-gram + 英文单词切词,chunk 命中计数排序。规避 embedding 模型的网络/重依赖,离线可跑、测试稳定。
    • Phase 2(规划):接 embedding + 向量库,做语义检索。
  • 工程决策(很加分的判断):"先跑通再上重依赖"——MVP 用 BM25 式关键词检索保证链路完整(解析→切块→入库→检索→注入全通),语义检索作为二期增强。检索质量受限于业务数据规模:门店知识库文档量小、术语固定(日清/损耗/临期),关键词命中在垂直场景够用;等文档量上来再换向量库。避免为用而用重型组件。
  • 存储设计(MySQL 三表):knowledge_bases(库)→ documents(文档,含 status/原文/chunk_count)→ chunks(id/doc_id/kb_name/seq/text)。chunk 带 seq 序号:可还原文档顺序,检索命中可回溯原文。
  • 踩坑:MySQL 用 MEDIUMTEXT 存原文 + JSON 存 chunk 元数据;若直接上 pgvector/Milvus 要处理"embedding 模型挂了怎么办"——所以降级路径(关键词检索)一直保留。

⑤ 检索(Retrieval)

  • 概念:query 和文档集算相似度,取 top-K。
  • 项目落地(真实代码):tokenize——中文切 2-gram 二元组("多智能体"→"多智/智能/能体")+ 英文/数字单词;_score——query 每个 token 在 chunk 里的出现次数求和rank_chunks——score>0 的按分倒序取 top-N。
  • 工程决策
    • 中文 2-gram 而非整词:不依赖分词库(jieba 等),零依赖、可预测、测试稳定;
    • 检索返回结构化素材(对齐 research 节点输入契约):{title: filename, snippet: 前120字, source: "kb:xxx", score: 命中数}——snippet 直接喂给撰写节点做引用,score 给审核节点做置信度判断
    • 跨知识库:按 kb_names 集合过滤(笑话:IN 占位符拼参数,避免 SQL 注入)。

⑥ 重排(Rerank)

  • 概念:向量召回 top-50 后,用更精准的模型把真正相关的排前面,取 top-5。
  • 项目落地:Phase 1 未做专门 rerank 模型——用命中分数排序 + snippet 截断作为轻量重排。Phase 2 可接 rerank API 或 LLM 二次筛选。
  • 话术:"召回是'尽量不错过',重排是'尽量不误伤'。小数据量下关键词分数排序够用;文档量大了之后 top-K 里掺噪声,才需要专门的 rerank。"

⑦ 注入 Prompt

  • 概念:把检索结果拼进 system/user message 给 LLM。
  • 项目落地:研究节点把素材结构化注入——每条带 title/snippet/source/score,正文封口"只允许引用素材中明确存在的事实,用 来源N 标记"。注入格式决定引用质量:给编号 + 强制引用标记,模型才能"按号作答"。
  • 工程决策:注入顺序按分数降序;超出上下文窗口时截断低分素材(保留 source 让引用可追溯)。

⑧ 生成 + 引用溯源(闭环)

  • 概念:LLM 基于注入材料生成,并标注引用来源。
  • 项目落地:撰写节点输出带 [来源N] 标记的正文 → 审核节点反向校验:引用序号是否在素材范围内、被引内容和素材是否一致、关键论断有无引用。这就是 RAG 防幻觉在工程上的闭环——不是靠提示词"别瞎编",而是检索→引用→校验形成证据链。
  • 踩坑:只有"检索+生成"没有"引用校验",幻觉只能靠运气;加上审核环节后,模型编造的内容会因为"查无此文"被审核拦下回写——这才是 RAG 落地和 Demo 的区别。

加分收尾:"RAG 不是把向量库一接就完事。真正决定效果的是检索质量(查得到)注入格式(读得懂)、**引用校验(敢信它)**三段——我项目里这三段都做了工程化,而且保留了关键词检索降级路径,保证任何环境都能跑通。"

RAG 完整链路图解(每步标注工程落地)

flowchart TD
    subgraph Offline["📥 离线索引链路(一次性 / 定期)"]
        Doc["原始文档<br/>PDF / Word / 网页"] --> Parse["① 解析<br/>MinerU 等工具抽文本+结构"]
        Parse --> Clean["② 清洗<br/>去页眉页脚 / 乱码 / 重复"]
        Clean --> Chunk["③ 分块 Chunk<br/>按语义/结构切,配 overlap"]
        Chunk --> Embed["④ 向量化<br/>embedding 模型"]
        Embed --> StoreDB[("⑤ 向量库<br/>+ 父子索引 / 元数据")]
    end

    subgraph Online["🔍 在线问答链路(每次提问)"]
        Q(["用户 Query"]) --> Rewrite["查询改写 / 扩写"]
        Rewrite --> Hybrid["⑥ 混合检索<br/>BM25 + 向量 → RRF 融合"]
        Hybrid --> Rerank["⑦ 重排 Rerank<br/>精算相关性"]
        Rerank --> Prompt["⑧ 拼装 Prompt<br/>问题 + Top-K 上下文 + 引用要求"]
        Prompt --> Gen["⑨ LLM 生成"]
        Gen --> Cite["⑩ <b>引用校验</b><br/>核对答案是否有出处支撑"]
        Cite -->|"校验通过"| Ans(["返回答案 + 引用来源"])
        Cite -->|"无依据 / 低置信"| Refuse["<b>拒答 / 澄清</b><br/>(防幻觉兜底)"]
    end

    StoreDB --> Hybrid
    Rerank --> Prompt

    style Cite fill:#ffe6cc,stroke:#d79b00
    style Refuse fill:#f8cecc,stroke:#b85450
    style Hybrid fill:#d5e8d4,stroke:#82b366

读图要点:面试讲 RAG 时,一定要按"离线索引链路 + 在线问答链路"两条链分开讲,比按 0→9 平铺更有条理。 最能加分的三个点:① 混合检索 + RRF 解决纯语义检索对专有名词不敏感的问题;② 父子索引解决"小块检索准、大块上下文全"的矛盾;③ 引用校验闭环——生成后回查引用是否真实存在,不存在就拒答,这是防幻觉的最后一公里,也是 2026 面试的高频深挖点。

C7. Agent 和 RAG 的区别?

答案:RAG 是"检索增强生成"——给模型喂外部资料减少幻觉,是单轮能力;Agent 是"能调用工具、多步决策"的智能体——规划、调用工具、观察结果、再决策。RAG 是 Agent 的一个能力组件,Agent 是更大的闭环。

工程落地(项目实例)

  • RAG 扮演 Agent 里的"研究节点":research 节点 = 搜索工具(Tavily 联网)+ 知识库检索(RAG)→ 输出结构化素材,喂给 write
  • Agent 把 RAG 变成证据链:research(查)→ write(引 来源N)→ review(校验引用)——RAG 负责"给料",Agent 负责"用料的管控",这是单轮 RAG 做不到的
  • 面试关键词:项目不是"RAG 应用",是"Agent 编排中内嵌 RAG 能力",RAG 是工具之一而非主体

C8. Function Calling / Tool Calling 原理?

答案:把工具定义(名称/描述/参数 JSON Schema)随 prompt 发给模型,模型输出"我要调用 tool_x,参数是 {...}"的结构化结果,程序解析后执行真实工具,把结果回传给模型继续生成。这是 Agent 调用外部世界的标准方式。

完整往返口述版(背这个讲)

  1. 请求带 tools 数组(每个工具 = name + description + parameters JSON Schema)
  2. 模型响应里 message.tool_calls(含 tool_call_id + 函数名 + arguments JSON 字符串)——模型只提议,不执行
  3. 程序 json.loads(arguments) 解析 → 校验 → 执行真实函数
  4. 把结果以 role:"tool", tool_call_id: 对应 id 回传
  5. 模型拿到工具结果后组织最终回答;可连环调用(上限控制)

好处(口述版,挑 3 个讲即可)

  1. 突破边界:补上模型的"实时数据 + 实际操作"能力——模型无法自己查库/发请求/操作业务系统,Function Calling 让程序来执行,模型只做决策
  2. 准确可信:参数由 JSON Schema 结构化约束、输出可解析校验——比"让模型在回复里编结果"可靠(不会幻觉出一串假数据),这是 RAG/Agent 落地的前提
  3. 安全管控:模型只"提议"、程序才"执行"——执行前可过白名单/权限/HITL 审批(项目里 autoApprove 豁免 + 其余走人工确认),AI 不能越权
  4. 可观测可留痕:每次调用参数+结果可落事件日志——调了什么、传了什么、返回什么都可回放(呼应 append-only 事件日志)
  5. Agent 的基石:LLM 决策 + Function Calling 执行 + 循环控制 = 真正的 Agent;没有它只是"聊天机器人"

工程落地(项目实例)

  • 工具登记三步:spec(工具名/描述/参数 JSON Schema + read_only 标记)→ impl(真实实现)→ registry(注册 + 白名单)——用 ToolSpec 结构统一描述,工具"能调什么"由 spec 约束
  • read_only 管控:只读工具默认安全放行;MCP 远端工具按 autoApprove 白名单豁免,其余走审批(HITL 延伸到工具调用)
  • 模型侧:LLM 拿到工具描述后输出结构化调用意图 → 程序校验参数再执行 → 结果回传继续生成;解析失败/参数非法走降级(不崩流水线)

Function Calling 完整往返图解(模型只提议、不执行)

sequenceDiagram
    autonumber
    participant App as 你的程序
    participant LLM as LLM(DeepSeek / OpenAI 兼容)
    participant Tool as 真实工具 / API

    App->>LLM: ① 请求带上 tools 定义(name / description / parameters JSON Schema)+ 用户消息
    Note over LLM: 模型做了什么?<br/>它<b>只输出"要调用哪个函数、参数是什么"</b><br/>——实际执行由你的程序负责
    LLM-->>App: ② 返回 message.tool_calls<br/>[{ id, function: { name, arguments: "JSON 字符串" } }]
    Note over App: ③ arguments 是<b>字符串</b>,需 json.loads 解析
    App->>App: 校验参数合法性(防注入 / 越权)
    App->>Tool: ④ 真正执行工具
    Tool-->>App: ⑤ 返回结果
    App->>LLM: ⑥ 回传 role="tool" 消息<br/>含 tool_call_id + 结果内容<br/><b>同时必须带上完整历史</b>
    Note over LLM: 模型结合工具结果生成自然语言答案
    LLM-->>App: ⑦ 最终回答(可能再次要求调用其他工具)

读图要点(三个高频追问)

  1. "是谁执行了函数?" —— 不是模型。模型只产出"调用意图 + 参数",执行发生在你的程序里。这是安全边界的关键,也是"为什么说 Agent 比 Chatbot 危险"的原因。
  2. "回传时要注意什么?" —— 必须回 role: "tool" + tool_call_id,且把之前的对话历史完整带上(包括那条带 tool_calls 的 assistant 消息)。漏了 tool_call_id 或截断历史,模型会报错或丢失上下文。
  3. "arguments 为什么是字符串?" —— 协议约定为 JSON 字符串而非对象,因为需要保证传输与拼接过程中的确定性;使用时必须 json.loads(),且要做异常处理(模型可能生成非法 JSON)。

C9. MCP 协议是什么?Server/Client/Transport?

答案:Model Context Protocol,标准化"模型 ↔ 工具/数据"的通信协议,类似 AI 界的 USB-C。Server 暴露工具/资源/提示,Client 连接调用。传输:stdio(本地子进程)/ streamable HTTP(远程)。工具名规则 {server}__{tool},autoApprove 白名单可免确认。

工程落地(项目实例)

  • 三层工具 Seam 架构spec(契约声明)→ impl(本地实现)→ mcp(远端调用代理)——工具"能不能用"由 spec+白名单定,"怎么实现"可本地可远端,互不影响
  • McpDispatcher:按 mcp_servers 表配置解析 client → 未配置/未启用(enabled=False)不可调用 → 运行时解密密钥注入 → 工具名 {server}__{tool} 路由
  • auth 扩展:autoApprove 白名单工具直通,其余工具调用走 interrupt 人工审批(HITL 的第二种形态)
  • 向后兼容:加密落库同时兼容旧的 {{ENV:VAR}} 环境变量引用——迁移不破坏现有配置

D. AI 工程落地(围绕你项目)

D1. LangGraph 核心概念?

答案:State(共享状态,TypedDict)、Node(处理函数,读 state 返回更新)、Edge(流转)、Conditional Edge(条件分支)、Checkpointer(持久化/中断恢复)、interrupt(HITL 挂起)。你的审核循环 = 条件边 + state 轮次字段;HITL = interrupt + checkpointer。

工程落地(项目实例)

  • StatePipelineState(TypedDict)——brief/target/素材/draft/review 结果/revision 轮次,节点间共享
  • Node:research/write/review/publish 各是一个函数:读 state → 调 LLM/工具 → 写回 state
  • 条件边:review 之后 add_conditional_edges 三向分流(通过/回写/转人工),轮次在 state 里
  • Checkpointer:memory(dev)/ MySQL(异步 AIOMySQLSaver,prod)双后端,选 MySQL 是为了任务中断/审批恢复后能从持久化状态续跑
  • 面试可画:State + 节点 + 条件边的图,标出 interrupt 点(发布确认)

D2. 多 Agent 协作模式?你项目是哪种?

答案:常见模式——Pipeline(流水线串行)、Supervisor(一个调度者分派)、Hierarchical(层级)、Debate(对抗评审)。你项目是 Supervisor + Pipeline 混合:supervisor 按 target 分流,然后角色节点串行(研究→撰写→审核→发布),审核不合格回写形成循环。

工程落地(项目实例)

  • Supervisor 职责单一:只做"查注册表 → 定 pipeline",不执行业务——分流逻辑与执行逻辑解耦
  • Pipeline 变体:不同场景复用同一批节点但不同顺序(content 四节点 / direct_answer 三节点 / video_short 带 video_gen),节点无场景感知
  • 审核循环 = Pipeline 的环:普通流水线是 DAG,加了条件边回写后变成"带环的有向图",LangGraph 条件边天然支持
  • 面试结论句:"多 Agent 不是越多越好——我们是编排主导 + 相互制衡(撰写 vs 审核对抗),比纯 Agent 自由协作更可控"

D3. 模型档位 fast/strong 怎么设计?

答案:按任务复杂度分配模型——research/publish 用 fast(中档,快省),write/review 用 strong(强档,质量优先)。OpenAI 兼容层封装,可切换 DeepSeek/通义/GLM,实现模型无关。

工程落地(项目实例)

  • LLMClientTier = Literal["fast","strong"]_tier_model() 按档位映射 Settings 里的 llm_model_fast/llm_model_strong
  • OpenAI 兼容OpenAI(base_url, api_key) 一个客户端接所有兼容端点——换模型只改配置不改代码(模型无关 ADR)
  • 档位分配在角色配置里agent_configs 表 model_tier 字段,B 端可改——模型档位是"运行时配置"不是硬编码
  • 降级LLM_MODE=mock 走确定性假文本(无 key 可跑测试);real 模式缺 key 明确报错再由节点层降级——不静默失败
  • 面试句:"档位设计本质是成本 × 质量 × 延迟的三维调度——不是所有任务都值得用最强模型"

D4. 怎么评估 Agent 质量?

答案:评估集(含正确答案的测试用例)→ 跑流水线 → 规则指标(引用正确率、格式合规)+ LLM-as-judge(让强模型按 rubric 打分)→ 回归对比。你在项目里用 130+ 测试用例(pytest)做流程级保障,评测集是后续升级方向。

工程落地(项目实例)

  • 130+ 用例分层(TDD):图集成测试(审核循环收敛/超限转人工/事件完整)→ API 测试(认证/权限/CRUD/幂等/CORS)→ 知识库测试(解析/分块/检索排序)→ 安全测试(Fernet 加解密/JWT)
  • Mock 让测试可复现LLM_MODE=mock 确定性输出,CI 里跑全流程不依赖真实模型 key——测试稳定性靠"可控的假实现"而不是真实 LLM
  • 内存后端隔离:测试用内存 store(CHECKPOINT_BACKEND=memory),不污染 MySQL
  • 评测集升级方向:业务指标(引用正确率/重写收敛率/人工介入率)→ LLM-as-judge 打分 → 每次 prompt/模型变更跑回归

D5. SSE 和 WebSocket 区别?为什么用 SSE?

答案:SSE 单向(服务器→客户端)、基于 HTTP、自动重连、简单;WebSocket 双向全双工。你项目是"服务器主动推送任务事件",单向足够 → SSE 更简单可靠(还能走 HTTP 缓存/代理)。

工程落地(项目实例)

  • 事件流 API:GET /api/v1/tasks/{id}/eventsStreamingResponse + async 生成器逐条推事件(SSE 格式),前端 EventSource 自动重连
  • 前端按 stage 分组渲染:组头(状态点 ✓/●/○ + 耗时 Badge)→ 点击事件 → 载荷快照面板(draft 全文/review 分数+意见/research 素材/发布计划 + 原始 JSON 折叠)
  • 为什么不用 WebSocket:单向推送 + 需要断线重连 + 想走 HTTP 网关 —— SSE 三个需求全满足,且实现简单得多
  • 面试句:"选型看通信方向——我们只需要服务端→客户端,SSE 是最小可用方案,不为双向能力付复杂度"

E. 后端/数据库

E1. MySQL 索引原理?B+树?最左前缀?

答案:索引用 B+ 树——叶子节点存数据且有序链表连接,范围查询高效。联合索引 (a,b,c) 遵循最左前缀:查询条件必须从最左列开始才能用到索引。覆盖索引:查询列都在索引内,不用回表。

工程落地(项目实例)

  • chunks 表INDEX idx_chunks_kb (kb_name)——检索按知识库过滤,命中即用索引;INDEX idx_chunks_doc (doc_id) 详情反查
  • documents 表INDEX idx_documents_kb (kb_name)——按库列文档
  • event_log 表:按 (task_id, ts) 建联合索引——事件流按任务+时间回放,最左前缀 task_id 命中
  • 只 INSERT 的表(事件日志/审计)不做 UPDATE——写放大小、锁竞争低,配合索引保证回放查询快
  • 面试可提:大表(事件日志)归档/分表是 A10 的延展,当前单表 + 索引够用

E2. 事务 ACID?隔离级别?

答案:原子性(要么全成要么全败)、一致性、隔离性、持久性。隔离级别(从松到严):读未提交(READ UNCOMMITTED)→ 读已提交(READ COMMITTED)→ 可重复读(REPEATABLE READ,MySQL 默认)→ 串行化(SERIALIZABLE)。脏读/不可重复读/幻读对应不同级别。

⚠️ 高频陷阱:MySQL 的默认隔离级别是可重复读(REPEATABLE READ),不是读已提交。读已提交是 Oracle / SQL Server / PostgreSQL 的默认。答错直接扣分。另外 InnoDB 的可重复读通过 MVCC + Next-Key Lock(间隙锁) 额外解决了大部分幻读,这点是加分项。

工程落地(项目实例)

  • 加文档 = 单事务add_document 一次插入 documents + 批量插入 chunks——要么全部成功要么全部回滚,防止"文档有了、chunk 丢了"的脏数据
  • 删除库 = 级联清理delete_base 按 chunks → documents → knowledge_bases 顺序删(先子后父),单事务保证一致性
  • 配置 upsert 幂等:agent_configs/scenarios 等 seed 用"查无则写",重复启动不产生重复配置(天然利用唯一键 + 先查后写)
  • 面试可讲:事务边界 = 业务原子操作,"传文档 + 切块 + 入库"是一个原子单元,拆开就会出半成品

E3. Redis 缓存穿透/击穿/雪崩?

答案:穿透:查不存在的数据(每次都打 DB)→ 布隆过滤器 / 缓存空值;击穿:热点 key 过期瞬间大量请求 → 互斥锁 / 逻辑过期;雪崩:大量 key 同时过期 → 过期时间加随机值 / 集群。

工程落地(项目实例)

  • 诚实声明:当前项目以 MySQL + 事件日志为主,Redis 未上线;但设计里已预留缓存点——热点配置(Agent/场景/工具注册表)可入 Redis 避免每次请求查库
  • 幂等表走 MySQL:任务创建用 IdempotencyStore(内存/MySQL 双实现)而非 Redis——换引擎不换接口,就是"存储可替换"的设计
  • 面试可讲:哪些数据适合 Redis(不常变的热点配置/限流计数),哪些不适合(append-only 事件日志要落库留痕)——答出"为什么没用 Redis 是设计选择而非缺失"就高级了
  • Redis 三连的标准解法:穿透(空值/布隆)、击穿(互斥锁/逻辑过期)、雪崩(随机 TTL/集群)——背熟,问必答

E4. 幂等设计怎么做?

答案:客户端生成幂等键(idempotency key)随请求发送;服务端按 key 查重——首次执行落库,重复请求直接返回首次结果。你项目里任务创建用了这个防重复提交。

工程落地(项目实例)

  • IdempotencyStore(内存/MySQL 双实现):get(key) 有记录 → 直接返回已有结果;无记录 → 落库 + 执行(原子)
  • 落地场景POST /tasks 创建任务——前端重复点击/网络重试不会创建两个任务
  • 并发安全:MySQL 版本用"唯一键 + 先查后写",重复提交在同事务内被唯一约束拦截
  • 面试扩展:幂等 3 要素——幂等键来源(客户端生成 UUID/业务单据号)存储(表/Redis)返回(重复请求返回首次结果)——讲到"任务创建必须幂等,否则前端双提交会双倍扣费/双倍发内容"就落地了

E5. JWT 认证流程?优缺点?

答案:登录发 token(header.payload.signature),客户端携带,服务端验签无需查库(无状态)。优点:无状态、跨域;缺点:无法主动失效(需黑名单/短过期+refresh)、泄露风险(HTTPS + 过期时间)。你项目 RBAC:admin 路由守卫 + 角色鉴权。

工程落地(项目实例)

  • 注册/登录:手机号 + 密码(服务端 bcrypt 哈希存储),登录签发 JWT——密码哈希进库、token 不进库(无状态)
  • RBAC 两角色:user / admin——Depends(require_admin) 守卫 B 端全部路由,前端 middleware.ts 做页面级守卫 + 路由重定向
  • 任务归属:任务创建记录 owner,详情/审批接口做归属校验(非本人 404 而非 403,避免泄露存在性)
  • 密钥分离设计:业务 token(JWT 短过期)与 MCP 密钥(Fernet 长生命周期)分开管理——两类凭据生命周期完全不同
  • 面试可讲:为什么 JWT 用短过期 + 前端刷新比长期 token 安全

F. 系统设计(15 分钟架构讲解)

F1. 请画出并讲解 AI 小灵的系统架构

讲解顺序

  1. 前端层:三端(Web/App/小程序)→ Next.js 一套代码基座 → 调 API + SSE 收事件
  2. API 层:FastAPI(认证/RBAC、任务创建、审批、B 端配置、事件流)
  3. 编排层:LangGraph 图(supervisor 分流 → 通用节点按 pipeline 执行 → 条件边审核循环 → interrupt 发布确认)
  4. 能力层:LLM 客户端(模型无关、档位映射)、工具(三层 Seam:Tavily 搜索/视频生成/MCP 远端)、知识库 RAG
  5. 存储层:MySQL(业务表 + checkpoint + 事件日志)、向量库(知识库)
  6. 横切:可观测(append-only 事件日志)、安全(Fernet 密钥)、幂等、重试降级

F2. 数据流:一次任务从创建到发布?

用户选 target + 填 brief → 创建任务(幂等)→ supervisor 按 target 查注册表得 pipeline → 依次执行节点(research 检索素材 → write 生成草稿 → review 打分,不合格回写循环 ≤3 轮 → publish 生成发布计划)→ interrupt 等人工确认 → 通过则执行发布工具 → 全程事件写入日志,前端 SSE 实时看到产物卡片。


G. 前端保底(被问概率低,但不能完全丢)

  • 手写 Promise.all / 防抖节流 / 深拷贝
  • React 性能优化手段(memo/useMemo/useCallback/虚拟列表)
  • HTTP 缓存(强缓存/协商缓存)、浏览器渲染原理
  • 微前端原理(qiankun:JS 沙箱、样式隔离、通信)→ 简历技能区写了微前端,必背 J 章(工程化专章)
  • 自研组件库(Rollup 多产物 / 按需引入 / Design Token)→ 见 J6-J7
  • Vite vs Webpack、Tree-shaking 原理

工程落地(项目实例)

  • Next.js App Router + Server Components:任务详情页首屏走 SSR(减少白屏),事件流部分用客户端组件 + EventSource 增量渲染
  • 一套代码基座三端:C 端 Web/App/小程序共用组件层(shadcn-ui 定性组件 + 业务组件),差异仅布局/交互适配——前端工程化核心卖点
  • shadcn-ui + Tailwind:copy-in 组件(非黑盒依赖),可深度定制样式/行为,组件演进不被 UI 库锁死
  • 微前端(智慧中台)+ 自研组件库 qdma-ui(门店助手 H5):简历里两条独立的工程化经历,别混着讲——一个是解耦发布权,一个是收敛契约 + 控体积
  • 技能区已含:Next.js(App Router) / Tailwind / shadcn-ui / 微前端(qiankun) / 组件库开发,与简历一致,别答岔

H. 行为面试

H1. 为什么从纯前端转向 AI Agent 全栈?

话术:"前端给了我工程化底座,但我在做 AI 小灵时发现,Agent 系统的价值瓶颈在编排和工具链,而不仅是界面。我花时间把 Python 后端和 LangGraph 补齐,是因为我想完整交付'从用户点下按钮到内容发布'的整条链路,而不是停在页面层。"

H2. 你带过人吗?如何推动跨团队?

话术:拿"权限重构 / 构建升级"举例——先立标杆(自己跑通全流程),再定规范(文档 + 评审),再推广(试点团队 → 全量),用数据说话(构建 21min→2.3min、错误率↓65%)。

H3. 你的职业规划?

话术:"短期:把 AI Agent 工程能力做深(编排/RAG/评测),成为团队里能独立交付 AI 应用的骨干;中期:往 AI 应用架构师方向走,负责平台级多 Agent 系统设计。"


I. 性能优化与高并发(结合项目作答 · 高频必考)

这一章专门练"用你的项目讲性能"。答题公式:先给指标 → 再讲瓶颈定位 → 最后讲优化手段 + 权衡。 切忌一上来就"我加了个 Redis"——面试官要的是会量、会定位、会权衡的人。

I1. 日均 5w+ 任务,峰值怎么扛住的?瓶颈到底在哪?

答案要点(结论先行,再拆):

  • 一句话结论:应用层不是瓶颈,瓶颈在上游 LLM 的配额与成本——单任务要多次 LLM 调用、耗时分钟级,所以吞吐上限由上游决定,不由我的服务决定。
  • 应用层做了什么
    1. 全异步执行:图执行走 ainvoke,MySQL checkpoint 用异步 saver(AIOMySQLSaver),不在事件循环里塞同步调用
    2. 连接池:业务 store(事件日志/任务表/配置表)共用连接池,FastAPI 同步端点跑在线程池、每请求借一条连接
    3. 幂等:任务创建带幂等键,重复提交返回首次结果,防"用户狂点提交"放大压力
    4. 只 INSERT 的事件日志:无读改写、无长事务,天然免锁,写入路径极短
  • 为什么不靠"加机器":加副本能扩应用层,但扩不动上游配额——所以真正的解法是模型分级 + 缓存 + 异步队列,而不是堆副本。
  • 主动给的下一步(体现规划能力):任务量再翻 10 倍,优先做 ① 全量异步化(入队即返回 + worker 消费)② 相似请求结果缓存 ③ 事件日志归档分表(A10 的答案)。

I2. LLM 调用延迟你优化过什么?TTFT 是怎么压下来的?

答题骨架(按链路讲,每段给一个具体动作):

flowchart LR
    U["用户发起"] --> GW["FastAPI 接入<br/>鉴权 + 幂等校验"]
    GW --> RT["Supervisor 路由<br/>1 次 LLM 调用"]
    RT --> RAG["检索<br/>BM25 + 向量 并行"]
    RAG --> LLM["子 Agent 生成<br/>流式"]
    LLM --> SSE["SSE 推前端"]

    RT -.- O1["模型分级:简单任务走 fast 档"]
    RAG -.- O2["多路召回 asyncio.gather 并行<br/>别串行等"]
    LLM -.- O3["固定前缀前置<br/>吃 Prompt Caching"]
    SSE -.- O4["服务端不缓冲,立即 flush"]

    style O1 fill:#e6f7e6,stroke:#3a3
    style O2 fill:#e6f7e6,stroke:#3a3
    style O3 fill:#e6f7e6,stroke:#3a3
    style O4 fill:#e6f7e6,stroke:#3a3
  • 模型侧模型档位 fast/strong——意图识别、路由、简单改写走 fast 档,只有关键推理(内容创作、审核)用 strong。这是延迟与成本的双收益。
  • 检索侧:多路召回并行发起asyncio.gather),而不是"先 BM25 再向量"串行等;Top-K 收敛,重排只作用于粗排后的 top-k。
  • 输入侧:Prompt 结构化——固定 system prompt 在前、可变内容在后,吃服务商的前缀缓存;上下文按需注入,不无脑塞全量历史。
  • 传输侧:SSE 流式输出,把感知延迟从"总延迟"降到"TTFT"——不用改模型、不用加机器,性价比最高的一次优化。
  • 连接侧:异步 HTTP 客户端全局复用(连接池 + keep-alive),避免每次调用重新握手。
  • 诚实边界:非核心链路(引用校验、后处理)不阻塞首答,放到流式输出之后做。

I3. 线上监控 Agent 的哪些指标?出问题怎么第一时间发现?

答案要点(分三层,别只答"看日志"):

  • 服务层(RED):请求率、错误率、延迟分布(P50/P95/P99)。延迟必须分段看:TTFT、总时长分开——否则"慢"到底慢在开始还是慢在输出,你根本不知道。
  • LLM 层:每任务 token 消耗、成本、上游 429/超时率、重试次数、模型档位分布。
  • Agent 业务层(这是别人答不出的加分项)
    • Supervisor 路由准确率(选对子 Agent 的比例,靠人工抽评)
    • 子 Agent 成功率、工具调用成功率与失败类型分布
    • 审核循环触发率与超限熔断次数(我的 ≤3 轮循环,超限就是信号)
    • HITL 触发率、死循环检测触发次数
    • 检索命中率、缓存命中率
  • 可观测性的三支柱:Metrics(趋势告警)+ Logs(细节定位)+ Traces(全链路 span,找慢在哪一步)。多 Agent 系统从第一天就要 trace——不然线上出问题只能靠猜。
  • 告警分级:P0(核心链路不可用)即时电话/群,P1(错误率抬升)群消息,P2(成本异常/趋势劣化)日报。
  • ⛓一个真实感细节:每个节点把 {status, data} 写进 State,事件日志按 stage 落库,任务详情页可以把整条链路的时间线复现出来——这既是用户体验,也是排障工具。

I4. 成本怎么控的?你算过 token 账吗?

答题骨架(先给公式,再给做过的动作,最后给监控):

  • 公式:成本 = Σ(输入 token × 单价) + Σ(输出 token × 单价) + embedding/rerank/向量库/带宽。一次任务的成本 = 链路里所有 LLM 调用之和(含 Supervisor 路由那一次,很多人漏算)。
  • 我实际做的(按性价比排序):
    1. 模型分级:fast/strong 分档,慢思考只留给关键环节
    2. 缓存:ContentBuddy 用 sha256 内容指纹做解析缓存(同一文档不重复解析),体现"缓存前置到最贵的环节"
    3. 上下文压缩:历史滚动摘要 + 工具结果裁剪 + 检索 Top-K 收敛
    4. 限制循环步数:审核循环 ≤3 轮硬上限,既是稳定性设计也是成本设计
    5. Prompt 结构化:固定前缀前置,吃前缀缓存
  • 监控归因按任务/节点维度记 token 账单,能回答"哪条链路最烧钱"。
  • 金句不做成本归因的 LLM 应用,规模一上来必然失控
  • 追问应对:"降本伤质量怎么办?" → 用 golden dataset 跑回归,质量指标不降才上线

I5. 你们做过压测吗?容量怎么定的?

答案要点没做过就坦诚讲 + 给思路,硬编数据必翻车):

  • 先讲特殊性(这是这题的区分度):LLM 应用压测和普通接口压测不一样——
    1. 上游真花钱、有配额,压测会把自己打到限流 → 正确做法是先压 mock 上游摸清应用层容量,再小流量打真实的验证配额
    2. 任务是有状态的(会话、checkpoint、thread_id),要造会话上下文,不能只打无状态接口
    3. 流式响应延迟要分段看(TTFT / 总时长),单一 P99 没意义
    4. 耗时与成本随输出 token 数变化,要按 token 分布压,不是按请求数压
  • 方法五步:定 SLO(如 P95 端到端 < 8s、成功率 > 99%)→ 造真实流量分布 → 梯度加压找拐点(延迟突然抬升处即单副本承载)→ 反推副本数(峰值 ÷ 单副本承载 × 安全系数,水位控 ~70%)→ HPA 保扩容速度并演练。
  • 我们项目落地的部分:已有 TDD 测试体系(130+ 用例)与性能基准意识(<200ms 类硬阈值在 3D 项目里是 CI 门槛);全链路压测是下一步计划——坦诚说"没做全量压测"比编一个 QPS 数字安全得多。
  • 可主动补的结论瓶颈在上游配额,不在应用层——所以容量结论往往落在"需要谈配额与降级方案",敢这么说比虚报 QPS 可信。

I6. 线上突然"某类任务变慢",你怎么定位?

答案要点(方法论,考的是不瞎猜):

  1. 先确认范围:是所有任务慢,还是某类场景/某租户/某模型档位慢?→ 决定是全局问题还是局部问题。
  2. 分段埋点把黑盒打散:网关 → 应用 → 检索 → LLM → 数据库,每段都有耗时,看 trace 找关键路径上最长的那个 span。典型分布:检索 1020%、LLM 5080%、后处理 <10%(用数据证明,不靠猜)。
  3. 对症下工具:CPU 热点用火焰图(py-spy);怀疑事件循环被阻塞就查 async def 里有没有同步调用(FastAPI 最高频事故);连接池看饱和度与获取耗时;DB 看慢查询日志 + EXPLAIN
  4. 常见陷阱(背下来很加分):
    • "LLM 慢"其实是事件循环被同步调用堵住
    • "接口慢"其实是大 payload JSON 序列化 / 同步写日志
    • "DB 慢"其实是无索引模糊查询或 ORM 循环里触发 N+1
    • "缓存没生效"其实是 key 里混了时间戳/随机值导致永不命中
  5. 纪律先测再改,改后回归——禁止猜测型优化(凭直觉加缓存往往没打到瓶颈,还把架构搞复杂)。

J. 前端工程化架构(微前端 / 自研组件库 · 结合项目作答)

这两条写在简历的「智慧中台 · 权限重构与微前端架构升级」和「门店助手 H5 · 性能优化与自研组件库建设」里,只要面试官顺着简历问前端工程化,就一定会落到这两题。 答题公式与 I 章一致:为什么要动 → 怎么定的方案 → 踩了什么坑 → 拿到了什么数 → 代价是什么。 详细展开(含 15 个微前端坑 + 12 个组件库坑 + 六阶段时间线)见《微前端迁移与自研组件库实战》专章,这里只给面试现场口径

J1. 微前端是为什么做的?拆分边界怎么定的?

答案要点

  • 一句话结论:不是为了"技术先进",是为了解耦发布权。智慧中台跑了三年,4 个业务团队共用一个仓库,改一行要等 21 分钟全量构建、发版要排队、一处改动可能白屏全站——瓶颈在组织协作,不在性能
  • 边界怎么定:按业务域(权限中心 / 资产管理 / 报表中心)拆,不按技术分层(不是 header-app/table-app 那种拆法)。判断标准三条:改动是否经常一起发生(高内聚)、跨域调用是否少而稳定(低耦合)、能不能有明确负责人(有主)。
  • 主应用只留横切能力:登录态、权限、菜单、路由分发、请求封装、埋点上报——不放任何业务组件。这条守不住,半年后主应用会变成第二个单体。
  • 首批选择:最小、最独立、负责人最配合的模块先试点,目的是踩完坑、沉淀 SOP,而不是为了迁完。
  • 为什么不直接重写:存量系统重写风险不可控,微前端的价值恰恰是路由级渐进迁移——迁一块切一块,出问题切回去。

J2. qiankun 的 JS 沙箱怎么实现的?它能拦住所有副作用吗?

答案要点(后半句是区分度):

  • 实现原理:基于 Proxy 给每个子应用造一个 fakeWindow,对 window 属性的读写都走代理;单实例场景也可用快照沙箱(记录 window 快照/diff,卸载时还原)。默认用 Proxy 沙箱支持多实例并存。
  • 它拦不住的(必答):
    1. 事件监听window.addEventListener('resize', fn) 注册后沙箱不管,卸载了回调还在跑;
    2. 定时器setInterval 没清就切走,回来再起一个,越切越快
    3. DOM / 原型副作用:往 document 上挂节点、改 body class、污染 prototype。
  • 我们的兜底:自研 useMicroAppCleanup 副作用收集器——mount 时通过统一的 onResize/onTimer/onGlobalEvent 注册,unmount 时批量清理。把"记得清理"从人的纪律变成框架的默认行为。
  • 金句沙箱管得住 window,管不住副作用。

J3. 样式隔离你怎么做的?为什么弹窗是重灾区?

答案要点

  • 方案组合(不要只答一种)
    1. 构建期 PostCSS 加子应用唯一前缀 → 主力防线,无运行时开销,覆盖 95%;
    2. 弹层统一容器 → 所有 Dialog/Drawer/Dropdown 传 getPopupContainer,别挂 document.body
    3. experimentalStyleIsolation → 兜底,不作为唯一手段。
  • 为什么不用 strictStyleIsolation(Shadow DOM):隔离最彻底,但挂到 body 的弹层会完全丢样式document 查询穿透失效、部分三方库不兼容。
  • 为什么弹窗是重灾区:隔离方案的边界都是"子应用的容器",而弹窗/抽屉/提示通过 appendTo: document.body 跑到了容器外面——容器前缀管不到它。
  • 金句样式隔离的难点不是隔离子应用内部的样式,是隔离"挂到 body 上的那部分样式"。
  • ⛓接项目:这就是我们没直接用 Shadow DOM 的实测原因。

J4. 主子应用通信怎么设计的?为什么不用共享 store?

答案要点

  • 三类场景三种手段:主→子传上下文用 props(token、userInfo、request 实例);广播型全局状态用 initGlobalState + onGlobalStateChange(主题、租户、权限变更);一次性事件通知用事件总线(要成对 on/off)。
  • 为什么不用共享 store:① 强耦合,改全局状态要动多个仓库;② 要求主子应用技术栈与版本严格同步;③ 等于把单体的耦合搬了个地方,只是从编译期变成运行期,更难排查。
  • 原则能用 props 就别用全局状态;全局状态只放"全局唯一的横切数据",绝不放业务数据。
  • 契约与版本:主子应用发版不同步是常态,事件名/字段变更必须做兼容——微前端把编译期耦合换成了运行期契约,就得用测试和监控把契约看住

J5. 迁移怎么保证不断业务?灰度和回滚怎么做的?

答案要点

  • 六阶段:调研盘点 → 边界划分 → 基座搭建 → 单点试点 → 批量迁移 → 收尾治理。
  • 不断业务的三道保险
    1. 路由级灰度/old/asset/asset 同时存在,配置中心按用户 / 租户 / 百分比切流;
    2. 每迁一个观察 3~5 天(错误率、首屏、接口成功率、投诉四个指标)再切下一个,不并行迁多个,保证出问题能归因;
    3. 一键切回老路由不依赖重新构建——这是微前端最大的安全感来源。
  • 兜底loadMicroApp 失败要降级成提示或退回老页面,绝不能让用户白屏
  • 收尾:清老路由、老构建配置、冗余依赖(这步最容易偷懒,但不做等于留了两套体系)+ 子应用维度监控看板。
  • ⛓接项目:这套"先打样再推广、用数据说话"的方法和 H2 里讲的权限重构/构建升级是同一套打法。

J6. 为什么自研组件库?从 0 到 1 怎么搭的?

答案要点

  • 先说什么时候不该自研(体现判断力):只要通用基础组件、团队小、业务还在探索期 → 用成熟库。别造一个更差的 antd。
  • 我们的三条硬理由
    1. 三库并存契约不统一——同一业务里 3 种按钮、3 套表单校验、2 套弹窗;换库解决不了"有多套"的问题
    2. 体积失控——包体 8.17MB、WiFi 加载 3.2s,三方库为通用性牺牲体积,只有自己的库能精确裁剪
    3. 业务定制被卡——门店要的扫码上传、省市区级联、离线表单,三方库改不动(fork 等于背 rebase 债)。
  • 一句话定位我们自研的不是基础组件,是"业务组件契约 + 体积控制权"。
  • 七阶段:现状盘点(引了 200 个只用了 30 个)→ 组件清单排优先级(使用频次 × 业务独特性)→ Rollup 多产物基建 + external: vue + d.tsDesign Token 先行 → 组件开发(API 契约 + 单测 + Storybook)→ 灰度替换(一次一类,可回滚) → 发布私有 npm + CI 治理。
  • 最值钱的顺序教训Token 必须先行。我们第一版先写组件后抽主题,结果颜色间距散落在 10 个组件里,抽 Token 等于重写一遍——顺序错了要付出重写的代价

J7. 组件库怎么保证按需引入?体积怎么保证不反弹?

答案要点

  • 按需引入五件事:① 构建 ESM 产物;② package.jsonsideEffects(只保留 CSS);③ 提供多入口路径引入;④ 老构建链路用 babel-plugin-import 兜底;⑤ CI 用体积门禁验证。
  • 最大的坑(必讲)"写了 ESM"不等于"能 tree-shake"。根因是 sideEffects 没配 + export * 全量导出 → 构建工具不敢裁 → 体积不降反升
  • externalpeerDependencies 必须成对:只写 external 或用带路径的引用(vue/dist/vue.esm-bundler.js)会导致外部化匹配失败 → Vue 被打进产物 → 双实例 → 响应式失效
  • 防回退三件套(这是"长效防回退机制"的真实含义):
    1. 体积门禁:PR 对比产物体积,增长 >10% 直接 fail;
    2. 依赖准入:新增运行时依赖必须评审(有没有替代?多大?能按需吗?);
    3. 性能预算:FCP/LCP/TBT/总体积写进 CI,任一超阈值即失败。
  • 金句性能优化不是一次性战役,是要把它变成流水线的一道门禁。没有门禁的优化,三个月后一定回退。

J8. 这两个改造的效益怎么量化?代价是什么?

答案要点必须带"但是"):

  • 微前端(智慧中台):构建 21min→2.3min(↓89.2%)、交付效率 ↑40%、评审评分 72→91、线上错误率 ↓65.4%;组织层收益最实在——4 个团队从"被迫同步发布"变成"各发各的";稳定性上故障半径从全站收敛到单个子应用,回滚从分钟级审批变成秒级切路由
  • 组件库(门店助手 H5):包体 8.17MB→5.29MB(↓35.3%)、依赖 ↓45.1%、WiFi 3.2s→2.14s(↓33.07%)、Lighthouse 61→89(↑45.9%)、FCP ↓20% / LCP ↓14.3% / TBT ↓47.2%;沉淀 10+ 组件 + Storybook + 私有 npm,后续新项目直接复用。
  • 代价要主动讲
    • 微前端:多一层加载(用预取 + 公共依赖共享压回来)、调试跨应用边界更难(补 trace 标识)、版本矩阵成为新的测试负担(发布清单 + 契约测试);
    • 组件库:自研等于长期维护成本(要 owner、要版本规范、要定期下线零引用组件)、组件 API 设计的返工风险(所以要先定契约)。
  • 收尾金句技术选型永远有代价——我要能说清这个代价是什么,才敢说这个方案是对的。

附:两周每日自测清单

必背题号
D1-D2A1-A4(项目主线 + 选型)
D3-D4B1-B8 + 手写题
D5-D6C1-C9(原理)
D7-D8A5-A9 + D1-D5(深挖)
D9-D10E1-E5 + F1-F2 + I1-I2(性能与并发)
D11-D12I3-I6 + J1-J4(微前端)
D13J5-J8(迁移灰度 / 组件库 / 效益与代价) + 默写架构图
D14全量重背 + 按实际面试反馈补 ⚠️