22 KiB
零资料新用户 AI 简历创建 Agent PRD
1. 文档信息
| 项目 | 内容 |
|---|---|
| 产品名称 | AI 创建第一份简历 |
| 文档版本 | MVP v1.0 |
| 适用用户 | 没有现成个人资料或简历的新用户 |
| 前端 | Vue |
| 后端 | Python + FastAPI |
| 核心编排 | Python 显式有限状态机(FSM) |
| 模型接入 | OpenAI Python SDK 兼容网关 |
| 当前实现目录 | `resume-agent-mvp/` |
本文档描述产品目标、交互规则、内容门禁、模型边界、技术方案和验收标准。它以“零资料新用户”为前提,不从已有个人资料或历史简历开始。
2. 产品定义
用户进入“AI 创建第一份简历”后,在一个连续的对话时间线中完成:隐私授权、简历手机号、姓名、求职阶段、第一段必要经历采集和确认。结构化信息使用对话消息中的可操作组件,自由描述使用聊天输入框。
产品的核心不是让模型自由规划流程,而是让模型处理事实,让确定性的状态机持续约束流程和内容门禁。
2.1 MVP 目标
- 新用户可以从 0 资料开始创建一份业务简历。
- 结构化步骤不依赖用户自由输入,全部以对话组件呈现。
- Agent 能从自然语言中抽取经历事实,并只追问当前最高优先级缺口。
- 模型不能自行决定阶段、组件、门禁或数据库写入。
- 业务简历创建不被可选字段阻塞;创建后继续丰富。
- 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 组件生命周期
- Python 状态机返回当前阶段。
- 渲染层根据阶段输出唯一主要活动组件。
- 用户提交组件事件。
- 服务端校验组件 ID、事件、会话版本和 payload。
- 原组件变为 `submitted` 或 `confirmed`,不可重复生效。
- 时间线追加结构化用户回复摘要和下一条 Agent 消息。
- 修改关键字段时,依赖它的确认状态失效并重新计算门禁。
同一时间只能有一个主要 `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 缺口优先级
工作和实习:
- 公司与职位。
- 开始和结束时间。
- 工作/实习归属歧义。
- 经历确认。
- 创建后追问职责、行动、方法和成果。
教育:
- 学校。
- 专业与学历。
- 开始和结束时间。
- 经历确认。
- 创建后追问全日制和学习亮点。
项目:
- 项目名与角色。
- 开始和结束时间。
- 经历确认。
- 创建后追问行动、方法和成果。
一轮只处理一个最高优先级缺口,但同一条消息可以抽取多个字段。
8.4 缺口组件策略
| 缺失内容 | 组件或交互 |
|---|---|
| 公司、职位、学校、专业、项目名 | 短文本组件或针对性追问 |
| 学历 | `DegreeSelector` |
| 开始和结束时间 | `DateRangeSelector` |
| 工作、实习、项目归属 | `ChoiceChips` |
| 职责、行动、方法、成果 | 自然语言问题 |
| 整段经历确认 | `ExperienceConfirmCard` |
结构化事实优先使用组件;需要回忆、组织和解释的内容优先使用自然语言。
9. 复杂交互规则
9.1 一次提供多段经历
- 拆分成多个临时记录。
- 选择符合求职类型的一段完成业务创建门禁。
- 其他记录保留到创建后的丰富阶段。
- 未确认的其他记录不得阻止业务简历创建。
9.2 内容归属不明确
显示:
这段内容更接近哪种经历?
选项:工作、实习、项目。选择前不得写入具体业务模块。
9.3 时间处理
- “至今”是合法结束状态。
- 结束时间早于开始时间必须要求修正。
- 模型不能自行推断“至今”。
- 未知月份不能自动填成一月。
- 年月统一使用 `YYYY-MM`。
9.4 修改和幂等
- 修改姓名会同步更新简历名称。
- 修改首段关键字段会使确认状态失效。
- 修改求职类型会重新计算必要锚点,但保留已采集事实。
- 已提交组件重复提交不能产生第二次状态变更。
- 创建简历按 session 幂等,重试不能产生重复业务简历。
9.5 解析失败
- Structured Output 校验失败自动重试一次。
- 再失败时不推进阶段。
- 保留原始用户消息,输出针对性的组件或追问。
- 网关不可用时按配置选择规则降级或安全报错。
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 改写确认。
后续迭代优先级:
- 接入正式业务简历服务和认证用户上下文。
- 完整支持教育、工作、实习、项目、竞赛、证书和技能字段的创建后组件。
- 增加 SSE 流式输出和更细粒度的进度状态。
- 增加 Playwright E2E 测试及 CI。
- 增加人工审核和事实冲突处理。
- 评估上传简历解析、旧简历合并和 JD 定制。
19. Definition of Done
满足以下条件才可称为 MVP 完成:
- 新用户可以从零资料进入完整首段经历流程。
- 所有结构化步骤均在对话时间线中完成。
- 未满足门禁时不会展示可创建按钮或允许创建接口成功。
- 满足门禁后由用户点击创建,且创建按 session 幂等。
- AI 改写在用户确认前不更新正式简历。
- LLM 失败时状态机不被推进到非法阶段。
- 测试、类型检查和生产构建通过。
- API Key 不出现在前端、日志、trace、源码或文档中。