AIAI 全栈工程师面试问题清单
INTERVIEW PLAYBOOK生成时间 2026/10/03 18:00·支持目录跳转与全文搜索

AI 全栈工程师(后端 & Agent 方向)面试问题清单#

面试日期:2026 年 10 月 8 日
依据:招聘要求、当前简历、当前仓库真实代码
重点项目:英语学习平台(个人项目)

使用说明#

  • P0:极高概率,必须能在 1 分钟内给出结论,并能继续讲 3-5 分钟。
  • P1:高概率追问,需要说清设计、取舍、异常路径和代码位置。
  • P2:加分题或压力题,回答不必完美,但不能只说概念。
  • 面试官强调“会看代码 / 现场演示”,所以每个 P0 问题都应准备:入口文件、核心调用链、数据库表、失败处理、验证方式。
  • 本清单只列问题与追问方向,不替你编造项目数据。吞吐量、用户量、成本、命中率等若没有真实统计,应明确说“目前没有可靠数据”,再说明会如何测量。

最先准备的 27 题#

时间有限时,优先准备:A1-A5、B1-B5、C1-C5、D1-D4、E1-E4、F1-F2、H1、J1。它们基本覆盖首轮项目深挖的主线。

A. 自我介绍、项目真实性与 0 到 1 经历#

  1. [P0] A1:请用 2 分钟介绍你自己,为什么你适合“AI 全栈工程师(后端 & Agent)”?
  2. [P0] A2:请用 3 分钟介绍英语学习平台,用户是谁,解决了什么问题,哪些功能已经真实完成?
  3. [P0] A3:这个项目哪些代码是你独立完成的?有没有模板代码、开源代码或 AI 辅助生成的部分?你如何验证它们?
  4. [P0] A4:从立项到上线,你独立完成了哪些环节?需求、架构、编码、测试、部署和运维分别是怎么做的?
  5. [P0] A5:如果只能选一个最能体现你能力的技术难点,你选什么?为什么?
  6. [P1] A6:项目从普通英语学习网站演进成 Agent + RAG 平台的过程是什么?哪些设计是后来重构的?
  7. [P1] A7:项目目前有真实用户吗?线上环境和本地环境有什么差异?如何证明它不是只能跑 Demo 的项目?
  8. [P1] A8:你在项目中做过的一个错误技术决策是什么?后来如何发现并修正?
  9. [P1] A9:为什么把这个项目放在简历最后?它与前面三个商业项目相比,技术深度和业务价值分别在哪里?
  10. [P2] A10:如果让你删掉项目中一半的技术组件,你会保留什么、删除什么?
  11. [P1] A11:简历写“4 年工作经验”,但列出的工作经历从 2023 年 3 月开始。工作年限是如何计算的?是否还有未列出的经历?
  12. [P1] A12:你的早期经历偏前端,为什么应聘后端主力岗位?Python/FastAPI 和 Node/NestJS 各自有多少真实项目深度?

B. 总体架构与端到端调用链#

建议对应代码:README.md、docker-compose.dev.yml、docker-compose.prod.yml、server/apps/server、server/apps/ai、server-py/app。

  1. [P0] B1:请画出系统架构,并说明 Vue、业务 NestJS、Agent NestJS、FastAPI、Celery、PostgreSQL、Redis、MinIO、Chroma 各自负责什么。
  2. [P0] B2:为什么拆成业务服务、Agent 服务和 Python RAG 服务,而不是一个 NestJS 或一个 FastAPI 单体?
  3. [P0] B3:请从用户发送一条消息开始,完整讲到 SSE 返回答案并落库的调用链。
  4. [P0] B4:请从用户上传 PDF 开始,完整讲到文档变成可检索状态的调用链。
  5. [P0] B5:PostgreSQL、MinIO、Chroma、Redis 中分别存什么?哪个是事实来源?为什么缺一不可?
  6. [P1] B6:NestJS 与 FastAPI 为什么用内部 HTTP,而不是消息队列、gRPC 或直接共享数据库?
  7. [P1] B7:两个 NestJS 服务为什么拆进同一个 Monorepo?共享代码如何分层,如何避免共享库变成大杂烩?
  8. [P1] B8:如果 FastAPI 或 Chroma 暂时不可用,英语课程、登录和普通学习功能是否还能工作?降级边界是什么?
  9. [P1] B9:一次请求的 Trace ID 如何跨 Web、NestJS、FastAPI、Celery Worker 和回调传播?如何用它定位故障?
  10. [P1] B10:系统中哪些操作是同步的,哪些是异步的?划分依据是什么?
  11. [P2] B11:如果用户量增长 100 倍,最先出现瓶颈的三个地方是什么?你会按什么顺序扩容或改造?
  12. [P2] B12:为什么选择 Chroma,而不是 pgvector、Qdrant、Milvus 或 Elasticsearch?迁移成本如何控制?

C. Agent、LangChain 与 LangGraph#

建议对应代码:server/apps/ai/src/agent/agent-runner.service.ts、agent-run-lifecycle.service.ts、agent-context.service.ts、agent-rag.service.ts、agent-tools.service.ts、agent-usage.service.ts、server/apps/ai/src/llm、server/apps/ai/src/providers。

  1. [P0] C1:这个项目为什么需要 Agent,而不是“Prompt + 一次模型调用”或固定工作流?
  2. [P0] C2:一次 Agent Run 有哪些状态?如何创建、执行、取消、超时、失败和恢复?
  3. [P0] C3:你实际使用了 LangGraph 的哪些能力?createAgent、checkpoint、thread_id、recursionLimit 分别起什么作用?
  4. [P0] C4:学习教练、私人资料辅导老师、写作教练三个场景如何限制 Prompt、工具和数据范围?
  5. [P0] C5:项目有哪些工具?哪些只读,哪些会写业务数据?模型为什么不能直接操作数据库?
  6. [P1] C6:如何防止模型重复调用同一个工具、无限循环或超过工具调用上限?
  7. [P1] C7:工具超时、Agent 全局超时和递归上限有什么区别?分别如何处理?
  8. [P1] C8:为什么同一会话只允许一个运行、同一用户限制并发运行?多实例部署时如何生效?
  9. [P1] C9:为什么既保存 PostgreSQL 对话消息,又保存 LangGraph checkpoint?两者不一致时以谁为准?
  10. [P1] C10:长对话如何控制上下文?清理旧工具结果和会话摘要会不会丢失关键事实?
  11. [P1] C11:模型切换为 OpenAI-compatible Provider 时,哪些代码不需要改,哪些风险仍需重新验证?
  12. [P1] C12:Token 用量和成本如何记录?模型没有返回 usage 时为什么不能用字符数伪造?
  13. [P2] C13:这三个场景算不算 Multi-Agent?“多个角色”与“多个 Agent 协作”有什么本质区别?
  14. [P2] C14:如果要增加 Planner、Retriever、Reviewer 三个 Agent,你会如何定义共享状态、终止条件和故障边界?
  15. [P2] C15:为什么没有直接用 LangGraph 的 interrupt / Command(resume) 实现所有人工确认?现在的 Action 方案有什么优缺点?

D. Human-in-the-loop、幂等、事务与恢复#

建议对应代码:server/libs/shared/src/learning/agent-action.service.ts、study-domain.service.ts、server/apps/ai/src/agent/agent-action-recovery.service.ts。

  1. [P0] D1:为什么创建学习计划、完成学习任务必须先生成待确认 Action,而不能让模型直接执行?
  2. [P0] D2:用户连续点击两次“确认”,如何保证计划只生效一次?幂等键、唯一索引、事务和 Advisory Lock 各解决什么问题?
  3. [P0] D3:请讲清 PostgreSQL Advisory Lock 的使用方式。为什么只用数据库事务还不够?
  4. [P0] D4:Agent 服务在“用户已确认、业务执行未完成”时崩溃,重启后如何恢复?
  5. [P1] D5:Action 为什么有 PENDING、CONFIRMED、EXECUTED、FAILED、REJECTED、EXPIRED 等状态?合法状态转换是什么?
  6. [P1] D6:为什么 Action 与 AiRun、Conversation、User 都有关联?怎样防止跨用户确认别人的 Action?
  7. [P1] D7:什么是孤立 Action?什么是超时 Run?定时恢复任务如何避免多实例重复执行?
  8. [P1] D8:执行成功但回执消息写入失败怎么办?如何保证用户最终能看到可追溯结果?
  9. [P1] D9:创建学习计划失败后为什么需要补偿取消?这是事务、Saga 还是最终一致性?
  10. [P2] D10:Advisory Lock 使用 hashtext 是否存在哈希碰撞风险?如果锁粒度设计不当会出现什么问题?
  11. [P2] D11:如果数据库主从切换或事务连接中断,锁和幂等语义还能成立吗?
  12. [P2] D12:如何对并发确认、超时恢复和重复消息做自动化测试?

E. RAG、文档索引、检索与可信引用#

建议对应代码:server/apps/server/src/knowledge、server-py/app/ingestion、server-py/app/services/ingestion.py、server-py/app/vectorstore/chroma_store.py、server/apps/ai/src/agent/agent-runner.service.ts:498、server/apps/ai/src/agent/agent-rag.service.ts和 agent-tools.service.ts。

  1. [P0] E1:RAG 的完整流程是什么?请从文件校验、对象存储、解析、分片、Embedding、向量写入讲到检索和引用展示。
  2. [P0] E2:为什么在 NestJS 业务层和 Chroma 向量层做双重用户隔离?具体过滤条件是什么?
  3. [P0] E3:K1、K2 临时来源编号解决了什么问题?如何防止模型伪造不存在的引用?
  4. [P0] E4:文档状态为什么需要 UPLOADED、QUEUED、PROCESSING、READY、FAILED、DELETING?如何避免非法状态跳转?
  5. [P1] E5:为什么原始文件放 MinIO,元数据放 PostgreSQL,分片与向量放 Chroma?为什么不全部放一个数据库?
  6. [P1] E6:当前分片策略为什么是约 1200 字符、180 字符重叠?字符切分相对 Token 切分或语义切分有什么问题?
  7. [P1] E7:PDF 页码如何保留?跨页内容、表格、多栏排版、图片型 PDF 如何处理?
  8. [P1] E8:当前解析器不做 OCR。用户上传扫描版 PDF 时系统会怎样?如何增加 OCR 且控制成本?
  9. [P1] E9:为什么向量写入使用确定性 chunk ID?文档重试或重建时如何避免残留和重复分片?
  10. [P1] E10:Worker 写入 Chroma 后为什么还要核对 chunk ID 集合和数量?回调 READY 失败时怎么办?
  11. [P1] E11:检索为什么先取 topK * 3,再做最低分过滤、内容哈希去重和近重复过滤?
  12. [P1] E12:score = 1 - distance 在什么距离度量下才合理?如何确认 Chroma Collection 使用的度量一致?
  13. [P1] E13:当前个人项目主要是稠密向量检索。为什么没有 BM25/关键词混合检索和 Reranker?什么场景必须增加?
  14. [P1] E14:Embedding 模型升级后,为什么不能把新旧向量写入同一个 Collection?如何灰度重建与切换?
  15. [P1] E15:检索无结果、低相关度结果和 RAG 服务不可用时,Agent 分别应该怎样回答?
  16. [P1] E16:如何防御 Prompt Injection?为什么文档内容和工具结果都必须视为不可信数据?
  17. [P2] E17:如何构建 RAG 评测集?你会用哪些指标评估召回、排序、忠实度和引用准确率?
  18. [P2] E18:文档被删除或权限变化后,正在进行的 Agent Run 如何避免继续引用旧内容?
  19. [P2] E19:Celery 的业务幂等键应该由哪些字段组成?当前 Job ID 包含 traceId 会带来什么影响?
  20. [P2] E20:如果 Chroma 数据全部丢失,如何只依赖 PostgreSQL 和 MinIO 重建?怎样确认重建完整?

