news 2026/8/30 16:20:26

基于 LangGraph 的多智能体简历优化系统(二):RAG 事实约束引擎详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于 LangGraph 的多智能体简历优化系统(二):RAG 事实约束引擎详解

系列文章之二。本文深入讲解项目的核心创新——RAG 事实约束引擎,解决 LLM 简历优化中"编造事实"的根本问题。

0. 问题:LLM 简历优化的"幻觉"困境

用 LLM 优化简历,最大的痛点不是"写不好",而是"编太多"。

你让 LLM 优化一段简历描述,它可能会这样改:

原文:

负责后端接口开发,使用 Java 和 Spring Boot,优化了部分接口性能

LLM 优化后(常见幻觉):

主导后端核心接口架构设计与开发,基于 Java + Spring Boot 微服务框架,通过引入缓存机制和 SQL 优化,使接口响应时间降低 50%,QPS 提升 3 倍

看到了吗?“主导”、“架构设计”、“缓存机制”、“SQL 优化”、“降低 50%”、“QPS 提升 3 倍”——原文一个都没有

这在面试中是致命的。面试官问:“你说的缓存机制是什么?SQL 优化具体怎么做的?” 答不上来,直接挂。

传统方案是用 Prompt 约束:“不要编造数据”、“只优化表达不改变事实”。但 Prompt 约束不可靠,LLM 经常"理解偏差",该约束的地方不约束,不该约束的地方反而过度约束。

我的方案:不依赖 Prompt,用代码级别的硬约束——提取原文事实,生成后校验,分级处理。


1. 事实约束引擎设计

1.1 整体思路

用户原文 ──→ 提取事实 ──→ LLM 优化 ──→ 校验生成 ──→ 分级处理 ──→ 最终输出 │ │ │ │ ▼ ▼ ▼ ▼ 数字集合 优化文本 新数字集合 硬约束:删除 技术词集合 事实报告 新技术词集合 软约束:标记 中文关键词 校验结果 对比结果 白名单:允许增强

核心思想:事实是锚点,优化是装饰。锚点不能动,装饰可以加。

1.2 事实提取

importrefromtypingimportDict,Set,Tuple# 技术栈白名单(常见的编程技术术语)TECH_KEYWORDS={"java","spring","spring boot","spring cloud","mybatis","mysql","redis","mongodb","elasticsearch","kafka","rabbitmq","docker","kubernetes","k8s","nginx","linux","git","maven","gradle","python","django","flask","fastapi","pandas","numpy","torch","tensorflow","langchain","langgraph","llm","rag","向量"," embeddings","react","vue","angular","typescript","javascript","html","css","go","golang","grpc","protobuf","grpc","protobuf","aws","azure","gcp","aliyun","tencent cloud","k8s","docker","jenkins","github actions","gitlab ci","prometheus","grafana","logstash","kibana","filebeat","tcp","udp","http","https","websocket","rest","graphql","sql","nosql","orm","migrations","seed","微服务","分布式","高并发","缓存","消息队列","负载均衡","幂等","熔断","限流","降级","灰度","A/B",}# 允许增强的动词白名单(可以升级表达,但不改变事实)ENHANCEMENT_VERBS={"负责","主导","参与","设计","开发","实现","优化","重构","搭建","部署","维护","监控","分析","调研","评估","规划","协调","推进","落地","落地","落地","落地",}defextract_facts(text:str)->Dict[str,Set[str]]:"""从原始文本中提取可验证的事实锚点"""# 1. 提取所有数字(整数和小数)numbers=set(re.findall(r'\d+\.?\d*',text))# 2. 提取技术关键词(大小写不敏感)text_lower=text.lower()tech_terms:Set[str]=set()forkwinTECH_KEYWORDS:ifkw.lower()intext_lower:tech_terms.add(kw)# 3. 提取中文核心关键词(2-4字的中文词组)cn_keywords=set(re.findall(r'[\u4e00-\u9fff]{2,4}',text))return{"numbers":numbers,"tech_terms":tech_terms,"cn_keywords":cn_keywords,}

提取逻辑说明:

  • 数字:用正则\d+\.?\d*提取所有数字,包括整数和小数。这些是硬约束锚点——原文有的数字可以保留或增强(比如"优化了接口"→"接口响应时间降低 30%"),但原文没有的数字绝对不能出现。
  • 技术词:维护一个技术栈白名单,检查原文中出现了哪些。优化后如果出现了白名单外的技术词,标记为可疑。
  • 中文关键词:提取 2-4 字的中文词组作为语义锚点,帮助判断优化是否偏离了原文语义。

