news 2026/8/31 23:51:24

法律知识图谱问答系统实战:RAG与向量检索融合方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
法律知识图谱问答系统实战:RAG与向量检索融合方案

简介:本资源是一套面向法律科技领域开发者与NLP研究者的法务智能知识图谱实践项目,聚焦法律问答与资讯检索场景,融合知识图谱构建、案由预测、问题分类及自动问答四大核心能力。项目基于20万条真实法务问答与法律资讯数据,完成案由知识库、法务咨询对话知识库及法律资讯知识图谱的构建,并提供对应模型训练与服务接口代码,适用于法律AI产品原型开发、司法辅助系统验证及高校法律信息学课程实践。压缩包为33.88MB的ZIP文件,含Python源码(含图谱构建、BERT微调、Neo4j交互模块)、结构化数据集(JSON/CSV格式)及配置说明文档,目录层次清晰,便于快速定位知识抽取、图谱存储与问答服务模块。目前已有674人学习下载,读者可直接复用完整流程代码、调试预置模型、拓展知识节点或对接自有法律语料,显著降低法律垂直领域知识图谱落地门槛。 去年年底我接手了一个挺有意思的法务问答项目:要做一个基于法务智能知识图谱的问答系统,手里有20万条法务问答数据,还要带上法律资讯问答功能。客户明确要求"含码源",也就是整个系统要能交付可跑的代码,不能只给个demo。当时团队内部讨论了好几次,核心纠结点就是:到底用纯向量检索做RAG,还是用知识图谱?最终我们定了知识图谱为主、向量库为辅的混合方案,跑完以后效果确实比纯向量检索稳不少。这篇就把从数据清洗、图谱构建、问答链路到服务化部署的完整过程捋一遍,代码结构和核心片段也都会拆开讲,给同样在做法律领域知识问答的朋友一个可参考的落地样板。

先交代一下背景。20W法务问答数据,来源主要是公开的裁判文书摘要、法律咨询平台的匿名问答、法条和司法解释的结构化文本。这里面有大量的人名、机构名、案由、法条引用,而且很多问题不是简单的"什么是"型,而是"劳动者被辞退后,经济补偿金怎么算"这种多实体、多条件约束的复合查询。如果只用向量库把文档切成块存起来,检索到的可能是包含相关关键词的段落,但很难准确回答"根据《劳动合同法》第四十七条,工作年限不满六个月支付一个月工资"这种需要精确引用条款的问题。知识图谱的优势在于它能把法条、案件事实、裁判规则之间的复杂关系显式建模,查询时可以沿着关系精准命中答案。

整个项目我们落地成了几个大块:图谱构建、问答引擎、资讯问答服务、FastAPI接口层。下面我会按这个顺序展开,每个环节都带上踩坑记录。

1. 项目缘起与整体思路

1.1 为什么选知识图谱而不是纯向量检索

纯向量检索在通用领域问答中表现很好,但在法律这种高精度领域有个致命短板:回答必须"有法可依"、有据可查,而向量相似度检索返回的是"语义相近"的文本片段,它不理解逻辑关系。举个例子,用户问"试用期被辞退有赔偿吗",一段纯文本可能反复提到"试用期""辞退",但并没有真正回答赔偿标准。而知识图谱可以设计成"劳动关系-辞退行为-经济补偿金"这样的路径,每个节点指向具体法条,检索时直接沿路径取答案。

当然,知识图谱也有构建成本高、覆盖不全的问题,所以我们后来引入向量检索作为兜底。简单说:能走图查询的就走图,图查不到再走向量召回,最后都交给LLM组织答案。这个"图谱优先、向量兜底"的策略,是整套系统的灵魂。

1.2 项目要解决的真实痛点

客户手上的数据虽然量大,但并没形成业务价值。原始的问答记录是半结构化的,有的问题多轮对话混在一起,有的答案引用法条但没写条文内容。传统的全文检索只能做关键词匹配,查"经济补偿金"就漏掉"N+1""赔偿金"这类同义表达。而要做大模型微调,20万条数据对7B模型来说又不够充分,而且法律文本的更新会造知识过时,微调一次成本太高。

所以真正的解决方案不是堆模型,而是把知识结构化。我们先把20W问答里包含的实体和关系抽出来,构建成图谱,再在这个图谱上做问答。这样不仅现有问题都能答,还能推理出新的组合问题。比如数据里没有"孕期被辞退怎么赔偿"这种具体问法,但图谱里有"孕期"保护条款、"辞退"行为、"赔偿"计算方式,系统就能组合出来。

