Сценарий · Inquir Compute

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

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

Обновлено: 2026-10-11

Суть ответа

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

Когда подходит

  • Многошаговые агенты
  • Доступ инструментов к закрытым данным
  • Сценарии агента, которым нужны повторы по шагам или одобрение человеком

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

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

Что агентам нужно на самом деле

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

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

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

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

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

Компонуемые инструменты-функции для AI-агентов

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

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

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

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

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

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

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

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

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

Авторизация на шлюзе до обработчика

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

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

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

Тёплые контейнеры для плотных циклов вызовов

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

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

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

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

1

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

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

2

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

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

3

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

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

Обработчики инструментов: Node.js, Python, Go, асинхронная передача и многошаговый пайплайн агента

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

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 (асинхронная передача)
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 }) };
}
pipelines/research-agent.json (многошаговый агент с шагом одобрения)
{
  "schemaVersion": 1,
  "nodes": [
    { "id": "in",      "kind": "httpTrigger", "name": "Start research", "config": { "method": "POST" } },
    { "id": "search",  "kind": "lambda", "name": "Search docs",
      "config": { "functionId": "search-docs",
                  "inputMapping": { "query": "{{trigger.body.query}}" },
                  "retryPolicy": { "maxAttempts": 3, "backoffMs": 1000, "strategy": "exponential" } } },
    { "id": "draft",   "kind": "lambda", "name": "Generate draft",
      "config": { "functionId": "generate-draft",
                  "inputMapping": { "results": "{{steps.search.output.results}}" } } },
    { "id": "approve", "kind": "humanGate", "name": "Approve publish",
      "config": { "mode": "approve", "promptTemplate": "Publish the draft for {{trigger.body.query}}?" } },
    { "id": "publish", "kind": "lambda", "name": "Publish result", "config": { "functionId": "publish-result" } },
    { "id": "reject",  "kind": "lambda", "name": "Notify rejected", "config": { "functionId": "notify-rejected" } }
  ],
  "edges": [
    { "id": "e1", "sourceNodeId": "in",      "targetNodeId": "search" },
    { "id": "e2", "sourceNodeId": "search",  "targetNodeId": "draft",   "sourceHandle": "success" },
    { "id": "e3", "sourceNodeId": "draft",   "targetNodeId": "approve", "sourceHandle": "success" },
    { "id": "e4", "sourceNodeId": "approve", "targetNodeId": "publish", "sourceHandle": "approve" },
    { "id": "e5", "sourceNodeId": "approve", "targetNodeId": "reject",  "sourceHandle": "reject" }
  ]
}

Когда подходит

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

  • Многошаговые агенты
  • Доступ инструментов к закрытым данным
  • Сценарии агента, которым нужны повторы по шагам или одобрение человеком

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

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

Частые вопросы

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

В продакшене — да: функция на инструмент (или на тесную группу) изолирует зависимости, снижает риск деплоя и делает логи атрибутируемыми — проще, чем монолит, где смешаны пользовательские сессии и ввод-вывод инструментов.

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

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

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

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

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

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

Можно оставить LangGraph или CrewAI для оркестрации?

Да. Оставьте свой фреймворк оркестрации, направьте его вызовы инструментов на маршруты шлюза Inquir, а долгие или повторяемые шаги вынесите в фоновые задачи или пайплайн.

Что происходит, если шаг многошагового пайплайна агента падает?

Шаг повторяется по своей политике повторов; уже выполненные шаги не перезапускаются. В истории выполнения видно, какой шаг упал, сколько было попыток и какой текст ошибки.