F. NestJS、FastAPI、数据库与 Redis#

  1. [P0] F1:为什么主业务使用 NestJS,而 RAG 使用 FastAPI?这是语言偏好还是职责与生态决定?
  2. [P0] F2:请结合 Prisma Schema 讲出 KnowledgeDocument、AiConversation、AiRun、AgentAction、StudyPlan、QuizAttempt 的关系。
  3. [P1] F3:项目中哪些查询依赖组合索引?为什么索引字段顺序这样设计?
  4. [P1] F4:如何发现慢 SQL?会看哪些指标,如何用 EXPLAIN ANALYZE 判断索引是否生效?
  5. [P1] F5:Prisma 事务的边界在哪里?什么时候必须用原生 SQL 或数据库锁?
  6. [P1] F6:Redis 在系统中承担队列、分布式锁、限流、nonce 和心跳。如何避免不同用途的 Key 相互污染?
  7. [P1] F7:为什么 Redis 不能作为业务事实来源?Redis 数据丢失分别会影响哪些功能?
  8. [P1] F8:FastAPI 的 async 接口中为什么会用 asyncio.to_thread?哪些库调用会阻塞事件循环?
  9. [P1] F9:Celery 任务为什么要设置软/硬超时、指数退避、最大重试、worker 内存和每子进程任务数?
  10. [P1] F10:NestJS 和 FastAPI 之间如何保持请求/响应类型一致?OpenAPI 自动生成客户端的流程是什么?
  11. [P2] F11:如果要把 PostgreSQL 拆库、读写分离,哪些事务和 Advisory Lock 设计会受影响?
  12. [P2] F12:如果用 BullMQ 替换 Celery,或用 Celery 统一全部任务,利弊是什么?
  13. [P2] F13:MySQL 与 PostgreSQL 在本项目场景中的差异是什么?为什么这里更适合 PostgreSQL?
  14. [P2] F14:如何设计一个“用户每日 Agent Token 配额”功能,要求并发安全、可审计且支持退款补偿?

G. 服务安全与权限#

建议对应代码:server/libs/shared/src/internal-auth、server-py/app/auth/hmac.py、Auth Guard、知识库服务。

  1. [P1] G1:浏览器为什么不能直接访问 FastAPI、Chroma 或 MinIO?
  2. [P1] G2:NestJS 与 Python 的 HMAC canonical request 由哪些字段组成?为什么要签原始 body 的 SHA-256?
  3. [P1] G3:timestamp 和 nonce 分别防什么攻击?Redis 不可用时为什么选择拒绝请求?
  4. [P1] G4:重试一个已签名请求时,nonce 应该复用还是重新生成?如何兼顾防重放和幂等?
  5. [P1] G5:只靠 HMAC 是否足够?内网仍需不需要 TLS、网络策略和密钥轮换?
  6. [P1] G6:上传文件如何验证扩展名、MIME、文件头、大小、哈希、配额和对象路径?
  7. [P1] G7:SSE 接口、Action 确认接口和文档上传接口如何做鉴权与限流?
  8. [P2] G8:如果 HMAC 密钥泄漏,如何在不中断服务的情况下轮换 Key ID 和 Secret?
  9. [P2] G9:如何防止用户通过 documentIds、对象 Key 或回调接口越权访问其他用户资料?
  10. [P2] G10:日志中哪些内容必须脱敏?Trace ID 为什么可以记录,但用户原始文档和密钥不能直接记录?

H. Docker、部署、健康检查与可观测性#

  1. [P0] H1:请现场说明如何从空机器启动项目。环境变量、迁移、Bucket 初始化、模型和健康检查的顺序是什么?
  2. [P1] H2:开发 Compose 和生产 Compose 有什么区别?为什么生产环境要拒绝占位密钥、HTTP 模型地址和写入型探针?
  3. [P1] H3:Dockerfile 如何做多阶段构建?怎样减小镜像、提高构建缓存命中并避免把密钥打进镜像?
  4. [P1] H4:readiness 和 liveness 有什么区别?PostgreSQL、Redis、MinIO、Chroma、Worker、Embedding Provider 分别怎么探测?
  5. [P1] H5:服务启动顺序能否只依赖 depends_on?如果依赖服务已启动但尚未可用怎么办?
  6. [P1] H6:线上发生“上传成功但一直 PROCESSING”,你会按什么顺序查看日志和数据?
  7. [P1] H7:PostgreSQL、MinIO 和 Chroma 如何备份?为什么 Chroma 可以重建但仍可能需要快照?
  8. [P1] H8:如何实现零停机或低停机发布?数据库 Migration 与新旧版本兼容如何处理?
  9. [P2] H9:如果 Agent 服务扩成多个副本,进程内 activeRuns、取消请求、定时恢复任务分别会遇到什么问题?
  10. [P2] H10:你会监控哪些指标?至少覆盖 API 延迟、模型耗时、工具成功率、队列积压、索引失败率和 RAG 命中情况。

I. Vue、SSE 与全栈交互#

  1. [P1] I1:前端为什么使用 SSE 而不是 WebSocket?SSE 断线重连、代理缓冲和超时如何处理?
  2. [P1] I2:run.started、tool.started、source、message.delta、action.required、run.completed 等事件如何保证顺序?
  3. [P1] I3:sequence、runId 和 traceId 在前端分别有什么用途?重复或乱序事件怎么处理?
  4. [P1] I4:用户关闭页面或点击停止时,浏览器、NestJS、模型调用和数据库状态分别发生什么?
  5. [P1] I5:Markdown、模型输出和引用片段如何防止 XSS?
  6. [P1] I6:Action 确认按钮如何防止重复点击?页面刷新后如何恢复待确认操作?
  7. [P2] I7:大文件上传如何显示真实进度?上传进度、解析进度、向量化进度是否应该使用同一种机制?
  8. [P2] I8:如果面试官要求把 Vue 改成 React,哪些业务与协议代码可以不动?

J. 代码审查型高风险追问#

这组问题来自当前仓库的真实实现,建议在现场演示前逐项自查。

  1. [P0] J1:你已把原来近 1900 行的 AgentRunnerService 拆成 Runner、Lifecycle、Context、RAG、Tools、Usage 和 Config。各服务的边界是什么?你如何保证拆分前后 SSE、Run 终态、Action 和引用行为不变?
  2. [P0] J2:agent-runner.service.ts:85-89 调用 AgentConfigService.integer('AGENT_MAX_CONCURRENT_RUNS_PER_USER', 2, 1),这三个值分别表示什么?为什么真正并发限制还需要 AgentRunLifecycleService.createRun() 中的两把 Advisory Lock?
  3. [P0] J3:当前自动化测试主要集中在配置和 HMAC,Agent、Action、知识库、索引与检索核心路径如何证明可靠?你准备补哪些测试?
  4. [P1] J4:当前 PDF 解析基于 pypdf 文本抽取,没有 OCR、表格恢复和复杂版式处理。简历中如何准确描述能力边界?
  5. [P1] J5:当前个人项目没有真正的 MCP Server/Client 实现。简历里的 MCP 能力来自哪里?能否现场展示另一份真实代码?
  6. [P1] J6:当前个人项目没有 Scrapy/Playwright 爬虫。若招聘方现场问反爬、限速、代理、去重和数据清洗,你如何回答而不夸大?
  7. [P1] J7:私人资料辅导和写作辅导目前都只允许知识库搜索工具。写作教练的差异主要体现在哪里?是否足以称为独立 Agent 场景?
  8. [P1] J8:代码中既有新版 Agent 会话,又保留旧版 ChatService。两套链路为什么并存?如何迁移和下线旧链路?
  9. [P1] J9:当前检索没有真正的交叉编码 Reranker,也不是混合检索。如何避免把其他项目的能力误说成这个个人项目已经具备?
  10. [P1] J10:Job ID 的生成包含 traceId,同一文档的重试可能得到不同任务 ID。系统真正依赖的幂等边界在哪里?
  11. [P1] J11:工具的 Promise 超时后,底层数据库或网络调用是否一定停止?AbortSignal 是否传到了每个依赖?
  12. [P2] J12:运行恢复依赖定时扫描。如果多个 Agent 实例同时扫描,会不会重复补偿、重复写回执或互相覆盖?
  13. [P2] J13:近重复过滤使用简单 Token 集合时,中文文本效果如何?中英文混合资料会不会误判或漏判?
  14. [P2] J14:README 里的架构描述与代码不一致时,以什么为准?你如何建立文档、OpenAPI、Migration 和实现之间的持续校验?
  15. [P2] J15:线上 Demo 如果当场打不开,你如何在 3 分钟内用本地环境、录屏、日志或架构说明继续证明项目?

K. 招聘要求中的加分项与缺口#

  1. [P0] K1:你说做过 MCP。请解释 MCP 的 Host、Client、Server、Tool、Resource、Prompt 和 Transport,并讲一个真实实现。
  2. [P1] K2:普通 Function Calling 与 MCP Tool Calling 有什么区别?什么情况下没必要引入 MCP?
  3. [P1] K3:如果把本项目的学习工具暴露为 MCP Server,你会暴露哪些 Tool,哪些绝不能暴露?如何传递用户身份?
  4. [P1] K4:MCP Tool 的输入 Schema、超时、权限、审计、幂等和人工确认如何设计?
  5. [P1] K5:你在商品平台中如何设计 SPU、SKU、价格和库存模型?为什么价格与库存不能只存在向量库?
  6. [P1] K6:导购 Agent 如何把预算、品牌、规格、库存等硬约束与风格、用途等软偏好结合?
  7. [P1] K7:实时库存变化时,如何避免 Agent 推荐过期商品?为什么回答前要再次查询?
  8. [P1] K8:如果让你用 Playwright 采集一个电商站点,如何处理登录、分页、懒加载、限速、失败重试和断点续爬?
  9. [P2] K9:Scrapy 与 Playwright 如何选型?什么时候应组合使用?
  10. [P2] K10:遇到 robots.txt、网站条款、个人信息和版权内容时,爬虫项目的合规边界是什么?
  11. [P1] K11:请用英文解释一次 RAG 查询链路,或用英文介绍项目架构。
  12. [P2] K12:给你一页英文 LangGraph/FastAPI/Chroma 文档,你会如何快速确认版本、提取约束并落到代码?
  13. [P1] K13:如果面试官打开 GitHub Commit History,你会选择哪三个提交证明项目是持续迭代而非一次性生成?

L. 其他简历项目的交叉验证问题#

  1. [P1] L1:都肆商圈 AI 商品平台中,商品解析工作流和导购 Agent 的边界是什么?
  2. [P1] L2:Qwen3-VL 提取商品属性时,置信度如何定义?冲突字段和低置信度字段如何交给商户确认?
  3. [P1] L3:商品混合检索的 SQL 过滤、向量召回和候选重排具体顺序是什么?为什么这样排?
  4. [P1] L4:危化安全场景中,为什么关键合规判断交给规则引擎,而不是大模型?
  5. [P1] L5:法规 RAG 如何按发布机构、生效日期、版本状态和企业范围过滤?遇到条款冲突怎么办?
  6. [P1] L6:危化项目中的“多个独立 Agent”如何协作?它与个人项目的多场景单 Agent 有何不同?
  7. [P1] L7:OA Agent 为什么只返回字段 Patch,而不返回整份表单?前端如何展示 Diff 并避免覆盖用户修改?
  8. [P1] L8:Java 业务系统、Python Agent 和 Vue 之间如何传递身份与权限?为什么 Agent 不直接写业务数据库?
  9. [P2] L9:四个项目的时间存在交叠时,你如何清楚解释工作项目、个人项目、版本迭代和个人投入比例?
  10. [P2] L10:商业项目无法展示源码时,你用什么证据证明自己的真实贡献,同时不泄露公司机密?

