ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于RAG与WebSocket构建法律AI咨询平台:从向量检索到流式响应的工程实践

2026/8/9 8:40:26 拓冰建站 浏览量
基于RAG与WebSocket构建法律AI咨询平台:从向量检索到流式响应的工程实践 1. 先搞清楚这个平台到底要解决什么问题看到“基于AI的法律援助在线咨询平台”这个标题很多人第一反应可能是“又一个聊天机器人”。但如果你仔细拆解后面的技术栈——PostgreSQL检索增强、LLM大模型、WebSocket即时通讯、向量数据库——就会发现它想做的远不止是简单的问答。这个平台的核心是解决一个非常具体且棘手的场景如何让一个AI助手在专业、严谨、信息量巨大的法律领域提供既准确又及时的咨询服务。普通聊天机器人遇到专业问题要么胡编乱造要么回答“我是AI无法提供法律建议”。而一个合格的法律援助平台必须能理解复杂的法律条文、案例细节并从海量知识库中精准找到相关依据再组织成通俗易懂的回复。这背后至少需要解决三个技术难题知识精准检索用户问“试用期被无故辞退怎么办”AI不能只凭训练数据泛泛而谈必须能立刻从《劳动合同法》、相关司法解释、地方性法规甚至类似判例中找到最相关的条款和解释。这就是“检索增强生成RAG”要干的活。对话实时性咨询是交互式的用户可能连续追问。用传统的HTTP请求-响应模式体验会非常割裂。WebSocket就是为了保证对话的流畅和即时让AI的“思考”和“打字”过程更自然。专业语义理解法律术语多表述严谨。“故意伤害”和“过失致人重伤”在法律上是完全不同的概念。传统的关键词匹配比如用LIKE搜索会漏掉大量语义相近但表述不同的内容。向量数据库的作用就是把法律条文和案例转换成数学向量让AI能进行“语义搜索”理解“工资”和“薪酬”在特定语境下是相近的。所以这个项目不是一个炫技的Demo而是一个有明确问题导向的工程方案。它适合两类人看一是想了解如何将LLM落地到垂直领域的开发者特别是司法、金融、医疗等强知识依赖的行业二是对RAG、实时AI应用架构感兴趣的技术人员。接下来我会按照一个实际搭建和验证的顺序拆解这个平台的关键环节。我们不会只讲概念而是聚焦在环境怎么配、代码怎么写、问题怎么查这些实操层面。2. 环境与核心组件选型别在第一步就踩坑在动手写代码之前先把地基打好。这个平台的技术栈看起来复杂但拆开看就是几个核心服务的组合。选型不对后面全是坑。2.1 数据库层PostgreSQL pgvector 是黄金搭档项目标题里提到了PostgreSQL和向量数据库。最直接、最稳定的方案就是使用PostgreSQL 的 pgvector 扩展。它把向量搜索能力直接做进了数据库里省去了维护另一个独立向量数据库如Milvus、Qdrant的复杂度对于中小规模的知识库和起步阶段的项目来说是性价比最高的选择。为什么是 pgvector运维简单你只需要维护一个PostgreSQL实例数据一致性、备份恢复都用同一套机制。开发友好直接用SQL就能进行向量相似度查询-运算符和你的业务查询可以轻松结合。足够用对于法律条文、案例摘要这类文本生成的向量通常用1536维的OpenAItext-embedding-3-small或 768维的bge系列模型pgvector的性能在百万级数据量内完全够用。安装与配置要点PostgreSQL版本确保你的PostgreSQL版本在11以上建议直接用15或16。在Ubuntu上安装很简单sudo apt update sudo apt install postgresql-15 postgresql-15-pgvector启用扩展安装后连接到你的数据库创建扩展CREATE EXTENSION IF NOT EXISTS vector;设计表结构你的知识库表至少需要包含这些字段CREATE TABLE legal_knowledge ( id BIGSERIAL PRIMARY KEY, content TEXT NOT NULL, -- 原始法律文本 embedding vector(1536), -- 向量字段维度取决于你的嵌入模型 metadata JSONB, -- 可以存放法条编号、颁布日期、效力级别等信息 created_at TIMESTAMP DEFAULT NOW() );注意vector(1536)这里的维度必须和你后面使用的文本嵌入模型输出的维度一致否则数据插不进去。2.2 LLM服务层选API还是本地部署这是核心决策点直接关系到成本、速度和可控性。云端API如OpenAI GPT-4/3.5-Turbo Anthropic Claude 国内大模型API优点开箱即用效果稳定无需担心算力。最适合快速验证和原型开发。缺点有使用成本数据经过第三方需关注合规性可能有速率限制注意热词里的error code: 429就是触发了速率限制。建议初期先用API跑通全流程。调用时一定要做好错误重试和降级处理比如当主要模型API失败时可以尝试备用模型或返回一个友好的提示。本地部署模型如 Llama 3.1、Qwen、ChatGLM优点数据完全私有无网络延迟长期成本可能更低。缺点需要强大的GPU至少16GB显存起步部署和调试复杂模型效果需要自己微调。建议如果法律咨询数据非常敏感或追求极致响应速度再考虑这条路。可以先从7B、8B参数量的“小”模型开始测试。对于法律援助平台我建议第一阶段用“云端API 本地知识库pgvector”的混合架构。这样既保证了核心知识数据在自己手里又利用了成熟大模型的强大推理能力。2.3 实时通讯层WebSocket 不是简单发消息WebSocket负责前端页面和后端服务之间的全双工通信。用户每发一条消息后端可能经历接收 - 检索知识 - 调用LLM生成 - 流式返回。这个过程可能长达十几秒必须用WebSocket来支持流式传输Streaming让用户看到AI是“一个字一个字打出来”的而不是长时间等待后突然出现一大段话。技术栈选择后端Spring Boot 有成熟的spring-boot-starter-websocket支持。Node.js 可以用ws或Socket.IO后者提供了更多自动重连等高级功能。前端直接用浏览器原生的WebSocketAPI 或Socket.IO-client。关键点处理好连接的生命周期。用户刷新页面、网络波动都会导致连接断开热词中websocket closed by server before res就是典型问题。后端需要能优雅地处理这些断开并清理相关资源。2.4 文本嵌入模型知识检索的“翻译官”这是RAG的“灵魂”。它负责把一段法律文本无论是用户问题还是知识库条文转换成向量。模型选得好检索精度就高。通用选择OpenAI 的text-embedding-3-small或text-embedding-ada-002。效果经过海量验证且维度适中1536或1024与pgvector配合良好。本地/开源选择BGEBAAI/bge-large-zh、text2vec系列。这些模型可以部署在自己的服务器上数据不出域。需要自己准备环境Python, PyTorch等来运行嵌入服务。重要原则知识库的嵌入和用户问题的嵌入必须使用同一个模型否则向量空间不一致检索结果毫无意义。3. 从零搭建五步跑通核心流程现在我们抛开理论用一个最小化的流程把整个平台的核心链路跑通。假设我们使用 Spring Boot 作为后端框架。3.1 第一步构建本地法律知识库在你开始写聊天代码之前先把“大脑”准备好。准备知识源收集法律文本可以是《民法典》等法律的TXT文件每条法律条文为一段。存入一个laws.txt。启动嵌入服务如果你用OpenAI API跳过这一步。如果用本地BGE模型你需要写一个简单的Python FastAPI服务来提供嵌入接口。向量化并入库写一个脚本读取laws.txt调用嵌入接口生成向量然后批量插入PostgreSQL。# 示例脚本片段 (Python with psycopg2 and openai) import psycopg2 from openai import OpenAI client OpenAI(api_keyyour-key) conn psycopg2.connect(dbnamelegal_db, userpostgres) cur conn.cursor() with open(laws.txt, r, encodingutf-8) as f: for line in f: law_text line.strip() if not law_text: continue # 调用嵌入API response client.embeddings.create(modeltext-embedding-3-small, inputlaw_text) embedding response.data[0].embedding # 插入数据库 cur.execute( INSERT INTO legal_knowledge (content, embedding) VALUES (%s, %s), (law_text, embedding) ) conn.commit()创建索引数据量大了以后必须对向量字段创建索引来加速检索。CREATE INDEX ON legal_knowledge USING ivfflat (embedding vector_cosine_ops) WITH (lists 100);注意ivfflat索引在数据插入后创建效果更好。lists参数需要权衡查询速度和精度数据量在10万条以内100是个不错的起点。3.2 第二步实现WebSocket通信与会话管理在后端创建一个WebSocket处理器。// 示例Spring Boot WebSocket 配置和处理器 Configuration EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new LegalAidWebSocketHandler(), /ws/legal-aid).setAllowedOrigins(*); } } Component public class LegalAidWebSocketHandler extends TextWebSocketHandler { private final SimpMessagingTemplate messagingTemplate; private final LegalAidService legalAidService; // 核心业务服务 Override public void afterConnectionEstablished(WebSocketSession session) { // 新连接建立可以初始化用户会话上下文 log.info(WebSocket连接建立: {}, session.getId()); } Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { // 收到用户消息 String userMessage message.getPayload(); String sessionId session.getId(); // 异步处理避免阻塞WebSocket线程 CompletableFuture.runAsync(() - { try { // 调用核心服务处理消息并获取流式响应 legalAidService.processQueryStreaming(userMessage, sessionId, (chunk) - { // 通过WebSocket将生成的文本块实时发送回前端 messagingTemplate.convertAndSendToUser(sessionId, /queue/reply, chunk); }); } catch (Exception e) { messagingTemplate.convertAndSendToUser(sessionId, /queue/error, 处理请求时发生错误); } }); } }前端需要连接ws://你的服务器地址/ws/legal-aid并监听/user/queue/reply和/user/queue/error来接收消息。3.3 第三步实现RAG检索增强的核心逻辑这是LegalAidService的核心。当用户提问时问题向量化将用户问题userQuestion用同样的嵌入模型转换为向量queryVector。向量检索在legal_knowledge表中查找与queryVector最相似的几条记录。SELECT content, metadata FROM legal_knowledge ORDER BY embedding - ?::vector LIMIT 5;这个?参数就是queryVector。-运算符计算余弦距离值越小越相似。构建提示词Prompt将检索到的法律条文和用户问题组合成一个清晰的指令交给LLM。你是一个专业的法律AI助手请严格根据以下提供的相关法律条文回答用户的问题。 如果提供的条文不足以完全回答问题请基于条文进行推理并明确指出你的回答中哪些部分是基于已知条文哪些部分是推理。切勿编造不存在的法律条款。 【相关法律条文】 {检索到的法律条文1} {检索到的法律条文2} ... 【用户问题】 {userQuestion} 【请回答】调用LLM生成将组装好的提示词发送给LLM API或本地模型并请求以流式Streaming方式返回结果。3.4 第四步流式输出与前端展示LLM API如OpenAI支持以流的形式返回Tokens。你的后端需要将这些Token块实时地通过WebSocket推送给前端。// 伪代码展示流式处理概念 public void processQueryStreaming(String question, String sessionId, ConsumerString chunkConsumer) { // 1. 检索知识 ListLegalDoc relevantLaws vectorSearchService.search(question); // 2. 构建Prompt String prompt buildPrompt(question, relevantLaws); // 3. 调用LLM流式接口 OpenAiStreamClient client new OpenAiStreamClient(apiKey); client.streamingChatCompletion(prompt, new ResponseHandler() { Override public void onData(String token) { // 收到一个Token就通过WebSocket发出去 chunkConsumer.accept(token); } Override public void onComplete() { chunkConsumer.accept([DONE]); // 发送结束信号 } }); }前端收到一个Token就立刻追加到对话框里从而实现“打字机”效果。3.5 第五步运行与验证启动服务确保PostgreSQL含pgvector已启动你的Spring Boot应用正常运行。打开前端页面一个简单的HTML页面包含连接WebSocket、发送消息和显示消息的区域。测试单轮问答问一个明确的问题如“劳动合同的试用期最长是多久”。观察后端日志是否有检索SQL执行LLM API是否被调用前端是否以流式方式逐渐显示回答回答内容是否引用了《劳动合同法》的相关条款测试多轮对话在同一个WebSocket连接下接着问“那如果试用期工资低于正式工资的80%呢”。系统需要能维护会话上下文通常是把之前的对话历史也作为提示词的一部分传给LLM。4. 关键细节与生产环境考量单任务跑通只是开始。要让这个平台真正可用以下几个细节必须处理好。4.1 检索质量优化光靠向量搜索不够单纯用向量相似度搜索可能会漏掉一些关键词完全匹配但语义稍远的重要法条或者搜到一些语义相近但无关的内容。混合搜索Hybrid Search结合向量搜索和传统的关键词搜索如PostgreSQL的全文检索tsvector。可以先各自检索出Top K个结果然后按分数进行加权融合如 Reciprocal Rank Fusion。查询重写Query Rewriting在检索前先用一个小模型或规则对用户问题进行扩展或重写。例如将“试用期被开除”重写为“试用期 解除劳动合同 辞退 开除”再用扩展后的查询去做向量化和关键词检索。元数据过滤你的metadataJSONB字段这时就派上用场了。检索时可以加上过滤条件比如只检索“效力级别法律”的条文或者“颁布年份2020”的新规。这能大幅提升精度。SELECT content FROM legal_knowledge WHERE metadata-law_type 劳动合同法 ORDER BY embedding - ?::vector LIMIT 5;4.2 上下文管理与流式传输的陷阱LLM有上下文长度限制。多轮对话后如何管理不断增长的对话历史滑动窗口只保留最近N轮对话。关键信息摘要当对话历史过长时调用LLM对之前的对话进行摘要然后用摘要代替原始历史。流式传输的完整性网络可能中断。前端在发送消息时可以附带一个唯一的messageId。后端流式返回的每个块也都包含这个ID。前端根据ID将内容拼接到正确的消息位置。这样即使网络波动导致连接重建历史消息也能正确显示。4.3 性能、安全与监控性能缓存对常见问题如“加班费怎么算”的最终答案或检索结果进行缓存可以极大减少对向量数据库和LLM的调用。异步处理如3.2节所示WebSocket消息处理一定要异步化避免阻塞。数据库连接池确保你的应用配置了合适的PostgreSQL连接池如HikariCP。安全输入检查与过滤对用户输入进行基本的清理防止Prompt注入攻击。例如检查用户输入中是否包含试图让AI忽略之前指令的特殊字符或语句。输出审查对于法律咨询这种严肃场景可以考虑对AI的输出进行二次审查例如用一个简单的规则检查是否包含“绝对”、“保证”等过于武断的词语或者是否给出了具体的金额、日期建议。WebSocket鉴权连接WebSocket时应该携带Token进行身份验证防止未授权访问。监控关键指标记录每次问答的耗时拆分为检索时间、LLM生成时间、Token使用量、检索到的条文数量。错误日志详细记录检索失败、LLM调用失败特别是429限流错误、WebSocket异常断开等信息。反馈机制在UI上提供“回答是否有用”的反馈按钮收集数据用于后续优化检索和提示词。5. 常见问题排查清单当你跑不起来或者效果不好时按这个顺序查。WebSocket连接失败检查后端服务是否启动端口是否正确。检查前端连接的WebSocket URL是否正确ws://或wss://。查看浏览器开发者工具F12的Console和Network标签是否有CORS错误或连接错误。检查服务器防火墙和安全组规则是否放行了WebSocket端口。检索结果为空或不准确认知识库有数据连上PostgreSQL执行SELECT COUNT(*) FROM legal_knowledge;。检查向量维度确认embedding字段定义的维度如vector(1536)与你的嵌入模型输出维度完全一致。手动测试向量搜索在数据库里找一条已知数据的向量然后用它去搜索看能否找到它自己和其他相关条目。检查嵌入模型确保知识库嵌入和问题嵌入用的是同一个模型。尝试混合搜索如果纯向量搜索效果差加入关键词搜索试试。LLM回答不相关或胡言乱语检查Prompt把组装好的Prompt打印到日志里看看格式是否正确检索到的法律条文是否被正确插入。检查上下文长度是否因为对话历史太长导致真正的Prompt被截断了检查LLM API调用确认API Key有效没有触发速率限制429错误。查看返回的完整响应看看是不是有错误信息。简化测试用一个极其简单的Prompt如“请说‘你好’”测试LLM调用是否正常。流式输出中断或混乱检查后端流式处理逻辑确保每个Token都被正确发送并且在流结束时发送了结束信号。检查前端WebSocket消息处理逻辑是否正确地按messageId将内容拼接。网络不稳定时前端应实现自动重连机制。性能缓慢使用EXPLAIN ANALYZE分析你的向量检索SQL看是否用上了索引。检查LLM API的响应时间考虑是否切换到更低延迟的模型或区域。检查应用服务器和数据库的CPU、内存使用率。6. 总结与进阶方向搭建这样一个平台真正的难点不在于调用几个API而在于如何将LLM、向量数据库、实时通讯和垂直领域知识可靠地、高效地、安全地整合在一起。它不是一个算法实验而是一个系统工程。对于想深入的同学可以朝这几个方向探索更复杂的RAG策略尝试不同的检索器如多路召回、句子窗口检索、重排序模型让LLM对检索结果进行相关性打分排序。智能体Agent架构让AI不仅能回答问题还能根据用户需求自动调用“法条查询工具”、“案例检索工具”、“赔偿计算器”等完成更复杂的任务。微调Fine-tuning收集高质量的法律问答对对一个小规模的本地LLM如7B模型进行微调让它更擅长法律语言的表达和推理减少对庞大提示词的依赖。评估体系建立一套评估标准从“事实准确性”、“引用完整性”、“逻辑清晰度”等维度量化评估你平台回答的质量这是持续迭代的基础。起步时不要追求大而全。先用最少的代码把“用户提问 - 向量检索 - LLM生成 - 流式返回”这个闭环跑通。然后再一步步加入混合搜索、缓存、会话管理、监控等生产级功能。记住在垂直领域一个能稳定解决80%常见问题的系统远比一个追求100%完美但不可靠的系统有价值得多。