Budget, stoppvillkor och kostnadskontroll
Kunna sätta budgetar och stoppvillkor så en agent aldrig springer iväg.
Förkunskaper
Intuition
En agent som loopar kostar pengar varje varv. Till skillnad från ett enskilt anrop finns ingen naturlig gräns — agenten bestämmer själv när den är klar, och ibland gör den aldrig det.
Fem oberoende stoppvillkor. Alla behövs, eftersom de fångar olika sorters haveri:
| Villkor | Fångar |
|---|---|
| Max steg | loopar |
| Max tid | långsamma verktyg, hängningar |
| Max kostnad | dyra modellanrop |
| Max verktygsanrop per typ | en agent som anropar samma sak om och om igen |
| Ingen framsteg | agenten upprepar sig utan att komma vidare |
Det sista är det svåraste att implementera och det mest värdefulla: en agent kan vara inom alla numeriska gränser och ändå inte göra något meningsfullt.
Formellt
Att upptäcka utebliven framsteg. Tre signaler, i ordning efter hur lätta de är att implementera:
| Signal | Hur |
|---|---|
| Identiskt verktygsanrop upprepas | hash argumenten och räkna |
| Observationerna slutar ändras | hash tillståndet efter varje steg |
| Ingen ny information | jämför entropin eller nyheten i observationerna |
Den första räcker långt: om samma anrop med samma argument görs tre gånger är agenten fast.
Budget som degraderar värdigt. En agent som bara stannar vid 100 % av budgeten ger användaren ingenting. Bättre är att trappa ner:
| Förbrukad budget | Beteende |
|---|---|
| < 50 % | normalt |
| 50–75 % | byt till en billigare modell för rutinsteg |
| 75–90 % | «avsluta det du håller på med, inga nya delmål» |
| 90–100 % | sammanfatta vad som gjorts och vad som återstår |
| 100 % | stopp med en användbar delleverans |
Den fjärde raden är den som gör skillnad: en agent som säger «jag hann göra A och B, C återstår och här är vad jag vet» är användbar. En som bara avbryts är det inte.
Kostnadsattribution per steg gör det möjligt att förbättra:
| Vad som loggas | Varför |
|---|---|
| Tokens in och ut per anrop | var kostnaden ligger |
| Vilket verktyg och hur länge | långsamma verktyg |
| Vilket delmål steget hörde till | vilken del av uppgiften som är dyr |
| Om steget gav ny information | ineffektivitet |
Den vanligaste kostnadsdrivaren i agenter är inte modellen utan kontexten. Historiken växer för varje steg, och eftersom hela historiken skickas med varje gång växer kostnaden kvadratiskt med antalet steg. En agent med 30 steg kan kosta mer än tio gånger en med 10 steg, inte tre gånger.
Motmedel: sammanfatta gamla steg, släng verktygsutdata som inte längre behövs, och använd prompt-caching på den fasta delen.
Sätt budgeten per uppgift, inte per anrop. Frågan «vad är den här uppgiften värd?» har ett svar; «vad är ett anrop värt?» har det inte.
Kod
import hashlib, json, time
from collections import Counter
from dataclasses import dataclass, field
@dataclass
class Budget:
max_steg: int = 30
max_sekunder: float = 300.0
max_kr: float = 5.0
max_per_verktyg: int = 8
max_upprepningar: int = 3
steg: int = 0
kr: float = 0.0
start: float = field(default_factory=time.perf_counter)
verktygsantal: Counter = field(default_factory=Counter)
anropshashar: Counter = field(default_factory=Counter)
tillstandshashar: list = field(default_factory=list)
logg: list = field(default_factory=list)
def forbrukad(self) -> float:
return max(self.steg / self.max_steg,
(time.perf_counter() - self.start) / self.max_sekunder,
self.kr / self.max_kr)
def lage(self) -> str:
f = self.forbrukad()
if f < 0.50: return "normal"
if f < 0.75: return "spara" # billigare modell för rutinsteg
if f < 0.90: return "avsluta" # inga nya delmål
if f < 1.00: return "sammanfatta"
return "stopp"
def kontrollera(self, verktyg: str, argument: dict, tillstand: str):
h = hashlib.sha256(
f"{verktyg}:{json.dumps(argument, sort_keys=True)}".encode()).hexdigest()[:16]
self.anropshashar[h] += 1
if self.anropshashar[h] > self.max_upprepningar:
return False, f"samma anrop till {verktyg} upprepat {self.anropshashar[h]} gånger"
if self.verktygsantal[verktyg] >= self.max_per_verktyg:
return False, f"maxantal anrop till {verktyg}"
self.tillstandshashar.append(tillstand)
if len(self.tillstandshashar) >= 4 and len(set(self.tillstandshashar[-4:])) == 1:
return False, "tillståndet har inte ändrats på fyra steg"
if self.lage() == "stopp":
return False, "budget slut"
return True, self.lage()
def registrera(self, verktyg, in_tok, ut_tok, pris_in, pris_ut, ny_information: bool):
kostnad = (in_tok * pris_in + ut_tok * pris_ut) / 1e6
self.steg += 1
self.kr += kostnad
self.verktygsantal[verktyg] += 1
self.logg.append({"steg": self.steg, "verktyg": verktyg, "kr": round(kostnad, 4),
"in_tok": in_tok, "ny_information": ny_information})
def rapport(self):
onodiga = sum(1 for r in self.logg if not r["ny_information"])
return {"steg": self.steg, "kr": round(self.kr, 3),
"sekunder": round(time.perf_counter() - self.start, 1),
"dyraste_verktyg": self.verktygsantal.most_common(3),
"andel_steg_utan_ny_information": round(onodiga / max(self.steg, 1), 3),
"kontexttillvaxt": [r["in_tok"] for r in self.logg]}
# Kontexten växer kvadratiskt om historiken skickas med varje gång
def kostnad_utan_komprimering(steg, tokens_per_steg=800, pris_in=30.0):
return sum((i * tokens_per_steg) * pris_in / 1e6 for i in range(1, steg + 1))
for n in (10, 20, 30):
print(f"{n:>2} steg: {kostnad_utan_komprimering(n):.3f} kr")
# 10 steg: 1.320 kr
# 20 steg: 5.040 kr
# 30 steg: 11.160 kr ← tre gånger fler steg, åtta gånger dyrare
Sista utskriften är det viktigaste talet i noden: kostnaden växer med kvadraten på antalet steg om historiken inte komprimeras.
Behärskning innebär
- Sätter flera oberoende stoppvillkor
- Upptäcker loopar och ineffektivitet
- Designar en budget som degraderar värdigt
Logga in för att göra övningarna och bygga upp din behärskning.
Källor
- OWASP Top 10 for LLM Applications — CC BY-SA 4.0
- Anthropic — Tool use — dokumentation, fri läsning
- arXiv — WebArena: A Realistic Web Environment for Building Autonomous Agents — arXiv (öppen åtkomst; licens per artikel)