Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions llms/openai/internal/openaiclient/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,13 @@ type ChatRequest struct {
FunctionCallBehavior FunctionCallBehavior `json:"function_call,omitempty"`

// Metadata allows you to specify additional information that will be passed to the model.
// Note: OpenAI requires store: true when using metadata.
Metadata map[string]any `json:"metadata,omitempty"`

// Store controls whether the request is stored for later retrieval.
// This is required when using metadata per OpenAI's API.
Store bool `json:"store,omitempty"`

// WebSearchOptions configures web search behavior for search-enabled models
// like gpt-4o-search-preview and gpt-4o-mini-search-preview.
WebSearchOptions *WebSearchOptions `json:"web_search_options,omitempty"`
Expand Down
90 changes: 90 additions & 0 deletions llms/openai/internal/openaiclient/marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,93 @@ func TestIsReasoningModel(t *testing.T) {
})
}
}

func TestChatRequest_StoreFieldMarshalJSON(t *testing.T) {
tests := []struct {
name string
request ChatRequest
wantStore bool
wantMetadata bool
}{
{
name: "no metadata - store should not be present",
request: ChatRequest{
Model: "gpt-4",
},
wantStore: false,
wantMetadata: false,
},
{
name: "with metadata - store should be true",
request: ChatRequest{
Model: "gpt-4",
Metadata: map[string]any{
"feature": "support",
},
Store: true,
},
wantStore: true,
wantMetadata: true,
},
{
name: "store explicitly set to false with metadata",
request: ChatRequest{
Model: "gpt-4",
Metadata: map[string]any{
"team": "ai",
},
Store: false,
},
wantStore: false,
wantMetadata: true,
},
{
name: "store explicitly set to true without metadata",
request: ChatRequest{
Model: "gpt-4",
Store: true,
},
wantStore: true,
wantMetadata: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data, err := json.Marshal(tt.request)
if err != nil {
t.Fatalf("failed to marshal: %v", err)
}

var result map[string]interface{}
if err := json.Unmarshal(data, &result); err != nil {
t.Fatalf("failed to unmarshal: %v", err)
}

hasStore := result["store"] != nil
hasMetadata := result["metadata"] != nil

if tt.wantStore && !hasStore {
t.Errorf("expected store to be present in JSON: %s", string(data))
}
if !tt.wantStore && hasStore {
t.Errorf("expected store to NOT be present in JSON: %s", string(data))
}
if tt.wantMetadata && !hasMetadata {
t.Errorf("expected metadata to be present in JSON: %s", string(data))
}
if !tt.wantMetadata && hasMetadata {
t.Errorf("expected metadata to NOT be present in JSON: %s", string(data))
}

if hasStore {
storeVal, ok := result["store"].(bool)
if !ok {
t.Errorf("store is not a bool: %T", result["store"])
} else if storeVal != tt.wantStore {
t.Errorf("store value: got %v, want %v", storeVal, tt.wantStore)
}
}
})
}
}
1 change: 1 addition & 0 deletions llms/openai/openaillm.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ func (o *LLM) GenerateContent(ctx context.Context, messages []llms.MessageConten
FunctionCallBehavior: openaiclient.FunctionCallBehavior(opts.FunctionCallBehavior),
Seed: opts.Seed,
Metadata: apiMetadata,
Store: len(apiMetadata) > 0,
WebSearchOptions: webSearchOptionsFromCallOptions(opts.WebSearchOptions),
}
if opts.JSONMode {
Expand Down
52 changes: 52 additions & 0 deletions llms/openai/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,55 @@ func TestWebSearchOptionsConversion(t *testing.T) {
t.Errorf("expected Country=GB, got %s", result2.UserLocation.Approximate.Country)
}
}

func TestWithMetadataSetsStoreField(t *testing.T) {
// Test that using llms.WithMetadata results in the Store field being set
// This tests the integration in openaillm.go where apiMetadata is populated
// and Store is set based on whether metadata is present

// Simulate the filtering logic from openaillm.go
opts := &llms.CallOptions{}
metadata := map[string]interface{}{
"feature": "support",
"environment": "local",
"team": "ai",
}
llms.WithMetadata(metadata)(opts)

// Verify metadata is set
if opts.Metadata == nil {
t.Fatal("expected Metadata to be set")
}

// Simulate the filtering that happens in openaillm.go
apiMetadata := make(map[string]any)
for k, v := range opts.Metadata {
if k == "thinking_config" { // simulating the HasPrefix check
continue
}
apiMetadata[k] = v
}

// Verify that when metadata is present, Store should be true
store := len(apiMetadata) > 0
if !store {
t.Error("expected Store to be true when metadata is present")
}

// Test with empty metadata
opts2 := &llms.CallOptions{}
llms.WithMetadata(map[string]interface{}{})(opts2)

apiMetadata2 := make(map[string]any)
for k, v := range opts2.Metadata {
if k == "thinking_config" {
continue
}
apiMetadata2[k] = v
}

store2 := len(apiMetadata2) > 0
if store2 {
t.Error("expected Store to be false when metadata is empty")
}
}
Loading