mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 03:55:09 +08:00
fix(llm): pin OpenAI-compatible HTTP client to HTTP/1.1 (#89)
This commit is contained in:
parent
3f1f83bbfe
commit
9fd5843dda
@ -1611,6 +1611,7 @@ public class AgentGraphBuilder {
|
|||||||
private RestClient.Builder applyHttpTimeouts(RestClient.Builder builder, Integer readTimeoutOverride) {
|
private RestClient.Builder applyHttpTimeouts(RestClient.Builder builder, Integer readTimeoutOverride) {
|
||||||
HttpClient httpClient = HttpClient.newBuilder()
|
HttpClient httpClient = HttpClient.newBuilder()
|
||||||
.connectTimeout(vip.mate.llm.chatmodel.HttpTimeouts.CONNECT_TIMEOUT)
|
.connectTimeout(vip.mate.llm.chatmodel.HttpTimeouts.CONNECT_TIMEOUT)
|
||||||
|
.version(HttpClient.Version.HTTP_1_1)
|
||||||
.build();
|
.build();
|
||||||
JdkClientHttpRequestFactory rf = new JdkClientHttpRequestFactory(httpClient);
|
JdkClientHttpRequestFactory rf = new JdkClientHttpRequestFactory(httpClient);
|
||||||
rf.setReadTimeout(vip.mate.llm.chatmodel.HttpTimeouts.resolveReadTimeout(readTimeoutOverride));
|
rf.setReadTimeout(vip.mate.llm.chatmodel.HttpTimeouts.resolveReadTimeout(readTimeoutOverride));
|
||||||
@ -1641,8 +1642,13 @@ public class AgentGraphBuilder {
|
|||||||
* {@link #applyHttpTimeouts(RestClient.Builder, Integer)}.
|
* {@link #applyHttpTimeouts(RestClient.Builder, Integer)}.
|
||||||
*/
|
*/
|
||||||
private WebClient.Builder applyHttpTimeoutsToWebClient(WebClient.Builder builder, Integer readTimeoutOverride) {
|
private WebClient.Builder applyHttpTimeoutsToWebClient(WebClient.Builder builder, Integer readTimeoutOverride) {
|
||||||
|
// Pin HTTP/1.1: many self-hosted OpenAI-compatible servers (vLLM, lmstudio,
|
||||||
|
// llama.cpp, ollama — all uvicorn/ASGI based) only speak HTTP/1.1 over
|
||||||
|
// cleartext and slam the socket on the JDK client's default H2C upgrade
|
||||||
|
// probe, surfacing as "header parser received no bytes" with no body sent.
|
||||||
HttpClient httpClient = HttpClient.newBuilder()
|
HttpClient httpClient = HttpClient.newBuilder()
|
||||||
.connectTimeout(vip.mate.llm.chatmodel.HttpTimeouts.CONNECT_TIMEOUT)
|
.connectTimeout(vip.mate.llm.chatmodel.HttpTimeouts.CONNECT_TIMEOUT)
|
||||||
|
.version(HttpClient.Version.HTTP_1_1)
|
||||||
.build();
|
.build();
|
||||||
org.springframework.http.client.reactive.JdkClientHttpConnector connector =
|
org.springframework.http.client.reactive.JdkClientHttpConnector connector =
|
||||||
new org.springframework.http.client.reactive.JdkClientHttpConnector(httpClient);
|
new org.springframework.http.client.reactive.JdkClientHttpConnector(httpClient);
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.client.JdkClientHttpRequestFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.client.RestClient;
|
import org.springframework.web.client.RestClient;
|
||||||
@ -13,6 +14,7 @@ import vip.mate.exception.MateClawException;
|
|||||||
import vip.mate.llm.model.*;
|
import vip.mate.llm.model.*;
|
||||||
import vip.mate.llm.oauth.OpenAIOAuthService;
|
import vip.mate.llm.oauth.OpenAIOAuthService;
|
||||||
|
|
||||||
|
import java.net.http.HttpClient;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
@ -413,7 +415,7 @@ public class ModelDiscoveryService {
|
|||||||
}
|
}
|
||||||
String apiKey = provider.getApiKey();
|
String apiKey = provider.getApiKey();
|
||||||
|
|
||||||
RestClient client = RestClient.builder()
|
RestClient client = openAiCompatibleClientBuilder()
|
||||||
.baseUrl(baseUrl)
|
.baseUrl(baseUrl)
|
||||||
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
||||||
.build();
|
.build();
|
||||||
@ -623,7 +625,7 @@ public class ModelDiscoveryService {
|
|||||||
Map<String, Object> kwargs = modelProviderService.readProviderGenerateKwargs(provider);
|
Map<String, Object> kwargs = modelProviderService.readProviderGenerateKwargs(provider);
|
||||||
String completionsPath = resolveCompletionsPath(baseUrl, kwargs);
|
String completionsPath = resolveCompletionsPath(baseUrl, kwargs);
|
||||||
|
|
||||||
RestClient.RequestHeadersSpec<?> spec = RestClient.builder()
|
RestClient.RequestHeadersSpec<?> spec = openAiCompatibleClientBuilder()
|
||||||
.baseUrl(baseUrl)
|
.baseUrl(baseUrl)
|
||||||
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||||
.build()
|
.build()
|
||||||
@ -915,6 +917,23 @@ public class ModelDiscoveryService {
|
|||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a RestClient.Builder pinned to HTTP/1.1 for self-hosted OpenAI-compatible
|
||||||
|
* servers. Java's HttpClient defaults to HTTP/2 and over cleartext attempts an
|
||||||
|
* H2C upgrade ({@code Upgrade: h2c, Connection: Upgrade, HTTP2-Settings: ...}).
|
||||||
|
* Uvicorn-based stacks (vLLM, lmstudio, llama.cpp, ollama) reject the upgrade
|
||||||
|
* by closing the socket mid-handshake — surfacing as either
|
||||||
|
* "header parser received no bytes" on the chat path or, more subtly, a
|
||||||
|
* 400 with body=None on the test path because the body never makes it past
|
||||||
|
* the upgrade negotiation.
|
||||||
|
*/
|
||||||
|
private RestClient.Builder openAiCompatibleClientBuilder() {
|
||||||
|
HttpClient httpClient = HttpClient.newBuilder()
|
||||||
|
.version(HttpClient.Version.HTTP_1_1)
|
||||||
|
.build();
|
||||||
|
return RestClient.builder().requestFactory(new JdkClientHttpRequestFactory(httpClient));
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void applyCustomHeaders(RestClient.RequestHeadersSpec<?> spec, Map<String, Object> kwargs) {
|
private void applyCustomHeaders(RestClient.RequestHeadersSpec<?> spec, Map<String, Object> kwargs) {
|
||||||
if (kwargs == null) {
|
if (kwargs == null) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user