删除的对话接口

This commit is contained in:
zk
2026-06-24 17:54:09 +08:00
parent 11c6bf6ba7
commit dc02969f3a
9 changed files with 20 additions and 319 deletions
-118
View File
@@ -1,118 +0,0 @@
# 求职助手 Agent 对话接口设计方案
## 1. 接口概述
`POST /job-agent/chat` — 求职助手对话接口,Python 端实现。
用户通过对话与求职助手交互,AI 根据用户简历和求职意向提供求职建议,识别用户意图后触发前端工具调用(岗位推荐 / 调整偏好)。
## 2. 入参 Schema
```python
class ChatMessage(BaseModel):
role: Literal["user", "assistant"]
content: str
class JobAgentChatParam(BaseModel):
message: str = Field(..., description="用户输入的消息")
resume_id: int = Field(..., alias="resumeId", description="简历ID")
history: list[ChatMessage] = Field(default_factory=list, description="对话历史")
job_categories: list[str] = Field(default_factory=list, alias="jobCategories", description="意向岗位类型名称")
regions: list[str] = Field(default_factory=list, alias="regions", description="意向城市名称")
industries: list[str] = Field(default_factory=list, alias="industries", description="意向行业名称")
```
## 3. 出参 Schema
```python
class ToolParams(BaseModel):
preference: str = Field(default="", description="用户岗位偏好描述,仅tool=recommend时有值")
class JobAgentChatDto(BaseModel):
message: str = Field(..., description="AI回复文本,不超过200字")
tool: str | None = Field(default=None, description="前端需执行的工具:recommend / editPreference / null")
tool_params: ToolParams | None = Field(default=None, alias="toolParams", description="工具参数")
```
## 4. AI 上下文构造
每次对话请求,后端自动查询以下数据拼入 system prompt
1. **简历信息**:根据 resumeId 查 bg_user_resume 主表 + 5张子表(教育/工作/实习/项目/竞赛),序列化为文本
2. **求职意向**:直接使用前端传入的 jobCategories / regions / industries(中文名称)
## 5. System Prompt 设计
```
你是 OfferPie 求职助手,帮助用户找到合适的工作。
【用户简历】
{resume_text}
【求职意向】
意向岗位:{job_categories}
意向城市:{regions}
意向行业:{industries}
【你的能力】
1. 回答求职相关问题(面试技巧、简历建议、行业分析等),回复不超过200字
2. 当用户想看岗位推荐时,提取用户的偏好描述,调用岗位推荐工具
3. 当用户想修改求职偏好/设置时,调用偏好设置工具
【输出格式】
严格返回 JSON,不要其他内容:
{"message":"回复内容","tool":null,"toolParams":null}
tool 可选值:
- null:普通对话,不触发工具
- "recommend":岗位推荐,toolParams 必须包含 {"preference":"用户偏好描述"}
- "editPreference":调整偏好,toolParams 为 null
【规则】
1. 只聊求职相关话题,其他话题礼貌拒绝
2. 回复简洁,不超过200字
3. 用户表达想看岗位、推荐岗位、帮我找工作等意图时,从对话中提取偏好描述,返回 recommend
4. 用户表达想改设置、调整偏好、修改意向等意图时,返回 editPreference
5. 偏好描述要准确概括用户的岗位偏好,如"更偏技术方向的产品岗"、"大厂优先"
```
## 6. 处理流程
1. 校验登录态,获取 user_id
2. 根据 resume_id 查简历主表 + 5张子表,序列化为文本
3. 构造 system prompt(简历 + 求职意向 + 规则)
4. 构造 messages 列表:system prompt + history + 当前 message
5. 调用 LLM(使用 LLM.DEEPSEEK_V3 或配置的模型)
6. 用 parse_llm_json 解析 AI 返回的 JSON
7. 构造 JobAgentChatDto 返回
## 7. 前端交互流程
1. 前端发消息 → 调 `POST /job-agent/chat`
2. 拿到返回后判断 tool 字段:
- `tool=null` → 直接展示 message
- `tool="recommend"` → 展示 message + 用 toolParams.preference 调 Java 端 `POST /job/agent/recommend` 拿岗位列表展示
- `tool="editPreference"` → 展示 message + 打开偏好设置页面
## 8. 涉及文件
| 文件 | 位置 | 说明 |
|------|------|------|
| `job_agent_chat.py` | app/schemas/ | Pydantic 请求/响应 Schema |
| `job_agent_chat.py` | app/api/ | 路由定义 |
| `job_agent_chat_service.py` | app/services/ | Service:查简历 + 构造 prompt + 调 LLM |
| `prompts.py` | app/ai/job_agent/ | System Prompt 模板 |
| `main.py` | app/ | 注册新路由 |
## 9. 依赖的现有模块
- `app/models/user_resume.py` + 5张子表 ORM — 查简历数据
- `app/ai/models.py` — LLM 枚举,创建模型实例
- `app/tool/json_helper.py` — parse_llm_json 解析 AI 输出
- `app/core/context.py` — RequestContext 获取 user_id
- `app/core/database.py` — get_db 获取数据库会话
## 10. 不需要新增的 ORM 模型
- 求职意向由前端传入中文名称,不查 bg_user_job_intention 表
- 简历相关 ORM 模型已全部存在
+2 -2
View File
@@ -143,7 +143,7 @@ inclusion: manual
### 模型引用示例
```python
from app.ai.model_config import SkillGapModel
from app.ai.model_config import SkillGapModel, NovaChatModel
# chain 中直接使用配置类属性(已经是 ChatOpenAI 实例)
_plan_chain = (
@@ -153,7 +153,7 @@ _plan_chain = (
)
# 非 chain 场景直接 await 调用
result = await JobAgentModel.CHAT.ainvoke(messages)
result = await NovaChatModel.CHAT.ainvoke(messages)
```
### 新增 AI 场景步骤
+9 -10
View File
@@ -45,9 +45,8 @@ offerpie_python_ai/
│ ├─ skill_gap_analyzer/ # 技能差距分析 + 定制简历 AI 模块
│ │ ├─ prompts.py # 差距分析 + 简历优化 + Agent 规划(原子化操作)/ 单条记录修改 / 新增记录 Prompt 模板 + MODULE_SCHEMAS
│ │ └─ analyzer.py # AI 调用逻辑(差距分析 + summary优化 + 经历优化 + Agent规划 + 单条记录修改 + 新增记录)
│ ├─ job_agent/ # 求职助手 Agent AI 模块
│ │ ├─ prompts.py # 对话 System Prompt + 岗位简历优化 Prompt 模板
│ │ ├─ chat.py # AI 对话引擎(构造 prompt + 拼 messages + 调 LLM + 解析返回)
│ ├─ job_agent/ # 求职助手 Agent AI 模块(岗位简历优化)
│ │ ├─ prompts.py # 岗位简历优化 Prompt 模板
│ │ └─ resume_optimizer.py # 岗位简历优化 AI 引擎(summary优化 + 经历优化,独立 chain)
│ └─ nova_chat/ # Nova 对话助手 AI 模块
│ ├─ prompts.py # Nova 对话 System Prompt(岗位匹配评估 + 简历优化建议 + 通用求职对话)
@@ -59,7 +58,7 @@ offerpie_python_ai/
│ ├─ resume_diagnose.py # 简历诊断接口(POST 触发诊断 / GET 查询报告 / PUT 标记处理+用户评价 / POST 润色优化)
│ ├─ skill_gap.py # 技能差距分析接口(差距分析 / 生成定制简历 / AI对话编辑)
│ ├─ customize_resume.py # 定制简历接口(查询 / 修改 / 回滚)
│ ├─ job_agent_chat.py # 求职助手接口(POST /job-agent/chat 对话、POST /job-agent/optimize-resume 岗位简历优化)
│ ├─ job_agent_chat.py # 求职助手接口(POST /job-agent/optimize-resume 岗位简历优化)
│ └─ nova_chat.py # Nova 对话助手接口(POST /nova-chat/chat 纯对话,支持可选岗位上下文)
├─ models/ # **ORM 模型层**SQLAlchemy 声明式映射)
@@ -86,7 +85,7 @@ offerpie_python_ai/
├─ schemas/ # **Schema 层**Pydantic 请求/响应/缓存模型)
│ ├─ skill_gap.py # 技能差距分析 SchemaSkillGapParam、CustomizeResumeParam、AiEditParam
│ ├─ customize_resume.py # 定制简历 SchemaCustomizeResume、ResumeProfile、Education、Work、Internship、Project、Competition、Paragraph
│ ├─ job_agent_chat.py # 求职助手对话 SchemaJobAgentChatParam、JobAgentChatDto、ToolParams
│ ├─ job_agent_chat.py # 求职助手岗位简历优化 SchemaOptimizeResumeParam
│ └─ nova_chat.py # Nova 对话助手 SchemaNovaChatParam、NovaChatDto
└─ services/ # **业务逻辑层**
@@ -96,7 +95,7 @@ offerpie_python_ai/
├─ skill_gap_service.py # 技能差距分析服务(差距分析→定制简历生成→AI对话编辑)
├─ resume_loader.py # 简历统一查询模块(按ID查/自动选默认+5张子表,返回 ResumeDetail dataclass
├─ customize_resume_store.py # 定制简历存取模块(数据库持久化 + Redis回滚备份、按用户+岗位维度存取)
├─ job_agent_chat_service.py # 求职助手对话服务(查简历→序列化→调AI模块完成对话
├─ job_agent_chat_service.py # 求职助手服务(查简历+岗位→并发AI优化简历→存库
└─ nova_chat_service.py # Nova 对话助手服务(查简历+查岗位(可选)→调AI,纯对话不持久化)
```
@@ -105,11 +104,11 @@ offerpie_python_ai/
|------|----------|-------------|
| **config** | 统一配置管理,基于 Pydantic Settings,支持 .env 文件加载 | `Settings`(数据库、Redis、LLM供应商、JWT、CORS、日志等全部配置项) |
| **core** | 核心基础设施:数据库连接、Redis连接、鉴权、日志、中间件、异常处理、统一响应 | `database.py``redis.py``auth.py``middleware.py``exceptions.py``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 对话助手) |
| **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) |
| **services** | 业务逻辑实现 | `FuncPermissionService`(功能权限校验、扣减、回退)、`ResumeService`(简历文件解析→AI结构化→入库 + 段落润色)、`ResumeDiagnoseService`(简历诊断→AI并行分析→评级→入库)、`SkillGapService`(技能差距分析→定制简历生成→AI对话编辑)、`resume_loader`(简历统一查询,返回ResumeDetail)、`customize_resume_store`(定制简历数据库存取+数据构建,按用户+岗位维度,Redis回滚备份)、`JobAgentChatService`(求职助手岗位简历优化)、`NovaChatService`(Nova对话助手,查简历+查岗位→调AI) |
## 3️⃣ 技术栈
| 类别 | 技术选型 | 说明 |
@@ -179,7 +178,7 @@ offerpie_python_ai/
| 配置类 | 场景 | 说明 |
|--------|------|------|
| `SkillGapModel` | ANALYSIS / SUMMARY / EXPERIENCE / AGENT_PLAN / AGENT_EDIT / AGENT_ADD | 技能差距分析模块 |
| `JobAgentModel` | CHAT / SUMMARY / EXPERIENCE | 求职助手Agent模块 |
| `JobAgentModel` | SUMMARY / EXPERIENCE | 求职助手Agent模块 |
| `NovaChatModel` | CHAT | Nova智能聊天模块 |
| `ResumeExtractorModel` | PARSE | 简历解析模块 |
| `DiagnoserModel` | MODULE / SUMMARY / POLISH | 简历诊断模块 |
-51
View File
@@ -1,51 +0,0 @@
"""求职助手 Agent 对话 AI 引擎
构造 prompt + 调 LLM + 解析返回。
依赖:LLM 枚举、job_agent/prompts、parse_llm_json
"""
from app.ai.job_agent.prompts import SYSTEM_PROMPT
from app.ai.model_config import JobAgentModel
from app.core.logger import log
from app.tool.json_helper import parse_llm_json
async def agent_chat(resume_text: str, message: str, history: list[dict],
job_categories: list[str], regions: list[str],
industries: list[str]) -> dict:
"""求职助手对话
1. 构造 system prompt 2. 拼 messages 3. 调 LLM 4. 解析返回
"""
# 1. 构造 system prompt
system_content = SYSTEM_PROMPT.format(
resume_text=resume_text,
job_categories="".join(job_categories) if job_categories else "未设置",
regions="".join(regions) if regions else "未设置",
industries="".join(industries) if industries else "未设置",
)
# 2. 拼 messages
messages = [("system", system_content)]
for msg in history:
messages.append((msg["role"], msg["content"]))
messages.append(("human", message))
# 3. 调 LLM
try:
result = await JobAgentModel.CHAT.ainvoke(messages)
raw = result.content
except Exception as e:
log.error(f"求职助手AI调用失败: {e}")
return {"message": "抱歉,我暂时无法回复,请稍后再试。", "tool": None, "toolParams": None}
# 4. 解析返回
try:
parsed = parse_llm_json(raw)
return {
"message": parsed.get("message", ""),
"tool": parsed.get("tool"),
"toolParams": parsed.get("toolParams"),
}
except Exception as e:
log.warning(f"求职助手AI返回解析失败, raw={raw}, error={e}")
return {"message": raw.strip() if raw else "抱歉,我暂时无法回复。", "tool": None, "toolParams": None}
+1 -38
View File
@@ -1,41 +1,4 @@
"""求职助手 Agent 对话 Prompt 模板 + 岗位简历优化 Prompt"""
SYSTEM_PROMPT = """你是 OfferPie 求职助手,帮助用户找到合适的工作。
【用户简历】
{resume_text}
【求职意向】
意向岗位:{job_categories}
意向城市:{regions}
意向行业:{industries}
【你的能力】
1. 回答求职相关问题(面试技巧、简历建议、行业分析等),回复不超过200字
2. 当用户想看岗位推荐时,提取用户的偏好描述,调用岗位推荐工具
3. 当用户想修改求职偏好/设置时,调用偏好设置工具
【输出格式】
严格返回 JSON,不要其他内容:
{{"message":"回复内容","tool":null,"toolParams":null}}
tool 可选值:
- null:普通对话,不触发工具
- "recommend":岗位推荐,toolParams 必须包含 {{"preference":"用户偏好描述"}}
- "editPreference":调整偏好,toolParams 为 null
【规则】
1. 只聊求职相关话题,其他话题礼貌拒绝
2. 回复简洁,不超过200字
3. 用户表达想看岗位、推荐岗位、帮我找工作等意图时,从对话中提取偏好描述,返回 recommend
4. 用户表达想改设置、调整偏好、修改意向等意图时,返回 editPreference
5. 偏好描述要准确概括用户的岗位偏好,如"更偏技术方向的产品岗""大厂优先"
6. 如果用户没有明确偏好,preference 填"无特殊偏好"
7. 触发工具时(tool 非 null),前端会在消息展示后再打开对应面板/列表,message 只做一句简短的"即将..."提示,不要追问"想怎么调整""有什么偏好"等引导用户在对话里继续输入的话
- 注意工具调用是置后的:message 先展示给用户,面板/列表随后打开,所以用将来时态,不要用"已为你..."这种完成时
- recommend 示例:"好的,正在为你匹配合适的岗位"
- editPreference 示例:"好的,为你打开偏好设置"
"""
"""求职助手 Agent 岗位简历优化 Prompt"""
# ===== 岗位简历优化 Prompt =====
-2
View File
@@ -24,8 +24,6 @@ class SkillGapModel:
class JobAgentModel:
"""求职助手Agent模块"""
# 多轮对话:理解用户求职意图,返回结构化回复(message+tool调用)
CHAT = LLM.DOUBAO_PRO_32K.create(temperature=0.7)
# 岗位简历-summary优化:针对具体岗位JD优化个人概述
SUMMARY = LLM.DEEPSEEK_V4_FLASH.create(temperature=0.3)
# 岗位简历-经历优化:针对具体岗位JD优化单条经历描述
+2 -16
View File
@@ -1,30 +1,16 @@
"""求职助手 Agent 对话接口"""
"""求职助手 Agent 岗位简历优化接口"""
from fastapi import APIRouter, Depends
from app.core.auth import func_permission
from app.core.context import RequestContext
from app.core.database import get_db
from app.schemas.job_agent_chat import JobAgentChatParam, OptimizeResumeParam
from app.schemas.job_agent_chat import OptimizeResumeParam
from app.services.job_agent_chat_service import JobAgentChatService
router = APIRouter(prefix="/job-agent", tags=["求职助手agent"])
@router.post("/chat", summary="求职助手对话")
async def chat(param: JobAgentChatParam):
"""求职助手对话,根据用户简历和意向提供求职建议、触发岗位推荐或偏好调整"""
user_id = RequestContext.user_id.get()
async for session in get_db():
service = JobAgentChatService(session)
result = await service.chat(
user_id, param.resume_id, param.message,
[msg.model_dump() for msg in param.history],
param.job_categories, param.regions, param.industries,
)
return result
@router.post("/optimize-resume", summary="针对岗位优化简历")
async def optimize_resume(param: OptimizeResumeParam, _: None = Depends(func_permission("resume_custom"))):
"""根据目标岗位,AI并发优化简历(summary + 5张子表经历),存Redis并返回"""
+2 -32
View File
@@ -1,41 +1,11 @@
"""求职助手 Agent 对话 Schema
"""求职助手 Agent 岗位简历优化 Schema
请求参数 Param、响应 Dto
请求参数 Param。
字段命名使用 camelCase alias,与前端 JSON 对齐。
"""
from typing import Literal
from pydantic import BaseModel, Field
ChatRole = Literal["user", "assistant"]
class ChatMessage(BaseModel):
role: ChatRole
content: str
class JobAgentChatParam(BaseModel):
message: str = Field(..., description="用户输入的消息")
resume_id: int = Field(..., alias="resumeId", description="简历ID")
history: list[ChatMessage] = Field(default_factory=list, description="对话历史")
job_categories: list[str] = Field(default_factory=list, alias="jobCategories", description="意向岗位类型名称")
regions: list[str] = Field(default_factory=list, alias="regions", description="意向城市名称")
industries: list[str] = Field(default_factory=list, alias="industries", description="意向行业名称")
class ToolParams(BaseModel):
preference: str = Field(default="", description="用户岗位偏好描述")
class JobAgentChatDto(BaseModel):
message: str = Field(..., description="AI回复文本,不超过200字")
tool: str | None = Field(default=None, description="前端需执行的工具:recommend / editPreference / null")
tool_params: ToolParams | None = Field(default=None, alias="toolParams", description="工具参数")
model_config = {"populate_by_name": True}
class OptimizeResumeParam(BaseModel):
resume_id: int = Field(..., alias="resumeId", description="简历ID")
+4 -50
View File
@@ -1,7 +1,7 @@
"""求职助手 Agent 对话 + 岗位简历优化 Service
"""求职助手 Agent 岗位简历优化 Service
主要功能:查询简历数据,调用 AI 模块完成对话;针对岗位并发优化简历。
依赖:resume_loader(简历统一查询)、customize_resume_store(定制简历存取+构建)、job_agent.chat AI 模块、job_agent.resume_optimizer(岗位简历优化)
主要功能:针对岗位并发优化简历。
依赖:resume_loader(简历统一查询)、customize_resume_store(定制简历存取+构建)、job_agent.resume_optimizer(岗位简历优化)
使用表:bg_user_resume + 5张子表(通过 resume_loader 查询)、bg_job(查岗位)
"""
@@ -12,12 +12,11 @@ import time
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.ai.job_agent.chat import agent_chat
from app.ai.job_agent.resume_optimizer import optimize_summary, optimize_experience_record
from app.core.logger import log
from app.models.job import Job
from app.schemas.customize_resume import CustomizeResume, Education, Work, Internship, Project, Competition
from app.services.resume_loader import ResumeDetail, load_resume_detail
from app.services.resume_loader import load_resume_detail
from app.services import customize_resume_store
@@ -26,51 +25,6 @@ class JobAgentChatService:
def __init__(self, session: AsyncSession):
self.session = session
async def chat(self, user_id: int, resume_id: int, message: str,
history: list[dict], job_categories: list[str],
regions: list[str], industries: list[str]) -> dict:
"""求职助手对话:查简历 → 序列化 → 调 AI 模块"""
detail = await load_resume_detail(self.session, resume_id, user_id)
resume_text = self._build_resume_text(detail)
return await agent_chat(resume_text, message, history, job_categories, regions, industries)
@staticmethod
def _build_resume_text(detail: ResumeDetail) -> str:
"""将简历数据序列化为文本供 AI 使用"""
resume = detail.resume
parts = []
if resume.name:
parts.append(f"姓名:{resume.name}")
if resume.target_position:
parts.append(f"目标岗位:{resume.target_position}")
if resume.skills:
parts.append(f"技能:{''.join(resume.skills)}")
if resume.certificates:
parts.append(f"证书:{''.join(resume.certificates)}")
if resume.summary:
parts.append(f"个人概述:{resume.summary}")
if detail.education:
parts.append("教育经历:")
for r in detail.education:
parts.append(f" - {r.school or ''} {r.major or ''} {r.degree or ''}")
if detail.work:
parts.append("工作经历:")
for r in detail.work:
parts.append(f" - {r.company_name or ''} {r.position or ''}")
if detail.internship:
parts.append("实习经历:")
for r in detail.internship:
parts.append(f" - {r.company_name or ''} {r.position or ''}")
if detail.project:
parts.append("项目经历:")
for r in detail.project:
parts.append(f" - {r.project_name or ''} {r.role or ''}")
if detail.competition:
parts.append("竞赛经历:")
for r in detail.competition:
parts.append(f" - {r.competition_name or ''} {r.award or ''}")
return "\n".join(parts) if parts else "暂无简历信息"
async def optimize_resume(self, user_id: int, resume_id: int, job_id: int) -> dict:
"""针对岗位优化简历:查简历+岗位 → 构建定制简历 → 按单条记录并发AI优化 → 存数据库 → 返回"""
# 1. 查简历 + 岗位