Skip to content

Latest commit

 

History

History
258 lines (229 loc) · 18.1 KB

File metadata and controls

258 lines (229 loc) · 18.1 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

AI Interview Platform

概述

多 agent 的 AI 视频面试平台。HR 上传 JD / 岗位要求 / 公司资料,候选人面试前上传 Resume; 系统结合 JD 与 Resume 自动生成面试计划、执行多轮面试与追问、产出结构化评估报告。 最终目标是「招聘端 + 候选人端」双边 AI 面试基础设施。

面试题目按 Question.category 分类,与 Question.type(题目风格)正交:

  • KNOWLEDGE:基础知识,由 JD / 岗位要求驱动
  • PROJECT_EXPERIENCE:项目/实习深挖,由 Resume 驱动
  • SELF_INTRO / SCENARIO:自我介绍 / 场景题(Sprint 5.5 起加入,见下文 stage 化)

面试支持两套 trackJobContext.track):

  • campus(校招):self_intro → knowledge(重) → project(结合 intro_text 现场生成) → scenario(轻)
  • lateral(社招):self_intro → knowledge(轻) → project(重) → scenario(重)

进度:Sprint 0–5.9 已落(骨架 → 持久化 → API → RAG → 候选人端 → HR Dashboard → track/stage 化 → Assessor → CompletionPolicy → calibration);字母 sprint 系列 (知识管线 / 简历分段 + 图片 OCR / Planner 主题匹配)已落;Sprint 6 视频面试 5/6 已落 (consent 门 / TTS 播报 / 三态 avatar / 语音作答 / 录制归档,Tier A 真口型待定); Sprint 6.5 效果评估 / 6.7 飞书接入 / 6.8 注册+owner 隔离已落; Sprint 8.1–8.5 Agent 能力升级全部已落(注入防御 / 决策 trace+回放 / 校准+信念驱动追问 / CandidateModel 记忆 / rubric 化打分+裁判团(默认关), 提案与评审见 AGENT_UPGRADES.md);8.3.1 sim 冻结回归基线已落; Sprint 9 异步队列 + 高并发承载(RQ / Milvus standalone / 会话锁)已落; Sprint 7(多模态分析) 未开。详见 sprint.md。 完整架构与合规约束见 ARCHITECTURE.md,特别是第 7 节多模态评价 + LLM-as-judge 的硬约束。

技术栈

  • Python 3.11+,pydantic v2
  • openai(LLM chat + embedding,单一 provider;Sprint 3 起从 anthropic 切过来 consolidate key/计费)
  • SQLAlchemy 2.0 + psycopg3(Postgres,Sprint 1)
  • redis-py(Redis 热存储,Sprint 1)
  • pymilvus + milvus-lite(向量存储,Sprint 3;dev/eval 单进程);Sprint 9 起部署态用 Milvus standalone(docker,MILVUS_SERVER_URI,多进程安全)
  • FastAPI(HTTP API,Sprint 2)
  • Next.js 16 + React 19 + Tailwind 4(web/ 候选人端 + HR 端,Sprint 4/5)
  • websockets(Sprint 6:火山流式 ASR 的 WS 客户端);TTS/STT 走 HTTP/WS 自研调用点, 不引厂商 SDK
  • WebRTC 仅在 Tier A 真口型数字人(LiveTalking,未开工)接入时引入

常用命令

source .venv/bin/activate
pip install -e .            # 装依赖(pyproject.toml 已声明)

python -m src.main          # Sprint 1 起:需要 Redis + Postgres 跑(无 API key 仍会进 stub 分支)

uvicorn api.main:app --reload    # Sprint 2 起:HTTP API,热重载;/docs 看 OpenAPI

brew services start postgresql   # 本机外部服务(macOS)
brew services start redis
docker compose up -d milvus   # Sprint 9: 独立 Milvus (多进程/高并发); 不起则用 milvus-lite
python -m src.jobs.worker     # Sprint 9: RQ worker (JOBS_QUEUE_ENABLED=1 时需要)
brew services stop redis         # 停 Redis

