Skip to content

feat: 面试前自我介绍功能 - AI 分析候选人背景动态调整提问#1

Merged
Blackman99 merged 8 commits intomainfrom
feat/self-introduction
Feb 27, 2026
Merged

feat: 面试前自我介绍功能 - AI 分析候选人背景动态调整提问#1
Blackman99 merged 8 commits intomainfrom
feat/self-introduction

Conversation

@Blackman99
Copy link
Copy Markdown
Owner

功能概述

面试开始后,不再直接进入提问环节,而是先请候选人做自我介绍,AI 自动分析其背景后动态调整后续面试方向。

改动内容

核心逻辑 (5 files, +142 lines)

  • types.ts — 新增 InterviewPhase 类型 (intro | questioning) 和 CandidateProfile 接口
  • db.ts — 新增 interview_phasecandidate_profile 列(含 migration),以及读写函数
  • interviewer.ts — 面试分两阶段:intro 阶段收集自我介绍 → AI 分析提取 profile → questioning 阶段注入背景信息指导提问
  • i18n (zh-CN/en-US) — 新增自我介绍相关提示文本

Landing Page (docs/index.html)

  • Chat demo 更新为展示自我介绍 → 定制化提问的完整流程
  • 新增「智能自我介绍分析」功能卡片
  • 流程从六步更新为七步,插入「自我介绍与背景分析」步骤
  • 中英文 i18n 同步更新

CandidateProfile 结构

interface CandidateProfile {
  tech_stack: string[]        // 技术栈列表
  experience_years: number    // 经验年限估计
  project_highlights: string[] // 项目亮点
  suggested_focus: string[]   // 建议深入考察的方向
}

面试流程变化

候选人开始 → 自我介绍 → AI 分析背景 → 定制化提问 → 评估报告

TypeScript 编译通过,兼容已有数据库。

- 新增 interview_phase (intro/questioning) 和 CandidateProfile 类型
- 面试开始后先收集候选人自我介绍
- AI 自动提取技术栈、经验年限、项目亮点
- 根据候选人背景动态调整面试提问方向和深度
- 更新 landing page 展示新功能(demo、features、flow)
- 中英文 i18n 同步更新
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Feb 27, 2026

⚠️ No Changeset found

Latest commit: 20c30cd

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Copy Markdown
Owner Author

@Blackman99 Blackman99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: feat/self-introduction

总体评价:功能设计清晰,代码结构合理,但有几个需要修复的问题,主要集中在逻辑正确性和健壮性上。


🔴 必须修复

1. intro→questioning 过渡时 trigger 消息永远不会被注入

文件:src/interviewer.ts

generate_interviewer_turn 里的 trigger 注入依赖 messages.length === 0

if (messages.length === 0) {
  const trigger = interview.interview_phase === 'intro' ? '...' : '...';
  messages.push({ role: 'user', content: trigger });
}

但在 intro 分支里,handle_candidate_reply 函数开头已经执行了 append_message(interview_id, 'user', candidate_message),所以当 phase 切换到 questioning 后调用 generate_interviewer_turn 时,get_conversation 返回的历史已经包含开场白 + 候选人自我介绍,messages.length 不为 0,questioning 阶段的过渡 trigger 永远不会被注入。

结果:AI 在 questioning 阶段第一轮没有明确的过渡指令,可能直接跳入提问而没有「感谢您的介绍」的过渡语。

建议修复:给 generate_interviewer_turn 增加一个可选的 inject_trigger?: string 参数,在 intro→questioning 过渡时显式传入:

async function generate_interviewer_turn(
  interview: Interview,
  time_remaining_ms: number | null,
  inject_trigger?: string,  // 新增
): Promise<string> {
  // ...
  if (messages.length === 0 || inject_trigger) {
    const trigger = inject_trigger ?? (interview.interview_phase === 'intro' ? '...' : '...');
    messages.push({ role: 'user', content: trigger });
  }
}

2. analyze_self_introductioninterview_id 参数未使用

文件:src/interviewer.ts:137

async function analyze_self_introduction(interview_id: number, intro_text: string): Promise<CandidateProfile>

interview_id 在函数体内完全未使用,是死参数,应删除。


3. DB migration 默认值导致历史数据状态错误

文件:src/db.ts:75

ALTER TABLE interviews ADD COLUMN interview_phase TEXT NOT NULL DEFAULT 'intro'

已处于 in_progresscompleted 状态的历史面试,升级后 interview_phase 会被设为 'intro'。若这些面试还有后续消息处理,会错误进入 intro 分支。

建议将默认值改为 'questioning',或在 migration 后补一条 UPDATE:

UPDATE interviews SET interview_phase = 'questioning'
WHERE status IN ('in_progress', 'completed', 'ready', 'notified')

🟡 建议改进

4. analyze_self_introduction 解析失败无日志

文件:src/interviewer.ts:163

JSON 解析失败时静默降级,生产环境难以排查。建议加 console.error 记录原始响应。


5. thinking: { type: 'adaptive' }max_tokens: 1500 可能冲突

文件:src/interviewer.ts:196

