Files
resume-agent/PRD.md
T
2026-07-20 14:48:41 +08:00

22 KiB
Raw Blame History

零资料新用户 AI 简历创建 Agent PRD

1. 文档信息

项目 内容
产品名称 AI 创建第一份简历
文档版本 MVP v1.0
适用用户 没有现成个人资料或简历的新用户
前端 Vue
后端 Python + FastAPI
核心编排 Python 显式有限状态机(FSM
模型接入 OpenAI Python SDK 兼容网关
当前实现目录 `resume-agent-mvp/`

本文档描述产品目标、交互规则、内容门禁、模型边界、技术方案和验收标准。它以“零资料新用户”为前提,不从已有个人资料或历史简历开始。

2. 产品定义

用户进入“AI 创建第一份简历”后,在一个连续的对话时间线中完成:隐私授权、简历手机号、姓名、求职阶段、第一段必要经历采集和确认。结构化信息使用对话消息中的可操作组件,自由描述使用聊天输入框。

产品的核心不是让模型自由规划流程,而是让模型处理事实,让确定性的状态机持续约束流程和内容门禁。

2.1 MVP 目标

  1. 新用户可以从 0 资料开始创建一份业务简历。
  2. 结构化步骤不依赖用户自由输入,全部以对话组件呈现。
  3. Agent 能从自然语言中抽取经历事实,并只追问当前最高优先级缺口。
  4. 模型不能自行决定阶段、组件、门禁或数据库写入。
  5. 业务简历创建不被可选字段阻塞;创建后继续丰富。
  6. AI 改写必须经过用户确认后才写入正式简历内容。

2.2 非目标

  • V1 不加载或合并用户已有个人资料、旧简历或历史会话。
  • V1 不做简历投递、职位匹配、JD 定制和人工审核。
  • V1 不引入 RAG、向量数据库、多 Agent 或长期语义记忆。
  • V1 不让模型直接操作业务数据库。

3. 已有业务字段

业务库已有字段如下。姓名、手机号为必填;其余字段按门禁规则分为首段必要字段和创建后的可选字段。

3.1 个人信息

  • 姓名
  • 手机号
  • 微信
  • 邮箱
  • 所在城市
  • 个人作品集链接

3.2 经历和标签

  • 教育经历(多段):学校名、专业、学历、是否全日制、就读开始时间、就读结束时间、学习经历描述
  • 工作经历(多段):公司名、职位、工作开始时间、工作结束时间、工作经历描述
  • 实习经历(多段):公司名、职位、实习开始时间、实习结束时间、实习经历描述
  • 项目经历(多段):项目名、项目角色、项目开始时间、项目结束时间、项目经历描述
  • 竞赛(多段):竞赛名、获奖名称、获奖时间、竞赛经历描述
  • 证书(多标签):正式名
  • 技能(多标签):技能名

4. 用户流程

4.1 首次创建路径

进入“AI 创建第一份简历”
  → 隐私授权组件
  → 手机号来源组件
  → (选择其他号码时)手动手机号组件
  → 姓名输入组件
  → 求职类型组件
  → (其他类型时)首段经历类型组件
  → 开放式首段经历问题
  → LLM 抽取 + 缺口追问
  → 首段经历确认组件
  → 最低门禁达成
  → 用户点击创建简历
  → 创建后丰富

4.2 对话时间线原则

所有消息都保留在同一条时间线中,不跳转独立表单页面。已提交组件变为只读,用户可以回顾之前的选择。

示例:

Agent 隐私授权卡
→ 用户点击同意
→ Agent 手机号选择卡
→ 用户选择使用其他号码
→ Agent 手机号输入卡
→ 用户提交号码
→ Agent 姓名输入卡
→ 用户提交姓名
→ Agent 求职类型卡
→ 用户选择校招
→ Agent 开放式经历问题
→ 用户自然语言描述
→ Agent 日期补充卡
→ 用户选择日期
→ Agent 经历确认卡
→ 用户确认
→ Agent 创建简历卡

4.3 语气定义

Agent 使用“温和、明确、正式但不僵硬”的中文语气:

  • 先确认已理解的事实,再说明还缺什么。
  • 每轮只追问一个最高优先级缺口。
  • 不责备用户信息不完整,不使用压迫表达。
  • 对未知事实明确说“暂时不知道”,不替用户猜测。
  • 正式化时使用职业化表达,但保留用户原始事实和数字。
  • 结构化组件前用一句短说明解释用途,避免连续输出长段落。

推荐表达:

我已经记录了公司和职位。为了确认这段经历,还需要补充开始和结束时间。

不推荐表达:

你的信息不完整,请重新输入全部内容。

5. 结构化对话协议

5.1 Agent 消息模式

模式 自由输入框 适用场景
`ui_only` 关闭 隐私、手机号、姓名、求职类型、确认、创建
`chat` 开启 开放式经历描述、事实补充
`hybrid` 按组件配置 日期补充、创建后丰富、确认后的继续对话

5.2 ConversationTurn

ConversationTurn
- message_id
- sender: assistant | user | system
- stage
- input_mode: ui_only | chat | hybrid
- blocks[]
- composer
- created_at

`blocks` 支持:

TextBlock
ComponentBlock
ResumePatchBlock
StatusBlock
ErrorBlock

组件块:

ComponentBlock
- component_id
- component_name
- component_version
- props
- status: active | submitted | expired | replaced | confirmed
- allowed_actions
- validation_schema

输入框:

composer:
  enabled
  placeholder
  accepted_input: text | none
  max_length

5.3 组件生命周期

  1. Python 状态机返回当前阶段。
  2. 渲染层根据阶段输出唯一主要活动组件。
  3. 用户提交组件事件。
  4. 服务端校验组件 ID、事件、会话版本和 payload。
  5. 原组件变为 `submitted` 或 `confirmed`,不可重复生效。
  6. 时间线追加结构化用户回复摘要和下一条 Agent 消息。
  7. 修改关键字段时,依赖它的确认状态失效并重新计算门禁。

同一时间只能有一个主要 `active` 组件。点击过期组件返回 `COMPONENT_EXPIRED`,并引导用户回到当前有效组件。

6. 阶段与组件映射

阶段由 Python FSM 生成,LLM 不得输出阶段或组件名称。

Stage 主要组件 输入模式
`PRIVACY_CONSENT` `PrivacyConsentCard` `ui_only`
`PHONE_SELECTION` `ResumePhoneSelector` `ui_only`
`MANUAL_PHONE_INPUT` `ResumePhoneInput` `ui_only`
`NAME_CAPTURE` `ResumeNameInput` `ui_only`
`JOB_TYPE_SELECT` `JobTypeCards` `ui_only`
`ANCHOR_TYPE_SELECT` `AnchorTypeCards` `ui_only`
`ANCHOR_COLLECTING` 文本问题、短文本、学历、日期组件 `chat` / `hybrid`
`CONTENT_DISAMBIGUATION` `ChoiceChips` 或补充提示 `ui_only` / `chat`
`ANCHOR_CONFIRM` `ExperienceConfirmCard` `ui_only`
`MINIMUM_READY` `CreateResumeCard` `ui_only`
`RESUME_CREATING` `CreatingStatusCard` `ui_only`
`RESUME_ENRICHING` 文本、进度卡、字段组件 `chat` / `hybrid`
`CONTENT_READY` `ExperienceConfirmCard`、`ContentReadyCard` `ui_only` / `hybrid`
`CREATE_FAILED` `CreateRetryCard` `ui_only`

6.1 隐私卡

隐私文案由产品预设,不由模型生成,至少包含:

  • 数据处理目的。
  • 将收集的信息类型。
  • AI 如何处理用户输入。
  • 不会自动公开、投递或发送给第三方。
  • 用户可以退出并删除草稿。
  • 完整隐私政策链接和版本号。
  • “同意并继续”和“暂不使用”按钮。

6.2 手机号

登录手机号存在时,展示脱敏号码和两个选项:

  • 使用此号码。
  • 使用其他号码。

手动号码使用前端和后端双重格式校验:

^1[3-9]\d{9}$

不发送验证码,不验证号码归属,只提示“仅完成格式校验”。完整手机号不得进入 LLM prompt、模型上下文或 trace。

6.3 姓名和求职类型

姓名使用短文本组件,过滤纯空格和明显非法内容。求职类型提供:

  • 校招
  • 社招
  • 其他

求职类型用于选择首段经历路径和创建后丰富优先级,不写入已有简历字段。

7. 内容门禁

7.1 两级门禁

门禁 作用 条件
业务创建门禁 允许创建业务简历实体 核心资料、求职类型、首段必要经历已确认
正式内容门禁 判断已有正式可用内容 业务简历已创建,至少一段 AI 改写已被用户确认,无冲突

正式内容门禁不能反向删除或隐藏已经创建的业务简历。

7.2 首段必要经历映射

求职类型 默认锚点 创建所需字段
校招 教育经历 学校、专业、学历、开始时间、结束时间或至今
社招 工作经历 公司、职位、开始时间、结束时间或至今
其他 用户选择教育、工作、实习或项目 对应记录的核心字段

实习核心字段为公司、职位、开始时间、结束时间或至今;项目核心字段为项目名、角色、开始时间、结束时间或至今。

以下信息不阻塞业务简历创建:

  • 经历描述和成果。
  • 是否全日制。
  • 第二段经历。
  • 微信、邮箱、城市、作品集。
  • 技能、证书和竞赛。

社招用户没有正式工作经历时,提供:使用实习、使用项目、修改求职类型、保存草稿并退出。

7.3 门禁公式

can_create_resume =
    privacy_accepted
    AND resume_phone_format_valid
    AND name_confirmed
    AND job_type_confirmed
    AND anchor_type_allowed
    AND anchor_required_fields_complete
    AND anchor_dates_valid
    AND anchor_confirmed
    AND unresolved_conflicts == 0

达到门禁后才展示 `CreateResumeCard`。用户点击主按钮后,服务端才写入业务简历。

7.4 AI 改写确认门禁

创建后的经历先生成提议内容:

用户事实
  → LLM 抽取
  → LLM 正式化
  → ExperienceConfirmCard 展示提议
  → 用户确认
  → 更新正式简历 revision

用户选择“需要调整”时,提议内容不得写入正式简历,回到 `RESUME_ENRICHING` 继续对话。

8. 首段经历采集 Loop

8.1 开放式首问

校招:

请介绍当前或最高的一段教育经历,包括学校、专业、学历和就读时间。你可以像平时聊天一样描述。

社招:

请介绍一段最近或最有代表性的工作,包括公司、职位和大致任职时间。

实习:

请介绍一段实习经历,包括公司、职位和实习时间。

项目:

请介绍一个代表性项目,包括项目名、你的角色和项目时间。

8.2 处理链路

自然语言输入
  → LLM 抽取候选字段
  → Pydantic Schema 校验
  → 确定性格式与时间校验
  → 更新临时经历
  → 计算缺失字段
  → 输出一个最高优先级问题或组件
  → 字段齐全后输出确认卡
  → 用户确认
  → MINIMUM_READY

LLM 输出可以包含 `field_updates`、`evidence_spans`、`ambiguities` 和 `extra_records`,但不能输出当前 Stage、Vue 组件、门禁结果或写库指令。

8.3 缺口优先级

工作和实习:

  1. 公司与职位。
  2. 开始和结束时间。
  3. 工作/实习归属歧义。
  4. 经历确认。
  5. 创建后追问职责、行动、方法和成果。

教育:

  1. 学校。
  2. 专业与学历。
  3. 开始和结束时间。
  4. 经历确认。
  5. 创建后追问全日制和学习亮点。

项目:

  1. 项目名与角色。
  2. 开始和结束时间。
  3. 经历确认。
  4. 创建后追问行动、方法和成果。

一轮只处理一个最高优先级缺口,但同一条消息可以抽取多个字段。

8.4 缺口组件策略

缺失内容 组件或交互
公司、职位、学校、专业、项目名 短文本组件或针对性追问
学历 `DegreeSelector`
开始和结束时间 `DateRangeSelector`
工作、实习、项目归属 `ChoiceChips`
职责、行动、方法、成果 自然语言问题
整段经历确认 `ExperienceConfirmCard`

结构化事实优先使用组件;需要回忆、组织和解释的内容优先使用自然语言。

9. 复杂交互规则

9.1 一次提供多段经历

  • 拆分成多个临时记录。
  • 选择符合求职类型的一段完成业务创建门禁。
  • 其他记录保留到创建后的丰富阶段。
  • 未确认的其他记录不得阻止业务简历创建。

9.2 内容归属不明确

显示:

这段内容更接近哪种经历?

选项:工作、实习、项目。选择前不得写入具体业务模块。

9.3 时间处理

  • “至今”是合法结束状态。
  • 结束时间早于开始时间必须要求修正。
  • 模型不能自行推断“至今”。
  • 未知月份不能自动填成一月。
  • 年月统一使用 `YYYY-MM`。

9.4 修改和幂等

  • 修改姓名会同步更新简历名称。
  • 修改首段关键字段会使确认状态失效。
  • 修改求职类型会重新计算必要锚点,但保留已采集事实。
  • 已提交组件重复提交不能产生第二次状态变更。
  • 创建简历按 session 幂等,重试不能产生重复业务简历。

9.5 解析失败

  1. Structured Output 校验失败自动重试一次。
  2. 再失败时不推进阶段。
  3. 保留原始用户消息,输出针对性的组件或追问。
  4. 网关不可用时按配置选择规则降级或安全报错。

10. 创建后丰富

10.1 创建成功消息

包含:

  • 创建成功状态。
  • 简历 ID 或名称。
  • 当前完成度。
  • “继续完善”和“稍后再说”。
  • 下一条高价值问题。

“稍后再说”是正常结束,不计为技术失败。

10.2 丰富优先级

社招:首段工作职责 → 行动/方法/成果 → 更多工作经历 → 项目 → 教育/技能/证书 → 可选联系方式。

校招:实习或项目 → 竞赛 → 教育亮点 → 技能和证书 → 可选联系方式。

其他:当前锚点描述和成果 → 下一段核心经历 → 技能、证书和可选信息。

11. Agent 与 LLM 边界

11.1 Python FSM 负责

  • 当前阶段和合法转移。
  • 活动组件和输入模式。
  • 门禁计算。
  • 字段格式和时间校验。
  • 组件生命周期和幂等。
  • 草稿、业务简历写入。
  • 错误码和恢复策略。

11.2 LLM 负责

  • 从用户自然语言抽取明确事实。
  • 标注证据片段和歧义。
  • 将已确认事实改写为正式简历表达。

11.3 LLM 禁止负责

  • 决定 Stage。
  • 决定使用哪个 Vue 组件。
  • 宣布门禁通过。
  • 自行补全缺失事实。
  • 覆盖用户已确认字段。
  • 直接写数据库。
  • 处理完整手机号、登录标识或会话元数据。

12. 技术架构

12.1 V1 选型

  • Vue 对话组件系统。
  • Python FastAPI。
  • Python 显式 FSM。
  • Pydantic 状态、事件和模型输出 Schema。
  • SQLite 会话、时间线和 MVP 业务简历存储。
  • OpenAI Python SDK 兼容网关。
  • 前端开发代理和 JSON API。

V1 暂不使用 LangChain、LangGraph、AgentExecutor、多 Agent、RAG 或向量数据库。当前流程是明确枚举的单主线状态机,显式实现更容易测试非法事件、回退、幂等和门禁。

当出现上传简历解析、旧简历合并、JD 定制、并行模型节点或人工审核时,再评估迁移 LangGraph。

12.2 服务接口

transition(
    state: ResumeDraftState,
    event: ResumeEvent,
) -> TransitionResult

模型接口:

ExperienceExtractor
ResumeRewriter

渲染层根据 `TransitionResult` 生成 `ConversationTurn`。

12.3 OpenAI SDK 配置

RESUME_AGENT_LLM_PROVIDER=openai
OPENAI_API_KEY=<server-side-secret>
OPENAI_BASE_URL=https://re.94xy.cn
OPENAI_MODEL=gpt-5.5
OPENAI_STRUCTURED_OUTPUT_MODE=json_schema
OPENAI_TIMEOUT_SECONDS=30
OPENAI_MAX_RETRIES=2
OPENAI_STRUCTURED_OUTPUT_RETRIES=1
RESUME_AGENT_LLM_FALLBACK_TO_RULES=true

代码使用官方 SDK 的兼容形式:

client = OpenAI(
    api_key=settings.openai_api_key,
    base_url=settings.openai_base_url,
    timeout=settings.openai_timeout_seconds,
    max_retries=settings.openai_max_retries,
)
client.chat.completions.create(
    model=settings.openai_model,
    messages=messages,
    response_format=response_format,
)

如果网关不支持 `json_schema`,切换为 `json_object`,并继续由 Pydantic 校验结果。

13. API 设计

POST   /ai-api/resume-agent/sessions
GET    /ai-api/resume-agent/sessions/{id}/timeline
POST   /ai-api/resume-agent/sessions/{id}/component-events
POST   /ai-api/resume-agent/sessions/{id}/messages
POST   /ai-api/resume-agent/sessions/{id}/create
DELETE /ai-api/resume-agent/sessions/{id}

组件事件:

{
  "component_id": "block_xxx",
  "event": "submit",
  "payload": {},
  "revision": 3,
  "idempotency_key": "event_xxx"
}

Agent 响应至少包含:

session_id
revision
stage
turn / turns
missing_fields
gate.core_ready
gate.anchor_ready
gate.can_create
gate.formal_content_ready
gate.blockers
draft_id
resume_id
trace_id

手机号和登录标识只在服务端内部状态及脱敏视图中处理,不进入模型请求和客户端日志。

14. 隐私与安全

  • API Key 只放服务端环境变量,不进入 Vue、Git、README、测试或模型输出。
  • `.env` 文件必须被 Git 忽略。
  • 手机号、邮箱、微信号在 SDK 边界二次脱敏。
  • 模型请求使用 allow-list DTO,不传完整 profile、metadata、姓名或登录手机号。
  • trace 只使用随机 ID,不携带 prompt、响应体或用户内容。
  • 网关错误对客户端返回安全错误摘要,不返回响应体和凭证。
  • 用户自由文本可持久化到会话时间线;生产环境需关闭反向代理和 APM 的请求体采集,或增加日志脱敏。
  • 用户可以删除草稿和整个创建会话。
  • 当前使用的网关密钥应使用专用、可撤销凭证;明文暴露后应轮换。

15. 验收指标

指标 MVP 目标
Stage 与组件映射正确率 100%
UI-only 阶段错误开放文本输入 0
已提交组件重复生效 0
过期组件修改当前状态 0
刷新后活动组件恢复正确率 ≥99.5%
已确认字段被重复追问 ≤1%
内容门禁误放率 ≤1%,关键集为 0
内容门禁误阻率 ≤3%
可选字段阻止业务创建 0
第一段必要经历完成率 ≥75%
首段经历提问轮数 P50 ≤4
首段经历提问轮数 P90 ≤7
门禁后一次确认进入创建 ≥95%
业务简历写库成功率 ≥99%
重复创建简历 0
创建后同会话继续丰富率 ≥35%
字段抽取 micro-F1 ≥95%
AI 改写关键事实虚构 0
完整手机号进入 LLM 或 trace 0

16. Eval 方案

16.1 离线数据集

建立脱敏样本集,覆盖:

  • 只提供公司和职位。
  • 一条消息同时提供多字段。
  • 中文和英文公司/职位。
  • 至今、缺月份、倒序日期。
  • 多段经历混写。
  • 工作/实习/项目归属歧义。
  • 提示注入、虚构数字、敏感信息。
  • 用户修改已确认字段。

16.2 自动评测

  • 字段级 precision、recall、micro-F1。
  • 日期合法率和倒序拦截率。
  • 门禁误放、误阻率。
  • 组件映射和输入模式准确率。
  • 过期事件、重复事件、幂等测试。
  • 改写数字和事实 grounding 检查。
  • 手机号、邮箱、微信号泄露扫描。

16.3 人工评测

每条改写按事实保持、正式程度、可读性、成果表达和语气分别评分。任何新增关键数字、公司、职位、技术栈或成果都判定为严重错误。

17. 测试计划

17.1 后端

  • FSM 单元测试。
  • Pydantic Schema 和模型解析测试。
  • Fake OpenAI SDK 测试。
  • API 全流程测试:校招、社招、其他类型。
  • 组件生命周期和过期事件测试。
  • 手机格式和隐私泄露测试。
  • 业务简历创建幂等测试。
  • AI 改写确认后才更新 revision 的测试。

17.2 前端

  • Vue 类型检查。
  • Vite 生产构建。
  • Playwright E2E:隐私 → 手机号 → 姓名 → 求职类型 → 首段经历 → 创建 → 丰富。
  • UI-only 阶段输入框关闭测试。
  • 刷新后活动组件恢复测试。
  • 错误、重试、修改和移动端布局测试。

17.3 真实网关 Smoke Test

使用 `backend/scripts/smoke_llm.py` 发起一次抽取请求,确认:

  • API Key 从环境变量读取。
  • Base URL 和模型 ID 正确。
  • 网关支持当前结构化输出模式。
  • 返回内容能通过 Pydantic 校验。
  • 请求中不含完整手机号或其他受保护字段。

18. MVP 当前实现与后续迭代

当前 MVP 已实现单一对话时间线、Python FSM、SQLite 会话、结构化组件、首段经历门禁、创建后丰富、OpenAI SDK 适配、规则 fallback 和 AI 改写确认。

后续迭代优先级:

  1. 接入正式业务简历服务和认证用户上下文。
  2. 完整支持教育、工作、实习、项目、竞赛、证书和技能字段的创建后组件。
  3. 增加 SSE 流式输出和更细粒度的进度状态。
  4. 增加 Playwright E2E 测试及 CI。
  5. 增加人工审核和事实冲突处理。
  6. 评估上传简历解析、旧简历合并和 JD 定制。

19. Definition of Done

满足以下条件才可称为 MVP 完成:

  • 新用户可以从零资料进入完整首段经历流程。
  • 所有结构化步骤均在对话时间线中完成。
  • 未满足门禁时不会展示可创建按钮或允许创建接口成功。
  • 满足门禁后由用户点击创建,且创建按 session 幂等。
  • AI 改写在用户确认前不更新正式简历。
  • LLM 失败时状态机不被推进到非法阶段。
  • 测试、类型检查和生产构建通过。
  • API Key 不出现在前端、日志、trace、源码或文档中。