fix: 修复知识库索引并发与向量化边界
- 复用 Lucene 和 Elasticsearch 客户端并支持有界批量写入与删除 - 记录脱敏后的 Embedding 失败请求与完整响应 - 为 BGE-M3 分块统一增加上下文硬上限
This commit is contained in:
@@ -11,13 +11,19 @@ import com.easyagents.core.store.VectorData;
|
||||
import com.easyagents.core.util.JSONUtil;
|
||||
import com.easyagents.core.util.Maps;
|
||||
import com.easyagents.core.util.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class OpenAIEmbeddingModel extends BaseEmbeddingModel<OpenAIEmbeddingConfig> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(OpenAIEmbeddingModel.class);
|
||||
private static final String REDACTED_HEADER_VALUE = "[REDACTED]";
|
||||
|
||||
private HttpClient httpClient = new HttpClient();
|
||||
|
||||
public OpenAIEmbeddingModel(OpenAIEmbeddingConfig config) {
|
||||
@@ -40,27 +46,45 @@ public class OpenAIEmbeddingModel extends BaseEmbeddingModel<OpenAIEmbeddingConf
|
||||
|
||||
String payload = promptToEmbeddingsPayload(document, options, config);
|
||||
String endpoint = config.getEndpoint();
|
||||
String requestUrl = endpoint + config.getRequestPath();
|
||||
// https://platform.openai.com/docs/api-reference/embeddings/create
|
||||
String response = httpClient.post(endpoint + config.getRequestPath(), headers, payload);
|
||||
String response = httpClient.post(requestUrl, headers, payload);
|
||||
|
||||
if (StringUtil.noText(response)) {
|
||||
logResponseParsingFailure(
|
||||
"response is null or empty",
|
||||
requestUrl,
|
||||
headers,
|
||||
payload,
|
||||
response
|
||||
);
|
||||
throw new ModelException("response is null or empty.");
|
||||
}
|
||||
|
||||
JSONObject jsonObject = JSON.parseObject(response);
|
||||
String errorMessage = JSONUtil.detectErrorMessage(jsonObject);
|
||||
if (errorMessage != null) {
|
||||
throw new ModelException(errorMessage);
|
||||
}
|
||||
try {
|
||||
JSONObject jsonObject = JSON.parseObject(response);
|
||||
String errorMessage = JSONUtil.detectErrorMessage(jsonObject);
|
||||
if (errorMessage != null) {
|
||||
logResponseParsingFailure(errorMessage, requestUrl, headers, payload, response);
|
||||
throw new ModelException(errorMessage);
|
||||
}
|
||||
|
||||
VectorData vectorData = new VectorData();
|
||||
double[] embedding = JSONUtil.readDoubleArray(jsonObject, "$.data[0].embedding");
|
||||
if (embedding == null || embedding.length == 0) {
|
||||
throw new ModelException(buildMissingEmbeddingMessage());
|
||||
}
|
||||
vectorData.setVector(embedding);
|
||||
VectorData vectorData = new VectorData();
|
||||
double[] embedding = JSONUtil.readDoubleArray(jsonObject, "$.data[0].embedding");
|
||||
if (embedding == null || embedding.length == 0) {
|
||||
String missingEmbeddingMessage = buildMissingEmbeddingMessage();
|
||||
logResponseParsingFailure(missingEmbeddingMessage, requestUrl, headers, payload, response);
|
||||
throw new ModelException(missingEmbeddingMessage);
|
||||
}
|
||||
vectorData.setVector(embedding);
|
||||
|
||||
return vectorData;
|
||||
return vectorData;
|
||||
} catch (ModelException e) {
|
||||
throw e;
|
||||
} catch (RuntimeException e) {
|
||||
logResponseParsingFailure(e.getMessage(), requestUrl, headers, payload, response);
|
||||
throw new ModelException("Failed to parse embedding response.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,4 +138,35 @@ public class OpenAIEmbeddingModel extends BaseEmbeddingModel<OpenAIEmbeddingConf
|
||||
+ ", endpoint=" + config.getEndpoint()
|
||||
+ ", requestPath=" + config.getRequestPath();
|
||||
}
|
||||
|
||||
private void logResponseParsingFailure(String reason,
|
||||
String requestUrl,
|
||||
Map<String, String> requestHeaders,
|
||||
String requestBody,
|
||||
String responseBody) {
|
||||
LOG.error(
|
||||
"Embedding response parsing failed: reason={}\n"
|
||||
+ "requestUrl={}\n"
|
||||
+ "requestHeaders={}\n"
|
||||
+ "requestBody={}\n"
|
||||
+ "responseBody={}",
|
||||
reason,
|
||||
requestUrl,
|
||||
sanitizeHeadersForLogging(requestHeaders),
|
||||
requestBody,
|
||||
responseBody
|
||||
);
|
||||
}
|
||||
|
||||
static Map<String, String> sanitizeHeadersForLogging(Map<String, String> headers) {
|
||||
Map<String, String> sanitizedHeaders = new LinkedHashMap<>();
|
||||
if (headers == null) {
|
||||
return sanitizedHeaders;
|
||||
}
|
||||
headers.forEach((name, value) -> sanitizedHeaders.put(
|
||||
name,
|
||||
"Authorization".equalsIgnoreCase(name) ? REDACTED_HEADER_VALUE : value
|
||||
));
|
||||
return sanitizedHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.easyagents.core.model.exception.ModelException;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -68,4 +69,46 @@ public class OpenAIEmbeddingModelTest {
|
||||
|
||||
Assert.assertTrue(exception.getMessage().contains("data[0].embedding"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that invalid JSON responses are reported as embedding response parsing failures.
|
||||
*/
|
||||
@Test
|
||||
public void shouldThrowModelExceptionWhenResponseIsInvalidJson() {
|
||||
OpenAIEmbeddingConfig config = new OpenAIEmbeddingConfig();
|
||||
config.setProvider("test-provider");
|
||||
config.setModel("BAAI/bge-m3");
|
||||
config.setApiKey("test-key");
|
||||
OpenAIEmbeddingModel model = new OpenAIEmbeddingModel(config);
|
||||
model.setHttpClient(new HttpClient() {
|
||||
@Override
|
||||
public String post(String url, Map<String, String> headers, String payload) {
|
||||
return "not-json";
|
||||
}
|
||||
});
|
||||
|
||||
ModelException exception = Assert.assertThrows(
|
||||
ModelException.class,
|
||||
() -> model.embed(Document.of("hello"))
|
||||
);
|
||||
|
||||
Assert.assertEquals("Failed to parse embedding response.", exception.getMessage());
|
||||
Assert.assertNotNull(exception.getCause());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that diagnostic headers retain ordinary values without exposing credentials.
|
||||
*/
|
||||
@Test
|
||||
public void shouldRedactAuthorizationHeaderForFailureLogging() {
|
||||
Map<String, String> headers = new LinkedHashMap<>();
|
||||
headers.put("Content-Type", "application/json");
|
||||
headers.put("Authorization", "Bearer test-key");
|
||||
|
||||
Map<String, String> sanitizedHeaders = OpenAIEmbeddingModel.sanitizeHeadersForLogging(headers);
|
||||
|
||||
Assert.assertEquals("application/json", sanitizedHeaders.get("Content-Type"));
|
||||
Assert.assertEquals("[REDACTED]", sanitizedHeaders.get("Authorization"));
|
||||
Assert.assertEquals("Bearer test-key", headers.get("Authorization"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user