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