Files
opus-orchestrator-ai/opus_orchestrator/pydanticai_agent.py
T

251 lines
7.2 KiB
Python

"""PydanticAI Agent for Opus Orchestrator.
Provides structured output validation and agent expertise using PydanticAI.
"""
import os
from typing import Any, Optional, Type
from pydantic import BaseModel
from pydantic_ai import Agent
from dotenv import load_dotenv
from opus_orchestrator.config import get_config
def get_pydanticai_model() -> str:
"""Get the model name for PydanticAI.
Returns:
Model string for PydanticAI
"""
config = get_config()
# Map our config to PydanticAI model names
if config.agent.provider == "openai":
return "openai:gpt-4o"
elif config.agent.provider == "anthropic":
return "anthropic:claude-3-5-sonnet-20241022"
else:
return "openai:gpt-4o"
class OpusPydanticAgent:
"""PydanticAI-powered agent with structured output validation.
This agent ensures all outputs conform to defined schemas,
providing type-safe, validated responses.
"""
def __init__(
self,
result_type: Optional[Type[BaseModel]] = None,
model: Optional[str] = None,
system_prompt: Optional[str] = None,
):
"""Initialize the PydanticAI agent.
Args:
result_type: Pydantic model for structured output
model: Override model name
system_prompt: System prompt for the agent
"""
self.config = get_config()
self.model = model or get_pydanticai_model()
self.result_type = result_type
self.system_prompt = system_prompt
# Create the PydanticAI agent
self._agent: Optional[Agent] = None
self._setup_agent()
def _setup_agent(self) -> None:
"""Set up the PydanticAI agent."""
model = self.model
# Build system prompt
system_prompt = self.system_prompt or """You are an expert writer and editor for Opus Orchestrator.
You produce high-quality, structured output that conforms to the given schema.
Always follow best practices for the content type you're creating."""
if self.result_type:
self._agent = Agent(
model=model,
output_type=self.result_type,
system_prompt=system_prompt,
)
else:
self._agent = Agent(
model=model,
system_prompt=system_prompt,
)
async def run(self, prompt: str) -> BaseModel | str:
"""Run the agent with a prompt.
Args:
prompt: User prompt
Returns:
Structured result or string
"""
if not self._agent:
self._setup_agent()
if self.result_type:
result = await self._agent.run(prompt)
return result.output
else:
result = await self._agent.run(prompt)
return result.output
def run_sync(self, prompt: str) -> BaseModel | str:
"""Run the agent synchronously.
Args:
prompt: User prompt
Returns:
Structured result or string
"""
import asyncio
if not self._agent:
self._setup_agent()
if self.result_type:
result = asyncio.run(self._agent.run(prompt))
return result.output
else:
result = asyncio.run(self._agent.run(prompt))
return result.output
# =============================================================================
# SCHEMAS FOR STRUCTURED OUTPUT
# =============================================================================
class StorySeed(BaseModel):
"""Structured story seed output."""
one_sentence: str
one_paragraph: str
genre: str
target_audience: str
tone: str
themes: list[str]
class CharacterProfile(BaseModel):
"""Character profile schema."""
name: str
role: str # protagonist, antagonist, supporting
description: str
motivations: list[str]
flaws: list[str]
backstory: str
arc: str # character transformation
class ChapterOutline(BaseModel):
"""Chapter outline schema."""
chapter_number: int
title: str
summary: str
pov_character: str
key_events: list[str]
setting: str
chapter_goal: str
chapter_resolution: str
class ChapterDraft(BaseModel):
"""Chapter draft schema."""
chapter_number: int
title: str
content: str
word_count: int
pov: str
tone: str
key_moments: list[str]
dialogue_snippets: list[str]
class CritiqueResult(BaseModel):
"""Critique result schema."""
score: float # 0.0 - 1.0
strengths: list[str]
weaknesses: list[str]
suggestions: list[str]
verdict: str # APPROVED, MINOR_REVISIONS, MAJOR_REVISIONS
class StyleGuide(BaseModel):
"""Style guide schema."""
tone: str
voice: str
vocabulary_level: str
sentence_structure: str
pacing: str
dialogue_style: str
description_style: str
prohibited_elements: list[str]
# =============================================================================
# FACTORY FUNCTIONS
# =============================================================================
def create_story_seed_agent() -> OpusPydanticAgent:
"""Create agent for generating story seeds."""
return OpusPydanticAgent(
result_type=StorySeed,
system_prompt="""You are a story architect expert. Create detailed story seeds
that capture the essence of a story concept. Include genre, audience, tone, and themes.""",
)
def create_character_agent() -> OpusPydanticAgent:
"""Create agent for generating character profiles."""
return OpusPydanticAgent(
result_type=CharacterProfile,
system_prompt="""You are a character development expert. Create rich, detailed
character profiles with clear motivations, flaws, and arcs. Make characters feel real.""",
)
def create_chapter_outline_agent() -> OpusPydanticAgent:
"""Create agent for generating chapter outlines."""
return OpusPydanticAgent(
result_type=ChapterOutline,
system_prompt="""You are a plot structure expert. Create detailed chapter outlines
with clear goals, events, and resolutions. Ensure pacing works for the genre.""",
)
def create_chapter_draft_agent() -> OpusPydanticAgent:
"""Create agent for writing chapter drafts."""
return OpusPydanticAgent(
result_type=ChapterDraft,
system_prompt="""You are an expert fiction writer. Write compelling, well-paced
chapters that follow the outline while adding rich detail, dialogue, and description.""",
)
def create_critique_agent() -> OpusPydanticAgent:
"""Create agent for critiquing content."""
return OpusPydanticAgent(
result_type=CritiqueResult,
system_prompt="""You are an expert literary critic. Evaluate content objectively,
providing constructive feedback. Score honestly - don't inflate scores. Be specific.""",
)
def create_style_guide_agent() -> OpusPydanticAgent:
"""Create agent for generating style guides."""
return OpusPydanticAgent(
result_type=StyleGuide,
system_prompt="""You are an expert editor. Create comprehensive style guides
that capture the voice and tone of the intended work. Be specific and actionable.""",
)