mikavehns / BookGPT

Writes complete books with given paramters, using GPT-3.
MIT License
353 stars 66 forks source link

This model's maximum context length is 4097 tokens. However, your messages resulted in 4113 tokens. Please reduce the length of the messages. #18

Open cometbus opened 1 year ago

cometbus commented 1 year ago

This model's maximum context length is 4097 tokens. However, your messages resulted in 4113 tokens. Please reduce the length of the messages.

every time I try to run it

cometbus commented 1 year ago

I don't know how to do pull requests, but I fixed it.

book.py import openai from tqdm import tqdm import prompts import tiktoken import json

enc = tiktoken.get_encoding("cl100k_base")

class Book: def init(self, **kwargs):

Joining the keyword arguments into a single string

    self.arguments = '; '.join([f'{key}: {value}' for key, value in kwargs.items() if key != 'tolerance'])

# Get 'tolerance' attribute from kwargs
    self.tolerance = kwargs.get('tolerance', 0.9)

# Assign a status variable
    self.status = 0

# Setting up the base prompt
    self.base_prompt = [
        self.get_message('system', prompts.INITIAL_INSTRUCTIONS),
        self.get_message('user', self.arguments),
        self.get_message('assistant', 'Ready')
]

# Setting up the title prompt
    self.title_prompt = [
        self.get_message('system', prompts.TITLE_INSTRUCTIONS),
        self.get_message('assistant', 'Ready'),
        self.get_message('user', self.arguments)
]

# Setting up the structure prompt
    self.structure_prompt = [
        self.get_message('system', prompts.STRUCTURE_INSTRUCTIONS),
        self.get_message('assistant', 'Ready'),
]

# Initialize the title attribute
    self.title = None

    self.output('Prompts set up. Ready to generate book.')

def get_title(self):
    if not self.title:
        self.title = Book.get_response(prompt=self.title_prompt, tokens_needed=20)
    return self.title

def get_structure(self):
    if not hasattr(self, 'title'):
        self.output('Title not generated. Please generate title first.')
        return
    else:
        structure_arguments = self.arguments + f'; title: {self.title}'
        self.structure_prompt.append(self.get_message('user', structure_arguments))
        self.structure = self.get_response(self.structure_prompt)
        self.chapters = self.convert_structure(self.structure)
        self.paragraph_amounts = self.get_paragraph_amounts(self.chapters)
        self.paragraph_words = self.get_paragraph_words(self.chapters)
        return self.structure, self.chapters

def finish_base(self):
    if not hasattr(self, 'title'):
        self.output('Title not generated. Please generate title first.')
        return
    elif not hasattr(self, 'structure'):
        self.output('Structure not generated. Please generate structure first.')
        return
    else:
        self.base_prompt.append(self.get_message('user', '!t'))
        self.base_prompt.append(self.get_message('assistant', self.title))

        self.base_prompt.append(self.get_message('user', '!s'))
        self.base_prompt.append(self.get_message('assistant', self.structure))
        return self.base_prompt

def calculate_max_status(self):
    if not hasattr(self, 'chapters'):
        self.output('Structure not generated. Please generate structure first.')
        return
    else:
        self.max_status = sum(self.get_paragraph_amounts(self.chapters))
        return self.max_status

def get_content(self):
    chapters = []
    for i in tqdm(range(len(self.chapters))):
        prompt = self.base_prompt.copy()
        chapter = self.get_chapter(i, prompt.copy())
        chapters.append(chapter)
    self.content = chapters
    return self.content

def save_book(self):
    # Save the book in md format
    with open(f'book.md', 'w') as file:
        file.write(f'# {self.title}\n\n')
        for chapter in self.content:
            file.write(f'## {self.chapters[self.content.index(chapter)]["title"]}\n\n')
            for paragraph in chapter:
                file.write(
                    f'### {self.chapters[self.content.index(chapter)]["paragraphs"][chapter.index(paragraph)]["title"]}\n\n')
                file.write(paragraph + '\n\n')
            file.write('\n\n')

