mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(llm): apply read timeout to streaming chat WebClient (openai-compat + anthropic)
This commit is contained in:
parent
92a7508dac
commit
6390abdecc
@ -1066,7 +1066,8 @@ public class AgentGraphBuilder {
|
||||
String completionsPath = resolveOpenAiCompletionsPath(baseUrl, kwargs);
|
||||
RestClient.Builder restClientBuilder = applyHttpTimeouts(
|
||||
restClientBuilderProvider.getIfAvailable(RestClient::builder));
|
||||
WebClient.Builder webClientBuilder = webClientBuilderProvider.getIfAvailable(WebClient::builder);
|
||||
WebClient.Builder webClientBuilder = applyHttpTimeoutsToWebClient(
|
||||
webClientBuilderProvider.getIfAvailable(WebClient::builder));
|
||||
|
||||
// Spring AI OpenAiApi 构造函数会先 set User-Agent 为 "spring-ai",再 addAll 我们的 headers,
|
||||
// 导致自定义 User-Agent 被追加而非覆盖。因此对需要伪装客户端身份的 provider(如 kimi-code),
|
||||
@ -1431,6 +1432,31 @@ public class AgentGraphBuilder {
|
||||
return builder.requestFactory(rf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply equivalent timeouts to the WebClient that backs OpenAI-compatible
|
||||
* STREAMING calls (chat completions with {@code stream:true}). The
|
||||
* RestClient version above only protects synchronous HTTP — without this,
|
||||
* the streaming code path uses the default {@code WebClient} which has
|
||||
* neither connect nor read timeout, so a stalled provider can hang the
|
||||
* call forever (observed: a single volcengine-plan request held the agent
|
||||
* thread for 9+ minutes with no error, until the user manually pressed
|
||||
* Stop). That kept the failover chain idle because nothing threw.
|
||||
* <p>
|
||||
* Uses {@link JdkClientHttpConnector} with the same {@link HttpClient} we
|
||||
* already use for the RestClient so the dependency surface stays clean
|
||||
* (reactor-netty is not on this project's classpath — Spring's webflux
|
||||
* starter is excluded by design).
|
||||
*/
|
||||
private WebClient.Builder applyHttpTimeoutsToWebClient(WebClient.Builder builder) {
|
||||
HttpClient httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
org.springframework.http.client.reactive.JdkClientHttpConnector connector =
|
||||
new org.springframework.http.client.reactive.JdkClientHttpConnector(httpClient);
|
||||
connector.setReadTimeout(Duration.ofSeconds(180));
|
||||
return builder.clientConnector(connector);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 generateKwargs.headers 中提取需要强制覆盖的 headers。
|
||||
* 用于通过 RestClient/WebClient 拦截器绕过 Spring AI OpenAiApi 的默认 User-Agent。
|
||||
|
||||
@ -86,7 +86,8 @@ public class AgentAnthropicChatModelBuilder implements ChatModelBuilder {
|
||||
String baseUrl = provider.getBaseUrl();
|
||||
RestClient.Builder restClientBuilder = applyHttpTimeouts(
|
||||
restClientBuilderProvider.getIfAvailable(RestClient::builder));
|
||||
WebClient.Builder webClientBuilder = webClientBuilderProvider.getIfAvailable(WebClient::builder);
|
||||
WebClient.Builder webClientBuilder = applyHttpTimeoutsToWebClient(
|
||||
webClientBuilderProvider.getIfAvailable(WebClient::builder));
|
||||
|
||||
AnthropicApi.Builder builder = AnthropicApi.builder()
|
||||
.apiKey(apiKey.trim())
|
||||
@ -200,4 +201,25 @@ public class AgentAnthropicChatModelBuilder implements ChatModelBuilder {
|
||||
rf.setReadTimeout(Duration.ofSeconds(180));
|
||||
return builder.requestFactory(rf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streaming counterpart of {@link #applyHttpTimeouts(RestClient.Builder)}.
|
||||
* Without this, Spring AI's AnthropicApi would back its streaming chat
|
||||
* call by a default WebClient with neither connect nor read timeout — a
|
||||
* stalled provider could hang the agent thread indefinitely while the
|
||||
* failover chain idles (no exception = no signal).
|
||||
* <p>
|
||||
* Mirrors AgentGraphBuilder.applyHttpTimeoutsToWebClient: same JDK
|
||||
* HttpClient + JdkClientHttpConnector path, so the dependency surface
|
||||
* doesn't pull in reactor-netty (excluded by this project's pom).
|
||||
*/
|
||||
static WebClient.Builder applyHttpTimeoutsToWebClient(WebClient.Builder builder) {
|
||||
HttpClient httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
org.springframework.http.client.reactive.JdkClientHttpConnector connector =
|
||||
new org.springframework.http.client.reactive.JdkClientHttpConnector(httpClient);
|
||||
connector.setReadTimeout(Duration.ofSeconds(180));
|
||||
return builder.clientConnector(connector);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user