十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java 21 + Spring Boot 3 构建企业级 RAG 智能体工作流引擎

Java 21 + Spring Boot 3 构建企业级 RAG 智能体工作流引擎 1. 项目概述为什么在 Java 生态里重做 RAG 智能体工作流不是“倒退”而是精准卡位别卷 Python 了——这句话不是情绪宣泄是我在连续交付 7 个 AI 增强型企业系统后的真实判断。过去两年我带团队用 Python LangChain LangGraph 搭过 4 套 RAGAgent 系统从销售知识助手到 IT 服务台工单分派引擎表面跑得通但一进生产环境就暴露硬伤内存泄漏导致的定时重启、多租户隔离下模型上下文污染、LangGraph 状态机在高并发下的状态不一致、以及最致命的——业务系统ERP/CRM/OAJava 微服务集群拒绝与 Python 进程直连最终只能靠 HTTP 轮询或消息队列桥接延迟从毫秒级拉到秒级客户直接拒收验收。所以当 Java 21 的虚拟线程Virtual Threads正式 GA、Spring Boot 3 全面拥抱 Jakarta EE 9 和 GraalVM 原生镜像、Spring AI 1.0 发布支持 RAG 流水线抽象时我立刻决定把整套 RAG智能体工作流引擎原生移植到 Java 生态。这不是炫技而是解决三个刚性问题第一与存量 Java 微服务零成本集成——同一 JVM 内调用无序列化开销事务可穿透第二用虚拟线程扛住高并发 Agent 编排——一个用户发起多轮对话并行检索外部 API 调用传统线程池会撑爆而虚拟线程让万级并发连接变成常驻内存的轻量对象第三用 Spring 的声明式事务和 AOP给 RAG 流程加“业务保险”——比如知识库检索失败时自动降级到规则引擎或 Agent 执行超时后触发人工审核流程这些在 Python 生态里得自己手写状态管理而在 Spring 里一行TransactionalRetryable就能兜底。这个项目的核心产出是一个可嵌入任意 Spring Boot 3 应用的rag-agent-workflow-starter启动器。它不替代 LangChain而是把 LangChain 的核心能力文档切分、向量检索、LLM 调用、Prompt 工程用 Spring 的方式重新封装所有组件可配置、可替换、可监控、可审计。比如你用的是 Qwen2-7B 还是 DeepSeek-V2只需改一个application.yml里的spring.ai.llm.model-name你用的是 Milvus 还是 PGVector只需换一个RagRetrievalStrategyBean 实现类。它解决的不是“能不能跑”而是“能不能在银行核心系统、医保结算平台、电力调度中控这类对稳定性、可观测性、合规审计有硬要求的场景里长期稳跑”。关键词“Java 21”“Spring Boot 3”“RAG”“智能体”“工作流引擎”在这里不是堆砌而是环环相扣的技术选型逻辑链Java 21 提供底层并发基石Spring Boot 3 提供应用框架抽象RAG 是信息增强的确定性能力智能体是决策执行的动态能力工作流引擎则是把两者编排成业务闭环的胶水层。如果你正在评估 Dify、Coze 或自建 FastAPILangGraph 方案但你的主业务系统是 Java 技术栈那这篇内容就是你跳过试错成本的直通路径。2. 架构设计与技术选型为什么不用 LangChain for Java而选择“Spring AI 自研工作流内核”2.1 整体分层架构四层解耦每层可插拔整个引擎采用清晰的四层架构不是为了炫技而是为了解决企业级落地中最常见的“技术债蔓延”问题接入层Ingress Layer提供统一的/v1/agent/chatREST 接口兼容 OpenAI 兼容协议OpenAI-compatible API这意味着前端 SDK、Postman 测试、甚至某些旧版 Dify 的前端模块无需修改即可对接。这一层不做任何业务逻辑只做协议转换、鉴权集成 Spring Security OAuth2 Resource Server、请求限流Resilience4j和日志脱敏避免 PII 数据落盘。编排层Orchestration Layer这是引擎的“大脑”由自研的WorkflowEngine驱动。它不基于 LangGraph 的状态机而是采用 Spring State Machine 的扩展实现核心优势在于状态持久化可选——你可以配置为内存状态开发调试快也可配置为 Redis Hash 存储生产环境高可用甚至对接 Oracle 的 JSON 列满足金融行业审计要求。每个智能体Agent被定义为一个WorkflowDefinitionYAML 文件例如销售顾问 Agent 的定义里明确写了“第1步调用 RAG 检索产品手册第2步若置信度0.85则调用 CRM API 查客户历史订单第3步生成话术并记录到 CDP”。这种声明式定义让业务人员也能看懂流程而不是面对一堆 Python 函数调用链。能力层Capability Layer这才是真正复用 LangChain 思想的地方但实现完全 Spring 化。我们拆出四个核心能力 BeanRagRetriever封装检索逻辑内部自动处理“查询重写Query Rewriting”——比如用户问“怎么退订宽带”系统先重写为“宽带服务终止流程”再检索避免语义鸿沟LlmInvoker统一 LLM 调用入口支持 OpenAI、Azure OpenAI、Ollama、本地 GGUF 模型关键是在调用前自动注入System Prompt片段如“你是一名资深电信客服请用中文回答禁止虚构政策条款”且该片段可按 Agent 类型动态加载ToolExecutor执行外部工具比如调用 SAP RFC 接口查库存、调用飞书机器人发通知。这里我们强制要求所有 Tool 必须实现ToolSpec接口包含name、description、parametersJSON Schema 格式这样 LLM 才能准确理解工具能力并生成有效参数MemoryManager管理对话历史不是简单存 Redis而是分三级短期记忆当前会话 Token 窗口内、中期记忆Redis Sorted Set 按时间戳排序保留最近 10 轮、长期记忆向量化存入 Milvus用于跨会话的用户偏好学习。数据层Data Layer彻底解耦存储。知识库文档走DocumentRepositoryJPA 接口向量索引走VectorStoreSpring AI 接口业务数据走JdbcTemplate。这意味着你可以把产品手册 PDF 存 MinIO向量存在 Milvus客户订单数据查 Oracle三者互不影响。我们甚至预留了DataSourceRouter当检测到某次 RAG 检索命中率持续低于阈值时自动切换到备用知识源比如从内部 Wiki 切到公开 API 文档。提示很多团队一上来就纠结“用 Milvus 还是 PGVector”其实大可不必。我们的方案是在application-dev.yml里配spring.ai.vector-store.typememory纯内存启动快适合本地调试在application-prod.yml里配spring.ai.vector-store.typemilvus通过 Spring Profile 控制。真正上线前用一套标准测试集100 个真实用户问题跑 A/B 测试看召回率和 P95 延迟数据说话不凭感觉。2.2 关键技术点深度解析虚拟线程如何拯救 Agent 并发瓶颈Python 的 asyncio 在 I/O 密集型场景表现优秀但遇到 CPU 密集型任务如文档切分、向量计算就会阻塞整个事件循环。而 Java 21 的虚拟线程是 JDK 层面的“绿色线程”由 JVM 调度而非 OS 内核调度。它的内存占用只有传统线程的 1/100约 2KB vs 1MB创建销毁成本极低。在我们的 Agent 工作流中一个典型用户请求会触发并行执行 3 个 RAG 检索产品知识、售后政策、竞品对比同时调用 2 个外部 APICRM 查客户等级、ERP 查库存最后将结果聚合交给 LLM 做最终决策。如果用传统线程池比如Executors.newFixedThreadPool(50)50 个线程同时阻塞在 HTTP 调用上系统就卡死了。而用虚拟线程// Spring Boot 3 中的典型用法 Bean public TaskExecutor taskExecutor() { return new ConcurrentTaskExecutor( Executors.newVirtualThreadPerTaskExecutor() ); }每次Async方法调用JVM 自动分配一个虚拟线程。实测数据在 4C8G 的测试服务器上同时处理 5000 个并发 Agent 请求平均响应时间稳定在 850msCPU 使用率仅 65%内存增长平缓。换成传统线程池200 并发就开始超时GC 频繁。这背后是 JVM 的“挂起-恢复”机制当虚拟线程执行HttpClient.send()等阻塞操作时JVM 不是让线程空转而是将其状态保存立即调度下一个虚拟线程运行等 IO 完成再恢复。这比 Python 的协程更底层、更透明开发者完全不用改异步编程模型照写同步代码就行。2.3 为什么放弃 LangChain for Java三点血泪教训LangChain for Java0.1.x我们深度试用过 3 个月最终弃用原因很实在第一生命周期管理混乱LangChain 的ChatClient、EmbeddingClient等组件没有 Spring Bean 的作用域概念。你在Service里new ChatClient()它就无法享受 Spring 的依赖注入、AOP 代理比如你没法给它加Retryable更别说连接池复用了。我们曾因一个未关闭的HttpClient导致连接耗尽排查了两天。第二配置体系割裂LangChain 的配置全靠Properties对象传参而 Spring Boot 的ConfigurationProperties是类型安全的。比如向量维度LangChain 里是config.setDimension(1024)写错成1023运行时报错Spring AI 里是spring.ai.vector-store.milvus.dimension1024启动时就校验错误前置。第三可观测性缺失LangChain for Java 没有内置 Micrometer 指标埋点。你想知道“今天 RAG 检索的平均延迟是多少哪个知识源命中率最低”得自己在每个retrieve()方法前后加Timer.Sample.start()代码侵入性强。而 Spring AI 的AiObservability自动上报ai.rag.retrieval.time、ai.llm.invocation.count等指标直接对接 PrometheusGrafana。所以我们的策略是用 Spring AI 做能力基座用自研工作流引擎做业务编排。Spring AI 负责“怎么调用模型”我们负责“什么时候调、调谁、失败了怎么办”。就像汽车Spring AI 是发动机我们是变速箱和驾驶系统。3. 核心模块实现与源码解析从知识库构建到 Agent 执行的完整链路3.1 知识库构建不只是“PDF 转向量”而是构建可审计的知识供应链企业知识库最大的痛点不是技术是“谁在什么时候更新了什么内容依据是什么”。我们的KnowledgeIngestionService设计把知识摄入变成一个可追溯、可回滚、可灰度的发布流程。第一步文档预处理Preprocessing不是简单调用PdfDocumentReader而是三阶段流水线格式清洗用 Apache PDFBox 提取文本后用正则过滤页眉页脚、扫描件 OCR 错误如“l”和“1”混淆、表格乱码。关键技巧我们维护一个correction-rules.json比如Qwen2-7B: Qwen2\\-7B在提取后自动修正。语义分块Semantic Chunking不用固定长度切分而是用RecursiveCharacterTextSplitter但设置了chunkSize512、chunkOverlap64更重要的是启用了separator策略优先按\n\n段落、其次\n句子、最后.句号空格切分。实测下来问答准确率比固定切分高 22%。元数据注入每一块文本都自动附加source_id文档唯一 ID、page_number、section_title从 PDF 标题结构解析、ingest_timestamp精确到毫秒、ingest_by操作人账号。这些元数据全部存入向量数据库的metadata字段后续检索时可作为过滤条件比如只检索“2024 年新版”的政策。第二步向量化与索引Embedding Indexing我们用Spring AI的EmbeddingClient但做了两处关键增强批量向量化LangChain 的embedDocuments()是单文档串行我们重写为embedBatch(ListDocument)一次 HTTP 请求提交 100 个文档吞吐量提升 8 倍。原理是把多个文档的文本拼成一个长字符串用模型的batch_encode_plus处理再按长度切分回向量。索引优化Milvus 配置index_typeIVF_FLAT、metric_typeL2、nlist100。nlist不是越大越好我们通过milvus_cli跑search命令压测发现nlist100时10 万向量的 P95 检索延迟是 42msnlist1000时是 68ms但内存占用翻倍。所以选 100 是精度和性能的平衡点。第三步知识发布Publishing这才是企业级的关键。我们不直接把向量写入生产库而是写入knowledge_staging表MySQL状态为DRAFT启动StagingValidator自动检查是否有重复文档、向量维度是否匹配、元数据是否完整人工在后台管理界面点击“发布”触发PublishTask将staging数据原子性地同步到knowledge_prod表并更新 Milvus 的collectionalias比如从knowledge_v1切到knowledge_v2整个过程 200ms业务无感。注意Milvus 的 collection alias 切换是原子操作比删旧建新安全得多。我们曾在线上误删 collection恢复花了 40 分钟。现在用 alias切回去只要 0.3 秒。3.2 RAG 检索增强超越“关键词匹配”实现意图驱动的混合检索纯向量检索Vector Search在专业领域容易“答非所问”。比如用户问“发票丢了怎么报销”向量可能匹配到“电子发票开具流程”但用户真正需要的是“丢失发票的补救措施”。我们的HybridRetriever实现了三路召回向量检索Vector Retrieval主路用MilvusVectorStoretopK5返回相似度分数。关键词检索Keyword Retrieval辅路用ElasticsearchRestTemplate对文档content字段做match_phrase查询topK3。关键是把用户查询做同义词扩展我们维护一个synonym-dict.txt比如“报销→报账、核销”查询时自动展开。元数据过滤Metadata Filtering保底路根据source_typePOLICY、valid_from2024-06-01等条件在向量检索前就缩小候选集。这步在MilvusVectorStore的filter参数里完成不增加额外查询。三路结果合并时不用简单加权平均而是用RRFReciprocal Rank Fusion算法score(doc) Σ(1 / (rank_i(doc) k))其中k60是经验常数rank_i(doc)是 doc 在第 i 路的排名从 1 开始。比如 docA 在向量路排第 11/(160)0.016在关键词路排第 21/(260)0.016在元数据路排第 10.016总分 0.048docB 在向量路排第 30.016关键词路没出现0元数据路排第 10.016总分 0.032。这样 docA 胜出即使它在某一路排名不高但综合表现好。实测在 500 份政策文档的测试集上混合检索的 MRRMean Reciprocal Rank达 0.83纯向量是 0.67关键词是 0.52。MRR 超过 0.8 就意味着80% 的问题正确答案都在 top3 里。3.3 智能体工作流引擎用 Spring State Machine 实现可配置、可追踪的 Agent 编排WorkflowEngine的核心是StateMachine但不是直接用StateMachineBuilder而是封装了一层WorkflowDefinition# sales-assistant.yaml id: sales-assistant initialState: INIT states: - name: INIT actions: [setConversationContext] - name: RETRIEVE_PRODUCT actions: [invokeRagRetriever, logRetrievalResult] - name: CHECK_INVENTORY actions: [invokeTool, validateStock] - name: GENERATE_RESPONSE actions: [invokeLlm, formatResponse] transitions: - source: INIT target: RETRIEVE_PRODUCT event: START - source: RETRIEVE_PRODUCT target: CHECK_INVENTORY event: RETRIEVAL_SUCCESS guard: spel: #result.confidence 0.7 - source: RETRIEVE_PRODUCT target: GENERATE_RESPONSE event: RETRIEVAL_LOW_CONFIDENCE guard: spel: #result.confidence 0.7这个 YAML 被WorkflowDefinitionLoader解析成WorkflowDefinition对象再注册到 Spring State Machine 的StateMachineFactory。关键创新点Guard 表达式用 SpEL#result.confidence 0.7#result是上一步 Action 的返回值confidence是RagRetrievalResult的字段。这意味着业务规则可以写在配置里不用改 Java 代码。Action 可插拔invokeRagRetriever是一个ServiceBean名字叫RagRetrieverAction它实现了WorkflowAction接口。如果你想换一种检索策略只需写个HybridRetrieverAction在配置里把invokeRagRetriever改成invokeHybridRetriever即可。状态持久化到 RedisRedisStateMachinePersister把当前状态、上下文变量如customerId,productSku序列化成 JSON 存 Redis HashKey 是workflow:{sessionId}。这样服务重启后用户对话不会断StateMachine从 Redis 加载状态继续执行。我们还做了个“工作流沙盒”在管理后台上传 YAML点“模拟运行”输入测试问题实时看到每一步的输入输出、耗时、状态变更。这极大降低了业务方参与流程设计的门槛。3.4 源码关键片段解析如何让 LLM 调用既安全又可控LlmInvoker是最核心也最危险的模块。我们绝不允许 LLM 直接接触生产数据库或调用支付接口。源码里最关键的控制点有三个第一Prompt 注入防御public class SecurePromptTemplate { public String build(String userQuery, ListDocument contextDocs) { // 1. 清洗 contextDocs移除所有 script、javascript:alert() 等 XSS 片段 String safeContext contextDocs.stream() .map(doc - Jsoup.clean(doc.getContent(), Whitelist.none())) .collect(Collectors.joining(\n---\n)); // 2. 注入 System Prompt 片段但用占位符防止用户 query 里有 {system} String systemPrompt Message.of( Role.SYSTEM, 你是一名{role}请严格遵守以下规则\n - 只回答与{domain}相关的问题\n - 禁止虚构政策条款不确定时回答请咨询人工客服\n - 输出必须是纯文本禁止 markdown 格式 ).toContent(); // 3. 组装最终 prompt确保 userQuery 在最后且用特殊分隔符 return String.format( %s\n\n CONTEXT \n%s\n\n QUESTION \n%s, systemPrompt, safeContext, userQuery ); } }这里Jsoup.clean()是防 XSS 的底线{role}和{domain}从WorkflowDefinition的metadata里读取确保每个 Agent 有专属人设。第二LLM 调用熔断Retryable( value {RuntimeException.class}, maxAttempts 3, backoff Backoff(delay 1000, multiplier 2) ) CircuitBreaker( openTimeout Duration.ofSeconds(30), resetTimeout Duration.ofMinutes(2) ) public AiResponse invoke(AiRequest request) { // 实际调用逻辑 }Retryable处理瞬时故障网络抖动CircuitBreaker处理雪崩如果 30 秒内失败率超 50%熔断器打开后续请求直接返回 fallback 响应如“系统繁忙请稍后再试”2 分钟后半开试探。第三输出后处理Post-processingpublic class OutputSanitizer { public String sanitize(String rawOutput) { // 1. 移除所有 URL防止泄露内网地址 rawOutput rawOutput.replaceAll(https?://[^\s], [URL_HIDDEN]); // 2. 检测敏感词手机号、身份证号用 * 替换 rawOutput rawOutput.replaceAll((1[3-9]\\d{9}), 1*********); rawOutput rawOutput.replaceAll((\\d{17}[0-9Xx]), *****************); // 3. 强制截断防 token 超限 if (rawOutput.length() 2000) { rawOutput rawOutput.substring(0, 2000) ...内容已截断; } return rawOutput; } }这三步做完LLM 的输出才交给前端。我们线上从未发生过敏感信息泄露事故。4. 实操部署与性能调优从本地启动到千并发压测的完整记录4.1 本地开发环境5 分钟启动一个可交互的 RAGAgent Demo很多教程一上来就讲 Kubernetes但开发者最需要的是“5 分钟看到效果”。我们的demo模块做到了克隆代码git clone https://github.com/your-org/rag-agent-engine.git启动依赖docker-compose -f docker-compose.dev.yml up -d启动 PostgreSQL、Milvus、Redis、MinIO导入知识库curl -X POST http://localhost:8080/v1/knowledge/ingest -F filemanual.pdf启动服务./gradlew bootRun自动加载application-dev.yml访问 UIhttp://localhost:8080/demo输入问题实时看到检索结果、Agent 步骤、LLM 回复。关键在于application-dev.yml的精巧设计# 开发模式所有能力都走内存或轻量服务 spring: ai: llm: type: ollama # 用本地 Ollama model-name: qwen2:0.5b # 小模型启动快 vector-store: type: memory # 内存向量库无需 Milvus dimension: 384 # 适配 qwen2:0.5b 的 embedding 维度 datasource: url: jdbc:postgresql://localhost:5432/demo # 本地 PG这样开发者不用装 Milvus、不用申请 API Key就能跑通全流程。等要上生产再切到application-prod.yml。4.2 生产环境部署Kubernetes 上的资源规划与配置要点生产环境我们用 K8s但配置不是照搬网上模板而是基于真实压测数据组件Pod 数量CPU Request/LimitMemory Request/Limit关键配置rag-agent-api32C/4C4Gi/8GiJAVA_TOOL_OPTIONS-XX:UseZGC -XX:MaxGCPauseMillis10ZGC 降低 GC 停顿rag-ingestion-worker14C/8C8Gi/16GiSPRING_PROFILES_ACTIVEingestion禁用 Web专注批处理milvus-standalone14C/8C8Gi/16Gietcd.maxTxnOps10240提高事务上限关键配置说明ZGC 选择Java 21 默认 GC 是 G1但在高并发 Agent 场景下G1 的停顿时间波动大有时 200ms。ZGC 保证停顿 10ms实测 P99 延迟从 1200ms 降到 850ms。JVM 参数-Xms4g -Xmx4g堆内存固定避免动态伸缩抖动-XX:UseStringDeduplication减少字符串重复内存。Milvus 调优cache.cache_size4GB设为内存的 50%避免 OOMrocksmq.retention.time_in_seconds86400消息保留 1 天够排查问题。我们用k6做压测k6 run --vus 1000 --duration 5m scripts/stress-test.jsstress-test.js模拟真实用户行为每秒 200 个请求每个请求包含 3 轮对话问产品、问价格、问售后。结果平均响应时间842msP95: 1120ms错误率0.02%主要是网络超时非服务异常CPU 使用率峰值 78%内存使用率稳定在 6.2Gi/8Gi结论3 个rag-agent-apiPod 能稳扛 1000 并发符合预期。4.3 性能瓶颈排查与优化一次真实的 P99 延迟飙升分析上线第三天监控告警P99 延迟从 1.2s 突增至 4.5s。我们按顺序排查第一步看 Grafanaai.rag.retrieval.time指标没变还是 80ms说明 Milvus 没问题ai.llm.invocation.time指标暴涨从 300ms 到 2.1s指向 LLM 调用jvm.gc.pause.time没异常排除 GC 问题。第二步看日志在llm-invoker日志里发现大量WARN c.e.r.s.LlmInvoker - LLM call timeout after 2000ms, fallback to rule engine说明 LLM 调用超时了。第三步查网络kubectl exec -it rag-agent-api-0 -- curl -v https://api.openai.com/v1/chat/completions发现 DNS 解析慢平均 800ms。原来 K8s CoreDNS 的forward配置指向了公司内网 DNS而 OpenAI 域名需走公网内网 DNS 会先尝试递归查询超时后才转发。解决方案在Deployment的env里加env: - name: JAVA_OPTS value: -Dsun.net.inetaddr.ttl60 -Dnetworkaddress.cache.ttl60 - name: COREDNS_FORWARD value: https://1.1.1.1 https://8.8.8.8 # 强制走公共 DNS同时在application-prod.yml里把spring.ai.llm.timeout30003 秒避免长时间等待。修复后P99 延迟回到 1.1s。这个案例说明AI 系统的瓶颈往往不在 AI 本身而在基础设施的细节。5. 常见问题与实战避坑指南那些文档里不会写的“血泪经验”5.1 知识库相关问题速查表问题现象根本原因解决方案验证方法检索结果完全不相关PDF 提取文本为空扫描件未 OCR用pdfinfo manual.pdf检查Pages:数量若为 0说明是图片 PDF需先用pdftoppmtesseractOCRcurl http://localhost:8080/v1/knowledge/debug/{docId}查看原始文本同一问题不同时间检索结果不同Milvus 的consistency_levelStrong未设置导致读取到未刷新的索引在MilvusVectorStore初始化时显式设置ConsistencyLevel.STRONG压测时用milvus_cli手动search对比结果一致性知识更新后旧答案仍被召回向量库未删除旧文档新文档只是新增在KnowledgeIngestionService的publish()方法里加vectorStore.delete(ids)删除旧 ID检查knowledge_staging表的status字段确保旧记录被标记为OBSOLETE5.2 Agent 工作流常见陷阱与绕过技巧陷阱一“状态丢失”幻觉现象用户说“刚才说的A产品价格多少”Agent 却答“我不记得之前聊过什么”。原因MemoryManager的短期记忆Token 窗口太小或ConversationContext未正确传递到 LLM Prompt。绕过技巧在WorkflowDefinition的GENERATE_RESPONSE状态加一个preActioninjectConversationHistory它会从 Redis 读取最近 3 轮对话拼成User: ... Assistant: ...格式强制注入 Prompt。我们设maxHistoryTurns3因为超过 3 轮LLM 就容易“忘记”。陷阱二“工具调用死循环”现象Agent 反复调用同一个 Tool如查库存直到超时。原因LLM 生成的 Tool 参数不合法如skuABC但数据库里是abcTool 执行失败返回空LLM 认为“没查到”又调一次。绕过技巧在ToolExecutor里加Retryable但maxAttempts1失败后立即返回{error: SKU not found}并在 Prompt 里明确写“如果 Tool 返回 error不要重试直接告诉用户”。陷阱三“多租户数据泄露”现象租户 A 的知识库内容出现在租户 B 的检索结果里。原因MilvusVectorStore的filter条件没加租户 ID或RagRetriever的query没做租户隔离。绕过技巧在RagRetriever的retrieve()方法开头强制加filter tenant_id tenantContext.getCurrentTenant() 并用PreAuthorize(hasRole(TENANT_ADMIN))做二次校验。5.3 LLM 集成避坑清单从模型选择到提示工程模型选择不是越大越好我们测试过 Qwen2-72B推理速度是 Qwen2-7B 的 1/12但问答准确率只高 3.2%在内部测试
返回列表