Проблема (той самий “маленький правочок”, який з’їдає тиждень)
Ти підкручуєш промпт, бо агент звучав трохи дивно.
У dev усе норм.
У проді:
- tool calls/run повзуть вгору (ніхто не помічає добу)
- латентність подвоюється під навантаженням
- агент починає “випадково” ігнорувати stop conditions
- рідкісний edge case стає щоденним інцидентом
Оптимізація промпта — це інженерна робота, не “підбір слів”. Якщо робити це як “поміняю текст, поки не подобається” — ти зашиєш регресії в реліз.
Чому це ламається в проді
У проді промпт жорстко зчеплений з:
- tool schemas (перейменував поле — модель “не розуміє”)
- бюджетами (інфляція токенів робить лупи дорогими)
- stop reasons (промпт може підштовхнути “ще одну спробу”)
- варіативністю середовища (search дрейфує, API стають flaky, rate limits)
Неприємна правда: без harness’а промпти безпечно не оптимізуються. Harness не має бути складним. Він має бути стабільним.
Діаграма: безпечний пайплайн промптів
Реальний код: версіонуй промпти як код (Python + JS)
Мінімум, який реально має сенс:
- кожен промпт має стабільний
prompt_id(hash або версія) - кожен run логуває
prompt_id - rollback — це один конфіг‑перемикач
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"];Реальний фейл (з цифрами)
Ми “покращили” support‑промпт, додавши довгу інструкцію “будь максимально thorough”.
Нічого не впало. Але модель зробила те, що ми попросили: вона стала thorough.
Імпакт за 36 годин:
- p95 tokens/run: 7.5k → 14.2k
- avg tool calls/run: 4 → 11
- витрати: +$620
- онкол: ~2 години, щоб довести, що це промпт, а не трафік
Фікс:
- капнути бюджети (steps/tool calls/tokens)
- golden‑task інваріанти: max tool calls і max tokens
- “thoroughness” зробити умовним правилом (тільки коли tool result не вистачає)
Компроміси
- Якість часто збільшує вартість. Якщо не вимірюєш — платиш.
- Короткі промпти швидкі, але часто розмиті по safety.
- Явні контракти не красиві, зате виживають.
Коли НЕ оптимізувати промпти
Не “оптимізуй промптом” те, що має бути інженерією:
- бюджети (
/uk/governance/budget-controls) - валідація tools (
/uk/tools/input-validation) - логи (
/uk/observability-monitoring/agent-logging) - тести (
/uk/testing-evaluation/unit-testing-agents)
Якщо агент нестабільний — промпт‑тюнінг просто пересуває симптоми.
Copy‑paste чекліст
- [ ] Стабільний
prompt_idу логах кожного run’а - [ ] Golden tasks (10–50) з реального трафіку
- [ ] Інваріанти: stop_reason, bound на tool_calls, bound на tokens
- [ ] Canary rollout + швидкий rollback
- [ ] Моніторинг: spend/run, tool_calls/run, latency/run
- [ ] 1 golden task на кожен інцидент
Safe default config (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
Implement in OnceOnly (опційно)
# 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)
Використовується в патернах
Пов’язані відмови
Пов’язані сторінки (3–6 лінків)
- Основи: How agents use tools · Planning vs reactive agents
- Фейли: Hallucinated sources · Budget explosion
- Governance: Budget controls · Tool permissions
- Спостережуваність: Логи AI‑агентів
- Тести: Юніт‑тести для AI‑агентів