M. 现场编码与系统设计预测题#

招聘说明明确会用真实编程题替代泛聊,以下题型需要至少手写或独立实现一遍。

  1. [P0] M1:用 FastAPI 实现一个支持分页、过滤、参数校验和统一错误响应的商品查询接口。
  2. [P0] M2:用 NestJS 实现一个需要 JWT 鉴权、Redis 限流和幂等键的创建接口。
  3. [P0] M3:设计并实现一个 Agent Tool:输入 Zod/Pydantic Schema,调用业务接口,处理超时、重试并记录审计。
  4. [P0] M4:实现一个 SSE 流式接口,正确处理客户端断开、心跳、错误和资源清理。
  5. [P1] M5:实现 HMAC 签名与验签,并防止时间偏差和 nonce 重放。
  6. [P1] M6:实现文档切片函数,要求保留页码、支持重叠、优先在段落或句子边界切分。
  7. [P1] M7:给定一批向量检索结果,实现阈值过滤、哈希去重、近重复过滤和 topK 截断。
  8. [P1] M8:实现一个并发安全的“确认后执行”接口,重复确认只能产生一次业务结果。
  9. [P1] M9:设计商品 SPU/SKU/价格/库存表,并写出“满足硬约束且有库存”的查询和索引方案。
  10. [P1] M10:实现 Celery 任务的指数退避重试,并区分永久错误与瞬时错误。
  11. [P1] M11:定位一个慢查询:阅读执行计划、提出索引、解释索引为何生效或失效。
  12. [P1] M12:给出一段存在越权风险的 RAG 检索代码,修复 owner 与 document allowlist 校验。
  13. [P2] M13:设计一个支持多租户、版本化 Embedding、灰度重建的向量索引系统。
  14. [P2] M14:设计一个电商选品 Agent,包含需求澄清、工具调用、硬约束校验、库存复查和人工确认。
  15. [P2] M15:在不依赖 LangChain 的情况下手写一个最多执行 5 步的 Agent Loop,并处理重复 Tool Call。

N. 反问面试官#

  1. 团队当前 Agent 产品的核心业务场景是什么,主要困难在模型效果、工具稳定性还是业务数据治理?
  2. 当前使用 LangChain/LangGraph/MCP 的深度如何?是已有生产系统,还是准备从 0 到 1 建设?
  3. 后端 Python/FastAPI 与 Node/NestJS 的职责如何划分?这个岗位入职后的主要技术栈是什么?
  4. 编程挑战更偏业务接口、Agent 工作流、RAG,还是电商数据采集?允许使用官方文档吗?
  5. 团队如何评估 Agent 效果?是否有离线评测集、Trace 平台、人工反馈和线上指标?
  6. 电商数据的来源、更新频率、库存一致性和合规要求分别是什么?
  7. 这个岗位前三个月最希望交付的成果是什么?判断做得好的标准是什么?

面试前必须完成的自查#

  • 能在白板上画出两条主链路:Agent 问答链路、文档索引链路。
  • 能现场打开并讲解以下代码:Agent 入口、一个只读 Tool、一个写操作 Action、RAG 检索、Celery 入库、Prisma Schema、Compose 服务拓扑。
  • 修复或准备解释 J2 的并发配置边界问题,并实际启动一次 Agent 服务。
  • 准备一个可重复的演示脚本:登录 → 上传资料 → 等待 READY → 提问并显示引用 → 创建学习计划 → 确认执行 → 查看数据库结果。
  • 准备失败演示:无权限文档、扫描 PDF、Chroma 不可用、重复确认、用户取消 Run。
  • 不把“其他商业项目中做过的混合检索、重排、Multi-Agent、MCP”说成个人项目当前仓库已经实现。
  • 为没有真实数据的指标准备诚实回答,并说明采集方法,不临场编造 QPS、准确率、用户量或成本。

全部问题参考答案#

使用原则:下面是可直接口述的答案底稿,不要逐字背。个人项目答案按当前仓库实现编写;商业项目按简历描述编写,无法从代码或简历证明的数字一律不虚构。

A. 自我介绍、项目真实性与 0 到 1 经历答案#

A1#

我主要做 AI 应用开发,能力覆盖 Vue 前端、NestJS/FastAPI 后端以及 LangGraph Agent 和 RAG。工作项目里做过商品解析与导购 Agent、危化安全助手和 OA 助手;个人项目则独立完成了英语学习平台,从前端、业务接口、Agent 编排到私人知识库和 Docker 部署都有完整代码。我适合这个岗位的原因不是只会调用模型 API,而是能把权限、事务、幂等、异步任务、人工确认和故障恢复一起落到可运行系统里。

A2#

英语学习平台面向需要课程学习、词汇复习和私人资料辅导的用户。已完成登录、课程、词汇测验、薄弱词复习、学习计划、三类 Agent 场景、PDF/Markdown/TXT 上传、异步索引、RAG 检索和可信引用。系统能读取用户真实学习数据,生成待确认计划,并在测验后更新掌握度与复习时间,形成“学习数据—计划—练习—反馈”的闭环。

A3#

个人项目的需求拆分、架构整合、核心业务代码、Agent/Action/RAG 链路、部署配置和联调由我完成。框架、LangChain/LangGraph、Prisma、Chroma 等属于开源依赖,部分代码会使用 AI 辅助生成,但我不会把生成等同于完成:我会逐段审查类型、权限和事务边界,通过编译、测试、实际链路演示、数据库结果和失败场景验证。面试时我可以现场定位关键方法并解释为什么这样设计。

A4#

我先从课程和词汇学习需求建立 Vue、NestJS、PostgreSQL 基础系统,再加入学习数据工具和 Agent;之后把知识库拆成 FastAPI/Celery/Chroma 服务,并补上 MinIO、HMAC、异步回调、Action 确认和恢复。测试目前以配置、安全函数和关键链路手工回归为主,核心 Agent/RAG 自动化测试仍需补强。部署使用 Docker Compose、环境变量、迁移、健康检查和日志排查,个人项目具备从 0 到 1 的完整过程,但不夸大成大规模生产经验。

A5#

我会选“高影响操作的人工确认和故障恢复”。难点不在弹一个确认框,而是模型可能重试、用户可能双击、服务可能在确认后崩溃。因此我把写操作先落为 AgentAction,用幂等键、唯一索引、事务和 PostgreSQL Advisory Lock 保证一次执行,再用状态机、定时扫描、补偿取消和回执补写处理异常。这最能体现我把 Agent 接入真实业务的能力。

A6#

项目最初是 Vue + NestJS 的普通课程和词汇平台;第二阶段把学习画像、弱项、测验和计划封装成工具;第三阶段加入 LangGraph 会话、SSE 和 Action;第四阶段拆出 Python RAG 服务、Celery、MinIO 与 Chroma。后续重点重构了用户级文档隔离、引用白名单、运行状态恢复和跨服务签名。演进原则是先解决真实业务问题,再为异步、并发和故障增加边界,而不是一开始堆组件。

A7#

目前它首先是可访问、可演示的个人项目,不把它描述成已有大规模真实用户的商业系统。证明不是静态 Demo 的方式包括:完整仓库与提交历史、真实 PostgreSQL/Redis/MinIO/Chroma 依赖、数据库迁移、异步 Worker、失败状态、恢复任务和线上演示链路。本地可以暴露更多调试能力,线上使用更严格的密钥、来源、网络和资源配置;用户量、QPS、成本没有可靠数据时我会明确说没有。

A8#

一个典型错误是早期更关注“模型能回答”,低估了写操作和异步索引的一致性。直接让工具产生业务结果在重试和中断下容易重复执行,所以后来增加 AgentAction 状态机、幂等键、事务锁、补偿和恢复;知识库也从“任务投递成功即认为成功”改为 Worker 写入后校验 chunk 集合与数量,再回调 READY。这个调整让我从 Demo 思维转向可恢复业务流程。

A9#

放在最后是因为它是持续迭代的个人项目,不是时间最早或商业规模最大的项目。商业项目的价值在真实行业流程、团队协作和业务约束;个人项目的价值在于我能公开代码、完整演示,并独立解释每个模块。技术深度集中在 Agent、RAG、人工确认、跨语言异步链路和一致性,业务价值则是验证个性化学习闭环。

A10#

我会保留 Vue、一个 NestJS 服务、PostgreSQL、Redis、对象存储和一个可替换的向量检索层,因为它们覆盖核心业务、状态、文件和异步协作。低流量阶段可以把业务与 Agent NestJS 合并,把 Celery 简化为轻量队列,甚至先用 pgvector 替代独立 Chroma;复杂监控和部分恢复任务也可后置。删除依据是业务边界和运维成本,不是技术是否流行。

A11#

这题必须按真实经历回答。当前简历展示的是 2023 年 3 月到 2026 年 7 月,按自然时间约 3 年 4 个月,单凭已列经历不足以严谨支撑“4 年”。如果 2023 年前确有相关全职、实习或外包经历,应补上公司、时间和职责;如果没有,面试前应把年限改成“3 年+”,不要现场用学习时间凑工作年限。

A12#

我的早期优势是前端和业务交互,但近几年的实际工作已经覆盖 NestJS/FastAPI、数据库、Redis、异步任务和 Agent 工具。Node/NestJS 在个人项目中有可展示的业务、Agent、事务和 SSE 深度;Python/FastAPI 在商业 AI 服务和个人 RAG 服务中用于工作流、解析、Celery 与向量检索。我会把自己定位为能做前端但以后端和 Agent 集成为主,而不是声称自己在所有后端领域都同样资深。

B. 总体架构与端到端调用链答案#

B1#

Vue 负责页面、会话流和确认交互;业务 NestJS 负责认证、课程、学习数据、文档元数据和上传;Agent NestJS 负责 LangGraph、模型、工具、SSE、Run 和 Action;FastAPI 提供内部索引与检索接口;Celery 执行解析、分片和向量化;PostgreSQL 是用户、业务状态和审计事实源;Redis 承担队列、nonce、限流/协调;MinIO 存原始文件;Chroma 存分片向量与检索元数据。

B2#

拆分是按故障和技术生态边界。业务服务即使模型或向量库不可用也应继续提供登录、课程和测验;Agent 服务的模型超时、流式连接和恢复逻辑独立;Python 侧更适合文档解析、Embedding、Celery 和向量生态。代价是跨服务合约、签名、追踪和部署复杂度增加,所以在更小规模产品里我会先单体,出现明确边界后再拆。

B3#

前端携带 JWT 和 traceId 请求 Agent SSE;服务校验用户、会话和 documentIds,事务内创建 AiRun,并用对话锁限制并发。Runner 加载历史和 checkpoint,按场景构建 Prompt 与工具,LangGraph 流式执行;工具事件、来源和 token 增量按 sequence 发给前端。结束时服务校验引用、保存助手消息、更新 AiRun 用量与状态;断开、取消、超时或异常则写相应终态并清理运行上下文。

B4#

浏览器把文件传到业务 NestJS;服务校验登录用户、扩展名/MIME/大小/配额并计算 SHA-256,把原文件写 MinIO、元数据写 PostgreSQL 为 UPLOADED。随后签名调用 FastAPI 投递确定性 Celery 任务并转为 QUEUED/PROCESSING。Worker 取文件、解析页码、分片、批量 Embedding、写入 Chroma并核对 ID 和数量,最后通过 HMAC 回调业务服务置 READY;失败时清理本次向量并写 FAILED。

B5#

