mirror of
https://github.com/run-llama/LlamaIndexTS.git
synced 2026-07-20 22:41:23 -04:00
feat: add responseFormat to llm.exec (#2167)
This commit is contained in:
committed by
GitHub
parent
7875ee91e6
commit
0267bb0e8e
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@llamaindex/core": patch
|
||||
---
|
||||
|
||||
Added responseFormat to llm.exec
|
||||
@@ -46,3 +46,31 @@ You should expect output something like:
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Using the `exec` method
|
||||
|
||||
Many LLMs do not natively support structured output, and often rely exclusively on prompt or context engineering.
|
||||
|
||||
In this sense, we proved you with an alternative for structured data extraction, using the `exec` method with `responseFormat`.
|
||||
|
||||
For example, you can, in a new folder, install our Anthropic integration and `zod` v3:
|
||||
|
||||
```package-install
|
||||
npm init
|
||||
npm i -D typescript @types/node
|
||||
npm i @llamaindex/anthropic zod@3.25.76
|
||||
```
|
||||
|
||||
And then try extracting data with this code:
|
||||
|
||||
<include cwd>../../examples/agents/tools/response-format-exec.ts</include>
|
||||
|
||||
The output should look like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "La Divina Commedia",
|
||||
"author": "Dante Alighieri",
|
||||
"year": 1321
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Anthropic } from "@llamaindex/anthropic";
|
||||
import { ChatMessage, ToolCall } from "llamaindex";
|
||||
import { z } from "zod";
|
||||
|
||||
const llm = new Anthropic({ model: "claude-4-0-sonnet" });
|
||||
|
||||
const responseSchema = z.object({
|
||||
title: z.string().describe("The title of the book"),
|
||||
author: z.string().describe("The author of the book"),
|
||||
year: z.number().describe("The publication year"),
|
||||
});
|
||||
|
||||
async function main() {
|
||||
const messages: ChatMessage[] = [];
|
||||
let toolCalls: ToolCall[] = [];
|
||||
do {
|
||||
const result = await llm.exec({
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: `You are a book expert. Your task is, given a user message, extract the title, author and publication year of the book and output them in JSON format.`,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: `I have been reading La Divina Commedia by Dante Alighieri, published in 1321, which tells the story of a guy who goes through Hell, Purgatory and Heaven just to meet his beloved ex-girlfriend.`,
|
||||
},
|
||||
],
|
||||
responseFormat: responseSchema,
|
||||
});
|
||||
console.log(result.newMessages[0].content);
|
||||
messages.push(...result.newMessages);
|
||||
toolCalls = result.toolCalls;
|
||||
} while (toolCalls.length == 0);
|
||||
|
||||
console.log(messages[1].content);
|
||||
console.log(toolCalls);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -1,4 +1,6 @@
|
||||
import { emptyLogger } from "@llamaindex/env";
|
||||
import type { JSONObject } from "../global";
|
||||
import { tool } from "../tools/";
|
||||
import { extractText } from "../utils/llms";
|
||||
import { streamConverter } from "../utils/stream";
|
||||
import { callToolToMessage, getToolCallsFromResponse } from "./tool-call";
|
||||
@@ -18,6 +20,7 @@ import type {
|
||||
PartialToolCall,
|
||||
ToolCallLLMMessageOptions,
|
||||
} from "./type";
|
||||
import { isZodSchema } from "./utils";
|
||||
|
||||
export abstract class BaseLLM<
|
||||
AdditionalChatOptions extends object = object,
|
||||
@@ -97,6 +100,31 @@ export abstract class BaseLLM<
|
||||
| ExecResponse<AdditionalMessageOptions>
|
||||
| ExecStreamResponse<AdditionalMessageOptions>
|
||||
> {
|
||||
const responseFormat = params.responseFormat;
|
||||
if (typeof responseFormat != "undefined" && isZodSchema(responseFormat)) {
|
||||
const structuredTool = tool({
|
||||
name: "format_output",
|
||||
description: "Respond with a JSON object",
|
||||
parameters: responseFormat,
|
||||
execute: (args) => {
|
||||
const result = responseFormat.safeParse(args);
|
||||
if (!result.success) {
|
||||
console.error("Invalid input from LLM:", result.error);
|
||||
return JSON.stringify({
|
||||
error: "Invalid schema",
|
||||
details: result.error,
|
||||
});
|
||||
}
|
||||
return result.data as JSONObject;
|
||||
},
|
||||
});
|
||||
if (Array.isArray(params.tools)) {
|
||||
params.tools.push(structuredTool);
|
||||
} else {
|
||||
params.tools = [structuredTool];
|
||||
}
|
||||
params.responseFormat = undefined;
|
||||
}
|
||||
if (params.stream) {
|
||||
return this.streamExec(params);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import type {
|
||||
ChatMessage,
|
||||
MessageContentImageDataDetail,
|
||||
@@ -26,3 +27,15 @@ export function addContentPart<AdditionalMessageOptions extends object>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isZodSchema(obj: unknown): obj is z.ZodType {
|
||||
return (
|
||||
obj !== null &&
|
||||
typeof obj === "object" &&
|
||||
"parse" in obj &&
|
||||
typeof (obj as { parse: unknown }).parse === "function" &&
|
||||
"safeParse" in obj &&
|
||||
typeof (obj as { safeParse: unknown }).safeParse === "function" &&
|
||||
"_def" in obj
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { z } from "zod";
|
||||
import { zodToJsonSchema } from "zod-to-json-schema";
|
||||
import type { JSONValue } from "../global";
|
||||
import type { BaseTool, ToolMetadata } from "../llms";
|
||||
import { isZodSchema } from "../llms/utils";
|
||||
|
||||
export class FunctionTool<
|
||||
T,
|
||||
@@ -94,7 +95,7 @@ export class FunctionTool<
|
||||
) {
|
||||
const { execute, parameters, ...restConfig } = fnOrConfig;
|
||||
|
||||
if (parameters instanceof z.ZodSchema) {
|
||||
if (isZodSchema(parameters)) {
|
||||
const jsonSchema = zodToJsonSchema(parameters);
|
||||
return new FunctionTool(
|
||||
execute,
|
||||
@@ -105,7 +106,6 @@ export class FunctionTool<
|
||||
parameters,
|
||||
);
|
||||
}
|
||||
|
||||
return new FunctionTool(execute, fnOrConfig);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user