Skip to content

Commit

Permalink
tech dept: split cli sources into sub files (#311)
Browse files Browse the repository at this point in the history
* moving run command in self file

* refactor spinner

* move atch out to file

* move spinner

* more refactoring

* spli cli

* more file spits

* updating tests

* unused symbol

* refactor spinner

* fix test
  • Loading branch information
pelikhan authored Apr 1, 2024
1 parent 3d77ec7 commit 68131c2
Show file tree
Hide file tree
Showing 22 changed files with 1,158 additions and 1,062 deletions.
4 changes: 3 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"cli",
"prompt",
"llm",
"generative ai"
"generative ai",
"gpt4",
"chatgpt"
],
"description": "A CLI for GenAIScript, a generative AI scripting framework.",
"license": "MIT",
Expand Down
256 changes: 256 additions & 0 deletions packages/cli/src/batch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
import {
FragmentTransformResponse,
host,
runTemplate,
MarkdownTrace,
convertDiagnosticToGitHubActionCommand,
convertDiagnosticToAzureDevOpsCommand,
dotGenaiscriptPath,
assert,
isCancelError,
normalizeInt,
normalizeFloat,
GENAI_JS_REGEX,
GPSPEC_REGEX,
FILES_NOT_FOUND_ERROR_CODE,
GENERATION_ERROR_CODE,
appendJSONL,
writeFileEdits,
parseVars,
} from "genaiscript-core"
import { basename, resolve, join, relative, dirname } from "node:path"
import { appendFile, writeFile } from "node:fs/promises"
import { emptyDir, ensureDir } from "fs-extra"
import { buildProject } from "./build"
import { createProgressSpinner } from "./spinner"

