llms/openai: `llms.WithMetadata` causes HTTP 400 from OpenAI: "metadata parameter is only allowed when store is enabled"
Summary
When using llms.WithMetadata(...) to pass request-level metadata to the OpenAI Chat Completions API, the library sends the metadata field without the required top-level "store": true parameter. OpenAI's API rejects the request with a 400 error.
Environment
- langchaingo version:
v0.1.14 - Go version:
1.25.6 - OS: macOS
- OpenAI model:
gpt-5-nano(also reproducible on other models)
Steps to Reproduce
package main
import (
"context"
"log"
"github.com/tmc/langchaingo/llms"
openai "github.com/tmc/langchaingo/llms/openai"
)
func main() {
ctx := context.Background()
llm, err := openai.New(
openai.WithModel("gpt-5-nano"),
)
if err != nil {
log.Fatal(err)
}
resp, err := llm.Call(
ctx,
"Summarize this customer support ticket.",
llms.WithMetadata(map[string]interface{}{
"feature": "support",
"environment": "local",
"team": "ai",
}),
)
if err != nil {
log.Fatal(err) // <-- error occurs here
}
log.Println(resp)
}Actual Result
API returned unexpected status code: 400: The 'metadata' parameter is only allowed when 'store' is enabled.Expected Result
The call succeeds. Metadata is forwarded to OpenAI alongside "store": true, which is the required flag for request-level metadata per OpenAI's documentation.
Root Cause
llms.WithMetadata values (after filtering out internal keys like openai:* and thinking_config) are passed directly into the ChatRequest.Metadata field in llms/openai/internal/openaiclient/chat.go. However, ChatRequest has no Store field and never sets "store": true in the serialized JSON body.
OpenAI treats metadata and store as co-dependent: store: true must be present for metadata to be accepted. Sending metadata without store results in an immediate 400.
Relevant code in llms/openai/openaillm.go:
// Filter out internal metadata that shouldn't be sent to API
apiMetadata := make(map[string]any)
if opts.Metadata != nil {
for k, v := range opts.Metadata {
if k == "thinking_config" || strings.HasPrefix(k, "openai:") {
continue
}
apiMetadata[k] = v
}
}
if len(apiMetadata) == 0 {
apiMetadata = nil
}
req := &openaiclient.ChatRequest{
// ...
Metadata: apiMetadata, // sent without store: true
}ChatRequest in llms/openai/internal/openaiclient/chat.go has no Store field:
type ChatRequest struct {
Model string `json:"model"`
Messages []*ChatMessage `json:"messages"`
// ... all other fields ...
Metadata map[string]any `json:"metadata,omitempty"`
// Store bool is missing
}Proposed Fix
- Add a
Store boolfield toChatRequest:
type ChatRequest struct {
// ... existing fields ...
Store bool `json:"store,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}- In
openaillm.go, setStore: truewhenever non-empty metadata is being sent:
req := &openaiclient.ChatRequest{
// ...
Store: len(apiMetadata) > 0,
Metadata: apiMetadata,
}Alternatively, expose a dedicated WithStore(bool) call option so callers can opt in to storage explicitly, and Store is automatically set to true when metadata is provided.
Workaround
Until this is fixed, call the OpenAI Chat Completions API directly with "store": true in the request body:
type chatRequest struct {
Model string `json:"model"`
Messages []chatMessage `json:"messages"`
Store bool `json:"store"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// ... build and POST to https://api.openai.com/v1/chat/completions manuallyReferences
Source: tmc/langchaingo