pipedog is an unofficial community port of Pipecat, not affiliated with Daily.
pipedog

Typed tools

Define LLM function calls with a Zod, Valibot or ArkType schema (Standard Schema) and get the JSON schema, inferred handler types and argument validation.

defineTool builds a function-calling tool from a Standard Schema object: Zod, Valibot, ArkType and any other library that implements the spec. One definition gives you:

  • the JSON schema the LLM needs, generated from the schema;
  • a handler whose args are typed from it;
  • validation of the arguments the LLM sends, before your handler runs.

This is pipedog's own addition, not part of Pipecat. It sits next to the ported ways to define tools (FunctionSchema, direct functions, the tool decorator), which keep working unchanged. Unlike direct functions, it never reads parameter names from function source, so it survives minification.

import { defineTool, registerTools } from "@pipedog/core/adapters/llm/schemas/standard_tool";

Define a tool

const getWeather = defineTool({
  name: "get_weather",
  description: "Get the current weather for a location.",
  parameters: z.object({
    location: z.string().describe("City name, e.g. 'Paris'"),
    unit: z.enum(["celsius", "fahrenheit"]).default("celsius"),
  }),
  // `args` is { location: string; unit: "celsius" | "fahrenheit" }, already validated.
  handler: async ({ location, unit }) => {
    return { location, unit, temperature: 21, conditions: "sunny" };
  },
});

defineTool returns a FunctionSchema that carries the handler, so it works with every provider adapter. The handler's return value is sent to the LLM as the function result. To report the result yourself, for example with FunctionCallResultProperties, call params.resultCallback (the second handler argument); the return value is then ignored.

Pass the tool's call options (cancelOnInterruption, timeoutSecs, cancellableByLlm) to defineTool directly, as you would to toolOptions.

Register it

Advertise the tool in a ToolsSchema. The LLM service registers its handler automatically:

// Advertising the tool registers its handler on the LLM service automatically.
const context = new LLMContext({
  tools: new ToolsSchema({ standardTools: [getWeather] }),
});

Or register explicitly:

// Or register explicitly, with `registerFunction` under the hood.
registerTools(llm, [getWeather]);

Invalid arguments

The arguments are checked with the schema's ~standard.validate. If they don't pass, your handler is not called. A warning is logged, and the issues go back to the LLM as the function result so it can retry:

{
  "error": "Invalid arguments. Fix the issues and call the function again.",
  "issues": [{ "message": "Invalid input: expected string, received undefined", "path": "location" }]
}

Valid arguments reach the handler as the schema's output, so defaults and transforms are applied.

Where the JSON schema comes from

defineTool asks the schema for it, using the companion spec Standard JSON Schema (~standard.jsonSchema.input, target draft-07; change it with target). The LLM supplies the input side, so a field with a default is optional in the schema it sees. Zod 4.2+ and ArkType 2.1.28+ implement it on their schemas; Valibot does through toStandardJsonSchema from @valibot/to-json-schema.

A schema that doesn't implement it needs a jsonSchema option: the JSON schema object, or a converter function.

const search = defineTool({
  name: "search",
  description: "Search the knowledge base.",
  parameters: v.object({ query: v.string() }),
  // This schema doesn't provide a JSON schema itself: pass a converter (or the object).
  jsonSchema: (schema) => toJsonSchema(schema) as Record<string, unknown>,
  handler: async ({ query }) => ({ hits: [query] }),
});

defineTool throws when there is no JSON schema, or when it isn't an object schema. $refs into $defs are inlined; recursive schemas are rejected. A FunctionSchema carries only properties and required, so other top-level keywords such as additionalProperties are not sent to the LLM.

// The handler's argument types are inferred; the JSON schema the LLM sees comes from the schema too.
const schemaForLlm = getWeather.toDefaultDict();

pipedog is an unofficial community TypeScript port of Pipecat. It is not affiliated with or endorsed by Daily or the Pipecat project.

Portions adapted from the Pipecat documentation, Copyright (c) 2024–2025 Daily, BSD-2-Clause (see apps/docs/LICENSE-pipecat-docs).

On this page