Skip to content
This repository has been archived by the owner on Jun 13, 2024. It is now read-only.

Commit

Permalink
feat: mustache prepare (#127)
Browse files Browse the repository at this point in the history
* feat: mustache prepare

Signed-off-by: Yuhang Shi <[email protected]>

* fix: last

Signed-off-by: Yuhang Shi <[email protected]>

* template

Signed-off-by: Yuhang Shi <[email protected]>

* fix: test

Signed-off-by: Yuhang Shi <[email protected]>

* feat: publish

Signed-off-by: Yuhang Shi <[email protected]>

* fix: build

Signed-off-by: Yuhang Shi <[email protected]>

* fix: remove publish test

Signed-off-by: Yuhang Shi <[email protected]>

* feat: bump nodejs version

Signed-off-by: Yuhang Shi <[email protected]>

---------

Signed-off-by: Yuhang Shi <[email protected]>
  • Loading branch information
Yuhang Shi authored Nov 6, 2023
1 parent b743a54 commit bb28b07
Show file tree
Hide file tree
Showing 26 changed files with 271 additions and 51 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/codegen.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Codegen

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

on:
workflow_dispatch:
pull_request:
paths:
- 'yarn.lock'
- 'tools/devkit/**'
- 'templates/**'
push:
branches: [main]
paths:
- 'yarn.lock'
- 'tools/devkit/**'
- 'templates/**'

jobs:
nodejs-template-codegen-validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup node
uses: actions/setup-node@v4
with:
node-version-file: 'package.json'
cache: yarn

- name: Install dependencies
run: yarn install --immutable

- name: validate
run: yarn dev ts-mustache-codegen
3 changes: 2 additions & 1 deletion .github/workflows/nodejs-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ on:
- '.github/workflows/nodejs-build-common.yml'
- '.github/workflows/nodejs-ci.yml'
- '.github/workflows/codeql.yml'
- 'templates/**'
push:
branches: [main]
paths:
Expand All @@ -21,7 +22,7 @@ on:
- '.github/workflows/nodejs-build-common.yml'
- '.github/workflows/nodejs-ci.yml'
- '.github/workflows/codeql.yml'

- 'templates/**'
jobs:
codeql:
name: nodejs-ci-codeql
Expand Down
6 changes: 5 additions & 1 deletion packages/nodejs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pleisto/node-flappy",
"version": "0.0.2",
"version": "0.0.2.beta-0",
"license": "Apache-2.0",
"exports": {
".": {
Expand All @@ -11,6 +11,7 @@
"main": "./src/index.ts",
"types": "./src/index.ts",
"files": [
"templates",
"README.md",
"dist"
],
Expand All @@ -27,6 +28,8 @@
"dependencies": {
"@pleisto/flappy-nodejs-bindings": "next",
"@roarr/middleware-serialize-error": "^1.0.0",
"glob": "^10.3.3",
"mustache": "^4.2.0",
"radash": "^11.0.0",
"roarr": "^7.15.1",
"yaml": "^2.3.3",
Expand All @@ -38,6 +41,7 @@
"openai": "^4.12.4"
},
"devDependencies": {
"@types/mustache": "^4",
"@vitest/coverage-v8": "^0.34.6",
"openai": "^4.12.4",
"vitest": "^0.34.6"
Expand Down
9 changes: 3 additions & 6 deletions packages/nodejs/src/features/codeInterpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from '../flappy-feature.interface'
import { evalPythonCode } from '@pleisto/flappy-nodejs-bindings'
import { log } from '../utils'
import { templateRenderer } from '../renderer'

export const codeInterpreterType = 'codeInterpreter'

Expand Down Expand Up @@ -50,11 +51,7 @@ export class CodeInterpreter<
TReturn extends typeof CodeInterpreterOutputZ
> extends FlappyFeatureBase<CodeInterpreterDefinition<TName>> {
public override buildDescription(): string {
return `
An safe sandbox that only support the built-in library. The execution time is limited to 120 seconds. The task is to define a function named "main" that doesn't take any parameters. The output should be a String. Network access is ${
this.options?.enableNetwork ? 'enabled' : 'disabled'
}
`.trim()
return templateRenderer('features/codeInterpreter/description', { enabled: this.options?.enableNetwork })
}

public override async call(_agent: FlappyAgentInterface, args: z.infer<TArgs>): Promise<z.infer<TReturn>> {
Expand All @@ -66,7 +63,7 @@ export class CodeInterpreter<
log.debug({ code }, 'Generated Code:')

const result = await evalPythonCode(
`${code}\nprint(main())`,
templateRenderer('features/codeInterpreter/evalCode', { code }),
this.options?.enableNetwork ?? false,
Object.entries(this.options?.env ?? {}),
codeInterpreterGlobalCacheDirectory
Expand Down
19 changes: 6 additions & 13 deletions packages/nodejs/src/features/synthesized.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { type JsonObject } from 'roarr/dist/types'
import { type FlappyFeatureMetadataBase, type CreateFunction } from '../flappy-feature.interface'
import { FlappyFeatureBase } from './base'
import { type FlappyAgentInterface } from '..'
import { templateRenderer } from '../renderer'

const extractSchema = (schema: any, prop: string): string =>
JSON.stringify(omit(schema.parameters.properties[prop], ['description']))
Expand All @@ -27,23 +28,18 @@ export class SynthesizedFunction<
TReturn extends z.ZodType
> extends FlappyFeatureBase<SynthesizedFunctionDefinition<TName, TArgs, TReturn>> {
public override async call(agent: FlappyAgentInterface, args: z.infer<TArgs>): Promise<z.infer<TReturn>> {
const describe = this.define.description
const describe = this.define.description ?? ''
const returnTypeSchema = extractSchema(this.callingSchema, 'returnType')
const argsSchema = extractSchema(this.callingSchema, 'args')
const prompt = (args as any) instanceof Object ? JSON.stringify(args) : args
const originalRequestMessage: ChatMLMessage[] = [
{
role: 'system',
content: `${describe}
User request according to the following JSON Schema:
${argsSchema}
Translate it into JSON objects according to the following JSON Schema:
${returnTypeSchema}`
content: templateRenderer('features/synthesized/systemMessage', { describe, argsSchema, returnTypeSchema })
},
{
role: 'user',
content: `user request:${prompt}\n\njson object:`
content: templateRenderer('features/synthesized/userMessage', { prompt })
}
]
let requestMessage = originalRequestMessage
Expand All @@ -69,14 +65,11 @@ export class SynthesizedFunction<
...originalRequestMessage,
{
role: 'assistant',
content: result?.data ?? ''
content: result.data
},
{
role: 'user',
content: `You response is invalid for the following reason:
${(err as Error).message}
Please try again.`
content: templateRenderer('error/retry', { message: (err as Error).message })
}
]
}
Expand Down
13 changes: 6 additions & 7 deletions packages/nodejs/src/flappy-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { createFlappyAgent } from './flappy-agent'
import { LLMBase } from './llms/llm-base'
import { type ChatMLMessage, type GenerateConfig, type ChatMLResponse } from './llms/interface'
import { type IsNever, z } from './flappy-type'
import { env } from 'node:process'
import { createCodeInterpreter, createInvokeFunction, createSynthesizedFunction } from './features'
import { type FindFlappyFeature } from './flappy-feature'
import { type FlappyFeatureDefinitions } from './flappy-feature.interface'
Expand Down Expand Up @@ -50,9 +49,9 @@ test('create flappy agent normally', async () => {

expect(agent.executePlanSystemMessage().content).toMatchInlineSnapshot(`
"You are an AI assistant that makes step-by-step plans to solve problems, utilizing external functions. Each step entails one plan followed by a function-call, which will later be executed to gather args for that step.
Make as few plans as possible if it can solve the problem.
The functions list is described using the following YAML schema array:
- name: synthesizedFunction
Make as few plans as possible if it can solve the problem.
The functions list is described using the following YAML schema array:
- name: synthesizedFunction
description: synthesizedFunction
parameters:
type: object
Expand Down Expand Up @@ -83,8 +82,8 @@ test('create flappy agent normally', async () => {
description: Function return type
Your specified plans should be output as JSON object array and adhere to the following JSON schema:
{
Your specified plans should be output as JSON object array and adhere to the following JSON schema:
{
\\"type\\": \\"array\\",
\\"items\\": {
\\"type\\": \\"object\\",
Expand Down Expand Up @@ -118,7 +117,7 @@ test('create flappy agent normally', async () => {
\\"description\\": \\"An array storing the steps.\\"
}
Only the listed functions are allowed to be used."
Only the listed functions are allowed to be used."
`)

type Features = (typeof agent)['config']['features']
Expand Down
18 changes: 4 additions & 14 deletions packages/nodejs/src/flappy-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { z } from './flappy-type'
import { convertJsonToYaml, zodToCleanJsonSchema, log } from './utils'
import { type FindFlappyFeature, type FlappyFeatureNames, type AnyFlappyFeature } from './flappy-feature'
import { type JsonObject } from 'roarr/dist/types'
import { templateRenderer } from './renderer'

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const lanOutputSchema = (enableCoT: boolean) => {
Expand Down Expand Up @@ -87,15 +88,7 @@ export class FlappyAgent<
const returnSchema = JSON.stringify(zodToCleanJsonSchema(zodSchema), null, 4)
return {
role: 'system',
content: `You are an AI assistant that makes step-by-step plans to solve problems, utilizing external functions. Each step entails one plan followed by a function-call, which will later be executed to gather args for that step.
Make as few plans as possible if it can solve the problem.
The functions list is described using the following YAML schema array:
${functions}
Your specified plans should be output as JSON object array and adhere to the following JSON schema:
${returnSchema}
Only the listed functions are allowed to be used.`
content: templateRenderer('agent/systemMessage', { functions, returnSchema })
}
}

Expand All @@ -110,7 +103,7 @@ export class FlappyAgent<
const zodSchema = lanOutputSchema(enableCot)
const originalRequestMessage: ChatMLMessage[] = [
this.executePlanSystemMessage(enableCot),
{ role: 'user', content: `Prompt: ${prompt}\n\nPlan array:` }
{ role: 'user', content: templateRenderer('agent/userMessage', { prompt }) }
]
let requestMessage = originalRequestMessage
let plan: any[] = []
Expand Down Expand Up @@ -150,10 +143,7 @@ export class FlappyAgent<
},
{
role: 'user',
content: `You response is invalid for the following reason:
${(err as Error).message}
Please try again.`
content: templateRenderer('error/retry', { message: (err as Error).message })
}
]
}
Expand Down
1 change: 1 addition & 0 deletions packages/nodejs/src/renderer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './renderer'
51 changes: 51 additions & 0 deletions packages/nodejs/src/renderer/mustacheTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
type MustacheValue = string | number | boolean

type MustacheRecord<T> = T

type MustacheSection<T> = T[] | T

interface Test_ping {
name: MustacheValue
}

interface Error_retry {
message: MustacheValue
}

interface Agent_userMessage {
prompt: MustacheValue
}

interface Agent_systemMessage {
functions: MustacheValue
returnSchema: MustacheValue
}

interface Features_synthesized_userMessage {
prompt: MustacheValue
}

interface Features_synthesized_systemMessage {
describe: MustacheValue
argsSchema: MustacheValue
returnTypeSchema: MustacheValue
}

interface Features_codeInterpreter_evalCode {
code: MustacheValue
}

interface Features_codeInterpreter_description {
enabled?: MustacheValue
}

export type TemplateMap = {
'test/ping': Test_ping,
'error/retry': Error_retry,
'agent/userMessage': Agent_userMessage,
'agent/systemMessage': Agent_systemMessage,
'features/synthesized/userMessage': Features_synthesized_userMessage,
'features/synthesized/systemMessage': Features_synthesized_systemMessage,
'features/codeInterpreter/evalCode': Features_codeInterpreter_evalCode,
'features/codeInterpreter/description': Features_codeInterpreter_description,
}
9 changes: 9 additions & 0 deletions packages/nodejs/src/renderer/renderer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { expect, test, describe } from 'vitest'
import { templateRenderer } from './renderer'

describe('renderer', () => {
test('ok', () => {
const result = templateRenderer('test/ping', { name: 'foo' })
expect(result).toEqual('foo pong')
})
})
31 changes: 31 additions & 0 deletions packages/nodejs/src/renderer/renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { type TemplateMap } from './mustacheTypes'
import { globSync } from 'glob'
import path from 'path'
import fs from 'fs'
import Mustache from 'mustache'

const TemplateFolderPath = './templates'
const MUSTACHE_EXTENSION = '.mustache'

const files = globSync(path.join(TemplateFolderPath, `**/*${MUSTACHE_EXTENSION}`))

if (files.length === 0) throw new Error('template not found')

const contents = files.map(f => fs.readFileSync(f, { encoding: 'utf8' }))

const templates = Object.fromEntries(
files.map((f, i) => [path.relative(TemplateFolderPath, f).replace(MUSTACHE_EXTENSION, ''), contents[i]])
)

export type TemplateRenderer = <K extends keyof TemplateMap>(templateName: K, params: TemplateMap[K]) => string

export type RendererType = {
[K in keyof TemplateMap]: (params: TemplateMap[K]) => string
}

export const templateRenderer: TemplateRenderer = (name, params) => {
const template = templates[name]
if (!template) throw new Error(`Unknown template '${String(name)}'`)

return Mustache.render(template, params, undefined, { escape: value => value })
}
1 change: 1 addition & 0 deletions packages/nodejs/templates
6 changes: 4 additions & 2 deletions renovate.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"commitMessage": "chore(deps): update deps",
"commitMessage": "chore(deps): upgrade dependencies",
"extends": [
"config:recommended",
"group:all"
Expand All @@ -12,5 +12,7 @@
"schedule": [
"on monday and thursday"
],
"ignoreDeps": []
"ignoreDeps": [
"chalk"
]
}
9 changes: 9 additions & 0 deletions templates/agent/systemMessage.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
You are an AI assistant that makes step-by-step plans to solve problems, utilizing external functions. Each step entails one plan followed by a function-call, which will later be executed to gather args for that step.
Make as few plans as possible if it can solve the problem.
The functions list is described using the following YAML schema array:
{{functions}}

Your specified plans should be output as JSON object array and adhere to the following JSON schema:
{{returnSchema}}

Only the listed functions are allowed to be used.
3 changes: 3 additions & 0 deletions templates/agent/userMessage.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Prompt: {{prompt}}

Plan array:
4 changes: 4 additions & 0 deletions templates/error/retry.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Your response is invalid for the following reason:
{{message}}

Please try again.
1 change: 1 addition & 0 deletions templates/features/codeInterpreter/description.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
An safe sandbox that only support the built-in library. The execution time is limited to 120 seconds. The task is to define a function named "main" that doesn't take any parameters. The output should be a String. Network access is {{#enabled}}enabled{{/enabled}}{{^enabled}}disabled{{/enabled}}
Loading

0 comments on commit bb28b07

Please sign in to comment.