Урок 6 показал две стратегии, но обе были фиксированы. Что если реальность отличается от плана?
Мы добавляем request_replan — системный tool (не skill!),
который позволяет агенту запросить перепланирование. Важное различие: skills — переиспользуемые
способности; системные tools — механизмы архитектуры агента.
Это и есть то, как работают реальные агенты. Три фазы в цикле:
Фаза 1: Plan — отдельный LLM-вызов без tools. Модель составляет структурированный JSON-план, не зная реального состояния системы.
Фаза 2: ReAct Execute — выполняет план с think tool.
На каждом шаге думает: «совпадает ли реальность с планом?» Если мелкая проблема —
адаптируется на лету.
Фаза 3: Replan — если агент вызвал request_replan, цикл
возвращается к планированию.
Но теперь планировщик знает: что уже сделано, что нового обнаружили, почему старый план не
подходит.
request_replan — системный tool, не skill
Почему request_replan — не skill? Потому что это механизм agent loop,
а не переиспользуемая способность. Skill — это то, что агент умеет делать
(читать файлы, искать в интернете, думать). request_replan — это то, как агент
управляет своим рабочим процессом. Его нельзя переиспользовать в другом контексте
без самого planning loop.
Мы добавляем его через extraTools в createAgent() — именно для этого
мы расширяли options в skill-utils.
// Skill — переиспользуемая способность: // skills/reasoning/SKILL.md → think tool // skills/bash/SKILL.md → run_bash tool // Любой агент может их подключить // Системный tool — механизм архитектуры: // request_replan → управляет planning loop // Существует только внутри createPlanningAgent() // Добавляем через extraTools, не через SKILL.md const agent = createAgent(skills, { extraTools: [replanTool], extraHandlers: { request_replan: () => "REPLAN_REQUESTED" }, });
Skills из предыдущих уроков
Новых SKILL.md файлов нет — мы переиспользуем те же skills из уроков 5 и 6:
filesystem, bash, reasoning.
Вся новая логика — в agent loop и system prompt.
// src/07-adaptive-agent.ts — Adaptive: Plan + ReAct + Replan
import OpenAI from "openai";
import {
discoverSkills, activateSkill, createAgent,
createFilesystemSkill, createBashSkill, createReasoningSkill,
Skill
} from "./skill-utils";
import dotenv from "dotenv";
dotenv.config();
const client = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: process.env.OPENROUTER_API_KEY ?? "",
});
const MODEL = "anthropic/claude-sonnet-4.5";
// === request_replan — СИСТЕМНЫЙ tool, не skill ===
const replanTool: OpenAI.ChatCompletionTool = {
type: "function",
function: {
name: "request_replan",
description:
"Вызови когда план нужно пересмотреть: новая информация, " +
"провал шага, или лучший путь к цели.",
parameters: {
type: "object",
properties: {
reason: { type: "string", description: "Почему нужен новый план" },
completed_steps: {
type: "array", items: { type: "string" },
description: "Какие шаги уже выполнены",
},
discoveries: {
type: "array", items: { type: "string" },
description: "Что нового узнали",
},
},
required: ["reason", "completed_steps", "discoveries"],
},
},
};
// === Загрузка skills (те же что в уроке 6) ===
function loadSkills(): Skill[] {
const discovered = discoverSkills("./skills");
console.log("\n🔌 Activation:");
const skills: Skill[] = [];
for (const d of discovered) {
const meta = activateSkill(d.location);
switch (meta.name) {
case "filesystem": skills.push(createFilesystemSkill(meta)); break;
case "bash": skills.push(createBashSkill(meta)); break;
case "reasoning": skills.push(createReasoningSkill(meta)); break;
}
}
console.log(`\n🚀 ${skills.length} skills: ${skills.map(s => s.name).join(", ")}`);
return skills;
}
// === ФАЗА 1: Планирование ===
interface Plan {
goal: string;
steps: { id: number; action: string; tool: string; reason: string }[];
}
async function createPlan(
task: string,
context?: { previousPlan?: Plan; completedSteps?: string[]; discoveries?: string[] }
): Promise<Plan> {
let systemPrompt = `Ты стратегический планировщик. Составь план.
Доступные tools: run_bash, read_file, write_file, list_dir, think.
Ответь ТОЛЬКО валидным JSON:
{ "goal": "цель", "steps": [{ "id": 1, "action": "...", "tool": "...", "reason": "..." }] }`;
if (context?.previousPlan) {
systemPrompt += `\n\nКОНТЕКСТ РЕПЛАНА:
Предыдущий план: ${JSON.stringify(context.previousPlan)}
Уже выполнено: ${context.completedSteps?.join(", ") || "ничего"}
Новые данные: ${context.discoveries?.join("; ") || "нет"}
Учти что уже сделано, не повторяй выполненные шаги.`;
}
const response = await client.chat.completions.create({
model: MODEL,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: task },
],
});
const text = response.choices[0].message.content ?? "";
return JSON.parse(text.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim());
}
// === ФАЗА 2: Выполнение с ReAct + replan ===
interface ExecutionResult {
status: "completed" | "replan_requested";
replanContext?: { reason: string; completedSteps: string[]; discoveries: string[] };
}
async function executeWithReact(skills: Skill[], task: string, plan: Plan): Promise<ExecutionResult> {
// Специальный handler для request_replan
let replanData: ExecutionResult["replanContext"] | null = null;
const agent = createAgent(skills, {
systemPrompt: `Ты исполнитель с аналитическим мышлением.
ТВОЙ ПЛАН:
${JSON.stringify(plan, null, 2)}
МЕТОД: think → act → think → act.
Если план нужно менять — вызови request_replan.
НЕ вызывай replan для мелочей — адаптируйся на лету.
<available_skills>
{{SKILLS}}
</available_skills>`,
maxIterations: 20,
extraTools: [replanTool],
extraHandlers: {
request_replan: (args: any) => {
replanData = {
reason: args.reason,
completedSteps: args.completed_steps,
discoveries: args.discoveries,
};
console.log(`\n 🔄 РЕПЛАН: ${args.reason}`);
return "REPLAN_REQUESTED";
},
},
});
await agent(task);
if (replanData) {
return { status: "replan_requested", replanContext: replanData };
}
return { status: "completed" };
}
// === Главный цикл: Plan → Execute → Replan? ===
async function adaptiveAgent(task: string) {
console.log("╔══════════════════════════════════════════════╗");
console.log("║ ADAPTIVE AGENT: Plan + ReAct + Replan ║");
console.log("╚══════════════════════════════════════════════╝");
console.log(`\n📌 Задача: ${task}\n`);
const skills = loadSkills();
let plan = await createPlan(task);
console.log(`\n📋 ПЛАН: ${plan.goal}`);
plan.steps.forEach(s => console.log(` ${s.id}. [${s.tool}] ${s.action}`));
const MAX_REPLANS = 3;
let replanCount = 0;
while (replanCount <= MAX_REPLANS) {
console.log(`\n⚡ ВЫПОЛНЕНИЕ (план v${replanCount + 1})\n`);
const result = await executeWithReact(skills, task, plan);
if (result.status === "completed") {
console.log("\n🎉 Агент завершил задачу.");
return;
}
replanCount++;
if (replanCount > MAX_REPLANS) {
console.log("\n⚠️ Лимит репланов. Завершаем.");
return;
}
console.log(`\n📋 РЕПЛАН #${replanCount}...`);
plan = await createPlan(task, {
previousPlan: plan,
completedSteps: result.replanContext?.completedSteps,
discoveries: result.replanContext?.discoveries,
});
console.log(`🔄 НОВЫЙ ПЛАН: ${plan.goal}`);
plan.steps.forEach(s => console.log(` ${s.id}. [${s.tool}] ${s.action}`));
}
}
// === Запуск ===
const task = process.argv[2] ||
"Проанализируй все .ts файлы в директории src/. " +
"Найди дублирование кода между файлами. " +
"Создай файл REFACTORING.md с рекомендациями.";
adaptiveAgent(task);
npx tsx src/07-adaptive-agent.ts
request_replan передан через extraTools
и что extraHandlers содержит его обработчик.
filesystem, bash,
reasoning).
Системные tools — механизмы архитектуры (request_replan).
Разница: skill живёт в SKILL.md и работает в любом агенте.
Системный tool существует только внутри конкретного agent loop.
Adaptive replanning: планировщик при реплане получает контекст — что уже сделано, что нового узнали, почему старый план не подходит. Это отличает адаптивного агента от тупого исполнителя.
extraTools в createAgent() — точка расширения.
Мы не меняли skill-utils.ts — просто передали системный tool через options.