Гайд · TNWS AI

Как выполнить параллельные tool calls через Ollama

WebLinuxmacOSWindows#Ollama#API#JavaScript
7 мин

Получаем несколько tool_calls, валидируем каждый, выполняем независимые функции и возвращаем все результаты одним следующим ходом.

Задача и применимость

Цель этого руководства — параллельно получить цену и остаток одного тестового товара без лишних последовательных запросов к модели. Используется точный интерфейс: parallel tool calling /api/chat. Материал подходит для локального сервиса, тестового контура или внутреннего API, где Ollama доступна по доверенному адресу. Не публикуйте порт 11434 напрямую в интернет и не подставляйте в примеры реальные клиентские данные.

В отличие от общего обзора здесь есть проверяемый контракт: запрос, структура ответа, позитивные assertions и негативный сценарий. Название модели в коде — пример; выберите установленную модель, которая поддерживает нужную capability, и зафиксируйте её имя в конфигурации проекта.

Проверенные факты и первоисточники

Сведения проверены 13 сентября 2026 года по официальной документации Ollama. Документация разрешает запросить несколько tool calls параллельно и затем передать модели все сообщения с результатами. Дополнительно структура базовых ответов сверена с Generate API, Chat API и Embed API.

Цены и тарифы не приводятся. Доступность конкретной модели, качество ответа и аппаратная производительность зависят от локальной установки, поэтому они не выдаются за универсальный факт. Метрики берутся только из фактического JSON-ответа вашего запуска.

Подготовка безопасного тестового стенда

  1. Убедитесь, что Ollama запущена локально и endpoint доступен приложению.
  2. Получите список установленных моделей через официальный endpoint tags и выберите модель, подходящую capability.
  3. Не зашивайте имя модели в десятки файлов: храните его в переменной окружения или одном конфигурационном модуле.
  4. Используйте искусственные значения DEMO, тестовые SKU и вымышленные тексты.
  5. Задайте timeout и проверяйте HTTP status до разбора JSON.
  6. Сохраняйте исходный request без секретов, версию приложения и имя модели для воспроизводимости.
  7. Разделяйте сетевую ошибку, ошибку JSON, нарушение схемы и неверный бизнес-результат.

Ollama генерирует вероятностный результат. Даже при стабильном запросе текст может отличаться, поэтому тестируйте инварианты: наличие поля, роль сообщения, допустимое имя tool, формат аргумента, число векторов или ожидаемое вычисление. Не сравнивайте длинный ответ целиком со snapshot без необходимости.

Пошаговая реализация

  1. Сформируйте payload только из документированных полей.
  2. Укажите model и обязательный prompt, messages либо input.
  3. Для примеров, которым нужен один JSON, явно задайте stream:false.
  4. Отправьте POST с Content-Type: application/json.
  5. При status вне диапазона 200–299 прочитайте текст ошибки и остановите обработку.
  6. Разберите JSON и проверьте типы нужных полей до использования.
  7. Проверьте связь ответа с входом: роли, индексы, tool_name или сохранённые фрагменты.
  8. Не исполняйте tool по имени напрямую через eval или динамический import; используйте allowlist dispatcher.
  9. Ограничьте число шагов agent loop и общий timeout.
  10. Запишите технические счётчики отдельно от пользовательского результата.
  11. Выполните негативный тест и убедитесь, что он действительно отклоняется.
  12. Только после этого переносите код в основной сервис.

Готовый пример

const handlers={get_price:({sku})=>({sku,price:1990,currency:"RUB"}),get_stock:({sku})=>({sku,available:7})};
const messages=[{role:"user",content:"Для SKU-DEMO дай цену и остаток"}];
const tools=Object.keys(handlers).map(name=>({type:"function",function:{name,description:name,parameters:{type:"object",required:["sku"],properties:{sku:{type:"string"}}}}}));
async function chat(){const r=await fetch("http://localhost:11434/api/chat",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({model:"qwen3",messages,tools,stream:false})});return r.json();}
const first=await chat();messages.push(first.message);
for(const c of first.message.tool_calls||[]){const fn=handlers[c.function.name];if(!fn)throw Error("unknown tool");messages.push({role:"tool",tool_name:c.function.name,content:JSON.stringify(fn(c.function.arguments))});}
if(messages.filter(x=>x.role==="tool").length!==2)throw Error("нужны два результата");
const final=await chat();console.log(final.message.content);

Код рассчитан на Node.js с глобальным fetch. Его можно адаптировать к Python, сохранив ту же JSON-схему и последовательность проверок. Перед production добавьте AbortController, структурированный журнал без содержимого пользовательского prompt и retry только для безопасных транспортных сбоев.

Не повторяйте автоматически запрос, который уже мог вызвать внешний tool с побочным эффектом. Для инструментов записи используйте idempotency key на стороне приложения и сохраняйте состояние выполненного вызова. Модель предлагает аргументы, но доверять им без валидации нельзя.

