Skip to content

Commit

Permalink
Dynamic prompting engine
Browse files Browse the repository at this point in the history
  • Loading branch information
csansoon committed Jun 21, 2024
1 parent 2041b23 commit f1c77d3
Show file tree
Hide file tree
Showing 38 changed files with 1,886 additions and 16 deletions.
4 changes: 4 additions & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --output human-verbose",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"query": "node scripts/dist/run_query.js",
"prompt": "node scripts/dist/run_prompt.js",
"materialize_queries": "node scripts/dist/materialize_queries.js"
},
"devDependencies": {
Expand Down Expand Up @@ -64,10 +65,13 @@
"@latitude-data/display_table": "workspace:^",
"@latitude-data/embedding": "workspace:*",
"@latitude-data/jwt": "workspace:*",
"@latitude-data/llm-manager": "workspace:^",
"@latitude-data/source-manager": "workspace:^",
"@latitude-data/storage-driver": "workspace:^",
"@latitude-data/svelte": "workspace:*",
"chalk": "^5.3.0",
"cheerio": "1.0.0-rc.12",
"cli-width": "^4.1.0",
"lodash-es": "^4.17.21",
"ora": "^8.0.1"
}
Expand Down
25 changes: 23 additions & 2 deletions apps/server/scripts.rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const materializeScript = {
...common,
}

