Skip to content
Draft
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
4 changes: 3 additions & 1 deletion llms/openai/internal/openaiclient/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ type ChatRequest struct {
// Deprecated: use Tools instead.
Functions []FunctionDefinition `json:"functions,omitempty"`
// Deprecated: use ToolChoice instead.
FunctionCallBehavior FunctionCallBehavior `json:"function_call,omitempty"`
// FunctionCallBehavior can be a string ("none", "auto") or an object {"name": "my_function"}
// to force the model to call a specific function.
FunctionCallBehavior any `json:"function_call,omitempty"`

// Metadata allows you to specify additional information that will be passed to the model.
Metadata map[string]any `json:"metadata,omitempty"`
Expand Down
52 changes: 52 additions & 0 deletions llms/openai/internal/openaiclient/chat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,58 @@ func TestChatMessage_MarshalUnmarshal(t *testing.T) {
require.Equal(t, msg, msg2)
}

func TestChatRequest_FunctionCallBehavior_Marshal(t *testing.T) {
t.Parallel()

t.Run("string none", func(t *testing.T) {
req := ChatRequest{
Model: "gpt-3.5-turbo",
FunctionCallBehavior: FunctionCallBehaviorNone,
}
b, err := json.Marshal(req)
require.NoError(t, err)
var m map[string]json.RawMessage
require.NoError(t, json.Unmarshal(b, &m))
assert.Equal(t, `"none"`, string(m["function_call"]))
})

t.Run("string auto", func(t *testing.T) {
req := ChatRequest{
Model: "gpt-3.5-turbo",
FunctionCallBehavior: FunctionCallBehaviorAuto,
}
b, err := json.Marshal(req)
require.NoError(t, err)
var m map[string]json.RawMessage
require.NoError(t, json.Unmarshal(b, &m))
assert.Equal(t, `"auto"`, string(m["function_call"]))
})

t.Run("object form forces specific function", func(t *testing.T) {
req := ChatRequest{
Model: "gpt-3.5-turbo",
FunctionCallBehavior: struct {
Name string `json:"name"`
}{Name: "my_function"},
}
b, err := json.Marshal(req)
require.NoError(t, err)
var m map[string]json.RawMessage
require.NoError(t, json.Unmarshal(b, &m))
assert.Equal(t, `{"name":"my_function"}`, string(m["function_call"]))
})

t.Run("unset is omitted", func(t *testing.T) {
req := ChatRequest{Model: "gpt-3.5-turbo"}
b, err := json.Marshal(req)
require.NoError(t, err)
var m map[string]json.RawMessage
require.NoError(t, json.Unmarshal(b, &m))
_, present := m["function_call"]
assert.False(t, present, "function_call should be omitted when unset")
})
}

func TestChatMessage_MarshalUnmarshal_WithReasoning(t *testing.T) {
t.Parallel()
msg := ChatMessage{
Expand Down
2 changes: 1 addition & 1 deletion llms/openai/openaillm.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ func (o *LLM) GenerateContent(ctx context.Context, messages []llms.MessageConten
}(),

ToolChoice: opts.ToolChoice,
FunctionCallBehavior: openaiclient.FunctionCallBehavior(opts.FunctionCallBehavior),
FunctionCallBehavior: opts.FunctionCallBehavior,
Seed: opts.Seed,
Metadata: apiMetadata,
WebSearchOptions: webSearchOptionsFromCallOptions(opts.WebSearchOptions),
Expand Down
25 changes: 21 additions & 4 deletions llms/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,10 @@ type CallOptions struct {
// Deprecated: Use Tools instead.
Functions []FunctionDefinition `json:"functions,omitempty"`
// FunctionCallBehavior is the behavior to use when calling functions.
//
// If a specific function should be invoked, use the format:
// `{"name": "my_function"}`
// It can be a FunctionCallBehavior string ("none", "auto") or a
// FunctionCallBehaviorFunctionObject to force calling a specific function.
// Deprecated: Use ToolChoice instead.
FunctionCallBehavior FunctionCallBehavior `json:"function_call,omitempty"`
FunctionCallBehavior any `json:"function_call,omitempty"`

// Metadata is a map of metadata to include in the request.
// The meaning of this field is specific to the backend in use.
Expand Down Expand Up @@ -153,6 +152,13 @@ const (
FunctionCallBehaviorAuto FunctionCallBehavior = "auto"
)

// FunctionCallBehaviorFunctionObject forces the model to call the named function.
// Use with WithFunctionCallBehaviorFunction.
// Deprecated: Use ToolChoice with a ToolChoice object instead.
type FunctionCallBehaviorFunctionObject struct {
Name string `json:"name"`
}

// WithModel specifies which model name to use.
func WithModel(model string) CallOption {
return func(o *CallOptions) {
Expand Down Expand Up @@ -274,13 +280,24 @@ func WithPresencePenalty(presencePenalty float64) CallOption {
}

// WithFunctionCallBehavior will add an option to set the behavior to use when calling functions.
// Accepts FunctionCallBehaviorNone or FunctionCallBehaviorAuto.
// To force a specific function, use WithFunctionCallBehaviorFunction instead.
// Deprecated: Use WithToolChoice instead.
func WithFunctionCallBehavior(behavior FunctionCallBehavior) CallOption {
return func(o *CallOptions) {
o.FunctionCallBehavior = behavior
}
}

// WithFunctionCallBehaviorFunction forces the model to call the named function.
// This sets function_call to {"name": "<name>"} in the OpenAI API request.
// Deprecated: Use WithToolChoice with a ToolChoice object instead.
func WithFunctionCallBehaviorFunction(name string) CallOption {
return func(o *CallOptions) {
o.FunctionCallBehavior = FunctionCallBehaviorFunctionObject{Name: name}
}
}

// WithFunctions will add an option to set the functions to include in the request.
// Deprecated: Use WithTools instead.
func WithFunctions(functions []FunctionDefinition) CallOption {
Expand Down
28 changes: 28 additions & 0 deletions llms/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,34 @@ func TestDeprecatedFunctionOptions(t *testing.T) {
})
}

func TestWithFunctionCallBehaviorFunction(t *testing.T) {
t.Run("sets object form", func(t *testing.T) {
var opts llms.CallOptions
llms.WithFunctionCallBehaviorFunction("my_function")(&opts)

want := llms.FunctionCallBehaviorFunctionObject{Name: "my_function"}
if !reflect.DeepEqual(opts.FunctionCallBehavior, want) {
t.Errorf("FunctionCallBehavior = %v, want %v", opts.FunctionCallBehavior, want)
}
})

t.Run("unset is nil", func(t *testing.T) {
var opts llms.CallOptions
if opts.FunctionCallBehavior != nil {
t.Errorf("default FunctionCallBehavior = %v, want nil", opts.FunctionCallBehavior)
}
})

t.Run("string and object forms are distinct", func(t *testing.T) {
var opts llms.CallOptions
llms.WithFunctionCallBehaviorFunction("get_weather")(&opts)

if opts.FunctionCallBehavior == llms.FunctionCallBehaviorAuto {
t.Error("object-form FunctionCallBehavior should not equal string-form auto")
}
})
}

func TestMultipleOptions(t *testing.T) {
var opts llms.CallOptions

Expand Down
Loading