В уроке 5 мы собрали агента из skills. Но стратегия была одна — отпустить агента в свободный полёт. Теперь мы добавим два новых skill'а и покажем, что одни и те же skills поддерживают принципиально разные стратегии: Plan-then-Execute и ReAct. Разница не в tools, а в том, как agent loop их оркестрирует.
Plan-then-Execute — два отдельных этапа. Сначала LLM (без tools) создаёт структурированный JSON-план, не зная реального состояния системы. Потом другой вызов получает план и выполняет по шагам. Плюс: предсказуемо, план можно показать пользователю. Минус: план может не совпасть с реальностью.
ReAct — один цикл, но
с think tool. Перед каждым действием агент «думает вслух»: что я знаю, что
делать дальше, почему.
Плюс: агент адаптируется на лету, видит реальные данные. Минус: менее предсказуемо, больше
итераций.
think tool — хитрый приём. Он ничего не делает технически, просто возвращает
"OK".
Но заставляет модель вербализовать рассуждение перед действием — и это сильно
повышает качество решений.
Шаг 1. Создаём reasoning skill
Think tool — это не просто функция, это skill. Оформляем его как отдельный
пакет с SKILL.md, чтобы любой агент мог его подключить.
mkdir -p skills/reasoning
--- name: reasoning description: Internal scratchpad for thinking and planning. Use when the agent needs to reason through a problem before acting. metadata: author: ai-agents-book version: "1.0" --- # Reasoning Skill Ты можешь «думать вслух» — использовать внутренний блокнот для рассуждений. ## Доступные инструменты - **think** — записать мысль, план или рассуждение. Результат не виден пользователю. ## Правила - Используй think перед сложным действием: обдумай, что знаешь и что делать дальше - Используй think после получения результата: оцени, совпало ли с ожиданиями - Чередуй think → action → think → action для лучших результатов
Обрати внимание на description: "Use when the agent needs to reason..." —
это подсказка для Discovery-уровня. Агент читает это и решает: нужен ли мне этот skill?
Шаг 2. Создаём bash skill
Второй новый skill — выполнение shell-команд. Вместе с filesystem из урока 5 это даёт агенту полный набор для работы с кодом.
mkdir -p skills/bash
--- name: bash description: Execute shell commands. Use when the agent needs to run scripts, install packages, or interact with the system. metadata: author: ai-agents-book version: "1.0" --- # Bash Skill Ты умеешь выполнять shell-команды через bash. ## Доступные инструменты - **run_bash** — выполнить bash-команду и получить stdout ## Правила - Используй для чтения структуры проекта (find, ls, tree) - Используй для анализа кода (grep, wc, diff) - Таймаут: 10 секунд. Не запускай долгие процессы - Будь осторожен с деструктивными командами (rm, mv)
ls skills/ # bash/ filesystem/ reasoning/ web-search/
Шаг 3. Shared модуль — skill-utils.ts
В уроке 5 весь код был в одном файле. Теперь выносим общую инфраструктуру
в src/skill-utils.ts — чтобы уроки 6, 7, 8 могли переиспользовать
discoverSkills(), activateSkill() и createAgent()
без дублирования кода.
Ключевое расширение: createAgent(skills, options?) теперь принимает
options — systemPrompt, maxIterations,
extraTools, extraHandlers. Это позволяет кастомизировать
агента без изменения самого createAgent.
// src/skill-utils.ts — Shared модуль для уроков 05–08
import OpenAI from "openai";
import { execSync } from "child_process";
import { readFileSync, writeFileSync, readdirSync, statSync } from "fs";
import { join } from "path";
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";
// === Интерфейс Skill ===
export interface Skill {
name: string;
description: string; // Discovery-уровень
instructions: string; // Activation-уровень
location: string;
tools: OpenAI.ChatCompletionTool[];
handlers: Record<string, (args: any) => string | Promise<string>>;
}
// === Парсер YAML frontmatter ===
export function parseFrontmatter(raw: string) {
const parts = raw.split("---");
if (parts.length < 3) throw new Error("Invalid SKILL.md: no frontmatter");
const frontmatter = parts[1].trim();
const body = parts.slice(2).join("---").trim();
const meta: Record<string, string> = {};
for (const line of frontmatter.split("\n")) {
const idx = line.indexOf(":");
if (idx > 0 && !line.startsWith(" "))
meta[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
}
return { name: meta.name, description: meta.description, body };
}
// === Discovery + Activation ===
export function discoverSkills(dir: string) { /* ... сканируем папку ... */ }
export function activateSkill(location: string) { /* ... загружаем SKILL.md ... */ }
// === Фабрики ===
export function createFilesystemSkill(meta) { /* tools: read_file, write_file, list_dir */ }
export function createBashSkill(meta) { /* tools: run_bash */ }
export function createReasoningSkill(meta) { /* tools: think → "OK" */ }
export function createWebSearchSkill(meta) { /* tools: web_search */ }
// === createAgent с options ===
interface AgentOptions {
systemPrompt?: string; // кастомный prompt ({{SKILLS}} заменяется)
maxIterations?: number;
extraTools?: OpenAI.ChatCompletionTool[]; // системные tools
extraHandlers?: Record<string, Function>; // их обработчики
}
export function createAgent(skills: Skill[], options: AgentOptions = {}) {
const allTools = [...skills.flatMap(s => s.tools), ...(options.extraTools ?? [])];
const allHandlers = { ...Object.assign({}, ...skills.map(s => s.handlers)),
...(options.extraHandlers ?? {}) };
// System prompt формируется из skills XML + options.systemPrompt
// Agent loop: итерации до maxIterations, async handlers
}
Шаг 4. Код планирующего агента
Теперь главное: обе стратегии используют одни и те же skills,
но по-разному. Plan-then-Execute — два вызова LLM (планировщик без tools +
исполнитель с tools). ReAct — один createAgent() с особым system prompt.
// src/06-planning.ts — Одни skills, две стратегии
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";
// === Загружаем skills ===
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;
// web-search пропускаем — для этой задачи не нужен
}
}
console.log(`\n🚀 Активировано ${skills.length} skills: ${skills.map(s => s.name).join(", ")}`);
return skills;
}
// ============================================================
// ПОДХОД 1: Plan-then-Execute
// ============================================================
async function planThenExecute(skills: Skill[], task: string) {
console.log("╔══════════════════════════════════════════╗");
console.log("║ ПОДХОД 1: Plan-then-Execute ║");
console.log("╚══════════════════════════════════════════╝\n");
// ФАЗА 1: Планирование (LLM без tools)
console.log("📋 ФАЗА 1: ПЛАНИРОВАНИЕ\n");
const planResponse = await client.chat.completions.create({
model: MODEL,
messages: [
{
role: "system",
content: `Ты планировщик. Составь пошаговый план.
Доступные инструменты: run_bash, read_file, write_file, list_dir, think.
Ответь ТОЛЬКО валидным JSON:
{
"goal": "краткое описание цели",
"steps": [
{ "id": 1, "action": "описание", "tool": "имя_tool", "reason": "зачем" }
]
}`,
},
{ role: "user", content: task },
],
});
const planText = planResponse.choices[0].message.content ?? "";
const cleanJson = planText.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
const plan = JSON.parse(cleanJson);
console.log(`🎯 Цель: ${plan.goal}`);
plan.steps.forEach((s: any) => {
console.log(` ${s.id}. [${s.tool}] ${s.action}`);
});
// ФАЗА 2: Выполнение (createAgent с планом в system prompt)
console.log("\n\n⚡ ФАЗА 2: ВЫПОЛНЕНИЕ\n");
const agent = createAgent(skills, {
systemPrompt: `Ты исполнитель задач. Выполни план пошагово, используя инструменты.
ПЛАН:
${JSON.stringify(plan, null, 2)}
<available_skills>
{{SKILLS}}
</available_skills>
Выполняй шаги по порядку. Когда все выполнены — подведи итог.`,
maxIterations: 15,
});
await agent(task);
}
// ============================================================
// ПОДХОД 2: ReAct (Reasoning + Acting)
// ============================================================
async function reactAgent(skills: Skill[], task: string) {
console.log("╔══════════════════════════════════════════╗");
console.log("║ ПОДХОД 2: ReAct (Reason + Act) ║");
console.log("╚══════════════════════════════════════════╝\n");
const agent = createAgent(skills, {
systemPrompt: `Ты агент, который решает задачи пошагово.
МЕТОД РАБОТЫ (ReAct):
1. Перед КАЖДЫМ действием — вызови "think": что известно? что дальше? почему?
2. Выполни действие нужным tool
3. После результата — снова "think": получилось? что это значит?
Всегда чередуй think → action → think → action.
<available_skills>
{{SKILLS}}
</available_skills>`,
maxIterations: 20,
});
await agent(task);
}
// ============================================================
// MAIN
// ============================================================
async function main() {
const skills = loadSkills();
const task =
"Проанализируй все .ts файлы в директории src/. " +
"Найди дублирование кода между файлами. " +
"Создай файл REFACTORING.md с рекомендациями по рефакторингу.";
const approach = process.argv[2] ?? "plan";
if (approach === "plan") {
await planThenExecute(skills, task);
} else if (approach === "react") {
await reactAgent(skills, task);
} else {
console.log("Использование: npx tsx src/06-planning.ts [plan|react]");
}
}
main();
npx tsx src/06-planning.ts plan # или "react"
plan — сначала JSON-план, затем пошаговое выполнение.
В режиме react — чередование think и action на каждом шаге.
Оба используют одни и те же 3 skills: filesystem, bash, reasoning.
skills/reasoning/ и skills/bash/ созданы
с корректными SKILL.md. Проверь что src/skill-utils.ts на месте.
createAgent(skills) собирает tools из skills — а system prompt определяет
как агент ими пользуется. Plan-then-Execute и ReAct — это не про tools,
а про оркестрацию.
Think tool — хитрый приём: он ничего не делает (возвращает
"OK"),
но заставляет модель вербализовать рассуждение. Это skill, а не хак — любой агент
может его подключить через skills/reasoning/SKILL.md.
Сравнение:
Plan-then-Execute: ✅ предсказуемо, план виден юзеру · ❌ план может не совпасть с реальностью
ReAct: ✅ адаптивно, агент видит реальные данные · ❌ менее предсказуемо, больше итераций
На практике лучший результат даёт комбинация обоих — именно это мы сделаем в следующем шаге.