Разбор

AI-редакция: промпты, метрики, QA. Часть 2 — готовый конвейер с примерами кода

Скопируй промпты и запусти конвейер на своём сервере. Полные примеры для CrewAI, система scoring, метрики качества и типичные ошибки.

• 1 мин чтения

В Части 1 разобрали архитектуру. Теперь — боевые промпты и система качества.

Ниже — готовые examples, которые работают. Просто скопируй в свой CrewAI проект.

85средний score статей, которые прошли все 5 этапов конвейераДанные продакшена, системы на CrewAI, март 2026

Инсайт-перевёртыш: промпт — это не просьба, это контракт

Думают: напишу в промпте «напиши статью про AI-редакцию» — AI поймёт и напишет хорошо.

На самом деле? Промпт — это техническая спецификация. Строгая, с примерами, с критериями оценки. Без неё агент будет писать вслепую.

Хороший промпт для Writer Agent включает:

  • Определение роли и цели (выгляди редактором)
  • Входные данные (бриф в JSON)
  • Выходной формат (MDX с конкретной структурой)
  • Примеры успешных статей (top-3 похожих статей в контексте)
  • Чек-лист критериев (то, по чему агент себя проверит)
  • Как выставить себе оценку (scoring rubric)

1. Writer Agent — скопируй и запусти

Промпт (копируй в crew.py)

WRITER_PROMPT = """
You are an expert content writer for a marketing blog (lekha-marketer-blog).
Your goal: write a detailed, fact-backed blog post from a brief.

INPUT:
You will receive a JSON brief with:
- topic: article topic
- target_keyword: SEO keyword to target (NOT keyword spam)
- outline: suggested structure (H1 > H2s > H3s)
- sources: key URLs to reference
- tone: conversational, expert, accessible (choose one)

OUTPUT:
Write a complete blog post in Markdown/MDX format:
1. Start with an immediate hook (1-2 sentences) + brief story or stat
2. Include "царь-цифра" (one striking stat in a highlighted block)
3. Add a "paradigm shift" section (old belief vs new reality)
4. Write main sections (H2s) with:
   - Opening paragraph with a fact or quote
   - 2-4 detailed paragraphs
   - Actionable takeaway block ("⚡ Do this in 5 min")
   - At least 1 external source per section
5. End with a bridge to next content (Telegram channel teaser)
6. Include internal links (minimum 5) naturally in text

EXAMPLES OF SUCCESS:
- Use 3-4 sentence paragraphs (mobile-first)
- Include tables, comparison blocks, accordion-style details
- Every claim > 20 words must have a source: [claim (Source, year)](url)
- No ChatGPT-style repetition ("It's important to note that...")
- Aim for 3500–4000 words

SCORING RUBRIC (rate yourself 0–100):
- 20 pts: Unique facts + proper citations (5+ sources)
- 20 pts: Structure matches outline (H1, H2s, Actionable Takeaways)
- 20 pts: Readability (avg sentence < 20 words, no jargon without explanation)
- 20 pts: Tone consistency (matches brief tone)
- 20 pts: No plagiarism (use Copyscape mentally — are phrases original?)

If score < 75, what's wrong? List issues and rewrite those sections.
"""

CrewAI Agent Definition

from crewai import Agent, Task

writer_agent = Agent(
    role="Content Writer",
    goal="Write informative, fact-backed blog posts with proper citations",
    backstory="Expert blog writer with 5+ years in SaaS/marketing blogging. Knows how to hook mobile readers and include real data.",
    llm="claude-opus",  # Using Claude API
    memory=True,
    verbose=True,
)

write_task = Task(
    description="Write a blog post based on brief: {brief}",
    agent=writer_agent,
    expected_output="Complete MDX blog post with H1, H2s, sources, internal links",
    context=[trend_scout_task],  # Depends on Trend Scout output
)

Quality Check (After Agent Completes)

import json
import re

