agent-recipes 0.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_recipes/__init__.py +27 -0
- agent_recipes/recipe_runtime/__init__.py +28 -0
- agent_recipes/recipe_runtime/core.py +385 -0
- agent_recipes/templates/ai-ab-hook-tester/recipe.yaml +45 -0
- agent_recipes/templates/ai-ab-hook-tester/tools.py +169 -0
- agent_recipes/templates/ai-angle-generator/recipe.yaml +49 -0
- agent_recipes/templates/ai-angle-generator/tools.py +182 -0
- agent_recipes/templates/ai-api-doc-generator/README.md +59 -0
- agent_recipes/templates/ai-api-doc-generator/TEMPLATE.yaml +29 -0
- agent_recipes/templates/ai-api-tester/README.md +60 -0
- agent_recipes/templates/ai-api-tester/TEMPLATE.yaml +29 -0
- agent_recipes/templates/ai-audio-enhancer/README.md +59 -0
- agent_recipes/templates/ai-audio-enhancer/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-audio-normalizer/README.md +13 -0
- agent_recipes/templates/ai-audio-normalizer/TEMPLATE.yaml +44 -0
- agent_recipes/templates/ai-audio-splitter/README.md +14 -0
- agent_recipes/templates/ai-audio-splitter/TEMPLATE.yaml +47 -0
- agent_recipes/templates/ai-background-music-generator/README.md +59 -0
- agent_recipes/templates/ai-background-music-generator/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-background-remover/README.md +60 -0
- agent_recipes/templates/ai-background-remover/TEMPLATE.yaml +27 -0
- agent_recipes/templates/ai-barcode-scanner/README.md +60 -0
- agent_recipes/templates/ai-barcode-scanner/TEMPLATE.yaml +26 -0
- agent_recipes/templates/ai-blog-generator/README.md +59 -0
- agent_recipes/templates/ai-blog-generator/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-brief-generator/recipe.yaml +52 -0
- agent_recipes/templates/ai-brief-generator/tools.py +231 -0
- agent_recipes/templates/ai-broll-builder/recipe.yaml +47 -0
- agent_recipes/templates/ai-broll-builder/tools.py +204 -0
- agent_recipes/templates/ai-calendar-scheduler/README.md +60 -0
- agent_recipes/templates/ai-calendar-scheduler/TEMPLATE.yaml +29 -0
- agent_recipes/templates/ai-changelog-generator/README.md +14 -0
- agent_recipes/templates/ai-changelog-generator/TEMPLATE.yaml +46 -0
- agent_recipes/templates/ai-chart-generator/README.md +61 -0
- agent_recipes/templates/ai-chart-generator/TEMPLATE.yaml +32 -0
- agent_recipes/templates/ai-code-documenter/README.md +12 -0
- agent_recipes/templates/ai-code-documenter/TEMPLATE.yaml +37 -0
- agent_recipes/templates/ai-code-refactorer/README.md +59 -0
- agent_recipes/templates/ai-code-refactorer/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-code-reviewer/README.md +59 -0
- agent_recipes/templates/ai-code-reviewer/TEMPLATE.yaml +31 -0
- agent_recipes/templates/ai-color-palette-extractor/README.md +60 -0
- agent_recipes/templates/ai-color-palette-extractor/TEMPLATE.yaml +27 -0
- agent_recipes/templates/ai-comment-miner/recipe.yaml +40 -0
- agent_recipes/templates/ai-comment-miner/tools.py +141 -0
- agent_recipes/templates/ai-commit-message-generator/README.md +59 -0
- agent_recipes/templates/ai-commit-message-generator/TEMPLATE.yaml +31 -0
- agent_recipes/templates/ai-content-calendar/recipe.yaml +43 -0
- agent_recipes/templates/ai-content-calendar/tools.py +170 -0
- agent_recipes/templates/ai-context-enricher/recipe.yaml +48 -0
- agent_recipes/templates/ai-context-enricher/tools.py +258 -0
- agent_recipes/templates/ai-contract-analyzer/README.md +60 -0
- agent_recipes/templates/ai-contract-analyzer/TEMPLATE.yaml +34 -0
- agent_recipes/templates/ai-csv-cleaner/README.md +13 -0
- agent_recipes/templates/ai-csv-cleaner/TEMPLATE.yaml +45 -0
- agent_recipes/templates/ai-cta-generator/recipe.yaml +54 -0
- agent_recipes/templates/ai-cta-generator/tools.py +174 -0
- agent_recipes/templates/ai-daily-news-show/recipe.yaml +103 -0
- agent_recipes/templates/ai-daily-news-show/tools.py +308 -0
- agent_recipes/templates/ai-data-anonymizer/README.md +60 -0
- agent_recipes/templates/ai-data-anonymizer/TEMPLATE.yaml +31 -0
- agent_recipes/templates/ai-data-profiler/README.md +14 -0
- agent_recipes/templates/ai-data-profiler/TEMPLATE.yaml +42 -0
- agent_recipes/templates/ai-dependency-auditor/README.md +12 -0
- agent_recipes/templates/ai-dependency-auditor/TEMPLATE.yaml +37 -0
- agent_recipes/templates/ai-doc-translator/README.md +12 -0
- agent_recipes/templates/ai-doc-translator/TEMPLATE.yaml +41 -0
- agent_recipes/templates/ai-duplicate-finder/README.md +59 -0
- agent_recipes/templates/ai-duplicate-finder/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-ebook-converter/README.md +60 -0
- agent_recipes/templates/ai-ebook-converter/TEMPLATE.yaml +27 -0
- agent_recipes/templates/ai-email-parser/README.md +59 -0
- agent_recipes/templates/ai-email-parser/TEMPLATE.yaml +29 -0
- agent_recipes/templates/ai-etl-pipeline/README.md +60 -0
- agent_recipes/templates/ai-etl-pipeline/TEMPLATE.yaml +30 -0
- agent_recipes/templates/ai-excel-formula-generator/README.md +59 -0
- agent_recipes/templates/ai-excel-formula-generator/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-face-blur/README.md +60 -0
- agent_recipes/templates/ai-face-blur/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-fact-checker/recipe.yaml +52 -0
- agent_recipes/templates/ai-fact-checker/tools.py +279 -0
- agent_recipes/templates/ai-faq-generator/README.md +59 -0
- agent_recipes/templates/ai-faq-generator/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-file-organizer/README.md +59 -0
- agent_recipes/templates/ai-file-organizer/TEMPLATE.yaml +29 -0
- agent_recipes/templates/ai-folder-packager/README.md +15 -0
- agent_recipes/templates/ai-folder-packager/TEMPLATE.yaml +48 -0
- agent_recipes/templates/ai-form-filler/README.md +60 -0
- agent_recipes/templates/ai-form-filler/TEMPLATE.yaml +30 -0
- agent_recipes/templates/ai-hashtag-optimizer/recipe.yaml +45 -0
- agent_recipes/templates/ai-hashtag-optimizer/tools.py +134 -0
- agent_recipes/templates/ai-hook-generator/recipe.yaml +50 -0
- agent_recipes/templates/ai-hook-generator/tools.py +177 -0
- agent_recipes/templates/ai-image-captioner/README.md +59 -0
- agent_recipes/templates/ai-image-captioner/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-image-cataloger/README.md +13 -0
- agent_recipes/templates/ai-image-cataloger/TEMPLATE.yaml +39 -0
- agent_recipes/templates/ai-image-optimizer/README.md +13 -0
- agent_recipes/templates/ai-image-optimizer/TEMPLATE.yaml +43 -0
- agent_recipes/templates/ai-image-resizer/README.md +12 -0
- agent_recipes/templates/ai-image-resizer/TEMPLATE.yaml +39 -0
- agent_recipes/templates/ai-image-tagger/README.md +59 -0
- agent_recipes/templates/ai-image-tagger/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-image-upscaler/README.md +60 -0
- agent_recipes/templates/ai-image-upscaler/TEMPLATE.yaml +27 -0
- agent_recipes/templates/ai-invoice-processor/README.md +60 -0
- agent_recipes/templates/ai-invoice-processor/TEMPLATE.yaml +34 -0
- agent_recipes/templates/ai-json-to-csv/README.md +12 -0
- agent_recipes/templates/ai-json-to-csv/TEMPLATE.yaml +36 -0
- agent_recipes/templates/ai-log-analyzer/README.md +59 -0
- agent_recipes/templates/ai-log-analyzer/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-markdown-to-pdf/README.md +12 -0
- agent_recipes/templates/ai-markdown-to-pdf/TEMPLATE.yaml +40 -0
- agent_recipes/templates/ai-meeting-summarizer/README.md +59 -0
- agent_recipes/templates/ai-meeting-summarizer/TEMPLATE.yaml +32 -0
- agent_recipes/templates/ai-meta-tag-generator/README.md +59 -0
- agent_recipes/templates/ai-meta-tag-generator/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-news-capture-pack/recipe.yaml +42 -0
- agent_recipes/templates/ai-news-capture-pack/tools.py +150 -0
- agent_recipes/templates/ai-news-crawler/recipe.yaml +99 -0
- agent_recipes/templates/ai-news-crawler/tools.py +417 -0
- agent_recipes/templates/ai-news-deduper/recipe.yaml +47 -0
- agent_recipes/templates/ai-news-deduper/tools.py +235 -0
- agent_recipes/templates/ai-newsletter-generator/README.md +59 -0
- agent_recipes/templates/ai-newsletter-generator/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-note-summarizer/README.md +59 -0
- agent_recipes/templates/ai-note-summarizer/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-pdf-summarizer/README.md +12 -0
- agent_recipes/templates/ai-pdf-summarizer/TEMPLATE.yaml +40 -0
- agent_recipes/templates/ai-pdf-to-markdown/README.md +19 -0
- agent_recipes/templates/ai-pdf-to-markdown/TEMPLATE.yaml +63 -0
- agent_recipes/templates/ai-performance-analyzer/recipe.yaml +45 -0
- agent_recipes/templates/ai-performance-analyzer/tools.py +159 -0
- agent_recipes/templates/ai-podcast-cleaner/README.md +117 -0
- agent_recipes/templates/ai-podcast-cleaner/TEMPLATE.yaml +117 -0
- agent_recipes/templates/ai-podcast-cleaner/agents.yaml +59 -0
- agent_recipes/templates/ai-podcast-cleaner/workflow.yaml +77 -0
- agent_recipes/templates/ai-podcast-transcriber/README.md +59 -0
- agent_recipes/templates/ai-podcast-transcriber/TEMPLATE.yaml +32 -0
- agent_recipes/templates/ai-post-copy-generator/recipe.yaml +41 -0
- agent_recipes/templates/ai-post-copy-generator/tools.py +105 -0
- agent_recipes/templates/ai-product-description-generator/README.md +59 -0
- agent_recipes/templates/ai-product-description-generator/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-publisher-pack/recipe.yaml +44 -0
- agent_recipes/templates/ai-publisher-pack/tools.py +252 -0
- agent_recipes/templates/ai-qr-code-generator/README.md +60 -0
- agent_recipes/templates/ai-qr-code-generator/TEMPLATE.yaml +26 -0
- agent_recipes/templates/ai-regex-generator/README.md +59 -0
- agent_recipes/templates/ai-regex-generator/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-repo-readme/README.md +13 -0
- agent_recipes/templates/ai-repo-readme/TEMPLATE.yaml +42 -0
- agent_recipes/templates/ai-report-generator/README.md +61 -0
- agent_recipes/templates/ai-report-generator/TEMPLATE.yaml +32 -0
- agent_recipes/templates/ai-resume-parser/README.md +60 -0
- agent_recipes/templates/ai-resume-parser/TEMPLATE.yaml +33 -0
- agent_recipes/templates/ai-rss-aggregator/README.md +60 -0
- agent_recipes/templates/ai-rss-aggregator/TEMPLATE.yaml +30 -0
- agent_recipes/templates/ai-schema-generator/README.md +12 -0
- agent_recipes/templates/ai-schema-generator/TEMPLATE.yaml +34 -0
- agent_recipes/templates/ai-screen-recorder/recipe.yaml +43 -0
- agent_recipes/templates/ai-screen-recorder/tools.py +184 -0
- agent_recipes/templates/ai-screenshot-capture/recipe.yaml +45 -0
- agent_recipes/templates/ai-screenshot-capture/tools.py +231 -0
- agent_recipes/templates/ai-screenshot-ocr/README.md +12 -0
- agent_recipes/templates/ai-screenshot-ocr/TEMPLATE.yaml +37 -0
- agent_recipes/templates/ai-script-writer/recipe.yaml +58 -0
- agent_recipes/templates/ai-script-writer/tools.py +297 -0
- agent_recipes/templates/ai-sentiment-analyzer/README.md +59 -0
- agent_recipes/templates/ai-sentiment-analyzer/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-seo-optimizer/README.md +59 -0
- agent_recipes/templates/ai-seo-optimizer/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-signal-ranker/recipe.yaml +54 -0
- agent_recipes/templates/ai-signal-ranker/tools.py +256 -0
- agent_recipes/templates/ai-sitemap-generator/README.md +59 -0
- agent_recipes/templates/ai-sitemap-generator/TEMPLATE.yaml +26 -0
- agent_recipes/templates/ai-sitemap-scraper/README.md +13 -0
- agent_recipes/templates/ai-sitemap-scraper/TEMPLATE.yaml +41 -0
- agent_recipes/templates/ai-slide-generator/README.md +60 -0
- agent_recipes/templates/ai-slide-generator/TEMPLATE.yaml +29 -0
- agent_recipes/templates/ai-slide-to-notes/README.md +12 -0
- agent_recipes/templates/ai-slide-to-notes/TEMPLATE.yaml +37 -0
- agent_recipes/templates/ai-social-media-generator/README.md +59 -0
- agent_recipes/templates/ai-social-media-generator/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-sql-generator/README.md +59 -0
- agent_recipes/templates/ai-sql-generator/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-subtitle-generator/README.md +59 -0
- agent_recipes/templates/ai-subtitle-generator/TEMPLATE.yaml +31 -0
- agent_recipes/templates/ai-test-generator/README.md +59 -0
- agent_recipes/templates/ai-test-generator/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-translation-batch/README.md +59 -0
- agent_recipes/templates/ai-translation-batch/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-url-to-markdown/README.md +14 -0
- agent_recipes/templates/ai-url-to-markdown/TEMPLATE.yaml +44 -0
- agent_recipes/templates/ai-video-chapter-generator/README.md +59 -0
- agent_recipes/templates/ai-video-chapter-generator/TEMPLATE.yaml +32 -0
- agent_recipes/templates/ai-video-compressor/README.md +59 -0
- agent_recipes/templates/ai-video-compressor/TEMPLATE.yaml +28 -0
- agent_recipes/templates/ai-video-editor/README.md +254 -0
- agent_recipes/templates/ai-video-editor/TEMPLATE.yaml +139 -0
- agent_recipes/templates/ai-video-editor/agents.yaml +36 -0
- agent_recipes/templates/ai-video-editor/requirements.txt +8 -0
- agent_recipes/templates/ai-video-editor/scripts/run.sh +10 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/__init__.py +45 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/__main__.py +8 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/__init__.cpython-312.pyc +0 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/cli.cpython-312.pyc +0 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/config.cpython-312.pyc +0 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/ffmpeg_probe.cpython-312.pyc +0 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/heuristics.cpython-312.pyc +0 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/llm_plan.cpython-312.pyc +0 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/models.cpython-312.pyc +0 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/pipeline.cpython-312.pyc +0 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/render.cpython-312.pyc +0 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/timeline.cpython-312.pyc +0 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/transcribe.cpython-312.pyc +0 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/__pycache__/utils.cpython-312.pyc +0 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/cli.py +343 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/config.py +102 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/ffmpeg_probe.py +92 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/heuristics.py +119 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/llm_plan.py +277 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/models.py +343 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/pipeline.py +287 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/render.py +274 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/timeline.py +278 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/transcribe.py +233 -0
- agent_recipes/templates/ai-video-editor/src/ai_video_editor/utils.py +222 -0
- agent_recipes/templates/ai-video-editor/src/input.mov +0 -0
- agent_recipes/templates/ai-video-editor/src/out.mp4 +0 -0
- agent_recipes/templates/ai-video-editor/tests/test_heuristics.py +130 -0
- agent_recipes/templates/ai-video-editor/tests/test_models.py +152 -0
- agent_recipes/templates/ai-video-editor/tests/test_timeline.py +105 -0
- agent_recipes/templates/ai-video-editor/workflow.yaml +51 -0
- agent_recipes/templates/ai-video-highlight-extractor/README.md +60 -0
- agent_recipes/templates/ai-video-highlight-extractor/TEMPLATE.yaml +33 -0
- agent_recipes/templates/ai-video-merger/recipe.yaml +40 -0
- agent_recipes/templates/ai-video-merger/tools.py +172 -0
- agent_recipes/templates/ai-video-thumbnails/README.md +16 -0
- agent_recipes/templates/ai-video-thumbnails/TEMPLATE.yaml +53 -0
- agent_recipes/templates/ai-video-to-gif/README.md +14 -0
- agent_recipes/templates/ai-video-to-gif/TEMPLATE.yaml +64 -0
- agent_recipes/templates/ai-voice-cloner/README.md +59 -0
- agent_recipes/templates/ai-voice-cloner/TEMPLATE.yaml +31 -0
- agent_recipes/templates/ai-voiceover-generator/recipe.yaml +41 -0
- agent_recipes/templates/ai-voiceover-generator/tools.py +194 -0
- agent_recipes/templates/ai-watermark-adder/README.md +59 -0
- agent_recipes/templates/ai-watermark-adder/TEMPLATE.yaml +26 -0
- agent_recipes/templates/ai-watermark-remover/README.md +60 -0
- agent_recipes/templates/ai-watermark-remover/TEMPLATE.yaml +32 -0
- agent_recipes/templates/data-transformer/README.md +75 -0
- agent_recipes/templates/data-transformer/TEMPLATE.yaml +63 -0
- agent_recipes/templates/data-transformer/agents.yaml +70 -0
- agent_recipes/templates/data-transformer/workflow.yaml +92 -0
- agent_recipes/templates/shorts-generator/README.md +61 -0
- agent_recipes/templates/shorts-generator/TEMPLATE.yaml +65 -0
- agent_recipes/templates/shorts-generator/agents.yaml +66 -0
- agent_recipes/templates/shorts-generator/workflow.yaml +86 -0
- agent_recipes/templates/transcript-generator/README.md +103 -0
- agent_recipes/templates/transcript-generator/TEMPLATE.yaml +57 -0
- agent_recipes/templates/transcript-generator/agents.yaml +62 -0
- agent_recipes/templates/transcript-generator/workflow.yaml +82 -0
- agent_recipes/templates/video-editor/README.md +70 -0
- agent_recipes/templates/video-editor/TEMPLATE.yaml +55 -0
- agent_recipes/templates/video-editor/agents.yaml +68 -0
- agent_recipes/templates/video-editor/workflow.yaml +92 -0
- agent_recipes-0.0.5.dist-info/METADATA +145 -0
- agent_recipes-0.0.5.dist-info/RECORD +269 -0
- agent_recipes-0.0.5.dist-info/WHEEL +5 -0
- agent_recipes-0.0.5.dist-info/top_level.txt +1 -0
- /236/326/177nE/243/231/214/232/265/322m/201/253/353/022C/372/321/266/b/225^=/272/017t/262/3337/310@/315wb/341pB/277z/216/330/314/004/265B/213/375/236/203/026/373/307/354z41/347#/374q/262/22589/032/276 /277/244Vh/322/017/004/224/215/004/367/377/375/335/n +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Recipes - Real-world AI agent templates for PraisonAI
|
|
3
|
+
|
|
4
|
+
This package provides ready-to-use templates for common AI agent workflows.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
__all__ = ["get_template_path", "list_templates"]
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_template_path(template_name: str) -> Path:
|
|
14
|
+
"""Get the path to a template directory."""
|
|
15
|
+
templates_dir = Path(__file__).parent / "templates"
|
|
16
|
+
template_path = templates_dir / template_name
|
|
17
|
+
if not template_path.exists():
|
|
18
|
+
raise ValueError(f"Template not found: {template_name}")
|
|
19
|
+
return template_path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def list_templates() -> list:
|
|
23
|
+
"""List all available templates."""
|
|
24
|
+
templates_dir = Path(__file__).parent / "templates"
|
|
25
|
+
if not templates_dir.exists():
|
|
26
|
+
return []
|
|
27
|
+
return [d.name for d in templates_dir.iterdir() if d.is_dir() and not d.name.startswith(".")]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipe Runtime - Shared utilities for all Agent-Recipes.
|
|
3
|
+
|
|
4
|
+
Provides:
|
|
5
|
+
- Output directory management
|
|
6
|
+
- run.json generation
|
|
7
|
+
- Logging setup
|
|
8
|
+
- Dry-run support
|
|
9
|
+
- Safety defaults
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .core import (
|
|
13
|
+
RecipeRunner,
|
|
14
|
+
RecipeConfig,
|
|
15
|
+
RecipeResult,
|
|
16
|
+
create_output_dir,
|
|
17
|
+
write_run_json,
|
|
18
|
+
setup_logging,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"RecipeRunner",
|
|
23
|
+
"RecipeConfig",
|
|
24
|
+
"RecipeResult",
|
|
25
|
+
"create_output_dir",
|
|
26
|
+
"write_run_json",
|
|
27
|
+
"setup_logging",
|
|
28
|
+
]
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core recipe runtime utilities.
|
|
3
|
+
|
|
4
|
+
Provides the foundation for all recipe execution including:
|
|
5
|
+
- Output directory management with timestamps
|
|
6
|
+
- run.json generation and validation
|
|
7
|
+
- Logging setup
|
|
8
|
+
- Dry-run support
|
|
9
|
+
- Safety defaults (no overwrites, resource limits)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import hashlib
|
|
13
|
+
import json
|
|
14
|
+
import logging
|
|
15
|
+
import time
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Dict, List, Optional, Union
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class RecipeConfig:
|
|
26
|
+
"""Configuration for a recipe run."""
|
|
27
|
+
recipe_name: str
|
|
28
|
+
input_path: str
|
|
29
|
+
output_dir: Optional[str] = None
|
|
30
|
+
preset: Optional[str] = None
|
|
31
|
+
dry_run: bool = False
|
|
32
|
+
force: bool = False
|
|
33
|
+
verbose: bool = False
|
|
34
|
+
config_file: Optional[str] = None
|
|
35
|
+
extra: Dict[str, Any] = field(default_factory=dict)
|
|
36
|
+
|
|
37
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
38
|
+
"""Convert to dictionary for run.json."""
|
|
39
|
+
return {
|
|
40
|
+
"recipe_name": self.recipe_name,
|
|
41
|
+
"input_path": self.input_path,
|
|
42
|
+
"output_dir": self.output_dir,
|
|
43
|
+
"preset": self.preset,
|
|
44
|
+
"dry_run": self.dry_run,
|
|
45
|
+
"force": self.force,
|
|
46
|
+
"verbose": self.verbose,
|
|
47
|
+
**self.extra,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class OutputFile:
|
|
53
|
+
"""Information about an output file."""
|
|
54
|
+
name: str
|
|
55
|
+
path: str
|
|
56
|
+
size: int
|
|
57
|
+
sha256: Optional[str] = None
|
|
58
|
+
|
|
59
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
60
|
+
return {
|
|
61
|
+
"name": self.name,
|
|
62
|
+
"path": self.path,
|
|
63
|
+
"size": self.size,
|
|
64
|
+
"sha256": self.sha256,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class RecipeResult:
|
|
70
|
+
"""Result of a recipe execution."""
|
|
71
|
+
recipe: str
|
|
72
|
+
version: str
|
|
73
|
+
status: str # success, failed, dry_run
|
|
74
|
+
started_at: str
|
|
75
|
+
completed_at: Optional[str] = None
|
|
76
|
+
input_path: str = ""
|
|
77
|
+
output_dir: str = ""
|
|
78
|
+
config: Dict[str, Any] = field(default_factory=dict)
|
|
79
|
+
outputs: List[OutputFile] = field(default_factory=list)
|
|
80
|
+
metrics: Dict[str, Any] = field(default_factory=dict)
|
|
81
|
+
logs: str = ""
|
|
82
|
+
error: Optional[str] = None
|
|
83
|
+
|
|
84
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
85
|
+
"""Convert to dictionary for run.json."""
|
|
86
|
+
return {
|
|
87
|
+
"recipe": self.recipe,
|
|
88
|
+
"version": self.version,
|
|
89
|
+
"started_at": self.started_at,
|
|
90
|
+
"completed_at": self.completed_at,
|
|
91
|
+
"status": self.status,
|
|
92
|
+
"input": self.input_path,
|
|
93
|
+
"output_dir": self.output_dir,
|
|
94
|
+
"config": self.config,
|
|
95
|
+
"outputs": [o.to_dict() for o in self.outputs],
|
|
96
|
+
"metrics": self.metrics,
|
|
97
|
+
"logs": self.logs,
|
|
98
|
+
"error": self.error,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def get_timestamp() -> str:
|
|
103
|
+
"""Get current timestamp in ISO 8601 format."""
|
|
104
|
+
return datetime.now(timezone.utc).isoformat()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def get_timestamp_dir() -> str:
|
|
108
|
+
"""Get timestamp string for directory naming."""
|
|
109
|
+
return datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def create_output_dir(
|
|
113
|
+
recipe_name: str,
|
|
114
|
+
base_dir: Optional[Union[str, Path]] = None,
|
|
115
|
+
force: bool = False,
|
|
116
|
+
) -> Path:
|
|
117
|
+
"""
|
|
118
|
+
Create output directory for a recipe run.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
recipe_name: Name of the recipe
|
|
122
|
+
base_dir: Base output directory (default: ./outputs)
|
|
123
|
+
force: Allow overwriting existing directory
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Path to created output directory
|
|
127
|
+
"""
|
|
128
|
+
if base_dir is None:
|
|
129
|
+
base_dir = Path("./outputs")
|
|
130
|
+
else:
|
|
131
|
+
base_dir = Path(base_dir)
|
|
132
|
+
|
|
133
|
+
# Create timestamped directory
|
|
134
|
+
timestamp = get_timestamp_dir()
|
|
135
|
+
output_dir = base_dir / recipe_name / timestamp
|
|
136
|
+
|
|
137
|
+
if output_dir.exists() and not force:
|
|
138
|
+
raise FileExistsError(
|
|
139
|
+
f"Output directory already exists: {output_dir}. "
|
|
140
|
+
"Use --force to overwrite."
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
144
|
+
return output_dir
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def calculate_sha256(path: Path) -> str:
|
|
148
|
+
"""Calculate SHA256 hash of a file."""
|
|
149
|
+
sha256 = hashlib.sha256()
|
|
150
|
+
with open(path, "rb") as f:
|
|
151
|
+
for chunk in iter(lambda: f.read(8192), b""):
|
|
152
|
+
sha256.update(chunk)
|
|
153
|
+
return sha256.hexdigest()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def collect_outputs(output_dir: Path, include_hash: bool = True) -> List[OutputFile]:
|
|
157
|
+
"""
|
|
158
|
+
Collect information about output files.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
output_dir: Output directory to scan
|
|
162
|
+
include_hash: Calculate SHA256 hashes
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
List of OutputFile objects
|
|
166
|
+
"""
|
|
167
|
+
outputs = []
|
|
168
|
+
|
|
169
|
+
for path in output_dir.rglob("*"):
|
|
170
|
+
if path.is_file() and path.name != "run.json" and path.name != "run.log":
|
|
171
|
+
rel_path = path.relative_to(output_dir)
|
|
172
|
+
sha256 = calculate_sha256(path) if include_hash else None
|
|
173
|
+
|
|
174
|
+
outputs.append(OutputFile(
|
|
175
|
+
name=path.name,
|
|
176
|
+
path=str(rel_path),
|
|
177
|
+
size=path.stat().st_size,
|
|
178
|
+
sha256=sha256,
|
|
179
|
+
))
|
|
180
|
+
|
|
181
|
+
return outputs
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def write_run_json(
|
|
185
|
+
output_dir: Path,
|
|
186
|
+
result: RecipeResult,
|
|
187
|
+
) -> Path:
|
|
188
|
+
"""
|
|
189
|
+
Write run.json to output directory.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
output_dir: Output directory
|
|
193
|
+
result: Recipe result
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Path to run.json
|
|
197
|
+
"""
|
|
198
|
+
run_json_path = output_dir / "run.json"
|
|
199
|
+
|
|
200
|
+
with open(run_json_path, "w", encoding="utf-8") as f:
|
|
201
|
+
json.dump(result.to_dict(), f, indent=2)
|
|
202
|
+
|
|
203
|
+
return run_json_path
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def setup_logging(
|
|
207
|
+
output_dir: Path,
|
|
208
|
+
verbose: bool = False,
|
|
209
|
+
) -> Path:
|
|
210
|
+
"""
|
|
211
|
+
Setup logging for a recipe run.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
output_dir: Output directory for log file
|
|
215
|
+
verbose: Enable verbose logging
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Path to log file
|
|
219
|
+
"""
|
|
220
|
+
log_path = output_dir / "run.log"
|
|
221
|
+
|
|
222
|
+
# Configure logging
|
|
223
|
+
level = logging.DEBUG if verbose else logging.INFO
|
|
224
|
+
|
|
225
|
+
# Create file handler
|
|
226
|
+
file_handler = logging.FileHandler(log_path)
|
|
227
|
+
file_handler.setLevel(level)
|
|
228
|
+
file_handler.setFormatter(logging.Formatter(
|
|
229
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
230
|
+
))
|
|
231
|
+
|
|
232
|
+
# Add to root logger
|
|
233
|
+
root_logger = logging.getLogger()
|
|
234
|
+
root_logger.addHandler(file_handler)
|
|
235
|
+
|
|
236
|
+
if verbose:
|
|
237
|
+
root_logger.setLevel(logging.DEBUG)
|
|
238
|
+
|
|
239
|
+
return log_path
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class RecipeRunner:
|
|
243
|
+
"""
|
|
244
|
+
Base class for running recipes with standard safety defaults.
|
|
245
|
+
|
|
246
|
+
Handles:
|
|
247
|
+
- Output directory creation
|
|
248
|
+
- run.json generation
|
|
249
|
+
- Logging
|
|
250
|
+
- Dry-run mode
|
|
251
|
+
- Error handling
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
def __init__(
|
|
255
|
+
self,
|
|
256
|
+
recipe_name: str,
|
|
257
|
+
version: str = "1.0.0",
|
|
258
|
+
):
|
|
259
|
+
self.recipe_name = recipe_name
|
|
260
|
+
self.version = version
|
|
261
|
+
self._start_time: Optional[float] = None
|
|
262
|
+
self._result: Optional[RecipeResult] = None
|
|
263
|
+
self._output_dir: Optional[Path] = None
|
|
264
|
+
self._log_path: Optional[Path] = None
|
|
265
|
+
|
|
266
|
+
def run(self, config: RecipeConfig) -> RecipeResult:
|
|
267
|
+
"""
|
|
268
|
+
Execute the recipe with the given configuration.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
config: Recipe configuration
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
RecipeResult with execution details
|
|
275
|
+
"""
|
|
276
|
+
self._start_time = time.time()
|
|
277
|
+
started_at = get_timestamp()
|
|
278
|
+
|
|
279
|
+
# Initialize result
|
|
280
|
+
self._result = RecipeResult(
|
|
281
|
+
recipe=self.recipe_name,
|
|
282
|
+
version=self.version,
|
|
283
|
+
status="running",
|
|
284
|
+
started_at=started_at,
|
|
285
|
+
input_path=config.input_path,
|
|
286
|
+
config=config.to_dict(),
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
# Validate input
|
|
291
|
+
self._validate_input(config)
|
|
292
|
+
|
|
293
|
+
# Create output directory
|
|
294
|
+
if config.output_dir:
|
|
295
|
+
self._output_dir = Path(config.output_dir)
|
|
296
|
+
self._output_dir.mkdir(parents=True, exist_ok=config.force)
|
|
297
|
+
else:
|
|
298
|
+
self._output_dir = create_output_dir(
|
|
299
|
+
self.recipe_name,
|
|
300
|
+
force=config.force,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
self._result.output_dir = str(self._output_dir)
|
|
304
|
+
|
|
305
|
+
# Setup logging
|
|
306
|
+
self._log_path = setup_logging(self._output_dir, config.verbose)
|
|
307
|
+
self._result.logs = str(self._log_path)
|
|
308
|
+
|
|
309
|
+
logger.info(f"Starting recipe: {self.recipe_name}")
|
|
310
|
+
logger.info(f"Input: {config.input_path}")
|
|
311
|
+
logger.info(f"Output: {self._output_dir}")
|
|
312
|
+
|
|
313
|
+
if config.dry_run:
|
|
314
|
+
# Dry run - just plan, don't execute
|
|
315
|
+
self._result.status = "dry_run"
|
|
316
|
+
logger.info("DRY RUN - no files will be created")
|
|
317
|
+
self._plan(config)
|
|
318
|
+
else:
|
|
319
|
+
# Execute the recipe
|
|
320
|
+
self._execute(config)
|
|
321
|
+
|
|
322
|
+
# Collect outputs
|
|
323
|
+
self._result.outputs = collect_outputs(self._output_dir)
|
|
324
|
+
self._result.status = "success"
|
|
325
|
+
|
|
326
|
+
except Exception as e:
|
|
327
|
+
self._result.status = "failed"
|
|
328
|
+
self._result.error = str(e)
|
|
329
|
+
logger.exception(f"Recipe failed: {e}")
|
|
330
|
+
raise
|
|
331
|
+
|
|
332
|
+
finally:
|
|
333
|
+
# Finalize result
|
|
334
|
+
self._result.completed_at = get_timestamp()
|
|
335
|
+
|
|
336
|
+
# Calculate metrics
|
|
337
|
+
duration = time.time() - self._start_time
|
|
338
|
+
self._result.metrics = {
|
|
339
|
+
"duration_sec": round(duration, 2),
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
# Write run.json
|
|
343
|
+
if self._output_dir:
|
|
344
|
+
write_run_json(self._output_dir, self._result)
|
|
345
|
+
|
|
346
|
+
return self._result
|
|
347
|
+
|
|
348
|
+
def _validate_input(self, config: RecipeConfig) -> None:
|
|
349
|
+
"""Validate input path exists."""
|
|
350
|
+
input_path = Path(config.input_path)
|
|
351
|
+
|
|
352
|
+
# Allow URLs
|
|
353
|
+
if config.input_path.startswith(("http://", "https://")):
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
if not input_path.exists():
|
|
357
|
+
raise FileNotFoundError(f"Input not found: {config.input_path}")
|
|
358
|
+
|
|
359
|
+
def _plan(self, config: RecipeConfig) -> None:
|
|
360
|
+
"""
|
|
361
|
+
Plan the recipe execution (for dry-run).
|
|
362
|
+
|
|
363
|
+
Override in subclasses to provide planning output.
|
|
364
|
+
"""
|
|
365
|
+
logger.info("Planning recipe execution...")
|
|
366
|
+
logger.info(f"Would process: {config.input_path}")
|
|
367
|
+
logger.info(f"Would output to: {self._output_dir}")
|
|
368
|
+
|
|
369
|
+
def _execute(self, config: RecipeConfig) -> None:
|
|
370
|
+
"""
|
|
371
|
+
Execute the recipe.
|
|
372
|
+
|
|
373
|
+
Override in subclasses to implement recipe logic.
|
|
374
|
+
"""
|
|
375
|
+
raise NotImplementedError("Subclasses must implement _execute()")
|
|
376
|
+
|
|
377
|
+
@property
|
|
378
|
+
def output_dir(self) -> Optional[Path]:
|
|
379
|
+
"""Get the output directory."""
|
|
380
|
+
return self._output_dir
|
|
381
|
+
|
|
382
|
+
@property
|
|
383
|
+
def result(self) -> Optional[RecipeResult]:
|
|
384
|
+
"""Get the current result."""
|
|
385
|
+
return self._result
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: ai-ab-hook-tester
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
description: Generate A/B test variants for hooks with tracking plan
|
|
4
|
+
author: PraisonAI
|
|
5
|
+
tags:
|
|
6
|
+
- testing
|
|
7
|
+
- ab-test
|
|
8
|
+
- hooks
|
|
9
|
+
- optimization
|
|
10
|
+
|
|
11
|
+
config:
|
|
12
|
+
num_variants: 3
|
|
13
|
+
test_duration_days: 7
|
|
14
|
+
metrics_to_track:
|
|
15
|
+
- ctr
|
|
16
|
+
- watch_time
|
|
17
|
+
- engagement
|
|
18
|
+
|
|
19
|
+
input:
|
|
20
|
+
type: object
|
|
21
|
+
properties:
|
|
22
|
+
original_hook:
|
|
23
|
+
type: string
|
|
24
|
+
topic:
|
|
25
|
+
type: string
|
|
26
|
+
|
|
27
|
+
output:
|
|
28
|
+
type: object
|
|
29
|
+
properties:
|
|
30
|
+
variants:
|
|
31
|
+
type: array
|
|
32
|
+
tracking_plan:
|
|
33
|
+
type: object
|
|
34
|
+
|
|
35
|
+
requires:
|
|
36
|
+
env:
|
|
37
|
+
- OPENAI_API_KEY
|
|
38
|
+
|
|
39
|
+
workflow:
|
|
40
|
+
agents:
|
|
41
|
+
- name: ab_tester
|
|
42
|
+
role: A/B Test Specialist
|
|
43
|
+
tools:
|
|
44
|
+
- generate_test_variants
|
|
45
|
+
- create_tracking_plan
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AI A/B Hook Tester Tools
|
|
3
|
+
|
|
4
|
+
Generate test variants and tracking plans for hook optimization.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
from datetime import datetime, timedelta, timezone
|
|
10
|
+
from typing import Any, Dict, List
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def call_llm(prompt: str, max_tokens: int = 600) -> str:
|
|
16
|
+
"""Call OpenAI API."""
|
|
17
|
+
import requests
|
|
18
|
+
api_key = os.environ.get("OPENAI_API_KEY")
|
|
19
|
+
if not api_key:
|
|
20
|
+
raise ValueError("OPENAI_API_KEY not set")
|
|
21
|
+
response = requests.post(
|
|
22
|
+
"https://api.openai.com/v1/chat/completions",
|
|
23
|
+
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
|
|
24
|
+
json={"model": "gpt-4o-mini", "messages": [{"role": "user", "content": prompt}], "max_tokens": max_tokens},
|
|
25
|
+
timeout=60,
|
|
26
|
+
)
|
|
27
|
+
response.raise_for_status()
|
|
28
|
+
return response.json()["choices"][0]["message"]["content"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def generate_test_variants(
|
|
32
|
+
original_hook: str,
|
|
33
|
+
topic: str,
|
|
34
|
+
num_variants: int = 3,
|
|
35
|
+
) -> Dict[str, Any]:
|
|
36
|
+
"""
|
|
37
|
+
Generate A/B test variants for a hook.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
original_hook: Original hook text
|
|
41
|
+
topic: Content topic
|
|
42
|
+
num_variants: Number of variants to generate
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Dictionary with variants
|
|
46
|
+
"""
|
|
47
|
+
prompt = f"""Create {num_variants} A/B test variants for this video hook:
|
|
48
|
+
|
|
49
|
+
Original Hook: "{original_hook}"
|
|
50
|
+
Topic: {topic}
|
|
51
|
+
|
|
52
|
+
For each variant:
|
|
53
|
+
1. Use a different psychological trigger (curiosity, fear, benefit, controversy)
|
|
54
|
+
2. Keep it under 15 words
|
|
55
|
+
3. Make it distinctly different from the original
|
|
56
|
+
|
|
57
|
+
Format each variant as:
|
|
58
|
+
VARIANT [number]: [hook text]
|
|
59
|
+
TRIGGER: [psychological trigger used]
|
|
60
|
+
HYPOTHESIS: [why this might perform better]"""
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
result = call_llm(prompt, max_tokens=500)
|
|
64
|
+
|
|
65
|
+
variants = []
|
|
66
|
+
current_variant = {}
|
|
67
|
+
|
|
68
|
+
for line in result.split("\n"):
|
|
69
|
+
line = line.strip()
|
|
70
|
+
if line.startswith("VARIANT"):
|
|
71
|
+
if current_variant:
|
|
72
|
+
variants.append(current_variant)
|
|
73
|
+
current_variant = {"text": line.split(":", 1)[-1].strip()}
|
|
74
|
+
elif line.startswith("TRIGGER:"):
|
|
75
|
+
current_variant["trigger"] = line.split(":", 1)[-1].strip()
|
|
76
|
+
elif line.startswith("HYPOTHESIS:"):
|
|
77
|
+
current_variant["hypothesis"] = line.split(":", 1)[-1].strip()
|
|
78
|
+
|
|
79
|
+
if current_variant:
|
|
80
|
+
variants.append(current_variant)
|
|
81
|
+
|
|
82
|
+
# Add control (original)
|
|
83
|
+
all_variants = [
|
|
84
|
+
{"text": original_hook, "trigger": "control", "hypothesis": "Original baseline"}
|
|
85
|
+
] + variants[:num_variants]
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
"variants": all_variants,
|
|
89
|
+
"original": original_hook,
|
|
90
|
+
"total_variants": len(all_variants),
|
|
91
|
+
}
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.error(f"Error generating variants: {e}")
|
|
94
|
+
return {"error": str(e)}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def create_tracking_plan(
|
|
98
|
+
variants: List[Dict[str, Any]],
|
|
99
|
+
test_duration_days: int = 7,
|
|
100
|
+
metrics: List[str] = None,
|
|
101
|
+
) -> Dict[str, Any]:
|
|
102
|
+
"""
|
|
103
|
+
Create a tracking plan for A/B test.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
variants: List of variant dictionaries
|
|
107
|
+
test_duration_days: Test duration in days
|
|
108
|
+
metrics: Metrics to track
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Tracking plan
|
|
112
|
+
"""
|
|
113
|
+
metrics = metrics or ["ctr", "watch_time", "engagement"]
|
|
114
|
+
|
|
115
|
+
start_date = datetime.now(timezone.utc)
|
|
116
|
+
end_date = start_date + timedelta(days=test_duration_days)
|
|
117
|
+
|
|
118
|
+
tracking_plan = {
|
|
119
|
+
"test_id": f"ab_test_{start_date.strftime('%Y%m%d_%H%M%S')}",
|
|
120
|
+
"start_date": start_date.isoformat(),
|
|
121
|
+
"end_date": end_date.isoformat(),
|
|
122
|
+
"duration_days": test_duration_days,
|
|
123
|
+
"variants": [
|
|
124
|
+
{
|
|
125
|
+
"id": f"variant_{i}",
|
|
126
|
+
"text": v.get("text", ""),
|
|
127
|
+
"trigger": v.get("trigger", ""),
|
|
128
|
+
}
|
|
129
|
+
for i, v in enumerate(variants)
|
|
130
|
+
],
|
|
131
|
+
"metrics": {
|
|
132
|
+
m: {"description": _get_metric_description(m), "target": _get_metric_target(m)}
|
|
133
|
+
for m in metrics
|
|
134
|
+
},
|
|
135
|
+
"sample_size_per_variant": 1000,
|
|
136
|
+
"statistical_significance": 0.95,
|
|
137
|
+
"instructions": [
|
|
138
|
+
"Run each variant for equal time/impressions",
|
|
139
|
+
"Track all specified metrics",
|
|
140
|
+
"Wait for statistical significance before declaring winner",
|
|
141
|
+
"Document any external factors that might affect results",
|
|
142
|
+
],
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {"tracking_plan": tracking_plan}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _get_metric_description(metric: str) -> str:
|
|
149
|
+
"""Get description for a metric."""
|
|
150
|
+
descriptions = {
|
|
151
|
+
"ctr": "Click-through rate from thumbnail/title",
|
|
152
|
+
"watch_time": "Average watch time in seconds",
|
|
153
|
+
"engagement": "Likes + comments + shares",
|
|
154
|
+
"retention": "Percentage of video watched",
|
|
155
|
+
"conversion": "Desired action completion rate",
|
|
156
|
+
}
|
|
157
|
+
return descriptions.get(metric, metric)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _get_metric_target(metric: str) -> str:
|
|
161
|
+
"""Get target for a metric."""
|
|
162
|
+
targets = {
|
|
163
|
+
"ctr": ">5%",
|
|
164
|
+
"watch_time": ">60 seconds",
|
|
165
|
+
"engagement": ">3%",
|
|
166
|
+
"retention": ">50%",
|
|
167
|
+
"conversion": ">2%",
|
|
168
|
+
}
|
|
169
|
+
return targets.get(metric, "improve over control")
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: ai-angle-generator
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
description: Generate multiple content angles for a topic - controversial, educational, business, risk, future prediction
|
|
4
|
+
author: PraisonAI
|
|
5
|
+
tags:
|
|
6
|
+
- content
|
|
7
|
+
- angles
|
|
8
|
+
- strategy
|
|
9
|
+
- ideation
|
|
10
|
+
|
|
11
|
+
config:
|
|
12
|
+
angle_types:
|
|
13
|
+
- controversial
|
|
14
|
+
- educational
|
|
15
|
+
- business
|
|
16
|
+
- risk
|
|
17
|
+
- future_prediction
|
|
18
|
+
- personal_story
|
|
19
|
+
- comparison
|
|
20
|
+
num_angles: 5
|
|
21
|
+
|
|
22
|
+
input:
|
|
23
|
+
type: object
|
|
24
|
+
properties:
|
|
25
|
+
topic:
|
|
26
|
+
type: string
|
|
27
|
+
angle_types:
|
|
28
|
+
type: array
|
|
29
|
+
num_angles:
|
|
30
|
+
type: integer
|
|
31
|
+
|
|
32
|
+
output:
|
|
33
|
+
type: object
|
|
34
|
+
properties:
|
|
35
|
+
angles:
|
|
36
|
+
type: array
|
|
37
|
+
|
|
38
|
+
requires:
|
|
39
|
+
env:
|
|
40
|
+
- OPENAI_API_KEY
|
|
41
|
+
|
|
42
|
+
workflow:
|
|
43
|
+
agents:
|
|
44
|
+
- name: angle_generator
|
|
45
|
+
role: Content Strategist
|
|
46
|
+
goal: Generate diverse content angles
|
|
47
|
+
tools:
|
|
48
|
+
- generate_angles
|
|
49
|
+
- evaluate_angles
|