mirror of https://github.com/ollama/ollama.git
Merge pull request #11761 from ollama/drifkin/openai-tool-names
openai: when converting role=tool messages, propagate the tool name
This commit is contained in:
commit
223a619468
|
@ -38,6 +38,8 @@ type Message struct {
|
||||||
Content any `json:"content"`
|
Content any `json:"content"`
|
||||||
Reasoning string `json:"reasoning,omitempty"`
|
Reasoning string `json:"reasoning,omitempty"`
|
||||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
ToolCallID string `json:"tool_call_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Choice struct {
|
type Choice struct {
|
||||||
|
@ -401,13 +403,20 @@ func toModel(r api.ShowResponse, m string) Model {
|
||||||
func fromChatRequest(r ChatCompletionRequest) (*api.ChatRequest, error) {
|
func fromChatRequest(r ChatCompletionRequest) (*api.ChatRequest, error) {
|
||||||
var messages []api.Message
|
var messages []api.Message
|
||||||
for _, msg := range r.Messages {
|
for _, msg := range r.Messages {
|
||||||
|
toolName := ""
|
||||||
|
if strings.ToLower(msg.Role) == "tool" {
|
||||||
|
toolName = msg.Name
|
||||||
|
if toolName == "" && msg.ToolCallID != "" {
|
||||||
|
toolName = nameFromToolCallID(r.Messages, msg.ToolCallID)
|
||||||
|
}
|
||||||
|
}
|
||||||
switch content := msg.Content.(type) {
|
switch content := msg.Content.(type) {
|
||||||
case string:
|
case string:
|
||||||
toolCalls, err := fromCompletionToolCall(msg.ToolCalls)
|
toolCalls, err := fromCompletionToolCall(msg.ToolCalls)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
messages = append(messages, api.Message{Role: msg.Role, Content: content, Thinking: msg.Reasoning, ToolCalls: toolCalls})
|
messages = append(messages, api.Message{Role: msg.Role, Content: content, Thinking: msg.Reasoning, ToolCalls: toolCalls, ToolName: toolName})
|
||||||
case []any:
|
case []any:
|
||||||
for _, c := range content {
|
for _, c := range content {
|
||||||
data, ok := c.(map[string]any)
|
data, ok := c.(map[string]any)
|
||||||
|
@ -466,6 +475,9 @@ func fromChatRequest(r ChatCompletionRequest) (*api.ChatRequest, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
messages[len(messages)-1].ToolCalls = toolCalls
|
messages[len(messages)-1].ToolCalls = toolCalls
|
||||||
|
if toolName != "" {
|
||||||
|
messages[len(messages)-1].ToolName = toolName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// content is only optional if tool calls are present
|
// content is only optional if tool calls are present
|
||||||
|
@ -563,6 +575,20 @@ func fromChatRequest(r ChatCompletionRequest) (*api.ChatRequest, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nameFromToolCallID(messages []Message, toolCallID string) string {
|
||||||
|
// iterate backwards to be more resilient to duplicate tool call IDs (this
|
||||||
|
// follows "last one wins")
|
||||||
|
for i := len(messages) - 1; i >= 0; i-- {
|
||||||
|
msg := messages[i]
|
||||||
|
for _, tc := range msg.ToolCalls {
|
||||||
|
if tc.ID == toolCallID {
|
||||||
|
return tc.Function.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func fromCompletionToolCall(toolCalls []ToolCall) ([]api.ToolCall, error) {
|
func fromCompletionToolCall(toolCalls []ToolCall) ([]api.ToolCall, error) {
|
||||||
apiToolCalls := make([]api.ToolCall, len(toolCalls))
|
apiToolCalls := make([]api.ToolCall, len(toolCalls))
|
||||||
for i, tc := range toolCalls {
|
for i, tc := range toolCalls {
|
||||||
|
|
|
@ -274,6 +274,94 @@ func TestChatMiddleware(t *testing.T) {
|
||||||
Stream: &False,
|
Stream: &False,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "tool response with call ID",
|
||||||
|
body: `{
|
||||||
|
"model": "test-model",
|
||||||
|
"messages": [
|
||||||
|
{"role": "user", "content": "What's the weather like in Paris Today?"},
|
||||||
|
{"role": "assistant", "tool_calls": [{"id": "id_abc", "type": "function", "function": {"name": "get_current_weather", "arguments": "{\"location\": \"Paris, France\", \"format\": \"celsius\"}"}}]},
|
||||||
|
{"role": "tool", "tool_call_id": "id_abc", "content": "The weather in Paris is 20 degrees Celsius"}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
req: api.ChatRequest{
|
||||||
|
Model: "test-model",
|
||||||
|
Messages: []api.Message{
|
||||||
|
{
|
||||||
|
Role: "user",
|
||||||
|
Content: "What's the weather like in Paris Today?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "assistant",
|
||||||
|
ToolCalls: []api.ToolCall{
|
||||||
|
{
|
||||||
|
Function: api.ToolCallFunction{
|
||||||
|
Name: "get_current_weather",
|
||||||
|
Arguments: map[string]any{
|
||||||
|
"location": "Paris, France",
|
||||||
|
"format": "celsius",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "tool",
|
||||||
|
Content: "The weather in Paris is 20 degrees Celsius",
|
||||||
|
ToolName: "get_current_weather",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Options: map[string]any{
|
||||||
|
"temperature": 1.0,
|
||||||
|
"top_p": 1.0,
|
||||||
|
},
|
||||||
|
Stream: &False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tool response with name",
|
||||||
|
body: `{
|
||||||
|
"model": "test-model",
|
||||||
|
"messages": [
|
||||||
|
{"role": "user", "content": "What's the weather like in Paris Today?"},
|
||||||
|
{"role": "assistant", "tool_calls": [{"id": "id", "type": "function", "function": {"name": "get_current_weather", "arguments": "{\"location\": \"Paris, France\", \"format\": \"celsius\"}"}}]},
|
||||||
|
{"role": "tool", "name": "get_current_weather", "content": "The weather in Paris is 20 degrees Celsius"}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
req: api.ChatRequest{
|
||||||
|
Model: "test-model",
|
||||||
|
Messages: []api.Message{
|
||||||
|
{
|
||||||
|
Role: "user",
|
||||||
|
Content: "What's the weather like in Paris Today?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "assistant",
|
||||||
|
ToolCalls: []api.ToolCall{
|
||||||
|
{
|
||||||
|
Function: api.ToolCallFunction{
|
||||||
|
Name: "get_current_weather",
|
||||||
|
Arguments: map[string]any{
|
||||||
|
"location": "Paris, France",
|
||||||
|
"format": "celsius",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "tool",
|
||||||
|
Content: "The weather in Paris is 20 degrees Celsius",
|
||||||
|
ToolName: "get_current_weather",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Options: map[string]any{
|
||||||
|
"temperature": 1.0,
|
||||||
|
"top_p": 1.0,
|
||||||
|
},
|
||||||
|
Stream: &False,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "chat handler with streaming tools",
|
name: "chat handler with streaming tools",
|
||||||
body: `{
|
body: `{
|
||||||
|
|
Loading…
Reference in New Issue