tool call vis improvement
This commit is contained in:
@@ -834,6 +834,7 @@ type Message struct {
|
|||||||
ReasoningContent string `json:"reasoning_content,omitempty"`
|
ReasoningContent string `json:"reasoning_content,omitempty"`
|
||||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
||||||
ToolCallID string `json:"tool_call_id,omitempty"`
|
ToolCallID string `json:"tool_call_id,omitempty"`
|
||||||
|
ToolCallsShown bool `json:"-"` // tool call headers already printed live during streaming
|
||||||
}
|
}
|
||||||
|
|
||||||
type ToolCall struct {
|
type ToolCall struct {
|
||||||
@@ -1133,6 +1134,28 @@ func parseStream(ctx context.Context, r io.Reader) (Message, Usage, error) {
|
|||||||
var lastUsage Usage
|
var lastUsage Usage
|
||||||
tcs := map[int]*ToolCall{}
|
tcs := map[int]*ToolCall{}
|
||||||
var order []int
|
var order []int
|
||||||
|
var toolCallsShown bool
|
||||||
|
|
||||||
|
// Live, in-place display of tool calls as they stream in, so the user sees
|
||||||
|
// the invocation text being shaped instead of a blank pause. Only used when
|
||||||
|
// stdout is a terminal; piped/redirected output is left untouched.
|
||||||
|
tty := isTerminal(int(os.Stdout.Fd()))
|
||||||
|
liveIdx := -1
|
||||||
|
liveActive := false
|
||||||
|
showLive := func() {
|
||||||
|
if !tty || len(order) == 0 { return }
|
||||||
|
cur := order[len(order)-1]
|
||||||
|
txt := fmt.Sprintf("[tool call: %s(%s)]", tcs[cur].Function.Name, filterText(tcs[cur].Function.Arguments))
|
||||||
|
if liveActive && liveIdx != cur {
|
||||||
|
// A new tool call started; finalize the previous live line.
|
||||||
|
fmt.Print("\n")
|
||||||
|
liveActive = false
|
||||||
|
}
|
||||||
|
fmt.Print("\r\033[K" + c(txt, 33))
|
||||||
|
liveActive = true
|
||||||
|
liveIdx = cur
|
||||||
|
toolCallsShown = true
|
||||||
|
}
|
||||||
|
|
||||||
flushTable := func() {
|
flushTable := func() {
|
||||||
if len(tblBuf) > 0 {
|
if len(tblBuf) > 0 {
|
||||||
@@ -1199,16 +1222,32 @@ func parseStream(ctx context.Context, r io.Reader) (Message, Usage, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, tc := range dl.ToolCalls {
|
if len(dl.ToolCalls) > 0 {
|
||||||
t, ok := tcs[tc.Index]
|
if inReasoning {
|
||||||
if !ok {
|
fmt.Print("\n" + c("--- reasoning end ---", 36) + "\n")
|
||||||
t = &ToolCall{Type: "function"}
|
inReasoning = false
|
||||||
tcs[tc.Index] = t
|
|
||||||
order = append(order, tc.Index)
|
|
||||||
}
|
}
|
||||||
if tc.ID != "" { t.ID = tc.ID }
|
if liveIdx == -1 {
|
||||||
if tc.Function.Name != "" { t.Function.Name += tc.Function.Name }
|
// First tool call of the stream: flush any pending content line so it
|
||||||
if tc.Function.Arguments != "" { t.Function.Arguments += tc.Function.Arguments }
|
// is not overwritten by the live tool-call line.
|
||||||
|
flushTable()
|
||||||
|
if lineBuf != "" {
|
||||||
|
fmt.Println(renderMDLine(lineBuf, &mdSt))
|
||||||
|
lineBuf = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, tc := range dl.ToolCalls {
|
||||||
|
t, ok := tcs[tc.Index]
|
||||||
|
if !ok {
|
||||||
|
t = &ToolCall{Type: "function"}
|
||||||
|
tcs[tc.Index] = t
|
||||||
|
order = append(order, tc.Index)
|
||||||
|
}
|
||||||
|
if tc.ID != "" { t.ID = tc.ID }
|
||||||
|
if tc.Function.Name != "" { t.Function.Name += tc.Function.Name }
|
||||||
|
if tc.Function.Arguments != "" { t.Function.Arguments += tc.Function.Arguments }
|
||||||
|
}
|
||||||
|
showLive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := ctx.Err(); err != nil { return Message{}, lastUsage, err }
|
if err := ctx.Err(); err != nil { return Message{}, lastUsage, err }
|
||||||
@@ -1221,10 +1260,16 @@ func parseStream(ctx context.Context, r io.Reader) (Message, Usage, error) {
|
|||||||
fmt.Println(renderMDLine(lineBuf, &mdSt))
|
fmt.Println(renderMDLine(lineBuf, &mdSt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if liveActive {
|
||||||
|
// Finalize the in-progress tool-call line so following output starts fresh.
|
||||||
|
fmt.Print("\n")
|
||||||
|
liveActive = false
|
||||||
|
}
|
||||||
if inReasoning {
|
if inReasoning {
|
||||||
fmt.Print("\n" + c("--- reasoning end ---", 36) + "\n")
|
fmt.Print("\n" + c("--- reasoning end ---", 36) + "\n")
|
||||||
}
|
}
|
||||||
m := Message{Role: "assistant"}
|
m := Message{Role: "assistant"}
|
||||||
|
m.ToolCallsShown = toolCallsShown
|
||||||
if content != "" { m.Content = strp(content) }
|
if content != "" { m.Content = strp(content) }
|
||||||
if reas != "" { m.ReasoningContent = reas }
|
if reas != "" { m.ReasoningContent = reas }
|
||||||
if len(tcs) > 0 {
|
if len(tcs) > 0 {
|
||||||
@@ -1393,7 +1438,9 @@ func AL(ctx context.Context, cfg *Cfg, msgs []Message) ([]Message, Usage, error)
|
|||||||
for _, tc := range m.ToolCalls {
|
for _, tc := range m.ToolCalls {
|
||||||
if err := ctx.Err(); err != nil { return msgs, turnUsage, err }
|
if err := ctx.Err(); err != nil { return msgs, turnUsage, err }
|
||||||
fn, astr := tc.Function.Name, filterText(tc.Function.Arguments)
|
fn, astr := tc.Function.Name, filterText(tc.Function.Arguments)
|
||||||
fmt.Println(c(fmt.Sprintf("[tool call: %s(%s)]", fn, astr), 33))
|
if !m.ToolCallsShown {
|
||||||
|
fmt.Println(c(fmt.Sprintf("[tool call: %s(%s)]", fn, astr), 33))
|
||||||
|
}
|
||||||
res, sty := "", 2
|
res, sty := "", 2
|
||||||
var a map[string]any
|
var a map[string]any
|
||||||
if err := json.Unmarshal([]byte(astr), &a); err != nil || a == nil {
|
if err := json.Unmarshal([]byte(astr), &a); err != nil || a == nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user