Installation
To install llm-functions-ts, you can use npm or yarn:
npm install llm-functions-ts
or
yarn add llm-functions-ts
LLM Function
You can run the following examples in llm-function example (opens in a new tab) sandbox
An LLM Function is composed of a few parts. Each part gives you additional functionality and more control over your output. To start, let's create a very simple function
import { llmFunction } from "llm-functions-ts";
const simpleFunction = llmFunction
.name("A function that does nothing")
.create();
We created the simplest function there can be. The function signature is () => Promise<string>
Let's call it to see what happens.
import { llmFunction } from "llm-functions-ts";
const functionResult = await simpleFunction();
console.log(functionResult);
// "There is no prompt provided. Please provide a prompt for me to generate output."
That's pretty useless. Let's add a prompt by using the instructions
method.
const bandNameGenerator = llmFunction
.name("Band name generator")
.instructions("Generate band names")
.create();
This tells GPT that we want it to generate band names. Let's call this function and see what happens!
const bandNames = await bandNameGenerator();
We get a string
1. Electric Stardust
2. Midnight Mirage
3. Cosmic Echoes
4. Neon Serenade
5. Velvet Thunder
6. Solar Symphony
7. Lunar Lullaby
8. Radiant Rhapsody
9. Sonic Oasis
10. Dreamy Daze
That's pretty good! However, the result is a string. We want an array of strings so we can use it in our app.
To do that, we can force GPT to output in a specific format using zod and the output
method.
const bandNameGenerator = llmFunction
.name("Band name generator")
.instructions("Generate band names")
.output(z.array(z.string())
.create();
The function signature is () => Promise<string[]>
. Let's call this function and see what happens!
We get
[
"Electric Dreams",
"Neon Nights",
"Cosmic Echoes",
"Galactic Groove",
"Starlight Serenade",
];
Much better! We can now map over the array and render it in a UI however we like.
Now, let's try something more complicated. Let's add a genre name to each of these band names.
const bandNameGenerator = llmFunction
.name("Band name generator")
.instructions("Generate some fake band names for my new band")
.output(
z.array(
z.object({
bandName: z.string(),
genre: z.string(),
})
)
.create();
We get
[
{
bandName: "Electric Dreams",
genre: "Indie Rock",
},
{
bandName: "Neon Nights",
genre: "Synthpop",
},
{
bandName: "The Midnight Riders",
genre: "Country",
},
];
We can also try this function out in the tracing UI!
Now let's say we only want to generate band names in rock
pop
and rap
category. We can make the genre into an enum and restrict it to those genres.
At the same time, we'll also tell GPT to generate the band names in spanish.
To do that, we can add furthur directions in the instructions
field - but there is a better way! We can use zod's decsribe
function to force GPT to give us spanish band names for the key bandName
.
This gives you granualr control over each key without having to write complicated prompts.
While we're at it, let's also add an album names field.
You can write prompt at the key level!
const bandNameGenerator = llmFunction
.name("Band name generator")
.instructions("Generate some fake band names for my new band")
.output(
z.array(
z.object({
// You can write prompt for each key!
bandName: z.string().describe('Band names are in Spanish'),
genre: z.enum(["rock", "pop", "rap"]),
albums: z.array(z.string()).describe('Their top charting albums. Titles could be in English or Spanish'),
})
)
.create();
The type of the function is automatically inferred from the zod schema passed to the output method:
() =>
Promise<
{
bandName: string;
genre: "pop" | "rock" | "rap";
albumName: string[];
}[]
>;
Let's run this function and see what happens!
It did all three things!
bandName
is in spanishgenre
is restricted torock
,pop
andrap
albumNames
is an array of strings
What if we wanted the function to take a genre as an input and return results in that genre? This would allow you to take a user input and pass it to the function! To do this, we can define and interpolate a variable in our instructions.
const bandNameGenerator = llmFunction
.name("Band name generator")
.instructions("Generate some fake band names for my new band ${genre}")
.output(
z.array(
z.object({
bandName: z.string(),
genre: z.enum(["rock", "pop", "rap"]),
albums: z.array(z.string()).describe('Their top charting albums. Titles could be in English or Spanish'),
})
)
.create();
The function now takes genre as an input. The type of the input is inferred from the interpolated variable in the instructions. Let's run this function and see what happens!
const bandNames = bandNamesGenerator({ instructions: { genre: "rock" } });
You can also run this function in the tracing UI. An input field was auotmatically created based on the variable
It's typesafe all the way down! You will get an error if you try to call the function without the variable genre
!
Next steps
There's a lot more you can do with llm-functions.
The UI can be used as a playground to test your functions.