Пошаговое руководство по созданию автономного AI-агента на Hono.js и Cloudflare Workers. Научитесь собирать данные, анализировать их и публиковать контент без лишних затрат.
Вы когда-нибудь хотели создать AI-агента, который работает без вашего участия: собирает данные, анализирует их, пишет посты и публикует? Большинство туториалов останавливаются на простом вызове LLM в цикле, но для реального продакшена нужен надёжный бэкенд. В этой статье я покажу архитектуру, которую использую сам: планировщик находит свежие новости, Claude решает, что стоит опубликовать, и агент публикует контент в соцсетях. Всё это работает на Hono.js и Cloudflare Workers — быстро, дёшево и без постоянного сервера.
Hono — это лёгкий и быстрый веб-фреймворк, работающий на Cloudflare Workers, Deno, Bun и Node. Для задач AI-агентов он подходит идеально по трём причинам:
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В файле 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-ответа.
Этот шаг должен быть простым. Возвращайте структурированные данные, не принимайте решений.
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
}Этот шаг часто пропускают, а зря. Не генерируйте пост для каждого события — сначала пусть модель отфильтрует важное. Это дешевле и не превращает ленту в спам.
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, без лишнего текста» избавляет от хрупкой очистки регулярками.
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 }
}Ничто не убивает доверие быстрее, чем повторная публикация из-за пересечения крон-задач. Используйте 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())
`
}У вас есть два варианта:
w_member_social, Reddit — своё OAuth-приложение со строгими лимитами. Всё реализуемо, но настройка занимает время.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 — нет, особенно на первых порах. Большинство активных сабреддитов имеют строгие правила против саморекламы, и аккаунт, который постит по расписанию с рекламным оттенком, быстро получает бан. Два решения:
LinkedIn более лоялен к регулярным постам, поэтому его можно автоматизировать первым.
Этот скелет — cron-триггер, получение данных, анализ, генерация, проверка дубликатов, публикация — работает не только для финансовых новостей. Замените источник на GitHub-релизы, обзоры продуктов, CFP конференций или метрики своего сервиса — и получите другого агента с теми же гарантиями надёжности.
Самое важное — правильно настроить шаг анализа. Агент, который постит всё подряд, — просто шум с лишними шагами. Агент, который говорит только когда есть настоящий угол, — тот, за кем следят.
Попробуйте прямо сейчас: возьмите этот код, замените источник данных на любой открытый API (например, GitHub Trending или новости CoinDesk) и запустите агента для LinkedIn. Через неделю оцените вовлечённость. Если хотите обсудить, что у вас получилось, напишите в комментариях — мне интересно, какие задачи вы автоматизируете.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →