Skip to content

Commit

Permalink
Add --edit handling in CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidSouther committed Apr 13, 2024
1 parent 022d7f8 commit a08941e
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 43 deletions.
27 changes: 13 additions & 14 deletions cli/args.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,10 @@ export function makeArgs(argv = process.argv) {
type: "boolean",
default: false,
},
engine: {
type: "string",
short: "e",
default: process.env["AILLY_ENGINE"],
},
model: {
type: "string",
short: "m",
default: process.env["AILLY_MODEL"],
},
edit: { type: 'boolean', default: false, short: 'e' },
lines: { type: 'string', default: "", short: 'l' },
engine: { type: "string", default: process.env["AILLY_ENGINE"] },
model: { type: "string", default: process.env["AILLY_MODEL"] },
plugin: {
type: "string",
default: process.env["AILLY_PLUGIN"] ?? "noop",
Expand Down Expand Up @@ -68,16 +62,19 @@ export function help() {
-r, --root sets base folder to search for content and system prompts.
-s, --system sets an initial system prompt.
-p, --prompt generate a final, single piece of content and print the response to standard out.
-i, --isolated will start in isolated mode, generating each file separately. Can be overridden with 'isolated: false' in .aillyrc files, and set with AILLY_ISOLATED=true environment variable.
-i, --isolated will start in isolated mode, generating each file separately. Can be overridden with 'isolated: false' in .aillyrc files.
-o, --out specify an output folder to work with responses. Defaults to --root. Will load responses from and write outputs to here, using .ailly file extensions.
-c, --context content | folder | none
'content' (default) loads files from the root folder and includes them alphabetically, chatbot history style, before the current file when generating.
'folder' includes all files in the folder at the same level as the current file when generating.
'none' includes no additional content (including no system context) when generating.
(note: context is separate from isolated. isolated: true with either 'content' or 'folder' will result in the same behavior with either. With 'none', Ailly will send _only_ the prompt when generating.)
-e, --engine will set the default engine. Can be set with AILLY_ENGINE environment variable. Default is openai. bedrock calls AWS Bedrock. noop is available for testing. (Probably? Check the code.)
-m, --model will set the model from the engine. Can be set with AILLY_MODEL environment variable. Default depends on the engine; OpenAI is gpt-4-0613, bedrock is anthropic-claude-3. (Probably? Check the code.)
-e, --edit use Ailly in edit mode. Provide a single file in paths, an edit marker, and a prompt. The path will be updated with the edit marker at the prompt.
-l, --lines the lines to edit as '[start]:[end]' with start inclusive, and end exclusive. With only '[start]', will insert after. With only ':[end]', will insert before.
--engine will set the default engine. Can be set with AILLY_ENGINE environment variable. Default is openai. bedrock calls AWS Bedrock. noop is available for testing. (Probably? Check the code.)
--model will set the model from the engine. Can be set with AILLY_MODEL environment variable. Default depends on the engine; OpenAI is gpt-4-0613, bedrock is anthropic-claude-3. (Probably? Check the code.)
--plugin can load a custom RAG plugin. Specify a path to import with "file://./path/to/plugin.mjs". plugin.mjs must export a single default function that meets the PluginBuilder interface in core/src/plugin/index.ts
--template-view loads a YAML or JSON file to use as a view for the prompt templates. This view will be merged after global, engine, and plugin views but before system and template views.
Expand All @@ -91,6 +88,8 @@ export function help() {
-h, --help will print this message and exit.
`);

// --update-db will create and update a Vectra database with the current content. When available, a local Vectra db will augment retrieval data.
// -n, --section use LLM + TreeSitter to find line numbers.
// --kb
// --sync will create and update a Vectra database with the current content. When available, a local Vectra db will augment retrieval data.
// --augment will look up augmentations in the db.
}
9 changes: 8 additions & 1 deletion cli/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,13 @@ export async function loadFs(args) {
}

if (hasPrompt) {
if (edit && content.length == 1) {
edit.file = content[0];
content = [];
}
const noContext = args.values.context == "none";
const folder = args.values.context == 'folder' ?
Object.values(context).find(c => dirname(c.path) == root)?.context.folder : edit ? [edit.file] : undefined;
const cliContent = {
name: 'stdout',
outPath: "/dev/stdout",
Expand All @@ -64,7 +70,8 @@ export async function loadFs(args) {
view: settings.templateView,
predecessor: noContext ? undefined : content.filter(c => dirname(c) == root).at(-1)?.path,
system: noContext ? [] : [{ content: args.values.system ?? "", view: {} }],
folder: args.values.context == 'folder' ? Object.values(context).find(c => dirname(c.path) == root)?.context.folder : undefined,
folder,
edit,
}
};
context['/dev/stdout'] = cliContent;
Expand Down
57 changes: 45 additions & 12 deletions cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,32 +49,65 @@ async function main() {
generator.start();
await generator.allSettled();

DEFAULT_LOGGER.info("Generated!");
DEFAULT_LOGGER.info("Generator all settled!");
if (last == "/dev/stdout") {
console.log(loaded.context[last].response);
const prompt = loaded.context[last];
if (prompt.meta?.debug?.finish == 'failed') {
console.error(prompt.meta.debug.error.message);
return;
}
const edit = prompt.context.edit;
if (edit) {
await doEdit(loaded, edit, prompt, args);
} else {
console.log(prompt.response);
}
} else {
await ailly.content.write(loaded.fs, loaded.content.map(c => loaded.context[c]));
}
break;
}
}

async function doEdit(loaded, edit, prompt, args) {
const out = loaded.context[edit.file];
const responseLines = out.prompt?.split("\n") ?? [];
const replaceLines = prompt.response?.split("\n") ?? [];
const editValue = [
`Edit ${out.name} ${edit.start + 1}:${edit.end + 1}\n`,
responseLines.slice(edit.start - 3, edit.start).map(s => ` ${s}`).join('\n'),
responseLines.slice(edit.start, edit.end + 1).map(s => `-${s}`).join('\n'),
replaceLines.map(s => `+${s}`).join("\n"),
responseLines.slice(edit.end + 1, edit.end + 4).map(s => ` ${s}`).join('\n'),
].join("\n");
console.log(editValue);
if (!args.values.yes) {
await check_or_exit('Continue? (y/N) ');
}
responseLines?.splice(edit.start, edit.end - edit.start, ...(replaceLines));
out.response = responseLines.join("\n");
await loaded.fs.writeFile(out.path, out.response);
}

async function check_should_run(args, { content }) {
if (args.values.summary) {
console.log(
`Found ${content.length} items, estimated cost TODO: CALCULATE`
);
if (!args.values.yes) {
const rl = createInterface({
input: process.stdin,
output: process.stdout,
});
const prompt = await rl.question(
"Continue with generating these prompts? (y/N) "
);
if (!prompt.toUpperCase().startsWith("Y")) {
process.exit(0);
}
await check_or_exit("Continue with generating these prompts? (y/N) ")
}
}
}

async function check_or_exit(prompt) {
const rl = createInterface({
input: process.stdin,
output: process.stdout,
});
const answer = await rl.question(prompt);
if (!answer.toUpperCase().startsWith("Y")) {
process.exit(0);
}
rl.close();
}
4 changes: 2 additions & 2 deletions core/src/actions/prompt_thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,11 @@ async function generateOne(
const has_response = (c.response?.length ?? 0) > 0;

if (c.meta?.skip || (!settings.overwrite && has_response)) {
DEFAULT_LOGGER.info(`Skipping ${c.name}`);
DEFAULT_LOGGER.info(`Skipping ${c.path}`);
return c;
}

DEFAULT_LOGGER.info(`Preparing ${c.name}`);
DEFAULT_LOGGER.info(`Preparing ${c.path}`);

const meta = c.meta;
engine.format([c], context);
Expand Down
2 changes: 1 addition & 1 deletion core/src/content/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
isAbsolute,
} from "@davidsouther/jiffies/lib/esm/fs.js";
import matter from "gray-matter";
// import * as yaml from "js-yaml";
import { stringify } from "yaml";
import { join, dirname } from "path";
import type { Message } from "../engine/index.js";
Expand Down Expand Up @@ -43,6 +42,7 @@ export interface Context {
predecessor?: string;
folder?: string[];
augment?: { score: number; content: string; name: string }[];
edit?: { start: number; end: number; file: string };
}

// Additional useful metadata.
Expand Down
30 changes: 17 additions & 13 deletions core/src/engine/bedrock/bedrock.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { dirname } from "node:path";
import { DEFAULT_LOGGER } from "@davidsouther/jiffies/lib/esm/log.js";
import {
BedrockRuntimeClient,
Expand Down Expand Up @@ -62,7 +63,7 @@ export async function generate(
console.warn(`Error from Bedrock for ${c.name}`, e);
return {
message: "💩",
debug: { finish: "Failed", error: { message: (e as Error).message } },
debug: { finish: "failed", error: { message: (e as Error).message } },
};
}
}
Expand Down Expand Up @@ -142,18 +143,21 @@ export function getMessagesFolder(
content: Content,
context: Record<string, Content>
): Message[] {
const system = (content.context.system ?? [])
.map((s) => s.content)
.join("\n");
const history: Content[] = [];

const augment = (content.context.folder ?? [])
.map((c) => context[c])
.map<Message[]>((c) => [
{ role: "user", content: `The contents of the file ${c.name}` },
{ role: "assistant", content: c.prompt ?? c.response ?? "" },
])
.flat();
const system =
(content.context.system ?? []).map((s) => s.content).join("\n") +
"\n" +
"Instructions are happening in the context of this folder:\n" +
`<folder name="${dirname(content.path)}">\n` +
(content.context.folder ?? [])
.map((c) => context[c])
.map<string>(
(c) => `<file name="${c.name}>\n${c.prompt ?? c.response ?? ""}</file>`
)
.join("\n") +
"\n</folder>";

const history: Content[] = [content];
const augment: Message[] = [];

const parts = history
.map<Array<Message | undefined>>((content) => [
Expand Down

0 comments on commit a08941e

Please sign in to comment.