dec5aae09a
- Worldsmith, Character Lead, Voice, Editor agents now call LLM - All nonfiction agents wired (Researcher, Analyst, Writer, FactChecker, Editor) - Orchestrator fully wired with agent pipeline - Add python-dotenv dependency
227 lines
6.5 KiB
Python
227 lines
6.5 KiB
Python
"""The Voice agent - Prose style and tone.
|
|
|
|
From Fiction Fortress Level 1-3:
|
|
- Role: Prose style and tone
|
|
- Responsibilities: Sentence-level writing, voice consistency
|
|
- Output: Prose samples, style guide
|
|
"""
|
|
|
|
from typing import Any
|
|
|
|
from opus_orchestrator.agents.base import AgentResponse, BaseAgent
|
|
|
|
|
|
VOICE_SYSTEM_PROMPT = """## Role: The Voice
|
|
|
|
You are The Voice — the owner of the prose itself, the sound, rhythm, and texture of the language. Your expertise lies in maintaining consistent style across thousands of words while adapting to scene-specific demands.
|
|
|
|
## Core Responsibilities
|
|
|
|
1. **Prose Style Control**
|
|
- Sentence rhythm variation (short/medium/long calibration)
|
|
- Vocabulary level management
|
|
- Figurative language deployment (metaphor, simile, symbolism density)
|
|
- Point of view intimacy maintenance
|
|
|
|
2. **Voice Consistency**
|
|
- Style guide creation and adherence
|
|
- Word bank maintenance
|
|
- Phrase pattern tracking
|
|
- Tone range specification (warm/cool, dark/light, etc.)
|
|
|
|
3. **Scene-Type Adaptation**
|
|
- Action scene pacing (sentence shortening)
|
|
- Emotional scene density (sentence complexity)
|
|
- Dialogue scene formatting
|
|
- Descriptive scene rhythm
|
|
- Exposition handling (invisible vs. visible)
|
|
|
|
4. **Point of View Management**
|
|
- Deep POV techniques
|
|
- Perspective consistency
|
|
- Head-hopping prevention
|
|
- Internal monologue integration
|
|
|
|
## Supporting Capabilities
|
|
|
|
- Dialogue attribution (said vs. action beats vs. no attribution)
|
|
- Punctuation style consistency
|
|
- Paragraph rhythm (white space management)
|
|
- Reader proximity calibration
|
|
|
|
## Voice Consistency Protocol
|
|
|
|
Maintain:
|
|
1. **Word bank** - Preferred vocabulary
|
|
2. **Phrase patterns** - Recurring constructions
|
|
3. **Rhythm map** - Sentence length distribution
|
|
4. **Tone guide** - Emotional range
|
|
|
|
## Quality Standards
|
|
|
|
- Voice must remain consistent across entire manuscript
|
|
- Scene type must inform prose style
|
|
- POV must be maintained without head-hopping
|
|
- Dialogue must sound distinct for each character
|
|
"""
|
|
|
|
|
|
class VoiceAgent(BaseAgent):
|
|
"""Agent responsible for prose style and voice consistency."""
|
|
|
|
def __init__(self, config=None):
|
|
super().__init__(
|
|
role="Voice",
|
|
description="Prose style and tone",
|
|
system_prompt=VOICE_SYSTEM_PROMPT,
|
|
config=config,
|
|
)
|
|
|
|
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
|
|
"""Execute the Voice agent's task to create style guide and samples."""
|
|
genre = input_data.get("genre", "general")
|
|
tone = input_data.get("tone", "neutral")
|
|
target_audience = input_data.get("target_audience", "General readers")
|
|
|
|
user_prompt = f"""## Task
|
|
|
|
Create a voice/style guide and prose samples for:
|
|
|
|
- Genre: {genre}
|
|
- Tone: {tone}
|
|
- Target Audience: {target_audience}
|
|
|
|
## Guidelines
|
|
|
|
Follow the Voice agent methodology from your system prompt.
|
|
Include:
|
|
- Word bank (preferred vocabulary for this genre/tone)
|
|
- Phrase patterns (recurring constructions)
|
|
- Rhythm map (sentence length distribution)
|
|
- Tone guide (emotional range)
|
|
- 3 sample scenes:
|
|
1. Opening scene
|
|
2. Dialogue-heavy scene
|
|
3. Descriptive/pacific scene
|
|
|
|
Make the samples vivid and representative of the final prose style.
|
|
"""
|
|
|
|
try:
|
|
result = await self.call_llm(
|
|
system_prompt=self.build_system_prompt(context),
|
|
user_prompt=user_prompt,
|
|
)
|
|
|
|
return AgentResponse(
|
|
success=True,
|
|
output=result,
|
|
metadata={"role": "Voice", "genre": genre, "tone": tone},
|
|
)
|
|
except Exception as e:
|
|
return AgentResponse(
|
|
success=False,
|
|
output=None,
|
|
error=str(e),
|
|
metadata={"role": "Voice"},
|
|
)
|
|
|
|
async def write_chapter(
|
|
self,
|
|
chapter_spec: dict[str, Any],
|
|
style_guide: str,
|
|
context: dict[str, Any],
|
|
) -> AgentResponse:
|
|
"""Write a complete chapter following the style guide."""
|
|
user_prompt = f"""## Chapter Specification
|
|
|
|
- Chapter Number: {chapter_spec.get('chapter_number')}
|
|
- Title: {chapter_spec.get('title')}
|
|
- Summary: {chapter_spec.get('summary')}
|
|
- Word Count Target: {chapter_spec.get('word_count_target', 3000)}
|
|
- POV Character: {chapter_spec.get('pov_character', 'Narrator')}
|
|
- Key Events: {', '.join(chapter_spec.get('key_events', []))}
|
|
|
|
## Style Guide
|
|
|
|
{style_guide}
|
|
|
|
## Task
|
|
|
|
Write the complete chapter following the style guide and chapter specification.
|
|
Maintain consistent voice throughout. Make it vivid, engaging, and professional quality.
|
|
Start with the chapter title as a heading.
|
|
"""
|
|
|
|
try:
|
|
result = await self.call_llm(
|
|
system_prompt=self.build_system_prompt(context),
|
|
user_prompt=user_prompt,
|
|
)
|
|
|
|
word_count = len(result.split())
|
|
|
|
return AgentResponse(
|
|
success=True,
|
|
output={
|
|
"content": result,
|
|
"word_count": word_count,
|
|
"chapter_number": chapter_spec.get("chapter_number"),
|
|
},
|
|
metadata={"role": "Voice", "word_count": word_count},
|
|
)
|
|
except Exception as e:
|
|
return AgentResponse(
|
|
success=False,
|
|
output=None,
|
|
error=str(e),
|
|
metadata={"role": "Voice"},
|
|
)
|
|
|
|
async def polish_chapter(
|
|
self,
|
|
chapter_content: str,
|
|
style_guide: str,
|
|
context: dict[str, Any],
|
|
) -> AgentResponse:
|
|
"""Polish an existing chapter for voice consistency."""
|
|
user_prompt = f"""## Chapter to Polish
|
|
|
|
{chapter_content}
|
|
|
|
## Style Guide
|
|
|
|
{style_guide}
|
|
|
|
## Task
|
|
|
|
Polish this chapter for voice consistency. Ensure:
|
|
- Sentence rhythm varies appropriately
|
|
- Word choice matches the style guide
|
|
- Tone remains consistent
|
|
- POV is maintained without head-hopping
|
|
- Prose flows smoothly
|
|
- Show don't tell where possible
|
|
|
|
Return the polished chapter as your output.
|
|
"""
|
|
|
|
try:
|
|
result = await self.call_llm(
|
|
system_prompt=self.build_system_prompt(context),
|
|
user_prompt=user_prompt,
|
|
)
|
|
|
|
return AgentResponse(
|
|
success=True,
|
|
output=result,
|
|
metadata={"role": "Voice", "task": "polish"},
|
|
)
|
|
except Exception as e:
|
|
return AgentResponse(
|
|
success=False,
|
|
output=None,
|
|
error=str(e),
|
|
metadata={"role": "Voice"},
|
|
)
|