const runScript = {
const runQueryScript = {
input: 'scripts/run_query/index.ts',
output: {
file: 'scripts/dist/run_query.js',
Expand All @@ -42,8 +42,29 @@ const runScript = {
...common,
}

const runPromptScript = {
input: 'scripts/run_prompt/index.ts',
output: {
file: 'scripts/dist/run_prompt.js',
format: 'esm',
sourcemap: true,
},
external: [
'node:util',
'ora',
'fs',
'chalk',
'cli-width',
'@latitude-data/source-manager',
'@latitude-data/llm-manager',
'@latitude-data/storage-driver',
'@latitude-data/custom_types',
],
...common,
}

/**
* @typedef {import('rollup').RollupOptions} RollupOptions
* @type {RollupOptions}
*/
export default [materializeScript, runScript]
export default [materializeScript, runQueryScript, runPromptScript]
128 changes: 128 additions & 0 deletions apps/server/scripts/run_prompt/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import modelManager from '../../src/lib/server/modelManager'
import ora from 'ora'
import chalk from 'chalk'
import cliWidth from 'cli-width'
import { parseArgs, type ParseArgsConfig } from 'node:util'
import { parseFromUrl } from '@latitude-data/custom_types'

type CommandArgs = {
prompt: string
params: Record<string, unknown>
debug: boolean
}

const OPTIONS = {
debug: {
type: 'boolean',
short: 'd',
},
param: {
type: 'string',
short: 'p',
multiple: true,
},
}
function getArgs(): CommandArgs {
const args = process.argv.slice(2)
const { values, positionals } = parseArgs({
args,
allowPositionals: true,
options: OPTIONS as ParseArgsConfig['options'],
})
const { debug, param } = values as { debug?: boolean; param?: string[] }
const paramsUrl = param
?.reduce((acc: string[], param: string) => {
const [key, ...rest] = param.split('=')
const value = rest.join('=')
return [...acc, `${key}=${value}`]
}, [])
.join('&')
const params = paramsUrl ? parseFromUrl(paramsUrl) : {}

const [prompt] = positionals as string[]
if (!prompt) throw new Error('Prompt is required')

return {
debug: debug ?? false,
prompt,
params,
}
}

async function runPrompt({ prompt, params, debug }: CommandArgs) {
const spinner = ora().start()

const onDebug = (message: string) => {
spinner.text = message
}

let lastToken = ''
let currentLine = 0
const onToken = async (token: string) => {
if (spinner.isSpinning) spinner.stop()

/* Fancy printing method ahead. I could just print the token and call it a day
but I'm going to try to make it look nicer. :)
*/

// Re-print the last token with its regular color
process.stdout.moveCursor(-lastToken.length, 0)
process.stdout.write(chalk.reset(lastToken))

// If there's going to be a new line (either by line break or by overflowing),
// print the part of the token before the new line with its regular color
if (token.includes('\n')) {
const parts = token.split('\n')
parts.slice(0, -1).forEach((part) => {
process.stdout.write(chalk.reset(part))
process.stdout.write('\n')
})
token = parts[parts.length - 1]!
currentLine = 0
}

const maxWidth = cliWidth()
if (currentLine + token.length > maxWidth) {
const leftChars = maxWidth - currentLine
process.stdout.write(token.slice(0, leftChars))
token = token.slice(leftChars)
currentLine = 0
}

// Print the rest of the token with a blue color
process.stdout.write(chalk.blue(token))
lastToken = token
currentLine += token.length
}

try {
const model = await modelManager.loadFromPrompt(prompt)

if (debug) {
const compiledPrompt = await model.compilePrompt({
promptPath: prompt,
params,
onDebug,
})
spinner.stop()
console.log(compiledPrompt.prompt)
return
}

await model.runPrompt({ promptPath: prompt, params, onDebug, onToken })
await onToken('')
if (spinner.isSpinning) spinner.stop()
console.log()
} catch (e) {
const error = e as Error
if (spinner.isSpinning) {
spinner.fail(error.message)
} else {
console.error(error.message)
}
}
}

const args = getArgs()

runPrompt(args)
1 change: 1 addition & 0 deletions apps/server/src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const BASE_STATIC_PATH = 'static/.latitude'
export const APP_CONFIG_PATH = `${BASE_STATIC_PATH}/latitude.json`
export const QUERIES_DIR = `${BASE_STATIC_PATH}/queries`
export const PROMPTS_DIR = `${BASE_STATIC_PATH}/prompts`
export const STORAGE_DIR = `${BASE_STATIC_PATH}/storage`
6 changes: 6 additions & 0 deletions apps/server/src/lib/server/modelManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { PROMPTS_DIR } from '../constants'
import { ModelManager } from '@latitude-data/llm-manager'
import sourceManager from './sourceManager'

const modelManager = new ModelManager(PROMPTS_DIR, sourceManager)
export default modelManager
17 changes: 17 additions & 0 deletions packages/cli/core/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import initSentry from '$src/integrations/sentry'
import loginCommand from './commands/cloud/login'
import logoutCommand from './commands/cloud/logout'
import runCommand from './commands/run'
import promptCommand from './commands/prompt'
import materializeCommand from './commands/materialize'
import sade from 'sade'
import setupCommand from './commands/setup'
Expand Down Expand Up @@ -96,6 +97,22 @@ CLI.command('run <query_name>')
.example('run users --param user_id=foo --param limit=10')
.action(runCommand)

CLI.command('prompt <prompt_name>')
.describe('Run a prompt from the Latitude app.')
.option(
'--debug',
'Watch debug messages from while the prompt is being generated',
)
.option(
'--param',
'Add a parameter to the prompt. Use the format --param <name>=<value>',
)
.example('prompt analysis')
.example(
'prompt analysis --param query=users --param user_id=foo --param limit=10',
)
.action(promptCommand)

CLI.command('materialize')
.describe(
'Materialize all queries that are configured with \n {@config materialize: true}',
Expand Down
54 changes: 54 additions & 0 deletions packages/cli/core/src/commands/prompt/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import config from '$src/config'
import setRootDir from '$src/lib/decorators/setRootDir'
import setup from '$src/lib/decorators/setup'
import spawn from '$src/lib/spawn'
import syncQueries from '$src/lib/sync/syncQueries'
import tracked from '$src/lib/decorators/tracked'
import syncLatitudeJson from '$src/lib/sync/syncLatitudeJson'
import syncPrompts from '$src/lib/sync/syncPrompts'

async function prompt(
promptPath: string,
opts?: {
param: string[] | string | undefined
debug: boolean
},
) {
const debug = opts?.debug || false
const params =
typeof opts?.param === 'string' ? [opts.param] : opts?.param ?? []

await syncQueries({ watch: false })
await syncPrompts({ watch: false })
await syncLatitudeJson({ watch: false })

const args = [
'run',
'prompt',
promptPath,
'--',
debug ? '--debug' : '',
...params.map((param) => `--param=${param}`),
].filter(Boolean)

return spawn(
'npm',
args,
{
detached: false,
cwd: config.appDir,
stdio: 'inherit',
},
{
onError: (error: Error) => {
console.error('Error running query:', error)
process.exit(1)
},
onClose: (code: number) => {
process.exit(code)
},
},
)
}

export default tracked('runCommand', setRootDir(setup(prompt)))
4 changes: 4 additions & 0 deletions packages/cli/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ class CLIConfig {
public get queriesDir() {
return path.join(this.rootDir, APP_FOLDER, 'static', '.latitude', 'queries')
}

public get promptsDir() {
return path.join(this.rootDir, APP_FOLDER, 'static', '.latitude', 'prompts')
}
}

export default new CLIConfig({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export default function isSourceFile(srcPath: string) {
export default function isConfigFile(srcPath: string) {
return srcPath.endsWith('.yml') || srcPath.endsWith('.yaml')
}
2 changes: 2 additions & 0 deletions packages/cli/core/src/lib/sync/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import syncDotenv from './syncDotenv'
import syncLatitudeJson from './syncLatitudeJson'
import syncQueries from './syncQueries'
import syncPrompts from './syncPrompts'
import syncStaticFiles from './syncStaticFiles'
import syncViews from './syncViews'

Expand All @@ -13,5 +14,6 @@ export default function sync(
syncDotenv({ watch }),
syncLatitudeJson({ watch }),
syncQueries({ watch }),
syncPrompts({ watch }),
])
}
Loading

0 comments on commit f1c77d3

Please sign in to comment.