04
Реальные Tools — bash и файловая система
Практика

Заменяем фейковые данные настоящими инструментами. Агент теперь может выполнять команды и читать файлы через tools. Запусти и посмотри: 5 итераций, модель сама решила что делать на каждом шаге — ls, read_file, ошибка, обходной путь, ответ.

Структура та же — agent loop. Но tools теперь взаимодействуют с реальной системой:

src/04-real-tools.ts
// src/04-real-tools.ts
import OpenAI from "openai";
import { execSync } from "child_process";
import { readFileSync } from "fs";
import dotenv from "dotenv";

dotenv.config();

const client = new OpenAI({
    baseURL: "https://openrouter.ai/api/v1",
    apiKey: process.env.OPENROUTER_API_KEY ?? "",
});

const tools: OpenAI.ChatCompletionTool[] = [
    {
        type: "function",
        function: {
            name: "run_bash",
            description: "Выполнить bash-команду и вернуть результат. Только безопасные read-only команды.",
            parameters: {
                type: "object",
                properties: {
                    command: {
                        type: "string",
                        description: "Bash-команда для выполнения",
                    },
                },
                required: ["command"],
            },
        },
    },
    {
        type: "function",
        function: {
            name: "read_file",
            description: "Прочитать содержимое файла",
            parameters: {
                type: "object",
                properties: {
                    path: {
                        type: "string",
                        description: "Путь к файлу",
                    },
                },
                required: ["path"],
            },
        },
    },
];

const toolHandlers: Record<string, (args: any) => string> = {
    run_bash: ({ command }) => {
        try {
            return execSync(command, {
                encoding: "utf-8",
                timeout: 5000,
            }).trim();
        } catch (e: any) {
            return `Ошибка: ${e.message}`;
        }
    },
    read_file: ({ path }) => {
        try {
            return readFileSync(path, "utf-8");
        } catch (e: any) {
            return `Ошибка: ${e.message}`;
        }
    },
};

async function agentLoop(userMessage: string) {
    const messages: OpenAI.ChatCompletionMessageParam[] = [
        {
            role: "system",
            content: `Ты полезный ассистент с доступом к файловой системе.
Можешь выполнять bash-команды и читать файлы.
Отвечай на русском.`,
        },
        { role: "user", content: userMessage },
    ];

    let iteration = 0;

    while (iteration < 10) {
        iteration++;
        console.log(`\n--- Итерация ${iteration} ---`);

        const response = await client.chat.completions.create({
            model: "anthropic/claude-sonnet-4.5",
            messages,
            tools,
        });

        const message = response.choices[0].message;
        messages.push(message);

        if (!message.tool_calls?.length) {
            console.log("\nАгент завершил работу.");
            console.log("Ответ:", message.content);
            return;
        }

        for (const toolCall of message.tool_calls) {
            const name = toolCall.function.name;
            const args = JSON.parse(toolCall.function.arguments);
            console.log(`Tool: ${name}(${JSON.stringify(args)})`);

            const result = toolHandlers[name]?.(args)
                ?? `Tool "${name}" не найден`;

            console.log(`Результат: ${result.slice(0, 200)}...`);

            messages.push({
                role: "tool",
                tool_call_id: toolCall.id,
                content: result,
            });
        }
    }
}

// Теперь агент может реально исследовать твою систему
agentLoop("Посмотри что за проект лежит в текущей директории. Какие зависимости установлены?");
npx tsx src/04-real-tools.ts
Демо в формате диалога
1/1
Пример вывода. У тебя может быть иначе — это нормально.
✅ Что должно получиться
Агент показывает вывод ls/cat, а затем коротко описывает проект.
🧯 Если не работает
Если ошибка прав доступа — попробуй другие файлы или директории. Если read_file падает — это баг с require в ESM, агент обойдёт через cat.
💡 Resilience агента — реальная история: В ESM-контексте require не работает. Агент вызвал read_file, получил ошибку — и не сломался. Он просто переключился на cat через run_bash, прочитал файл и продолжил работу. LLM адаптируется к ошибкам — это одна из его суперспособностей. Ты можешь даже не фиксить баг: агент найдёт обходной путь сам.