Spring Boot 接入 Gemini 3.7 Flash:Agent 模式下的 JSON...

Spring Boot 接入 Gemini 3.7 Flash:Agent 模式下的 JSON 契约漂移与类型安全重构

上周处理了一组线上日志,发现集成 Google 最新发布的 Gemini 3.7 Flash 模型后,偶发的 JsonSyntaxException 频率上升了 15%。Google 在 2026 年 8 月 13 日推出了这款专为编程和 Agent 工作流设计的新模型,官方宣称其代码生成能力大幅跃升,甚至能直接输出接近生产环境的代码。

然而,当我们把这套模型引入到基于 Spring Boot 3.4 的后端架构中时,却撞上了一堵隐形的墙。不是因为模型不够智能,而是因为 Agent 模式下的输出结构,正在悄悄偏离我们传统的 JSON Schema 契约。

问题现象

错误通常出现在解析模型返回的 Function Calling 参数时。堆栈信息如下:

```java
com.fasterxml.jackson.databind.exc.MismatchedInputException:
Cannot deserialize instance of java.util.ArrayList out of VALUE_STRING token
at [Source: (String)"{"tools": ["write_file", "search_code"]}"; line 1, column 15]
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1754)
```

初看日志,我们会以为是 Jackson 的配置问题,或者网络传输中的截断。但在排查过程中,我们发现这种现象集中在 Agent 进行多步推理时。模型不再稳定地输出标准的 JSON 对象,而是偶尔混入字符串化的 JSON 片段,或者将 List 类型误报为逗号分隔的字符串。

排查过程

猜测:是网络层还是解析层?

最开始,我怀疑是 HTTPS 连接池在高频调用下出现了报文拼接错误。通过增加 OkHttpClient 的日志拦截器,抓取原始响应体,发现传入 Java 代码的字符串本身就已经包含了格式异常。这意味着问题不出在传输层,而是出在生成层。

验证:模型版本对比

为了确认这是 Gemini 3.7 Flash 的特定行为,我拉取了上一代 Gemini 1.5 Pro 和刚发布的 Gemini 3.0 Ultra 作为对照组。

  • Gemini 1.5 Pro:输出极度规整,严格遵循 JSON Schema。
  • Gemini 3.0 Ultra:多模态能力极强,但在纯代码生成任务中,偶尔会使用自然语言包裹 JSON。
  • Gemini 3.7 Flash:这是问题的核心。由于它被定位为“Agent 场景专用”,其底层训练数据大量包含非结构化的 Agent 交互日志。模型倾向于使用更紧凑的 Token 表达方式,有时会将数组序列化为扁平字符串,或者在 Tool Choice 参数上使用枚举值而非对象结构。

示意图

转折点:Schema 漂移

我们在 Postman 中手动复现了请求。当 Prompt 要求模型执行复杂的多步调试任务时,Gemini 3.7 Flash 返回的 ToolCalls 结构中,arguments 字段并不总是符合预定义的 POJO 结构。

这个现象让我意识到,不能再用传统的“强类型映射”思维来对待 Agent 模式的输入。模型的“智能”带来了灵活性的提升,但也打破了接口的刚性约束。我们需要在 Spring Boot 层面建立一道更宽容的解析防线,而不是被动依赖模型严格遵守 JSON Schema。

根因分析

根本原因在于 Gemini 3.7 Flash 的 Agent 优化策略与 Spring Boot 静态类型系统之间的错位

Google 在发布 Gemini 3.7 Flash 时强调,该模型针对 Coding 和 Agents 场景进行了强化,意味着它更擅长理解意图而非拘泥于格式。在 Function Calling 场景中,模型有时会为了节省 Token 或加速推理,输出一段“伪 JSON”或混合格式的内容。例如,它可能返回:

```json
{
"tool": "search_code",
"args": "query: 'NullPointerException' limit: 10"
}
```

而不是标准的:

```json
{
"tool_calls": [
{
"type": "function",
"function": {
"name": "search_code",
"arguments": {
"query": "NullPointerException",
"limit": 10
}
}
}
]
}
```

