Skip to content

Commit

Permalink
Add Zod validation support, switch from axios to fetch API
Browse files Browse the repository at this point in the history
  • Loading branch information
ahejlsberg committed Dec 12, 2023
1 parent 839c2bd commit ef74a9b
Show file tree
Hide file tree
Showing 29 changed files with 999 additions and 382 deletions.
6 changes: 3 additions & 3 deletions examples/calendar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
"license": "MIT",
"dependencies": {
"dotenv": "^16.3.1",
"typechat": "^0.0.10"
"typechat": "^1.0.0"
},
"devDependencies": {
"@types/node": "^20.3.1",
"@types/node": "^20.10.4",
"copyfiles": "^2.4.1",
"typescript": "^5.1.3"
"typescript": "^5.3.3"
}
}
6 changes: 4 additions & 2 deletions examples/calendar/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import fs from "fs";
import path from "path";
import dotenv from "dotenv";
import { createLanguageModel, createJsonTranslator, processRequests } from "typechat";
import { createTypeScriptJsonValidator } from "typechat/ts";
import { CalendarActions } from './calendarActionsSchema';

// TODO: use local .env file.
dotenv.config({ path: path.join(__dirname, "../../../.env") });

const model = createLanguageModel(process.env);
const schema = fs.readFileSync(path.join(__dirname, "calendarActionsSchema.ts"), "utf8");
const translator = createJsonTranslator<CalendarActions>(model, schema, "CalendarActions");
translator.validator.stripNulls = true;
const validator = createTypeScriptJsonValidator<CalendarActions>(schema, "CalendarActions");
const translator = createJsonTranslator(model, validator);
//translator.stripNulls = true;

// Process requests interactively or from the input file specified on the command line
processRequests("📅> ", process.argv[2], async (request) => {
Expand Down
45 changes: 45 additions & 0 deletions examples/coffeeShop-zod/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Coffee Shop

The Coffee Shop example shows how to capture user intent as a set of "nouns".
In this case, the nouns are items in a coffee order, where valid items are defined starting from the [`Cart`](./src/coffeeShopSchema.ts) type.
This example also uses the [`UnknownText`](./src/coffeeShopSchema.ts) type as a way to capture user input that doesn't match to an existing type in [`Cart`](./src/coffeeShopSchema.ts).

# Try Coffee Shop

To run the Coffee Shop example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment).

# Usage

Example prompts can be found in [`src/input.txt`](./src/input.txt) and [`src/input2.txt`](./src/input2.txt).

For example, we could use natural language to describe our coffee shop order:

**Input**:

```
☕> we'd like a cappuccino with a pack of sugar
```

**Output**:

```json
{
"items": [
{
"type": "lineitem",
"product": {
"type": "LatteDrinks",
"name": "cappuccino",
"options": [
{
"type": "Sweeteners",
"name": "sugar",
"optionQuantity": "regular"
}
]
},
"quantity": 1
}
]
}
```
23 changes: 23 additions & 0 deletions examples/coffeeShop-zod/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "coffeeshop-zod",
"version": "0.0.1",
"private": true,
"description": "",
"main": "dist/main.js",
"scripts": {
"build": "tsc -p src",
"postbuild": "copyfiles -u 1 src/**/*.txt dist"
},
"author": "",
"license": "MIT",
"dependencies": {
"dotenv": "^16.3.1",
"typechat": "^1.0.0",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^20.10.4",
"copyfiles": "^2.4.1",
"typescript": "^5.3.3"
}
}
131 changes: 131 additions & 0 deletions examples/coffeeShop-zod/src/coffeeShopSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { z } from "zod";

export const OptionQuantity = z.union([z.literal('no'), z.literal('light'), z.literal('regular'), z.literal('extra'), z.number()]);

export const BakeryOptions = z.object({
type: z.literal('BakeryOptions'),
name: z.enum(['butter', 'strawberry jam', 'cream cheese']),
optionQuantity: OptionQuantity.optional()
});

export const BakeryPreparations = z.object({
type: z.literal('BakeryPreparations'),
name: z.enum(['warmed', 'cut in half'])
});

export const BakeryProducts = z.object({
type: z.literal('BakeryProducts'),
name: z.enum(['apple bran muffin', 'blueberry muffin', 'lemon poppyseed muffin', 'bagel']),
options: z.discriminatedUnion("type", [BakeryOptions, BakeryPreparations]).array()
})

export const CoffeeTemperature = z.enum(['hot', 'extra hot', 'warm', 'iced']);

export const CoffeeSize = z.enum(['short', 'tall', 'grande', 'venti']);

export const Milks = z.object({
type: z.literal('Milks'),
name: z.enum(['whole milk', 'two percent milk', 'nonfat milk', 'coconut milk', 'soy milk', 'almond milk', 'oat milk'])
})