环境变量见 .env.exampleOPENAI_API_KEY / OPENAI_CHAT_MODEL / OPENAI_EMBEDDING_MODEL / OPENAI_BASE_URL / POSTGRES_URL / REDIS_URL / MILVUS_LITE_URI / MILVUS_SERVER_URI(Sprint 9,配了优先) / JOBS_QUEUE_ENABLED(Sprint 9,开队列须另起 worker) / SESSION_TTL_SECONDS / LLM_CACHE_TTL_SECONDS / EMBEDDING_CACHE_TTL_SECONDS;Sprint 6 媒体(全可选,不配 = 纯文字面试): TTS_PROVIDER / STT_PROVIDER / VOLC_* / AZURE_SPEECH_* / MEDIA_STORAGE_DIR。 运行时按需读取,缺哪个就退到对应的回退分支。

跑 eval(stdlib unittest,无第三方依赖):

python -m unittest evals.test_skeleton                            # 全部
python -m unittest evals.test_skeleton.ComplianceInvariantTests   # 单类
python -m unittest discover -s evals                              # discover

evals/ 里所有 TestCase 都被强制走 LLM stub(清掉 OPENAI_API_KEY),保证结构性护栏快、稳、不烧 token。 需要 PG+Redis 的端到端 TestCase 在缺 env 时自动 skip。

坑提醒pymilvus.settings 在 import 时调 load_dotenv() 自动把 .env 塞回 os.environ。 所以 import 了 vector_store 的 eval(如 test_seed_questions、未来的 Planner RAG eval), 模块顶 pop 太早,必须在 setUp 里 pop 才稳。test_skeleton 不 import pymilvus,模块顶 pop 仍然有效。

代码结构(已实现部分)

  • src/schemas/ — 全部 pydantic 数据契约。Agent 输入输出都是这里的类型。
  • src/llm/ — OpenAI Chat Completions 的唯一调用点。complete(system, user) 同步入口。
  • src/embeddings/ — OpenAI Embeddings 的唯一调用点。embed(text) 同步入口。
  • src/agents/{planner,interviewer,assessor,evaluator,analyzer}/ — 每个 agent 一个 __init__.py, 暴露一个动词函数:plan / next_turn / assess / evaluate / analyze。 Assessor 是 Sprint 5.6 新增的独立模块(单题在线打 sufficiency/confidence/followup_goal), 不要把它揉进 Interviewer 或 Evaluator —— 它的并发模型和延迟预算都不一样。
  • src/orchestrator/ — 串联 agent 的唯一入口。Agent 之间绝不互相调用。 也是内部状态的独家写入方(blackboard):信念更新 / CandidateModel 沉淀 / integrity_flags / per-session 锁。
  • src/trace/ — 决策 trace 收集(contextvars 旁路) + 确定性回放 + diff (Sprint 8.2)。与 coverage 同级的纯共享模块,agent 可 import 埋点。
  • src/beliefs.py — competency 信念高斯共轭更新(8.3);src/candidate_model.py — 跨 stage 候选人记忆(8.4);src/coverage.py — coverage/richness 计算。 三者都是纯数值/纯规则共享模块,不调 LLM 不碰 DB(candidate_model 的 reflection 是唯一例外,timeout+失败跳过)。
  • src/jobs/ — RQ 任务队列(Sprint 9):enqueue 封装 + worker 入口。默认关, 开启后候选人上传的 ingest+plan 走独立 worker;任何不可用退回 BackgroundTasks。
  • src/db/ — Postgres 归档:base.py engine(池容量 env 化)、models.py ORM、 repository.py save/load。
  • src/cache/ — Redis:会话/trace 热存储 + LLM/embedding/TTS 缓存 + per-session 锁(locks.py)。
  • src/vector_store/ — Milvus:questions 题库 + documents RAG 切片。 双模式(Sprint 9):MILVUS_SERVER_URI(standalone,多进程) 优先, MILVUS_LITE_URI(单文件,dev/eval) 兜底;检索恒 Strong 一致性。
  • src/auth/ — JWT + bcrypt + httpOnly cookie(cookie 优先,Bearer 兜底—— 写共享 TestClient 的 eval 必须 cookies.clear() 才能用 Bearer 切身份)。
  • src/connectors/ — 飞书 OpenAPI(6.7):wiki/docx 拉取,凭证 env 优先 / DB(Fernet 加密) 兜底。
  • src/tts/ / src/stt/ — 面试官语音合成 / 候选人流式转写的唯一调用点(Sprint 6), TTS_PROVIDER/STT_PROVIDER 按 region 路由(volc/azure);未配置一律返 None/False, 前端自动退纯文字。新增媒体调用点保持同款「绝不 raise、降级保底」模式。
  • src/media_store/ — 面试录像归档(Sprint 6-5)。只录不判:任何"拿录像做 分析/打分"的调用都属 Sprint 7 且受 §7 约束,不许从这里长出来。
  • src/knowledge_pipeline/ + src/derivation/ — md 语料解析 + 反向出题。离线管线, 不进面试运行时链路,不要与 agent 体系混用。
  • src/main.py — 写死 JD + Resume + 候选人回答跑通全链路的 demo 入口。
  • api/ — FastAPI 层(Sprint 2 起):只做 HTTP 入口 + 校验 + 异常映射,业务下沉到 orchestrator。
  • scripts/ — 运维脚本(seed_* / ingest / golden 录制 / frozen 冻结 / 压测 / 归属迁移 / 复核统计等,各脚本头注释即用法)。
  • evals/ — stdlib unittest,结构性 + 合规护栏 + API smoke(强制 stub)。
  • sim/真 LLM 效果评估(烧 token,只能显式跑):persona 仿真 / 对抗 / 公平性 / LLM-as-judge / 各类校准 / RAG 指标 / 冻结基线。与 evals 物理分离, 纪律见 EVALUATION.md。

