From 35080327d63ea615a089431b61a82bf678209a25 Mon Sep 17 00:00:00 2001 From: zk Date: Thu, 13 Aug 2026 15:51:23 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=BF=BD=E7=95=A5=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 23 + .kiro/specs/resume-diagnose.md | 427 --------------- .kiro/specs/skill-gap-analysis.md | 718 -------------------------- .kiro/steering/code-with-standards.md | 31 -- .kiro/steering/代码开发风格文档.md | 245 --------- .kiro/steering/项目结构说明.md | 201 ------- 6 files changed, 23 insertions(+), 1622 deletions(-) delete mode 100644 .kiro/specs/resume-diagnose.md delete mode 100644 .kiro/specs/skill-gap-analysis.md delete mode 100644 .kiro/steering/code-with-standards.md delete mode 100644 .kiro/steering/代码开发风格文档.md delete mode 100644 .kiro/steering/项目结构说明.md diff --git a/.gitignore b/.gitignore index a722676..cc182ae 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,26 @@ nosetests.xml coverage.xml *.cover .hypothesis/ + +# Kiro 本地配置 +.kiro/ + +# ===== IDE / 编辑器(补充) ===== +.vs/ +.fleet/ +.history/ +*.iml +*.ipr +*.iws + +# ===== AI 编码工具 ===== +.claude/ +.codex/ +.cursor/ +.cursorrules +.windsurf/ +.continue/ +.aider* + +# ===== 操作系统 ===== +desktop.ini diff --git a/.kiro/specs/resume-diagnose.md b/.kiro/specs/resume-diagnose.md deleted file mode 100644 index 0dfc985..0000000 --- a/.kiro/specs/resume-diagnose.md +++ /dev/null @@ -1,427 +0,0 @@ -# 简历诊断功能 - 技术方案 - -## 一、功能概述 - -对用户已有简历的**描述文本**进行 AI 诊断,找出问题、给出改进建议和 AI 改写版本,生成诊断报告。 - -### 诊断范围(只诊断描述文本) - -| 来源表 | 字段 | module_type | -|--------|------|-------------| -| bg_user_resume | summary(个人概述) | summary | -| bg_user_resume_education | description | education | -| bg_user_resume_work | description | work | -| bg_user_resume_internship | description | internship | -| bg_user_resume_project | description | project | -| bg_user_resume_competition | description | competition | - -### 执行策略:先分后合 - -``` -第一阶段:所有模块记录并行 AI 诊断(asyncio.gather) -第二阶段:汇总 issues → 代码算评级 → AI 生成整体评价 → 写入数据库 -``` - -AI 模型:`LLM.DOUBAO_SEED_PRO`,暂不接入功能权限校验。 - ---- - -## 二、问题分类与诊断判断思路 - -### 2.1 紧急修复(urgent) - -#### typo — 错别字 / 语病 - -**判断思路:** -- 检查文本中是否存在明显的**错别字**(如"功则"应为"功能"、"负责"写成"付责") -- 检查是否存在**语病**:主谓搭配不当、语序混乱、成分残缺 -- 检查**标点符号**使用错误:中英文标点混用、缺少句号、逗号过多造成长句 -- 检查**用词不当**:近义词误用、口语化表达出现在正式简历中 - -**AI 判断信号:** -- 同音字替换(的/得/地 混用) -- 句子读不通顺,需要反复阅读才能理解 -- 专业术语拼写错误 -- 一句话中出现多个逗号,缺少句号断句 - ---- - -### 2.2 重点优化(important) - -#### no_result — 缺少成果 - -**判断思路:** -- 描述只写了"做了什么"(任务/职责),没有写"做出了什么结果" -- 典型的**流水账式描述**:只有动作没有产出 -- 缺少对业务/团队/项目的**实际贡献**和**影响** - -**AI 判断信号:** -- 只有动词+宾语("负责XX系统开发"、"参与XX项目"),没有后续的结果说明 -- 全是过程描述,找不到"提升了"、"优化了"、"实现了"、"完成了"等结果性表述 -- 对比 STAR 法则:有 Situation + Task + Action,但缺 Result - -**正面示例(有成果):** -> 负责用户中心系统重构,将接口响应时间从 800ms 降至 200ms,用户投诉率下降 60% - -**反面示例(缺成果):** -> 负责用户中心系统重构,使用 Spring Boot + Redis 实现了新的架构 - -#### no_quantify — 缺少量化 - -**判断思路:** -- 有成果描述但**没有具体数字**支撑 -- 使用了模糊表达:"大幅提升"、"显著改善"、"大量用户",但没有具体数值 -- 缺少以下任何维度的量化:人数、金额、百分比、时间周期、覆盖范围、处理规模 - -**AI 判断信号:** -- 出现"大幅"、"显著"、"有效"、"极大"等模糊程度副词,但没有跟随数字 -- 提到了结果但只是定性描述,没有定量数据 -- 可以合理推断应该有数据但未提供(如"提升了性能"没说提升多少) - -**正面示例(有量化):** -> 优化数据库查询,将平均响应时间从 2s 降至 200ms,日处理订单量从 5 万提升至 20 万 - -**反面示例(缺量化):** -> 优化数据库查询,显著提升了系统性能,改善了用户体验 - -#### weak_relevance — 岗位相关性弱 - -> **前置条件:仅在 `target_position` 有值时才判断,未填目标岗位则跳过此维度,计数为 0。** - -**判断思路:** -- 需要结合 `target_position`(目标岗位)进行判断 -- 描述内容与目标岗位的**核心职责**关联度低 -- 花大量篇幅描述与目标岗位**无关的技能或经历** -- 对于目标岗位来说,这段描述**无法体现匹配度** - -**AI 判断信号:** -- 目标岗位是"Java后端工程师",但描述中全是前端或运营内容 -- 描述的技能/工具与目标岗位的 JD 常见要求差距大 -- 可转移技能存在但未被强调,反而突出了无关内容 - ---- - -### 2.3 表达提升(expression) - -#### not_concise — 表述不精炼 - -**判断思路:** -- 句子**偏长**(单句超过 50 字),信息密度低 -- 存在**赘词和重复表达**:"进行了开发"可简化为"开发了" -- **信息堆叠**:一句话塞了太多内容,应拆分为多个要点 -- 使用了**空泛的修饰词**:"充分"、"积极"、"认真"等无实质信息 - -**AI 判断信号:** -- 单个描述段落超过 80 字但核心信息只有一个 -- 出现"进行了"、"完成了对...的"、"负责了...的工作"等冗余句式 -- 同一段落中重复表达相似的意思 -- 可以删去一半文字而不损失关键信息 - -**正面示例(精炼):** -> 设计并实现分布式缓存方案,QPS 从 1000 提升至 8000,缓存命中率 95% - -**反面示例(不精炼):** -> 在项目中,我积极主动地参与了分布式缓存方案的设计与实现工作,通过对缓存策略的深入研究和反复优化,最终成功地将系统的 QPS 从原来的 1000 提升到了 8000 - -#### format_inconsistent — 格式不统一 - -**判断思路:** -- 同一份简历中**时间格式不统一**(有的写"2023.06",有的写"2023年6月",有的写"2023/06") -- **标点风格不统一**:有的段落用分号结尾,有的用句号,有的不加标点 -- **数字写法不统一**:有的用阿拉伯数字,有的用中文数字 -- **项目符号不统一**:有的用"•",有的用"-",有的用"1." -- **人称不统一**:有的用"我",有的用第三人称,有的省略主语 - -**AI 判断信号:** -- 同一段描述中出现两种以上的格式风格 -- 与模块上下文中的时间格式不一致 -- 段落之间的排版结构差异明显 - ---- - -## 三、综合评级规则 - -由代码硬算,不依赖 AI 判断: - -| 评级 | 条件 | 评语 | -|------|------|------| -| A(优秀) | urgent=0, important<=1, expression<=1 | 您的简历相当出彩,在求职市场中格外抢眼,能清晰展现您的优势与经历,已经超越绝大多数候选人了。 | -| B(良好) | urgent=0, important 2-3, expression<=2 | 简历已经很棒了,但还有提升的潜力。再调整一下细节,会有更具有竞争力! | -| C(一般) | urgent=1, 或 important 3-4 | 你的简历还有打磨空间,多推敲细节、补充些具体内容,整体会更出彩。 | -| D(待提升) | urgent>=2, 或 (important>=4 且 has_weak_relevance) | 您的简历目前还有较大提升空间,建议尽快补充关键经历、完善内容表达,并优化整体结构。 | - -判断优先级:从 D → C → B → A 依次判断,命中即返回。 - ---- - -## 四、数据库表设计 - -### bg_resume_diagnosis_report - -| 字段 | 类型 | 说明 | -|------|------|------| -| id | BigInteger | 主键,雪花ID | -| resume_id | BigInteger | 关联 bg_user_resume.id | -| user_id | BigInteger | 用户ID | -| grade | VARCHAR(1) | 评级 A/B/C/D | -| summary | TEXT | AI 生成的整体评价 | -| urgent_total | Integer | 紧急修复总数 | -| important_total | Integer | 重点优化总数 | -| expression_total | Integer | 表达提升总数 | -| create_time | DateTime | 创建时间 | -| update_time | DateTime | 更新时间 | - -### bg_resume_diagnosis_issue - -| 字段 | 类型 | 说明 | -|------|------|------| -| id | BigInteger | 主键,雪花ID | -| report_id | BigInteger | 关联 report.id | -| resume_id | BigInteger | 关联 bg_user_resume.id | -| user_id | BigInteger | 用户ID | -| module_type | VARCHAR(32) | summary/education/work/internship/project/competition | -| module_record_id | BigInteger | 模块记录ID(summary 时为 resume_id) | -| finding | TEXT | 诊断发现 | -| importance | TEXT | 为什么重要 | -| suggestion | TEXT | 改进建议 | -| urgent_issues | JSON | {"typo": 0} | -| important_issues | JSON | {"no_result": 0, "no_quantify": 0, "weak_relevance": 0} | -| expression_issues | JSON | {"not_concise": 0, "format_inconsistent": 0} | -| optimized_content | JSON | AI改写后的内容。子表模块(education/work/internship/project/competition)与原 description 格式一致 `[{id, text}]`,保持原 id 不变只改写 text;summary 模块为纯文本字符串 | -| status | Integer | 0=待处理 1=已处理 | -| user_feedback | Integer | 0=未评价 1=符合 2=不符合 | -| create_time | DateTime | 创建时间 | -| update_time | DateTime | 更新时间 | - -### 建表 SQL - -```sql -CREATE TABLE `bg_resume_diagnosis_report` ( - `id` bigint NOT NULL COMMENT '主键,雪花ID', - `resume_id` bigint NOT NULL COMMENT '关联bg_user_resume.id', - `user_id` bigint NOT NULL COMMENT '用户ID', - `grade` varchar(1) DEFAULT NULL COMMENT '评级 A/B/C/D', - `summary` text COMMENT 'AI生成的整体评价', - `urgent_total` int NOT NULL DEFAULT '0' COMMENT '紧急修复总数', - `important_total` int NOT NULL DEFAULT '0' COMMENT '重点优化总数', - `expression_total` int NOT NULL DEFAULT '0' COMMENT '表达提升总数', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`id`), - KEY `idx_resume_id` (`resume_id`), - KEY `idx_user_id` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='简历诊断报告表'; - -CREATE TABLE `bg_resume_diagnosis_issue` ( - `id` bigint NOT NULL COMMENT '主键,雪花ID', - `report_id` bigint NOT NULL COMMENT '关联report.id', - `resume_id` bigint NOT NULL COMMENT '关联bg_user_resume.id', - `user_id` bigint NOT NULL COMMENT '用户ID', - `module_type` varchar(32) NOT NULL COMMENT '模块类型: summary/education/work/internship/project/competition', - `module_record_id` bigint NOT NULL COMMENT '模块记录ID,summary时为resume_id', - `finding` text COMMENT '诊断发现', - `importance` text COMMENT '为什么重要', - `suggestion` text COMMENT '改进建议', - `urgent_issues` json DEFAULT NULL COMMENT '紧急修复子类型计数 {"typo": 0}', - `important_issues` json DEFAULT NULL COMMENT '重点优化子类型计数 {"no_result": 0, "no_quantify": 0, "weak_relevance": 0}', - `expression_issues` json DEFAULT NULL COMMENT '表达提升子类型计数 {"not_concise": 0, "format_inconsistent": 0}', - `optimized_content` json DEFAULT NULL COMMENT 'AI改写后的内容,子表模块与原description格式一致[{id,text}]保持原id只改写text,summary模块为纯文本字符串', - `status` int NOT NULL DEFAULT '0' COMMENT '0=待处理 1=已处理', - `user_feedback` int NOT NULL DEFAULT '0' COMMENT '0=未评价 1=符合 2=不符合', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`id`), - KEY `idx_report_id` (`report_id`), - KEY `idx_resume_id` (`resume_id`), - KEY `idx_user_id` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='简历诊断问题表'; -``` - ---- - -## 五、API 设计 - -### 1. POST /resume/diagnose — 触发诊断 - -**请求体:** `{"resume_id": 123}` - -**执行流程:** - -``` -┌──────────────────────────────────────────────────┐ -│ 1. RequestContext.user_id.get() 获取当前用户 │ -├──────────────────────────────────────────────────┤ -│ 2. 短事务1(只读) │ -│ async for session in get_db(): │ -│ service = ResumeDiagnoseService(session) │ -│ resume, tasks = await service │ -│ .load_resume_data(resume_id, user_id) │ -│ → 加载主表 + 5 张子表数据 │ -│ → 组装 AI 任务列表 │ -│ → 事务结束,释放数据库连接 │ -├──────────────────────────────────────────────────┤ -│ 3. 校验 tasks 非空 │ -│ → 空则 raise ValueError("无可诊断内容") │ -├──────────────────────────────────────────────────┤ -│ 4. 并行 AI 诊断(不持有数据库连接) │ -│ ai_results = await diagnose_all(tasks) │ -│ → asyncio.gather 并行调用 N 条诊断链 │ -│ → 每条链独立容错,失败返回空结果 │ -├──────────────────────────────────────────────────┤ -│ 5. 汇总统计 + 评级(纯计算,无 IO) │ -│ → 遍历 ai_results 统计问题数量 │ -│ → 过滤无问题的记录(所有计数都为 0 则跳过) │ -│ → 代码硬算评级(_calc_grade) │ -├──────────────────────────────────────────────────┤ -│ 6. AI 生成整体评价(不持有数据库连接) │ -│ summary = await generate_summary(...) │ -│ → 传入评级、统计、all_findings │ -│ → 返回纯文本评价 │ -├──────────────────────────────────────────────────┤ -│ 7. 短事务2(纯写入,无 AI 调用) │ -│ async for session in get_db(): │ -│ service = ResumeDiagnoseService(session) │ -│ report_id = await service.save_report(...) │ -│ → 写入 report + issues 表 │ -│ → 事务提交 │ -└──────────────────────────────────────────────────┘ -``` - -**响应:** `{"reportId": 456}` - -### 2. GET /resume/diagnose/{resume_id} — 查询最近一次报告 - -**执行流程:** -1. 获取 user_id -2. 查 report 表(ORDER BY create_time DESC LIMIT 1) -3. 查该 report 下所有 issues -4. 返回 `{"report": {...}, "issues": [...]}` - -**响应示例:** -```json -{ - "report": { - "id": "123", - "resumeId": "456", - "grade": "B", - "summary": "您的简历整体质量良好...", - "urgentTotal": 0, - "importantTotal": 3, - "expressionTotal": 1, - "createTime": "2026-04-07 10:30:00" - }, - "issues": [ - { - "id": "789", - "moduleType": "work", - "moduleRecordId": "1001", - "finding": "工作描述缺少量化数据...", - "importance": "量化数据能让招聘者...", - "suggestion": "建议在描述中添加...", - "urgentIssues": {"typo": 0}, - "importantIssues": {"no_result": 0, "no_quantify": 1, "weak_relevance": 0}, - "expressionIssues": {"not_concise": 0, "format_inconsistent": 0}, - "optimizedContent": [{"id": "abc123", "text": "负责XX系统后端开发,日均处理100万+请求..."}], - "status": 0, - "userFeedback": 0 - } - ] -} -``` - -### 3. PUT /resume/diagnose/issue/{issue_id}/resolve — 标记已处理 - -**请求体:** `{"user_feedback": 1}` (1=符合 2=不符合) - -**执行流程:** -1. 获取 user_id -2. 查 issue 记录(校验 user_id) -3. 设置 status=1, user_feedback -4. 返回 null - ---- - -## 六、文件变更清单 - -### 新增 7 个文件 - -``` -app/models/resume_diagnosis_report.py — 诊断报告 ORM -app/models/resume_diagnosis_issue.py — 诊断问题 ORM -app/ai/resume_diagnoser/__init__.py — 包初始化(空文件) -app/ai/resume_diagnoser/prompts.py — Prompt 模板 -app/ai/resume_diagnoser/diagnoser.py — AI 诊断引擎 -app/services/resume_diagnose_service.py — 业务逻辑 -app/api/resume_diagnose.py — API 路由 -``` - -### 修改 1 个文件 - -``` -app/main.py — 注册新路由(加 2 行) -``` - ---- - -## 七、模块设计 - -### AI 诊断模块 (`app/ai/resume_diagnoser/`) - -**prompts.py** — 两个 Prompt 模板: -- `DIAGNOSE_MODULE_PROMPT`:第一阶段,单条记录诊断,输入 module_type/target_position/context/description_text(子表传原始 JSON `[{id,text}]`,summary 传纯文本),输出 JSON -- `SUMMARY_PROMPT`:第二阶段,汇总评价,输入统计数据 + 所有 findings,输出纯文本 - -**diagnoser.py** — 参照 `app/ai/resume_extractor/extractor.py` 的模式: -- 诊断链:`ChatPromptTemplate → DOUBAO_SEED_PRO(temperature=0) → JsonOutputParser` -- 汇总链:`ChatPromptTemplate → DOUBAO_SEED_PRO(temperature=0.3) → StrOutputParser` -- `diagnose_all(tasks)` — asyncio.gather 并行 -- `generate_summary(...)` — AI 生成整体评价 -- `_safe_invoke()` — 容错 - -### Service 层 (`app/services/resume_diagnose_service.py`) - -``` -ResumeDiagnoseService(session: AsyncSession) - ├─ load_resume_data(resume_id, user_id) → (resume, tasks) - │ 加载主表 + 5 张子表,组装 AI 任务列表 - │ - ├─ save_report(resume_id, user_id, grade, summary, stats, tasks, ai_results) → report_id - │ 纯写入:接收已算好的 grade、summary、统计数据,写入 report + issues - │ 跳过无问题的记录(所有计数为 0 则不创建 issue 行) - │ - ├─ get_latest_report(resume_id, user_id) → dict | None - └─ resolve_issue(issue_id, user_id, user_feedback) → None - -工具函数(无状态,路由层或 Service 外部可调用): - ├─ _build_description_text(description) — 子表传原始 JSON 字符串 [{id,text}],summary 传纯文本 - ├─ _calc_grade(urgent, important, expression, has_weak_relevance) — 评级硬算 - ├─ _aggregate_results(tasks, ai_results) — 统计汇总 + 过滤无问题记录 - └─ _issue_to_dict(issue) — ORM → camelCase dict -``` - -**description_text 传入格式:** -- 子表模块:传原始 JSON 字符串 `[{"id": "abc123", "text": "负责XX系统..."}, ...]`,AI 能看到每个段落的 id -- summary 模块:传纯文本(summary 字段本身就是 VARCHAR) - -**optimized_content 返回格式:** -- 子表模块:与原始 description 格式一致 `[{id, text}]`,保持原 id 不变,只改写 text -- summary 模块:纯文本字符串(与原始 summary 字段格式一致) - -### API 路由 (`app/api/resume_diagnose.py`) - -``` -router = APIRouter(prefix="/resume/diagnose", tags=["简历诊断"]) - ├─ POST "" — 触发诊断 - ├─ GET "/{resume_id}" — 查询报告 - └─ PUT "/issue/{issue_id}/resolve" — 标记已处理 -``` - ---- - -## 八、实施顺序 - -1. ORM 模型(`resume_diagnosis_report.py` + `resume_diagnosis_issue.py`) -2. AI 模块(`__init__.py` + `prompts.py` + `diagnoser.py`) -3. Service 层(`resume_diagnose_service.py`) -4. API 路由(`resume_diagnose.py`)+ 修改 `main.py` -5. 更新项目结构文档 diff --git a/.kiro/specs/skill-gap-analysis.md b/.kiro/specs/skill-gap-analysis.md deleted file mode 100644 index e8d5b66..0000000 --- a/.kiro/specs/skill-gap-analysis.md +++ /dev/null @@ -1,718 +0,0 @@ -# 岗位简历技能差距分析 + 定制简历 — 完整方案 - -## 一、需求概述 - -三步流程: -1. **差距分析**:根据岗位技能标签和用户简历,AI 判断缺失技能,纯计算匹配分 -2. **定制简历**:用户选择要优化的模块和要新增的技能,AI 生成优化后的简历内容 -3. **预览 + AI 对话编辑**:前端渲染定制简历,用户可通过 AI 对话继续编辑,用于投递时使用(不写回原简历) - -定制简历存 Redis,过期时间 12 小时,不落库。一个用户同时只有一份定制简历。 - ---- - -## 二、接口总览 - -| 序号 | 路径 | 方法 | 说明 | -|------|------|------|------| -| 1 | `/api/job/skill-gap` | POST | 差距分析 | -| 2 | `/api/job/customize-resume` | POST | 生成定制简历 | -| 3 | `/api/job/customize-resume` | GET | 查询定制简历 | -| 4 | `/api/job/customize-resume` | PUT | 手动编辑定制简历 | -| 5 | `/api/job/customize-resume/rollback` | POST | 回滚定制简历 | -| 6 | `/api/job/customize-resume/ai-edit` | POST | AI 对话式编辑定制简历 | - ---- - -## 三、接口一:差距分析 - -### 接口信息 - -| 项目 | 值 | -|------|-----| -| 路径 | `POST /api/job/skill-gap` | -| 入参 | `{ "jobId": Long }` | -| 鉴权 | 需要登录态,从 token 取 userId | - -### 处理流程 - -1. 从 token 取 userId -2. 查简历(自动选择,不传 resumeId): - - 先查 `bg_user_resume` 中 `user_id=userId AND is_default=1`,按 `update_time DESC` 取第一条 - - 没有默认简历 → 查 `user_id=userId`,按 `update_time DESC` 取第一条 - - 没有任何简历 → 报错"请先创建简历" -3. 查岗位: - - 查 `bg_job` 拿 id、title、skill_tags - - 岗位不存在 → 报错 - - skill_tags 为空 → 直接返回满分 10,gaps 为空数组 -4. 查简历子表(拼 AI 输入): - - `bg_user_resume_education` - - `bg_user_resume_work` - - `bg_user_resume_internship` - - `bg_user_resume_project` - - `bg_user_resume_competition` -5. 调 AI(一次): - - 输入:岗位 skill_tags 列表 + 简历 skills 字段 + 各子表经历描述 - - 输出:差距条目 JSON 数组,每条含 keyword(缺失技能,skill_tags 子集)、title(≤12字)、description(≤30字) -6. 计算匹配分:`score = (skill_tags总数 - gaps条目数) / skill_tags总数 × 10`,保留一位小数 - -### 返回 - -```json -{ - "score": 2.5, - "job": { - "jobId": "1234567890", - "title": "数据产品经理", - "skillTags": ["Python", "SQL", "项目管理", "团队协作", "数据分析", "跨部门沟通"] - }, - "resume": { - "resumeId": "1234567890", - "resumeName": "李华_产品经理", - "targetPosition": "电商产品经理" - }, - "gaps": [ - { "keyword": "SQL", "title": "缺少 SQL 关键词", "description": "岗位要求数据查询分析能力,简历未体现。" }, - { "keyword": "数据分析", "title": "数据分析证据不足", "description": "建议补充指标拆解与结论产出的经历。" } - ] -} -``` - -### 边界处理 - -| 场景 | 处理 | -|------|------| -| 用户无简历 | 报错"请先创建简历" | -| 岗位不存在 | 报错 | -| skill_tags 为空 | 满分 10,gaps 为空数组 | -| AI 调用失败 | 降级:全部标记为缺失,分数 0 | - -### AI Prompt - -``` -你是一个技能匹配助手。给定岗位要求的技能标签列表和用户简历信息,找出用户简历中未覆盖的技能并生成差距说明。 - -【岗位技能标签】 -{skill_tags} - -【用户简历】 -{resume_json} - -规则: -1. 逐个判断岗位技能标签,用户简历中是否体现了该技能(包括直接提及、经历中隐含的技能) -2. 只针对未覆盖的技能输出差距条目,已覆盖的技能不输出 -3. 每个差距条目包含三个字段: - - keyword:缺失的技能,必须是岗位技能标签中的原文,不要修改 - - title:差距点的简短标题,不超过 12 个字(例如"缺少 SQL 关键词") - - description:结合岗位要求的一句说明或建议,不超过 30 个字 -4. 返回 JSON 数组格式,如: - [{"keyword": "SQL", "title": "缺少 SQL 关键词", "description": "岗位要求数据查询分析能力,简历未体现。"}] -5. 如果全部覆盖,返回空数组 [] -6. 只返回 JSON 数组,不要其他内容 -``` - ---- - -## 四、接口二:生成定制简历 - -### 接口信息 - -| 项目 | 值 | -|------|-----| -| 路径 | `POST /api/job/customize-resume` | -| 入参 | 见下方 | -| 鉴权 | 需要登录态,从 token 取 userId | - -### 入参 - -```json -{ - "jobId": "Long", - "resumeId": "Long", - "optimizeModules": ["summary", "skills", "experience"], - "addSkills": ["Python", "SQL"] -} -``` - -- `resumeId`:以哪份简历为模板(来自差距分析返回的 resumeId,用户可能切换过简历) -- `optimizeModules`:用户勾选要优化的模块,可选值:summary(个人概述)、skills(技能)、experience(过往经历) -- `addSkills`:用户勾选要新增的技能关键词(来自差距分析 gaps 的 keyword) - -### 处理流程 - -1. 查简历主表 + 所有子表(完整简历数据) -2. 查岗位信息(title、description、requirement) -3. 按用户选择的模块分别处理(各模块并发执行,最后合并): - -**summary(个人概述)**: -- 调 AI,根据岗位信息微调 summary,融入选中的技能关键词 -- 避免过度优化,保持原文风格,只做轻微润色 - -**skills(技能)**: -- 把 addSkills 追加到现有 skills 列表,不调 AI,纯内存操作 - -**experience(过往经历)**: -- 按子表(education/work/internship/project/competition)为单位,每个子表一个 AI 调用,传入该子表的完整数据 -- 让描述更贴合岗位方向,避免过度优化,基本保持原文不变 -- 不融入 addSkills,经历描述不硬塞技能关键词 - -**addSkills 影响范围**:只影响 skills(直接追加)和 summary(自然融入),不影响 experience。 - -**并发策略**:summary 优化 和 各子表优化 全部并发执行(asyncio.gather),skills 纯内存操作不需要等待。最终合并所有结果。 - -4. 未勾选的模块保持原数据不动 -5. 组装完整的定制简历数据,存 Redis(key:`customize:resume:{userId}`,过期 12 小时,重新生成会覆盖) -6. 返回成功标识,不返回简历数据(前端通过 GET 接口查询) - -### 返回 - -```json -{ - "success": true -} -``` - -说明:简历数据前端通过 `GET /api/job/customize-resume` 查询。子表记录的 id 使用随机 8 位字符串作为标识(从数据库查出时生成),不使用数据库原始 id。 - -### 边界处理 - -| 场景 | 处理 | -|------|------| -| 简历不存在 | 报错 | -| 岗位不存在 | 报错 | -| optimizeModules 为空 | 报错"请至少选择一个优化模块" | -| AI 调用失败 | 该模块保持原数据不动,不影响其他模块 | - ---- - -## 五、接口三:查询定制简历 - -### 接口信息 - -| 项目 | 值 | -|------|-----| -| 路径 | `GET /api/job/customize-resume` | -| 入参 | 无 | -| 鉴权 | 需要登录态,从 token 取 userId | - -### 处理流程 - -1. 从 Redis 取定制简历数据(key:`customize:resume:{userId}`) -2. 不存在 → 返回 null -3. 返回完整简历 JSON - ---- - -## 六、接口四:修改定制简历(手动编辑) - -### 接口信息 - -| 项目 | 值 | -|------|-----| -| 路径 | `PUT /api/job/customize-resume` | -| 入参 | 完整简历 JSON(整体覆盖) | -| 鉴权 | 需要登录态,从 token 取 userId | - -### 处理流程 - -1. 校验入参 -2. 整体覆盖 Redis 中的定制简历数据(key:`customize:resume:{userId}`) -3. 刷新过期时间为 12 小时 -4. 不存在时也直接写入 - ---- - -## 七、接口五:回滚定制简历 - -### 接口信息 - -| 项目 | 值 | -|------|-----| -| 路径 | `POST /api/job/customize-resume/rollback` | -| 入参 | 无 | -| 鉴权 | 需要登录态,从 token 取 userId | - -### 处理流程 - -1. 从 Redis 取回滚数据(key:`customize:resume:rollback:{userId}`) -2. 不存在 → 报错"没有可回滚的版本" -3. 用回滚数据覆盖当前定制简历(key:`customize:resume:{userId}`) -4. 删除回滚数据 -5. 刷新定制简历过期时间为 12 小时 - ---- - -## 七、接口五:AI 对话式编辑定制简历 - -### 接口信息 - -| 项目 | 值 | -|------|-----| -| 路径 | `POST /api/job/customize-resume/ai-edit` | -| 入参 | 见下方 | -| 鉴权 | 需要登录态,从 token 取 userId | - -### 入参 - -```json -{ - "jobId": "Long", - "instruction": "精简一下第一段工作经历", - "chatHistory": [ - { "role": "user", "content": "优化描述" }, - { "role": "assistant", "content": "你想优化哪一部分?" } - ] -} -``` - -- `instruction`:用户当前输入的指令 -- `chatHistory`:之前的对话历史,前端维护,每次请求带上 - -### 消息类型 - -返回两种消息类型: - -**message(普通对话)**:AI 追问、引导,不修改简历 -```json -{ - "type": "message", - "message": "你想优化哪一部分的描述?是最新的实习还是所有工作经历?" -} -``` - -**updated(修改通知)**:AI 修改了简历,返回新版本 -```json -{ - "type": "updated", - "message": "完成!已更新:个人简介、技能、工作经验" -} -``` - -### 处理流程(两步走) - -核心思路:将用户操作原子化,把模块级并发拆成记录级并发,减少单次 AI 输出量,提升响应速度。 - -#### 第一步:准备数据 - -1. 从 Redis 取当前定制简历(不存在则报错) -2. 查 `bg_job` 拿 title、description、requirement - -#### 第二步:规划 AI(意图识别 + 操作原子化) - -输入:用户指令 + 对话历史 + 当前完整简历内容 + 岗位信息(title + description + requirement) - -输出两种结果: - -**对话(指令不明确)**: -```json -{ "action": "chat", "message": "你想优化哪一部分?" } -``` -→ 直接返回 `{ "type": "message", "message": "..." }`,结束。 - -**操作计划(指令明确)**: -```json -{ - "action": "modify", - "operations": [ - { "type": "delete", "module": "work", "id": "abc12345" }, - { "type": "update", "module": "work", "id": "def67890", "instruction": "精简描述,突出量化成果" }, - { "type": "update", "module": "resume", "instruction": "在summary中融入数据分析关键词" }, - { "type": "add", "module": "internship", "instruction": "新增一段数据分析实习经历" } - ] -} -``` - -操作类型: -- **delete**:删除记录,需要 module + id -- **update**:修改记录,需要 module + id + instruction(resume 主表不需要 id) -- **add**:新增记录,需要 module + instruction - -规则: -- 每个操作对应一条原子操作,一个用户指令可拆出多条操作 -- `instruction` 限 50 字以内 -- `delete` 和 `update`(非 resume)必须带 `id`,从当前简历中匹配 -- `update` module 为 `resume` 时不需要 `id`(主表是单对象) -- `add` 不需要 `id` - -模块划分(按表结构,共 6 个): - -| 模块名 | 对应表 | 可修改字段 | -|--------|--------|-----------| -| `resume` | `bg_user_resume` | avatarUrl、name、email、mobileNumber、city、wechatNumber、portfolioUrl、skills、certificates、summary | -| `education` | `bg_user_resume_education` | 全部字段 | -| `work` | `bg_user_resume_work` | 全部字段 | -| `internship` | `bg_user_resume_internship` | 全部字段 | -| `project` | `bg_user_resume_project` | 全部字段 | -| `competition` | `bg_user_resume_competition` | 全部字段 | - -#### 第三步:按操作类型并发执行 - -先处理 delete(零 AI 开销),再并发执行所有 update 和 add(asyncio.gather): - -- **delete**:后端直接按 module + id 从数组移除,不调 AI -- **update(含 resume 主表和子表记录)**:按 id 取出单条记录 + instruction + schema + 岗位信息(title + description + requirement)+ 最近 10 条对话历史,调 AI 修改,只输出单条记录。resume 主表和子表记录复用同一个 prompt -- **add**:instruction + schema + 岗位信息(title + description + requirement)+ 最近 10 条对话历史,调 AI 生成一条新记录 - -#### 第四步:合并 - -- delete 已直接处理 -- update 按 module + id 替换回原数据(resume 主表直接整体替换) -- add 按 module 追加到对应数组 -- AI 调用失败的操作跳过,不影响其他操作 - -#### 第五步:保存 + 返回 - -1. 当前简历存为回滚数据(key:`customize:resume:rollback:{userId}`,过期 30 分钟) -2. 新简历覆盖 Redis(key:`customize:resume:{userId}`),刷新过期时间 12 小时 -3. `updatedModulesLabel` 由后端根据 operations 中的 module 去重映射中文名拼接 -4. 返回 `type: updated` + 消息(前端通过 GET 接口查询新简历,通过回滚接口恢复) - -### description 字段处理 - -子表的 description 字段格式为 `[{id, text}, {id, text}]`,AI 操作规则(通过 prompt 约束): -- **修改**:保留原 id,只改 text -- **新增**:AI 自行生成随机 8 位字符串作为 id -- **删除**:直接从数组中移除 - -不做后端校验,完全依靠 prompt 约束 AI 行为。 - -### 边界处理 - -| 场景 | 处理 | -|------|------| -| 定制简历不存在 | 报错"定制简历不存在,请先生成" | -| 规划 AI 失败 | 返回 `type: message`,提示重试 | -| 某个模块修改 AI 失败 | 该模块保持原数据,其他模块正常返回 | - ---- - -## 八、AI Prompt 汇总 - -### 1. 差距分析 Prompt - -``` -你是一个技能匹配助手。给定岗位要求的技能标签列表和用户简历信息,找出用户简历中未覆盖的技能并生成差距说明。 - -【岗位技能标签】 -{skill_tags} - -【用户简历】 -{resume_json} - -规则: -1. 逐个判断岗位技能标签,用户简历中是否体现了该技能(包括直接提及、经历中隐含的技能) -2. 只针对未覆盖的技能输出差距条目,已覆盖的技能不输出 -3. 每个差距条目包含三个字段: - - keyword:缺失的技能,必须是岗位技能标签中的原文,不要修改 - - title:差距点的简短标题,不超过 12 个字(例如"缺少 SQL 关键词") - - description:结合岗位要求的一句说明或建议,不超过 30 个字 -4. 返回 JSON 数组格式,如: - [{"keyword": "SQL", "title": "缺少 SQL 关键词", "description": "岗位要求数据查询分析能力,简历未体现。"}] -5. 如果全部覆盖,返回空数组 [] -6. 只返回 JSON 数组,不要其他内容 -``` - -### 2. 定制简历 - summary 优化 Prompt - -``` -你是一个简历优化助手。根据目标岗位信息,微调用户的个人概述。 - -【目标岗位】 -{job_title} - -【需要融入的技能关键词】 -{add_skills} - -【原始个人概述】 -{original_summary} - -规则: -1. 保持原文风格和主体内容不变 -2. 只做轻微润色,让概述更贴合目标岗位方向 -3. 自然融入需要新增的技能关键词,不要生硬堆砌 -4. 避免过度优化,改动越少越好 -5. 直接输出优化后的文本,不要其他内容 -``` - -### 3. 定制简历 - experience 优化 Prompt - -``` -你是一个简历优化助手。根据目标岗位信息,微调用户的经历描述。 - -【目标岗位】 -{job_title} -{job_description} - -【原始经历数据】 -{original_module_data} - -规则: -1. 基本保持原文不变,只在可以优化的地方做轻微调整 -2. 让描述更贴合目标岗位方向,但不要编造内容 -3. 避免过度优化,改动越少越好 -4. description 字段是 [{id, text}] 格式:修改时保留原 id 只改 text,新增段落生成随机8位字符串作为 id,删除段落直接移除 -5. 返回修改后的完整模块数据(JSON 格式,与输入格式一致) -``` - -### 4. Agent - 规划 Prompt - -``` -你是一个简历编辑助手。分析用户的指令,将其拆解为原子操作。 - -【目标岗位】 -{job_title} -{job_description} - -【当前简历】 -{resume_json} - -【对话历史】 -{chat_history} - -【用户指令】 -{instruction} - -如果用户指令不明确或需要澄清,返回: -{"action": "chat", "message": "你的追问内容"} - -如果用户指令明确,将其拆解为原子操作列表,返回: -{"action": "modify", "operations": [...]} - -操作类型: -1. 删除记录:{"type": "delete", "module": "模块名", "id": "记录id"} -2. 修改记录:{"type": "update", "module": "模块名", "id": "记录id", "instruction": "修改说明(50字内)"} -3. 修改主表:{"type": "update", "module": "resume", "instruction": "修改说明(50字内)"} -4. 新增记录:{"type": "add", "module": "模块名", "instruction": "新增说明(50字内)"} - -模块名可选:resume(主表,包含 name、email、mobileNumber、city、wechatNumber、portfolioUrl、skills、certificates、summary、avatarUrl)、education(教育)、work(工作)、internship(实习)、project(项目)、competition(竞赛) - -规则: -1. 每条操作对应一个最小粒度的修改,一个用户指令可拆出多条操作 -2. delete 和 update(非resume)必须带 id,从当前简历中匹配 -3. instruction 不超过50字,简明扼要 -4. 只返回 JSON,不要其他内容 -``` - -### 5. Agent - 单条记录修改 Prompt - -``` -你是一个简历编辑助手。根据修改要求,修改简历中的一条记录。 - -【目标岗位】 -{job_title} -{job_description} - -【修改要求】 -{instruction} - -【最近对话】 -{chat_history} - -【模块数据结构】 -{module_schema} - -【当前记录数据】 -{record_data} - -规则: -1. 严格按照修改要求操作 -2. 未要求修改的字段保持不变 -3. 不要编造用户简历中不存在的内容 -4. 保持原文格式和结构 -5. description 字段是 [{id, text}] 格式:修改时保留原 id 只改 text,新增段落生成随机8位字符串作为 id,删除段落直接移除 -6. 返回修改后的完整记录数据(JSON 格式,与输入格式一致) -``` - -### 6. Agent - 新增记录 Prompt - -``` -你是一个简历编辑助手。根据要求,生成一条新的简历记录。 - -【目标岗位】 -{job_title} -{job_description} - -【新增要求】 -{instruction} - -【最近对话】 -{chat_history} - -【模块数据结构】 -{module_schema} - -规则: -1. 按照模块数据结构生成完整字段 -2. id 使用随机8位字符串 -3. description 中每个段落的 id 也使用随机8位字符串 -4. 内容要合理真实,贴合目标岗位方向 -5. 返回一条完整记录的 JSON,与模块数据结构一致 -``` - -### 各模块数据结构定义(传入 prompt 的 module_schema) - -**resume(主表)**: -```json -{ "avatarUrl": "string", "name": "string", "email": "string", "mobileNumber": "string", "city": "string", "wechatNumber": "string", "portfolioUrl": "string", "skills": ["string"], "certificates": ["string"], "summary": "string" } -``` - -**education**: -```json -[{ "id": "string(8位)", "school": "string", "major": "string", "degree": "大专/本科/硕士/博士", "studyType": "全日制/非全日制", "startDate": "2023.09", "endDate": "2024.06", "description": [{"id": "string(8位)", "text": "string"}] }] -``` - -**work**: -```json -[{ "id": "string(8位)", "companyName": "string", "position": "string", "startDate": "2023.06", "endDate": "2023.09", "description": [{"id": "string(8位)", "text": "string"}] }] -``` - -**internship**: -```json -[{ "id": "string(8位)", "companyName": "string", "position": "string", "startDate": "2023.06", "endDate": "2023.09", "description": [{"id": "string(8位)", "text": "string"}] }] -``` - -**project**: -```json -[{ "id": "string(8位)", "companyName": "string", "projectName": "string", "role": "string", "startDate": "2023.06", "endDate": "2023.09", "description": [{"id": "string(8位)", "text": "string"}] }] -``` - -**competition**: -```json -[{ "id": "string(8位)", "competitionName": "string", "award": "string", "awardDate": "2023.07", "description": [{"id": "string(8位)", "text": "string"}] }] -``` - ---- - -## 九、Redis 设计 - -### Key 格式 - -- 定制简历:`customize:resume:{userId}` -- 回滚数据:`customize:resume:rollback:{userId}` - -### Value 结构 - -```python -class CustomizeResume: - """定制简历缓存结构""" - resume: ResumeProfile # 主表信息 - education: list[Education] # 教育经历 - work: list[Work] # 工作经历 - internship: list[Internship] # 实习经历 - project: list[Project] # 项目经历 - competition: list[Competition] # 竞赛经历 - -class ResumeProfile: - """主表可修改字段""" - avatarUrl: str - name: str - email: str - mobileNumber: str - city: str - wechatNumber: str - portfolioUrl: str - skills: list[str] - certificates: list[str] - summary: str - -class Education: - id: str # 随机8位标识 - school: str - major: str - degree: str # 大专/本科/硕士/博士 - studyType: str # 全日制/非全日制 - startDate: str # 格式:2023.09 - endDate: str # 格式:2024.06 - description: list[Paragraph] - -class Work: - id: str - companyName: str - position: str - startDate: str - endDate: str - description: list[Paragraph] - -class Internship: - id: str - companyName: str - position: str - startDate: str - endDate: str - description: list[Paragraph] - -class Project: - id: str - companyName: str - projectName: str - role: str - startDate: str - endDate: str - description: list[Paragraph] - -class Competition: - id: str - competitionName: str - award: str - awardDate: str # 格式:2023.07 - description: list[Paragraph] - -class Paragraph: - id: str # 随机8位标识 - text: str -``` - -定制简历和回滚数据使用相同的 `CustomizeResume` 结构。代码实现时使用 Pydantic model,存取 Redis 通过 `model_dump_json()` / `model_validate_json()`。 - -### 常量 - -```python -CUSTOMIZE_RESUME_KEY_PREFIX = "customize:resume:" -CUSTOMIZE_RESUME_EXPIRE = 12 * 60 * 60 # 12小时 -CUSTOMIZE_RESUME_ROLLBACK_KEY_PREFIX = "customize:resume:rollback:" -CUSTOMIZE_RESUME_ROLLBACK_EXPIRE = 30 * 60 # 30分钟 -``` - -### 过期时间 - -- 定制简历:12 小时,每次写入/修改时刷新 -- 回滚数据:30 分钟,每次 AI 编辑时覆盖 - ---- - -## 十、数据依赖 - -| 表 | 读写 | 用途 | -|----|------|------| -| `bg_job` | 读 | 取岗位信息(title、description、requirement、skill_tags) | -| `bg_user_resume` | 读 | 取简历主表数据 | -| `bg_user_resume_education` | 读 | 取教育经历 | -| `bg_user_resume_work` | 读 | 取工作经历 | -| `bg_user_resume_internship` | 读 | 取实习经历 | -| `bg_user_resume_project` | 读 | 取项目经历 | -| `bg_user_resume_competition` | 读 | 取竞赛经历 | -| Redis | 写/读 | 存取定制简历,过期 12 小时 | - -无新建表,无数据库写操作。 - ---- - -## 十一、文件规划 - -### 新建文件 - -| 文件 | 职责 | -|------|------| -| `app/models/job.py` | Job 表 ORM 模型(bg_job,只读) | -| `app/core/schemas/skill_gap.py` | Pydantic Schema(请求参数 Param + 响应 Dto + Redis 缓存模型 CustomizeResume) | -| `app/ai/skill_gap_analyzer/__init__.py` | 模块初始化 | -| `app/ai/skill_gap_analyzer/prompts.py` | 所有 AI prompt 模板 | -| `app/ai/skill_gap_analyzer/analyzer.py` | AI 调用逻辑(差距分析 + 定制简历优化 + Agent 规划/执行) | -| `app/services/skill_gap_service.py` | 业务逻辑层(含 Redis 常量、简历查询、Redis 读写) | -| `app/api/skill_gap.py` | 路由层(6 个接口) | - -### 修改文件 - -| 文件 | 改动 | -|------|------| -| `app/main.py` | 注册 skill_gap 路由 | diff --git a/.kiro/steering/code-with-standards.md b/.kiro/steering/code-with-standards.md deleted file mode 100644 index 60d3cf3..0000000 --- a/.kiro/steering/code-with-standards.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -inclusion: always ---- - -# 项目规范执行指引 - -## 方案讨论前 - -必读 `#[[file:.kiro/steering/项目结构说明.md]]`,全面了解: -- 项目分层结构:`config` → `core` → `ai` → `models` → `services` → `api` -- 所有 ORM 模型及其关联 -- 鉴权体系设计(JWT + Redis + 功能权限) -- 现有的公共能力(中间件、日志、异常处理、AI 模型枚举),避免重复造轮子 -- 与 Java 后端(back-end)的关系:共享数据库、Redis、JWT Secret - -方案讨论时: -- 优先给出简洁的方案思路(涉及哪些模块、新增内容放在哪、核心流程概要),不要一开始就铺开所有细节 -- 用户明确要求时,再给出详细的方案流程(表结构、接口清单、完整逻辑步骤等) -- 做好解耦,说明与现有模块的关系 - -## 开发方案输出前 / 写代码前 - -必读 `#[[file:.kiro/steering/代码开发风格文档.md]]`,严格遵守: -- 命名约定、类型注解规范 -- 分包规则(api/services/models/schemas 按功能模块组织) -- Service 类注释规范(主要功能、依赖服务、使用的表、方法逻辑流程) -- 接口规范、异常处理、Redis 使用、数据库设计风格 - -## 写完代码后 - -涉及新增文件、新增模块或目录结构变更时,必须同步更新 `#[[file:.kiro/steering/项目结构说明.md]]`,保持文档与实际代码一致。 diff --git a/.kiro/steering/代码开发风格文档.md b/.kiro/steering/代码开发风格文档.md deleted file mode 100644 index e7218f5..0000000 --- a/.kiro/steering/代码开发风格文档.md +++ /dev/null @@ -1,245 +0,0 @@ ---- -inclusion: manual ---- - -# 代码开发风格文档 - -本项目为 FastAPI + SQLAlchemy (asyncio) 的 Python 3.12 后端项目,应用主目录为 `app/`。 - -## 项目结构 - -- `app/config/` — 配置层:Pydantic Settings 统一配置 -- `app/core/` — 核心基础设施:数据库、Redis、鉴权、中间件、异常处理、日志、统一响应 -- `app/ai/` — AI 能力层:LLM 模型枚举与实例创建 -- `app/api/` — 路由层:REST API 接口定义 -- `app/models/` — ORM 模型层:SQLAlchemy 声明式映射 -- `app/services/` — 业务逻辑层:Service 类 -- `app/core/schemas/` — 公共 Schema:统一响应模型等 - -## 命名约定 - -### 文件命名 -- 全部小写,下划线分隔,如 `func_permission_service.py`、`user_func_usage_log.py` -- 路由文件以业务名命名,如 `health.py`、`resume.py` -- ORM 模型文件与表名对应(去掉 `bg_` 前缀),如 `func_permission.py` 对应 `bg_func_permission` - -### 类命名 -- Service 以 `Service` 结尾,如 `FuncPermissionService` -- ORM 模型用 PascalCase 业务名,无后缀,如 `FuncPermission`、`UserFuncUsageLog` -- Pydantic Schema 按用途命名:请求参数以 `Param` 结尾,响应以 `Dto` 结尾,如 `ResumeParam`、`ResumeDto` -- 枚举类以大写命名,如 `LLM` - -### 变量与函数命名 -- 函数和变量使用 snake_case,如 `check_and_deduct`、`user_id` -- 私有函数以单下划线开头,如 `_insert_usage_log` -- 常量使用全大写下划线,如 `_FRIENDLY_MESSAGES`、`_SKIP_PATHS` - -## 类型注解 - -- 所有函数参数和返回值必须有类型注解 -- ORM 模型字段使用 `Mapped[T]` + `mapped_column()` 声明 -- Pydantic 模型字段使用标准类型注解 + `Field()` -- 可选字段使用 `Optional[T]` 或 `T | None` -- 集合类型使用 `list[T]`、`dict[K, V]`(Python 3.12 内置泛型) - -## 注释规范 - -- 模块级注释使用文件顶部的 docstring,说明模块用途和使用示例 -- 类注释使用 docstring,说明对应的表名和用途 -- 方法注释使用 docstring,简洁描述功能 -- 复杂逻辑用行内注释 `#` 说明 - -### ORM 模型类注释 -- 类 docstring 说明对应的表名和用途 -- 特殊字段通过 `comment` 参数说明含义,如 `comment="状态 1=启用 0=禁用"` - -### Service 类注释 -- 模块级 docstring 说明该服务的主要功能、依赖服务、使用的表 -- 格式示例: - ```python - """功能权限 Service - - 校验用户功能权限并扣减库存,业务异常时回退。 - 逻辑与 Java 端 FuncPermissionService 完全一致。 - """ - ``` -- 每个方法用 docstring 简要说明逻辑流程,复杂方法可分步骤描述 - -## 分包规则 - -### API 路由(`app/api/`) -- 每个业务模块一个路由文件,如 `health.py`、`resume.py` -- 使用 `APIRouter(prefix="/xxx", tags=["xxx"])` 定义路由前缀和标签 -- 在 `app/main.py` 中注册路由 - -### Service(`app/services/`) -- 每个业务模块一个 Service 文件 -- Service 类通过构造函数接收 `AsyncSession`,如 `def __init__(self, session: AsyncSession)` -- 不使用全局 Service 实例,每次请求通过依赖注入创建 - -### ORM 模型(`app/models/`) -- 每个表一个模型文件 -- 所有模型继承 `app.core.database.Base` -- 表名通过 `__tablename__` 指定 - -### Pydantic Schema(`app/core/schemas/`) -- 公共 Schema 放在 `app/core/schemas/` 下,如 `responses.py` -- 业务相关的请求/响应 Schema 放在对应的 `app/api/` 或 `app/services/` 同级目录,或集中在 `app/core/schemas/{功能模块}/` 下 - -## 获取当前登录用户 - -- 通过 `RequestContext.user_id.get()` 获取当前登录用户 ID -- 或通过依赖注入 `Depends(require_login)` 获取并校验 -- 需要功能权限校验时使用 `Depends(func_permission("func_code"))` - -## 接口规范 - -- Router 只负责参数接收和调用 Service,不写业务逻辑 -- 白名单路径(无需鉴权)在 `settings.auth_whitelist` 中配置 -- POST 用 `@router.post()`,GET 用 `@router.get()` -- 复杂参数使用 Pydantic 模型 + `Body()`,简单参数使用 `Query()` 或 `Path()` -- 路由方法直接返回业务数据,由 `ResponseWrapMiddleware` 自动包装为 `StandardResponse` - -## 异常处理 - -### 核心约定 -- 统一异常与全局处理器都集中在 `app/core/exceptions.py`,`main.py` 只调用一次 `register_exception_handlers(app)` -- **自定义异常一律 HTTP 500**,真实业务含义靠响应体里的 `code` 区分(前端拦截器读 `code`) -- 抛异常时传一句具体原因(detail)即可,不用记错误码;不传就用类别名兜底 - - `raise BizError("不支持的商品")` → msg = `业务异常[不支持的商品]` - - `raise BizError()` → msg = `业务异常` -- **不要** catch 后吞掉异常,交由全局异常处理器统一处理 - -### 自定义异常类(`app/core/exceptions.py`) -业务代码优先抛下列语义化异常,不要直接 `raise HTTPException`: - -| 异常类 | code | 类别名 | 典型场景 | -|--------|------|--------|----------| -| `ParamError` | 4000 | 参数异常 | 入参缺失/非法、模型档位不支持 | -| `AssertError` | 4100 | 断言异常 | 业务前置条件不满足(由 `Assert` 工具抛出) | -| `BizError` | 4200 | 业务异常 | 资源不存在、限频、验证码、越权、会员校验 | -| `CreditError` | 4300 | 扣费异常 | 余额不足、扣费失败(前端引导充值) | -| `SysError` | 5000 | 系统异常 | 已知内部错误 / 上游模型调用失败(主动抛) | - -- 所有自定义异常继承 `AppError`,处理器只在基类上注册,靠继承链自动覆盖全部子类 -- 流式(SSE)场景用 `exc.to_event()` 输出 error 事件;上游异常包装用 `SysError.from_exc(exc)` -- 兜底:未建模的异常由 `global_exception_handler` 统一按系统异常(code=5000)返回,dev 环境暴露细节,生产只回通用提示 - -```python -from app.core.exceptions import BizError, CreditError, ParamError - -if not resume: - raise BizError("简历不存在") -if balance < cost: - raise CreditError("积分不足") -if model not in ALLOWED: - raise ParamError(f"不支持的模型档位: {model}") -``` - -### 业务断言工具(`app/core/asserts.py`) -前置条件校验优先用 `Assert` 工具类(风格参考 Spring `Assert`),条件不满足时抛 `AssertError`(code=4100),**不要**再手写 `if not ... raise` 或裸 `assert`: - -```python -from app.core.asserts import Assert - -Assert.not_none(user, "用户不存在") -Assert.has_text(func_code, "功能编码不能为空") -Assert.not_empty(items, "列表不能为空") -Assert.gt(days, 0, "天数必须大于0") -Assert.eq(status, 1, "状态不可用") -``` - -常用方法:`is_true` / `is_false` / `not_none` / `is_none` / `has_text` / `not_empty` / `gt` / `gte` / `lt` / `lte` / `eq` / `ne` - -### HTTPException -- 仅在需要返回特定 HTTP 状态码的场景(如框架层、鉴权 401/403)使用 `raise HTTPException(status_code=xxx, detail="描述")` -- 业务拒绝一律用上面的自定义异常,不要用 `HTTPException` 表达业务错误 - -## Redis 使用规范 - -- 通过 `app.core.redis.redis_client` 或依赖注入 `Depends(get_redis)` 获取客户端 -- key 命名与 Java 端保持一致,如 `login:token:{userId}` -- 值统一 JSON 序列化(`json.dumps` / `json.loads`) -- 设置 TTL 时使用 `ex` 参数(秒) - -## 数据库设计风格 - -- 与 Java 端共享同一数据库,表结构由 Java 端管理 -- 表名以 `bg_` 前缀,下划线命名,如 `bg_func_permission` -- 主键 `id`,类型 `BigInteger` -- 时间字段使用 `DateTime` 类型,包含 `create_time` 和 `update_time` -- 逻辑删除字段 `is_delete`,类型 `BigInteger`,0=正常,非0=删除 -- 状态字段用 `Integer`,0/1 表示,通过 `comment` 说明含义 -- 查询使用 SQLAlchemy `select()` + `where()` 构建条件 -- 更新使用 `update()` + `where()` + `values()` -- 会话通过 `get_db()` 依赖注入获取,自动 commit/rollback/close - -## 异步规范 - -- 所有数据库操作、Redis 操作、HTTP 请求使用 `async/await` -- Service 方法统一使用 `async def` -- 路由处理函数统一使用 `async def` -- 避免在异步上下文中使用同步阻塞操作 - -## AI 调用规范 - -- 业务代码**不直接使用** `LLM` 枚举,而是从 `app.ai.model_config` 中引用对应模块的场景配置类 -- `model_config.py` 中每个模块一个 class,每个场景一个类属性,属性值为预创建的 `ChatOpenAI` 实例 -- 修改模型或调整参数只需改 `model_config.py` 一个文件,业务代码不动 -- AI 调用应做好异常捕获和容错,单次失败不应影响整体流程 -- 长耗时 AI 调用考虑异步执行 - -### 模型引用示例 -```python -from app.ai.model_config import SkillGapModel, NovaChatModel - -# chain 中直接使用配置类属性(已经是 ChatOpenAI 实例) -_plan_chain = ( - ChatPromptTemplate.from_messages([...]) - | SkillGapModel.AGENT_PLAN - | StrOutputParser() -) - -# 非 chain 场景直接 await 调用 -result = await NovaChatModel.CHAT.ainvoke(messages) -``` - -### 新增 AI 场景步骤 -1. 在 `app/ai/model_config.py` 对应模块的 class 中新增一个类属性,指定模型和参数 -2. 在业务代码中 `from app.ai.model_config import XxxModel`,引用该属性 -3. 如需新增模块,在 `model_config.py` 中新建一个 class - -### AI 输出 JSON 解析 -- LLM 返回的 JSON 经常被 markdown 代码块(` ```json ... ``` `)包裹,**禁止**直接使用 LangChain 的 `JsonOutputParser` -- 统一使用 `app.tool.json_helper.parse_llm_json` 解析 AI 输出的 JSON 文本 -- `parse_llm_json` 会自动剥离 markdown 代码块标记,并通过 `json_repair` 做容错修复 -- **不要**在各模块中自行编写 JSON 清洗/解析逻辑,统一复用 `parse_llm_json` - -## 代码格式规范 - -### 紧凑风格 -- 避免过度换行,保持代码紧凑易读 -- 链式调用尽量写在一行,除非超过 120 字符 -- 方法参数列表较多时,可适当换行但保持紧凑 -- f-string 拼接优先写在一行 - -### 示例 - -**推荐(紧凑风格):** -```python -# 查询语句一行 -result = await session.execute(select(FuncPermission).where(FuncPermission.func_code == func_code, FuncPermission.status == 1)) - -# 链式操作一行 -perm = result.scalar_one_or_none() - -# f-string 拼接一行 -log.info(f"功能权限校验 userId:{user_id} funcCode:{func_code}") - -# 方法参数紧凑排列 -async def check_and_deduct(self, user_id: int, func_code: str) -> int: - -# 多条件 where 紧凑排列 -result = await self.session.execute(select(UserFuncPermissionStock).where( - UserFuncPermissionStock.user_id == user_id, UserFuncPermissionStock.func_code == func_code)) -``` diff --git a/.kiro/steering/项目结构说明.md b/.kiro/steering/项目结构说明.md deleted file mode 100644 index c2b600d..0000000 --- a/.kiro/steering/项目结构说明.md +++ /dev/null @@ -1,201 +0,0 @@ ---- -inclusion: manual ---- - -# OfferPie Python AI 项目结构说明 - -## 1️⃣ 项目整体层次 -``` -offerpie_python_ai/ -│ -├─ .env / .env.test / .env.prod # 环境变量配置(dev/test/prod) -├─ requirements.txt # Python 依赖清单 -│ -└─ app/ # 应用主目录 - ├─ main.py # FastAPI 应用入口(注册异常处理、中间件、CORS、路由) - ├─ banner.txt # 启动 Banner - │ - ├─ config/ # **配置层** - │ └─ settings.py # Pydantic Settings 统一配置(环境、数据库、Redis、LLM供应商、JWT、CORS、日志等) - │ - ├─ core/ # **核心基础设施层** - │ ├─ auth.py # 权限校验依赖(require_login、func_permission 装饰器) - │ ├─ context.py # 请求上下文变量(RequestContext:request_id、user_id) - │ ├─ database.py # SQLAlchemy 异步引擎 + 会话工厂(init_db、close_db、get_db) - │ ├─ redis.py # Redis 异步连接池(init_redis、close_redis、get_redis) - │ ├─ lifespan.py # FastAPI 生命周期管理(启动初始化 DB/Redis,关闭释放资源) - │ ├─ logger.py # Loguru 日志配置(控制台+文件,自动注入 request_id/user_id) - │ ├─ middleware.py # 中间件注册(RequestID、JWT鉴权、登录拦截、请求日志、响应统一包装) - │ ├─ exceptions.py # 统一异常定义 + 全局异常处理器(AppError/ParamError/BizError/CreditError/AssertError/SysError + HTTP/验证/断言/未知兜底) - │ ├─ asserts.py # 业务断言工具类 Assert(风格参考 Spring Assert,条件不满足抛 AssertError code=4100) - │ └─ schemas/ - │ └─ responses.py # 统一响应模型 StandardResponse(code/msg/data/timestamp/uuid) - │ - ├─ ai/ # **AI 能力层** - │ ├─ models.py # LLM 模型枚举(LLM.DOUBAO_PRO_32K、DEEPSEEK_V3、GPT_4O 等),基于 LangChain ChatOpenAI - │ ├─ model_config.py # AI 模型场景配置(集中管理各模块的模型选择与参数,业务代码引用此文件而非直接使用 LLM 枚举) - │ ├─ resume_extractor/ # 简历 AI 提取模块 - │ │ ├─ prompts.py # 5 个提取任务的 System Prompt(个人信息/教育/工作+实习/项目/竞赛) - │ │ └─ extractor.py # AI 并行提取(extract_all 入口,asyncio.gather 5 路并行) - │ ├─ resume_polisher/ # 简历段落润色模块 - │ │ ├─ prompts.py # 润色 Prompt 模板(仅格式/错字/表达优化,不改内容) - │ │ └─ polisher.py # AI 段落润色(polish_paragraphs 入口,输出等长数组) - │ ├─ resume_diagnoser/ # 简历 AI 诊断模块 - │ │ ├─ prompts.py # 诊断 Prompt 模板(分模块诊断 + 汇总评价) - │ │ └─ diagnoser.py # AI 并行诊断(diagnose_all 入口 + generate_summary 汇总评价) - │ ├─ skill_gap_analyzer/ # 技能差距分析 + 定制简历 AI 模块 - │ │ ├─ prompts.py # 差距分析 + 简历优化 + Agent 规划(原子化操作)/ 单条记录修改 / 新增记录 Prompt 模板 + MODULE_SCHEMAS - │ │ └─ analyzer.py # AI 调用逻辑(差距分析 + summary优化 + 经历优化 + Agent规划 + 单条记录修改 + 新增记录) - │ ├─ job_agent/ # 求职助手 Agent AI 模块(岗位简历优化) - │ │ ├─ prompts.py # 岗位简历优化 Prompt 模板 - │ │ └─ resume_optimizer.py # 岗位简历优化 AI 引擎(summary优化 + 经历优化,独立 chain) - │ └─ nova_chat/ # Nova 对话助手 AI 模块 - │ ├─ prompts.py # Nova 对话 System Prompt(岗位匹配评估 + 简历优化建议 + 通用求职对话) - │ └─ chat.py # Nova 对话 AI 引擎(拼 prompt + 调 LLM,返回纯文本) - │ - ├─ api/ # **路由层**(REST API 接口) - │ ├─ health.py # 健康检查接口 GET /health/ - │ ├─ resume.py # 简历接口 POST /resume/upload(上传文件AI解析) - │ ├─ resume_diagnose.py # 简历诊断接口(POST 触发诊断 / GET 查询报告 / PUT 标记处理+用户评价 / POST 润色优化) - │ ├─ skill_gap.py # 技能差距分析接口(差距分析 / 生成定制简历 / AI对话编辑) - │ ├─ customize_resume.py # 定制简历接口(查询 / 修改 / 回滚) - │ ├─ job_agent_chat.py # 求职助手接口(POST /job-agent/optimize-resume 岗位简历优化) - │ └─ nova_chat.py # Nova 对话助手接口(POST /nova-chat/chat 纯对话,支持可选岗位上下文) - │ - ├─ models/ # **ORM 模型层**(SQLAlchemy 声明式映射) - │ ├─ func_permission.py # 功能权限定义表(bg_func_permission) - │ ├─ user_func_permission_stock.py # 用户功能权限库存表(bg_user_func_permission_stock) - │ ├─ user_func_usage_log.py # 用户功能使用记录表(bg_user_func_usage_log) - │ ├─ user_resume.py # 用户简历主表(bg_user_resume) - │ ├─ user_resume_education.py # 简历-教育经历表(bg_user_resume_education) - │ ├─ user_resume_work.py # 简历-工作经历表(bg_user_resume_work) - │ ├─ user_resume_internship.py # 简历-实习经历表(bg_user_resume_internship) - │ ├─ user_resume_project.py # 简历-项目经历表(bg_user_resume_project) - │ ├─ user_resume_competition.py # 简历-竞赛经历表(bg_user_resume_competition) - │ ├─ resume_diagnosis_report.py # 简历诊断报告表(bg_resume_diagnosis_report) - │ ├─ resume_diagnosis_issue.py # 简历诊断问题表(bg_resume_diagnosis_issue) - │ ├─ job.py # 岗位表(bg_job,只读,用于技能差距分析) - │ ├─ job_agent_config.py # 求职助手配置表(bg_job_agent_config) - │ └─ user_job_customize_resume.py # 用户岗位定制简历表(bg_user_job_customize_resume) - │ - ├─ tool/ # **工具层**(无状态、无业务依赖的通用工具) - │ ├─ file_parser.py # 文件解析工具(PDF/Word(.docx)/TXT/Markdown → 文本单元数组,parse_to_segments 入口方法) - │ ├─ json_helper.py # AI 输出 JSON 解析工具(自动去除 markdown 代码块包裹 + json_repair 容错,parse_llm_json 入口方法) - │ └─ snowflake.py # 雪花 ID 生成工具(next_id) - │ - ├─ schemas/ # **Schema 层**(Pydantic 请求/响应/缓存模型) - │ ├─ skill_gap.py # 技能差距分析 Schema(SkillGapParam、CustomizeResumeParam、AiEditParam) - │ ├─ customize_resume.py # 定制简历 Schema(CustomizeResume、ResumeProfile、Education、Work、Internship、Project、Competition、Paragraph) - │ ├─ job_agent_chat.py # 求职助手岗位简历优化 Schema(OptimizeResumeParam) - │ └─ nova_chat.py # Nova 对话助手 Schema(NovaChatParam、NovaChatDto) - │ - └─ services/ # **业务逻辑层** - ├─ func_permission_service.py # 功能权限服务(校验+扣减+回退,逻辑与Java端一致) - ├─ resume_service.py # 简历服务(文件解析→AI结构化→写入主表+5张子表) - ├─ resume_diagnose_service.py # 简历诊断服务(加载简历→AI并行诊断→统计评级→写入报告) - ├─ skill_gap_service.py # 技能差距分析服务(差距分析→定制简历生成→AI对话编辑) - ├─ resume_loader.py # 简历统一查询模块(按ID查/自动选默认+5张子表,返回 ResumeDetail dataclass) - ├─ customize_resume_store.py # 定制简历存取模块(数据库持久化 + Redis回滚备份、按用户+岗位维度存取) - ├─ job_agent_chat_service.py # 求职助手服务(查简历+岗位→并发AI优化简历→存库) - └─ nova_chat_service.py # Nova 对话助手服务(查简历+查岗位(可选)→调AI,纯对话不持久化) -``` - -## 2️⃣ 各层模块职责 -| 层级 | 主要职责 | 关键类/文件 | -|------|----------|-------------| -| **config** | 统一配置管理,基于 Pydantic Settings,支持 .env 文件加载 | `Settings`(数据库、Redis、LLM供应商、JWT、CORS、日志等全部配置项) | -| **core** | 核心基础设施:数据库连接、Redis连接、鉴权、日志、中间件、异常处理、断言工具、统一响应 | `database.py`、`redis.py`、`auth.py`、`middleware.py`、`exceptions.py`(统一异常+全局处理器)、`asserts.py`(业务断言工具 `Assert`)、`logger.py`、`StandardResponse` | -| **ai** | AI 模型管理 + 业务 AI 能力 | `LLM` 枚举(models.py)、`model_config.py`(场景模型配置)、`resume_extractor/`(简历并行提取)、`resume_polisher/`(简历段落润色)、`resume_diagnoser/`(简历诊断)、`skill_gap_analyzer/`(技能差距分析 + 定制简历优化 + Agent 原子化规划 + 单条记录修改/新增)、`job_agent/`(求职助手岗位简历优化)、`nova_chat/`(Nova 对话助手,纯对话) | -| **api** | REST API 路由定义 | `health.py`(健康检查)、`resume.py`(简历上传解析 + 段落润色)、`resume_diagnose.py`(简历诊断)、`skill_gap.py`(技能差距分析 + 生成定制简历 + AI对话编辑)、`customize_resume.py`(定制简历查询/修改/回滚)、`job_agent_chat.py`(求职助手岗位简历优化)、`nova_chat.py`(Nova 对话助手) | -| **models** | SQLAlchemy ORM 模型,与 Java 端共享同一数据库 | `FuncPermission`、`UserFuncPermissionStock`、`UserFuncUsageLog`、`UserResume`、`UserResumeEducation`/`Work`/`Internship`/`Project`/`Competition`、`ResumeDiagnosisReport`、`ResumeDiagnosisIssue`、`Job`(只读)、`JobAgentConfig`、`UserJobCustomizeResume` | -| **tool** | 无状态通用工具,不依赖数据库/Redis/用户上下文 | `file_parser.py`(PDF/Word(.docx)/TXT/Markdown 文件解析为文本单元数组)、`json_helper.py`(AI 输出 JSON 解析,去 markdown 代码块 + json_repair 容错)、`snowflake.py`(雪花ID生成) | -| **services** | 业务逻辑实现 | `FuncPermissionService`(功能权限校验、扣减、回退)、`ResumeService`(简历文件解析→AI结构化→入库 + 段落润色)、`ResumeDiagnoseService`(简历诊断→AI并行分析→评级→入库)、`SkillGapService`(技能差距分析→定制简历生成→AI对话编辑)、`resume_loader`(简历统一查询,返回ResumeDetail)、`customize_resume_store`(定制简历数据库存取+数据构建,按用户+岗位维度,Redis回滚备份)、`JobAgentChatService`(求职助手岗位简历优化)、`NovaChatService`(Nova对话助手,查简历+查岗位→调AI) | - -## 3️⃣ 技术栈 -| 类别 | 技术选型 | 说明 | -|------|----------|------| -| **Web 框架** | FastAPI + Uvicorn + Gunicorn | 异步 ASGI 框架 | -| **ORM** | SQLAlchemy 2.0 (asyncio) + asyncmy | 异步 MySQL 驱动 | -| **缓存** | redis-py (asyncio) | 异步 Redis 客户端 | -| **AI/LLM** | LangChain + LangChain-OpenAI + LangGraph | AI 编排框架,兼容 OpenAI 协议的多供应商接入 | -| **配置** | Pydantic Settings + python-dotenv | 类型安全的环境变量管理 | -| **日志** | Loguru | 结构化日志,自动注入请求上下文 | -| **鉴权** | PyJWT | JWT 解析,与 Java 端共享同一 jwt_secret | -| **数据处理** | Pandas + NumPy | 数据分析与处理 | -| **HTTP** | httpx | 异步 HTTP 客户端 | -| **文件解析** | pdfplumber + python-docx | PDF 和 Word 文件内容提取 | - -## 4️⃣ 中间件执行链(由外到内) -| 顺序 | 中间件 | 职责 | -|------|--------|------| -| 1 | `RequestIDMiddleware` | 生成 ShortUUID 请求ID,写入响应头 X-Request-ID | -| 2 | `JwtAuthMiddleware` | 从 Cookie/Header 解析 JWT,校验 Redis 登录信息,续期,写入 RequestContext.user_id | -| 3 | `AuthRequiredMiddleware` | 非白名单路径必须有 user_id,否则返回 401 | -| 4 | `RequestLogMiddleware` | 记录请求方法、URL、参数、响应状态码和耗时 | -| 5 | `ResponseWrapMiddleware` | 将业务路由的 JSON 响应统一包装为 StandardResponse 格式 | - -## 5️⃣ 鉴权体系 -- 与 Java 端共享同一 JWT Secret 和 Redis 登录信息 -- Token 来源:优先 Cookie `Token`,其次 Header `Token` -- 白名单路径(`/health/**`、`/docs/**`、`/redoc/**`、`/openapi.json`)跳过鉴权 -- 功能权限校验通过 `func_permission(func_code)` 依赖注入实现,逻辑与 Java 端 `FuncPermissionAspect` 完全一致: - 1. 校验每日免费额度 - 2. 查付费库存(时间+次数维度) - 3. SQL 原子扣减 - 4. 业务异常自动回退 - -## 6️⃣ AI 模型配置 - -### 模型定义(`app/ai/models.py`) -| 供应商 | 模型 | 枚举值 | -|--------|------|--------| -| 火山引擎 | doubao-1-5-pro-32k-250115 | `LLM.DOUBAO_PRO_32K` | -| 火山引擎 | doubao-1-5-lite-32k-250115 | `LLM.DOUBAO_LITE_32K` | -| 火山引擎 | deepseek-v3-250324 | `LLM.DEEPSEEK_V3` | -| 火山引擎 | deepseek-r1-250528 | `LLM.DEEPSEEK_R1` | -| 火山引擎 | doubao-seed-2-0-mini-260215 | `LLM.DOUBAO_SEED_MINI` | -| 火山引擎 | doubao-seed-2-0-lite-260215 | `LLM.DOUBAO_SEED_LITE` | -| 火山引擎 | doubao-seed-2-0-pro-260215 | `LLM.DOUBAO_SEED_PRO` | -| 加鱼 | gpt-4o | `LLM.GPT_4O` | -| 接口 | gpt-4o-mini | `LLM.GPT_4O_MINI` | -| 接口 | gemini-2.5-flash | `LLM.GEMINI_FLASH` | -| 加鱼 | claude-sonnet-4.5 | `LLM.JIAYU_CLAUDE_SONNET_4_5` | -| 加鱼 | claude-haiku-4.5 | `LLM.JIAYU_CLAUDE_HAIKU_4_5` | -| 加鱼 | deepseek-3.2 | `LLM.JIAYU_DEEPSEEK_3_2` | -| 加鱼 | glm-5 | `LLM.JIAYU_GLM_5` | -| 加鱼 | qwen3-coder-next | `LLM.JIAYU_QWEN3_CODER_NEXT` | -| 加鱼 | minimax-m2.5 | `LLM.JIAYU_MINIMAX_M2_5` | -| ZM | gpt-5.5 | `LLM.ZM_GPT_5_5` | -| ZM | gpt-5.4 | `LLM.ZM_GPT_5_4` | -| ZM | gpt-5.4-mini | `LLM.ZM_GPT_5_4_MINI` | -| ZM | gpt-5.2 | `LLM.ZM_GPT_5_2` | - -所有模型通过 `LLM.XXX.create(**kwargs)` 创建 LangChain `ChatOpenAI` 实例,kwargs 透传 temperature、max_tokens 等参数。 - -### 场景模型配置(`app/ai/model_config.py`) - -业务代码**不直接使用** `LLM` 枚举,而是通过 `model_config.py` 中的场景配置类引用预创建的模型实例。修改模型或参数只需改此文件。 - -| 配置类 | 场景 | 说明 | -|--------|------|------| -| `SkillGapModel` | ANALYSIS / SUMMARY / EXPERIENCE / AGENT_PLAN / AGENT_EDIT / AGENT_ADD | 技能差距分析模块 | -| `JobAgentModel` | SUMMARY / EXPERIENCE | 求职助手Agent模块 | -| `NovaChatModel` | CHAT | Nova智能聊天模块 | -| `ResumeExtractorModel` | PARSE | 简历解析模块 | -| `DiagnoserModel` | MODULE / SUMMARY / POLISH | 简历诊断模块 | -| `BrowserPlugModel` | FORM_FILL | 浏览器插件模块 | - -业务代码引用示例:`from app.ai.model_config import SkillGapModel`,然后在 chain 中直接使用 `SkillGapModel.ANALYSIS`。 - -## 7️⃣ 与 Java 后端的关系 -- **共享数据库**:Python 端与 Java 端(back-end)连接同一 MySQL 数据库(offerpie),ORM 模型对应相同的表 -- **共享 Redis**:共享登录态(`login:token:{userId}`),JWT Secret 一致 -- **共享权限体系**:功能权限校验逻辑与 Java 端 `FuncPermissionAspect` 完全对齐 -- **职责分工**:Java 端负责业务 CRUD(用户、简历、岗位等),Python 端负责 AI 能力(LLM 调用、智能分析等) - -## 8️⃣ 构建与运行 -- **虚拟环境**:项目使用 `.venv` 目录管理 Python 虚拟环境 -- **依赖安装**:`pip install -r requirements.txt` -- **开发启动**:`python -m app.main`(默认 ENV=dev,端口由 settings.server_port 控制) -- **生产部署**:通过 Gunicorn + Uvicorn Worker 运行 -- **环境切换**:通过 `.env` / `.env.test` / `.env.prod` 文件控制环境变量