02
Добавляем первый Tool
Начало

Даём модели возможность «действовать» — вызывать функции (tool). Ключевой момент: модель не выдумывает данные, а просит нас выполнить функцию.

Современные LLM API уже имеют нативную поддержку tools — ты описываешь tools в запросе через JSON Schema, и модель сама возвращает tool_calls в структурированном виде. Не нужно просить «верни JSON» — это уже встроено в протокол.

Ключевой момент: модель решает сама — вызвать tool или ответить текстом. Ты не пишешь if/else — модель видит описание tools и контекст вопроса, и принимает решение за тебя.

src/02-with-tool.ts
// src/02-with-tool.ts
import OpenAI from "openai";
import dotenv from "dotenv";

dotenv.config();

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

// 1. Описываем tools — это просто JSON Schema
const tools: OpenAI.ChatCompletionTool[] = [
    {
        type: "function",
        function: {
            name: "get_weather",
            description: "Получить текущую погоду в городе",
            parameters: {
                type: "object",
                properties: {
                    city: {
                        type: "string",
                        description: "Название города",
                    },
                },
                required: ["city"],
            },
        },
    },
];

// 2. Реализация tool — обычная функция
function get_weather(city: string): string {
    // Пока фейковая, потом можно реальный API
    const data: Record<string, string> = {
        "Москва": "−5°C, снег",
        "Анапа": "+8°C, облачно",
        "Лондон": "+3°C, дождь",
    };
    return data[city] ?? `Нет данных для города ${city}`;
}

async function main() {
    const response = await client.chat.completions.create({
        model: "anthropic/claude-sonnet-4.5",
        messages: [
            { role: "user", content: "Какая погода в Анапе?" },
        ],
        tools,
    });

    const message = response.choices[0].message;

    // 3. Смотрим что вернула модель
    console.log("=== Ответ модели ===");
    console.log("Content:", message.content);
    console.log("Tool calls:", JSON.stringify(message.tool_calls, null, 2));
}

main();

Подсказка: если убрать tool_choice, модель будет решать сама — так реалистичнее, но демо может быть нестабильным.

npx tsx src/02-with-tool.ts
Демо в формате диалога
1/1
Пример вывода. У тебя может быть иначе — это нормально.
✅ Что должно получиться
В выводе видно tool_calls с вызовом get_weather и аргументом city.
🧯 Если не работает
Если tool_calls пустой — проверь, что есть tool_choice, или укажи в промпте «вызови get_weather».
💡 Обрати внимание: Content пустой! Модель не стала выдумывать погоду. Она вернула tool_calls с именем функции и аргументами — попросила нас выполнить get_weather. Вот это и есть structured output — нативный, не нужно просить «верни JSON».