作为一名长期从事企业级AI应用开发的工程师,我深知在Spring AI框架中正确处理响应结果的重要性。响应对象不仅是模型输出的容器,更是连接业务逻辑与AI能力的桥梁。本文将基于我在多个生产项目中的实践经验,深入剖析Spring AI 1.x系列中的响应结果对象体系。
在实际项目中,我们首先需要关注API调用的错误处理。智普API采用双层错误码体系:
java复制// 典型错误处理代码示例
try {
ChatResponse response = chatClient.call();
} catch (HttpClientErrorException e) {
if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) {
// 处理认证失败
}
// 解析业务错误码
ErrorResponse error = objectMapper.readValue(e.getResponseBodyAsString(), ErrorResponse.class);
switch (error.getCode()) {
case 1001:
// 参数缺失处理
break;
// 其他错误码处理
}
}
重要提示:生产环境中必须实现完整的错误处理逻辑,特别是对于限流(429)和服务不可用(503)等情况,需要加入重试机制。
智普API支持两种数据格式,各有适用场景:
| 格式类型 | 适用场景 | 特点 |
|---|---|---|
| application/json | 常规请求 | 一次性返回完整结果,延迟较高 |
| text/event-stream | 流式请求 | 分块返回,实时性高 |
对于JSON响应,核心字段的工程意义如下:
java复制public class ZhiPuResponse {
private String id; // 用于链路追踪
private String model; // 模型版本管理
private List<Choice> choices; // 多候选结果
private Usage usage; // 成本核算依据
private List<ContentFilter> contentFilter; // 内容安全审核
}
在实际开发中,我们通常会封装一个统一的响应处理器:
java复制public class ResponseHandler {
public static String extractContent(ZhiPuResponse response) {
if (response.getChoices() != null && !response.getChoices().isEmpty()) {
return response.getChoices().get(0).getMessage().getContent();
}
throw new IllegalStateException("Invalid response structure");
}
public static void validateResponse(ZhiPuResponse response) {
// 检查内容安全过滤
if (response.getContentFilter() != null) {
response.getContentFilter().forEach(filter -> {
if (filter.getLevel() <= 1) {
throw new ContentSafetyException("Unsafe content detected");
}
});
}
}
}
ModelResponse作为顶层接口,其设计体现了Spring AI的重要抽象原则:
java复制public interface ModelResponse<T extends ModelResult<?>> {
T getResult(); // 单结果获取
List<T> getResults(); // 多结果获取
ResponseMetadata getMetadata(); // 元数据访问
}
在实际工程中,我们通常需要处理以下几种常见场景:
java复制String content = response.getResult().getOutput().getContent();
java复制List<String> contents = response.getResults().stream()
.map(r -> r.getOutput().getContent())
.collect(Collectors.toList());
java复制ResponseMetadata metadata = response.getMetadata();
log.info("Request ID: {}, Model: {}", metadata.getId(), metadata.getModel());
ChatResponse是对话场景的具体实现,其核心结构值得关注:
java复制public class ChatResponse implements ModelResponse<Generation> {
private final ChatResponseMetadata metadata;
private final List<Generation> generations;
// 其他方法...
}
工程实践建议:
java复制MDC.put("requestId", response.getMetadata().getId());
java复制@Bean
public ChatClient chatClient() {
return new ChatClientBuilder()
.withCandidateCount(3) // 获取3个候选结果
.build();
}
java复制@Cacheable(value = "aiResponses", key = "#response.metadata.id")
public String processResponse(ChatResponse response) {
// 处理逻辑
}
ChatResponseMetadata包含的关键元数据对系统运维至关重要:
java复制public class ChatResponseMetadata {
private String id; // 请求唯一标识
private String model; // 模型版本
private RateLimit rateLimit; // 限流信息
private Usage usage; // Token统计
private PromptMetadata promptMetadata; // 提示词信息
}
生产环境监控建议:
java复制// 注册监控指标
meterRegistry.gauge("ai.token.prompt",
response.getMetadata().getUsage().getPromptTokens());
meterRegistry.gauge("ai.token.completion",
response.getMetadata().getUsage().getCompletionTokens());
java复制RateLimit limit = response.getMetadata().getRateLimit();
if (limit.getRemaining() < 100) {
alertService.sendWarning("AI API quota running low");
}
java复制// 记录模型版本使用情况
statsService.trackModelUsage(response.getMetadata().getModel());
同步调用是最简单的集成方式,适用于大多数业务场景:
java复制// 基本内容获取
String content = chatClient.prompt("你好").call().content();
// 完整响应获取
ChatResponse response = chatClient.prompt("你好").call().chatResponse();
// 带上下文的响应
ChatClientResponse clientResponse = chatClient.prompt("你好").call().chatClientResponse();
性能优化技巧:
java复制@Bean
public ChatClient chatClient() {
return new ChatClientBuilder()
.withTimeout(Duration.ofSeconds(30))
.build();
}
java复制@Bean
public ChatClient chatClient() {
return new ChatClientBuilder()
.withRetryTemplate(retryTemplate())
.build();
}
private RetryTemplate retryTemplate() {
return new RetryTemplateBuilder()
.maxAttempts(3)
.exponentialBackoff(1000, 2, 5000)
.retryOn(HttpServerErrorException.class)
.build();
}
流式处理对于长文本生成和实时交互场景至关重要:
java复制Flux<String> contentFlux = chatClient.prompt("写一篇关于Spring AI的文章")
.stream()
.content();
响应式编程最佳实践:
java复制contentFlux
.onBackpressureBuffer(100) // 缓冲100个元素
.subscribe(content -> {
// 处理内容
});
java复制contentFlux
.doOnSubscribe(sub -> log.info("Stream started"))
.doOnNext(chunk -> log.debug("Received chunk: {}", chunk))
.doOnComplete(() -> log.info("Stream completed"))
.doOnError(e -> log.error("Stream error", e))
.subscribe();
java复制Mono<String> fullContent = contentFlux
.collect(Collectors.joining());
Token消耗直接关联成本,需要精细化管理:
java复制public class TokenAnalyzer {
public void analyze(ChatResponse response) {
Usage usage = response.getMetadata().getUsage();
double cost = calculateCost(usage);
log.info("Request cost: ${}", cost);
if (usage.getPromptTokens() > 1000) {
suggestPromptOptimization();
}
}
private double calculateCost(Usage usage) {
// 根据实际定价模型计算
return usage.getPromptTokens() * 0.000002 +
usage.getCompletionTokens() * 0.000003;
}
}
优化建议:
原始AI响应通常需要后处理才能满足业务需求:
java复制public class ResponsePostProcessor {
public String process(String rawContent) {
// 1. 内容清理
String cleaned = cleanContent(rawContent);
// 2. 格式标准化
String formatted = formatContent(cleaned);
// 3. 敏感信息脱敏
String safeContent = desensitize(formatted);
return safeContent;
}
// 其他处理方法...
}
健壮的异常处理是生产系统必备能力:
java复制@ControllerAdvice
public class AIExceptionHandler {
@ExceptionHandler(HttpClientErrorException.class)
public ResponseEntity<ErrorResponse> handleAIException(HttpClientErrorException e) {
if (e.getStatusCode() == HttpStatus.TOO_MANY_REQUESTS) {
return ResponseEntity.status(429)
.body(new ErrorResponse("请求过于频繁,请稍后再试"));
}
// 其他异常处理
}
@ExceptionHandler(ContentSafetyException.class)
public ResponseEntity<ErrorResponse> handleSafetyException() {
return ResponseEntity.badRequest()
.body(new ErrorResponse("内容包含不安全信息"));
}
}
Spring AI允许扩展元数据以满足特定需求:
java复制public class CustomMetadata implements ChatResponseMetadata {
private String businessId;
private String department;
// 其他自定义字段
}
// 注册自定义实现
@Bean
public MetadataFactory metadataFactory() {
return new CustomMetadataFactory();
}
对于结构化输出需求,可以开发定制转换器:
java复制public class ProductInfoConverter implements StructuredOutputConverter<Product> {
@Override
public Product convert(String raw) {
// 解析AI响应为领域对象
return parseProductInfo(raw);
}
}
// 使用示例
Product product = chatClient.prompt("生成手机产品描述")
.call()
.entity(new ProductInfoConverter());
将AI调用纳入统一监控体系:
java复制public class AIMonitoringAspect {
@Around("execution(* com.example.ai..*(..))")
public Object monitor(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
Object result = pjp.proceed();
if (result instanceof ChatResponse) {
recordMetrics((ChatResponse)result);
}
return result;
} catch (Exception e) {
meterRegistry.counter("ai.errors").increment();
throw e;
}
}
private void recordMetrics(ChatResponse response) {
Timer.Sample sample = Timer.start(meterRegistry);
sample.stop(meterRegistry.timer("ai.latency"));
meterRegistry.counter("ai.requests").increment();
// 其他指标记录
}
}
java复制@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS));
return manager;
}
}
@Service
public class AIService {
@Cacheable(value = "aiResponses", key = "#prompt.hashCode()")
public String getAIResponse(String prompt) {
return chatClient.prompt(prompt).call().content();
}
}
java复制public class BatchProcessor {
@Async
public CompletableFuture<List<String>> processBatch(List<String> prompts) {
List<CompletableFuture<String>> futures = prompts.stream()
.map(prompt -> CompletableFuture.supplyAsync(
() -> chatClient.prompt(prompt).call().content()))
.collect(Collectors.toList());
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()));
}
}
java复制public class AdaptiveTimeoutController {
private long baseTimeout = 3000;
private double factor = 1.5;
public String getResponseWithTimeout(String prompt) {
long start = System.currentTimeMillis();
try {
return chatClient.prompt(prompt)
.withTimeout(Duration.ofMillis(calculateTimeout()))
.call()
.content();
} catch (TimeoutException e) {
adjustTimeout(System.currentTimeMillis() - start);
throw e;
}
}
private long calculateTimeout() {
// 基于历史响应时间计算
return (long)(baseTimeout * factor);
}
private void adjustTimeout(long actualTime) {
// 动态调整超时阈值
baseTimeout = (long)(actualTime * 1.2);
}
}
在长期的企业级AI应用开发实践中,我深刻体会到正确处理响应结果的重要性。一个好的响应处理体系不仅能提高系统稳定性,还能显著降低运营成本。特别是在处理大规模AI调用时,对Token消耗的精细化管理往往能节省大量成本。建议开发团队建立完善的监控体系,持续优化提示词工程,并定期审查AI使用成本。