【Spring AI实战】第8章 SpringAI Function Calling 函数调用实战
1. Function Calling 核心原理:大模型自动调用本地业务接口
Function Calling 的核心原理是让大语言模型(LLM)能够理解并自动调用开发者定义的本地业务函数/接口 ,从而将 LLM 的通用能力与特定业务逻辑和数据无缝结合。
核心思想:将函数/工具作为“扩展能力”暴露给 LLM
传统 LLM 只能基于训练数据进行文本生成。Function Calling 机制允许 LLM 在对话过程中,识别用户意图是否需要调用外部工具 ,并生成结构化请求 来调用这些工具,最后将工具执行结果融入回复中。
Spring AI 在此过程中扮演 “桥梁” 角色:
- 定义工具 :将本地 Java 方法(或 REST API)包装成 LLM 可识别的“函数”。
- 对话管理 :在上下文中告知 LLM 可用的函数列表及其描述。
- 解析与执行 :当 LLM 返回“调用请求”时,Spring AI 解析并反射调用对应的 Java 方法。
- 结果整合 :将方法返回值返回给 LLM,由 LLM 生成最终用户回复。
技术实现流程(以 OpenAI 函数调用为例)
步骤 1:定义业务函数(Java 方法)
@Bean
public Function<WeatherRequest, WeatherResponse> weatherFunction() {
return request -> {
// 实际业务逻辑:查询数据库或调用外部 API
return new WeatherResponse(request.location(), 22, "Sunny");
};
}
步骤 2:向 LLM 声明函数元数据
Spring AI 会将 Java 函数转换为 LLM 所需的 JSON Schema 格式:
{
"name": "getWeather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称"
}
},
"required": ["location"]
}
}
关键点 :LLM 只接收函数描述,而非具体代码。
步骤 3:对话触发函数调用
用户提问:
“北京天气怎么样?”
LLM 分析后可能返回:
{
"role": "assistant",
"content": null,
"function_call": {
"name": "getWeather",
"arguments": "{\"location\": \"北京\"}"
}
}
注意:LLM 只生成调用请求,并不实际执行 。
步骤 4:Spring AI 执行函数
// Spring AI 内部处理流程
if (response.hasFunctionCall()) {
String functionName = response.getFunctionCall().getName();
Map<String, Object> args = parseArguments(response);
// 查找注册的 Function 并执行
Function<?, ?> function = functionRegistry.get(functionName);
Object result = function.apply(args);
// 将结果封装为新的消息放入对话上下文
ChatMessage functionMessage = new FunctionMessage(result);
chatContext.add(functionMessage);
}
步骤 5:LLM 生成最终回复
Spring AI 将函数执行结果(如 {"temperature": 22, "condition": "Sunny"})发送给 LLM,LLM 将其转化为自然语言:
“北京目前天气晴朗,气温 22 度。”
Spring AI 的关键抽象与组件
FunctionCallback :函数调用的核心接口
- 提供函数名称、描述、参数 Schema
- 包含实际执行逻辑
FunctionCallbackRegistry :函数注册中心
- 管理所有可用函数
- 支持按名称查找
-
ChatClient 的扩展 :
chatClient.prompt()
.functions("getWeather", "searchProducts") // 声明可用函数
.call();
- 消息类型扩展 :
-
SystemMessage:可包含函数定义 -
FunctionCallMessage:LLM 的调用请求 -
FunctionResponseMessage:函数执行结果
与其他技术方案的对比
表格 还在加载中,请等待加载完成后再尝试复制
最佳实践与注意事项
- 函数设计原则 :
- 单一职责:一个函数完成一个明确任务
- 描述清晰:函数名和参数描述要准确,帮助 LLM 正确选择
- 安全边界:函数应进行权限验证和输入清理
- 错误处理 :
try {
return function.apply(request);
} catch (Exception e) {
// 返回结构化错误信息,让 LLM 告知用户
return Map.of("error", "查询失败,请稍后重试");
}
- 性能优化 :
- 函数调用有额外 token 开销,避免定义过多函数
- 对耗时函数进行异步处理
- 安全考虑 :
- 限制可调用函数范围
- 验证 LLM 生成的参数,防止注入攻击
典型应用场景
- 数据查询 :数据库、API 查询
- 业务操作 :下单、预订、状态更新
- 计算服务 :汇率换算、单位转换
- 系统交互 :发送邮件、生成报表
代码示例(简化版)
@SpringBootApplication
public class FunctionCallApp {
@Bean
public Function<WeatherRequest, WeatherResponse> weatherFunction() {
return request -> {
// 实际业务逻辑
return weatherService.getWeather(request.location());
};
}
@Bean
public ChatClient chatClient(OpenAiChatClient client,
FunctionCallbackRegistry registry) {
return ChatClient.builder(client)
.functionCallbacks(registry)
.build();
}
public static void main(String[] args) {
SpringApplication.run(FunctionCallApp.class, args);
}
}
总结
Spring AI Function Calling 的核心价值在于:
- 扩展性 :让 LLM 突破训练数据限制,接入实时数据和业务逻辑
- 解耦 :LLM 负责意图识别,本地代码负责具体执行
- 可控性 :开发者完全控制可调用的函数范围和权限
这种模式正在成为构建 AI 应用的标准范式,而 Spring AI 通过熟悉的 Spring 编程模型,为 Java 开发者提供了高效、安全的实现路径。
2. 自定义函数注册、参数自动解析、结果回传
函数定义与注册
1.1 定义函数类
@Service
public class WeatherService {
@Function(name = "getCurrentWeather", description = "获取指定城市的当前天气")
public Weather getCurrentWeather(
@Parameter(description = "城市名称,例如:北京、上海") String city,
@Parameter(description = "温度单位,celsius 或 fahrenheit", required = false)
@DefaultValue("celsius") String unit) {
// 模拟天气数据
Weather weather = new Weather();
weather.setCity(city);
weather.setTemperature(unit.equals("celsius") ? 25.0 : 77.0);
weather.setUnit(unit);
weather.setCondition("晴朗");
weather.setHumidity(65);
return weather;
}
@Function(name = "getWeatherForecast", description = "获取天气预报")
public List<Weather> getWeatherForecast(
@Parameter(description = "城市名称") String city,
@Parameter(description = "天数,1-7") int days) {
List<Weather> forecast = new ArrayList<>();
for (int i = 0; i < days; i++) {
Weather weather = new Weather();
weather.setCity(city);
weather.setTemperature(20 + i * 2);
weather.setCondition(i % 2 == 0 ? "晴朗" : "多云");
weather.setDate(LocalDate.now().plusDays(i));
forecast.add(weather);
}
return forecast;
}
}
// 数据类
@Data
public class Weather {
private String city;
private double temperature;
private String unit;
private String condition;
private int humidity;
private LocalDate date;
}
1.2 注册函数到 AI 模型
@Configuration
public class FunctionConfiguration {
@Bean
public FunctionCallback weatherFunctionCallback(WeatherService weatherService) {
return FunctionCallbackWrapper.builder(weatherService)
.withName("weatherService")
.withDescription("天气服务")
.build();
}
@Bean
public ChatClient chatClient(
ChatModel chatModel,
List<FunctionCallback> functionCallbacks) {
return ChatClient.builder(chatModel)
.defaultFunctions(functionCallbacks.stream()
.map(FunctionCallback::getName)
.toArray(String[]::new))
.build();
}
}
参数自动解析机制
2.1 参数类型支持
Spring AI 支持多种参数类型自动解析:
@Service
public class MultiTypeService {
@Function(name = "calculate", description = "数学计算")
public Result calculate(
// 基本类型
@Parameter(description = "操作数A") double a,
@Parameter(description = "操作数B") double b,
// 枚举类型
@Parameter(description = "操作类型") Operation operation,
// 复杂对象(需要 JSON 解析)
@Parameter(description = "计算选项") Options options,
// 可选参数
@Parameter(description = "精度", required = false)
@DefaultValue("2") int precision) {
double result = switch (operation) {
case ADD -> a + b;
case SUBTRACT -> a - b;
case MULTIPLY -> a * b;
case DIVIDE -> a / b;
};
return new Result(result, precision);
}
@Function(name = "searchProducts", description = "搜索商品")
public List<Product> searchProducts(
@Parameter(description = "搜索条件") SearchCriteria criteria) {
// 复杂对象参数会自动从 JSON 解析
return productRepository.search(criteria);
}
}
// 枚举
public enum Operation {
ADD, SUBTRACT, MULTIPLY, DIVIDE
}
// 复杂参数对象
@Data
public class SearchCriteria {
private String keyword;
private Double minPrice;
private Double maxPrice;
private List<String> categories;
private SortOrder sortBy;
}
2.2 自定义参数解析器
@Component
public class CustomArgumentResolver implements FunctionArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 支持自定义类型的参数解析
return parameter.getParameterType().equals(CustomDate.class);
}
@Override
public Object resolveArgument(
MethodParameter parameter,
Message message,
ConversionService conversionService) {
// 从 AI 返回的 JSON 中提取并转换
String dateStr = extractDateFromMessage(message);
return CustomDate.fromString(dateStr);
}
private String extractDateFromMessage(Message message) {
// 解析消息内容获取日期参数
// 实际实现根据消息格式而定
return message.getContent();
}
}
// 注册自定义解析器
@Configuration
public class ResolverConfig {
@Bean
public FunctionArgumentResolver customDateResolver() {
return new CustomArgumentResolver();
}
}
结果回传与处理
3.1 基本结果回传
@Service
public class ResultHandlingService {
@Function(name = "getUserInfo", description = "获取用户信息")
public UserInfo getUserInfo(
@Parameter(description = "用户ID") String userId) {
UserInfo user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("用户不存在"));
// 结果会自动转换为 JSON 返回给 AI
return user;
}
@Function(name = "batchProcess", description = "批量处理")
public BatchResult batchProcess(
@Parameter(description = "处理项列表") List<ProcessItem> items) {
BatchResult result = new BatchResult();
for (ProcessItem item : items) {
ProcessResult processResult = processItem(item);
result.addResult(processResult);
}
// 复杂嵌套对象也会被正确序列化
return result;
}
}
// 支持复杂嵌套结果
@Data
public class BatchResult {
private int total;
private int success;
private int failed;
private List<ProcessResult> results;
private Statistics statistics;
@Data
public static class Statistics {
private double averageTime;
private double successRate;
private Map<String, Integer> statusCount;
}
}
3.2 结果后处理
@Component
public class ResultPostProcessor {
@Autowired
private ObjectMapper objectMapper;
public String processFunctionResult(Object result) {
// 1. 转换为 JSON
String jsonResult = objectMapper.writeValueAsString(result);
// 2. 添加额外信息
Map<String, Object> enhancedResult = new HashMap<>();
enhancedResult.put("data", result);
enhancedResult.put("timestamp", Instant.now());
enhancedResult.put("success", true);
// 3. 格式化输出(可选)
return formatForAI(enhancedResult);
}
private String formatForAI(Map<String, Object> result) {
// 根据 AI 模型的要求格式化结果
StringBuilder sb = new StringBuilder();
sb.append("执行结果:\n");
if (result.get("data") instanceof List) {
sb.append("找到 ").append(((List<?>) result.get("data")).size()).append(" 条记录");
} else {
sb.append("操作成功完成");
}
return sb.toString();
}
}
完整示例:电商助手
@RestController
@RequestMapping("/api/ai")
public class ECommerceAssistantController {
@Autowired
private ChatClient chatClient;
@Autowired
private ProductService productService;
@Autowired
private OrderService orderService;
@PostMapping("/chat")
public ResponseEntity<ChatResponse> chat(@RequestBody UserMessage request) {
// 注册多个功能
List<FunctionCallback> callbacks = Arrays.asList(
FunctionCallbackWrapper.builder(productService)
.withName("productService")
.build(),
FunctionCallbackWrapper.builder(orderService)
.withName("orderService")
.build()
);
// 创建聊天客户端
ChatClient client = ChatClient.builder(chatClient.getChatModel())
.defaultFunctions(callbacks.stream()
.map(FunctionCallback::getName)
.toArray(String[]::new))
.defaultSystem("""
你是一个电商助手,可以:
1. 搜索商品 (使用 productService.searchProducts)
2. 查询订单 (使用 orderService.getOrderDetails)
3. 推荐商品 (使用 productService.getRecommendations)
4. 检查库存 (使用 productService.checkStock)
""")
.build();
// 执行对话
AssistantMessage response = client.prompt()
.user(request.getMessage())
.call()
.chatResponse()
.getResult();
return ResponseEntity.ok(new ChatResponse(response.getContent()));
}
}
// 商品服务
@Service
public class ProductService {
@Function(name = "searchProducts", description = "搜索商品")
public SearchResult searchProducts(
@Parameter(description = "搜索关键词") String keyword,
@Parameter(description = "分类", required = false) String category,
@Parameter(description = "最低价格", required = false) Double minPrice,
@Parameter(description = "最高价格", required = false) Double maxPrice,
@Parameter(description = "排序方式", required = false)
@DefaultValue("relevance") String sortBy) {
// 执行搜索逻辑
List<Product> products = productRepository.search(
keyword, category, minPrice, maxPrice, sortBy);
SearchResult result = new SearchResult();
result.setProducts(products);
result.setTotal(products.size());
result.setKeyword(keyword);
// 添加建议
if (products.isEmpty()) {
result.setSuggestions(getSuggestions(keyword));
}
return result;
}
@Function(name = "getRecommendations", description = "获取商品推荐")
public List<Product> getRecommendations(
@Parameter(description = "用户ID,用于个性化推荐") String userId,
@Parameter(description = "推荐数量", required = false)
@DefaultValue("5") int count) {
return recommendationEngine.getRecommendations(userId, count);
}
}
// 订单服务
@Service
public class OrderService {
@Function(name = "getOrderDetails", description = "获取订单详情")
public OrderDetails getOrderDetails(
@Parameter(description = "订单号") String orderId) {
Order order = orderRepository.findByOrderId(orderId);
OrderDetails details = new OrderDetails();
details.setOrderId(orderId);
details.setStatus(order.getStatus());
details.setItems(order.getItems());
details.setTotalAmount(order.getTotalAmount());
details.setShippingAddress(order.getShippingAddress());
details.setEstimatedDelivery(order.getEstimatedDelivery());
return details;
}
@Function(name = "trackOrder", description = "订单物流跟踪")
public TrackingInfo trackOrder(
@Parameter(description = "订单号") String orderId) {
return logisticsService.trackOrder(orderId);
}
}
高级配置
5.1 函数调用策略配置
@Configuration
public class AdvancedFunctionConfig {
@Bean
public FunctionCallingOptions functionCallingOptions() {
return FunctionCallingOptions.builder()
.withFunctionCall(FunctionCallingOptions.FunctionCall.AUTO)
.withFunctions("weatherService", "calculatorService")
.withTemperature(0.2) // 降低随机性,使函数调用更稳定
.build();
}
@Bean
public ChatClient chatClient(
ChatModel chatModel,
FunctionCallingOptions options) {
return ChatClient.builder(chatModel)
.defaultOptions(options)
.defaultSystem("""
请根据用户需求选择合适的函数调用。
如果需要精确数据,请调用相应函数。
""")
.build();
}
}
5.2 错误处理
@ControllerAdvice
public class FunctionExceptionHandler {
@ExceptionHandler(FunctionCallException.class)
@ResponseBody
public ErrorResponse handleFunctionError(FunctionCallException e) {
ErrorResponse error = new ErrorResponse();
error.setErrorCode("FUNCTION_ERROR");
error.setMessage("函数调用失败: " + e.getMessage());
error.setTimestamp(Instant.now());
// 提供修复建议
if (e.getCause() instanceof IllegalArgumentException) {
error.setSuggestion("请检查参数是否正确");
}
return error;
}
@ExceptionHandler(JsonProcessingException.class)
@ResponseBody
public ErrorResponse handleJsonError(JsonProcessingException e) {
return new ErrorResponse(
"JSON_PARSE_ERROR",
"参数解析失败,请检查参数格式",
Instant.now()
);
}
}
测试函数调用
@SpringBootTest
class FunctionCallingTest {
@Autowired
private ChatClient chatClient;
@Test
void testWeatherFunction() {
AssistantMessage response = chatClient.prompt()
.user("北京现在的天气怎么样?")
.call()
.chatResponse()
.getResult();
assertThat(response.getContent()).contains("天气");
assertThat(response.getContent()).contains("北京");
}
@Test
void testComplexFunctionChain() {
// 测试多个函数链式调用
AssistantMessage response = chatClient.prompt()
.system("""
用户想买一件商品,请先搜索商品,
然后检查库存,最后给出购买建议。
""")
.user("我想买一台笔记本电脑,预算8000左右")
.call()
.chatResponse()
.getResult();
// 验证响应包含函数调用结果
assertThat(response.getContent())
.containsPattern("搜索到.*笔记本")
.contains("库存")
.contains("建议");
}
}
关键要点总结
- 函数注册 :使用
@Function注解标记方法,通过FunctionCallbackWrapper注册 - 参数解析 :Spring AI 自动处理基本类型、枚举、复杂对象的 JSON 解析
- 结果回传 :函数返回值自动序列化为 JSON 返回给 AI 模型
- 链式调用 :支持多个函数的链式调用和结果传递
- 错误处理 :完善的异常处理和错误信息反馈机制
- 类型安全 :强类型参数和返回值,减少运行时错误
这种设计让 Spring AI 的函数调用既灵活又类型安全,能够很好地处理复杂的业务逻辑。
3. 实战开发:AI查询业务数据、AI自动生成报表、AI接口查询
我来分享一个完整的Spring AI实战开发方案,涵盖AI查询业务数据、自动生成报表和接口查询三大场景。
一、项目架构设计
技术栈选择
# pom.xml 关键依赖
dependencies:
- spring-boot-starter-web
- spring-ai-openai-spring-boot-starter # 或Azure OpenAI、Ollama
- spring-ai-pgvector-store # 向量数据库
- spring-data-jpa
- mysql-connector-java
- lombok
- springdoc-openapi-starter-webmvc-ui # API文档
项目结构
src/main/java/com/example/aiassistant/
├── config/
│ ├── AiConfig.java # AI配置
│ └── VectorStoreConfig.java # 向量存储
├── domain/
│ ├── entity/ # 业务实体
│ ├── repository/ # 数据访问
│ └── dto/ # 数据传输对象
├── service/
│ ├── DataQueryService.java # 数据查询服务
│ ├── ReportService.java # 报表服务
│ ├── AiQueryService.java # AI查询服务
│ └── EmbeddingService.java # 嵌入服务
├── controller/
│ ├── AiQueryController.java
│ └── ReportController.java
└── Application.java
二、核心功能实现
AI查询业务数据
@Service
@Slf4j
public class AiQueryService {
@Autowired
private ChatClient chatClient;
@Autowired
private DataQueryService dataQueryService;
/**
* 自然语言查询业务数据
*/
public String queryBusinessData(String naturalLanguageQuery) {
// 1. 解析查询意图
String sqlQuery = parseQueryToSQL(naturalLanguageQuery);
// 2. 执行SQL查询
List<Map<String, Object>> results = dataQueryService.executeQuery(sqlQuery);
// 3. 使用AI格式化结果
String formattedResult = formatResultsWithAI(results, naturalLanguageQuery);
return formattedResult;
}
private String parseQueryToSQL(String naturalQuery) {
String prompt = """
请将以下自然语言查询转换为SQL语句:
用户查询:"%s"
数据库表结构:
1. sales_order表:id, customer_name, product_name, quantity, amount, order_date
2. customer表:id, name, region, customer_level
3. product表:id, name, category, price
转换规则:
- 只生成SQL语句,不要解释
- 使用中文字段别名
- 考虑性能优化
""".formatted(naturalQuery);
return chatClient.call(prompt);
}
private String formatResultsWithAI(List<Map<String, Object>> results, String originalQuery) {
String dataJson = convertToJson(results);
String prompt = """
根据以下数据回答用户查询:
用户问题:%s
查询结果数据:%s
请以清晰、专业的方式呈现结果,包含:
1. 数据总结
2. 关键洞察
3. 建议(如果需要)
使用中文回复。
""".formatted(originalQuery, dataJson);
return chatClient.call(prompt);
}
}
AI自动生成报表
@Service
public class ReportGenerationService {
@Autowired
private ChatClient chatClient;
@Autowired
private VectorStore vectorStore;
/**
* 生成智能报表
*/
public ReportDTO generateReport(ReportRequest request) {
// 1. 收集相关数据
List<BusinessData> data = collectReportData(request);
// 2. AI分析数据并生成洞察
String analysis = analyzeDataWithAI(data, request.getReportType());
// 3. 生成报表内容(文本+图表建议)
ReportContent content = generateReportContent(analysis, data);
// 4. 格式化为多种格式
return formatReport(content, request.getFormat());
}
private String analyzeDataWithAI(List<BusinessData> data, String reportType) {
String dataSummary = summarizeData(data);
String prompt = """
作为数据分析专家,请分析以下业务数据并生成%s报告:
数据概览:%s
请提供:
1. 关键趋势分析
2. 异常点识别
3. 业务建议
4. 可视化图表建议(如:折线图、柱状图、饼图)
使用专业但易于理解的商业语言。
""".formatted(reportType, dataSummary);
return chatClient.call(prompt);
}
private ReportContent generateReportContent(String aiAnalysis, List<BusinessData> data) {
// 解析AI建议的图表类型
List<ChartConfig> charts = extractChartSuggestions(aiAnalysis);
// 生成数据表格
String dataTable = generateDataTable(data);
// 生成执行摘要
String executiveSummary = generateExecutiveSummary(aiAnalysis);
return ReportContent.builder()
.title("智能业务分析报告")
.executiveSummary(executiveSummary)
.detailedAnalysis(aiAnalysis)
.dataTables(dataTable)
.chartConfigs(charts)
.recommendations(extractRecommendations(aiAnalysis))
.build();
}
/**
* 导出多种格式
*/
public byte[] exportReport(ReportDTO report, ExportFormat format) {
switch (format) {
case PDF:
return generatePdfReport(report);
case EXCEL:
return generateExcelReport(report);
case HTML:
return generateHtmlReport(report);
case MARKDOWN:
return generateMarkdownReport(report);
default:
throw new IllegalArgumentException("不支持的格式");
}
}
}
AI接口查询系统
@RestController
@RequestMapping("/api/ai-query")
public class AiQueryController {
@Autowired
private AiQueryService aiQueryService;
/**
* 自然语言查询接口
*/
@PostMapping("/natural-query")
public ApiResponse<String> naturalLanguageQuery(
@RequestBody NaturalQueryRequest request) {
String result = aiQueryService.queryBusinessData(request.getQuery());
return ApiResponse.success(result);
}
/**
* 流式响应查询
*/
@GetMapping("/stream-query")
public Flux<String> streamQuery(@RequestParam String query) {
return aiQueryService.streamQuery(query);
}
/**
* 带上下文的查询
*/
@PostMapping("/contextual-query")
public ApiResponse<QueryResult> contextualQuery(
@RequestBody ContextualQueryRequest request) {
// 获取历史上下文
List<ChatMessage> history = getQueryHistory(request.getSessionId());
// 执行带上下文的查询
QueryResult result = aiQueryService.queryWithContext(
request.getQuery(),
history
);
return ApiResponse.success(result);
}
}
/**
* 智能API查询服务
*/
@Service
public class IntelligentApiService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private ChatClient chatClient;
/**
* AI辅助API查询
*/
public Object queryApi(String naturalLanguageRequest) {
// 1. AI解析用户意图
ApiRequest parsedRequest = parseApiRequest(naturalLanguageRequest);
// 2. 构建API请求
HttpEntity<?> requestEntity = buildRequestEntity(parsedRequest);
// 3. 执行请求
ResponseEntity<String> response = restTemplate.exchange(
parsedRequest.getUrl(),
parsedRequest.getMethod(),
requestEntity,
String.class
);
// 4. AI处理响应
return processApiResponse(response.getBody(), naturalLanguageRequest);
}
private ApiRequest parseApiRequest(String naturalLanguage) {
String prompt = """
请将以下用户请求解析为API调用参数:
用户请求:%s
可用API列表:
1. 用户查询:GET /api/users?department={dept}
2. 订单查询:GET /api/orders?startDate={start}&endDate={end}
3. 销售统计:GET /api/sales/stats?region={region}
4. 产品列表:GET /api/products?category={category}
请返回JSON格式,包含:
- url: API地址
- method: HTTP方法
- parameters: 参数Map
- headers: 请求头
""".formatted(naturalLanguage);
String jsonResponse = chatClient.call(prompt);
return parseJsonToApiRequest(jsonResponse);
}
}
三、高级功能实现
RAG增强查询
@Service
public class RagEnhancedService {
@Autowired
private VectorStore vectorStore;
/**
* RAG增强的业务查询
*/
public String ragQuery(String question) {
// 1. 从向量库检索相关文档
List<Document> relevantDocs = vectorStore.similaritySearch(question);
// 2. 构建增强的Prompt
String context = buildContextFromDocs(relevantDocs);
String prompt = """
基于以下业务文档回答问题:
相关业务文档:
%s
用户问题:%s
要求:
1. 基于文档内容回答
2. 如果文档中没有相关信息,请明确说明
3. 引用相关文档片段
4. 用中文回答
""".formatted(context, question);
return chatClient.call(prompt);
}
/**
* 文档向量化存储
*/
public void storeBusinessDocuments(List<BusinessDocument> docs) {
List<Document> documents = docs.stream()
.map(doc -> new Document(doc.getContent(), doc.getMetadata()))
.collect(Collectors.toList());
vectorStore.add(documents);
}
}
智能数据可视化
@Service
public class VisualizationService {
@Autowired
private ChatClient chatClient;
/**
* AI建议的数据可视化
*/
public VisualizationConfig suggestVisualization(List<Map<String, Object>> data) {
String dataSample = data.stream()
.limit(10)
.map(Object::toString)
.collect(Collectors.joining("\n"));
String prompt = """
分析以下数据,建议最适合的可视化方案:
数据样例:
%s
请建议:
1. 图表类型(折线图、柱状图、散点图等)
2. X轴和Y轴字段
3. 颜色方案建议
4. 是否需要分组
5. 交互建议
以JSON格式返回配置。
""".formatted(dataSample);
String configJson = chatClient.call(prompt);
return parseVisualizationConfig(configJson);
}
/**
* 生成ECharts配置
*/
public String generateEChartsConfig(VisualizationConfig config) {
String prompt = """
根据以下配置生成ECharts选项JSON:
配置:%s
要求:
1. 生成完整的ECharts option配置
2. 包含标题、图例、工具提示
3. 美观的颜色方案
4. 响应式设计
5. 返回纯JSON,不要解释
""".formatted(config.toString());
return chatClient.call(prompt);
}
}
四、配置示例
Spring AI配置
# application.yml
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4
temperature: 0.3
max-tokens: 2000
vectorstore:
pgvector:
enabled: true
index-type: HNSW
dimensions: 1536
datasource:
url: jdbc:mysql://localhost:3306/business_db
username: root
password: password
业务数据实体
@Entity
@Table(name = "sales_data")
@Data
public class SalesData {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String region;
private String productCategory;
private BigDecimal salesAmount;
private Integer quantity;
private LocalDate saleDate;
private String customerSegment;
@Column(columnDefinition = "json")
private String additionalMetrics;
}
五、最佳实践建议
性能优化
@Configuration
public class AiPerformanceConfig {
@Bean
public CachingChatClient cachingChatClient(ChatClient chatClient) {
return new CachingChatClient(chatClient);
}
@Bean
public RetryTemplate aiRetryTemplate() {
return RetryTemplate.builder()
.maxAttempts(3)
.exponentialBackoff(1000, 2, 5000)
.retryOn(OpenAiApiException.class)
.build();
}
}
安全考虑
@Service
public class AiSecurityService {
/**
* 输入验证和防护
*/
public String sanitizeInput(String userInput) {
// 1. 检查注入攻击
if (containsSqlInjection(userInput)) {
throw new SecurityException("检测到可疑输入");
}
// 2. 敏感信息过滤
String filtered = filterSensitiveInfo(userInput);
// 3. 长度限制
if (filtered.length() > 1000) {
filtered = filtered.substring(0, 1000);
}
return filtered;
}
/**
* 输出内容审查
*/
public boolean validateOutput(String aiOutput) {
// 检查有害内容
return !containsHarmfulContent(aiOutput);
}
}
监控和日志
@Aspect
@Component
@Slf4j
public class AiServiceMonitor {
@Around("@within(org.springframework.stereotype.Service)")
public Object monitorAiCalls(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName();
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - start;
log.info("AI服务调用成功 - 方法: {}, 耗时: {}ms",
methodName, duration);
// 记录到监控系统
metricsService.recordAiCall(methodName, duration, true);
return result;
} catch (Exception e) {
metricsService.recordAiCall(methodName,
System.currentTimeMillis() - start, false);
throw e;
}
}
}
六、完整示例:销售分析系统
@RestController
@RequestMapping("/api/sales-ai")
public class SalesAiController {
@PostMapping("/analyze")
public SalesAnalysis analyzeSales(@RequestBody AnalysisRequest request) {
// 1. 查询销售数据
List<SalesData> salesData = salesService.getSalesData(request);
// 2. AI分析趋势
String trendAnalysis = aiAnalyzer.analyzeSalesTrend(salesData);
// 3. 生成建议
String recommendations = aiAdvisor.generateRecommendations(salesData);
// 4. 创建可视化配置
VisualizationConfig charts = visualizationService.suggestCharts(salesData);
return SalesAnalysis.builder()
.period(request.getPeriod())
.summary(generateSummary(salesData))
.trendAnalysis(trendAnalysis)
.keyInsights(extractInsights(trendAnalysis))
.recommendations(recommendations)
.visualizationConfig(charts)
.generatedAt(LocalDateTime.now())
.build();
}
@PostMapping(value = "/export-report", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<byte[]> exportReport(@RequestBody ReportRequest request) {
SalesAnalysis analysis = analyzeSales(request.getAnalysisRequest());
byte[] reportBytes = reportService.exportToPdf(analysis);
return ResponseEntity.ok()
.header("Content-Disposition",
"attachment; filename=\"sales-report.pdf\"")
.body(reportBytes);
}
}
关键点总结
- 分层架构 :清晰的Controller-Service-Repository分层
- 错误处理 :完善的异常处理和重试机制
- 性能优化 :缓存、流式响应、异步处理
- 安全性 :输入验证、输出审查、API防护
- 可扩展性 :支持多AI模型、多数据源
- 可观测性 :完整的日志和监控
这个方案提供了完整的Spring AI实战开发框架,你可以根据具体业务需求进行调整和扩展。
4. 函数调用异常处理、参数校验、重试机制
Spring AI 的函数调用(Function Calling)提供了强大的工具调用能力,但在实际应用中需要完善的异常处理、参数校验和重试机制。以下是完整的实现方案:
异常处理机制
1.1 全局异常处理器
@RestControllerAdvice
public class SpringAIExceptionHandler {
@ExceptionHandler(ApiClientException.class)
public ResponseEntity<ErrorResponse> handleApiClientException(ApiClientException ex) {
log.error("AI API调用异常: {}", ex.getMessage(), ex);
return ResponseEntity.status(HttpStatus.BAD_GATEWAY)
.body(ErrorResponse.of("AI服务暂时不可用", ex.getMessage()));
}
@ExceptionHandler(FunctionCallException.class)
public ResponseEntity<ErrorResponse> handleFunctionCallException(FunctionCallException ex) {
log.error("函数调用异常: {}", ex.getMessage(), ex);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ErrorResponse.of("函数调用失败", ex.getMessage()));
}
@ExceptionHandler(InvalidFunctionParameterException.class)
public ResponseEntity<ErrorResponse> handleInvalidParameterException(
InvalidFunctionParameterException ex) {
log.error("函数参数异常: {}", ex.getMessage(), ex);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ErrorResponse.of("参数校验失败", ex.getValidationErrors()));
}
}
// 自定义异常类
public class FunctionCallException extends RuntimeException {
public FunctionCallException(String message) {
super(message);
}
public FunctionCallException(String message, Throwable cause) {
super(message, cause);
}
}
public class InvalidFunctionParameterException extends RuntimeException {
private final Map<String, String> validationErrors;
public InvalidFunctionParameterException(Map<String, String> errors) {
super("函数参数校验失败");
this.validationErrors = errors;
}
}
1.2 函数调用包装器
@Component
public class FunctionCallWrapper {
@Autowired
private FunctionCallingOptions functionCallingOptions;
public <T> T executeWithExceptionHandling(
Supplier<T> functionCall,
String functionName) {
try {
return functionCall.get();
} catch (ApiClientException e) {
log.error("AI API调用失败 - 函数: {}", functionName, e);
throw new FunctionCallException(
String.format("调用函数%s时AI服务异常", functionName), e);
} catch (IllegalArgumentException e) {
log.error("参数异常 - 函数: {}", functionName, e);
throw new InvalidFunctionParameterException(
Map.of("error", e.getMessage()));
} catch (Exception e) {
log.error("未知异常 - 函数: {}", functionName, e);
throw new FunctionCallException(
String.format("调用函数%s时发生未知错误", functionName), e);
}
}
}
参数校验机制
2.1 基于注解的校验
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
public class WeatherRequest {
@NotBlank(message = "城市名称不能为空")
@Size(max = 50, message = "城市名称长度不能超过50")
private String city;
@NotBlank(message = "国家代码不能为空")
@Pattern(regexp = "^[A-Z]{2}$", message = "国家代码必须是2位大写字母")
private String countryCode;
@NotNull(message = "日期不能为空")
@FutureOrPresent(message = "日期不能是过去时间")
private LocalDate date;
@Min(value = -50, message = "温度不能低于-50℃")
@Max(value = 60, message = "温度不能高于60℃")
private Integer temperature;
// getters and setters
}
2.2 函数参数校验器
@Component
public class FunctionParameterValidator {
private final Validator validator;
public FunctionParameterValidator() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
this.validator = factory.getValidator();
}
public <T> void validate(T request) {
Set<ConstraintViolation<T>> violations = validator.validate(request);
if (!violations.isEmpty()) {
Map<String, String> errors = violations.stream()
.collect(Collectors.toMap(
v -> v.getPropertyPath().toString(),
ConstraintViolation::getMessage
));
throw new InvalidFunctionParameterException(errors);
}
}
public void validateString(String value,
@Nullable Integer maxLength,
@Nullable Pattern pattern,
String fieldName) {
if (value == null || value.trim().isEmpty()) {
throw new InvalidFunctionParameterException(
Map.of(fieldName, "不能为空"));
}
if (maxLength != null && value.length() > maxLength) {
throw new InvalidFunctionParameterException(
Map.of(fieldName, String.format("长度不能超过%d", maxLength)));
}
if (pattern != null && !pattern.matcher(value).matches()) {
throw new InvalidFunctionParameterException(
Map.of(fieldName, "格式不正确"));
}
}
}
2.3 带校验的函数实现
@Component
public class WeatherFunction implements Function<WeatherRequest, WeatherResponse> {
@Autowired
private FunctionParameterValidator validator;
@Autowired
private WeatherService weatherService;
@Override
public WeatherResponse apply(WeatherRequest request) {
// 参数校验
validator.validate(request);
// 业务逻辑校验
validateBusinessLogic(request);
// 执行业务逻辑
return weatherService.getWeather(request);
}
private void validateBusinessLogic(WeatherRequest request) {
// 示例:检查城市是否支持
if (!weatherService.isCitySupported(request.getCity(), request.getCountryCode())) {
throw new InvalidFunctionParameterException(
Map.of("city", "不支持的城市: " + request.getCity()));
}
// 示例:检查日期范围
if (request.getDate().isAfter(LocalDate.now().plusDays(14))) {
throw new InvalidFunctionParameterException(
Map.of("date", "只能查询未来14天的天气"));
}
}
@Bean
public Function<Message, WeatherResponse> weatherFunction() {
return message -> {
WeatherRequest request = convertMessageToRequest(message);
return apply(request);
};
}
}
重试机制
3.1 基于Spring Retry的配置
@Configuration
@EnableRetry
public class RetryConfig {
@Bean
public RetryTemplate aiFunctionRetryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
// 重试策略:最多重试3次,指数退避
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(1000);
backOffPolicy.setMultiplier(2.0);
backOffPolicy.setMaxInterval(10000);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(3);
retryTemplate.setRetryPolicy(retryPolicy);
retryTemplate.setBackOffPolicy(backOffPolicy);
return retryTemplate;
}
@Bean
public RetryTemplate circuitBreakerRetryTemplate() {
CircuitBreakerRetryPolicy circuitBreakerPolicy =
new CircuitBreakerRetryPolicy(new SimpleRetryPolicy(3));
circuitBreakerPolicy.setOpenTimeout(5000);
circuitBreakerPolicy.setResetTimeout(10000);
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(circuitBreakerPolicy);
return retryTemplate;
}
}
3.2 带重试的函数调用器
@Component
public class ResilientFunctionCaller {
@Autowired
private RetryTemplate aiFunctionRetryTemplate;
@Autowired
private CircuitBreakerFactory circuitBreakerFactory;
@Autowired
private MeterRegistry meterRegistry;
private final Counter successCounter;
private final Counter failureCounter;
private final Timer executionTimer;
public ResilientFunctionCaller(MeterRegistry meterRegistry) {
this.successCounter = meterRegistry.counter("function.call.success");
this.failureCounter = meterRegistry.counter("function.call.failure");
this.executionTimer = meterRegistry.timer("function.execution.time");
}
public <T> T callWithRetry(Supplier<T> function,
String functionName,
Map<String, Object> context) {
return executionTimer.record(() -> {
try {
T result = aiFunctionRetryTemplate.execute(context -> {
try {
return function.get();
} catch (ApiClientException e) {
if (shouldRetry(e)) {
throw e; // 触发重试
}
throw new NonRetryableException("不可重试的异常", e);
} catch (InvalidFunctionParameterException e) {
throw new NonRetryableException("参数错误,不重试", e);
}
});
successCounter.increment();
return result;
} catch (Exception e) {
failureCounter.increment();
log.error("函数调用失败,已重试: {}", functionName, e);
throw new FunctionCallException(
String.format("调用%s失败,已重试多次", functionName), e);
}
});
}
private boolean shouldRetry(ApiClientException e) {
// 根据异常类型决定是否重试
return e.getStatusCode() != null &&
e.getStatusCode().is5xxServerError() &&
e.getStatusCode().value() != 501; // 不重试501错误
}
// 使用熔断器
public <T> T callWithCircuitBreaker(Supplier<T> function,
String functionName) {
CircuitBreaker circuitBreaker = circuitBreakerFactory.create(functionName);
return circuitBreaker.run(() -> {
try {
return callWithRetry(function, functionName, Map.of());
} catch (Exception e) {
throw new RuntimeException(e);
}
}, throwable -> {
// 降级逻辑
log.warn("熔断器触发,使用降级逻辑: {}", functionName);
return getFallbackResult(functionName);
});
}
private <T> T getFallbackResult(String functionName) {
// 返回默认值或缓存值
if ("getWeather".equals(functionName)) {
return (T) new WeatherResponse("服务暂时不可用", null, null);
}
return null;
}
}
3.3 注解式重试
@Service
public class WeatherService {
@Retryable(
value = {ApiClientException.class, TimeoutException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
@Recover
public WeatherResponse recover(ApiClientException e, WeatherRequest request) {
log.warn("天气服务调用失败,使用缓存数据: {}", request.getCity());
return getCachedWeather(request);
}
@CircuitBreaker(
name = "weatherService",
fallbackMethod = "getWeatherFallback"
)
@RateLimiter(name = "weatherService", limit = 10)
@TimeLimiter(name = "weatherService")
public CompletableFuture<WeatherResponse> getWeatherAsync(WeatherRequest request) {
return CompletableFuture.supplyAsync(() -> getWeather(request));
}
public WeatherResponse getWeatherFallback(WeatherRequest request, Exception e) {
return new WeatherResponse("降级响应", null, null);
}
}
完整集成示例
4.1 配置类
@Configuration
public class SpringAIFunctionConfig {
@Bean
public FunctionCallingOptions functionCallingOptions() {
return FunctionCallingOptions.builder()
.withFunctionCallbacks(List.of(
FunctionCallback.builder()
.withName("getWeather")
.withDescription("获取天气信息")
.withInputType(WeatherRequest.class)
.withOutputType(WeatherResponse.class)
.build()
))
.build();
}
@Bean
public ChatClient chatClient(
ChatModel chatModel,
FunctionCallingOptions options,
ResilientFunctionCaller resilientCaller) {
return ChatClient.builder(chatModel)
.defaultOptions(options)
.defaultSystem("""
你是一个天气助手。请根据用户需求调用适当的函数。
如果参数不完整,请询问用户补充信息。
""")
.build();
}
}
4.2 服务层整合
@Service
@Slf4j
public class AIChatService {
@Autowired
private ChatClient chatClient;
@Autowired
private ResilientFunctionCaller resilientCaller;
@Autowired
private FunctionParameterValidator validator;
@Autowired
private WeatherFunction weatherFunction;
public String chatWithFunctions(String userMessage) {
return resilientCaller.callWithCircuitBreaker(() -> {
// 执行AI对话,包含函数调用
ChatResponse response = chatClient.prompt()
.user(userMessage)
.call()
.chatResponse();
// 处理函数调用结果
return processFunctionCalls(response);
}, "aiChatFunction");
}
private String processFunctionCalls(ChatResponse response) {
if (response.getToolCalls() != null && !response.getToolCalls().isEmpty()) {
for (ToolCall toolCall : response.getToolCalls()) {
if ("getWeather".equals(toolCall.function().name())) {
WeatherRequest request = parseWeatherRequest(toolCall);
// 执行带校验和异常处理的函数调用
WeatherResponse weatherResponse = resilientCaller.callWithRetry(
() -> weatherFunction.apply(request),
"getWeather",
Map.of("request", request)
);
// 将结果返回给AI
return chatClient.prompt()
.tools(ToollCallResponse.from(weatherResponse))
.call()
.content();
}
}
}
return response.content();
}
private WeatherRequest parseWeatherRequest(ToolCall toolCall) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(
toolCall.function().arguments(),
WeatherRequest.class
);
} catch (Exception e) {
throw new FunctionCallException("解析函数参数失败", e);
}
}
}
4.3 监控和日志
@Component
@Aspect
@Slf4j
public class FunctionCallMonitor {
@Around("@annotation(MonitorFunctionCall)")
public Object monitorFunctionCall(ProceedingJoinPoint joinPoint) throws Throwable {
String functionName = joinPoint.getSignature().getName();
long startTime = System.currentTimeMillis();
MDC.put("functionName", functionName);
log.info("开始执行函数: {}", functionName);
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - startTime;
log.info("函数执行成功: {}, 耗时: {}ms", functionName, duration);
return result;
} catch (Exception e) {
log.error("函数执行失败: {}", functionName, e);
throw e;
} finally {
MDC.remove("functionName");
}
}
@EventListener
public void handleFunctionCallEvent(FunctionCallEvent event) {
log.info("函数调用事件: {}, 状态: {}",
event.getFunctionName(),
event.getStatus());
// 发送到监控系统
Metrics.counter("function.call.event")
.tag("function", event.getFunctionName())
.tag("status", event.getStatus().name())
.increment();
}
}
最佳实践建议
- 分层处理 :将参数校验、业务逻辑、异常处理分层实现
- 优雅降级 :为关键函数提供降级逻辑
- 监控告警 :对函数调用成功率、耗时等关键指标进行监控
- 配置化 :将重试策略、超时时间等配置化,便于调整
- 幂等性 :确保可重试的函数具有幂等性
- 超时控制 :为每个函数设置合理的超时时间
- 限流保护 :对高频函数进行限流,防止系统过载
这套方案提供了完整的异常处理、参数校验和重试机制,能够确保Spring AI函数调用的稳定性和可靠性。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)