Skip to content

Commit

Permalink
Added doc generation (#2)
Browse files Browse the repository at this point in the history
- Added script to verify that prompts are valid
- Created a basic readme
- Added typedoc library and npm script
- Added GitHub action to automatically deploy docs
  • Loading branch information
Bullrich authored May 4, 2024
1 parent ac607e3 commit e7d6b45
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 2 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Deploy docs

on:
push:
branches:
- main

permissions:
contents: read
pages: write
id-token: write

jobs:
deploy-docs:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Get npm cache directory
id: npm-cache-dir
shell: bash
run: echo "dir=$(npm config get cache)" >> ${GITHUB_OUTPUT}
- name: Cache node modules
uses: actions/cache@v4
id: npm-cache
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: npm ci
- name: Generate docs
run: npm run docs
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./docs
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
dist
docs
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Story-GPT

Typescript library used to generate the stories for [StoryBot](https://storybot.dev)

## Installation

`npm install --save story-gpt`

## Usage

```typescript
import { createStory } from "story-gpt";

const story = await createStory("A story about a happy horse", new OpenAI({apiKey: ">my api key<"}));

console.log("The story is named %s and it's tells the following story:", story.title, story.content);
console.log("See the cover picture for the story here:", story.image);
```
100 changes: 100 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc",
"lint": "eslint",
"fix": "eslint --fix"
"fix": "eslint --fix",
"docs": "typedoc --readme README.md src/index.ts"
},
"repository": {
"type": "git",
Expand All @@ -30,6 +31,7 @@
"@eslint/js": "^9.2.0",
"eslint": "^8.57.0",
"globals": "^15.1.0",
"typedoc": "^0.25.13",
"typescript": "^5.4.5",
"typescript-eslint": "^7.8.0"
},
Expand Down
11 changes: 10 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import { OpenAI } from "openai";
import { Story, systemInfo } from "./story";
import { ImageGenerator } from "./image";
import { verifyPrompt } from "./verify";

/** Payload with story data */
export type StoryPayload = {
/** Prompt used to generate the story */
prompt: string;
title: string;
/** Content of the story */
content: string;
/** Temperature used to generate the story */
temperature: number;
/** URL for the story image.
* This link expires so be sure to download it */
image: string;
}

/**
* Utility method that creates a story object with a title and an image
* @param prompt the prompt to be used as the entry point
* @param openai the authenticated open ai client
* @returns a payload with all the information required for the story {@link StoryPayload}
*/
export async function createStory(prompt: string, openai: OpenAI): Promise<StoryPayload> {
const story = await Story.generateStory(prompt, openai);
return {
Expand All @@ -25,4 +34,4 @@ export async function createStory(prompt: string, openai: OpenAI): Promise<Story
}
}

export { Story, ImageGenerator, systemInfo }
export { Story, ImageGenerator, systemInfo, verifyPrompt }
1 change: 1 addition & 0 deletions src/story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ChatAssistant } from "./chat";

type StoryParams = { prompt: string, story: string, temperature: number };

/** Prompt used to generate the story */
export const systemInfo = `You are Story Bot, a language model that helps users create stories, scripts and more.
Follow the user's instructions carefully and generate the content they requested.
When writing a post, story or script, try to extend the text as much as possible without making it boring.
Expand Down
55 changes: 55 additions & 0 deletions src/verify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { OpenAI } from "openai";

type StoryResult = { validStory: true } | { validStory: false, reasonForRejection: string }

/**
* Utility method that verifies if a prompt qualifies as a story.
* Useful to be used as the filter before processing a story and receiving an answer like
* "I'm sorry, but X is not a prompt for a story"
* @param prompt The prompt to analyze
* @param openai The openai authenticated client
* @param chatModel optional, the model to use. Defaults to gpt-4-turbo
* @returns a `{validStory:boolean,reasonForRejection?:string}` object.
* If validStory is false, the reasonForRejection will contain the information
*/
export async function verifyPrompt(prompt: string, openai: OpenAI, chatModel: string = "gpt-4-turbo"): Promise<StoryResult> {
const response = await openai.chat.completions.create({
model: chatModel,
messages: [{
role: "system", content: "You verify if a prompt is a set of instructions to a story or blogpost or if it is an unrelated command"
},
{ role: "user", content: `Is the following prompt a prompt for a story?\n\n${prompt}` },
],
tools: [{
type: "function",
function: {
name: "is_story_prompt",
description: "Informs if a prompt is a story prompt",
parameters: {
type: "object",
properties: {
isStory: {
type: "boolean",
description: "True if the prompt is an instruction for a story or a blog. False if the prompt is unrelated"
},
kindOfPrompt: {
type: "string",
description: "Explain what is missing to be a story prompt. Around 140 characters"
}
},
required: ["isStory", "kindOfPrompt"],
}
}
}],
tool_choice: "auto"
});

const responseMessage = response.choices[0]?.message;
if (!responseMessage?.tool_calls || !responseMessage.tool_calls[0]?.function.arguments) {
throw new Error("Missing tool calls");
}

const { isStory, kindOfPrompt } = JSON.parse(responseMessage.tool_calls[0].function.arguments) as { isStory: boolean, kindOfPrompt: string };

return isStory ? { validStory: true } : { validStory: false, reasonForRejection: kindOfPrompt };
}

0 comments on commit e7d6b45

Please sign in to comment.