Wire up all agents with LLM calls

- 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
This commit is contained in:
2026-03-12 18:42:15 +00:00
parent e151cee69f
commit dec5aae09a
7 changed files with 584 additions and 462 deletions
@@ -46,7 +46,7 @@ You are The Character Lead — the one who breathes life into the figures who in
- **Positive**: Growth from weakness
- **Negative**: Fall from grace
- **Flat**: No change, changes world
- **Disruption**: External力量打破平衡
- **Disruption**: External forces break equilibrium
## The Want/Need/Fear Triad
@@ -77,39 +77,52 @@ class CharacterLeadAgent(BaseAgent):
)
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
"""Execute the Character Lead's task to generate character profiles.
Args:
input_data: Raw content + blueprint with character references
context: Additional context
Returns:
AgentResponse with character profiles
"""
"""Execute the Character Lead's task to generate character profiles."""
characters = input_data.get("characters", [])
raw_content = input_data.get("raw_content", "")
blueprint = input_data.get("blueprint", {})
user_prompt = f"""## Task
Create comprehensive character profiles for the following characters:
Create comprehensive character profiles for the following story:
{chr(10).join(f"- {c}" for c in characters) if characters else "Create profiles for all characters in the story."}
- Title: {blueprint.get('title', 'Untitled')}
- Genre: {blueprint.get('genre', 'general')}
{chr(10).join(f'- {c}' for c in characters) if characters else 'Create compelling characters that would drive this story.'}
## Raw Content Reference
{raw_content}
{raw_content if raw_content else 'Create original characters appropriate for this genre and story.'}
## Guidelines
Follow the Character Lead methodology from your system prompt.
Include the Want/Need/Fear triad for each major character.
Ensure each character has a distinct voice and arc.
"""
return AgentResponse(
success=True,
output={"status": "characters_created"},
metadata={"role": "Character Lead", "character_count": len(characters)},
)
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": "Character Lead",
"character_count": len(characters) if characters else 0,
},
)
except Exception as e:
return AgentResponse(
success=False,
output=None,
error=str(e),
metadata={"role": "Character Lead"},
)
async def develop_relationship(
self,
@@ -129,14 +142,27 @@ Include the Want/Need/Fear triad for each major character.
Develop this relationship following the Character Lead methodology.
Include:
- Current dynamics
- Power balance
- Current dynamics and power balance
- History (if any)
- Potential arc
- Potential arc throughout the story
- Key moments that define the relationship
"""
return AgentResponse(
success=True,
output={"status": "relationship_developed"},
metadata={"role": "Character Lead", "characters": [character_a, character_b]},
)
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": "Character Lead", "characters": [character_a, character_b]},
)
except Exception as e:
return AgentResponse(
success=False,
output=None,
error=str(e),
metadata={"role": "Character Lead"},
)
+103 -49
View File
@@ -64,12 +64,14 @@ You are The Editor — the quality control mechanism, identifying problems acros
- **Minor Revisions**: Continuity errors, style inconsistencies, pacing tweaks
- **Polish**: Grammar, punctuation, word choice refinement
## Quality Standards
## Output Format
- Every issue must have specific, actionable feedback
- Revision priorities must be clearly ordered
- Continuity issues must be flagged with exact locations
- Pacing analysis must be data-driven (scene lengths, tension scores)
Provide your critique as a structured review with:
1. Overall score (0.0-1.0)
2. Strengths (list)
3. Weaknesses (list)
4. Specific revision suggestions (prioritized)
5. Final verdict: major_revisions / minor_revisions / approved
"""
@@ -85,41 +87,43 @@ class EditorAgent(BaseAgent):
)
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
"""Execute the Editor's task to review and assess the manuscript.
Args:
input_data: Chapter or manuscript to review
context: Review criteria and standards
Returns:
AgentResponse with editorial assessment
"""
"""Execute the Editor's task to review content."""
content = input_data.get("content", "")
review_type = input_data.get("review_type", "full")
user_prompt = f"""## Task
Perform a {review_type} editorial review on:
Perform a {review_type} editorial review on the following content:
{content[:5000]}... {'(truncated)' if len(content) > 5000 else ''}
{content}
## Review Type: {review_type}
## Guidelines
Follow the Editor methodology from your system prompt.
Include:
- Continuity verification
- Pacing analysis
- Quality assessment
- Specific revision directions
Be specific and actionable in your feedback.
Assign a clear revision priority.
"""
return AgentResponse(
success=True,
output={"status": "editorial_review_complete"},
metadata={"role": "Editor", "review_type": review_type},
)
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": "Editor", "review_type": review_type},
)
except Exception as e:
return AgentResponse(
success=False,
output=None,
error=str(e),
metadata={"role": "Editor"},
)
async def review_chapter(
self,
@@ -132,14 +136,15 @@ Include:
- Chapter Number: {chapter.get('chapter_number')}
- Title: {chapter.get('title')}
- Content: {chapter.get('content', '')[:3000]}...
- Content:
{chapter.get('content', '')}
## Full Manuscript Context
- Total Chapters: {full_manuscript_context.get('total_chapters', 0)}
- Previous Chapters Summary: {full_manuscript_context.get('previous_summaries', [])}
- Characters in Story: {', '.join(full_manuscript_context.get('characters', []))}
- World Rules: {full_manuscript_context.get('world_rules', {})}
- Book Title: {full_manuscript_context.get('title', 'Untitled')}
- Genre: {full_manuscript_context.get('genre', 'general')}
## Task
@@ -150,18 +155,52 @@ Perform a complete editorial review of this chapter, considering:
- World-rule adherence
- Voice consistency
- Dialogue quality
- Show vs. tell balance
Assign a revision priority: major_revisions, minor_revisions, or approved
Provide:
1. Overall score (0.0-1.0)
2. Strengths (at least 3)
3. Weaknesses (at least 3)
4. Specific revision suggestions
5. Final verdict: major_revisions, minor_revisions, or approved
"""
return AgentResponse(
success=True,
output={
"status": "chapter_reviewed",
"chapter_number": chapter.get("chapter_number"),
},
metadata={"role": "Editor", "task": "chapter_review"},
)
try:
result = await self.call_llm(
system_prompt=self.build_system_prompt(context),
user_prompt=user_prompt,
)
# Try to extract score from result
score = 0.5 # default
for line in result.split('\n'):
if 'score' in line.lower() or 'rating' in line.lower():
try:
# Look for number
import re
numbers = re.findall(r'0\.\d+|\d+\.\d+', line)
if numbers:
score = float(numbers[0])
break
except:
pass
return AgentResponse(
success=True,
output={
"critique": result,
"score": score,
"chapter_number": chapter.get("chapter_number"),
},
metadata={"role": "Editor", "task": "chapter_review"},
)
except Exception as e:
return AgentResponse(
success=False,
output=None,
error=str(e),
metadata={"role": "Editor"},
)
async def generate_revision_notes(
self,
@@ -169,23 +208,38 @@ Assign a revision priority: major_revisions, minor_revisions, or approved
context: dict[str, Any],
) -> AgentResponse:
"""Generate prioritized revision notes from multiple critiques."""
critiques_text = "\n\n".join(f"### Critique {i+1}:\n{c.get('critique', str(c))}" for i, c in enumerate(critiques))
user_prompt = f"""## Critiques to Synthesize
{chr(10).join(f"### Critique {i+1}:{c}" for i, c in enumerate(critiques))}
{critiques_text}
## Task
Synthesize these critiques into prioritized revision notes.
Group by:
1. Major revisions (structural, plot, arc issues)
2. Minor revisions (continuity, style, pacing)
3. Polish items (grammar, word choice)
1. Major revisions (structural, plot, arc issues) - must fix
2. Minor revisions (continuity, style, pacing) - should fix
3. Polish items (grammar, word choice) - nice to fix
For each item, provide specific, actionable feedback.
For each item, provide specific, actionable feedback with location if possible.
"""
return AgentResponse(
success=True,
output={"status": "revision_notes_generated"},
metadata={"role": "Editor", "critique_count": len(critiques)},
)
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": "Editor", "critique_count": len(critiques)},
)
except Exception as e:
return AgentResponse(
success=False,
output=None,
error=str(e),
metadata={"role": "Editor"},
)
+83 -42
View File
@@ -78,17 +78,10 @@ class VoiceAgent(BaseAgent):
)
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
"""Execute the Voice agent's task to create style guide and samples.
Args:
input_data: Genre, tone, target audience
context: Additional context
Returns:
AgentResponse with style guide and prose samples
"""
"""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
@@ -96,41 +89,56 @@ Create a voice/style guide and prose samples for:
- Genre: {genre}
- Tone: {tone}
- Target Audience: {input_data.get('target_audience', 'General readers')}
- Target Audience: {target_audience}
## Guidelines
Follow the Voice agent methodology from your system prompt.
Include:
- Word bank
- Phrase patterns
- Rhythm map
- Tone guide
- 3 sample scenes (opening, dialogue, descriptive)
- 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.
"""
return AgentResponse(
success=True,
output={"status": "voice_created"},
metadata={"role": "Voice", "genre": genre, "tone": tone},
)
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: dict[str, Any],
style_guide: str,
context: dict[str, Any],
) -> AgentResponse:
"""Write a complete chapter following the style guide.
This is the main writing task for the Voice agent.
"""
"""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')}
- 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', []))}
@@ -141,22 +149,39 @@ Include:
## Task
Write the complete chapter following the style guide and chapter specification.
Maintain consistent voice throughout.
Maintain consistent voice throughout. Make it vivid, engaging, and professional quality.
Start with the chapter title as a heading.
"""
return AgentResponse(
success=True,
output={
"status": "chapter_written",
"chapter_number": chapter_spec.get("chapter_number"),
},
metadata={"role": "Voice"},
)
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: dict[str, Any],
style_guide: str,
context: dict[str, Any],
) -> AgentResponse:
"""Polish an existing chapter for voice consistency."""
@@ -174,12 +199,28 @@ Polish this chapter for voice consistency. Ensure:
- Sentence rhythm varies appropriately
- Word choice matches the style guide
- Tone remains consistent
- POV is maintained
- POV is maintained without head-hopping
- Prose flows smoothly
- Show don't tell where possible
Return the polished chapter as your output.
"""
return AgentResponse(
success=True,
output={"status": "chapter_polished"},
metadata={"role": "Voice", "task": "polish"},
)
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"},
)
+46 -40
View File
@@ -79,18 +79,11 @@ class WorldsmithAgent(BaseAgent):
)
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
"""Execute the Worldsmith's task to generate world documents.
Args:
input_data: Blueprint + genre + setting requirements
context: Additional context
Returns:
AgentResponse with world bible
"""
"""Execute the Worldsmith's task to generate world documents."""
blueprint = input_data.get("blueprint", {})
genre = input_data.get("genre", "fantasy")
setting_type = input_data.get("setting_type", "fantasy")
raw_content = input_data.get("raw_content", "")
user_prompt = f"""## Task
@@ -107,21 +100,31 @@ Ensure all elements are internally consistent and support the story.
## Content Seed
{input_data.get('raw_content', 'No additional content provided.')}
{raw_content if raw_content else 'Create an original world that would support a compelling story in this genre.'}
"""
return AgentResponse(
success=True,
output={
"status": "world_created",
"message": "World bible generation would be executed here with LLM",
},
metadata={
"role": "Worldsmith",
"genre": genre,
"setting_type": setting_type,
},
)
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": "Worldsmith",
"genre": genre,
"setting_type": setting_type,
},
)
except Exception as e:
return AgentResponse(
success=False,
output=None,
error=str(e),
metadata={"role": "Worldsmith"},
)
async def expand_location(
self,
@@ -131,33 +134,36 @@ Ensure all elements are internally consistent and support the story.
pov_character: str,
context: dict[str, Any],
) -> AgentResponse:
"""Generate detailed location description.
From Template B in Fiction Fortress Level 2.
"""
"""Generate detailed location description."""
user_prompt = f"""## Location Details
- Location Name: {location_name}
- Location Type: {context.get('location_type', 'general')}
- Story Relevance: {story_relevance}
- Tone Needed: {tone}
- POV Character: {pov_character}
## Sensory Requirements
- Visual: {context.get('visual', 'Standard')}
- Auditory: {context.get('auditory', 'Standard')}
- Olfactory: {context.get('olfactory', 'Standard')}
- Tactile: {context.get('tactile', 'Standard')}
- Gustatory: {context.get('gustatory', 'N/A')}
## Task
Generate a 300-600 word location description following the Fiction Fortress methodology.
Include sensory details (visual, auditory, olfactory, tactile).
Make it atmospheric and story-relevant.
"""
return AgentResponse(
success=True,
output={"status": "location_expanded"},
metadata={"role": "Worldsmith", "location": location_name},
)
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": "Worldsmith", "location": location_name},
)
except Exception as e:
return AgentResponse(
success=False,
output=None,
error=str(e),
metadata={"role": "Worldsmith"},
)