conductor-oss/conductor · error · RuntimeException

Embeddings API call failed:

Error message

Embeddings API call failed: 

What it means

OpenAI.java wraps an IOException from OpenAIEmbeddingsApi.createEmbeddings() in a RuntimeException with the message "Embeddings API call failed: " + underlying message. The underlying IOException is thrown by OpenAIEmbeddingsApi when the HTTP response is non-2xx (error 217) or when the OkHttp call itself fails (DNS, timeout, connection refused). This wrapper preserves the cause chain via the second constructor argument.

Source

Thrown at ai/src/main/java/org/conductoross/conductor/ai/providers/openai/OpenAI.java:110

    public String getModelProvider() {
        return NAME;
    }

    @Override
    public List<Float> generateEmbeddings(EmbeddingGenRequest embeddingGenRequest) {
        try {
            var request =
                    new OpenAIEmbeddingsApi.EmbeddingRequest(
                            embeddingGenRequest.getModel(),
                            embeddingGenRequest.getText(),
                            embeddingGenRequest.getDimensions());
            var result = embeddingsApi.createEmbeddings(request);
            if (result.data() != null && !result.data().isEmpty()) {
                return result.data().getFirst().embedding();
            }
            return List.of();
        } catch (IOException e) {
            throw new RuntimeException("Embeddings API call failed: " + e.getMessage(), e);
        }
    }

    @Override
    public ChatOptions getChatOptions(ChatCompletion input) {
        List<Tool> tools = convertTools(input);

        OpenAIResponsesChatOptions.OpenAIResponsesChatOptionsBuilder builder =
                OpenAIResponsesChatOptions.builder()
                        .model(input.getModel())
                        .topP(input.getTopP())
                        .frequencyPenalty(input.getFrequencyPenalty())
                        .presencePenalty(input.getPresencePenalty())
                        .maxTokens(input.getMaxTokens())
                        .stopSequences(input.getStopWords())
                        .previousResponseId(input.getPreviousResponseId())
                        .reasoningEffort(input.getReasoningEffort())
                        .reasoningSummary(input.getReasoningSummary())

View on GitHub (pinned to cf7c3e4a8a)

Solutions

  1. Check the wrapped cause (getCause()) — it contains the HTTP status and response body from the API (e.g. 401 Unauthorized, 429 rate-limited).
  2. Verify the embedding model name matches an OpenAI embeddings model (text-embedding-3-small, text-embedding-3-large, text-embedding-ada-002).
  3. Verify the API key is valid and the baseURL ends with /v1 (the OpenAI constructor normalizes this, but check if using a custom OpenAI instance).
  4. For 429 rate limits, add backoff/retry in the calling workflow task.

Example fix

// before
try {
    List<Float> emb = llm.generateEmbeddings(req);
} catch (RuntimeException e) {
    log.error("embedding failed", e);
}
// after
try {
    List<Float> emb = llm.generateEmbeddings(req);
} catch (RuntimeException e) {
    Throwable cause = e.getCause();
    if (cause instanceof IOException io) {
        log.error("Embeddings API IOException: {}", io.getMessage());
    }
    throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate model name and API key before calling generateEmbeddings()
String model = embeddingGenRequest.getModel();
if (model == null || model.isBlank()) {
    throw new IllegalArgumentException("Embedding model name is required");
}
// Validate it's a known embeddings model
Set<String> validEmbeddingModels = Set.of(
    "text-embedding-3-small", "text-embedding-3-large", "text-embedding-ada-002");
if (!validEmbeddingModels.contains(model)) {
    log.warn("Model '{}' is not in known embeddings model list", model);
}

Type guard

null

Try / catch

try {
    List<Float> embeddings = llm.generateEmbeddings(request);
} catch (RuntimeException e) {
    Throwable cause = e.getCause();
    if (cause instanceof IOException) {
        // Network or HTTP error — check the message for status code
        log.error("Embeddings API failed: {}", cause.getMessage());
        if (cause.getMessage().contains("429")) {
            // Rate limit — retry with backoff
            Thread.sleep(backoffMs);
            return retry(request);
        }
    }
    throw e;
}

Prevention

When it happens

Trigger: generateEmbeddings() is called (via LLMHelper) and the POST /v1/embeddings request fails: invalid/expired API key (401), rate limit (429), wrong model name (404), wrong baseURL (404/connection refused), or network timeout. The IOException from the API client is caught and rethrown as RuntimeException.

Common situations: Expired or revoked API key; embedding model name typo (e.g. "text-embedding-3" instead of "text-embedding-3-small"); baseURL misconfigured without /v1 suffix (handled by ensureV1 but custom configs may bypass); network proxy/firewall blocking api.openai.com; rate limit during bulk indexing.

Related errors


AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14). Data as JSON: /api/errors/a6d7b14ea34758f5. Report an issue: GitHub.