Problème (la “petite modif” qui te mange une semaine)
Tu ajustes un prompt parce que l’agent a une phrase bizarre.
En dev, ça a l’air OK.
En prod :
- tool calls/run montent doucement (personne ne regarde)
- la latence explose sous charge
- l’agent “oublie” les stop conditions
- un edge case rare devient un incident quotidien
L’optimisation de prompt, c’est de l’ingénierie. Pas du copywriting. Si tu fais “je modifie du texte jusqu’à ce que ça sonne mieux”, tu vas shipper des régressions.
Pourquoi ça casse en prod
En prod, un prompt est collé à :
- des schémas de tools (un champ renommé et le modèle se perd)
- des budgets (l’inflation tokens rend les boucles chères)
- des stop reasons (un prompt peut encourager “encore un essai”)
- de la variance externe (search change, APIs flaky, rate limits)
La vérité pas fun : tu ne peux pas optimiser un prompt sans harness. Pas besoin d’un truc fancy. Besoin d’un truc stable.
Diagramme : pipeline safe
Code réel : versionne tes prompts comme du code (Python + JS)
Le minimum viable :
- chaque prompt a une
prompt_idstable (hash ou version) - chaque run log la
prompt_id - rollback = un switch de config
import hashlib
from dataclasses import dataclass
from typing import Any, Dict
def prompt_id(text: str) -> str:
return hashlib.sha256(text.encode("utf-8")).hexdigest()[:12]
@dataclass(frozen=True)
class Prompt:
name: str
version: str
text: str
@property
def id(self) -> str:
return f"{self.name}:{self.version}:{prompt_id(self.text)}"
class Logger:
def event(self, name: str, fields: Dict[str, Any]) -> None: ...
def build_system_prompt(p: Prompt) -> str:
return (
"You are a production agent. You must follow tool policies and budgets.\n"
"Always stop with a stop_reason.\n\n"
f"[prompt_id={p.id}]\n"
+ p.text.strip()
)
def run_agent(task: str, *, prompt: Prompt, logger: Logger, budgets: Dict[str, Any]) -> Dict[str, Any]:
build_system_prompt(prompt)
logger.event("agent_start", {"prompt_id": prompt.id, "budget": budgets})
return {"output": "ok", "prompt_id": prompt.id, "stop_reason": "finish"}
PROMPTS = {
"support:v12": Prompt("support", "v12", "Answer using KB. If unsure, ask a clarifying question."),
"support:v13": Prompt("support", "v13", "Answer using KB. Cite tool results. If unsure, ask a clarifying question."),
}
ACTIVE_PROMPT = PROMPTS["support:v13"]import crypto from "node:crypto";
export function promptId(text) {
return crypto.createHash("sha256").update(text, "utf8").digest("hex").slice(0, 12);
}
export function makePrompt({ name, version, text }) {
const id = `${name}:${version}:${promptId(text)}`;
return { name, version, text, id };
}
export function buildSystemPrompt(prompt) {
return [
"You are a production agent. You must follow tool policies and budgets.",
"Always stop with a stop_reason.",
"",
"[prompt_id=" + prompt.id + "]",
prompt.text.trim(),
].join("\n");
}
export function runAgent(task, { prompt, logger, budgets }) {
buildSystemPrompt(prompt);
logger.event("agent_start", { prompt_id: prompt.id, budget: budgets });
return { output: "ok", prompt_id: prompt.id, stop_reason: "finish" };
}
const PROMPTS = {
"support:v12": makePrompt({ name: "support", version: "v12", text: "Answer using KB. If unsure, ask a clarifying question." }),
"support:v13": makePrompt({ name: "support", version: "v13", text: "Answer using KB. Cite tool results. If unsure, ask a clarifying question." }),
};
const ACTIVE_PROMPT = PROMPTS["support:v13"];Panne réelle (avec chiffres)
On a “amélioré” un prompt support en ajoutant une longue consigne “sois exhaustif”.
Rien n’a crashé. Mais le modèle a fait ce qu’on lui demandait : il est devenu exhaustif.
Impact sur 36h :
- p95 tokens/run : 7.5k → 14.2k
- avg tool calls/run : 4 → 11
- spend : +$620
- astreinte : ~2h pour prouver que c’était le prompt, pas le trafic
Fix :
- budgets (steps/tool calls/tokens)
- invariant golden tasks : max tool calls + max tokens
- rendre “exhaustif” conditionnel (seulement quand un tool manque)
Compromis
- Qualité vs coût : tu dois choisir ce que tu surveilles.
- Prompts courts = rapides, mais souvent trop flous côté safety.
- Contrats explicites = moins “joli”, plus safe.
Quand NE PAS optimiser le prompt
Ne “prompt‑optimize” pas pour remplacer :
- budgets (
/fr/governance/budget-controls) - validation tools (
/fr/tools/input-validation) - logging (
/fr/observability-monitoring/agent-logging) - tests (
/fr/testing-evaluation/unit-testing-agents)
Si l’agent est instable, toucher au prompt c’est juste bouger les symptômes.
Checklist copy-paste
- [ ]
prompt_idstable loggée sur chaque run - [ ] Golden tasks (10–50) proches du trafic réel
- [ ] Invariants : stop_reason, tool_calls bound, tokens bound
- [ ] Canary + rollback switch
- [ ] Monitor : spend/run, tool_calls/run, latency/run
- [ ] 1 golden task par incident
Config safe par défaut (YAML)
prompts:
active: "support:v13"
rollback: "support:v12"
require_prompt_id: true
testing:
golden_tasks:
- id: "kb_lookup"
expect_stop_reason: "finish"
max_tool_calls: 6
max_tokens: 9000
budgets:
max_steps: 25
max_tool_calls: 12
max_seconds: 60
observability:
log_prompt_id: true
alert_on_token_spike: true
Implémenter dans OnceOnly (optionnel)
# onceonly-python: budgets + safe rollout guardrails
import os
from onceonly import OnceOnly
client = OnceOnly(api_key=os.environ["ONCEONLY_API_KEY"])
agent_id = "support-bot"
# Set budgets/limits before you ship prompt changes
client.gov.upsert_policy({
"agent_id": agent_id,
"max_actions_per_hour": 200,
"max_spend_usd_per_day": 50.0,
"max_calls_per_tool": {"kb.search": 6},
"allowed_tools": ["kb.search", "send_email"],
})
# After rollout, watch for spend/tool spikes
m = client.gov.agent_metrics(agent_id, period="day")
print("actions=", m.total_actions, "spend_usd=", m.total_spend_usd)
FAQ (3–5)
Utilisé par les patterns
Pannes associées
Gouvernance requise
Pages liées (3–6 liens)
- Fondations: How agents use tools · Planning vs reactive agents
- Pannes: Hallucinated sources · Budget explosion
- Gouvernance: Budget controls · Tool permissions
- Observabilité: Logging d’agents IA
- Tests: Unit tests pour agents IA