Сценарий · Inquir Compute

Бэкенд AI-агента на serverless-функциях

Инструменты модели становятся HTTP-маршрутами на API-шлюзе: закрывайте их API-ключами, подставляйте секреты на уровне функции, выносите долгие шаги в задачи или пайплайны и пишите хендлеры на Node.js, Python или Go с той же наблюдаемостью, что и у остального стека.

Обновлено: 2026-06-28

Суть ответа

Бэкенд AI-агента на serverless-функциях. Функция на инструмент локализует зависимости и риск деплоя; история выполнений в консоли совпадает с вызовами конкретного инструмента — дежурить проще, чем с общим монолитом.

Когда подходит и когда нет

  • Многошаговые агенты
  • Доступ инструментов к закрытым данным

На что обратить внимание

  • Привилегированные инструменты на устройстве пользователя перестают работать, как только данные становятся регулируемыми, а ноутбук — жёстко заблокированным.
  • Один большой сервер со всеми инструментами превращает каждый деплой в рискованное изменение, а строки логов невозможно привязать к конкретной возможности агента.

Что агенту на самом деле нужно в продакшене

Ответ модели — только верхушка. В продакшене агенты ходят в закрытые данные, пишут в учётные системы, эскалируют вопросы людям и соблюдают ограничения — и у каждого из этих шагов должны быть понятные ретраи и обработка сбоев.

Без нормального бэкенда побочные эффекты уезжают в промпт или браузер — там их почти нельзя ни проконтролировать, ни отозвать.

Паттерны, которые ломаются в продакшене

Привилегированные инструменты на устройстве пользователя перестают работать, как только данные становятся регулируемыми, а ноутбук — жёстко заблокированным.

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

Инструменты агента как отдельные serverless-функции

Функция на инструмент локализует зависимости и риск деплоя; история выполнений в консоли совпадает с вызовами конкретного инструмента — дежурить проще, чем с общим монолитом.

Хендлеры используют те же рантаймы Node.js, Python и Go, что и вся платформа. Опциональные тёплые пулы срезают накладные расходы холодного старта, когда модель вызывает инструменты в плотном цикле, — замеряйте под реальной нагрузкой.

Правила реализации инструментов AI-агента

Одна функция — один инструмент

Разделяйте функции, если зависимости не связаны жёстко. Один инструмент на функцию — риск деплоя минимален, логи привязаны к конкретной возможности.

Проверяйте входные данные, возвращайте структурированный JSON

Отклоняйте невалидные запросы как можно раньше. Возвращайте стабильную JSON-схему, которую оркестратор сможет разобрать без исключений.

Секреты в переменных окружения, не в промптах

Выдавайте каждой функции-инструменту свои API-ключи в секретах рабочего пространства. Ротируйте ключи независимо от версий модели, не трогая шаблоны промптов, — секреты не попадают ни в логи, ни в контекстные окна.

Модель безопасности: аутентификация на шлюзе до хендлера

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

Длинную работу — в задачи или пайплайны

Когда шаг инструмента превышает таймаут шлюза, сразу верните ID задачи, а обогащение или побочные эффекты продолжите в фоновом пайплайне. Оркестратор опрашивает статус или получает вебхук по завершении.

Тёплые контейнеры для частых циклов вызовов инструментов

Когда модель вызывает инструменты подряд, холодный старт накапливается. Включите тёплые пулы для функций с устойчивым трафиком — замерьте p95/p99 до и после, чтобы оценить выигрыш.

Отслеживайте сбои по каждому эндпоинту инструмента

Настройте алерты по ошибкам на маршрут инструмента, а не только на сессию чата. Один упавший инструмент должен всплыть в наблюдаемости раньше, чем тихо ухудшит качество агента.

Как строить инструменты AI-агента на Inquir Compute

1

Описать контракт входных данных инструмента

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

2

Описать схему вывода и модель доступа

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

3

Описать повторы, идемпотентность и передачу по таймауту

Решите, когда повторять на месте, когда возвращать ID задачи и продолжать через пайплайн, и как ключевать записи, чтобы повторы не создавали дубликатов.

