spring-projects / spring-projects/spring-ai
Per-call `customHeaders` not propagated in `OpenAiEmbeddingOptions`
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 9.5k
- Forks
- 2.9k
- Avg merge
- 1d 10h
- Merged PRs (30d)
- 5
Description
A note before the report: I'm not a native English speaker, so I drafted
this issue with the help of an LLM to describe the problem as precisely as I
could. Apologies for any awkward phrasing, and thank you for your understanding.
Bug description
OpenAiEmbeddingOptions#customHeaders set on a per-EmbeddingRequest basis are
never applied to the outgoing OpenAI embeddings request. OpenAiEmbeddingOptions#toOpenAiCreateParams(...)
builds the EmbeddingCreateParams from only model / input / user /
encodingFormat / dimensions, and never consumes getCustomHeaders():
// OpenAiEmbeddingOptions#toOpenAiCreateParams (main, 2.0.1-SNAPSHOT)
public EmbeddingCreateParams toOpenAiCreateParams(List<String> instructions) {
EmbeddingCreateParams.Builder builder = EmbeddingCreateParams.builder();
// ... model / input / user / encodingFormat / dimensions ...
return builder.build(); // customHeaders never applied
}
The map is only consumed once, when the underlying OpenAIClient is built in
OpenAiSetup.setupSyncClient(...). So when an OpenAIClient is injected
directly into OpenAiEmbeddingModel, headers configured at runtime (per call)
are silently dropped.
This is inconsistent with:
- the chat side (
OpenAiChatModel#createRequest), which propagates per-call
customHeaderstoChatCompletionCreateParamsviaputAdditionalHeader
(added in2.0.0-M5, commit f36dce3), and - the image side, which had the exact same bug and was already fixed in
#6082 / #6083 (OpenAiImageOptionsnow calls
getCustomHeaders().forEach(builder::putAdditionalHeader)).
Embedding is the remaining OpenAI model type where per-call customHeaders are
still dropped.
Use case affected
Calling LLM-gateway proxies that require a per-request header derived from the
business request (e.g. a distributed-tracing / correlation header such as
trace-id). A static client-level header is not sufficient, because a single
embedding model/client is shared across many callers. This is the same
motivation described in #6082 for images.
Environment
- Spring AI:
main(verified on commit ac8b2da26,2.0.1-SNAPSHOT) - module:
spring-ai-openai
Steps to reproduce
EmbeddingResponse resp = openAiEmbeddingModel.call(new EmbeddingRequest(
List.of("hello world"),
OpenAiEmbeddingOptions.builder()
.model("text-embedding-3-small")
.customHeaders(Map.of("trace-id", "TRACE_123"))
.build()));
Inspect the outgoing HTTP request (OPENAI_LOG=debug). The trace-id header
is not present on the /v1/embeddings call.
Expected behavior
Per-call customHeaders should be propagated as additionalHeaders on the
EmbeddingCreateParams sent to openAiClient.embeddings().create(...),
consistent with chat (OpenAiChatModel#createRequest) and image (#6082/#6083).
Suggested fix
Mirror the image fix (#6083) in OpenAiEmbeddingOptions#toOpenAiCreateParams:
if (!CollectionUtils.isEmpty(this.getCustomHeaders())) {
this.getCustomHeaders().forEach(builder::putAdditionalHeader);
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in OpenAiEmbeddingOptions#toOpenAiCreateParams in the spring-ai-openai module and compare its builder usage with OpenAiImageOptions and OpenAiChatModel#createRequest. Reproduce the issue with OPENAI_LOG=debug and a per-call trace-id header. Done means the header appears on the /v1/embeddings request when supplied through OpenAiEmbeddingOptions.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- api
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100