这种 Schema 漂移(Schema Drift)在传统后端服务中是致命的,但在 AI 原生的 Agent 链路中却日益常见。我们之前的代码直接通过 ObjectMapper.treeToValue() 强转,一旦模型输出略微变形,整个解析链路就会崩溃。

解决方案

解决方案的核心思路是:在接入层引入“柔性解析”机制,并明确区分“结构化数据”与“指令参数”

1. 定义宽松的 DTO 结构

不要直接使用 Spring AI 默认的严格 Bean,而是自定义一个能够兼容多种输出格式的接收对象。利用 Jackson 的 @JsonAnySetter@JsonAnyGetter 来捕获非标准字段。

```java
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;

@Data
public class FlexibleAgentResponse {
private String id;
private String model;
// 兼容工具调用列表,可能是 Array 也可能是单个对象
private Object toolCalls;
private Map extraArgs = new HashMap<>();

@JsonAnySetter
public void setAdditionalField(String key, Object value) {
if (value instanceof Map) {
this.extraArgs.put(key, value);
} else {
// 处理可能的字符串化 JSON
if (value instanceof String && ((String) value).startsWith("{")) {
try {
this.extraArgs.put(key, objectMapper.readValue((String) value, Object.class));
} catch (Exception e) {
this.extraArgs.put(key, value);
}
} else {
this.extraArgs.put(key, value);
}
}
}

// 省略 getter/setter 和 objectMapper 注入
}
```

2. 构建适配器层

在 Spring Boot 的配置类中,注册一个自定义的 ResponseExtractor,专门用于处理 Gemini 3.7 Flash 返回的混合内容。这里的关键是降级处理:如果标准解析失败,则尝试解析为 Map,从中提取关键字段。

```java
import org.springframework.ai.google.chat.model.GoogleChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
public class GeminiAgentConfig {

@Autowired
private RestTemplate restTemplate;

示意图

@Bean
public GoogleChatModel geminiChatModel() {
// 显式指定版本,确保使用 2026-08 发布的最新端点
return GoogleChatModel.builder()
.restTemplate(restTemplate)
.modelName("gemini-3.7-flash-preview") // 注意:具体 API 名称需参照 Google AI Studio 最新文档
.defaultOptions(GoogleChatOptions.builder()
.temperature(0.2) // Agent 任务需要低温度以保证稳定性
.build())
.build();
}
}
```

3. 业务层防抖与重试

针对 Gemini 3.7 Flash 在复杂逻辑下可能出现的瞬时格式错误,我们在 Service 层增加了一个简单的重试与修正机制。当解析失败时,不立即抛出异常,而是将错误信息反馈给模型,要求其“重新以标准 JSON 格式输出”。

```java
public AgentResult executeAgentTask(String userQuery) {
int retryCount = 0;
while (retryCount < 3) {
try {
ChatResponse response = chatModel.call(
Prompt.builder().message(userQuery).build()
);
return parseAgentResult(response);
} catch (JsonSyntaxException | MismatchedInputException e) {
retryCount++;
if (retryCount == 3) {
log.error("Gemini 3.7 Flash output format drift after 3 retries", e);
throw new AgentExecutionException("Model output format invalid", e);
}
// 追加修正指令重试
userQuery += "\n\nIMPORTANT: Output MUST be a valid JSON object conforming to the schema. Do not include markdown fences.";
}
}
return null;
}
```

经验复盘

Gemini 3.7 Flash 的上线标志着 AI 模型从“对话者”向“协作者”的转变。对于后端开发者而言,这意味着我们必须放弃对 LLM 输出的绝对确定性幻想。接入最新的 AI 模型时,首要任务不是测试它的智商,而是测试它的“纪律性”

建议在新版本模型上线初期,先在沙箱环境中收集至少 100 次的输出样本,分析其 Schema 漂移的频率和模式,再决定是否将其直接暴露在用户流量下。对于关键的业务流程,保留“模型输出修正”环节,是保证系统稳定性的必要成本。

#后端 #Java #SpringBoot #Gemini #LLM集成


你在实际项目中有遇到类似问题吗?欢迎在评论区分享你的经验和解决方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

红信鸽科技

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值