Skip to content

Commit 08ccfd9

Browse files
authored
Merge pull request #771 from docker/add-web-search
gracefully fall back when model chat template doesn't support tools
2 parents fc7f5ea + b0228e1 commit 08ccfd9

File tree

5 files changed

+666
-141
lines changed

5 files changed

+666
-141
lines changed

cmd/cli/commands/run.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/docker/model-runner/cmd/cli/commands/completion"
1717
"github.com/docker/model-runner/cmd/cli/desktop"
1818
"github.com/docker/model-runner/cmd/cli/readline"
19+
"github.com/docker/model-runner/cmd/cli/tools"
1920
"github.com/docker/model-runner/pkg/inference"
2021
"github.com/docker/model-runner/pkg/inference/scheduling"
2122
"github.com/fatih/color"
@@ -24,6 +25,15 @@ import (
2425
"golang.org/x/term"
2526
)
2627

28+
// defaultTools returns the tools enabled by default for interactive sessions.
29+
// Web search can be disabled by setting DOCKER_MODEL_NO_WEBSEARCH=1.
30+
func defaultTools() []desktop.ClientTool {
31+
if os.Getenv("DOCKER_MODEL_NO_WEBSEARCH") != "" {
32+
return nil
33+
}
34+
return []desktop.ClientTool{&tools.WebSearchTool{}}
35+
}
36+
2737
// readMultilineInput reads input from stdin, supporting both single-line and multiline input.
2838
// For multiline input, it detects triple-quoted strings and shows continuation prompts.
2939
func readMultilineInput(cmd *cobra.Command, scanner *bufio.Scanner) (string, error) {
@@ -632,11 +642,13 @@ func chatWithMarkdownContext(ctx context.Context, cmd *cobra.Command, client *de
632642
// This reflects exactly what the model receives.
633643
processedUserMessage = buildUserMessage(prompt, imageURLs)
634644

645+
activeTools := defaultTools()
646+
635647
if !useMarkdown {
636648
// Simple case: just stream as plain text
637649
assistantResponse, err = client.ChatWithMessagesContext(ctx, model, conversationHistory, prompt, imageURLs, func(content string) {
638650
cmd.Print(content)
639-
}, false)
651+
}, false, activeTools...)
640652
return assistantResponse, processedUserMessage, err
641653
}
642654

@@ -655,7 +667,7 @@ func chatWithMarkdownContext(ctx context.Context, cmd *cobra.Command, client *de
655667
} else if rendered != "" {
656668
cmd.Print(rendered)
657669
}
658-
}, true)
670+
}, true, activeTools...)
659671
if err != nil {
660672
return assistantResponse, processedUserMessage, err
661673
}

cmd/cli/desktop/api.go

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,37 @@
11
package desktop
22

3+
// Tool represents an OpenAI function tool definition.
4+
type Tool struct {
5+
Type string `json:"type"`
6+
Function ToolFunction `json:"function"`
7+
}
8+
9+
// ToolFunction holds the schema for a tool.
10+
type ToolFunction struct {
11+
Name string `json:"name"`
12+
Description string `json:"description,omitempty"`
13+
Parameters any `json:"parameters,omitempty"`
14+
}
15+
16+
// ToolCall represents a tool call in a message or streaming delta.
17+
type ToolCall struct {
18+
ID string `json:"id,omitempty"`
19+
Type string `json:"type,omitempty"`
20+
Index int `json:"index"`
21+
Function ToolCallFunction `json:"function"`
22+
}
23+
24+
// ToolCallFunction holds the name and accumulated arguments for a tool call.
25+
type ToolCallFunction struct {
26+
Name string `json:"name,omitempty"`
27+
Arguments string `json:"arguments,omitempty"`
28+
}
29+
330
type OpenAIChatMessage struct {
4-
Role string `json:"role"`
5-
Content interface{} `json:"content"` // Can be string or []ContentPart for multimodal
31+
Role string `json:"role"`
32+
Content any `json:"content,omitempty"` // Can be string or []ContentPart for multimodal
33+
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
34+
ToolCallID string `json:"tool_call_id,omitempty"`
635
}
736

837
// ContentPart represents a part of multimodal content (text or image)
@@ -21,6 +50,7 @@ type OpenAIChatRequest struct {
2150
Model string `json:"model"`
2251
Messages []OpenAIChatMessage `json:"messages"`
2352
Stream bool `json:"stream"`
53+
Tools []Tool `json:"tools,omitempty"`
2454
}
2555

2656
type OpenAIChatResponse struct {
@@ -30,13 +60,15 @@ type OpenAIChatResponse struct {
3060
Model string `json:"model"`
3161
Choices []struct {
3262
Delta struct {
33-
Content string `json:"content"`
34-
Role string `json:"role,omitempty"`
35-
ReasoningContent string `json:"reasoning_content,omitempty"`
63+
Content string `json:"content"`
64+
Role string `json:"role,omitempty"`
65+
ReasoningContent string `json:"reasoning_content,omitempty"`
66+
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
3667
} `json:"delta"`
3768
Message struct {
38-
Content string `json:"content"`
39-
Role string `json:"role,omitempty"`
69+
Content string `json:"content"`
70+
Role string `json:"role,omitempty"`
71+
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
4072
} `json:"message"`
4173
Index int `json:"index"`
4274
FinishReason string `json:"finish_reason"`

0 commit comments

Comments
 (0)