ГлавнаяБлогСоздание AI-агента на Hono.js и Cloudflare Workers
Алгоритмы

Создание AI-агента на Hono.js и Cloudflare Workers

Пошаговое руководство по созданию автономного AI-агента на Hono.js и Cloudflare Workers. Научитесь собирать данные, анализировать их и публиковать контент без лишних затрат.

Al
Редакция Algolitalgolit.ru
12 мин чтения19 июля 2026 г.

Зачем читать эту статью?

Вы когда-нибудь хотели создать AI-агента, который работает без вашего участия: собирает данные, анализирует их, пишет посты и публикует? Большинство туториалов останавливаются на простом вызове LLM в цикле, но для реального продакшена нужен надёжный бэкенд. В этой статье я покажу архитектуру, которую использую сам: планировщик находит свежие новости, Claude решает, что стоит опубликовать, и агент публикует контент в соцсетях. Всё это работает на Hono.js и Cloudflare Workers — быстро, дёшево и без постоянного сервера.

Почему Hono.js для бэкенда AI-агентов?

Hono — это лёгкий и быстрый веб-фреймворк, работающий на Cloudflare Workers, Deno, Bun и Node. Для задач AI-агентов он подходит идеально по трём причинам:

  • Cron-триггеры Cloudflare — агенты по расписанию не требуют отдельного планировщика или всегда включённого сервера.
  • Edge-рантайм с почти нулевым холодным стартом — агент просыпается, делает работу и исчезает. Вы платите только за выполнение, а не за простой.
  • Модель middleware — аутентификация, логирование и ограничение запросов для админ-роутов агента идут из коробки.

Архитектура агента

Cron-триггер (Hono на Cloudflare Workers)
   │
   ├── 1. Шаг получения данных → запрос к внешнему API
   ├── 2. Шаг анализа → Claude решает, что стоит публиковать
   ├── 3. Шаг генерации → Claude пишет текст для каждой платформы
   ├── 4. Проверка дубликатов → Postgres/Neon, пропускаем уже опубликованное
   └── 5. Шаг публикации → API соцсетей (или универсальный провайдер)

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

Настройка проекта

npm create hono@latest social-agent
cd social-agent
npm install

Выберите шаблон cloudflare-workers. Затем установите зависимости:

npm install @anthropic-ai/sdk drizzle-orm @neondatabase/serverless

Шаг 1: Cron-триггер

В файле wrangler.toml укажите расписание:

[triggers]
crons = ["0 * * * 1-5"]  # каждый час, только будни

В приложении Hono обрабатывайте событие scheduled отдельно от HTTP-роутов:

import { Hono } from 'hono'

const app = new Hono()

export default {
  fetch: app.fetch,
  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
    ctx.waitUntil(runAgentCycle(env))
  },
}

ctx.waitUntil важен — он говорит рантайму Worker держать экземпляр активным, пока асинхронная работа не завершится, даже если нет HTTP-ответа.

Шаг 2: Получение свежих данных

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

interface RawEvent {
  id: string
  title: string
  payload: Record<string, unknown>
}

async function fetchLatestEvents(env: Env): Promise<RawEvent[]> {
  const res = await fetch('https://api.example.com/events?window=today', {
    headers: {
      Authorization: `Bearer ${env.SOURCE_API_KEY}`
    },
  })
  if (!res.ok) throw new Error(`Ошибка получения данных: ${res.status}`)
  const data = await res.json()
  return data.events
}

Шаг 3: Анализ с помощью Claude

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

import Anthropic from '@anthropic-ai/sdk'

async function triageEvents(events: RawEvent[], env: Env) {
  const anthropic = new Anthropic({ apiKey: env.ANTHROPIC_API_KEY })
  const message = await anthropic.messages.create({
    model: 'claude-sonnet-4-20241022',
    max_tokens: 1024,
    system: 'Ты анализируешь события для соцсетей. Отмечай только те, у которых есть действительно интересный угол — сюрприз, паттерн, выделяющееся число. Верни строгий JSON, без лишнего текста.',
    messages: [
      { role: 'user', content: JSON.stringify(events) },
    ],
  })
  const text = message.content.find(c => c.type === 'text')?.text ?? '[]'
  return JSON.parse(text) as { id: string; angle: string }[]
}

Требование «строгий JSON, без лишнего текста» избавляет от хрупкой очистки регулярками.

Шаг 4: Генерация текста для платформ

LinkedIn и Reddit имеют разные нормы. LinkedIn ценит уверенный аналитический тон, Reddit наказывает за рекламный стиль. Генерируйте оба варианта в одном запросе, но явно укажите различия.

