简介:这是一份基于DeepSeek的法律舆情智能分析与应对策略生成方案PDF,核心是通过事件抽取技术完成法律热点事件脉络梳理与公关应对方案自动生成,面向NLP算法工程师、法律科技产品经理及舆情分析研究人员。全卷共709页、56个大章节,系统展开数据预处理、法律专业语料库与标注体系、分词词性标注、命名实体识别、触发词识别、BERT事件抽取、注意力机制适配、多标签分类、共指消解、时序与因果关系抽取、事件脉络时序图谱、节点权重计算及可视化数据结构等完整技术链,并覆盖多源舆情采集、社交媒体实时抓取和论坛文本清洗环节。从章节设计看,每个主题都包含问题分析、模型架构、特征工程、训练优化与评估部署,能帮助读者形成可落地的实践路径,而非仅停留在概念层面。文档为单个PDF文件,大小14.3MB,支持书签大纲与章节跳转,文字图表显示正常。目前已有82人学习,适合希望系统掌握法律领域事件抽取、舆情监控与智能应对方案设计的读者深入研读。
1. 法律舆情响应为什么要从“事件抽取”做起
法律热点事件在社交平台上发酵的速度,往往比法务和公关团队整理事实的速度快得多。一个诉讼案件从立案到判决,中间可能穿插着上诉、管辖权异议、证据交换、开庭延期等多个节点,每一条新闻和帖子只披露其中一部分。传统舆情系统用关键词命中去做告警,能告诉你“出事了”,但说不清“事情到底发展到哪一步了”,公关团队拿到告警后还是要人工翻几十篇报道去拼时间线。DeepSeek法律舆情智能分析与应对策略生成方案要解决的问题,正是把这个“人工拼图”的过程自动化:用事件抽取技术从海量文本里抽出结构化事件,再按时间与主体串联成脉络,最后让模型基于脉络自动生成公关应对策略。这套思路适合三类人:做舆情系统的工程师、律所或企业法务部的数字化负责人、以及想用大模型替代重复性舆情研判工作的产品经理。它不是一个花哨的 Demo,而是一条能把几小时的人工研判压缩到几分钟的落地路径。
2. 基于 DeepSeek 做法律事件抽取:从非结构化文本到结构化事件
2.1 为什么事件抽取比关键词监控高一档
传统舆情监控的核心是“词”,比如监控“某上市公司”“起诉”“判决”这些词,命中就告警。但词的组合天然有歧义:“某公司起诉”和“某公司被起诉”是两种完全相反的法律关系,关键词系统区分不了;“驳回上诉”和“驳回起诉”在法律上也是两个不同的程序节点,前者是二审程序终结,后者是立案阶段被挡在门外。事件抽取把目光从“词”抬升到“事件”,一次抽取输出的是“谁在什么时间对谁做了什么、法律后果是什么”这样的完整结构,下游的脉络梳理和策略生成才有可靠的数据基础。
在 DeepSeek 这类大模型出现之前,事件抽取通常依赖 pipeline:先用命名实体识别(NER)抽出人名、机构名、时间,再用关系分类判断实体之间的关系,最后用事件触发词分类去判定事件类型。这套方案的痛点是每个环节的错误会向后传导,而且需要为每个法律领域标注大量训练数据。用 DeepSeek 做事件抽取,等于把 NER、关系分类、事件类型判定合并成一步:模型直接读一段文本,输出结构化的 JSON。缺点是输出格式可能不稳定,但可以通过约束 prompt 和校验逻辑来解决。
2.2 DeepSeek API 调用与事件抽取的 prompt 设计
事件抽取的第一步是先定义 Schema,明确我们关心哪些事件类型和字段。法律舆情场景下,最常用的事件类型包括:立案、开庭、保全、上诉、撤诉、判决、执行、再审。每个事件需要抽取的字段包括:事件类型、发生时间、原告/申请方、被告/被申请方、法院或仲裁机构、案由、涉及法条、事件描述原文、信息来源。
下面是一个用 DeepSeek API 做事件抽取的最小调用示例。假设我们有一条新闻报道文本,要从中抽取出事件结构化数据。
import os from openai import OpenAI client = OpenAI( api_key=os.environ.get("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com" ) EVENT_EXTRACT_PROMPT = """ 你是一个法律舆情事件抽取专家。请从给定的新闻报道中抽取法律事件,并输出 JSON。 事件类型限定为:立案、开庭、保全、上诉、撤诉、判决、执行、再审。 每个事件必须包含以下字段: - event_type: 事件类型 - date: 事件发生日期,格式 YYYY-MM-DD,无法确定时填 null - plaintiff: 原告或申请方 - defendant: 被告或被申请方 - court: 法院或仲裁机构 - cause: 案由 - law_basis: 报道中明确提到的法条,没有则填 null - source_text: 触发该事件的原文片段 要求: 1. 同一段文本中可能包含多个事件,全部抽出来。 2. 不要推测报道中不存在的信息,字段无法确定就填 null。 3. 只输出 JSON 数组,不要输出其他任何文字。 新闻报道: {news_text} """ def extract_events(news_text: str): response = client.chat.completions.create( model="deepseek-chat", messages=[ {"role": "system", "content": "你是一个严谨的法律事件抽取引擎,只输出合法 JSON。"}, {"role": "user", "content": EVENT_EXTRACT_PROMPT.format(news_text=news_text)}, ], temperature=0.1, response_format={"type": "json_object"}, max_tokens=2048 ) return response.choices[0].message.content这段代码的核心在于把事件抽取定义成一个受约束的生成任务。temperature=0.1是必调参数,法律文本的抽取不依赖创造力,温度越低输出越稳定,实测 0.2 以上就开始出现事件类型漂移。response_format={"type": "json_object"}让 DeepSeek 返回结构化的 JSON,省去自己写正则解析的麻烦。prompt 里“不要推测报道中不存在的信息”这句很关键,不加这句话模型会脑补法条和日期,尤其会把“据报道可能上诉”这种推测性表述直接抽成已发生的上诉事件。
拿到 JSON 字符串后,还需要做一次格式校验和字段清洗,不能直接入库。常见做法是用json.loads()解析,捕获异常并记录失败的文本,后面会讲这部分坑。
2.3 事件类型 Schema 与存储结构设计
事件抽取的输出要落到存储里,才能支撑后面的脉络梳理。表结构设计上,我一般会分两张表:一张是事件表,存每个事件本身;一张是新闻源表,存原文和抽取的对应关系。事件表的字段建议如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| event_id | varchar(64) | 事件唯一 ID,用 UUID 或雪花 ID |
| event_type | varchar(16) | 事件类型,枚举值:立案/开庭/保全等 |
| event_date | date | 事件发生日期,允许为 null |
| plaintiff | varchar(128) | 原告/申请方 |
| defendant | varchar(128) | 被告/被申请方 |
| court | varchar(128) | 法院/仲裁机构 |
| cause | varchar(256) | 案由,如“合同纠纷”“侵权责任纠纷” |
| law_basis | varchar(256) | 法条原文,可以为空 |
| source_text | text | 触发该事件的原文片段 |
| news_id | varchar(64) | 关联的新闻报道 ID |
| confidence | float | 模型置信度,由校验逻辑生成 |
confidence字段需要说明一下,它不是模型直接输出的,而是我们自己算的。规则是:所有必填字段都有值记 1.0,每出现一个 null 字段扣 0.2,最低 0.2。这个分数后面用来做脉络梳理时的噪音过滤,低于 0.6 的事件不进时间线,避免把垃圾信息拼进正式脉络。
建表语句里给event_type和event_date建联合索引,因为脉络梳理时最常见的查询就是“按时间范围查某类事件”。另外cause字段要做归一化处理,“合同纠纷”和“买卖合同纠纷”在模型输出里可能同时出现,不归一化会导致同一案由被拆成两个节点。
3. 法律热点事件脉络梳理:把离散事件拼成可信时间线
3.1 事件链构建:按时间排序只是第一步
拿到结构化事件表后,最直觉的做法是ORDER BY event_date按时间排一下就当脉络用。这在简单案件里勉强能看,但真实舆情场景下马上会暴露两个问题:一是不同媒体对同一事件的报道存在时间差,同一天的开庭被报道成三个独立事件;二是案件可能存在多个平行的法律程序,比如一个公司同时涉及民事诉讼和行政诉讼,单纯按时间排会两条线混在一起。
事件链构建的正确姿势是先按“案件主体+案由”分组,再在组内按时间排序。具体来说,用plaintiff + defendant + cause三个字段做分组键,因为这三个字段能唯一定位一个法律关系。分组后每个组就是一条独立的案件线,组内事件按时间排列,再对相邻事件做合并去重。
去重逻辑要处理“同一事件的多次报道”问题。判定的依据是:事件类型相同、时间相同、且原告被告相同,这三个条件同时满足就认为是同一次事件,保留confidence最高的那一条,其余标记为冗余。
3.2 主体对齐与共指消解:模型命名的“同人不同名”问题
做事件抽取时你会很快遇到一个玄学问题:同一家公司在不同报道里的名字不一样。一审报道里叫“某某控股集团股份有限公司”,二审报道里变成“某某控股”,自媒体帖子里可能直接叫“某控股”。如果不去对齐,事件链会被拆得七零八落。
常见的对齐方案有两种。第一种是维护一个法律主体别名表,把“公司全称-简称-股票代码”映射关系提前维护进数据库,抽取后用别名表做一次后处理替换。第二种是对齐不上的情况,用 DeepSeek 做一次批量判定:把两个主体名放到一个 prompt 里,问它们是否指向同一实体,要求只回答是或否。这个方案在冷启动阶段最实用,但要注意控制调用量,主体对齐不是实时链路,可以每天跑一次批处理。
主体对齐后还要做共指消解,解决“该公司”“原告”“被告”这类代词。一个实用技巧是:在 prompt 的事件抽取阶段就要求模型把代词替换成具体的实体名。做法是在抽取规则里加一条:“如果原文用代词指代主体,请在输出时还原为全称。”实测 DeepSeek 在上下文里有明确指代时能完成这个任务,但偶尔会还原错,所以要在校验阶段检查plaintiff和defendant不能等于“原告”“被告”“该公司”这三个词,命中就标记为抽取失败。
3.3 用 DeepSeek 判定事件关系:从时间线升级为脉络图
纯时间线是线性的,但法律事件的关系不只是先后顺序。一个“上诉”事件依赖于之前的“一审判决”事件,一个“再审”事件说明此前的“判决”可能被推翻。如果要生成真正有用的脉络梳理,需要把事件之间的因果关系和程序关系也标出来。
事件关系判定我把它设计成一个独立的后处理步骤,不依赖事件抽取时的输出。做法是把同一案件组内的所有事件逐对丢给 DeepSeek,让它判断两个事件之间的关系类型。关系类型限定四种:cause_of(前导原因)、follow_up(程序后续)、overrule(推翻)、unrelated(无关)。一次调用只判定一对事件,避免长上下文的干扰。
RELATION_PROMPT = """ 你是法律程序分析专家。给定同一案件中的两个法律事件,判断它们之间的关系。 事件A:{event_a} 事件B:{event_b} 关系类型只允许以下四种: 1. cause_of: 事件A是事件B发生的原因或前置程序 2. follow_up: 事件B是事件A的正常程序后续(如一审判决后上诉) 3. overrule: 事件B推翻了事件A的结果(如再审改判) 4. unrelated: 两个事件之间没有直接的法律程序关系 只输出 JSON:{{"relation": "关系类型", "reason": "判断理由"}} """ def infer_relation(event_a: dict, event_b: dict) -> dict: response = client.chat.completions.create( model="deepseek-chat", messages=[ {"role": "system", "content": "你是一个谨慎的法律程序关系判定引擎。"}, {"role": "user", "content": RELATION_PROMPT.format( event_a=json.dumps(event_a, ensure_ascii=False), event_b=json.dumps(event_b, ensure_ascii=False) )}, ], temperature=0.1, max_tokens=512 ) return json.loads(response.choices[0].message.content)这段代码在实际跑的时候要注意:event_a和event_b传入的是事件结构化字段的 JSON 字符串,不是原文。因为原文里包含大量无关信息,会造成关系误判。事件关系判定的计算量是 O(n²),一个事件多的案件可能有上百对组合,所以这个步骤要在离线任务里跑,不能放实时接口里。
脉络图构建完成后,输出结构是一个事件节点列表加上节点间的关系边。节点用event_id标识,边用(event_a_id, event_b_id, relation_type)三元组表示。前端展示时用时间轴为主、关系连线为辅的布局,关系类型用不同颜色区分,这样公关团队一眼能看出案件现在卡在哪个程序环节。
4. 公关应对方案自动生成:分层策略与内容落地的实现路径
4.1 风险等级评估:热度、情绪与法律程序的三维打分
应对方案的起点不是“怎么写声明”,而是“这件事值不值得回应、需要用多大力度回应”。风险等级评估我采用三个维度的加权打分:传播热度、负面情绪强度、法律程序严重度。
传播热度来自舆情系统的常规数据,包括原始帖数量、转载量、阅读量,归一化到 0 到 100 分。负面情绪强度用 DeepSeek 对舆情文本做情感分类,分类标签是“负面/中性/正面”,负面比例超过 60% 计 100 分,低于 30% 计 0 分,中间线性插值。法律程序严重度比较特殊,它由事件链上的最后一个事件类型决定:立案计 30 分,一审判决计 60 分,二审判决计 80 分,再审计 90 分,执行计 95 分。
def compute_risk_score(heat_score: float, sentiment_score: float, legal_score: float) -> str: weight = {"heat": 0.4, "sentiment": 0.3, "legal": 0.3} total = ( heat_score * weight["heat"] + sentiment_score * weight["sentiment"] + legal_score * weight["legal"] ) if total >= 75: return "red" elif total >= 50: return "orange" else: return "blue"风险等级只有三档,刻意不做五档,因为应对策略本质上只有“观望、回应、强回应”三种选择,分太细会导致策略模板重叠。红色意味着必须 4 小时内做出回应,橙色是 24 小时内,蓝色不是不回应,而是不需要公开声明,只需要准备内部材料应对媒体垂询。热度的权重定为 0.4 是因为法律舆情的特殊性:程序严重度再高,如果没有媒体关注,公开回应的紧迫性就低;反过来,一个立案阶段的小案件如果被顶上热搜,公关压力反而更大。
4.2 应对策略模板与策略选择逻辑
策略生成不能完全靠模型自由发挥,那样输出质量不稳定。我采用的是“模板框架 + 模型填充”的结构:模板决定方案的骨架,DeepSeek 负责填充事实和打磨措辞。
按风险等级对应三类策略模板。蓝色策略是“事实待核实,暂不公开回应”,输出内容是一份内部口径清单,列出已确认的事实、待核实的事实、禁止对外说的内容。橙色策略是“公开回应 + 事实澄清”,输出内容包含回应时间窗口、回应渠道、核心口径、法律事实摘要。红色策略是“全面应对 + 法律行动预告”,在橙色基础上增加行动项:是否起诉侵权账号、是否申请禁令、是否召开说明会。
策略选择逻辑除了看风险等级,还要看事件链上的一个关键信息:当前法律程序中是否存在对己方不利的判决。从事件链上找到最后一个overrule关系或判决事件,如果判决结果对被告不利,策略会倾向红色档,即便热度分数没到 75。
STRATEGY_TEMPLATE = """ 你是企业公关与法律合规顾问。基于以下材料生成公关应对方案。 【风险等级】{risk_level} 【事件脉络】{timeline} 【已确认事实】{confirmed_facts} 【待核实信息】{unconfirmed_info} 请按以下结构输出方案: 1. 应对策略总纲:一句话说明本次应对的核心策略 2. 事实口径:分条列出可以对外说的法律事实,每一条必须能在事件脉络中找到依据 3. 待核实事项:列出不能对外确认、需要内部核查的问题 4. 行动时间表:以小时为单位,从当前时间开始排列行动项 5. 法律行动建议:基于事件链给出可执行的法律动作(如申请不公开审理、提起管辖权异议) 要求:所有事实表述必须严格来源于事件脉络,禁止推测,禁止使用情绪化词汇。 """这里有个细节值得注意:confirmed_facts不是人工整理的,而是从事件链里自动提取的。提取规则是取confidence >= 0.8的事件,把source_text字段拼起来作为事实来源。这样做的好处是模型生成声明时能引用原文,减少凭空捏造的可能。
4.3 方案生成参数与输出格式约束
方案生成阶段的模型参数和事件抽取阶段完全不同。事件抽取要低温度保证稳定,方案生成需要中等温度让语气自然。我一般用temperature=0.7,但需要加一层输出格式约束。
def generate_response_plan(risk_level, timeline, confirmed_facts, unconfirmed_info): completion = client.chat.completions.create( model="deepseek-chat", messages=[ {"role": "system", "content": "你是有十年上市公司舆情应对经验的公关法务顾问,输出风格冷静、克制、严谨。"}, {"role": "user", "content": STRATEGY_TEMPLATE.format( risk_level=risk_level, timeline=timeline, confirmed_facts=confirmed_facts, unconfirmed_info=unconfirmed_info )}, ], temperature=0.7, max_tokens=4096 ) plan_text = completion.choices[0].message.content return plan_textmax_tokens=4096是经验值,公关应对方案要覆盖五个模块,低于 2048 会明显感觉内容被截断,超过 4096 容易生成重复的排比句。生成后要进行敏感性检查,把方案文本和事件链里的事实做对照,凡是方案中出现事件链中不存在的具体日期、金额、人名,都要标记出来人工复核。这一步是法律舆情的底线,模型生成的内容只能作为草稿,不能直接发出。
自动生成的方案落库后,建议保留 prompt 版本和模型版本,方便后续出问题时回溯。方案文档还有一个“口径红线”字段,由人工在发布前填写,模型生成的内容里如果触碰红线则自动阻止导出。整个流程跑通后,从舆情告警到拿到第一版应对方案,耗时可以控制在 5 分钟以内,这是人工研判很难达到的速度。
5. 法律舆情系统落地的 5 个高频踩坑:现象、原因、解决
5.1 DeepSeek 返回非法 JSON 导致抽取链路中断
现象:事件抽取接口偶发报错,json.loads直接抛异常,日志里能看到模型返回了带解释性文字的 JSON,比如“以下是抽取结果:”这样的前缀,或者 JSON 里混入了注释。
原因:即便设置了response_format={"type": "json_object"},DeepSeek 在长文本处理时仍可能把一些调试性输出夹带进结果。另外,当原文里包含大量对话或采访引述时,模型的输出格式稳定性会明显下降。
解决:不要直接信任模型输出,在解析前先做一下“JSON 净化”。做法是找到第一个[或{字符,截取到最后一个]或}之间的内容再解析。这个土办法能解决 90% 的解析失败。剩余 10% 的重试一次,重试时把 prompt 里加上“严格输出 JSON,不要做任何解释”。如果重试仍然失败,就放弃这次抽取并记录失败原因,不要在一个文本上无限重试,会把 API 成本打高。
5.2 模型把“可能上诉”抽成了“上诉事件”
现象:事件脉络里出现一个事件类型为“上诉”的节点,但回看原文,报道只写了“该公司表示或将提起上诉”,并没有实际提交上诉状。
原因:DeepSeek 在理解时会把“计划/可能/正在准备”这类表态性文本当作已发生事实。事件抽取的触发词判断在模糊语境下过于激进,尤其是“上诉”这个词在新闻标题里经常被用来制造悬念。
解决:在 prompt 里增加事件发生状态的判定字段,每个事件额外输出event_status,枚举值为occurred(已发生)和pending(未发生/可能发生)。下游只用occurred事件构建时间线,pending事件单独存一个“待确认事件”表,人工复核后再决定是否并入。同时在后处理里加一条规则:抽取结果的source_text中如果包含“或将、可能、计划、正在考虑”这些词,自动把event_status置为pending,无视模型输出。
5.3 长文本截断导致事件丢尾
现象:一篇深度报道超过 4000 字,DeepSeek 的max_tokens=2048只返回了前半部分的事件,最后一个判决事件漏抽,导致事件链不完整。
原因:法律深度报道通常在结尾才给出审判结果,而模型生成是流式的,输出长度受max_tokens限制,截断时先丢的是末尾内容。
解决:不要用整篇长文去抽事件,改用“段落级抽取”。做法是把长文本按段落切分,每个段落单独调用一次抽取接口,段落与段落之间有重叠句子作为衔接。重叠逻辑是保留相邻段落各 2 行,防止一个事件的描述恰好被切到两个段落。这样每个请求的输入控制在 1000 字以内,输出也能在 2048 tokens 内完整落地,代价是 API 调用次数增加,但事件抽取本来就不是实时高频链路。
5.4 法律主体对齐失误造成“一案拆成两链”
现象:同一个案件因为公司在报道中时而带“股份有限公司”后缀,时而不带,事件链被拆成两条平行的线,公关团队看到的是两份不完整的脉络。
原因:字符串精确匹配在中文实体上天然不可靠。“某某控股”和“某某控股股份有限公司”在字符级别完全不一致,但指向同一个法律主体。别名表太薄,冷启动阶段大多数主体不在表里。
解决:除了维护别名表,增加一层“核心名称提取”。在事件入库前,用规则去掉公司法务主体名称的常见后缀词,包括“股份有限公司”“有限公司”“集团有限公司”“控股”等,提取核心名称作为分组键。比如“某某控股集团股份有限公司”降级为“某某控股”,与“某某控股”直接匹配。这套规则不完美,但能把 80% 的同体不同名问题消掉,剩下 20% 靠每天一次的批处理共指消解补齐。
5.5 生成方案与事件链事实脱节
现象:自动生成的公关方案里出现了一个金额数字,但回查事件链,这个金额只出现在某篇自媒体的推测里,没有进入任何已确认事件。
原因:生成阶段传入的confirmed_facts只取了confidence >= 0.8的事件,但 0.8 分无法完全排除“发泄性内容”或“传闻内容”被抽取为事件的情况。自媒体文章里“据悉赔偿金额可能高达数亿元”这种表述,会被抽成一个含金额数字的事件,且字段完整度高,拿到 0.8 分以上。
解决:在来源层过滤,只从三类可信来源抽取事件:法院公告、官方媒体、律师声明。其他来源的内容一律不进入事件链,只进入热度统计。这个限制牺牲了对自媒体信息的敏感性,但换来了策略生成材料的高可信度。落地时在事件表加一个source_category字段,来源为“官方/媒体/自媒体”,策略生成只读source_category IN ('官方', '媒体')的事件。
6. 用人工标注集验证事件抽取与策略质量:评估指标与验收技巧
6.1 事件抽取的验收:事件三元组匹配率
事件抽取做得好不好,不能靠肉眼抽查几篇报道觉得“还行”就上线。我在每个项目里都会建一个最小验收集,规模不用大,100 篇新闻足够,覆盖立案、开庭、判决、上诉、执行这五类高频事件,每篇人工标注出事件三元组(事件类型, 原告, 被告)。验收时把模型抽取结果和人工标注做匹配,匹配规则是三元组三个字段完全一致才算命中。
def evaluate_extraction(predicted_events, gold_events): predicted_set = { (e["event_type"], e["plaintiff"], e["defendant"]) for e in predicted_events } gold_set = { (e["event_type"], e["plaintiff"], e["defendant"]) for e in gold_events } correct = len(predicted_set & gold_set) precision = correct / len(predicted_set) if predicted_set else 0 recall = correct / len(gold_set) if gold_set else 0 f1 = 2 * precision * recall / (precision + recall) if (precision + recall) else 0 return {"precision": precision, "recall": recall, "f1": f1}这里有一个实战中的强烈建议:验收时只看精确率和召回率不够,还要单独统计“多余事件率”,即模型抽出了但人工标注里不存在的事件占比。法律舆情的场景里,多余事件比漏抽事件更有害,因为它会让公关团队去回应一个根本没有发生过的事情。我见过一个项目漏抽率只有 5%,但多余事件里全是“可能性表述”,上线两周差点让法务针对不存在的判决发了声明。F1 达到 0.85 以上可以试点,多余事件率高于 10% 必须先解决来源过滤问题再谈上线。
6.2 脉络完整性验证:用事件链覆盖率替代人工抽检
脉络梳理这一步,验证的目标是“关键法律程序节点是否齐全”。验证方法是:对一个已知完整流程的案件,人工列出应有的事件节点清单,然后看系统生成的事件链覆盖了多少个。比如一个典型合同纠纷案,通常包含立案 → 一审开庭 → 一审判决 → 上诉 → 二审开庭 → 二审判决,共 6 个节点。计算覆盖率只需要在测试集上做一次简单的集合比较。
覆盖率低于 70% 时,优先检查是不是分段截断导致的丢尾问题,把日志里的 API 调用记录调出来看是否有文本长度接近max_tokens限制的请求。覆盖率高于 90% 但脉络图看着别扭时,问题大概率出在事件关系判定上。关系判定质量的验证方式更简单:随机抽 50 对事件关系,人工判断overrule和follow_up的标注是否正确,准确率小于 80% 就降低temperature并重新跑批。
6.3 策略质量的评估:用“事实溯源率”卡住底线
公关应对方案的质量评估和文本生成任务不一样,不能只看流畅度,核心指标是“方案中的每条事实表述都能在事件链里找到源头”。落地做法是:把生成的方案按句号拆成句子,对每句话做一次实体匹配,检查句中包含的日期、金额、机构名是否存在于事件链中。匹配率低于 85% 的方案直接标记为“需人工重写”,不放行进审批流。
def check_fact_support(plan_text: str, event_chain: dict) -> dict: facts_in_event_chain = set() for event in event_chain: facts_in_event_chain.add(event.get("date")) facts_in_event_chain.add(event.get("court")) facts_in_event_chain.add(event.get("plaintiff")) facts_in_event_chain.add(event.get("defendant")) sentences = [s for s in plan_text.split("。") if len(s) > 5] supported, unsupported = [], [] for sentence in sentences: if any(fact in sentence for fact in facts_in_event_chain if fact): supported.append(sentence) else: unsupported.append(sentence) support_rate = len(supported) / len(sentences) return {"support_rate": support_rate, "unsupported_sentences": unsupported}这套校验脚本我每个项目都留着,并且会在迁移 DeepSeek 模型版本后重新跑一遍。大模型升级后抽取能力和生成风格都会变,这次的指标只能代表当时的效果。回归测试的间隔不用太频繁,每季度一次或者模型厂商发布重大版本更新时跑一次就够了。测试集加上回归流程,这套方案从“能跑”到“敢用”的关键就是这两件事。希望这些从实际项目里攒下来的方法和踩坑记录能帮你少走一段弯路。
本文还有配套的精品资源,点击获取