From 3c5e681702177e6196be23c4d62fd47338e8cc13 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 4 Sep 2023 03:03:36 +0800 Subject: [PATCH] Add draft of Modelling TypeChat's CoffeeShop in .NET --- ..._building-typechat-coffeeshop-modelling.md | 1130 +++++++++++++++++ MyApp/wwwroot/css/app.css | 5 + .../db.png | Bin 0 -> 245091 bytes .../portal-products.png | Bin 0 -> 280232 bytes .../portal-update-category.png | Bin 0 -> 216750 bytes .../query-products.png | Bin 0 -> 276747 bytes .../update-category.png | Bin 0 -> 170944 bytes 7 files changed, 1135 insertions(+) create mode 100644 MyApp/_posts/2023-08-31_building-typechat-coffeeshop-modelling.md create mode 100644 MyApp/wwwroot/img/posts/building-typechat-coffeeshop-modelling/db.png create mode 100644 MyApp/wwwroot/img/posts/building-typechat-coffeeshop-modelling/portal-products.png create mode 100644 MyApp/wwwroot/img/posts/building-typechat-coffeeshop-modelling/portal-update-category.png create mode 100644 MyApp/wwwroot/img/posts/building-typechat-coffeeshop-modelling/query-products.png create mode 100644 MyApp/wwwroot/img/posts/building-typechat-coffeeshop-modelling/update-category.png diff --git a/MyApp/_posts/2023-08-31_building-typechat-coffeeshop-modelling.md b/MyApp/_posts/2023-08-31_building-typechat-coffeeshop-modelling.md new file mode 100644 index 00000000..43a67360 --- /dev/null +++ b/MyApp/_posts/2023-08-31_building-typechat-coffeeshop-modelling.md @@ -0,0 +1,1130 @@ +--- +title: Modelling TypeChat's CoffeeShop in .NET +summary: We look at Modelling TypeChat's CoffeeShop App +tags: [autoquery, c#, dev, servicestack] +image: https://images.unsplash.com/photo-1501516069922-a9982bd6f3bd?crop=entropy&fit=crop&h=1000&w=2000 +author: Demis Bellot +draft: true +--- + +## Building a TypeChat CoffeeShop .NET App + +Since the release of [Open AI's Chat GPT](https://chat.openai.com) we've been exploring its potential +in unlocking new AI powered capabilities in our Apps that were previously limited to Large companies +with dedicated AI development teams, now their capabilities is within everyone's reach with just 1 API call away - +as-is the [nature of remote APIs](https://docs.servicestack.net/service-complexity-and-dto-roles#services). + +### Chain of thought Prompt engineering + +Our initial approach of leveraging LLMs was to [create ChatGPT Agents to call APIs](/posts/chat-gpt-agents) +by adopting the [Chain-of-Thought](https://arxiv.org/abs/2201.11903) technique popularized by +[Auto-GPT](https://github.com/Significant-Gravitas/Auto-GPT) where along with the goal we also ask it for its +**plan**, **reasoning**, and **criticism** which influences its future decisions to help break down its tasks into +smaller, more achievable steps. + +We adopt this approach within a scoped context with just the APIs that we want Chat GPT to know about in order to +accomplish purpose specific tasks we assign it. We showcase the utility of this approach in +[GPTMeetingAgent(github)](https://github.com/NetCoreApps/GPTMeetingAgent) in which we use the GPT Meeting Agent +to use available APIs to search for Users and book Meetings: + +
+ +

+ Use Natural Language to get GPT Agents to book meetings with your APIs +

+
+
+ +
+
+
+ +This approach relies on a fairly sophisticated prompt to get the desired outcome whose probability of a successful response +is dependent on the ambiguity and complexity of a command and the surface complexity of the APIs they need to call. + +The difficulty then becomes how best to construct the interface of our System APIs to LLMs and how best to detect a valid +response from an invalid one. In our experience the key to best guiding LLMs to produce a valid response is to front load +the prompt with descriptive information of available functionality it should utilize and constrain its output to a restricted +surface area. + +### TypeChat + +Microsoft's new [TypeChat](https://github.com/microsoft/TypeChat) library takes another interesting approach to interfacing with LLMs where instead of +using chain of thought to continually refine LLM outputs towards a valid successful response, it relies on using +TypeScript schemas to define and restrict what valid responses Chat GPT should return, which both validates LLM responses +to verify if they're valid and if not replies back with Schema Validation errors to guide GPT into returning a successful response. + +Whilst this approach is less ambitious and open ended from harnessing the reasoning capabilities of LLMs than Chain of Thought, +it's easier to develop from a pragmatic view where instead of tweaking and refining prompt templates to get more desirable +outcomes you're defining TypeScript schemas of what you want the Natural Language free text to convert into. + +[TypeChat's CoffeeShop](https://github.com/microsoft/TypeChat/tree/main/examples/coffeeShop) +is a good example of what this looks like in real world application which uses LLMs to +implement a natural language ordering system by capturing all the different ways a Customer can order at a Cafe, +as defined in: +[coffeeShopSchema.ts](https://github.com/microsoft/TypeChat/blob/main/examples/coffeeShop/src/coffeeShopSchema.ts) + +```ts +// The following is a schema definition for ordering lattes. + +export interface Cart { + items: (LineItem | UnknownText)[]; +} + +// Use this type for order items that match nothing else +export interface UnknownText { + type: 'unknown', + text: string; // The text that wasn't understood +} + +export interface LineItem { + type: 'lineitem', + product: Product; + quantity: number; +} + +export type Product = BakeryProducts | LatteDrinks | EspressoDrinks | CoffeeDrinks; + +export interface BakeryProducts { + type: 'BakeryProducts'; + name: 'apple bran muffin' | 'blueberry muffin' | 'lemon poppyseed muffin' |'bagel' + options: (BakeryOptions | BakeryPreparations)[]; +} + +export interface BakeryOptions { + type: 'BakeryOptions'; + name: 'butter' | 'strawberry jam' | 'cream cheese'; + optionQuantity?: OptionQuantity; +} + +export interface BakeryPreparations { + type: 'BakeryPreparations'; + name: 'warmed' | 'cut in half'; +} + +export interface LatteDrinks { + type: 'LatteDrinks'; + name: 'cappuccino' | 'flat white' | 'latte' | 'macchiato' | 'mocha' |'chai latte' + temperature?: CoffeeTemperature; + size?: CoffeeSize; // The default is 'grande' + options?:(Milks | Sweeteners | Syrups | Toppings | Caffeines|LattePreparations)[] +} + +// more categories and products... +export type CoffeeTemperature = 'hot' | 'extra hot' | 'warm' | 'iced'; + +export type CoffeeSize = 'short' | 'tall' | 'grande' | 'venti'; + +export type EspressoSize = 'solo' | 'doppio' | 'triple' | 'quad'; + +export type OptionQuantity = 'no' | 'light' | 'regular' | 'extra' | number; +``` + +We can see TypeScript's expressive Type System really shines here which is easily able to succinctly express all +available products and options with minimal syntax. It's also worth noting the schema is solely concerned with the orders +customers are able to make and not about how the data is modelled in a datastore which is a good approach when interfacing +with LLMs to increase the probability of a successful result. + +But to be useful App's still need to model their data model which as a goal needs to: +- Capture all categories and products Customers can order +- Be able to dynamically generate the resulting TypeScript Schema +- Persist in a Data Store +- Enable management through a User Friendly UI + +Which will be the initial goal of our .NET App. FortunatelyTypeScript schema also serves as a great requirements documentation, +clearly and precisely defining all the categories, products, relationships and variants our Data Model needs to support. + +### Code-First Data Modelling + +This can be easily done in code-first ORMs like [OrmLite](https://docs.servicestack.net/ormlite/) which lets you design +RDBMS Tables with simple POCO classes. Since we'll also be using the Data Model to generate our online store we'll also add +an `ImageUrl` on **Category** and **Product** Models. + +OrmLite also supports persisting complex types on Data Models which are serialized with the RDBMS configured +[Complex Type Serializer](https://docs.servicestack.net/ormlite/complex-type-serializers) which saves from requiring +a number of unnecessary code tables and inefficient table joins for table data that doesn't need to be queried server-side. + +By utilizing complex type collections we can get this down to **5 tables** to define a data model that supports capturing the +categories, products, options and relationships in the [coffeeShopSchema.ts](https://github.com/microsoft/TypeChat/blob/main/examples/coffeeShop/src/coffeeShopSchema.ts): + +```csharp +public class Category +{ + [AutoIncrement] + public int Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public List? Temperatures { get; set; } + public string? DefaultTemperature { get; set; } + public List? Sizes { get; set; } + public string? DefaultSize { get; set; } + public string? ImageUrl { get; set; } + + [Reference] + public List Products { get; set; } + + [Reference] + public List CategoryOptions { get; set; } +} + +public class Product +{ + [AutoIncrement] + public int Id { get; set; } + [References(typeof(Category))] + public int CategoryId { get; set; } + public string Name { get; set; } + public decimal Cost { get; set; } + public string? ImageUrl { get; set; } + + [Reference] + public Category Category { get; set; } +} + +public class Option +{ + [AutoIncrement] + public int Id { get; set; } + public string Type { get; set; } + public List Names { get; set; } + public bool? AllowQuantity { get; set; } + public string? QuantityLabel { get; set; } +} + +public class OptionQuantity +{ + [AutoIncrement] + public int Id { get; set; } + public string Name { get; set; } + public decimal Value { get; set; } +} + +public class CategoryOption +{ + [AutoIncrement] + public int Id { get; set; } + [References(typeof(Category))] + public int CategoryId { get; set; } + [References(typeof(Option))] + public int OptionId { get; set; } +} +``` + +:::info +The `[Reference]` attributes defines [POCO References](https://docs.servicestack.net/ormlite/reference-support) for pulling +in data from related tables in OrmLite's `Load*` APIs +::: + +## Creating the CoffeeShop Database + +Next step is to create the RDBMS tables, which we recommend doing from within a +[Code-First DB Migration](https://docs.servicestack.net/ormlite/db-migrations) class so they can be easily run, re-run +and extend over time. + +For this we can just copy all data models into a +[Migration1000.cs](https://github.com/NetCoreApps/CoffeeShop/blob/main/CoffeeShop/Migrations/Migration1000.cs) +class which represents the initial state of the Application database, where all RDBMS tables are created and populated +in the `Up()` method and tables deleted and drop in the `Down()` method: + +```csharp +public class Migration1000 : MigrationBase +{ + // Embedded copy of Data Models... + + public override void Up() + { + Db.CreateTable(); + Db.CreateTable