Пример входа и ожидаемого результата

Вход: SKU-DEMO; функции возвращают цену 1990 RUB и остаток 7.

Ожидаемый результат: Assistant запрашивает два tools, приложение добавляет два role=tool сообщения, финальный ответ содержит цену и остаток.

Фактическая формулировка модели может отличаться. Критерием служат явно перечисленные свойства. Если критерий зависит от знания модели, используйте небольшой набор тест-кейсов и ручную выборочную проверку, но не выдумывайте процент точности без измерения.

Негативный тест

Верните модели финальный запрос после первого результата, не дожидаясь второго. Тест должен отклонить неполный набор tool messages.

Негативный тест запускайте автоматически рядом с позитивным. Он должен завершаться конкретной ошибкой и не оставлять частично записанное состояние. Для tools проверьте неизвестное имя, лишние аргументы, пропущенное обязательное поле и неверный тип. Для embeddings проверяйте количество, размерность и конечность каждого числа.

Production-обёртка

Создайте одну функцию client.request, которая добавляет endpoint, timeout, заголовок и обработку статусов. Поверх неё сделайте отдельные функции generate, chat и embed с собственными схемами валидации. Так изменение одного endpoint не разнесётся по всему приложению.

В журнал пишите request_id, model, endpoint, status, done_reason и длительности, но не полный prompt, системную инструкцию, tool output или embedding. Вектор сам по себе не следует считать анонимным: применяйте к хранилищу те же правила доступа и срока хранения, что и к исходному документу.

Для agent loop храните messages в рамках одного задания, ограничивайте их размер и число ходов. Assistant message с tool_calls нужно добавить в историю до сообщений role=tool. Все результаты независимых параллельных вызовов добавьте до следующего обращения к модели.

Копируемый шаблон проверки

Endpoint: parallel tool calling /api/chat
Задача: параллельно получить цену и остаток одного тестового товара без лишних последовательных запросов к модели
Модель и capability проверены: <да/нет>
Искусственный вход: <значение>
Обязательные поля request: <список>
Обязательные поля response: <список>
Позитивный invariant: <условие>
Негативный сценарий: <условие>
Timeout: <значение проекта>
Max turns для цикла: <значение>
Allowlist tools: <имена>
PII и секреты отсутствуют: <да>
Источник: https://docs.ollama.com/capabilities/tool-calling
Дата проверки: 13 сентября 2026 года

Финальная проверка результата

Выполните тест дважды: после холодного старта и при загруженной модели. Не смешивайте эти измерения. Для времени переводите наносекунды явно и сохраняйте исходные значения. Для text generation проверяйте done и done_reason, если они участвуют в логике. Для chat проверяйте role и непустой content либо валидные tool_calls.

После обновления Ollama или модели снова прогоните контрактные тесты. Имя поля из документации важнее визуального сходства ответа. Если capability не подтверждена для выбранной модели, не маскируйте это повторными запросами: выберите совместимую модель или измените UX.

Чек-лист

  • Slug и title уникальны.
  • Endpoint и поля сверены 13 сентября 2026 года.
  • Модель поддерживает нужную capability.
  • Вход содержит только искусственные данные.
  • Установлен timeout.
  • HTTP status проверяется до JSON.
  • Типы ключевых полей проверяются.
  • Никакие tool calls не исполняются через eval.
  • Аргументы tools проходят схему и allowlist.
  • Agent loop имеет max turns.
  • Индексы embeddings не смещаются.
  • Метрики считаются из реальных полей ответа.
  • Негативный тест завершается ожидаемо.
  • Логи не содержат prompt, PII и секреты.

FAQ

Почему в примерах указан stream:false?

Так проще получить один JSON и проверить итоговые служебные поля. Для потокового UX нужен отдельный NDJSON-парсер, аккумуляция чанков и обработка финального done.

Можно ли считать ответ модели детерминированным?

Нет. Проверяйте структуру и бизнес-инварианты. Если нужна воспроизводимость, фиксируйте поддерживаемые параметры и модель, но всё равно оставляйте валидацию результата.

Нужно ли доверять аргументам tool_calls?

Нет. Это сгенерированный ввод. Проверяйте имя по allowlist, аргументы по JSON Schema и полномочия пользователя до выполнения функции.

Можно ли логировать весь payload для отладки?

Только на искусственном стенде. В production журнал ограничивают техническими идентификаторами, статусом и измерениями; чувствительное содержимое редактируют или исключают.

Что делать при несовместимой модели?

Остановить сценарий с понятной диагностикой и выбрать модель, для которой нужная capability подтверждена. Нельзя выдавать случайный текст за корректный structured/tool/FIM результат.

Читайте также

Комментарии

Пока тихо. Скажите первое слово