Skip to content

AskIt (for JavaScript/TypeScript): Unified programming interface for large language models (GPT-4, GPT-3.5)

Notifications You must be signed in to change notification settings

katsumiok/ts-askit

Repository files navigation

AskIt (ts-askit)

TypeScript CI arXiv

Overview

AskIt is a language plugin for TypeScript that enables you to leverage the capabilities of a large language model (LLM), such as GPT-4, directly within your programming environment, no complex APIs needed. AskIt's extensive range of applications includes:

  • Translation
  • Paraphrasing
  • Sentiment analysis
  • Math problem solving
  • Code generation
  • And many more...

Built upon the OpenAI API, AskIt provides a user-friendly interface for incorporating LLMs into your applications. You can use AskIt not only in TypeScript, but also in JavaScript and Python.

For integrating AskIt with JavaScript, please refer to the corresponding JavaScript section.

If Python is your preferred language, you can learn more about how to utilize AskIt by visiting our dedicated AskIt (pyaskit) page.

Key Features

  • Type-Guided Output Control: Get a response in the specified type.

    • No need to specify the output format in the prompt
    • No need to parse the response to extract the desired output

    Type-guided output control demonstration

  • Template-Based Function Definition: Define functions using a prompt template. Template-based function definition demonstration

  • Code Generation: Generate functions from the unified interface. See Code Generation with AskIt for more details.

  • Programming by Example (PBE): Define functions using examples. See Programming by Example with AskIt for more details.

Installation

Before starting, ensure that Node.js and npm are installed on your system. Then, execute the following command:

npm install ts-askit

This package relies on ts-patch. To install ts-patch, run:

npx ts-patch install

Add the following snippet to your tsconfig.json:

"compilerOptions": {
    "plugins": [{ "transform": "ts-askit/transform" }]
}

This modification allows the TypeScript compiler to support type parameters for the ask and define APIs in AskIt.

The ts-patch package is crucial for unleashing the full potential of AskIt, as it extends the TypeScript compiler to fully integrate AskIt's type system. While AskIt can be used without ts-patch, this integration offers a more feature-rich experience.

Before using AskIt, you need to set your OpenAI API key as an environment variable OPENAI_API_KEY:

export OPENAI_API_KEY=<your OpenAI API key>

<your OpenAI API key> is a string that looks like this: sk-<your key>. You can find your API key in the OpenAI dashboard.

You can also specify the model name as an environment variable ASKIT_MODEL:

export ASKIT_MODEL=<model name>

<model name> is the name of the model you want to use. The latest AskIt is tested with gpt-4 and gpt-3.5-turbo-16k. You can find the list of available models in the OpenAI API documentation.

API Usage

Here are some introductory examples:

import { ask } from 'ts-askit';

ask<string>('Paraphrase "Hello World!"').then((result) => {
  console.log(result);
});

In this example, ask is an API function that allows your program to pose queries to a large language model (LLM). The type parameter represents the expected output type from the LLM. Here, the output type is string. The prompt is passed as an argument in natural language, describing the task for the LLM to perform. ask is asynchronous, returning a Promise of the specified output type. The code snippet above should print something like this:

Greetings, Universe!

For a prompt with parameters, you can use the define API as follows:

import { define } from 'ts-askit';

const paraphrase = define<string>('Paraphrase {{text}}');

paraphrase({ text: 'Hello World!' }).then((result) => {
  console.log(result);
});

define is an API function that allows you to define a custom function. Its type parameter indicates the output type of the LLM, and consequently, the return value of the function. The function receives a string template as an argument, serving as the LLM's task prompt. The template can include parameters enclosed in double curly braces. In the example above, text is a parameter within the template, and it can be any valid JavaScript identifier.

Once the function is defined, it can be invoked like any other function. This function accepts an object as an argument, which maps to the template parameters' values. In this case, text maps to the string 'Hello World!'.

Code Generation with AskIt

AskIt is adept at generating code from natural language descriptions. Here's an example:

import { define } from 'ts-askit';

const sort = define<number[], { numbers: number[] }>(
  'Sort {{numbers}} in ascending order'
);

This example showcases a function that sorts an array of numbers in ascending order, utilizing the define API to instruct the LLM to define the function. While efficient in concept, this method may seem computationally heavy as each function call requires a new LLM task.

To streamline this process, we can leverage the LLM to generate the sorting function's code, rather than resorting to the LLM for every sorting task. This optimizes the function without requiring any changes in its implementation, thanks to AskIt's code generation capabilities.

The code for the aforementioned function can be generated in three steps:

  1. First, compile the code using the TypeScript compiler tsc. The AskIt analyzer scans the code and generates a jsonl file containing details about the define and ask API calls.
  2. Next, run the ts-askit command to generate the function's code:
npx askgen <jsonl file>
  1. Finally, recompile the code with the TypeScript compiler tsc. This time, the define and ask API calls are replaced by the references and calls to the newly generated function, respectively, thanks to AskIt's auto-replacement feature.

Programming by Example with AskIt