1.3 技术选型全景

技术栈如下:

模块技术方案选型理由
图谱存储Neo4j Community Edition 5.x成熟稳定,Cypher查询方便,支持APOC插件做实体解析
向量召回ChromaDB / Milvus(二选一)数据量不大时用ChromaDB足够,20W文档切块后约50W向量,ChromaDB完全扛得住
LLM推理llama.cpp + Qwen2-7B-Instruct本地化部署,避免API调用费用和数据合规问题
服务框架FastAPI异步支持好,配合uvicorn+httpx并发表现不错
关系抽取规则 + DeepKE(轻量序列标注模型)法律实体有强模式,规则能解决80%,模型兜底
部署Docker Compose一键拉起Neo4j、向量库、推理服务

这里重点说一下为什么用llama.cpp跑Qwen2-7B。法律数据敏感,客户要求所有数据不出内网。用llama.cpp量化Qwen2-7B到Q4_K_M,在单张消费级显卡上(比如RTX 3090)推理速度能到每秒20~30 token,虽然不如API快,但对问答场景够用了。而且llama.cpp的gguf格式对CPU也有不错的支持,没有GPU也能跑,只是慢一点。后面我会专门说怎么部署。

另外,FastAPI几乎没什么争议。异步接口、OpenAPI自动文档、参数校验,这些对快速交付太重要了。后续所有接口都用Pydantic模型做请求校验,前端传错参数直接返回400,省了很多联调时间。

2. 知识图谱构建:从法条到实体的结构化之路

2.1 数据来源与清洗

20W问答数据的来源比较杂,有文本抽取的,有JSON导出的,还有一部分是PDF转的。第一步不是急着抽取,而是统一格式。我们定义了一个中间结构:

{ "id": "QA_000001", "question": "公司被收购后,员工拒绝转签新公司,是否有经济补偿?", "answer": "根据《劳动合同法》第四十六条,用人单位被合并的,原劳动合同继续有效……", "tags": ["劳动纠纷", "经济补偿"], "law_citations": ["劳动合同法-第四十六条"], "facts": ["公司被收购", "员工拒绝转签"] }

清洗规则有几条比较关键:

  • 去重:用MinHash计算文本相似度,相似度高于0.85的只保留一条。20W去重后剩17W左右,去掉了大量咨询平台互相转载的内容。
  • 空值和噪声处理:答案长度少于30字的直接丢弃,因为这种回答没有实际参考价值。
  • 法条引用归一化:纯文本里的"《劳动合同法》第46条"、"劳动合同法第四十六条"、"第四十六条规定"全部统一成"劳动合同法-第四十六条"这种标准ID格式。这里我们写了50多条正则,法律文本的表述相对固定,正则可以做得比较干净。

2.2 实体关系抽取:规则+模型的混合策略

实体和关系抽取决定了图谱的上限。如果只靠通用NLP模型,法律文书的"当事人""诉讼请求""法院认为"这些结构很难识别出来。我们最终用的是三层抽取管线:

  1. 规则层:基于关键词和句法模式抽取。
    • 法条实体:正则匹配书名号内条文,比如《劳动法》第[一二三四五六七八九十百千0-9]+条
    • 时间实体:\d{4}年\d{1,2}月\d{1,2}日
    • 金额实体:\d+(\.\d+)?万元
    • 行为实体:维护一个行为词表,如"辞退、裁员、未签合同、未缴社保、拖欠工资……"
  2. 模型层:用DeepKE训练了一个序列标注模型,识别"原告、被告、法院、案由、赔偿金额"等有监督实体。训练数据自己标注了3000条,效果基本够用。
  3. 关系规则:实体有了之后,根据共现关系和位置模式抽关系。比如,如果一句话里同时出现"辞退"和"经济补偿金",且"辞退"在主语位置,就会建立"辞退->触发->经济补偿"的关系。

最终我们抽取出来20种实体类型和32种关系类型。这是图谱构建的核心资产,比模型本身值钱。

2.3 Neo4j图模型设计

图谱的节点和关系设计直接影响查询效率。我们设计的是这样一个核心模式:

(:Law {id, name, chapter, content}) // 法条节点 (:LegalDocument {id, title, content}) // 法律文书节点 (:Question {id, text, user_type}) // 用户提问节点 (:Answer {id, text, source}) // 答案节点 (:Entity {id, name, type}) // 通用实体:行为、期限、金额等 (Question)-[:HAS_ANSWER]->(Answer) (Question)-[:INVOLVES_ENTITY]->(Entity) (Question)-[:CITES_LAW]->(Law) (Answer)-[:BASED_ON]->(Law) (Answer)-[:REFERENCES_DOC]->(LegalDocument) (Law)-[:RELATED_TO]->(Law)

这里有几个设计细节值得一说:

  • Question节点和Answer节点是分离的,因为一个问可能对应多答,一个答也可能覆盖多问,分离开查询更灵活。
  • Law节点存条文全文,而不是只存"第X条"这个编号。LLM在生成回答时需要引用内容,图里直接取全文就不用再回查文档。
  • RELATED_TO关系用于法条之间的关联。比如"劳动合同法-第四十六条"和"劳动合同法-第四十七条"存在引用关系,这个是从法律知识整理中提前配好的。

Neo4j的索引也用了不少。节点至少500万量级,没有索引的话一个动作查询轻松卡死。我们给Entity.name建有唯一约束索引,给Law.id建唯一约束,给Question.id建普通索引。

代码示例,建索引和约束:

CREATE CONSTRAINT law_id IF NOT EXISTS FOR (l:Law) REQUIRE l.id IS UNIQUE; CREATE CONSTRAINT entity_name IF NOT EXISTS FOR (e:Entity) REQUIRE e.name IS UNIQUE; CREATE INDEX question_id_idx IF NOT EXISTS FOR (q:Question) ON (q.id); CREATE INDEX law_content_idx IF NOT EXISTS FOR (l:Law) ON (l.content);

注意:唯一约束会自动创建索引,但普通索引一定要手动加,否则大批量写入时查询会慢到无法接受。

2.4 码源结构说明

项目代码结构是标准的模块化:

legal_kg_qa/ ├── app/ │ ├── api/ # FastAPI路由 │ │ ├── answer.py # 法务问答接口 │ │ ├── news.py # 法律资讯问答接口 │ │ └── health.py # 健康检查 │ ├── core/ │ │ ├── config.py # 配置 │ │ ├── neo4j_client.py # Neo4j驱动封装 │ │ └── llm_client.py # llama.cpp推理客户端 │ ├── graph/ │ │ ├── builder.py # 图谱构建主流程 │ │ ├── extractor.py # 实体关系抽取 │ │ └── loader.py # 批量写入Neo4j │ ├── retrieval/ │ │ ├── graph_retriever.py # 图谱查询器 │ │ ├── vector_retriever.py # 向量检索器 │ │ └── fusion.py # 融合排序 │ ├── llm/ │ │ └── generator.py # 答案生成 │ └── schemas/ │ └── request.py # Pydantic模型 ├── data/ # 原始数据与导出数据 ├── models/ # gguf模型文件 ├── scripts/ │ ├── build_graph.py │ ├── load_vectors.py │ └── test_api.py └── requirements.txt

码源能跑的关键在于配置文件和客户端封装都做得很薄,适配不同环境只需要改core/config.py里的连接参数。

3. 问答系统实现:RAG与图谱问答的融合

3.1 基础问答链路

整个问答链路跑起来是这样:

  1. 接收用户问题。
  2. 先用NER模型抽取问题里的实体和意图。
  3. 根据实体生成Cypher查询,优先走图谱检索。
  4. 如果图谱检索结果不足N条(比如少于3条),则用向量库做补充召回。
  5. 将图谱结果和向量结果合并,按得分排序。
  6. 拼接成Prompt,交给Qwen2-7B生成最终答案,要求LLM必须基于检索到的内容回答,做不到就回答"根据现有资料无法确认"。

这套链路的两个关键点:

  • 实体抽取的准确率直接决定图谱查询的准确率。我们使用了一个轻量级规则+模型的混合NER,准确率大概在90%左右,对常见法务问题够用。法务问题的实体往往是"劳动纠纷""经济补偿""工伤认定"等长尾词,我们把这些词维护成一个词表,再对每个词关联到图谱节点ID,这样只要实体识别命中词表,就能直接跳到节点。
  • 图谱和向量检索的融合排序并不是简单拼接。我们设计了一个简单的打分函数:final_score = 0.7 * graph_score + 0.3 * vector_scoregraph_score根据路径长度和实体匹配数计算,路径越短、实体匹配越多,分越高。vector_score用向量相似度。实验证明这个加权方式比纯取max效果好,因为图谱命中的答案更精准,给它更高权重是合理的。

