| """Copywriter agent: generates product descriptions and social captions.""" |
|
|
| from jinja2 import Template |
|
|
| from agents.llm import LLMClient |
| from agents.models import AgentTrace, CatalogerOutput, CopywriterOutput |
|
|
| SYSTEM_PROMPT = ( |
| "You are a warm, authentic copywriter for handmade crafts. You write " |
| "product descriptions and social media captions that feel personal and " |
| "artisan -- never corporate or salesy.\n\n" |
| "Your tone: warm, genuine, storytelling, inviting. Write as if a real " |
| "craftsperson is sharing their work.\n\n" |
| "Always respond with valid JSON." |
| ) |
|
|
| USER_TEMPLATE = Template( |
| "Write marketing copy for this {{ craft_type }} item:\n\n" |
| "Category: {{ catalog.category }} / {{ catalog.sub_category }}\n" |
| "Materials: {{ catalog.materials | join(', ') }}\n" |
| "Colors: {{ catalog.colors | join(', ') }}\n" |
| "Size: {{ catalog.estimated_size }}\n" |
| "Complexity: {{ catalog.complexity }}\n" |
| "{% if user_notes %}Creator's notes: {{ user_notes }}{% endif %}\n\n" |
| "Provide:\n" |
| "1. A catchy product title (under 60 characters)\n" |
| "2. A short description (1-2 sentences)\n" |
| "3. A longer description (1 paragraph, warm and personal)\n" |
| "4. Exactly 3 Instagram caption variations (each under 200 characters, " |
| "include relevant hashtags)" |
| ) |
|
|
|
|
| async def run( |
| llm: LLMClient, |
| craft_type: str, |
| catalog: CatalogerOutput, |
| user_notes: str | None = None, |
| ) -> tuple[CopywriterOutput, AgentTrace]: |
| prompt = USER_TEMPLATE.render( |
| craft_type=craft_type, |
| catalog=catalog, |
| user_notes=user_notes, |
| ) |
| result, duration_ms = await llm.agenerate( |
| system=SYSTEM_PROMPT, |
| prompt=prompt, |
| output_schema=CopywriterOutput, |
| max_tokens=1024, |
| ) |
| trace = AgentTrace( |
| agent_name="copywriter", |
| input_text=prompt, |
| output_data=result.model_dump(), |
| duration_ms=duration_ms, |
| model_id=llm.model_id, |
| ) |
| return result, trace |
|
|