Compare commits

...

9 Commits

Author SHA1 Message Date
Alex Yang bbf7c55656 changeset update 2024-11-11 11:58:50 -08:00
Alex Yang 6c7fec63f2 Create warm-candles-explain.md 2024-11-11 11:55:28 -08:00
Alex Yang e0b044668c fix: deprecate api 2024-11-11 11:52:18 -08:00
Alex Yang 9d34391147 revert: core workflow api 2024-11-11 11:47:35 -08:00
Alex Yang f5b99c0765 feat: migrate to new workflow API 2024-11-11 11:45:10 -08:00
Alex Yang 9156b180ac refactor(core): migrate to new workflow 2024-11-11 10:42:19 -08:00
Alex Yang d89ebe0261 chore: update changeset 2024-11-11 10:11:04 -08:00
Alex Yang 968feb32cd feat: better input type for function tool with zod (#1464) 2024-11-11 10:10:03 -08:00
Alex Yang 43f6f56c5b docs(next): fix turbo.json (#1465) 2024-11-11 10:07:12 -08:00
18 changed files with 351 additions and 126 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@llamaindex/core": patch
---
feat: better support for zod schema
+5
View File
@@ -0,0 +1,5 @@
---
"@llamaindex/core": patch
---
chore: add warning on legacy workflow API
+5
View File
@@ -0,0 +1,5 @@
---
"@llamaindex/workflow": patch
---
fix: infinite loop issue caused by multiple inputs in edge cases
+8
View File
@@ -1,6 +1,14 @@
{
"extends": ["//"],
"tasks": {
"build": {
"outputs": [
".next",
".source",
"next-env.d.ts",
"src/content/docs/cloud/api/**"
]
},
"dev": {
"dependsOn": ["^build"]
}
+1
View File
@@ -167,6 +167,7 @@ For questions about more specific sections, please use the vector_tool.`,
const mockCall = t.mock.fn(({ query }: { query: string }) => {
return originalCall({ query });
});
// @ts-expect-error what?
queryEngineTools[1]!.call = mockCall;
const toolMapping = SimpleToolNodeMapping.fromObjects(queryEngineTools);
+65 -30
View File
@@ -1,14 +1,19 @@
import {
Context,
HandlerContext,
StartEvent,
StopEvent,
Workflow,
WorkflowEvent,
} from "@llamaindex/core/workflow";
} from "@llamaindex/workflow";
import { OpenAI } from "llamaindex";
const MAX_REVIEWS = 3;
type Context = {
specification: string;
numberReviews: number;
};
// Using the o1-preview model (see https://platform.openai.com/docs/guides/reasoning?reasoning-prompt-examples=coding-planning)
const llm = new OpenAI({ model: "o1-preview", temperature: 1 });
@@ -20,7 +25,9 @@ stores the question/answer pair in the database.`;
// Create custom event types
export class MessageEvent extends WorkflowEvent<{ msg: string }> {}
export class CodeEvent extends WorkflowEvent<{ code: string }> {}
export class ReviewEvent extends WorkflowEvent<{
review: string;
code: string;
@@ -34,12 +41,13 @@ const truncate = (str: string) => {
};
// the architect is responsible for writing the structure and the initial code based on the specification
const architect = async (context: Context, ev: StartEvent) => {
// get the specification from the start event and save it to context
context.set("specification", ev.data.input);
const spec = context.get("specification");
const architect = async (
context: HandlerContext<Context>,
_: StartEvent<string>,
) => {
const spec = context.data.specification;
// write a message to send an update to the user
context.writeEventToStream(
context.sendEvent(
new MessageEvent({
msg: `Writing app using this specification: ${truncate(spec)}`,
}),
@@ -50,13 +58,13 @@ const architect = async (context: Context, ev: StartEvent) => {
};
// the coder is responsible for updating the code based on the review
const coder = async (context: Context, ev: ReviewEvent) => {
const coder = async (context: HandlerContext<Context>, ev: ReviewEvent) => {
// get the specification from the context
const spec = context.get("specification");
const spec = context.data.specification;
// get the latest review and code
const { review, code } = ev.data;
// write a message to send an update to the user
context.writeEventToStream(
context.sendEvent(
new MessageEvent({
msg: `Update code based on review: ${truncate(review)}`,
}),
@@ -67,32 +75,35 @@ const coder = async (context: Context, ev: ReviewEvent) => {
};
// the reviewer is responsible for reviewing the code and providing feedback
const reviewer = async (context: Context, ev: CodeEvent) => {
const reviewer = async (context: HandlerContext<Context>, ev: CodeEvent) => {
// get the specification from the context
const spec = context.get("specification");
const spec = context.data.specification;
// get latest code from the event
const { code } = ev.data;
// update and check the number of reviews
const numberReviews = context.get("numberReviews", 0) + 1;
context.set("numberReviews", numberReviews);
if (numberReviews > MAX_REVIEWS) {
context.data.numberReviews++;
if (context.data.numberReviews > MAX_REVIEWS) {
// the we've done this too many times - return the code
context.writeEventToStream(
context.sendEvent(
new MessageEvent({
msg: `Already reviewed ${numberReviews - 1} times, stopping!`,
msg: `Already reviewed ${
context.data.numberReviews - 1
} times, stopping!`,
}),
);
return new StopEvent({ result: code });
}
// write a message to send an update to the user
context.writeEventToStream(
new MessageEvent({ msg: `Review #${numberReviews}: ${truncate(code)}` }),
context.sendEvent(
new MessageEvent({
msg: `Review #${context.data.numberReviews}: ${truncate(code)}`,
}),
);
const prompt = `Review this code: <code>${code}</code>. Check if the code quality and whether it correctly implements this specification: <spec>${spec}</spec>. If you're satisfied, just return 'Looks great', nothing else. If not, return a review with a list of changes you'd like to see.`;
const review = (await llm.complete({ prompt })).text;
if (review.includes("Looks great")) {
// the reviewer is satisfied with the code, let's return the review
context.writeEventToStream(
context.sendEvent(
new MessageEvent({
msg: `Reviewer says: ${review}`,
}),
@@ -103,20 +114,44 @@ const reviewer = async (context: Context, ev: CodeEvent) => {
return new ReviewEvent({ review, code });
};
const codeAgent = new Workflow({ validate: true });
codeAgent.addStep(StartEvent, architect, { outputs: CodeEvent });
codeAgent.addStep(ReviewEvent, coder, { outputs: CodeEvent });
codeAgent.addStep(CodeEvent, reviewer, { outputs: ReviewEvent });
const codeAgent = new Workflow<Context, string, string>();
codeAgent.addStep(
{
inputs: [StartEvent<string>],
outputs: [CodeEvent],
},
architect,
);
codeAgent.addStep(
{
inputs: [ReviewEvent],
outputs: [CodeEvent],
},
coder,
);
codeAgent.addStep(
{
inputs: [CodeEvent],
outputs: [ReviewEvent, StopEvent],
},
reviewer,
);
// Usage
async function main() {
const run = codeAgent.run(specification);
for await (const event of codeAgent.streamEvents()) {
const msg = (event as MessageEvent).data.msg;
console.log(`${msg}\n`);
const run = codeAgent.run(specification).with({
specification,
numberReviews: 0,
});
for await (const event of run) {
if (event instanceof MessageEvent) {
const msg = (event as MessageEvent).data.msg;
console.log(`${msg}\n`);
} else if (event instanceof StopEvent) {
const result = (event as StopEvent<string>).data;
console.log("Final code:\n", result);
}
}
const result = await run;
console.log("Final code:\n", result.data.result);
}
main().catch(console.error);
@@ -1,10 +1,10 @@
import {
Context,
HandlerContext,
StartEvent,
StopEvent,
Workflow,
WorkflowEvent,
} from "@llamaindex/core/workflow";
} from "@llamaindex/workflow";
import { OpenAI } from "llamaindex";
// Create LLM instance
@@ -12,59 +12,77 @@ const llm = new OpenAI();
// Create custom event types
export class JokeEvent extends WorkflowEvent<{ joke: string }> {}
export class CritiqueEvent extends WorkflowEvent<{ critique: string }> {}
export class AnalysisEvent extends WorkflowEvent<{ analysis: string }> {}
const generateJoke = async (_context: Context, ev: StartEvent) => {
const prompt = `Write your best joke about ${ev.data.input}.`;
const generateJoke = async (_: unknown, ev: StartEvent<string>) => {
const prompt = `Write your best joke about ${ev.data}.`;
const response = await llm.complete({ prompt });
return new JokeEvent({ joke: response.text });
};
const critiqueJoke = async (_context: Context, ev: JokeEvent) => {
const critiqueJoke = async (_: unknown, ev: JokeEvent) => {
const prompt = `Give a thorough critique of the following joke: ${ev.data.joke}`;
const response = await llm.complete({ prompt });
return new CritiqueEvent({ critique: response.text });
};
const analyzeJoke = async (_context: Context, ev: JokeEvent) => {
const analyzeJoke = async (_: unknown, ev: JokeEvent) => {
const prompt = `Give a thorough analysis of the following joke: ${ev.data.joke}`;
const response = await llm.complete({ prompt });
return new AnalysisEvent({ analysis: response.text });
};
const reportJoke = async (
context: Context,
ev: AnalysisEvent | CritiqueEvent,
context: HandlerContext,
ev1: AnalysisEvent,
ev2: CritiqueEvent,
) => {
const events = context.collectEvents(ev, [AnalysisEvent, CritiqueEvent]);
if (!events) {
return;
}
const subPrompts = events.map((event) => {
if (event instanceof AnalysisEvent) {
return `Analysis: ${event.data.analysis}`;
} else if (event instanceof CritiqueEvent) {
return `Critique: ${event.data.critique}`;
}
return "";
});
const subPrompts = [ev1.data.analysis, ev2.data.critique];
const prompt = `Based on the following information about a joke:\n${subPrompts.join("\n")}\nProvide a comprehensive report on the joke's quality and impact.`;
const prompt = `Based on the following information about a joke:\n${subPrompts.join(
"\n",
)}\nProvide a comprehensive report on the joke's quality and impact.`;
const response = await llm.complete({ prompt });
return new StopEvent({ result: response.text });
return new StopEvent(response.text);
};
const jokeFlow = new Workflow();
jokeFlow.addStep(StartEvent, generateJoke);
jokeFlow.addStep(JokeEvent, critiqueJoke);
jokeFlow.addStep(JokeEvent, analyzeJoke);
jokeFlow.addStep([AnalysisEvent, CritiqueEvent], reportJoke);
const jokeFlow = new Workflow<unknown, string, string>();
jokeFlow.addStep(
{
inputs: [StartEvent<string>],
outputs: [JokeEvent],
},
generateJoke,
);
jokeFlow.addStep(
{
inputs: [JokeEvent],
outputs: [CritiqueEvent],
},
critiqueJoke,
);
jokeFlow.addStep(
{
inputs: [JokeEvent],
outputs: [AnalysisEvent],
},
analyzeJoke,
);
jokeFlow.addStep(
{
inputs: [AnalysisEvent, CritiqueEvent],
outputs: [StopEvent<string>],
},
reportJoke,
);
// Usage
async function main() {
const result = await jokeFlow.run("pirates");
console.log(result.data.result);
console.log(result.data);
}
main().catch(console.error);
+21 -10
View File
@@ -1,10 +1,9 @@
import {
Context,
StartEvent,
StopEvent,
Workflow,
WorkflowEvent,
} from "@llamaindex/core/workflow";
} from "@llamaindex/workflow";
import { OpenAI } from "llamaindex";
// Create LLM instance
@@ -13,26 +12,38 @@ const llm = new OpenAI();
// Create a custom event type
export class JokeEvent extends WorkflowEvent<{ joke: string }> {}
const generateJoke = async (_context: Context, ev: StartEvent) => {
const prompt = `Write your best joke about ${ev.data.input}.`;
const generateJoke = async (_: unknown, ev: StartEvent<string>) => {
const prompt = `Write your best joke about ${ev.data}.`;
const response = await llm.complete({ prompt });
return new JokeEvent({ joke: response.text });
};
const critiqueJoke = async (_context: Context, ev: JokeEvent) => {
const critiqueJoke = async (_: unknown, ev: JokeEvent) => {
const prompt = `Give a thorough critique of the following joke: ${ev.data.joke}`;
const response = await llm.complete({ prompt });
return new StopEvent({ result: response.text });
return new StopEvent(response.text);
};
const jokeFlow = new Workflow({ verbose: true });
jokeFlow.addStep(StartEvent, generateJoke);
jokeFlow.addStep(JokeEvent, critiqueJoke);
const jokeFlow = new Workflow<unknown, string, string>();
jokeFlow.addStep(
{
inputs: [StartEvent<string>],
outputs: [JokeEvent],
},
generateJoke,
);
jokeFlow.addStep(
{
inputs: [JokeEvent],
outputs: [StopEvent<string>],
},
critiqueJoke,
);
// Usage
async function main() {
const result = await jokeFlow.run("pirates");
console.log(result.data.result);
console.log(result.data);
}
main().catch(console.error);
+32 -15
View File
@@ -1,10 +1,10 @@
import {
Context,
HandlerContext,
StartEvent,
StopEvent,
Workflow,
WorkflowEvent,
} from "@llamaindex/core/workflow";
} from "@llamaindex/workflow";
import { OpenAI } from "llamaindex";
// Create LLM instance
@@ -12,38 +12,55 @@ const llm = new OpenAI();
// Create custom event types
export class JokeEvent extends WorkflowEvent<{ joke: string }> {}
export class MessageEvent extends WorkflowEvent<{ msg: string }> {}
const generateJoke = async (context: Context, ev: StartEvent) => {
context.writeEventToStream(
new MessageEvent({ msg: `Generating a joke about: ${ev.data.input}` }),
const generateJoke = async (context: HandlerContext, ev: StartEvent) => {
context.sendEvent(
new MessageEvent({ msg: `Generating a joke about: ${ev.data}` }),
);
const prompt = `Write your best joke about ${ev.data.input}.`;
const prompt = `Write your best joke about ${ev.data}.`;
const response = await llm.complete({ prompt });
return new JokeEvent({ joke: response.text });
};
const critiqueJoke = async (context: Context, ev: JokeEvent) => {
context.writeEventToStream(
const critiqueJoke = async (context: HandlerContext, ev: JokeEvent) => {
context.sendEvent(
new MessageEvent({ msg: `Write a critique of this joke: ${ev.data.joke}` }),
);
const prompt = `Give a thorough critique of the following joke: ${ev.data.joke}`;
const response = await llm.complete({ prompt });
return new StopEvent({ result: response.text });
return new StopEvent(response.text);
};
const jokeFlow = new Workflow();
jokeFlow.addStep(StartEvent, generateJoke);
jokeFlow.addStep(JokeEvent, critiqueJoke);
jokeFlow.addStep(
{
inputs: [StartEvent<string>],
outputs: [JokeEvent],
},
generateJoke,
);
jokeFlow.addStep(
{
inputs: [JokeEvent],
outputs: [StopEvent<string>],
},
critiqueJoke,
);
// Usage
async function main() {
const run = jokeFlow.run("pirates");
for await (const event of jokeFlow.streamEvents()) {
console.log((event as MessageEvent).data.msg);
for await (const event of run) {
if (event instanceof MessageEvent) {
console.log("Message:");
console.log((event as MessageEvent).data.msg);
} else if (event instanceof StopEvent) {
console.log("Result:");
console.log((event as StopEvent<string>).data);
}
}
const result = await run;
console.log(result.data.result);
}
main().catch(console.error);
+25 -14
View File
@@ -1,19 +1,21 @@
import {
Context,
StartEvent,
StopEvent,
Workflow,
} from "@llamaindex/core/workflow";
import { StartEvent, StopEvent, Workflow } from "@llamaindex/workflow";
const longRunning = async (_context: Context, ev: StartEvent) => {
const longRunning = async (_: unknown, ev: StartEvent<string>) => {
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait for 2 seconds
return new StopEvent({ result: "We waited 2 seconds" });
return new StopEvent("We waited 2 seconds");
};
async function timeout() {
const workflow = new Workflow({ verbose: true, timeout: 1 });
workflow.addStep(StartEvent, longRunning);
// This will timeout
const workflow = new Workflow<unknown, string, string>({
timeout: 1,
});
workflow.addStep(
{
inputs: [StartEvent<string>],
outputs: [StopEvent<string>],
},
longRunning,
);
try {
await workflow.run("Let's start");
} catch (error) {
@@ -23,14 +25,23 @@ async function timeout() {
async function notimeout() {
// Increase timeout to 3 seconds - no timeout
const workflow = new Workflow({ verbose: true, timeout: 3 });
workflow.addStep(StartEvent, longRunning);
const workflow = new Workflow<unknown, string, string>({
timeout: 3,
});
workflow.addStep(
{
inputs: [StartEvent<string>],
outputs: [StopEvent<string>],
},
longRunning,
);
const result = await workflow.run("Let's start");
console.log(result.data.result);
console.log(result.data);
}
async function main() {
await timeout();
console.log("---");
await notimeout();
}
+40 -15
View File
@@ -1,10 +1,9 @@
import {
Context,
StartEvent,
StopEvent,
Workflow,
WorkflowEvent,
} from "@llamaindex/core/workflow";
} from "@llamaindex/workflow";
import { OpenAI } from "llamaindex";
// Create LLM instance
@@ -13,40 +12,66 @@ const llm = new OpenAI();
// Create a custom event type
export class JokeEvent extends WorkflowEvent<{ joke: string }> {}
const generateJoke = async (_context: Context, ev: StartEvent) => {
const prompt = `Write your best joke about ${ev.data.input}.`;
const generateJoke = async (_: unknown, ev: StartEvent<string>) => {
const prompt = `Write your best joke about ${ev.data}.`;
const response = await llm.complete({ prompt });
return new JokeEvent({ joke: response.text });
};
const critiqueJoke = async (_context: Context, ev: JokeEvent) => {
const critiqueJoke = async (_: unknown, ev: JokeEvent) => {
const prompt = `Give a thorough critique of the following joke: ${ev.data.joke}`;
const response = await llm.complete({ prompt });
return new StopEvent({ result: response.text });
return new StopEvent(response.text);
};
async function validateFails() {
try {
const jokeFlow = new Workflow({ verbose: true, validate: true });
jokeFlow.addStep(StartEvent, generateJoke, { outputs: StopEvent });
jokeFlow.addStep(JokeEvent, critiqueJoke, { outputs: StopEvent });
await jokeFlow.run("pirates");
const jokeFlow = new Workflow();
jokeFlow.addStep(
{
inputs: [StartEvent<string>],
outputs: [StopEvent<string>],
},
// @ts-expect-error outputs should be JokeEvent
generateJoke,
);
jokeFlow.addStep(
{
inputs: [JokeEvent],
outputs: [StopEvent],
},
critiqueJoke,
);
await jokeFlow.run("pirates").strict();
} catch (e) {
console.error("Validation failed:", e);
}
}
async function validate() {
const jokeFlow = new Workflow({ verbose: true, validate: true });
jokeFlow.addStep(StartEvent, generateJoke, { outputs: JokeEvent });
jokeFlow.addStep(JokeEvent, critiqueJoke, { outputs: StopEvent });
const result = await jokeFlow.run("pirates");
console.log(result.data.result);
const jokeFlow = new Workflow();
jokeFlow.addStep(
{
inputs: [StartEvent<string>],
outputs: [JokeEvent],
},
generateJoke,
);
jokeFlow.addStep(
{
inputs: [JokeEvent],
outputs: [StopEvent<string>],
},
critiqueJoke,
);
const result = await jokeFlow.run("pirates").strict();
console.log(result.data);
}
// Usage
async function main() {
await validateFails();
console.log("---");
await validate();
}
+21 -13
View File
@@ -4,18 +4,12 @@ import { zodToJsonSchema } from "zod-to-json-schema";
import type { JSONValue } from "../global";
import type { BaseTool, ToolMetadata } from "../llms";
const kOriginalFn = Symbol("originalFn");
export class FunctionTool<T, R extends JSONValue | Promise<JSONValue>>
implements BaseTool<T>
{
[kOriginalFn]?: (input: T) => R;
#fn: (input: T) => R;
#metadata: ToolMetadata<JSONSchemaType<T>>;
// todo: for the future, we can use zod to validate the input parameters
// eslint-disable-next-line no-unused-private-class-members
#zodType: z.ZodType<T> | null = null;
readonly #metadata: ToolMetadata<JSONSchemaType<T>>;
readonly #zodType: z.ZodType<T> | null = null;
constructor(
fn: (input: T) => R,
metadata: ToolMetadata<JSONSchemaType<T>>,
@@ -32,6 +26,12 @@ export class FunctionTool<T, R extends JSONValue | Promise<JSONValue>>
fn: (input: T) => JSONValue | Promise<JSONValue>,
schema: ToolMetadata<JSONSchemaType<T>>,
): FunctionTool<T, JSONValue | Promise<JSONValue>>;
static from<R extends z.ZodType>(
fn: (input: z.infer<R>) => JSONValue | Promise<JSONValue>,
schema: Omit<ToolMetadata, "parameters"> & {
parameters: R;
},
): FunctionTool<z.infer<R>, JSONValue | Promise<JSONValue>>;
static from<T, R extends z.ZodType<T>>(
fn: (input: T) => JSONValue | Promise<JSONValue>,
schema: Omit<ToolMetadata, "parameters"> & {
@@ -40,15 +40,15 @@ export class FunctionTool<T, R extends JSONValue | Promise<JSONValue>>
): FunctionTool<T, JSONValue>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static from(fn: any, schema: any): any {
if (schema.parameter instanceof z.ZodSchema) {
const jsonSchema = zodToJsonSchema(schema.parameter);
if (schema.parameters instanceof z.ZodSchema) {
const jsonSchema = zodToJsonSchema(schema.parameters);
return new FunctionTool(
fn,
{
...schema,
parameters: jsonSchema,
},
schema.parameter,
schema.parameters,
);
}
return new FunctionTool(fn, schema);
@@ -58,7 +58,15 @@ export class FunctionTool<T, R extends JSONValue | Promise<JSONValue>>
return this.#metadata as BaseTool<T>["metadata"];
}
call(input: T) {
call = (input: T) => {
if (this.#zodType) {
const result = this.#zodType.safeParse(input);
if (result.success) {
return this.#fn.call(null, result.data);
} else {
console.warn(result.error.errors);
}
}
return this.#fn.call(null, input);
}
};
}
+14
View File
@@ -13,6 +13,8 @@ export type StepFunction<T extends WorkflowEvent = WorkflowEvent> = (
type EventTypeParam = EventTypes | EventTypes[];
let once = false;
export class Workflow {
#steps: Map<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -29,8 +31,20 @@ export class Workflow {
verbose?: boolean;
timeout?: number;
validate?: boolean;
ignoreDeprecatedWarning?: boolean;
} = {},
) {
if (!once && !params.ignoreDeprecatedWarning) {
console.warn(
"@llamaindex/core/workflow is going to use the new workflow API in the next major version.",
"Please update your imports to @llamaindex/workflow",
);
console.warn(
"See https://ts.llamaindex.ai/docs/llamaindex/guide/workflow for more information",
);
once = true;
}
this.#verbose = params.verbose ?? false;
this.#timeout = params.timeout ?? null;
this.#validate = params.validate ?? false;
+20
View File
@@ -289,6 +289,15 @@ export class WorkflowContext<Start = string, Stop = string, Data = unknown>
(event) => event instanceof requestEvent,
);
if (acceptableInput) {
// remove the event from the queue, in case of infinite loop
const protocolIdx = this.#queue.findIndex(
(protocol) =>
protocol.type === "event" &&
protocol.event === acceptableInput,
);
if (protocolIdx !== -1) {
this.#queue.splice(protocolIdx, 1);
}
this.#pendingInputQueue.splice(
this.#pendingInputQueue.indexOf(acceptableInput),
1,
@@ -329,6 +338,17 @@ export class WorkflowContext<Start = string, Stop = string, Data = unknown>
inputs,
[event, ...acceptableInputs],
);
// remove the event from the queue, in case of infinite loop
events.forEach((event) => {
const protocolIdx = this.#queue.findIndex(
(protocol) =>
protocol.type === "event" &&
protocol.event === event,
);
if (protocolIdx !== -1) {
this.#queue.splice(protocolIdx, 1);
}
});
if (events.length !== inputs.length) {
if (this.#verbose) {
console.log(
+4
View File
@@ -79,6 +79,10 @@ export class Workflow<ContextData, Start, Stop> {
return this;
}
hasStep(stepFn: StepHandler): boolean {
return this.#steps.has(stepFn);
}
removeStep(stepFn: StepHandler): this {
this.#steps.delete(stepFn);
return this;
+3
View File
@@ -1474,6 +1474,9 @@ importers:
tree-sitter-typescript:
specifier: ^0.23.0
version: 0.23.0(tree-sitter@0.22.0)
zod:
specifier: ^3.23.8
version: 3.23.8
devDependencies:
'@azure/cosmos':
specifier: ^4.1.1
+2 -1
View File
@@ -26,6 +26,7 @@
"react-dom": "^18.3.1",
"tree-sitter": "^0.22.0",
"tree-sitter-javascript": "^0.23.0",
"tree-sitter-typescript": "^0.23.0"
"tree-sitter-typescript": "^0.23.0",
"zod": "^3.23.8"
}
}
+34
View File
@@ -0,0 +1,34 @@
import { FunctionTool } from "@llamaindex/core/tools";
import { describe, expect, test } from "vitest";
import { z } from "zod";
describe("function-tool", () => {
test("zod type check", () => {
const inputSchema = z.object({
name: z.string(),
age: z.coerce.number(),
});
const tool = FunctionTool.from(
(input) => {
if (typeof input.age !== "number") {
throw new Error("Age should be a number");
}
return "Hello " + input.name + " " + input.age;
},
{
name: "get-user",
description: "Get user by name and age",
parameters: inputSchema,
},
);
{
const response = tool.call({ name: "John", age: 30 });
expect(response).toBe("Hello John 30");
}
{
// @ts-expect-error age should be a number
const response = tool.call({ name: "John", age: "30" });
expect(response).toBe("Hello John 30");
}
});
});