3.2 图查询到LLM:Cypher生成与约束

这里有个容易踩坑的地方:用LLM直接生成Cypher非常不可控。我们一开始尝试让Qwen2-7B直接根据问题写Cypher,结果在测试集上语法正确率只有70%左右,一旦生成错误语句,整个查询就崩了。最终我们放弃"让LLM写Cypher",改成模板化图查询 + LLM结合约束生成

具体做法是:根据实体类型预定义若干查询模板。比如,识别出行为实体后,使用模板:

MATCH (e:Entity {name: $behavior})<-[:INVOLVES_ENTITY]-(q:Question) MATCH (q)-[:HAS_ANSWER]->(a:Answer) RETURN q.text, a.text LIMIT 5

再比如,识别出法条实体后,使用模板:

MATCH (l:Law {id: $law_id})<-[:CITES_LAW]-(q:Question) MATCH (q)-[:HAS_ANSWER]->(a:Answer) RETURN q.text, a.text LIMIT 5

LLM在这里只做一个工作:从用户问题中抽取实体并映射到模板参数。这比直接写Cypher简单可靠得多。我们把合法的模板参数做成白名单,每个参数在查询前校验,只有匹配^[\u4e00-\u9fa5a-zA-Z0-9\-()()]{1,50}$才允许执行,防止注入。

当然,模板化也限制了灵活性,无法处理特别复杂的多条件问题。比如"怀孕女职工在试用期被辞退,可以要双倍赔偿吗"这种同时涉及"孕期保护""试用期""辞退""赔偿"四个实体的问题,单个模板装不下。我们的方案是多模板并行查询,每种实体组合生成一个子查询,最后取所有子查询结果的并集。实测下来,多模板并行的召回率远高于单个模板。

3.3 向量数据库引入:弥补图谱覆盖不足

图谱再大也覆盖不完所有知识,特别是资讯类内容。20W问答里有些问题比较冷门,在图谱中匹配不到足够多的邻居节点。这时就需要向量检索兜底。

向量检索的流程不复杂,但有几个细节要注意。我们用中文法律文本训练了一个BGE-M3模型做Embedding(后来替换成bge-large-zh-v1.5),切块策略是按法律文件结构切,一个法条一个块,一个问答对一块。这样切块的好处是每块语义完整,不会出现前一句话在A块、后一句话在B块的情况。向量库选择ChromaDB,因为20W问答对切块后约50W向量,单机部署很轻松。存储时带上元数据:来源文档ID、法条ID、标题、时间戳。

查询时先向量召回TopK(K=20),然后用一个裁判模型(用一个轻量分类器判断相关性)重排,取Top5。这个重排模型我们用的是BGE-reranker-base,法律领域虽然没微调,但效果比纯相似度好很多。

3.4 基于llama.cpp+Qwen2-7B的本地推理部署

LLM推理这块是交付过程中客户最关心的,毕竟涉及隐私数据。llama.cpp部署Qwen2-7B-Instruct的步骤我直接贴出来:

git clone https://github.com/ggerganov/llama.cpp cd llama.cpp && make -j4 LLAMA_CUBLAS=1 # 下载Qwen2-7B-Instruct的gguf格式文件 # 建议使用Q4_K_M量化,显存占用约6.5GB ./build/bin/llama-server \ -m /models/qwen2-7b-instruct-q4_k_m.gguf \ --host 0.0.0.0 \ --port 8080 \ -c 8192 \ -ngl 35 \ --api

启动了llama-server后,FastAPI通过HTTP请求调用它。这里有一个很重要的参数:-c 8192,上下文长度。如果设为默认的2048,Prompt长一点就会截断,答案生成质量明显下降。我们实测Qwen2-7B在8192上下文下能处理包含3~5个检索片段的问题,生成答案较完整。

-ngl 35是把35层模型层放到GPU,需要根据显存调整。如果显存只有8GB,建议层数设少一点,或者改用Q3_K_S量化。还有一些参数需要在FastAPI调用时设置:

payload = { "prompt": final_prompt, "temperature": 0.1, "top_p": 0.9, "max_tokens": 512, "stop": ["<|im_end|>"] }

