尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java 转大模型开发:把方案拆到可执行

Java 转大模型开发:把方案拆到可执行 这篇不先堆名词。我们把《大模型岗位变了Java工程师该补的还是算法吗》拆成几级台阶看完至少知道下一步该学什么、该练什么。摘要从Java后端转大模型应用开发很多人以为学会LangChain或Spring AI就能上手。但真实项目里Demo跑通只是起点。本文复盘一个文档问答Agent从本地Demo到生产上线的踩坑过程重点讲权限配置、日志追踪、可观测性这三项工程硬技能——它们往往比模型调用本身更考验后端开发者的适应能力。---目录Java开发者的天然优势需要补齐的AI技能地图Spring AI vs LangChain4j选型与实战真实案例一个Agent上线第一天的翻车记录排查过程权限、日志、可观测性逐一定位代码解释失败原因拆解业务错误、配置错误、环境错误适用边界什么时候该用、什么时候不该用项目练习建议面试准备要点总结---Java开发者的天然优势转大模型应用开发Java背景的人其实有优势只是很多人没意识到。我见过不少从Python转过来的人模型调参、Prompt工程上手很快但一碰到权限控制、链路追踪、异常重试这些工程问题就露怯。Java开发者恰恰相反——Spring生态里封装的过滤器、拦截器、事务管理、日志体系和大模型应用需要的工程能力是高度重合的。比如大模型应用里最常见的防止Prompt注入问题本质上和Web安全里的XSS过滤是一个思路再比如Agent调用多个工具时的状态一致性和分布式事务的补偿机制异曲同工。优势是有的但短板也很明显对概率性输出、Token计费、Embedding这些概念陌生容易用确定性思维去处理不确定性问题。---需要补齐的AI技能地图我从Java转大模型应用开发补了这些东西按优先级排第一层基础概念1-2周Transformer基本原理不需要推导公式但要理解Attention机制为什么能处理长文本Token的概念和计费逻辑这是成本控制的起点Embedding和向量检索的基本原理RAG的基石第二层框架使用2-4周Spring AI或LangChain4j至少精通一个我选的是Spring AI因为和Java生态无缝对接Prompt工程的基本技巧System Prompt、Few-shot、Chain-of-ThoughtRAG的基本实现文档切分、Embedding、向量存储、检索重排第三层工程化能力持续积累权限控制用户身份传递、数据隔离日志追踪每个Agent调用链路的完整记录可观测性延迟、Token消耗、成功率、错误分类第三层是最容易被忽视的也是Demo和上线之间最大的鸿沟。---Spring AI vs LangChain4j选型与实战这两个框架我都用过说下真实感受。Spring AI的优势是和Spring Boot生态无缝集成配置方式熟悉对于Java开发者来说学习曲线更平滑。它的核心抽象是ChatClient写起来很自然ChatResponse response chatClient.prompt() .system(你是一个文档助手只根据提供的上下文回答问题) .user(question) .call() .response();LangChain4j的优势是生态更丰富支持更多模型提供商和工具调用方式但配置相对繁琐需要理解更多概念。我的建议如果你已经有Spring Boot经验优先选Spring AI如果想接触更多AI原生概念比如Agent、Tool Calling可以同步看LangChain4j。---真实案例一个Agent上线第一天的翻车记录去年我做了一个内部文档问答Agent基于Spring AI PostgreSQL向量扩展pgvector。本地测试一切正常RAG检索准确率90%以上响应速度也在可接受范围内。上线第一天就出了问题现象1部分用户反馈回答质量很差答非所问现象2系统日志里出现大量401错误但错误信息不明确现象3高峰期响应时间从2秒飙升到15秒这三个问题分别对应了权限、日志、可观测性。---排查过程权限、日志、可观测性逐一定位问题一回答质量差排查动作我抓取了几个典型问题的完整调用链发现一个问题——不同用户输入相同问题返回的答案差异很大。进一步分析发现Prompt里没有严格限制上下文来源。系统Prompt写的是根据提供的信息回答问题但没有明确说只使用提供的上下文不要使用训练数据中的知识。这导致模型在上下文不足时会用自己的训练知识脑补答案而不同用户的会话历史不同模型的脑补方向也不同。修复方案修改System Prompt加入明确的边界约束你是一名企业内部文档助手。你的回答必须严格基于以下上下文信息 如果上下文中没有相关信息请明确回复根据现有文档无法回答该问题 不要尝试使用其他知识进行推测。 上下文 {context} 用户问题 {question}问题二401错误排查动作日志里只有Authentication failed没有用户信息无法定位是哪个用户、在什么场景下触发。我意识到问题出在权限传递上。Agent调用链是用户 → API网关 → 业务服务 → 向量检索服务 → 大模型。但用户身份只在API网关层验证后续服务没有接收到用户上下文。修复方案在请求头中透传用户信息并在每个服务层记录用户ID和权限级别// 在Filter中注入用户上下文 public class AuthContextFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest (HttpServletRequest) request; String userId httpRequest.getHeader(X-User-Id); String userRole httpRequest.getHeader(X-User-Role); // 设置到ThreadLocal方便后续服务获取 UserContext.setUserId(userId); UserContext.setUserRole(userRole); chain.doFilter(request, response); } }然后在日志里记录完整的用户上下文这样出了问题可以快速定位。问题三高峰期响应慢排查动作通过链路追踪发现问题出在向量检索。高峰期大量请求同时查询pgvector导致数据库连接池耗尽。更严重的是我没有对向量检索做降级处理。当数据库响应超时整个请求卡死用户体验极差。修复方案增加缓存层和超时控制Service public class DocumentRetrievalService { Cacheable(value rag-cache, key #question - #topK) public ListDocumentChunk retrieveWithContext(String question, int topK) { // 向量检索逻辑 return vectorStore.similaritySearch(question, topK); } public ListDocumentChunk retrieveWithFallback(String question, int topK) { try { // 设置超时避免卡死 return CompletableFuture.supplyAsync( () - retrieveWithContext(question, topK), executor ).get(2, TimeUnit.SECONDS); } catch (TimeoutException e) { // 降级返回空列表让模型基于通用知识回答 log.warn(向量检索超时降级处理. question: {}, question); return Collections.emptyList(); } catch (Exception e) { log.error(向量检索异常, e); return Collections.emptyList(); } } }---代码解释下面对上面三处关键代码做逐段拆解讲清楚输入、核心逻辑、输出和异常处理。1. ChatClient 调用链ChatResponse response chatClient.prompt() .system(你是一个文档助手只根据提供的上下文回答问题) .user(question) .call() .response();输入question是用户自然语言问题字符串类型。核心逻辑这是Spring AI的链式调用写法。prompt()创建一个请求构建器.system()设置系统提示词.user()设置用户输入.call()发起实际的网络请求调用大模型.response()提取响应体。整个调用是同步阻塞的。输出ChatResponse对象包含模型返回的消息内容、Token消耗统计、以及可能的元数据。异常处理这段代码没有显式异常处理。实际生产环境中call()可能抛出ResourceAccessException网络超时或IllegalStateException模型返回异常需要在外层用 try-catch 包裹并配合重试机制。2. AuthContextFilter 用户上下文透传public class AuthContextFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest (HttpServletRequest) request; String userId httpRequest.getHeader(X-User-Id); String userRole httpRequest.getHeader(X-User-Role); UserContext.setUserId(userId); UserContext.setUserRole(userRole); chain.doFilter(request, response); } }输入HTTP请求对象期望请求头中包含X-User-Id和X-User-Role两个字段。核心逻辑这是一个Servlet Filter在请求进入业务逻辑之前执行。它从请求头中读取用户身份信息存入UserContext一个ThreadLocal封装类。使用ThreadLocal的原因是在同一个请求线程内任何代码都能通过UserContext.getUserId()获取当前用户无需逐层传递参数。输出无直接返回值副作用是将用户上下文绑定到当前线程。chain.doFilter()继续执行后续过滤器和Controller。异常处理如果请求头缺少X-User-IduserId会是 null。生产环境应该在这里做校验对缺少用户信息的请求直接返回401而不是让null渗透到业务层。另外Filter执行完毕后应该调用UserContext.clear()清理ThreadLocal防止线程池复用时的数据污染。3. DocumentRetrievalService 向量检索与降级Service public class DocumentRetrievalService { Cacheable(value rag-cache, key #question - #topK) public ListDocumentChunk retrieveWithContext(String question, int topK) { return vectorStore.similaritySearch(question, topK); } public ListDocumentChunk retrieveWithFallback(String question, int topK) { try { return CompletableFuture.supplyAsync( () - retrieveWithContext(question, topK), executor ).get(2, TimeUnit.SECONDS); } catch (TimeoutException e) { log.warn(向量检索超时降级处理. question: {}, question); return Collections.emptyList(); } catch (Exception e) { log.error(向量检索异常, e); return Collections.emptyList(); } } }输入question是用户问题topK是希望检索返回的文档片段数量。核心逻辑retrieveWithContext方法带有Cacheable注解Spring会在调用前先检查缓存。如果相同questiontopK的组合已经计算过直接返回缓存结果跳过向量检索。retrieveWithFallback是对外暴露的方法它用CompletableFuture.supplyAsync将检索任务提交到线程池异步执行并通过.get(2, TimeUnit.SECONDS)设置2秒超时。输出返回ListDocumentChunk即与问题最相关的文档片段列表。如果检索超时或异常返回空列表由上层服务决定如何让模型基于空上下文回答。异常处理这里分两层处理。外层retrieveWithFallback捕获TimeoutException和通用Exception超时和异常都走降级逻辑——记录日志并返回空列表。内层retrieveWithContext的Cacheable本身不会抛异常但如果底层vectorStore.similaritySearch抛出异常会被外层捕获。需要注意的是CompletableFuture.get()还可能抛出InterruptedException和ExecutionException前者需要恢复中断标志后者需要展开底层异常。---失败原因拆解业务错误、配置错误、环境错误这三个问题对应的错误类型完全不同业务错误Prompt设计不合理导致模型行为不符合预期。这类错误的特点是功能上能跑但结果不可控。排查方法是分析输出质量而不是看日志有没有报错。配置错误用户上下文没有透传导致权限丢失。这类错误的特点是日志里有错误但原因不明显。排查方法是从请求入口到出口逐层检查参数传递。环境错误数据库连接池配置不合理高峰期资源耗尽。这类错误的特点是低负载时正常高负载时出问题。排查方法是在压测环境下复现观察资源使用情况。很多开发者容易混淆这三类错误。比如把Prompt问题当成模型质量问题把配置问题当成代码Bug。区分它们的办法是业务错误看输出质量配置错误看参数传递环境错误看资源监控。---适用边界什么时候该用、什么时候不该用上面提到的权限、日志、可观测性方案有明确的适用边界适用场景企业内部的Agent应用需要多用户隔离需要审计和合规要求的场景高并发、需要稳定性的生产环境不适用场景个人学习项目Demo阶段可以简化外部开放的Agent应用权限模型可能完全不同纯实验性质的探索工程化投入不划算取舍的关键是你的应用是否需要多人协作、是否需要审计、是否需要稳定性保证。如果答案都是否可以先不做完整的工程化等真正需要的时候再补。---项目练习建议如果你想系统练习建议按这个顺序1. 第一个项目基于Spring AI实现一个简单的RAG问答系统重点练习Prompt工程和文档切分2. 第二个项目给第一个项目加上用户权限控制练习上下文透传和日志记录3. 第三个项目增加链路追踪和指标监控练习可观测性建设每个项目都要有明确的上线标准比如支持10个并发用户、响应时间P99小于3秒、错误日志可追溯。---面试准备要点现在大模型岗位的面试除了算法和框架越来越关注工程化能力。准备时可以突出以下几点讲清楚你做过的项目里Agent调用链的完整结构说明你在权限、日志、可观测性方面做过哪些具体工作准备一个你踩过的坑以及你是如何定位和解决的我上次面试面试官问的问题不是怎么实现RAG而是你的Agent怎么处理用户权限隔离和出现异常时你怎么定位问题。这两个问题正是我上面案例里踩过的坑。---总结从Java转大模型应用开发算法不是最大的门槛工程化能力才是。Demo能跑和能上线之间隔着权限、日志、可观测性这三道坎。我的建议是不要急着追新模型、新框架先把Spring AI或LangChain4j用扎实再补上工程化的短板。这样在真实项目里你才不会成为Demo工程师。资料展示下面是我整理的AI大模型学习资料和工具包预览适合收藏后按主题逐步学习。需要这份AI大模型资料清单的话在评论区回复「清单」即可我会根据大家的问题继续补充对应的实战内容。
返回列表