-
Notifications
You must be signed in to change notification settings - Fork 391
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Zod validation support, switch from axios to fetch API
- Loading branch information
1 parent
839c2bd
commit ef74a9b
Showing
29 changed files
with
999 additions
and
382 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
] | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!"); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.