M5 AP-003 Timeout-Konfiguration korrigiert und Adapter-Tests auf echten
Request-Pfad geschärft
This commit is contained in:
+35
-9
@@ -100,6 +100,7 @@ public class OpenAiHttpAdapter implements AiInvocationPort {
|
||||
private final URI apiBaseUrl;
|
||||
private final String apiModel;
|
||||
private final String apiKey;
|
||||
private final int apiTimeoutSeconds;
|
||||
|
||||
/**
|
||||
* Creates an adapter with configuration from startup configuration.
|
||||
@@ -113,7 +114,27 @@ public class OpenAiHttpAdapter implements AiInvocationPort {
|
||||
* @throws IllegalArgumentException if API base URL or model is missing/empty
|
||||
*/
|
||||
public OpenAiHttpAdapter(StartConfiguration config) {
|
||||
this(config, HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(config.apiTimeoutSeconds()))
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an adapter with a custom HTTP client (primarily for testing).
|
||||
* <p>
|
||||
* This constructor allows tests to inject a mock or configurable HTTP client
|
||||
* while keeping configuration validation consistent with the production constructor.
|
||||
* <p>
|
||||
* <strong>For testing only:</strong> This is package-private to remain internal to the adapter.
|
||||
*
|
||||
* @param config the startup configuration containing API settings; must not be null
|
||||
* @param httpClient the HTTP client to use; must not be null
|
||||
* @throws NullPointerException if config or httpClient is null
|
||||
* @throws IllegalArgumentException if API base URL or model is missing/empty
|
||||
*/
|
||||
OpenAiHttpAdapter(StartConfiguration config, HttpClient httpClient) {
|
||||
Objects.requireNonNull(config, "config must not be null");
|
||||
Objects.requireNonNull(httpClient, "httpClient must not be null");
|
||||
if (config.apiBaseUrl() == null) {
|
||||
throw new IllegalArgumentException("API base URL must not be null");
|
||||
}
|
||||
@@ -124,13 +145,11 @@ public class OpenAiHttpAdapter implements AiInvocationPort {
|
||||
this.apiBaseUrl = config.apiBaseUrl();
|
||||
this.apiModel = config.apiModel();
|
||||
this.apiKey = config.apiKey() != null ? config.apiKey() : "";
|
||||
|
||||
this.httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(config.apiTimeoutSeconds()))
|
||||
.build();
|
||||
this.apiTimeoutSeconds = config.apiTimeoutSeconds();
|
||||
this.httpClient = httpClient;
|
||||
|
||||
LOG.debug("OpenAiHttpAdapter initialized with base URL: {}, model: {}, timeout: {}s",
|
||||
apiBaseUrl, apiModel, config.apiTimeoutSeconds());
|
||||
apiBaseUrl, apiModel, apiTimeoutSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,8 +162,8 @@ public class OpenAiHttpAdapter implements AiInvocationPort {
|
||||
* The request representation contains:
|
||||
* <ul>
|
||||
* <li>Prompt content and identifier (for audit)</li>
|
||||
* <li>Document text (already limited to max characters by Application)</li>
|
||||
* <li>Exact character count sent to the AI</li>
|
||||
* <li>Document text prepared by the Application layer</li>
|
||||
* <li>Character count metadata (for audit, not used to truncate content)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* These are formatted as system and user messages for the Chat Completions API.
|
||||
@@ -207,6 +226,7 @@ public class OpenAiHttpAdapter implements AiInvocationPort {
|
||||
* <li>Endpoint URL: {@code {apiBaseUrl}/v1/chat/completions}</li>
|
||||
* <li>Headers: Authorization with Bearer token, Content-Type application/json</li>
|
||||
* <li>Body: JSON with model, messages (system = prompt, user = document text)</li>
|
||||
* <li>Timeout: configured timeout from startup configuration</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param request the request representation with prompt and document text
|
||||
@@ -221,7 +241,7 @@ public class OpenAiHttpAdapter implements AiInvocationPort {
|
||||
.header("Content-Type", CONTENT_TYPE)
|
||||
.header(AUTHORIZATION_HEADER, BEARER_PREFIX + apiKey)
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.timeout(Duration.ofSeconds(30)) // Additional timeout on request builder
|
||||
.timeout(Duration.ofSeconds(apiTimeoutSeconds))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -251,6 +271,12 @@ public class OpenAiHttpAdapter implements AiInvocationPort {
|
||||
* </ul>
|
||||
* <p>
|
||||
* The prompt is sent as system message; the document text as user message.
|
||||
* <p>
|
||||
* <strong>Text limiting:</strong> The document text is already limited to the maximum
|
||||
* characters by the Application layer before creating the request representation.
|
||||
* The {@link AiRequestRepresentation#sentCharacterCount()} is recorded as audit metadata
|
||||
* but is <strong>not</strong> used to truncate content in the adapter. The full
|
||||
* document text is sent to the AI service.
|
||||
*
|
||||
* @param request the request with prompt and document text
|
||||
* @return JSON string ready to send in HTTP body
|
||||
@@ -266,7 +292,7 @@ public class OpenAiHttpAdapter implements AiInvocationPort {
|
||||
|
||||
JSONObject userMessage = new JSONObject();
|
||||
userMessage.put("role", "user");
|
||||
userMessage.put("content", request.documentText().substring(0, request.sentCharacterCount()));
|
||||
userMessage.put("content", request.documentText());
|
||||
|
||||
body.put("messages", new org.json.JSONArray()
|
||||
.put(systemMessage)
|
||||
|
||||
Reference in New Issue
Block a user