Structured outputs
Workflows regularly require getting structured outputs (JSON) from LLMs. This guide shows how to use structured outputs with both @gensx/openai and @gensx/vercel-ai. You can also find similar examples in OpenAI examples and Vercel AI examples in the GitHub repo.
Structured outputs with the Vercel AI SDK
The @gensx/vercel-ai
package provides two ways to get structured outputs from LLMs: generateObject
and streamObject
. Two key benefits of using the Vercel AI SDK are that you can stream the output as it’s generated, and you can use it with models that don’t natively support structured outputs.
Using generateObject
Start by defining the Zod schema for the output format you want:
import { z } from "zod";
const ExtractEntitiesSchema = z.object({
people: z.array(z.string()),
places: z.array(z.string()),
organizations: z.array(z.string()),
});
Then define the component and set the schema
param on the generateObject
function:
interface ExtractEntitiesInput {
text: string;
}
const ExtractEntities = gensx.Component(
"ExtractEntities",
({ text }: ExtractEntitiesInput) => {
const prompt = `Please review the following text and extract all the people, places, and organizations mentioned.
<text>
${text}
</text>
Please return JSON with the following format:
{
"people": ["person1", "person2", "person3"],
"places": ["place1", "place2", "place3"],
"organizations": ["org1", "org2", "org3"]
}`;
const result = generateObject({
model: openai("gpt-4o-mini"),
schema: ExtractEntitiesSchema,
messages: [{ role: "user", content: prompt }],
});
return result.object;
},
);
When you run this component, it will return the structured output directly matching the type of the ExtractEntitiesSchema
with no extra parsing required.
Using streamObject
The streamObject
function is similar to generateObject
but it streams the output as it’s generated.
const ExtractEntitiesStreaming = gensx.Component(
"ExtractEntitiesStreaming",
({ text }: ExtractEntitiesInput) => {
const prompt = `Please review the following text and extract all the people, places, and organizations mentioned.
<text>
${text}
</text>
Please return JSON with the following format:
{
"people": ["person1", "person2", "person3"],
"places": ["place1", "place2", "place3"],
"organizations": ["org1", "org2", "org3"]
}`;
const result = streamObject({
model: openai("gpt-4o-mini"),
schema: ExtractEntitiesSchema,
messages: [{ role: "user", content: prompt }],
});
const generator = async function* () {
for await (const chunk of result.partialObjectStream) {
yield chunk;
}
};
return generator();
},
);
Then you can consume the output of the component like this:
const structuredStreamResult = await ExtractEntitiesStreaming({
text: "John Doe is a software engineer at Google.",
});
console.log("Response:");
for await (const chunk of structuredStreamResult) {
console.clear();
console.log(chunk);
}
Structured outputs with the OpenAI SDK
You can also use the @gensx/openai
package to get structured outputs from OpenAI models and compatible APIs.
The OpenAI SDK provides a parse
method that you can use to automatically parse the response of the structured output.
Here’s an example of how to extract entities using the OpenAI SDK:
import { OpenAI } from "@gensx/openai";
import { zodResponseFormat } from "openai/helpers/zod.mjs";
import { z } from "zod";
const openai = new OpenAI();
const EntityExtractionSchema = z.object({
people: z.array(z.string()).describe("List of people mentioned in the text"),
places: z.array(z.string()).describe("List of places mentioned in the text"),
organizations: z
.array(z.string())
.describe("List of organizations mentioned in the text"),
});
const ExtractEntities = gensx.Component(
"ExtractEntities",
async ({ text }: { text: string }) => {
const result = await openai.beta.chat.completions.parse({
model: "gpt-4",
messages: [
{
role: "user",
content: `Please extract all people, places, and organizations from this text:\n\n${text}`,
},
],
response_format: zodResponseFormat(
EntityExtractionSchema,
"entityExtraction",
),
});
return result.choices[0].message.parsed!;
},
);
When you run this component, it will return a typed object matching the EntityExtractionSchema
structure. The parse
method ensures that the response is properly validated against your schema before returning it.
You can use it like this:
const result = await ExtractEntities({
text: "John works at Google in New York City.",
});
console.log(result);
// Output:
// {
// people: ["John"],
// places: ["New York City"],
// organizations: ["Google"]
// }
Alternatively, you can just call openai.chat.completions.create
and then parse the response yourself with Zod:
const parsed = EntityExtractionSchema.parse(result.choices[0].message.content);