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.

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.

Appendix: 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)