The guardrails' judge has its own command, and its trouble is shown
llmgw ctl guard judge status lists each judge model the policies ask: in the catalog, as a judge, replicas ready, its last failure to answer, and what is wrong in words. enable serves the missing one through model serve - Llama Guard 3 1B on CPUs, role guard, under the name the policies give it - with --replace for a model of that name that is not a judge; the console has it under Do. GET /api/v1/guard/judges, and the summary's judges, put the problem on ctl status and the console's Overview. A Llama Guard file served from the mirror page is added as a judge, under the name the policies ask, rather than as a model clients use the policies never find. ctl says plainly when the API answers with a page. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
2a942409ea
commit
a8bb417618
24 files changed
+667
-13
No files matched your search
@@ -88,8 +88,10 @@ type AccountPages struct {
|
||||
oidc *oidcLogin
|
||||
// audit records every change made through these pages; nil records none.
|
||||
audit audit.Sink
|
||||
// hasGuard is set when /admin/guard is registered.
|
||||
hasGuard bool
|
||||
// hasGuard is set when /admin/guard is registered, and guardLibrary
|
||||
// with it: the policies in force, whose judges the mirror page serves.
|
||||
hasGuard bool
|
||||
guardLibrary func() guard.Config
|
||||
// guardFor names the guardrail policies in force for someone, and
|
||||
// whether they keep nothing; nil when there are none.
|
||||
guardFor func(guard.Subject) (string, bool)
|
||||
|
||||
@@ -50,7 +50,7 @@ var guardWindows = map[string]time.Duration{"24h": 24 * time.Hour, "7d": 7 * 24
|
||||
// RegisterAdminGuard mounts /admin/guard: what the guardrails did, where, by
|
||||
// which policy — names and counts, never text.
|
||||
func (p *AccountPages) RegisterAdminGuard(mux *http.ServeMux, s GuardAdminSource, library func() guard.Config) {
|
||||
p.hasGuard = true
|
||||
p.hasGuard, p.guardLibrary = true, library
|
||||
mux.HandleFunc("GET /admin/guard", p.adminOnly(func(w http.ResponseWriter, r *http.Request, admin store.User) {
|
||||
ctx, l := r.Context(), langOf(r)
|
||||
lib := library()
|
||||
|
||||
@@ -324,10 +324,14 @@ func (p *AccountPages) RegisterAdminMirror(mux *http.ServeMux, d MirrorDeps) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// A judge's weights are served as a judge, under the name the
|
||||
// policies ask: served as a model clients use, the policies
|
||||
// never find it and refuse every request they judge.
|
||||
judge, renamed := p.asJudge(&model)
|
||||
// The CPU while GPUs sit free is a choice to make on purpose:
|
||||
// made by default, it was a model running at a tenth of the
|
||||
// speed with nothing saying why.
|
||||
if model.Profiles[0].CPUOnly() && r.FormValue("cpu_ok") == "" {
|
||||
// speed with nothing saying why. A judge is meant for the CPU.
|
||||
if !judge && model.Profiles[0].CPUOnly() && r.FormValue("cpu_ok") == "" {
|
||||
if n := freeGPUs(p.fleet); n > 0 {
|
||||
return "", errors.New(l.N("serve.err.cpu_with_free_gpus", int64(n), model.Name))
|
||||
}
|
||||
@@ -346,8 +350,15 @@ func (p *AccountPages) RegisterAdminMirror(mux *http.ServeMux, d MirrorDeps) {
|
||||
}
|
||||
// Placed on the next pass, not now: saying it is serving would
|
||||
// be wrong for however long the weights take to reach a node.
|
||||
return l.T("serve.msg.added", model.Name,
|
||||
l.N("serve.replicas_n", int64(model.Scaling.Min)), describeServe(l, model)), nil
|
||||
msg := l.T("serve.msg.added", model.Name,
|
||||
l.N("serve.replicas_n", int64(model.Scaling.Min)), describeServe(l, model))
|
||||
switch {
|
||||
case renamed != "":
|
||||
msg += " " + l.T("serve.msg.judge_renamed", renamed, model.Name)
|
||||
case judge:
|
||||
msg += " " + l.T("serve.msg.judge", model.Name)
|
||||
}
|
||||
return msg, nil
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"gitea.mynodes.xyz/ilker/llmgw/internal/control/catalog"
|
||||
"gitea.mynodes.xyz/ilker/llmgw/internal/guard"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
@@ -745,3 +746,23 @@ func TestASafetensorsSetIsFetchedWithWhatTheEngineReadsBesideIt(t *testing.T) {
|
||||
t.Errorf("the companions are not said; got %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAJudgesWeightsAreServedAsTheJudgeThePoliciesAsk(t *testing.T) {
|
||||
p := &AccountPages{guardLibrary: func() guard.Config {
|
||||
return guard.Config{Policies: map[string]guard.Policy{
|
||||
"baseline": {Judge: &guard.JudgePolicy{Model: "llama-guard-3-1b"}},
|
||||
}}
|
||||
}}
|
||||
m := catalog.Model{Name: "llama-guard", Source: "http://10.0.0.146:8081/llama-guard/x/Llama-Guard-3-1B.Q4_K_M.gguf",
|
||||
RoutingProfile: "chat"}
|
||||
judge, renamed := p.asJudge(&m)
|
||||
if !judge || renamed != "llama-guard" || m.Name != "llama-guard-3-1b" || !m.IsGuard() ||
|
||||
m.Guard.Format != "llamaguard3" || m.RoutingProfile != "" {
|
||||
t.Errorf("judge %v renamed %q: %+v", judge, renamed, m)
|
||||
}
|
||||
|
||||
plain := catalog.Model{Name: "qwen", Source: "http://m/qwen.gguf", RoutingProfile: "chat"}
|
||||
if judge, _ := p.asJudge(&plain); judge || plain.IsGuard() || plain.Name != "qwen" {
|
||||
t.Errorf("a model clients use became a judge: %+v", plain)
|
||||
}
|
||||
}
|
||||
@@ -585,3 +585,36 @@ func argInt(args []string, flag string) int {
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// isJudgeWeights is whether a source is a guard model's weights: Llama
|
||||
// Guard, which reads a conversation and says whether it is safe.
|
||||
func isJudgeWeights(source string) bool {
|
||||
return strings.Contains(strings.ToLower(source), "llama-guard")
|
||||
}
|
||||
|
||||
// asJudge makes a model built from the serve form a judge when its weights
|
||||
// are one's: role guard, Llama Guard's format, under the name the policies
|
||||
// give their judge when they give one and the form gave another. It says
|
||||
// whether it did, and the name the form gave when it was changed.
|
||||
func (p *AccountPages) asJudge(m *catalog.Model) (judge bool, renamed string) {
|
||||
if !isJudgeWeights(m.Source) {
|
||||
return false, ""
|
||||
}
|
||||
m.Role, m.Guard, m.RoutingProfile = catalog.RoleGuard, &catalog.GuardModel{Format: "llamaguard3"}, ""
|
||||
if p.guardLibrary == nil {
|
||||
return true, ""
|
||||
}
|
||||
asked := map[string]bool{}
|
||||
for _, pol := range p.guardLibrary().Policies {
|
||||
if pol.Judge != nil && pol.Judge.Model != "" {
|
||||
asked[pol.Judge.Model] = true
|
||||
}
|
||||
}
|
||||
if asked[m.Name] || len(asked) != 1 {
|
||||
return true, ""
|
||||
}
|
||||
for name := range asked {
|
||||
renamed, m.Name = m.Name, name
|
||||
}
|
||||
return true, renamed
|
||||
}
|
||||
@@ -77,5 +77,7 @@
|
||||
"serve.err.cpu_with_free_gpus.other": "this fleet has %d GPUs free and %s was set to run on the CPU; choose a GPU placement, or tick \"run on the CPU anyway\"",
|
||||
"serve.msg.added": "%s added to the catalog, %s: %s; it is placed on the next pass",
|
||||
"serve.replicas_n.one": "%d replica",
|
||||
"serve.replicas_n.other": "%d replicas"
|
||||
"serve.replicas_n.other": "%d replicas",
|
||||
"serve.msg.judge": "It is the guardrails' judge (role: guard): policies naming %s ask it; clients do not see it.",
|
||||
"serve.msg.judge_renamed": "Its weights are a judge's, so it is the guardrails' judge (role: guard), named %[2]s as the policies ask rather than %[1]s."
|
||||
}
|
||||
@@ -74,5 +74,7 @@
|
||||
"serve.err.placement": "%q geçerli bir yerleşim değil",
|
||||
"serve.err.cpu_with_free_gpus.other": "bu kümede %d GPU boşta, %s ise CPU üzerinde çalışacak biçimde ayarlandı; bir GPU yerleşimi seçin ya da \"yine de CPU üzerinde çalıştır\" kutusunu işaretleyin",
|
||||
"serve.msg.added": "%s kataloğa eklendi, %s: %s; bir sonraki geçişte yerleştirilecek",
|
||||
"serve.replicas_n.other": "%d replika"
|
||||
"serve.replicas_n.other": "%d replika",
|
||||
"serve.msg.judge": "Guardrail'lerin judge modeli olarak eklendi (role: guard): %s adını veren politikalar ona soruyor; istemciler görmüyor.",
|
||||
"serve.msg.judge_renamed": "Ağırlıkları bir judge modelinin olduğu için guardrail judge'ı olarak eklendi (role: guard); adı %[1]s yerine politikaların istediği gibi %[2]s."
|
||||
}
|
||||
Reference in new issue
Block a user