Паттерны хендлера инструмента — Node.js, Python, Go и async handoff

Синхронные инструменты используют один контракт шлюза: body приходит строкой, возвращайте { statusCode, body }. Языки можно смешивать: Python — ML-классификация, Node.js — поиск по справочникам и вызовы API, Go 1.22 — быстрые поисковые запросы. Когда шаг не укладывается в таймаут шлюза, верните 202 с ID задачи и продолжите в пайплайне (четвёртый блок).

tools/lookup.mjs (Node.js 22)
export async function handler(event) {
  const { id } = JSON.parse(event.body || '{}');
  if (!id) return { statusCode: 400, body: JSON.stringify({ error: 'id required' }) };
  // API key auth is enforced at the gateway route — handler assumes authenticated caller
  const row = await db.findById(id);
  if (!row) return { statusCode: 404, body: JSON.stringify({ error: 'not found' }) };
  return { statusCode: 200, body: JSON.stringify({ row }) };
}
tools/classify.py (Python 3.12)
import json, os
from openai import OpenAI

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])  # injected from workspace secrets

def handler(event, context):
    body = json.loads(event.get("body") or "{}")
    text = body.get("text")
    if not text:
        return {"statusCode": 400, "body": json.dumps({"error": "text required"})}
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"Classify intent of: {text}"}],
    )
    return {"statusCode": 200, "body": json.dumps({"intent": r.choices[0].message.content})}
tools/lookup.go (Go 1.22)
package main

import (
	"encoding/json"
)

func parsePayload(event map[string]interface{}) map[string]interface{} {
	if s, ok := event["body"].(string); ok && s != "" {
		var out map[string]interface{}
		if err := json.Unmarshal([]byte(s), &out); err != nil || out == nil {
			return map[string]interface{}{}
		}
		return out
	}
	return event
}

// Handler — API key auth enforced at the gateway route
func Handler(event map[string]interface{}, ctx map[string]interface{}) (interface{}, error) {
	payload := parsePayload(event)
	id, _ := payload["id"].(string)
	if id == "" {
		b, _ := json.Marshal(map[string]string{"error": "id required"})
		return map[string]interface{}{"statusCode": 400, "body": string(b)}, nil
	}
	row, err := db.FindByID(id)
	if err != nil {
		b, _ := json.Marshal(map[string]string{"error": "not found"})
		return map[string]interface{}{"statusCode": 404, "body": string(b)}, nil
	}
	b, _ := json.Marshal(map[string]interface{}{"row": row})
	return map[string]interface{}{"statusCode": 200, "body": string(b)}, nil
}
tools/enrich-async.mjs (async handoff)
export async function handler(event) {
  const { customerId } = JSON.parse(event.body || '{}');
  if (!customerId) return { statusCode: 400, body: JSON.stringify({ error: 'customerId required' }) };
  // Return fast; continue in pipeline — orchestrator polls /jobs/:jobId or receives webhook
  const { instanceId: jobId } = await global.durable.startNew('enrich-customer', undefined, { customerId });
  return { statusCode: 202, body: JSON.stringify({ jobId }) };
}

Когда выносить инструменты агента в отдельные функции

Когда это уместно

  • Многошаговые агенты
  • Доступ инструментов к закрытым данным

Когда лучше выбрать другое

  • Разовые ответы модели без побочных эффектов

Вопросы и ответы

Инструменты должны быть отдельными HTTP-функциями?

В продакшене обычно да — проще изоляция, меньше риск при публикации новой версии и понятнее логи, чем у монолита.

Где хранить секреты?

В секретах рабочего пространства с подстановкой в окружение — ключи не в промптах и не в клиентском бандле.

Стриминг ответов пользователю?

Стриминг к конечному пользователю — забота API-шлюза; между оркестратором и инструментами чаще остаётся обычный JSON request/response — так проще ретраи и идемпотентность.

Как сделать вызовы инструментов идемпотентными при повторных попытках модели?

Ключуйте записи стабильными ID из payload инструмента (ID клиента, заказа, внешней записи). Возвращайте ту же JSON-схему при повторе, чтобы оркестратор мог считать дубликат безопасным no-op.