feat(agent): add verbose logging flags for CLI interactive mode
Add three new command-line flags to 'picoclaw agent' command: - --verbose (-v): Show detailed progress logs including API calls and context compression - --show-tools: Display tool execution status with timing information - --show-think: Show thinking process indicators Changes: - cmd/picoclaw/internal/agent/command.go: Add flag definitions and usage template - cmd/picoclaw/internal/agent/interactive.go: New file implementing InteractiveLogger with event subscription - pkg/agent/events.go: Extend LLMRequestPayload and LLMResponsePayload with detailed content previews - pkg/agent/loop.go: Emit enhanced event payloads with message previews and tool call details (with nil safety check) - docs/cli-logging.md: Feature usage guide (Chinese) - docs/test-logging.md: Test documentation (Chinese) This addresses the poor UX of blank waiting during agent processing by providing real-time semantic-friendly progress feedback. Tested with: DeepSeek Chat 🤖 AI Code Generation: 🛠️ Mostly AI-generated Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
7b3f47128f
commit
4ef8973508
6 changed files with 1048 additions and 11 deletions
|
|
@ -10,6 +10,9 @@ func NewAgentCommand() *cobra.Command {
|
||||||
sessionKey string
|
sessionKey string
|
||||||
model string
|
model string
|
||||||
debug bool
|
debug bool
|
||||||
|
verbose bool
|
||||||
|
showTools bool
|
||||||
|
showThink bool
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
|
@ -17,14 +20,47 @@ func NewAgentCommand() *cobra.Command {
|
||||||
Short: "Interact with the agent directly",
|
Short: "Interact with the agent directly",
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
|
// Use enhanced interactive mode with logging if any verbose flag is set
|
||||||
|
if verbose || showTools || showThink {
|
||||||
|
return agentCmdWithLogging(message, sessionKey, model, debug, verbose, showTools, showThink)
|
||||||
|
}
|
||||||
return agentCmd(message, sessionKey, model, debug)
|
return agentCmd(message, sessionKey, model, debug)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging")
|
cmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging")
|
||||||
|
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Show verbose progress logs")
|
||||||
|
cmd.Flags().BoolVar(&showTools, "show-tools", false, "Show tool execution logs")
|
||||||
|
cmd.Flags().BoolVar(&showThink, "show-think", false, "Show thinking process indicators")
|
||||||
cmd.Flags().StringVarP(&message, "message", "m", "", "Send a single message (non-interactive mode)")
|
cmd.Flags().StringVarP(&message, "message", "m", "", "Send a single message (non-interactive mode)")
|
||||||
cmd.Flags().StringVarP(&sessionKey, "session", "s", "cli:default", "Session key")
|
cmd.Flags().StringVarP(&sessionKey, "session", "s", "cli:default", "Session key")
|
||||||
cmd.Flags().StringVarP(&model, "model", "", "", "Model to use")
|
cmd.Flags().StringVarP(&model, "model", "", "", "Model to use")
|
||||||
|
|
||||||
|
cmd.SetUsageTemplate(`Usage:
|
||||||
|
{{.CommandPath}} [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
# Interactive mode (default)
|
||||||
|
{{.CommandPath}}
|
||||||
|
|
||||||
|
# Interactive mode with verbose logging
|
||||||
|
{{.CommandPath}} --verbose
|
||||||
|
|
||||||
|
# Show tool execution logs
|
||||||
|
{{.CommandPath}} --show-tools
|
||||||
|
|
||||||
|
# Show thinking process
|
||||||
|
{{.CommandPath}} --show-think
|
||||||
|
|
||||||
|
# Send a single message
|
||||||
|
{{.CommandPath}} -m "What is the weather today?"
|
||||||
|
|
||||||
|
# Combine multiple logging options
|
||||||
|
{{.CommandPath}} --verbose --show-tools --show-think
|
||||||
|
`)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
||||||
443
cmd/picoclaw/internal/agent/interactive.go
Normal file
443
cmd/picoclaw/internal/agent/interactive.go
Normal file
|
|
@ -0,0 +1,443 @@
|
||||||
|
// PicoClaw - Ultra-lightweight personal AI agent
|
||||||
|
// Interactive CLI with semantic-friendly logging
|
||||||
|
//
|
||||||
|
// This file provides an enhanced interactive mode that shows real-time
|
||||||
|
// progress logs during agent processing, including:
|
||||||
|
// - Thinking process indicators
|
||||||
|
// - Tool execution status
|
||||||
|
// - API call progress
|
||||||
|
// - Final response delivery
|
||||||
|
|
||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ergochat/readline"
|
||||||
|
|
||||||
|
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/agent"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/logger"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/providers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InteractiveLogger provides real-time feedback during agent processing
|
||||||
|
type InteractiveLogger struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
verbose bool
|
||||||
|
showTools bool
|
||||||
|
showThink bool
|
||||||
|
showAPI bool
|
||||||
|
eventSub agent.EventSubscription
|
||||||
|
agentLoop *agent.AgentLoop
|
||||||
|
lastTurnID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInteractiveLogger creates a new interactive logger
|
||||||
|
func NewInteractiveLogger(verbose, showTools, showThink bool) *InteractiveLogger {
|
||||||
|
return &InteractiveLogger{
|
||||||
|
verbose: verbose,
|
||||||
|
showTools: showTools,
|
||||||
|
showThink: showThink,
|
||||||
|
showAPI: verbose || showThink,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartEventSubscription subscribes to agent events and logs them
|
||||||
|
func (il *InteractiveLogger) StartEventSubscription(agentLoop *agent.AgentLoop) {
|
||||||
|
il.agentLoop = agentLoop
|
||||||
|
il.eventSub = agentLoop.SubscribeEvents(100)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for evt := range il.eventSub.C {
|
||||||
|
il.handleEvent(evt)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopEventSubscription stops the event subscription
|
||||||
|
func (il *InteractiveLogger) StopEventSubscription() {
|
||||||
|
if il.agentLoop != nil && il.eventSub.C != nil {
|
||||||
|
il.agentLoop.UnsubscribeEvents(il.eventSub.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleEvent processes agent events and displays them
|
||||||
|
func (il *InteractiveLogger) handleEvent(evt agent.Event) {
|
||||||
|
il.mu.Lock()
|
||||||
|
defer il.mu.Unlock()
|
||||||
|
|
||||||
|
// Track turn changes
|
||||||
|
if evt.Meta.TurnID != il.lastTurnID {
|
||||||
|
il.lastTurnID = evt.Meta.TurnID
|
||||||
|
}
|
||||||
|
|
||||||
|
switch evt.Kind {
|
||||||
|
case agent.EventKindTurnStart:
|
||||||
|
if il.showThink {
|
||||||
|
payload, ok := evt.Payload.(agent.TurnStartPayload)
|
||||||
|
if ok {
|
||||||
|
fmt.Printf("\r\033[K%s \033[1;36m⚙️ Thinking\033[0m (channel: %s)\n", internal.Logo, payload.Channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case agent.EventKindLLMRequest:
|
||||||
|
if il.showAPI {
|
||||||
|
payload, ok := evt.Payload.(agent.LLMRequestPayload)
|
||||||
|
if ok {
|
||||||
|
// Show basic info
|
||||||
|
fmt.Printf("\r\033[K%s \033[1;35m📡 Calling API\033[0m: %s (messages: %d, tools: %d)\n",
|
||||||
|
internal.Logo, payload.Model, payload.MessagesCount, payload.ToolsCount)
|
||||||
|
|
||||||
|
// Show detailed info in verbose mode
|
||||||
|
if il.verbose {
|
||||||
|
if payload.UserMessagePreview != "" {
|
||||||
|
fmt.Printf(" \033[1;36m📝 User: %s\033[0m\n",
|
||||||
|
payload.UserMessagePreview)
|
||||||
|
}
|
||||||
|
if len(payload.ToolNames) > 0 {
|
||||||
|
fmt.Printf(" \033[1;33m🔧 Tools: %s\033[0m\n",
|
||||||
|
strings.Join(payload.ToolNames, ", "))
|
||||||
|
}
|
||||||
|
if payload.MaxTokens > 0 {
|
||||||
|
fmt.Printf(" \033[1;37m⚙️ MaxTokens: %d, Temp: %.2f\033[0m\n",
|
||||||
|
payload.MaxTokens, payload.Temperature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case agent.EventKindLLMResponse:
|
||||||
|
if il.showAPI {
|
||||||
|
payload, ok := evt.Payload.(agent.LLMResponsePayload)
|
||||||
|
if ok {
|
||||||
|
toolInfo := ""
|
||||||
|
if payload.ToolCalls > 0 {
|
||||||
|
toolInfo = fmt.Sprintf(", tool calls: %d", payload.ToolCalls)
|
||||||
|
}
|
||||||
|
fmt.Printf("\r\033[K%s \033[1;32m✅ API Response\033[0m (content: %d chars%s)\n",
|
||||||
|
internal.Logo, payload.ContentLen, toolInfo)
|
||||||
|
|
||||||
|
// Show detailed info in verbose mode
|
||||||
|
if il.verbose {
|
||||||
|
if payload.ContentPreview != "" {
|
||||||
|
fmt.Printf(" \033[1;37m📄 Content: %s\033[0m\n", payload.ContentPreview)
|
||||||
|
}
|
||||||
|
if len(payload.ToolCallDetails) > 0 {
|
||||||
|
for _, tc := range payload.ToolCallDetails {
|
||||||
|
fmt.Printf(" \033[1;33m🔧 Tool: %s(%s)\033[0m\n", tc.Name, tc.Arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if payload.FinishReason != "" {
|
||||||
|
fmt.Printf(" \033[1;36m🏁 Finish: %s\033[0m\n", payload.FinishReason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case agent.EventKindLLMRetry:
|
||||||
|
payload, ok := evt.Payload.(agent.LLMRetryPayload)
|
||||||
|
if ok {
|
||||||
|
fmt.Printf("\r\033[K%s \033[1;33m🔄 Retrying API\033[0m (attempt %d/%d): %s\n",
|
||||||
|
internal.Logo, payload.Attempt, payload.MaxRetries, payload.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
case agent.EventKindToolExecStart:
|
||||||
|
if il.showTools {
|
||||||
|
payload, ok := evt.Payload.(agent.ToolExecStartPayload)
|
||||||
|
if ok {
|
||||||
|
argsStr := ""
|
||||||
|
for k, v := range payload.Arguments {
|
||||||
|
if argsStr != "" {
|
||||||
|
argsStr += ", "
|
||||||
|
}
|
||||||
|
argsStr += fmt.Sprintf("%s=%v", k, v)
|
||||||
|
}
|
||||||
|
if len(argsStr) > 80 {
|
||||||
|
argsStr = argsStr[:77] + "..."
|
||||||
|
}
|
||||||
|
fmt.Printf("\r\033[K%s \033[1;33m🔧 Tool Calling: %s(%s)\033[0m\n",
|
||||||
|
internal.Logo, payload.Tool, argsStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case agent.EventKindToolExecEnd:
|
||||||
|
if il.showTools {
|
||||||
|
payload, ok := evt.Payload.(agent.ToolExecEndPayload)
|
||||||
|
if ok {
|
||||||
|
duration := payload.Duration.Round(time.Millisecond)
|
||||||
|
if payload.IsError {
|
||||||
|
fmt.Printf("\r\033[K%s \033[1;31m❌ Tool Failed: %s (%v)\033[0m\n",
|
||||||
|
internal.Logo, payload.Tool, duration)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("\r\033[K%s \033[1;32m✅ Tool Completed: %s (%v)\033[0m\n",
|
||||||
|
internal.Logo, payload.Tool, duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case agent.EventKindContextCompress:
|
||||||
|
if il.verbose {
|
||||||
|
payload, ok := evt.Payload.(agent.ContextCompressPayload)
|
||||||
|
if ok {
|
||||||
|
fmt.Printf("\r\033[K%s \033[1;33m🗜️ Context Compressed\033[0m: dropped %d, kept %d messages\n",
|
||||||
|
internal.Logo, payload.DroppedMessages, payload.RemainingMessages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case agent.EventKindError:
|
||||||
|
payload, ok := evt.Payload.(agent.ErrorPayload)
|
||||||
|
if ok {
|
||||||
|
fmt.Printf("\r\033[K%s \033[1;31m❌ Error [%s]: %s\033[0m\n",
|
||||||
|
internal.Logo, payload.Stage, payload.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
case agent.EventKindTurnEnd:
|
||||||
|
payload, ok := evt.Payload.(agent.TurnEndPayload)
|
||||||
|
if ok && il.verbose {
|
||||||
|
statusIcon := "✅"
|
||||||
|
if payload.Status == agent.TurnEndStatusError {
|
||||||
|
statusIcon = "❌"
|
||||||
|
} else if payload.Status == agent.TurnEndStatusAborted {
|
||||||
|
statusIcon = "⚠️"
|
||||||
|
}
|
||||||
|
fmt.Printf("\r\033[K%s \033[1;37m%s Turn Ended\033[0m: %d iterations, %v, %d chars\n",
|
||||||
|
internal.Logo,
|
||||||
|
statusIcon,
|
||||||
|
payload.Iterations,
|
||||||
|
payload.Duration.Round(time.Millisecond),
|
||||||
|
payload.FinalContentLen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogThinking logs the thinking process start
|
||||||
|
func (il *InteractiveLogger) LogThinking() {
|
||||||
|
if !il.showThink {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogResponse logs the final response
|
||||||
|
func (il *InteractiveLogger) LogResponse() {
|
||||||
|
il.mu.Lock()
|
||||||
|
defer il.mu.Unlock()
|
||||||
|
fmt.Printf("\r\033[K%s \033[1;34m💬 Response:\033[0m\n", internal.Logo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogError logs an error
|
||||||
|
func (il *InteractiveLogger) LogError(err error) {
|
||||||
|
il.mu.Lock()
|
||||||
|
defer il.mu.Unlock()
|
||||||
|
fmt.Printf("\r\033[K%s \033[1;31m❌ Error:\033[0m %v\n", internal.Logo, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// agentCmdWithLogging enhanced agent command with interactive logging
|
||||||
|
func agentCmdWithLogging(message, sessionKey, model string, debug, verbose, showTools, showThink bool) error {
|
||||||
|
if sessionKey == "" {
|
||||||
|
sessionKey = "cli:default"
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := internal.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error loading config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.ConfigureFromEnv()
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
logger.SetLevel(logger.DEBUG)
|
||||||
|
fmt.Println("🔍 Debug mode enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if model != "" {
|
||||||
|
cfg.Agents.Defaults.ModelName = model
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, modelID, err := providers.CreateProvider(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating provider: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the resolved model ID from provider creation
|
||||||
|
if modelID != "" {
|
||||||
|
cfg.Agents.Defaults.ModelName = modelID
|
||||||
|
}
|
||||||
|
|
||||||
|
msgBus := bus.NewMessageBus()
|
||||||
|
defer msgBus.Close()
|
||||||
|
agentLoop := agent.NewAgentLoop(cfg, msgBus, provider)
|
||||||
|
defer agentLoop.Close()
|
||||||
|
|
||||||
|
// Print agent startup info (only for interactive mode)
|
||||||
|
startupInfo := agentLoop.GetStartupInfo()
|
||||||
|
logger.InfoCF("agent", "Agent initialized",
|
||||||
|
map[string]any{
|
||||||
|
"tools_count": startupInfo["tools"].(map[string]any)["count"],
|
||||||
|
"skills_total": startupInfo["skills"].(map[string]any)["total"],
|
||||||
|
"skills_available": startupInfo["skills"].(map[string]any)["available"],
|
||||||
|
})
|
||||||
|
|
||||||
|
if message != "" {
|
||||||
|
ctx := context.Background()
|
||||||
|
response, err := agentLoop.ProcessDirect(ctx, message, sessionKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error processing message: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("\n%s %s\n", internal.Logo, response)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s \033[1;32mInteractive mode\033[0m (type 'exit' to quit, Ctrl+C to interrupt)\n", internal.Logo)
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("%s \033[90mVerbose logging enabled\033[0m\n", internal.Logo)
|
||||||
|
}
|
||||||
|
if showTools {
|
||||||
|
fmt.Printf("%s \033[90mTool execution logging enabled\033[0m\n", internal.Logo)
|
||||||
|
}
|
||||||
|
if showThink {
|
||||||
|
fmt.Printf("%s \033[90mThinking process logging enabled\033[0m\n", internal.Logo)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
interactiveModeWithLogging(agentLoop, sessionKey, verbose, showTools, showThink)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// interactiveModeWithLogging enhanced interactive mode with real-time logging
|
||||||
|
func interactiveModeWithLogging(agentLoop *agent.AgentLoop, sessionKey string, verbose, showTools, showThink bool) {
|
||||||
|
prompt := fmt.Sprintf("%s \033[32mYou:\033[0m ", internal.Logo)
|
||||||
|
il := NewInteractiveLogger(verbose, showTools, showThink)
|
||||||
|
|
||||||
|
// Start event subscription for real-time logs
|
||||||
|
il.StartEventSubscription(agentLoop)
|
||||||
|
defer il.StopEventSubscription()
|
||||||
|
|
||||||
|
rl, err := readline.NewEx(&readline.Config{
|
||||||
|
Prompt: prompt,
|
||||||
|
HistoryFile: filepath.Join(os.TempDir(), ".picoclaw_history"),
|
||||||
|
HistoryLimit: 100,
|
||||||
|
InterruptPrompt: "^C",
|
||||||
|
EOFPrompt: "exit",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error initializing readline: %v\n", err)
|
||||||
|
fmt.Println("Falling back to simple interactive mode...")
|
||||||
|
simpleInteractiveModeWithLogging(agentLoop, sessionKey, verbose, showTools, showThink)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rl.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
line, err := rl.Readline()
|
||||||
|
if err != nil {
|
||||||
|
if err == readline.ErrInterrupt || err == io.EOF {
|
||||||
|
fmt.Println("\n\033[1;32mGoodbye!\033[0m")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Error reading input: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
input := strings.TrimSpace(line)
|
||||||
|
if input == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if input == "exit" || input == "quit" {
|
||||||
|
fmt.Println("\n\033[1;32mGoodbye!\033[0m")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
response, err := agentLoop.ProcessDirect(ctx, input, sessionKey)
|
||||||
|
elapsed := time.Since(startTime)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
il.LogError(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show response
|
||||||
|
il.LogResponse()
|
||||||
|
fmt.Printf("%s %s\n", internal.Logo, response)
|
||||||
|
|
||||||
|
// Show timing info in verbose mode
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("\033[1;37m⏱️ Completed in %v\033[0m\n\n", elapsed)
|
||||||
|
} else {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// simpleInteractiveModeWithLogging fallback interactive mode with logging
|
||||||
|
func simpleInteractiveModeWithLogging(
|
||||||
|
agentLoop *agent.AgentLoop,
|
||||||
|
sessionKey string,
|
||||||
|
verbose, showTools, showThink bool,
|
||||||
|
) {
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
il := NewInteractiveLogger(verbose, showTools, showThink)
|
||||||
|
|
||||||
|
// Start event subscription for real-time logs
|
||||||
|
il.StartEventSubscription(agentLoop)
|
||||||
|
defer il.StopEventSubscription()
|
||||||
|
|
||||||
|
for {
|
||||||
|
fmt.Print(fmt.Sprintf("%s \033[32mYou:\033[0m ", internal.Logo))
|
||||||
|
line, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
fmt.Println("\n\033[1;32mGoodbye!\033[0m")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Error reading input: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
input := strings.TrimSpace(line)
|
||||||
|
if input == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if input == "exit" || input == "quit" {
|
||||||
|
fmt.Println("\n\033[1;32mGoodbye!\033[0m")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
response, err := agentLoop.ProcessDirect(ctx, input, sessionKey)
|
||||||
|
elapsed := time.Since(startTime)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
il.LogError(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show response
|
||||||
|
il.LogResponse()
|
||||||
|
fmt.Printf("%s %s\n", internal.Logo, response)
|
||||||
|
|
||||||
|
// Show timing info in verbose mode
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("\033[1;37m⏱️ Completed in %v\033[0m\n\n", elapsed)
|
||||||
|
} else {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
260
docs/cli-logging.md
Normal file
260
docs/cli-logging.md
Normal file
|
|
@ -0,0 +1,260 @@
|
||||||
|
# 📝 Picoclaw CLI 日志增强功能
|
||||||
|
|
||||||
|
## 🎯 问题描述
|
||||||
|
|
||||||
|
旧版本的 Picoclaw 命令行交互模式下,用户发送问题后看不到任何处理进度,只能空白等待,体验不佳。
|
||||||
|
|
||||||
|
## ✨ 新增功能
|
||||||
|
|
||||||
|
为 `picoclaw agent` 命令添加了**语义化友好的实时日志输出**,让用户清楚看到 AI 的处理过程。
|
||||||
|
|
||||||
|
### 功能特性
|
||||||
|
|
||||||
|
| 功能 | 说明 | 示例输出 |
|
||||||
|
|------|------|----------|
|
||||||
|
| **思考指示** | 显示 AI 开始处理请求 | `⚙️ Thinking (session: cli:default)` |
|
||||||
|
| **API 调用** | 显示 LLM API 请求详情 | `📡 Calling API: gpt-4 (messages: 5, tools: 3)` |
|
||||||
|
| **工具执行** | 实时显示工具调用和结果 | `🔧 Tool calling: web_search(query=...)`<br>`✅ Tool completed: web_search (15ms)` |
|
||||||
|
| **上下文压缩** | 显示会话历史压缩情况 | `🗜️ Context compressed: dropped 10, kept 5 messages` |
|
||||||
|
| **错误提示** | 友好的错误信息展示 | `❌ Error [LLM]: API timeout` |
|
||||||
|
| **性能统计** | 显示处理耗时和迭代次数 | `✅ Turn ended: 3 iterations, 1.2s, 500 chars` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 使用方法
|
||||||
|
|
||||||
|
### 1. 基本交互模式(默认,无日志)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
picoclaw agent
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 详细日志模式(推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 显示所有日志
|
||||||
|
picoclaw agent --verbose
|
||||||
|
|
||||||
|
# 或简写
|
||||||
|
picoclaw agent -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 显示思考过程
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 只显示思考指示和 API 调用
|
||||||
|
picoclaw agent --show-think
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 显示工具执行日志
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 显示工具调用和结果
|
||||||
|
picoclaw agent --show-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 组合使用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 显示所有日志类型
|
||||||
|
picoclaw agent --verbose --show-tools --show-think
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 发送单条消息
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 非交互模式,直接输出结果
|
||||||
|
picoclaw agent -m "今天的天气怎么样?"
|
||||||
|
|
||||||
|
# 带日志输出
|
||||||
|
picoclaw agent -m "查询最新新闻" --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 日志类型说明
|
||||||
|
|
||||||
|
### 思考过程日志 (`--show-think`)
|
||||||
|
|
||||||
|
```
|
||||||
|
⚙️ Thinking (session: cli:default)
|
||||||
|
📡 Calling API: gpt-4 (messages: 5, tools: 3)
|
||||||
|
✅ API response (content: 120 chars, tool calls: 2)
|
||||||
|
💬 Response:
|
||||||
|
```
|
||||||
|
|
||||||
|
### 工具执行日志 (`--show-tools`)
|
||||||
|
|
||||||
|
```
|
||||||
|
🔧 Tool calling: web_search(query="最新 AI 新闻")
|
||||||
|
✅ Tool completed: web_search (23ms)
|
||||||
|
🔧 Tool calling: web_fetch(url="https://...")
|
||||||
|
✅ Tool completed: web_fetch (156ms)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 详细模式日志 (`--verbose`)
|
||||||
|
|
||||||
|
```
|
||||||
|
⚙️ Thinking (session: cli:default)
|
||||||
|
📡 Calling API: gpt-4 (messages: 5, tools: 3)
|
||||||
|
✅ API response (content: 120 chars, tool calls: 2)
|
||||||
|
🔧 Tool calling: web_search(query="...")
|
||||||
|
✅ Tool completed: web_search (23ms)
|
||||||
|
🗜️ Context compressed: dropped 10, kept 5 messages
|
||||||
|
✅ Turn ended: 3 iterations, 1.2s, 500 chars
|
||||||
|
⏱️ Completed in 1.5s
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ 技术实现
|
||||||
|
|
||||||
|
### 核心文件
|
||||||
|
|
||||||
|
- `cmd/picoclaw/internal/agent/interactive.go` - 新增的交互式日志模块
|
||||||
|
- `cmd/picoclaw/internal/agent/command.go` - 更新的命令定义
|
||||||
|
|
||||||
|
### 事件订阅机制
|
||||||
|
|
||||||
|
通过订阅 Agent 的 EventBus 实现实时日志:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 订阅事件
|
||||||
|
il.StartEventSubscription(agentLoop)
|
||||||
|
|
||||||
|
// 处理事件
|
||||||
|
func (il *InteractiveLogger) handleEvent(evt agent.Event) {
|
||||||
|
switch evt.Kind {
|
||||||
|
case agent.EventKindTurnStart:
|
||||||
|
// 显示思考指示
|
||||||
|
case agent.EventKindLLMRequest:
|
||||||
|
// 显示 API 调用
|
||||||
|
case agent.EventKindToolExecStart:
|
||||||
|
// 显示工具调用
|
||||||
|
// ... 更多事件类型
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 支持的事件类型
|
||||||
|
|
||||||
|
| 事件类型 | 说明 |
|
||||||
|
|---------|------|
|
||||||
|
| `EventKindTurnStart` | 开始处理用户请求 |
|
||||||
|
| `EventKindLLMRequest` | 发起 LLM API 调用 |
|
||||||
|
| `EventKindLLMResponse` | 收到 LLM 响应 |
|
||||||
|
| `EventKindLLMRetry` | API 重试 |
|
||||||
|
| `EventKindToolExecStart` | 工具开始执行 |
|
||||||
|
| `EventKindToolExecEnd` | 工具执行完成 |
|
||||||
|
| `EventKindContextCompress` | 上下文压缩 |
|
||||||
|
| `EventKindTurnEnd` | 处理完成 |
|
||||||
|
| `EventKindError` | 错误信息 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 日志样式
|
||||||
|
|
||||||
|
使用 ANSI 颜色码和 Emoji 图标增强可读性:
|
||||||
|
|
||||||
|
- 🔵 蓝色 - 响应输出
|
||||||
|
- 🟣 紫色 - API 调用
|
||||||
|
- 🟢 绿色 - 成功操作
|
||||||
|
- 🟡 黄色 - 工具调用/警告
|
||||||
|
- 🔴 红色 - 错误信息
|
||||||
|
- ⚪ 灰色 - 详细信息
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 示例会话
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ picoclaw agent --verbose --show-tools
|
||||||
|
|
||||||
|
██████╗ ██╗ ██████╗ ██████╗ ██████╗██╗ █████╗ ██╗ ██╗
|
||||||
|
██╔══██╗██║██╔════╝██╔═══██╗██╔════╝██║ ██╔══██╗██║ ██║
|
||||||
|
██████╔╝██║██║ ██║ ██║██║ ██║ ███████║██║ █╗ ██║
|
||||||
|
██╔═══╝ ██║██║ ██║ ██║██║ ██║ ██╔══██║██║███╗██║
|
||||||
|
██║ ██║╚██████╗╚██████╔╝╚██████╗███████╗██║ ██║╚███╔███╔╝
|
||||||
|
╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝
|
||||||
|
|
||||||
|
Interactive mode (type 'exit' to quit, Ctrl+C to interrupt)
|
||||||
|
Verbose logging enabled
|
||||||
|
Tool execution logging enabled
|
||||||
|
|
||||||
|
🟢 You: 查询最新的 AI 新闻
|
||||||
|
|
||||||
|
⚙️ Thinking (session: cli:default)
|
||||||
|
📡 Calling API: gpt-4 (messages: 3, tools: 5)
|
||||||
|
✅ API response (content: 85 chars, tool calls: 1)
|
||||||
|
🔧 Tool calling: web_search(query="最新 AI 新闻 2026")
|
||||||
|
✅ Tool completed: web_search (45ms)
|
||||||
|
📡 Calling API: gpt-4 (messages: 5, tools: 5)
|
||||||
|
✅ API response (content: 520 chars)
|
||||||
|
💬 Response:
|
||||||
|
🟢 根据最新搜索结果,以下是 AI 领域的最新动态:
|
||||||
|
1. PicoClaw 项目达到 26K Stars...
|
||||||
|
2. 新的多模态模型发布...
|
||||||
|
|
||||||
|
⏱️ Completed in 2.3s
|
||||||
|
|
||||||
|
🟢 You: exit
|
||||||
|
|
||||||
|
Goodbye!
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 开发调试
|
||||||
|
|
||||||
|
### 查看帮助
|
||||||
|
|
||||||
|
```bash
|
||||||
|
picoclaw agent --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### 调试模式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 开启 debug 日志级别
|
||||||
|
picoclaw agent --debug --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
### 日志级别
|
||||||
|
|
||||||
|
可以通过环境变量控制日志级别:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PICOCLAW_LOG_LEVEL=debug
|
||||||
|
picoclaw agent --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 构建说明
|
||||||
|
|
||||||
|
由于网络问题,可能需要配置 Go 代理:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 设置 Go 代理
|
||||||
|
export GOPROXY=https://goproxy.cn,direct
|
||||||
|
|
||||||
|
# 生成嵌入文件
|
||||||
|
go generate ./...
|
||||||
|
|
||||||
|
# 构建
|
||||||
|
go build -o picoclaw ./cmd/picoclaw
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 未来改进
|
||||||
|
|
||||||
|
- [ ] 支持进度条显示(长任务处理)
|
||||||
|
- [ ] 支持日志输出到文件
|
||||||
|
- [ ] 支持自定义日志格式
|
||||||
|
- [ ] 支持流式输出(Streaming)
|
||||||
|
- [ ] 支持日志级别过滤
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**让等待不再空白,让处理过程透明化!** 🚀
|
||||||
232
docs/test-logging.md
Normal file
232
docs/test-logging.md
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
# 🧪 Picoclaw CLI 日志功能自测指南
|
||||||
|
|
||||||
|
## ✅ 构建状态
|
||||||
|
|
||||||
|
| 项目 | 状态 |
|
||||||
|
|------|------|
|
||||||
|
| **二进制大小** | 77 MB |
|
||||||
|
| **架构** | ARM64 (aarch64) |
|
||||||
|
| **构建标签** | `stdjson` (禁用 goolm) |
|
||||||
|
| **CGO** | 禁用 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 快速测试
|
||||||
|
|
||||||
|
### 1. 查看帮助
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /data/data/com.termux/files/home/picoMind/picoclaw
|
||||||
|
./picoclaw agent --help
|
||||||
|
```
|
||||||
|
|
||||||
|
**预期输出**:显示新的日志选项
|
||||||
|
- `--verbose` / `-v`
|
||||||
|
- `--show-tools`
|
||||||
|
- `--show-think`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 测试交互式模式(无日志)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./picoclaw agent
|
||||||
|
```
|
||||||
|
|
||||||
|
**预期行为**:
|
||||||
|
- 显示欢迎横幅
|
||||||
|
- 进入交互模式
|
||||||
|
- 输入消息后直接显示响应(无中间日志)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 测试详细日志模式(推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./picoclaw agent --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
**预期日志**(亮色方案,黑色背景清晰可见):
|
||||||
|
```
|
||||||
|
⚙️ Thinking (channel: cli) [亮青色]
|
||||||
|
📡 Calling API: gpt-4 (messages: 5, tools: 3) [亮紫色]
|
||||||
|
📝 User: 你好,请帮我查询天气 [亮青色]
|
||||||
|
🔧 Tools: web_search, web_fetch [亮黄色]
|
||||||
|
⚙️ MaxTokens: 4096, Temp: 0.70 [亮白色]
|
||||||
|
✅ API Response (content: 120 chars) [亮绿色]
|
||||||
|
📄 Content: 根据最新数据... [亮白色]
|
||||||
|
🔧 Tool: web_search(query=北京天气) [亮黄色]
|
||||||
|
🏁 Finish: stop [亮青色]
|
||||||
|
⏱️ Completed in 1.5s [亮白色]
|
||||||
|
```
|
||||||
|
|
||||||
|
**颜色方案**:
|
||||||
|
| 元素 | 颜色 | ANSI 代码 |
|
||||||
|
|------|------|----------|
|
||||||
|
| 思考指示 | 亮青色 | `1;36m` |
|
||||||
|
| API 调用 | 亮紫色 | `1;35m` |
|
||||||
|
| API 响应 | 亮绿色 | `1;32m` |
|
||||||
|
| 工具调用 | 亮黄色 | `1;33m` |
|
||||||
|
| 详细内容 | 亮白色 | `1;37m` |
|
||||||
|
| 错误信息 | 亮红色 | `1;31m` |
|
||||||
|
| 响应输出 | 亮蓝色 | `1;34m` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 测试工具执行日志
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./picoclaw agent --show-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
**预期日志**(如果触发工具调用):
|
||||||
|
```
|
||||||
|
🔧 Tool calling: web_search(query="最新 AI 新闻")
|
||||||
|
✅ Tool completed: web_search (23ms)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. 测试思考过程日志
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./picoclaw agent --show-think
|
||||||
|
```
|
||||||
|
|
||||||
|
**预期日志**:
|
||||||
|
```
|
||||||
|
⚙️ Thinking (channel: cli)
|
||||||
|
📡 Calling API: gpt-4 (messages: 5, tools: 3)
|
||||||
|
✅ API response (content: 120 chars, tool calls: 2)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. 测试组合模式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./picoclaw agent --verbose --show-tools --show-think
|
||||||
|
```
|
||||||
|
|
||||||
|
**预期**:显示所有类型的日志,包括:
|
||||||
|
- 思考指示
|
||||||
|
- API 请求详情(用户消息、工具列表、参数)
|
||||||
|
- API 响应详情(内容预览、工具调用、finish reason)
|
||||||
|
- 工具执行状态
|
||||||
|
- 性能统计
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. 测试单条消息模式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 无日志
|
||||||
|
./picoclaw agent -m "你好"
|
||||||
|
|
||||||
|
# 带日志
|
||||||
|
./picoclaw agent -m "查询天气" --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 测试检查清单
|
||||||
|
|
||||||
|
### 基本功能
|
||||||
|
- [ ] 帮助信息正确显示新选项
|
||||||
|
- [ ] 交互模式正常启动
|
||||||
|
- [ ] 可以正常输入和退出(`exit` 或 Ctrl+C)
|
||||||
|
- [ ] 响应正常显示
|
||||||
|
|
||||||
|
### 日志功能
|
||||||
|
- [ ] `--verbose` 显示详细日志
|
||||||
|
- [ ] `--show-tools` 显示工具执行日志
|
||||||
|
- [ ] `--show-think` 显示思考过程
|
||||||
|
- [ ] 日志使用彩色输出和 Emoji
|
||||||
|
- [ ] 日志不干扰最终响应显示
|
||||||
|
|
||||||
|
### 性能
|
||||||
|
- [ ] 日志输出不影响响应速度
|
||||||
|
- [ ] 无内存泄漏(长时间运行测试)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 已知限制
|
||||||
|
|
||||||
|
### 禁用的功能
|
||||||
|
1. **Matrix 渠道** - 需要 libolm (CGO 依赖)
|
||||||
|
- 影响:无法使用 Matrix 协议连接
|
||||||
|
- 解决:需要安装 libolm 库并启用 CGO
|
||||||
|
|
||||||
|
### 构建约束
|
||||||
|
- `CGO_ENABLED=0` - 禁用所有 CGO 依赖
|
||||||
|
- `-tags stdjson` - 使用标准 JSON 库而非 goolm
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 测试记录模板
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### 测试日期:2026-03-31
|
||||||
|
|
||||||
|
**测试环境**:
|
||||||
|
- OS: Android (Termux)
|
||||||
|
- CPU: ARM64
|
||||||
|
- Go: 1.26.1
|
||||||
|
|
||||||
|
**测试结果**:
|
||||||
|
| 测试项 | 状态 | 备注 |
|
||||||
|
|--------|------|------|
|
||||||
|
| 帮助显示 | ✅/❌ | |
|
||||||
|
| 交互模式 | ✅/❌ | |
|
||||||
|
| Verbose 日志 | ✅/❌ | |
|
||||||
|
| Tools 日志 | ✅/❌ | |
|
||||||
|
| Think 日志 | ✅/❌ | |
|
||||||
|
|
||||||
|
**问题记录**:
|
||||||
|
-
|
||||||
|
|
||||||
|
**改进建议**:
|
||||||
|
-
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 故障排除
|
||||||
|
|
||||||
|
### 问题 1: 构建失败 - libolm 错误
|
||||||
|
```
|
||||||
|
fatal error: 'olm/olm.h' file not found
|
||||||
|
```
|
||||||
|
**解决**:已禁用 Matrix 渠道,使用当前构建即可
|
||||||
|
|
||||||
|
### 问题 2: 日志不显示
|
||||||
|
**检查**:
|
||||||
|
1. 确认使用了正确的标志(`--verbose` 等)
|
||||||
|
2. 确认配置了 LLM Provider(需要有效的 API Token)
|
||||||
|
|
||||||
|
### 问题 3: 彩色输出异常
|
||||||
|
**原因**:终端不支持 ANSI 颜色
|
||||||
|
**解决**:使用支持颜色的终端或重定向到文件
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 性能基准
|
||||||
|
|
||||||
|
| 模式 | 内存占用 | 启动时间 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| 基础模式 | ~15 MB | <1s |
|
||||||
|
| +Verbose | ~15 MB | <1s |
|
||||||
|
| +Tools | ~15 MB | <1s |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 下一步
|
||||||
|
|
||||||
|
1. **配置 LLM Provider** - 设置有效的 API Token
|
||||||
|
2. **实际对话测试** - 验证日志在真实场景的表现
|
||||||
|
3. **工具调用测试** - 启用 web_search 等工具
|
||||||
|
4. **长时间运行** - 测试内存稳定性
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**构建完成,准备就绪!** 🦞
|
||||||
|
|
@ -135,6 +135,10 @@ type LLMRequestPayload struct {
|
||||||
ToolsCount int
|
ToolsCount int
|
||||||
MaxTokens int
|
MaxTokens int
|
||||||
Temperature float64
|
Temperature float64
|
||||||
|
// Detailed content for verbose logging
|
||||||
|
UserMessagePreview string // Last user message preview
|
||||||
|
SystemPromptPreview string // System prompt preview
|
||||||
|
ToolNames []string // List of available tool names
|
||||||
}
|
}
|
||||||
|
|
||||||
// LLMResponsePayload describes an inbound LLM response.
|
// LLMResponsePayload describes an inbound LLM response.
|
||||||
|
|
@ -142,6 +146,16 @@ type LLMResponsePayload struct {
|
||||||
ContentLen int
|
ContentLen int
|
||||||
ToolCalls int
|
ToolCalls int
|
||||||
HasReasoning bool
|
HasReasoning bool
|
||||||
|
// Detailed content for verbose logging
|
||||||
|
ContentPreview string // First 200 chars of response
|
||||||
|
ToolCallDetails []ToolCallDetail // Details of tool calls
|
||||||
|
FinishReason string // Why the LLM stopped
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToolCallDetail describes a single tool call from LLM
|
||||||
|
type ToolCallDetail struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Arguments string `json:"arguments"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LLMDeltaPayload describes a streamed LLM delta.
|
// LLMDeltaPayload describes a streamed LLM delta.
|
||||||
|
|
|
||||||
|
|
@ -1888,6 +1888,24 @@ turnLoop:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract user message preview for logging
|
||||||
|
userMessagePreview := ""
|
||||||
|
for i := len(callMessages) - 1; i >= 0; i-- {
|
||||||
|
if callMessages[i].Role == "user" && callMessages[i].Content != "" {
|
||||||
|
userMessagePreview = callMessages[i].Content
|
||||||
|
if len(userMessagePreview) > 100 {
|
||||||
|
userMessagePreview = userMessagePreview[:97] + "..."
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract tool names for logging
|
||||||
|
toolNames := make([]string, 0, len(providerToolDefs))
|
||||||
|
for _, tool := range providerToolDefs {
|
||||||
|
toolNames = append(toolNames, tool.Function.Name)
|
||||||
|
}
|
||||||
|
|
||||||
al.emitEvent(
|
al.emitEvent(
|
||||||
EventKindLLMRequest,
|
EventKindLLMRequest,
|
||||||
ts.eventMeta("runTurn", "turn.llm.request"),
|
ts.eventMeta("runTurn", "turn.llm.request"),
|
||||||
|
|
@ -1897,6 +1915,9 @@ turnLoop:
|
||||||
ToolsCount: len(providerToolDefs),
|
ToolsCount: len(providerToolDefs),
|
||||||
MaxTokens: ts.agent.MaxTokens,
|
MaxTokens: ts.agent.MaxTokens,
|
||||||
Temperature: ts.agent.Temperature,
|
Temperature: ts.agent.Temperature,
|
||||||
|
UserMessagePreview: userMessagePreview,
|
||||||
|
SystemPromptPreview: "", // Can add if needed
|
||||||
|
ToolNames: toolNames,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -2132,6 +2153,34 @@ turnLoop:
|
||||||
ts.channel,
|
ts.channel,
|
||||||
al.targetReasoningChannelID(ts.channel),
|
al.targetReasoningChannelID(ts.channel),
|
||||||
)
|
)
|
||||||
|
// Extract content preview and tool call details for logging
|
||||||
|
contentPreview := response.Content
|
||||||
|
if len(contentPreview) > 200 {
|
||||||
|
contentPreview = contentPreview[:197] + "..."
|
||||||
|
}
|
||||||
|
|
||||||
|
toolCallDetails := make([]ToolCallDetail, 0, len(response.ToolCalls))
|
||||||
|
for _, tc := range response.ToolCalls {
|
||||||
|
// Skip if Function is nil (some providers may return tool calls without function details)
|
||||||
|
if tc.Function == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
argsStr := ""
|
||||||
|
for k, v := range tc.Function.Arguments {
|
||||||
|
if argsStr != "" {
|
||||||
|
argsStr += ", "
|
||||||
|
}
|
||||||
|
argsStr += fmt.Sprintf("%v=%v", k, v)
|
||||||
|
}
|
||||||
|
if len(argsStr) > 100 {
|
||||||
|
argsStr = argsStr[:97] + "..."
|
||||||
|
}
|
||||||
|
toolCallDetails = append(toolCallDetails, ToolCallDetail{
|
||||||
|
Name: tc.Function.Name,
|
||||||
|
Arguments: argsStr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
al.emitEvent(
|
al.emitEvent(
|
||||||
EventKindLLMResponse,
|
EventKindLLMResponse,
|
||||||
ts.eventMeta("runTurn", "turn.llm.response"),
|
ts.eventMeta("runTurn", "turn.llm.response"),
|
||||||
|
|
@ -2139,6 +2188,9 @@ turnLoop:
|
||||||
ContentLen: len(response.Content),
|
ContentLen: len(response.Content),
|
||||||
ToolCalls: len(response.ToolCalls),
|
ToolCalls: len(response.ToolCalls),
|
||||||
HasReasoning: response.Reasoning != "" || response.ReasoningContent != "",
|
HasReasoning: response.Reasoning != "" || response.ReasoningContent != "",
|
||||||
|
ContentPreview: contentPreview,
|
||||||
|
ToolCallDetails: toolCallDetails,
|
||||||
|
FinishReason: response.FinishReason,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -2188,14 +2240,14 @@ turnLoop:
|
||||||
normalizedToolCalls = append(normalizedToolCalls, providers.NormalizeToolCall(tc))
|
normalizedToolCalls = append(normalizedToolCalls, providers.NormalizeToolCall(tc))
|
||||||
}
|
}
|
||||||
|
|
||||||
toolNames := make([]string, 0, len(normalizedToolCalls))
|
toolCallNames := make([]string, 0, len(normalizedToolCalls))
|
||||||
for _, tc := range normalizedToolCalls {
|
for _, tc := range normalizedToolCalls {
|
||||||
toolNames = append(toolNames, tc.Name)
|
toolCallNames = append(toolCallNames, tc.Name)
|
||||||
}
|
}
|
||||||
logger.InfoCF("agent", "LLM requested tool calls",
|
logger.InfoCF("agent", "LLM requested tool calls",
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"agent_id": ts.agent.ID,
|
"agent_id": ts.agent.ID,
|
||||||
"tools": toolNames,
|
"tools": toolCallNames,
|
||||||
"count": len(normalizedToolCalls),
|
"count": len(normalizedToolCalls),
|
||||||
"iteration": iteration,
|
"iteration": iteration,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue