spring-projects / spring-projects/spring-ai

Per-call `customHeaders` not propagated in `OpenAiAudioTranscriptionOptions`, `OpenAiAudioSpeechOptions` and `OpenAiModerationOptions`

Open
#6,922 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status: waiting-for-triage
Dominant language
Java
Stars
9.5k
Forks
2.9k
Avg merge
1d 7h
Merged PRs (30d)
6

Description

Bug description

Per-call customHeaders are dropped by the remaining three affected OpenAI models: audio transcription, audio speech, and moderation.
OpenAiChatModel and OpenAiImageOptions already propagate them; embeddings are covered by #6574 and its pending fix #6575.

Root cause

Each model merges the per-call options, then builds request params without reading getCustomHeaders():

Model Request builder on bf122ac7f
Audio transcription buildParams(...)
Audio speech buildSpeechCreateParams(...)
Moderation call(...)

The map is otherwise consumed only while constructing an internally managed client, as shown by OpenAiModerationModel and OpenAiAudioTranscriptionModel.Builder. Per-call values therefore arrive after that read, and supplying an OpenAIClient skips it entirely.

The three buildRequestOptions(...) methods (transcription, speech, and moderation) can set only the timeout because SDK RequestOptions has no header field. The SDK instead exposes putAdditionalHeader on the transcription, speech, and moderation params builders.

Use case affected

A gateway or proxy may require a header derived from each business request, which a static client-level header cannot express on a shared model bean.
This is the same use case as #6082 and #5939.

Environment

  • Spring AI: main at bf122ac7f (2.0.2-SNAPSHOT), and 2.0.0
  • module: spring-ai-openai
  • openai-java: 4.49.0, as pinned by openai-sdk.version on main

Steps to reproduce

ModerationResponse resp = openAiModerationModel.call(new ModerationPrompt("hi",
        OpenAiModerationOptions.builder()
            .customHeaders(Map.of("x-some-header-id", "VALUE_123"))
            .build()));

x-some-header-id is absent from the outgoing /v1/moderations request, and transcription and speech behave the same way.

Expected behavior

Per-call customHeaders are propagated as additionalHeaders on the params sent to the client, matching the chat and image semantics.

Minimal Complete Reproducible Example

This representative test fails on unmodified main; equivalent transcription and speech tests fail the same way:

@Test
void moderationPropagatesPerCallCustomHeaders() {
    OpenAIClient mockClient = mock(OpenAIClient.class, RETURNS_DEEP_STUBS);
    when(mockClient.moderations().create(any(ModerationCreateParams.class), any(RequestOptions.class)))
        .thenReturn(ModerationCreateResponse.builder()
            .id("TEST_ID").model("TEST_MODEL").results(List.of()).build());

    OpenAiModerationModel model = OpenAiModerationModel.builder().openAiClient(mockClient).build();

    model.call(new ModerationPrompt("hi",
            OpenAiModerationOptions.builder()
                .customHeaders(Map.of("x-some-header-id", "VALUE_123"))
                .build()));

    ArgumentCaptor<ModerationCreateParams> captor = ArgumentCaptor.forClass(ModerationCreateParams.class);
    verify(mockClient.moderations()).create(captor.capture(), any(RequestOptions.class));
    assertThat(captor.getValue()._additionalHeaders().values("x-some-header-id"))
        .containsExactly("VALUE_123");
}

Suggested fix

Apply the same addition in all three request builders, matching the OpenAiImageOptions fix from #6083:

if (!CollectionUtils.isEmpty(options.getCustomHeaders())) {
    options.getCustomHeaders().forEach(builder::putAdditionalHeader);
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioTranscriptionModel.java, OpenAiAudioSpeechModel.java, and OpenAiModerationModel.java, focusing on their request-building methods and the provided moderation reproduction. Run or add focused tests that capture the SDK parameter builders; done means per-call customHeaders appear as additional headers for transcription, speech, and moderation requests.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.