export async function batchScript(
tool: string,
specs: string[],
options: {
excludedFiles: string[]
out: string
outSummary: string
removeOut: boolean
retry: string
retryDelay: string
maxDelay: string
label: string
temperature: string
topP: string
seed: string
maxTokens: string
model: string
cache: boolean
applyEdits: boolean
vars: string[]
}
) {
const spinner = createProgressSpinner("preparing tool and files")

const {
out = dotGenaiscriptPath("results"),
removeOut,
model,
cache,
label,
outSummary,
applyEdits,
excludedFiles,
vars,
} = options
const outAnnotations = join(out, "annotations.jsonl")
const outData = join(out, "data.jsonl")
const outFenced = join(out, "fenced.jsonl")
const outOutput = join(out, "output.md")
const outErrors = join(out, "errors.jsonl")
const outFileEdits = join(out, "file-edits.jsonl")

const retry = normalizeInt(options.retry) || 12
const retryDelay = normalizeInt(options.retryDelay) || 15000
const maxDelay = normalizeInt(options.maxDelay) || 360000
const temperature = normalizeFloat(options.temperature)
const topP = normalizeFloat(options.topP)
const seed = normalizeFloat(options.seed)
const maxTokens = normalizeInt(options.maxTokens)

const toolFiles: string[] = []
if (GENAI_JS_REGEX.test(tool)) toolFiles.push(tool)
const specFiles = new Set<string>()
for (const arg of specs) {
const ffs = await host.findFiles(arg)
for (const f of ffs) {
if (GPSPEC_REGEX.test(f)) specFiles.add(f)
else {
const fp = `${f}.gpspec.md`
const md = `# Specification
- [${basename(f)}](./${basename(f)})\n`
host.setVirtualFile(fp, md)
specFiles.add(fp)
}
}
}

if (excludedFiles?.length) {
for (const arg of excludedFiles) {
const ffs = await host.findFiles(arg)
for (const f of ffs) specFiles.delete(f)
}
}

if (!specFiles.size) {
spinner.fail("no file found")
process.exit(FILES_NOT_FOUND_ERROR_CODE)
}

const prj = await buildProject({
toolFiles,
specFiles: Array.from(specFiles),
})
const script = prj.templates.find(
(t) =>
t.id === tool ||
(t.filename &&
GENAI_JS_REGEX.test(tool) &&
resolve(t.filename) === resolve(tool))
)
if (!script) throw new Error(`tool ${tool} not found`)

spinner.succeed(
`tool: ${script.id} (${script.title}), files: ${specFiles.size}, out: ${resolve(out)}`
)

let errors = 0
let totalTokens = 0
if (removeOut) await emptyDir(out)
await ensureDir(out)
for (let i = 0; i < prj.rootFiles.length; i++) {
const specFile = prj.rootFiles[i].filename
const file = specFile.replace(GPSPEC_REGEX, "")
const meta = { tool, file }
try {
spinner.start(`${file} (${i + 1}/${specFiles.size})`)
const fragment = prj.rootFiles.find(
(f) => resolve(f.filename) === resolve(specFile)
).fragments[0]
assert(fragment !== undefined, `${specFile} not found`)
let tokens = 0
const result: FragmentTransformResponse = await runTemplate(
script,
fragment,
{
infoCb: () => {},
partialCb: ({ tokensSoFar }) => {
tokens = tokensSoFar
spinner.report({ count: tokens })
},
skipLLM: false,
label,
cache,
temperature,
topP,
seed,
maxTokens,
model,
retry,
retryDelay,
maxDelay,
vars: parseVars(vars),
}
)

const fileEdits = result.fileEdits || {}
if (Object.keys(fileEdits).length) {
if (applyEdits && !result.error) await writeFileEdits(result)
// save results in various files
await appendJSONL(
outFileEdits,
[{ fileEdits: result.fileEdits }],
meta
)
}
if (result.error)
await appendJSONL(outErrors, [{ error: result.error }], meta)
if (result.annotations?.length)
await appendJSONL(outAnnotations, result.annotations, meta)
if (result.fences?.length)
await appendJSONL(outFenced, result.fences, meta)
if (result.frames?.length)
await appendJSONL(outData, result.frames, meta)
// add to summary
if (outSummary) {
const st = new MarkdownTrace()
st.details(file, result.text)
await appendFile(outSummary, st.content)
}
// save results
const outText = join(
out,
`${relative(".", specFile).replace(GPSPEC_REGEX, ".output.md")}`
)
const outTrace = join(
out,
`${relative(".", specFile).replace(GPSPEC_REGEX, ".trace.md")}`
)
const outJSON = join(
out,
`${relative(".", specFile).replace(GPSPEC_REGEX, ".json")}`
)
await ensureDir(dirname(outText))
await writeFile(outText, result.text, { encoding: "utf8" })
await writeFile(outTrace, result.trace, { encoding: "utf8" })
await appendFile(
outOutput,
`- ${result.error ? (isCancelError(result.error as any) ? "⚠" : "❌") : "✅"} [${relative(".", specFile).replace(GPSPEC_REGEX, "")}](${relative(out, outText)}) ([trace](${relative(out, outTrace)}))\n`,
{ encoding: "utf8" }
)
await writeFile(outJSON, JSON.stringify(result, null, 2), {
encoding: "utf8",
})

if (result.error) {
if (isCancelError(result.error as Error))
spinner.warn(
`${spinner.text}, cancelled, ${(result.error as Error).message || result.error}`
)
else {
errors++
spinner.fail(
`${spinner.text}, ${(result.error as Error).message || result.error}`
)
}
} else spinner.succeed()

totalTokens += tokens

// if in a CI/GitHub Actions build, print annotations
if (
result.annotations?.length &&
process.env.CI &&
process.env.GITHUB_ACTION
)
result.annotations
.map(convertDiagnosticToGitHubActionCommand)
.forEach((d) => console.log(d))
else if (
// Azure DevOps
result.annotations?.length &&
process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI
)
result.annotations
.map(convertDiagnosticToAzureDevOpsCommand)
.forEach((d) => console.log(d))
} catch (e) {
errors++
await appendJSONL(
outErrors,
[{ error: e.message + "\n" + e.stack }],
meta
)
spinner.fail(`${spinner.text}, ${e.error}`)
}
}

if (errors) process.exit(GENERATION_ERROR_CODE)
}
28 changes: 28 additions & 0 deletions packages/cli/src/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { GENAI_EXT, host, parseProject } from "genaiscript-core"

export async function buildProject(options?: {
toolFiles?: string[]
specFiles?: string[]
toolsPath?: string
specsPath?: string
}) {
const {
toolFiles,
specFiles,
toolsPath = "**/*" + GENAI_EXT,
specsPath = "**/*.gpspec.md",
} = options || {}

const gpspecFiles = specFiles?.length
? specFiles
: await host.findFiles(specsPath)
const scriptFiles = toolFiles?.length
? toolFiles
: await host.findFiles(toolsPath)

const newProject = await parseProject({
gpspecFiles,
scriptFiles,
})
return newProject
}
Loading

0 comments on commit 68131c2

Please sign in to comment.