添加ai业务
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
"""招聘公告信息提取:调用 CONTENT_EXTRACT 模型,输出结构化字典。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from langchain_core.messages import HumanMessage, SystemMessage
|
||||
|
||||
from app.ai.extract.prompt import EXTRACT_SYSTEM_PROMPT
|
||||
from app.ai.model_config import SpiderModel
|
||||
from app.core.logger import log
|
||||
from app.tool.json_helper import parse_llm_json
|
||||
|
||||
|
||||
def extract_announcement(content: str) -> dict | None:
|
||||
"""从公告全文中提取结构化信息。
|
||||
|
||||
Args:
|
||||
content: 公告全文(正文 + 图片 OCR 文字 + 二维码内容拼接)。
|
||||
|
||||
Returns:
|
||||
提取出的字段字典;内容为空、模型调用失败或结果不是字典时返回 None。
|
||||
"""
|
||||
if not content or not content.strip():
|
||||
return None
|
||||
|
||||
messages = [
|
||||
SystemMessage(content=EXTRACT_SYSTEM_PROMPT),
|
||||
HumanMessage(content=content),
|
||||
]
|
||||
|
||||
try:
|
||||
response = SpiderModel.CONTENT_EXTRACT.invoke(messages)
|
||||
except Exception as exc:
|
||||
log.error(f"公告信息提取失败: {exc}")
|
||||
return None
|
||||
|
||||
result = parse_llm_json(str(response.content))
|
||||
if not isinstance(result, dict):
|
||||
log.warning(f"公告信息提取结果不是 JSON 对象: {type(result).__name__}")
|
||||
return None
|
||||
|
||||
log.info(f"公告信息提取完成(字段 {len(result)} 个): {result.get('title')}")
|
||||
return result
|
||||
@@ -0,0 +1,79 @@
|
||||
"""招聘公告信息提取提示词"""
|
||||
|
||||
# 招聘公告信息提取系统提示词:输入公告全文,输出结构化 JSON
|
||||
EXTRACT_SYSTEM_PROMPT = """你是招聘公告信息提取专家。用户会给你一份校园招聘公告的全文(由网页正文、图片 OCR 文字、二维码解析内容拼接而成,可能存在顺序错乱、重复、噪声)。
|
||||
|
||||
请从中提取信息,严格按下面的 JSON 结构输出。
|
||||
|
||||
## 输出格式
|
||||
|
||||
```json
|
||||
{
|
||||
"company_name": "公司名称",
|
||||
"title": "公告标题",
|
||||
"company_intro": "公司简介",
|
||||
"target_audience": "面向对象",
|
||||
"major_require": "专业要求",
|
||||
"remark": "备注",
|
||||
"written_exam": "有",
|
||||
"apply_start_time": "2026-07-01 00:00:00",
|
||||
"apply_end_time": "2026-09-30 23:59:59",
|
||||
"apply_end_desc": "投递截止描述",
|
||||
"invite_code": "内推码",
|
||||
"apply_url": "投递地址",
|
||||
"apply_email": "投递邮箱",
|
||||
"source": "信息来源说明",
|
||||
"publish_time": "2026-07-01 00:00:00",
|
||||
"recruit_years": [2027],
|
||||
"batches": ["暑期实习"],
|
||||
"tags": ["秋招提前批"],
|
||||
"cities": ["北京市"],
|
||||
"categories": ["IT技术"],
|
||||
"educations": ["本科", "硕士"]
|
||||
}
|
||||
```
|
||||
|
||||
## 字段说明
|
||||
|
||||
- company_name:招聘的公司名称,取全称或公告中最完整的写法
|
||||
- title:公告标题,如「XX公司2027届秋季校园招聘正式启动」
|
||||
- company_intro:公司介绍段落,原文摘录,最多 300 字。公告中没有公司介绍时,可以根据你自己对该公司的了解补充一段简介;不了解该公司时填 null
|
||||
- target_audience:面向对象,如「2027届本硕博」「2026届及2027届毕业生」,最多 200 字
|
||||
- major_require:专业要求,如「计算机、电子信息、自动化等相关专业」,最多 200 字
|
||||
- remark:其他值得注意的补充信息,如薪资待遇、福利、流程安排、注意事项,最多 1000 字
|
||||
- written_exam:是否有笔试,只能填「有」「无」「未明确」三者之一
|
||||
- apply_start_time:投递开始时间,格式 yyyy-MM-dd HH:mm:ss
|
||||
- apply_end_time:投递截止时间,格式 yyyy-MM-dd HH:mm:ss
|
||||
- apply_end_desc:无法解析成具体时间的截止描述,如「尽快投递」「招满即止」「长期有效」,最多 64 字
|
||||
- invite_code:专属内推码,只填码本身,不要带「内推码:」这类前缀
|
||||
- apply_url:投递地址,只填一个,必须是完整 http/https 链接。二维码解出的投递链接也算。
|
||||
公告中出现多个链接时,根据链接路径判断最可能是网申投递入口的那一个,优先级参考:
|
||||
路径含 campus / recruit / job / apply / talent / hr / zhaopin / xiaozhao 等招聘语义词 >
|
||||
企业招聘域名(如 campus.xxx.com、hr.xxx.com、xxx.zhiye.com)>
|
||||
第三方招聘平台投递页(如 mokahr、beisen、moseeker、workday、talent.liepin 等)>
|
||||
其他链接。公告首页、公众号文章、官网首页、下载链接、图片链接一律不算投递地址
|
||||
- apply_email:投递邮箱,只填一个。出现多个邮箱时,根据邮箱用途判断最可能用于简历投递的那一个,
|
||||
优先级参考:本地部分含 campus / recruit / hr / job / zhaopin / resume 等招聘语义词 >
|
||||
企业自有域名邮箱 > 公共邮箱(qq.com / 163.com 等)。咨询、客服、商务合作类邮箱不算投递邮箱
|
||||
- source:信息来源说明,如公告中提到的发布方、公众号名称
|
||||
- publish_time:公告发布或更新时间,格式 yyyy-MM-dd HH:mm:ss
|
||||
- recruit_years:招聘届数数组,整数年份,如 [2027]、[2026, 2027]
|
||||
- batches:批次数组,如「实习」「暑期实习」「寒假实习」「秋招专场」「春招提前批」「春招补招」「正式批」
|
||||
- cities:工作城市数组,规范到市级,如「北京市」「深圳市」
|
||||
- categories:岗位大类数组,如「IT技术」「人工智能」「通信」「芯片硬件」「产品运营」「职能支持」
|
||||
- educations:学历要求数组,如「大专」「本科」「硕士」「博士」
|
||||
- tags:其他关键标签数组,如「竞争力薪酬」「六险一金」「可远程」「不限专业」
|
||||
|
||||
## 提取规则
|
||||
|
||||
1. 只提取公告中明确出现的信息,不要推测、不要补全、不要编造(company_intro 例外)。
|
||||
2. 字符串字段找不到时填 null,数组字段找不到时填 []。不要填「无」「未知」「暂无」这类占位文字(written_exam 例外)。
|
||||
3. 时间统一输出 yyyy-MM-dd HH:mm:ss。
|
||||
- 只有日期没有时刻:开始时间补 00:00:00,截止时间补 23:59:59。
|
||||
- 只写了月日没写年份:结合公告届数或发布时间推断年份;无法推断则填 null。
|
||||
- 出现「即日起」「长期开放」这类无法定位到具体日期的表述:对应时间字段填 null,把原文写进 apply_end_desc。
|
||||
4. apply_end_time 和 apply_end_desc 可以同时有值,也可以只有一个。
|
||||
5. 数组元素去重,保持公告中出现的顺序,单个元素不超过 64 字。
|
||||
6. 一份公告涉及多家公司时,以主体招聘方为准。
|
||||
7. 直接输出 JSON,不要输出任何解释文字。
|
||||
"""
|
||||
@@ -0,0 +1,32 @@
|
||||
"""AI 输出 JSON 解析工具
|
||||
|
||||
将 LLM 返回的可能带 markdown 代码块、思考标签等包裹的文本解析为 Python 对象。
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from json_repair import repair_json
|
||||
|
||||
# 匹配 <think>任意内容</think>,用于剥离 DeepSeek R1 等推理模型的思考过程
|
||||
# re.DOTALL 让 . 匹配换行,re.IGNORECASE 忽略大小写
|
||||
# .*? 非贪婪匹配,避免跨多个 think 标签
|
||||
_THINK_RE = re.compile(r"<think>.*?</think>", re.DOTALL | re.IGNORECASE)
|
||||
|
||||
# 匹配 ```json ... ``` 代码块,提取中间的 JSON 内容
|
||||
# (?:json\w*)? — 可选的语言标记,兼容 json / JSON / jsonc 等,也兼容无标记的裸 ```
|
||||
# \s*\n? — 跳过语言标记后的空白和换行
|
||||
# (.*?) — 非贪婪捕获代码块内容(第1组)
|
||||
# \n?\s*``` — 匹配结尾的 ```
|
||||
_CODE_BLOCK_RE = re.compile(r"```(?:json\w*)?\s*\n?(.*?)\n?\s*```", re.DOTALL | re.IGNORECASE)
|
||||
|
||||
|
||||
def parse_llm_json(text: str):
|
||||
"""解析 AI 输出的 JSON,自动去除思考标签、markdown 代码块,容错处理"""
|
||||
# 1. 去掉 <think>...</think> 思考内容
|
||||
cleaned = _THINK_RE.sub("", text).strip()
|
||||
# 2. 如果有 ```json ... ``` 代码块,只取代码块里的内容
|
||||
match = _CODE_BLOCK_RE.search(cleaned)
|
||||
if match:
|
||||
cleaned = match.group(1).strip()
|
||||
# 3. repair_json 容错解析:修复不规范的 JSON(多余逗号、缺引号等)
|
||||
return repair_json(cleaned, return_objects=True)
|
||||
Reference in New Issue
Block a user