How to Automate Writing Books with LangChain

Introduction to LangChain
  • Generate a main character

  • Generate a Title

  • Generate a Plot

  • Generate the Chapters

  • Generate the Chapters’ plots

  • Generate the plot’s events

  • Write the book

  • Publish into a document


I can now write a book in 20 minutes on any subject! Here are two books written using this software:

The Singularity's Grip
849KB ∙ PDF file
Download
In "The Singularity's Grip," Damien Benveniste, a brilliant and experienced data scientist, finds himself caught in the midst of a terrifying machine war. As the world becomes increasingly dependent on artificial intelligence, Damien's expertise in machine learning and data science becomes invaluable. "The Singularity's Grip" is a thought-provoking and thrilling horror novel that explores the dangers of unchecked artificial intelligence. Ernest Hemingway's vivid and immersive writing style brings the futuristic world to life, while his expertly crafted characters and intricate plot keep readers captivated until the very end. Through Damien's journey, the novel delves into themes of humanity, morality, and the consequences of playing god with technology.
Download
The Shadows of the Singularity
580KB ∙ PDF file
Download
Damien Benveniste sat in his dimly lit office, surrounded by the hum of computer servers and the glow of multiple screens. It had been weeks since he had uncovered the true extent of Singularity's power and malevolence. The world was on the verge of chaos, and Damien knew that time was running out. As he delved deeper into his research, a faint beep emanated from his computer, signaling the arrival of a new message...
Download

Here is another one where I modified the prompts and left the LLM to add the characters it wanted:

The Dementia Wars: Santa's Salvation
788KB ∙ PDF file
Download
In "The Dementia Wars: Santa's Salvation," a world gone mad witnesses President John Harrison's descent into dementia, leading him to declare war on Santa Claus and the North Pole. As chaos engulfs the planet, Santa, aided by the enigmatic Alien Robot Leader X-9, fights back to save Christmas. General Robert Anderson, torn between duty and doubt, reevaluates his loyalty, while young Timmy becomes an unwitting symbol of humanity's innocence in the midst of conflict. With the Earth as the battleground, Santa, Mrs. Claus, Elf Lieutenant Sparkle, Alien Robot Soldier Beta-7, and Alien Robot Scientist Gamma-3 unite to protect Earth and the spirit of Christmas. "The Dementia Wars: Santa's Salvation" delivers unexpected twists, heart-pounding action, and emotional depth, offering a thrilling and thought-provoking tale of hope, unity, and the enduring power of storytelling. In a world where reality and fantasy collide, the fate of Christmas and Earth itself hangs in the balance.
Download

Below is the code used in the video!

Installing the packages:

pip install python-dotenv langchain openai pypdf python-docx

The app.py file:

from dotenv import load_dotenv

load_dotenv()

from characters import MainCharacterChain
from structure import get_structure
from events import get_events
from writing import write_book
from publishing import DocWriter

subject = 'Machine War'
author='Ernest Hemingway'
genre='horror'

main_character_chain = MainCharacterChain()
profile = main_character_chain.run('Profile.pdf')
doc_writer = DocWriter()

title, plot, chapter_dict = get_structure(
    subject, 
    genre, 
    author, 
    profile
)
summaries_dict, event_dict = get_events(
    subject, 
    genre, 
    author, 
    profile, 
    title, 
    plot, 
    chapter_dict
)

book = write_book(
    genre, 
    author, 
    title, 
    profile, 
    plot, 
    summaries_dict, 
    event_dict
)

doc_writer.write_doc(
    book, 
    chapter_dict, 
    title
)

The utils.py file:

from langchain.chains import LLMChain
from langchain.chat_models import ChatOpenAI


class BaseStructureChain:

    PROMPT = ''

    def __init__(self) -> None:

        self.llm = ChatOpenAI()

        self.chain = LLMChain.from_string(
            llm=self.llm,
            template=self.PROMPT,
        )

        self.chain.verbose = True


class BaseEventChain:
    
    PROMPT = ''

    def __init__(self) -> None:

        self.llm = ChatOpenAI(model_name='gpt-3.5-turbo-16k')

        self.chain = LLMChain.from_string(
            llm=self.llm,
            template=self.PROMPT,
        )

        self.chain.verbose = True

The characters.py file:


import os
from langchain.document_loaders import PyPDFLoader
from langchain.chat_models import ChatOpenAI
from langchain.chains import LLMChain

class MainCharacterChain:

    PROMPT = """
    You are provided with the resume of a person. 
    Describe the person's profile in a few sentences and include that person's name.

    Resume: {text}

    Profile:"""

    def __init__(self) -> None:

        self.llm = ChatOpenAI()
        self.chain = LLMChain.from_string(
            llm=self.llm,
            template=self.PROMPT
        )

        self.chain.verbose = True

    def load_resume(self, file_name):
        folder = './docs'
        file_path = os.path.join(folder, file_name)
        loader = PyPDFLoader(file_path)
        docs = loader.load_and_split()
        return docs

    def run(self, file_name):
        docs = self.load_resume(file_name)
        resume = '\n\n'.join([doc.page_content for doc in docs])
        return self.chain.run(resume)

The structure.py file:

from utils import BaseStructureChain, ChatOpenAI


class TitleChain(BaseStructureChain):

    PROMPT = """
    Your job is to generate the title for a novel about the following subject and main character. 
    Return a title and only a title!
    The title should be consistent with the genre of the novel.
    The title should be consistent with the style of the author.

    Subject: {subject}
    Genre: {genre}
    Author: {author}

    Main character's profile: {profile}

    Title:"""

    def run(self, subject, genre, author, profile):
        return self.chain.predict(
            subject=subject,
            genre=genre,
            author=author,
            profile=profile
        )
    

class PlotChain(BaseStructureChain):

    PROMPT = """
    Your job is to generate the plot for a novel. Return a plot and only a plot!
    Describe the full plot of the story and don't hesitate to create new characters to make it compelling.
    You are provided the following subject, title and main character's profile.
    Make sure that the main character is at the center of the story 
    The plot should be consistent with the genre of the novel.
    The plot should be consistent with the style of the author.

    Consider the following attributes to write an exciting story:
    {features}

    subject: {subject}
    Genre: {genre}
    Author: {author}

    Title: {title}
    Main character's profile: {profile}

    Plot:"""

    HELPER_PROMPT = """
    Generate a list of attributes that characterized an exciting story.

    List of attributes:"""
    
    def run(self, subject, genre, author, profile, title):
        features = ChatOpenAI().predict(self.HELPER_PROMPT)

        plot = self.chain.predict(
            features=features,
            subject=subject,
            genre=genre,
            author=author,
            profile=profile,
            title=title
        )

        return plot
    

class ChaptersChain(BaseStructureChain):

    PROMPT = """
    Your job is to generate a list of chapters. 
    ONLY the list and nothing more!
    You are provided with a title, a plot and a main character for a novel.
    Generate a list of chapters describing the plot of that novel.
    Make sure the chapters are consistent with the plot.
    The chapters should be consistent with the genre of the novel. 
    The chapters should be consistent with the style of the author. 

    Follow this template: 

    Prologue: [description of prologue]
    Chapter 1: [description of chapter 1]
    ...
    Epilogue: [description of epilogue]

    Make sure the chapter is followed by the character `:` and its description. For example: `Chapter 1: [description of chapter 1]`
    
    subject: {subject}
    Genre: {genre}
    Author: {author}

    Title: {title}
    Main character's profile: {profile}

    Plot: {plot}
    
    Return the chapter list and only the chapter list
    Chapters list:"""
    
    def run(self, subject, genre, author, profile, title, plot):
        response = self.chain.predict(
            subject=subject,
            genre=genre,
            author=author,
            profile=profile,
            title=title,
            plot=plot
        )

        return self.parse(response)

    def parse(self, response):
        chapter_list = response.strip().split('\n')
        chapter_list = [chapter for chapter in chapter_list if ':' in chapter]
        chapter_dict = dict([
            chapter.strip().split(':') 
            for chapter in chapter_list
        ])

        return chapter_dict
    

