Add Calibrate AI persona presets with per-mode system prompts
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
This commit is contained in:
144
server/internal/ai/court_prompt_test.go
Normal file
144
server/internal/ai/court_prompt_test.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"crypto-miner-server/internal/strategy"
|
||||
)
|
||||
|
||||
type mockCourt struct {
|
||||
atlas string
|
||||
phenotype *strategy.FleetPhenotype
|
||||
}
|
||||
|
||||
func (m *mockCourt) FailureAtlasSummary(_, _ string) string { return m.atlas }
|
||||
func (m *mockCourt) BestPhenotype(_, _ string) (*strategy.FleetPhenotype, bool) {
|
||||
if m.phenotype == nil {
|
||||
return nil, false
|
||||
}
|
||||
return m.phenotype, true
|
||||
}
|
||||
|
||||
func stuckSpreadAttempts() []TierAttempt {
|
||||
attempts := make([]TierAttempt, 0, len(defaultSpreadTiers))
|
||||
for _, tier := range defaultSpreadTiers {
|
||||
attempts = append(attempts, TierAttempt{Tier: tier, OK: false, Error: "blocked"})
|
||||
}
|
||||
return attempts
|
||||
}
|
||||
|
||||
func TestBuildCourtPromptProsecutorFailures(t *testing.T) {
|
||||
snap := AgentSnapshot{
|
||||
AgentID: "agent-stuck",
|
||||
Name: "stuck-host",
|
||||
GOOS: "windows",
|
||||
MiningHashrate: 0,
|
||||
Stuck: true,
|
||||
ChainExhausted: true,
|
||||
LOTLAttempts: stuckSpreadAttempts(),
|
||||
}
|
||||
bundle := BuildCourtPrompt(snap, "docker 8/8 failed (100%)", nil, PersonaBalanced)
|
||||
if !strings.Contains(bundle.SystemPrompt, "## PROSECUTOR") {
|
||||
t.Fatal("expected prosecutor section in system prompt")
|
||||
}
|
||||
if !strings.Contains(bundle.ProsecutorSnippet, "14/14 failed") {
|
||||
t.Fatalf("prosecutor missing failure count: %s", bundle.ProsecutorSnippet)
|
||||
}
|
||||
if !strings.Contains(bundle.ProsecutorSnippet, "Failure atlas: docker 8/8 failed") {
|
||||
t.Fatalf("prosecutor missing atlas: %s", bundle.ProsecutorSnippet)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCourtPromptDefenderPhenotype(t *testing.T) {
|
||||
phenotype := &strategy.FleetPhenotype{
|
||||
Fingerprint: "windows|1|0|1|0|0|10.0.0",
|
||||
SourceAgentID: "agent-ok",
|
||||
SourceAgentName: "agent-ok",
|
||||
ActiveTier: "container",
|
||||
PeakHashrate: 1500,
|
||||
TierOrder: []string{"container", "inprocess"},
|
||||
}
|
||||
snap := AgentSnapshot{
|
||||
AgentID: "agent-stuck",
|
||||
Stuck: true,
|
||||
LOTLAttempts: []TierAttempt{
|
||||
{Tier: "docker", OK: false, Error: "denied"},
|
||||
},
|
||||
}
|
||||
bundle := BuildCourtPrompt(snap, "", phenotype, PersonaPersuasive)
|
||||
if !strings.Contains(bundle.DefenderSnippet, "Fleet phenotype from agent-ok") {
|
||||
t.Fatalf("defender missing phenotype: %s", bundle.DefenderSnippet)
|
||||
}
|
||||
if !strings.Contains(bundle.DefenderSnippet, "Winning tier: container") {
|
||||
t.Fatalf("defender missing winning tier: %s", bundle.DefenderSnippet)
|
||||
}
|
||||
if !strings.Contains(bundle.SystemPrompt, "## DEFENDER") {
|
||||
t.Fatal("expected defender section in system prompt")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCommandsFromCourtResponse(t *testing.T) {
|
||||
raw := "Verdict: restart mining after tier exhaustion.\n{\"commands\":[{\"type\":\"restart_mining\",\"args\":{}},{\"type\":\"noop\",\"args\":{}}]}"
|
||||
verdict := ExtractJudgeVerdict(raw)
|
||||
if !strings.Contains(verdict, "restart mining") {
|
||||
t.Fatalf("verdict: %q", verdict)
|
||||
}
|
||||
cmds := ParseCommands(raw)
|
||||
if len(cmds) != 2 || cmds[0].Type != CmdRestartMining {
|
||||
t.Fatalf("cmds: %+v", cmds)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldUseCourtStuckOrAllFailed(t *testing.T) {
|
||||
if !ShouldUseCourt(AgentSnapshot{Stuck: true}) {
|
||||
t.Fatal("expected court for stuck")
|
||||
}
|
||||
snap := AgentSnapshot{LOTLAttempts: stuckSpreadAttempts()}
|
||||
if !ShouldUseCourt(snap) {
|
||||
t.Fatal("expected court when all spread tiers failed")
|
||||
}
|
||||
if ShouldUseCourt(AgentSnapshot{LOTLAttempts: []TierAttempt{{Tier: "docker", OK: true}}}) {
|
||||
t.Fatal("expected mission prompt when a tier succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSchedulerUsesCourtWhenStuck(t *testing.T) {
|
||||
old := DecideFunc
|
||||
defer func() { DecideFunc = old }()
|
||||
var gotSystem, gotUser string
|
||||
DecideFunc = func(_ context.Context, _, _, systemPrompt, userPrompt string) (string, error) {
|
||||
gotSystem = systemPrompt
|
||||
gotUser = userPrompt
|
||||
return "Verdict: noop.\n{\"commands\":[{\"type\":\"noop\",\"args\":{}}]}", nil
|
||||
}
|
||||
|
||||
exec := &mockExec{}
|
||||
store := &mockStore{}
|
||||
court := &mockCourt{atlas: "wsl 5/5 failed (100%)"}
|
||||
sched := NewScheduler(
|
||||
&mockCfg{cfg: Config{Enabled: true, Endpoint: "http://test/v1", IntervalSec: 1}},
|
||||
&mockSnap{
|
||||
ids: []string{"agent-1"},
|
||||
snap: AgentSnapshot{
|
||||
AgentID: "agent-1", Name: "host", Stuck: true,
|
||||
LOTLAttempts: stuckSpreadAttempts(),
|
||||
},
|
||||
},
|
||||
exec,
|
||||
store,
|
||||
court,
|
||||
nil,
|
||||
)
|
||||
sched.lastRun["agent-1"] = time.Now().Add(-2 * time.Minute)
|
||||
sched.Tick()
|
||||
|
||||
if !strings.Contains(gotSystem, "## PROSECUTOR") {
|
||||
t.Fatalf("expected court system prompt, got: %s", gotSystem)
|
||||
}
|
||||
if !strings.Contains(gotUser, "## JUDGE") {
|
||||
t.Fatalf("expected judge user prompt, got: %s", gotUser)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user