mirror of
https://github.com/ollama/ollama
synced 2026-04-23 08:45:14 +00:00
Merge a4654f9b2f into 160660e572
This commit is contained in:
commit
6dfddac28b
81
cmd/cmd.go
81
cmd/cmd.go
|
|
@ -596,6 +596,12 @@ func RunHandler(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
opts.HideThinking = hidethinking
|
||||
|
||||
thinkingstderr, err := cmd.Flags().GetBool("thinkingstderr")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.ThinkingToStderr = thinkingstderr
|
||||
|
||||
keepAlive, err := cmd.Flags().GetString("keepalive")
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -761,7 +767,7 @@ func RunHandler(cmd *cobra.Command, args []string) error {
|
|||
|
||||
// Use experimental agent loop with tools
|
||||
if isExperimental {
|
||||
return xcmd.GenerateInteractive(cmd, opts.Model, opts.WordWrap, opts.Options, opts.Think, opts.HideThinking, opts.KeepAlive, yoloMode, enableWebsearch)
|
||||
return xcmd.GenerateInteractive(cmd, opts.Model, opts.WordWrap, opts.Options, opts.Think, opts.HideThinking, opts.ThinkingToStderr, opts.KeepAlive, yoloMode, enableWebsearch)
|
||||
}
|
||||
|
||||
return generateInteractive(cmd, opts)
|
||||
|
|
@ -1424,8 +1430,9 @@ type runOptions struct {
|
|||
MultiModal bool
|
||||
KeepAlive *api.Duration
|
||||
Think *api.ThinkValue
|
||||
HideThinking bool
|
||||
ShowConnect bool
|
||||
HideThinking bool
|
||||
ThinkingToStderr bool
|
||||
ShowConnect bool
|
||||
}
|
||||
|
||||
func (r runOptions) Copy() runOptions {
|
||||
|
|
@ -1475,8 +1482,9 @@ func (r runOptions) Copy() runOptions {
|
|||
MultiModal: r.MultiModal,
|
||||
KeepAlive: r.KeepAlive,
|
||||
Think: think,
|
||||
HideThinking: r.HideThinking,
|
||||
ShowConnect: r.ShowConnect,
|
||||
HideThinking: r.HideThinking,
|
||||
ThinkingToStderr: r.ThinkingToStderr,
|
||||
ShowConnect: r.ShowConnect,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1491,7 +1499,11 @@ type displayResponseState struct {
|
|||
}
|
||||
|
||||
func displayResponse(content string, wordWrap bool, state *displayResponseState) {
|
||||
termWidth, _, _ := term.GetSize(int(os.Stdout.Fd()))
|
||||
displayResponseTo(os.Stdout, int(os.Stdout.Fd()), content, wordWrap, state)
|
||||
}
|
||||
|
||||
func displayResponseTo(w io.Writer, fd int, content string, wordWrap bool, state *displayResponseState) {
|
||||
termWidth, _, _ := term.GetSize(fd)
|
||||
if termWidth == 0 {
|
||||
termWidth = 80
|
||||
}
|
||||
|
|
@ -1499,7 +1511,7 @@ func displayResponse(content string, wordWrap bool, state *displayResponseState)
|
|||
for _, ch := range content {
|
||||
if state.lineLength+1 > termWidth-5 {
|
||||
if runewidth.StringWidth(state.wordBuffer) > termWidth-10 {
|
||||
fmt.Printf("%s%c", state.wordBuffer, ch)
|
||||
fmt.Fprintf(w, "%s%c", state.wordBuffer, ch)
|
||||
state.wordBuffer = ""
|
||||
state.lineLength = 0
|
||||
continue
|
||||
|
|
@ -1508,15 +1520,15 @@ func displayResponse(content string, wordWrap bool, state *displayResponseState)
|
|||
// backtrack the length of the last word and clear to the end of the line
|
||||
a := runewidth.StringWidth(state.wordBuffer)
|
||||
if a > 0 {
|
||||
fmt.Printf("\x1b[%dD", a)
|
||||
fmt.Fprintf(w, "\x1b[%dD", a)
|
||||
}
|
||||
fmt.Printf("\x1b[K\n")
|
||||
fmt.Printf("%s%c", state.wordBuffer, ch)
|
||||
fmt.Fprintf(w, "\x1b[K\n")
|
||||
fmt.Fprintf(w, "%s%c", state.wordBuffer, ch)
|
||||
chWidth := runewidth.RuneWidth(ch)
|
||||
|
||||
state.lineLength = runewidth.StringWidth(state.wordBuffer) + chWidth
|
||||
} else {
|
||||
fmt.Print(string(ch))
|
||||
fmt.Fprint(w, string(ch))
|
||||
state.lineLength += runewidth.RuneWidth(ch)
|
||||
if runewidth.RuneWidth(ch) >= 2 {
|
||||
state.wordBuffer = ""
|
||||
|
|
@ -1535,7 +1547,7 @@ func displayResponse(content string, wordWrap bool, state *displayResponseState)
|
|||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("%s%s", state.wordBuffer, content)
|
||||
fmt.Fprintf(w, "%s%s", state.wordBuffer, content)
|
||||
if len(state.wordBuffer) > 0 {
|
||||
state.wordBuffer = ""
|
||||
}
|
||||
|
|
@ -1592,32 +1604,42 @@ func chat(cmd *cobra.Command, opts runOptions) (*api.Message, error) {
|
|||
var thinkTagOpened bool = false
|
||||
var thinkTagClosed bool = false
|
||||
|
||||
showThinking := !opts.HideThinking || opts.ThinkingToStderr
|
||||
thinkWriter := io.Writer(os.Stdout)
|
||||
thinkFd := int(os.Stdout.Fd())
|
||||
thinkPlainText := false
|
||||
if opts.ThinkingToStderr {
|
||||
thinkWriter = os.Stderr
|
||||
thinkFd = int(os.Stderr.Fd())
|
||||
thinkPlainText = !term.IsTerminal(thinkFd)
|
||||
}
|
||||
|
||||
role := "assistant"
|
||||
|
||||
fn := func(response api.ChatResponse) error {
|
||||
if response.Message.Content != "" || !opts.HideThinking {
|
||||
if response.Message.Content != "" || showThinking {
|
||||
p.StopAndClear()
|
||||
}
|
||||
|
||||
latest = response
|
||||
|
||||
role = response.Message.Role
|
||||
if response.Message.Thinking != "" && !opts.HideThinking {
|
||||
if response.Message.Thinking != "" && showThinking {
|
||||
if !thinkTagOpened {
|
||||
fmt.Print(thinkingOutputOpeningText(false))
|
||||
fmt.Fprint(thinkWriter, thinkingOutputOpeningText(thinkPlainText))
|
||||
thinkTagOpened = true
|
||||
thinkTagClosed = false
|
||||
}
|
||||
thinkingContent.WriteString(response.Message.Thinking)
|
||||
displayResponse(response.Message.Thinking, opts.WordWrap, state)
|
||||
displayResponseTo(thinkWriter, thinkFd, response.Message.Thinking, opts.WordWrap, state)
|
||||
}
|
||||
|
||||
content := response.Message.Content
|
||||
if thinkTagOpened && !thinkTagClosed && (content != "" || len(response.Message.ToolCalls) > 0) {
|
||||
if !strings.HasSuffix(thinkingContent.String(), "\n") {
|
||||
fmt.Println()
|
||||
fmt.Fprintln(thinkWriter)
|
||||
}
|
||||
fmt.Print(thinkingOutputClosingText(false))
|
||||
fmt.Fprint(thinkWriter, thinkingOutputClosingText(thinkPlainText))
|
||||
thinkTagOpened = false
|
||||
thinkTagClosed = true
|
||||
state = &displayResponseState{}
|
||||
|
|
@ -1725,29 +1747,39 @@ func generate(cmd *cobra.Command, opts runOptions) error {
|
|||
|
||||
plainText := !term.IsTerminal(int(os.Stdout.Fd()))
|
||||
|
||||
showThinking := !opts.HideThinking || opts.ThinkingToStderr
|
||||
thinkWriter := io.Writer(os.Stdout)
|
||||
thinkFd := int(os.Stdout.Fd())
|
||||
thinkPlainText := plainText
|
||||
if opts.ThinkingToStderr {
|
||||
thinkWriter = os.Stderr
|
||||
thinkFd = int(os.Stderr.Fd())
|
||||
thinkPlainText = !term.IsTerminal(thinkFd)
|
||||
}
|
||||
|
||||
fn := func(response api.GenerateResponse) error {
|
||||
latest = response
|
||||
content := response.Response
|
||||
|
||||
if response.Response != "" || !opts.HideThinking {
|
||||
if response.Response != "" || showThinking {
|
||||
p.StopAndClear()
|
||||
}
|
||||
|
||||
if response.Thinking != "" && !opts.HideThinking {
|
||||
if response.Thinking != "" && showThinking {
|
||||
if !thinkTagOpened {
|
||||
fmt.Print(thinkingOutputOpeningText(plainText))
|
||||
fmt.Fprint(thinkWriter, thinkingOutputOpeningText(thinkPlainText))
|
||||
thinkTagOpened = true
|
||||
thinkTagClosed = false
|
||||
}
|
||||
thinkingContent.WriteString(response.Thinking)
|
||||
displayResponse(response.Thinking, opts.WordWrap, state)
|
||||
displayResponseTo(thinkWriter, thinkFd, response.Thinking, opts.WordWrap, state)
|
||||
}
|
||||
|
||||
if thinkTagOpened && !thinkTagClosed && (content != "" || len(response.ToolCalls) > 0) {
|
||||
if !strings.HasSuffix(thinkingContent.String(), "\n") {
|
||||
fmt.Println()
|
||||
fmt.Fprintln(thinkWriter)
|
||||
}
|
||||
fmt.Print(thinkingOutputClosingText(plainText))
|
||||
fmt.Fprint(thinkWriter, thinkingOutputClosingText(thinkPlainText))
|
||||
thinkTagOpened = false
|
||||
thinkTagClosed = true
|
||||
state = &displayResponseState{}
|
||||
|
|
@ -2208,6 +2240,7 @@ func NewCLI() *cobra.Command {
|
|||
runCmd.Flags().String("think", "", "Enable thinking mode: true/false or high/medium/low for supported models")
|
||||
runCmd.Flags().Lookup("think").NoOptDefVal = "true"
|
||||
runCmd.Flags().Bool("hidethinking", false, "Hide thinking output (if provided)")
|
||||
runCmd.Flags().Bool("thinkingstderr", false, "Output thinking to stderr instead of stdout")
|
||||
runCmd.Flags().Bool("truncate", false, "For embedding models: truncate inputs exceeding context length (default: true). Set --truncate=false to error instead")
|
||||
runCmd.Flags().Int("dimensions", 0, "Truncate output embeddings to specified dimension (embedding models only)")
|
||||
runCmd.Flags().Bool("experimental", false, "Enable experimental agent loop with tools")
|
||||
|
|
|
|||
|
|
@ -433,6 +433,7 @@ func TestRunEmbeddingModel(t *testing.T) {
|
|||
cmd.Flags().String("format", "", "")
|
||||
cmd.Flags().String("think", "", "")
|
||||
cmd.Flags().Bool("hidethinking", false, "")
|
||||
cmd.Flags().Bool("thinkingstderr", false, "")
|
||||
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
|
|
@ -525,6 +526,7 @@ func TestRunEmbeddingModelWithFlags(t *testing.T) {
|
|||
cmd.Flags().String("format", "", "")
|
||||
cmd.Flags().String("think", "", "")
|
||||
cmd.Flags().Bool("hidethinking", false, "")
|
||||
cmd.Flags().Bool("thinkingstderr", false, "")
|
||||
|
||||
if err := cmd.Flags().Set("truncate", "true"); err != nil {
|
||||
t.Fatalf("failed to set truncate flag: %v", err)
|
||||
|
|
@ -626,6 +628,7 @@ func TestRunEmbeddingModelPipedInput(t *testing.T) {
|
|||
cmd.Flags().String("format", "", "")
|
||||
cmd.Flags().String("think", "", "")
|
||||
cmd.Flags().Bool("hidethinking", false, "")
|
||||
cmd.Flags().Bool("thinkingstderr", false, "")
|
||||
|
||||
// Capture stdin
|
||||
oldStdin := os.Stdin
|
||||
|
|
@ -701,6 +704,7 @@ func TestRunEmbeddingModelNoInput(t *testing.T) {
|
|||
cmd.Flags().String("format", "", "")
|
||||
cmd.Flags().String("think", "", "")
|
||||
cmd.Flags().Bool("hidethinking", false, "")
|
||||
cmd.Flags().Bool("thinkingstderr", false, "")
|
||||
|
||||
cmd.SetOut(io.Discard)
|
||||
cmd.SetErr(io.Discard)
|
||||
|
|
@ -752,6 +756,7 @@ func TestRunHandler_CloudAuthErrorOnShow_PrintsSigninMessage(t *testing.T) {
|
|||
cmd.Flags().String("format", "", "")
|
||||
cmd.Flags().String("think", "", "")
|
||||
cmd.Flags().Bool("hidethinking", false, "")
|
||||
cmd.Flags().Bool("thinkingstderr", false, "")
|
||||
|
||||
oldStdout := os.Stdout
|
||||
readOut, writeOut, _ := os.Pipe()
|
||||
|
|
@ -820,6 +825,7 @@ func TestRunHandler_CloudAuthErrorOnGenerate_PrintsSigninMessage(t *testing.T) {
|
|||
cmd.Flags().String("format", "", "")
|
||||
cmd.Flags().String("think", "", "")
|
||||
cmd.Flags().Bool("hidethinking", false, "")
|
||||
cmd.Flags().Bool("thinkingstderr", false, "")
|
||||
|
||||
oldStdout := os.Stdout
|
||||
readOut, writeOut, _ := os.Pipe()
|
||||
|
|
@ -904,6 +910,7 @@ func TestRunHandler_ExplicitCloudStubMissing_PullsNormalizedNameTEMP(t *testing.
|
|||
cmd.Flags().String("format", "", "")
|
||||
cmd.Flags().String("think", "", "")
|
||||
cmd.Flags().Bool("hidethinking", false, "")
|
||||
cmd.Flags().Bool("thinkingstderr", false, "")
|
||||
|
||||
err := RunHandler(cmd, []string{"gpt-oss:20b:cloud", "hi"})
|
||||
if err != nil {
|
||||
|
|
@ -975,6 +982,7 @@ func TestRunHandler_ExplicitCloudStubPresent_SkipsPullTEMP(t *testing.T) {
|
|||
cmd.Flags().String("format", "", "")
|
||||
cmd.Flags().String("think", "", "")
|
||||
cmd.Flags().Bool("hidethinking", false, "")
|
||||
cmd.Flags().Bool("thinkingstderr", false, "")
|
||||
|
||||
err := RunHandler(cmd, []string{"gpt-oss:20b:cloud", "hi"})
|
||||
if err != nil {
|
||||
|
|
@ -1042,6 +1050,7 @@ func TestRunHandler_ExplicitCloudStubPullFailure_IsBestEffortTEMP(t *testing.T)
|
|||
cmd.Flags().String("format", "", "")
|
||||
cmd.Flags().String("think", "", "")
|
||||
cmd.Flags().Bool("hidethinking", false, "")
|
||||
cmd.Flags().Bool("thinkingstderr", false, "")
|
||||
|
||||
err := RunHandler(cmd, []string{"gpt-oss:20b:cloud", "hi"})
|
||||
if err != nil {
|
||||
|
|
|
|||
66
x/cmd/run.go
66
x/cmd/run.go
|
|
@ -149,8 +149,9 @@ type RunOptions struct {
|
|||
Options map[string]any
|
||||
KeepAlive *api.Duration
|
||||
Think *api.ThinkValue
|
||||
HideThinking bool
|
||||
Verbose bool
|
||||
HideThinking bool
|
||||
ThinkingToStderr bool
|
||||
Verbose bool
|
||||
|
||||
// Agent fields (managed externally for session persistence)
|
||||
Tools *tools.Registry
|
||||
|
|
@ -201,32 +202,42 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) {
|
|||
var consecutiveErrors int // Track consecutive 500 errors for retry limit
|
||||
var latest api.ChatResponse
|
||||
|
||||
showThinking := !opts.HideThinking || opts.ThinkingToStderr
|
||||
thinkWriter := io.Writer(os.Stdout)
|
||||
thinkFd := int(os.Stdout.Fd())
|
||||
thinkPlainText := false
|
||||
if opts.ThinkingToStderr {
|
||||
thinkWriter = os.Stderr
|
||||
thinkFd = int(os.Stderr.Fd())
|
||||
thinkPlainText = !term.IsTerminal(thinkFd)
|
||||
}
|
||||
|
||||
role := "assistant"
|
||||
messages := opts.Messages
|
||||
|
||||
fn := func(response api.ChatResponse) error {
|
||||
if response.Message.Content != "" || !opts.HideThinking {
|
||||
if response.Message.Content != "" || showThinking {
|
||||
p.StopAndClear()
|
||||
}
|
||||
|
||||
latest = response
|
||||
role = response.Message.Role
|
||||
if response.Message.Thinking != "" && !opts.HideThinking {
|
||||
if response.Message.Thinking != "" && showThinking {
|
||||
if !thinkTagOpened {
|
||||
fmt.Print(thinkingOutputOpeningText(false))
|
||||
fmt.Fprint(thinkWriter, thinkingOutputOpeningText(thinkPlainText))
|
||||
thinkTagOpened = true
|
||||
thinkTagClosed = false
|
||||
}
|
||||
thinkingContent.WriteString(response.Message.Thinking)
|
||||
displayResponse(response.Message.Thinking, opts.WordWrap, state)
|
||||
displayResponseTo(thinkWriter, thinkFd, response.Message.Thinking, opts.WordWrap, state)
|
||||
}
|
||||
|
||||
content := response.Message.Content
|
||||
if thinkTagOpened && !thinkTagClosed && (content != "" || len(response.Message.ToolCalls) > 0) {
|
||||
if !strings.HasSuffix(thinkingContent.String(), "\n") {
|
||||
fmt.Println()
|
||||
fmt.Fprintln(thinkWriter)
|
||||
}
|
||||
fmt.Print(thinkingOutputClosingText(false))
|
||||
fmt.Fprint(thinkWriter, thinkingOutputClosingText(thinkPlainText))
|
||||
thinkTagOpened = false
|
||||
thinkTagClosed = true
|
||||
state = &displayResponseState{}
|
||||
|
|
@ -549,12 +560,16 @@ type displayResponseState struct {
|
|||
}
|
||||
|
||||
func displayResponse(content string, wordWrap bool, state *displayResponseState) {
|
||||
termWidth, _, _ := term.GetSize(int(os.Stdout.Fd()))
|
||||
displayResponseTo(os.Stdout, int(os.Stdout.Fd()), content, wordWrap, state)
|
||||
}
|
||||
|
||||
func displayResponseTo(w io.Writer, fd int, content string, wordWrap bool, state *displayResponseState) {
|
||||
termWidth, _, _ := term.GetSize(fd)
|
||||
if wordWrap && termWidth >= 10 {
|
||||
for _, ch := range content {
|
||||
if state.lineLength+1 > termWidth-5 {
|
||||
if len(state.wordBuffer) > termWidth-10 {
|
||||
fmt.Printf("%s%c", state.wordBuffer, ch)
|
||||
fmt.Fprintf(w, "%s%c", state.wordBuffer, ch)
|
||||
state.wordBuffer = ""
|
||||
state.lineLength = 0
|
||||
continue
|
||||
|
|
@ -563,14 +578,14 @@ func displayResponse(content string, wordWrap bool, state *displayResponseState)
|
|||
// backtrack the length of the last word and clear to the end of the line
|
||||
a := len(state.wordBuffer)
|
||||
if a > 0 {
|
||||
fmt.Printf("\x1b[%dD", a)
|
||||
fmt.Fprintf(w, "\x1b[%dD", a)
|
||||
}
|
||||
fmt.Printf("\x1b[K\n")
|
||||
fmt.Printf("%s%c", state.wordBuffer, ch)
|
||||
fmt.Fprintf(w, "\x1b[K\n")
|
||||
fmt.Fprintf(w, "%s%c", state.wordBuffer, ch)
|
||||
|
||||
state.lineLength = len(state.wordBuffer) + 1
|
||||
} else {
|
||||
fmt.Print(string(ch))
|
||||
fmt.Fprint(w, string(ch))
|
||||
state.lineLength++
|
||||
|
||||
switch ch {
|
||||
|
|
@ -585,7 +600,7 @@ func displayResponse(content string, wordWrap bool, state *displayResponseState)
|
|||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("%s%s", state.wordBuffer, content)
|
||||
fmt.Fprintf(w, "%s%s", state.wordBuffer, content)
|
||||
if len(state.wordBuffer) > 0 {
|
||||
state.wordBuffer = ""
|
||||
}
|
||||
|
|
@ -662,7 +677,7 @@ func checkModelCapabilities(ctx context.Context, modelName string) (supportsTool
|
|||
// This is called from cmd.go when --experimental flag is set.
|
||||
// If yoloMode is true, all tool approvals are skipped.
|
||||
// If enableWebsearch is true, the web search tool is registered.
|
||||
func GenerateInteractive(cmd *cobra.Command, modelName string, wordWrap bool, options map[string]any, think *api.ThinkValue, hideThinking bool, keepAlive *api.Duration, yoloMode bool, enableWebsearch bool) error {
|
||||
func GenerateInteractive(cmd *cobra.Command, modelName string, wordWrap bool, options map[string]any, think *api.ThinkValue, hideThinking bool, thinkingToStderr bool, keepAlive *api.Duration, yoloMode bool, enableWebsearch bool) error {
|
||||
scanner, err := readline.New(readline.Prompt{
|
||||
Prompt: ">>> ",
|
||||
AltPrompt: "... ",
|
||||
|
|
@ -1058,15 +1073,16 @@ func GenerateInteractive(cmd *cobra.Command, modelName string, wordWrap bool, op
|
|||
|
||||
verbose, _ := cmd.Flags().GetBool("verbose")
|
||||
opts := RunOptions{
|
||||
Model: modelName,
|
||||
Messages: messages,
|
||||
WordWrap: wordWrap,
|
||||
Format: format,
|
||||
Options: options,
|
||||
Think: think,
|
||||
HideThinking: hideThinking,
|
||||
KeepAlive: keepAlive,
|
||||
Tools: toolRegistry,
|
||||
Model: modelName,
|
||||
Messages: messages,
|
||||
WordWrap: wordWrap,
|
||||
Format: format,
|
||||
Options: options,
|
||||
Think: think,
|
||||
HideThinking: hideThinking,
|
||||
ThinkingToStderr: thinkingToStderr,
|
||||
KeepAlive: keepAlive,
|
||||
Tools: toolRegistry,
|
||||
Approval: approval,
|
||||
YoloMode: yoloMode,
|
||||
Verbose: verbose,
|
||||
|
|
|
|||
Loading…
Reference in a new issue