measure provider prompt cache hits

This commit is contained in:
Ollie Agent 2026-08-15 14:09:00 +02:00
parent 64b76d9bf0
commit 8553bc7283
3 changed files with 44 additions and 36 deletions

View File

@ -21,18 +21,18 @@ import (
// a new Agent is built from the new agent config while the session
// host remains stable.
type Agent struct {
history *History
runtime *Runtime
name string // display name (defaults to id)
profile string // config profile name (e.g. "default")
agentsDir string
systemPrompt string // system prompt for /agent reloads
envBlock string // environment block for /agent reloads
newToolServer func() *toolsrv.Conn
newBackend func(string) (backend.Backend, error)
currentAction atomic.Pointer[actionHandle]
warnedContext bool
resultCache sync.Map
history *History
runtime *Runtime
name string // display name (defaults to id)
profile string // config profile name (e.g. "default")
agentsDir string
systemPrompt string // system prompt for /agent reloads
envBlock string // environment block for /agent reloads
newToolServer func() *toolsrv.Conn
newBackend func(string) (backend.Backend, error)
currentAction atomic.Pointer[actionHandle]
warnedContext bool
resultCache sync.Map
// Execution state — owned by the agent, protected by stateMu.
state string // "idle", "thinking", "calling: <tool>"
@ -43,10 +43,10 @@ type Agent struct {
Feed Feed // streaming input gate (change-detecting)
toolCallCount atomic.Int64
pendingInject atomic.Pointer[string]
submitMu sync.Mutex // serializes Submit calls (commands + turns)
stateMu sync.RWMutex
signalMu sync.Mutex
signalCh chan struct{} // closed on state change; replaced with a fresh channel
submitMu sync.Mutex // serializes Submit calls (commands + turns)
stateMu sync.RWMutex
signalMu sync.Mutex
signalCh chan struct{} // closed on state change; replaced with a fresh channel
// Event handler — receives events from this agent.
output EventHandler
@ -96,6 +96,7 @@ type UsageStats struct {
Estimated bool
LastTurnCostUSD float64
SessionCostUSD float64
CacheHitRatio float64
}
// Usage returns the agent's usage statistics.
@ -112,6 +113,7 @@ func (ag *Agent) Usage() *UsageStats {
Estimated: ag.history.Estimated,
LastTurnCostUSD: ag.history.LastTurnCostUSD,
SessionCostUSD: ag.history.SessionCostUSD,
CacheHitRatio: ag.history.cacheHitRatio(),
}
}
@ -491,7 +493,6 @@ const (
WatchFeed = "feed"
)
// SetSessionEnv injects session env vars into the execute server.
func (ag *Agent) SetSessionEnv(sessionID string) {
if ag.runtime == nil || ag.runtime.ToolServer == nil {
@ -670,26 +671,24 @@ func ConsumeFeed(ctx context.Context, ag *Agent) {
}
}
// AgentParams holds the runtime dependencies for constructing a new Agent.
// Not to be confused with AgentConfig, which is the on-disk JSON schema.
type AgentParams struct {
ID string // unique agent identity (uname)
SessionID string // id of session the agent belongs to
Profile string // config profile name (e.g. "default" → agents/default.json)
History *History
Runtime *Runtime
AgentsDir string
Cwd string // working directory for tool execution
SystemPrompt string
EnvBlock string
NewToolServer func() *toolsrv.Conn
NewBackend func(string) (backend.Backend, error)
Log *olog.Logger
StartupMsgs []string
Save func()
Flush func()
ID string // unique agent identity (uname)
SessionID string // id of session the agent belongs to
Profile string // config profile name (e.g. "default" → agents/default.json)
History *History
Runtime *Runtime
AgentsDir string
Cwd string // working directory for tool execution
SystemPrompt string
EnvBlock string
NewToolServer func() *toolsrv.Conn
NewBackend func(string) (backend.Backend, error)
Log *olog.Logger
StartupMsgs []string
Save func()
Flush func()
}
// NewAgent constructs an Agent from the given configuration.

View File

@ -151,6 +151,13 @@ func (s *History) addUsage(u backend.Usage, estimated bool) {
}
}
func (s *History) cacheHitRatio() float64 {
total := s.TotalInputTokens + s.TotalCachedInputTokens
if total == 0 {
return 0
}
return float64(s.TotalCachedInputTokens) / float64(total)
}
func (s *History) resetTurnAccumulators() {
s.turnInputTokens = 0
s.turnCachedTokens = 0

View File

@ -165,13 +165,15 @@ func (ag *Agent) executeTurn(ctx context.Context, input string) string {
var in, out, est, cached, creation int
var costUSD float64
fmt.Sscanf(ev.Content, "%d %d %d %g %d %d", &in, &out, &est, &costUSD, &cached, &creation)
ag.history.addUsage(backend.Usage{
usage := backend.Usage{
InputTokens: in,
CachedInputTokens: cached,
CacheCreationTokens: creation,
OutputTokens: out,
CostUSD: costUSD,
}, est != 0)
}
ag.history.addUsage(usage, est != 0)
ag.log.Debug("usage: model=%s input=%d cached=%d cache_create=%d output=%d hit_ratio=%.3f cost=$%.6f", ag.runtime.Backend.Model(), in, cached, creation, out, ag.history.cacheHitRatio(), costUSD)
ag.notifyChange()
}
if origOutput != nil {