Bound agent result cache
This commit is contained in:
parent
062642dfd3
commit
6c1de81cf9
|
|
@ -36,7 +36,7 @@ type Agent struct {
|
||||||
closed atomic.Bool
|
closed atomic.Bool
|
||||||
actionMu sync.Mutex // serializes action registration with shutdown
|
actionMu sync.Mutex // serializes action registration with shutdown
|
||||||
warnedContext bool
|
warnedContext bool
|
||||||
resultCache sync.Map
|
resultCache resultCache
|
||||||
|
|
||||||
// Execution state — owned by the agent, protected by stateMu.
|
// Execution state — owned by the agent, protected by stateMu.
|
||||||
state string // "idle", "thinking", "calling: <tool>"
|
state string // "idle", "thinking", "calling: <tool>"
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,56 @@ type cachedResult struct {
|
||||||
Size int64 // file size at cache time; -1 if no file
|
Size int64 // file size at cache time; -1 if no file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
resultCacheMaxEntries = 256
|
||||||
|
resultCacheMaxBytes = 32 * 1024 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
type resultCache struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
items map[string]cachedResult
|
||||||
|
bytes int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *resultCache) Load(key string) (cachedResult, bool) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
v, ok := c.items[key]
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *resultCache) Delete(key string) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
if v, ok := c.items[key]; ok {
|
||||||
|
c.bytes -= len(v.Result)
|
||||||
|
delete(c.items, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *resultCache) Store(key string, value cachedResult) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
if c.items == nil {
|
||||||
|
c.items = make(map[string]cachedResult)
|
||||||
|
}
|
||||||
|
if old, ok := c.items[key]; ok {
|
||||||
|
c.bytes -= len(old.Result)
|
||||||
|
}
|
||||||
|
if len(value.Result) > resultCacheMaxBytes {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.items[key] = value
|
||||||
|
c.bytes += len(value.Result)
|
||||||
|
for len(c.items) > resultCacheMaxEntries || c.bytes > resultCacheMaxBytes {
|
||||||
|
for k, v := range c.items {
|
||||||
|
delete(c.items, k)
|
||||||
|
c.bytes -= len(v.Result)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// extractFilePath attempts to pull a "path" field from JSON tool args.
|
// extractFilePath attempts to pull a "path" field from JSON tool args.
|
||||||
func extractFilePath(args json.RawMessage) string {
|
func extractFilePath(args json.RawMessage) string {
|
||||||
var m map[string]json.RawMessage
|
var m map[string]json.RawMessage
|
||||||
|
|
@ -533,8 +583,7 @@ func (ag *Agent) execOne(ctx context.Context, call backend.ToolCall) (toolResult
|
||||||
readSafe := rt.ToolMeta[call.Name].Scope == "read"
|
readSafe := rt.ToolMeta[call.Name].Scope == "read"
|
||||||
if readSafe {
|
if readSafe {
|
||||||
key := call.Name + "\x00" + string(call.Arguments)
|
key := call.Name + "\x00" + string(call.Arguments)
|
||||||
if v, ok := resultCache.Load(key); ok {
|
if entry, ok := resultCache.Load(key); ok {
|
||||||
entry := v.(cachedResult)
|
|
||||||
if cacheValid(entry, call.Arguments) {
|
if cacheValid(entry, call.Arguments) {
|
||||||
ag.emit(Event{Role: "tool", Name: call.Name, Content: entry.Result, OutputFormat: toolOutputFormat(rt, call.Name)})
|
ag.emit(Event{Role: "tool", Name: call.Name, Content: entry.Result, OutputFormat: toolOutputFormat(rt, call.Name)})
|
||||||
return toolResult{ToolCallID: call.ID, Name: call.Name, Content: entry.Result}, false
|
return toolResult{ToolCallID: call.ID, Name: call.Name, Content: entry.Result}, false
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue