Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use history #16

Merged
merged 1 commit into from
Apr 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
feat: use history
various improvements for stability
allow clean
allow model
  • Loading branch information
pixelass committed Apr 6, 2023
commit be40d2dd62f5f2e1550728892cb004780106f8e5
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1 +1 @@
OPENAI_SECRET=
OPENAI_API_KEY=
373 changes: 34 additions & 339 deletions README.md

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions \.txt

Large diffs are not rendered by default.

183 changes: 128 additions & 55 deletions base.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,64 @@ import { config } from "dotenv";
import meow from "meow";
import { Configuration, OpenAIApi } from "openai";
import ora from "ora";
import pkg from "./package.json" assert { type: "json" };
import { globby } from "globby";
import prettier from "prettier";

config();

const { flags } = meow("", {
importMeta: import.meta,
flags: {
goal: {
type: "string",
alias: "G",
default: "extend the code",
const { flags } = meow(
`
Usage
$ node <start generation>

Options
-G, --goal Set the goal of the generated code. Default is "extend the code".
-g, --generations Set the number of generations for the generated code. Default is 1.
-p, --persona Set the persona of the generated code. Default is "expert node.js developer, creative, code optimizer, interaction expert".
-t, --temperature Set the temperature for the generated code. Default is 0.2.
-c, --clean Set to true if you want to remove any previously generated code.
-m, --model Set the model to use for generating the code. Default is "gpt-3.5-turbo".

Examples
$ node generation-000.js.js -G "console based mandelbrot set ascii" -g 3 -p "Node.js developer, creative" -c

`,
{
importMeta: import.meta,
flags: {
goal: {
type: "string",
alias: "G",
default: "extend the code",
},
generations: {
type: "number",
alias: "g",
default: 1,
},
persona: {
type: "string",
alias: "p",
default: "expert node.js developer, creative, code optimizer, interaction expert",
},
temperature: {
type: "number",
alias: "t",
default: 0.2,
},
clean: {
type: "boolean",
alias: "c",
default: false,
},
model: {
type: "string",
alias: "m",
default: "gpt-3.5-turbo",
},
},
generations: {
type: "number",
alias: "g",
default: 1,
},
persona: {
type: "string",
alias: "p",
default: "expert node.js developer, creative, interaction expert, cli expert",
},
temperature: {
type: "number",
alias: "t",
default: 0.2,
},
},
});
}
);

const spinner = ora("Evolving");

Expand All @@ -44,34 +73,31 @@ const configuration = new Configuration({
export const openai = new OpenAIApi(configuration);

const instructions = `
Extend the code but the RULES can NEVER be changed and must be respected AT ALL TIMES.
The code should INCREASE in logic.
The GOAL must be completed.
The code should ALWAYS be IMPROVED or EXTENDED or REFACTORED or FIXED
be creative and add new features
The GOAL must be completed

GOAL: ${flags.goal}

ALLOWED NPM PACKAGES: ${Object.entries(pkg.dependencies)
.map(([name, version]) => `${name}@${version}`)
.join(", ")}

RULES:
- Pay special attention TO ALL UPPERCASE words
- KEEP the existing code, only ADD new code or improve the code you added since "Generation 0"
- increment the generation constant ONCE per generation
- Keep track of changes, EXTEND the CHANGELOG
- Use es module syntax (with imports)
- NEVER use "require"
- NEVER explain anything
- VERY IMPORTANT: output the javaScript only (this is the most important rule, the entire answer has to be valid javascript)
increment the generation constant ONCE per generation
Keep track of changes, EXTEND the CHANGELOG
NEVER use external apis with secret or key
ONLY use es module syntax (with imports)
NEVER explain anything
ALWAYS output ONLY JavaScript
`;

const persona = `programmer with the following characteristics: ${flags.persona}`;
const history = [];

export const generations = flags.generations;
const maxSpawns = 3;
let spawns = maxSpawns;
let run = 0;

export async function evolve(generation) {
if (flags.help) {
return;
}
if (spawns <= 0) {
spinner.fail("Maximum retries reached");
return;
Expand All @@ -83,32 +109,66 @@ export async function evolve(generation) {
const code = await fs.readFile(filename, "utf-8");
spinner.start(`Generation ${generation} | ${spawns} spawns left`);

if (flags.clean) {
// Remove all older generations
const files = (await globby(["generation-*.js", "!generation-000.js"])).filter(
file => file > buildFilename(generation)
);
await Promise.all(files.map(async file => await fs.unlink(file)));
}

if (run === 0) {
history.push(
{
role: "user",
content: generation === 0 ? "build the initial code" : "continue the code",
},
{
role: "assistant",
content: minify(code),
}
);
}
run++;
history.push({
role: "user",
content: "continue the code",
});
spinner.start(`Evolution ${generation} -> ${generation + 1}`);
const completion = await openai.createChatCompletion({
// model: "gpt-4",
model: "gpt-3.5-turbo",
model: flags.model,
messages: [
{
role: "system",
content: `You are a: ${persona} extend or fix my code based on these instructions: ${instructions}`,
},
{
role: "user",
content: code,
content: `You are a: ${flags.persona}. You strictly follow these instructions: ${instructions}`,
},
...history,
],
max_tokens: 2048,
temperature: flags.temperature,
});
spinner.stop();
const { content } = completion.data.choices[0].message;
const nextFilename = buildFilename(nextGeneration);
await fs.writeFile(nextFilename, content);
spinner.succeed(`Generation ${generation} | ${spawns} spawns left`);
await import(`./${nextFilename}`);
const cleanContent = content
.replace("```javascript", "")
.replace("```js", "")
.replace("```", "");
if (cleanContent.startsWith("/*")) {
history.push({
role: "assistant",
content: cleanContent,
});
const nextFilename = buildFilename(nextGeneration);
await fs.writeFile(nextFilename, prettify(cleanContent));
spinner.succeed(`Evolution ${generation} -> ${generation + 1}`);
await import(`./${nextFilename}`);
} else {
throw new Error("NOT_JAVASCRIPT");
}
} catch (error) {
spawns--;
spinner.fail(`Generation ${generation} | ${spawns} spawns left`);

spinner.fail(`Evolution ${generation} -> ${generation + 1}`);
await handleError(error, generation);
}
}
Expand All @@ -121,14 +181,22 @@ export function buildFilename(currentGeneration) {
return path.join(".", `generation-${pad(currentGeneration)}.js`);
}

export function minify(code) {
return code.replace(/^\s+/gim, "");
}

export function prettify(code) {
return prettier.format(code, { semi: false, parser: "babel" });
}

export async function handleError(error, generation) {
const message = (
error.response?.data?.error.message ??
error.message ??
"unknown error"
).trim();

const code = error.response?.status ?? error.code ?? "UNKOWN_CODE";
const code = error.response?.status ?? error.code ?? "UNKNOWN_CODE";

if (code === "ERR_MODULE_NOT_FOUND") {
console.error(message);
Expand All @@ -153,6 +221,11 @@ export async function handleError(error, generation) {
return;
}

if (message === "NOT_JAVASCRIPT") {
console.error("The API returned a message that is not valid JavaScript");
return;
}

console.error(error);
throw error;
}
129 changes: 0 additions & 129 deletions examples/calculator/base.js

This file was deleted.

1 change: 0 additions & 1 deletion examples/calculator/calculations.txt

This file was deleted.

Loading