system-prompts-and-models-o.../dealix/autonomous_growth/agents/content.py
2026-05-01 14:03:52 +03:00

160 lines
5.1 KiB
Python

"""
Content Creator Agent — generates bilingual articles, LinkedIn posts, case studies.
وكيل إنشاء المحتوى — يُنشئ مقالات و منشورات و دراسات حالة ثنائية اللغة.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Literal
from core.agents.base import BaseAgent
from core.config.models import Task
from core.llm.base import Message
from core.prompts import get_prompt
from core.utils import generate_id, utcnow
ContentType = Literal["article", "linkedin_post", "case_study", "newsletter", "tweet_thread"]
Channel = Literal["blog", "linkedin", "twitter", "email", "whatsapp_broadcast"]
@dataclass
class ContentPiece:
id: str
content_type: ContentType
channel: Channel
locale: str
topic: str
title: str
body_markdown: str
word_count: int
tags: list[str] = field(default_factory=list)
cta: str = ""
created_at: datetime = field(default_factory=utcnow)
def to_dict(self) -> dict[str, Any]:
return {
"id": self.id,
"content_type": self.content_type,
"channel": self.channel,
"locale": self.locale,
"topic": self.topic,
"title": self.title,
"body_markdown": self.body_markdown,
"word_count": self.word_count,
"tags": self.tags,
"cta": self.cta,
"created_at": self.created_at.isoformat(),
}
DEFAULT_LENGTHS: dict[ContentType, int] = {
"article": 800,
"linkedin_post": 200,
"case_study": 600,
"newsletter": 400,
"tweet_thread": 250,
}
DEFAULT_AUDIENCE: dict[str, str] = {
"ar": "مدراء العمليات والمبيعات في الشركات السعودية المتوسطة والكبيرة",
"en": "Operations and sales leaders at mid-to-large Saudi companies",
}
DEFAULT_CTA: dict[str, str] = {
"ar": "احجز استشارة مجانية 30 دقيقة لمعرفة كيف نطبّق هذا على شركتك",
"en": "Book a free 30-minute consultation to see how this applies to your company",
}
class ContentCreatorAgent(BaseAgent):
"""Generates channel-appropriate content."""
name = "content_creator"
async def run(
self,
*,
topic: str,
content_type: ContentType = "article",
channel: Channel = "blog",
locale: str = "ar",
audience: str | None = None,
goal: str = "educate prospects and drive consultation bookings",
length: int | None = None,
cta: str | None = None,
**_: Any,
) -> ContentPiece:
"""Generate a content piece."""
length = length or DEFAULT_LENGTHS.get(content_type, 500)
audience = audience or DEFAULT_AUDIENCE.get(locale, DEFAULT_AUDIENCE["en"])
cta = cta or DEFAULT_CTA.get(locale, DEFAULT_CTA["en"])
prompt = get_prompt(
"content_writer",
audience=audience,
goal=goal,
channel=channel,
locale=locale,
topic=topic,
length=length,
cta=cta,
)
# Arabic content → GLM (stronger for Arabic), else Claude
task = Task.ARABIC_TASKS if locale == "ar" else Task.PAGE_COPY
response = await self.router.run(
task=task,
messages=[Message(role="user", content=prompt)],
max_tokens=min(length * 4, 4000),
temperature=0.65,
)
body = response.content.strip()
title = self._extract_title(body) or topic
word_count = len(body.split())
tags = self._extract_tags(topic, content_type)
piece = ContentPiece(
id=generate_id("cnt"),
content_type=content_type,
channel=channel,
locale=locale,
topic=topic,
title=title,
body_markdown=body,
word_count=word_count,
tags=tags,
cta=cta,
)
self.log.info(
"content_generated",
id=piece.id,
content_type=content_type,
locale=locale,
words=word_count,
)
return piece
# ── Helpers ─────────────────────────────────────────────────
@staticmethod
def _extract_title(body: str) -> str | None:
for line in body.splitlines():
stripped = line.strip()
if stripped.startswith("# "):
return stripped.lstrip("# ").strip()
if stripped and not stripped.startswith("#"):
# First non-empty line as fallback title
return stripped[:120]
return None
@staticmethod
def _extract_tags(topic: str, content_type: ContentType) -> list[str]:
tags = [content_type, "ai", "saudi-arabia"]
keywords = ["healthcare", "real_estate", "logistics", "retail", "finance", "education"]
for k in keywords:
if k in topic.lower().replace(" ", "_"):
tags.append(k)
return tags