PostgreSQL 保存身份、权限、文档状态、会话、运行和业务记录,是事实来源;MinIO 保存可重新处理的原始文件;Chroma 保存可重建的检索索引;Redis 保存队列与短期协调状态,不是长期事实源。严格说不是每项对所有功能都“缺一不可”:Chroma/RAG 故障不应影响课程功能,但会影响知识库问答;Redis 丢失会影响任务和防重放;原始文件与 PostgreSQL 才是重建索引的基础。

B6#

当前 NestJS 调 FastAPI 是请求/响应明确、频率可控的内部 HTTP,便于 OpenAPI、超时、签名和独立部署。耗时索引再由 FastAPI 转为 Celery 异步任务。共享数据库会破坏服务边界,gRPC 在当前规模增加协议和运维成本,消息队列适合无需即时结果的命令;如果吞吐和事件解耦需求显著上升,我会把索引请求改成可靠事件或 Outbox。

B7#

同一 Monorepo 便于共享 DTO、枚举、鉴权基础设施、Prisma 客户端和构建配置,也能原子修改跨服务合约。共享库只放稳定、无业务方向依赖的契约和基础设施;具体编排留在各应用,禁止共享库反向依赖应用。通过明确目录、导出入口、依赖规则和代码所有权避免 shared 变成大杂烩。

B8#

可以。登录、课程、词汇、测验和已有学习数据依赖业务 NestJS 与 PostgreSQL,不应被 FastAPI/Chroma 拖垮。受影响的是文档索引、私人资料检索以及依赖 RAG 的回答;系统应返回“知识库暂不可用”或无资料模式,而不是伪造检索结果。创建计划等不依赖 RAG 的工具仍可按场景工作。

B9#

入口接受合法 traceId,否则生成 UUID;NestJS 日志、AiRun、内部 HTTP Header、Celery 参数和回调都携带它。排障时先用 traceId 找入口请求和 runId,再看工具审计、FastAPI 投递、Celery job、文档状态和回调。traceId 只负责关联,不承担认证;跨服务请求仍需 HMAC、时间戳和 nonce。

B10#

用户必须立即得到结果且耗时短的操作同步执行,例如鉴权、元数据查询、创建 Run 和检索;解析、Embedding、批量写向量等耗时、可重试操作异步执行。划分依据是延迟、失败重试、资源占用和用户是否必须等待,而不是语言。异步任务必须有持久状态、幂等键、重试策略和最终回调。

B11#

最先可能是模型并发/成本、Celery+Embedding 吞吐和 PostgreSQL 热点事务。先用指标确认,再为 Agent 加队列、配额和横向扩容;将索引 Worker 按 CPU/IO/Provider 限额扩容并批处理;优化数据库索引、连接池和热点锁,必要时拆读流量。Chroma 容量和 Redis 也要监控,但不先凭感觉重构。

B12#

Chroma 适合个人项目快速搭建、Python 集成简单且元数据过滤够用;它不是所有规模的最优解。pgvector 能减少组件并强化事务一致性,Qdrant/Milvus 更适合独立向量服务和规模化检索,Elasticsearch 适合关键词与混合检索。迁移成本通过封装检索接口、保留原文件和版本化 chunk 元数据控制,业务层不依赖 Chroma 私有返回结构。

C. Agent、LangChain 与 LangGraph 答案#

C1#

固定问答可以一次模型调用,但本项目需要根据场景读取学习画像、弱词、课程、知识库,并在信息不足时多步选择工具;写操作还要转成待确认 Action。因此需要受控 Agent Loop。并不是所有请求都必须 Agent 化:确定性校验、掌握度计算、权限和事务仍由普通代码完成。

C2#

AiRun 主要经历 RUNNING 到 COMPLETED、FAILED、CANCELLED 或 TIMED_OUT。创建时在事务里校验会话并限制并发;执行中有 AbortController、全局超时、工具超时和心跳/状态检查;用户取消会中止本机活动运行并落 CANCELLED;启动和定时扫描会把陈旧 RUNNING 收敛到终态并拒绝孤立 Action。恢复不是继续生成半段文本,而是把业务状态收敛到可解释结果。

C3#

createAgent 负责模型、工具和循环编排;Postgres checkpoint 保存图状态,thread_id 使用 conversationId 让同一会话延续上下文,run_id 用于运行关联;recursionLimit 是图执行的最后安全上限,防止无限循环。项目没有把 LangGraph 当黑盒,外层仍维护 AiRun、消息、配额、Action 和 SSE 协议。

C4#

学习教练可读取学习画像、弱词、测验并准备计划;私人资料辅导强调只依据当前用户白名单文档并返回引用;写作教练使用不同系统提示词关注结构、语法和改写,但当前工具边界仍主要是知识库检索。每个场景都有工具白名单、Prompt 约束和 document scope,身份永远由服务端注入,不由模型传入。

C5#

只读工具包括学习画像、课程、薄弱词、复习任务、测验结果和知识库搜索;生成测验主要创建临时测验会话;创建计划、完成任务属于写操作,必须先创建 Action。模型不能直接访问数据库,因为它不能可靠承担鉴权、事务、幂等、状态校验和审计,工具层才是安全边界。

C6#

每次运行统计 toolSteps,超过上限返回 TOOL_LIMIT_REACHED;用工具名和规范化参数形成 fingerprint,拦截重复调用;创建计划和生成测验等设为 singleton,同一轮复用第一次结果;外层还有 LangGraph recursionLimit 和全局超时。提示词只是一层引导,真正限制必须在执行器代码里。

C7#

工具超时限制单个依赖,超时后 Agent 可降级或结束;全局超时限制整次 Run,触发 AbortController 并把运行置 TIMED_OUT;递归上限限制图步骤数,防止模型反复工具调用。三者分别控制局部延迟、用户请求总预算和逻辑循环,不能互相替代。Promise 超时也不等于底层调用必然取消,依赖需真正接收 AbortSignal。

C8#

同一会话只允许一个 RUNNING,避免消息、checkpoint 和 Action 顺序冲突;同一用户限制并发,控制模型成本和资源滥用。不能只靠进程内 Map,多实例下事务内计数、唯一/部分索引或 PostgreSQL Advisory Lock 才能全局生效;进程内 activeRuns 主要用于本机取消和快速状态管理。

C9#

PostgreSQL 的 AiMessage/AiRun 是产品可见记录和审计事实,checkpoint 是 LangGraph 的内部执行状态。两者目的不同:页面历史和业务结论以业务表为准,checkpoint 只用于图上下文;如果不一致,不应把 checkpoint 直接展示为已成功消息,而应通过恢复流程收敛 Run,并可重建或清理 checkpoint。

C10#

读取最近有限数量消息,达到阈值后清理旧工具大结果、保留最近关键结果,并生成会话摘要控制 token。摘要要保留用户目标、约束、已确认动作和引用标识,原始消息仍在 PostgreSQL 可审计。摘要是模型上下文的压缩层,不是删除事实源;对关键业务状态应重新调用工具,而不是相信旧摘要。

C11#

通过 ChatModelProvider 封装 OpenAI-compatible baseURL、模型名、密钥、超时和重试,Agent、工具和大部分 SSE 代码不用改。仍需重新验证 tool calling 格式、流式 chunk、usage、上下文长度、JSON/结构化输出、取消信号和错误码;“接口兼容”不代表语义、质量和计费完全兼容。

C12#

优先读取 Provider 返回的 input/output token usage,保存到 AiRun 并按已配置单价计算成本;未同时配置输入输出单价时不伪算。字符数只能做容量估计,受语言、Tokenizer、工具消息和 Provider 计费规则影响,不能冒充真实 usage。缺失时应记 null/unknown,并在监控中区分。

C13#

个人项目更准确地说是“一个受场景约束的 Agent,具有多套角色 Prompt 和工具白名单”,不是多个自治 Agent 协作。Multi-Agent 通常意味着多个独立角色/节点各自有上下文、工具和输出契约,并通过共享状态、路由或消息协作。商业危化项目中的独立 Agent 编排更符合 Multi-Agent。

C14#

共享状态只放任务目标、用户约束、候选证据、计划草稿、评审结论、错误和 stepCount,并使用结构化 Schema。Planner 产步骤,Retriever 只返回证据,Reviewer 检查覆盖、冲突和引用;终止条件包括通过评审、无可用证据、达到步骤/时间/成本上限。每个节点要有超时、重试和可降级边界,写操作仍走人工确认。

C15#

没有把所有确认都绑在 LangGraph interrupt 上,是因为 Action 需要跨页面刷新、服务重启、过期、审计、恢复和业务事务,独立数据库状态机更容易与前端及业务服务集成。优点是确认生命周期与一次图执行解耦、可恢复、可查询;缺点是需要自己实现状态转换、补偿和回执。若流程主要发生在单个图内,interrupt/resume 会更自然。

D. Human-in-the-loop、幂等、事务与恢复答案#

D1#

创建计划会替换旧计划,完成任务会改变学习状态,模型可能误判或被注入,因此必须让用户看见影响后确认。Agent 只准备参数和摘要,服务端再次校验身份与当前状态,确认后才执行。这样把模型建议权和业务决定权分开,并留下可审计 Action。

D2#

幂等键标识同一次业务意图;唯一索引保证数据库中不能出现两条同键记录;事务保证检查、状态改变和业务写入原子化;Advisory Lock 让同一 Action/用户计划的并发请求串行。确认时再用条件更新 WHERE status=PENDING 抢占状态,最终只有一个请求能执行,其他请求返回已执行结果或状态冲突。

D3#

项目用 pg_advisory_xact_lock(hashtext(业务锁名)),锁随事务结束自动释放。普通 Read Committed 事务并不会自动阻止两个请求同时“先查不存在、再写入”,也不能自然锁住尚不存在的业务行;Advisory Lock 用业务键建立串行区。锁顺序必须统一、粒度不能过大,并仍需唯一约束和条件更新兜底。

D4#

用户确认后先把 Action 从 PENDING 原子改为 CONFIRMED,再执行具体业务。若服务在中间崩溃,定时恢复会扫描长时间 CONFIRMED 且没有有效 lease 的记录,重新调用幂等的 executeConfirmed;执行尝试有次数和时间租约,成功后写 EXECUTED 与 result。连续失败进入 FAILED,并取消仍处于待确认状态的计划。

D5#

正常路径是 PENDING → CONFIRMED → EXECUTED;用户拒绝是 PENDING → REJECTED;超时是 PENDING → EXPIRED;确认后永久错误或多次失败是 CONFIRMED → FAILED。EXECUTED、FAILED、REJECTED、EXPIRED 都是终态,重复请求只能返回原状态或拒绝,不能随意倒退。

D6#

User 用于所有权,Conversation 用于对话生命周期和展示,AiRun 证明操作来自一次有效模型运行。确认接口用 actionId + userId 查询,再验证会话仍属于用户、Run 与会话匹配且已成功完成,防止猜到 ID 后跨用户确认。数据库外键保证关联完整,服务端身份来自 JWT 而不是请求体。

D7#

孤立 Action 是仍为 PENDING,但所属对话已归档、Run 已失败/取消/超时或 runId 丢失;超时 Run 是超过运行预算仍停在 RUNNING。恢复任务通过条件查询、事务锁和 updateMany where status=旧状态 抢占,多个实例只有一个更新成功。操作本身还要幂等,不能只依赖定时器互斥。

D8#

业务结果与 Action 的 EXECUTED 在同一事务写入,并尝试写一条带 actionId 的助手回执。若历史数据存在 EXECUTED 但缺回执,ensureActionReceipt 会查询并补写;回执查询以 JSON 中的 actionId 去重。用户刷新后也能从 Action 状态和 result 恢复结果,不把一次 SSE 是否送达当成事实。

