别只调API了!在Spring AI里给ChatGPT装上‘天气查询’外挂(函数调用实战)

别只调API了!在Spring AI里给ChatGPT装上‘天气查询’外挂(函数调用实战) 让Spring AI学会查天气函数调用实战与智能代理设计想象一下你的AI助手不仅能回答北京今天天气如何还能直接调用后台服务返回实时数据——这正是Spring AI的函数调用能力带来的变革。对于已经熟悉基础对话开发的Java工程师而言掌握这项技能意味着能将大语言模型真正转化为业务场景中的智能代理。本文将手把手带你实现一个天气查询函数并深入探讨这种模式在复杂系统中的设计哲学。1. 为什么函数调用是AI集成的分水岭传统API调用就像让盲人转述天气预报——开发者需要预先定义所有可能的查询参数和返回格式。而函数调用则是给AI装上了感官系统让它能自主决定何时调用、如何解析外部服务。这种能力转变带来了三个维度的提升动态决策模型根据对话上下文自主触发函数无需硬编码条件判断服务编排单个查询可串联多个函数调用如先查天气再推荐穿搭认知增强突破训练数据的时间限制获取实时信息在Spring AI中这一切通过FunctionCallback机制实现。与直接调用REST API不同你需要先教会AI两件事什么情况下应该调用这个函数通过自然语言描述如何理解函数的输入输出通过Schema定义2. 构建天气服务函数我们先从核心的MockWeatherService实现开始。这个示例类展示了函数调用的标准契约public class WeatherFunction implements FunctionWeatherFunction.Request, WeatherFunction.Response { public record Request(String location, Unit unit) {} public record Response(double temp, Unit unit, String condition) {} public enum Unit { C, F } Override public Response apply(Request request) { // 实际项目这里替换为真实API调用 return new Response( ThreadLocalRandom.current().nextDouble(-10, 35), request.unit(), List.of(晴,多云,小雨).get(ThreadLocalRandom.current().nextInt(3)) ); } }关键设计要点使用Java 16的record类型定义DTO减少样板代码温度单位采用枚举值避免魔法字符串响应中增加天气状况描述增强实用性3. 函数注册与提示工程注册函数时需要精心设计元数据这直接影响AI的调用决策质量。下面是进阶配置示例FunctionCallbackWrapper.builder(new WeatherFunction()) .withName(GetCurrentWeather) .withDescription(获取指定地点的实时天气数据包括温度值和天气状况) .withInputType(WeatherFunction.Request.class) .withResponseConverter(response - { // 自定义响应转换逻辑 var temp response.temp(); return String.format(地点%s 温度%.1f°%s 天气%s, request.location(), temp, response.unit().name(), response.condition()); }) .build()提示词设计技巧在用户问题中暗示需要实时数据如当前、最新等关键词使用withFunction()明确指定允许调用的函数示例对话Prompt prompt new Prompt( 对比上海和深圳当前天气用摄氏度显示分析哪个城市更适合户外活动, OpenAiChatOptions.builder() .withFunction(GetCurrentWeather) .withTemperature(0.2) // 降低随机性 .build() );4. 解析与错误处理实战函数调用的响应需要特殊处理完整流程应包含以下环节ChatResponse response chatClient.call(prompt); ListFunctionCall functionCalls response.getResults() .stream() .flatMap(r - r.getMetadata().getFunctionCalls().stream()) .toList(); if (!functionCalls.isEmpty()) { functionCalls.forEach(call - { try { Object result functionRegistry.get(call.getName()) .apply(call.getArguments()); // 将结果重新注入对话上下文 } catch (Exception e) { log.error(函数调用失败: {}, call.getName(), e); } }); }常见问题解决方案问题现象可能原因调试方法函数未被调用描述信息不清晰检查withDescription是否准确参数解析失败Schema类型不匹配验证record字段命名响应格式错误转换器逻辑异常添加响应日志5. 生产级扩展方案基础实现后还需要考虑以下工程化问题性能优化为函数调用添加缓存层Caffeine注解方案Cacheable(value weather, key #request.location()) public Response apply(Request request) { // 真实API调用 }熔断降级集成Resilience4j处理第三方服务超时CircuitBreaker(name weatherService, fallbackMethod fallbackWeather) public Response apply(Request request) { // ... } private Response fallbackWeather(Request request, Exception e) { return new Response(25.0, Unit.C, 数据暂不可用); }监控指标通过Micrometer暴露关键指标MeterRegistry.counter(ai.function.calls, name, weather) .increment();6. 智能代理设计模式将单一函数扩展为智能代理系统时推荐采用以下架构用户请求 → 路由决策器 → 功能模块集 ↑ ↓ 上下文记忆 ← 结果聚合器具体实现策略按领域划分功能模块天气、日历、电商等使用向量数据库维护对话历史实现优先级调度算法处理并发调用在Spring生态中可以结合Spring StateMachine管理复杂对话状态StateMachineState, Event stateMachine ...; stateMachine.sendEvent(new FunctionCallEvent(weather));实际项目中我们发现最影响体验的不是技术实现而是函数描述的精确度。曾经因为将获取天气描述为查询气象数据导致AI在用户问会下雨吗时没有触发函数。这提醒我们设计函数时要用最终用户的自然语言习惯而不是开发者的技术术语。