Spring Boot 3.4 + Spring AI 1.0.0-M6 实战:手把手教你用Ollama本地模型打造一个能“思考”的Agent

张开发
2026/4/15 20:59:52 15 分钟阅读

分享文章

Spring Boot 3.4 + Spring AI 1.0.0-M6 实战:手把手教你用Ollama本地模型打造一个能“思考”的Agent
Spring Boot 3.4 Spring AI 1.0.0-M6 实战构建具备工具调用能力的本地AI代理当Java开发者第一次看到AI代理这个词时脑海中浮现的可能是科幻电影中的智能助手。但实际上借助Spring AI框架和Ollama本地模型我们完全可以在企业级Java应用中构建出能理解指令、调用工具并给出智能响应的AI代理。这不再是未来科技而是当下每个Spring Boot开发者都能掌握的实用技能。想象这样一个场景你的企业内部系统收到用户请求下周三下午3点安排与客户的会议并计算从公司到客户地址的驾车时间。传统做法需要开发复杂的业务逻辑链而现在一个集成了工具调用能力的AI代理可以自动分解任务、查询日历API、调用地图服务最终生成完整响应。这就是我们要实现的会思考的Agent。1. 环境准备与基础配置在开始构建AI代理之前我们需要搭建好开发环境。与常见的云模型调用不同这次我们选择Ollama作为本地模型运行环境这不仅能保护数据隐私还能避免网络延迟带来的体验问题。1.1 Ollama本地模型部署首先下载并安装Ollama支持Windows/Mac/Linux然后通过命令行拉取适合的中小型模型ollama pull qwen:7b # 阿里云开源的70亿参数模型 ollama pull mistral:7b-instruct # 适合指令跟随的轻量模型启动模型服务非常简单ollama serve # 默认监听11434端口为了验证模型是否正常工作可以尝试用curl测试curl http://localhost:11434/api/generate -d { model: qwen:7b, prompt: 用Java写一个快速排序 }1.2 Spring Boot项目初始化使用Spring Initializr创建项目时除了常规的Web依赖需要特别添加dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-ollama-spring-boot-starter/artifactId version1.0.0-M6/version /dependency在application.yml中配置模型连接spring: ai: ollama: base-url: http://localhost:11434 chat: options: model: qwen:7b temperature: 0.7 # 控制创造性 top-p: 0.9 # 核采样参数提示对于生产环境建议在配置中添加连接超时和重试策略避免服务不可用导致线程阻塞。2. Spring AI核心架构解析Spring AI的设计哲学是提供统一的抽象接口让开发者无需关心底层模型差异。理解这个架构对构建可靠代理至关重要。2.1 三层抽象模型Spring AI的抽象主要分为三个层次抽象层核心接口功能描述模型适配层ChatModel/EmbeddingModel对接不同厂商的模型API提示工程层PromptTemplate管理提示词模板和变量替换代理协调层ChatClient/ToolCaller组合模型调用与工具执行一个典型的对话请求处理流程如下用户输入经过预处理后构建Prompt对象ChatModel处理Prompt生成原始响应如果响应包含工具调用请求ToolCaller执行具体方法工具执行结果重新注入Prompt进行二次模型调用最终响应返回给用户2.2 消息系统设计Spring AI的消息系统采用了类似ChatGPT的对话结构public class Message { private MessageType messageType; // SYSTEM, HUMAN, AI private String content; private MapString, Object properties; // 元数据 }这种设计允许我们在对话中嵌入不同类型的消息系统消息设定AI行为准则你是一个专业的Java助手用户消息实际的用户输入AI消息模型生成的响应工具消息工具调用的输入输出3. 实现工具调用能力真正的AI代理区别于普通聊天机器人的核心能力就是工具调用。让我们实现两个典型工具时间查询和数学计算。3.1 基础工具实现首先创建工具类并用Tool注解标记public class CalculatorTools { Tool(name TimeQuery, description 查询当前系统时间) public String getCurrentTime( Parameter(description 时区例如Asia/Shanghai) String timezone) { return ZonedDateTime.now(ZoneId.of(timezone)) .format(DateTimeFormatter.ISO_ZONED_DATE_TIME); } Tool(name MathCalculator, description 执行数学运算) public double calculate( Parameter(description 数学表达式如(35)*2) String expression) { ScriptEngine engine new ScriptEngineManager().getEngineByName(graal.js); try { return Double.parseDouble(engine.eval(expression).toString()); } catch (Exception ex) { throw new RuntimeException(计算失败: ex.getMessage()); } } }注意Java 15移除了Nashorn引擎建议使用GraalJS或添加nashorn-core依赖。3.2 工具注册与发现Spring AI提供了自动化的工具注册机制Configuration public class ToolConfig { Bean public ToolCallbackProvider toolProvider() { return ToolCallbackProvider.from( new CalculatorTools(), // 可以继续添加其他工具类 ); } }工具调用时Spring AI会分析模型响应识别工具调用意图匹配参数名称和类型执行对应方法并将结果返回给模型模型整合结果生成最终响应3.3 高级工具设计模式对于企业级应用我们可以采用更复杂的设计工具网关模式Tool(name ServiceGateway) public Object callInternalService( Parameter String serviceName, Parameter MapString, Object params) { // 统一路由到内部微服务 return serviceDispatcher.dispatch(serviceName, params); }异步工具执行Async Tool(name AsyncQuery) public CompletableFutureString queryDatabase(String sql) { return CompletableFuture.supplyAsync(() - jdbcTemplate.query(sql)); }工具权限控制Tool public String sensitiveOperation(Context SecurityContext ctx) { if (!ctx.hasRole(ADMIN)) { throw new AccessDeniedException(权限不足); } return doSensitiveWork(); }4. 构建完整AI代理服务现在我们将各个模块组合成完整的代理服务处理更复杂的业务场景。4.1 代理控制器实现RestController public class AgentController { private final ChatClient chatClient; public AgentController(ChatModel model, ToolCallbackProvider tools) { this.chatClient ChatClient.builder(model) .defaultSystem( 你是一个企业级AI助手专门处理办公自动化任务。 当用户请求涉及工具调用时你必须严格遵循 1. 确认用户意图是否需要工具 2. 明确告知用户将执行什么操作 3. 获得用户确认后再调用工具 ) .defaultTools(tools) .build(); } PostMapping(/ask) public FluxString askAgent(RequestBody UserQuery query) { return chatClient.prompt() .user(query.text()) .stream() .content(); } }4.2 对话状态管理真正的代理需要维护对话上下文Service public class SessionService { private final MapString, ListMessage sessions new ConcurrentHashMap(); public void addMessage(String sessionId, Message message) { sessions.computeIfAbsent(sessionId, k - new ArrayList()) .add(message); } public ListMessage getContext(String sessionId) { return sessions.getOrDefault(sessionId, List.of()); } }在控制器中集成上下文PostMapping(/ask) public FluxString askAgent( RequestHeader(X-Session-ID) String sessionId, RequestBody UserQuery query) { ListMessage history sessionService.getContext(sessionId); Prompt prompt new Prompt(query.text(), history); return chatClient.prompt(prompt) .stream() .doOnNext(response - { sessionService.addMessage(sessionId, new AiMessage(response.content())); }) .content(); }4.3 性能优化技巧流式响应GetMapping(/stream) public SseEmitter streamChat(RequestParam String message) { SseEmitter emitter new SseEmitter(); chatClient.prompt() .user(message) .stream() .subscribe( chunk - emitter.send(chunk.getContent()), emitter::completeWithError, emitter::complete ); return emitter; }缓存策略Cacheable(value aiResponses, key #query.hashCode()) public String getCachedResponse(String query) { return chatClient.prompt(query).call().content(); }负载测试指标场景平均响应时间吞吐量 (req/s)错误率纯文本问答320ms450.1%简单工具调用580ms280.5%复杂工具链1.2s121.2%5. 企业级应用实践将AI代理集成到实际业务中需要考虑更多工程化因素。5.1 安全防护措施输入验证Bean public ValidatingChatClient validatingClient(ChatClient.Builder builder) { return builder .defaultOptions(options - options .withFilter(input - { if (containsMaliciousContent(input)) { throw new IllegalArgumentException(输入包含危险内容); } })) .build(); }输出过滤public class OutputSanitizer implements ChatModel { private final ChatModel delegate; Override public ChatResponse call(Prompt prompt) { ChatResponse response delegate.call(prompt); return filterSensitiveInfo(response); } }5.2 监控与可观测性集成Micrometer监控关键指标Bean public MeterRegistryCustomizerMeterRegistry aiMetrics() { return registry - { Timer.builder(ai.request.time) .description(AI请求处理时间) .register(registry); Counter.builder(ai.tool.calls) .tag(tool, toolName) .register(registry); }; }建议监控的黄金指标请求延迟P50/P95/P99工具调用成功率令牌使用效率会话超时率5.3 持续交付流水线典型的AI代理部署流程模型测试阶段单元测试验证工具调用逻辑集成测试检查端到端流程对抗测试确保安全性蓝绿部署策略# 新版本测试 kubectl apply -f ai-agent-canary.yaml # 流量切换 kubectl set image deployment/ai-agent *new-image:tag回滚机制# 快速回滚到上一版本 kubectl rollout undo deployment/ai-agent6. 前沿技术集成随着Spring AI生态的演进我们可以集成更先进的能力提升代理智能水平。6.1 记忆增强设计实现长期记忆存储public class VectorMemoryService { private final VectorStore vectorStore; public void remember(String sessionId, String information) { vectorStore.add(List.of( new Document(information, Map.of(sessionId, sessionId)) )); } public ListString recall(String sessionId, String query) { return vectorStore.similaritySearch(query) .stream() .filter(doc - sessionId.equals(doc.getMetadata().get(sessionId))) .map(Document::getContent) .toList(); } }6.2 多模态扩展处理图像和文档Tool(name DocumentAnalyzer) public String analyzeDocument(Parameter MultipartFile file) { if (file.getContentType().startsWith(image/)) { return imageModel.analyze(file.getBytes()); } else { return documentAiService.process(file); } }6.3 分布式工具调用跨服务工具执行Tool(name RemoteService) public String callMicroservice( Parameter String serviceName, Parameter MapString, Object params) { return webClient.post() .uri(serviceRegistry.getUri(serviceName)) .bodyValue(params) .retrieve() .bodyToMono(String.class) .block(); }在实际项目中我发现最常出现的问题是工具参数匹配失败。比如当用户说计算从北京到上海的里程时模型可能生成distance_calculator(start北京, end上海)但实际工具参数可能是fromCity和toCity。解决这类问题需要在工具描述中使用与自然语言高度一致的参数名或者在Prompt中加入参数映射说明。

更多文章