ARTICLE DETAIL

资讯详情

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

Spring AI + RAG + Tool Calling 构建企业岗位分析系统实战

Spring AI + RAG + Tool Calling 构建企业岗位分析系统实战 岗位分析这件事表面上看着不复杂——把 JD 读一遍提取技能要求、经验门槛、薪资区间再跟候选人或者团队现状做比对。但真要把这套流程做成一个能落地、能对接企业内部数据的系统事情就没那么简单了。我用 Spring AI 搭建了一套 RAG Tool Calling 岗位分析系统把从文档解析、向量化、检索增强到工具调用的完整链路都走了一遍中间踩了不少文档里查不到的坑。这篇文章把整体设计、关键代码和排错经验一并记录下来给正在调研 Spring AI、准备上手 RAG 项目、或者纠结“RAG 和 Agent 到底怎么结合”的朋友做个参照。这篇文章适合三类人正在做 Spring AI 技术选型评估的后端工程师负责企业内部知识库和数据分析系统改造的技术 owner以及想完整跑通一遍“检索增强 工具调用”实战流程的 AI 应用开发者。我不会只贴代码每个关键决策背后的理由、每处坑的排查过程都会写到希望能帮你少走半个月弯路。1. 项目背景与整体设计思路1.1 岗位分析系统到底在解决什么问题岗位分析听起来像 HR 的活实际落到工程上它要处理的是典型的企业知识割裂问题。一个岗位信息分散在三四个地方招聘系统里有岗位描述JD、部门共享盘里有任职资格和职级文档、薪酬系统里有带宽数据、历史面试记录散落在邮件和表格里。模型本身再强如果拿不到这些上下文回答也只能泛泛而谈没法给出企业内部的真实结论。我最初的目标很朴素搭建一个系统让用户输入岗位名称或者贴一段岗位描述系统就能基于企业内部文档和结构化数据自动输出技能需求分析、经验年限判断、薪资区间参考、候选人匹配建议。这个场景天然适合用 RAG 来做——把非结构化文档向量化存储检索出相关内容后交给大模型生成结构化结论。但只靠 RAG 是不够的因为薪资带宽、技能库、职级对照表这些数据是结构化的存在数据库里单纯靠向量检索查不准也查不全。这就需要 Tool Calling 来补齐模型在分析过程中按需调用查询工具拿到结构化数据后再结合检索到的文档内容生成完整分析报告。1.2 为什么选 Spring AI 而不是 LangChain4j 或 LangGraph4j先说结论如果你所在的团队本身就是 Java/Spring 技术栈Spring AI 是目前把 AI 能力嵌进现有服务最平滑的方案。我当时对比过 LangChain4j 和 LangGraph4jLangChain4j 对 LangChain 概念的迁移度高但项目的活跃度和文档完整度相对弱一些跟 Spring Boot 生态的整合还需要自己做不少胶水代码。LangGraph4j 在 Agent 编排上很有想法但更适合已经明确要跑复杂多智能体流程的团队用它从零搭一个偏业务的系统心智负担偏重。Spring AI 的优势在于官方出身跟 Spring Boot 的自动装配、配置体系、注解风格完全一致。我用Service、Component注册的 Bean 可以直接参与 AI 流程向量存储、模型客户端、工具调用都有统一的抽象接口。对我这种常年写 Spring 的老开发来说几乎没有学习曲线。另外你搜到的“spring ai 2.0”确实已经发布了API 比 0.x 时代稳定很多ChatClient的 builder 风格很接近 Spring 系一贯的流畅写法。如果担心模型供应商绑定问题Spring AI 还做了 OpenAI 兼容适配国内模型如通义千问走 DashScope 也能接这一点对在国内做项目的团队很重要——你不需要因为换一家模型就重写业务代码。1.3 整体架构与数据流设计系统的整体结构分为五层数据接入层负责读取 PDF、Word、Markdown、HTML 等格式的岗位文档做清洗和切分。向量化与存储层将切分后的文档块交给 Embedding 模型生成向量写入向量数据库同时保留结构化字段以便后续过滤。检索层根据用户问题做向量相似度检索可选结合关键词过滤返回 TopK 候选文档。工具调用层封装薪资查询、技能库匹配、职级对照等业务工具通过 Spring AI 的Tool机制暴露给模型。生成层使用ChatClient组合系统提示词、检索到的文档、工具返回的结构化数据最终输出岗位分析报告。打个比方RAG 相当于给模型配了一个企业知识库图书管理员模型回答问题时先去查资料Tool Calling 则相当于给模型接了一双可以实际操作的手需要精确数据时直接去数据库取。两者配合才把“读文档”和“查数据”这两类能力统一到一个对话流程里。2. 环境准备与核心依赖搭建2.1 版本选型与依赖引入Spring AI 的版本演进非常快我从 0.8 看到 1.0 再到 2.0同一套 API 在不同版本里经常变。这次项目我直接选用 Spring Boot 3.4.x Spring AI 1.0.xMaven Central 已发布这套组合在稳定性和新特性之间比较均衡。JDK 用 17 或 21 都行Spring Boot 3.x 强制要求 JDK 17 以上。Maven 依赖方面核心是三个 starterdependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId version1.0.0/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-vector-store-pgvector/artifactId version1.0.0/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency这里有个坑要提前说如果你用的是 0.x 或 1.0 RC 版本需要额外配置 Spring 的 Milestone 仓库否则依赖拉不下来。1.0 正式版发布后已经上 Maven Central直接解析就行。我最初用 0.9 版本发现ChatClient还没完全定型代码写法和 1.0 差异很大后来统一升级到 1.0 才算消停。建议新项目直接锁 1.0 以上别折腾旧版。2.2 核心配置与模型接入配置文件里需要声明模型供应商、API Key、向量库连接信息。我用的 OpenAI 兼容接口配置比较简单spring: ai: openai: api-key: ${OPENAI_API_KEY} base-url: ${OPENAI_BASE_URL} chat: options: model: gpt-4o-mini temperature: 0.2 max-tokens: 2048 vectorstore: pgvector: index-type: HNSW distance-type: COSINE_DISTANCE dimensions: 1536 datasource: url: jdbc:postgresql://localhost:5432/job_analysis username: postgres password: postgres几个配置项解释一下temperature调低到 0.2是为了让岗位分析结果尽量确定和可复现dimensions必须跟 Embedding 模型输出维度对应用 OpenAI 的 text-embedding-3-small 就是 1536 维如果用 bge-m3 这类开源模型就得改成 1024 或对应维度。PGVector 的HNSW索引在大规模数据下检索性能更好COSINE_DISTANCE适合语义检索场景。如果你不想用 OpenAI 接口可以换 Spring AI Alibaba 的 DashScope 适配。Spring AI Alibaba 对国内模型的支持做得不错还带了 NL2SQL 等开箱即用的能力。我在项目里保留了切换开关通过配置文件的 profile 来切换供应商这样线上如果哪家模型出问题可以快速降级切换。2.3 环境准备阶段踩过的坑第一个坑是 PGVector 的扩展建不出来。Spring AI 虽然会在启动时自动创建向量表但 PostgreSQL 需要先安装 vector 扩展。我在 Linux 服务器上装 PG 时漏了pgvector包启动直接报错。解决办法是提前执行CREATE EXTENSION IF NOT EXISTS vector;或者用 Docker 镜像pgvector/pgvector:pg17省掉这个步骤。第二个坑是 Embedding 维度和向量表维度不一致。我中途换过一次 Embedding 模型旧表已经按 1536 维创建新模型输出 768 维插入时直接报维度错误。这个没有优雅的迁移方案最简单的处理是删表重建或者建表时预估好维度。建议在项目初期就定死 Embedding 模型不要频繁更换。3. RAG 核心链路从零搭建3.1 文档接入与清洗岗位分析系统的知识来源主要是三类历史 JD 文档PDF/Word、企业内部任职资格说明Markdown、职级能力对照表Excel/CSV。Spring AI 的PagePdfDocumentReader、TextDocumentReader可以直接读这些文件用法很简单var reader new PagePdfDocumentReader(classpath:docs/java_architect_jd.pdf); ListDocument documents reader.read();但直接读出来的文档不能直接用。我的经验是必须先做一轮清洗去掉 PDF 页眉页脚、公司宣传语、招聘网站的免责声明否则这些噪声也会被切成知识块检索时容易干扰模型判断。清洗时我写了个简单的处理器按文本行做过滤遇到重复的公司抬头、联系方式、统一社会信用代码这类信息直接丢弃。Word 文档读取建议转成纯文本再处理。我之前用 POI 直接解析 .docx格式标记混在文本里切分效果很差后来统一用TextDocumentReader读纯文本文件反而最稳。实际项目中我建议把各种格式的文档统一转成 Markdown 或纯文本存放在固定目录再批量导入这样后续清洗和更新都方便。3.2 文档切分中文场景的专门处理RAG 的检索质量一半取决于切分策略。Spring AI 默认的TokenTextSplitter按 token 数切分对英文效果尚可但中文文本经常把完整语义切得稀碎。比如一段话“要求具备 Java 并发编程经验熟悉 JUC 工具包理解 AQS 原理”如果正好在“并发编程”和“经验”之间切断检索时这句关键信息就不完整了。我最终的切分方案是先按段落拆再按 token 上限做二次约束TextSplitter textSplitter TokenTextSplitter.builder() .withChunkSize(500) .withChunkOverlap(80) .build();操作顺序是先按标题和换行符把文档切成逻辑块比如“岗位职责”是一个块、“任职要求”是另一个块然后再把过长的块交给TokenTextSplitter做细分加一个 80 token 的 overlap保证跨块语义不断裂。overlap 的比例大概是 chunk 的 15%-20%太少衔接不上太多会产生大量重复内容、浪费向量库空间。切分完成后给每个Document打上元数据比如来源文件名、岗位类型、所属部门后面检索时可以通过元数据过滤来缩小范围。3.3 向量化与存储选型Embedding 模型我选了 OpenAI 的 text-embedding-3-small理由很简单按量计费便宜1536 维对岗位分析这种中等规模文档库足够用而且开箱即用不用自己部署。如果你对数据出境有要求可以用本地部署的 bge-m3Spring AI 的TransformersEmbeddingModel可以直接加载本地模型但首次推理的冷启动时间比较长生产环境需要做预热。向量存储方面数据量不大的时候用 Spring AI 内置的SimpleVectorStore本地文件模式都能跑但考虑到后续要接生产环境我选了 PGVector。原因很实在PostgreSQL 本身就在项目里不需要额外引入一套服务运维成本低。如果你已经用了 Redis也可以选 Redis 向量存储Spring AI 对这两者的封装都做得比较完整。写入向量的代码vectorStore.add(chunks);就这么一行剩下的交给 Spring AI。但你要注意调用之前确保VectorStore的 Bean 已经正确初始化而且 Embedding 模型和向量表维度匹配。向量的批量写入建议做异步处理几百个文档块可能要好几分钟同步写会阻塞用户请求。3.4 检索阶段召回率怎么提检索是 RAG 效果的分水岭。很多人以为把文档切好、向量存好就完事了实际上检索阶段的细节决定了大模型最终回答的丰富度和准确性。Spring AI 的VectorStore.similaritySearch是最基础的召回手段ListDocument docs vectorStore.similaritySearch( SearchRequest.query(Java 架构师 岗位职责 技能要求) .withTopK(5) .withSimilarityThreshold(0.3) .withFilterExpression(type jd) );这里两个参数很考验手感。TopK决定了每次送多少文档块给模型取太少信息不全取太多会把无关内容混进来我一般取 4 到 8 之间具体看业务复杂程度。SimilarityThreshold是相似度阈值设置太高会直接过滤掉可能相关的文档设置太低又会让噪声进入上下文。这个值没法拍脑袋定我建议你把真实问题拉一批样本过一遍统计相关文档和不相关文档的相似度分布找一个区分度最高的切点。我在项目里最后定在 0.3-0.4 之间因为岗位文档的语义接近度高阈值太严反而是负担。还有一个提升召回率的技巧是查询改写。用户原始问题往往很短直接拿去向量检索效果一般。我写了一个预处理器把“这个岗位要什么技术栈”改成“岗位职责、技能要求、技术栈、任职资格”通过同义扩展增强召回。有时候也会先跑一次轻量级模型做意图判断拆出多个子查询分别检索再合并结果。这一步对中文场景尤其重要相同的意思用词差异太大单次相似度检索很容易漏。3.5 提示词模板与输出约束RAG 的最终生成结果很大程度上由提示词决定。我的系统提示词分三层第一层交代角色和任务边界第二层说明引用文档的规则第三层规定输出格式。这里有一个很容易犯的错误把 RAG 检索到的内容原样全塞给模型不做任何引导。模型可能直接复制原文也可能无视检索内容胡说。我实际使用的模板结构是这样的String systemPrompt 你是一名资深的岗位分析顾问。请根据【参考文档】中的信息结合【工具数据】 对用户提出的岗位进行系统性分析。 要求 1. 所有结论必须优先引用参考文档和工具数据不要编造。 2. 如果参考文档中缺少某个维度的信息明确回答该维度暂无数据。 3. 输出格式技能要求、经验要求、学历要求、薪资范围、岗位风险提示。 ;配合检索到的文档片段用PromptTemplate组装成最终请求。这里有个细节参考文档片段不宜太长我一般每个片段限制在 500-800 字超过的部分宁愿截断也不要无脑塞进去否则占用大量上下文窗口还容易让模型迷失重点。4. Tool Calling让系统真正具备数据操作能力4.1 为什么 RAG 还不够RAG 擅长处理的是“知识在文档里”的场景但岗位分析系统里有一类问题是文档无法回答的“Java 架构师岗位在深圳地区的薪资中位数是多少”、“公司现在有多少岗位在招人”、“某个技能在近三个月的岗位描述里出现的频率”。这些问题要查的是数据库里的结构化数据靠向量检索根本查不出来。我最初的设计只做了 RAG结果模型经常一本正经地编造薪资区间这显然是无法接受的。解决思路就是引入 Tool Calling。把数据库查询、接口调用封装成工具函数模型在回答过程中按需调用。比如用户问薪资模型就会触发querySalaryRange工具拿到真实数据后再组织回答。Spring AI 对 Tool Calling 的支持非常简洁核心就是Tool注解。4.2 用 Tool 定义岗位分析工具我封装了三个最核心的工具薪资查询、技能库匹配、岗位数据统计。Component public class JobAnalysisTools { Tool(description 根据岗位名称和城市查询薪资范围返回薪资区间字符串输入如Java架构师、北京) public String querySalaryRange(String jobTitle, String city) { // 查询薪资带宽表 return salaryRepository.findByTitleAndCity(jobTitle, city); } Tool(description 根据技能关键词查询公司内部技能库中匹配度最高的技能说明) public String querySkillLibrary(String skillKeyword) { // 查询技能库 return skillRepository.searchByKeyword(skillKeyword); } Tool(description 统计指定时间段内某岗位的招聘数量) public String countJobPostings(String jobTitle, String startDate, String endDate) { return jobPostingRepository.countByTitleAndPeriod(jobTitle, startDate, endDate); } }Tool注解里的 description 非常关键它是模型决定是否调用这个工具的依据。description 写得模糊模型就判断不准。我踩过一个坑描述里只写“查询薪资”结果用户问“这个岗位赚多少钱”时模型死活不调工具因为“赚多少钱”和“查询薪资”的语义距离太远。后来把描述改成“查询指定岗位在指定城市的薪资区间适用于用户询问工资、薪水、待遇、年薪等场景”调用准确率明显提升。这个细节跟提示词工程一个道理工具是要被模型理解的描述要站在模型的角度去写。4.3 模型如何决定“调用哪个工具”Spring AI 在底层做了这样的处理当你把包含Tool方法的 Bean 传给ChatClient框架启动时会把这些方法序列化成模型的函数描述发给模型。模型在生成回复时如果发现用户意图与某个工具描述匹配会返回一个工具调用请求而不是直接输出文字。框架收到请求后执行对应 Java 方法把结果回传给模型模型再基于工具结果生成最终回答。从代码层面看只需要在ChatClient构建时注册工具 BeanChatClient chatClient ChatClient.builder(chatModel) .defaultTools(new JobAnalysisTools()) .build();之后正常的 prompt 流程不需要额外处理工具调用。模型可能调用一个工具也可能连续调用多个工具比如用户问“对比一下 Java 架构师和前端负责人的薪资与技能要求”模型会先调用两次薪资查询再调用技能库匹配最后汇总成对比报告。我给你提一个建议工具方法里不要做耗时的外部 IO 操作模型多轮工具调用总耗时是累加的SQL 查询尽量加缓存否则用户感知会很差。4.4 RAG 和 Tool Calling 的协同编排单纯把 RAG 和 Tool Calling 并列摆在系统里是不够的要设计好两者的执行顺序。我实测下来有两种路径。路径一用户提问后先做一次 RAG 检索把相关内容拼进 prompt同时把工具列表暴露给模型让模型在需要实时数据时主动调用工具。这种方式的优点是实现简单一次对话就能完成缺点是如果工具调用返回的数据量很大会占用大量上下文空间。路径二先做意图分类把问题拆成“文档检索类”和“结构化查询类”前者走 RAG后者直接触发固定工具。对岗位分析这种业务边界相对清晰的场景路径二更可控。我在代码里用一个轻量级的规则加上一次大模型分类把问题路由到不同的处理链路上最后统一组装生成报告。实际效果比纯靠模型自动调工具要稳定得多。这里也回应一下你搜到的“skill怎么和rag结合起来”这个问题在 Spring AI 里RAG 是一种检索技能Tool Calling 是一种动作技能两者可以通过 ChatClient 的调用链自然结合不需要额外引入复杂的 Agent 框架。大部分业务场景下一个会检索、会查数据的对话流程就已经是“Agent 化”的雏形了。5. 岗位分析系统的完整落地实现5.1 数据准备与结构化设计数据准备阶段花的时间比我预想的多。我需要把历史岗位描述、任职资格、薪资带宽整理成统一格式。JD 文档很多是 PDF我先把它们统一转成 Markdown 存在docs/jd/目录下并按照“岗位名称、技术方向、城市、日期”四个维度重命名这样后续过滤和归档都方便。结构化数据方面建了三个表job_posting存岗位发布信息岗位名、部门、发布时间、状态、salary_range存薪资带宽岗位名、城市、低值、高值、中位数、skill_library存技能说明技能名、描述、所属分类。这些表的数据通过工具方法暴露给模型。这里特别说明一下JD 文档和薪资表是两类不同来源的数据JD 文档非结构化、语言表达多种多样适合走 RAG薪资带宽是精确数值必须走工具查询两者不能混为一谈。5.2 岗位分析维度与输出格式设计分析一个岗位最终输出的内容要有固定结构不能每次格式都不一样。我跟业务方确认后定了这样几个维度核心技术栈要求从 JD 文档中提取经验与学历门槛从 JD 文档与任职资格中提取薪资区间参考从工具查询中获取常见面试考察点从历史文档中归纳岗位风险提示如技能要求过窄、薪资低于市场分位等为了让大模型稳定输出结构化结果我在提示词里明确要求模型返回 JSON并预定义好 JSON Schema。Spring AI 支持结构化输出可以通过BeanOutputConverter来做var outputConverter new BeanOutputConverter(JobAnalysisReport.class); String formatInstruction outputConverter.getFormatInstruction();在提示词末尾加上formatInstruction模型返回的文本就能自动反序列化成 Java 对象。这个机制非常实用后面接报表系统、存数据库都对得上。5.3 完整调用链路的代码实现用户输入“分析一下 Java 架构师岗位”之后后端的主流程是这样的意图判断 - 构造查询向量 - RAG 检索 - 模型决策是否调用工具 - 组装 report。核心代码片段Service public class JobAnalysisService { private final ChatClient chatClient; private final VectorStore vectorStore; public JobAnalysisService(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) { this.chatClient chatClientBuilder .defaultTools(new JobAnalysisTools()) .build(); this.vectorStore vectorStore; } public JobAnalysisReport analyze(String question) { // 1. RAG 检索 ListDocument docs vectorStore.similaritySearch( SearchRequest.query(question).withTopK(5) ); String context docs.stream() .map(Document::getContent) .collect(Collectors.joining(\n)); // 2. 组装提示词 PromptTemplate promptTemplate new PromptTemplate( 你是资深岗位分析顾问。请基于以下参考文档回答问题。 参考文档 {context} 用户问题{question} 请按照规定的 JSON 格式输出岗位分析报告。 ); Message message promptTemplate.createMessage(Map.of( context, context, question, question )); // 3. 调用 ChatClient 生成 String response chatClient.prompt() .system(systemPrompt) .user(message.getContent()) .call() .content(); // 4. 解析结构化输出 return outputConverter.convert(response); } }这里有几个细节值得说明。ChatClient以 Builder 方式注入每次构建会带上工具注册信息工具类用new还是用 Spring Bean 注入都行但如果你要在工具方法里访问数据库最好作为 Bean 注入给ChatClient.Builder。similaritySearch的参数在前面说过TopK 取 5阈值根据实际测试定。5.4 从原型到可用系统的关键改进第一个版本跑通之后业务方反馈“输出太啰嗦”一页纸的报告一半是废话。问题出在系统提示词里的角色设定太宽泛没有给模型规定详略标准。我的调整是在提示词里加了一条硬性规则“优先输出结论性内容背景性解释不超过两句数值和结论必须以文档或工具数据为准”。这一条改动让报告质量提升明显。第二个改进是缓存。岗位分析里很多查询是高频且重复的比如“Java 架构师 北京”的分析可能要支撑多个用人部门的咨询。我在服务层加了一层基于岗位名城市的本地缓存相同的分析请求直接返回上一次的结果减少了模型调用成本。RAG 检索结果也可以缓存因为企业文档更新的频率远低于查询频率。第三个改进是敏感数据处理。岗位分析结果可能涉及内部薪资和编制信息接入企业门户时要加权限控制。我的方案很简单在工具方法里加一层用户上下文判断只有具备权限的用户才能调用薪资查询工具。Spring AI 的工具调用入口跟普通 Bean 方法一样这层拦截用 Spring Security 或者自定义注解都能做关键是想起来做。6. 常见问题与排查技巧实录6.1 中文切分场景下检索效果差症状用户问“需要什么技术栈”模型回答说不知道但知识库里明明有相关文档。排查后发现是切分把“技术栈”这几个字恰好从文档块中切掉了相似度检索完全匹配不上。解决办法是前面提到的“先按逻辑块切、再按 token 上限切”的两步切分法。另外给每个文档块留 metadata比如jobType、docType检索时可以先按 metadata 过滤一遍把范围从整个知识库缩小到“JD 文档”这类子集命中率会明显提升。6.2 RAG 召回命中率hit rate低模型总在编这是我遇到最多的一个问题。一开始我以为是切分问题后来发现是查询词和文档用词风格不一致。用户问的是“待遇”文档里写的是“薪酬福利”向量相似度拉不近。这个问题的答案还是查询改写把用户问题扩展成多个语义相近的查询词组取并集。我维护了一个同义词表再加上让模型做一次查询扩展召回覆盖率能提升不少。另外一个容易被忽略的原因是文档里的噪声太多。PDF 转出来的文档带着大量格式字符和无意义内容向量库里这类垃圾占位越多真正有效的信息被稀释得越严重。清洗环节做不好后面所有环节都会受影响。所以如果你发现 hit rate 低第一步不是调检索参数而是回头看看你喂给向量库的到底是什么。6.3 Tool Calling 不调用或者乱调用工具模型不调用工具十个有八个是工具描述写得不对。我试过一个典型例子工具描述是“查询薪资”用户问“这个岗位一个月拿多少钱”模型不调用因为单纯从语义上不觉得是一回事。把描述改成覆盖用户口语化表达的版本之后调用率上来了。反过来也有乱调用的场景用户问“岗位发布的招聘数量”模型却调用了薪资查询。检查发现是工具描述里“岗位”“数量”这些词跟薪资工具的描述重叠度太高。解决办法是把工具描述写得更具区分度同时在系统提示词里明确每个工具的使用条件比如“只有用户询问金额、薪资、年包时才允许调用薪资查询工具”。6.4 上下文长度超限导致的报错岗位分析如果检索到 8 个文档块每个 800 字再加上工具返回的结构化数据和历史对话上下文很容易顶到模型限制。我遇到过几次调用直接报错的情况。解决策略有三控制 RAG 检索块数量和质量TopK 不要贪大、裁掉多余的历史对话只保留最近两轮、工具返回的数据做摘要而不是全量返回。这三种策略组合起来在绝大多数场景下都能把上下文压在模型限制之内。6.5 问题速查表问题可能原因快速处理向量写入报维度错误Embedding 模型维度与表结构不一致删表重建统一模型检索结果都是无关内容文档噪声多切片粒度不合理做清洗按逻辑块切分模型回答与文档无关提示词未显式要求引用文档强化“必须引用参考文档”约束工具始终不触发工具 description 语义描述不清改写描述覆盖口语化表达响应速度慢工具调用多次 大文档片段加缓存压缩上下文裁剪历史输出格式不稳定提示词未给样例使用 BeanOutputConverter 强约束向量库检索耗时高数据量大索引未建给 PGVector 加 HNSW 索引6.6 几个容易忽视的细节Spring AI 版本升级导致的 API 变动是最容易忽视的。我在从 0.9 升级到 1.0 时ChatClient的构建方式就变了之前用ChatClient.create(chatModel)后来改成 Builder 模式。这类变动在 release note 里都有但实际项目里很容易被忽略升级完直接编译报错反过头再翻文档很耽误时间。建议在 pom 里锁定 Spring AI 版本升级前先跑一遍现有的 AI 相关单测。另一个细节是向量库的数据更新策略。企业文档不是一成不变的JD 更新了、任职资格调整了旧的向量如果不删除检索时新旧内容混在一起结论就容易过时。Spring AI 的VectorStore支持按 metadata 过滤删除我建议开工前先想好文档版本管理机制最简单的做法是文档 ID 带版本号更新时删旧版本再写入新版本。一些个人实践经验项目做下来体会最深的一点是RAG 和 Tool Calling 都不是银弹但它们组合在一起确实能解决企业里很普遍的知识割裂问题。岗位分析只是一个切入点同样的架构完全可以迁移到产品说明书问答、内部制度查询、售前方案生成等场景。你只需换一套文档、换几个工具方法主体框架不动。最后分享一个小建议别一上来就追求功能大而全。先把一条最核心的链路跑通——比如“输入岗位名输出分析报告”——让业务方看到可用的东西再逐步叠加对比分析、数据可视化、批量分析这类功能。系统是迭代出来的尤其涉及 AI 效果的部分先有基线才有优化的起点。
返回列表