Problem (dein Agent lief… bis zum Deploy)
Notebook‑Agent: okay.
Deploy‑Agent: hier lebt der Schmerz:
- er erreicht das Netzwerk nicht, das du „angenommen“ hast
- er OOMKill’t, weil jemand „full trace logging“ anmacht
- er retried sich in eine Rate‑Limit‑Storm
- Secrets sind im Image (bitte nicht)
Containerizing ist kein Dockerfile‑Theater. Es ist der Moment, wo du den Agent zwingst, sich wie ein Service zu benehmen.
Warum das in Prod scheitert
Agents sind unangenehme Workloads:
- bursty Traffic (Traffic‑Spikes = Token‑Spikes)
- viel I/O (Tools) + Hänger bei Timeouts
- lange Tails (p95 okay, p99 Chaos)
Wenn dein Container Budgets/Timeouts nicht enforced, macht Production das. Nur eben via 504s, OOMKills und Rechnungen.
Diagramm: was du wirklich deployest
Echter Code: container‑freundlicher Entrypoint (Python + JS)
Langweilig ist gut:
- Config aus Env
- Budgets/Timeouts enforced
- Health Endpoint
import os
import time
from dataclasses import dataclass
from typing import Any, Dict
@dataclass(frozen=True)
class Budgets:
max_steps: int
max_tool_calls: int
max_seconds: int
def load_budgets() -> Budgets:
return Budgets(
max_steps=int(os.getenv("AGENT_MAX_STEPS", "25")),
max_tool_calls=int(os.getenv("AGENT_MAX_TOOL_CALLS", "12")),
max_seconds=int(os.getenv("AGENT_MAX_SECONDS", "60")),
)
def run_request(task: str, *, budgets: Budgets) -> Dict[str, Any]:
t0 = time.time()
steps = 0
tool_calls = 0
while True:
steps += 1
if steps > budgets.max_steps:
return {"output": "", "stop_reason": "max_steps"}
if tool_calls > budgets.max_tool_calls:
return {"output": "", "stop_reason": "max_tool_calls"}
if time.time() - t0 > budgets.max_seconds:
return {"output": "", "stop_reason": "max_seconds"}
return {"output": "ok", "stop_reason": "finish"}
def health() -> Dict[str, str]:
return {"ok": "true"}export function loadBudgets() {
return {
maxSteps: Number(process.env.AGENT_MAX_STEPS ?? 25),
maxToolCalls: Number(process.env.AGENT_MAX_TOOL_CALLS ?? 12),
maxSeconds: Number(process.env.AGENT_MAX_SECONDS ?? 60),
};
}
export function runRequest(task, { budgets }) {
const t0 = Date.now();
let steps = 0;
let toolCalls = 0;
while (true) {
steps += 1;
if (steps > budgets.maxSteps) return { output: "", stop_reason: "max_steps" };
if (toolCalls > budgets.maxToolCalls) return { output: "", stop_reason: "max_tool_calls" };
if ((Date.now() - t0) / 1000 > budgets.maxSeconds) return { output: "", stop_reason: "max_seconds" };
return { output: "ok", stop_reason: "finish" };
}
}
export function health() {
return { ok: true };
}Dockerfile (multi-stage, keine Secrets im Image)
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["npm","run","start"]
Realer Ausfall (incident-style, mit Zahlen)
Wir haben “debug logging” default‑on deployed. Es hat komplette Tool Results geloggt.
Impact an einem Nachmittag:
- Memory stieg bis OOMKill
- Retries verstärkten Load (Client retried, Agent retried Tools)
- ~12% request failure rate
- On‑Call: ~3 Stunden (weil Logs riesig und trotzdem nutzlos waren)
Fix:
- sampled logging + Redaction default (
/de/observability-monitoring/agent-logging) - Budgets im Runtime enforce’n (max seconds + max tool calls)
- Kill‑Switch‑Config, um teure Tools im Incident abzuschalten
Abwägungen
- Tight Timeouts reduzieren Tail‑Latency und können Quality senken.
- Mehr Logging hilft Debugging und tut Datenschutz/Kosten weh.
- “One container per agent” ist simpel und teuer. Shared Services sind billiger und schwerer.
Wann NICHT containerisieren
Wenn du das nicht als Service betreibst (kein Traffic, keine SLOs), überbau’s nicht. Aber sobald echte User das triggern können, betreibst du einen Service. Glückwunsch.
Copy/Paste Checkliste
- [ ] Budgets aus Env laden und im Runtime enforce’n
- [ ] Tool‑Gateway: timeouts/retries/allowlists enforced
- [ ] Health + readiness checks
- [ ] Secrets via Platform injecten (nicht baked)
- [ ] Kill switch config (disable tools / disable writes)
- [ ] Logs strukturiert + sampled; PII‑Redaction default‑on
Safe default config (YAML)
runtime:
env:
AGENT_MAX_STEPS: 25
AGENT_MAX_TOOL_CALLS: 12
AGENT_MAX_SECONDS: 60
tools:
allowlist: ["search.read", "http.get"]
timeouts_ms: { default: 8000 }
retries: { max: 2, backoff_ms: [200, 800] }
observability:
sampled_tool_results: true
result_sample_rate: 0.01
rollout:
canary_percent: 10
rollback_on_error_rate: 0.05
In OnceOnly umsetzen (optional)
# onceonly-python: tool allowlist + governed tool call
import os
from onceonly import OnceOnly
client = OnceOnly(
api_key=os.environ["ONCEONLY_API_KEY"],
timeout=5.0,
max_retries_429=2,
)
agent_id = "billing-agent"
client.gov.upsert_policy({
"agent_id": agent_id,
"allowed_tools": ["search.read", "http.get"],
"max_actions_per_hour": 200,
"max_spend_usd_per_day": 10.0,
})
res = client.ai.run_tool(
agent_id=agent_id,
tool="http.get",
args={"url": "https://example.com/health"},
spend_usd=0.001,
)
if not res.allowed:
raise RuntimeError(res.policy_reason)
FAQ (3–5)
Von Patterns genutzt
Verwandte Failures
Verwandte Seiten (3–6 Links)
- Grundlagen: Was einen Agent production-ready macht
- Failures: Cascading tool failures · Partial outage handling
- Governance: Kill switch · Budget controls
- Observability: Logging für KI‑Agenten
- Testing: Unit Tests für KI‑Agenten