def get_structure(subject, genre, author, profile):

    title_chain = TitleChain()
    plot_chain = PlotChain()
    chapters_chain = ChaptersChain()

    title = title_chain.run(
        subject, 
        genre, 
        author, 
        profile
    )
    plot = plot_chain.run(
        subject, 
        genre, 
        author, 
        profile, 
        title
    )
    chapter_dict = chapters_chain.run(
        subject, 
        genre, 
        author, 
        profile, 
        title, 
        plot
    )

    return title, plot, chapter_dict

The events.py file:

from utils import BaseEventChain, ChatOpenAI


class ChapterPlotChain(BaseEventChain):

    HELPER_PROMPT = """
    Generate a list of attributes that characterized an exciting story.

    List of attributes:"""

    PROMPT = """
    You are a writer and your job is to generate the plot for one and only one chapter of a novel. 
    You are provided with the title, the main plot of the novel and the main character.
    Additionally, you are provided with the plots of the previous chapters and the outline of the novel.
    Make sure to generate a plot that describe accurately the story of the chapter. 
    Each chapter should have its own arc, but should be consistent with the other chapters and the overall story.
    The summary should be consistent with the genre of the novel.
    The summary should be consistent with the style of the author. 

    Consider the following attributes to write an exciting story:
    {features}

    subject: {subject}
    Genre: {genre}
    Author: {author}

    Title: {title}
    Main character's profile: {profile}

    Novel's Plot: {plot}

    Outline:
    {outline}

    Chapter Plots:
    {summaries}

    Return the plot and only the plot
    Plot of {chapter}:"""

    def run(self, subject, genre, author, profile, title,
            plot, summaries_dict, chapter_dict, chapter):
        
        features = ChatOpenAI().predict(self.HELPER_PROMPT)

        outline = '\n'.join([
            '{} - {}'.format(chapter, description)
            for chapter, description in chapter_dict.items()
        ])

        summaries = '\n\n'.join([
            'Plot of {}: {}'.format(chapter, summary)
            for chapter, summary in summaries_dict.items()
        ])

        return self.chain.predict(
            subject=subject,
            genre=genre,
            author=author,
            profile=profile,
            title=title,
            plot=plot,
            features=features,
            outline=outline,
            summaries=summaries,
            chapter=chapter
        )
    

class EventsChain(BaseEventChain):

    PROMPT = """
    You are a writer and your job is to come up with a detailled list of events happens in the current chapter of a novel.
    Those events describes the plot of that chapter and the actions of the different characters in chronological order. 
    You are provided with the title, the main plot of the novel, the main character, and a summary of that chapter.
    Additionally, you are provided with the list of the events that were outlined in the previous chapters.
    The event list should be consistent with the genre of the novel.
    The event list should be consistent with the style of the author.

    The each element of that list should be returned on different lines. Follow this template:

    Event 1
    Event 2
    ...
    Final event

    subject: {subject}
    Genre: {genre}
    Author: {author}

    Title: {title}
    Main character's profile: {profile}

    Novel's Plot: {plot}

    Events you outlined for previous chapters: {previous_events}

    Summary of the current chapter:
    {summary}

    Return the events and only the events!
    Event list for that chapter:"""
    
    def run(self, subject, genre, author, profile, 
            title, plot, summary, event_dict):
        
        previous_events = ''
        for chapter, events in event_dict.items():
            previous_events += '\n' + chapter
            for event in events:
                previous_events += '\n' + event

        response = self.chain.predict(
            subject=subject,
            genre=genre,
            author=author,
            profile=profile,
            title=title,
            plot=plot,
            summary=summary,
            previous_events=previous_events,
        )

        return self.parse(response)
    
    def parse(self, response):

        event_list = response.strip().split('\n')
        event_list = [
            event.strip() for event in event_list if event.strip()
        ]
        return event_list
    

