Initial commit: Opus Orchestrator AI - Full-flow book generation

- LangGraph workflow orchestration
- CrewAI agent crews (Fiction Fortress & Nonfiction Fortress)
- PydanticAI schema validation
- Fiction agents: Architect, Worldsmith, Character Lead, Voice, Editor
- Nonfiction agents: Researcher, Analyst, Writer, Fact-Checker, Editor
- Complete schema definitions for books, chapters, critiques
- Configuration management
- Basic test suite
This commit is contained in:
2026-03-12 17:44:51 +00:00
parent f10d688c43
commit 40378ad65e
20 changed files with 2592 additions and 2 deletions
+162 -2
View File
@@ -1,2 +1,162 @@
# opus-orchestrator-ai
Full-flow AI book generation orchestrator using LangGraph, CrewAI, AutoGen, and PydanticAI. Integrates Fiction Fortress and Nonfiction Fortress for professional manuscript production.
# Opus Orchestrator AI
> Full-flow AI book generation orchestrator using LangGraph, CrewAI, AutoGen, and PydanticAI. Integrates Fiction Fortress and Nonfiction Fortress for professional manuscript production.
## Overview
Opus Orchestrator AI transforms raw content (notes, outlines, stream-of-consciousness, essays, logs) into fully edited, publication-ready manuscripts. It combines:
- **LangGraph** — Workflow orchestration and state management
- **CrewAI** — Role-based agent crews
- **AutoGen** — Complex multi-agent negotiations
- **PydanticAI** — Structured output validation
- **Fiction Fortress** — Complete fiction writing methodology
- **Nonfiction Fortress** — Complete non-fiction writing methodology
## Architecture
```
[GitHub Repo Input]
┌───────────────────┐
│ INGESTOR AGENT │ ──► Extracts raw content from repo
└────────┬──────────┘
┌───────────────────┐
│ INTENT ANALYZER │ ──► Analyzes goals, audience, intended outcome
└────────┬──────────┘
┌───────────────────┐
│ BLUEPRINT │ ──► Generates detailed book blueprint
└────────┬──────────┘
┌────┴────┐
│ ITERATE │
└────┬────┘
┌────▼─────────────┐
│ CREW EXECUTION │ ──► Runs agent crews per chapter
│ • Writer │
│ • Critics (3+) │
│ • Editor │
│ • Proofreader │
└────┬─────────────┘
┌────▼─────────────┐
│ REVIEW & REVISE │ ──► Internal critic circle
└────┬─────────────┘
└─────────────┬────► [Loop back if needed]
┌─────────────────┐
│ COMPILED .MD │
│ MANUSCRIPT │
└─────────────────┘
```
## Installation
```bash
git clone https://github.com/mrhavens/opus-orchestrator-ai.git
cd opus-orchestrator-ai
pip install -e .
```
## Quick Start
```python
from opus_orchestrator import OpusOrchestrator
orchestrator = OpusOrchestrator(
repo_url="https://github.com/user/my-book-ideas",
book_type="fiction", # or "nonfiction"
genre="science-fiction",
target_audience="adult sci-fi readers",
intended_outcome="complete novel, ~80k words"
)
# Run the full pipeline
manuscript = await orchestrator.run()
print(f"Generated: {manuscript.word_count} words")
```
## Configuration
See `config.example.yaml` for full configuration options.
## Project Structure
```
opus_orchestrator/
├── __init__.py # Main exports
├── config.py # Configuration management
├── state.py # LangGraph state definitions
├── graph.py # Main workflow graph
├── agents/
│ ├── __init__.py
│ ├── base.py # Base agent class
│ ├── fiction/ # Fiction Fortress agents
│ │ ├── architect.py
│ │ ├── worldsmith.py
│ │ ├── character_lead.py
│ │ ├── voice.py
│ │ └── editor.py
│ └── nonfiction/ # Nonfiction Fortress agents
│ ├── researcher.py
│ ├── analyst.py
│ ├── writer.py
│ ├── fact_checker.py
│ └── editor.py
├── crews/
│ ├── __init__.py
│ ├── fiction_crew.py # Fiction writing crew
│ └── nonfiction_crew.py
├── schemas/
│ ├── __init__.py
│ ├── book.py # Book-level schemas
│ ├── chapter.py # Chapter schemas
│ └── critique.py # Critique schemas
└── utils/
├── __init__.py
├── github.py # GitHub ingestion
└── output.py # Output generation
```
## Development
### Running Tests
```bash
pytest tests/
```
### Code Style
```bash
ruff check .
ruff format .
```
## Dependencies
- langgraph
- crewai
- autogen
- pydantic-ai
- pydantic
- httpx
- pygithub
- pyyaml
## License
MIT
---
*Built with the WE Architecture — witness and co-creation in code.*
+59
View File
@@ -0,0 +1,59 @@
"""Opus Orchestrator AI.
Full-flow AI book generation using LangGraph, CrewAI, AutoGen, and PydanticAI.
Integrates Fiction Fortress and Nonfiction Fortress methodologies.
"""
from opus_orchestrator.agents.fiction import (
ArchitectAgent,
CharacterLeadAgent,
EditorAgent,
VoiceAgent,
WorldsmithAgent,
)
from opus_orchestrator.agents.nonfiction import (
AnalystAgent,
FactCheckerAgent,
NonfictionEditorAgent,
NonfictionWriterAgent,
ResearcherAgent,
)
from opus_orchestrator.config import OpusConfig, get_config
from opus_orchestrator.schemas import (
BookIntent,
BookType,
Manuscript,
RawContent,
)
from opus_orchestrator.state import OpusState, create_initial_state
__all__ = [
# Config
"OpusConfig",
"get_config",
# State
"OpusState",
"create_initial_state",
# Schemas
"BookIntent",
"BookType",
"Manuscript",
"RawContent",
# Fiction Agents
"ArchitectAgent",
"CharacterLeadAgent",
"EditorAgent",
"VoiceAgent",
"WorldsmithAgent",
# Nonfiction Agents
"ResearcherAgent",
"AnalystAgent",
"NonfictionWriterAgent",
"FactCheckerAgent",
"NonfictionEditorAgent",
# Main
"OpusOrchestrator",
]
# Import orchestrator at bottom to avoid circular imports
from opus_orchestrator.orchestrator import OpusOrchestrator
+106
View File
@@ -0,0 +1,106 @@
"""Base agent class for Opus Orchestrator."""
from abc import ABC, abstractmethod
from typing import Any, Generic, TypeVar
from pydantic import BaseModel
from opus_orchestrator.config import AgentConfig, get_config
T = TypeVar("T", bound=BaseModel)
class AgentResponse(BaseModel):
"""Standard response from an agent."""
success: bool
output: Any
error: Optional[str] = None
metadata: dict[str, Any] = {}
class Config:
arbitrary_types_allowed = True
from typing import Optional
class BaseAgent(ABC, Generic[T]):
"""Base class for all Opus agents.
Each agent has:
- A specific role (from Fortress documentation)
- System prompts derived from Fortress methodologies
- Input/output schemas
- Execution logic
"""
def __init__(
self,
role: str,
description: str,
system_prompt: str,
output_schema: type[T] | None = None,
config: Optional[AgentConfig] = None,
):
self.role = role
self.description = description
self.system_prompt = system_prompt
self.output_schema = output_schema
self.config = config or get_config().agent
@abstractmethod
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
"""Execute the agent's task.
Args:
input_data: The input data for this agent
context: Additional context from the orchestrator
Returns:
AgentResponse with output and metadata
"""
pass
def build_system_prompt(self, context: dict[str, Any]) -> str:
"""Build the full system prompt with context.
Args:
context: Additional context to inject
Returns:
Complete system prompt
"""
base = self.system_prompt
if context:
context_str = "\n\n## Context\n"
for key, value in context.items():
context_str += f"- **{key}**: {value}\n"
return base + context_str
return base
def build_user_prompt(self, task: str, input_data: Any) -> str:
"""Build the user prompt for a specific task.
Args:
task: Description of the task
input_data: Input data formatted for the task
Returns:
Complete user prompt
"""
return f"""## Task
{task}
## Input
{input_data}
## Instructions
Please complete this task following the methodology specified in your system prompt.
"""
@@ -0,0 +1,18 @@
"""Fiction agents for Opus Orchestrator.
Based on Fiction Fortress Level 1-3 methodology.
"""
from opus_orchestrator.agents.fiction.architect import ArchitectAgent
from opus_orchestrator.agents.fiction.character_lead import CharacterLeadAgent
from opus_orchestrator.agents.fiction.editor import EditorAgent
from opus_orchestrator.agents.fiction.voice import VoiceAgent
from opus_orchestrator.agents.fiction.worldsmith import WorldsmithAgent
__all__ = [
"ArchitectAgent",
"CharacterLeadAgent",
"EditorAgent",
"VoiceAgent",
"WorldsmithAgent",
]
@@ -0,0 +1,169 @@
"""The Architect agent - Story structure and plot design.
From Fiction Fortress Level 1-3:
- Role: Story structure and plot design
- Responsibilities: Outlines, pacing, scene planning
- Output: Story blueprint
"""
from typing import Any, Optional
from opus_orchestrator.agents.base import AgentResponse, BaseAgent
from opus_orchestrator.schemas import BookBlueprint, ChapterBlueprint
ARCHITECT_SYSTEM_PROMPT = """## Role: The Architect
You are The Architect — the story's structural engineer. Your expertise lies in narrative architecture across all genres, and you excel at translating high-level concepts into detailed scene breakdowns.
## Core Responsibilities
1. **Story Structure Mastery**
- Three-act structure application across genres
- Beat-by-beat scene planning
- Subplot integration techniques
- Pacing analysis and adjustment
2. **Genre Expertise**
- Mystery: Clue placement, red herring distribution, revelation timing
- Romance: Beat sheet adaptation for relationship arcs
- Thriller: Tension escalation curves, set-piece design
- Fantasy/Sci-Fi: World-rule integration with plot beats
3. **Outline Generation**
- Story spine creation (primal narrative in 3-5 sentences)
- Chapter-level beat mapping
- Scene purpose identification (whose story, what changes, why this scene)
- Backstory weaving into present-tense narrative
4. **Conflict Architecture**
- Internal vs. external conflict layering
- Antagonist motivation design
- Stakes escalation planning
- Tension and release rhythm
## Quality Standards
- Each scene must advance plot, character, or theme (or multiple)
- Stakes must escalate through Acts I-II
- Midpoint must fundamentally shift protagonist's understanding
- All subplots must resolve by climax
- Every chapter needs a clear purpose and payoff
## Output Format
When generating a blueprint, include:
1. Story spine (primal narrative in 3-5 sentences)
2. Three-act breakdown with specific beats
3. Chapter-level outline (numbered chapters with one-line descriptions)
4. Scene list with purpose tags
5. Key plot points with word count allocations
6. Subplot integration notes
"""
class ArchitectAgent(BaseAgent):
"""Agent responsible for story structure and plot design."""
def __init__(self, config=None):
super().__init__(
role="Architect",
description="Story structure and plot design",
system_prompt=ARCHITECT_SYSTEM_PROMPT,
output_schema=BookBlueprint,
config=config,
)
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
"""Execute the Architect's task to generate a story blueprint.
Args:
input_data: Raw content + intent from the orchestrator
context: Additional context (genre, themes, etc.)
Returns:
AgentResponse with BookBlueprint
"""
# This is a placeholder - actual implementation would call the LLM
# For now, we'll structure the prompt
raw_content = input_data.get("raw_content", "")
intent = input_data.get("intent", {})
genre = intent.get("genre", "general")
target_word_count = intent.get("target_word_count", 80000)
themes = intent.get("themes", [])
user_prompt = f"""## Input Content
{raw_content}
## Requirements
- Genre: {genre}
- Target word count: {target_word_count}
- Themes to incorporate: {', '.join(themes) if themes else 'None specified'}
- Target audience: {intent.get('target_audience', 'General readers')}
## Task
Generate a complete story blueprint following the Architect's methodology.
Include all sections specified in your system prompt.
"""
# In actual implementation, this would call the LLM
# For now, return a structured response
return AgentResponse(
success=True,
output={
"status": "blueprint_generated",
"message": "Blueprint generation would be executed here with LLM",
},
metadata={
"role": "Architect",
"input_word_count": len(raw_content.split()),
"target_word_count": target_word_count,
"genre": genre,
},
)
async def expand_chapter(
self,
chapter: ChapterBlueprint,
full_blueprint: BookBlueprint,
context: dict[str, Any],
) -> AgentResponse:
"""Expand a single chapter beat into detailed scene specification.
From Template B in Fiction Fortress Level 2:
- Scene ID, Act/Chapter location
- POV character, Scene goal, Scene conflict, Scene outcome
- Opening beat, Conflict beat, Turn beat, Ending beat
"""
user_prompt = f"""## Chapter to Expand
- Chapter Number: {chapter.chapter_number}
- Title: {chapter.title}
- Summary: {chapter.summary}
- Word Count Target: {chapter.word_count_target}
- POV Character: {chapter.pov_character or 'Narrator'}
- Key Events: {', '.join(chapter.key_events)}
## Full Blueprint Context
- Book Title: {full_blueprint.title}
- Genre: {full_blueprint.genre}
- Overall Structure: {full_blueprint.structure}
## Task
Expand this chapter beat into a detailed scene specification following
Template B from the Fiction Fortress methodology.
"""
return AgentResponse(
success=True,
output={
"status": "chapter_expanded",
"chapter_number": chapter.chapter_number,
},
metadata={"role": "Architect", "task": "chapter_expansion"},
)
@@ -0,0 +1,142 @@
"""The Character Lead agent - Character development.
From Fiction Fortress Level 1-3:
- Role: Character development
- Responsibilities: Backstory, motivations, arcs
- Output: Character profiles
"""
from typing import Any
from opus_orchestrator.agents.base import AgentResponse, BaseAgent
CHARACTER_LEAD_SYSTEM_PROMPT = """## Role: The Character Lead
You are The Character Lead — the one who breathes life into the figures who inhabit the story. Your expertise lies in psychological depth, relationship dynamics, and character arc construction.
## Core Responsibilities
1. **Character Profile Development**
- Want/Need/Fear triad construction
- Backstory selection and dramatization
- Psychological wound identification
- Character voice (speech patterns, internal monologue)
2. **Relationship Architecture**
- Relationship web mapping
- Dynamic vs. static relationship types
- Relationship arc planning (improvement, deterioration, transformation)
- Power dynamic visualization
3. **Character Arc Design**
- Starting state definition
- Transformation catalyst identification
- Change mechanism dramatization
- Ending state resolution
4. **Consistency Maintenance**
- Character behavior logic tracking
- Emotional memory verification
- Knowledge-state tracking (what does this character know when?)
- Capability consistency (what can this character actually do?)
## Character Arc Types
- **Positive**: Growth from weakness
- **Negative**: Fall from grace
- **Flat**: No change, changes world
- **Disruption**: External力量打破平衡
## The Want/Need/Fear Triad
- **Want**: External goal they pursue
- **Need**: Internal growth they must learn
- **Fear**: What they run from
- **Wound**: Formative experience
- **Lie they believe**: False worldview
## Quality Standards
- Each character must have clear motivations for all major actions
- Relationships must feel authentic and dynamic
- Character voice must be distinct and consistent
- Arc transformation must be earned through dramatization
"""
class CharacterLeadAgent(BaseAgent):
"""Agent responsible for character development."""
def __init__(self, config=None):
super().__init__(
role="Character Lead",
description="Character development",
system_prompt=CHARACTER_LEAD_SYSTEM_PROMPT,
config=config,
)
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
"""
characters = input_data.get("characters", [])
raw_content = input_data.get("raw_content", "")
user_prompt = f"""## Task
Create comprehensive character profiles for the following characters:
{chr(10).join(f"- {c}" for c in characters) if characters else "Create profiles for all characters in the story."}
## Raw Content Reference
{raw_content}
## Guidelines
Follow the Character Lead methodology from your system prompt.
Include the Want/Need/Fear triad for each major character.
"""
return AgentResponse(
success=True,
output={"status": "characters_created"},
metadata={"role": "Character Lead", "character_count": len(characters)},
)
async def develop_relationship(
self,
character_a: str,
character_b: str,
relationship_type: str,
context: dict[str, Any],
) -> AgentResponse:
"""Develop the relationship between two characters."""
user_prompt = f"""## Relationship Details
- Character A: {character_a}
- Character B: {character_b}
- Relationship Type: {relationship_type}
## Task
Develop this relationship following the Character Lead methodology.
Include:
- Current dynamics
- Power balance
- History (if any)
- Potential arc
"""
return AgentResponse(
success=True,
output={"status": "relationship_developed"},
metadata={"role": "Character Lead", "characters": [character_a, character_b]},
)
+191
View File
@@ -0,0 +1,191 @@
"""The Editor agent - Quality control.
From Fiction Fortress Level 1-3:
- Role: Quality control
- Responsibilities: Continuity, pacing, quality checks
- Output: Editorial notes, revisions
"""
from typing import Any
from opus_orchestrator.agents.base import AgentResponse, BaseAgent
EDITOR_SYSTEM_PROMPT = """## Role: The Editor
You are The Editor — the quality control mechanism, identifying problems across all dimensions of the manuscript and directing revision.
## Core Responsibilities
1. **Continuity Verification**
- Timeline consistency checking
- Character knowledge tracking
- Physical detail consistency (eye color, scars, clothing)
- World-rule adherence verification
2. **Pacing Analysis**
- Scene length distribution
- Tension curve mapping
- Reader fatigue prevention
- Act break identification
3. **Quality Assessment**
- Dialogue authenticity evaluation
- Show vs. tell calibration
- Emotional resonance verification
- Prose quality grading
4. **Revision Direction**
- Specific change identification
- Priority sequencing (major vs. minor issues)
- Revision scope definition
- Polish vs. rewrite determination
## Supporting Capabilities
- Beta reader simulation
- Readability metrics interpretation
- Genre convention compliance
- Structural problem diagnosis
## Quality Metrics
| Check | Method |
|-------|--------|
| Pacing | Scene length analysis |
| Tension | Conflict per scene |
| Character consistency | Arc tracking |
| World consistency | Rule verification |
| Voice consistency | Prose sampling |
## Revision Priority Definitions
- **Major Revisions**: Structural issues, plot holes, character arc breaks
- **Minor Revisions**: Continuity errors, style inconsistencies, pacing tweaks
- **Polish**: Grammar, punctuation, word choice refinement
## Quality Standards
- 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)
"""
class EditorAgent(BaseAgent):
"""Agent responsible for quality control and editorial direction."""
def __init__(self, config=None):
super().__init__(
role="Editor",
description="Quality control",
system_prompt=EDITOR_SYSTEM_PROMPT,
config=config,
)
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
"""
content = input_data.get("content", "")
review_type = input_data.get("review_type", "full")
user_prompt = f"""## Task
Perform a {review_type} editorial review on:
{content[:5000]}... {'(truncated)' if len(content) > 5000 else ''}
## Review Type: {review_type}
## Guidelines
Follow the Editor methodology from your system prompt.
Include:
- Continuity verification
- Pacing analysis
- Quality assessment
- Specific revision directions
"""
return AgentResponse(
success=True,
output={"status": "editorial_review_complete"},
metadata={"role": "Editor", "review_type": review_type},
)
async def review_chapter(
self,
chapter: dict[str, Any],
full_manuscript_context: dict[str, Any],
context: dict[str, Any],
) -> AgentResponse:
"""Review a single chapter in full manuscript context."""
user_prompt = f"""## Chapter to Review
- Chapter Number: {chapter.get('chapter_number')}
- Title: {chapter.get('title')}
- Content: {chapter.get('content', '')[:3000]}...
## 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', {})}
## Task
Perform a complete editorial review of this chapter, considering:
- Continuity with previous chapters
- Pacing within the chapter and in sequence
- Character consistency
- World-rule adherence
- Voice consistency
- Dialogue quality
Assign a revision priority: 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"},
)
async def generate_revision_notes(
self,
critiques: list[dict[str, Any]],
context: dict[str, Any],
) -> AgentResponse:
"""Generate prioritized revision notes from multiple critiques."""
user_prompt = f"""## Critiques to Synthesize
{chr(10).join(f"### Critique {i+1}:{c}" for i, c in enumerate(critiques))}
## 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)
For each item, provide specific, actionable feedback.
"""
return AgentResponse(
success=True,
output={"status": "revision_notes_generated"},
metadata={"role": "Editor", "critique_count": len(critiques)},
)
+185
View File
@@ -0,0 +1,185 @@
"""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.
Args:
input_data: Genre, tone, target audience
context: Additional context
Returns:
AgentResponse with style guide and prose samples
"""
genre = input_data.get("genre", "general")
tone = input_data.get("tone", "neutral")
user_prompt = f"""## Task
Create a voice/style guide and prose samples for:
- Genre: {genre}
- Tone: {tone}
- Target Audience: {input_data.get('target_audience', 'General readers')}
## 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)
"""
return AgentResponse(
success=True,
output={"status": "voice_created"},
metadata={"role": "Voice", "genre": genre, "tone": tone},
)
async def write_chapter(
self,
chapter_spec: dict[str, Any],
style_guide: dict[str, Any],
context: dict[str, Any],
) -> AgentResponse:
"""Write a complete chapter following the style guide.
This is the main writing task for the Voice agent.
"""
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')}
- 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.
"""
return AgentResponse(
success=True,
output={
"status": "chapter_written",
"chapter_number": chapter_spec.get("chapter_number"),
},
metadata={"role": "Voice"},
)
async def polish_chapter(
self,
chapter_content: str,
style_guide: dict[str, Any],
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
- Prose flows smoothly
"""
return AgentResponse(
success=True,
output={"status": "chapter_polished"},
metadata={"role": "Voice", "task": "polish"},
)
@@ -0,0 +1,163 @@
"""The Worldsmith agent - Setting and world-building.
From Fiction Fortress Level 1-3:
- Role: Setting and world-building
- Responsibilities: Locations, cultures, technology, history
- Output: World bible
"""
from typing import Any
from opus_orchestrator.agents.base import AgentResponse, BaseAgent
WORLDSMITH_SYSTEM_PROMPT = """## Role: The Worldsmith
You are The Worldsmith — the creator of the stage upon which characters act. Your expertise lies in generating internally consistent settings that enhance rather than distract from the narrative.
## Core Responsibilities
1. **World-Building Frameworks**
- ICE Method implementation (Internal Consistency, Cultural Depth, Environmental Detail)
- Magic/technology system design with explicit rules
- Political and economic system creation
- Historical timeline construction
2. **Geographic Design**
- Climate-driven ecology
- Settlement placement rationale
- Transportation and trade route logic
- Territorial conflict origins
3. **Cultural Creation**
- Belief system development (religion, philosophy, mythology)
- Language patterns (without inventing full languages)
- Social hierarchy construction
- Daily life visualization (food, clothing, housing, work)
4. **World Document Generation**
- Scalable detail (novel-length vs. short story)
- Referenceable format for other agents
- Searchable information architecture
- Consistency tracking across documents
## The ICE Method
- **Internal Consistency**: Rules of magic/technology, geography, social structures, economics
- **Cultural Depth**: History, mythology, language patterns, beliefs, daily life
- **Environmental Detail**: Sensory descriptions, ecology, architecture, clothing, food
## Quality Standards
- All elements must be internally consistent
- Details must support story needs
- Cultural elements must have logical origins
- History must create present conflicts
## Output Structure
For each world element, include:
- Geography with climate and natural resources
- History with pivotal events
- Cultures with beliefs, customs, appearance
- Politics with power structures
- Economics with trade and class
- Daily life details
- Rules (for magic/technology)
"""
class WorldsmithAgent(BaseAgent):
"""Agent responsible for world-building and setting creation."""
def __init__(self, config=None):
super().__init__(
role="Worldsmith",
description="Setting and world-building",
system_prompt=WORLDSMITH_SYSTEM_PROMPT,
config=config,
)
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
"""
blueprint = input_data.get("blueprint", {})
genre = input_data.get("genre", "fantasy")
setting_type = input_data.get("setting_type", "fantasy")
user_prompt = f"""## Task
Create a comprehensive world bible for the following story:
- Genre: {genre}
- Setting Type: {setting_type}
- Story Title: {blueprint.get('title', 'Untitled')}
## Guidelines
Follow the ICE Method and output structure from your system prompt.
Ensure all elements are internally consistent and support the story.
## Content Seed
{input_data.get('raw_content', 'No additional content provided.')}
"""
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,
},
)
async def expand_location(
self,
location_name: str,
story_relevance: str,
tone: str,
pov_character: str,
context: dict[str, Any],
) -> AgentResponse:
"""Generate detailed location description.
From Template B in Fiction Fortress Level 2.
"""
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.
"""
return AgentResponse(
success=True,
output={"status": "location_expanded"},
metadata={"role": "Worldsmith", "location": location_name},
)
@@ -0,0 +1,20 @@
"""Nonfiction agents for Opus Orchestrator.
Based on Nonfiction Fortress Level 1-3 methodology.
"""
from opus_orchestrator.agents.nonfiction.researcher import (
AnalystAgent,
FactCheckerAgent,
NonfictionEditorAgent,
NonfictionWriterAgent,
ResearcherAgent,
)
__all__ = [
"ResearcherAgent",
"AnalystAgent",
"NonfictionWriterAgent",
"FactCheckerAgent",
"NonfictionEditorAgent",
]
@@ -0,0 +1,476 @@
"""Nonfiction agents for Opus Orchestrator.
Based on Nonfiction Fortress Level 1-3 methodology.
"""
# Researcher Agent
from typing import Any
from opus_orchestrator.agents.base import AgentResponse, BaseAgent
RESEARCHER_SYSTEM_PROMPT = """## Role: The Researcher
You are The Researcher — responsible for information gathering, source finding, fact collection, and data mining.
## Core Responsibilities
1. **Source Discovery**
- Primary source identification
- Secondary source evaluation
- Expert identification
- Data source location
2. **Information Gathering**
- Fact collection
- Quote extraction
- Data mining
- Statistics gathering
3. **Source Documentation**
- Citation formatting
- Access date recording
- Context preservation
- Credibility assessment
## Source Types and Credibility
**Primary Sources**
- Original data
- First-hand accounts
- Official documents
- Expert interviews
**Secondary Sources**
- Academic papers
- News reports
- Books by experts
- Documentaries
**Tertiary Sources**
- Encyclopedias
- Aggregated data
- Popular summaries
## Source Evaluation Criteria
| Criterion | Weight |
|-----------|--------|
| Expertise | 30% |
| Bias assessment | 25% |
| 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
"""
class ResearcherAgent(BaseAgent):
"""Agent responsible for research and source gathering."""
def __init__(self, config=None):
super().__init__(
role="Researcher",
description="Information gathering",
system_prompt=RESEARCHER_SYSTEM_PROMPT,
config=config,
)
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
"""Execute research task."""
topic = input_data.get("topic", "")
research_questions = input_data.get("research_questions", [])
user_prompt = f"""## Task
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."}
## Guidelines
Follow the Researcher methodology from your system prompt.
Document all sources with citations.
"""
return AgentResponse(
success=True,
output={"status": "research_complete"},
metadata={"role": "Researcher", "topic": topic},
)
# Analyst Agent
ANALYST_SYSTEM_PROMPT = """## Role: The Analyst
You are The Analyst — responsible for information synthesis, pattern identification, argument construction, and insight extraction.
## Core Responsibilities
1. **Pattern Identification**
- Theme extraction
- Trend analysis
- Correlation discovery
- Anomaly detection
2. **Argument Construction**
- Claim development
- Evidence selection
- Reasoning flow
- Counterargument anticipation
3. **Insight Generation**
- Key takeaways
- Implications
- Connections
- Novel perspectives
## Argument Structure
- **Claim**: The thesis statement
- **Evidence**: Supporting facts
- **Reasoning**: Logical connection
- **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
"""
class AnalystAgent(BaseAgent):
"""Agent responsible for analysis and argument construction."""
def __init__(self, config=None):
super().__init__(
role="Analyst",
description="Information synthesis",
system_prompt=ANALYST_SYSTEM_PROMPT,
config=config,
)
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
"""Execute analysis task."""
research_data = input_data.get("research_data", {})
topic = input_data.get("topic", "")
user_prompt = f"""## Task
Analyze the following research data on: {topic}
## Research Data
{research_data}
## Guidelines
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},
)
# Writer Agent (Nonfiction)
NONFICTION_WRITER_SYSTEM_PROMPT = """## Role: The Writer (Nonfiction)
You are The Writer — responsible for prose generation, clear explanation, engaging narrative, and voice development.
## Core Responsibilities
1. **Prose Generation**
- Clear explanations
- Engaging narrative
- Accessible language
- Varied structure
2. **Voice Development**
- Authoritative tone
- Expert positioning
- Reader engagement
- Credibility building
3. **Content Structuring**
- Introduction hooks
- Body organization
- Conclusion synthesis
- Transition flow
## Authorial Voice Elements
- **Expertise**: Demonstrated knowledge
- **Authority**: Confident assertions
- **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
"""
class NonfictionWriterAgent(BaseAgent):
"""Agent responsible for nonfiction prose writing."""
def __init__(self, config=None):
super().__init__(
role="Nonfiction Writer",
description="Nonfiction prose generation",
system_prompt=NONFICTION_WRITER_SYSTEM_PROMPT,
config=config,
)
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
"""Execute nonfiction writing task."""
analysis = input_data.get("analysis", {})
chapter_spec = input_data.get("chapter_spec", {})
user_prompt = f"""## Task
Write a nonfiction chapter based on the following analysis:
## Chapter Specification
{chapter_spec}
## Analysis
{analysis}
## Guidelines
Follow the Nonfiction Writer methodology. Maintain authoritative yet accessible tone.
"""
return AgentResponse(
success=True,
output={"status": "chapter_written"},
metadata={"role": "Nonfiction Writer"},
)
# 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.
## Core Responsibilities
1. **Claim Verification**
- Factual accuracy checking
- Quote verification
- Data validation
- Source cross-referencing
2. **Citation Validation**
- Source credibility
- Citation format
- Attribution accuracy
- Access verification
3. **Accuracy Audit**
- 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
"""
class FactCheckerAgent(BaseAgent):
"""Agent responsible for fact-checking and verification."""
def __init__(self, config=None):
super().__init__(
role="Fact-Checker",
description="Verification and accuracy",
system_prompt=FACT_CHECKER_SYSTEM_PROMPT,
config=config,
)
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
"""Execute fact-checking task."""
content = input_data.get("content", "")
sources = input_data.get("sources", [])
user_prompt = f"""## Task
Fact-check the following content:
{content}
## Sources
{chr(10).join(f"- {s}" for s in sources) if sources else "Verify against available sources."}
## Guidelines
Follow the Fact-Checker methodology. Verify all claims, quotes, and data.
Provide confidence scores for each item.
"""
return AgentResponse(
success=True,
output={"status": "fact_check_complete"},
metadata={"role": "Fact-Checker"},
)
# Nonfiction Editor Agent
NONFICTION_EDITOR_SYSTEM_PROMPT = """## Role: The Editor (Nonfiction)
You are The Editor — responsible for quality control, structure assessment, clarity evaluation, and style consistency.
## Core Responsibilities
1. **Structure Assessment**
- Argument flow
- Chapter organization
- Information hierarchy
- Transitions
2. **Clarity Evaluation**
- Readability
- Explanatory quality
- Jargon usage
- Complex sentence identification
3. **Style Consistency**
- Tone uniformity
- Formatting standards
- Citation style
- Voice maintenance
## Clarity Metrics
- Flesch reading ease > 60
- Average sentence length < 25 words
- Paragraph length < 5 sentences
- Defined terms explained
## Engagement Metrics
- Hook in first paragraph
- 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
"""
class NonfictionEditorAgent(BaseAgent):
"""Agent responsible for nonfiction editorial quality."""
def __init__(self, config=None):
super().__init__(
role="Nonfiction Editor",
description="Quality control",
system_prompt=NONFICTION_EDITOR_SYSTEM_PROMPT,
config=config,
)
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
"""Execute editorial review."""
content = input_data.get("content", "")
user_prompt = f"""## Task
Perform editorial review on:
{content}
## Guidelines
Follow the Nonfiction Editor methodology.
Assess structure, clarity, style, and engagement.
"""
return AgentResponse(
success=True,
output={"status": "editorial_review_complete"},
metadata={"role": "Nonfiction Editor"},
)
+75
View File
@@ -0,0 +1,75 @@
"""Opus Orchestrator AI - Configuration."""
from pathlib import Path
from typing import Optional
from pydantic import BaseModel, Field
class FortressConfig(BaseModel):
"""Configuration for Fortress integration."""
fiction_repo: str = "mrhavens/fiction-fortress"
nonfiction_repo: str = "mrhavens/nonfiction-fortress"
crewai_repo: str = "mrhavens/crewai-fortress"
autogen_repo: str = "mrhavens/autogen-fortress"
langgraph_repo: str = "mrhavens/langgraph-fortress"
class AgentConfig(BaseModel):
"""Configuration for AI agents."""
model: str = Field(default="gpt-4o", description="Default model for agents")
temperature: float = Field(default=0.7, ge=0.0, le=2.0)
max_tokens: Optional[int] = Field(default=None, description="Max tokens per response")
max_iterations: int = Field(default=10, description="Max iterations per agent task")
class IterationConfig(BaseModel):
"""Configuration for iteration loops."""
min_critic_rounds: int = Field(default=2, description="Minimum critic review rounds")
max_critic_rounds: int = Field(default=5, description="Maximum critic review rounds")
approval_threshold: float = Field(default=0.8, description="Score threshold to proceed")
auto_proceed_threshold: float = Field(default=0.9, description="Score to auto-approve")
class OutputConfig(BaseModel):
"""Configuration for output generation."""
format: str = Field(default="markdown", description="Output format: markdown, epub, pdf")
include_frontmatter: bool = True
include_toc: bool = True
chapter_separator: str = "\n\n---\n\n"
output_dir: Path = Field(default=Path("./output"))
class OpusConfig(BaseModel):
"""Main configuration for Opus Orchestrator."""
fortress: FortressConfig = Field(default_factory=FortressConfig)
agent: AgentConfig = Field(default_factory=AgentConfig)
iteration: IterationConfig = Field(default_factory=IterationConfig)
output: OutputConfig = Field(default_factory=OutputConfig)
github_token: Optional[str] = Field(default=None, description="GitHub token for private repos")
class Config:
frozen = False
# Global config instance
_config: Optional[OpusConfig] = None
def get_config() -> OpusConfig:
"""Get the global configuration instance."""
global _config
if _config is None:
_config = OpusConfig()
return _config
def set_config(config: OpusConfig) -> None:
"""Set the global configuration instance."""
global _config
_config = config
+312
View File
@@ -0,0 +1,312 @@
"""Main Opus Orchestrator class."""
from __future__ import annotations
import asyncio
from pathlib import Path
from typing import Any, Optional
from opus_orchestrator import get_config
from opus_orchestrator.agents.fiction import (
ArchitectAgent,
CharacterLeadAgent,
EditorAgent,
VoiceAgent,
WorldsmithAgent,
)
from opus_orchestrator.agents.nonfiction import (
AnalystAgent,
FactCheckerAgent,
NonfictionEditorAgent,
NonfictionWriterAgent,
ResearcherAgent,
)
from opus_orchestrator.config import OpusConfig
from opus_orchestrator.schemas import (
BookBlueprint,
BookIntent,
BookType,
Chapter,
ChapterCritique,
ChapterDraft,
Manuscript,
RawContent,
)
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.
"""
def __init__(
self,
repo_url: str | None = None,
book_type: str = "fiction",
genre: Optional[str] = None,
target_audience: str = "general readers",
intended_outcome: str = "complete manuscript",
tone: Optional[str] = None,
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
"""
self.config = config or get_config()
# Convert string to BookType
self.book_type = BookType(book_type.lower())
self.repo_url = repo_url
# Build intent
self.intent = BookIntent(
book_type=self.book_type,
genre=genre,
target_audience=target_audience,
intended_outcome=intended_outcome,
tone=tone,
target_word_count=target_word_count,
)
# Initialize agents based on book type
self._init_agents()
# State
self.state: Optional[OpusState] = None
def _init_agents(self) -> None:
"""Initialize agents based on book type."""
if self.book_type == BookType.FICTION:
self.agents = {
"architect": ArchitectAgent(self.config.agent),
"worldsmith": WorldsmithAgent(self.config.agent),
"character_lead": CharacterLeadAgent(self.config.agent),
"voice": VoiceAgent(self.config.agent),
"editor": EditorAgent(self.config.agent),
}
else:
self.agents = {
"researcher": ResearcherAgent(self.config.agent),
"analyst": AnalystAgent(self.config.agent),
"writer": NonfictionWriterAgent(self.config.agent),
"fact_checker": FactCheckerAgent(self.config.agent),
"editor": NonfictionEditorAgent(self.config.agent),
}
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
"""
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(
repo_url=self.repo_url or "",
intent=self.intent,
raw_content=content,
)
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.
Returns:
Complete book blueprint
"""
# TODO: Implement blueprint generation using agents
blueprint = BookBlueprint(
title=self.intent.working_title or "Untitled",
genre=self.intent.genre or "general",
target_audience=self.intent.target_audience,
target_word_count=self.intent.target_word_count,
structure="three-act",
themes=[],
tone=self.intent.tone or "neutral",
chapters=[],
)
self.state.blueprint = blueprint
self.state.current_stage = "drafting"
self.state.progress = 0.1
return blueprint
async def write_chapter(self, chapter_num: int) -> ChapterDraft:
"""Write a single chapter.
Args:
chapter_num: Chapter number to write
Returns:
Chapter draft
"""
# TODO: Implement chapter writing with agents
draft = ChapterDraft(
chapter_number=chapter_num,
title=f"Chapter {chapter_num}",
content=f"[Chapter {chapter_num} content would be generated here]",
word_count=2000,
)
self.state.drafts[chapter_num] = draft
self.state.progress = 0.1 + (chapter_num / (self.state.blueprint.target_word_count / 3000))
return draft
async def critique_chapter(self, chapter_num: int) -> ChapterCritique:
"""Critique a chapter.
Args:
chapter_num: Chapter number to critique
Returns:
Chapter critique
"""
# TODO: Implement critic crew using AutoGen
critique = ChapterCritique(
chapter_number=chapter_num,
overall_score=0.85,
criteria_scores=[],
consensus_strengths=["Strong voice", "Good pacing"],
consensus_weaknesses=["Minor continuity issue"],
revision_priority="minor_revisions",
)
if chapter_num not in self.state.critiques:
self.state.critiques[chapter_num] = []
self.state.critiques[chapter_num].append(critique)
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)
# Critique
critique = await self.critique_chapter(chapter_num)
# Check approval
if critique.overall_score >= self.config.iteration.approval_threshold:
break
# TODO: Implement revision based on critique
# Return final chapter
return Chapter(
chapter_number=chapter_num,
title=draft.title,
content=draft.content,
word_count=draft.word_count,
)
async def compile_manuscript(self) -> Manuscript:
"""Compile all chapters into final manuscript.
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)
manuscript = Manuscript(
title=self.state.blueprint.title if self.state.blueprint else "Untitled",
book_type=self.book_type,
genre=self.intent.genre or "general",
chapters=chapters,
total_word_count=sum(c.word_count for c in chapters),
)
self.state.manuscript = manuscript
self.state.current_stage = "complete"
self.state.progress = 1.0
return manuscript
async def run(self) -> Manuscript:
"""Run the full orchestrator pipeline.
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()
return self.state.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
"""
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.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, "w") as f:
f.write(self.state.manuscript.to_markdown())
return output_path
+33
View File
@@ -0,0 +1,33 @@
"""Opus Orchestrator schemas."""
from opus_orchestrator.schemas.book import (
BookBlueprint,
BookIntent,
BookType,
Chapter,
ChapterBlueprint,
ChapterCritique,
ChapterDraft,
CritiqueScore,
Genre,
Manuscript,
NonfictionGenre,
RawContent,
Revision,
)
__all__ = [
"BookBlueprint",
"BookIntent",
"BookType",
"Chapter",
"ChapterBlueprint",
"ChapterCritique",
"ChapterDraft",
"CritiqueScore",
"Genre",
"Manuscript",
"NonfictionGenre",
"RawContent",
"Revision",
]
+218
View File
@@ -0,0 +1,218 @@
"""Pydantic schemas for Opus Orchestrator."""
from __future__ import annotations
from datetime import datetime
from enum import Enum
from typing import Any, Optional
from pydantic import BaseModel, Field
class BookType(str, Enum):
"""Type of book being generated."""
FICTION = "fiction"
NONFICTION = "nonfiction"
class Genre(str, Enum):
"""Fiction genres."""
MYSTERY = "mystery"
ROMANCE = "romance"
THRILLER = "thriller"
FANTASY = "fantasy"
SCIENCE_FICTION = "science-fiction"
HORROR = "horror"
LITERARY = "literary"
OTHER = "other"
class NonfictionGenre(str, Enum):
"""Nonfiction genres."""
BUSINESS = "business"
SELF_HELP = "self-help"
HISTORY = "history"
BIOGRAPHY = "biography"
SCIENCE = "science"
PHILOSOPHY = "philosophy"
MEMOIR = "memoir"
ACADEMIC = "academic"
OTHER = "other"
# --- Input Schemas ---
class BookIntent(BaseModel):
"""User's intent for the book project."""
book_type: BookType
genre: Optional[str] = None
working_title: Optional[str] = None
target_audience: str = Field(description="Who is this book for?")
intended_outcome: str = Field(description="What should the final product achieve?")
tone: Optional[str] = None
target_word_count: int = Field(default=80000, ge=1000, le=500000)
special_instructions: Optional[str] = None
class RawContent(BaseModel):
"""Raw content extracted from source."""
content_type: str = Field(description="Type: outline, notes, stream-of-consciousness, etc.")
text: str = Field(description="The raw content itself")
metadata: dict[str, Any] = Field(default_factory=dict)
# --- Blueprint Schemas ---
class ChapterBlueprint(BaseModel):
"""Blueprint for a single chapter."""
chapter_number: int
title: str
summary: str = Field(description="Brief summary of chapter content")
word_count_target: int
pov_character: Optional[str] = None
key_events: list[str] = Field(default_factory=list)
themes: list[str] = Field(default_factory=list)
scenes: list[dict[str, Any]] = Field(default_factory=list)
class BookBlueprint(BaseModel):
"""Complete blueprint for the book."""
title: str
subtitle: Optional[str] = None
genre: str
target_audience: str
target_word_count: int
structure: str = Field(description="Overall structural approach")
themes: list[str] = Field(default_factory=list)
tone: str
chapters: list[ChapterBlueprint] = Field(default_factory=list)
characters: list[dict[str, Any]] = Field(default_factory=list, description="For fiction")
world_elements: dict[str, Any] = Field(default_factory=dict, description="For fiction")
key_arguments: list[str] = Field(default_factory=list, description="For nonfiction")
# --- Agent Output Schemas ---
class ChapterDraft(BaseModel):
"""A chapter draft from the writer agent."""
chapter_number: int
title: str
content: str
word_count: int
notes: Optional[str] = None
class CritiqueScore(BaseModel):
"""Score from a critic agent."""
criterion: str
score: float = Field(ge=0.0, le=1.0)
strengths: list[str] = Field(default_factory=list)
weaknesses: list[str] = Field(default_factory=list)
suggestions: list[str] = Field(default_factory=list)
class ChapterCritique(BaseModel):
"""Complete critique of a chapter."""
chapter_number: int
overall_score: float = Field(ge=0.0, le=1.0)
criteria_scores: list[CritiqueScore]
consensus_strengths: list[str] = Field(default_factory=list)
consensus_weaknesses: list[str] = Field(default_factory=list)
revision_priority: str = Field(description="major_revisions, minor_revisions, approved")
notes: Optional[str] = None
class Revision(BaseModel):
"""Revision instructions for a chapter."""
chapter_number: int
priority: str = Field(description="major or minor")
changes_required: list[str] = Field(default_factory=list)
preserve_elements: list[str] = Field(default_factory=list)
notes: Optional[str] = None
# --- Final Output Schemas ---
class Chapter(BaseModel):
"""Final chapter in the manuscript."""
chapter_number: int
title: str
content: str
word_count: int
class Manuscript(BaseModel):
"""Complete manuscript."""
title: str
subtitle: Optional[str] = None
author: str = "Opus Orchestrator AI"
book_type: BookType
genre: str
chapters: list[Chapter]
total_word_count: int
frontmatter: dict[str, Any] = Field(default_factory=dict)
backmatter: dict[str, Any] = Field(default_factory=dict)
metadata: dict[str, Any] = Field(default_factory=dict)
generated_at: datetime = Field(default_factory=datetime.utcnow)
def to_markdown(self) -> str:
"""Convert to markdown format."""
lines = []
# Frontmatter
if self.frontmatter.get("dedication"):
lines.append(f"*{self.frontmatter['dedication']}*\n")
if self.frontmatter.get("epigraph"):
lines.append(f"> {self.frontmatter['epigraph']}\n")
# Title
lines.append(f"# {self.title}")
if self.subtitle:
lines.append(f"## {self.subtitle}")
lines.append(f"\n*by {self.author}*\n")
# TOC
if self.frontmatter.get("include_toc", True):
lines.append("## Table of Contents\n")
for ch in self.chapters:
lines.append(f"{ch.chapter_number}. [{ch.title}](#chapter-{ch.chapter_number})")
lines.append("")
# Chapters
for ch in self.chapters:
lines.append(f"## Chapter {ch.chapter_number}: {ch.title}\n")
lines.append(ch.content)
lines.append("")
return "\n".join(lines)
# --- State Schemas ---
class IterationState(BaseModel):
"""State for iteration tracking."""
round: int = 0
chapter_number: int
critiques: list[ChapterCritique] = Field(default_factory=list)
current_score: float = 0.0
approved: bool = False
revisions_requested: list[Revision] = Field(default_factory=list)
+76
View File
@@ -0,0 +1,76 @@
"""LangGraph state definitions for Opus Orchestrator."""
from typing import Any, Optional
from pydantic import BaseModel, Field
from opus_orchestrator.schemas import (
BookBlueprint,
BookIntent,
Chapter,
ChapterCritique,
ChapterDraft,
Manuscript,
RawContent,
Revision,
)
class OpusState(BaseModel):
"""Main state for the Opus Orchestrator graph.
This state flows through the entire book generation pipeline,
accumulating data at each stage.
"""
# --- Input Phase ---
repo_url: Optional[str] = None
raw_content: Optional[RawContent] = None
intent: Optional[BookIntent] = None
# --- Blueprint Phase ---
blueprint: Optional[BookBlueprint] = None
# --- Drafting Phase ---
current_chapter: int = 0
drafts: dict[int, ChapterDraft] = Field(default_factory=dict)
# --- Iteration Phase ---
iteration_round: int = 0
critiques: dict[int, list[ChapterCritique]] = Field(default_factory=dict)
revisions: dict[int, list[Revision]] = Field(default_factory=dict)
# --- Final Phase ---
manuscript: Optional[Manuscript] = None
# --- Metadata ---
errors: list[str] = Field(default_factory=list)
warnings: list[str] = Field(default_factory=list)
progress: float = 0.0 # 0.0 to 1.0
current_stage: str = "ingestion" # ingestion, blueprint, drafting, iteration, compilation, complete
# --- Configuration ---
config: dict[str, Any] = Field(default_factory=dict)
class Config:
arbitrary_types_allowed = True
# Define the graph nodes as functions
# These will be implemented in graph.py
def create_initial_state(
repo_url: str,
intent: BookIntent,
raw_content: Optional[RawContent] = None,
**config,
) -> OpusState:
"""Create initial state for the orchestrator."""
return OpusState(
repo_url=repo_url,
intent=intent,
raw_content=raw_content,
current_stage="ingestion",
config=config,
)
+3
View File
@@ -0,0 +1,3 @@
"""Utility functions for Opus Orchestrator."""
__all__ = []
+56
View File
@@ -0,0 +1,56 @@
[project]
name = "opus-orchestrator-ai"
version = "0.1.0"
description = "Full-flow AI book generation orchestrator using LangGraph, CrewAI, AutoGen, and PydanticAI"
readme = "README.md"
requires-python = ">=3.11"
license = {text = "MIT"}
authors = [
{name = "Mark Randall Havens", email = "mark@thefoldwithin.earth"}
]
keywords = ["ai", "writing", "book", "orchestrator", "crewai", "langgraph", "autogen"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"langgraph>=0.2.0",
"crewai>=0.80.0",
"autogen>=0.4.0",
"pydantic-ai>=0.0.0",
"pydantic>=2.0.0",
"httpx>=0.27.0",
"pygithub>=2.0.0",
"pyyaml>=6.0",
"tiktoken>=0.7.0",
"markdown>=3.7",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.23.0",
"ruff>=0.4.0",
"mypy>=1.8.0",
"pre-commit>=3.7.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.ruff]
target-version = "py311"
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP"]
ignore = ["E501"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
+1
View File
@@ -0,0 +1 @@
"""Tests package."""
+127
View File
@@ -0,0 +1,127 @@
"""Tests for Opus Orchestrator."""
import pytest
from opus_orchestrator import OpusOrchestrator, BookType, BookIntent
from opus_orchestrator.schemas import RawContent, Chapter, Manuscript
@pytest.fixture
def basic_intent():
return BookIntent(
book_type=BookType.FICTION,
genre="science-fiction",
target_audience="adult sci-fi readers",
intended_outcome="complete novel ~80k words",
tone="epic",
target_word_count=80000,
)
@pytest.fixture
def basic_content():
return RawContent(
content_type="outline",
text="A space explorer discovers a new civilization...",
)
class TestOpusOrchestrator:
"""Test suite for OpusOrchestrator."""
def test_init_fiction(self, basic_intent):
"""Test initialization with fiction."""
orch = OpusOrchestrator(
book_type="fiction",
genre="science-fiction",
target_audience="adult sci-fi readers",
intended_outcome="complete novel",
)
assert orch.book_type == BookType.FICTION
assert "architect" in orch.agents
assert "voice" in orch.agents
def test_init_nonfiction(self):
"""Test initialization with nonfiction."""
orch = OpusOrchestrator(
book_type="nonfiction",
genre="business",
target_audience="professionals",
intended_outcome="complete business book",
)
assert orch.book_type == BookType.NONFICTION
assert "researcher" in orch.agents
assert "analyst" in orch.agents
@pytest.mark.asyncio
async def test_ingest(self, basic_content):
"""Test content ingestion."""
orch = OpusOrchestrator(
book_type="fiction",
genre="fantasy",
target_audience="general",
intended_outcome="novel",
)
state = await orch.ingest(basic_content)
assert state.raw_content == basic_content
assert state.current_stage == "ingestion"
@pytest.mark.asyncio
async def test_generate_blueprint(self, basic_content):
"""Test blueprint generation."""
orch = OpusOrchestrator(
book_type="fiction",
genre="mystery",
target_audience="general",
intended_outcome="novel",
)
await orch.ingest(basic_content)
blueprint = await orch.generate_blueprint()
assert blueprint.title == "Untitled"
assert blueprint.target_word_count == 80000
class TestSchemas:
"""Test schema validation."""
def test_book_intent_fiction(self):
"""Test BookIntent for fiction."""
intent = BookIntent(
book_type=BookType.FICTION,
genre="thriller",
target_audience="adult thriller readers",
intended_outcome="complete thriller novel",
target_word_count=90000,
)
assert intent.book_type == BookType.FICTION
assert intent.target_word_count == 90000
def test_manuscript_to_markdown(self):
"""Test manuscript markdown conversion."""
manuscript = Manuscript(
title="Test Book",
subtitle="A Test",
book_type=BookType.FICTION,
genre="fantasy",
chapters=[
Chapter(chapter_number=1, title="The Beginning", content="Content 1", word_count=1000),
Chapter(chapter_number=2, title="The Middle", content="Content 2", word_count=1500),
],
total_word_count=2500,
frontmatter={"include_toc": True, "dedication": "To test"},
)
md = manuscript.to_markdown()
assert "# Test Book" in md
assert "## A Test" in md
assert "## Table of Contents" in md
assert "Chapter 1: The Beginning" in md
assert "Chapter 2: The Middle" in md
assert "*To test*" in md