mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
47b90afce6
Advisor tool calls currently reject questions over 2000 runes, which can leave the parent model retrying the same invalid call. This documents the limit in the advisor tool schema and guidance, then truncates oversized questions rune-safely before building the nested advisor prompt. > Mux working on behalf of Mike.
76 lines
2.8 KiB
Go
76 lines
2.8 KiB
Go
package chatadvisor
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"charm.land/fantasy"
|
|
)
|
|
|
|
// ToolName is the identifier the advisor tool registers under. The parent
|
|
// agent's exclusive-tool policy and the advisor-guidance block both reference
|
|
// this name, so keeping them synchronized requires a single source of truth.
|
|
const ToolName = "advisor"
|
|
|
|
// advisorQuestionMaxRunes caps the parent agent's question at a length
|
|
// that leaves room in the advisor prompt for system preamble and recent
|
|
// conversation context.
|
|
const advisorQuestionMaxRunes = 2000
|
|
|
|
// ToolOptions configures the built-in advisor tool.
|
|
type ToolOptions struct {
|
|
Runtime *Runtime
|
|
GetConversationSnapshot func() []fantasy.Message
|
|
PublishAdviceDelta func(toolCallID string, delta string)
|
|
PublishAdviceReset func(toolCallID string)
|
|
}
|
|
|
|
// Tool returns a fantasy.AgentTool that asks a nested model for concise
|
|
// strategic guidance. The nested advisor sees recent conversation
|
|
// context, runs without tools, and is limited to a single model step.
|
|
func Tool(opts ToolOptions) fantasy.AgentTool {
|
|
return fantasy.NewAgentTool(
|
|
ToolName,
|
|
"Ask a separate advisor pass for strategic guidance about planning, architecture, tradeoffs, or debugging strategy. Provide a brief question of 2000 runes or fewer, summarizing context instead of pasting long logs or transcripts. The advisor sees recent conversation context, runs without tools for a single step, and responds to the parent agent rather than the end user.",
|
|
func(ctx context.Context, args AdvisorArgs, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
|
|
if opts.Runtime == nil {
|
|
return fantasy.NewTextErrorResponse("advisor runtime is not configured"), nil
|
|
}
|
|
if opts.GetConversationSnapshot == nil {
|
|
return fantasy.NewTextErrorResponse("conversation snapshot provider is not configured"), nil
|
|
}
|
|
|
|
question := strings.TrimSpace(args.Question)
|
|
if question == "" {
|
|
return fantasy.NewTextErrorResponse("question is required"), nil
|
|
}
|
|
|
|
var runOpts *RunAdvisorOptions
|
|
if call.ID != "" && (opts.PublishAdviceDelta != nil || opts.PublishAdviceReset != nil) {
|
|
runOpts = &RunAdvisorOptions{}
|
|
if opts.PublishAdviceDelta != nil {
|
|
runOpts.OnAdviceDelta = func(delta string) {
|
|
opts.PublishAdviceDelta(call.ID, delta)
|
|
}
|
|
}
|
|
if opts.PublishAdviceReset != nil {
|
|
runOpts.OnAdviceReset = func() {
|
|
opts.PublishAdviceReset(call.ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
result, err := opts.Runtime.RunAdvisor(ctx, question, opts.GetConversationSnapshot(), runOpts)
|
|
if err != nil {
|
|
return fantasy.NewTextErrorResponse(err.Error()), nil
|
|
}
|
|
data, err := json.Marshal(result)
|
|
if err != nil {
|
|
return fantasy.NewTextResponse("{}"), nil
|
|
}
|
|
return fantasy.NewTextResponse(string(data)), nil
|
|
},
|
|
)
|
|
}
|