Anthropic extended thinking 要求 max_tokens 足够大(thinking tokens + output tokens)。max_tokens: 1500 在启用 adaptive thinking 时可能导致 API 报错或输出被截断。建议提高到 16000 或移除 thinking 参数。


6. candidate_profile 反序列化缺少结构校验

文件:src/db.ts:129

candidate_profile: row.candidate_profile ? JSON.parse(row.candidate_profile as string) : null,

直接类型断言,没有校验字段结构。数据库中若存有格式不符的旧数据,会在运行时静默出错。


7. i18n key analyzingintro_received 是死代码

文件:src/i18n/locales/zh-CN.jsonen-US.json

两个新增 key 在 interviewer.ts 中均未使用。候选人提交自我介绍后需等待两次 AI 调用,期间没有任何反馈消息。建议在 intro 分支中实际使用这些 key 向用户发送中间状态提示,否则删除。


8. Landing page flow5 描述主语不一致

文件:docs/index.html

中文 flow5 描述「面试官请候选人做自我介绍」与整体「面试官只需完成第一步」的定位矛盾。建议改为「AI 自动请候选人做自我介绍」。


✅ 做得好的地方

  • CandidateProfile 类型定义清晰,字段语义明确
  • migration 用 try/catch 包裹,兼容已有数据库
  • analyze_self_introduction 有完善的 fallback 默认值
  • intro 阶段 system prompt 独立构建,职责分离清晰
  • i18n 中英文同步更新,无遗漏
  • HTML demo 对话和 flow steps 与功能逻辑一致

Copy link
Copy Markdown
Owner Author

@Blackman99 Blackman99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: feat/self-introduction

总体评价:功能方向正确,但有几个需要修复的问题才能合并。


严重问题

1. Race condition:interview_phase 未做并发保护(interviewer.ts:108-125)

handle_candidate_reply 先读取 phase,再异步调用 analyze_self_introduction,再写回 phase。如果候选人在分析期间快速发送第二条消息,两个并发调用都会进入 intro 分支,导致:

  • analyze_self_introduction 被调用两次(浪费 API 调用)
  • 两次 generate_interviewer_turn 都用 questioning 的 trigger,但 conversation history 里只有一条用户消息,第二次调用会产生重复的过渡语

建议:在内存中维护一个 Set<number> 记录「正在处理 intro」的 interview_id,进入前检查并加锁,完成后释放。

2. analyze_self_introduction 参数 interview_id 未使用(interviewer.ts:137)

函数签名接收 interview_id: number 但函数体完全没有用到它。这是死参数,会让读者误以为函数会读取历史记录或做持久化操作。应改为 analyze_self_introduction(intro_text: string)


中等问题

3. get_candidate_profile 是多余的 DB 往返(db.ts:290-293)

该函数内部调用 get_interview 再取 candidate_profile,但目前代码里没有任何地方调用这个函数,是死代码。建议删除,调用方直接用 interview.candidate_profile

4. interview_phase migration 默认值语义问题(db.ts:77)

Migration 给已有记录设置 DEFAULT 'intro',但这些记录可能已经是 in_progresscompleted 状态的面试。给已完成的面试打上 intro phase 语义不正确。

建议:migration 应根据 status 设置合理默认值,或在 SCHEMA_SQL 里直接加上这两列,避免 migration 的歧义。

5. JSON.parse 无类型校验(db.ts:131,interviewer.ts:170)

JSON.parse(row.candidate_profile as string) as CandidateProfile 没有运行时校验。如果 DB 里存了格式不对的数据,会在运行时静默产生类型错误。analyze_self_introduction 里 parse 后直接 cast 同理,没有校验 tech_stack 是否为数组等。

建议:加一个简单的 type guard 函数做运行时校验。


小问题 / 建议

6. analyzing i18n key 已定义但未使用

en-US.jsonzh-CN.json 都加了 interview.analyzing,但 interviewer.ts 里没有任何地方发送这条消息给候选人。候选人在等待 AI 分析时没有任何反馈。建议在 intro 分支开始时先发送一条「正在分析...」消息。

7. SCHEMA_SQL 未同步新列(db.ts:23-58)

新增的 interview_phasecandidate_profile 列只在 migration 里加了,SCHEMA_SQL 里的 CREATE TABLE 没有更新。对全新部署没有影响,但 SCHEMA_SQL 作为文档不准确。建议把这两列加到 CREATE TABLE 里。

8. intro phase 的 system prompt 缺少 questions_text(interviewer.ts:21-36)

intro phase 的 prompt 里没有注入面试题目。如果有意省略,加个注释说明原因。

- 添加 intro 阶段并发处理 race condition 保护 (Set guard)
- 修复 DB migration 默认值:已有面试默认 questioning,仅新面试为 intro
- SCHEMA_SQL 同步新增 interview_phase/candidate_profile 列
- 添加 CandidateProfile 运行时类型校验 (is_valid_candidate_profile)
- safe_parse_candidate_profile 替代裸 JSON.parse
- 删除未使用的 get_candidate_profile 死代码
- analyze_self_introduction 移除未使用的 interview_id 参数
- generate_interviewer_turn 新增 inject_trigger 参数解决过渡消息问题
- JSON.parse 失败时增加 console.error 日志
- max_tokens 从 1500 提升到 16000 (adaptive thinking 需要)
- 移除未使用的 i18n keys (analyzing, intro_received)
- Landing page flow5 描述修正
Copy link
Copy Markdown
Owner Author

