-
Notifications
You must be signed in to change notification settings - Fork 122
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Henry Fontanier
committed
Feb 18, 2025
1 parent
b33960e
commit 1eeefa1
Showing
11 changed files
with
561 additions
and
165 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
async function main() { | ||
// | ||
} | ||
|
||
main().catch(console.error); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import { describe, expect, it } from "bun:test"; | ||
import { z } from "zod"; | ||
import { generateToolDocs } from "./helpers"; | ||
import { defineTool } from "../tools/helpers"; | ||
import { ToolOutput } from "../tools/types"; | ||
|
||
describe("generateToolDocs", () => { | ||
it("should generate docs for a simple tool", () => { | ||
const simpleTool = defineTool( | ||
"A simple test function", | ||
z.object({ | ||
name: z.string().describe("The name parameter"), | ||
}), | ||
z.string().describe("The return value"), | ||
async () => ({ type: "success", result: "test" }) | ||
); | ||
|
||
const docs = generateToolDocs({ simpleTool }); | ||
expect(docs).toContain("Available functions:"); | ||
expect(docs).toContain( | ||
"All functions may return None if they fail (check for None before accessing the result)." | ||
); | ||
expect(docs).toContain( | ||
"simpleTool(name): async function - A simple test function" | ||
); | ||
expect(docs).toContain("* name: The name parameter"); | ||
expect(docs).toContain("Returns:"); | ||
expect(docs).toContain("The return value"); | ||
}); | ||
|
||
it("should generate docs for a tool with complex types", () => { | ||
const complexTool = defineTool( | ||
"A complex test function", | ||
z.object({ | ||
user: z | ||
.object({ | ||
name: z.string().describe("User's name"), | ||
age: z.number().describe("User's age"), | ||
}) | ||
.describe("User object"), | ||
options: z.array(z.string()).describe("List of options"), | ||
}), | ||
z.object({ | ||
id: z.number().describe("User ID"), | ||
settings: z | ||
.array( | ||
z.object({ | ||
key: z.string().describe("Setting key"), | ||
value: z.string().describe("Setting value"), | ||
}) | ||
) | ||
.describe("User settings"), | ||
}), | ||
async () => ({ type: "success", result: { id: 1, settings: [] } }) | ||
); | ||
|
||
const docs = generateToolDocs({ complexTool }); | ||
expect(docs).toContain( | ||
"complexTool(user, options): async function - A complex test function" | ||
); | ||
expect(docs).toContain("* user: dictionary with keys:"); | ||
expect(docs).toContain(" * name: User's name"); | ||
expect(docs).toContain(" * age: User's age"); | ||
expect(docs).toContain("* options: array of string"); | ||
expect(docs).toContain("Returns:"); | ||
expect(docs).toContain("dictionary with keys:"); | ||
expect(docs).toContain("* id: User ID"); | ||
expect(docs).toContain("* settings: array of dictionary with keys:"); | ||
expect(docs).toContain(" * key: Setting key"); | ||
expect(docs).toContain(" * value: Setting value"); | ||
}); | ||
|
||
it("should handle multiple tools", () => { | ||
const tool1 = defineTool( | ||
"First tool", | ||
z.object({ a: z.string() }), | ||
z.number(), | ||
async () => ({ type: "success", result: 1 }) | ||
); | ||
|
||
const tool2 = defineTool( | ||
"Second tool", | ||
z.object({ b: z.boolean() }), | ||
z.string(), | ||
async () => ({ type: "success", result: "test" }) | ||
); | ||
|
||
const docs = generateToolDocs({ tool1, tool2 }); | ||
expect(docs).toContain("tool1(a): async function - First tool"); | ||
expect(docs).toContain("tool2(b): async function - Second tool"); | ||
}); | ||
|
||
it("should handle tools with nested output types", () => { | ||
const outputTool = defineTool( | ||
"Output test function", | ||
z.object({ input: z.string() }), | ||
z.string(), | ||
async () => ({ type: "success", result: "test" }) | ||
); | ||
|
||
const docs = generateToolDocs({ outputTool }); | ||
// Should only show the success case type | ||
expect(docs).toContain("Returns:"); | ||
expect(docs).toContain("string"); | ||
// Should not show the discriminated union structure | ||
expect(docs).not.toContain("type:"); | ||
expect(docs).not.toContain("result:"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import type { Tool } from "../tools/types"; | ||
import { z } from "zod"; | ||
|
||
function describeZodType(schema: z.ZodType, indent: string = ""): string { | ||
if (schema instanceof z.ZodArray) { | ||
return `array of ${describeZodType(schema.element, indent + " ")}`; | ||
} else if (schema instanceof z.ZodObject) { | ||
let desc = "dictionary with keys:\n"; | ||
for (const [fieldName, fieldSchema] of Object.entries(schema.shape)) { | ||
desc += `${indent} * ${fieldName}: ${describeZodType( | ||
fieldSchema as z.ZodType, | ||
indent + " " | ||
) | ||
.split("\n") | ||
.join("\n" + indent)}\n`; | ||
} | ||
return desc; | ||
} else if (schema instanceof z.ZodUnion && schema.options.length === 2) { | ||
// Check if this is a ToolOutput schema | ||
const successCase = schema.options.find( | ||
(opt: z.ZodType) => | ||
opt instanceof z.ZodObject && opt.shape.type?.value === "success" | ||
) as z.ZodObject<any> | undefined; | ||
|
||
if (successCase?.shape.result) { | ||
return describeZodType(successCase.shape.result, indent); | ||
} | ||
// If we can't handle this union type, just describe it as a union | ||
return `union of ${schema.options | ||
.map((opt: z.ZodType) => describeZodType(opt, indent + " ")) | ||
.join(" | ")}`; | ||
} else { | ||
return ( | ||
schema.description || | ||
schema.constructor.name.replace("Zod", "").toLowerCase() || | ||
"any" | ||
); | ||
} | ||
} | ||
|
||
export function generateToolDocs(tools: Record<string, Tool>): string { | ||
let docs = "Available functions:\n"; | ||
docs += | ||
"Note: All functions may return None if they fail (check for None before accessing the result).\n\n"; | ||
|
||
for (const [fnName, { description, input, output }] of Object.entries( | ||
tools | ||
)) { | ||
// Function signature with description | ||
const inputObject = input as z.ZodObject<any>; | ||
|
||
docs += `- ${fnName}(${Object.keys(inputObject.shape).join( | ||
", " | ||
)}): async function - ${description}\n`; | ||
|
||
// Input parameters | ||
docs += " Parameters:\n"; | ||
for (const [paramName, paramSchema] of Object.entries(inputObject.shape)) { | ||
docs += ` * ${paramName}: ${describeZodType( | ||
paramSchema as z.ZodType, | ||
" " | ||
)}\n`; | ||
} | ||
|
||
// Output fields | ||
docs += " Returns:\n"; | ||
docs += describeZodType(output, " "); | ||
} | ||
|
||
return docs; | ||
} |
Oops, something went wrong.