tool results: tier classification (hot/warm/cold) with immediate cold summarization
This commit is contained in:
parent
da36db8f2b
commit
779b99fd9b
|
|
@ -34,6 +34,7 @@ type AgentEnv struct {
|
|||
tools []backend.Tool
|
||||
exec toolExecutor
|
||||
classifyTool toolClassifier
|
||||
classifyTier func(name string) ResultTier
|
||||
Hooks Hooks
|
||||
preamble string
|
||||
genParams backend.GenerationParams
|
||||
|
|
@ -138,6 +139,22 @@ func BuildAgentEnv(cfg *config.Config, d tools.Dispatcher, cwd string) AgentEnv
|
|||
}
|
||||
}
|
||||
|
||||
var tierFn func(string) ResultTier
|
||||
if srv, ok := d.GetServer("execute"); ok {
|
||||
if tc, ok := srv.(tools.TierClassifier); ok {
|
||||
tierFn = func(name string) ResultTier {
|
||||
switch tc.ResultTier(name) {
|
||||
case "cold":
|
||||
return TierCold
|
||||
case "warm":
|
||||
return TierWarm
|
||||
default:
|
||||
return TierHot
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var backendName, modelName string
|
||||
if cfg != nil {
|
||||
backendName = cfg.Backend
|
||||
|
|
@ -149,6 +166,7 @@ func BuildAgentEnv(cfg *config.Config, d tools.Dispatcher, cwd string) AgentEnv
|
|||
tools: allTools,
|
||||
exec: exec,
|
||||
classifyTool: classify,
|
||||
classifyTier: tierFn,
|
||||
Hooks: hooks,
|
||||
preamble: preamble,
|
||||
genParams: genParams,
|
||||
|
|
@ -352,6 +370,7 @@ func NewAgentCore(cfg AgentCoreConfig) Core {
|
|||
Tools: cfg.Env.tools,
|
||||
Exec: cfg.Env.exec,
|
||||
ClassifyTool: cfg.Env.classifyTool,
|
||||
ClassifyTier: cfg.Env.classifyTier,
|
||||
ToolResultMaxBytes: defaultToolResultMaxBytes,
|
||||
GenerationParams: cfg.Env.genParams,
|
||||
MaxSteps: cfg.Env.maxSteps,
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ type agentConfig struct {
|
|||
Tools []backend.Tool
|
||||
Exec toolExecutor
|
||||
ClassifyTool func(name string) bool // nil=treat all as serial; true=parallel-read-safe
|
||||
ClassifyTier func(name string) ResultTier // nil=TierHot for all; tools self-classify retention
|
||||
ToolResultMaxBytes int // 0=unlimited; truncate tool results beyond this size
|
||||
Output EventHandler
|
||||
preamble string // compiled system+agent prompt sent as the system role
|
||||
|
|
@ -332,7 +333,11 @@ func run(ctx context.Context, cfg agentConfig, state state) error {
|
|||
resultCache.Store(tc.Name+"\x00"+string(tc.Arguments), result)
|
||||
}
|
||||
emit(cfg, Event{Role: "tool", Name: tc.Name, Content: result})
|
||||
return toolResult{ToolCallID: tc.ID, Name: tc.Name, Content: result, IsError: isErr}, false
|
||||
tier := TierHot
|
||||
if !isErr && cfg.ClassifyTier != nil {
|
||||
tier = cfg.ClassifyTier(tc.Name)
|
||||
}
|
||||
return toolResult{ToolCallID: tc.ID, Name: tc.Name, Content: result, IsError: isErr, Tier: tier}, false
|
||||
}
|
||||
|
||||
isParallelSafe := func(name string) bool {
|
||||
|
|
|
|||
|
|
@ -216,14 +216,28 @@ func (s *Session) recordTurnCost(model string) {
|
|||
func (s *Session) update(assistant backend.Message, results []toolResult) {
|
||||
s.messages = append(s.messages, assistant)
|
||||
for _, r := range results {
|
||||
content := r.Content
|
||||
if r.Tier == TierCold && !r.IsError {
|
||||
// Immediately collapse cold results to a one-line summary.
|
||||
content = summarizeToolResult(r.Name, content)
|
||||
}
|
||||
s.messages = append(s.messages, backend.Message{
|
||||
Role: "tool",
|
||||
Content: r.Content,
|
||||
Content: content,
|
||||
ToolCallID: r.ToolCallID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// summarizeToolResult produces a compact one-line summary of a tool result
|
||||
// for cold-tier results that don't need to stay verbatim.
|
||||
func summarizeToolResult(name, content string) string {
|
||||
if len(content) <= 200 {
|
||||
return content
|
||||
}
|
||||
return fmt.Sprintf("[%s: %d bytes, first 200: %s…]", name, len(content), content[:200])
|
||||
}
|
||||
|
||||
// removeCancelledToolResults filters out tool results that were cancelled due
|
||||
// to interrupt, keeping completed work. Also removes the corresponding tool
|
||||
// calls from assistant messages to maintain a valid message sequence.
|
||||
|
|
|
|||
|
|
@ -2,12 +2,22 @@ package agent
|
|||
|
||||
import "ollie/pkg/backend"
|
||||
|
||||
// ResultTier classifies how long a tool result stays in the hot message list.
|
||||
type ResultTier int
|
||||
|
||||
const (
|
||||
TierHot ResultTier = iota // stays verbatim in messages (default)
|
||||
TierWarm // summarized on next compaction pass
|
||||
TierCold // immediately summarized at update time
|
||||
)
|
||||
|
||||
// toolResult holds the output of a single tool call.
|
||||
type toolResult struct {
|
||||
ToolCallID string // may be empty (Ollama does not always set this)
|
||||
Name string
|
||||
Content string
|
||||
IsError bool
|
||||
Tier ResultTier // default TierHot; tools may self-classify
|
||||
}
|
||||
|
||||
// state is the interface both ephemeral and bead-backed state must satisfy.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
package execute
|
||||
|
||||
import "strings"
|
||||
|
||||
// ResultTier implements tools.TierClassifier. It reads the tool script's
|
||||
// "ollie:tier" annotation to determine result retention. Returns "hot" if
|
||||
// no annotation is found.
|
||||
func (e *Server) ResultTier(name string) string {
|
||||
code, err := ReadTool(name)
|
||||
if err != nil {
|
||||
return "hot"
|
||||
}
|
||||
return detectTier(code)
|
||||
}
|
||||
|
||||
// detectTier scans the first 10 lines of a tool script for an
|
||||
// "ollie:tier" annotation. Matches "ollie:tier hot", "ollie:tier warm",
|
||||
// or "ollie:tier cold" anywhere in the line.
|
||||
func detectTier(code string) string {
|
||||
lines := strings.SplitN(code, "\n", 11)
|
||||
if len(lines) > 10 {
|
||||
lines = lines[:10]
|
||||
}
|
||||
for _, line := range lines {
|
||||
idx := strings.Index(line, "ollie:tier")
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
rest := strings.TrimSpace(line[idx+len("ollie:tier"):])
|
||||
switch {
|
||||
case rest == "cold" || strings.HasPrefix(rest, "cold "):
|
||||
return "cold"
|
||||
case rest == "warm" || strings.HasPrefix(rest, "warm "):
|
||||
return "warm"
|
||||
case rest == "hot" || strings.HasPrefix(rest, "hot "):
|
||||
return "hot"
|
||||
}
|
||||
}
|
||||
return "hot"
|
||||
}
|
||||
|
|
@ -126,3 +126,10 @@ type ToolRestrictionSetter interface {
|
|||
type ParallelClassifier interface {
|
||||
IsParallelRead(name string) bool
|
||||
}
|
||||
|
||||
// TierClassifier is implemented by tool servers that can report the retention
|
||||
// tier of a tool's results: "hot" (verbatim), "warm" (summarized on compaction),
|
||||
// or "cold" (immediately summarized). Returns "hot" for unknown tools.
|
||||
type TierClassifier interface {
|
||||
ResultTier(name string) string
|
||||
}
|
||||
|
|
|
|||
Reference in New Issue