Changeset 0.29.1 (#196)
This commit is contained in:
@@ -67,6 +67,8 @@ func (e *Evaluator) Evaluate(ctx *Context, policies []Policy, candidates []Candi
|
||||
result, matched = applyCostLimit(result, p.Config, ctx)
|
||||
case PolicyModelAlias:
|
||||
result, matched = applyModelAlias(result, p.Config, ctx)
|
||||
case PolicyCapabilityMatch:
|
||||
result, matched = applyCapabilityMatch(result, p.Config, ctx)
|
||||
}
|
||||
|
||||
if matched {
|
||||
@@ -218,6 +220,102 @@ func applyModelAlias(candidates []Candidate, cfg PolicyConfig, ctx *Context) ([]
|
||||
return candidates, true
|
||||
}
|
||||
|
||||
// applyCapabilityMatch filters candidates to those whose model has all
|
||||
// required capabilities. Optionally sorts remaining candidates by cheapest
|
||||
// output price when PreferCheapest is set.
|
||||
//
|
||||
// Required capabilities map to ModelCapabilities boolean fields:
|
||||
// "tool_calling", "vision", "thinking", "reasoning",
|
||||
// "code_optimized", "web_search", "streaming"
|
||||
func applyCapabilityMatch(candidates []Candidate, cfg PolicyConfig, ctx *Context) ([]Candidate, bool) {
|
||||
if len(cfg.RequiredCaps) == 0 {
|
||||
return candidates, false
|
||||
}
|
||||
if ctx.Capabilities == nil {
|
||||
return candidates, false
|
||||
}
|
||||
|
||||
filtered := make([]Candidate, 0, len(candidates))
|
||||
for _, c := range candidates {
|
||||
key := c.ConfigID + ":" + c.Model
|
||||
caps, ok := ctx.Capabilities[key]
|
||||
if !ok {
|
||||
continue // no capability data — skip (conservative)
|
||||
}
|
||||
if hasAllCaps(caps, cfg.RequiredCaps) {
|
||||
c.Reason = "capability match"
|
||||
filtered = append(filtered, c)
|
||||
}
|
||||
}
|
||||
|
||||
if len(filtered) == 0 {
|
||||
// No candidates match — fall back to original set rather than
|
||||
// returning empty (better to try than fail immediately).
|
||||
return candidates, false
|
||||
}
|
||||
|
||||
// Sort by cheapest output price if requested
|
||||
if cfg.PreferCheapest && ctx.Pricing != nil {
|
||||
sort.SliceStable(filtered, func(i, j int) bool {
|
||||
ki := filtered[i].ConfigID + ":" + filtered[i].Model
|
||||
kj := filtered[j].ConfigID + ":" + filtered[j].Model
|
||||
pi, oki := ctx.Pricing[ki]
|
||||
pj, okj := ctx.Pricing[kj]
|
||||
if !oki || pi == nil {
|
||||
return false // unknown price sorts last
|
||||
}
|
||||
if !okj || pj == nil {
|
||||
return true
|
||||
}
|
||||
return pi.OutputPerM < pj.OutputPerM
|
||||
})
|
||||
}
|
||||
|
||||
return filtered, true
|
||||
}
|
||||
|
||||
// hasAllCaps checks whether a ModelCapabilities struct has all requested
|
||||
// capability flags set. Unrecognized capability names are ignored.
|
||||
func hasAllCaps(caps *models.ModelCapabilities, required []string) bool {
|
||||
if caps == nil {
|
||||
return false
|
||||
}
|
||||
for _, req := range required {
|
||||
switch req {
|
||||
case "tool_calling":
|
||||
if !caps.ToolCalling {
|
||||
return false
|
||||
}
|
||||
case "vision":
|
||||
if !caps.Vision {
|
||||
return false
|
||||
}
|
||||
case "thinking":
|
||||
if !caps.Thinking {
|
||||
return false
|
||||
}
|
||||
case "reasoning":
|
||||
if !caps.Reasoning {
|
||||
return false
|
||||
}
|
||||
case "code_optimized":
|
||||
if !caps.CodeOptimized {
|
||||
return false
|
||||
}
|
||||
case "web_search":
|
||||
if !caps.WebSearch {
|
||||
return false
|
||||
}
|
||||
case "streaming":
|
||||
if !caps.Streaming {
|
||||
return false
|
||||
}
|
||||
// Unrecognized caps are ignored (forward-compatible)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ── Health Sorting ──────────────────────────
|
||||
|
||||
var statusOrder = map[models.ProviderStatus]int{
|
||||
|
||||
Reference in New Issue
Block a user