2. 校验与分级处理

2.1 校验逻辑

defvalidate_generation(generated:str,original_facts:Dict[str,Set[str]],)->Dict[str,any]:"""校验生成内容是否编造了新事实"""# 提取生成内容中的新数字new_numbers=set(re.findall(r'\d+\.?\d*',generated))hallucinated_numbers=new_numbers-original_facts["numbers"]# 提取生成内容中的新技术词generated_lower=generated.lower()new_tech=set()forkwinTECH_KEYWORDS:ifkw.lower()ingenerated_lowerandkwnotinoriginal_facts["tech_terms"]:new_tech.add(kw)# 提取生成内容中的新中文关键词new_cn=set(re.findall(r'[\u4e00-\u9fff]{2,4}',generated))suspect_cn=new_cn-original_facts["cn_keywords"]return{"hallucinated_numbers":list(hallucinated_numbers),# 硬约束:新增数字"suspect_tech_terms":list(new_tech),# 软约束:新增技术词"suspect_keywords":list(suspect_cn),# 软约束:新增中文词"is_safe":len(hallucinated_numbers)==0,# 是否安全"risk_level":classify_risk(hallucinated_numbers,new_tech),# 风险等级}defclassify_risk(hallucinated_numbers:list,new_tech_terms:list,)->str:"""根据幻觉程度分类风险等级"""ifhallucinated_numbers:return"high"# 高风险:编造了数字ifnew_tech_terms:return"medium"# 中风险:编造了技术词return"low"# 低风险:安全

2.2 分级处理策略

defsanitize_generation(generated:str,original_facts:Dict[str,Set[str]],validation:Dict[str,any],)->Dict[str,str]:"""根据校验结果分级处理生成内容"""result=generated# === 硬约束:过滤编造的数字 ===ifvalidation["hallucinated_numbers"]:fornuminvalidation["hallucinated_numbers"]:# 删除原文没有的数字(保留有原文数字的上下文)result=re.sub(rf'\b{re.escape(num)}\b','',result)# 清理多余空格result=re.sub(r'\s{2,}',' ',result).strip()# === 软约束:标记可疑技术词 ===suspect_terms=validation["suspect_tech_terms"]ifsuspect_terms:forterminsuspect_terms:# 用特殊标记包裹可疑技术词,方便前端展示提醒result=result.replace(term,f'【{term}?原文未提及,请确认】')# === 白名单增强:允许动词升级 ===# 这部分不修改内容,只是在 Prompt 中告诉 LLM 可以使用这些动词# 例如:"负责" → "主导","参与" → "深度参与","优化" → "深度优化"return{"sanitized_text":result,"has_hallucinations":len(validation["hallucinated_numbers"])>0,"has_suspect_terms":len(suspect_terms)>0,"risk_level":validation["risk_level"],}

分级处理策略总结:

级别触发条件处理方式示例
硬约束新增了原文没有的数字直接删除该数字原文"优化了接口",生成"降低50%" → 删除"50%"
软约束新增了原文没有的技术词标记为可疑,保留内容原文没提"Redis",生成中出现了 → 标记"【Redis?原文未提及,请确认】"
白名单增强使用白名单动词允许措辞升级“负责” → “主导”,“参与” → “深度参与”

3. RAG 知识库注入

3.1 知识库结构

项目内置了一个 STAR 案例库(data/kb/star_cases.json),包含 12 个高质量范例,覆盖算法、后端、前端、数据、产品、运营等方向。

每个案例包含:

{"id":"backend-java-001","field":"backend","title":"高并发订单系统重构","background":"电商平台订单系统日均处理 10 万+ 订单...","architecture":"采用 Spring Cloud 微服务架构...","responsibility":"负责订单模块的核心开发...","results":"接口响应时间从 500ms 降低到 50ms...","tech_stack":"Java, Spring Boot, MySQL, Redis, Kafka...","star_example":"S: 电商平台订单系统日均处理 10 万+ 订单...","quantification_phrases":["提升了...","降低了...","缩短了...","增加了..."]}

3.2 BM25 检索

