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:
@@ -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.*
|
||||
|
||||
@@ -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
|
||||
@@ -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]},
|
||||
)
|
||||
@@ -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)},
|
||||
)
|
||||
@@ -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"},
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
"""Utility functions for Opus Orchestrator."""
|
||||
|
||||
__all__ = []
|
||||
@@ -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"]
|
||||
@@ -0,0 +1 @@
|
||||
"""Tests package."""
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user