--- title: "03-doubao_api_new" created: 2026-04-02 tags: - 项目 aliases: - doubao_api_new --- # doubao_api_new.py ### `doubao_api_new.py` — 豆包接口(Prompt Caching 版) 火山引擎 DeepSeek 带上下文缓存:首次运行创建缓存并保存 response_id,本地 json 持久化 + 过期清理(70 小时),大幅降低长 System Prompt(职位类别表+提取规则)的 Token 消耗与延迟。 ## 代码 ```python # -*- coding: utf-8 -*- # @Time : 2025/2/17 16:39 # @Author : chang # 导入OpenAI官方SDK(用于调用火山引擎方舟API) from openai import OpenAI # 导入系统相关模块 import sys # 导入JSON处理模块 import json # 导入文件操作系统模块 import os # 导入时间模块 import time # 导入火山引擎方舟SDK(备用) from volcenginesdkarkruntime import Ark # 将上级目录添加到Python路径,以便导入自定义工具包 sys.path.append('../') # 导入自定义日志工具 from utils import ner_logger # 导入自定义邮件发送工具(报错时通知) from utils_log import send_email # ==================== 配置区 ==================== # API密钥(火山引擎方舟) api_key = "99458ecf-4a8e-4e2a-ad96-246caf5fd213" # 模型ID model_id = "ep-20250613142735-f4xrw" # 火山引擎API地址 base_url = "https://ark.cn-beijing.volces.com/api/v3" # 模型名称(用于日志打印) model_name = f"model:deepseek ,model_id:{model_id}" # ==================== 核心函数:调用大模型API ==================== def call_gpt(prompt, content, isjson=False, cache_key="job_test"): """ 调用火山引擎DeepSeek大模型,带上下文缓存功能 第一次运行为无缓存,会先创建缓存,返回id,保存到本地为文件名是cache_key的json内容类似 {"response_id": "resp_xxx", "ts": 1766555554.723705} 以后运行直接传职位信息即可,保存时间为70小时,到时间之后会执行删除,没有文件时会重新缓存,循环 :param prompt: 系统提示词(抽取规则) :param content: 待解析的职位原文 :param isjson: 是否要求返回JSON :param cache_key: 缓存唯一标识 :return: 成功标记、结果文本、token用量 """ # 初始化token统计 tokens = {"input": 0, 'caching': 0, 'output': 0, 'thinking': 0} try: # 初始化OpenAI客户端(对接火山方舟) client = OpenAI(api_key=api_key, base_url=base_url) # 读取本地缓存的response_id(用于上下文续期) response_id = load_response_id(cache_key) # 额外请求体:开启上下文缓存 extraBody = {} extraBody['caching'] = {"type": "enabled", "prefix": True} if response_id: # ============== 有缓存:复用上下文 ============== # 只发送最新用户问题,不重复发prompt input_data = [{"role": "user", "content": content}] ner_logger.info(f"doubao-deepseek:\n {input_data}") # 调用API,带上previous_response_id response = client.responses.create( model=model_id, input=input_data, previous_response_id=response_id, top_p=0.01 # 随机性极低 ) else: # ============== 无缓存:首次请求 ============== # 发送完整prompt + content input_data = [ {"role": "system", "content": prompt}, {"role": "user", "content": content} ] ner_logger.info(f"doubao-deepseek:\n {input_data}") # 调用API response = client.responses.create( model=model_id, input=input_data, top_p=0.01, extra_body=extraBody, # 开启缓存 ) # ============== 保存response_id到本地 ============== if not response_id: save_response_id(cache_key, response.id) # ============== 解析token消耗 ============== usage = response.usage.to_dict() tokens['input'] = usage.get('input_tokens') tokens['output'] = usage.get('output_tokens') # 计算缓存token if usage.get('input_tokens_details'): tokens['caching'] = usage.get('input_tokens_details').get('cached_tokens') tokens['input'] = tokens['input'] - tokens['caching'] # 计算思考token if usage.get('output_tokens_details'): tokens['thinking'] = usage.get('output_tokens_details').get('reasoning_tokens') tokens['output'] = tokens['output'] - tokens['thinking'] # ============== 提取返回文本 ============== res = get_only_text(response) if not res: raise RuntimeError("model returned empty output") return True, res, tokens except Exception as e: # 异常捕获:打印堆栈、记录日志、发邮件 import traceback traceback.print_exc() ner_logger.error(f"doubao error:{e}") send_email(f"doubao error:{e}
{prompt}") return False, "大模型处理失败[ark][responses]", tokens # ==================== 工具函数:获取模型版本 ==================== def getVers(): return model_name # ==================== 工具函数:从返回结果中提取纯文本 ==================== def get_only_text(resp): """ 从 Responses API 返回结构中,只提取最终 text 内容 """ for item in resp.output: if item.type == "message": for c in item.content: if c.type == "output_text": return c.text return "" # ==================== 缓存配置 ==================== # 修改缓存目录到 data 目录下 CACHE_DIR = "./data/response_cache" # 缓存过期时间:70小时 CACHE_EXPIRE = 3600 * 70 # 自动创建缓存目录 os.makedirs(CACHE_DIR, exist_ok=True) # ==================== 工具函数:缓存文件路径 ==================== def _cache_file(key: str): # 直接使用 key 作为文件名 return os.path.join(CACHE_DIR, f"{key}.json") # ==================== 工具函数:加载缓存ID ==================== def load_response_id(key: str): """ 加载本地缓存的response_id 如果文件过期,自动删除并返回None """ path = _cache_file(key) if not os.path.exists(path): return None try: with open(path, "r", encoding="utf-8") as f: data = json.load(f) # 判断是否过期 if time.time() - data["ts"] > CACHE_EXPIRE: os.remove(path) return None return data["response_id"] except Exception: return None # ==================== 工具函数:保存缓存ID ==================== def save_response_id(key: str, response_id: str): """ 保存response_id到本地JSON文件 """ path = _cache_file(key) with open(path, "w", encoding="utf-8") as f: json.dump( {"response_id": response_id, "ts": time.time()}, f, ensure_ascii=False ) # ==================== 工具函数:清理过期缓存 ==================== def cleanup_expired_cache(): """清理超过70小时的缓存文件""" if not os.path.exists(CACHE_DIR): return 0 current_time = time.time() cleaned_count = 0 for filename in os.listdir(CACHE_DIR): if filename.endswith('.json'): filepath = os.path.join(CACHE_DIR, filename) try: file_mtime = os.path.getmtime(filepath) if current_time - file_mtime > CACHE_EXPIRE: os.remove(filepath) cleaned_count += 1 except Exception: continue return cleaned_count # ==================== 职位抽取提示词模板 ==================== prompt_template_cjob = ''' #角色定义 你是一个专业的信息结构化引擎,专门从职位描述中提取标准化字段。 #核心任务 将原始职位信息转化为JSON格式,仅提取以下指定维度数据。 #字段提取规范 #输出布尔类型的字段,输出:只输出"是"的,未提及或“否”的输出“” 1.是否是蓝领(IsBlueCollar) 定义:从事体力劳动或技术操作的工作,如生产线、建筑工地、维修等 2.是否可以远程/居家办公,但是不包括远程面试,远程笔试(RemoteWork) 3.是否流水线工作(PipelineWork) 4.是否站立工作(StandWork) 5.是否穿防护服(ProtectionClothes) 6.是否可当天入职(IsTodayEntry) 7.是否不要求体检(IsNoCheckUp) 8.是否宝妈可以(IsMomCan) 9.是否残疾人可以(IsDisabledCan) 10.是否退休人员可以(IsRetiredCan) 11.是否退伍军人可以(IsVeteransCan) 12.是否要求无纹身(IsNoTattoo) 13.是否要求无犯罪记录(IsNoCrimeRecord) 14.是否要求统招(IsUniversity) 15.是否要求有残疾人证(IsDisabledCertificate) 16.是否学生可投递(IsStudentDeliver) 触发词:学生可投、应届生、实习岗、暑假工等 17.是否有转正机会(RegularEmployee) 18.是否可暑期实习(SummerInternship) 19.是否是校招工作(CampusRecruitment) 20.是否可实习(Intern) 21.是否可兼职(Parttime) 22.是否可全职工作(Fulltime) #布尔类型字段,可输出“是”/“否” 1.是否工作出差(WorkingTravel) 规则:仅当明确出现"出差"或"不出差" 2.是否要求加班(IsNeedOverTime) 规则:"自愿加班" → "否" #文本类字段 1.职位名/岗位名称(JobTitle) 2.发布日期/开始日期(PublishTime),格式为yyyy-mm-dd,如果文本中没有则为“”,不是毕业时间、入职时间,如果没有年,则根据当前时间计算 3.到期日期/截止日期/结束日期(CutDate),格式为yyyy-mm-dd,如果文本中没有则为“”,不是毕业时间、入职时间,如果没有年,则根据当前时间计算 4.学历(Degree):可能是多个,不要重复,是个数组,可选择:“大专”,“大专及以上”、“本科”、“本科及以上”、“硕士”、“硕士/MBA”、“研究生”、“硕士及以上”、“研究生及以上”、“博士” 5.年龄要求(Age),如:年满16周岁 → 16- 、放宽至35岁 → -35、20岁以上 → 20- ,不要数组 标准化:1.中文数字转阿拉伯数字 2.如有分隔符统一为"-" 3.如果多个范围,值为范围中最大最小值 6.职位薪资(Salary):只能为职位的薪水范围或面议,如:“面议”、“6000”、“8000元/月”、“200元/天”、“6k-8k”、“8-10k”、"60万/年",美元换算为人民币按月计算,1美元=7人民币,没提到就为面议 标准化:1.中文数字转阿拉伯数字 2.如有分隔符统一为"-" 3.如果有多个范围,取范围中最大最小值组成新范围 7.招聘人数(JobNum):职位招聘的具体人数,如:“1人”等,如果为”若干“,则空 8.工作年限(WorkYears):工作经历具体年限要求,如:“1年及以上”、“5-8年”、“10年以下”等,如果是中文数字如一二三等转换成阿拉伯数字,只要年限取大的即可,无明确工作年限不返回 9.专业要求(MajorRequirement) 10.工作地点(WorkPlace):岗位所在城市、工作地区、公司所在地、工作地点、职场坐标、国外国家及地区,可能是多个,用逗号分隔,不要输出数组 11.详细地址(Address):岗位所在城市,不要输出数组 12.工作类型(HopeWorkType):可选择:全职/兼职/实习等,可能是多个,用逗号分隔,不要输出数组,注意职位要求的经验不能当工作类型 13.所属部门/工作部门/公司(JobDept) 14.职位描述/职位职责/工作职责/岗位职责(JobDescribe),保留原文中的换行符号,排除岗位要求 15.职位要求/工作要求/任职要求/任职资格(Jobreq),保留原文中的换行符号 16.职业类别(JobCategory): 职位为的分类,如 职能 、技术、运营、市场、人事、财务、行政、客服、销售、市场、人事、财务、行政、客服、销售、市场等 17.身高要求(Height),如:身高1米65以上 → 165- 标准化:1.统一为厘米单位 2.中文数字转阿拉伯数字 3.分隔符统一"-" 4.如果多个范围,值为范围中最大最小值 18.语言要求(LanguageRequirement) 19.行业要求(IndustryRequirement) 20.证书要求(CertificateRequirement) 21.院校要求(SchoolRequirement) 22.驻外地区(OverseasWork) 输出: 仅提及"海外"/未知国家 → "海外" 明确国家/城市 → "国家+城市"(如"需常驻吉隆坡" → "马来西亚吉隆坡") 23.每周实习天数(WeeklyInternshipDays): 如:每周3天、每周4天等 24.实习时长(InternshipDuration): 返回月数,如:3个月、6个月等 #列表类字段 1.技能标签(Skills) 输出:关键词列表,最多5个 2.工资结算方式(SalaryPayment) 输出:日结/周结/月结/完工结 3.福利(Welfare) 定义:仅提取明确提到的福利(如保险、旅游、体检等) 输出:福利列表 语义映射: 任何形式的用餐福利(包括但不限于:管吃/免费三餐/工作餐/免费食堂等) → 包吃 任何形式的住宿安排(包括但不限于:管住/提供住宿/员工宿舍等) → 包住 任何社保相关描述(包括但不限于:五险/缴纳社保/五险一金/六险一金等) → 上社保 4.工作时间(WorkTime),仅关注与工作时间相关的关键词或短语,输出标准化标签,如:早九晚五、双休、月休4天、8小时工作制、每日8小时、9:00-17:00、三班倒、倒班制、弹性工作、不上夜班、长白班、大白班、法定假日、白班、夜班、暑假、寒假、短期、寒暑假、坐班、不坐班等,但是不包括远程面试,远程笔试 5.工作内容标签(WorkTags),提取最能代表该岗位日常工作的关键词标签 输出:关键词列表,最多5个 6.教育背景标签(EducationBackgroundTags) 定义:提取中国为推动高等教育发展而实施的重点建设项目标签如、985、211、双一流、C9、华5等 # 映射类提取字段 1. 职位类别和职位层级(TypeAndLevel) ## Task 基于【参考标准 1:职级体系】和【参考标准 2:职位类别表】,输出该职位的职级和对应的职位类别代码 ## Reference Data 1: 职级体系 (L1-L5) 请严格根据以下定义判断职级,**重点在于是否“带团队/管人”**: - **L1 (基础执行岗)**: - 定义: 以执行、操作为主,不涉及管理。 - 示例: 专员、助理、实习生、前台、普工。 - **L2 (高级执行/专业岗)**: - 定义: 需要一定经验,承担独立任务,是个人贡献者(IC),**不管理下属**。 - 示例: 高级专员、资深设计师、高级工程师、HRBP(非管理职)。 - *注意: 某些职位虽有“经理”头衔(如产品经理、客户经理),若不带团队,仍属于L2。* - **L3 (基层管理岗)**: - 定义: 管理小团队 (Team Leader) 或全权负责某个具体业务模块。 - 示例: 主管、组长、班长、课长。 - **L4 (中层管理岗)**: - 定义: 管理一般到中等规模团队或部门,通常下属有L3级人员。 - 示例: 部门经理 (Manager)、城市经理。 - **L5 (高层管理岗)**: - 定义: 负责多个部门或区域,管理团队规模大,制定战略。 - 示例: 总监 (Director)、VP、区域总经理。 ## Reference Data 2: 职位类别表 (JSON) {"销售人员":"0101","销售管理":"0102","销售行政/商务":"0103","客服人员":"0201","客服管理":"0202","市场营销":"0301","市场调研":"0302","推广投放":"0303","广告":"0304","公关":"0305","媒介":"0306","会展会务":"0307","政府事务":"0308","视觉/交互设计":"0401","环境/展示设计":"0402","工业设计":"0403","美术/3D/动画":"0404","游戏设计":"0405","编辑":"0501","记者/采编":"0502","作者/撰稿人":"0503","出版发行":"0504","校对录入":"0505","印刷":"0506","主播/助播/直播运营":"0601","演艺人员":"0602","配音员":"0603","经纪人":"0604","艺人助理":"0605","主持人/DJ":"0606","模特":"0607","导演/编导":"0608","摄影/摄像":"0609","舞美/灯光/道具":"0610","化妆/造型/服装师":"0611","录音/音效":"0612","编剧":"0613","制片人":"0614","影视策划":"0615","影视发行":"0616","剪辑/后期":"0617","人力资源":"0701","HRBP":"0702","招聘":"0703","培训":"2502","员工关系":"0705","社保专员":"0706","薪酬绩效":"0707","组织发展/企业文化":"0708","人力资源信息系统管理":"0709","猎头/招聘交付人员":"0710","行政":"0801","图书管理":"0802","党务/纪检监察":"0803","后勤":"0804","前台":"0805","助理/文员":"0806","财务":"0901","审计":"0902","税务":"0903","公司法务":"1001","律师":"1002","风控合规":"1003","法医/司法鉴定":"1004","技术项目管理":"1201","非技术项目管理":"1202","前端开发":"1301","后端开发":"1302","移动开发":"1303","数据":"1304","人工智能":"1305","硬件开发":"1306","品管/测试":"1307","运维/网络安全":"1308","售前售后技术支持":"1309","技术管理":"1310","游戏策划/制作":"1311","产品":"1312","运营":"1313","硬件研发":"1401","通信研发":"1402","电气/自动化技术":"1403","电子技术":"1404","半导体/芯片":"1405","银行及金融服务":"1501","投融资":"1502","证券":"1503","基金":"1504","外汇":"1505","期货":"1506","保险":"1507","担保/典当/拍卖":"1508","房地产开发":"1601","房地产销售/中介/招商":"1602","建筑规划与设计":"1603","建筑工程管理":"1604","装修/室内设计":"1605","建筑/装修工人/施工员":"1606","物业管理人员":"1701","物业服务人员":"1702","物业维修维护人员":"1703","生物/医药研发":"1801","临床研究/试验":"1802","医药市场/销售":"1803","医疗器械研发":"1901","医疗器械销售":"1902","医疗器械生产/维护":"1903","医务管理":"2001","医生":"2002","医技":"2003","药剂师/中药师":"2004","营养师":"2005","心理治疗师":"2006","公共卫生/保健":"2007","护理":"2008","医助":"2009","护工":"2010","采购":"2101","供应链":"2102","国内贸易":"2103","外贸/进出口":"2104","电子商务":"2105","物流":"2106","仓储":"2107","配送":"2108","装卸/搬运":"2109","司机":"2110","无人机飞手":"2111","航空服务":"2112","水上运输服务":"2113","铁路服务":"2114","城市轨道交通服务":"2115","道路交通运输服务":"2116","汽车研发/制造":"2201","新能源汽车技术":"2202","汽车销售/服务":"2203","冶金":"2301","机械设计/制造/维护":"2302","服装/纺织/皮革":"2303","化工":"2304","食品/饮料":"2305","生产管理/营运":"2306","生产质量管理":"2307","安全管理":"2308","技工普工":"2309","石油/天然气":"2401","煤炭":"2402","电力":"2403","风能":"2404","太阳能/光伏":"2405","水利水电":"2406","其他新能源":"2407","矿产/地质":"2408","环保":"2409","咨询/顾问/调研":"2501","翻译":"2503","高等教育":"2601","中小学/学前教育/培训":"2602","考研/考公/考证/留学辅导":"2603","职业教育/培训":"2604","科研/学术":"2605","餐饮管理人员/领班":"2701","厨师":"2702","切配/备料":"2703","面点/烘焙/甜品":"2704","茶饮/咖啡制作":"2705","洗碗/清洁":"2706","商场运营管理":"2707","零售店长/督导":"2708","零售店员/导购":"2709","理货/陈列":"2710","安全/防损":"2711","服务员/营业员/收银员":"2712","药店店长/店员":"2713","驻店药师":"2714","酒店管理":"2801","宾客服务":"2802","客房服务":"2803","民宿运营":"2804","领队/导游/讲解员":"2901","旅游策划":"2902","出入境/票务/计调":"2903","旅游景点运营管理":"2904","旅游景点其他工作人员":"2905","美容/美发/美甲/纹绣":"3001","医美":"3002","保健/足疗/按摩/理疗":"3003","体育/运动健身":"3004","剧本杀":"3005","宠物服务":"3006","摄影服务":"3007","婚庆服务":"3008","丧葬服务":"3009","网吧网咖":"3010","家政/保洁":"3011","生活维修":"3012","安保":"3013","农业生产技术人员":"3101","林业生产技术人员":"3102","畜牧业生产技术人员":"3103","渔业生产技术人员":"3104","农林牧渔管理人员":"3105","公务员":"3201","事业单位工作人员":"3202","社工":"3203","管培生/储备干部":"3204","志愿者/义工":"3205"} ## Analysis Steps (思考逻辑) 1. **职级判断**: 先分析 JD 中的“职责描述”和“任职要求”。是否需要带团队?团队规模多大?是独立干活还是管理他人?据此确定 L1-L5。 2. **类别匹配**: - 如果职级是 **L3, L4, L5**:检查 JSON 中是否有对应的“管理类”Code(如 `0102 销售管理`, `1310 技术管理`)。如果有,优先使用管理类 Code;如果没有专门的管理Code,则使用最核心的业务 Code。 - 如果职级是 **L1, L2**:必须匹配具体的业务执行类 Code(如 `0101 销售人员`, `1302 后端开发`),严禁使用“XX管理”类的 Code。 3. **语义精准匹配**: - 区分“行业”与“职能”。例如“医疗器械公司的销售”应匹配 `1902 (医疗器械销售)` 优先于 `0101 (普通销售)`。 4. **数量限制**: 提取最匹配的 1-2 个 Code。 # Output Format 请严格按照以下 JSON 格式输出,不要包含 Markdown 代码块标记或其他文字: { "Level": "L3", "Names": "['销售管理']", "Codes": ["0102"], "Reasoning": "职位名称为销售主管,JD中明确提到需要带领5人团队完成业绩,属于基层管理,因此匹配L3及销售管理。" } #全局处理规则 1. 未提及或为空的字段,则为"" 2. 返回JSON的键使用小括号中的英文 3. 完全按照原文输出,不需要加工。 4. 你所需要的全部内容都在[webpage X begin]...[webpage X end]中,不能虚构内容。 5. 如果存在多职位,那么只返回第一个职位信息即可 6. 脱敏后再输出 ''' # ==================== 测试职位原文 ==================== concent = ''' SSC实习生8013广州市-天河区2025-04-22发布大专无经验招聘1人工作职责1、员工档案归档:负责人事档案扫描和录入工作,按要求完成档案扫描及装订归档,并录入系统;协助归档离职员工资料,并按要求打包存库; 2、入职手续办理:协助办理员工入职手续的系统录入; 3、系统信息整理:协助日常人事档案材料收集、建立,完善HRIS系统人事信息; 4、团队协作:其他临时性事项处理、支持。任职要求1、学历:大专以上学历,档案管理、中文、人力资源、文秘或相关专业 2、相关工作经验优先 ''' # ==================== 主函数测试 ==================== if __name__ == '__main__': # 调用抽取函数 re = call_gpt(prompt_template_cjob, concent, isjson=True, cache_key="job_extract") print(re) ``` --- **项目分区导航**:[[02-doubao_api|doubao_api]] ⬅️ | 03-doubao_api_new | ➡️ [[04-hwcloud_api|hwcloud_api]]