
简介一份DeepSeek法律智能助手对话系统构建方案文档共554页面向AI产品经理、法律科技研发人员及NLP算法工程师系统讲解如何基于对话管理框架实现多轮法律咨询场景下的上下文理解与精准应答生成。内容从法律行业服务痛点、技术选型论证到底层架构解析、实体识别与意图识别均有完整展开。资源包仅有1个PDF文件大小14.38MB支持目录章节跳转和阅读器书签大纲定位文字、图表、目录显示清晰完整。全文共分50个大章节前18章聚焦法律实体识别与意图识别两条主线细致拆解专业词汇库构建、数据标注规范、训练数据准备、模型训练参数设置、微调、蒸馏及轻量化部署等关键环节同时涵盖损失函数选择、优化器配置、模糊意图消歧与场景迁移等工程实践内容。已有108人学习下载适合作为法律智能对话系统设计、法律领域NLP方案研发的技术参考能够帮助读者系统梳理从语料采集到模型部署优化的全流程落地思路。1. DeepSeek法律智能助手为什么“多轮”才是法律咨询真正的门槛法律咨询场景里最难的从来不是第一次回答而是第二轮以后。用户第一轮说“房东不退押金”你给出法律意见很简单他马上补一句“但合同里写了租期内退租押金不退”系统如果还按上一轮结论来答就是在给用户帮倒忙。一个能落地的DeepSeek法律智能助手核心不是把提示词写漂亮而是先用一套对话管理框架把“谁、什么事、诉求多少、有什么证据”盯住再把每轮新增事实和法条检索结果喂给大模型生成带依据的答复。这套方案适合有基础研发能力的团队从零搭一个能在真实咨询中跑起来、能排查、能验收的最小系统。后面所有内容都围绕这条主线展开。2. 对话管理框架选型先定骨架再让DeepSeek发挥2.1 法律咨询不是聊天而是有目标的证据收集很多团队拿到法律咨询需求后习惯性地写一个大prompt把DeepSeek包起来让模型“扮演律师”。单轮聊还可以多轮一长就露馅用户在第一轮说的是“合同没到期房东让我搬走”第二、第三轮开始解释背景“房子是亲戚的”“房租是微信转的”模型可能只记得最后一句话把房屋租赁当成房屋买卖来答。原因很直接大模型本身没有跨轮状态每次调用都是一次“失忆重来”。法律咨询的对话结构其实非常稳定先采集案情事实再识别争议焦点再查法条最后给方案和风险提示。这中间用户可能跳跃式补充信息可能推翻自己上一轮的说法“其实没签合同”“不是押金是定金”也可能一口气问好几个问题。这种结构天然适合“状态机 槽位”的思路把法律咨询要收集的关键信息抽象成槽位用对话管理框架维护槽位和轮次每轮先把用户的话理解成槽位更新再决定下一步是该追问、该查法条还是该出结论。“对话管理框架”这个词听起来很重实际操作里可以轻量实现。常见做法是自建一个会话状态管理器负责槽位追踪slot tracking和对话策略决策语言理解与生成这两段交给DeepSeek。这个组合比纯规则状态机灵活又比纯粹让大模型自由发挥可控是法律咨询类项目里比较稳妥的起点。2.2 三条路线对比状态机、槽位框架与大模型编排路线核心机制优点短板规则状态机预定义节点与转移条件完全可控、可审计、延迟低流程一改就要改代码用户稍微绕就掉出节点槽位填充框架Rasa/自建DST定义槽位与填槽策略NLU每轮提取槽位DST维护状态策略决定动作每轮状态可读、可回滚与LLM结合后理解能力强需要设计槽位复杂推理要靠外部模型大模型智能体编排LangGraph等把检索、回答做成图节点模型决定流程灵活、能做多工具协同行动路径难审计会为了“完成任务”省略追问且比自建状态机难排查法律咨询不同于闲聊或写代码助手它的输出有潜在后果无法接受“模型觉得差不多就给结论”。我一般会选第二条路线自定义槽位填充框架当骨架DeepSeek当大脑。理由有两条。一是可审计。每轮更新了哪个槽位、触发什么动作代码里都有记录。用户问“押金是多少”和“合同还剩多久”系统能准确得出“诉求金额”和“剩余租期”两个槽位的变化。二是可兜底。遇到没定义好的槽位类型可以走“澄清追问”而不是让模型随口回答。这个框架下DeepSeek负责把自然语言转成槽位再把槽位和法条生成自然语言答案它不必去记“第几轮说了什么”那部分交给状态管理器。2.3 DeepSeek接入API配置与最小调用DeepSeek提供OpenAI兼容接口用openai的SDK就能直接调起来。这个兼容性帮了大忙不用给团队引入两套客户端。最小调用代码from openai import OpenAI client OpenAI( api_keysk-你的密钥, base_urlhttps://api.deepseek.com, ) def call_deepseek(messages, temperature0.3, max_tokens1024): resp client.chat.completions.create( modeldeepseek-chat, messagesmessages, temperaturetemperature, max_tokensmax_tokens, streamFalse, ) return resp.choices[0].message.content几个参数值得先说清楚base_url填API网关地址DeepSeek兼容OpenAI协议端口和路径不要自己拼model默认用deepseek-chat日常法律咨询响应速度更快需要复杂法理分析时可以切换deepseek-reasoner那个会先产出推理链回答更严谨但延迟明显增加temperature建议压在0.3到0.5之间。法律咨询要稳定输出温度太高会出现同一个问题两次回答措辞相差很大max_tokens不要给太小的值生成内容一旦包含“法律依据”和“风险提示”就很容易超过512我一般给1024以上超出窗口会自动截断。这里先不展开具体提示词模板先理解调用骨架即可。下一章会把调用代码和对话状态串起来做成真正的多轮系统。3. 多轮上下文理解落地从“记住说过什么”到“知道该问什么”3.1 法律咨询的槽位表先定义系统要盯住的信息把法律咨询中频繁出现的要素抽出来做成一张槽位表槽位含义示例备注consult_type咨询类型房屋租赁/劳动/借贷/婚姻决定后续法条范围identity用户在这个纠纷里的角色租客、房东、员工影响权利主张方向dispute_focus争议焦点押金退还、合同解除、工资拖欠可多个amount标的额/诉求金额4500元金额影响管辖与程序evidence已有证据合同照片、转账记录列表追加facts用户陆续陈述的关键事实合同未到期房东说要卖房会被改口覆盖confirmed是否已确认核心诉求true/false决定是否进入生成阶段这张表是后面所有逻辑的地基。槽位定义得越清楚多轮上下文理解就越可控。比如用户说“押金他到现在没退”系统能提取amount押金、dispute_focus押金退还用户说“我提前30天跟房东说了”系统只更新facts不改变dispute_focus。用代码定义from dataclasses import dataclass, field dataclass class LegalSessionState: session_id: str consult_type: str | None None identity: str | None None dispute_focus: list[str] field(default_factorylist) amount: str | None None evidence: list[str] field(default_factorylist) facts: list[str] field(default_factorylist) confirmed: bool False def summary(self) - str: return ( f咨询类型:{self.consult_type or 未知} 角色:{self.identity or 未知} f争议焦点:{,.join(self.dispute_focus) or 未知} f标的额:{self.amount or 未知} 证据:{,.join(self.evidence) or 暂无} f核心陈述:{.join(self.facts) or 暂无} 已确认:{self.confirmed} )这里用Python的dataclass直接落地。每个会话有一个session_id外部请求带着同一个session_id进来状态管理器从字典里取对应对象不同用户不同session_id天然隔离。summary()方法很关键后面生成回复时直接把这个字符串塞进system提示模型就能看到全局状态不用每个人反复重读所有历史。3.2 槽位更新策略让DeepSeek做抽取让规则做决策槽位填充不是简单让模型跑一趟就完事。我通常拆成两步。第一步用DeepSeek做槽位抽取只输出JSON不生成回答import json SLOT_KEYS [consult_type, identity, dispute_focus, amount, evidence, facts, confirmed] def extract_slots(client, user_text, recent_messages): messages [ {role: system, content: ( 你是会话状态抽取器。只输出JSON不要解释。 从用户本轮表述中抽取这些字段 consult_type、identity、dispute_focus、amount、evidence、facts、confirmed。 没提到的字段给null用户表示确认时confirmed给true facts存放用户新陈述的事实按原意改写为简短句子。 )}, *recent_messages[-4:], {role: user, content: user_text} ] resp client.chat.completions.create( modeldeepseek-chat, messagesmessages, response_format{type: json_object}, temperature0.1, max_tokens512, ) return json.loads(resp.choices[0].message.content)这里几个点容易踩response_format固定成JSON否则模型可能夹带解释文字json.loads直接抛异常temperature打到0.1抽取任务不允许发挥稳定优先recent_messages只带最近4条避免整段历史把抽取逻辑带偏。抽取类任务靠的是当前表述和最近几轮上下文evidence用列表结构用户每轮提到新证据就追加而不是覆盖facts则相反下面讲覆盖策略。第二步状态管理器拿到JSON做更新决策。这一步我坚持用规则不交给模型def update_state(state: LegalSessionState, slots: dict) - LegalSessionState: for k, v in slots.items(): if v is None or v null: continue if k dispute_focus: if isinstance(v, list): state.dispute_focus list(set(state.dispute_focus v)) elif v not in state.dispute_focus: state.dispute_focus.append(v) elif k evidence: if v not in state.evidence: state.evidence.append(v) elif k facts: state.facts.append(v) elif k confirmed and v: state.confirmed True else: setattr(state, k, v) return state多轮对话里有一个真实高频问题用户推翻上一轮的说法。比如第一轮说“房东不退押金”第二轮说“其实合同没签押金是现金给的”。如果facts是全量追加生成模型会同时看到“签了合同”和“没签合同”两种矛盾描述答案没法看。我的做法是facts也采用“改口覆盖”策略——每条事实保留来源轮次每当新陈述与旧事实在语义上冲突时用新值替换旧值。实际实现时最简单的方式是给每轮facts打一个轮次标签更新规则命中同一个dispute_focus时覆盖旧的。这里的代码是简版先按追加处理更重的业务系统可以引入“事实版本号”。3.3 每轮生成状态摘要、法条检索与消息序列拼装槽位更新完把状态摘要、法条检索结果和用户最新问题拼成一个独立完整的生成请求这一步是“多轮上下文理解”真正落地的位置。def build_generation_messages(state_summary, user_text, law_hits): law_block \n.join( f《{h[law_name]}》第{h[article_no]}条{h[content]} for h in law_hits ) system_prompt ( 你是法律咨询助手的应答生成器。先阅读用户画像与案情摘要 再基于给出的法条回答。\n f案情摘要{state_summary}\n\n f可引用法条\n{law_block}\n\n 回答要求\n 1. 只引用上面列出的法条不要凭记忆补充条文。\n 2. 如果案情摘要缺少关键信息先向用户追问不要急着下结论。\n 3. 输出分四段事实梳理/法律依据/结论与建议/风险提示。 ) return [ {role: system, content: system_prompt}, {role: user, content: user_text} ]注意这里没有把全部对话历史塞进去只放了两样东西状态摘要和最新一条用户消息。这样的设计在工程上叫“带外状态”让模型每一轮都看到完整状态而不是靠历史去复盘。经验是当历史超过6轮后摘要比raw历史可靠而且省token。长对话时甚至可以额外加一条由上一轮生成的“会话小结”把更老的内容压缩成两三句话避免上下文窗口直接被撑满。4. 精准应答生成引用法条、抑制幻觉、输出可操作的结论4.1 “精准”在法律咨询里的三层含义第一层是“不知道就说不可以”。用户问“我这种情况能拿多少赔偿”如果案情缺关键数据系统应该追问而不是编一个数。第二层是“有依据才能下结论”。法条是唯一的信源模型不该凭训练记忆背法条背错一条后果比不答更恶劣。第三层是“结论要可执行”——回答以“你可以向法院起诉需要准备以下材料……”结束而不是“建议进一步咨询律师”。这层要落地RAG几乎绕不过去把法条库做好索引与检索每次生成前先取回相关条文让DeepSeek只在限定的法条范围内作答。DeepSeek本身的文本生成能力再强也扛不住把整部民法典塞进上下文去记忆所以思路不是让模型更强而是把法条检索从生成环节里独立出来做成可控的证据来源。4.2 法条切分按“条”切不按字符切RAG领域最经典的坑就是“按固定长度切分”放在法条上这是致命的。一条法律条文通常包含多款多项按512个字符切分很容易把“第X条”后半段的某个款项切断导致检索只召回半条。正确的做法是按条边界切分import re def split_into_articles(law_text: str) - list[str]: pattern r(?第[一二三四五六七八九十百零〇0-9]条) articles re.split(pattern, law_text) return [ a.strip() for a in articles if re.match(r^第[一二三四五六七八九十百零〇0-9]条, a.strip()) ]逻辑说明用零宽断言(?...)匹配“第X条”的位置在不消费字符的前提下切分保留“第X条”字头切出来的每一块就是一条相对完整的法条文本。这里有个关键点法条里还会嵌套“第X款”“第X项”这些不该作为顶层切分点所以模式里只匹配“条”不匹配“款”“项”。这是法律域RAG和通用文本RAG在切分策略上的重要区别。切完后建立索引。中文向量模型可以选m3e-base或顺手的中文embedding模型向量维度、精度差异不大时优先选响应快的from sentence_transformers import SentenceTransformer import faiss import numpy as np encoder SentenceTransformer(moka-ai/m3e-base) def build_faiss_index(articles: list[dict]): texts [a[article_text] for a in articles] vecs encoder.encode(texts, normalize_embeddingsTrue) index faiss.IndexFlatIP(vecs.shape[1]) index.add(np.array(vecs)) return index def retrieve_laws(query, index, articles, top_k3): qv encoder.encode([query], normalize_embeddingsTrue) scores, ids index.search(np.array(qv), top_k) return [articles[i] for i in ids[0]]逻辑说明IndexFlatIP与normalize_embeddingsTrue配合做的是余弦相似度检索。top_k3是法条场景里比较稳的选择——太少不够覆盖争议焦点太多会把不相关内容塞进上下文干扰生成。检索时query不要直接用用户原文我建议用state.summary()拼上当前用户消息作为查询词让槽位信息参与召回。一个更稳的增强先用BM25或Lucene做一遍关键词召回再对召回结果做向量重排。法律条文里的“定金”“违约金”是强信号词向量检索不一定把它们排到最前关键词召回能兜底。如果法条库规模不大几千条直接用内存中的RankBM25即可不用上ES。4.3 生成端约束让模型只在“可见法条”里作答检索完的下一步是把法条拼进system提示并在提示里写明“不得引用可见法条之外的条文”。这是我做过之后最想强调的一个习惯。def build_structured_answer_prompt(state_summary, user_text, law_hits): law_block \n.join( f《{h[law_name]}》第{h[article_no]}条{h[content]} for h in law_hits ) system ( 你是法律咨询助手的结构化回复生成器。 输出JSON不要包含JSON以外的文字。字段 opinion(string, 对用户问题的直接结论)、reasoning(string, 事实梳理与法律推理)、 legal_basis(array of string, 引用法条的具体条文号)、 action_advice(string, 可执行的下一步建议)、 risk_tips(array of string, 风险提示)。 f\n案情摘要{state_summary}\n f可引用法条\n{law_block}\n 约束只能引用可引用法条中的条文信息不足时opinion明确写‘需要补充以下材料’。 ) return [ {role: system, content: system}, {role: user, content: user_text} ]调用时temperature继续压在0.2左右配上response_format{type: json_object}锁定JSON输出。后端把JSON字段拆出来渲染成Web页面或企业微信的文本卡片比让用户读一大段对话体体验好很多。把完整链路组装起来def handle_turn(client, session_id, user_text, history, state_store): state state_store.get(session_id) slots extract_slots(client, user_text, history) state update_state(state, slots) query state.summary() user_text law_hits retrieve_laws(query, index, law_articles, top_k3) messages build_structured_answer_prompt(state.summary(), user_text, law_hits) answer call_deepseek(messages, temperature0.2, max_tokens1024) return answer, state这一版已经能跑通一个最小的多轮法律咨询系统。下一节把最常把人绊倒的几个问题列出来。5. 避坑与排查多轮法律对话系统的高频事故现场5.1 用户第二轮改口系统却还在用旧结论现象用户先问“房东不退押金怎么办”系统回答“可以要求返还”下一轮用户补充“其实没有签合同押金是直接打微信的”系统仍坚持上一轮依据“合同约定”的推理。原因状态管理器没有对facts做“最新覆盖”旧事实“签了合同”和新事实“没签合同”同时存在于状态里模型看到互相矛盾的输入无法判断该信哪个。解决在update_state中加入冲突检测。简单做法是facts带轮次标签当新fact与同一dispute_focus下的旧fact语义相似时用新值顶掉旧值高级做法是每轮让DeepSeek先做一次“用户是否推翻上一轮陈述”的二分类。我早期在这一步图省事结果翻车翻得最难看。5.2 回复“很有法律感”但没有任何一条具体法条现象模型输出大段“根据相关法律规定您有权主张……”但全文找不到一条真实法律条文就是听起来专业的废话。原因system prompt没做硬约束模型觉得“泛泛而谈”够安全也有一部分原因是RAG没有命中但没有触发“未命中”分支模型只能用训练记忆里的法律知识硬编。解决一是生成提示里明确写“只允许引用已提供的法条未命中时输出固定话术”二是实现零命中分支检索结果为空时不让大模型生成而是回复“请补充合同签订日期、双方身份等材料我才能匹配具体条款”再引导用户补充信息。5.3 对话超过10轮后调用直接报context length exceeded现象用户聊了十几轮每轮都拼全量历史最终请求体超过模型的上下文窗口API直接报错。原因消息序列把历史全都带上了忽略了摘要压缩。法律咨询本来就是要聊很久才能把案情讲完整越聊越长的场景天然容易踩中。解决限制历史窗口为最近4到6轮更早的内容由上一轮的对话总结成一段“会话摘要”放进状态。状态摘要本身已经压缩了大量信息这比盲目塞原文更有效。另外给输入设置token预算超了就先把最老的轮次折叠进摘要。5.4 检索命中了法条但引用错位、答非所问现象问“押金不退”检索结果给了租赁合同解除的条文但生成的回答却引用了关于违约金的部分。原因第一法条切分没有按“条”边界做导致引用了残缺条文第二top_k里混入了与当前争议焦点无关的条目第三检索query用的是用户原文没把槽位里的“纠纷焦点”参与进来。解决切分务必按条边界检索query换成“案情摘要 最新问题”top_k降到3提示里写明某条法条与案情无关时标记为不引用。这样即使某次命中噪声模型也更容易舍弃它。5.5 本地一切正常部署后多用户会话相互串场现象A用户的案情出现在B用户的回复里而且开发环境只有一个人测试时基本复现不出来。原因用字典存状态时键用了全局变量而不是session_id或session_id没从请求头传进来导致两个请求共享同一个“最近状态”。解决session_id必须由外部渠道显式传入比如企业微信的userId、网页的会话token状态库不要用进程内全局字典建议放Rediskey设计为legal:session:{userId}value就是LegalSessionState的JSON序列化。上线前用两个模拟用户并发跑一轮专门检查串场。5.6 DeepSeek tool calls 报错答到一半就断现象在引入法条检索工具后API返回类似tool calls need immediate results的错误整个对话流程中断。原因请求中声明了tools模型返回的补全结果里带着tool_callsOpenAI兼容协议要求调用方必须把工具执行结果作为roletool的消息追加回去再发起一次补全请求。很多人把tools声明当成给模型“参考”没做后续闭环协议就报错。解决收到tool_calls后立即执行函数再把结果回填到消息序列里发起第二轮请求。def chat_with_law_search(client, messages): resp client.chat.completions.create( modeldeepseek-chat, messagesmessages, tools[law_search_tool_schema], temperature0.2, ) msg resp.choices[0].message if not msg.tool_calls: return msg.content # 把带tool_calls的assistant消息追加回序列这是协议要求的 messages.append({ role: msg.role, content: msg.content, tool_calls: [ { id: tc.id, type: function, function: { name: tc.function.name, arguments: tc.function.arguments, }, } for tc in msg.tool_calls ], }) # 立即执行工具调用并把结果挂回对应的tool_call_id for call in msg.tool_calls: if call.function.name search_law: args json.loads(call.function.arguments) result retrieve_laws(args[query], index, law_articles) messages.append({ role: tool, tool_call_id: call.id, content: json.dumps(result, ensure_asciiFalse), }) final client.chat.completions.create(modeldeepseek-chat, messagesmessages) return final.choices[0].message.content逻辑说明messages.append把带tool_calls的assistant消息加回序列然后用tool_call_id把检索结果挂到对应调用下这是兼容API的强制要求第二轮请求通过后才给出最终文本。配套还有两点tools的description要写清楚“根据案情查法条”避免模型把“查法条”理解成“帮我写法条”tool执行时间不能太长必要时加超时检索要做到单次200ms内返回。6. 上线的最后一公里评测集、回归测试与成本控制6.1 二十条评测集把系统钉死在及格线上我会在项目里长期维护一个小型评测集二十条左右覆盖房屋租赁、劳动、借贷、婚姻、侵权五类高频咨询。每条包含一段多轮对话、期望的槽位结果和期望的回复动作。例子EVAL_CASES [ { id: case_01, dialog: [ (u, 我租的房子还没到期房东让我搬走), (a, 请问合同剩余租期还有多久), (u, 还有三个月房东说要把房子卖掉), ], expected_slots: { consult_type: 房屋租赁, dispute_focus: [合同未到期解除], }, expected_action: query_remaining_term_and_reason, }, ]跑测试时只看三个硬指标槽位准确率抽取的槽位与expected_slots一致、法条引用合法率引用是否来自RAG返回集、拒答率信息不足时应追问而不是硬给结论。这三个指标能拦住绝大多数回归事故。6.2 成本控制与最小调用DeepSeek本身的定价已经很低但法律咨询一次长对话会触发多次调用抽取一次、生成一次、可能还有工具调用一次。我最后的优化是抽取和生成合并成一次调用用JSON结构把槽位和回复一起返回只有信息确实不足时才走追问分支。这样成本再降一块延迟也能压到体验可接受的范围内。我自己第一次上线这个系统时就吃了没做回归集的亏改了一句system prompt以为只是调整措辞结果第二天用户问“押金是退多少”系统答“根据民法典第七百条”而那条法条根本没在RAG结果里。从那以后任何提示词改动都先跑一遍评测集再发版。现在这个习惯帮我拦下了不少蠢错误。评测集本身也会持续生长每一条线上翻车的会话修好后都会被补成一条新的用例。希望帮到你。本文还有配套的精品资源点击获取