Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added to typechat.js #43

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 93 additions & 110 deletions src/typechat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,117 +2,100 @@ import { Result, error } from "./result";
import { TypeChatLanguageModel } from "./model";
import { TypeChatJsonValidator, createJsonValidator } from "./validate";

/**
* Represents an object that can translate natural language requests in JSON objects of the given type.
*/
export interface TypeChatJsonTranslator<T extends object> {
/**
* The associated `TypeChatLanguageModel`.
*/
model: TypeChatLanguageModel;
/**
* The associated `TypeChatJsonValidator<T>`.
*/
validator: TypeChatJsonValidator<T>;
/**
* A boolean indicating whether to attempt repairing JSON objects that fail to validate. The default is `true`,
* but an application can set the property to `false` to disable repair attempts.
*/
attemptRepair: boolean;
/**
* A boolean indicating whether to delete properties with null values from parsed JSON objects. Some language
* models (e.g. gpt-3.5-turbo) have a tendency to assign null values to optional properties instead of omitting
* them. The default for this property is `false`, but an application can set the property to `true` for schemas
* that don't permit null values.
*/
stripNulls: boolean;
/**
* Creates an AI language model prompt from the given request. This function is called by `completeAndValidate`
* to obtain the prompt. An application can assign a new function to provide a different prompt.
* @param request The natural language request.
* @returns A prompt that combines the request with the schema and type name of the underlying validator.
*/
createRequestPrompt(request: string): string;
/**
* Creates a repair prompt to append to an original prompt/response in order to repair a JSON object that
* failed to validate. This function is called by `completeAndValidate` when `attemptRepair` is true and the
* JSON object produced by the original prompt failed to validate. An application can assign a new function
* to provide a different repair prompt.
* @param validationError The error message returned by the validator.
* @returns A repair prompt constructed from the error message.
*/
createRepairPrompt(validationError: string): string;
/**
* Translates a natural language request into an object of type `T`. If the JSON object returned by
* the language model fails to validate and the `attemptRepair` property is `true`, a second
* attempt to translate the request will be made. The prompt for the second attempt will include the
* diagnostics produced for the first attempt. This often helps produce a valid instance.
* @param request The natural language request.
* @returns A promise for the resulting object.
*/
translate(request: string): Promise<Result<T>>;
interface TypeChatJsonTranslator<T extends object> {
model: TypeChatLanguageModel;
validator: TypeChatJsonValidator<T>;
attemptRepair: boolean;
stripNulls: boolean;
createRequestPrompt: (naturalLanguageRequest: string) => string;
createRepairPrompt: (validationError: string) => string;
translate: (naturalLanguageRequest: string) => Promise<Result<T>>;
}

/**
* Creates an object that can translate natural language requests into JSON objects of the given type.
* The specified type argument `T` must be the same type as `typeName` in the given `schema`. The function
* creates a `TypeChatJsonValidator<T>` and stores it in the `validator` property of the returned instance.
* @param model The language model to use for translating requests into JSON.
* @param schema A string containing the TypeScript source code for the JSON schema.
* @param typeName The name of the JSON target type in the schema.
* @returns A `TypeChatJsonTranslator<T>` instance.
*/
export function createJsonTranslator<T extends object>(model: TypeChatLanguageModel, schema: string, typeName: string): TypeChatJsonTranslator<T> {
const validator = createJsonValidator<T>(schema, typeName);
const typeChat: TypeChatJsonTranslator<T> = {
model,
validator,
attemptRepair: true,
stripNulls: false,
createRequestPrompt,
createRepairPrompt,
translate
};
return typeChat;

function createRequestPrompt(request: string) {
return `You are a service that translates user requests into JSON objects of type "${validator.typeName}" according to the following TypeScript definitions:\n` +
`\`\`\`\n${validator.schema}\`\`\`\n` +
`The following is a user request:\n` +
`"""\n${request}\n"""\n` +
`The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`;
}

function createRepairPrompt(validationError: string) {
return `The JSON object is invalid for the following reason:\n` +
`"""\n${validationError}\n"""\n` +
`The following is a revised JSON object:\n`;
}

async function translate(request: string) {
let prompt = typeChat.createRequestPrompt(request);
let attemptRepair = typeChat.attemptRepair;
while (true) {
const response = await model.complete(prompt);
if (!response.success) {
return response;
}
const responseText = response.data;
const startIndex = responseText.indexOf("{");
const endIndex = responseText.lastIndexOf("}");
if (!(startIndex >= 0 && endIndex > startIndex)) {
return error(`Response is not JSON:\n${responseText}`);
}
const jsonText = responseText.slice(startIndex, endIndex + 1);
const validation = validator.validate(jsonText);
if (validation.success) {
return validation;
}
if (!attemptRepair) {
return error(`JSON validation failed: ${validation.message}\n${jsonText}`);
}
prompt += `${responseText}\n${typeChat.createRepairPrompt(validation.message)}`;
attemptRepair = false;
function createJsonTranslator<T extends object>(
model: TypeChatLanguageModel,
schema: string,
typeName: string
): TypeChatJsonTranslator<T> {
const validator = createJsonValidator<T>(schema, typeName);

const translator: TypeChatJsonTranslator<T> = {
model,
validator,
attemptRepair: true,
stripNulls: false,

createRequestPrompt(naturalLanguageRequest) {
return `
You are a service that translates user requests into JSON objects of type "${this.validator.typeName}" according to the following TypeScript definitions:
\`\`\`
${this.validator.schema}
\`\`\`
The following is a user request:
"""
${naturalLanguageRequest}
"""

The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:
`;
},

createRepairPrompt(validationError) {
return `
The JSON object is invalid for the following reason:
"""
${validationError}
"""

The following is a revised JSON object:
`;
},

async translate(naturalLanguageRequest) {
let prompt = this.createRequestPrompt(naturalLanguageRequest);
let attemptRepair = this.attemptRepair;

while (true) {
const response = await this.model.complete(prompt);

if (!response.success) {
return response;
}

const aiResponseText = response.data;
const startIndex = aiResponseText.indexOf("{");
const endIndex = aiResponseText.lastIndexOf("}");

if (startIndex == -1 || endIndex == -1 || startIndex >= endIndex) {
// invalid JSON, return error
}
}

const parsedJsonText = aiResponseText.slice(startIndex, endIndex + 1);

const validation = this.validator.validate(parsedJsonText);

if (validation.success) {
return validation;
}

if (!attemptRepair) {
return error(
`JSON validation failed: ${validation.message}\n${parsedJsonText}`
);
}

prompt += `${aiResponseText}\n${this.createRepairPrompt(
validation.message
)}`;

attemptRepair = false;
}
},
};

return translator;
}