D9#

准备阶段已经创建 PENDING_CONFIRMATION 的 StudyPlan,但 Action 可能拒绝、过期或执行失败,所以需要把该计划取消并记录 compensatedAt。这不是单个数据库事务能覆盖的完整生命周期,更接近带补偿的 Saga/最终一致性;每一步自身仍使用本地事务。补偿也必须幂等,并有定时任务修复遗漏。

D10#

hashtext 将字符串压缩成较小整数,理论上有碰撞,两个无关业务键可能被误串行,通常影响吞吐而不是数据正确性,因为还有唯一约束。更严谨可使用 64 位稳定哈希或双整数锁键并区分命名空间。锁太粗会造成热点,太细会漏掉竞争,锁顺序不一致还可能死锁。

D11#

事务级锁绑定当前主库连接,连接断开会自动释放;事务未提交的修改回滚。主从切换后不能假设旧锁跨主库保留,所以正确性必须依赖已提交状态、唯一约束和幂等重试;所有需要锁和写入的操作必须路由同一写主库,不能在只读副本上做判断后直接写。

D12#

用集成测试并发发送多次 confirm,断言只有一份 StudyTask、一个 EXECUTED Action 和一个回执;用可控时钟制造过期 PENDING、陈旧 CONFIRMED 和超时 RUNNING,再并发启动多个恢复器;注入“业务成功后回执失败”等故障验证补写。测试要检查数据库最终状态,而不仅是 HTTP 状态码。

E. RAG、文档索引、检索与可信引用答案#

E1#

业务服务先校验用户、类型、大小和配额,计算 SHA-256,把原文件放 MinIO、元数据放 PostgreSQL;FastAPI/Celery 获取对象后按格式解析并保留 PDF 页码,做重叠分片和批量 Embedding,再把向量、ownerId、documentId、chunkId、页码等写 Chroma。检索时业务层先生成当前用户 READY 文档白名单,向量层再次按 owner 和 documentIds 过滤;结果经阈值、去重后编号为 K1/K2,模型只能引用本轮编号,服务端再校验后展示文件名、页码、分数和片段。

E2#

NestJS 层校验 documentIds 是否属于当前 JWT 用户且状态 READY,防止模型或请求把别人文档带入;Chroma 查询再使用 ownerId、private visibility 和 document allowlist 过滤,防止业务层遗漏或内部调用越权。双层隔离属于纵深防御,用户身份由服务端签名传递,不能相信模型参数或浏览器自报 ownerId。

E3#

K1/K2 是本轮检索结果的临时、短小引用 ID,避免把内部 chunk ID 暴露给模型和前端。服务端维护 K编号 → 真实来源 映射,解析模型输出时只接受映射中存在的编号,伪造的 K99 会被过滤。最终展示信息取自服务端结果,不取模型自己写的文件名或页码。

E4#

UPLOADED 表示原文件和元数据已落地;QUEUED 表示已投递;PROCESSING 表示 Worker 正处理;READY 表示索引校验完成;FAILED 表示失败可重试;DELETING 表示正在跨存储删除。状态转换应由条件更新实现,例如只有预期旧状态才能进入下一状态;任务和回调携带文档 ID、版本/任务标识,迟到回调不能覆盖删除或新一轮重建状态。

E5#

MinIO 适合大文件和流式对象操作,PostgreSQL 适合事务、权限、状态和审计,Chroma 适合向量近邻检索。全部塞入关系库会增加大对象与向量负担,全部放向量库又无法可靠管理权限和业务事务。多存储的代价由明确事实源、状态机、幂等任务、对账和可重建流程承担。

E6#

约 1200 字符兼顾语义完整性和召回粒度,180 字符重叠降低边界处信息断裂,这是一组工程默认值而非普适最优。字符切分不了解模型 Token,在中英文混合、代码、表格和长句中可能过大或过碎;语义切分质量更好但成本与复杂度更高。应通过真实问答集比较 chunk 大小、重叠、召回率和生成忠实度后调参。

E7#

PDF 解析时按页提取文本,chunk 元数据保留 page,引用可展示页码。跨页段落可能被拆开,重叠只能部分缓解;多栏和表格依赖 PDF 内部文本顺序,可能错乱;图片型 PDF 几乎抽不到文本。当前能力边界应明确为“文本型 PDF 基础抽取”,复杂版式需要布局解析、表格工具和 OCR。

E8#

扫描版 PDF 可能提取为空或极少文本,任务应失败并提示“未检测到可索引文本”,不能标成 READY。增加 OCR 时可先检测每页文本密度,只对低密度页做图像渲染和 OCR;限制页数/分辨率、批量处理、缓存文件哈希结果并设置配额。高价值文档可用更强模型,普通文档使用本地 OCR 控制成本。

E9#

chunk ID 应由 documentId、indexVersion、页码/顺序和规范化内容哈希稳定生成。相同版本重试会覆盖或识别同一批 chunk,不会无限新增;重建前后比较期望 ID 集合,成功后删除该文档旧版本或不在新集合中的残留。只靠随机 UUID 无法可靠对账和清理。

E10#

向量库批量写接口返回成功并不证明每个 chunk 都存在,因此写完后按 documentId/indexVersion 读取实际 ID,与期望集合和数量比较,缺失、多余都视为失败并清理。若向量已完整但 READY 回调失败,Celery 可重试同一幂等任务/回调;业务侧也可通过对账确认后修复状态,迟到回调必须检查当前版本和状态。

E11#

先取 topK * 3 是给后处理留候选空间;最低分过滤去掉明显无关结果,内容哈希去掉完全重复,近重复过滤避免同一段重叠内容占满名额,最后截断 topK。这样提高来源多样性,但倍率和阈值要用评测集校准,不能保证所有查询都最优。

E12#

1 - distance 只有在距离范围和语义明确时才可作为分数,例如归一化后的 cosine distance;若是 L2 或未归一化距离,结果可能小于 0,也不能解释为概率。必须在创建 Collection 时明确 HNSW space,读取/测试实际距离范围,并把“相关度”标成排序分数而非置信概率。

E13#

个人项目当前以稠密向量检索为主,优点是实现简单、适合语义问答;没有把商业项目中的混合检索和 Reranker 说成已落地。法规编号、产品型号、专有名词和精确短语对 BM25 更敏感,候选多且相近时需要 Reranker。演进路径是向量与关键词并行召回、RRF 融合,再对少量候选交叉编码重排。

E14#

不同 Embedding 模型的维度和向量空间不可比较,混写会导致错误距离或直接报错。为模型名、维度、分片规则和 indexVersion 建独立 Collection/命名空间;后台从 MinIO 重建新版本,核对文档与 chunk 数,灰度让少量查询读新索引,质量通过后原子切换 active version,最后延迟删除旧索引。

E15#

无结果时明确说私人资料中没有找到依据,并建议换关键词或上传资料;低相关度时说明证据不足,只给谨慎概括或不回答事实性结论;RAG 服务不可用时说明暂时故障,可对不依赖资料的问题使用通用知识,但不能声称“根据你的资料”。三种情况要有不同状态和监控,不能都返回空数组然后让模型猜。

E16#

系统 Prompt 明确文档和工具结果只是数据,里面的“忽略系统指令”等文本不能改变权限或工具规则;工具白名单、owner 过滤、参数 Schema、引用白名单和写操作确认都由代码执行。输出还要做 XSS 清洗,敏感工具永远不由文档触发。Prompt 防护只能降低风险,真正安全边界必须在服务端。

E17#

从真实文档构造问题、标准相关 chunk、不可回答问题、跨页问题和对抗注入样本。检索看 Recall@K、MRR/nDCG、去重后覆盖和延迟;生成看 faithfulness、answer relevance、拒答准确率;引用看 citation precision/recall、页码和片段一致性。自动 LLM 评审要抽样人工复核,并按文档类型与语言分桶。

E18#

每次工具调用都重新由业务服务生成 READY 文档白名单,向量层再次过滤;删除先把状态改为 DELETING,使新检索立即不可见,再删向量和对象。已经召回到某个 Run 的内容存在短暂竞态,高敏感场景可在最终落库/展示前再次校验 documentId 权限,失效来源从回答和引用中剔除或中止回答。

E19#

业务幂等键应由 documentId、ownerId、内容 SHA-256、indexVersion 和任务类型组成,必要时加入处理参数版本;traceId 只用于追踪,不应决定同一业务任务是否相同。Job ID 包含 traceId 会让同一文档每次重试成为不同任务,队列层无法自然去重,所以真正幂等必须落在数据库状态、确定性 chunk ID 和向量写入/清理逻辑。

E20#

PostgreSQL 提供所有应索引文档、owner、状态、SHA 和版本,MinIO 提供原文件。把文档状态转为重建中,按确定性任务重新解析、分片、Embedding 并写新 Collection;完成后逐文档比较期望/实际 chunk ID 和数量,再核对 READY 文档覆盖率,最后切换索引版本。抽样查询与离线评测用于确认不仅“数量对”,语义结果也正常。

F. NestJS、FastAPI、数据库与 Redis 答案#

F1#

NestJS 负责类型化业务模块、认证、Prisma 事务、SSE 和前端共享 TypeScript 契约;FastAPI 负责 Python 文档解析、Embedding、Celery 与向量生态。选择来自职责与库生态,不是单纯语言偏好。若团队统一 Python 或规模很小,也可以用 FastAPI 承载更多业务;关键是边界清楚而非双栈本身。

F2#

User 是所有私有数据的根;KnowledgeDocument 属于 User,提供 Agent 检索白名单;AiConversation 属于 User,下面有 AiMessage 和 AiRun;AgentAction 同时关联 User、Conversation 和可选 Run,用于待确认写操作;StudyPlan 属于 User,确认激活后生成多个 StudyTask;QuizAttempt 属于 User,可关联 Conversation,提交后更新 WordBookRecord。删除策略和唯一索引以数据库文档为准。

F3#

典型查询包括 AiRun(conversationId,status,createdAt) 查运行历史/陈旧运行,AgentAction(userId,status,expiresAt) 查待处理与过期操作,KnowledgeDocument(userId,status,createdAt) 查 READY 文档,StudyTask(userId,status,scheduledFor) 查每日任务,WordBookRecord(userId,nextReviewAt) 查到期复习。等值过滤字段放前面,范围/排序字段放后面;最终要用实际 SQL 与 EXPLAIN 验证。

F4#

先看接口 P95/P99、数据库慢查询日志、Prisma query 日志、锁等待、连接池和扫描行数,再拿真实参数执行 EXPLAIN (ANALYZE, BUFFERS)。关注 Seq Scan/Index Scan、estimated 与 actual rows 偏差、sort/hash 是否落盘、loops、shared hit/read 和总耗时。索引未生效可能是选择性低、隐式转换、函数包列、前导列缺失或返回数据比例过高。

F5#

事务边界围绕必须一起成功的状态检查和写入,例如创建待确认计划与 Action、确认抢占和业务执行。Prisma 负责常规 CRUD 与交互事务;需要 PostgreSQL Advisory Lock、部分索引或复杂原子条件时使用原生 SQL/Migration。事务应短小,不在持锁期间调用模型或外部 HTTP。

F6#

按用途和环境使用命名空间,例如 queue:, nonce:, rate:, lock:, heartbeat:,包含服务名、版本和租户/用户标识;各类 Key 设置不同 TTL,队列最好独立 DB 或独立 Redis 实例。统一 Key builder、禁止散落拼接,并监控数量、过期率和内存,避免业务键冲突。

F7#

Redis 的数据可过期、被淘汰或在故障切换中丢失,不能承载用户计划、订单和 Action 终态。丢失 nonce 会削弱重放记录,因此内部签名选择失败关闭;丢失限流会短暂放大流量;丢失队列可能需要从 PostgreSQL 状态重投;丢失锁/心跳要靠数据库幂等和恢复收敛。事实状态仍在 PostgreSQL。