async function draftPosts(item: RawEvent, angle: string, env: Env) {
  const anthropic = new Anthropic({ apiKey: env.ANTHROPIC_API_KEY })
  const message = await anthropic.messages.create({
    model: 'claude-sonnet-4-20241022',
    max_tokens: 800,
    system: `Напиши две версии поста об этом событии.
LinkedIn: 80-150 слов, аналитический тон, одно мягкое упоминание контекста, без спама хештегами.
Reddit: как начало дискуссии, без рекламного языка, заканчивается искренним вопросом.
Верни JSON: { "linkedin": "...", "reddit": "..." }`,
    messages: [
      { role: 'user', content: `Событие: ${item.title}\nУгол: ${angle}\nДанные: ${JSON.stringify(item.payload)}` },
    ],
  })
  const text = message.content.find(c => c.type === 'text')?.text ?? '{}'
  return JSON.parse(text) as { linkedin: string; reddit: string }
}

Шаг 5: Проверка дубликатов с Postgres

Ничто не убивает доверие быстрее, чем повторная публикация из-за пересечения крон-задач. Используйте Neon — серверный драйвер работает через HTTP, что важно для Workers без постоянного TCP-соединения.

import { neon } from '@neondatabase/serverless'

async function alreadyPosted(env: Env, eventId: string) {
  const sql = neon(env.DATABASE_URL)
  const rows = await sql`SELECT 1 FROM posted_events WHERE event_id = ${eventId}`
  return rows.length > 0
}

async function markPosted(env: Env, eventId: string, platform: string, postId: string) {
  const sql = neon(env.DATABASE_URL)
  await sql`
    INSERT INTO posted_events (event_id, platform, post_id, posted_at)
    VALUES (${eventId}, ${platform}, ${postId}, now())
  `
}

Шаг 6: Публикация

У вас есть два варианта:

  1. Нативные API платформ — LinkedIn требует одобренную страницу компании и scope w_member_social, Reddit — своё OAuth-приложение со строгими лимитами. Всё реализуемо, но настройка занимает время.
  2. Универсальный провайдер (например, Ayrshare) — абстрагирует несколько платформ за одним API. Гораздо быстрее для MVP, стоит попробовать, если вы проверяете идею.
async function publish(platform: 'linkedin' | 'reddit', content: string, env: Env) {
  const res = await fetch('https://api.ayrshare.com/api/post', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${env.AYRSHARE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ post: content, platforms: [platform] }),
  })
  if (!res.ok) throw new Error(`Ошибка публикации на ${platform}: ${res.status}`)
  return res.json()
}

Собираем всё вместе

async function runAgentCycle(env: Env) {
  const events = await fetchLatestEvents(env)
  const flagged = await triageEvents(events, env)

  for (const { id, angle } of flagged) {
    if (await alreadyPosted(env, id)) continue

    const source = events.find(e => e.id === id)!
    const { linkedin, reddit } = await draftPosts(source, angle, env)

    const li = await publish('linkedin', linkedin, env)
    await markPosted(env, id, 'linkedin', li.id)

    // Reddit: оставляем на проверку человеку — см. примечание ниже
    await queueForReview(env, id, reddit)
  }
}

Важное ограничение

Автоматизация цепочки «получение → черновик» безопасна. Автоматическая публикация на Reddit — нет, особенно на первых порах. Большинство активных сабреддитов имеют строгие правила против саморекламы, и аккаунт, который постит по расписанию с рекламным оттенком, быстро получает бан. Два решения:

  • Человек в цикле для Reddit. Отправляйте черновик в Slack, email или админ-роут того же Hono-приложения и требуйте ручного подтверждения.
  • Язык описательный, не советующий. Для финансовых тем «выручка превысила прогнозы на X%» — это комментарий, а «вам стоит купить» — уже совет, за который вы несёте ответственность.

LinkedIn более лоялен к регулярным постам, поэтому его можно автоматизировать первым.

Что дальше?

Этот скелет — cron-триггер, получение данных, анализ, генерация, проверка дубликатов, публикация — работает не только для финансовых новостей. Замените источник на GitHub-релизы, обзоры продуктов, CFP конференций или метрики своего сервиса — и получите другого агента с теми же гарантиями надёжности.

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

Практический вывод

Попробуйте прямо сейчас: возьмите этот код, замените источник данных на любой открытый API (например, GitHub Trending или новости CoinDesk) и запустите агента для LinkedIn. Через неделю оцените вовлечённость. Если хотите обсудить, что у вас получилось, напишите в комментариях — мне интересно, какие задачи вы автоматизируете.

#AI-агент#Hono.js#Cloudflare Workers#автоматизация#Claude
Al
Редакция Algolit

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

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →