feat(tool): debug tool usage via channels (#1332)
* feat(tool): debug usage via channel * set defaults * fix conflicts
This commit is contained in:
parent
a4b5a9eec1
commit
7673b626b3
5 changed files with 129 additions and 17 deletions
|
|
@ -8,7 +8,11 @@
|
||||||
"temperature": 0.7,
|
"temperature": 0.7,
|
||||||
"max_tool_iterations": 20,
|
"max_tool_iterations": 20,
|
||||||
"summarize_message_threshold": 20,
|
"summarize_message_threshold": 20,
|
||||||
"summarize_token_percent": 75
|
"summarize_token_percent": 75,
|
||||||
|
"tool_feedback": {
|
||||||
|
"enabled": false,
|
||||||
|
"max_args_length": 300
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"model_list": [
|
"model_list": [
|
||||||
|
|
|
||||||
|
|
@ -31,3 +31,69 @@ When this flag is active, the global truncation function is disabled. This is ex
|
||||||
* Verifying the exact syntax of the messages sent to the provider.
|
* Verifying the exact syntax of the messages sent to the provider.
|
||||||
* Reading the complete output of tools like `exec`, `web_fetch`, or `read_file`.
|
* Reading the complete output of tools like `exec`, `web_fetch`, or `read_file`.
|
||||||
* Debugging the session history saved in memory.
|
* Debugging the session history saved in memory.
|
||||||
|
|
||||||
|
## Tool Call Visibility in Debug Logs
|
||||||
|
|
||||||
|
When debug mode is active, the agent emits structured log entries at each stage of the tool execution lifecycle. These entries carry a `component=agent` label and use `INFO` or `DEBUG` level depending on the amount of detail:
|
||||||
|
|
||||||
|
| Log message | Level | Key fields | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `LLM requested tool calls` | INFO | `tools`, `count`, `iteration` | List of tool names the model decided to call |
|
||||||
|
| `Tool call: <name>(<args>)` | INFO | `tool`, `iteration` | The tool name and a preview of its arguments (truncated to 200 chars) |
|
||||||
|
| `Sent tool result to user` | DEBUG | `tool`, `content_len` | Fired when a tool result is forwarded to the chat channel |
|
||||||
|
| `TTL tick after tool execution` | DEBUG | `agent_id`, `iteration` | MCP tool-discovery TTL decrement after each tool round |
|
||||||
|
| `Async tool completed, publishing result` | INFO | `tool`, `content_len`, `channel` | Only for tools that run asynchronously in the background |
|
||||||
|
|
||||||
|
### Reading a tool call log entry
|
||||||
|
|
||||||
|
A typical synchronous tool call produces two consecutive lines in the console:
|
||||||
|
|
||||||
|
```
|
||||||
|
[...] [INFO] agent: LLM requested tool calls {tools=[web_search], count=1, iteration=1}
|
||||||
|
[...] [INFO] agent: Tool call: web_search({"query":"picoclaw release notes"}) {tool=web_search, iteration=1}
|
||||||
|
```
|
||||||
|
|
||||||
|
The arguments preview is hard-capped at **200 characters** in the logs regardless of the `--no-truncate` flag, because it belongs to the `INFO`-level path. Use `--no-truncate` together with `--debug` to see the full `tools_json` field emitted by the `Full LLM request` DEBUG entry, which contains every tool definition sent to the model.
|
||||||
|
|
||||||
|
## Real-Time Tool Feedback in Chat (tool_feedback)
|
||||||
|
|
||||||
|
Debug logs are server-side only. If you want the agent to send a visible notification directly into the chat channel every time it executes a tool—useful when sharing the bot with other users or for transparency—enable the `tool_feedback` feature in `config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"agents": {
|
||||||
|
"defaults": {
|
||||||
|
"tool_feedback": {
|
||||||
|
"enabled": true,
|
||||||
|
"max_args_length": 300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When `enabled` is `true`, every tool call sends a short message to the chat before the tool result is returned to the model. The message looks like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
🔧 `web_search`
|
||||||
|
{"query": "picoclaw release notes"}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Field | Type | Default | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `enabled` | bool | `false` | Send a chat notification for each tool call |
|
||||||
|
| `max_args_length` | int | `300` | Maximum characters of the serialised arguments included in the notification |
|
||||||
|
|
||||||
|
### Environment variables
|
||||||
|
|
||||||
|
Both fields can also be set via environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PICOCLAW_AGENTS_DEFAULTS_TOOL_FEEDBACK_ENABLED=true
|
||||||
|
PICOCLAW_AGENTS_DEFAULTS_TOOL_FEEDBACK_MAX_ARGS_LENGTH=300
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** `tool_feedback` is independent of `--debug` mode. It works in production and does not require the gateway to be started with any special flag.
|
||||||
|
|
|
||||||
|
|
@ -1322,6 +1322,22 @@ func (al *AgentLoop) runLLMIteration(
|
||||||
"iteration": iteration,
|
"iteration": iteration,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Send tool feedback to chat channel if enabled
|
||||||
|
if al.cfg.Agents.Defaults.IsToolFeedbackEnabled() && opts.Channel != "" {
|
||||||
|
feedbackPreview := utils.Truncate(
|
||||||
|
string(argsJSON),
|
||||||
|
al.cfg.Agents.Defaults.GetToolFeedbackMaxArgsLength(),
|
||||||
|
)
|
||||||
|
feedbackMsg := fmt.Sprintf("\U0001f527 `%s`\n```\n%s\n```", tc.Name, feedbackPreview)
|
||||||
|
fbCtx, fbCancel := context.WithTimeout(ctx, 3*time.Second)
|
||||||
|
_ = al.bus.PublishOutbound(fbCtx, bus.OutboundMessage{
|
||||||
|
Channel: opts.Channel,
|
||||||
|
ChatID: opts.ChatID,
|
||||||
|
Content: feedbackMsg,
|
||||||
|
})
|
||||||
|
fbCancel()
|
||||||
|
}
|
||||||
|
|
||||||
// Create async callback for tools that implement AsyncExecutor.
|
// Create async callback for tools that implement AsyncExecutor.
|
||||||
// When the background work completes, this publishes the result
|
// When the background work completes, this publishes the result
|
||||||
// as an inbound system message so processSystemMessage routes it
|
// as an inbound system message so processSystemMessage routes it
|
||||||
|
|
|
||||||
|
|
@ -219,23 +219,32 @@ type RoutingConfig struct {
|
||||||
Threshold float64 `json:"threshold"` // complexity score in [0,1]; score >= threshold → primary model
|
Threshold float64 `json:"threshold"` // complexity score in [0,1]; score >= threshold → primary model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToolFeedbackConfig controls whether tool execution details are sent to the
|
||||||
|
// chat channel as real-time feedback messages. When enabled, every tool call
|
||||||
|
// produces a short notification with the tool name and its parameters.
|
||||||
|
type ToolFeedbackConfig struct {
|
||||||
|
Enabled bool `json:"enabled" env:"PICOCLAW_AGENTS_DEFAULTS_TOOL_FEEDBACK_ENABLED"`
|
||||||
|
MaxArgsLength int `json:"max_args_length" env:"PICOCLAW_AGENTS_DEFAULTS_TOOL_FEEDBACK_MAX_ARGS_LENGTH"`
|
||||||
|
}
|
||||||
|
|
||||||
type AgentDefaults struct {
|
type AgentDefaults struct {
|
||||||
Workspace string `json:"workspace" env:"PICOCLAW_AGENTS_DEFAULTS_WORKSPACE"`
|
Workspace string `json:"workspace" env:"PICOCLAW_AGENTS_DEFAULTS_WORKSPACE"`
|
||||||
RestrictToWorkspace bool `json:"restrict_to_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"`
|
RestrictToWorkspace bool `json:"restrict_to_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"`
|
||||||
AllowReadOutsideWorkspace bool `json:"allow_read_outside_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_ALLOW_READ_OUTSIDE_WORKSPACE"`
|
AllowReadOutsideWorkspace bool `json:"allow_read_outside_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_ALLOW_READ_OUTSIDE_WORKSPACE"`
|
||||||
Provider string `json:"provider" env:"PICOCLAW_AGENTS_DEFAULTS_PROVIDER"`
|
Provider string `json:"provider" env:"PICOCLAW_AGENTS_DEFAULTS_PROVIDER"`
|
||||||
ModelName string `json:"model_name" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL_NAME"`
|
ModelName string `json:"model_name" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL_NAME"`
|
||||||
Model string `json:"model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"` // Deprecated: use model_name instead
|
Model string `json:"model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"` // Deprecated: use model_name instead
|
||||||
ModelFallbacks []string `json:"model_fallbacks,omitempty"`
|
ModelFallbacks []string `json:"model_fallbacks,omitempty"`
|
||||||
ImageModel string `json:"image_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_IMAGE_MODEL"`
|
ImageModel string `json:"image_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_IMAGE_MODEL"`
|
||||||
ImageModelFallbacks []string `json:"image_model_fallbacks,omitempty"`
|
ImageModelFallbacks []string `json:"image_model_fallbacks,omitempty"`
|
||||||
MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"`
|
MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"`
|
||||||
Temperature *float64 `json:"temperature,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"`
|
Temperature *float64 `json:"temperature,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"`
|
||||||
MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
|
MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
|
||||||
SummarizeMessageThreshold int `json:"summarize_message_threshold" env:"PICOCLAW_AGENTS_DEFAULTS_SUMMARIZE_MESSAGE_THRESHOLD"`
|
SummarizeMessageThreshold int `json:"summarize_message_threshold" env:"PICOCLAW_AGENTS_DEFAULTS_SUMMARIZE_MESSAGE_THRESHOLD"`
|
||||||
SummarizeTokenPercent int `json:"summarize_token_percent" env:"PICOCLAW_AGENTS_DEFAULTS_SUMMARIZE_TOKEN_PERCENT"`
|
SummarizeTokenPercent int `json:"summarize_token_percent" env:"PICOCLAW_AGENTS_DEFAULTS_SUMMARIZE_TOKEN_PERCENT"`
|
||||||
MaxMediaSize int `json:"max_media_size,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_MEDIA_SIZE"`
|
MaxMediaSize int `json:"max_media_size,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_MEDIA_SIZE"`
|
||||||
Routing *RoutingConfig `json:"routing,omitempty"`
|
Routing *RoutingConfig `json:"routing,omitempty"`
|
||||||
|
ToolFeedback ToolFeedbackConfig `json:"tool_feedback,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const DefaultMaxMediaSize = 20 * 1024 * 1024 // 20 MB
|
const DefaultMaxMediaSize = 20 * 1024 * 1024 // 20 MB
|
||||||
|
|
@ -247,6 +256,19 @@ func (d *AgentDefaults) GetMaxMediaSize() int {
|
||||||
return DefaultMaxMediaSize
|
return DefaultMaxMediaSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetToolFeedbackMaxArgsLength returns the max args preview length for tool feedback messages.
|
||||||
|
func (d *AgentDefaults) GetToolFeedbackMaxArgsLength() int {
|
||||||
|
if d.ToolFeedback.MaxArgsLength > 0 {
|
||||||
|
return d.ToolFeedback.MaxArgsLength
|
||||||
|
}
|
||||||
|
return 300
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsToolFeedbackEnabled returns true when tool feedback messages should be sent to the chat.
|
||||||
|
func (d *AgentDefaults) IsToolFeedbackEnabled() bool {
|
||||||
|
return d.ToolFeedback.Enabled
|
||||||
|
}
|
||||||
|
|
||||||
// GetModelName returns the effective model name for the agent defaults.
|
// GetModelName returns the effective model name for the agent defaults.
|
||||||
// It prefers the new "model_name" field but falls back to "model" for backward compatibility.
|
// It prefers the new "model_name" field but falls back to "model" for backward compatibility.
|
||||||
func (d *AgentDefaults) GetModelName() string {
|
func (d *AgentDefaults) GetModelName() string {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,10 @@ func DefaultConfig() *Config {
|
||||||
MaxToolIterations: 50,
|
MaxToolIterations: 50,
|
||||||
SummarizeMessageThreshold: 20,
|
SummarizeMessageThreshold: 20,
|
||||||
SummarizeTokenPercent: 75,
|
SummarizeTokenPercent: 75,
|
||||||
|
ToolFeedback: ToolFeedbackConfig{
|
||||||
|
Enabled: true,
|
||||||
|
MaxArgsLength: 300,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Bindings: []AgentBinding{},
|
Bindings: []AgentBinding{},
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue