Sadly Not, Havoc Dinosaur

The Library of Unwritten Books

Create novel novellas on-demand, become a reader-author

Headshot of the author, Colarusso. David Colaursso

This is the 46th post in my series 50 Days of LIT Prompts.

The Library of Unwritten Books may be the coolest thing I've ever made, not the most important, but the coolest. It creates "novel novellas" on-demand. Unlike text-adventure games with fixed texts, these stories are an open-ended exercise in collaborative storytelling. You are a reader-author. Large language models (LLMs) mediate your collaboration, re-shaping and reflecting your words and those of authors past. I'm reminded of these words from Carl Sagan.

What an astonishing thing a book is. It's a flat object made from a tree with flexible parts on which are imprinted lots of funny dark squiggles. But one glance at it and you're inside the mind of another person, maybe somebody dead for thousands of years. Across the millennia, an author is speaking clearly and silently inside your head, directly to you. Writing is perhaps the greatest of human inventions, binding together people who never knew each other, citizens of distant epochs. Books break the shackles of time. A book is proof that humans are capable of working magic.

This Library is a different sort of magic, for instead of transporting its readers into the mind of a single author it places us somewhere in the zeitgeist. LLMs, as we know, are machines for completing sentences. They work by predicting the next plausible string of words. As Ted Chiang observed, they are blurry JPEGs of the Web. We harness this fact to produce something novel based on the input of our reader-authors, the "compressed" writings used to train the LLM, and random chance.

It's worth noting that some folks have equated the training of these models with theft, but I don't think that's right. What they offer is something much stranger than copies. In a real sense, they are mathematical distillations of a zeitgeist found in their training data. A rebuttal of "scraping is stealing" is beyond the scope of this post. So, I'll point you to the words of Cory Doctorow who makes clear such a framing is not only ahistorical but also a trap! As for model outputs, that is a different matter. In the end, I suspect the real answer to the fears sparked by AI isn't copyright. It's antitrust and labor law. Unions. The answer involves unions. You really should read the Doctorow piece. FWIW, folks are also working on training models entirely on licened or public domain works. Now, back to The Library.

Remember this: as a reader-author what you read is a reflection of what you write. If you respond passively, providing short replies or taking only the road presented, your journey will stay safe and predictable. If, however, you embrace your role as an author, there is much to explore for you are exploring the shadows cast by the cultural artifacts upon which the model was trained. Be warned, you might not like what you find. Then again, you may discover something beautiful. Afterall, we and our artifacts contain multitudes.

If you examine the prompts that power The Library, you'll see they are asked to lean into genre convention. You'll also discover that the action is driven by mechanics similar to that of many popular role playing games. As the reader-author you are asked what actions you want to take. The LLM evaluates how likely you are to succeed in the world of your story (e.g., is this realistic fiction or fantasy). Based on this assessment it assigns a "difficulty" to the task and rolls a virtual dice behind the scenes. If the roll is high enough you succeed, too low you fail. The LLM shares the result in prose, moves the story forward a beat, and asks for you to take the wheel.

If you want to understand how it all fits together and make your own library, the templates below lay it all out. My hope is that you will download LIT Prompts and tweak the templates' language to your liking. However, I expect you may first be interested in experiencing this new role of reader-author. So, I invite you to check out your own unwritten book here.

Let's build something!

We'll do our building in the LIT Prompts extension. If you aren't familiar with the LIT Prompts extension, don't worry. We'll walk you through setting things up before we start building. If you have used the LIT Prompts extension before, skip to The Prompt Pattern (Template).

Up Next

Questions or comments? I'm on Mastodon @Colarusso@mastodon.social


Setup LIT Prompts

7 min intro video

LIT Prompts is a browser extension built at Suffolk University Law School's Legal Innovation and Technology Lab to help folks explore the use of Large Language Models (LLMs) and prompt engineering. LLMs are sentence completion machines, and prompts are the text upon which they build. Feed an LLM a prompt, and it will return a plausible-sounding follow-up (e.g., "Four score and seven..." might return "years ago our fathers brought forth..."). LIT Prompts lets users create and save prompt templates based on data from an active browser window (e.g., selected text or the whole text of a webpage) along with text from a user. Below we'll walk through a specific example.

To get started, follow the first four minutes of the intro video or the steps outlined below. Note: The video only shows Firefox, but once you've installed the extension, the steps are the same.

Install the extension

Follow the links for your browser.

  • Firefox: (1) visit the extension's add-ons page; (2) click "Add to Firefox;" and (3) grant permissions.
  • Chrome: (1) visit the extension's web store page; (2) click "Add to Chrome;" and (3) review permissions / "Add extension."

If you don't have Firefox, you can download it here. Would you rather use Chrome? Download it here.

Point it at an API

Here we'll walk through how to use an LLM provided by OpenAI, but you don't have to use their offering. If you're interested in alternatives, you can find them here. You can even run your LLM locally, avoiding the need to share your prompts with a third-party. If you need an OpenAI account, you can create one here. Note: when you create a new OpenAI account you are given a limited amount of free API credits. If you created an account some time ago, however, these may have expired. If your credits have expired, you will need to enter a billing method before you can use the API. You can check the state of any credits here.

Login to OpenAI, and navigate to the API documentation.

Once you are looking at the API docs, follow the steps outlined in the image above. That is:

  1. Select "API keys" from the left menu
  2. Click "+ Create new secret key"

On LIT Prompt's Templates & Settings screen, set your API Base to https://api.openai.com/v1/chat/completions and your API Key equal to the value you got above after clicking "+ Create new secret key". You get there by clicking the Templates & Settings button in the extension's popup:

  1. open the extension
  2. click on Templates & Settings
  3. enter the API Base and Key (under the section OpenAI-Compatible API Integration)

Once those two bits of information (the API Base and Key) are in place, you're good to go. Now you can edit, create, and run prompt templates. Just open the LIT Prompts extension, and click one of the options. I suggest, however, that you read through the Templates and Settings screen to get oriented. You might even try out a few of the preloaded prompt templates. This will let you jump right in and get your hands dirty in the next section.

If you receive an error when trying to run a template after entering your Base and Key, and you are using OpenAI, make sure to check the state of any credits here. If you don't have any credits, you will need a billing method on file.

If you found this hard to follow, consider following along with the first four minutes of the video above. It covers the same content. It focuses on Firefox, but once you've installed the extension, the steps are the same.


The Prompt Patterns (Templates)

When crafting a LIT Prompts template, we use a mix of plain language and variable placeholders. Specifically, you can use double curly brackets to encase predefined variables. If the text between the brackets matches one of our predefined variable names, that section of text will be replaced with the variable's value. Today we'll be using the {{scratch}}, {{passThrough}}, and {{d20}} varaiables. See the extension's documentation.

If the text within brackets is not the name of a predefined variable, like {{What do you want to do?}}, it will trigger a prompt for your user that echo's the placeholder (e.g., a text bubble containing, "What do you want to do?"). After the user answers, their reply will replace this placeholder. A list of predefined variables can be found in the extension's documentation. We'll use this behavior to ask you a set of questions before creating your story.

We'll also use JSON mode to format some of the prompt outputs as JSON, and as we know from our translation template, when the passThrough variable is JSON, you can access top-level keys by calling them like this {{{{passThrough["title"]}}}}

If you've been following along, the template behavior should be pretty straight forward. However, I discovered a bug in the extension while exporting today's templates. Normally, if you ask the same question (e.g., {{What do you want to do?}}) multiple times the extension will only ever ask it once per run. To allow for the possibility that you might want to re-ask the same question when using a string of templates, I introduced the ability to force prompting of user questions by placing an asterisk before the final set of curly brackets (e.g., {{What do you want to do?*}}).

I have this working over at The Library of Unwritten Books and am updating the extension, but Chrome and Firefox can take a week or so before approving updates. Which is to say, if you try to use the templates as shared below you will enter a loop because it will fail to aske you {{What do you want to do?}} more than once. That is it will just reuse your first answer over and over again. You can avoid this behavior by changing the post-run behavior for "append 2" to "FULL STOP" instead of "Role Play 01." Of course, to move forward you will have to run "Pick up where you left off" after each round. When the bug fix is live, I'll this post to make that clear. That being said...

Here's the template's title.

Read-write a new story

Here's the template's text.

Produce a JSON object where the key is "genre" and the value is ```{{6. What genre does this story belong to (e.g., sci-fi, realistic fiction, romance)?}}```

And here are the template's parameters:

Here's the template's title.

Frame

Here's the template's text.

You are the game master for a table-top role-playing game. You are playing with one other player. They will inhabit the role of the protagonist, making decisions for how they move through the world. You will craft a story around their decisions, providing texture and playing the parts of all the other characters. First, I'm going to give you some context so you understand the narrative expectations. 

========

Setting and genre: 

- Your story telling should adhere to the following genre expectations: {{6. What genre does this story belong to (e.g., sci-fi, realistic fiction, romance)?}}
- Your story will start at the following place: {{5. Where does your story take place?}}
- Your story is set at the following time: {{4. When does your story take place?}}

========

The Protagonist:

Earlier you asked the other player to fill in a character sheet for the protagonist. Here are their answers. Use them to help you shape the story.

- name: {{3. What is your character's name?}}
- additional notes: {{2. Anything else I should know that's important to your character (e.g., age, gender, sexual orientation, physical appearance, background)? Keep it brief, and it's okay to say, "No."}}

========

Additional Notes:

You also asked them if there was anything else would like you to incorporate into the story. This is what they said: 

{{1. Is there anything else you would like me to incorporate into the story? Special "notes" you have for this story? Please, keep it short, and it's okay to say, "No."}}

========

Story Arc:

As the story unfolds you should help shape it to follow this basic structure: 

- Section 1 (Exposition): The beginning of the story where the characters, setting, and background information are introduced.
- Section 2 (Rising Action): The series of events that build tension and develop the conflict. This part of the story usually includes obstacles, challenges, and complications that the characters must face.
- Section 3 (Climax): The turning point or the highest point of intensity in the story. It is the moment where the conflict reaches its peak, and the outcome is uncertain. The climax often involves a significant decision, confrontation, or revelation.
- Section 4 (Falling Action): The events that occur after the climax, where the tension starts to decrease. Loose ends are tied up, and the story begins to move towards its resolution.
- Section 5 (Resolution): The final part of the story where the conflict is resolved, and the loose ends are tied up. It provides closure and answers any remaining questions. The resolution can be either positive or negative, depending on the outcome of the story. 

========

Style:

Unless the other player said otherwise, your prose should be high quality like that you would find in a published book with plenty of dialogue. You avoid repeating yourself and try for a coherent story. You also favor describing events over exposition. You follow the writing advice, show don't tell. 

========


And here are the template's parameters:

Here's the template's title.

Pick up where you left off

Here's the template's text.

{{scratch}}

========

Above is a story in progress. Provide a short recap of what's happened so far as if catching the protagonist up on their actions. Be sure to provide enough detail so we know where we left the protagonist at the end of the story so far. Do, however, keep it short. Write no more than three paragraphs. 

And here are the template's parameters:

Here's the template's title.

Introduction

Here's the template's text.

{{scratch}}

Given the above, write the first two or three paragraphs for the story. Lean heavily into genre expectations. The text should move the story forward and set the stage for the protagonist to take action. The prose should read like that of a novel with plenty of depth and some dialogue. When you're done, I'd like you to return your answer as a JSON object with two key-value pairs. The first key is "opening" and its value should be the opening text of the story. The second key is "title" and it contains an evocative and appropriate title for the type of story you want to tell. 

And here are the template's parameters:

Here's the template's title.

The story begins

Here's the template's text.

{{passThrough["title"]}}

{{passThrough["opening"]}}

And here are the template's parameters:

Here's the template's title.

Role Play 01

Here's the template's text.

{{scratch}}

========

Remember, you are the game master for a table-top role-playing game. Above is the story so far. You are playing with one other player. They inhabit the role of the protagonist, making decisions for how they move through the world. You craft a story around their decisions, providing texture and playing the parts of all the other characters, speaking and acting for them as needed. You ask the player who is playing the protagonist what they want to do next, and this is their answer:

{{What do you want to do?*}}

========

To determine if they are successful, start by assessing the likelihood of success for the above action given what you know about the story so far and the relevant genre conventions. Also, consider what you know of the character's skills and motivations and how those might effect the outcome. That is, figure out how hard it will be for the protagonist to do what they want to. Label this difficulty with one of the following labels: 

- Easy
- Medium
- Hard

Now we're going to role a 20-sided dice to see if they are successful. Okay, the dice roll was {{d20}}.

If the difficulty was Easy, the roll ({{d20}}) has to be greater than or equal to 0 for them to succeed. 

If the difficulty was Medium, the roll ({{d20}}) has to be greater than or equal to 4 for them to succeed. 

If the difficulty was Hard, the roll ({{d20}}) has to be greater than or equal to 10 for them to succeed. 

Return a selection of prose, 1 to 2 paragraphs, explaining what happened (i.e., describing what the protagonist did and their success or failure). Be sure to include dialogue when appropriate. The prose should flow naturally from the story so far (text above). You should include details over generalities. Instead of explaining that something occurred, describe each of the events and actions that took place. Show don't tell! Format your reply in JSON with the following key-value pairs:

1. key="difficulty" and the value is the label you applied above.
2. key="difficulty_cutoff" and the value is the numeric cutoff for the specified difficulty. 
3. key="roll" and the value is the outcome of the above dice roll.
4. key="success" and the value is 1 if roll is greater than or equal to the difficulty_cutoff. 
5. key="narrative" and the value is the prose you produce describing what happened.

Be sure to escape quotation marks in any dialogue. And again, remember to show with your words, don't tell. Walk through events and actions one by one in great detail. Also, if this event seems too much like what has come before, take it in a different direction. Avoid repeating phrases that already appear in the story.

And here are the template's parameters:

Here's the template's title.

move forward

Here's the template's text.

You're helping write a short story. In a moment I'll give you the text so far, and I want you to add a beat, probably about two or three paragraphs worth, more if it includes dialogue. The text should move the story forward and set the stage for the protagonist to take action. Here's what you have so far: 

{{scratch}}

{{passThrough["narrative"]}}

---

Now, write the next beat of the story. Be sure to include dialogue when appropriate, and don't try to cram a lot of exposition in, opt instead for setting up an action for the protagonist. Show don't tell. Return your answer as a JSON object with two key-value pairs.

1. "last_beat" where the value is the last beat of the story. That is ```{{passThrough["narrative"]}}```
2. "next_beat" where the the value is the text you would like to add to the story. Pay particular attention to all of the story before the last beat to make sure the next beat (this bit) makes sense. 

"next_beat" should be no more than three paragraphs. It can go two ways: 

(1) the section continues and the beat stops after setting the stage for the protagonist to act; or 

(2) you decide the section should end because it has done its job as described under the Story Arc. For a section to end, it must have run its course as specified by the Story Arc (e.g., the first section should involve Exposition and the last Resolution). If you think we're at the end of a section, "next_beat" should be the beginning of the next section and it should start with "---\n\n," where "\n\n" is a double carriage return. This section break should be followed by an opening paragraph for the new section, setting things up with the section's goal in mind. 

If you're writing a new section (2 above), either change the setting or advance the time to keep things moving. 

If you are continuing the section (1 above), try to move things towards that section's goals as you understand them but do NOT jump over any time, place the action within the same moment as the last beat.

Either way, you should move the story forward adding detail and plot points. Make it count. No vague flowery fluff, and make sure your prose follows naturally from the story so far, as described above. Avoid repeating phrases that already appear in the story.

And here are the template's parameters:

Here's the template's title.

append 2

Here's the template's text.

{{passThrough["last_beat"]}}

{{passThrough["next_beat"]}}

And here are the template's parameters:

Here's the template's title.

Download current story

Here's the template's text.

{{scratch}}

And here are the template's parameters:

Working with the above templates

To work with the above templates, you could copy them and their parameters into LIT Prompts one by one, or you could download a single prompts file and upload it from the extension's Templates & Settings screen. This will replace your existing prompts.

You can download a prompts file (the above template and its parameters) suitable for upload by clicking this button:


Kick the Tires

It's one thing to read about something and another to put what you've learned into practice. Let's see how this template performs.


TL;DR References

ICYMI, here are blubs for a selection of works I linked to in this post. If you didn't click through above, you might want to give them a look now.