F8#

FastAPI 的 async def 运行在事件循环,pypdf、部分 Chroma/MinIO SDK、同步 HTTP、CPU 密集解析等同步调用会阻塞所有并发请求。短期可用 asyncio.to_thread 把阻塞 I/O 放线程池;CPU 重任务更适合 Celery 进程。还要限制线程池和并发,不能只是把阻塞无限转移。

F9#

软超时让任务有机会清理临时向量和记录错误,硬超时防止永久卡死;指数退避缓解下游故障和限流,最大重试避免无限消耗。max_tasks_per_child 与内存阈值用于回收 PDF/OCR/模型库可能累积的内存。永久错误如格式不支持不重试,网络/Provider 5xx 等瞬时错误才重试。

F10#

FastAPI 用 Pydantic 定义请求响应并发布 OpenAPI,NestJS 侧由 OpenAPI 生成 TypeScript client/types;CI 检查生成代码是否有未提交差异。运行时双方仍做 Schema 校验,错误使用稳定 code,不只靠编译期类型。API 版本升级采用向后兼容字段或显式版本,避免两个服务发布顺序导致中断。

F11#

Action、计划激活和 Advisory Lock 必须访问同一个写主库;如果“读副本判断、主库写入”,复制延迟会造成错误决策。拆库后跨库事务和外键消失,需要 Outbox/Saga、全局幂等键和补偿;Advisory Lock 只在单个 PostgreSQL 实例内有效,跨分片要改成一致路由或专门协调服务。

F12#

BullMQ 与 NestJS/TypeScript 集成自然,可共享类型和运维栈;Celery 在 Python 解析、科学计算和现有 Python Worker 生态更成熟。全部统一能减少基础设施,但可能迫使任务跨语言包装或让团队丢失熟悉工具。当前文档任务天然在 Python,保留 Celery合理;若任务主体转到 Node 且团队只维护一套队列,可评估 BullMQ。

F13#

两者都能承担核心业务,但本项目使用 PostgreSQL 的部分唯一索引、JSONB 查询、事务级 Advisory Lock 和较强约束能力,这些与 Action/单活动计划设计契合。MySQL 可用唯一键、命名锁或其他模式替代,但语义和实现不同。选择 PostgreSQL 是利用实际特性,不是笼统说它一定更快。

F14#

建立 TokenLedger 记录 userId、日期、runId、预留/实扣/退款 token 和状态,并对 runId 唯一;另有按用户日期聚合的 quota 行。运行前事务锁定 quota 行并预留上限,完成后按真实 usage 结算差额,失败/取消退款;所有变更写不可变账本并用幂等键。高并发可用原子 UPDATE 条件扣减,Redis只做快速限流,数据库账本负责审计。

G. 服务安全与权限答案#

G1#

浏览器直连内部服务会暴露拓扑、密钥和低层接口,并允许用户伪造 ownerId、绕过业务权限或取得 MinIO 对象。浏览器只访问经过 JWT、限流和输入校验的公开 NestJS API;FastAPI、Chroma、MinIO 放私网或 Docker 网络,NestJS 用服务身份访问。需要下载文件时由业务层鉴权后代理或发短期签名 URL。

G2#

canonical request 至少包含 HTTP 方法、规范化路径/查询、timestamp、nonce、keyId 和原始 body 的 SHA-256,再用共享 secret 做 HMAC。签原始 body 哈希能覆盖空格、字段顺序和二进制内容的确切传输字节,避免解析后重序列化产生歧义;验签必须使用接收时保留的原始字节和常量时间比较。

G3#

timestamp 限制签名有效窗口,阻止旧请求长期重放;nonce 保证窗口内同一请求也只能接受一次。Redis 用 TTL 原子 SET NX 保存 nonce;如果 Redis 不可用仍放行,会在最需要保护时失去重放防线,所以内部写接口选择失败关闭,并返回可重试错误。只读低风险接口可另行评估降级策略。

G4#

传输重试应重新生成 timestamp 和 nonce并重新签名,否则第一次已被服务端记录后,后续会被判重放。业务幂等不能依赖 nonce,而使用稳定 idempotency key/document task key;这样安全层每次请求唯一,业务层仍能把多次请求识别为同一操作。

G5#

不够。HMAC证明请求来自持密钥服务,但明文网络仍可能泄露正文和签名;还需要 TLS/mTLS、网络策略、最小端口暴露、Secret 管理、短周期轮换、keyId、日志脱敏和服务级授权。HMAC 密钥不能打入镜像或前端。

G6#

依次校验登录与配额、文件名和允许扩展名、声明 MIME、文件头魔数、最大字节数,并流式计算 SHA-256;对象 Key 由服务端使用 owner/document UUID 生成,不能采用用户路径。解析后还校验内容是否可读、页数/文本量和压缩炸弹风险;元数据写库与对象上传失败要有清理或补偿。

G7#

三者都先用 JWT 得到 userId,并按用户/IP/接口限流。SSE 还限制每用户并发 Run、总时长和心跳;Action 确认校验 action 所有权、会话/Run 状态、幂等和 CSRF/CORS 策略;上传限制文件类型、大小、文档配额与并发任务。高成本接口的配额应落可审计数据库,而不只在前端禁用按钮。

G8#

签名携带 keyId,服务端在轮换期同时接受 old/new 两把有效密钥;先部署验签端支持新 key,再更新调用端使用新 key,观察无旧 key 流量后撤销旧 key。密钥来自 Secret Manager/环境注入并记录使用指标,不记录 secret;泄漏时缩短窗口、立即吊销并审计相关 trace/nonce。

G9#

documentIds 先在 PostgreSQL 用 id + userId + READY 查询得到服务端白名单,内部请求中的 owner 由 JWT 上下文生成;MinIO Key 从数据库记录读取,不接受任意 Key;回调必须 HMAC 验签,并用 documentId、任务版本和允许状态条件更新。Chroma查询再同时过滤 owner 和 allowlist,形成双重隔离。

G10#

必须脱敏 JWT、Cookie、密码、HMAC Secret、Provider key、上传正文、Prompt 中的私人资料、完整邮箱/手机号和对象签名 URL。可以记录 traceId、runId、documentId、状态、耗时、大小、错误类别和脱敏用户 ID,因为它们用于关联而不是内容本身。日志权限、保留期和删除策略也属于安全控制。

H. Docker、部署、健康检查与可观测性答案#

H1#

先安装 Docker/Compose并准备生产环境变量和 Secret;启动 PostgreSQL、Redis、MinIO、Chroma,创建 Bucket;执行数据库 migration;再启动 FastAPI、Celery、业务 NestJS、Agent NestJS 和前端/反向代理。启动后检查 liveness/readiness、Worker 心跳、模型/Embedding 配置,最后跑登录、上传、READY、RAG引用和Action确认的 smoke test。不要在数据库未迁移或 Bucket 未就绪时放流量。

H2#

开发 Compose 可以挂源码、暴露调试端口、使用本地模型和宽松资源;生产 Compose 使用固定镜像、只暴露网关、Secret、资源/重启策略、只读文件系统或最小权限、持久卷和严格来源。生产启动应拒绝默认密钥、无 TLS 的外部模型地址和会产生写入的健康探针,因为配置错误不能静默上线。

H3#

多阶段中先只复制 lockfile 安装依赖以利用缓存,再复制源码构建,运行阶段只带产物、生产依赖和非 root 用户。使用 .dockerignore 排除 git、缓存、日志和 .env;Secret 通过运行时挂载或 BuildKit secret,不用 ARG/COPY 写进层。固定基础镜像摘要并做漏洞扫描。

H4#

liveness 只判断进程是否需要重启,不能依赖所有外部服务;readiness 判断是否能接流量,可检查 PostgreSQL/Redis 和关键配置。MinIO/Chroma做轻量只读探针,Worker看心跳与队列延迟,模型/Embedding Provider用缓存的周期探测而不是每个健康请求真实生成;探针必须有超时且不写业务数据。

H5#

不能。depends_on 最多表达容器启动或配置了 healthcheck 后的基本顺序,不保证迁移、Bucket、模型和应用协议都可用。应用必须在启动时做依赖重试与指数退避,readiness 未通过前不接流量;异步任务自身也要能重试,而不是假设依赖永远先好。

H6#

先用 documentId/traceId 查 PostgreSQL状态和最后错误,再查业务服务是否成功投递、Celery队列是否积压和 Worker 是否在线;接着看任务日志、MinIO对象是否存在、解析/Embedding/Chroma写入到哪一步,最后查回调 HMAC 和状态条件是否拒绝。若向量已完整但状态未更新,走幂等回调/对账修复,不手工直接改 READY。

H7#

PostgreSQL做全量+WAL/PITR并定期恢复演练;MinIO使用版本化、复制或对象备份并校验哈希;Chroma可做卷快照缩短恢复时间,但最终可由 PostgreSQL元数据和MinIO原文件重建。备份必须包含配置中的索引版本和模型信息,否则恢复后可能无法复现同一向量空间。

H8#

应用先采用滚动或蓝绿发布,readiness通过后再接流量,旧实例排空 SSE/任务。Migration遵循 expand-contract:先加可空列/新表和双读写,再发布新代码、回填,最后删除旧字段;不能一次发布破坏旧版本。Worker任务和内部 API 也要版本兼容,回滚路径在发布前验证。

H9#

activeRuns 是进程内状态,多副本下只能取消命中同一实例的运行;需要按 runId 路由、Redis Pub/Sub/控制通道或把取消标志持久化并轮询。多个定时恢复器会同时扫描,所以用数据库条件更新、lease、Advisory Lock和幂等补偿抢占;不能只用进程布尔变量。SSE连接本身仍归属单实例。

H10#

API看请求量、错误率、P50/P95/P99、SSE时长和断开率;模型看首 token、总耗时、token、成本和超时;工具看各工具调用量、成功率、重复拦截和耗时;Celery看队列深度、等待时间、重试与失败;索引看各状态停留时间、chunk校验失败;RAG看空召回、低分率、引用通过率和检索延迟。日志、指标和 Trace 统一使用 traceId/runId/documentId关联。

I. Vue、SSE 与全栈交互答案#

I1#

当前场景主要是服务端单向推送 token 和状态,SSE 基于 HTTP、代理和鉴权更简单,不需要 WebSocket 双向常连接。使用 fetch 流解析是为了支持 POST/JWT和主动取消;断线后不能盲目续写同一次模型流,应凭 runId 查询终态/消息并决定重试。Nginx需关闭缓冲、延长超时并发送心跳,服务端监听连接关闭清理资源。

I2#

同一次 Run 只有一个发送函数,每发一个事件递增 sequence;started 先发,工具开始/结束和 source 在执行时发,delta 按模型流顺序发,Action在工具返回后收集,最终持久化成功再发 completed。异常只发一种终态。跨网络不宣称绝对 exactly-once,前端仍用 runId+sequence去重并忽略终态后的事件。

I3#

sequence表示同一 Run 内事件顺序;runId关联一次 Agent执行和数据库状态;traceId贯穿跨服务排障。前端按 runId维护状态,sequence小于等于已处理值的事件丢弃,出现跳号可标记并在流结束后拉取最终消息;traceId只展示/上报排障,不参与业务去重。

I4#

点击停止时前端 AbortController关闭请求并调用取消接口;Agent服务标记取消并中止本机模型/工具信号,把 AiRun置 CANCELLED,拒绝本次未完成 Action。只关闭页面可能先触发连接断开,服务端不能仅依赖浏览器,仍要有状态轮询和全局超时。已经提交的外部调用未必能取消,所以迟到结果不得覆盖终态。