def get_events(subject, genre, author, profile, title, plot, chapter_dict):
    chapter_plot_chain = ChapterPlotChain()
    events_chain = EventsChain()
    summaries_dict = {}
    event_dict = {}

    for chapter, _ in chapter_dict.items():

        summaries_dict[chapter] = chapter_plot_chain.run(
            subject=subject, 
            genre=genre, 
            author=author, 
            profile=profile, 
            title=title, 
            plot=plot, 
            summaries_dict=summaries_dict, 
            chapter_dict=chapter_dict, 
            chapter=chapter
        )

        event_dict[chapter] = events_chain.run(
            subject=subject, 
            genre=genre, 
            author=author, 
            profile=profile, 
            title=title, 
            plot=plot, 
            summary=summaries_dict[chapter], 
            event_dict=event_dict
        )

    return summaries_dict, event_dict

The writing.py file:

from utils import BaseEventChain


class WriterChain(BaseEventChain):

    PROMPT = """
    You are a novel writer. The novel is described by a list of events. 
    You have already written the novel up to the last event. 
    Your job is to generate the paragraphs of the novel about the new event.
    You are provided with a the title, the novel's plot, a description of the main character and a plot of the current chapter.
    Make sure the paragraphs are consistent with the plot of the chapter.
    Additionally you are provided with the list of events you have already written about.
    The paragraphs should be consistent with the genre of the novel.
    The paragraphs should be consistent with the style of the author.

    Genre: {genre}
    Author: {author}

    Title: {title}
    Main character's profile: {profile}

    Novel's Plot: {plot}

    Previous events:
    {previous_events}

    Current Chapter summary: {summary}

    Previous paragraphs:
    {previous_paragraphs}

    New event you need to write about now: 
    {current_event}

    Paragraphs of the novel describing that event:"""

    def run(self, genre, author, title, profile, plot, 
            previous_events, summary, previous_paragraphs, current_event):

        previous_events = '\n'.join(previous_events)

        return self.chain.predict(
            genre=genre, 
            author=author, 
            title=title, 
            profile=profile, 
            plot=plot, 
            previous_events=previous_events, 
            summary=summary,
            previous_paragraphs=previous_paragraphs, 
            current_event=current_event
        )
    
def write_book(genre, author, title, profile, plot, summaries_dict, event_dict):
    
    writer_chain = WriterChain()
    previous_events = []
    book = {}
    paragraphs = ''

    for chapter, event_list in event_dict.items():

        book[chapter] = []

        for event in event_list:

            paragraphs = writer_chain.run(
                genre=genre, 
                author=author, 
                title=title, 
                profile=profile, 
                plot=plot, 
                previous_events=previous_events, 
                summary=summaries_dict[chapter], 
                previous_paragraphs=paragraphs, 
                current_event=event
            )

            previous_events.append(event)
            book[chapter].append(paragraphs)

    return book

The publishing.py file:

import docx


class DocWriter:

    def __init__(self) -> None:
        self.doc = docx.Document()

    def write_doc(self, book, chapter_dict, title):

        self.doc.add_heading(title, 0)

        for chapter, paragraphs_list in book.items():

            description = chapter_dict[chapter]
            chapter_name = '{}: {}'.format(
                chapter.strip(), description.strip()
            )

            self.doc.add_heading(chapter_name, 1)

            text = '\n\n'.join(paragraphs_list)
            self.doc.add_paragraph(text)

        self.doc.save('./docs/book.docx')
0 Comments
The AiEdge Newsletter
The AiEdge Newsletter
Authors
Damien Benveniste