删除无效代码
This commit is contained in:
@@ -79,7 +79,7 @@ offerpie_python_ai/
|
||||
│ └─ user_job_customize_resume.py # 用户岗位定制简历表(bg_user_job_customize_resume)
|
||||
│
|
||||
├─ tool/ # **工具层**(无状态、无业务依赖的通用工具)
|
||||
│ ├─ file_parser.py # 文件解析工具(PDF/Word/TXT → 纯文本,parse_to_text 入口方法)
|
||||
│ ├─ 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)
|
||||
│
|
||||
@@ -108,7 +108,7 @@ offerpie_python_ai/
|
||||
| **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/TXT 文件解析为纯文本)、`json_helper.py`(AI 输出 JSON 解析,去 markdown 代码块 + json_repair 容错)、`snowflake.py`(雪花ID生成) |
|
||||
| **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️⃣ 技术栈
|
||||
|
||||
+21
-44
@@ -1,14 +1,10 @@
|
||||
"""文件解析工具
|
||||
|
||||
将上传的简历文件(PDF / Word / TXT)转换为「文本单元数组」或纯文本字符串。
|
||||
将上传的简历文件解析为「文本单元数组」,供 AI 按行号定位 description/summary,避免照抄长文本。
|
||||
- PDF:使用 LiteParse(JSON 模式)按文本块的阅读顺序提取,每个文本块作为一个单元。
|
||||
关闭 OCR —— 文本型简历无需 OCR,且 OCR 默认会联网下载字库导致严重阻塞。
|
||||
- Word:按段落(paragraph)切分,表格行用 \t 拼成一个单元。
|
||||
- TXT:按换行切分。
|
||||
单元数组用于 AI 提取时按行号定位 description/summary,避免 AI 照抄长文本。
|
||||
|
||||
另提供 parse_to_markdown:基于 LiteParse 的 Markdown 结构化输出(保留标题/列表/加粗等
|
||||
语义层级),可用于需要整篇结构化文本的 AI 场景。
|
||||
- Word(.docx):按段落(paragraph)切分,表格行用 \t 拼成一个单元。
|
||||
- TXT / Markdown(.md):自动检测编码,按换行切分。
|
||||
"""
|
||||
|
||||
import io
|
||||
@@ -20,8 +16,7 @@ from liteparse import LiteParse
|
||||
from app.core.logger import log
|
||||
|
||||
# LiteParse 解析器复用实例(关闭 OCR、静默日志)。线程安全由调用方的 to_thread 串行保证。
|
||||
_PDF_SEGMENT_PARSER = LiteParse(output_format="json", ocr_enabled=False, quiet=True)
|
||||
_PDF_MARKDOWN_PARSER = LiteParse(output_format="markdown", ocr_enabled=False, quiet=True)
|
||||
_PDF_PARSER = LiteParse(output_format="json", ocr_enabled=False, quiet=True)
|
||||
|
||||
|
||||
def _drop_repeated(segments: list[str], threshold: int = 3) -> list[str]:
|
||||
@@ -35,7 +30,7 @@ def _pdf_segments(content: bytes) -> list[str]:
|
||||
|
||||
不在预处理阶段合并行——是否合并/分段/排除噪声全部交给 AI 的行号区间决定。
|
||||
"""
|
||||
result = _PDF_SEGMENT_PARSER.parse(content)
|
||||
result = _PDF_PARSER.parse(content)
|
||||
segments: list[str] = []
|
||||
for page in result.pages:
|
||||
for item in page.text_items:
|
||||
@@ -50,7 +45,7 @@ def _docx_segments(content: bytes) -> list[str]:
|
||||
try:
|
||||
doc = Document(io.BytesIO(content))
|
||||
except Exception:
|
||||
raise ValueError("无法解析该 Word 文件,如果是旧版 .doc 格式,请另存为 .docx 后重试")
|
||||
raise ValueError("无法解析该 Word 文件,请确认为有效的 .docx 格式")
|
||||
segments: list[str] = []
|
||||
for para in doc.paragraphs:
|
||||
text = para.text.strip()
|
||||
@@ -65,7 +60,7 @@ def _docx_segments(content: bytes) -> list[str]:
|
||||
|
||||
|
||||
def _txt_segments(content: bytes) -> list[str]:
|
||||
"""解析 TXT,自动检测编码,按换行切分为单元"""
|
||||
"""解析纯文本 (.txt / .md),自动检测编码,按换行切分为单元"""
|
||||
text = None
|
||||
for encoding in ("utf-8", "gbk", "gb2312", "latin-1"):
|
||||
try:
|
||||
@@ -78,38 +73,20 @@ def _txt_segments(content: bytes) -> list[str]:
|
||||
return [line.strip() for line in text.splitlines() if line.strip()]
|
||||
|
||||
|
||||
# 后缀 → 解析函数。新增格式只需在此登记一行。
|
||||
_PARSERS = {
|
||||
".pdf": _pdf_segments,
|
||||
".docx": _docx_segments,
|
||||
".txt": _txt_segments,
|
||||
".md": _txt_segments,
|
||||
}
|
||||
|
||||
|
||||
def parse_to_segments(filename: str, content: bytes) -> list[str]:
|
||||
"""根据文件名后缀自动选择解析方法,返回「文本单元数组」"""
|
||||
"""根据文件名后缀选择解析器,返回去重后的「文本单元数组」"""
|
||||
suffix = filename[filename.rfind("."):].lower() if "." in filename else ""
|
||||
log.info(f"解析文件: {filename},类型: {suffix}")
|
||||
if suffix == ".pdf":
|
||||
segments = _pdf_segments(content)
|
||||
elif suffix in (".docx", ".doc"):
|
||||
segments = _docx_segments(content)
|
||||
elif suffix == ".txt":
|
||||
segments = _txt_segments(content)
|
||||
else:
|
||||
raise ValueError(f"不支持的文件类型: {suffix},支持: .pdf, .docx, .doc, .txt")
|
||||
return _drop_repeated(segments)
|
||||
|
||||
|
||||
def parse_to_text(filename: str, content: bytes) -> str:
|
||||
"""根据文件名后缀自动选择解析方法,返回纯文本(单元用换行拼接)"""
|
||||
return "\n".join(parse_to_segments(filename, content))
|
||||
|
||||
|
||||
def parse_to_markdown(filename: str, content: bytes) -> str:
|
||||
"""将简历解析为 Markdown 结构化文本(保留标题/列表/加粗等语义层级)。
|
||||
|
||||
PDF 使用 LiteParse 的 Markdown 输出;Word/TXT 无版式信息,退化为换行拼接的纯文本。
|
||||
适用于需要整篇结构化文本喂给 LLM 的场景。
|
||||
"""
|
||||
suffix = filename[filename.rfind("."):].lower() if "." in filename else ""
|
||||
log.info(f"解析文件(markdown): {filename},类型: {suffix}")
|
||||
if suffix == ".pdf":
|
||||
return _PDF_MARKDOWN_PARSER.parse(content).text
|
||||
if suffix in (".docx", ".doc"):
|
||||
return "\n".join(_drop_repeated(_docx_segments(content)))
|
||||
if suffix == ".txt":
|
||||
return "\n".join(_drop_repeated(_txt_segments(content)))
|
||||
raise ValueError(f"不支持的文件类型: {suffix},支持: .pdf, .docx, .doc, .txt")
|
||||
parser = _PARSERS.get(suffix)
|
||||
if parser is None:
|
||||
raise ValueError(f"不支持的文件类型: {suffix},支持: {', '.join(_PARSERS)}")
|
||||
return _drop_repeated(parser(content))
|
||||
|
||||
Reference in New Issue
Block a user