├── .devcontainer └── devcontainer.json ├── .github ├── dependabot.yml └── workflows │ ├── ci.js.yml │ ├── ci.python.yml │ └── github-pages.yml ├── .gitignore ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── TypeChat.code-workspace ├── dotnet └── README.md ├── python ├── .gitignore ├── LICENSE ├── README.md ├── examples │ ├── README.md │ ├── calendar │ │ ├── README.md │ │ ├── demo.py │ │ ├── input.txt │ │ └── schema.py │ ├── coffeeShop │ │ ├── README.md │ │ ├── demo.py │ │ ├── input.txt │ │ ├── input2.txt │ │ └── schema.py │ ├── healthData │ │ ├── README.md │ │ ├── demo.py │ │ ├── input.txt │ │ ├── schema.py │ │ └── translator.py │ ├── math │ │ ├── README.md │ │ ├── demo.py │ │ ├── input.txt │ │ ├── program.py │ │ ├── schema.py │ │ └── schemaV2.py │ ├── multiSchema │ │ ├── README.md │ │ ├── agents.py │ │ ├── demo.py │ │ ├── input.txt │ │ └── router.py │ ├── music │ │ ├── README.md │ │ ├── client.py │ │ ├── demo.py │ │ ├── input.txt │ │ ├── schema.py │ │ └── spotipyWrapper.py │ ├── restaurant │ │ ├── README.md │ │ ├── demo.py │ │ ├── input.txt │ │ └── schema.py │ └── sentiment │ │ ├── README.md │ │ ├── demo.py │ │ ├── input.txt │ │ └── schema.py ├── notebooks │ ├── calendar.ipynb │ ├── coffeeShop.ipynb │ ├── healthData.ipynb │ ├── math.ipynb │ ├── music.ipynb │ ├── restaurant.ipynb │ └── sentiment.ipynb ├── package-lock.json ├── package.json ├── pyproject.toml ├── pyrightconfig.json ├── src │ └── typechat │ │ ├── __about__.py │ │ ├── __init__.py │ │ ├── _internal │ │ ├── __init__.py │ │ ├── interactive.py │ │ ├── model.py │ │ ├── result.py │ │ ├── translator.py │ │ ├── ts_conversion │ │ │ ├── __init__.py │ │ │ ├── python_type_to_ts_nodes.py │ │ │ ├── ts_node_to_string.py │ │ │ └── ts_type_nodes.py │ │ └── validator.py │ │ └── py.typed └── tests │ ├── __init__.py │ ├── __py3.11_snapshots__ │ ├── test_conflicting_names_1 │ │ └── test_conflicting_names_1.schema.d.ts │ └── test_hello_world │ │ └── test_generic_alias1.schema.d.ts │ ├── __py3.12_snapshots__ │ ├── test_conflicting_names_1 │ │ └── test_conflicting_names_1.schema.d.ts │ └── test_hello_world │ │ └── test_generic_alias1.schema.d.ts │ ├── __snapshots__ │ ├── test_coffeeshop │ │ └── test_coffeeshop_schema.schema.d.ts │ ├── test_dataclasses │ │ └── test_data_classes.schema.d.ts │ ├── test_generic_alias_1 │ │ └── test_generic_alias1.schema.d.ts │ ├── test_generic_alias_2 │ │ └── test_generic_alias1.schema.d.ts │ ├── test_translator.ambr │ ├── test_tuple_errors_1 │ │ └── test_tuples_2.schema.d.ts │ └── test_tuples_1 │ │ └── test_tuples_1.schema.d.ts │ ├── coffeeshop_deprecated.py │ ├── test_coffeeshop.py │ ├── test_conflicting_names_1.py │ ├── test_dataclasses.py │ ├── test_generic_alias_1.py │ ├── test_generic_alias_2.py │ ├── test_hello_world.py │ ├── test_translator.py │ ├── test_tuple_errors_1.py │ ├── test_tuples_1.py │ ├── test_validator.py │ └── utilities.py ├── site ├── .eleventy.js ├── .gitignore ├── jsconfig.json ├── package-lock.json ├── package.json └── src │ ├── _data │ ├── docsTOC.json │ └── headernav.json │ ├── _includes │ ├── base.njk │ ├── blog.njk │ ├── doc-page.njk │ ├── docs.njk │ ├── footer.njk │ └── header-prologue.njk │ ├── blog │ ├── announcing-typechat-0-1-0.md │ ├── index.njk │ └── introducing-typechat.md │ ├── css │ ├── 5.3.0_dist_css_bootstrap.min.css │ ├── noscript-styles.css │ └── styles.css │ ├── docs │ ├── examples.md │ ├── faq.md │ ├── index.njk │ ├── introduction.md │ ├── techniques.md │ └── typescript │ │ └── basic-usage.md │ ├── index.njk │ └── js │ └── interactivity.js └── typescript ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── README.md ├── calendar │ ├── .vscode │ │ └── launch.json │ ├── README.md │ ├── package.json │ └── src │ │ ├── calendarActionsSchema.ts │ │ ├── expectedOutput.txt │ │ ├── input.txt │ │ ├── main.ts │ │ └── tsconfig.json ├── coffeeShop-zod │ ├── README.md │ ├── package.json │ └── src │ │ ├── coffeeShopSchema.ts │ │ ├── input.txt │ │ ├── input2.txt │ │ ├── main.ts │ │ └── tsconfig.json ├── coffeeShop │ ├── .vscode │ │ └── launch.json │ ├── README.md │ ├── package.json │ └── src │ │ ├── coffeeShopSchema.ts │ │ ├── input.txt │ │ ├── input2.txt │ │ ├── main.ts │ │ └── tsconfig.json ├── crossword │ ├── README.md │ ├── package.json │ └── src │ │ ├── crosswordSchema.ts │ │ ├── input.txt │ │ ├── main.ts │ │ ├── puzzleScreenshot.jpeg │ │ ├── translator.ts │ │ └── tsconfig.json ├── healthData │ ├── README.md │ ├── package.json │ └── src │ │ ├── healthDataSchema.ts │ │ ├── input.txt │ │ ├── main.ts │ │ ├── translator.ts │ │ └── tsconfig.json ├── math │ ├── .vscode │ │ └── launch.json │ ├── README.md │ ├── package.json │ └── src │ │ ├── input.txt │ │ ├── main.ts │ │ ├── mathSchema.ts │ │ └── tsconfig.json ├── multiSchema │ ├── README.md │ ├── package.json │ └── src │ │ ├── agent.ts │ │ ├── classificationSchema.ts │ │ ├── input.txt │ │ ├── main.ts │ │ ├── router.ts │ │ └── tsconfig.json ├── music │ ├── .vscode │ │ └── launch.json │ ├── README.md │ ├── migrations.md │ ├── musicLibrary.db │ ├── package.json │ └── src │ │ ├── authz.ts │ │ ├── callback.html │ │ ├── chatifyActionsSchema.ts │ │ ├── dbInterface.ts │ │ ├── endpoints.ts │ │ ├── input.txt │ │ ├── localParser.ts │ │ ├── main.ts │ │ ├── playback.ts │ │ ├── service.ts │ │ ├── trackCollections.ts │ │ ├── trackFilter.ts │ │ └── tsconfig.json ├── restaurant │ ├── .vscode │ │ └── launch.json │ ├── README.md │ ├── package.json │ └── src │ │ ├── foodOrderViewSchema.ts │ │ ├── input.txt │ │ ├── main.ts │ │ └── tsconfig.json ├── sentiment-zod │ ├── README.md │ ├── package.json │ └── src │ │ ├── input.txt │ │ ├── main.ts │ │ ├── sentimentSchema.ts │ │ └── tsconfig.json └── sentiment │ ├── .vscode │ └── launch.json │ ├── README.md │ ├── package.json │ └── src │ ├── input.txt │ ├── main.ts │ ├── sentimentSchema.ts │ └── tsconfig.json ├── package-lock.json ├── package.json └── src ├── index.ts ├── interactive ├── index.ts └── interactive.ts ├── model.ts ├── result.ts ├── ts ├── index.ts ├── program.ts └── validate.ts ├── tsconfig.json ├── typechat.ts └── zod ├── index.ts └── validate.ts /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu 3 | { 4 | "name": "TypeChat Development (Python and TypeScript)", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/python:1-3.12", 7 | 8 | // Features to add to the dev container. More info: https://containers.dev/features. 9 | "features": { 10 | "ghcr.io/devcontainers/features/node:1": { 11 | "nodeGypDependencies": true, 12 | "version": "lts", 13 | "nvmVersion": "latest" 14 | }, 15 | "ghcr.io/devcontainers-contrib/features/hatch:2": { 16 | "version": "latest" 17 | } 18 | }, 19 | 20 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 21 | "forwardPorts": [7860, 7861, 7862, 7863, 7864, 7865, 7866, 7867, 7868, 7869, 7870], 22 | 23 | // Configure tool-specific properties. 24 | "customizations": { 25 | "vscode": { 26 | "extensions": [ 27 | "ms-python.black-formatter" 28 | ], 29 | "settings": { 30 | // Force the editor to pick up on the right environment and interpreter. 31 | "python.defaultInterpreterPath": "/workspaces/TypeChat/.venv", 32 | 33 | // Respect the paths of the interpreter. 34 | "python.analysis.autoSearchPaths": false 35 | } 36 | } 37 | }, 38 | 39 | // Use 'postCreateCommand' to run commands after the container is created. 40 | "postCreateCommand": { 41 | "site - npm": "cd site; npm ci", 42 | "typescript - npm": "cd typescript; npm ci", 43 | "python - hatch": "cd python; hatch env create", 44 | "python - npm": "cd python; npm ci" 45 | } 46 | 47 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 48 | // "remoteUser": "root" 49 | } 50 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for more information: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | # https://containers.dev/guide/dependabot 6 | 7 | version: 2 8 | updates: 9 | - package-ecosystem: "devcontainers" 10 | directory: "/" 11 | schedule: 12 | interval: weekly 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | permissions: 13 | contents: read 14 | 15 | # Ensure scripts are run with pipefail. See: 16 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference 17 | defaults: 18 | run: 19 | shell: bash 20 | working-directory: typescript 21 | 22 | jobs: 23 | build: 24 | 25 | runs-on: ubuntu-latest 26 | 27 | strategy: 28 | matrix: 29 | node-version: [18.x, 20.x] 30 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 31 | 32 | steps: 33 | - uses: actions/checkout@v3 34 | - name: Use Node.js ${{ matrix.node-version }} 35 | uses: actions/setup-node@v3 36 | with: 37 | node-version: ${{ matrix.node-version }} 38 | cache: "npm" 39 | cache-dependency-path: "typescript/package-lock.json" 40 | - run: npm ci 41 | - run: npm run build-all 42 | -------------------------------------------------------------------------------- /.github/workflows/ci.python.yml: -------------------------------------------------------------------------------- 1 | name: Python CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | defaults: 12 | run: 13 | shell: bash 14 | working-directory: ./python 15 | 16 | jobs: 17 | pyright: 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: 22 | - ubuntu-latest 23 | python-version: 24 | - '3.11' 25 | - '3.12' 26 | 27 | runs-on: ${{ matrix.os }} 28 | 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: actions/setup-node@v4 32 | - name: Set up Python ${{ matrix.python-version }} 33 | uses: actions/setup-python@v5 34 | with: 35 | python-version: ${{ matrix.python-version }} 36 | 37 | - name: Install Hatch 38 | run: | 39 | python -m pip install --upgrade pip 40 | pip install hatch 41 | 42 | - name: Set up Hatch Environment 43 | run: | 44 | hatch env create 45 | HATCH_ENV=$(hatch env find) 46 | echo $HATCH_ENV 47 | echo "$HATCH_ENV/bin" >> $GITHUB_PATH 48 | 49 | - name: Get Pyright Version 50 | id: pyright-version 51 | run: | 52 | PYRIGHT_VERSION=$(jq -r '.devDependencies.pyright' < package.json) 53 | echo $PYRIGHT_VERSION 54 | echo "version=$PYRIGHT_VERSION" >> $GITHUB_OUTPUT 55 | 56 | - name: Run pyright ${{ steps.pyright-version.outputs.version }} 57 | uses: jakebailey/pyright-action@v2 58 | with: 59 | version: ${{ steps.pyright-version.outputs.version }} 60 | python-version: ${{ matrix.python-version}} 61 | annotate: ${{ matrix.python-version == '3.11' && matrix.os == 'ubuntu-latest' }} # Only let one build post comments. 62 | working-directory: ./python 63 | 64 | - name: Test with Pytest 65 | run: | 66 | pytest -vv 67 | 68 | -------------------------------------------------------------------------------- /.github/workflows/github-pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | permissions: 8 | contents: read 9 | id-token: write 10 | pages: write 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | - name: Use Node.js 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 18.x 23 | cache: 'npm' 24 | cache-dependency-path: "site/package-lock.json" 25 | - name: Build site 26 | run: | 27 | cd site 28 | echo "Building the site" 29 | npm ci 30 | npm run build 31 | - name : Upload artifact 32 | uses: actions/upload-pages-artifact@v2 33 | with: 34 | name: github-pages 35 | path: site/_site 36 | - name: Deploy to GitHub Pages from artifacts 37 | uses: actions/deploy-pages@v2 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | out/ 4 | node_modules/ 5 | .venv/ 6 | .env* 7 | *.map 8 | *.out.txt 9 | *.bat 10 | 11 | # Local development and debugging 12 | .scratch/ 13 | **/.vscode/* 14 | **/tsconfig.debug.json 15 | !**/.vscode/launch.json 16 | **/build.bat 17 | 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // TODO: 3 | // * https://github.com/microsoft/vscode-python/issues/23078 4 | // * https://github.com/microsoft/vscode/issues/197603 5 | "python.envFile": "${workspaceFolder}/.file-that-should-hopefully-not-exist" 6 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. 6 | Please search the existing issues before filing new issues to avoid duplicates. 7 | For new issues, file your bug or feature request as a new issue. 8 | 9 | For help and questions about using this project, please either use the project's GitHub Discussions area or Stack Overflow. 10 | 11 | ## Microsoft Support Policy 12 | 13 | Support for this project is limited to the resources listed above. 14 | -------------------------------------------------------------------------------- /TypeChat.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "name": "TypeChat Root", 5 | "path": "./" 6 | }, 7 | { 8 | "name": "Python", 9 | "path": "./python" 10 | }, 11 | { 12 | "name": "TypeScript", 13 | "path": "./typescript" 14 | }, 15 | ], 16 | "settings": { 17 | } 18 | } -------------------------------------------------------------------------------- /dotnet/README.md: -------------------------------------------------------------------------------- 1 | # TypeChat for .NET 2 | 3 | TypeChat in .NET and C# is currently available on a separate [TypeChat.NET repository](https://github.com/microsoft/TypeChat.net). 4 | -------------------------------------------------------------------------------- /python/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /python/examples/calendar/README.md: -------------------------------------------------------------------------------- 1 | # Calendar 2 | 3 | The Calendar example shows how you can capture user intent as a sequence of actions, such as adding event to a calendar or searching for an event as defined by the [`CalendarActions`](./schema.py) type. 4 | 5 | # Try Calendar 6 | 7 | To run the Calendar example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 8 | 9 | # Usage 10 | 11 | Example prompts can be found in [`input.txt`](./input.txt). 12 | 13 | For example, we could use natural language to describe an event coming up soon: 14 | 15 | **Input**: 16 | 17 | ``` 18 | 📅> I need to get my tires changed from 12:00 to 2:00 pm on Friday March 15, 2024 19 | ``` 20 | 21 | **Output**: 22 | 23 | ```json 24 | { 25 | "actions": [ 26 | { 27 | "actionType": "add event", 28 | "event": { 29 | "day": "Friday March 15, 2024", 30 | "timeRange": { 31 | "startTime": "12:00 pm", 32 | "endTime": "2:00 pm" 33 | }, 34 | "description": "get my tires changed" 35 | } 36 | } 37 | ] 38 | } 39 | ``` -------------------------------------------------------------------------------- /python/examples/calendar/demo.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | 4 | import sys 5 | from dotenv import dotenv_values 6 | import schema as calendar 7 | from typechat import Failure, TypeChatJsonTranslator, TypeChatValidator, create_language_model, process_requests 8 | 9 | async def main(): 10 | env_vals = dotenv_values() 11 | model = create_language_model(env_vals) 12 | validator = TypeChatValidator(calendar.CalendarActions) 13 | translator = TypeChatJsonTranslator(model, validator, calendar.CalendarActions) 14 | 15 | async def request_handler(message: str): 16 | result = await translator.translate(message) 17 | if isinstance(result, Failure): 18 | print(result.message) 19 | else: 20 | result = result.value 21 | print(json.dumps(result, indent=2)) 22 | if any(item["actionType"] == "Unknown" for item in result["actions"]): 23 | print("I did not understand the following") 24 | for item in result["actions"]: 25 | if item["actionType"] == "Unknown": 26 | print(item["text"]) 27 | 28 | file_path = sys.argv[1] if len(sys.argv) == 2 else None 29 | await process_requests("📅> ", file_path, request_handler) 30 | 31 | 32 | if __name__ == "__main__": 33 | asyncio.run(main()) 34 | -------------------------------------------------------------------------------- /python/examples/calendar/input.txt: -------------------------------------------------------------------------------- 1 | I need to get my tires changed from 12:00 to 2:00 pm on Friday March 15, 2024 2 | Search for any meetings with Gavin this week 3 | Set up an event for friday named Jeffs pizza party at 6pm 4 | Please add Jennifer to the scrum next Thursday 5 | Will you please add an appointment with Jerri Skinner at 9 am? I need it to last 2 hours 6 | Do I have any plan with Rosy this month? 7 | I need to add a meeting with my boss on Monday at 10am. Also make sure to schedule and appointment with Sally, May, and Boris tomorrow at 3pm. Now just add to it Jesse and Abby and make it last ninety minutes 8 | Add meeting with team today at 2 9 | can you record lunch with Luis at 12pm on Friday and also add Isobel to the Wednesday ping pong game at 4pm 10 | I said I'd meet with Jenny this afternoon at 2pm and after that I need to go to the dry cleaner and then the soccer game. Leave an hour for each of those starting at 3:30 11 | -------------------------------------------------------------------------------- /python/examples/calendar/schema.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import Literal, NotRequired, TypedDict, Annotated, Doc 2 | 3 | class UnknownAction(TypedDict): 4 | """ 5 | if the user types text that can not easily be understood as a calendar action, this action is used 6 | """ 7 | 8 | actionType: Literal["Unknown"] 9 | text: Annotated[str, Doc("text typed by the user that the system did not understand")] 10 | 11 | 12 | class EventTimeRange(TypedDict, total=False): 13 | startTime: str 14 | endTime: str 15 | duration: str 16 | 17 | 18 | class Event(TypedDict): 19 | day: Annotated[str, Doc("date (example: March 22, 2024) or relative date (example: after EventReference)")] 20 | timeRange: EventTimeRange 21 | description: str 22 | location: NotRequired[str] 23 | participants: NotRequired[Annotated[list[str], Doc("a list of people or named groups like 'team'")]] 24 | 25 | 26 | class EventReference(TypedDict, total=False): 27 | """ 28 | properties used by the requester in referring to an event 29 | these properties are only specified if given directly by the requester 30 | """ 31 | 32 | day: Annotated[str, Doc("date (example: March 22, 2024) or relative date (example: after EventReference)")] 33 | dayRange: Annotated[str, Doc("(examples: this month, this week, in the next two days)")] 34 | timeRange: EventTimeRange 35 | description: str 36 | location: str 37 | participants: list[str] 38 | 39 | 40 | class FindEventsAction(TypedDict): 41 | actionType: Literal["find events"] 42 | eventReference: Annotated[EventReference, Doc("one or more event properties to use to search for matching events")] 43 | 44 | 45 | class ChangeDescriptionAction(TypedDict): 46 | actionType: Literal["change description"] 47 | eventReference: NotRequired[Annotated[EventReference, Doc("event to be changed")]] 48 | description: Annotated[str, Doc("new description for the event")] 49 | 50 | 51 | class ChangeTimeRangeAction(TypedDict): 52 | actionType: Literal["change time range"] 53 | eventReference: NotRequired[Annotated[EventReference, Doc("event to be changed")]] 54 | timeRange: Annotated[EventTimeRange, Doc("new time range for the event")] 55 | 56 | 57 | class AddParticipantsAction(TypedDict): 58 | actionType: Literal["add participants"] 59 | eventReference: NotRequired[ 60 | Annotated[EventReference, Doc("event to be augmented; if not specified assume last event discussed")] 61 | ] 62 | participants: NotRequired[Annotated[list[str], "new participants (one or more)"]] 63 | 64 | 65 | class RemoveEventAction(TypedDict): 66 | actionType: Literal["remove event"] 67 | eventReference: EventReference 68 | 69 | 70 | class AddEventAction(TypedDict): 71 | actionType: Literal["add event"] 72 | event: Event 73 | 74 | 75 | Actions = ( 76 | AddEventAction 77 | | RemoveEventAction 78 | | AddParticipantsAction 79 | | ChangeTimeRangeAction 80 | | ChangeDescriptionAction 81 | | FindEventsAction 82 | | UnknownAction 83 | ) 84 | 85 | 86 | class CalendarActions(TypedDict): 87 | actions: list[Actions] 88 | -------------------------------------------------------------------------------- /python/examples/coffeeShop/README.md: -------------------------------------------------------------------------------- 1 | # Coffee Shop 2 | 3 | The Coffee Shop example shows how to capture user intent as a set of "nouns". 4 | In this case, the nouns are items in a coffee order, where valid items are defined starting from the [`Cart`](./schema.py) type. 5 | This example also uses the [`UnknownText`](./schema.py) type as a way to capture user input that doesn't match to an existing type in [`Cart`](./schema.py). 6 | 7 | # Try Coffee Shop 8 | 9 | To run the Coffee Shop example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 10 | 11 | # Usage 12 | 13 | Example prompts can be found in [`src/input.txt`](./input.txt) and [`src/input2.txt`](./input2.txt). 14 | 15 | For example, we could use natural language to describe our coffee shop order: 16 | 17 | **Input**: 18 | 19 | ``` 20 | ☕> we'd like a cappuccino with a pack of sugar 21 | ``` 22 | 23 | **Output**: 24 | 25 | ```json 26 | { 27 | "items": [ 28 | { 29 | "type": "lineitem", 30 | "product": { 31 | "type": "LatteDrinks", 32 | "name": "cappuccino", 33 | "options": [ 34 | { 35 | "type": "Sweeteners", 36 | "name": "sugar", 37 | "optionQuantity": "regular" 38 | } 39 | ] 40 | }, 41 | "quantity": 1 42 | } 43 | ] 44 | } 45 | ``` -------------------------------------------------------------------------------- /python/examples/coffeeShop/demo.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import sys 4 | 5 | import schema as coffeeshop 6 | from dotenv import dotenv_values 7 | 8 | from typechat import Failure, TypeChatJsonTranslator, TypeChatValidator, create_language_model, process_requests 9 | 10 | async def main(): 11 | env_vals = dotenv_values() 12 | model = create_language_model(env_vals) 13 | validator = TypeChatValidator(coffeeshop.Cart) 14 | translator = TypeChatJsonTranslator(model, validator, coffeeshop.Cart) 15 | 16 | 17 | async def request_handler(message: str): 18 | result = await translator.translate(message) 19 | if isinstance(result, Failure): 20 | print(result.message) 21 | else: 22 | result = result.value 23 | print(json.dumps(result, indent=2)) 24 | if any(item["type"] == "Unknown" for item in result["items"]): 25 | print("I did not understand the following") 26 | for item in result["items"]: 27 | if item["type"] == "Unknown": 28 | print(item["text"]) 29 | 30 | file_path = sys.argv[1] if len(sys.argv) == 2 else None 31 | await process_requests("☕> ", file_path, request_handler) 32 | 33 | if __name__ == "__main__": 34 | asyncio.run(main()) 35 | -------------------------------------------------------------------------------- /python/examples/coffeeShop/input.txt: -------------------------------------------------------------------------------- 1 | i'd like a latte that's it 2 | i'll have a dark roast coffee thank you 3 | get me a coffee please 4 | could i please get two mochas that's all 5 | we need twenty five flat whites and that'll do it 6 | how about a tall cappuccino 7 | i'd like a venti iced latte 8 | i'd like a iced venti latte 9 | i'd like a venti latte iced 10 | i'd like a latte iced venti 11 | we'll also have a short tall latte 12 | i wanna latte macchiato with vanilla 13 | how about a peppermint latte 14 | may i also get a decaf soy vanilla syrup caramel latte with sugar and foam 15 | i want a latte with peppermint syrup with peppermint syrup 16 | i'd like a decaf half caf latte 17 | can I get a skim soy latte 18 | i'd like a light nutmeg espresso that's it 19 | can i have an cappuccino no foam 20 | can i have an espresso with no nutmeg 21 | we want a light whipped no foam mocha with extra hazelnut and cinnamon 22 | i'd like a latte cut in half 23 | i'd like a strawberry latte 24 | i want a five pump caramel flat white 25 | i want a flat white with five pumps of caramel syrup 26 | i want a two pump peppermint three squirt raspberry skinny vanilla latte with a pump of caramel and two sugars 27 | i want a latte cappuccino espresso and an apple muffin 28 | i'd like a tall decaf latte iced a grande cappuccino double espresso and a warmed poppyseed muffin sliced in half 29 | we'd like a latte with soy and a coffee with soy 30 | i want a latte latte macchiato and a chai latte 31 | we'd like a cappuccino with two pumps of vanilla 32 | make that cappuccino with three pumps of vanilla 33 | we'd like a cappuccino with a pack of sugar 34 | make that cappuccino with two packs of sugar 35 | we'd like a cappuccino with a pack of sugar 36 | make that with two packs of sugar 37 | i'd like a flat white with two equal 38 | add three equal to the flat white 39 | i'd like a flat white with two equal 40 | two tall lattes. the first one with no foam. the second one with whole milk. 41 | two tall lattes. the first one with no foam. the second one with whole milk. actually make the first one a grande. 42 | un petit cafe 43 | en lille kaffe 44 | a raspberry latte 45 | a strawberry latte 46 | roses are red 47 | two lawnmowers, a grande latte and a tall tree 48 | -------------------------------------------------------------------------------- /python/examples/coffeeShop/input2.txt: -------------------------------------------------------------------------------- 1 | two tall lattes. the first one with no foam. the second one with whole milk. 2 | two tall lattes. the first one with no foam. the second one with whole milk. actually make the first one a grande. 3 | un petit cafe 4 | en lille kaffe 5 | a raspberry latte 6 | a strawberry latte 7 | roses are red 8 | two lawnmowers, a grande latte and a tall tree 9 | -------------------------------------------------------------------------------- /python/examples/healthData/README.md: -------------------------------------------------------------------------------- 1 | # Health Data Agent 2 | 3 | This example requires GPT-4. 4 | 5 | Demonstrates a ***strongly typed*** chat: a natural language interface for entering health information. You work with a *health data agent* to interactively enter your medications or conditions. 6 | 7 | The Health Data Agent shows how strongly typed **agents with history** could interact with a user to collect information needed for one or more data types ("form filling"). 8 | 9 | ## Target models 10 | 11 | For best and consistent results, use **gpt-4**. 12 | 13 | ## Try the Health Data Agent 14 | 15 | To run the Sentiment example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 16 | 17 | ## Usage 18 | 19 | Example prompts can be found in [`input.txt`](./input.txt). 20 | 21 | For example, given the following input statement: 22 | 23 | **Input**: 24 | 25 | ```console 26 | 🤧> I am taking klaritin for my allergies 27 | 28 | ``` 29 | 30 | **Output**: 31 | -------------------------------------------------------------------------------- /python/examples/healthData/demo.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import sys 4 | from dotenv import dotenv_values 5 | import schema as health 6 | from typechat import Failure, TypeChatValidator, create_language_model, process_requests 7 | from translator import TranslatorWithHistory 8 | 9 | health_instructions = """ 10 | Help me enter my health data step by step. 11 | Ask specific questions to gather required and optional fields I have not already providedStop asking if I don't know the answer 12 | Automatically fix my spelling mistakes 13 | My health data may be complex: always record and return ALL of it. 14 | Always return a response: 15 | - If you don't understand what I say, ask a question. 16 | - At least respond with an OK message. 17 | 18 | """ 19 | 20 | async def main(): 21 | env_vals = dotenv_values() 22 | model = create_language_model(env_vals) 23 | validator = TypeChatValidator(health.HealthDataResponse) 24 | translator = TranslatorWithHistory( 25 | model, validator, health.HealthDataResponse, additional_agent_instructions=health_instructions 26 | ) 27 | 28 | async def request_handler(message: str): 29 | result = await translator.translate(message) 30 | if isinstance(result, Failure): 31 | print(result.message) 32 | else: 33 | result = result.value 34 | print(json.dumps(result, indent=2)) 35 | 36 | agent_message = result.get("message", "None") 37 | not_translated = result.get("notTranslated", None) 38 | 39 | if agent_message: 40 | print(f"\n📝: {agent_message}") 41 | 42 | if not_translated: 43 | print(f"\n🤔: I did not understand\n {not_translated}") 44 | 45 | 46 | file_path = sys.argv[1] if len(sys.argv) == 2 else None 47 | await process_requests("💉💊🤧> ", file_path, request_handler) 48 | 49 | 50 | if __name__ == "__main__": 51 | asyncio.run(main()) 52 | -------------------------------------------------------------------------------- /python/examples/healthData/input.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Conversations with a Health Data Agent 3 | # For each conversation: 4 | # You start with the first line 5 | # Then type the next line in response 6 | # 7 | 8 | # ================ 9 | # USE GPT4 10 | # ================ 11 | # Conversation: 12 | i want to record my shingles 13 | August 2016 14 | It lasted 3 months 15 | I also broke my foot 16 | I broke it in high school 17 | 2001 18 | The foot took a year to be ok 19 | 20 | # Conversation: 21 | klaritin 22 | 2 tablets 3 times a day 23 | 300 mg 24 | actually that is 1 tablet 25 | @clear 26 | 27 | # Conversation: 28 | klaritin 29 | 1 pill, morning and before bedtime 30 | Can't remember 31 | Actually, that is 3 tablets 32 | 500 mg 33 | @clear 34 | 35 | #Conversation 36 | I am taking binadryl now 37 | As needed. Groceery store strength 38 | That is all I have 39 | I also got allergies. Pollen 40 | @clear 41 | 42 | # Conversation: 43 | Robotussin 44 | 1 cup 45 | Daily, as needed 46 | Robotussin with Codeine 47 | Put down strength as I don't know 48 | @clear 49 | 50 | # Conversation: 51 | Hey 52 | Melatonin 53 | 1 3mg tablet every night 54 | @clear 55 | 56 | # Conversation: 57 | I got the flu 58 | Started 2 weeks ago 59 | Its gone now. Only lasted about a week 60 | I took some sudafed though 61 | I took 2 sudafed twice a day. Regular strength 62 | @clear 63 | 64 | -------------------------------------------------------------------------------- /python/examples/healthData/schema.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import TypedDict, Annotated, NotRequired, Literal, Doc 2 | 3 | 4 | class Quantity(TypedDict): 5 | value: Annotated[float, Doc("Exact number")] 6 | units: Annotated[str, Doc("UNITS include mg, kg, cm, pounds, liter, ml, tablet, pill, cup, per-day, per-week..ETC")] 7 | 8 | 9 | class ApproxDatetime(TypedDict): 10 | displayText: Annotated[str, Doc("Default: Unknown. Required")] 11 | timestamp: NotRequired[Annotated[str, Doc("If precise timestamp can be set")]] 12 | 13 | 14 | class ApproxQuantity(TypedDict): 15 | displayText: Annotated[str, Doc("Default: Unknown. Required")] 16 | quantity: NotRequired[Annotated[Quantity, Doc("Optional: only if precise quantities are available")]] 17 | 18 | 19 | class OtherHealthData(TypedDict): 20 | """ 21 | Use for health data that match nothing else. E.g. immunization, blood prssure etc 22 | """ 23 | 24 | text: str 25 | when: NotRequired[ApproxDatetime] 26 | 27 | 28 | class Condition(TypedDict): 29 | """ 30 | Disease, Ailment, Injury, Sickness 31 | """ 32 | 33 | name: Annotated[str, Doc("Fix any spelling mistakes, especially phonetic spelling")] 34 | startDate: Annotated[ApproxDatetime, Doc("When the condition started? Required")] 35 | status: Annotated[ 36 | Literal["active", "recurrence", "relapse", "inactive", "remission", "resolved", "unknown"], 37 | Doc("Always ask for current status of the condition"), 38 | ] 39 | endDate: NotRequired[Annotated[ApproxDatetime, Doc("If the condition was no longer active")]] 40 | 41 | 42 | class Medication(TypedDict): 43 | """ 44 | Meds, pills etc. 45 | """ 46 | 47 | name: Annotated[str, Doc("Fix any spelling mistakes, especially phonetic spelling")] 48 | dose: Annotated[ApproxQuantity, Doc("E.g. 2 tablets, 1 cup. Required")] 49 | frequency: Annotated[ApproxQuantity, Doc("E.g. twice a day. Required")] 50 | strength: Annotated[ApproxQuantity, Doc("E.g. 50 mg. Required")] 51 | 52 | 53 | class HealthData(TypedDict, total=False): 54 | medication: list[Medication] 55 | condition: list[Condition] 56 | other: list[OtherHealthData] 57 | 58 | 59 | class HealthDataResponse(TypedDict, total=False): 60 | data: Annotated[HealthData, Doc("Return this if JSON has ALL required information. Else ask questions")] 61 | message: Annotated[str, Doc("Use this to ask questions and give pertinent responses")] 62 | notTranslated: Annotated[str, Doc("Use this parts of the user request not translateed, off topic, etc")] 63 | -------------------------------------------------------------------------------- /python/examples/healthData/translator.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing_extensions import TypeVar, Any, override, TypedDict, Literal 3 | 4 | from typechat import TypeChatValidator, TypeChatLanguageModel, TypeChatJsonTranslator, Result, Failure, PromptSection 5 | 6 | from datetime import datetime 7 | 8 | T = TypeVar("T", covariant=True) 9 | 10 | 11 | class ChatMessage(TypedDict): 12 | source: Literal["system", "user", "assistant"] 13 | body: Any 14 | 15 | 16 | class TranslatorWithHistory(TypeChatJsonTranslator[T]): 17 | _chat_history: list[ChatMessage] 18 | _max_prompt_length: int 19 | _additional_agent_instructions: str 20 | 21 | def __init__( 22 | self, model: TypeChatLanguageModel, validator: TypeChatValidator[T], target_type: type[T], additional_agent_instructions: str 23 | ): 24 | super().__init__(model=model, validator=validator, target_type=target_type) 25 | self._chat_history = [] 26 | self._max_prompt_length = 2048 27 | self._additional_agent_instructions = additional_agent_instructions 28 | 29 | @override 30 | async def translate(self, input: str, *, prompt_preamble: str | list[PromptSection] | None = None) -> Result[T]: 31 | result = await super().translate(input=input, prompt_preamble=prompt_preamble) 32 | if not isinstance(result, Failure): 33 | self._chat_history.append(ChatMessage(source="assistant", body=result.value)) 34 | return result 35 | 36 | @override 37 | def _create_request_prompt(self, intent: str) -> str: 38 | # TODO: drop history entries if we exceed the max_prompt_length 39 | history_str = json.dumps(self._chat_history, indent=2, default=lambda o: None, allow_nan=False) 40 | 41 | now = datetime.now() 42 | 43 | prompt = F""" 44 | user: You are a service that translates user requests into JSON objects of type "{self._type_name}" according to the following TypeScript definitions: 45 | ''' 46 | {self._schema_str} 47 | ''' 48 | 49 | user: 50 | Use precise date and times RELATIVE TO CURRENT DATE: {now.strftime('%A, %m %d, %Y')} CURRENT TIME: {now.strftime("%H:%M:%S")} 51 | Also turn ranges like next week and next month into precise dates 52 | 53 | user: 54 | {self._additional_agent_instructions} 55 | 56 | system: 57 | IMPORTANT CONTEXT for the user request: 58 | {history_str} 59 | 60 | user: 61 | The following is a user request: 62 | ''' 63 | {intent} 64 | ''' 65 | The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined: 66 | """ 67 | return prompt 68 | -------------------------------------------------------------------------------- /python/examples/math/README.md: -------------------------------------------------------------------------------- 1 | # Math 2 | 3 | The Math example shows how to use TypeChat for program generation based on an API schema with the `evaluateJsonProgram` function. This example translates calculations into simple programs given an [`API`](./schema.py) type that can perform the four basic mathematical operations. 4 | 5 | # Try Math 6 | 7 | To run the Math example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 8 | 9 | # Usage 10 | 11 | Example prompts can be found in [`input.txt`](./input.txt). 12 | 13 | For example, we could use natural language to describe mathematical operations, and TypeChat will generate a program that can execute the math API defined in the schema. 14 | 15 | **Input**: 16 | 17 | ``` 18 | 🟰> multiply two by three, then multiply four by five, then sum the results 19 | ``` 20 | 21 | **Output**: 22 | 23 | ``` 24 | import { API } from "./schema"; 25 | function program(api: API) { 26 | const step1 = api.mul(2, 3); 27 | const step2 = api.mul(4, 5); 28 | return api.add(step1, step2); 29 | } 30 | Running program: 31 | mul(2, 3) 32 | mul(4, 5) 33 | add(6, 20) 34 | Result: 26 35 | ``` 36 | -------------------------------------------------------------------------------- /python/examples/math/demo.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from collections.abc import Sequence 3 | import json 4 | import sys 5 | from typing import cast 6 | from dotenv import dotenv_values 7 | import schema as math 8 | from typechat import Failure, create_language_model, process_requests 9 | from program import TypeChatProgramTranslator, TypeChatProgramValidator, evaluate_json_program 10 | 11 | async def main(): 12 | env_vals = dotenv_values() 13 | model = create_language_model(env_vals) 14 | validator = TypeChatProgramValidator() 15 | translator = TypeChatProgramTranslator(model, validator, math.MathAPI) 16 | 17 | async def request_handler(message: str): 18 | result = await translator.translate(message) 19 | if isinstance(result, Failure): 20 | print(result.message) 21 | else: 22 | result = result.value 23 | print(json.dumps(result, indent=2)) 24 | math_result = await evaluate_json_program(result, apply_operations) 25 | print(f"Math Result: {math_result}") 26 | 27 | file_path = sys.argv[1] if len(sys.argv) == 2 else None 28 | await process_requests("🧮> ", file_path, request_handler) 29 | 30 | 31 | async def apply_operations(func: str, args: Sequence[object]) -> int | float: 32 | print(f"{func}({json.dumps(args)}) ") 33 | 34 | for arg in args: 35 | if not isinstance(arg, (int, float)): 36 | raise ValueError("All arguments are expected to be numeric.") 37 | 38 | args = cast(Sequence[int | float], args) 39 | 40 | match func: 41 | case "add": 42 | return args[0] + args[1] 43 | case "sub": 44 | return args[0] - args[1] 45 | case "mul": 46 | return args[0] * args[1] 47 | case "div": 48 | return args[0] / args[1] 49 | case "neg": 50 | return -1 * args[0] 51 | case "id": 52 | return args[0] 53 | case _: 54 | raise ValueError(f'Unexpected function name {func}') 55 | 56 | if __name__ == "__main__": 57 | asyncio.run(main()) 58 | -------------------------------------------------------------------------------- /python/examples/math/input.txt: -------------------------------------------------------------------------------- 1 | 1 + 2 2 | 1 + 2 * 3 3 | 2 * 3 + 4 * 5 4 | 2 3 * 4 5 * + 5 | multiply two by three, then multiply four by five, then sum the results 6 | -------------------------------------------------------------------------------- /python/examples/math/schema.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import TypedDict, Annotated, Callable, Doc 2 | 3 | 4 | class MathAPI(TypedDict): 5 | """ 6 | This is API for a simple calculator 7 | """ 8 | 9 | add: Annotated[Callable[[float, float], float], Doc("Add two numbers")] 10 | sub: Annotated[Callable[[float, float], float], Doc("Subtract two numbers")] 11 | mul: Annotated[Callable[[float, float], float], Doc("Multiply two numbers")] 12 | div: Annotated[Callable[[float, float], float], Doc("Divide two numbers")] 13 | neg: Annotated[Callable[[float], float], Doc("Negate a number")] 14 | id: Annotated[Callable[[float], float], Doc("Identity function")] 15 | unknown: Annotated[Callable[[str], float], Doc("Unknown request")] 16 | -------------------------------------------------------------------------------- /python/examples/math/schemaV2.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import Protocol, runtime_checkable 2 | 3 | 4 | @runtime_checkable 5 | class MathAPI(Protocol): 6 | """ 7 | This is API for a simple calculator 8 | """ 9 | 10 | def add(self, x: float, y: float) -> float: 11 | """ 12 | Add two numbers 13 | """ 14 | ... 15 | 16 | def sub(self, x: float, y: float) -> float: 17 | """ 18 | Subtract two numbers 19 | """ 20 | ... 21 | 22 | def mul(self, x: float, y: float) -> float: 23 | """ 24 | Multiply two numbers 25 | """ 26 | ... 27 | 28 | def div(self, x: float, y: float) -> float: 29 | """ 30 | Divide two numbers 31 | """ 32 | ... 33 | 34 | def neg(self, x: float) -> float: 35 | """ 36 | Negate a number 37 | """ 38 | ... 39 | 40 | def id(self, x: float, y: float) -> float: 41 | """ 42 | Identity function 43 | """ 44 | ... 45 | 46 | def unknown(self, text: str) -> float: 47 | """ 48 | unknown request 49 | """ 50 | ... 51 | -------------------------------------------------------------------------------- /python/examples/multiSchema/README.md: -------------------------------------------------------------------------------- 1 | # MultiSchema 2 | 3 | This application demonstrates a simple way to write a **super-app** that automatically routes user requests to child apps. 4 | 5 | In this example, the child apps are existing TypeChat chat examples: 6 | 7 | * CoffeeShop 8 | * Restaurant 9 | * Calendar 10 | * Sentiment 11 | * Math 12 | * Plugins 13 | * HealthData 14 | 15 | ## Target Models 16 | 17 | Works with GPT-3.5 Turbo and GPT-4. 18 | 19 | Sub-apps like HealthData and Plugins work best with GPT-4. 20 | 21 | # Usage 22 | 23 | Example prompts can be found in [`input.txt`](input.txt). 24 | -------------------------------------------------------------------------------- /python/examples/multiSchema/demo.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | examples_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) 5 | if examples_path not in sys.path: 6 | sys.path.append(examples_path) 7 | 8 | import asyncio 9 | from dotenv import dotenv_values 10 | from typechat import create_language_model, process_requests 11 | 12 | from router import TextRequestRouter 13 | from agents import MathAgent, JsonPrintAgent, MusicAgent 14 | import examples.restaurant.schema as restaurant 15 | import examples.calendar.schema as calendar 16 | import examples.coffeeShop.schema as coffeeShop 17 | import examples.sentiment.schema as sentiment 18 | 19 | 20 | async def handle_unknown(_line: str): 21 | print("The input did not match any registered agents") 22 | 23 | async def main(): 24 | env_vals = dotenv_values() 25 | model = create_language_model(env_vals) 26 | router = TextRequestRouter(model=model) 27 | 28 | # register agents 29 | math_agent = MathAgent(model=model) 30 | router.register_agent( 31 | name="Math", description="Calculations using the four basic math operations", handler=math_agent.handle_request 32 | ) 33 | 34 | music_agent = MusicAgent(model=model, authentication_vals=env_vals) 35 | await music_agent.authenticate() 36 | router.register_agent( 37 | name="Music Player", 38 | description="Actions related to music, podcasts, artists, and managing music libraries", 39 | handler=music_agent.handle_request, 40 | ) 41 | 42 | coffee_agent = JsonPrintAgent(model=model, target_type=coffeeShop.Cart) 43 | router.register_agent( 44 | name="CoffeeShop", 45 | description="Order Coffee Drinks (Italian names included) and Baked Goods", 46 | handler=coffee_agent.handle_request, 47 | ) 48 | 49 | calendar_agent = JsonPrintAgent(model=model, target_type=calendar.CalendarActions) 50 | router.register_agent( 51 | name="Calendar", 52 | description="Actions related to calendars, appointments, meetings, schedules", 53 | handler=calendar_agent.handle_request, 54 | ) 55 | 56 | restaurant_agent = JsonPrintAgent(model=model, target_type=restaurant.Order) 57 | router.register_agent( 58 | name="Restaurant", description="Order pizza, beer and salads", handler=restaurant_agent.handle_request 59 | ) 60 | 61 | sentiment_agent = JsonPrintAgent(model=model, target_type=sentiment.Sentiment) 62 | router.register_agent( 63 | name="Sentiment", 64 | description="Statements with sentiments, emotions, feelings, impressions about places, things, the surroundings", 65 | handler=sentiment_agent.handle_request, 66 | ) 67 | 68 | # register a handler for unknown results 69 | router.register_agent(name="No Match", description="Handles all unrecognized requests", handler=handle_unknown) 70 | 71 | async def request_handler(message: str): 72 | await router.route_request(message) 73 | 74 | file_path = sys.argv[1] if len(sys.argv) == 2 else None 75 | await process_requests("🔀> ", file_path, request_handler) 76 | 77 | 78 | if __name__ == "__main__": 79 | asyncio.run(main()) 80 | -------------------------------------------------------------------------------- /python/examples/multiSchema/input.txt: -------------------------------------------------------------------------------- 1 | I'd like two large, one with pepperoni and the other with extra sauce. The pepperoni gets basil and the extra sauce gets Canadian bacon. And add a whole salad. 2 | I also want an espresso with extra foam and a muffin with jam 3 | And book me a lunch with Claude Debussy next week at 12.30 at Le Petit Chien! 4 | I bought 4 shoes for 12.50 each. How much did I spend? 5 | Its cold! 6 | Its cold and I want hot cafe to warm me up 7 | The coffee is cold 8 | The coffee is awful 9 | (2*4)+(9*7) -------------------------------------------------------------------------------- /python/examples/multiSchema/router.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing_extensions import Any, Callable, Awaitable, TypedDict, Annotated 3 | from typechat import Failure, TypeChatValidator, TypeChatLanguageModel, TypeChatJsonTranslator 4 | 5 | 6 | class AgentInfo(TypedDict): 7 | name: str 8 | description: str 9 | handler: Callable[[str], Awaitable[Any]] 10 | 11 | 12 | class TaskClassification(TypedDict): 13 | task_kind: Annotated[str, "Describe the kind of task to perform."] 14 | 15 | 16 | class TextRequestRouter: 17 | _current_agents: dict[str, AgentInfo] 18 | _validator: TypeChatValidator[TaskClassification] 19 | _translator: TypeChatJsonTranslator[TaskClassification] 20 | 21 | def __init__(self, model: TypeChatLanguageModel): 22 | super().__init__() 23 | self._validator = TypeChatValidator(TaskClassification) 24 | self._translator = TypeChatJsonTranslator(model, self._validator, TaskClassification) 25 | self._current_agents = {} 26 | 27 | def register_agent(self, name: str, description: str, handler: Callable[[str], Awaitable[Any]]): 28 | agent = AgentInfo(name=name, description=description, handler=handler) 29 | self._current_agents[name] = agent 30 | 31 | async def route_request(self, line: str): 32 | classes_str = json.dumps(self._current_agents, indent=2, default=lambda o: None, allow_nan=False) 33 | 34 | prompt_fragment = F""" 35 | Classify ""{line}"" using the following classification table: 36 | ''' 37 | {classes_str} 38 | ''' 39 | """ 40 | 41 | result = await self._translator.translate(prompt_fragment) 42 | if isinstance(result, Failure): 43 | print("Translation Failed ❌") 44 | print(f"Context: {result.message}") 45 | else: 46 | result = result.value 47 | print("Translation Succeeded! ✅\n") 48 | print(f"The target class is {result['task_kind']}") 49 | target = self._current_agents[result["task_kind"]] 50 | await target.get("handler")(line) 51 | -------------------------------------------------------------------------------- /python/examples/music/README.md: -------------------------------------------------------------------------------- 1 | # Music 2 | 3 | The Music example shows how to capture user intent as actions in JSON which corresponds to a simple dataflow program over the API provided in the intent schema. This example shows this pattern using natural language to control the Spotify API to play music, create playlists, and perform other actions from the API. 4 | 5 | # Try Music 6 | 7 | A Spotify Premium account is required to run this example. 8 | 9 | To run the Music example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 10 | 11 | This example also requires additional setup to use the Spotify API: 12 | 13 | 1. Go to https://developer.spotify.com/dashboard. 14 | 2. Log into Spotify with your user account if you are not already logged in. 15 | 3. Click the button in the upper right labeled "Create App". 16 | 4. Fill in the form, making sure the Redirect URI is http://localhost:PORT/callback, where PORT is a four-digit port number you choose for the authorization redirect. 17 | 5. Click the settings button and copy down the Client ID and Client Secret (the client secret requires you to click 'View client secret'). 18 | 6. In your `.env` file, set `SPOTIFY_APP_CLI` to your Client ID and `SPOTIFY_APP_CLISEC` to your Client Secret. Also set `SPOTIFY_APP_PORT` to the PORT on your local machine that you chose in step 4. 19 | 20 | # Usage 21 | Example prompts can be found in [`input.txt`](./input.txt). 22 | 23 | For example, use natural language to start playing a song with the Spotify player: 24 | 25 | **Input**: 26 | 27 | ``` 28 | 🎵> play shake it off by taylor swift 29 | ``` 30 | 31 | **Output**: 32 | 33 | ``` 34 | JSON View 35 | { 36 | "actions": [ 37 | { 38 | "actionName": "play", 39 | "parameters": { 40 | "artist": "taylor swift", 41 | "trackName": "shake it off", 42 | "quantity": 0 43 | } 44 | } 45 | ] 46 | } 47 | Playing... 48 | Shake It Off 49 | ``` -------------------------------------------------------------------------------- /python/examples/music/demo.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | 4 | import sys 5 | from dotenv import dotenv_values 6 | import schema as music 7 | from typechat import Failure, TypeChatJsonTranslator, TypeChatValidator, create_language_model, process_requests 8 | from client import handle_call, get_client_context 9 | 10 | async def main(): 11 | env_vals = dotenv_values() 12 | model = create_language_model(env_vals) 13 | validator = TypeChatValidator(music.PlayerActions) 14 | translator = TypeChatJsonTranslator(model, validator, music.PlayerActions) 15 | player_context = await get_client_context(env_vals) 16 | 17 | async def request_handler(message: str): 18 | result = await translator.translate(message) 19 | if isinstance(result, Failure): 20 | print(result.message) 21 | else: 22 | result = result.value 23 | print(json.dumps(result, indent=2)) 24 | try: 25 | for action in result["actions"]: 26 | await handle_call(action, player_context) 27 | except Exception as error: 28 | print("An exception occurred: ", error) 29 | 30 | if any(item["actionName"] == "Unknown" for item in result["actions"]): 31 | print("I did not understand the following") 32 | for item in result["actions"]: 33 | if item["actionName"] == "Unknown": 34 | print(item["text"]) 35 | 36 | file_path = sys.argv[1] if len(sys.argv) == 2 else None 37 | await process_requests("🎵> ", file_path, request_handler) 38 | 39 | 40 | if __name__ == "__main__": 41 | asyncio.run(main()) 42 | -------------------------------------------------------------------------------- /python/examples/music/input.txt: -------------------------------------------------------------------------------- 1 | play Taylor Swift Shake It Off 2 | get my top 20 favorites and make a playlist named animalTracks of the tracks that have animals in their names 3 | get my favorite 100 tracks from the last two months and show only the ones by Bach 4 | make it loud 5 | get my favorite 80 tracks from the last 8 months and create one playlist named class8 containing the classical tracks and another playlist containing the blues tracks 6 | toggle shuffle on and skip to the next track 7 | go back to the last song 8 | play my playlist class8 9 | play the fourth one 10 | show me my queue 11 | -------------------------------------------------------------------------------- /python/examples/restaurant/README.md: -------------------------------------------------------------------------------- 1 | # Restaurant 2 | 3 | The Restaurant example shows how to capture user intent as a set of "nouns", but with more complex linguistic input. 4 | This example can act as a "stress test" for language models, illustrating the line between simpler and more advanced language models in handling compound sentences, distractions, and corrections. 5 | This example also shows how we can create a "user intent summary" to display to a user. 6 | It uses a natural language experience for placing an order with the [`Order`](./schema.py) type. 7 | 8 | # Try Restaurant 9 | 10 | To run the Restaurant example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 11 | 12 | # Usage 13 | 14 | Example prompts can be found in [`input.txt`](./input.txt). 15 | 16 | For example, given the following order: 17 | 18 | **Input**: 19 | 20 | ``` 21 | 🍕> I want three pizzas, one with mushrooms and the other two with sausage. Make one sausage a small. And give me a whole Greek and a Pale Ale. And give me a Mack and Jacks. 22 | ``` 23 | 24 | **Output**: 25 | 26 | *This is GPT-4-0613 output; GPT-3.5-turbo and most other models miss this one.* 27 | 28 | ``` 29 | 1 large pizza with mushrooms 30 | 1 large pizza with sausage 31 | 1 small pizza with sausage 32 | 1 whole Greek salad 33 | 1 Pale Ale 34 | 1 Mack and Jacks 35 | ``` 36 | 37 | > **Note** 38 | > 39 | > Across different models, you may see that model responses may not correspond to the user intent. 40 | > In the above example, some models may not be able to capture the fact that the order is still only for 3 pizzas, 41 | > and that "make one sausage a small" is not a request for a new pizza. 42 | > 43 | > ```diff 44 | > 1 large pizza with mushrooms 45 | > - 1 large pizza with sausage 46 | > + 2 large pizza with sausage 47 | > 1 small pizza with sausage 48 | > 1 whole Greek salad 49 | > 1 Pale Ale 50 | > 1 Mack and Jacks 51 | > ``` 52 | > 53 | > The output here from GPT 3.5-turbo incorrectly shows 1 mushroom pizza and 3 sausage pizzas. 54 | 55 | Because all language models are probabilistic and therefore will sometimes output incorrect inferences, the TypeChat pattern includes asking the user for confirmation (or giving the user an easy way to undo actions). It is important to ask for confirmation without use of the language model so that incorrect inference is guaranteed not to be part of the intent summary generated. 56 | 57 | In this example, the function `printOrder` in the file `main.ts` summarizes the food order (as seen in the above output) without use of a language model. The `printOrder` function can work with a strongly typed `Order object` because the TypeChat validation process has checked that the emitted JSON corresponds to the `Order` type: 58 | 59 | ```typescript 60 | function printOrder(order: Order) { 61 | ``` 62 | 63 | Having a validated, typed data structure simplifies the task of generating a succinct summary suitable for user confirmation. -------------------------------------------------------------------------------- /python/examples/restaurant/demo.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import sys 4 | from dotenv import dotenv_values 5 | import schema as restaurant 6 | from typechat import Failure, TypeChatJsonTranslator, TypeChatValidator, create_language_model, process_requests 7 | 8 | async def main(): 9 | env_vals = dotenv_values() 10 | model = create_language_model(env_vals) 11 | validator = TypeChatValidator(restaurant.Order) 12 | translator = TypeChatJsonTranslator(model, validator, restaurant.Order) 13 | 14 | async def request_handler(message: str): 15 | result = await translator.translate(message) 16 | if isinstance(result, Failure): 17 | print(result.message) 18 | else: 19 | result = result.value 20 | print(json.dumps(result, indent=2)) 21 | if any(item["itemType"] == "Unknown" for item in result["items"]): 22 | print("I did not understand the following") 23 | for item in result["items"]: 24 | if item["itemType"] == "Unknown": 25 | print(item["text"]) 26 | 27 | file_path = sys.argv[1] if len(sys.argv) == 2 else None 28 | await process_requests("🍕> ", file_path, request_handler) 29 | 30 | 31 | if __name__ == "__main__": 32 | asyncio.run(main()) 33 | -------------------------------------------------------------------------------- /python/examples/restaurant/input.txt: -------------------------------------------------------------------------------- 1 | I'd like two large, one with pepperoni and the other with extra sauce. The pepperoni gets basil and the extra sauce gets Canadian bacon. And add a whole salad. Make the Canadian bacon a medium. Make the salad a Greek with no red onions. And give me two Mack and Jacks and a Sierra Nevada. Oh, and add another salad with no red onions. 2 | I'd like two large with olives and mushrooms. And the first one gets extra sauce. The second one gets basil. Both get arugula. And add a Pale Ale. Give me a two Greeks with no red onions, a half and a whole. And a large with sausage and mushrooms. Plus three Pale Ales and a Mack and Jacks. 3 | I'll take two large with pepperoni. Put olives on one of them. Make the olive a small. And give me whole Greek plus a Pale Ale and an M&J. 4 | I want three pizzas, one with mushrooms and the other two with sausage. Make one sausage a small. And give me a whole Greek and a Pale Ale. And give me a Mack and Jacks. 5 | I would like to order one with basil and one with extra sauce. Throw in a salad and an ale. 6 | I would love to have a pepperoni with extra sauce, basil and arugula. Lovely weather we're having. Throw in some pineapple. And give me a whole Greek and a Pale Ale. Boy, those Mariners are doggin it. And how about a Mack and Jacks. 7 | I'll have two pepperoni, the first with extra sauce and the second with basil. Add pineapple to the first and add olives to the second. 8 | I sure am hungry for a pizza with pepperoni and a salad with no croutons. And I'm thirsty for 3 Pale Ales 9 | give me three regular salads and two Greeks and make the regular ones with no red onions 10 | I'll take four large pepperoni pizzas. Put extra sauce on two of them. plus an M&J and a Pale Ale 11 | I'll take a yeti, a pale ale and a large with olives and take the extra cheese off the yeti and add a Greek 12 | I'll take a medium Pig with no arugula 13 | I'll take a small Pig with no arugula and a Greek with croutons and no red onions 14 | 15 | -------------------------------------------------------------------------------- /python/examples/restaurant/schema.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import Literal, Required, NotRequired, TypedDict, Annotated, Doc 2 | 3 | 4 | class UnknownText(TypedDict): 5 | """ 6 | Use this type for order items that match nothing else 7 | """ 8 | 9 | itemType: Literal["Unknown"] 10 | text: Annotated[str, "The text that wasn't understood"] 11 | 12 | 13 | class Pizza(TypedDict, total=False): 14 | itemType: Required[Literal["Pizza"]] 15 | size: Annotated[Literal["small", "medium", "large", "extra large"], "default: large"] 16 | addedToppings: Annotated[list[str], Doc("toppings requested (examples: pepperoni, arugula)")] 17 | removedToppings: Annotated[list[str], Doc("toppings requested to be removed (examples: fresh garlic, anchovies)")] 18 | quantity: Annotated[int, "default: 1"] 19 | name: Annotated[ 20 | Literal["Hawaiian", "Yeti", "Pig In a Forest", "Cherry Bomb"], 21 | Doc("used if the requester references a pizza by name"), 22 | ] 23 | 24 | 25 | class Beer(TypedDict): 26 | itemType: Literal["Beer"] 27 | kind: Annotated[str, Doc("examples: Mack and Jacks, Sierra Nevada Pale Ale, Miller Lite")] 28 | quantity: NotRequired[Annotated[int, "default: 1"]] 29 | 30 | 31 | SaladSize = Literal["half", "whole"] 32 | 33 | SaladStyle = Literal["Garden", "Greek"] 34 | 35 | 36 | class Salad(TypedDict, total=False): 37 | itemType: Required[Literal["Salad"]] 38 | portion: Annotated[str, "default: half"] 39 | style: Annotated[str, "default: Garden"] 40 | addedIngredients: Annotated[list[str], Doc("ingredients requested (examples: parmesan, croutons)")] 41 | removedIngredients: Annotated[list[str], Doc("ingredients requested to be removed (example: red onions)")] 42 | quantity: Annotated[int, "default: 1"] 43 | 44 | 45 | OrderItem = Pizza | Beer | Salad 46 | 47 | 48 | class Order(TypedDict): 49 | items: list[OrderItem | UnknownText] 50 | -------------------------------------------------------------------------------- /python/examples/sentiment/README.md: -------------------------------------------------------------------------------- 1 | # Sentiment 2 | 3 | The Sentiment example shows how to match user intent to a set of nouns, in this case categorizing user sentiment of the input as negative, neutral, or positive with the [`SentimentResponse`](./schema.py) type. 4 | 5 | # Try Sentiment 6 | To run the Sentiment example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 7 | 8 | # Usage 9 | Example prompts can be found in [`input.txt`](./input.txt). 10 | 11 | For example, given the following input statement: 12 | 13 | **Input**: 14 | ``` 15 | 😀> TypeChat is awesome! 16 | ``` 17 | 18 | **Output**: 19 | ``` 20 | The sentiment is positive 21 | ``` -------------------------------------------------------------------------------- /python/examples/sentiment/demo.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import sys 4 | from dotenv import dotenv_values 5 | import schema as sentiment 6 | from typechat import Failure, TypeChatJsonTranslator, TypeChatValidator, create_language_model, process_requests 7 | 8 | async def main(): 9 | env_vals = dotenv_values() 10 | model = create_language_model(env_vals) 11 | validator = TypeChatValidator(sentiment.Sentiment) 12 | translator = TypeChatJsonTranslator(model, validator, sentiment.Sentiment) 13 | 14 | async def request_handler(message: str): 15 | result = await translator.translate(message) 16 | if isinstance(result, Failure): 17 | print(result.message) 18 | else: 19 | result = result.value 20 | print(f"The sentiment is {result.sentiment}") 21 | 22 | file_path = sys.argv[1] if len(sys.argv) == 2 else None 23 | await process_requests("😀> ", file_path, request_handler) 24 | 25 | 26 | if __name__ == "__main__": 27 | asyncio.run(main()) 28 | -------------------------------------------------------------------------------- /python/examples/sentiment/input.txt: -------------------------------------------------------------------------------- 1 | hello, world 2 | TypeChat is awesome! 3 | I'm having a good day 4 | it's very rainy outside -------------------------------------------------------------------------------- /python/examples/sentiment/schema.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing_extensions import Literal, Annotated, Doc 3 | 4 | @dataclass 5 | class Sentiment: 6 | """ 7 | The following is a schema definition for determining the sentiment of a some user input. 8 | """ 9 | 10 | sentiment: Annotated[Literal["negative", "neutral", "positive"], Doc("The sentiment for the text")] 11 | -------------------------------------------------------------------------------- /python/notebooks/sentiment.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%pip install --upgrade setuptools\n", 10 | "%pip install --upgrade gradio" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "import json\n", 20 | "\n", 21 | "\n", 22 | "import sys\n", 23 | "from dotenv import dotenv_values\n", 24 | "\n", 25 | "import os\n", 26 | "import sys\n", 27 | "module_path = os.path.abspath(os.path.join('..'))\n", 28 | "if module_path not in sys.path:\n", 29 | " sys.path.append(module_path)\n", 30 | "\n", 31 | "from examples.sentiment import schema as sentiment\n", 32 | "from typechat import Failure, TypeChatJsonTranslator, TypeChatValidator, create_language_model" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "env_vals = dotenv_values()\n", 42 | "model = create_language_model(env_vals)\n", 43 | "validator = TypeChatValidator(sentiment.Sentiment)\n", 44 | "translator = TypeChatJsonTranslator(model, validator, sentiment.Sentiment)" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": null, 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "async def get_translation(message, history):\n", 54 | " result = await translator.translate(message)\n", 55 | " if isinstance(result, Failure):\n", 56 | " return f\"Translation Failed ❌ \\n Context: {result.message}\"\n", 57 | " else:\n", 58 | " result = result.value\n", 59 | " return f\"Translation Succeeded! ✅\\n The sentiment is {result['sentiment']}\"" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "import setuptools\n", 69 | "import gradio as gr\n", 70 | "\n", 71 | "gr.ChatInterface(get_translation, title=\"😀 Sentiment\").launch()\n" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [] 80 | } 81 | ], 82 | "metadata": { 83 | "kernelspec": { 84 | "display_name": "Python 3 (ipykernel)", 85 | "language": "python", 86 | "name": "python3" 87 | }, 88 | "language_info": { 89 | "codemirror_mode": { 90 | "name": "ipython", 91 | "version": 3 92 | }, 93 | "file_extension": ".py", 94 | "mimetype": "text/x-python", 95 | "name": "python", 96 | "nbconvert_exporter": "python", 97 | "pygments_lexer": "ipython3", 98 | "version": "3.12.0" 99 | } 100 | }, 101 | "nbformat": 4, 102 | "nbformat_minor": 4 103 | } 104 | -------------------------------------------------------------------------------- /python/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typechat-py", 3 | "version": "0.0.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "typechat-py", 9 | "version": "0.0.1", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "pyright": "1.1.358" 13 | } 14 | }, 15 | "node_modules/fsevents": { 16 | "version": "2.3.3", 17 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 18 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 19 | "dev": true, 20 | "hasInstallScript": true, 21 | "optional": true, 22 | "os": [ 23 | "darwin" 24 | ], 25 | "engines": { 26 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 27 | } 28 | }, 29 | "node_modules/pyright": { 30 | "version": "1.1.358", 31 | "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.358.tgz", 32 | "integrity": "sha512-M3zlRf+7bK/8vv5Q1EyfW9hQ2J0yOyS7j2GRwBrl2bJKPTS2AtPy8eR3IAitfOclENxUFDA/4PilHq0IrCj8Yg==", 33 | "dev": true, 34 | "bin": { 35 | "pyright": "index.js", 36 | "pyright-langserver": "langserver.index.js" 37 | }, 38 | "engines": { 39 | "node": ">=14.0.0" 40 | }, 41 | "optionalDependencies": { 42 | "fsevents": "~2.3.3" 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /python/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typechat-py", 3 | "private": true, 4 | "version": "0.0.1", 5 | "description": "TypeChat is a library that makes it easy to build natural language interfaces using types.", 6 | "scripts": { 7 | "check": "pyright" 8 | }, 9 | "author": "Microsoft", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "pyright": "1.1.358" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /python/pyrightconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "typeCheckingMode": "strict", 3 | "reportCallInDefaultInitializer": "error", 4 | "reportImplicitOverride": "error", 5 | "reportImplicitStringConcatenation": "error", 6 | "reportImportCycles": "error", 7 | "reportMissingSuperCall": "error", 8 | "reportPropertyTypeMismatch": "error", 9 | "reportShadowedImports": "error", 10 | "reportUninitializedInstanceVariable": "error", 11 | "reportUnnecessaryTypeIgnoreComment": "error", 12 | "reportUnusedCallResult": "none", 13 | "pythonVersion": "3.11", 14 | "include": [ 15 | "**/*", 16 | ], 17 | } -------------------------------------------------------------------------------- /python/src/typechat/__about__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Microsoft Corporation 2 | # 3 | # SPDX-License-Identifier: MIT 4 | __version__ = "0.0.2" 5 | -------------------------------------------------------------------------------- /python/src/typechat/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Microsoft Corporation 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | from typechat._internal.model import PromptSection, TypeChatLanguageModel, create_language_model, create_openai_language_model, create_azure_openai_language_model 6 | from typechat._internal.result import Failure, Result, Success 7 | from typechat._internal.translator import TypeChatJsonTranslator 8 | from typechat._internal.ts_conversion import python_type_to_typescript_schema 9 | from typechat._internal.validator import TypeChatValidator 10 | from typechat._internal.interactive import process_requests 11 | 12 | __all__ = [ 13 | "TypeChatLanguageModel", 14 | "TypeChatJsonTranslator", 15 | "TypeChatValidator", 16 | "Success", 17 | "Failure", 18 | "Result", 19 | "python_type_to_typescript_schema", 20 | "PromptSection", 21 | "create_language_model", 22 | "create_openai_language_model", 23 | "create_azure_openai_language_model", 24 | "process_requests", 25 | ] 26 | -------------------------------------------------------------------------------- /python/src/typechat/_internal/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/TypeChat/fb4bdeb8eefc5ed33718c5ed83417f38f83f1480/python/src/typechat/_internal/__init__.py -------------------------------------------------------------------------------- /python/src/typechat/_internal/interactive.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Awaitable 2 | 3 | async def process_requests(interactive_prompt: str, input_file_name: str | None, process_request: Callable[[str], Awaitable[None]]): 4 | """ 5 | A request processor for interactive input or input from a text file. If an input file name is specified, 6 | the callback function is invoked for each line in file. Otherwise, the callback function is invoked for 7 | each line of interactive input until the user types "quit" or "exit". 8 | 9 | Args: 10 | interactive_prompt: Prompt to present to user. 11 | input_file_name: Input text file name, if any. 12 | process_request: Async callback function that is invoked for each interactive input or each line in text file. 13 | """ 14 | if input_file_name is not None: 15 | with open(input_file_name, "r") as file: 16 | lines = filter(str.rstrip, file) 17 | for line in lines: 18 | if line.startswith("# "): 19 | continue 20 | print(interactive_prompt + line) 21 | await process_request(line) 22 | else: 23 | try: 24 | # Use readline to enable input editing and history 25 | import readline # type: ignore 26 | except ImportError: 27 | pass 28 | while True: 29 | try: 30 | line = input(interactive_prompt) 31 | except EOFError: 32 | print("\n") 33 | break 34 | if line.lower().strip() in ("quit", "exit"): 35 | break 36 | else: 37 | await process_request(line) 38 | -------------------------------------------------------------------------------- /python/src/typechat/_internal/result.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing_extensions import Generic, TypeAlias, TypeVar 3 | 4 | T = TypeVar("T", covariant=True) 5 | 6 | @dataclass 7 | class Success(Generic[T]): 8 | "An object representing a successful operation with a result of type `T`." 9 | value: T 10 | 11 | 12 | @dataclass 13 | class Failure: 14 | "An object representing an operation that failed for the reason given in `message`." 15 | message: str 16 | 17 | 18 | """ 19 | An object representing a successful or failed operation of type `T`. 20 | """ 21 | Result: TypeAlias = Success[T] | Failure 22 | -------------------------------------------------------------------------------- /python/src/typechat/_internal/ts_conversion/__init__.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing_extensions import TypeAliasType 3 | 4 | from typechat._internal.ts_conversion.python_type_to_ts_nodes import python_type_to_typescript_nodes 5 | from typechat._internal.ts_conversion.ts_node_to_string import ts_declaration_to_str 6 | 7 | __all__ = [ 8 | "python_type_to_typescript_schema", 9 | "TypeScriptSchemaConversionResult", 10 | ] 11 | 12 | @dataclass 13 | class TypeScriptSchemaConversionResult: 14 | typescript_schema_str: str 15 | """The TypeScript declarations generated from the Python declarations.""" 16 | 17 | typescript_type_reference: str 18 | """The TypeScript string representation of a given Python type.""" 19 | 20 | errors: list[str] 21 | """Any errors that occurred during conversion.""" 22 | 23 | def python_type_to_typescript_schema(py_type: type | TypeAliasType) -> TypeScriptSchemaConversionResult: 24 | """Converts a Python type to a TypeScript schema.""" 25 | 26 | node_conversion_result = python_type_to_typescript_nodes(py_type) 27 | 28 | decl_strs = map(ts_declaration_to_str, node_conversion_result.type_declarations) 29 | schema_str = "\n".join(decl_strs) 30 | 31 | return TypeScriptSchemaConversionResult( 32 | typescript_schema_str=schema_str, 33 | typescript_type_reference=py_type.__name__, 34 | errors=node_conversion_result.errors, 35 | ) 36 | -------------------------------------------------------------------------------- /python/src/typechat/_internal/ts_conversion/ts_type_nodes.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing_extensions import TypeAlias 5 | 6 | TypeNode: TypeAlias = "TypeReferenceNode | UnionTypeNode | LiteralTypeNode | ArrayTypeNode | TupleTypeNode" 7 | 8 | @dataclass 9 | class IdentifierNode: 10 | text: str 11 | 12 | @dataclass 13 | class QualifiedNameNode: 14 | left: QualifiedNameNode | IdentifierNode 15 | right: IdentifierNode 16 | 17 | @dataclass 18 | class TypeReferenceNode: 19 | name: QualifiedNameNode | IdentifierNode 20 | type_arguments: list[TypeNode] | None = None 21 | 22 | @dataclass 23 | class UnionTypeNode: 24 | types: list[TypeNode] 25 | 26 | @dataclass 27 | class LiteralTypeNode: 28 | value: str | int | float | bool 29 | 30 | @dataclass 31 | class ArrayTypeNode: 32 | element_type: TypeNode 33 | 34 | @dataclass 35 | class TupleTypeNode: 36 | element_types: list[TypeNode] 37 | 38 | @dataclass 39 | class InterfaceDeclarationNode: 40 | name: str 41 | type_parameters: list[TypeParameterDeclarationNode] | None 42 | comment: str 43 | base_types: list[TypeNode] | None 44 | members: list[PropertyDeclarationNode | IndexSignatureDeclarationNode] 45 | 46 | @dataclass 47 | class TypeParameterDeclarationNode: 48 | name: str 49 | constraint: TypeNode | None = None 50 | 51 | @dataclass 52 | class PropertyDeclarationNode: 53 | name: str 54 | is_optional: bool 55 | comment: str 56 | type: TypeNode 57 | 58 | @dataclass 59 | class IndexSignatureDeclarationNode: 60 | key_type: TypeNode 61 | value_type: TypeNode 62 | 63 | @dataclass 64 | class TypeAliasDeclarationNode: 65 | name: str 66 | type_parameters: list[TypeParameterDeclarationNode] | None 67 | comment: str 68 | type: TypeNode 69 | 70 | TopLevelDeclarationNode: TypeAlias = "InterfaceDeclarationNode | TypeAliasDeclarationNode" 71 | 72 | StringTypeReferenceNode = TypeReferenceNode(IdentifierNode("string")) 73 | NumberTypeReferenceNode = TypeReferenceNode(IdentifierNode("number")) 74 | BooleanTypeReferenceNode = TypeReferenceNode(IdentifierNode("boolean")) 75 | AnyTypeReferenceNode = TypeReferenceNode(IdentifierNode("any")) 76 | NullTypeReferenceNode = TypeReferenceNode(IdentifierNode("null")) 77 | NeverTypeReferenceNode = TypeReferenceNode(IdentifierNode("never")) 78 | ThisTypeReferenceNode = TypeReferenceNode(IdentifierNode("this")) 79 | -------------------------------------------------------------------------------- /python/src/typechat/_internal/validator.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing_extensions import Generic, TypeVar 3 | 4 | import pydantic 5 | import pydantic_core 6 | 7 | from typechat._internal.result import Failure, Result, Success 8 | 9 | T = TypeVar("T", covariant=True) 10 | 11 | class TypeChatValidator(Generic[T]): 12 | """ 13 | Validates an object against a given Python type. 14 | """ 15 | 16 | _adapted_type: pydantic.TypeAdapter[T] 17 | 18 | def __init__(self, py_type: type[T]): 19 | """ 20 | Args: 21 | 22 | py_type: The schema type to validate against. 23 | """ 24 | super().__init__() 25 | self._adapted_type = pydantic.TypeAdapter(py_type) 26 | 27 | def validate_object(self, obj: object) -> Result[T]: 28 | """ 29 | Validates the given Python object according to the associated schema type. 30 | 31 | Returns a `Success[T]` object containing the object if validation was successful. 32 | Otherwise, returns a `Failure` object with a `message` property describing the error. 33 | """ 34 | try: 35 | # TODO: Switch to `validate_python` when validation modes are exposed. 36 | # https://github.com/pydantic/pydantic-core/issues/712 37 | # We'd prefer to keep `validate_object` as the core method and 38 | # allow translators to concern themselves with the JSON instead. 39 | # However, under Pydantic's `strict` mode, a `dict` isn't considered compatible 40 | # with a dataclass. So for now, jump back to JSON and validate the string. 41 | json_str = pydantic_core.to_json(obj) 42 | typed_dict = self._adapted_type.validate_json(json_str, strict=True) 43 | return Success(typed_dict) 44 | except pydantic.ValidationError as validation_error: 45 | return _handle_error(validation_error) 46 | 47 | 48 | def _handle_error(validation_error: pydantic.ValidationError) -> Failure: 49 | error_strings: list[str] = [] 50 | for error in validation_error.errors(include_url=False): 51 | error_string = "" 52 | loc_path = error["loc"] 53 | if loc_path: 54 | error_string += f"Validation path `{'.'.join(map(str, loc_path))}` " 55 | else: 56 | error_string += "Root validation " 57 | input = error["input"] 58 | error_string += f"failed for value `{json.dumps(input)}` because:\n {error['msg']}" 59 | error_strings.append(error_string) 60 | 61 | if len(error_strings) > 1: 62 | failure_message = "Several possible issues may have occurred with the given data.\n\n" 63 | else: 64 | failure_message = "" 65 | failure_message += "\n".join(error_strings) 66 | 67 | return Failure(failure_message) 68 | -------------------------------------------------------------------------------- /python/src/typechat/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/TypeChat/fb4bdeb8eefc5ed33718c5ed83417f38f83f1480/python/src/typechat/py.typed -------------------------------------------------------------------------------- /python/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Microsoft Corporation 2 | # 3 | # SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /python/tests/__py3.11_snapshots__/test_conflicting_names_1/test_conflicting_names_1.schema.d.ts: -------------------------------------------------------------------------------- 1 | // Entry point is: 'Derived' 2 | 3 | interface Derived { 4 | my_attr_1: string; 5 | my_attr_2: number; 6 | } 7 | -------------------------------------------------------------------------------- /python/tests/__py3.11_snapshots__/test_hello_world/test_generic_alias1.schema.d.ts: -------------------------------------------------------------------------------- 1 | // Entry point is: 'D_or_E' 2 | 3 | type D_or_E = D | E 4 | 5 | // This is the definition of the class E. 6 | interface E extends C { 7 | tag: "E"; 8 | next: this | null; 9 | } 10 | 11 | // This is a generic class named C. 12 | interface C { 13 | x?: T; 14 | c: C; 15 | } 16 | 17 | // This is the definition of the class D. 18 | interface D extends C { 19 | tag?: "D"; 20 | // This comes from string metadata 21 | // within an Annotated hint. 22 | y: boolean | null; 23 | z?: number[] | null; 24 | other?: IndirectC; 25 | non_class?: NonClass; 26 | // This comes from later metadata. 27 | multiple_metadata?: string; 28 | } 29 | 30 | interface NonClass { 31 | a: number; 32 | "my-dict": Record; 33 | } 34 | 35 | type IndirectC = C 36 | -------------------------------------------------------------------------------- /python/tests/__py3.12_snapshots__/test_conflicting_names_1/test_conflicting_names_1.schema.d.ts: -------------------------------------------------------------------------------- 1 | // Entry point is: 'Derived' 2 | 3 | // ERRORS: 4 | // !!! Cannot create a schema using two types with the same name. C conflicts between .C'> and .C'> 5 | 6 | interface Derived extends C, C { 7 | } 8 | 9 | interface C { 10 | my_attr_2: number; 11 | } 12 | 13 | interface C { 14 | my_attr_1: string; 15 | } 16 | -------------------------------------------------------------------------------- /python/tests/__py3.12_snapshots__/test_hello_world/test_generic_alias1.schema.d.ts: -------------------------------------------------------------------------------- 1 | // Entry point is: 'D_or_E' 2 | 3 | type D_or_E = D | E 4 | 5 | // This is the definition of the class E. 6 | interface E extends C { 7 | tag: "E"; 8 | next: this | null; 9 | } 10 | 11 | // This is a generic class named C. 12 | interface C { 13 | x?: T; 14 | c: C; 15 | } 16 | 17 | // This is the definition of the class D. 18 | interface D extends C { 19 | tag?: "D"; 20 | // This comes from string metadata 21 | // within an Annotated hint. 22 | y: boolean | null; 23 | z?: number[] | null; 24 | other?: IndirectC; 25 | non_class?: NonClass; 26 | // This comes from later metadata. 27 | multiple_metadata?: string; 28 | } 29 | 30 | interface NonClass { 31 | a: number; 32 | "my-dict": Record; 33 | } 34 | 35 | type IndirectC = C 36 | -------------------------------------------------------------------------------- /python/tests/__snapshots__/test_dataclasses/test_data_classes.schema.d.ts: -------------------------------------------------------------------------------- 1 | // Entry point is: 'Response' 2 | 3 | interface Response { 4 | attr_1: string; 5 | // Hello! 6 | attr_2: number; 7 | attr_3: string | null; 8 | attr_4?: string; 9 | attr_5?: string | null; 10 | attr_6?: string[]; 11 | attr_7?: Options; 12 | _underscore_attr_1?: number; 13 | } 14 | 15 | // TODO: someone add something here. 16 | interface Options { 17 | } 18 | -------------------------------------------------------------------------------- /python/tests/__snapshots__/test_generic_alias_1/test_generic_alias1.schema.d.ts: -------------------------------------------------------------------------------- 1 | // Entry point is: 'FirstOrSecond' 2 | 3 | type FirstOrSecond = First | Second 4 | 5 | interface Second { 6 | kind: "second"; 7 | second_attr: T; 8 | } 9 | 10 | interface First { 11 | kind: "first"; 12 | first_attr: T; 13 | } 14 | -------------------------------------------------------------------------------- /python/tests/__snapshots__/test_generic_alias_2/test_generic_alias1.schema.d.ts: -------------------------------------------------------------------------------- 1 | // Entry point is: 'Nested' 2 | 3 | interface Nested { 4 | item: FirstOrSecond; 5 | } 6 | 7 | type FirstOrSecond = First | Second 8 | 9 | interface Second { 10 | kind: "second"; 11 | second_attr: T; 12 | } 13 | 14 | interface First { 15 | kind: "first"; 16 | first_attr: T; 17 | } 18 | -------------------------------------------------------------------------------- /python/tests/__snapshots__/test_tuple_errors_1/test_tuples_2.schema.d.ts: -------------------------------------------------------------------------------- 1 | // Entry point is: 'TupleContainer' 2 | 3 | // ERRORS: 4 | // !!! '()' cannot be used as a type annotation. 5 | // !!! '()' cannot be used as a type annotation. 6 | // !!! '()' cannot be used as a type annotation. 7 | // !!! The tuple type 'tuple[...]' is ill-formed. Tuples with an ellipsis can only take the form 'tuple[SomeType, ...]'. 8 | // !!! The tuple type 'tuple[int, int, ...]' is ill-formed. Tuples with an ellipsis can only take the form 'tuple[SomeType, ...]'. 9 | // !!! The tuple type 'tuple[..., int]' is ill-formed because the ellipsis (...) cannot be the first element. 10 | // !!! The tuple type 'tuple[..., ...]' is ill-formed because the ellipsis (...) cannot be the first element. 11 | // !!! The tuple type 'tuple[int, ..., int]' is ill-formed. Tuples with an ellipsis can only take the form 'tuple[SomeType, ...]'. 12 | // !!! The tuple type 'tuple[int, ..., int, ...]' is ill-formed. Tuples with an ellipsis can only take the form 'tuple[SomeType, ...]'. 13 | 14 | interface TupleContainer { 15 | empty_tuples_args_1: [any, any]; 16 | empty_tuples_args_2: any[]; 17 | arbitrary_length_1: any[]; 18 | arbitrary_length_2: any[]; 19 | arbitrary_length_3: any[]; 20 | arbitrary_length_4: any[]; 21 | arbitrary_length_5: any[]; 22 | arbitrary_length_6: any[]; 23 | } 24 | -------------------------------------------------------------------------------- /python/tests/__snapshots__/test_tuples_1/test_tuples_1.schema.d.ts: -------------------------------------------------------------------------------- 1 | // Entry point is: 'TupleContainer' 2 | 3 | interface TupleContainer { 4 | empty_tuple: []; 5 | tuple_1: [number]; 6 | tuple_2: [number, string]; 7 | tuple_3: [number, string]; 8 | arbitrary_length_1: number[]; 9 | arbitrary_length_2: number[]; 10 | arbitrary_length_3: number[]; 11 | arbitrary_length_4: number[]; 12 | arbitrary_length_5: number[] | [number]; 13 | arbitrary_length_6: number[] | [number] | [number, number]; 14 | } 15 | -------------------------------------------------------------------------------- /python/tests/test_conflicting_names_1.py: -------------------------------------------------------------------------------- 1 | from typing import Any, TypedDict, cast 2 | 3 | from typechat import python_type_to_typescript_schema 4 | from .utilities import PyVersionedTypeScriptSchemaSnapshotExtension 5 | 6 | 7 | def a(): 8 | class C(TypedDict): 9 | my_attr_1: str 10 | return C 11 | 12 | 13 | def b(): 14 | class C(TypedDict): 15 | my_attr_2: int 16 | return C 17 | 18 | A = a() 19 | B = b() 20 | 21 | class Derived(A, B): # type: ignore 22 | pass 23 | 24 | def test_conflicting_names_1(snapshot: Any): 25 | assert python_type_to_typescript_schema(cast(type, Derived)) == snapshot(extension_class=PyVersionedTypeScriptSchemaSnapshotExtension) 26 | -------------------------------------------------------------------------------- /python/tests/test_dataclasses.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import Any 2 | from typing import Annotated 3 | from dataclasses import dataclass, field 4 | from typechat import python_type_to_typescript_schema 5 | from .utilities import TypeScriptSchemaSnapshotExtension 6 | 7 | @dataclass 8 | class Options: 9 | """ 10 | TODO: someone add something here. 11 | """ 12 | ... 13 | 14 | @dataclass 15 | class Response: 16 | attr_1: str 17 | attr_2: Annotated[int, "Hello!"] 18 | attr_3: str | None 19 | attr_4: str = "hello!" 20 | attr_5: str | None = None 21 | attr_6: list[str] = field(default_factory=list) 22 | attr_7: Options = field(default_factory=Options) 23 | _underscore_attr_1: int = 123 24 | 25 | def do_something(self): 26 | print(f"{self.attr_1=}") 27 | 28 | 29 | def test_data_classes(snapshot: Any): 30 | assert(python_type_to_typescript_schema(Response) == snapshot(extension_class=TypeScriptSchemaSnapshotExtension)) 31 | -------------------------------------------------------------------------------- /python/tests/test_generic_alias_1.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import TypeAliasType, Any 2 | from typing import Literal, TypedDict, TypeVar, Generic 3 | from typechat import python_type_to_typescript_schema 4 | from .utilities import TypeScriptSchemaSnapshotExtension 5 | 6 | T = TypeVar("T", covariant=True) 7 | 8 | 9 | class First(Generic[T], TypedDict): 10 | kind: Literal["first"] 11 | first_attr: T 12 | 13 | 14 | class Second(Generic[T], TypedDict): 15 | kind: Literal["second"] 16 | second_attr: T 17 | 18 | 19 | FirstOrSecond = TypeAliasType("FirstOrSecond", First[T] | Second[T], type_params=(T,)) 20 | 21 | 22 | def test_generic_alias1(snapshot: Any): 23 | assert(python_type_to_typescript_schema(FirstOrSecond) == snapshot(extension_class=TypeScriptSchemaSnapshotExtension)) 24 | -------------------------------------------------------------------------------- /python/tests/test_generic_alias_2.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import TypeAliasType, Any 2 | from typing import Literal, TypedDict, Generic, TypeVar 3 | from typechat import python_type_to_typescript_schema 4 | from .utilities import TypeScriptSchemaSnapshotExtension 5 | 6 | T = TypeVar("T", covariant=True) 7 | 8 | class First(Generic[T], TypedDict): 9 | kind: Literal["first"] 10 | first_attr: T 11 | 12 | 13 | class Second(Generic[T], TypedDict): 14 | kind: Literal["second"] 15 | second_attr: T 16 | 17 | 18 | FirstOrSecond = TypeAliasType("FirstOrSecond", First[T] | Second[T], type_params=(T,)) 19 | 20 | 21 | class Nested(TypedDict): 22 | item: FirstOrSecond[str] 23 | 24 | 25 | 26 | def test_generic_alias1(snapshot: Any): 27 | assert(python_type_to_typescript_schema(Nested) == snapshot(extension_class=TypeScriptSchemaSnapshotExtension)) 28 | 29 | -------------------------------------------------------------------------------- /python/tests/test_hello_world.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Literal, NotRequired, Optional, Required, Self, TypedDict, TypeVar, Generic, Any 2 | from typing_extensions import TypeAliasType 3 | from typechat import python_type_to_typescript_schema 4 | from .utilities import PyVersionedTypeScriptSchemaSnapshotExtension 5 | 6 | T = TypeVar("T", covariant=True) 7 | 8 | class C(Generic[T], TypedDict): 9 | "This is a generic class named C." 10 | x: NotRequired[T] 11 | c: "C[int | float | None]" 12 | 13 | IndirectC = TypeAliasType("IndirectC", C[int]) 14 | 15 | 16 | class D(C[str], total=False): 17 | "This is the definition of the class D." 18 | tag: Literal["D"] 19 | y: Required[Annotated[bool | None, "This comes from string metadata\nwithin an Annotated hint."]] 20 | z: Optional[list[int]] 21 | other: IndirectC 22 | non_class: "NonClass" 23 | 24 | multiple_metadata: Annotated[str, None, str, "This comes from later metadata.", int] 25 | 26 | 27 | NonClass = TypedDict("NonClass", {"a": int, "my-dict": dict[str, int]}) 28 | 29 | 30 | class E(C[str]): 31 | "This is the definition of the class E." 32 | tag: Literal["E"] 33 | next: Self | None 34 | 35 | 36 | D_or_E = TypeAliasType("D_or_E", D | E) 37 | 38 | 39 | def test_generic_alias1(snapshot: Any): 40 | assert(python_type_to_typescript_schema(D_or_E) == snapshot(extension_class=PyVersionedTypeScriptSchemaSnapshotExtension)) 41 | -------------------------------------------------------------------------------- /python/tests/test_tuple_errors_1.py: -------------------------------------------------------------------------------- 1 | 2 | from dataclasses import dataclass 3 | from typing import Any 4 | 5 | from typechat import python_type_to_typescript_schema 6 | from .utilities import TypeScriptSchemaSnapshotExtension 7 | 8 | @dataclass 9 | class TupleContainer: 10 | empty_tuples_args_1: tuple[(), ()] # type: ignore 11 | empty_tuples_args_2: tuple[(), ...] # type: ignore 12 | 13 | # Arbitrary-length tuples have exactly two type arguments – the type and an ellipsis. 14 | # Any other tuple form that uses an ellipsis is invalid. 15 | arbitrary_length_1: tuple[...] # type: ignore 16 | arbitrary_length_2: tuple[int, int, ...] # type: ignore 17 | arbitrary_length_3: tuple[..., int] # type: ignore 18 | arbitrary_length_4: tuple[..., ...] # type: ignore 19 | arbitrary_length_5: tuple[int, ..., int] # type: ignore 20 | arbitrary_length_6: tuple[int, ..., int, ...] # type: ignore 21 | 22 | def test_tuples_2(snapshot: Any): 23 | assert python_type_to_typescript_schema(TupleContainer) == snapshot(extension_class=TypeScriptSchemaSnapshotExtension) -------------------------------------------------------------------------------- /python/tests/test_tuples_1.py: -------------------------------------------------------------------------------- 1 | 2 | from dataclasses import dataclass 3 | from typing import Any 4 | 5 | from typechat import python_type_to_typescript_schema 6 | from .utilities import TypeScriptSchemaSnapshotExtension 7 | 8 | @dataclass 9 | class TupleContainer: 10 | # The empty tuple can be annotated as tuple[()]. 11 | empty_tuple: tuple[()] 12 | 13 | tuple_1: tuple[int] 14 | tuple_2: tuple[int, str] 15 | tuple_3: tuple[int, str] | tuple[float, str] 16 | 17 | 18 | # Arbitrary-length homogeneous tuples can be expressed using one type and an ellipsis, for example tuple[int, ...]. 19 | arbitrary_length_1: tuple[int, ...] 20 | arbitrary_length_2: tuple[int, ...] | list[int] 21 | arbitrary_length_3: tuple[int, ...] | tuple[int, ...] 22 | arbitrary_length_4: tuple[int, ...] | tuple[float, ...] 23 | arbitrary_length_5: tuple[int, ...] | tuple[int] 24 | arbitrary_length_6: tuple[int, ...] | tuple[int] | tuple[int, int] 25 | 26 | def test_tuples_1(snapshot: Any): 27 | assert python_type_to_typescript_schema(TupleContainer) == snapshot(extension_class=TypeScriptSchemaSnapshotExtension) -------------------------------------------------------------------------------- /python/tests/test_validator.py: -------------------------------------------------------------------------------- 1 | 2 | from dataclasses import dataclass 3 | import typechat 4 | 5 | @dataclass 6 | class Example: 7 | a: str 8 | b: int 9 | c: bool 10 | 11 | v = typechat.TypeChatValidator(Example) 12 | 13 | def test_dict_valid_as_dataclass(): 14 | r = v.validate_object({"a": "hello!", "b": 42, "c": True}) 15 | assert r == typechat.Success(Example(a="hello!", b=42, c=True)) 16 | -------------------------------------------------------------------------------- /python/tests/utilities.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import sys 3 | 4 | from typing_extensions import Any, override 5 | import pytest 6 | 7 | from syrupy.extensions.single_file import SingleFileSnapshotExtension, WriteMode 8 | from syrupy.location import PyTestLocation 9 | 10 | from typechat._internal.ts_conversion import TypeScriptSchemaConversionResult 11 | 12 | class TypeScriptSchemaSnapshotExtension(SingleFileSnapshotExtension): 13 | _write_mode = WriteMode.TEXT 14 | _file_extension = "schema.d.ts" 15 | 16 | @override 17 | def serialize(self, data: TypeScriptSchemaConversionResult, *, 18 | exclude: Any = None, 19 | include: Any = None, 20 | matcher: Any = None, 21 | ) -> str: 22 | result_str = f"// Entry point is: '{data.typescript_type_reference}'\n\n" 23 | if data.errors: 24 | result_str += "// ERRORS:\n" 25 | for err in data.errors: 26 | result_str += f"// !!! {err}\n" 27 | result_str += "\n" 28 | 29 | result_str += data.typescript_schema_str 30 | return result_str 31 | 32 | class PyVersionedTypeScriptSchemaSnapshotExtension(TypeScriptSchemaSnapshotExtension): 33 | py_ver_dir: str = f"__py{sys.version_info.major}.{sys.version_info.minor}_snapshots__" 34 | 35 | @override 36 | @classmethod 37 | def dirname(cls, *, test_location: PyTestLocation) -> str: 38 | result = Path(test_location.filepath).parent.joinpath( 39 | f"{cls.py_ver_dir}", 40 | test_location.basename, 41 | ) 42 | return str(result) 43 | 44 | @pytest.fixture 45 | def snapshot_schema(snapshot: Any): 46 | return snapshot.with_defaults(extension_class=TypeScriptSchemaSnapshotExtension) 47 | -------------------------------------------------------------------------------- /site/.eleventy.js: -------------------------------------------------------------------------------- 1 | 2 | const shiki = require("shiki"); 3 | 4 | // @ts-expect-error 5 | const { EleventyHtmlBasePlugin } = require("@11ty/eleventy"); 6 | 7 | const dateFormatter = new Intl.DateTimeFormat("en-US", { year: "numeric", month: "long", day: "numeric" }); 8 | const listFormatter = new Intl.ListFormat("en-US", { style: "long", type: "conjunction" }); 9 | 10 | /** 11 | * 12 | * @param {import("@11ty/eleventy").UserConfig} eleventyConfig 13 | */ 14 | module.exports = function (eleventyConfig) { 15 | eleventyConfig.addPlugin(EleventyHtmlBasePlugin); 16 | 17 | eleventyConfig.addPassthroughCopy("./src/css"); 18 | eleventyConfig.addPassthroughCopy("./src/js"); 19 | 20 | eleventyConfig.addFilter("formatDate", value => dateFormatter.format(value)); 21 | eleventyConfig.addFilter("formatList", value => listFormatter.format(value)); 22 | 23 | eleventyConfig.setNunjucksEnvironmentOptions({ 24 | throwOnUndefined: true, 25 | }); 26 | 27 | eleventyConfig.amendLibrary("md", () => { }); 28 | eleventyConfig.on("eleventy.before", async () => { 29 | const highlighter = await shiki.getHighlighter({ 30 | langs: [ 31 | "typescript", "javascript", "tsx", "jsx", 32 | "jsonc", "json", 33 | "html", "diff", 34 | "bat", "sh", 35 | "python", "py", 36 | ], 37 | theme: "dark-plus" 38 | }); 39 | eleventyConfig.amendLibrary("md", (mdLib) => 40 | mdLib.set({ 41 | highlight: (code, lang) => highlighter.codeToHtml(code, { lang }), 42 | }) 43 | ); 44 | }); 45 | 46 | return { 47 | dir: { 48 | input: "src", 49 | output: "_site" 50 | }, 51 | pathPrefix: "TypeChat", 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | _site -------------------------------------------------------------------------------- /site/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "module": "nodenext", 5 | "lib": ["esnext", "es2021.intl"], 6 | "noEmit": true, 7 | "checkJs": true 8 | }, 9 | "include": [ 10 | "./.eleventy.js", 11 | ], 12 | "exclude": [] 13 | } 14 | -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typechat-site", 3 | "private": true, 4 | "version": "0.0.1", 5 | "description": "Website for TypeChat", 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "eleventy", 9 | "serve": "eleventy --serve" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/microsoft/TypeChat.git" 14 | }, 15 | "author": "", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/microsoft/TypeChat/issues" 19 | }, 20 | "homepage": "https://github.com/microsoft/TypeChat#readme", 21 | "devDependencies": { 22 | "@11ty/eleventy": "^2.0.1", 23 | "shiki": "^0.14.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /site/src/_data/docsTOC.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "groupName": "Home", 4 | "pages": [ 5 | { "title": "Introduction", "url": "/docs/introduction/"}, 6 | { "title": "Examples", "url": "/docs/examples/"}, 7 | { "title": "Techniques", "url": "/docs/techniques/"}, 8 | { "title": "FAQ", "url": "/docs/faq/"}, 9 | { "title": "Basic Usage for TypeScript", "url": "/docs/typescript/basic-usage/"} 10 | ] 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /site/src/_data/headernav.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Home", 4 | "dest": "/" 5 | }, 6 | { 7 | "title": "Docs", 8 | "dest": "/docs/" 9 | }, 10 | { 11 | "title": "Blog", 12 | "dest": "/blog/" 13 | }, 14 | { 15 | "title": "GitHub", 16 | "dest": "https://github.com/microsoft/TypeChat", 17 | "isExternal": true 18 | } 19 | ] -------------------------------------------------------------------------------- /site/src/_includes/base.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% if title %}{{title}} - TypeChat{% else %}TypeChat{% endif %} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% include "header-prologue.njk" %} 18 | 19 | {{ content | safe }} 20 | 21 | {% include "footer.njk" %} 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /site/src/_includes/blog.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base 3 | --- 4 | 5 |
6 | 22 |
23 | 43 |
44 | {{ content | safe }} 45 |
46 |
47 |
-------------------------------------------------------------------------------- /site/src/_includes/doc-page.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: docs 3 | --- 4 |

{{title}}

5 | {{content | safe}} -------------------------------------------------------------------------------- /site/src/_includes/docs.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base 3 | title: Docs 4 | --- 5 |
6 | 27 |
28 | 51 |
52 | {{ content | safe }} 53 |
54 |
55 |
-------------------------------------------------------------------------------- /site/src/_includes/footer.njk: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /site/src/_includes/header-prologue.njk: -------------------------------------------------------------------------------- 1 | Skip to main content 2 |
3 |
4 | 5 | 8 | TypeChat 9 | 10 | 11 | 22 |
23 |
24 | -------------------------------------------------------------------------------- /site/src/blog/index.njk: -------------------------------------------------------------------------------- 1 | --- 2 | title: Blog 3 | --- 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% set latestPost = collections.post[0] %} 12 | {% if latestPost %} 13 | 14 | Redirecting to {{latestPost.data.title}} 15 | 16 | 17 | {%endif%} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% include "header-prologue.njk" %} 26 | 27 | {% include "footer.njk" %} 28 |
29 | 30 | 31 | 32 | {#- 33 |
34 |

Blog Posts

35 | 45 | 46 | {% endfor %} 47 | 48 |
49 | -#} 50 | -------------------------------------------------------------------------------- /site/src/css/noscript-styles.css: -------------------------------------------------------------------------------- 1 | .typechat-hero button { 2 | display: none !important; 3 | } 4 | 5 | .typechat-docs-smol-nav { 6 | display: none !important; 7 | } 8 | -------------------------------------------------------------------------------- /site/src/css/styles.css: -------------------------------------------------------------------------------- 1 | .skip-to-main { 2 | position: absolute; 3 | opacity: 0; 4 | z-index: -999999999; 5 | margin: 0 auto; 6 | padding: 2rem 0; 7 | background-color: #000; 8 | color: #fff; 9 | top: 0; 10 | left: 0; 11 | width: 100%; 12 | height: 60px; 13 | text-align: center; 14 | } 15 | 16 | .skip-to-main:focus { 17 | opacity: 1; 18 | z-index: 999999999; 19 | } 20 | 21 | .with-sidebar { 22 | display: flex; 23 | flex-wrap: wrap; 24 | gap: 2rem; 25 | } 26 | 27 | .with-sidebar > :first-child { 28 | flex-basis: 140px; 29 | flex-grow: 1; 30 | } 31 | 32 | .with-sidebar > :last-child { 33 | flex-basis: 0; 34 | flex-grow: 999; 35 | min-inline-size: 50%; 36 | } 37 | 38 | :root { 39 | --typechat-monospace: Consolas, Menlo, Monaco, Roboto, monospace; 40 | --typechat-inline-code-color: #a10615; 41 | --typechat-rounding-radius: 0.5rem; 42 | } 43 | 44 | .typechat-cap-content-width { 45 | max-width: 1000px; 46 | } 47 | 48 | .typechat-hero .typechat-code-copy { 49 | background-color: #212529; 50 | color: #fff; 51 | font-style: var(--typechat-monospace); 52 | border-radius: var(--typechat-rounding-radius); 53 | padding: 0.75rem 1rem; 54 | text-align: center; 55 | } 56 | 57 | .typechat-hero .typechat-code-copy code { 58 | background-color: inherit; 59 | color: inherit; 60 | } 61 | 62 | .typechat-hero .typechat-code-copy button { 63 | height: 100%; 64 | width: fit-content; 65 | right: 0; 66 | top: 0; 67 | border: none; 68 | /* border-radius: var(--typechat-rounding-radius) 0 0 var(--typechat-rounding-radius); */ 69 | border-radius: 0 var(--typechat-rounding-radius) var(--typechat-rounding-radius) 0; 70 | } 71 | 72 | .typechat-prose-content :is(pre, blockquote) { 73 | padding: 1rem; 74 | border-radius: 0.5rem; 75 | box-shadow: 0px 2px 5px #666; 76 | } 77 | 78 | .typechat-prose-content blockquote { 79 | background-color: #f5f8fa; 80 | } 81 | 82 | .typechat-prose-content code { 83 | font-family: var(--typechat-monospace); 84 | color: var(--typechat-inline-code-color); 85 | font-size: inherit; 86 | } 87 | 88 | .typechat-prose-content :not(pre) code { 89 | word-break: break-all; 90 | } 91 | 92 | .typechat-prose-content blockquote > *:last-child { 93 | margin-bottom: 0; 94 | } 95 | 96 | .typechat-prose-content :not(h1, h2, h3, h4, h5, h6) + :is(h1, h2, h3, h4, h5, h6) { 97 | margin-top: 1rem; 98 | } 99 | 100 | .typechat-prose-content table { 101 | margin-bottom: 1rem; 102 | border-collapse: collapse; 103 | } 104 | 105 | .typechat-prose-content td, th { 106 | border: 1px solid #666; 107 | border-left: 0; 108 | border-right: 0; 109 | padding: 0.5rem; 110 | } 111 | 112 | .typechat-prose-content th { 113 | border-top: 0; 114 | } 115 | -------------------------------------------------------------------------------- /site/src/docs/index.njk: -------------------------------------------------------------------------------- 1 | --- 2 | title: Docs 3 | --- 4 | 5 | 6 | 7 | 8 | 9 | 10 | {% set firstDoc = docsTOC[0].pages[0] %} 11 | {% if firstDoc %} 12 | 13 | Redirecting to {{firstDoc.title}} 14 | 15 | 16 | {%endif%} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% include "header-prologue.njk" %} 25 | 26 | {% include "footer.njk" %} 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /site/src/docs/techniques.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: doc-page 3 | title: Techniques 4 | --- 5 | 6 | This document defines techniques for working with TypeChat. 7 | 8 | ### Schema Engineering 9 | 10 | TypeChat replaces _prompt engineering_ with _schema engineering_: Instead of writing unstructured natural language prompts to describe the format of your desired output, you write TypeScript type definitions. These TypeScript schema aren't necessarily the exact types your application uses to process and store your data. Rather, they're types that bridge between natural language and your application logic by _controlling and constraining_ LLM responses in ways that are meaningful to your application. 11 | 12 | To use an analogy, in the Model-View-ViewModel (MVVM) user interface design pattern, the ViewModel bridges between the user interface and the application logic, but it isn't the model the application uses to process and store information. The schema you design for TypeChat are like the ViewModel, but are perhaps more meaningfully called _Response Models_. 13 | 14 | To maximize success with TypeChat, we recommend the following best practices when defining Response Model types: 15 | 16 | * Keep it simple (primitives, arrays, and objects). 17 | * Only use types that are representable as JSON (i.e. no classes). 18 | * Make data structures as flat and regular as possible. 19 | * Include comments on types and properties that describe intent in natural language. 20 | * Restrict use of generics. 21 | * Avoid deep inheritance hierarchies. 22 | * Don't use conditional, mapped, and indexed access types. 23 | * Allow room for LLMs to color slightly outside the lines (e.g. use `string` instead of literal types). 24 | * Include an escape hatch to suppress hallucinations. 25 | 26 | The last point merits further elaboration. We've found that when Response Models attempt to fit user requests into narrow schema with no wiggle room, the LLMs are likely to hallucinate answers for user requests that are outside the domain. For example, if you ask your coffee shop bot for "two tall trees", given no other option it may well turn that into two tall lattes (without letting you know it did so). 27 | 28 | However, when you include an _escape hatch_ in the form of an "unknown" category in your schema, the LLMs happily route non-domain requests into that bucket. Not only does this greatly suppress hallucinations, it also gives you a convenient way of letting the user know which parts of a request weren't understood. The examples in the TypeChat repo all use this technique. -------------------------------------------------------------------------------- /site/src/index.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base 3 | --- 4 | 5 |
6 |
7 |
8 |

TypeChat

9 |

TypeChat helps get well-typed responses from language models to build 10 | pragmatic natural language interfaces.

11 |

All powered through your types.

12 |
13 |
14 |
15 | Docs 16 | GitHub 17 |
18 |
19 | 20 | npm install typechat 21 |
22 |
23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /site/src/js/interactivity.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | { 4 | /** @type {any} */ 5 | let lastTimeout; 6 | 7 | /** @type {HTMLButtonElement | null} */ 8 | const copyButton = document.querySelector(".typechat-code-copy button"); 9 | copyButton?.addEventListener("click", async () => { 10 | clearTimeout(lastTimeout); 11 | try { 12 | await navigator.clipboard?.writeText("npm install typechat"); 13 | copyButton.textContent = "✅"; 14 | copyButton.title = copyButton.ariaLabel = "Command copied." 15 | } 16 | catch { 17 | copyButton.textContent = "❌"; 18 | copyButton.title = copyButton.ariaLabel = "Error copying." 19 | } 20 | lastTimeout = setTimeout(() => { 21 | copyButton.textContent = "📋"; 22 | copyButton.title = copyButton.ariaLabel = "Copy 'npm install' command." 23 | }, 1500); 24 | }); 25 | } 26 | 27 | { 28 | const selectElements = /** @type {HTMLCollectionOf} */ (document.getElementsByClassName("nav-on-change")); 29 | for (const select of selectElements) { 30 | const change = () => { 31 | window.location.pathname = select.value; 32 | }; 33 | select.onchange = change; 34 | // if (select.options.length === 1 && window.location.pathname !== select.value) { 35 | // change(); 36 | // } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /typescript/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | out/ 4 | node_modules/ 5 | .env 6 | *.map 7 | *.out.txt 8 | *.bat 9 | 10 | # Local development and debugging 11 | .scratch/ 12 | **/.vscode/* 13 | **/tsconfig.debug.json 14 | !**/.vscode/launch.json 15 | **/build.bat 16 | 17 | -------------------------------------------------------------------------------- /typescript/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /typescript/examples/calendar/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/dist/main.js", 15 | "console": "externalTerminal" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /typescript/examples/calendar/README.md: -------------------------------------------------------------------------------- 1 | # Calendar 2 | 3 | The Calendar example shows how you can capture user intent as a sequence of actions, such as adding event to a calendar or searching for an event as defined by the [`CalendarActions`](./src/calendarActionsSchema.ts) type. 4 | 5 | # Try Calendar 6 | 7 | To run the Calendar example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 8 | 9 | # Usage 10 | 11 | Example prompts can be found in [`src/input.txt`](./src/input.txt). 12 | 13 | For example, we could use natural language to describe an event coming up soon: 14 | 15 | **Input**: 16 | 17 | ``` 18 | 📅> I need to get my tires changed from 12:00 to 2:00 pm on Friday March 15, 2024 19 | ``` 20 | 21 | **Output**: 22 | 23 | ```json 24 | { 25 | "actions": [ 26 | { 27 | "actionType": "add event", 28 | "event": { 29 | "day": "Friday March 15, 2024", 30 | "timeRange": { 31 | "startTime": "12:00 pm", 32 | "endTime": "2:00 pm" 33 | }, 34 | "description": "get my tires changed" 35 | } 36 | } 37 | ] 38 | } 39 | ``` -------------------------------------------------------------------------------- /typescript/examples/calendar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "calendar", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "main": "dist/main.js", 7 | "scripts": { 8 | "build": "tsc -p src", 9 | "postbuild": "copyfiles -u 1 src/**/*Schema.ts src/**/*.txt dist" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "dotenv": "^16.3.1", 15 | "find-config": "^1.0.0", 16 | "typechat": "^0.1.0", 17 | "typescript": "^5.3.3" 18 | }, 19 | "devDependencies": { 20 | "@types/find-config": "1.0.4", 21 | "@types/node": "^20.10.4", 22 | "copyfiles": "^2.4.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /typescript/examples/calendar/src/calendarActionsSchema.ts: -------------------------------------------------------------------------------- 1 | // The following types define the structure of an object of type CalendarActions that represents a list of requested calendar actions 2 | 3 | export type CalendarActions = { 4 | actions: Action[]; 5 | }; 6 | 7 | export type Action = 8 | | AddEventAction 9 | | RemoveEventAction 10 | | AddParticipantsAction 11 | | ChangeTimeRangeAction 12 | | ChangeDescriptionAction 13 | | FindEventsAction 14 | | UnknownAction; 15 | 16 | export type AddEventAction = { 17 | actionType: 'add event'; 18 | event: Event; 19 | }; 20 | 21 | export type RemoveEventAction = { 22 | actionType: 'remove event'; 23 | eventReference: EventReference; 24 | }; 25 | 26 | export type AddParticipantsAction = { 27 | actionType: 'add participants'; 28 | // event to be augmented; if not specified assume last event discussed 29 | eventReference?: EventReference; 30 | // new participants (one or more) 31 | participants: string[]; 32 | }; 33 | 34 | export type ChangeTimeRangeAction = { 35 | actionType: 'change time range'; 36 | // event to be changed 37 | eventReference?: EventReference; 38 | // new time range for the event 39 | timeRange: EventTimeRange; 40 | }; 41 | 42 | export type ChangeDescriptionAction = { 43 | actionType: 'change description'; 44 | // event to be changed 45 | eventReference?: EventReference; 46 | // new description for the event 47 | description: string; 48 | }; 49 | 50 | export type FindEventsAction = { 51 | actionType: 'find events'; 52 | // one or more event properties to use to search for matching events 53 | eventReference: EventReference; 54 | }; 55 | 56 | // if the user types text that can not easily be understood as a calendar action, this action is used 57 | export interface UnknownAction { 58 | actionType: 'unknown'; 59 | // text typed by the user that the system did not understand 60 | text: string; 61 | } 62 | 63 | export type EventTimeRange = { 64 | startTime?: string; 65 | endTime?: string; 66 | duration?: string; 67 | }; 68 | 69 | export type Event = { 70 | // date (example: March 22, 2024) or relative date (example: after EventReference) 71 | day: string; 72 | timeRange: EventTimeRange; 73 | description: string; 74 | location?: string; 75 | // a list of people or named groups like 'team' 76 | participants?: string[]; 77 | }; 78 | 79 | // properties used by the requester in referring to an event 80 | // these properties are only specified if given directly by the requester 81 | export type EventReference = { 82 | // date (example: March 22, 2024) or relative date (example: after EventReference) 83 | day?: string; 84 | // (examples: this month, this week, in the next two days) 85 | dayRange?: string; 86 | timeRange?: EventTimeRange; 87 | description?: string; 88 | location?: string; 89 | participants?: string[]; 90 | }; 91 | -------------------------------------------------------------------------------- /typescript/examples/calendar/src/input.txt: -------------------------------------------------------------------------------- 1 | I need to get my tires changed from 12:00 to 2:00 pm on Friday March 15, 2024 2 | Search for any meetings with Gavin this week 3 | Set up an event for friday named Jeffs pizza party at 6pm 4 | Please add Jennifer to the scrum next Thursday 5 | Will you please add an appointment with Jerri Skinner at 9 am? I need it to last 2 hours 6 | Do I have any plan with Rosy this month? 7 | I need to add a meeting with my boss on Monday at 10am. Also make sure to schedule and appointment with Sally, May, and Boris tomorrow at 3pm. Now just add to it Jesse and Abby and make it last ninety minutes 8 | Add meeting with team today at 2 9 | can you record lunch with Luis at 12pm on Friday and also add Isobel to the Wednesday ping pong game at 4pm 10 | I said I'd meet with Jenny this afternoon at 2pm and after that I need to go to the dry cleaner and then the soccer game. Leave an hour for each of those starting at 3:30 11 | -------------------------------------------------------------------------------- /typescript/examples/calendar/src/main.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import dotenv from "dotenv"; 3 | import findConfig from "find-config"; 4 | import fs from "fs"; 5 | import path from "path"; 6 | import { createJsonTranslator, createLanguageModel } from "typechat"; 7 | import { createTypeScriptJsonValidator } from "typechat/ts"; 8 | import { processRequests } from "typechat/interactive"; 9 | import { CalendarActions } from './calendarActionsSchema'; 10 | 11 | const dotEnvPath = findConfig(".env"); 12 | assert(dotEnvPath, ".env file not found!"); 13 | dotenv.config({ path: dotEnvPath }); 14 | 15 | const model = createLanguageModel(process.env); 16 | const schema = fs.readFileSync(path.join(__dirname, "calendarActionsSchema.ts"), "utf8"); 17 | const validator = createTypeScriptJsonValidator(schema, "CalendarActions"); 18 | const translator = createJsonTranslator(model, validator); 19 | //translator.stripNulls = true; 20 | 21 | // Process requests interactively or from the input file specified on the command line 22 | processRequests("📅> ", process.argv[2], async (request) => { 23 | const response = await translator.translate(request); 24 | if (!response.success) { 25 | console.log(response.message); 26 | return; 27 | } 28 | const calendarActions = response.data; 29 | console.log(JSON.stringify(calendarActions, undefined, 2)); 30 | if (calendarActions.actions.some(item => item.actionType === "unknown")) { 31 | console.log("I didn't understand the following:"); 32 | for (const action of calendarActions.actions) { 33 | if (action.actionType === "unknown") console.log(action.text); 34 | } 35 | return; 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /typescript/examples/calendar/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "lib": ["es2021"], 5 | "module": "node16", 6 | "types": ["node"], 7 | "outDir": "../dist", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "exactOptionalPropertyTypes": true, 14 | "inlineSourceMap": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /typescript/examples/coffeeShop-zod/README.md: -------------------------------------------------------------------------------- 1 | # Coffee Shop 2 | 3 | The Coffee Shop example shows how to capture user intent as a set of "nouns". 4 | In this case, the nouns are items in a coffee order, where valid items are defined starting from the [`Cart`](./src/coffeeShopSchema.ts) type. 5 | 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). 6 | 7 | # Try Coffee Shop 8 | 9 | To run the Coffee Shop example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 10 | 11 | # Usage 12 | 13 | Example prompts can be found in [`src/input.txt`](./src/input.txt) and [`src/input2.txt`](./src/input2.txt). 14 | 15 | For example, we could use natural language to describe our coffee shop order: 16 | 17 | **Input**: 18 | 19 | ``` 20 | ☕> we'd like a cappuccino with a pack of sugar 21 | ``` 22 | 23 | **Output**: 24 | 25 | ```json 26 | { 27 | "items": [ 28 | { 29 | "type": "lineitem", 30 | "product": { 31 | "type": "LatteDrinks", 32 | "name": "cappuccino", 33 | "options": [ 34 | { 35 | "type": "Sweeteners", 36 | "name": "sugar", 37 | "optionQuantity": "regular" 38 | } 39 | ] 40 | }, 41 | "quantity": 1 42 | } 43 | ] 44 | } 45 | ``` -------------------------------------------------------------------------------- /typescript/examples/coffeeShop-zod/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coffeeshop-zod", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "main": "dist/main.js", 7 | "scripts": { 8 | "build": "tsc -p src", 9 | "postbuild": "copyfiles -u 1 src/**/*.txt dist" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "dotenv": "^16.3.1", 15 | "find-config": "^1.0.0", 16 | "typechat": "^0.1.0", 17 | "zod": "^3.22.4" 18 | }, 19 | "devDependencies": { 20 | "@types/find-config": "1.0.4", 21 | "@types/node": "^20.10.4", 22 | "copyfiles": "^2.4.1", 23 | "typescript": "^5.3.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /typescript/examples/coffeeShop-zod/src/input.txt: -------------------------------------------------------------------------------- 1 | i'd like a latte that's it 2 | i'll have a dark roast coffee thank you 3 | get me a coffee please 4 | could i please get two mochas that's all 5 | we need twenty five flat whites and that'll do it 6 | how about a tall cappuccino 7 | i'd like a venti iced latte 8 | i'd like a iced venti latte 9 | i'd like a venti latte iced 10 | i'd like a latte iced venti 11 | we'll also have a short tall latte 12 | i wanna latte macchiato with vanilla 13 | how about a peppermint latte 14 | may i also get a decaf soy vanilla syrup caramel latte with sugar and foam 15 | i want a latte with peppermint syrup with peppermint syrup 16 | i'd like a decaf half caf latte 17 | can I get a skim soy latte 18 | i'd like a light nutmeg espresso that's it 19 | can i have an cappuccino no foam 20 | can i have an espresso with no nutmeg 21 | we want a light whipped no foam mocha with extra hazelnut and cinnamon 22 | i'd like a latte cut in half 23 | i'd like a strawberry latte 24 | i want a five pump caramel flat white 25 | i want a flat white with five pumps of caramel syrup 26 | i want a two pump peppermint three squirt raspberry skinny vanilla latte with a pump of caramel and two sugars 27 | i want a latte cappuccino espresso and an apple muffin 28 | i'd like a tall decaf latte iced a grande cappuccino double espresso and a warmed poppyseed muffin sliced in half 29 | we'd like a latte with soy and a coffee with soy 30 | i want a latte latte macchiato and a chai latte 31 | we'd like a cappuccino with two pumps of vanilla 32 | make that cappuccino with three pumps of vanilla 33 | we'd like a cappuccino with a pack of sugar 34 | make that cappuccino with two packs of sugar 35 | we'd like a cappuccino with a pack of sugar 36 | make that with two packs of sugar 37 | i'd like a flat white with two equal 38 | add three equal to the flat white 39 | i'd like a flat white with two equal 40 | two tall lattes. the first one with no foam. the second one with whole milk. 41 | two tall lattes. the first one with no foam. the second one with whole milk. actually make the first one a grande. 42 | un petit cafe 43 | en lille kaffe 44 | a raspberry latte 45 | a strawberry latte 46 | roses are red 47 | two lawnmowers, a grande latte and a tall tree 48 | -------------------------------------------------------------------------------- /typescript/examples/coffeeShop-zod/src/input2.txt: -------------------------------------------------------------------------------- 1 | two tall lattes. the first one with no foam. the second one with whole milk. 2 | two tall lattes. the first one with no foam. the second one with whole milk. actually make the first one a grande. 3 | un petit cafe 4 | en lille kaffe 5 | a raspberry latte 6 | a strawberry latte 7 | roses are red 8 | two lawnmowers, a grande latte and a tall tree 9 | -------------------------------------------------------------------------------- /typescript/examples/coffeeShop-zod/src/main.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import dotenv from "dotenv"; 3 | import findConfig from "find-config"; 4 | import { createJsonTranslator, createLanguageModel } from "typechat"; 5 | import { createZodJsonValidator } from "typechat/zod"; 6 | import { processRequests } from "typechat/interactive"; 7 | import { z } from "zod"; 8 | import { CoffeeShopSchema } from "./coffeeShopSchema"; 9 | 10 | const dotEnvPath = findConfig(".env"); 11 | assert(dotEnvPath, ".env file not found!"); 12 | dotenv.config({ path: dotEnvPath }); 13 | 14 | const model = createLanguageModel(process.env); 15 | const validator = createZodJsonValidator(CoffeeShopSchema, "Cart"); 16 | const translator = createJsonTranslator(model, validator); 17 | 18 | function processOrder(cart: z.TypeOf) { 19 | // Process the items in the cart 20 | void cart; 21 | } 22 | 23 | // Process requests interactively or from the input file specified on the command line 24 | processRequests("☕> ", process.argv[2], async (request) => { 25 | const response = await translator.translate(request); 26 | if (!response.success) { 27 | console.log(response.message); 28 | return; 29 | } 30 | const cart = response.data; 31 | console.log(JSON.stringify(cart, undefined, 2)); 32 | if (cart.items.some(item => item.type === "unknown")) { 33 | console.log("I didn't understand the following:"); 34 | for (const item of cart.items) { 35 | if (item.type === "unknown") console.log(item.text); 36 | } 37 | return; 38 | } 39 | processOrder(cart); 40 | console.log("Success!"); 41 | }); 42 | -------------------------------------------------------------------------------- /typescript/examples/coffeeShop-zod/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "lib": ["es2021"], 5 | "module": "node16", 6 | "types": ["node"], 7 | "outDir": "../dist", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "exactOptionalPropertyTypes": true, 14 | "inlineSourceMap": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /typescript/examples/coffeeShop/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/dist/main.js", 15 | "console": "externalTerminal" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /typescript/examples/coffeeShop/README.md: -------------------------------------------------------------------------------- 1 | # Coffee Shop 2 | 3 | The Coffee Shop example shows how to capture user intent as a set of "nouns". 4 | In this case, the nouns are items in a coffee order, where valid items are defined starting from the [`Cart`](./src/coffeeShopSchema.ts) type. 5 | 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). 6 | 7 | # Try Coffee Shop 8 | 9 | To run the Coffee Shop example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 10 | 11 | # Usage 12 | 13 | Example prompts can be found in [`src/input.txt`](./src/input.txt) and [`src/input2.txt`](./src/input2.txt). 14 | 15 | For example, we could use natural language to describe our coffee shop order: 16 | 17 | **Input**: 18 | 19 | ``` 20 | ☕> we'd like a cappuccino with a pack of sugar 21 | ``` 22 | 23 | **Output**: 24 | 25 | ```json 26 | { 27 | "items": [ 28 | { 29 | "type": "lineitem", 30 | "product": { 31 | "type": "LatteDrinks", 32 | "name": "cappuccino", 33 | "options": [ 34 | { 35 | "type": "Sweeteners", 36 | "name": "sugar", 37 | "optionQuantity": "regular" 38 | } 39 | ] 40 | }, 41 | "quantity": 1 42 | } 43 | ] 44 | } 45 | ``` -------------------------------------------------------------------------------- /typescript/examples/coffeeShop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coffeeshop", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "main": "dist/main.js", 7 | "scripts": { 8 | "build": "tsc -p src", 9 | "postbuild": "copyfiles -u 1 src/**/*Schema.ts src/**/*.txt dist" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "dotenv": "^16.3.1", 15 | "find-config": "^1.0.0", 16 | "typechat": "^0.1.0", 17 | "typescript": "^5.3.3" 18 | }, 19 | "devDependencies": { 20 | "@types/find-config": "1.0.4", 21 | "@types/node": "^20.10.4", 22 | "copyfiles": "^2.4.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /typescript/examples/coffeeShop/src/input.txt: -------------------------------------------------------------------------------- 1 | i'd like a latte that's it 2 | i'll have a dark roast coffee thank you 3 | get me a coffee please 4 | could i please get two mochas that's all 5 | we need twenty five flat whites and that'll do it 6 | how about a tall cappuccino 7 | i'd like a venti iced latte 8 | i'd like a iced venti latte 9 | i'd like a venti latte iced 10 | i'd like a latte iced venti 11 | we'll also have a short tall latte 12 | i wanna latte macchiato with vanilla 13 | how about a peppermint latte 14 | may i also get a decaf soy vanilla syrup caramel latte with sugar and foam 15 | i want a latte with peppermint syrup with peppermint syrup 16 | i'd like a decaf half caf latte 17 | can I get a skim soy latte 18 | i'd like a light nutmeg espresso that's it 19 | can i have an cappuccino no foam 20 | can i have an espresso with no nutmeg 21 | we want a light whipped no foam mocha with extra hazelnut and cinnamon 22 | i'd like a latte cut in half 23 | i'd like a strawberry latte 24 | i want a five pump caramel flat white 25 | i want a flat white with five pumps of caramel syrup 26 | i want a two pump peppermint three squirt raspberry skinny vanilla latte with a pump of caramel and two sugars 27 | i want a latte cappuccino espresso and an apple muffin 28 | i'd like a tall decaf latte iced a grande cappuccino double espresso and a warmed poppyseed muffin sliced in half 29 | we'd like a latte with soy and a coffee with soy 30 | i want a latte latte macchiato and a chai latte 31 | we'd like a cappuccino with two pumps of vanilla 32 | make that cappuccino with three pumps of vanilla 33 | we'd like a cappuccino with a pack of sugar 34 | make that cappuccino with two packs of sugar 35 | we'd like a cappuccino with a pack of sugar 36 | make that with two packs of sugar 37 | i'd like a flat white with two equal 38 | add three equal to the flat white 39 | i'd like a flat white with two equal 40 | two tall lattes. the first one with no foam. the second one with whole milk. 41 | two tall lattes. the first one with no foam. the second one with whole milk. actually make the first one a grande. 42 | un petit cafe 43 | en lille kaffe 44 | a raspberry latte 45 | a strawberry latte 46 | roses are red 47 | two lawnmowers, a grande latte and a tall tree 48 | -------------------------------------------------------------------------------- /typescript/examples/coffeeShop/src/input2.txt: -------------------------------------------------------------------------------- 1 | two tall lattes. the first one with no foam. the second one with whole milk. 2 | two tall lattes. the first one with no foam. the second one with whole milk. actually make the first one a grande. 3 | un petit cafe 4 | en lille kaffe 5 | a raspberry latte 6 | a strawberry latte 7 | roses are red 8 | two lawnmowers, a grande latte and a tall tree 9 | -------------------------------------------------------------------------------- /typescript/examples/coffeeShop/src/main.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import dotenv from "dotenv"; 3 | import findConfig from "find-config"; 4 | import fs from "fs"; 5 | import path from "path"; 6 | import { createJsonTranslator, createLanguageModel } from "typechat"; 7 | import { createTypeScriptJsonValidator } from "typechat/ts"; 8 | import { processRequests } from "typechat/interactive"; 9 | import { Cart } from "./coffeeShopSchema"; 10 | 11 | const dotEnvPath = findConfig(".env"); 12 | assert(dotEnvPath, ".env file not found!"); 13 | dotenv.config({ path: dotEnvPath }); 14 | 15 | const model = createLanguageModel(process.env); 16 | const schema = fs.readFileSync(path.join(__dirname, "coffeeShopSchema.ts"), "utf8"); 17 | const validator = createTypeScriptJsonValidator(schema, "Cart"); 18 | const translator = createJsonTranslator(model, validator); 19 | 20 | function processOrder(cart: Cart) { 21 | // Process the items in the cart 22 | void cart; 23 | } 24 | 25 | // Process requests interactively or from the input file specified on the command line 26 | processRequests("☕> ", process.argv[2], async (request) => { 27 | const response = await translator.translate(request); 28 | if (!response.success) { 29 | console.log(response.message); 30 | return; 31 | } 32 | const cart = response.data; 33 | console.log(JSON.stringify(cart, undefined, 2)); 34 | if (cart.items.some(item => item.type === "unknown")) { 35 | console.log("I didn't understand the following:"); 36 | for (const item of cart.items) { 37 | if (item.type === "unknown") console.log(item.text); 38 | } 39 | return; 40 | } 41 | processOrder(cart); 42 | console.log("Success!"); 43 | }); 44 | -------------------------------------------------------------------------------- /typescript/examples/coffeeShop/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "lib": ["es2021"], 5 | "module": "node16", 6 | "types": ["node"], 7 | "outDir": "../dist", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "exactOptionalPropertyTypes": true, 14 | "inlineSourceMap": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /typescript/examples/crossword/README.md: -------------------------------------------------------------------------------- 1 | # Crossword 2 | 3 | The Crossword example shows how to include an image in a multimodal prompt and use the image to answer a user's question. The responses follow the [`CrosswordActions`](./src/crosswordSchema.ts) type. 4 | 5 | ## Target models 6 | 7 | This example explores multi-modal input. Torun this, you will need a model that accepts images as input. The example has beeentested with **gpt-4-vision** and **gpt-4-omni** models. 8 | 9 | # Try Crossword 10 | To run the Crossword example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 11 | 12 | # Usage 13 | Example prompts can be found in [`src/input.txt`](./src/input.txt). 14 | 15 | For example, given the following input statement: 16 | 17 | **Input**: 18 | ``` 19 | 🏁> What is the clue for 61 across 20 | ``` 21 | 22 | **Output**: 23 | ``` 24 | "Monogram in French fashion" 25 | ``` -------------------------------------------------------------------------------- /typescript/examples/crossword/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crossword", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "main": "dist/main.js", 7 | "scripts": { 8 | "build": "tsc -p src", 9 | "postbuild": "copyfiles -u 1 src/**/*Schema.ts src/**/*.txt src/**/*.jpeg dist" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "dotenv": "^16.3.1", 15 | "find-config": "^1.0.0", 16 | "typechat": "^0.1.0", 17 | "typescript": "^5.3.3" 18 | }, 19 | "devDependencies": { 20 | "@types/find-config": "1.0.4", 21 | "@types/node": "^20.10.4", 22 | "copyfiles": "^2.4.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /typescript/examples/crossword/src/crosswordSchema.ts: -------------------------------------------------------------------------------- 1 | // The following is a schema definition for determining the sentiment of a some user input. 2 | 3 | export type GetClueText = { 4 | actionName: "getClueText"; 5 | parameters: { 6 | clueNumber: number; 7 | clueDirection: "across" | "down"; 8 | value: string; 9 | }; 10 | }; 11 | 12 | // This gives the answer for the requested crossword clue 13 | export type GetAnswerValue = { 14 | actionName: "getAnswerValue"; 15 | parameters: { 16 | proposedAnswer: string; 17 | clueNumber: number; 18 | clueDirection: "across" | "down"; 19 | }; 20 | }; 21 | 22 | export type UnknownAction = { 23 | actionName: "unknown"; 24 | parameters: { 25 | // text typed by the user that the system did not understand 26 | text: string; 27 | }; 28 | }; 29 | 30 | export type CrosswordActions = 31 | | GetClueText 32 | | GetAnswerValue 33 | | UnknownAction; -------------------------------------------------------------------------------- /typescript/examples/crossword/src/input.txt: -------------------------------------------------------------------------------- 1 | What is the clue for 1 down 2 | Give me a hint for solving 4 down -------------------------------------------------------------------------------- /typescript/examples/crossword/src/main.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import dotenv from "dotenv"; 3 | import findConfig from "find-config"; 4 | import fs from "fs"; 5 | import path from "path"; 6 | import { createLanguageModel } from "typechat"; 7 | import { processRequests } from "typechat/interactive"; 8 | import { createTypeScriptJsonValidator } from "typechat/ts"; 9 | import { CrosswordActions } from "./crosswordSchema"; 10 | import { createCrosswordActionTranslator } from "./translator"; 11 | 12 | const dotEnvPath = findConfig(".env"); 13 | assert(dotEnvPath, ".env file not found!"); 14 | dotenv.config({ path: dotEnvPath }); 15 | 16 | const model = createLanguageModel(process.env); 17 | const schema = fs.readFileSync(path.join(__dirname, "crosswordSchema.ts"), "utf8"); 18 | 19 | const rawImage = fs.readFileSync(path.join(__dirname, "puzzleScreenshot.jpeg"),"base64"); 20 | const screenshot = `data:image/jpeg;base64,${rawImage}`; 21 | 22 | const validator = createTypeScriptJsonValidator(schema, "CrosswordActions"); 23 | const translator = createCrosswordActionTranslator(model, validator, screenshot); 24 | 25 | // Process requests interactively or from the input file specified on the command line 26 | processRequests("🏁> ", process.argv[2], async (request) => { 27 | const response = await translator.translate(request); 28 | if (!response.success) { 29 | console.log(response.message); 30 | return; 31 | } 32 | 33 | console.log(JSON.stringify(response.data)); 34 | }); 35 | -------------------------------------------------------------------------------- /typescript/examples/crossword/src/puzzleScreenshot.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/TypeChat/fb4bdeb8eefc5ed33718c5ed83417f38f83f1480/typescript/examples/crossword/src/puzzleScreenshot.jpeg -------------------------------------------------------------------------------- /typescript/examples/crossword/src/translator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TypeChatLanguageModel, 3 | createJsonTranslator, 4 | TypeChatJsonTranslator, 5 | MultimodalPromptContent, 6 | PromptContent, 7 | } from "typechat"; 8 | import { TypeScriptJsonValidator } from "typechat/ts"; 9 | 10 | export function createCrosswordActionTranslator( 11 | model: TypeChatLanguageModel, 12 | validator: TypeScriptJsonValidator, 13 | crosswordImage: string 14 | ): TypeChatJsonTranslator { 15 | const _imageContent = crosswordImage; 16 | 17 | const _translator = createJsonTranslator(model, validator); 18 | _translator.createRequestPrompt = createRequestPrompt 19 | 20 | return _translator; 21 | 22 | function createRequestPrompt(request: string): PromptContent { 23 | const screenshotSection = getScreenshotPromptSection(_imageContent); 24 | const contentSections = [ 25 | { 26 | type: "text", 27 | text: "You are a virtual assistant that can help users to complete requests by interacting with the UI of a webpage.", 28 | }, 29 | ...screenshotSection, 30 | { 31 | type: "text", 32 | text: ` 33 | Use the layout information provided to answer user queries. 34 | The responses should be translated into JSON objects of type ${_translator.validator.getTypeName()} using the typescript schema below: 35 | 36 | ''' 37 | ${_translator.validator.getSchemaText()} 38 | ''' 39 | `, 40 | }, 41 | { 42 | type: "text", 43 | text: ` 44 | The following is a user request: 45 | ''' 46 | ${request} 47 | ''' 48 | The following is the assistant's response translated into a JSON object with 2 spaces of indentation and no properties with the value undefined: 49 | `, 50 | }, 51 | ] as MultimodalPromptContent[]; 52 | 53 | return contentSections; 54 | } 55 | 56 | function getScreenshotPromptSection(screenshot: string | undefined) { 57 | let screenshotSection = []; 58 | if (screenshot) { 59 | screenshotSection.push({ 60 | type: "text", 61 | text: "Here is a screenshot of the currently visible webpage", 62 | }); 63 | 64 | screenshotSection.push({ 65 | type: "image_url", 66 | image_url: { 67 | url: screenshot, 68 | detail: "high" 69 | }, 70 | }); 71 | 72 | screenshotSection.push({ 73 | type: "text", 74 | text: `Use the top left corner as coordinate 0,0 and draw a virtual grid of 1x1 pixels, 75 | where x values increase for each pixel as you go from left to right, and y values increase 76 | as you go from top to bottom. 77 | `, 78 | }); 79 | } 80 | return screenshotSection; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /typescript/examples/crossword/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "lib": ["es2021"], 5 | "module": "node16", 6 | "types": ["node"], 7 | "outDir": "../dist", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "exactOptionalPropertyTypes": true, 14 | "inlineSourceMap": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /typescript/examples/healthData/README.md: -------------------------------------------------------------------------------- 1 | # Health Data Agent 2 | 3 | This example requires GPT-4. 4 | 5 | Demonstrates a ***strongly typed*** chat: a natural language interface for entering health information. You work with a *health data agent* to interactively enter your medications or conditions. 6 | 7 | The Health Data Agent shows how strongly typed **agents with history** could interact with a user to collect information needed for one or more data types ("form filling"). 8 | 9 | ## Target models 10 | 11 | For best and consistent results, use **gpt-4**. 12 | 13 | ## Try the Health Data Agent 14 | 15 | To run the Sentiment example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 16 | 17 | ## Usage 18 | 19 | Example prompts can be found in [`src/input.txt`](./src/input.txt). 20 | 21 | For example, given the following input statement: 22 | 23 | **Input**: 24 | 25 | ```console 26 | 🤧> I am taking klaritin for my allergies 27 | 28 | ``` 29 | 30 | **Output**: 31 | -------------------------------------------------------------------------------- /typescript/examples/healthData/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "health-data", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "main": "dist/main.js", 7 | "scripts": { 8 | "build": "tsc -p src", 9 | "postbuild": "copyfiles -u 1 src/**/*Schema.ts src/**/*.txt dist" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "dotenv": "^16.3.1", 15 | "find-config": "^1.0.0", 16 | "typechat": "^0.1.0", 17 | "typescript": "^5.1.3" 18 | }, 19 | "devDependencies": { 20 | "@types/find-config": "1.0.4", 21 | "@types/node": "^20.3.1", 22 | "copyfiles": "^2.4.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /typescript/examples/healthData/src/healthDataSchema.ts: -------------------------------------------------------------------------------- 1 | // The following is a schema definition for enetring health data. 2 | 3 | export interface HealthDataResponse { 4 | // ONLY present when ALL required information is known. 5 | // Otherwise, use 'message' to keep asking questions. 6 | data?: HealthData; 7 | // Use this to ask questions and give pertinent responses 8 | message?: string; 9 | // Use this parts of the user request not translated, off topic, etc. 10 | notTranslated?: string; 11 | } 12 | 13 | export interface HealthData { 14 | medication?: Medication[]; 15 | condition?: Condition[]; 16 | other?: OtherHealthData[]; 17 | } 18 | 19 | // Meds, pills etc. 20 | export interface Medication { 21 | // Fix any spelling mistakes, especially phonetic spelling 22 | name: string; 23 | // E.g. 2 tablets, 1 cup. Required 24 | dose: ApproxQuantity; 25 | // E.g. twice a day. Required 26 | frequency: ApproxQuantity; 27 | // E.g. 50 mg. Required 28 | strength: ApproxQuantity; 29 | } 30 | 31 | // Disease, Ailment, Injury, Sickness 32 | export interface Condition { 33 | // Fix any spelling mistakes, especially phonetic spelling 34 | name: string; 35 | // When the condition started. 36 | startDate: ApproxDatetime; 37 | // Always ask for current status of the condition 38 | status: "active" | "recurrence" | "relapse" | "inactive" | "remission" | "resolved" | "unknown"; 39 | // If the condition was no longer active 40 | endDate?: ApproxDatetime; 41 | } 42 | 43 | // Use for health data that match nothing else. E.g. immunization, blood prssure etc 44 | export interface OtherHealthData { 45 | text: string; 46 | when?: ApproxDatetime; 47 | } 48 | 49 | export interface ApproxQuantity { 50 | // Default: "unknown" 51 | displayText: string; 52 | // Only specify if precise quantities are available 53 | quantity?: Quantity; 54 | } 55 | 56 | export interface ApproxDatetime { 57 | // Default: "unknown" 58 | displayText: string; 59 | // If precise timestamp can be set 60 | timestamp?: string; 61 | } 62 | 63 | export interface Quantity { 64 | // Exact number 65 | value: number; 66 | // Units like mg, kg, cm, pounds, liter, ml, tablet, pill, cup, per-day, per-week, etc. 67 | units: string; 68 | } -------------------------------------------------------------------------------- /typescript/examples/healthData/src/input.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Conversations with a Health Data Agent 3 | # For each conversation: 4 | # You start with the first line 5 | # Then type the next line in response 6 | # 7 | 8 | # ================ 9 | # USE GPT4 10 | # ================ 11 | # Conversation: 12 | i want to record my shingles 13 | August 2016 14 | It lasted 3 months 15 | I also broke my foot 16 | I broke it in high school 17 | 2001 18 | The foot took a year to be ok 19 | 20 | # Conversation: 21 | klaritin 22 | 2 tablets 3 times a day 23 | 300 mg 24 | actually that is 1 tablet 25 | @clear 26 | 27 | # Conversation: 28 | klaritin 29 | 1 pill, morning and before bedtime 30 | Can't remember 31 | Actually, that is 3 tablets 32 | 500 mg 33 | @clear 34 | 35 | #Conversation 36 | I am taking binadryl now 37 | As needed. Groceery store strength 38 | That is all I have 39 | I also got allergies. Pollen 40 | @clear 41 | 42 | # Conversation: 43 | Robotussin 44 | 1 cup 45 | Daily, as needed 46 | Robotussin with Codeine 47 | Put down strength as I don't know 48 | @clear 49 | 50 | # Conversation: 51 | Hey 52 | Melatonin 53 | 1 3mg tablet every night 54 | @clear 55 | 56 | # Conversation: 57 | I got the flu 58 | Started 2 weeks ago 59 | Its gone now. Only lasted about a week 60 | I took some sudafed though 61 | I took 2 sudafed twice a day. Regular strength 62 | @clear 63 | 64 | -------------------------------------------------------------------------------- /typescript/examples/healthData/src/main.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import dotenv from "dotenv"; 3 | import findConfig from "find-config"; 4 | import fs from "fs"; 5 | import path from "path"; 6 | import { createLanguageModel } from "typechat"; 7 | import { processRequests } from "typechat/interactive"; 8 | import { HealthDataResponse } from "./healthDataSchema"; 9 | import { createHealthDataTranslator } from "./translator"; 10 | 11 | const dotEnvPath = findConfig(".env"); 12 | assert(dotEnvPath, ".env file not found!"); 13 | dotenv.config({ path: dotEnvPath }); 14 | 15 | const healthInstructions = ` 16 | Help me enter my health data step by step. 17 | Ask specific questions to gather required and optional fields 18 | I have not already providedStop asking if I don't know the answer 19 | Automatically fix my spelling mistakes 20 | My health data may be complex: always record and return ALL of it. 21 | Always return a response: 22 | - If you don't understand what I say, ask a question. 23 | - At least respond with an OK message. 24 | `; 25 | 26 | const model = createLanguageModel(process.env); 27 | const schema = fs.readFileSync(path.join(__dirname, "healthDataSchema.ts"), "utf8"); 28 | const translator = createHealthDataTranslator(model, schema, "HealthDataResponse", 29 | healthInstructions); 30 | 31 | // Process requests interactively or from the input file specified on the command line 32 | processRequests("🤧> ", process.argv[2], async (request) => { 33 | const response = await translator.translate(request); 34 | if (!response.success) { 35 | console.log("Translation Failed ❌"); 36 | console.log(`Context: ${response.message}`); 37 | } 38 | else { 39 | const healthData = response.data; 40 | console.log("Translation Succeeded! ✅\n"); 41 | console.log("JSON View"); 42 | console.log(JSON.stringify(healthData, undefined, 2)); 43 | 44 | const message = healthData.message; 45 | const notTranslated = healthData.notTranslated; 46 | 47 | if (message) { 48 | console.log(`\n📝: ${message}`); 49 | } 50 | 51 | if (notTranslated) { 52 | console.log(`\n🤔: I did not understand\n ${notTranslated}`) 53 | } 54 | } 55 | }); 56 | -------------------------------------------------------------------------------- /typescript/examples/healthData/src/translator.ts: -------------------------------------------------------------------------------- 1 | import {Result, TypeChatLanguageModel, createJsonTranslator, TypeChatJsonTranslator} from "typechat"; 2 | import { createTypeScriptJsonValidator } from "typechat/ts"; 3 | 4 | type ChatMessage = { 5 | source: "system" | "user" | "assistant"; 6 | body: object; 7 | }; 8 | 9 | export interface TranslatorWithHistory { 10 | _chatHistory: ChatMessage[]; 11 | _maxPromptLength: number; 12 | _additionalAgentInstructions: string; 13 | _translator: TypeChatJsonTranslator; 14 | translate(request: string): Promise>; 15 | } 16 | 17 | export function createHealthDataTranslator(model: TypeChatLanguageModel, schema: string, typename: string, additionalAgentInstructions: string): TranslatorWithHistory { 18 | const _chatHistory: ChatMessage[] = []; 19 | const _maxPromptLength = 2048; 20 | const _additionalAgentInstructions = additionalAgentInstructions; 21 | 22 | const validator = createTypeScriptJsonValidator(schema, typename); 23 | const _translator = createJsonTranslator(model, validator); 24 | _translator.createRequestPrompt = createRequestPrompt; 25 | 26 | const customtranslator: TranslatorWithHistory = { 27 | _chatHistory, 28 | _maxPromptLength, 29 | _additionalAgentInstructions, 30 | _translator, 31 | translate, 32 | }; 33 | 34 | return customtranslator; 35 | 36 | async function translate(request: string): Promise> { 37 | const response = await _translator.translate(request); 38 | if (response.success) { 39 | _chatHistory.push({ source: "assistant", body: response.data }); 40 | } 41 | return response; 42 | 43 | } 44 | 45 | function createRequestPrompt(intent: string): string { 46 | // TODO: drop history entries if we exceed the max_prompt_length 47 | const historyStr = JSON.stringify(_chatHistory, undefined, 2); 48 | 49 | const now = new Date(); 50 | 51 | const prompt = ` 52 | user: You are a service that translates user requests into JSON objects of type "${typename}" according to the following TypeScript definitions: 53 | ''' 54 | ${schema} 55 | ''' 56 | 57 | user: 58 | Use precise date and times RELATIVE TO CURRENT DATE: ${now.toLocaleDateString()} CURRENT TIME: ${now.toTimeString().split(' ')[0]} 59 | Also turn ranges like next week and next month into precise dates 60 | 61 | user: 62 | ${_additionalAgentInstructions} 63 | 64 | system: 65 | IMPORTANT CONTEXT for the user request: 66 | ${historyStr} 67 | 68 | user: 69 | The following is a user request: 70 | ''' 71 | ${intent} 72 | ''' 73 | The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined: 74 | """ 75 | `; 76 | return prompt; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /typescript/examples/healthData/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "lib": ["es2021"], 5 | "module": "node16", 6 | "types": ["node"], 7 | "outDir": "../dist", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "exactOptionalPropertyTypes": true, 14 | "inlineSourceMap": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /typescript/examples/math/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/dist/main.js", 15 | "console": "externalTerminal" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /typescript/examples/math/README.md: -------------------------------------------------------------------------------- 1 | # Math 2 | 3 | The Math example shows how to use TypeChat for program generation based on an API schema with the `evaluateJsonProgram` function. This example translates calculations into simple programs given an [`API`](./src/mathSchema.ts) type that can perform the four basic mathematical operations. 4 | 5 | # Try Math 6 | 7 | To run the Math example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 8 | 9 | # Usage 10 | 11 | Example prompts can be found in [`src/input.txt`](./src/input.txt). 12 | 13 | For example, we could use natural language to describe mathematical operations, and TypeChat will generate a program that can execute the math API defined in the schema. 14 | 15 | **Input**: 16 | 17 | ``` 18 | 🟰> multiply two by three, then multiply four by five, then sum the results 19 | ``` 20 | 21 | **Output**: 22 | 23 | ``` 24 | import { API } from "./schema"; 25 | function program(api: API) { 26 | const step1 = api.mul(2, 3); 27 | const step2 = api.mul(4, 5); 28 | return api.add(step1, step2); 29 | } 30 | Running program: 31 | mul(2, 3) 32 | mul(4, 5) 33 | add(6, 20) 34 | Result: 26 35 | ``` 36 | -------------------------------------------------------------------------------- /typescript/examples/math/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "math", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "main": "dist/main.js", 7 | "scripts": { 8 | "build": "tsc -p src", 9 | "postbuild": "copyfiles -u 1 src/**/*Schema.ts src/**/*.txt dist" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "dotenv": "^16.3.1", 15 | "find-config": "^1.0.0", 16 | "typechat": "^0.1.0", 17 | "typescript": "^5.3.3" 18 | }, 19 | "devDependencies": { 20 | "@types/find-config": "1.0.4", 21 | "@types/node": "^20.10.4", 22 | "copyfiles": "^2.4.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /typescript/examples/math/src/input.txt: -------------------------------------------------------------------------------- 1 | 1 + 2 2 | 1 + 2 * 3 3 | 2 * 3 + 4 * 5 4 | 2 3 * 4 5 * + 5 | multiply two by three, then multiply four by five, then sum the results 6 | -------------------------------------------------------------------------------- /typescript/examples/math/src/main.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import dotenv from "dotenv"; 3 | import findConfig from "find-config"; 4 | import fs from "fs"; 5 | import path from "path"; 6 | import { createLanguageModel, getData } from "typechat"; 7 | import { processRequests } from "typechat/interactive"; 8 | import { createModuleTextFromProgram, createProgramTranslator, evaluateJsonProgram } from "typechat/ts"; 9 | 10 | const dotEnvPath = findConfig(".env"); 11 | assert(dotEnvPath, ".env file not found!"); 12 | dotenv.config({ path: dotEnvPath }); 13 | 14 | const model = createLanguageModel(process.env); 15 | const schema = fs.readFileSync(path.join(__dirname, "mathSchema.ts"), "utf8"); 16 | const translator = createProgramTranslator(model, schema); 17 | 18 | // Process requests interactively or from the input file specified on the command line 19 | processRequests("🧮 > ", process.argv[2], async (request) => { 20 | const response = await translator.translate(request); 21 | if (!response.success) { 22 | console.log(response.message); 23 | return; 24 | } 25 | const program = response.data; 26 | console.log(getData(createModuleTextFromProgram(program))); 27 | console.log("Running program:"); 28 | const result = await evaluateJsonProgram(program, handleCall); 29 | console.log(`Result: ${typeof result === "number" ? result : "Error"}`); 30 | }); 31 | 32 | async function handleCall(func: string, args: any[]): Promise { 33 | console.log(`${func}(${args.map(arg => typeof arg === "number" ? arg : JSON.stringify(arg, undefined, 2)).join(", ")})`); 34 | switch (func) { 35 | case "add": 36 | return args[0] + args[1]; 37 | case "sub": 38 | return args[0] - args[1]; 39 | case "mul": 40 | return args[0] * args[1]; 41 | case "div": 42 | return args[0] / args[1]; 43 | case "neg": 44 | return -args[0]; 45 | case "id": 46 | return args[0]; 47 | } 48 | return NaN; 49 | } 50 | -------------------------------------------------------------------------------- /typescript/examples/math/src/mathSchema.ts: -------------------------------------------------------------------------------- 1 | // This is a schema for writing programs that evaluate expressions. 2 | 3 | export type API = { 4 | // Add two numbers 5 | add(x: number, y: number): number; 6 | // Subtract two numbers 7 | sub(x: number, y: number): number; 8 | // Multiply two numbers 9 | mul(x: number, y: number): number; 10 | // Divide two numbers 11 | div(x: number, y: number): number; 12 | // Negate a number 13 | neg(x: number): number; 14 | // Identity function 15 | id(x: number): number; 16 | // Unknown request 17 | unknown(text: string): number; 18 | } 19 | -------------------------------------------------------------------------------- /typescript/examples/math/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "lib": ["es2021"], 5 | "module": "node16", 6 | "types": ["node"], 7 | "outDir": "../dist", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "exactOptionalPropertyTypes": true, 14 | "inlineSourceMap": true, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /typescript/examples/multiSchema/README.md: -------------------------------------------------------------------------------- 1 | # MultiSchema 2 | 3 | This application demonstrates a simple way to write a **super-app** that automatically routes user requests to child apps. 4 | 5 | In this example, the child apps are existing TypeChat chat examples: 6 | 7 | * CoffeeShop 8 | * Restaurant 9 | * Calendar 10 | * Sentiment 11 | * Math 12 | * Plugins 13 | * HealthData 14 | 15 | ## Target Models 16 | 17 | Works with GPT-3.5 Turbo and GPT-4. 18 | 19 | Sub-apps like HealthData and Plugins work best with GPT-4. 20 | 21 | # Usage 22 | 23 | Example prompts can be found in [`src/input.txt`](src/input.txt). 24 | -------------------------------------------------------------------------------- /typescript/examples/multiSchema/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multi-schema", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "main": "dist/main.js", 7 | "scripts": { 8 | "build": "tsc -p src", 9 | "postbuild": "copyfiles -u 1 -f ../../examples/**/src/**/*Schema.ts src/**/*.txt dist" 10 | 11 | }, 12 | "author": "", 13 | "license": "MIT", 14 | "dependencies": { 15 | "dotenv": "^16.3.1", 16 | "typechat": "^0.1.0", 17 | "find-config": "^1.0.0", 18 | "music": "^0.0.1", 19 | "typescript": "^5.3.3" 20 | }, 21 | "devDependencies": { 22 | "@types/find-config": "1.0.4", 23 | "@types/node": "^20.3.1", 24 | "copyfiles": "^2.4.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /typescript/examples/multiSchema/src/classificationSchema.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface TaskClassification { 3 | name: string; 4 | description: string; 5 | } 6 | 7 | /** 8 | * Represents the response of a task classification. 9 | */ 10 | export interface TaskClassificationResponse { 11 | // Describe the kind of task to perform. 12 | taskType: string; 13 | } 14 | -------------------------------------------------------------------------------- /typescript/examples/multiSchema/src/input.txt: -------------------------------------------------------------------------------- 1 | I'd like two large, one with pepperoni and the other with extra sauce. The pepperoni gets basil and the extra sauce gets Canadian bacon. And add a whole salad. 2 | I also want an espresso with extra foam and a muffin with jam 3 | And book me a lunch with Claude Debussy next week at 12.30 at Le Petit Chien! 4 | I bought 4 shoes for 12.50 each. How much did I spend? 5 | Its cold! 6 | Its cold and I want hot cafe to warm me up 7 | The coffee is cold 8 | The coffee is awful 9 | (2*4)+(9*7) -------------------------------------------------------------------------------- /typescript/examples/multiSchema/src/main.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import dotenv from "dotenv"; 3 | import findConfig from "find-config"; 4 | import fs from "fs"; 5 | import path from "path"; 6 | import { createLanguageModel } from "typechat"; 7 | import { processRequests } from "typechat/interactive"; 8 | import { createJsonMathAgent, createJsonPrintAgent } from "./agent"; 9 | import { createAgentRouter } from "./router"; 10 | 11 | const dotEnvPath = findConfig(".env"); 12 | assert(dotEnvPath, ".env file not found!"); 13 | dotenv.config({ path: dotEnvPath }); 14 | 15 | const model = createLanguageModel(process.env); 16 | const taskClassificationSchema = fs.readFileSync(path.join(__dirname, "classificationSchema.ts"), "utf8"); 17 | const router = createAgentRouter(model, taskClassificationSchema, "TaskClassificationResponse") 18 | 19 | const sentimentSchema = fs.readFileSync(path.join(__dirname, "sentimentSchema.ts"), "utf8"); 20 | const sentimentAgent = createJsonPrintAgent 21 | ("Sentiment", 22 | "Statements with sentiments, emotions, feelings, impressions about places, things, the surroundings", 23 | model, sentimentSchema, "SentimentResponse" 24 | ); 25 | router.registerAgent("Sentiment", sentimentAgent); 26 | 27 | const coffeeShopSchema = fs.readFileSync(path.join(__dirname, "coffeeShopSchema.ts"), "utf8"); 28 | const coffeeShopAgent = createJsonPrintAgent( 29 | "CoffeeShop", 30 | "Order Coffee Drinks (Italian names included) and Baked Goods", 31 | model, coffeeShopSchema, "Cart" 32 | ); 33 | router.registerAgent("CoffeeShop", coffeeShopAgent); 34 | 35 | const calendarSchema = fs.readFileSync(path.join(__dirname, "calendarActionsSchema.ts"), "utf8"); 36 | const calendarAgent = createJsonPrintAgent( 37 | "Calendar", 38 | "Actions related to calendars, appointments, meetings, schedules", 39 | model, calendarSchema, "CalendarActions" 40 | ); 41 | router.registerAgent("Calendar", calendarAgent); 42 | 43 | const orderSchema = fs.readFileSync(path.join(__dirname, "foodOrderViewSchema.ts"), "utf8"); 44 | const restaurantOrderAgent = createJsonPrintAgent( 45 | "Restaurant", 46 | "Order pizza, beer and salads", 47 | model, orderSchema, "Order" 48 | ); 49 | router.registerAgent("Restaurant", restaurantOrderAgent); 50 | 51 | const mathSchema = fs.readFileSync(path.join(__dirname, "mathSchema.ts"), "utf8"); 52 | const mathAgent = createJsonMathAgent( 53 | "Math", 54 | "Calculations using the four basic math operations", 55 | model, mathSchema 56 | ); 57 | router.registerAgent("Math", mathAgent); 58 | 59 | // Process requests interactively or from the input file specified on the command line 60 | processRequests("🔀> ", process.argv[2], async (request) => { 61 | await router.routeRequest(request); 62 | }); -------------------------------------------------------------------------------- /typescript/examples/multiSchema/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "lib": ["es2021"], 5 | "module": "node16", 6 | "types": ["node"], 7 | "outDir": "../dist", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "exactOptionalPropertyTypes": true, 14 | "inlineSourceMap": true 15 | }, 16 | "references": [ 17 | { "path": "../../music/src" } 18 | ] 19 | } -------------------------------------------------------------------------------- /typescript/examples/music/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/dist/main.js", 15 | "console": "externalTerminal" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /typescript/examples/music/README.md: -------------------------------------------------------------------------------- 1 | # Music 2 | 3 | The Music example shows how to capture user intent as actions in JSON which corresponds to a simple dataflow program over the API provided in the intent schema. This example shows this pattern using natural language to control the Spotify API to play music, create playlists, and perform other actions from the API. 4 | 5 | # Try Music 6 | 7 | A Spotify Premium account is required to run this example. 8 | 9 | To run the Music example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 10 | 11 | This example also requires additional setup to use the Spotify API: 12 | 13 | 1. Go to https://developer.spotify.com/dashboard. 14 | 2. Log into Spotify with your user account if you are not already logged in. 15 | 3. Click the button in the upper right labeled "Create App". 16 | 4. Fill in the form, making sure the Redirect URI is http://localhost:PORT/callback, where PORT is a four-digit port number you choose for the authorization redirect. 17 | 5. Click the settings button and copy down the Client ID and Client Secret (the client secret requires you to click 'View client secret'). 18 | 6. In your `.env` file, set `SPOTIFY_APP_CLI` to your Client ID and `SPOTIFY_APP_CLISEC` to your Client Secret. Also set `SPOTIFY_APP_PORT` to the PORT on your local machine that you chose in step 4. 19 | 20 | # Usage 21 | Example prompts can be found in [`src/input.txt`](./src/input.txt). 22 | 23 | For example, use natural language to start playing a song with the Spotify player: 24 | 25 | **Input**: 26 | 27 | ``` 28 | 🎵> play shake it off by taylor swift 29 | ``` 30 | 31 | **Output**: 32 | 33 | ``` 34 | Plan Validated: 35 | { 36 | "@steps": [ 37 | { 38 | "@func": "searchTracks", 39 | "@args": [ 40 | "shake it off taylor swift" 41 | ] 42 | }, 43 | { 44 | "@func": "play", 45 | "@args": [ 46 | { 47 | "@ref": 0 48 | } 49 | ] 50 | } 51 | ] 52 | } 53 | import { API } from "./schema"; 54 | function program(api: API) { 55 | const step1 = api.searchTracks("shake it off taylor swift"); 56 | return api.play(step1); 57 | } 58 | Playing... 59 | Shake It Off 60 | ``` -------------------------------------------------------------------------------- /typescript/examples/music/migrations.md: -------------------------------------------------------------------------------- 1 | # Local Music DB Migrations 2 | 3 | Tracks table 4 | ```[SQL] 5 | CREATE TABLE tracks ( 6 | id INTEGER PRIMARY KEY, 7 | title TEXT NOT NULL, 8 | artist_id INTEGER NOT NULL, 9 | album_id INTEGER, 10 | duration INTEGER, 11 | release_date TEXT, 12 | genre TEXT, 13 | ); 14 | ``` 15 | Albums table 16 | ```[SQL] 17 | CREATE TABLE albums ( 18 | id INTEGER PRIMARY KEY, 19 | title TEXT NOT NULL, 20 | artist_id INTEGER NOT NULL, 21 | release_date TEXT, 22 | genre TEXT, 23 | ); 24 | ``` 25 | Playlists table 26 | ```[SQL] 27 | CREATE TABLE playlists ( 28 | id INTEGER PRIMARY KEY, 29 | title TEXT NOT NULL, 30 | user_id INTEGER NOT NULL, 31 | creation_date TEXT, 32 | description TEXT, 33 | ); 34 | ``` 35 | Artists table 36 | ```[SQL] 37 | CREATE TABLE artists ( 38 | id INTEGER PRIMARY KEY, 39 | name TEXT NOT NULL, 40 | country TEXT, 41 | genre TEXT 42 | ); 43 | ``` 44 | -------------------------------------------------------------------------------- /typescript/examples/music/musicLibrary.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/TypeChat/fb4bdeb8eefc5ed33718c5ed83417f38f83f1480/typescript/examples/music/musicLibrary.db -------------------------------------------------------------------------------- /typescript/examples/music/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "music", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "main": "dist/main.js", 7 | "scripts": { 8 | "build": "tsc -p src", 9 | "postbuild": "copyfiles -u 1 src/**/*Schema.ts src/**/*.txt src/**/*.html dist" 10 | }, 11 | "exports": { 12 | "./*": ["./dist/*.js"] 13 | }, 14 | "author": "", 15 | "license": "MIT", 16 | "dependencies": { 17 | "axios": "^1.6.2", 18 | "chalk": "^2.3.1", 19 | "dotenv": "^16.3.1", 20 | "express": "^4.18.2", 21 | "find-config": "^1.0.0", 22 | "open": "^7.0.4", 23 | "sqlite3": "^5.1.6", 24 | "typechat": "^0.1.0", 25 | "typescript": "^5.3.3" 26 | }, 27 | "devDependencies": { 28 | "@types/express": "^4.17.17", 29 | "@types/find-config": "1.0.4", 30 | "@types/node": "^20.10.4", 31 | "@types/spotify-api": "^0.0.22", 32 | "copyfiles": "^2.4.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /typescript/examples/music/src/callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 17 | 18 | -------------------------------------------------------------------------------- /typescript/examples/music/src/chatifyActionsSchema.ts: -------------------------------------------------------------------------------- 1 | // This is a schema for writing programs that control a Spotify music player 2 | 3 | type Track = { name: string }; 4 | type TrackList = Track[]; 5 | type Playlist = TrackList; 6 | 7 | export type API = { 8 | // play track list 9 | play( 10 | // track list to play 11 | trackList: TrackList, 12 | // start playing at this track index 13 | startIndex?: number, 14 | // play this many tracks 15 | count?: number 16 | ): void; 17 | // print a list of tracks 18 | printTracks(trackList: TrackList): void; 19 | // see what is up next 20 | getQueue(): void; 21 | // show now playing 22 | status(): void; 23 | // control playback 24 | // pause playback 25 | pause(): void; 26 | // next track 27 | next(): void; 28 | // previous track 29 | previous(): void; 30 | // turn shuffle on 31 | shuffleOn(): void; 32 | // turn shuffle off 33 | shuffleOff(): void; 34 | // resume playing 35 | resume(): void; 36 | // list available playback devices 37 | listDevices(): void; 38 | // select playback device by keyword 39 | selectDevice(keyword: string): void; 40 | // set volume 41 | setVolume(newVolumeLevel: number): void; 42 | // change volume 43 | changeVolume(volumeChangeAmount: number): void; 44 | // query is a Spotify search expression such as 'Rock Lobster' or 'te kanawa queen of night' 45 | searchTracks(query: string): TrackList; 46 | // return the last track list shown to the user 47 | // for example, if the user types "play the third one" the player plays the third track 48 | // from the last track list shown 49 | getLastTrackList(): TrackList; 50 | // list all playlists 51 | listPlaylists(): void; 52 | // get playlist by name 53 | getPlaylist(name: string): Playlist; 54 | // get album by name; if name is "", use the currently playing track 55 | getAlbum(name: string): TrackList; 56 | // Return a list of the user's favorite tracks 57 | getFavorites(count?: number): TrackList; 58 | // apply a filter to match tracks 59 | filterTracks( 60 | // track list to filter 61 | trackList: TrackList, 62 | // filter type is one of "genre", "artist", "name"; name does a fuzzy match on the track name 63 | // for example, filterType: "name", filter: "color" matches "Red Red Wine" 64 | filterType: "genre" | "artist" | "name", 65 | filter: string, 66 | negate?: boolean 67 | ): TrackList; 68 | // create a Spotify playlist from a list of tracks 69 | createPlaylist(trackList: TrackList, name: string): void; 70 | // Delete playlist given by playlist 71 | deletePlaylist(playlist: Playlist): void; 72 | // call this function for requests that weren't understood 73 | unknownAction(text: string): void; 74 | // call this function if the user asks a non-music question; non-music non-questions use UnknownAction 75 | nonMusicQuestion(text: string): void; 76 | }; 77 | -------------------------------------------------------------------------------- /typescript/examples/music/src/dbInterface.ts: -------------------------------------------------------------------------------- 1 | import sqlite3 from "sqlite3"; 2 | 3 | const dbPath = "../musicLibrary.db" 4 | 5 | type Row = { [key:string] : unknown } 6 | 7 | function executeQuery(query: string, params: any[] = []): Row[] | void { 8 | const db = new sqlite3.Database(dbPath, (error) => { 9 | if (error) { 10 | console.log(`Error executing query: ${query} against ${dbPath}`); 11 | return; 12 | } 13 | 14 | db.all(query, params, (error: Error, rows: Row[]) => { 15 | db.close(); 16 | 17 | if (error) { 18 | console.log(`Error executing query: ${query} against ${dbPath}`); 19 | return; 20 | } 21 | 22 | return rows; 23 | }); 24 | }); 25 | } 26 | 27 | export function insertTracks(tracks: SpotifyApi.TrackObjectFull[]) { 28 | let insertQuery = 'INSERT INTO tracks (id, title, artist_id, album_id, duration, release_data, genre)\nVALUES\n'; 29 | for (const track of tracks) { 30 | // TODO: genre 31 | insertQuery += ` (${track.id},${track.name},${track.artists[0].id},${track.album.id},${track.duration_ms},${track.album.release_date})`; 32 | } 33 | 34 | 35 | } 36 | export function getArtists() { 37 | const query = "SELECT * FROM artists"; 38 | const artists = executeQuery(query); 39 | return artists; 40 | } 41 | -------------------------------------------------------------------------------- /typescript/examples/music/src/input.txt: -------------------------------------------------------------------------------- 1 | play Taylor Swift Shake It Off 2 | get my top 20 favorites and make a playlist named animalTracks of the tracks that have animals in their names 3 | get my favorite 100 tracks from the last two months and show only the ones by Bach 4 | make it loud 5 | get my favorite 80 tracks from the last 8 months and create one playlist named class8 containing the classical tracks and another playlist containing the blues tracks 6 | toggle shuffle on and skip to the next track 7 | go back to the last song 8 | play my playlist class8 9 | play the fourth one 10 | show me my queue 11 | -------------------------------------------------------------------------------- /typescript/examples/music/src/service.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export type ClientData = { 4 | clientId: string; 5 | clientSecret: string; 6 | }; 7 | 8 | export type User = { 9 | username?: string; 10 | token: string; 11 | id?: string; 12 | }; 13 | 14 | export class SpotifyService { 15 | private accessToken?: string; 16 | private clientId: string; 17 | private clientSecret: string; 18 | private loggedIn: boolean; 19 | private loggedInUser: User | null; 20 | 21 | constructor(clientData: ClientData) { 22 | this.clientId = clientData.clientId; 23 | this.clientSecret = clientData.clientSecret; 24 | this.loggedIn = false; 25 | this.loggedInUser = null; 26 | } 27 | 28 | storeToken(token: string): string { 29 | this.accessToken = token; 30 | return this.accessToken; 31 | } 32 | 33 | retrieveToken(): string { 34 | if (!this.accessToken) { 35 | throw new Error('SpotifyService: no accessToken'); 36 | } 37 | return this.accessToken; 38 | } 39 | 40 | isLoggedIn(): boolean { 41 | return this.loggedIn; 42 | } 43 | 44 | retrieveUser(): User { 45 | if (this.loggedInUser === null) { 46 | throw new Error('SpotifyService: no loggedInUser'); 47 | } 48 | return this.loggedInUser; 49 | } 50 | 51 | storeUser(user: User) { 52 | this.loggedInUser = user; 53 | } 54 | 55 | async init(): Promise { 56 | const authConfig = { 57 | headers: { 58 | Authorization: `Basic ${Buffer.from( 59 | `${this.clientId}:${this.clientSecret}` 60 | ).toString('base64')}`, 61 | }, 62 | }; 63 | 64 | try { 65 | const authData = await axios.post( 66 | 'https://accounts.spotify.com/api/token', 67 | 'grant_type=client_credentials', 68 | authConfig 69 | ); 70 | this.storeToken(authData.data.access_token); 71 | 72 | return authData.data; 73 | } catch (e) { 74 | // TODO: REVIEW: should we really be returning the response 75 | // data in an error condition? 76 | if (e instanceof axios.AxiosError) { 77 | // TODO: REVIEW: the type returned here may not be Promise 78 | return e.response?.data; 79 | } else { 80 | throw e; 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /typescript/examples/music/src/trackCollections.ts: -------------------------------------------------------------------------------- 1 | import { SpotifyService } from "./service"; 2 | 3 | // for now, no paging of track lists; later add offset and count 4 | export interface ITrackCollection { 5 | getTrackCount(): number; 6 | getTracks(service: SpotifyService): Promise; 7 | getContext(): string | undefined; 8 | getPlaylist(): SpotifyApi.PlaylistObjectSimplified | undefined; 9 | } 10 | 11 | export class TrackCollection implements ITrackCollection { 12 | contextUri: string | undefined = undefined; 13 | 14 | constructor(public tracks: SpotifyApi.TrackObjectFull[], public trackCount:number) { 15 | 16 | } 17 | getContext() { 18 | return this.contextUri; 19 | } 20 | 21 | async getTracks( 22 | service: SpotifyService 23 | ) { 24 | return this.tracks; 25 | } 26 | 27 | getTrackCount(): number { 28 | return this.trackCount; 29 | } 30 | 31 | getPlaylist(): SpotifyApi.PlaylistObjectSimplified | undefined { 32 | return undefined; 33 | } 34 | } 35 | 36 | export class PlaylistTrackCollection extends TrackCollection { 37 | constructor(public playlist: SpotifyApi.PlaylistObjectSimplified, 38 | tracks: SpotifyApi.TrackObjectFull[]) { 39 | super(tracks,0); 40 | this.contextUri = playlist.uri; 41 | this.trackCount = tracks.length; 42 | } 43 | 44 | getPlaylist() { 45 | return this.playlist; 46 | } 47 | } 48 | 49 | export class AlbumTrackCollection extends TrackCollection { 50 | constructor(public album: SpotifyApi.AlbumObjectSimplified, 51 | tracks: SpotifyApi.TrackObjectSimplified[] 52 | ) { 53 | super([], 0); 54 | this.contextUri = album.uri; 55 | this.trackCount = tracks.length; 56 | this.tracks = tracks.map((albumItem) => { 57 | const fullTrack = albumItem as SpotifyApi.TrackObjectFull; 58 | fullTrack.album = album; 59 | return fullTrack; 60 | }); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /typescript/examples/music/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "lib": ["es2021"], 5 | "module": "node16", 6 | "types": ["node", "spotify-api"], 7 | "outDir": "../dist", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "inlineSourceMap": true, 13 | "declaration": true, 14 | "composite": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /typescript/examples/restaurant/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/dist/main.js", 15 | "console": "externalTerminal" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /typescript/examples/restaurant/README.md: -------------------------------------------------------------------------------- 1 | # Restaurant 2 | 3 | The Restaurant example shows how to capture user intent as a set of "nouns", but with more complex linguistic input. 4 | This example can act as a "stress test" for language models, illustrating the line between simpler and more advanced language models in handling compound sentences, distractions, and corrections. 5 | This example also shows how we can create a "user intent summary" to display to a user. 6 | It uses a natural language experience for placing an order with the [`Order`](./src/foodOrderViewSchema.ts) type. 7 | 8 | # Try Restaurant 9 | 10 | To run the Restaurant example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 11 | 12 | # Usage 13 | 14 | Example prompts can be found in [`src/input.txt`](./src/input.txt). 15 | 16 | For example, given the following order: 17 | 18 | **Input**: 19 | 20 | ``` 21 | 🍕> I want three pizzas, one with mushrooms and the other two with sausage. Make one sausage a small. And give me a whole Greek and a Pale Ale. And give me a Mack and Jacks. 22 | ``` 23 | 24 | **Output**: 25 | 26 | *This is GPT-4-0613 output; GPT-3.5-turbo and most other models miss this one.* 27 | 28 | ``` 29 | 1 large pizza with mushrooms 30 | 1 large pizza with sausage 31 | 1 small pizza with sausage 32 | 1 whole Greek salad 33 | 1 Pale Ale 34 | 1 Mack and Jacks 35 | ``` 36 | 37 | > **Note** 38 | > 39 | > Across different models, you may see that model responses may not correspond to the user intent. 40 | > In the above example, some models may not be able to capture the fact that the order is still only for 3 pizzas, 41 | > and that "make one sausage a small" is not a request for a new pizza. 42 | > 43 | > ```diff 44 | > 1 large pizza with mushrooms 45 | > - 1 large pizza with sausage 46 | > + 2 large pizza with sausage 47 | > 1 small pizza with sausage 48 | > 1 whole Greek salad 49 | > 1 Pale Ale 50 | > 1 Mack and Jacks 51 | > ``` 52 | > 53 | > The output here from GPT 3.5-turbo incorrectly shows 1 mushroom pizza and 3 sausage pizzas. 54 | 55 | Because all language models are probabilistic and therefore will sometimes output incorrect inferences, the TypeChat pattern includes asking the user for confirmation (or giving the user an easy way to undo actions). It is important to ask for confirmation without use of the language model so that incorrect inference is guaranteed not to be part of the intent summary generated. 56 | 57 | In this example, the function `printOrder` in the file `main.ts` summarizes the food order (as seen in the above output) without use of a language model. The `printOrder` function can work with a strongly typed `Order object` because the TypeChat validation process has checked that the emitted JSON corresponds to the `Order` type: 58 | 59 | ```typescript 60 | function printOrder(order: Order) { 61 | ``` 62 | 63 | Having a validated, typed data structure simplifies the task of generating a succinct summary suitable for user confirmation. -------------------------------------------------------------------------------- /typescript/examples/restaurant/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restaurant", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "main": "dist/main.js", 7 | "scripts": { 8 | "build": "tsc -p src", 9 | "postbuild": "copyfiles -u 1 src/**/*Schema.ts src/**/*.txt dist" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "dotenv": "^16.3.1", 15 | "find-config": "^1.0.0", 16 | "typechat": "^0.1.0", 17 | "typescript": "^5.3.3" 18 | }, 19 | "devDependencies": { 20 | "@types/find-config": "1.0.4", 21 | "@types/node": "^20.10.4", 22 | "copyfiles": "^2.4.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /typescript/examples/restaurant/src/foodOrderViewSchema.ts: -------------------------------------------------------------------------------- 1 | // an order from a restaurant that serves pizza, beer, and salad 2 | export type Order = { 3 | items: (OrderItem | UnknownText)[]; 4 | }; 5 | 6 | export type OrderItem = Pizza | Beer | Salad; 7 | 8 | // Use this type for order items that match nothing else 9 | export interface UnknownText { 10 | itemType: "unknown", 11 | text: string; // The text that wasn't understood 12 | } 13 | 14 | export type Pizza = { 15 | itemType: "pizza"; 16 | // default: large 17 | size?: "small" | "medium" | "large" | "extra large"; 18 | // toppings requested (examples: pepperoni, arugula) 19 | addedToppings?: string[]; 20 | // toppings requested to be removed (examples: fresh garlic, anchovies) 21 | removedToppings?: string[]; 22 | // default: 1 23 | quantity?: number; 24 | // used if the requester references a pizza by name 25 | name?: "Hawaiian" | "Yeti" | "Pig In a Forest" | "Cherry Bomb"; 26 | }; 27 | 28 | export type Beer = { 29 | itemType: "beer"; 30 | // examples: Mack and Jacks, Sierra Nevada Pale Ale, Miller Lite 31 | kind: string; 32 | // default: 1 33 | quantity?: number; 34 | }; 35 | 36 | export const saladSize = ["half", "whole"]; 37 | 38 | export const saladStyle = ["Garden", "Greek"]; 39 | 40 | export type Salad = { 41 | itemType: "salad"; 42 | // default: half 43 | portion?: string; 44 | // default: Garden 45 | style?: string; 46 | // ingredients requested (examples: parmesan, croutons) 47 | addedIngredients?: string[]; 48 | // ingredients requested to be removed (example: red onions) 49 | removedIngredients?: string[]; 50 | // default: 1 51 | quantity?: number; 52 | }; 53 | 54 | -------------------------------------------------------------------------------- /typescript/examples/restaurant/src/input.txt: -------------------------------------------------------------------------------- 1 | I'd like two large, one with pepperoni and the other with extra sauce. The pepperoni gets basil and the extra sauce gets Canadian bacon. And add a whole salad. Make the Canadian bacon a medium. Make the salad a Greek with no red onions. And give me two Mack and Jacks and a Sierra Nevada. Oh, and add another salad with no red onions. 2 | I'd like two large with olives and mushrooms. And the first one gets extra sauce. The second one gets basil. Both get arugula. And add a Pale Ale. Give me a two Greeks with no red onions, a half and a whole. And a large with sausage and mushrooms. Plus three Pale Ales and a Mack and Jacks. 3 | I'll take two large with pepperoni. Put olives on one of them. Make the olive a small. And give me whole Greek plus a Pale Ale and an M&J. 4 | I want three pizzas, one with mushrooms and the other two with sausage. Make one sausage a small. And give me a whole Greek and a Pale Ale. And give me a Mack and Jacks. 5 | I would like to order one with basil and one with extra sauce. Throw in a salad and an ale. 6 | I would love to have a pepperoni with extra sauce, basil and arugula. Lovely weather we're having. Throw in some pineapple. And give me a whole Greek and a Pale Ale. Boy, those Mariners are doggin it. And how about a Mack and Jacks. 7 | I'll have two pepperoni, the first with extra sauce and the second with basil. Add pineapple to the first and add olives to the second. 8 | I sure am hungry for a pizza with pepperoni and a salad with no croutons. And I'm thirsty for 3 Pale Ales 9 | give me three regular salads and two Greeks and make the regular ones with no red onions 10 | I'll take four large pepperoni pizzas. Put extra sauce on two of them. plus an M&J and a Pale Ale 11 | I'll take a yeti, a pale ale and a large with olives and take the extra cheese off the yeti and add a Greek 12 | I'll take a medium Pig with no arugula 13 | I'll take a small Pig with no arugula and a Greek with croutons and no red onions 14 | 15 | -------------------------------------------------------------------------------- /typescript/examples/restaurant/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "lib": ["es2021"], 5 | "module": "node16", 6 | "types": ["node"], 7 | "outDir": "../dist", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "exactOptionalPropertyTypes": true, 14 | "inlineSourceMap": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /typescript/examples/sentiment-zod/README.md: -------------------------------------------------------------------------------- 1 | # Sentiment 2 | 3 | The Sentiment example shows how to match user intent to a set of nouns, in this case categorizing user sentiment of the input as negative, neutral, or positive with the [`SentimentResponse`](./src/sentimentSchema.ts) type. 4 | 5 | # Try Sentiment 6 | To run the Sentiment example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 7 | 8 | # Usage 9 | Example prompts can be found in [`src/input.txt`](./src/input.txt). 10 | 11 | For example, given the following input statement: 12 | 13 | **Input**: 14 | ``` 15 | 😀> TypeChat is awesome! 16 | ``` 17 | 18 | **Output**: 19 | ``` 20 | The sentiment is positive 21 | ``` -------------------------------------------------------------------------------- /typescript/examples/sentiment-zod/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sentiment-zod", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "main": "dist/main.js", 7 | "scripts": { 8 | "build": "tsc -p src", 9 | "postbuild": "copyfiles -u 1 src/**/*.txt dist" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "dotenv": "^16.3.1", 15 | "find-config": "^1.0.0", 16 | "typechat": "^0.1.0" 17 | }, 18 | "devDependencies": { 19 | "@types/find-config": "1.0.4", 20 | "@types/node": "^20.10.4", 21 | "copyfiles": "^2.4.1", 22 | "typescript": "^5.3.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /typescript/examples/sentiment-zod/src/input.txt: -------------------------------------------------------------------------------- 1 | hello, world 2 | TypeChat is awesome! 3 | I'm having a good day 4 | it's very rainy outside -------------------------------------------------------------------------------- /typescript/examples/sentiment-zod/src/main.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import dotenv from "dotenv"; 3 | import findConfig from "find-config"; 4 | import { createJsonTranslator, createLanguageModel } from "typechat"; 5 | import { processRequests } from "typechat/interactive"; 6 | import { createZodJsonValidator } from "typechat/zod"; 7 | import { SentimentSchema } from "./sentimentSchema"; 8 | 9 | const dotEnvPath = findConfig(".env"); 10 | assert(dotEnvPath, ".env file not found!"); 11 | dotenv.config({ path: dotEnvPath }); 12 | 13 | const model = createLanguageModel(process.env); 14 | const validator = createZodJsonValidator(SentimentSchema, "SentimentResponse"); 15 | const translator = createJsonTranslator(model, validator); 16 | 17 | // Process requests interactively or from the input file specified on the command line 18 | processRequests("😀> ", process.argv[2], async (request) => { 19 | const response = await translator.translate(request); 20 | if (!response.success) { 21 | console.log(response.message); 22 | return; 23 | } 24 | console.log(`The sentiment is ${response.data.sentiment}`); 25 | }); 26 | -------------------------------------------------------------------------------- /typescript/examples/sentiment-zod/src/sentimentSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const SentimentResponse = z.object({ 4 | sentiment: z.enum(["negative", "neutral", "positive"]).describe("The sentiment of the text") 5 | }); 6 | 7 | export const SentimentSchema = { 8 | SentimentResponse 9 | }; 10 | -------------------------------------------------------------------------------- /typescript/examples/sentiment-zod/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "lib": ["es2021"], 5 | "module": "node16", 6 | "types": ["node"], 7 | "outDir": "../dist", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "exactOptionalPropertyTypes": true, 14 | "inlineSourceMap": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /typescript/examples/sentiment/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/dist/main.js", 15 | "console": "externalTerminal" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /typescript/examples/sentiment/README.md: -------------------------------------------------------------------------------- 1 | # Sentiment 2 | 3 | The Sentiment example shows how to match user intent to a set of nouns, in this case categorizing user sentiment of the input as negative, neutral, or positive with the [`SentimentResponse`](./src/sentimentSchema.ts) type. 4 | 5 | # Try Sentiment 6 | To run the Sentiment example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). 7 | 8 | # Usage 9 | Example prompts can be found in [`src/input.txt`](./src/input.txt). 10 | 11 | For example, given the following input statement: 12 | 13 | **Input**: 14 | ``` 15 | 😀> TypeChat is awesome! 16 | ``` 17 | 18 | **Output**: 19 | ``` 20 | The sentiment is positive 21 | ``` -------------------------------------------------------------------------------- /typescript/examples/sentiment/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sentiment", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "main": "dist/main.js", 7 | "scripts": { 8 | "build": "tsc -p src", 9 | "postbuild": "copyfiles -u 1 src/**/*Schema.ts src/**/*.txt dist" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "dotenv": "^16.3.1", 15 | "find-config": "^1.0.0", 16 | "typechat": "^0.1.0", 17 | "typescript": "^5.3.3" 18 | }, 19 | "devDependencies": { 20 | "@types/find-config": "1.0.4", 21 | "@types/node": "^20.10.4", 22 | "copyfiles": "^2.4.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /typescript/examples/sentiment/src/input.txt: -------------------------------------------------------------------------------- 1 | hello, world 2 | TypeChat is awesome! 3 | I'm having a good day 4 | it's very rainy outside -------------------------------------------------------------------------------- /typescript/examples/sentiment/src/main.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import dotenv from "dotenv"; 3 | import findConfig from "find-config"; 4 | import fs from "fs"; 5 | import path from "path"; 6 | import { createJsonTranslator, createLanguageModel } from "typechat"; 7 | import { processRequests } from "typechat/interactive"; 8 | import { createTypeScriptJsonValidator } from "typechat/ts"; 9 | import { SentimentResponse } from "./sentimentSchema"; 10 | 11 | const dotEnvPath = findConfig(".env"); 12 | assert(dotEnvPath, ".env file not found!"); 13 | dotenv.config({ path: dotEnvPath }); 14 | 15 | const model = createLanguageModel(process.env); 16 | const schema = fs.readFileSync(path.join(__dirname, "sentimentSchema.ts"), "utf8"); 17 | const validator = createTypeScriptJsonValidator(schema, "SentimentResponse"); 18 | const translator = createJsonTranslator(model, validator); 19 | 20 | // Process requests interactively or from the input file specified on the command line 21 | processRequests("😀> ", process.argv[2], async (request) => { 22 | const response = await translator.translate(request); 23 | if (!response.success) { 24 | console.log(response.message); 25 | return; 26 | } 27 | console.log(`The sentiment is ${response.data.sentiment}`); 28 | }); 29 | -------------------------------------------------------------------------------- /typescript/examples/sentiment/src/sentimentSchema.ts: -------------------------------------------------------------------------------- 1 | // The following is a schema definition for determining the sentiment of a some user input. 2 | 3 | export interface SentimentResponse { 4 | sentiment: "negative" | "neutral" | "positive"; // The sentiment of the text 5 | } 6 | -------------------------------------------------------------------------------- /typescript/examples/sentiment/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "lib": ["es2021"], 5 | "module": "node16", 6 | "types": ["node"], 7 | "outDir": "../dist", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "exactOptionalPropertyTypes": true, 14 | "inlineSourceMap": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typechat", 3 | "author": "Microsoft", 4 | "version": "0.1.1", 5 | "license": "MIT", 6 | "description": "TypeChat is an experimental library that makes it easy to build natural language interfaces using types.", 7 | "keywords": [ 8 | "schema", 9 | "LLM", 10 | "prompt", 11 | "TypeScript", 12 | "validation" 13 | ], 14 | "homepage": "https://github.com/microsoft/TypeChat#readme", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/microsoft/TypeChat.git" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/microsoft/TypeChat/issues" 21 | }, 22 | "scripts": { 23 | "build": "tsc -p src", 24 | "build-all": "npm run build --workspaces", 25 | "prepare": "npm run build-all" 26 | }, 27 | "exports": { 28 | ".": "./dist/index.js", 29 | "./ts": "./dist/ts/index.js", 30 | "./zod": "./dist/zod/index.js", 31 | "./interactive": "./dist/interactive/index.js" 32 | }, 33 | "engines": { 34 | "node": ">=18" 35 | }, 36 | "files": [ 37 | "dist", 38 | "LICENSE", 39 | "README.md", 40 | "SECURITY.md" 41 | ], 42 | "dependencies": {}, 43 | "peerDependencies": { 44 | "typescript": "^5.3.3", 45 | "zod": "^3.22.4" 46 | }, 47 | "peerDependenciesMeta": { 48 | "typescript": { 49 | "optional": true 50 | }, 51 | "zod": { 52 | "optional": true 53 | } 54 | }, 55 | "devDependencies": { 56 | "@types/node": "^20.10.4" 57 | }, 58 | "workspaces": [ 59 | "./", 60 | "./examples/*" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './result'; 2 | export * from './model'; 3 | export * from './typechat'; 4 | -------------------------------------------------------------------------------- /typescript/src/interactive/index.ts: -------------------------------------------------------------------------------- 1 | export * from './interactive'; 2 | -------------------------------------------------------------------------------- /typescript/src/interactive/interactive.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import readline from "readline/promises"; 3 | 4 | /** 5 | * A request processor for interactive input or input from a text file. If an input file name is specified, 6 | * the callback function is invoked for each line in file. Otherwise, the callback function is invoked for 7 | * each line of interactive input until the user types "quit" or "exit". 8 | * @param interactivePrompt Prompt to present to user. 9 | * @param inputFileName Input text file name, if any. 10 | * @param processRequest Async callback function that is invoked for each interactive input or each line in text file. 11 | */ 12 | export async function processRequests(interactivePrompt: string, inputFileName: string | undefined, processRequest: (request: string) => Promise) { 13 | if (inputFileName) { 14 | const lines = fs.readFileSync(inputFileName).toString().split(/\r?\n/); 15 | for (const line of lines) { 16 | if (line.length) { 17 | console.log(interactivePrompt + line); 18 | await processRequest(line); 19 | } 20 | } 21 | } 22 | else { 23 | const stdio = readline.createInterface({ input: process.stdin, output: process.stdout }); 24 | while (true) { 25 | const input = await stdio.question(interactivePrompt); 26 | if (input.toLowerCase() === "quit" || input.toLowerCase() === "exit") { 27 | break; 28 | } 29 | else if (input.length) { 30 | await processRequest(input); 31 | } 32 | } 33 | stdio.close(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /typescript/src/result.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An object representing a successful operation with a result of type `T`. 3 | */ 4 | export type Success = { success: true, data: T }; 5 | 6 | /** 7 | * An object representing an operation that failed for the reason given in `message`. 8 | */ 9 | export type Error = { success: false, message: string }; 10 | 11 | /** 12 | * An object representing a successful or failed operation of type `T`. 13 | */ 14 | export type Result = Success | Error; 15 | 16 | /** 17 | * Returns a `Success` object. 18 | * @param data The value for the `data` property of the result. 19 | * @returns A `Success` object. 20 | */ 21 | export function success(data: T): Success { 22 | return { success: true, data }; 23 | } 24 | 25 | /** 26 | * Returns an `Error` object. 27 | * @param message The value for the `message` property of the result. 28 | * @returns An `Error` object. 29 | */ 30 | export function error(message: string): Error { 31 | return { success: false, message }; 32 | } 33 | 34 | /** 35 | * Obtains the value associated with a successful `Result` or throws an exception if 36 | * the result is an error. 37 | * @param result The `Result` from which to obtain the `data` property. 38 | * @returns The value of the `data` property. 39 | */ 40 | export function getData(result: Result) { 41 | if (result.success) { 42 | return result.data; 43 | } 44 | throw new Error(result.message); 45 | } 46 | -------------------------------------------------------------------------------- /typescript/src/ts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './validate'; 2 | export * from './program'; 3 | -------------------------------------------------------------------------------- /typescript/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "lib": ["es2021"], 5 | "module": "node16", 6 | "types": ["node"], 7 | "esModuleInterop": true, 8 | "outDir": "../dist", 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "exactOptionalPropertyTypes": true, 12 | "declaration": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /typescript/src/zod/index.ts: -------------------------------------------------------------------------------- 1 | export * from './validate'; 2 | --------------------------------------------------------------------------------