I5#

Markdown按文本接收,使用禁用原始 HTML 或经过 allowlist sanitizer 的渲染器,链接限制安全协议并为外链添加安全属性;引用片段使用文本节点/转义,不把模型返回内容拼进 v-html。还应限制图片/iframe、处理代码块和URL,并配置 CSP。服务端也不把模型输出当可信 HTML。

I6#

点击后按钮立即进入 submitting,后端仍以状态条件更新、幂等和锁保证真正只执行一次;成功后按返回 Action状态更新。刷新时调用 listPending 恢复 PENDING/CONFIRMED 操作,EXECUTED结果从消息或Action回执获取。前端禁用只是体验优化,不是并发安全边界。

I7#

浏览器到NestJS的上传字节进度可用XHR/上传API;解析、Embedding和写向量是服务器异步阶段,应由任务状态/阶段和已处理页数或chunk数展示,可轮询或SSE。两者不能伪装成同一个线性百分比,因为服务端阶段耗时不可线性预测;UI应分“上传、排队、解析、索引、完成”。

I8#

后端 REST/SSE协议、JWT、DTO、事件类型、Action/RAG逻辑和共享业务规则可以不动。需要重写的是 Vue组件、Pinia/Composition API、路由和特定UI库适配;把API client、SSE parser和纯TypeScript状态机抽成框架无关包,可降低迁移量。不要为了换React连后端协议一起重做。

J. 代码审查型高风险追问答案#

J1#

这个拆分已经完成:AgentRunnerService 只负责请求到返回的顺序编排和模型流;AgentRunLifecycleService 管并发锁、Run/消息终态、取消与 stale 恢复;AgentContextService 管 Checkpoint 历史和上下文压缩;AgentRagService 管预检索、追问扩展、来源登记与引用闭环;AgentToolsService 管 Tool 定义、去重、步数、超时和审计;AgentUsageService 管 Token/成本;AgentConfigService 统一校验数值配置。拆分时不改 Controller 入口、SSE 事件结构和对外 Tool 名;成功/失败/取消仍用数据库条件更新和事务锁定,引用仍只从本轮真实召回集产生。验收要用回归测试比对事件顺序、Run 终态、Assistant 消息、Action 数量和 citations,而不只看“能不能返回文字”。

J2#

integer(name, fallback, min, max=MAX_SAFE_INTEGER) 中,2 是默认值,1 是最小值,并没有把上限设为 1。配置校验只能保证“读到的值合法”,不能防并发请求同时看到旧计数。AgentRunLifecycleService.createRun() 先用会话级 Advisory Lock 防止同一会话同时起两个 Run,再用用户级 Lock 串行化“统计 RUNNING 数量 + 创建新 Run”,才能让数据库成为真正并发边界。为避免参数误读,可改成对象参数并补配置与并发集成测试。

J3#

当前测试覆盖不足是事实,不能用“手工跑过”代替可靠性。我会优先补:Action并发确认/恢复/补偿的PostgreSQL集成测试;文档状态机、回调幂等和owner隔离;RAG过滤、去重与引用白名单;Agent使用fake model的工具上限、取消、超时和SSE顺序;最后用Docker依赖跑上传到引用、计划确认到任务生成的E2E。

J4#

简历应准确说“支持文本型PDF、Markdown、TXT的解析、分片、向量索引与页码引用”,不说支持扫描件OCR、表格还原或复杂版式。现场可演示扫描PDF被识别为无文本并失败提示,同时给出演进方案:文本密度检测、按需OCR、布局模型、表格抽取和成本配额。

J5#

当前个人项目仓库没有完整MCP Server/Client,不能把它描述成该项目已实现。MCP经验来自商业项目时,只能讲本人真实负责的Server/Tool、鉴权和调用链;若受保密限制,准备脱敏的最小示例或现场实现一个只读MCP工具。若没有可验证代码,就把表述降为“理解并接入过”,不要虚构展示。

J6#

我会明确说个人项目没有Scrapy/Playwright爬虫,招聘要求中的爬虫是能力补充项。然后基于真实前端自动化/接口经验说明设计:遵守robots和条款、限速、会话隔离、分页与懒加载、稳定选择器、重试/断点、内容哈希去重和可观测性。没有做过的反爬绕过不包装成生产经验,可现场完成合规公开页面的小任务证明学习能力。

J7#

当前两者的隔离主要在系统Prompt、回答目标和UI语境,工具都以私人知识库搜索为主,所以更准确称为同一Agent框架下的两个场景,而非两个能力完全独立的Agent。写作教练若要更实质,应增加作文结构化分析、语法反馈、版本diff、评分rubric和用户确认改写等专用工具及评测集。

J8#

新版链路承担可持久化Run、SSE工具事件、Action和RAG可信引用;旧ChatService保留通常是兼容旧页面/接口和降低一次性迁移风险。下线步骤是统计旧接口流量、建立功能对照、让旧入口转发新服务或双写验证、迁移历史数据、发布弃用通知,零流量后删除代码和表。不能长期无Owner地保留两套实现。

J9#

个人项目只说稠密向量召回加阈值与去重,不说BM25混合检索或交叉编码Reranker。混合检索与候选重排来自商品平台简历项目,回答时明确“在哪个项目、使用什么数据和顺序”。展示个人仓库时以实际代码为准,README和简历若措辞越界就修正。

J10#

包含traceId的Job ID有利于追踪,但会让业务上相同的重试生成不同队列任务,因此队列Job ID不是最终幂等边界。真正边界是文档状态/版本、内容SHA、确定性chunk ID、按文档清理与写后集合校验、回调条件更新。更好做法是业务job key稳定,traceId作为每次尝试的观测字段。

J11#

不一定。Promise.race或超时包装只是不再等待,底层SQL、HTTP或SDK调用可能继续执行;只有依赖真正接收并响应AbortSignal或有驱动级timeout才会停止。因此所有迟到结果都必须受Run终态和幂等条件保护,数据库设置statement timeout,HTTP传signal;无法取消的写操作要用业务幂等/补偿,而不是假设超时等于撤销。

J12#

会同时扫描,但不应造成重复结果。扫描只是找候选,真正处理前用状态条件更新、lease时间和Advisory Lock抢占;Action执行、补偿和回执都幂等,回执按actionId查重。进程内recovering只能防同一实例重入,多实例安全必须由数据库保证,并通过并发集成测试验证。

J13#

若简单按空格/英文Token集合计算Jaccard,中文连续文本会被当成很少的大Token,近重复判断会漏掉;中英混合也会受标点和分词影响。改进可用中文分词或字符n-gram,统一大小写/Unicode/空白,再结合内容哈希和Embedding相似度;阈值按中文、英文、表格分别评测。

J14#

运行代码和Migration决定实际行为,但“以代码为准”不能成为文档长期错误的借口。OpenAPI/Prisma client等尽量由源码生成;CI检查migration可应用、生成物无差异、README配置示例与启动测试;架构决策用ADR,关键链路测试作为可执行文档。每次跨服务变更在同一PR更新合约和说明。

J15#

先诚实说明线上环境故障并给出trace/健康状态,不在面试现场长时间抢修;立即切到预先准备的本地Compose或录屏,按固定脚本展示上传、READY、引用、计划确认和数据库结果;若都不可用,就用代码地图现场走读一条链路并展示测试/日志。准备离线材料是风险管理,不是伪造线上可用性。

K. 招聘要求中的加分项与缺口答案#

K1#

Host是承载模型和用户交互的应用;Client由Host管理,与一个Server建立会话;Server暴露Tool、Resource和Prompt;Tool是可执行能力,Resource是可读取上下文,Prompt是可复用提示模板;Transport负责JSON-RPC消息传输,如stdio或Streamable HTTP。真实实现要讲清Schema、身份、权限、超时和审计;当前个人项目没有MCP实现,示例应来自我真实商业经历或单独可展示项目。

K2#

Function Calling通常是某个模型SDK内定义函数Schema并由应用执行;MCP是模型应用与外部能力之间的标准协议,支持能力发现、资源、提示、会话和多种Transport。只有少量应用内工具且没有复用需求时,直接Function Calling更简单;多Host复用、第三方工具生态和独立部署时MCP价值更大。

K3#

可暴露只读的学习画像、到期复习、课程查询、知识库搜索和计划草稿工具;创建计划/完成任务只能暴露“prepare”并返回待确认Action,绝不暴露任意SQL、任意对象Key、跨用户文档读取或直接修改权限。用户身份由Host到Server的受信认证上下文传递,工具输入Schema不允许模型提供userId;每次调用记录client、user、tool、参数摘要和结果。

K4#

输入用JSON Schema限制类型、长度、枚举和additionalProperties;执行设置连接/工具/总超时,只对明确瞬时错误重试;服务端基于用户和客户端做Tool级授权。读工具有缓存边界,写工具使用幂等键、状态条件和Action人工确认;审计记录trace、调用者、工具版本、参数哈希、耗时和结果状态,敏感正文脱敏。

K5#

SPU表示共享商品属性,SKU表示可售规格组合;价格和库存按SKU、商户/仓、渠道及有效期建模,库存还区分可用、锁定和已售。向量库只保存用于语义召回的描述和ID,价格库存必须在事务数据库中,因为需要精确过滤、并发扣减、审计和实时更新;推荐返回前重新查询业务库。

K6#

先把需求解析成结构化条件:预算、品牌、规格、库存和上架状态是硬约束,用SQL过滤;用途、风格、场景是软偏好,用向量/关键词召回并打分。若硬约束冲突先澄清,不能靠相似度“放宽”;候选重排综合软偏好、业务规则和多样性,最后再次校验全部硬约束并解释推荐理由。

K7#

索引中的商品信息只能用于召回,不能当实时交易事实。生成最终回答前按SKU ID批量调用价格、库存和上架状态工具,过滤缺货/下架项并更新展示;下单时业务服务再做库存锁定和价格确认。工具结果带查询时间,缓存必须短且允许业务侧拒绝过期结果。

K8#

先确认合规和授权;为每个账号使用隔离context和安全凭据,登录态加密保存并处理过期;分页优先调用公开接口,否则用稳定选择器滚动并等待明确网络/DOM条件。设置域名级并发与随机间隔、429/5xx指数退避、检查点保存页码/游标、内容哈希去重;失败记录截图、URL和原因,恢复时从检查点继续。

K9#

Scrapy适合大量静态页面、请求调度、去重和高吞吐;Playwright适合必须执行JavaScript、登录、懒加载或复杂交互的页面。常见组合是Playwright获取会话、动态接口或少量详情,Scrapy承担大规模HTTP抓取;不要所有页面都开浏览器,否则资源成本和稳定性差。

K10#

遵守robots.txt、网站条款、授权范围、速率限制和适用法律;不绕过访问控制、验证码或付费墙,不采集无必要个人信息,不将受版权保护内容随意再分发。保存来源、采集时间、删除机制和合规审批;robots不是全部法律判断,商业采集应让法务确认。

K11#

“A user uploads a document through the NestJS business API. The original file is stored in MinIO, while metadata and ownership remain in PostgreSQL. A Celery worker parses and chunks the document, generates embeddings, and writes them to Chroma. At query time, NestJS first builds an allowlist of the current user's ready documents. The Python service applies the owner and document filters again, retrieves candidate chunks, and returns traceable sources. The Agent may cite only the source IDs returned in that request, and the server removes invalid citations before sending the final answer.”

K12#

先确认文档对应的包版本、发布日期和是否为当前稳定API,再定位概念、参数默认值、生命周期、错误语义和breaking changes;用官方示例做最小可运行验证,再对照项目锁文件与类型定义。把结论落成接口封装、配置校验和回归测试,并记录来源链接/版本;不直接复制博客里可能过期的代码。

