Пошаговое руководство по созданию команды AI-агентов для автоматизации продвижения музыки. От настройки окружения до полного развёртывания 8 агентов и продуктизации в SaaS.
Установка всех инструментов, зависимостей и получение API-ключей
soundwave-ai/ # Агенты agents/ ceo/ # CEO-координатор social/ # Social Media Manager analytics/ # Аналитик content/ # Контент-генератор distribution/ # Дистрибуция playlist/ # Плейлист-менеджер engagement/ # Fan Engagement youtube/ # YouTube Manager # Общие модули core/ tools/ # Shared tools db/ # DB models & migrations config/ # Settings, .env loader api/ # FastAPI endpoint dashboard/ # Next.js frontend docker-compose.yml pyproject.toml .env
# Основные зависимости uv pip install crewai crewai-tools uv pip install langgraph langchain-anthropic uv pip install langchain-community # База данных и кэш uv pip install sqlalchemy asyncpg alembic uv pip install redis aioredis # API и утилиты uv pip install fastapi uvicorn uv pip install httpx python-dotenv pydantic # Социальные сети uv pip install tweepy uv pip install spotipy # Spotify API
# docker-compose.yml (локальная разработка) services: postgres: image: postgres:16-alpine ports: ["5432:5432"] environment: POSTGRES_DB: soundwave POSTGRES_PASSWORD: localdev redis: image: redis:7-alpine ports: ["6379:6379"] qdrant: image: qdrant/qdrant ports: ["6333:6333"]
# === AI === ANTHROPIC_API_KEY=sk-ant-... # === Social Media === TWITTER_API_KEY=... TWITTER_API_SECRET=... TWITTER_ACCESS_TOKEN=... TWITTER_ACCESS_SECRET=... INSTAGRAM_ACCESS_TOKEN=... INSTAGRAM_BUSINESS_ID=... REDDIT_CLIENT_ID=... REDDIT_CLIENT_SECRET=... # === Database === DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/soundwave REDIS_URL=redis://localhost:6379 # === Optional (Month 2) === SPOTIFY_CLIENT_ID=... SPOTIFY_CLIENT_SECRET=... YOUTUBE_API_KEY=... REPLICATE_API_TOKEN=... SENDGRID_API_KEY=... DISCORD_BOT_TOKEN=...
Прототип на CrewAI -- постинг в Twitter с генерацией контента через Claude
# agents/social/agent.py from crewai import Agent, Task, Crew from tools.twitter import twitter_post_tool, twitter_metrics_tool from tools.content import content_generator social_agent = Agent( role="Social Media Manager", goal="Post engaging content about ambient music", backstory="""Expert in music promotion on social media. Knows ambient/electronic music culture deeply. Creates authentic, non-spammy content that resonates with listeners and playlist curators.""", llm="claude-sonnet-4-20250514", tools=[twitter_post_tool, content_generator, twitter_metrics_tool], verbose=True, memory=True, ) # Задача на генерацию и публикацию поста post_task = Task( description="""Generate and post a tweet about the latest track. Track: {track_name} by {artist_name} Genre: {genre} Mood: {mood} Style guidelines: - Keep it authentic, not salesy - Use 1-2 relevant hashtags max - Vary format: quote, behind-the-scenes, release announce - Include a call to listen (link in bio / direct link)""", expected_output="Published tweet URL and engagement prediction", agent=social_agent, ) # Запуск crew = Crew( agents=[social_agent], tasks=[post_task], verbose=True, ) result = crew.kickoff(inputs={ "track_name": "Midnight Signal", "artist_name": "TOPAMB", "genre": "ambient electronic", "mood": "atmospheric, contemplative", })
# core/tools/twitter.py import tweepy from crewai.tools import tool from core.config import settings client = tweepy.Client( consumer_key=settings.TWITTER_API_KEY, consumer_secret=settings.TWITTER_API_SECRET, access_token=settings.TWITTER_ACCESS_TOKEN, access_token_secret=settings.TWITTER_ACCESS_SECRET, ) @tool("Post Tweet") def twitter_post_tool(text: str) -> str: """Post a tweet to Twitter/X. Max 280 characters.""" response = client.create_tweet(text=text) tweet_id = response.data["id"] return f"Posted: https://x.com/i/status/{tweet_id}" @tool("Get Tweet Metrics") def twitter_metrics_tool(tweet_id: str) -> str: """Get engagement metrics for a tweet.""" tweet = client.get_tweet( tweet_id, tweet_fields=["public_metrics"] ) m = tweet.data.public_metrics return f"Likes: {m['like_count']}, " \ f"RT: {m['retweet_count']}, " \ f"Views: {m['impression_count']}"
# core/tools/content.py from crewai.tools import tool from langchain_anthropic import ChatAnthropic llm = ChatAnthropic( model="claude-sonnet-4-20250514", temperature=0.8, ) STYLES = [ "behind_the_scenes", "listening_recommendation", "release_announcement", "mood_quote", "production_insight", ] @tool("Generate Post Content") def content_generator( track_info: str, style: str = "auto" ) -> str: """Generate social media post text for a music track.""" prompt = f"""Write a tweet about: {track_info} Style: {style} Rules: - Max 250 chars (leave room for link) - Authentic voice, not corporate - 1-2 hashtags maximum - No emojis """ response = llm.invoke(prompt) return response.content
# crontab -e
0 */4 * * * cd /app && \
python -m agents.social.run
LangGraph StateGraph для оркестрации всей команды агентов
# agents/ceo/graph.py from typing import TypedDict, Annotated, Literal from langgraph.graph import StateGraph, END from langchain_anthropic import ChatAnthropic from core.db import get_pending_tasks, save_result class TeamState(TypedDict): command: str # Входная команда tasks: list # Список задач для агентов results: dict # Результаты выполнения current_agent: str # Текущий активный агент plan: str # План действий от CEO llm = ChatAnthropic(model="claude-sonnet-4-20250514") def ceo_plan(state: TeamState) -> TeamState: """CEO анализирует команду и создаёт план.""" response = llm.invoke( f"""You are the CEO of a music promotion AI team. Command: {state['command']} Create a plan: which agents need to act, in what order. Available agents: social, analytics, content, distribution, playlist, engagement, youtube Return JSON: {{"tasks": [{{"agent": "...", "action": "..."}}]}}""" ) # Парсим план и создаём задачи state["plan"] = response.content return state def route_to_agent(state: TeamState) -> str: """Определяет следующего агента.""" pending = [t for t in state["tasks"] if t["status"] == "pending"] if not pending: return "summarize" return pending[0]["agent"] def social_node(state): ... # Вызывает Social Agent def analytics_node(state): ... # Вызывает Analytics Agent def summarize_node(state): ... # CEO подводит итоги # Сборка графа graph = StateGraph(TeamState) graph.add_node("ceo_plan", ceo_plan) graph.add_node("social", social_node) graph.add_node("analytics", analytics_node) graph.add_node("summarize", summarize_node) graph.set_entry_point("ceo_plan") graph.add_conditional_edges("ceo_plan", route_to_agent) graph.add_edge("social", "ceo_plan") # Возврат к CEO graph.add_edge("analytics", "ceo_plan") graph.add_edge("summarize", END) app = graph.compile()
# api/main.py from fastapi import FastAPI from agents.ceo.graph import app as ceo_graph api = FastAPI(title="Soundwave AI") @api.post("/command") async def send_command(cmd: str): """Отправить команду CEO-агенту.""" result = await ceo_graph.ainvoke({ "command": cmd, "tasks": [], "results": {}, "current_agent": "", "plan": "", }) return result @api.get("/status") async def get_status(): """Статус всех агентов.""" ...
Сбор данных со всех платформ, хранение в PostgreSQL, генерация отчётов
-- core/db/models.sql CREATE TABLE platform_metrics ( id SERIAL PRIMARY KEY, platform VARCHAR(32), metric VARCHAR(64), value NUMERIC, track_id VARCHAR(128), collected TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_metrics_platform ON platform_metrics(platform, collected); CREATE TABLE daily_summaries ( id SERIAL PRIMARY KEY, date DATE UNIQUE, summary JSONB, created TIMESTAMPTZ DEFAULT NOW() );
Развёртывание всех 8 агентов, миграция на LangGraph, vector memory
PostgreSQL для состояния, Redis для событий, файлы для отчётов
-- Задачи агентов CREATE TABLE agent_tasks ( id UUID DEFAULT gen_random_uuid(), agent_id VARCHAR(32) NOT NULL, task TEXT NOT NULL, status VARCHAR(16) DEFAULT 'pending', -- pending | running | done | failed result JSONB, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- Память агентов (для RAG) CREATE TABLE agent_memory ( id UUID DEFAULT gen_random_uuid(), agent_id VARCHAR(32), type VARCHAR(32), -- observation | decision | feedback content TEXT, embedding VECTOR(1536), created_at TIMESTAMPTZ DEFAULT NOW() ); -- Ежедневные отчёты CREATE TABLE daily_reports ( id SERIAL PRIMARY KEY, date DATE, agent_id VARCHAR(32), report_json JSONB, UNIQUE(date, agent_id) );
# core/events.py import redis.asyncio as redis r = redis.from_url(settings.REDIS_URL) # Каналы событий CHANNELS = { "new_release": # Новый релиз # -> distribution, content, social "task_complete": # Задача выполнена # -> CEO подбирает результат "metrics_ready": # Свежие метрики # -> analytics, CEO "content_ready": # Контент сгенерирован # -> social, youtube } async def publish_event(channel, data): await r.publish(channel, json.dumps(data)) async def subscribe(channels): pubsub = r.pubsub() await pubsub.subscribe(*channels) async for msg in pubsub.listen(): if msg["type"] == "message": yield msg
// daily_reports.report_json example { "agent": "social", "date": "2026-04-04", "actions": [ {"type": "tweet", "url": "...", "likes": 24, "views": 1830}, {"type": "instagram", "reach": 450} ], "metrics": { "total_engagement": 78, "follower_change": +12, "best_post": "tweet_id_xxx" }, "insights": "Behind-the-scenes posts get 2.3x more engagement than announcements.", "tomorrow_plan": "Try production insight style post at 18:00 UTC" }
Каждый агент пишет отчёт в формате JSON в таблицу daily_reports.
CEO-агент собирает все отчёты и формирует сводный daily intel.
Дополнительно -- файловый backup:
# Каждый агент также пишет:
reports/
2026-04-04/
social-DAILY-REPORT.md
analytics-DAILY-REPORT.md
ceo-DAILY-SUMMARY.md
Три варианта хостинга -- от serverless до VPS
import modal app = modal.App("soundwave-ai") @app.function(schedule=modal.Cron("0 */4 * * *")) def social_agent_run(): # Runs every 4 hours ...
MVP: ~$500/mo. Полная система: ~$2,000-3,000/mo
Social + Analytics + CEO
Все агенты + vector DB + tools
Превращение системы в подписочный продукт для других артистов