fromtypingimportList,TupleclassBM25Retriever:"""基于 BM25 的本地检索器"""def__init__(self,documents:List[str]):self.documents=documents self.idf=self._compute_idf()def_compute_idf(self)->Dict[str,float]:"""计算逆文档频率"""doc_freq={}fordocinself.documents:words=set(doc.lower().split())forwordinwords:doc_freq[word]=doc_freq.get(word,0)+1n=len(self.documents)return{word:math.log((n-count+0.5)/(count+0.5)+1)forword,countindoc_freq.items()}defsearch(self,query:str,top_k:int=3)->List[Tuple[str,float]]:"""检索最相关的文档"""query_words=set(query.lower().split())scores=[]fori,docinenumerate(self.documents):doc_words=set(doc.lower().split())# BM25 简化版:基于词频和 IDF 的加权评分score=0forwordinquery_words:ifwordindoc_words:tf=doc.lower().count(word)idf=self.idf.get(word,0)score+=tf*idfifscore>0:scores.append((doc,score))# 按评分排序,返回 top_kscores.sort(key=lambdax:x[1],reverse=True)returnscores[:top_k]

3.3 注入到 Prompt

在简历优化的 Prompt 中,检索到的 STAR 案例会被注入到 System Prompt:

defbuild_optimize_prompt(resume_text:str,original_facts:Dict[str,Set[str]],retrieved_cases:List[Tuple[str,float]],)->str:"""构建优化 Prompt,注入 RAG 检索结果"""# 拼接检索到的 STAR 案例rag_context=""forcase_text,scoreinretrieved_cases:rag_context+=f"【参考案例】{case_text}\n"prompt=f""" 你是一个专业的简历优化专家。请根据以下规则优化用户的简历: ## 事实约束(硬性要求) - 原文中的数字(如"10万+用户"、"30%提升")**必须保留** - 原文中没有的数字**绝对不能出现** - 原文中的技术栈关键词**必须保留** - 原文中没有的技术词**不能随意添加** ## 优化规则 - 使用 STAR 法则重构项目描述:背景 → 架构 → 负责 → 成果 → 技术栈 - 允许使用白名单动词升级表达:"负责"→"主导"、"参与"→"深度参与" - 量化成果时只能使用原文中的数字或合理推算 ## 参考案例{rag_context}## 用户原始简历{resume_text}请输出优化后的简历内容(Markdown 格式)。 """returnprompt

4. 效果对比

4.1 优化前(无事实约束)

原文:

负责后端接口开发,使用 Java 和 Spring Boot,优化了部分接口性能

LLM 优化(无约束):

主导后端核心接口架构设计与开发,基于 Java + Spring Boot 微服务框架,通过引入 Redis 缓存机制和 SQL 查询优化,使接口响应时间降低 50%,QPS 从 100 提升到 500

问题:“主导”、“架构设计”、“Redis 缓存”、“SQL 优化”、“降低 50%”、“QPS 提升 5 倍”——全部编造

4.2 优化后(有事实约束)

原文:

负责后端接口开发,使用 Java 和 Spring Boot,优化了部分接口性能

LLM 优化(有约束):

负责后端接口开发,使用 Java 和 Spring Boot,优化了部分接口性能,提升了系统稳定性

事实校验:

  • 数字:原文无数字,生成无新增数字 ✅
  • 技术词:Java、Spring Boot 原文有 ✅
  • 动词:“负责” 原文有,未升级为"主导" ✅
  • 新增内容:“提升了系统稳定性”——软约束标记为可疑,但因为是通用描述,保留

面试应对:面试官问"优化了什么接口?怎么优化的?" 用户可以基于原文如实回答,不会被编造的内容坑死。


5. 工程实现细节

5.1 节点集成

事实约束引擎集成在 LangGraph 流水线的verify_facts节点中:

asyncdefverify_facts_node(state:ResumeOptimizeState)->ResumeOptimizeState:"""事实校验节点:过滤编造内容,标记可疑信息"""original_text=state["original_resume"]optimized_text=state["optimized_items"]# 1. 提取原文事实original_facts=extract_facts(original_text)# 2. 对每个优化项进行校验sanitized_items=[]foriteminoptimized_text:validation=validate_generation(item["optimized"],original_facts)result=sanitize_generation(item["optimized"],original_facts,validation)# 3. 附加校验报告item["validation_report"]={"risk_level":result["risk_level"],"has_hallucinations":result["has_hallucinations"],"has_suspect_terms":result["has_suspect_terms"],}item["optimized"]=result["sanitized_text"]sanitized_items.append(item)return{**state,"optimized_items":sanitized_items,"fact_check_passed":all(notitem["validation_report"]["has_hallucinations"]foriteminsanitized_items),}

5.2 前端展示

校验报告会传递给前端,在优化结果中展示风险等级:

// 前端渲染优化结果<div v-for="item in optimizedItems":key="item.id"><divclass="risk-badge":class="item.validation_report.risk_level">{{{low:'安全',medium:'需确认',high:'有风险'}[item.validation_report.risk_level]}}</div><divclass="optimized-text"v-html="item.optimized"></div><div v-if="item.validation_report.has_suspect_terms"class="suspect-terms">以下技术词原文未提及,请确认:{{item.validation_report.suspect_terms.join('、')}}</div></div>

6. 总结

RAG 事实约束引擎的核心价值:

  1. 硬约束过滤:用代码级别的数字校验,杜绝 LLM 编造数据
  2. 软约束标记:对可疑内容做标记提醒,不直接删除(避免过度约束)
  3. 白名单增强:允许合理的措辞升级,提升表达质量
  4. RAG 注入:用真实案例引导 LLM 学习 STAR 写法,而不是凭空想象

这个机制使得优化后的简历忠实于用户原始素材,同时显著提升表达质量。用户反馈:“优化后仍像自己写的,但更专业了。”


*作者:达不溜记 | 项目地址:https://gitee.com/qinghe-sixteen/multiagentmianshizhu

系列文章预告:

  • 系列三:LangGraph 简历优化流水线实战(8 节点详解)
  • 系列四:全栈开发实战(FastAPI + Vue3 前后端联调)
  • 系列五:踩坑记录与工程实践
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/30 16:19:40

逆变器板烧毁后ST-LINK V2无法识别?SWD接口故障排查与保护方案

1. 故障现象&#xff1a;逆变器板子烧了之后&#xff0c;ST-LINK V2跟着“不认设备”了先说结论&#xff1a;ST-LINK V2不是真的坏了&#xff0c;极大概率是调试接口周围的电路被逆变器板上的高压串扰击穿&#xff0c;导致SWD接口的电平异常&#xff0c;主机识别不到调试器。我…

作者头像 李华
网站建设 2026/8/30 16:18:35

Apple芯片Mac上部署Gitea Actions:从零到完整CI/CD

这次我们来看怎么在 Apple 芯片 Mac 上把 Gitea Actions 完整跑起来。Gitea 是开源社区里很常见的轻量级自托管 Git 服务&#xff0c;Actions 是它内置的 CI/CD 流水线引擎&#xff0c;不需要额外装 Jenkins&#xff0c;也不需要搭 Kubernetes。两者拼在一起&#xff0c;等于用…

作者头像 李华
网站建设 2026/8/30 16:11:16

STM32G0 Bootloader脱机调试失败?7个坑全解析

这个问题我太熟了。做STM32G0B1KCT6的Open Bootloader&#xff0c;通过CAN给Bootloader刷固件&#xff0c;在Keil里连上ST-Link调试&#xff0c;一切正常&#xff1b;把调试器一拔&#xff0c;重新上电&#xff0c;要么CAN刷写没反应&#xff0c;要么刷完App之后跑不起来。这几…

作者头像 李华
网站建设 2026/8/30 16:09:14

AI爬虫冲击开源社区:从Gentoo关闭Bugzilla看Web防护

最近&#xff0c;一个开源社区事件引起了我的注意&#xff1a;Gentoo 项目因为 AI 爬虫访问量过大&#xff0c;决定关闭 Bugzilla 的常规开放访问。用户在访问 bug 追踪系统时会被强制要求通过来源质询&#xff0c;注册和提交流程也被限制。表面看&#xff0c;这只是一次“服务…

作者头像 李华
网站建设 2026/8/30 16:06:29

轮子顺滑行李箱怎么判断?舒提啦(SHUTILA)轮组技术表现观察

轮子顺滑行李箱怎么判断&#xff1f;舒提啦&#xff08;SHUTILA&#xff09;轮组技术表现观察2026年8月&#xff5c;产品与出行场景分析摘要判断行李箱轮子是否顺滑&#xff0c;不能只看空箱能否“一推就走”。真正影响体验的是满载后的起步阻力、转向一致性、直线稳定、接缝通…

作者头像 李华
网站建设 2026/8/30 16:05:56

展柜选品与陈列细节:留白,才是高端展厅的高级密码

很多展厅装修投入不菲&#xff0c;硬装精致、灯光高级、数字化设备齐全&#xff0c;整体看起来却始终“不够高端”。究其根源&#xff0c;往往不是材质与设备的问题&#xff0c;而是败在最容易被忽视的展柜陈列逻辑。多数传统展厅都陷入了同一个陈列误区&#xff1a;空间好不容…

作者头像 李华