AskIt allows you to leverage the power of Programming by Example (PBE). PBE simplifies the programming process by enabling you to define functionality through examples rather than hard-coded logic. The following example illustrates this by showing you how to add two binary numbers using PBE with AskIt.

import { define, Example } from 'ts-askit';

const trainingExamples: Example[] = [
  { input: { x: '1', y: '0' }, output: '1' },
  { input: { x: '1', y: '1' }, output: '10' },
  { input: { x: '101', y: '11' }, output: '1000' },
  { input: { x: '1001', y: '110' }, output: '1111' },
  { input: { x: '1111', y: '1' }, output: '10000' },
];
const testExamples = [
  { input: { x: '0', y: '1' }, output: '1' },
  { input: { x: '10', y: '0' }, output: '10' },
  { input: { x: '110', y: '10' }, output: '1000' },
];
const addInBase2 = define<string, { x: string; y: string }>(
  'Add {{x}} and {{y}}',
  trainingExamples,
  testExamples
);

async function doit() {
  console.log(await addInBase2({ x: '101', y: '11' }));
}

doit();

In this example, we define a function addInBase2 that takes two binary numbers (represented as strings) and adds them. The define function is invoked with a prompt and two arrays of examples: training examples and test examples. The training examples are reflected in the prompt in a few-shot learning manner. On the other hand, the test examples are used to validate the generated function's correctness. Test examples are not required if you don't generate code for the function.

The result is a powerful feature allowing you to instruct the LLM to perform complex operations, like binary addition, using nothing but examples. This approach enables you to develop complex functionality rapidly and with less explicit logic.

Once the function addInBase2 is defined, you can call it with binary number strings to perform addition in base 2. As with traditional function calls, AskIt's ask operation returns a promise that resolves with the computed result.

Leverage AskIt in JavaScript

Type-guided Output Control in JavaScript

JavaScript developers can fully exploit the potential of type-guided output control offered by AskIt. Just like its sibling TypeScript, JavaScript incorporates the API method ask to achieve this. The function ask takes two parameters: a type and a prompt.

Here is an array of examples demonstrating its usage:

const ai = require('ts-askit')
const t = require('ts-askit/types')

ai.ask(t.number, 'What is the third prime number?').then((answer) => { console.log(answer) });
ai.ask(t.string, "What is the month number of 'January'?").then((answer) => { console.log(answer) });
ai.ask(t.array(t.number), "What are the month numbers in the second quarter?");
const monthType = t.type({
    name: t.string,
    number: t.number
})
ai.ask(monthType, "What is the month number of 'October'?").then((answer) => { console.log(answer) });
ai.ask(t.array(monthType), "What are the months in the second quarter?").then((answer) => { console.log(answer) });

In the code snippet above, the ask function is invoked with a type and a prompt. The type parameter serves the purpose of informing AskIt about the format and structure of the desired output. This becomes extremely handy when you're dealing with complex data structures.

Template-based Function Definition in JavaScript

With AskIt, JavaScript developers have the ability to define functions using easy-to-understand templates. The define method is the hero behind the scenes here, as it helps create functions based on the task template provided. Once created, these functions can be called with any object that provides values for the placeholders in the template.

Here's an example of how it's done:

const ai = require('ts-askit')
const t = require('ts-askit/types')

let f = ai.define(t.string, 'Translate {{text}} into {{language}}');
f({text: 'Hello', language: 'French'}).then((answer) => { console.log(answer) });

In the code above, the define method is employed to establish a function f using a task template 'Translate {{text}} into {{language}}'. The function f is then invoked with an object that provides values for text and language.

Diverse Supported Types in JavaScript

The 'ts-askit/types' module is a treasure trove of types that you can utilize to guide AskIt's output. Here's a table to help you quickly grasp these types:

Type Description Type Example Value Example
NumberType Numeric type t.number 123
StringType String type t.string "Hello, World!"
BooleanType Boolean type t.boolean true
LiteralType Literal value type t.literal(123) 123
ArrayType Array type t.array(t.number) [1, 2, 3]
UnionType Union type (Multiple Possible Values) t.union([t.literal('yes'), t.literal('no')]) "yes" or "no"
InterfaceType Interface/Dictionary Type t.type({a: t.number, b: t.number}) {a: 1, b: 2}
CodeType Code type t.code('python') "def hello_world(): print('Hello, World!')"

Each type has specific properties that AskIt uses to comprehend the task at hand and properly format the output.

Code Generation in JavaScript: The Road Ahead

As of the time of writing, the code generation feature is exclusively available in TypeScript. However, efforts are in full swing to extend this powerful feature to the realm of JavaScript. If your requirements call for the use of code generation in the interim, we recommend using TypeScript until further updates.

Contributing

For details on our code of conduct and the process for submitting pull requests, please refer to CONTRIBUTING.md.

License

This project is licensed under the MIT License. For more information, see the LICENSE file.

@misc{okuda2023askit,
      title={AskIt: Unified Programming Interface for Programming with Large Language Models}, 
      author={Katsumi Okuda and Saman Amarasinghe},
      year={2023},
      eprint={2308.15645},
      archivePrefix={arXiv},
      primaryClass={cs.PL}
}