temperature设0.1而不是0,是为了让答案在稳定基础上保留一点点多样性。如果完全设为0,虽然稳定性好,但多个相似问题可能输出一字不差的回答,维权类场景会显得奇怪。stop参数必须设置,否则模型可能一直续写到max_tokens上限。

下面简单贴一个FastAPI调用本地推理服务的核心代码:

import httpx class LLMClient: def __init__(self, base_url: str = "http://127.0.0.1:8080"): self.base_url = base_url async def generate(self, prompt: str, max_tokens: int = 512) -> str: payload = { "prompt": prompt, "temperature": 0.1, "top_p": 0.9, "max_tokens": max_tokens, "stop": ["<|im_end|>"] } async with httpx.AsyncClient(timeout=60) as client: resp = await client.post(f"{self.base_url}/completion", json=payload) data = resp.json() return data["content"]

调用llama-server的/completion接口,直接拿到生成文本。注意这里的timeout要设置长一些,因为7B模型在较长的Prompt下生成速度并不快,我遇到过需要45秒的场景。

4. 法律资讯问答功能的独立设计

4.1 资讯数据的采集与结构化

除了20W问答,系统还需要具备法律资讯问答能力。所谓资讯问答,不是回答"什么是正当防卫"这种百科型问题,而是回答"2024年最高人民法院发布了哪些劳动争议典型案例"或者"最新的个税专项附加扣除政策是什么"。这类问题时效性强,且答案可能随时间变化。

资讯数据我们通过合规渠道采集了公开的法律新闻、政策解读、典型案例发布稿,去重后差不多5W篇。存入系统前,每篇资讯要做结构化:

  • 抽取标题、发布时间、来源、正文。
  • 用正文的TF-IDF关键词生成标签。
  • 提取正文中引用的法条和案例名称。

每篇资讯存成两个节点:一个是News节点,保存原始正文;另一个是NewsSummary节点,保存摘要和关键信息点。为什么分开?因为正文一般很长,如果都作为属性存在图里,写入慢,查询也慢。摘要节点方便快速匹配,正文则存到文档数据库或干脆以文件形式保存,图里只存引用ID。

4.2 时效性处理与答案引用

资讯问答的难点在于时效性。法律政策一变,如果系统回答的还是旧内容,会误导用户。我们给资讯节点加了一个effective_dateexpired_date属性。查询时加条件:

MATCH (n:News) WHERE n.effective_date <= date() AND (n.expired_date IS NULL OR n.expired_date > date()) RETURN n.title, n.summary LIMIT 10

这样过期资讯就不会被检索到。此外,在Prompt中明确告诉LLM"你只能使用当前有效的信息回答,如果检索到的信息包含过期内容,请忽略"。

对于"最新"类问题,比如"最新的劳动法解释",我们还需要一个时间排序逻辑。在向量检索和关键词检索之后,如果问题中包含"最新""近期""2024"等时间词,就对结果按发布时间倒序排序,再取TopK。这里有一个小技巧:如果用户没有指定时间,但答案库中同一问题的多个答案发布时间不同,应该优先采用发布时间较新的答案。我们在答案生成Prompt中把时间信息带进去:

以下是检索到的相关资讯,请根据这些内容回答问题: 1. 标题:xxx,发布时间:2024-05-01,内容:xxx 2. 标题:xxx,发布时间:2022-01-01,内容:xxx 用户问题:xxx 请优先采用发布时间较新的信息,并在回答末尾标注信息来源及发布日期。

这样能有效降低"拿旧法条回答新问题"的风险。

5. FastAPI服务封装与工程化落地

5.1 API设计与并发模型

全部功能最终通过FastAPI对外提供,接口只有三个:

方法路径功能
POST/api/v1/legal/ask法务知识图谱问答
POST/api/v1/news/ask法律资讯问答
GET/api/v1/health健康检查

legal/ask的请求体:

{ "question": "公司拖欠工资三个月,我主动离职有经济补偿吗?", "user_id": "u_12345", "from_source": "app" }

响应体:

{ "code": 0, "answer": "根据《劳动合同法》第三十八条和第四十六条,用人单位未及时足额支付劳动报酬的,劳动者可以解除劳动合同,并有权要求用人单位支付经济补偿金。", "sources": [ { "type": "law", "id": "劳动合同法-第三十八条", "content": "用人单位有下列情形之一的,劳动者可以解除劳动合同:……" } ], "confidence": 0.92 }

