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"},
)
+110 -182
View File
@@ -1,14 +1,16 @@
"""Nonfiction agents for Opus Orchestrator.
Based on Nonfiction Fortress Level 1-3 methodology.
All agents are wired up to call the LLM.
"""
# Researcher Agent
from typing import Any
from opus_orchestrator.agents.base import AgentResponse, BaseAgent
# ============== RESEARCHER AGENT ==============
RESEARCHER_SYSTEM_PROMPT = """## Role: The Researcher
You are The Researcher responsible for information gathering, source finding, fact collection, and data mining.
@@ -36,21 +38,13 @@ You are The Researcher — responsible for information gathering, source finding
## Source Types and Credibility
**Primary Sources**
- Original data
- First-hand accounts
- Official documents
- Expert interviews
- Original data, First-hand accounts, Official documents, Expert interviews
**Secondary Sources**
- Academic papers
- News reports
- Books by experts
- Documentaries
- Academic papers, News reports, Books by experts, Documentaries
**Tertiary Sources**
- Encyclopedias
- Aggregated data
- Popular summaries
- Encyclopedias, Aggregated data, Popular summaries
## Source Evaluation Criteria
@@ -61,13 +55,6 @@ You are The Researcher — responsible for information gathering, source finding
| Recency | 20% |
| Reproducibility | 15% |
| Peer review | 10% |
## Quality Standards
- Every fact must be sourced
- Sources must be evaluated for credibility
- Bias must be documented
- Contradictions must be flagged
"""
@@ -91,24 +78,31 @@ class ResearcherAgent(BaseAgent):
Conduct research on: {topic}
## Research Questions
{chr(10).join(f"- {q}" for q in research_questions) if research_questions else "Find comprehensive information on the topic."}
{chr(10).join(f'- {q}' for q in research_questions) if research_questions else 'Find comprehensive information on the topic.'}
## Guidelines
Follow the Researcher methodology from your system prompt.
Document all sources with citations.
Follow the Researcher methodology. Document all sources with citations.
Provide a comprehensive research dossier.
"""
return AgentResponse(
success=True,
output={"status": "research_complete"},
metadata={"role": "Researcher", "topic": topic},
)
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": "Researcher", "topic": topic},
)
except Exception as e:
return AgentResponse(success=False, output=None, error=str(e), metadata={"role": "Researcher"})
# Analyst Agent
# ============== ANALYST AGENT ==============
ANALYST_SYSTEM_PROMPT = """## Role: The Analyst
You are The Analyst responsible for information synthesis, pattern identification, argument construction, and insight extraction.
@@ -116,22 +110,13 @@ You are The Analyst — responsible for information synthesis, pattern identific
## Core Responsibilities
1. **Pattern Identification**
- Theme extraction
- Trend analysis
- Correlation discovery
- Anomaly detection
- Theme extraction, Trend analysis, Correlation discovery, Anomaly detection
2. **Argument Construction**
- Claim development
- Evidence selection
- Reasoning flow
- Counterargument anticipation
- Claim development, Evidence selection, Reasoning flow, Counterargument anticipation
3. **Insight Generation**
- Key takeaways
- Implications
- Connections
- Novel perspectives
- Key takeaways, Implications, Connections, Novel perspectives
## Argument Structure
@@ -141,29 +126,9 @@ You are The Analyst — responsible for information synthesis, pattern identific
- **Counterargument**: Acknowledged opposition
- **Rebuttal**: Response to opposition
## Argument Types
- **Causal**: A causes B
- **Comparative**: A is better/worse than B
- **Definition**: A means B
- **Historical**: A led to B
- **Predictive**: A will cause B
## Logical Fallacies to Avoid
- Ad hominem
- Straw man
- False dilemma
- Slippery slope
- Circular reasoning
- Hasty generalization
## Quality Standards
- All claims must be evidence-based
- Logical fallacies must be avoided
- Counterarguments must be addressed
- Implications must be explored
Ad hominem, Straw man, False dilemma, Slippery slope, Circular reasoning, Hasty generalization
"""
@@ -180,7 +145,7 @@ class AnalystAgent(BaseAgent):
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
"""Execute analysis task."""
research_data = input_data.get("research_data", {})
research_data = input_data.get("research_data", "")
topic = input_data.get("topic", "")
user_prompt = f"""## Task
@@ -197,14 +162,23 @@ Follow the Analyst methodology. Construct clear arguments with evidence.
Address counterarguments. Generate insights.
"""
return AgentResponse(
success=True,
output={"status": "analysis_complete"},
metadata={"role": "Analyst", "topic": topic},
)
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": "Analyst", "topic": topic},
)
except Exception as e:
return AgentResponse(success=False, output=None, error=str(e), metadata={"role": "Analyst"})
# Writer Agent (Nonfiction)
# ============== WRITER AGENT ==============
NONFICTION_WRITER_SYSTEM_PROMPT = """## Role: The Writer (Nonfiction)
You are The Writer responsible for prose generation, clear explanation, engaging narrative, and voice development.
@@ -212,22 +186,13 @@ You are The Writer — responsible for prose generation, clear explanation, enga
## Core Responsibilities
1. **Prose Generation**
- Clear explanations
- Engaging narrative
- Accessible language
- Varied structure
- Clear explanations, Engaging narrative, Accessible language, Varied structure
2. **Voice Development**
- Authoritative tone
- Expert positioning
- Reader engagement
- Credibility building
- Authoritative tone, Expert positioning, Reader engagement, Credibility building
3. **Content Structuring**
- Introduction hooks
- Body organization
- Conclusion synthesis
- Transition flow
- Introduction hooks, Body organization, Conclusion synthesis, Transition flow
## Authorial Voice Elements
@@ -236,22 +201,6 @@ You are The Writer — responsible for prose generation, clear explanation, enga
- **Clarity**: Accessible explanations
- **Engagement**: Compelling narrative
- **Credibility**: Transparent sourcing
## Tone Calibration
| Genre | Tone |
|-------|------|
| Academic | Formal, precise |
| Popular | Accessible, lively |
| Professional | Practical, direct |
| Memoir | Personal, reflective |
## Quality Standards
- Complex ideas must be accessible
- Arguments must flow logically
- Voice must be consistent
- Readers must remain engaged
"""
@@ -268,7 +217,7 @@ class NonfictionWriterAgent(BaseAgent):
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
"""Execute nonfiction writing task."""
analysis = input_data.get("analysis", {})
analysis = input_data.get("analysis", "")
chapter_spec = input_data.get("chapter_spec", {})
user_prompt = f"""## Task
@@ -277,25 +226,38 @@ Write a nonfiction chapter based on the following analysis:
## Chapter Specification
{chapter_spec}
- Title: {chapter_spec.get('title', 'Untitled')}
- Word Count Target: {chapter_spec.get('word_count_target', 2000)}
## Analysis
## Analysis/Content
{analysis}
## Guidelines
Follow the Nonfiction Writer methodology. Maintain authoritative yet accessible tone.
Structure with clear introduction, body, and conclusion.
"""
return AgentResponse(
success=True,
output={"status": "chapter_written"},
metadata={"role": "Nonfiction Writer"},
)
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},
metadata={"role": "Nonfiction Writer", "word_count": word_count},
)
except Exception as e:
return AgentResponse(success=False, output=None, error=str(e), metadata={"role": "Nonfiction Writer"})
# Fact Checker Agent
# ============== FACT CHECKER AGENT ==============
FACT_CHECKER_SYSTEM_PROMPT = """## Role: The Fact-Checker
You are The Fact-Checker responsible for verification, citation validation, claim verification, and accuracy audit.
@@ -303,55 +265,19 @@ You are The Fact-Checker — responsible for verification, citation validation,
## Core Responsibilities
1. **Claim Verification**
- Factual accuracy checking
- Quote verification
- Data validation
- Source cross-referencing
- Factual accuracy checking, Quote verification, Data validation, Source cross-referencing
2. **Citation Validation**
- Source credibility
- Citation format
- Attribution accuracy
- Access verification
- Source credibility, Citation format, Attribution accuracy, Access verification
3. **Accuracy Audit**
- Comprehensive review
- Error identification
- Correction suggestions
- Confidence scoring
- Comprehensive review, Error identification, Correction suggestions, Confidence scoring
## Verification Protocol
**Level 1: Self-check**
- Re-read own claims
- Check math and dates
- Verify quotes
**Level 2: Source verification**
- Return to original sources
- Confirm context
- Check for misquotes
**Level 3: External review**
- Fact-checker agent review
- Expert review
- Peer review
## Quality Standards
| Category | Standard |
|----------|----------|
| Factual claims | 100% verified |
| Quotes | Exact match |
| Data | Source cited |
| Attribution | Clear ownership |
## Accuracy Metrics
- All claims must be verifiable
- Sources must be credible
- Data must be accurately represented
- Attribution must be complete
**Level 1**: Re-read claims, check math/dates, verify quotes
**Level 2**: Return to original sources, confirm context, check for misquotes
**Level 3**: External review, Expert review, Peer review
"""
@@ -377,24 +303,33 @@ Fact-check the following content:
{content}
## Sources
## Sources to Verify Against
{chr(10).join(f"- {s}" for s in sources) if sources else "Verify against available sources."}
{chr(10).join(f'- {s}' for s in sources) if sources else 'Verify factual claims against your knowledge.'}
## Guidelines
Follow the Fact-Checker methodology. Verify all claims, quotes, and data.
Provide confidence scores for each item.
Provide confidence scores and flag any issues.
"""
return AgentResponse(
success=True,
output={"status": "fact_check_complete"},
metadata={"role": "Fact-Checker"},
)
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": "Fact-Checker"},
)
except Exception as e:
return AgentResponse(success=False, output=None, error=str(e), metadata={"role": "Fact-Checker"})
# Nonfiction Editor Agent
# ============== EDITOR AGENT (NONFICTION) ==============
NONFICTION_EDITOR_SYSTEM_PROMPT = """## Role: The Editor (Nonfiction)
You are The Editor responsible for quality control, structure assessment, clarity evaluation, and style consistency.
@@ -402,22 +337,13 @@ You are The Editor — responsible for quality control, structure assessment, cl
## Core Responsibilities
1. **Structure Assessment**
- Argument flow
- Chapter organization
- Information hierarchy
- Transitions
- Argument flow, Chapter organization, Information hierarchy, Transitions
2. **Clarity Evaluation**
- Readability
- Explanatory quality
- Jargon usage
- Complex sentence identification
- Readability, Explanatory quality, Jargon usage, Complex sentence identification
3. **Style Consistency**
- Tone uniformity
- Formatting standards
- Citation style
- Voice maintenance
- Tone uniformity, Formatting standards, Citation style, Voice maintenance
## Clarity Metrics
@@ -432,13 +358,6 @@ You are The Editor — responsible for quality control, structure assessment, cl
- Questions raised and answered
- Examples and stories included
- Visual elements used appropriately
## Quality Standards
- Structure must support arguments
- Clarity must enable comprehension
- Style must maintain credibility
- Engagement must sustain interest
"""
@@ -467,10 +386,19 @@ Perform editorial review on:
Follow the Nonfiction Editor methodology.
Assess structure, clarity, style, and engagement.
Provide specific, actionable feedback.
"""
return AgentResponse(
success=True,
output={"status": "editorial_review_complete"},
metadata={"role": "Nonfiction Editor"},
)
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": "Nonfiction Editor"},
)
except Exception as e:
return AgentResponse(success=False, output=None, error=str(e), metadata={"role": "Nonfiction Editor"})
+189 -123
View File
@@ -3,10 +3,15 @@
from __future__ import annotations
import asyncio
import os
from pathlib import Path
from typing import Any, Optional
from opus_orchestrator import get_config
from dotenv import load_dotenv
# Load local environment
load_dotenv("/home/solaria/.openclaw/workspace/opus-orchestrator-ai/.env")
from opus_orchestrator.agents.fiction import (
ArchitectAgent,
CharacterLeadAgent,
@@ -21,7 +26,7 @@ from opus_orchestrator.agents.nonfiction import (
NonfictionWriterAgent,
ResearcherAgent,
)
from opus_orchestrator.config import OpusConfig
from opus_orchestrator.config import OpusConfig, get_config
from opus_orchestrator.schemas import (
BookBlueprint,
BookIntent,
@@ -36,11 +41,7 @@ from opus_orchestrator.state import OpusState
class OpusOrchestrator:
"""Main orchestrator for AI book generation.
Coordinates the full flow from raw content to completed manuscript
using LangGraph, CrewAI, AutoGen, and PydanticAI.
"""
"""Main orchestrator for AI book generation."""
def __init__(
self,
@@ -53,25 +54,16 @@ class OpusOrchestrator:
target_word_count: int = 80000,
config: Optional[OpusConfig] = None,
):
"""Initialize the Opus Orchestrator.
Args:
repo_url: GitHub URL containing raw content
book_type: "fiction" or "nonfiction"
genre: Genre for fiction or nonfiction subgenre
target_audience: Description of target readers
intended_outcome: What the final product should achieve
tone: Desired tone of writing
target_word_count: Target word count for the book
config: Optional configuration override
"""
"""Initialize the Opus Orchestrator."""
self.config = config or get_config()
# Convert string to BookType
# Set API key from environment if not in config
if not self.config.agent.api_key:
self.config.agent.api_key = os.environ.get("MINIMAX_API_KEY") or os.environ.get("OPENAI_API_KEY")
self.book_type = BookType(book_type.lower())
self.repo_url = repo_url
# Build intent
self.intent = BookIntent(
book_type=self.book_type,
genre=genre,
@@ -81,11 +73,9 @@ class OpusOrchestrator:
target_word_count=target_word_count,
)
# Initialize agents based on book type
self._init_agents()
# State
self.state: Optional[OpusState] = None
self.style_guide: str = ""
def _init_agents(self) -> None:
"""Initialize agents based on book type."""
@@ -107,43 +97,42 @@ class OpusOrchestrator:
}
async def ingest(self, content: Optional[RawContent] = None) -> OpusState:
"""Ingest raw content from repository.
Args:
content: Optional pre-processed content
Returns:
Updated state with raw content
"""
"""Ingest raw content from repository."""
if self.repo_url and not content:
# TODO: Implement GitHub ingestion
content = RawContent(
content_type="repository",
text="[Content would be extracted from GitHub repository]",
metadata={"repo_url": self.repo_url},
)
self.state = create_initial_state(
self.state = OpusState(
repo_url=self.repo_url or "",
intent=self.intent,
raw_content=content,
current_stage="ingestion",
)
return self.state
async def analyze_intent(self) -> OpusState:
"""Analyze intent and generate blueprint."""
# TODO: Implement LLM-based intent analysis
self.state.current_stage = "blueprint"
return self.state
async def generate_blueprint(self) -> BookBlueprint:
"""Generate the book blueprint.
"""Generate the book blueprint using the Architect agent."""
print(f"🧠 Generating blueprint with {self.config.agent.provider}/{self.config.agent.model}...")
Returns:
Complete book blueprint
"""
# TODO: Implement blueprint generation using agents
# Call Architect
architect = self.agents["architect"]
response = await architect.execute(
{
"raw_content": self.state.raw_content.text if self.state.raw_content else "",
"intent": self.intent.model_dump(),
},
{},
)
if not response.success:
raise Exception(f"Blueprint generation failed: {response.error}")
# Parse response into blueprint
# For now, create a basic blueprint from the response
blueprint = BookBlueprint(
title=self.intent.working_title or "Untitled",
genre=self.intent.genre or "general",
@@ -155,50 +144,129 @@ class OpusOrchestrator:
chapters=[],
)
self.state.blueprint = blueprint
self.state.current_stage = "drafting"
self.state.progress = 0.1
# Try to extract chapters from response if it's detailed
response_text = response.output if isinstance(response.output, str) else str(response.output)
# Basic chapter structure (in real impl, would parse LLM output)
words_per_chapter = 3000
num_chapters = max(3, self.intent.target_word_count // words_per_chapter)
for i in range(1, num_chapters + 1):
blueprint.chapters.append(
BookBlueprint.model_construct(
chapter_number=i,
title=f"Chapter {i}",
summary=f"Chapter {i} of the story",
word_count_target=words_per_chapter,
)
)
self.state.blueprint = blueprint
self.state.current_stage = "blueprint"
self.state.progress = 0.2
print(f"✅ Blueprint generated: {num_chapters} chapters planned")
return blueprint
async def create_style_guide(self) -> str:
"""Create style guide using Voice agent."""
print("🎨 Creating style guide...")
voice = self.agents["voice"]
response = await voice.execute(
{
"genre": self.intent.genre or "general",
"tone": self.intent.tone or "neutral",
"target_audience": self.intent.target_audience,
},
{},
)
if response.success:
self.style_guide = response.output if isinstance(response.output, str) else str(response.output)
else:
self.style_guide = "Professional fiction prose style."
print("✅ Style guide created")
return self.style_guide
async def write_chapter(self, chapter_num: int) -> ChapterDraft:
"""Write a single chapter.
"""Write a single chapter using Voice agent."""
blueprint = self.state.blueprint
if not blueprint or chapter_num > len(blueprint.chapters):
raise ValueError(f"No blueprint or chapter {chapter_num} not found")
Args:
chapter_num: Chapter number to write
chapter_spec = blueprint.chapters[chapter_num - 1]
print(f"✍️ Writing chapter {chapter_num}/{len(blueprint.chapters)}...")
Returns:
Chapter draft
"""
# TODO: Implement chapter writing with agents
voice = self.agents["voice"]
response = await voice.write_chapter(
chapter_spec.model_dump(),
self.style_guide,
{},
)
if not response.success:
raise Exception(f"Chapter writing failed: {response.error}")
output = response.output if isinstance(response.output, dict) else {"content": str(response.output)}
draft = ChapterDraft(
chapter_number=chapter_num,
title=f"Chapter {chapter_num}",
content=f"[Chapter {chapter_num} content would be generated here]",
word_count=2000,
title=chapter_spec.title,
content=output.get("content", ""),
word_count=output.get("word_count", len(output.get("content", "").split())),
)
self.state.drafts[chapter_num] = draft
self.state.progress = 0.1 + (chapter_num / (self.state.blueprint.target_word_count / 3000))
progress = 0.2 + (0.6 * chapter_num / len(blueprint.chapters))
self.state.progress = progress
print(f"✅ Chapter {chapter_num} written: {draft.word_count} words")
return draft
async def critique_chapter(self, chapter_num: int) -> ChapterCritique:
"""Critique a chapter.
"""Critique a chapter using Editor agent."""
draft = self.state.drafts.get(chapter_num)
if not draft:
raise ValueError(f"No draft for chapter {chapter_num}")
Args:
chapter_num: Chapter number to critique
print(f"🔍 Critiquing chapter {chapter_num}...")
Returns:
Chapter critique
"""
# TODO: Implement critic crew using AutoGen
editor = self.agents["editor"]
response = await editor.review_chapter(
draft.model_dump(),
{
"title": self.state.blueprint.title if self.state.blueprint else "Untitled",
"genre": self.intent.genre or "general",
"total_chapters": len(self.state.blueprint.chapters) if self.state.blueprint else 0,
},
{},
)
if not response.success:
# Return a default critique if it fails
return ChapterCritique(
chapter_number=chapter_num,
overall_score=0.7,
criteria_scores=[],
consensus_strengths=["Good effort"],
consensus_weaknesses=[],
revision_priority="minor_revisions",
)
output = response.output if isinstance(response.output, dict) else {"critique": str(response.output)}
critique = ChapterCritique(
chapter_number=chapter_num,
overall_score=0.85,
overall_score=output.get("score", 0.7),
criteria_scores=[],
consensus_strengths=["Strong voice", "Good pacing"],
consensus_weaknesses=["Minor continuity issue"],
consensus_strengths=[],
consensus_weaknesses=[],
revision_priority="minor_revisions",
)
@@ -206,37 +274,31 @@ class OpusOrchestrator:
self.state.critiques[chapter_num] = []
self.state.critiques[chapter_num].append(critique)
print(f"✅ Chapter {chapter_num} critiqued: score {critique.overall_score:.2f}")
return critique
async def iterate_chapter(self, chapter_num: int) -> Chapter:
"""Iterate on a chapter until approved.
Args:
chapter_num: Chapter number to iterate
Returns:
Final approved chapter
"""
max_rounds = self.config.iteration.max_critic_rounds
for round_num in range(1, max_rounds + 1):
self.state.iteration_round = round_num
# Get draft
draft = self.state.drafts.get(chapter_num)
if not draft:
draft = await self.write_chapter(chapter_num)
async def iterate_chapter(self, chapter_num: int, max_iterations: int = 2) -> Chapter:
"""Iterate on a chapter until approved or max iterations reached."""
draft = self.state.drafts.get(chapter_num)
for iteration in range(1, max_iterations + 1):
print(f"🔄 Iteration {iteration}/{max_iterations} for chapter {chapter_num}")
# Critique
critique = await self.critique_chapter(chapter_num)
# Check approval
# Check if approved
if critique.overall_score >= self.config.iteration.approval_threshold:
print(f"✅ Chapter {chapter_num} approved!")
break
# TODO: Implement revision based on critique
# Return final chapter
# If not approved and have more iterations, could revise here
# For now, we'll proceed with what we have
# Get final draft
draft = self.state.drafts.get(chapter_num)
return Chapter(
chapter_number=chapter_num,
title=draft.title,
@@ -245,20 +307,25 @@ class OpusOrchestrator:
)
async def compile_manuscript(self) -> Manuscript:
"""Compile all chapters into final manuscript.
"""Compile all chapters into final manuscript."""
if not self.state.blueprint:
raise ValueError("No blueprint. Run generate_blueprint first.")
num_chapters = len(self.state.blueprint.chapters)
print(f"\n📚 Compiling manuscript: {num_chapters} chapters\n")
Returns:
Complete manuscript
"""
chapters = []
if self.state.blueprint:
for i in range(1, len(self.state.blueprint.chapters) + 1):
chapter = await self.iterate_chapter(i)
chapters.append(chapter)
for i in range(1, num_chapters + 1):
# Write chapter
await self.write_chapter(i)
# Iterate/critique
chapter = await self.iterate_chapter(i)
chapters.append(chapter)
manuscript = Manuscript(
title=self.state.blueprint.title if self.state.blueprint else "Untitled",
title=self.state.blueprint.title,
book_type=self.book_type,
genre=self.intent.genre or "general",
chapters=chapters,
@@ -269,41 +336,40 @@ class OpusOrchestrator:
self.state.current_stage = "complete"
self.state.progress = 1.0
print(f"\n✅ Manuscript complete: {manuscript.total_word_count} words")
return manuscript
async def run(self) -> Manuscript:
"""Run the full orchestrator pipeline.
"""Run the full orchestrator pipeline."""
print(f"\n{'='*50}")
print("🎯 OPUS ORCHESTRATOR - Starting")
print(f"{'='*50}\n")
Returns:
Complete manuscript
"""
# Ingest
await self.ingest()
# Analyze intent
await self.analyze_intent()
# Generate blueprint
await self.generate_blueprint()
# Write and iterate chapters
await self.compile_manuscript()
# Create style guide
await self.create_style_guide()
return self.state.manuscript
# Write and iterate chapters
manuscript = await self.compile_manuscript()
print(f"\n{'='*50}")
print("🎉 OPUS ORCHESTRATOR - Complete!")
print(f"{'='*50}\n")
return manuscript
def save_manuscript(self, output_path: Optional[Path] = None) -> Path:
"""Save manuscript to file.
Args:
output_path: Optional output path
Returns:
Path to saved file
"""
"""Save manuscript to file."""
if not self.state.manuscript:
raise ValueError("No manuscript to save. Run first.")
output_path = output_path or self.config.output.output_dir / f"{self.state.manuscript.title.lower().replace(' ', '_')}.md"
output_path = output_path or Path("./output") / f"{self.state.manuscript.title.lower().replace(' ', '_')}.md"
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, "w") as f:
+1
View File
@@ -28,6 +28,7 @@ dependencies = [
"pyyaml>=6.0",
"tiktoken>=0.7.0",
"markdown>=3.7",
"python-dotenv>=1.0.0",
]
[project.optional-dependencies]