diff --git a/examples/healthData/README.md b/examples/healthData/README.md new file mode 100644 index 00000000..28ee1ea3 --- /dev/null +++ b/examples/healthData/README.md @@ -0,0 +1,30 @@ +# Health Data Agent + +This example requires GPT-4. + +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. + +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"). + +## Target models + +For best and consistent results, use **gpt-4**. + +## Try the Health Data Agent + +To run the Sentiment example, follow the instructions in the [examples README](../README.md#step-1-configure-your-development-environment). + +## Usage + +Example prompts can be found in [`input.txt`](./src/input.txt). + +For example, given the following input statement: + +**Input**: + +```console +🤧> I am taking klaritin for my allergies + +``` + +**Output**: diff --git a/examples/healthData/package.json b/examples/healthData/package.json new file mode 100644 index 00000000..3008582c --- /dev/null +++ b/examples/healthData/package.json @@ -0,0 +1,22 @@ +{ + "name": "health-data", + "version": "0.0.1", + "private": true, + "description": "", + "main": "dist/main.js", + "scripts": { + "build": "tsc -p src", + "postbuild": "copyfiles -u 1 src/**/*Schema.ts src/**/*.txt dist" + }, + "author": "", + "license": "MIT", + "dependencies": { + "dotenv": "^16.3.1", + "typechat": "^0.0.10" + }, + "devDependencies": { + "@types/node": "^20.3.1", + "copyfiles": "^2.4.1", + "typescript": "^5.1.3" + } +} diff --git a/examples/healthData/src/healthDataSchema.ts b/examples/healthData/src/healthDataSchema.ts new file mode 100644 index 00000000..4971436a --- /dev/null +++ b/examples/healthData/src/healthDataSchema.ts @@ -0,0 +1,68 @@ +// The following is a schema definition for enetring health data. + +export interface HealthDataResponse { + // ONLY present when ALL required information is known. + // Otherwise, use 'message' to keep asking questions. + data?: HealthData; + // Use this to ask questions and give pertinent responses + message?: string; + // Use this parts of the user request not translated, off topic, etc. + notTranslated?: string; +} + +export interface HealthData { + medication?: Medication[]; + condition?: Condition[]; + other?: OtherHealthData[]; +} + +// Meds, pills etc. +export interface Medication { + // Fix any spelling mistakes, especially phonetic spelling + name: string; + // E.g. 2 tablets, 1 cup. Required + dose: ApproxQuantity; + // E.g. twice a day. Required + frequency: ApproxQuantity; + // E.g. 50 mg. Required + strength: ApproxQuantity; +} + +// Disease, Ailment, Injury, Sickness +export interface Condition { + // Fix any spelling mistakes, especially phonetic spelling + name: string; + // When the condition started. + startDate: ApproxDatetime; + // Always ask for current status of the condition + status: "active" | "recurrence" | "relapse" | "inactive" | "remission" | "resolved" | "unknown"; + // If the condition was no longer active + endDate?: ApproxDatetime; +} + +// Use for health data that match nothing else. E.g. immunization, blood prssure etc +export interface OtherHealthData { + text: string; + when?: ApproxDatetime; +} + +export interface ApproxQuantity { + // Default: "unknown" + displayText: string; + // Only specify if precise quantities are available + quantity?: Quantity; +} + +export interface ApproxDatetime { + // Default: "unknown" + displayText: string; + // If precise timestamp can be set + timestamp?: string; +} + +export interface Quantity { + // Exact number + value: number; + // Units like mg, kg, cm, pounds, liter, ml, tablet, pill, cup, per-day, per-week, etc. + units: string; +} \ No newline at end of file diff --git a/examples/healthData/src/input.txt b/examples/healthData/src/input.txt new file mode 100644 index 00000000..21138470 --- /dev/null +++ b/examples/healthData/src/input.txt @@ -0,0 +1,63 @@ +# +# Conversations with a Health Data Agent +# For each conversation: +# You start with the first line +# Then type the next line in response +# + +# ================ +# USE GPT4 +# ================ +# Conversation: +i want to record my shingles +August 2016 +It lasted 3 months +I also broke my foot +I broke it in high school +2001 +The foot took a year to be ok + +# Conversation: +klaritin +2 tablets 3 times a day +300 mg +actually that is 1 tablet +@clear + +# Conversation: +klaritin +1 pill, morning and before bedtime +Can't remember +Actually, that is 3 tablets +500 mg +@clear + +#Conversation +I am taking binadryl now +As needed. Groceery store strength +That is all I have +I also got allergies. Pollen +@clear + +# Conversation: +Robotussin +1 cup +Daily, as needed +Robotussin with Codeine +Put down strength as I don't know +@clear + +# Conversation: +Hey +Melatonin +1 3mg tablet every night +@clear + +# Conversation: +I got the flu +Started 2 weeks ago +Its gone now. Only lasted about a week +I took some sudafed though +I took 2 sudafed twice a day. Regular strength +@clear + diff --git a/examples/healthData/src/main.ts b/examples/healthData/src/main.ts new file mode 100644 index 00000000..3039e2df --- /dev/null +++ b/examples/healthData/src/main.ts @@ -0,0 +1,51 @@ +import fs from "fs"; +import path from "path"; +import dotenv from "dotenv"; +import { createHealthDataTranslator } from "./translator"; +import { createLanguageModel, processRequests } from "typechat"; +import { HealthDataResponse } from "./healthDataSchema"; + +// TODO: use local .env file. +dotenv.config({ path: path.join(__dirname, "../../../.env") }); + +const healthInstructions = ` +Help me enter my health data step by step. +Ask specific questions to gather required and optional fields +I have not already providedStop asking if I don't know the answer +Automatically fix my spelling mistakes +My health data may be complex: always record and return ALL of it. +Always return a response: +- If you don't understand what I say, ask a question. +- At least respond with an OK message. +`; + +const model = createLanguageModel(process.env); +const schema = fs.readFileSync(path.join(__dirname, "healthDataSchema.ts"), "utf8"); +const translator = createHealthDataTranslator(model, schema, "HealthDataResponse", + healthInstructions); + +// Process requests interactively or from the input file specified on the command line +processRequests("🤧> ", process.argv[2], async (request) => { + const response = await translator.translate(request); + if (!response.success) { + console.log("Translation Failed āŒ"); + console.log(`Context: ${response.message}`); + } + else { + const healthData = response.data; + console.log("Translation Succeeded! āœ…\n"); + console.log("JSON View"); + console.log(JSON.stringify(healthData, undefined, 2)); + + const message = healthData.message; + const notTranslated = healthData.notTranslated; + + if (message) { + console.log(`\nšŸ“: ${message}`); + } + + if (notTranslated) { + console.log(`\nšŸ¤”: I did not understand\n ${notTranslated}`) + } + } +}); diff --git a/examples/healthData/src/translator.ts b/examples/healthData/src/translator.ts new file mode 100644 index 00000000..c5b1987c --- /dev/null +++ b/examples/healthData/src/translator.ts @@ -0,0 +1,76 @@ +import {Result, TypeChatLanguageModel, createJsonTranslator, TypeChatJsonTranslator} from "typechat"; + +type ChatMessage = { + source: "system" | "user" | "assistant"; + body: object; +}; + +export interface TranslatorWithHistory { + _chatHistory: ChatMessage[]; + _maxPromptLength: number; + _additionalAgentInstructions: string; + _translator: TypeChatJsonTranslator; + translate(request: string): Promise>; +} + +export function createHealthDataTranslator(model: TypeChatLanguageModel, schema: string, typename: string, additionalAgentInstructions: string): TranslatorWithHistory { + const _chatHistory: ChatMessage[] = []; + const _maxPromptLength = 2048; + const _additionalAgentInstructions = additionalAgentInstructions; + + const _translator = createJsonTranslator(model, schema, typename); + _translator.createRequestPrompt = createRequestPrompt; + + const customtranslator: TranslatorWithHistory = { + _chatHistory, + _maxPromptLength, + _additionalAgentInstructions, + _translator, + translate, + }; + + return customtranslator; + + async function translate(request: string): Promise> { + const response = await _translator.translate(request); + if (response.success) { + _chatHistory.push({ source: "assistant", body: response.data }); + } + return response; + + } + + function createRequestPrompt(intent: string): string { + // TODO: drop history entries if we exceed the max_prompt_length + const historyStr = JSON.stringify(_chatHistory, undefined, 2); + + const now = new Date(); + + const prompt = ` +user: You are a service that translates user requests into JSON objects of type "${typename}" according to the following TypeScript definitions: +''' +${schema} +''' + +user: +Use precise date and times RELATIVE TO CURRENT DATE: ${now.toLocaleDateString()} CURRENT TIME: ${now.toTimeString().split(' ')[0]} +Also turn ranges like next week and next month into precise dates + +user: +${_additionalAgentInstructions} + +system: +IMPORTANT CONTEXT for the user request: +${historyStr} + +user: +The following is a user request: +''' +${intent} +''' +The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined: +""" +`; + return prompt; + } +} diff --git a/examples/healthData/src/tsconfig.json b/examples/healthData/src/tsconfig.json new file mode 100644 index 00000000..0f8bbec6 --- /dev/null +++ b/examples/healthData/src/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "es2021", + "lib": ["es2021"], + "module": "node16", + "types": ["node"], + "outDir": "../dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "exactOptionalPropertyTypes": true, + "inlineSourceMap": true + } +} diff --git a/package-lock.json b/package-lock.json index f0c9488b..eb01a17f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,6 +63,20 @@ "typescript": "^5.1.3" } }, + "examples/healthData": { + "name": "health-data", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "dotenv": "^16.3.1", + "typechat": "^0.0.10" + }, + "devDependencies": { + "@types/node": "^20.3.1", + "copyfiles": "^2.4.1", + "typescript": "^5.1.3" + } + }, "examples/math": { "version": "0.0.1", "license": "MIT", @@ -1135,6 +1149,10 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, + "node_modules/health-data": { + "resolved": "examples/healthData", + "link": true + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",