FastAPI最大的优势是异步支持。我们在调用LLM时用httpx.AsyncClient,这样多个用户同时提问时,一个请求在等待LLM时不会阻塞其他请求。配合uvicorn的多worker,实测单机QPS能达到20左右,对内部系统够用了。

5.2 缓存与批量更新策略

20W问答的查询结果有很强的重复性。比如"试用期工资怎么算"这个问题,100个用户问的可能是差不多的。我们在Redis里做了三层缓存:

  1. 完全文本匹配缓存:完全相同的问题直接返回。
  2. 实体组合缓存:如果问题解析出的实体组合与之前某个问题一致,直接复用答案。
  3. 路径缓存:图谱查询结果缓存对应Cypher语句的hash,相同查询不用再跑图。

缓存时间设置为24小时。法律答案变化慢,不需要实时更新,但资讯类答案缓存时间要短,我们设了30分钟,保证时效性。

批量更新这块也很重要。图谱不是一次性建完就不管了,法律数据会更新。我们做了每日增量任务:新到的问答对先做实体抽取,然后批量写入Neo4j。写入要用UNWIND批量提交,一次1000条,比一条条CREATE快10倍以上。另外,Neo4j写库时要注意事务大小,事务太大会内存溢出,太小又太慢,1000条是平衡点。

UNWIND $batch AS row MERGE (q:Question {id: row.q_id}) SET q.text = row.q_text MERGE (a:Answer {id: row.a_id}) SET a.text = row.a_text MERGE (q)-[:HAS_ANSWER]->(a)

注意这里的MERGE不是CREATE,防止重复写入。如果要更新现有节点属性,用SET即可,但关系只能用MERGE保证唯一性。

6. 常见问题与排查实录

6.1 实体抽取过粗导致图查询失败

我们一开始的NER训练集里只有"赔偿金""补偿金"这种粗粒度实体,结果用户问"经济补偿金"时,实体识别成了"补偿金",图谱里匹配不到节点。后来把训练标签细化,区分"经济补偿金""赔偿金""违约金"等,同时维护了一个同义词表:经济补偿金->经济补偿金|补偿金|N+1|赔偿经济补偿,查询前先做一次输入归一化。这个方法见效很快,图谱查询命中率从68%提升到82%。

6.2 LLM回答不稳定如何约束

LLM在生成答案时偶尔会"自由发挥",尤其是法律场景,这是大忌。我们加了两个约束:

  • 在Prompt中明确写:"如果检索到信息不足以回答问题,必须回复'根据现有资料无法确认',不要编造。"
  • 在后处理时检查答案中是否包含检索内容的高频关键词。如果完全不相关,就返回默认话术。

还有一个技巧:给LLM的检索片段不要超过5个,太多反而容易混淆。而且每条片段前面要加标签,比如【法条】【问答】【资讯】,LLM能更清楚信息来源。

6.3 Neo4j查询超时

Neo4j默认查询超时是30秒,我们有些复杂路径查询会超过这个时间。可以从两个方向解决:一是简化查询模式,减少深度;二是给Neo4j配置加超时时间,或者在应用层用CALL apoc.util.sleep配合超时控制。更实用的是在查询之前先做一次EXPLAIN,确认有没有走索引。我遇到过几次因为忘了加索引导致全库扫描的情况,加上索引后查询时间从8秒降到200毫秒。

6.4 20W数据量下的性能优化

20W在Neo4j里不算大,但如果设计不当也会变慢。我们一个核心优化是把Question.text重复内容做归一化存储,同一问题的变体统一映射到标准问题ID。另一个优化是给关系加类型属性,比如HAS_ANSWER关系带source属性,查询时过滤掉不需要的来源,减少数据扫描。向量库的优化则体现在批量写入时调大批大小,ChromaDB默认写入速度较慢,批量写入设置成256个documents,性能提升明显。

6.5 知识图谱与向量数据库如何选择

这个也是社区里经常讨论的问题。我的经验是:如果数据中实体关系密集,且需要精确逻辑推理,必须上图谱;如果只是大量文本的语义匹配,向量库更高效。法律领域两者都很需要,所以我们的方案是"图谱当主干,向量当兜底"。在实际部署中,两者是互补关系,不是二选一。RAG相关网络热词里也一直在讨论图谱和向量的融合,这个方向我认为是法律AI落地最靠谱的一条路。

7. 实操心得与后续扩展