def get_chapter(self, chapter_index, prompt):
    if len(self.base_prompt) == 3:
        self.finish_base()

    paragraphs = []
    for i in range(self.paragraph_amounts[chapter_index]):
        paragraph = self.get_paragraph(prompt.copy(), chapter_index, i)
        prompt.append(self.get_message('user', f'!w {chapter_index + 1} {i + 1}'))
        prompt.append(self.get_message('assistant', paragraph))
        self.status += 1
        paragraphs.append(paragraph)
    return paragraphs

def get_paragraph(self, prompt, chapter_index, paragraph_index):
    prompt.append(self.get_message('user', f'!w {chapter_index + 1} {paragraph_index + 1}'))
    paragraph = ""
    response = ""

    available_tokens = 4096 - len(enc.encode(json.dumps(prompt))) - 100

    while len(paragraph.split(' ')) < int(self.paragraph_words[chapter_index][paragraph_index] * self.tolerance) and available_tokens > 0:
        try:
            response = Book.get_response(prompt)
            paragraph += response
            prompt.append(self.get_message('assistant', response))
            prompt.append(self.get_message('system', '!c'))
            available_tokens -= len(enc.encode(response))
        except ValueError:
            response = None
            break

    return paragraph

@staticmethod
def get_message(role, content):
    return {"role": role, "content": content}

@staticmethod
def convert_structure(structure):
    chapters = structure.split("Chapter")
    chapters = [x for x in chapters if x != '']
    chapter_information = []

    for chapter in chapters:
        for line in chapter.split("\n"):
            if 'paragraphs' in line.lower():
                split_line = line.split('): ')
                if len(split_line) > 1:
                    chapter_information.append({'title': split_line[1], 'paragraphs': []})
            elif 'paragraph' in line.lower():
                chapter_information[-1]['paragraphs'].append(
                    {'title': line.split('): ')[1], 'words': line.split('(')[1].split(')')[0].split(' ')[0]})
        chapter_information[-1]['paragraph_amount'] = len(chapter_information[-1]['paragraphs'])

    return chapter_information

@staticmethod
def get_paragraph_amounts(structure):
    amounts = []
    for chapter in structure:
        amounts.append(chapter['paragraph_amount'])
    return amounts

@staticmethod
def get_paragraph_words(structure):
    words = []
    for chapter in structure:
        words.append([int(x['words']) for x in chapter['paragraphs']])
    return words

@staticmethod
def get_response(prompt, tokens_needed=None):
    enc = tiktoken.encoding_for_model("gpt-3.5-turbo")
    token_count = len(enc.encode(json.dumps(prompt)))
    print(f"Token count for the prompt: {token_count}")
    max_tokens = 4096
    if token_count >= max_tokens:
        raise ValueError("The total token count in the prompt exceeds the model's limit of 4096 tokens.")
    safety_margin = 100
    available_tokens = max_tokens - token_count - safety_margin
    print(f"Available tokens for the response: {available_tokens}")
    if available_tokens <= 0:
        raise ValueError("There are not enough tokens available for the response.")

    if tokens_needed and tokens_needed < available_tokens:
        response_length = tokens_needed
    else:
        response_length = available_tokens

    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0301",
        messages=prompt,
        max_tokens=response_length
    )["choices"][0]["message"]["content"]

    return response

@staticmethod
def output(message):
    print(message)

askopenai.js const askOpenAI = async function (prompt, role, modelChoice = 'gpt-3.5-turbo', tokens=5000, temp=0.65) { let now = new Date(); // let model = 'gpt-3.5-turbo' // //gpt-4-0314 let roleContent = "You are an ChatGPT-powered chat bot."

if (role == 'machine') {
    roleContent = "You are a computer program attempting to comply with the user's wishes."
}

if (role == 'writer') {
    roleContent = "You are a professional fiction writer who is a best-selling author. You use all of the rhetorical devices you know to write a compelling book."
}

return new Promise(function(resolve, reject) {
    fetch('https://api.openai.com/v1/chat/completions', {
        method: 'POST',
        mode: 'cors',
        cache: 'no-cache',
        credentials: 'same-origin',
        'headers': {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${process.env.API_KEY}`,
        },
        'body': JSON.stringify({
            'model': modelChoice,
            "messages": [
                {"role": "system", "content": roleContent},
                {"role": "user", "content": prompt},
            ],
            "max_tokens": tokens,
            "temperature": temp,
        })
    }).then(response => response.json()).then(data => {
        try {
            let elapsed = new Date() - now;
            console.log('\nOpenAI response time: ' + elapsed + 'ms\n')
            resolve (data)
        } catch(e) {
            console.log(e);
            resolve (e);
        }
    })
});

}

exports.askOpenAI = askOpenAI;

fdtory commented 1 year ago

Running into the same issue here. But can't really implement your changes, @cometbus. Do you think you could submit the code in a code box? Or in any more intuitive format for someone with very little programming background?

Thanks!

gursheyss commented 1 year ago

@fdtory changing book.py to the following should work


import openai
from tqdm import tqdm
import prompts
import tiktoken
import json

enc = tiktoken.get_encoding("cl100k_base")

class Book:
    def __init__(self, **kwargs):
        # Joining the keyword arguments into a single string
        self.arguments = '; '.join([f'{key}: {value}' for key, value in kwargs.items() if key != 'tolerance'])

            # Get 'tolerance' attribute from kwargs
        self.tolerance = kwargs.get('tolerance', 0.9)

    # Assign a status variable
        self.status = 0

    # Setting up the base prompt
        self.base_prompt = [
            self.get_message('system', prompts.INITIAL_INSTRUCTIONS),
            self.get_message('user', self.arguments),
            self.get_message('assistant', 'Ready')
    ]

    # Setting up the title prompt
        self.title_prompt = [
            self.get_message('system', prompts.TITLE_INSTRUCTIONS),
            self.get_message('assistant', 'Ready'),
            self.get_message('user', self.arguments)
    ]

    # Setting up the structure prompt
        self.structure_prompt = [
            self.get_message('system', prompts.STRUCTURE_INSTRUCTIONS),
            self.get_message('assistant', 'Ready'),
    ]

    # Initialize the title attribute
        self.title = None

        self.output('Prompts set up. Ready to generate book.')

    def get_title(self):
        if not self.title:
            self.title = Book.get_response(prompt=self.title_prompt, tokens_needed=20)
        return self.title

    def get_structure(self):
        if not hasattr(self, 'title'):
            self.output('Title not generated. Please generate title first.')
            return
        else:
            structure_arguments = self.arguments + f'; title: {self.title}'
            self.structure_prompt.append(self.get_message('user', structure_arguments))
            self.structure = self.get_response(self.structure_prompt)
            self.chapters = self.convert_structure(self.structure)
            self.paragraph_amounts = self.get_paragraph_amounts(self.chapters)
            self.paragraph_words = self.get_paragraph_words(self.chapters)
            return self.structure, self.chapters

    def finish_base(self):
        if not hasattr(self, 'title'):
            self.output('Title not generated. Please generate title first.')
            return
        elif not hasattr(self, 'structure'):
            self.output('Structure not generated. Please generate structure first.')
            return
        else:
            self.base_prompt.append(self.get_message('user', '!t'))
            self.base_prompt.append(self.get_message('assistant', self.title))

            self.base_prompt.append(self.get_message('user', '!s'))
            self.base_prompt.append(self.get_message('assistant', self.structure))
            return self.base_prompt

    def calculate_max_status(self):
        if not hasattr(self, 'chapters'):
            self.output('Structure not generated. Please generate structure first.')
            return
        else:
            self.max_status = sum(self.get_paragraph_amounts(self.chapters))
            return self.max_status

    def get_content(self):
        chapters = []
        for i in tqdm(range(len(self.chapters))):
            prompt = self.base_prompt.copy()
            chapter = self.get_chapter(i, prompt.copy())
            chapters.append(chapter)
        self.content = chapters
        return self.content

    def save_book(self):
        # Save the book in md format
        with open(f'book.md', 'w') as file:
            file.write(f'# {self.title}\n\n')
            for chapter in self.content:
                file.write(f'## {self.chapters[self.content.index(chapter)]["title"]}\n\n')
                for paragraph in chapter:
                    file.write(
                        f'### {self.chapters[self.content.index(chapter)]["paragraphs"][chapter.index(paragraph)]["title"]}\n\n')
                    file.write(paragraph + '\n\n')
                file.write('\n\n')

    def get_chapter(self, chapter_index, prompt):
        if len(self.base_prompt) == 3:
            self.finish_base()

        paragraphs = []
        for i in range(self.paragraph_amounts[chapter_index]):
            paragraph = self.get_paragraph(prompt.copy(), chapter_index, i)
            prompt.append(self.get_message('user', f'!w {chapter_index + 1} {i + 1}'))
            prompt.append(self.get_message('assistant', paragraph))
            self.status += 1
            paragraphs.append(paragraph)
        return paragraphs

    def get_paragraph(self, prompt, chapter_index, paragraph_index):
        prompt.append(self.get_message('user', f'!w {chapter_index + 1} {paragraph_index + 1}'))
        paragraph = ""
        response = ""

        available_tokens = 4096 - len(enc.encode(json.dumps(prompt))) - 100

        while len(paragraph.split(' ')) < int(self.paragraph_words[chapter_index][paragraph_index] * self.tolerance) and available_tokens > 0:
            try:
                response = Book.get_response(prompt)
                paragraph += response
                prompt.append(self.get_message('assistant', response))
                prompt.append(self.get_message('system', '!c'))
                available_tokens -= len(enc.encode(response))
            except ValueError:
                response = None
                break

        return paragraph

    @staticmethod
    def get_message(role, content):
        return {"role": role, "content": content}

    @staticmethod
    def convert_structure(structure):
        chapters = structure.split("Chapter")
        chapters = [x for x in chapters if x != '']
        chapter_information = []

        for chapter in chapters:
            for line in chapter.split("\n"):
                if 'paragraphs' in line.lower():
                    split_line = line.split('): ')
                    if len(split_line) > 1:
                        chapter_information.append({'title': split_line[1], 'paragraphs': []})
                elif 'paragraph' in line.lower():
                    chapter_information[-1]['paragraphs'].append(
                        {'title': line.split('): ')[1], 'words': line.split('(')[1].split(')')[0].split(' ')[0]})
            chapter_information[-1]['paragraph_amount'] = len(chapter_information[-1]['paragraphs'])

        return chapter_information

    @staticmethod
    def get_paragraph_amounts(structure):
        amounts = []
        for chapter in structure:
            amounts.append(chapter['paragraph_amount'])
        return amounts

    @staticmethod
    def get_paragraph_words(structure):
        words = []
        for chapter in structure:
            words.append([int(x['words']) for x in chapter['paragraphs']])
        return words

    @staticmethod
    def get_response(prompt, tokens_needed=None):
        enc = tiktoken.encoding_for_model("gpt-3.5-turbo")
        token_count = len(enc.encode(json.dumps(prompt)))
        print(f"Token count for the prompt: {token_count}")
        max_tokens = 4096
        if token_count >= max_tokens:
            raise ValueError("The total token count in the prompt exceeds the model's limit of 4096 tokens.")
        safety_margin = 100
        available_tokens = max_tokens - token_count - safety_margin
        print(f"Available tokens for the response: {available_tokens}")
        if available_tokens <= 0:
            raise ValueError("There are not enough tokens available for the response.")

        if tokens_needed and tokens_needed < available_tokens:
            response_length = tokens_needed
        else:
            response_length = available_tokens

        response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo-0301",
            messages=prompt,
            max_tokens=response_length
        )["choices"][0]["message"]["content"]

        return response

    @staticmethod
    def output(message):
        print(message)
ldavis9000aws commented 1 year ago

@gursheyss @cometbus Thank you both! The last print statement had 3 extra characters that also needed to be removed. I also had to add tiktoken to the requirements.txt file and ran the "pip install -r requirements.txt" command again. Everything worked as expected after that.