@Blackman99 Blackman99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test

Comment thread src/interviewer.ts
Copy link
Copy Markdown
Owner Author

@Blackman99 Blackman99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

整体设计思路清晰,intro 阶段的拆分合理,race condition guard 有考虑到。但有几个功能性问题需要修复:

必须修复

  1. analyze_self_introduction 只分析最后一条消息,多轮自我介绍会丢失信息(见 interviewer.ts:121)
  2. set_candidate_profile + set_interview_phase 非原子写入,崩溃后会出现 phase=questioning 但 profile=null 的不一致状态(见 interviewer.ts:122)
  3. 并发 intro 被拦截时静默返回空字符串,调用方无法区分「正常空回复」和「被拦截」(见 interviewer.ts:116)

建议修复
4. String.replace 只替换第一次出现的 INTERVIEW_COMPLETE(见 interviewer.ts:138)
5. 贪婪正则可能匹配到错误范围(见 interviewer.ts:184)
6. parse_interview 中 InterviewPhase 没有合法性校验(见 db.ts:159)

顺带一提(不在本次 diff 内)generate_summary"generated_at": ${Date.now()} 把时间戳硬编码进 prompt,期望 LLM 原样回传,这很脆弱。建议在 extract_summary 解析完成后由代码赋值。

Comment thread src/interviewer.ts Outdated
Comment thread src/interviewer.ts Outdated
Comment thread src/interviewer.ts Outdated
Comment thread src/interviewer.ts Outdated
Comment thread src/interviewer.ts
Comment thread src/db.ts Outdated
1. Race condition 静默失败 → 添加 warn 日志,返回 skipped 标记
2. 自我介绍只取最后一条 → 收集 intro 阶段所有 user 消息拼接
3. 非原子写入 → 新增 set_phase_and_profile 原子函数
4. String.replace → replaceAll 避免 INTERVIEW_COMPLETE 残留
5. 贪婪正则 → 非贪婪匹配 JSON
6. InterviewPhase 无校验 → 添加合法值校验,非法值 fallback 到 intro
- 功能列表新增「智能自我介绍分析」
- 状态流转图新增 intro 阶段
- 状态表新增 intro 状态说明
- 候选人操作流程新增自我介绍步骤
- types.ts 架构描述更新
- eslint.config.js: flat config, typescript-eslint recommended
- .github/workflows/ci.yml: PR 和 push 触发 lint + typecheck
- package.json: 新增 lint / lint:fix scripts
- vitest.config.ts: 测试配置
- src/db.test.ts: 25 个数据库层单元测试
  - CRUD、状态流转、phase/profile 原子操作
  - 异常 JSON 处理、无效 phase fallback
  - 消息、语言偏好、查询辅助函数
  - research notes 和 summary
- .github/workflows/ci.yml: 新增 test job
- package.json: 新增 test/test:watch scripts
新增测试文件:
- parser.test.ts: 15 个测试 — parse_schedule_request 完整覆盖
  (JSON 解析、时间校验、duration 范围、@ 去除、嵌入 JSON、边界值)
- interviewer.test.ts: 16 个测试 — build_interviewer_system_prompt + extract_summary
  (intro/questioning phase、candidate_profile、research_notes、时间显示、
   JSON 提取、markdown code block、fallback、thinking blocks)
- scheduler.test.ts: 4 个测试 — fmt_time 时区格式化
  (zh-CN/en-US locale、CST 时区、跨日期)

扩展 db.test.ts (+18 个测试):
- is_valid_candidate_profile: 10 个边界用例
  (null/undefined/非对象/缺字段/类型错误)
- safe_parse_candidate_profile: 8 个用例
  (null/undefined/空字符串/无效 JSON/结构错误/数组/类型错误)

导出内部函数用于测试 (@internal):
- db.ts: is_valid_candidate_profile, safe_parse_candidate_profile
- interviewer.ts: build_interviewer_system_prompt, extract_summary
- scheduler.ts: fmt_time
- 移动 src/*.test.ts → tests/*.test.ts
- 更新 import 路径 ./xxx.js → ../src/xxx.js
- 更新 vi.mock 路径 ./xxx.js → ../src/xxx.js
- 新增 tsconfig.test.json (extends tsconfig.json, 包含 src + tests)
- 更新 eslint.config.js: projectService.defaultProject + allowDefaultProject
- 更新 vitest.config.ts: include tests/**/*.test.ts
- 更新 package.json lint 脚本包含 tests/
- 修复 db.test.ts 中 vi.mock factory 内错误嵌入的 describe 块
@Blackman99 Blackman99 merged commit 5aa0885 into main Feb 27, 2026
2 checks passed
@Blackman99 Blackman99 deleted the feat/self-introduction branch February 27, 2026 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant