当前位置: 首页 > news >正文

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> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-ollama-spring-boot-starter</artifactId> <version>1.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组合模型调用与工具执行

一个典型的对话请求处理流程如下:

  1. 用户输入经过预处理后构建Prompt对象
  2. ChatModel处理Prompt生成原始响应
  3. 如果响应包含工具调用请求,ToolCaller执行具体方法
  4. 工具执行结果重新注入Prompt进行二次模型调用
  5. 最终响应返回给用户

2.2 消息系统设计

Spring AI的消息系统采用了类似ChatGPT的对话结构:

public class Message { private MessageType messageType; // SYSTEM, HUMAN, AI private String content; private Map<String, 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 = "数学表达式,如(3+5)*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会:

  1. 分析模型响应,识别工具调用意图
  2. 匹配参数名称和类型
  3. 执行对应方法并将结果返回给模型
  4. 模型整合结果生成最终响应

3.3 高级工具设计模式

对于企业级应用,我们可以采用更复杂的设计:

工具网关模式

@Tool(name = "ServiceGateway") public Object callInternalService( @Parameter String serviceName, @Parameter Map<String, Object> params) { // 统一路由到内部微服务 return serviceDispatcher.dispatch(serviceName, params); }

异步工具执行

@Async @Tool(name = "AsyncQuery") public CompletableFuture<String> 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 Flux<String> askAgent(@RequestBody UserQuery query) { return chatClient.prompt() .user(query.text()) .stream() .content(); } }

4.2 对话状态管理

真正的代理需要维护对话上下文:

@Service public class SessionService { private final Map<String, List<Message>> sessions = new ConcurrentHashMap<>(); public void addMessage(String sessionId, Message message) { sessions.computeIfAbsent(sessionId, k -> new ArrayList<>()) .add(message); } public List<Message> getContext(String sessionId) { return sessions.getOrDefault(sessionId, List.of()); } }

在控制器中集成上下文:

@PostMapping("/ask") public Flux<String> askAgent( @RequestHeader("X-Session-ID") String sessionId, @RequestBody UserQuery query) { List<Message> 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 MeterRegistryCustomizer<MeterRegistry> aiMetrics() { return registry -> { Timer.builder("ai.request.time") .description("AI请求处理时间") .register(registry); Counter.builder("ai.tool.calls") .tag("tool", "toolName") .register(registry); }; }

建议监控的黄金指标:

  1. 请求延迟(P50/P95/P99)
  2. 工具调用成功率
  3. 令牌使用效率
  4. 会话超时率

5.3 持续交付流水线

典型的AI代理部署流程:

  1. 模型测试阶段

    • 单元测试验证工具调用逻辑
    • 集成测试检查端到端流程
    • 对抗测试确保安全性
  2. 蓝绿部署策略

    # 新版本测试 kubectl apply -f ai-agent-canary.yaml # 流量切换 kubectl set image deployment/ai-agent *=new-image:tag
  3. 回滚机制

    # 快速回滚到上一版本 kubectl rollout undo deployment/ai-agent

6. 前沿技术集成

随着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 List<String> 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 Map<String, Object> params) { return webClient.post() .uri(serviceRegistry.getUri(serviceName)) .bodyValue(params) .retrieve() .bodyToMono(String.class) .block(); }

在实际项目中,我发现最常出现的问题是工具参数匹配失败。比如当用户说"计算从北京到上海的里程"时,模型可能生成distance_calculator(start="北京", end="上海"),但实际工具参数可能是fromCitytoCity。解决这类问题需要在工具描述中使用与自然语言高度一致的参数名,或者在Prompt中加入参数映射说明。

http://www.jsqmd.com/news/646643/

相关文章:

  • header标签怎么用_网页头部区域使用方法【教程】
  • 战略规划管理化技术IT战略与企业战略对齐
  • HTML打包EXE工具标签页模式详解 - 像浏览器一样管理多个页面
  • 告别虚拟机卡顿:在WSL2的Ubuntu 20.04上丝滑搭建QGroundControl开发环境
  • uniapp地图实战:高德API与polyline绘制动态导航轨迹
  • ModelSim覆盖率统计从0%到100%:新手最常遇到的5个坑及排查指南
  • ComfyUI融合WAN2.1:单图驱动LoRA炼成IP角色全场景通用模型
  • 如何高效进行SWF逆向分析:JPEXS专业安全工具实战指南
  • 如何快速解除极域电子教室控制:面向学生的完整指南
  • Harness Engineering 深度学习指南
  • mysql数据库占用空间优化_MyISAM与InnoDB存储结构差异
  • 阿克曼公式在控制系统设计中的实战应用
  • Java学习之 EasyExcel
  • 从零上手Cursor:AI编程助手的核心功能与实战演练
  • Waifu2x-Extension-GUI终极实战指南:三步解决图像模糊、视频卡顿的完整方案
  • Midscene.js企业级容器化架构设计:高可用AI自动化服务部署方案
  • RPG Maker解密工具终极指南:3分钟掌握游戏资源提取技巧
  • MATLAB图像分割实战:从Otsu阈值到形态学滤波,手把手教你处理一张飞机图片
  • Quartus II 13.0入门指南:VHDL仿真全流程解析
  • 树莓派4B+DHT11温湿度监控:从Python库到GPIO底层驱动,哪种方案更适合你?
  • FreeRTOS在智能家居中的实战:如何用任务管理优化STM32的传感器响应与功耗
  • AI 日报 - 2026年4月15日(周三)
  • 数学建模竞赛数据预处理全攻略:从清洗到增强的完整流程与代码实践
  • OpenRGB:免费开源工具如何一站式管理所有RGB灯光设备?
  • OpenWrt在VMWare中的安装与配置全攻略
  • 2026年3月金属滤袋门店选哪家,粉尘超低排放/高温滤袋/金属滤袋,金属滤袋直销厂家选哪家 - 品牌推荐师
  • 新手避坑指南:超声波探伤仪A扫波形图到底怎么看?从杂波识别到缺陷定级的实战解析
  • PyTorch实战:用Attention Transfer给模型‘开小灶’,提升小模型性能(附完整代码)
  • Wand-Enhancer终极指南:如何免费解锁WeMod完整功能
  • 用MATLAB复现DSSS+8PSK通信系统:从扩频码生成到误码率曲线对比(附完整代码)