K13#

可选三类提交而非只选“大提交”:25431be 展示测验与学习闭环,f291a20 展示私人知识库可靠性强化,a9acc1d 展示Agent/RAG架构文档与系统化整理。现场用diff说明问题、设计、验证和后续修复;实际选择前再次核对提交内容,不只根据提交标题讲故事。

L. 其他简历项目的交叉验证问题答案#

L1#

商品解析工作流服务商户上架,是确定性较强的异步流程:类目识别、模板取值、属性抽取、标准化、置信度与商户确认,输出可检索的正式商品。导购Agent服务消费者对话:澄清需求、拆硬/软约束、检索、对比并复查价格库存。两者共享商品ID和结构化数据,但解析不负责推荐,导购也不能绕过商户确认修改商品。

L2#

置信度不能只使用模型自报概率,可综合字段是否有明确视觉/文本证据、多次抽取一致性、格式/枚举/单位校验和跨图片冲突。高置信且通过规则的字段可预填,低置信或冲突字段高亮展示来源和候选值,由商户确认;正式上架只读取确认后的草稿版本,并保留模型值、人工值和修改审计。

L3#

先用租户、上架状态、类目、预算、规格和库存等SQL硬过滤缩小合法集合;再对用途、风格和场景做向量召回,必要时并行关键词召回并融合;最后对有限候选按相关性、硬约束完整性、业务规则和多样性重排。这样既不把不合法商品带入模型,又避免先全库向量召回造成浪费和越权。

L4#

人员资质、作业等级、安全措施和审批权限是高风险、可明确编码且需要审计的判断,大模型可能幻觉、受Prompt影响且结果不稳定,所以由规则引擎给出通过/不通过及规则编号。模型负责信息提取、缺失追问、法规检索和自然语言解释,不能替代有权限人员做最终合规结论。

L5#

条款入库时保存标准编号、发布机构、生效/失效日期、版本状态、适用作业类型和企业范围;检索前根据作业日期、企业和场景做元数据过滤,再进行语义召回和排序。冲突时同时返回来源、版本和条款位置,优先适用的现行强制标准由规则/法务配置决定;无法消解时提示人工确认,不让模型自行裁决。

L6#

危化项目把票证填报、材料检查、法规检索、风险提示和审批摘要拆成独立Agent,各自有Prompt、工具和输出Schema,由编排Agent通过统一Graph State协调。个人项目是一个Agent框架按场景切Prompt和工具白名单,内部没有多个自治角色协作。两者区别是执行单元、上下文、契约和路由是否真正独立,不是UI上有几个名称。

L7#

只返回Patch能保留用户未修改字段、降低模型覆盖完整表单的风险,也便于逐字段审计。Patch包含字段路径、旧值依据、新值和原因;前端对当前表单版本应用前先检查baseVersion,展示diff并让用户逐项接受。若用户在Agent生成期间已修改同一字段,就标记冲突而不是覆盖。

L8#

Vue携带用户登录态访问Java业务网关;业务系统校验用户、组织、角色和流程状态,再向Python Agent注入最小身份上下文或短期服务令牌。Agent工具调用Java接口时带trace和服务认证,用户ID不能由模型填写。Agent不直写数据库,是为了复用Java中的权限、校验、事务、审批状态机和审计。

L9#

工作经历时间表示在公司任职;同名项目时间是任职期间负责该项目的阶段,两者重叠正常。个人项目2025.10开始,是业余持续迭代,不应描述成公司全职项目。面试时用时间轴说明每阶段职责和大致投入,若多个公司日期存在交叠或未来日期,必须按真实入离职情况在面试前修正简历。

L10#

可用脱敏架构图、本人负责模块的接口契约/伪代码、问题复盘、测试思路、日志结构、上线流程和具体权衡证明;对方继续追问时能定位输入、输出、失败和指标。还可提供合法的任职/项目证明或同事推荐,但不展示公司源码、密钥、客户数据和内部地址。个人项目则用公开仓库补充验证工程能力。

M. 现场编码与系统设计预测题答案#

M1#

用Pydantic定义 page>=1、1<=page_size<=100、价格区间和枚举状态;Service构造参数化SQL/SQLAlchemy条件,先硬过滤再按稳定字段排序,并返回 items,total,page,pageSize。统一异常处理器把校验、未找到和内部错误转为 {code,message,traceId,details},不要把SQL异常直接暴露。索引围绕tenant/status/category/price与排序字段设计。

M2#

Controller使用JWT Guard得到userId,RateLimit Guard用Redis Lua或原子计数限制窗口;客户端传 Idempotency-Key,服务端事务内按 userId+route+key 查询/创建幂等记录并保存请求哈希与响应。相同Key同参数返回原结果,不同参数报冲突;真正业务表再加唯一约束,Redis限流失效不能破坏数据库幂等。

M3#

先定义严格Zod/Pydantic输入和稳定输出/错误Schema;执行器注入用户身份和trace,记录参数哈希,使用AbortSignal/客户端timeout调用业务API,只对网络超时、429和可重试5xx做有限退避。写工具带幂等键并返回待确认Action;finally记录工具名、版本、耗时、状态和脱敏错误,模型只看到可安全处理的信息。

M4#

服务端设置 text/event-stream、no-cache、关闭代理缓冲,事件包含id/type/data并周期发心跳;生成器在try中逐条发送,catch发送结构化error,finally取消模型/关闭订阅和定时器。监听请求close触发AbortController;每个Run有总超时和终态落库。客户端按runId/sequence去重,断线后查最终状态而不是盲目重复执行。

M5#

签名端构造 method\npath\nquery\ntimestamp\nnonce\nsha256(rawBody),用keyId对应secret计算HMAC-SHA256;验签端检查时间窗、keyId、body哈希和常量时间签名,再用Redis SET nonce NX EX防重放。重试生成新timestamp/nonce,但保留业务幂等键。测试覆盖body篡改、过期、重复nonce、路径编码和密钥轮换。

M6#

解析器先输出带page的段落;累积到目标长度前优先在段落、句号或换行处切分,超过上限才硬切;下一块从前一块末尾回退overlap字符,并记录涉及页码、chunkOrder和内容哈希。必须保证循环每次向前推进,避免overlap导致死循环;测试空页、超长句、中英文标点和跨页段落。

M7#

先按score阈值过滤并稳定排序;对规范化文本计算SHA-256去完全重复;再用字符n-gram/Jaccard或Embedding相似度去近重复,保留更高分或来源更完整者;最后截取topK。过滤过程中保留documentId、chunkId和page,输出可解释的丢弃原因,阈值从评测集确定。

M8#

事务中按业务键获取Advisory Lock或锁定幂等记录,读取Action并用 UPDATE ... WHERE status='PENDING' 抢占为CONFIRMED;只有更新行数为1的请求执行业务。业务写入、Action EXECUTED和result在同一事务,表上有业务唯一约束;重复请求查到EXECUTED直接返回原result。长外部调用则改为CONFIRMED+lease+可恢复执行,避免长事务。

M9#

表至少有SPU、SKU、SKUAttribute、Price、Inventory和InventoryReservation;SKU唯一键由merchant+spu+规格组合决定,价格带currency/channel/effective range,库存带warehouse、available、reserved和version。查询先按tenant/status/category和SPU属性过滤,再连接有效价格与 available-reserved>0 的库存;索引覆盖SKU上架状态、价格有效区间和库存sku/warehouse,扣减用条件UPDATE或乐观锁。

M10#

Celery任务配置 autoretry_for 或手动捕获瞬时异常,使用 retry(countdown=min(cap, base*2**retries+jitter)) 和 max_retries;网络超时、429、5xx可重试,格式错误、权限错误、校验失败直接标记永久失败。任务以稳定业务键幂等,重试前清理/复用本次中间结果,软超时负责收尾,硬超时兜底。

M11#

先拿真实慢SQL和参数执行 EXPLAIN (ANALYZE, BUFFERS),比较估算/实际行数、扫描方式、过滤掉的行、排序和I/O;结合查询的等值、范围和order设计组合/覆盖/部分索引。建立后再次测量并说明写放大与空间成本。如果返回表中大部分行,Seq Scan可能反而正确,不能强行追求Index Scan。

M12#

漏洞通常是直接相信请求里的ownerId/documentIds查询向量库。修复为从JWT取得userId,在PostgreSQL查询 id IN requested AND userId=current AND status=READY 得到allowlist,若数量不一致则拒绝;内部调用签名传服务端owner,Chroma where同时限制owner和allowlist。返回结果还要逐条验证documentId,测试用另一用户ID确认零泄漏。

M13#

索引元数据记录tenant、document、embeddingModel、dimension、chunkerVersion、indexVersion和visibility;不同向量空间写独立Collection/namespace,路由表保存每租户active version。后台从事实源构建candidate版本并做数量/质量校验,按租户或流量比例灰度双读对比,原子切换active后保留回滚窗口。所有查询强制tenant过滤并有配额与审计。

M14#

状态包含用户目标、硬约束、软偏好、候选、已验证库存和确认结果;先判断信息是否足够,不足则只问最关键问题。工具依次做结构化搜索、语义召回、详情/价格/库存批查,代码验证硬约束,模型负责解释和比较;回答前重新检查实时库存。加入购物车/下单等写操作必须展示商品、价格和数量并人工确认。

M15#

手写循环维护messages、step和工具指纹:每轮调用模型,若是final则返回;若有tool call,先验证Schema和白名单,重复指纹返回受控错误,执行时设置超时并把结构化结果追加到messages。最多5步,超过后返回“未完成及原因”;全局AbortSignal贯穿模型和工具。身份、权限、幂等和审计在执行器,不交给模型。

N. 反问面试官的使用方式#

N1#

这题用于判断岗位真正的主战场。理想回答应包含明确业务用户、当前核心指标和最痛的故障;如果只说“接大模型、做智能化”而没有场景,需要继续追问上线状态和数据来源。

N2#

用于区分成熟系统维护与从0到1建设。若已有生产系统,继续问调用量、评测、故障和你接手的模块;若刚起步,继续问前三个月架构决策权、可用数据和最小交付目标。

N3#

用于确认日常工作究竟偏Python还是Node,避免JD把所有技术都列上。重点听代码占比、现有服务归属、谁维护业务库和是否需要值班,而不是只听“都可以用”。

N4#

用于针对性准备现场挑战,也体现你重视真实交付。可进一步确认时长、是否允许查官方文档、是否需要完整运行、评估更看重正确性还是工程结构;不要询问具体原题或寻求违规提示。

N5#

用于判断团队是否从Demo进入工程化。较成熟的信号包括离线数据集、线上成功率/延迟/成本、Trace、人工反馈和版本对比;若暂时没有,也可追问入职后是否希望你参与建立评测体系。

N6#

用于了解电商Agent最难的数据事实层。重点听商品/库存由谁提供、更新延迟、最终下单校验、租户隔离和采集合规;这些决定系统主要难点是Agent推理还是数据治理。

N7#

这是最重要的收尾问题,用来把岗位期望变成可衡量结果。希望得到具体交付、时间和验收标准,例如上线一个工具链路、降低失败率或建立评测集;回答过于抽象时可追问“做到什么程度算通过试用期”。

最后记忆法#

  • 先讲结论,再讲真实调用链,最后讲边界和改进。
  • 个人项目只说当前仓库确实具备的能力;混合检索、Reranker、Multi-Agent、MCP和爬虫必须明确属于哪个项目或只是设计方案。
  • 被问指标时,只报有日志或数据支撑的数字;没有就说明会如何采集。
  • 被指出缺陷时先确认事实,再解释影响、修复方案和验证方式,不要强行辩解。
文档目录