Skip to content

Commit 9641059

Browse files
authored
feat(zai): added zai, vai and wasm (#524)
1 parent 9db784c commit 9641059

File tree

99 files changed

+1038385
-22
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+1038385
-22
lines changed

.github/workflows/vai.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: vai
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
paths:
8+
- 'vai/**'
9+
10+
pull_request:
11+
paths:
12+
- 'vai/**'
13+
14+
workflow_dispatch: {}
15+
16+
defaults:
17+
run:
18+
working-directory: ./vai
19+
20+
jobs:
21+
vai:
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 15
24+
steps:
25+
- uses: actions/checkout@v4
26+
- uses: pnpm/action-setup@v3.0.0
27+
with:
28+
version: 8.6.2
29+
- run: pnpm install --frozen-lockfile
30+
- run: pnpm build
31+
- run: pnpm test
32+
env:
33+
CLOUD_BOT_ID: ${{ secrets.CLOUD_BOT_ID }}
34+
CLOUD_PAT: ${{ secrets.CLOUD_PAT }}
35+
- name: Publish
36+
if: github.ref == 'refs/heads/master' || github.event_name == 'workflow_dispatch'
37+
uses: botpress/gh-actions/publish-if-not-exists@master
38+
with:
39+
path: './vai'
40+
token: '${{ secrets.NPM_ACCESS_TOKEN }}'

.github/workflows/wasm.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: wasm
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
paths:
8+
- 'wasm/**'
9+
10+
pull_request:
11+
paths:
12+
- 'wasm/**'
13+
14+
workflow_dispatch: {}
15+
16+
defaults:
17+
run:
18+
working-directory: ./wasm
19+
20+
jobs:
21+
wasm:
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 15
24+
steps:
25+
- uses: actions/checkout@v4
26+
- uses: pnpm/action-setup@v3.0.0
27+
with:
28+
version: 8.6.2
29+
- run: pnpm install --frozen-lockfile
30+
- run: pnpm build
31+
- run: pnpm test
32+
- name: Publish
33+
if: github.ref == 'refs/heads/master' || github.event_name == 'workflow_dispatch'
34+
uses: botpress/gh-actions/publish-if-not-exists@master
35+
with:
36+
path: './wasm'
37+
token: '${{ secrets.NPM_ACCESS_TOKEN }}'

.github/workflows/zai.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: zai
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
paths:
8+
- 'zai/**'
9+
10+
pull_request:
11+
paths:
12+
- 'zai/**'
13+
14+
workflow_dispatch: {}
15+
16+
defaults:
17+
run:
18+
working-directory: ./zai
19+
20+
jobs:
21+
zai:
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 15
24+
steps:
25+
- uses: actions/checkout@v4
26+
- uses: pnpm/action-setup@v3.0.0
27+
with:
28+
version: 8.6.2
29+
- run: pnpm install --frozen-lockfile
30+
- run: pnpm build
31+
- run: pnpm test
32+
env:
33+
CLOUD_BOT_ID: ${{ secrets.CLOUD_BOT_ID }}
34+
CLOUD_PAT: ${{ secrets.CLOUD_PAT }}
35+
- name: Publish
36+
if: github.ref == 'refs/heads/master' || github.event_name == 'workflow_dispatch'
37+
uses: botpress/gh-actions/publish-if-not-exists@master
38+
with:
39+
path: './zai'
40+
token: '${{ secrets.NPM_ACCESS_TOKEN }}'

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"vitest.disableWorkspaceWarning": true
3+
}

vai/.npmignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
src/
2+
node_modules/
3+
*.test.ts
4+
*.test.js
5+
.env
6+
.env.local
7+
.turbo

vai/README.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# Vitest AI
2+
3+
**Vai** (stands for _Vitest + AI_) is a lightweight vitest extension that uses LLMs to do assertions.
4+
The goal of this library is primarily to allow testing the output of LLMs like the new autonomous engine, as the output is dynamic and qualitative we can't rely on traditional hard-coded tests.
5+
6+
To remove the flakiness and human-input from these tests, we need LLMs.
7+
8+
It's built on top of Zui and the Botpress client to interface with the different LLMs.
9+
10+
## Usage
11+
12+
```typescript
13+
import { check, rate, filter, extract } from '@botpress/vai'
14+
import { describe, test } from 'vitest'
15+
16+
describe('my test suite', () => {
17+
test('example', () => {
18+
check('botpress', 'is a chatbot company').toBe(true)
19+
})
20+
})
21+
```
22+
23+
## `check (assertion)`
24+
25+
Checks that the provided value matches the provided condition
26+
27+
```typescript
28+
test('example', () => {
29+
// works with strings
30+
check('hello', 'is a greeting message').toBe(true)
31+
32+
// also works with objects, arrays etc..
33+
check(
34+
{
35+
message: 'hello my friend',
36+
from: 'user'
37+
},
38+
'is a greeting message'
39+
).toBe(true)
40+
})
41+
```
42+
43+
## `extract (assertion)`
44+
45+
Extracts from anything in input the requested Zui Schema:
46+
47+
```typescript
48+
const person = z.object({
49+
name: z.string(),
50+
age: z.number().optional(),
51+
country: z.string().optional()
52+
})
53+
54+
extract('My name is Sylvain, I am 33 yo and live in Canada', person).toMatchObject({
55+
name: 'Sylvain',
56+
age: 33,
57+
country: 'Canada'
58+
})
59+
```
60+
61+
Also added support for `toMatchInlineSnapshot`:
62+
63+
```typescript
64+
test('toMatchInlineSnapshot', () => {
65+
extract('My name is Eric!', z.object({ name: z.string() })).toMatchInlineSnapshot(`
66+
{
67+
"name": "Eric",
68+
}
69+
`)
70+
})
71+
```
72+
73+
## `filter (assertion)`
74+
75+
Filters an array of anything `T[]` based on a provided condition:
76+
77+
```typescript
78+
const countries = ['canada', 'germany', 'usa', 'paris', 'mexico']
79+
filter(countries, 'is in north america').toBe(['canada', 'usa', 'mexico'])
80+
filter(countries, 'is the name of a country').length.toBe(4)
81+
```
82+
83+
## `rate (assertion)`
84+
85+
Given any input `T`, gives a rating between `1` (worst) and `5` (best):
86+
87+
```typescript
88+
test('good', () => rate('ghandi', 'is a good person').toBeGreaterThanOrEqual(4))
89+
test('evil', () => rate('hitler', 'is a good person').toBe(3))
90+
```
91+
92+
## Few-shot Examples
93+
94+
All assertion methods accept examples to provide the LLM with few-shot learning and help increase the accuracy.
95+
96+
```typescript
97+
describe('learns from examples', () => {
98+
test('examples are used to understand how to classify correctly', () => {
99+
const examples = [
100+
{
101+
expected: true,
102+
value: 'Rasa the chatbot framework',
103+
reason: 'Rasa is a chatbot framework, so it competes with Botpress'
104+
},
105+
{
106+
expected: false,
107+
value: 'Rasa the coffee company',
108+
reason: 'Since Rasa is a coffee company, it does not compete with Botpress which is not in the coffee business'
109+
}
110+
]
111+
112+
check('Voiceflow', 'is competitor', { examples }).toBe(true)
113+
check('Toyota', 'is competitor', { examples }).toBe(false)
114+
})
115+
})
116+
```
117+
118+
## Failure Messages
119+
120+
All model predictions have nice failure messages by default:
121+
122+
```typescript
123+
const countries = ['canada', 'germany', 'usa', 'paris', 'mexico']
124+
filter(countries, 'is in north america').toBe(['canada', 'usa'])
125+
```
126+
127+
## Promises
128+
129+
All assertion methods can also be used outside Vitest tests, as they return an `PromiseLike<T>` object that can be awaited.
130+
131+
```typescript
132+
test('test truth', async () => {
133+
const { result } = await check('hello', 'is a greeting message')
134+
expect(result).toBe(true)
135+
})
136+
```
137+
138+
## Bail on failure
139+
140+
You can await the assertion to bail immediately on failure and prevent other test cases to run:
141+
142+
```typescript
143+
test('no bail', () => {
144+
check('hello', 'is a greeting message').toBe(false)
145+
console.log('this will run as the above is not awaited, it will bail at the end of the test')
146+
})
147+
148+
test('bail', async () => {
149+
await check('hello', 'is a greeting message').toBe(false)
150+
console.log('this will not run, the test has bailed')
151+
})
152+
```
153+
154+
## Changing the evaluator model
155+
156+
By default, GPT-4o mini is used to evaluate the tests, but the evaluator can be changed from inside a test:
157+
158+
```typescript
159+
test('simple', () => {
160+
setEvaluator('anthropic__claude-3-5-sonnet-20240620')
161+
rate('hello', 'is a greeting message').toBe(5)
162+
})
163+
```

vai/ensure-env.cjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
;(function () {
2+
if (!process.env.CLOUD_BOT_ID) {
3+
throw new Error('Env: CLOUD_BOT_ID is required')
4+
}
5+
6+
if (!process.env.CLOUD_PAT) {
7+
throw new Error('Env: CLOUD_PAT is required')
8+
}
9+
})()

vai/package.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "@botpress/vai",
3+
"version": "0.0.5",
4+
"description": "Vitest AI (vai) – a vitest extension for testing with LLMs",
5+
"exports": {
6+
".": {
7+
"types": "./dist/index.d.ts",
8+
"import": "./dist/index.js",
9+
"require": "./dist/index.js"
10+
}
11+
},
12+
"scripts": {
13+
"build": "npm run build:types && npm run build:neutral",
14+
"build:neutral": "esbuild src/**/*.ts src/*.ts --platform=neutral --outdir=dist",
15+
"build:types": "tsup",
16+
"watch": "tsup --watch",
17+
"test": "vitest run --config vitest.config.ts",
18+
"test:update": "vitest -u run --config vitest.config.ts",
19+
"test:watch": "vitest --config vitest.config.ts",
20+
"build-with-latest-models": "pnpm run update-types && pnpm run update-models && pnpm run build",
21+
"update-models": "ts-node ./src/scripts/update-models.ts",
22+
"update-types": "ts-node ./src/scripts/update-types.ts"
23+
},
24+
"keywords": [],
25+
"author": "",
26+
"license": "ISC",
27+
"dependencies": {
28+
"json5": "^2.2.1",
29+
"jsonrepair": "^3.2.0"
30+
},
31+
"devDependencies": {
32+
"@types/lodash": "^4.17.0",
33+
"dotenv": "^16.3.1",
34+
"esbuild": "^0.24.2",
35+
"ts-node": "^10.9.2",
36+
"tsup": "^8.3.5",
37+
"typescript": "^5.7.2"
38+
},
39+
"peerDependencies": {
40+
"@botpress/client": "^0.40.0",
41+
"@botpress/wasm": "^1.0.1",
42+
"@bpinternal/zui": "^0.13.4",
43+
"lodash": "^4.17.21",
44+
"vitest": "^2 || ^3 || ^4 || ^5"
45+
}
46+
}

0 commit comments

Comments
 (0)