后续 web/(Next.js)见 sprint.md。

关键设计模式(要看多文件才能拼出来)

LLM / Embedding stub 回退

src/llm/complete()OPENAI_API_KEY 未配置或 SDK 不可用时,返回前缀 [stub] ... 的占位文本。每个调用 LLM 的 agent 都必须 if llm.is_stub(text): return fallback, 让骨架在无 key 环境也产出真实可用的输出(而非占位字符串泄漏到结果)。 同款模式:src/embeddings/embed() 缺 key 时返回全零 stub 向量,is_stub_vector() 让 调用方在入 Milvus 前判断是否跳过(全零进库污染向量空间)。 新增 agent / 新增 LLM / embedding 调用点时保持这个模式。

Postgres / Redis 惰性连接

src/dbsrc/cache 顶层 import 读环境变量、建连接。 调用 init_db() / save_session() / get_redis() 等才真正连接;未配置 URL 时抛 DatabaseNotConfigured / RedisNotConfigured。新增持久化能力时保持这个模式。 注:Sprint 0 时 src.main 可以无 PG/Redis 跑通;Sprint 1 之后 orchestrator 强依赖 两者(状态机基于 Redis 读写),src.main 需要 PG+Redis 才能跑。

会话状态机基于 Redis

Orchestrator 三段式 API:start_sessionsubmit_answer*Nfinalize, 中途可用 resume_session(session_id) 重发当前待答提示(中断恢复)。Session 与 Plan 都在 Redis 里同 TTL 同生共死(SESSION_TTL_SECONDS);finalize 时把 session+report 归档到 Postgres,并立刻从 Redis 删 session 和 plan。run_interview() 是这三段的便利 封装,保留 Sprint 0 风格的一把跑完。

Agent 间通信只走 orchestrator

Agent 模块 import 范围:src.llm / src.schemas + 纯共享模块src.coverage / src.trace / src.beliefs / src.candidate_model—— 无 I/O、不碰 DB/Cache 的计算与埋点),互相 import,接触 DB/Cache。 所有路由(planner → interviewer 循环 ↔ assessor → analyzer → evaluator)以及与 Redis/PG 的 交互都在 orchestrator 内完成。新增 agent 时遵循同款:动词函数 + 输入输出都是 schemas 类型。

Stage 化的面试推进(Sprint 5.5)

stage 挂在 InterviewRound.stage 上(self_intro / knowledge / project / scenario),session 不单存 stage——当前 stage 由「当前题所在 round」派生。 Interviewer 按 plan 的 round 顺序推进。self_intro 永远 0 追问, 拿到的回答存进 session.intro_text 给后续 stage 用(也回灌 evaluator 作软信号)。 Knowledge / scenario 题在 plan 阶段就生成;project 题用 lazy generation:等候选人 self_intro 答完,进 project stage 时再用 intro_text + Resume RAG + CandidateModel 存疑条目 现场生成项目深挖题,避开"读简历瞎猜项目"的失真。追问配额是每题上限 (FollowUpPolicy.max_followups_per_question),stage 默认表在 FollowUpPolicy.for_stage(self_intro=0 / knowledge=1 / project=2 / scenario=2), 新增 stage 时同步更新该表。

Assessor 在循环中:结构化 AnswerAssessment(Sprint 5.6)

Interviewer 每收到一个回答,调用 Assessor 拿一份 AnswerAssessmentsufficiency / confidence / missing_signals / strengths / concerns / followup_goal / stop_reason),再决定追问 or 跳到下一题。Assessor 的硬约束:

  • gpt-4o-mini + 10s timeout,LLM 调用失败/超时一律回退到 Sprint 0 的启发式判断 (回答字数 + 含项目关键词触发追问),双路径永远共存,不能拆掉启发式。
  • AnswerAssessment 会落库(InterviewSession.assessments),但绝不暴露给 HR UI—— HR 只看 evaluator 的最终结构化报告 + overall;sufficiency / confidence 这些数字是 LLM-as-judge 的中间产物,校准前不可见,校准后也仅作内部诊断信号。
  • 上线前必须跑 calibration eval(20–30 条人工标注样本对齐 sufficiency 阈值), evals 跑过才能把 Assessor 接进 production codepath。改 Assessor prompt = 重跑校准。

FollowUpPolicy / CompletionPolicy(5.6 / 5.7,8.3 扩展)

追问与结束都用配置驱动,避免 if-else 散落各处:

  • FollowUpPolicy:每题上限 max_followups_per_question(stage 默认表 self_intro=0 / knowledge=1 / project=2 / scenario=2)+ raw/校准双阈值 + Sprint 8.3 的全局预算(total_followup_budget)与信念门 (min_variance_to_probe + min_established_mean,只作用于校准路径)。 Assessor 给的 followup_goal(+ CandidateModel 澄清目标)拼进追问 prompt。 HR 在 UI 配的 raw 阈值会在建 job 时经 Platt 映射折算进校准阈值(两路径同一意图)。
  • CompletionPolicy:基于 competency_coverage 终止,mandatory 每维度还须 ≥min_assessed_per_mandatory(默认 2)道不同题被评过(F5 防单发幸运分逃逸); 硬 max_total_questions cap 防失控;use_belief_lcb 灰度开关(默认关)可用 信念置信下界替代裸 max。 绝对不做动态补题——题库由 plan + lazy project gen 一次确定,coverage 不够就让 HR 复核 环节人工兜底,不能让 LLM 在线生成新题再考一遍候选人(结果不可复现 + 公平性塌方)。

第 N 类内部数据(只决策/审计,不外泄)

除 AnswerAssessment 外,以下同待遇——不进总分、不进 HR UI 明细、不见候选人: CompetencyBelief(8.3) / CandidateModel+SkillClaim(8.4) / DecisionTrace(8.2,仅审计导出端点) / integrity_flags(8.1)。 新增任何数字信号默认归入此类,想展示须显式评审。

回归双门禁(Sprint 8.2/8.3.1 起)

  • golden trace(stub,零 token):3 场典型面试的决策序列 diff,改 prompt/ policy 变红属预期信号,确认合理后 scripts/record_golden_traces 重录同 commit。
  • frozen 批次(真 LLM):9 persona 录制答案复放,输入全固定,批次间分差 只可能来自评估端。题集变更型改动的 frozen Δ 是"变更检测"不是质量裁决, 质量用生成式批次复核;出题变更后 scripts/freeze_persona_answers 重冻结。

合规约束写进 schema

EvaluationReportcontent_scores(内容维度,进总分)与 performance_observations (表现维度,软信号)分开;overall 只由 content_scores 加权得出,不能依赖软信号。 任何新增表现/软信号永远走 PerformanceObservation,永远不进 DimensionScore。 Sprint 5.6 起,Assessor 的 AnswerAssessment第三类——既不进总分也不展示给 HR,只在 orchestrator 内做追问决策 + 落库审计。背景见 ARCHITECTURE.md 第 7 节。

DB schema 取舍

嵌套结构(history / answers / content_scores / performance_observations)走 JSONB, 顶层可查询字段(status / job_id / overall / needs_human_review)提列。 有按嵌套字段查询/聚合的需求时再拆子表,不要一次拆完。 Sprint 1 阶段不引 Alembic,用 Base.metadata.create_all;schema 真的开始演进时再切。

约定

  • 所有函数带类型注解;数据契约一律放 src/schemas/,不在 agent 里另起 dataclass
  • LLM 调用一律走 src/llm/,embedding 调用一律走 src/embeddings/,不要在 agent 里直接 import openai
  • 每个 agent 暴露一个清晰的入口函数,输入输出都是 schemas 里的类型
  • 一次只做 sprint.md 里的一个 task,做完立刻验证并 commit,不批量推进
  • 改 prompt 模板前确认对应 eval 存在(eval 尚未引入时先记账,Sprint 1 末补上)
  • 改任何 prompt / policy 阈值 = 双门禁(Sprint 8.2 起):跑 python -m unittest evals.test_trace_replay(golden trace 决策序列 diff, 变红属预期信号)+ 对应 calibration eval;确认变化合理后 python -m scripts.record_golden_traces 重录 golden 并随代码同 commit, golden 更新必须显式出现在 diff 里
  • 改 Assessor / FollowUpPolicy / CompletionPolicy 前必须先跑 calibration eval—— 这类改动直接影响候选人体验和公平性,不能凭感觉调阈值
  • 新增 LLM 调用必带 timeout + 启发式 fallback;不能引入"LLM 挂了整条链路就挂"的依赖

不要做的事

  • 多模态分析(视线/表情/语气打分)是 Sprint 7 且必须先落 ARCHITECTURE.md §7 全部护栏才许实现。媒体层(TTS/STT/avatar/录制)已存在但只是纯适配器—— 录像只录不判,任何"拿录像/音频做打分"的代码都不许写
  • 不要让 TTS/STT/avatar/录制成为面试链路的硬依赖——任一媒体环节失败必须降级回 文字问答(文字是永远的保底路径)
  • 不要把 API key 写进代码(包括 .env.example),用环境变量
  • 不要在没有对应 eval 的情况下改 prompt 模板
  • 多模态「眼神/语气」信号只能作为参考证据,绝不可作为自动淘汰的唯一依据 (原因见 ARCHITECTURE.md 第 7 节)
  • 不要把 AnswerAssessment 的数字(sufficiency / confidence)暴露给 HR UI 或候选人—— 那是 LLM-as-judge 的中间产物,校准前不可信,校准后也只内部诊断用
  • 候选人端不返回 EvaluationReport / DimensionScore——候选人不接触自己的报告
  • 不做动态补题——任何"LLM 在线生成新题再考一遍"的设计直接拒,破坏可复现性 + 公平性
  • 不要拆掉 Sprint 0 的启发式 fallback——Assessor 失败时它就是保底
  • MAE 校准门禁未过不许开 EVAL_PANEL_ENABLED(Sprint 8.5)——裁判团 替代公式分之前,必须有报告级人工标注 + sim/calibrate_evaluator 通过; rubric 权重(RUBRIC_WEIGHT)调整同样要 frozen 复验 + 区分度不塌