项目交付到现在跑了快半年,线上效果基本稳定。我个人的体会是:法律问答系统最难的不是模型,而是知识结构化程度。数据抽出高质量实体关系比换个更大的LLM重要得多。如果你也想做类似系统,建议先从1000条高频问答开始,把图谱模型和问答链路跑通,再扩大到20W,千万不要一上来就把全部数据灌进去,不然调试成本极高。

几个具体建议:

  • 版本管理:图谱构建脚本和抽取规则一定要用Git管理,改坏了可以回滚。我们的实体抽取规则迭代了十几版,每次改动都会重新构建增量图,没有版本控制会乱套。
  • 监控:统计每天不能回答的问题,定期人工review,把新问题补进图谱。这个闭环才是系统越用越聪明的原因。
  • 扩展方向:目前只做了文本问答,后续可以做多轮对话和意图反查。也可以把图谱推理能力加进去,比如自动计算经济补偿金的具体金额,而不只是引用法条。不过那需要更多的业务规则参与,等下一阶段再说。

最后分享一个小技巧:如果你也在用llama.cpp做本地推理,建议开启--mlock参数锁住内存,避免swap导致推理变慢。我们线上服务器的吞吐量因此提升了差不多一成。这套系统不依赖任何商业API,全部代码可以自己掌控,我觉得这也是法务场景最稳妥的做法。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 23:50:16

模块电源三种冷却方式解析:VCCM600散热设计与降额选型指南

VoxPower的VCCM600系列刚看到的时候&#xff0c;我的第一反应是&#xff1a;600W级模块电源说"可以用三种方式冷却"&#xff0c;这不是什么新鲜事&#xff0c;市面上很多半砖、全砖产品在设计时就考虑了传导、对流、强制风冷几种散热路径&#xff0c;但多数只是"…

作者头像 李华
网站建设 2026/8/31 23:49:01

YOLOv8+PyQt5实战:构建一个可交互的手势识别系统

简介&#xff1a;本资源是一套基于YOLOv8与PyQt5开发的手势检测识别完整实践方案&#xff0c;面向计算机视觉初学者、人机交互研究者及智能交互系统开发者&#xff0c;解决非接触式手势控制中的实时检测与可视化部署问题。压缩包共2000个文件&#xff0c;含914张标注图像&#…

作者头像 李华
网站建设 2026/8/31 23:42:33

高精度DC-DC转换器:电压调节精度的设计、选型与实测指南

这些年我经手过不少电源方案&#xff0c;有一点感触特别深&#xff1a;很多人选DC-DC转换器时&#xff0c;眼睛只盯着效率、纹波和静态功耗&#xff0c;却把"电压调节精度"这个参数放到了次要位置。直到板子从常温挪到高低温箱里&#xff0c;或者负载从空载瞬间跳到满…

作者头像 李华
网站建设 2026/8/31 23:42:10

中低功率电机驱动芯片选型与PCB设计实战指南

电机驱动这个东西&#xff0c;看起来不起眼&#xff0c;但几乎所有带电机的设备都绕不开它。我这些年做过的项目&#xff0c;从陪护机器人的轮子、智能门锁的微型电机&#xff0c;到车窗升降器、电动阀门执行器&#xff0c;几乎每块板子上都少不了这颗小小的驱动器件。说句实话…

作者头像 李华
网站建设 2026/8/31 23:42:08

Windows 无法使用 ssh 连接虚拟机(ubuntu),网络没问题的情况下

目录 1. 确认虚拟机是否开机且网络可达 2. 在虚拟机上检查 SSH 服务 3. 安装并启动 SSH 服务 4. 确认端口监听正常 5. 检查防火墙 6. 再次连接 Connection refused 表示目标主机的 22 端口主动拒绝了连接&#xff0c;这通常意味着 SSH 服务没有运行或未安装。 1. 确认虚…

作者头像 李华
网站建设 2026/8/31 23:31:03

FPGA实战:用Verilog实现MT25QL NOR FLASH读写擦除控制器

简介&#xff1a;本资源是面向FPGA开发初学者与嵌入式硬件工程师的MT25QL系列SPI Flash读写实战工程&#xff0c;聚焦于高速可靠存储接口的底层驱动实现与验证。工程基于Xilinx Vivado 2018.3平台构建&#xff0c;完整实现了50MHz输入时钟经PLL倍频至80MHz后&#xff0c;以1152…

作者头像 李华