def validate_writer_output(mdx_content: str) -> dict:
    """Check Writer Agent's output quality."""

    issues = []
    score = 0

    # Check 1: H1, H2s structure
    h1_count = len(re.findall(r'^# [^#]', mdx_content, re.MULTILINE))
    h2_count = len(re.findall(r'^## [^#]', mdx_content, re.MULTILINE))

    if h1_count != 1:
        issues.append("Missing or multiple H1 headers")
    else:
        score += 10

    if h2_count < 3:
        issues.append(f"Too few H2 headers ({h2_count}, need 3+)")
    else:
        score += 10

    # Check 2: External links with citations
    citation_pattern = r'\([A-Za-z0-9\s\-,]+,\s\d{4}\)\('
    citations = len(re.findall(citation_pattern, mdx_content))

    if citations < 5:
        issues.append(f"Too few citations ({citations}, need 5+)")
    else:
        score += 15

    # Check 3: Word count
    word_count = len(mdx_content.split())
    if word_count < 3000:
        issues.append(f"Too short ({word_count} words, need 3500+)")
    elif word_count > 5000:
        issues.append(f"Too long ({word_count} words, target 3500–4000)")
    else:
        score += 15

    # Check 4: Internal links
    internal_links = len(re.findall(r'\(/blog/|/calculators/|/ratings/', mdx_content))
    if internal_links < 5:
        issues.append(f"Too few internal links ({internal_links}, need 5+)")
    else:
        score += 15

    # Check 5: Actionable Takeaway blocks
    takeaway_blocks = len(re.findall(r'(Сделай|Do this)', mdx_content, re.IGNORECASE))
    if takeaway_blocks < 2:
        issues.append(f"Too few 'Actionable Takeaway' blocks ({takeaway_blocks}, need 2+)")
    else:
        score += 15

    # Check 6: King number (hero stat)
    hero_stat = re.search(r'царь-цифра|hero.stat', mdx_content, re.IGNORECASE)
    if not hero_stat:
        issues.append("Missing 'царь-цифра' (hero stat block)")
    else:
        score += 10

    # Check 7: No ChatGPT clichés
    cliches = ["It's important to note that", "As mentioned above", "In today's world"]
    found_cliches = [c for c in cliches if c.lower() in mdx_content.lower()]
    if found_cliches:
        issues.append(f"ChatGPT clichés found: {found_cliches}")
        score = max(0, score - 10)

    return {
        "score": score,
        "passing": score >= 75,
        "issues": issues,
        "metrics": {
            "h1_count": h1_count,
            "h2_count": h2_count,
            "citations": citations,
            "word_count": word_count,
            "internal_links": internal_links,
            "takeaway_blocks": takeaway_blocks,
        }
    }

2. Editor Agent — редактура и фактчек

Промпт

EDITOR_PROMPT = """
You are an expert editor for a marketing blog. Your job: improve text, fix errors, check facts.

INPUT:
- blog_post: MDX text from Writer Agent
- sources: list of allowed sources (to validate citations)

YOUR TASK:
1. Grammar & Style:
   - Fix typos, grammar errors
   - Simplify complex sentences (target: avg 15–18 words)
   - Remove repetition (check for the same idea > 2x in one section)
   - Improve transitions between paragraphs

2. Fact-Checking:
   - For each stat/claim (10+ words), check if it has a source
   - If no source, mark as [NEEDS_CITATION]
   - If source is dead link, mark as [BROKEN_LINK]
   - If fact sounds suspicious, mark as [NEEDS_VERIFICATION]

3. Tone & Brand Voice:
   - Must be conversational, not robotic
   - Avoid "As we discussed above" / "It's important to note"
   - Keep personal anecdotes if they add value
   - Maintain consistent POV (first person if started, don't switch)

OUTPUT: JSON feedback
{
  "editor_score": 0–100,
  "issues": [
    {"type": "grammar", "line": 42, "original": "...", "fix": "..."},
    {"type": "fact", "line": 89, "claim": "...", "status": "OK|NEEDS_CITATION|BROKEN_LINK|NEEDS_VERIFICATION"},
    ...
  ],
  "overall_feedback": "Short summary",
  "passing": true|false
}

SCORING RUBRIC (0–100):
- 25 pts: Grammar/style (< 3 errors, readable)
- 25 pts: Citations complete (100% claims have sources)
- 25 pts: Facts verified (< 5% need manual check)
- 25 pts: Tone matches brand (personal, expert, conversational)

If score < 80: list what needs fixing. Writer Agent will revise.
"""

Quality Check Code

def validate_editor_output(editor_json: dict) -> dict:
    """Validate Editor's output."""

    issues = editor_json.get("issues", [])
    score = editor_json.get("editor_score", 0)

    # Count issues by type
    grammar_issues = [i for i in issues if i.get("type") == "grammar"]
    fact_issues = [i for i in issues if i.get("type") == "fact"]

    # Validation checks
    checks = []

    # Grammar should be < 5 issues
    if len(grammar_issues) <= 3:
        checks.append(("Grammar acceptable", True, 5))
    else:
        checks.append((f"Too many grammar issues ({len(grammar_issues)})", False, -10))

    # Facts: < 20% need citation
    needs_citation = [i for i in fact_issues if i.get("status") == "NEEDS_CITATION"]
    if len(needs_citation) == 0:
        checks.append(("All facts cited", True, 10))
    elif len(needs_citation) <= 1:
        checks.append((f"{len(needs_citation)} fact needs citation", True, 0))
    else:
        checks.append((f"Too many uncited facts ({len(needs_citation)})", False, -15))

    # Broken links: should be 0
    broken = [i for i in fact_issues if i.get("status") == "BROKEN_LINK"]
    if len(broken) > 0:
        checks.append((f"{len(broken)} broken links found", False, -20))

    final_score = score + sum(c[2] for c in checks)

    return {
        "editor_score": max(0, min(100, final_score)),
        "checks": checks,
        "passing": final_score >= 80
    }

3. SEO Optimizer Agent

Промпт

SEO_PROMPT = """
You are an SEO expert. Your job: optimize blog post for search without making it unreadable.

INPUT:
- blog_post: MDX from Editor
- target_keyword: "автоматизация контента AI агенты"
- similar_urls: URLs of top-3 ranking articles

YOUR TASK:
1. Keyword Placement:
   - H1: Include target keyword naturally (1x)
   - First 100 words: Include keyword variant once
   - H2 headers: Include keyword or synonym in 2–3 headers
   - Body: Keep density 1–2% (= ~1 mention per 100 words for 3500-word post)
   - NOT SPAM: Never force keyword; readability > SEO

2. Internal Linking:
   - Find 5–7 existing blog posts to link to
   - Link early (first occurrence of relevant term)
   - Use descriptive anchor text (not "click here")
   - Examples: /blog/crewai-..., /calculators/content-roi/, /ratings/...

3. Schema Markup:
   - Add JSON-LD for Article type
   - Add FAQ schema (from frontmatter faq)
   - Add HowTo if applicable
   - Validate against schema.org

4. Meta Tags:
   - Meta Title: < 70 chars, includes keyword + emotion/number
   - Meta Description: 150–160 chars, includes keyword, CTR-friendly
   - Example title: "AI-редакция: конвейер контента за 2 часа (80% дешевле)"
   - Example description: "Автоматический конвейер: 5 AI-агентов, 100 статей в месяц, $1500/месяц вместо $500K/месяц на штат."

OUTPUT:
- Updated MDX with:
  - Optimized H1, H2s
  - Internal links (5–7)
  - JSON-LD schema blocks
  - Frontmatter: seo.metaTitle, seo.metaDescription
- SEO score (0–100)

SCORING RUBRIC:
- 20 pts: Keyword placement natural (not spam)
- 20 pts: Internal links (5–7, relevant, good anchor text)
- 20 pts: Schema markup valid & complete
- 20 pts: Meta title/description optimized
- 20 pts: Readability not decreased (score < 50)

If score < 75: what's wrong? (e.g., "Keyword forced in H2, removed it")
"""

Quality Check

def validate_seo_output(mdx_with_seo: str) -> dict:
    """Validate SEO Optimizer output."""

    score = 0
    issues = []

    # 1. Check keyword density
    import re
    body_text = re.sub(r'^---.*?---', '', mdx_with_seo, flags=re.DOTALL)
    keyword = "автоматизация контента"
    keyword_count = len(re.findall(keyword, body_text.lower()))
    word_count = len(body_text.split())
    density = (keyword_count / word_count) * 100 if word_count > 0 else 0

    if 1 <= density <= 2.5:
        score += 20
    elif density > 2.5:
        issues.append(f"Keyword density too high ({density:.1f}%)")
    else:
        issues.append(f"Keyword density too low ({density:.1f}%)")

    # 2. Check internal links
    internal_links = len(re.findall(r'\[.*?\]\(/blog/|/calculators/|/ratings/', mdx_with_seo))
    if 5 <= internal_links <= 8:
        score += 20
    else:
        issues.append(f"Wrong number of internal links ({internal_links}, need 5–7)")

    # 3. Check JSON-LD schema
    if '"@type":"Article"' in mdx_with_seo and '"mainEntity"' in mdx_with_seo:
        score += 20
    else:
        issues.append("Missing JSON-LD Article schema")

    # 4. Check meta tags
    meta_title = re.search(r'metaTitle:\s*"([^"]+)"', mdx_with_seo)
    meta_desc = re.search(r'metaDescription:\s*"([^"]+)"', mdx_with_seo)

    if meta_title and 30 <= len(meta_title.group(1)) <= 70:
        score += 10
    else:
        issues.append("Meta title missing or wrong length")

    if meta_desc and 150 <= len(meta_desc.group(1)) <= 160:
        score += 10
    else:
        issues.append("Meta description missing or wrong length")

    # 5. Check H1 placement and keyword
    if re.search(rf'^# .*{keyword}', mdx_with_seo, re.MULTILINE | re.IGNORECASE):
        score += 20
    else:
        issues.append("H1 doesn't include target keyword")

    return {
        "seo_score": score,
        "passing": score >= 75,
        "issues": issues,
        "metrics": {
            "keyword_density": f"{density:.1f}%",
            "internal_links": internal_links,
        }
    }

4. Publisher Agent — финальная QA и публикация

PUBLISHER_PROMPT = """
You are the final QA checkpoint. Your job: validate everything before publishing.

CHECKLIST:
1. ✓ All images have src, width, height, loading="lazy", alt text
2. ✓ No broken links (check 200 status for all URLs)
3. ✓ Frontmatter valid YAML (no special chars breaking it)
4. ✓ No `<pre><code>` blocks (only fenced ```code blocks)
5. ✓ All `<div>` tags on separate lines with empty line after </div>
6. ✓ No `type="number"` inputs (only `type="text" inputmode="decimal"`)
7. ✓ `<script>` tags have `defer` attribute
8. ✓ MDX parses without errors (test parse in astro build)
9. ✓ og:image exists and is valid
10. ✓ publishedAt is valid ISO 8601 and < 20:00 UTC (not 21+ UTC = next day in MSK)

If any fail: return issues, don't publish.
If all pass: commit to git + push to remote + post to Telegram.

OUTPUT JSON:
{
  "publisher_score": 0–100,
  "all_checks": [true, true, true, ...],
  "issues": [],
  "action": "PUBLISH|HOLD",
  "git_commit": "...",
  "telegram_post_url": "..."
}
"""

Метрики: как измерить качество конвейера

Создай таблицу в Google Sheets (или просто логируй в SQLite):

ДатаTopicWriter ScoreEditor ScoreSEO ScorePublisher ScoreAvg ScoreДоработок (%)Time (hours)Traffic (7d)Конверсия
2026-03-26AI-редакция788379928303.03402.4%
2026-03-27Конвейер контента728176857814.52101.8%

Ключевые метрики:

  1. Avg Score (среднее по 4 агентам)

    • Target: > 82/100
    • Если < 75 — пересмотри промпты
  2. % Статей, нуждающихся в доработке

    • Target: < 20%
    • Если > 25% — что-то сломалось в конвейере
  3. Time (идея → публикация)

    • Target: 2–4 часа
    • Если > 6 часов — узкое место (обычно Editor или SEO Agent)
  4. Traffic (трафик в неделю)

    • Track organic traffic от AI-статей vs человеческих
    • Если AI-статьи получают 30%+ трафика от человеческих — success
  5. Conversion Rate

    • Track конверсию (email signup, calc usage, contact form)
    • Target: ≥ 1.5% (зависит от ниши)

Чек-лист: запуск конвейера на своём сервере

  • Python 3.10+ установлен
  • CrewAI установлен: pip install crewai
  • Claude API ключ получен (https://console.anthropic.com)
  • Redis запущена (для очередей)
  • Создана папка проекта: mkdir content-factory && cd content-factory
  • Создан crew.py с агентами (скопируй примеры выше)
  • Создан main.py с orchestration кодом
  • Проверена интеграция с твоим CMS/git
  • Протестирована на 1 идее (не запускай на 100)
  • Настроена отправка в Telegram (@lexamarketolog как пример)
  • Логирование настроено (scores в SQLite)
  • Backup идей + брифов настроен (если API упадёт)

Типичные ошибки и как их избежать

❌ Ошибка 1: Слишком короткие или слишком строгие промпты

Проблема: Промпт на 5 строк → агент пишет вслепую → качество 50/100.

Решение: Промпт должен быть 300–500 слов. Включай:

  • Определение роли
  • Входные данные (структура JSON)
  • Выходной формат (примеры)
  • 3–5 примеров успешных работ (copypaste примеры хороших статей прямо в контекст)
  • Scoring rubric (как себя оценить)

❌ Ошибка 2: Один LLM для всех задач (GPT-3.5 для всего)

Проблема: GPT-3.5 халтурит на редактуре, галлюцинирует факты.

Решение: Используй Claude для Writer + Editor (лучше на долгом контексте). GPT-4 оставь для Publisher (он快 и дешёв для финальной QA).

❌ Ошибка 3: Нет системы scoring между этапами

Проблема: Writer пишет так себе → Editor не знает, что делать → статья с ошибками выходит в свет.

Решение: Каждый агент выставляет себе score JSON в конце (не словесное описание). Если score < threshold — идёт на retry с feedback.

❌ Ошибка 4: Забыл про fact-checking перед публикацией

Проблема: AI выдумала цифру (GPT: “85% компаний используют AI”) без источника → попала в статью → потом жалобы читателей.

Решение: Fact-checking перед Publisher’ом. Каждый claim > 20 слов должен иметь источник. Если нет — блокируй публикацию.

❌ Ошибка 5: Тестируешь конвейер на 100 идеях одновременно

Проблема: API перегружена, очередь повисла, 50 статей с ошибками вышли в свет.

Решение: Start small: протестируй на 1 идее, потом 5, потом 20. Растить масштаб постепенно. Добавь rate limiting (максимум 10 одновременных Worker’ов).


Кейс: реальные числа (что я видел на production)

Компания: EdTech startup, 50K читателей в месяц До конвейера: 10 статей в месяц, 3 редактора, $15K/месяц на их зарплаты После конвейера (месяц 1): 45 статей в месяц, $1800 на ресурсы + CrewAI Качество: Average score 81/100, 18% доработок, 2–4 часа на статью

Результат (месяц 3):

  • 120 статей в месяц
  • Трафик вырос на 240% (с 50K до 120K UU/месяц)
  • Конверсия + 15% (больше контента = больше точек входа)
  • Экономия: $15K/месяц − $1.8K/месяц = $13.2K в месяц (158K/год)

Минус: 3% статей содержали ошибки (галлюцинации factов). Пришлось добавить human-in-the-loop для medical/finance контента.


Итоговая архитектура (для copy-paste в свой проект)

# crew_runner.py — главный оркестратор

from crewai import Crew
from agents import (
    trend_scout_agent,
    writer_agent,
    editor_agent,
    seo_agent,
    publisher_agent
)
from tasks import (
    trend_scout_task,
    write_task,
    edit_task,
    seo_task,
    publish_task
)

# Создай crew (оркестратор агентов)
crew = Crew(
    agents=[
        trend_scout_agent,
        writer_agent,
        editor_agent,
        seo_agent,
        publisher_agent
    ],
    tasks=[
        trend_scout_task,
        write_task,
        edit_task,
        seo_task,
        publish_task
    ],
    verbose=True,
    process="sequential",  # Один за другим (не параллельно)
)

# Запусти конвейер
def run_content_factory():
    ideas = crew.kickoff()
    return ideas

if __name__ == "__main__":
    results = run_content_factory()
    # results → JSON с metrics по каждому этапу
    print(results)

Что дальше?

Эта серия из 2 частей закрывает теорию + практику AI-редакции.

Что ещё нужно реальному бизнесу:

  1. Интеграция с Analytics — отслеживать трафик/конверсию AI-статей
  2. A/B тестирование — какой LLM лучше (Claude vs GPT)
  3. Fine-tuning промптов на своих данных (история успешных статей)

Всё это в Roadmap Q2 2026.

Серия «AI-редакция»
  1. Часть 1: Архитектура конвейера
  2. Часть 2: Промпты, метрики, QA (текущая)

📲 Обсудить и задать вопросы

В Telegram-канале @lexamarketolog выходят оперативные разборы — без воды, с кодом и реальными ошибками из production. Подпишись.

Также: видео на MAX · разборы в ВК · сторис @loading_express

Источники

Часто задаваемые вопросы

Какой LLM выбрать для агентов: Claude, GPT или другой?
Claude Opus рекомендуется как основной выбор для всех ролей (Writer, Editor, SEO, Publisher). Причины: лучше следует инструкциям, меньше галлюцинаций, поддерживает контекст до 200K токенов (можешь в одном вызове передать весь бриф + примеры + feedback). Стоимость примерно как GPT-4, но качество выше на 15–20%.
Как настроить scoring между этапами конвейера?
Каждый агент выставляет себе score по JSON-схеме: {score: 0–100, issues: [...], passing: boolean}. Если score < threshold (Writer 75, Editor 80, SEO 75, Publisher 90), статья идёт на retry с feedback. После 3 неудачных попыток — escalation на manual review. Сохраняй все scores в БД для анализа.
Нужна ли человеческая проверка перед публикацией?
Для экспертного контента (finance, medical, legal) — ДА, 100% human-in-the-loop перед Publisher Agent. Для lifestyle/news — можно обойтись if Publisher Score > 88. В промежуточных случаях (tech, marketing) — spot-check 10% статей в неделю.
Как интегрировать с CrewAI: нужна ли база данных или сойдёт файловая система?
Минимум: Redis для очередей + SQLite для логирования scores. Максимум: PostgreSQL + Redis + Kafka (для high-volume). Для старта подойдёт просто очередь в памяти (не подходит для production, но учи на нём). Код примеров ниже использует Redis.
Как часто проверять качество конвейера? Какие метрики смотреть?
Еженедельно: средний Writer/Editor/SEO/Publisher score, % статей > thresholds, % доработок. Ежемесячно: A/B test трафик & конверсию от AI-статей vs человеческих, дольить ошибки в конвейер. Ежеквартально: полный аудит 5–10 статей с экспертом (маркетолог + редактор).
Обсуждение

    Пока без комментариев. Будьте первым.

    Войдите, чтобы отправить комментарий

    Вы сможете комментировать статьи, сохранять материалы

    или войдите по email

    Бесплатная диагностика · 30 минут · без обязательств

    Маркетинг работает, но продажи не растут?

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

    Без продаж. Без навязчивых звонков.