export const Sweeteners = z.object({
type: z.literal('Sweeteners'),
name: z.enum(['equal', 'honey', 'splenda', 'sugar', 'sugar in the raw', 'sweet n low', 'espresso shot']),
optionQuantity: OptionQuantity.optional()
});

export const Syrups = z.object({
type: z.literal('Syrups'),
name: z.enum(['almond syrup', 'buttered rum syrup', 'caramel syrup', 'cinnamon syrup', 'hazelnut syrup',
'orange syrup', 'peppermint syrup', 'raspberry syrup', 'toffee syrup', 'vanilla syrup']),
optionQuantity: OptionQuantity.optional()
});

export const Toppings = z.object({
type: z.literal('Toppings'),
name: z.enum(['cinnamon', 'foam', 'ice', 'nutmeg', 'whipped cream', 'water']),
optionQuantity: OptionQuantity.optional()
});

export const Caffeines = z.object({
type: z.literal('Caffeines'),
name: z.enum(['regular', 'two thirds caf', 'half caf', 'one third caf', 'decaf'])
});

export const LattePreparations = z.object({
type: z.literal('LattePreparations'),
name: z.enum(['for here cup', 'lid', 'with room', 'to go', 'dry', 'wet'])
});

export const LatteDrinks = z.object({
type: z.literal('LatteDrinks'),
name: z.enum(['cappuccino', 'flat white', 'latte', 'latte macchiato', 'mocha', 'chai latte']),
temperature: CoffeeTemperature.optional(),
size: CoffeeSize.describe("The default is 'grande'"),
options: z.discriminatedUnion("type", [Milks, Sweeteners, Syrups, Toppings, Caffeines, LattePreparations]).array().optional(),
});

export const EspressoSize = z.enum(['solo', 'doppio', 'triple', 'quad']);

export const Creamers = z.object({
type: z.literal('Creamers'),
name: z.enum(['whole milk creamer', 'two percent milk creamer', 'one percent milk creamer', 'nonfat milk creamer',
'coconut milk creamer', 'soy milk creamer', 'almond milk creamer', 'oat milk creamer', 'half and half', 'heavy cream'])
});

export const EspressoDrinks = z.object({
type: z.literal('EspressoDrinks'),
name: z.enum(['espresso', 'lungo', 'ristretto', 'macchiato']),
temperature: CoffeeTemperature.optional(),
size: EspressoSize.optional().describe("The default is 'doppio'"),
options: z.discriminatedUnion("type", [Creamers, Sweeteners, Syrups, Toppings, Caffeines, LattePreparations]).array().optional()
});

export const CoffeeDrinks = z.object({
type: z.literal('CoffeeDrinks'),
name: z.enum(['americano', 'coffee']),
temperature: CoffeeTemperature.optional(),
size: CoffeeSize.optional().describe("The default is 'grande'"),
options: z.discriminatedUnion("type", [Creamers, Sweeteners, Syrups, Toppings, Caffeines, LattePreparations]).array().optional()
});

export const Product = z.discriminatedUnion("type", [BakeryProducts, LatteDrinks, EspressoDrinks, CoffeeDrinks]);

export const LineItem = z.object({
type: z.literal('lineitem'),
product: Product,
quantity: z.number()
});

export const UnknownText = z.object({
type: z.literal('unknown'),
text: z.string().describe("The text that wasn't understood")
});

export const Cart = z.object({
items: z.discriminatedUnion("type", [LineItem, UnknownText]).array()
});

export const CoffeeShopSchema = {
Cart: Cart.describe("A schema definition for ordering coffee and bakery products"),
UnknownText: UnknownText.describe("Use this type for order items that match nothing else"),
LineItem,
Product,
BakeryProducts,
BakeryOptions,
BakeryPreparations,
LatteDrinks,
EspressoDrinks,
CoffeeDrinks,
Syrups,
Caffeines,
Milks,
Creamers,
Toppings,
LattePreparations,
Sweeteners,
CoffeeTemperature,
CoffeeSize,
EspressoSize,
OptionQuantity
};
47 changes: 47 additions & 0 deletions examples/coffeeShop-zod/src/input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
i'd like a latte that's it
i'll have a dark roast coffee thank you
get me a coffee please
could i please get two mochas that's all
we need twenty five flat whites and that'll do it
how about a tall cappuccino
i'd like a venti iced latte
i'd like a iced venti latte
i'd like a venti latte iced
i'd like a latte iced venti
we'll also have a short tall latte
i wanna latte macchiato with vanilla
how about a peppermint latte
may i also get a decaf soy vanilla syrup caramel latte with sugar and foam
i want a latte with peppermint syrup with peppermint syrup
i'd like a decaf half caf latte
can I get a skim soy latte
i'd like a light nutmeg espresso that's it
can i have an cappuccino no foam
can i have an espresso with no nutmeg
we want a light whipped no foam mocha with extra hazelnut and cinnamon
i'd like a latte cut in half
i'd like a strawberry latte
i want a five pump caramel flat white
i want a flat white with five pumps of caramel syrup
i want a two pump peppermint three squirt raspberry skinny vanilla latte with a pump of caramel and two sugars
i want a latte cappuccino espresso and an apple muffin
i'd like a tall decaf latte iced a grande cappuccino double espresso and a warmed poppyseed muffin sliced in half
we'd like a latte with soy and a coffee with soy
i want a latte latte macchiato and a chai latte
we'd like a cappuccino with two pumps of vanilla
make that cappuccino with three pumps of vanilla
we'd like a cappuccino with a pack of sugar
make that cappuccino with two packs of sugar
we'd like a cappuccino with a pack of sugar
make that with two packs of sugar
i'd like a flat white with two equal
add three equal to the flat white
i'd like a flat white with two equal
two tall lattes. the first one with no foam. the second one with whole milk.
two tall lattes. the first one with no foam. the second one with whole milk. actually make the first one a grande.
un petit cafe
en lille kaffe
a raspberry latte
a strawberry latte
roses are red
two lawnmowers, a grande latte and a tall tree
8 changes: 8 additions & 0 deletions examples/coffeeShop-zod/src/input2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
two tall lattes. the first one with no foam. the second one with whole milk.
two tall lattes. the first one with no foam. the second one with whole milk. actually make the first one a grande.
un petit cafe
en lille kaffe
a raspberry latte
a strawberry latte
roses are red
two lawnmowers, a grande latte and a tall tree
38 changes: 38 additions & 0 deletions examples/coffeeShop-zod/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import path from "path";
import dotenv from "dotenv";
import { z } from "zod";
import { createLanguageModel, createJsonTranslator, processRequests } from "typechat";
import { createZodJsonValidator } from "typechat/zod";
import { CoffeeShopSchema } from "./coffeeShopSchema";

// TODO: use local .env file.
dotenv.config({ path: path.join(__dirname, "../../../.env") });

const model = createLanguageModel(process.env);
const validator = createZodJsonValidator(CoffeeShopSchema, CoffeeShopSchema.Cart);
const translator = createJsonTranslator(model, validator);

function processOrder(cart: z.TypeOf<typeof CoffeeShopSchema.Cart>) {
// Process the items in the cart
void cart;
}

// Process requests interactively or from the input file specified on the command line
processRequests("☕> ", process.argv[2], async (request) => {
const response = await translator.translate(request);
if (!response.success) {
console.log(response.message);
return;
}
const cart = response.data;
console.log(JSON.stringify(cart, undefined, 2));
if (cart.items.some(item => item.type === "unknown")) {
console.log("I didn't understand the following:");
for (const item of cart.items) {
if (item.type === "unknown") console.log(item.text);
}
return;
}
processOrder(cart);
console.log("Success!");
});
16 changes: 16 additions & 0 deletions examples/coffeeShop-zod/src/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es2021",
"lib": ["es2021"],
"module": "node16",
"types": ["node"],
"outDir": "../dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"inlineSourceMap": true
}
}
6 changes: 3 additions & 3 deletions examples/coffeeShop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
"license": "MIT",
"dependencies": {
"dotenv": "^16.3.1",
"typechat": "^0.0.10"
"typechat": "^1.0.0"
},
"devDependencies": {
"@types/node": "^20.3.1",
"@types/node": "^20.10.4",
"copyfiles": "^2.4.1",
"typescript": "^5.1.3"
"typescript": "^5.3.3"
}
}
4 changes: 3 additions & 1 deletion examples/coffeeShop/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import fs from "fs";
import path from "path";
import dotenv from "dotenv";
import { createLanguageModel, createJsonTranslator, processRequests } from "typechat";
import { createTypeScriptJsonValidator } from "typechat/ts";
import { Cart } from "./coffeeShopSchema";

// TODO: use local .env file.
dotenv.config({ path: path.join(__dirname, "../../../.env") });

const model = createLanguageModel(process.env);
const schema = fs.readFileSync(path.join(__dirname, "coffeeShopSchema.ts"), "utf8");
const translator = createJsonTranslator<Cart>(model, schema, "Cart");
const validator = createTypeScriptJsonValidator<Cart>(schema, "Cart");
const translator = createJsonTranslator(model, validator);

function processOrder(cart: Cart) {
// Process the items in the cart
Expand Down
6 changes: 3 additions & 3 deletions examples/math/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
"license": "MIT",
"dependencies": {
"dotenv": "^16.3.1",
"typechat": "^0.0.10"
"typechat": "^1.0.0"
},
"devDependencies": {
"@types/node": "^20.3.1",
"@types/node": "^20.10.4",
"copyfiles": "^2.4.1",
"typescript": "^5.1.3"
"typescript": "^5.3.3"
}
}
Loading

0 comments on commit ef74a9b

Please sign in to comment.