When building an agentic loop, you need to define a schema that allows an LLM to choose between different tasks. While z.union is a common approach in Zod, it translates to a JSON Schema oneOf structure which can be difficult for LLMs to parse reliably.
Instead, use a single z.object with a type field defined as an enum, and make the specific payload fields (like query or urls) .optional(). This approach is more robust for Structured Outputs.
import { z } from "zod";
export const actionSchema = z.object({
type:
z
.enum(["search", "scrape", "answer"])
.describe(
`The type of action to take.
- 'search': Search the web for more information.
- 'scrape': Scrape a URL.
- 'answer': Answer the user's question and complete the loop.`,
),
query:
z
.string()
.describe(
"The query to search for. Required if type is 'search'.",
)
.optional(),
urls:
z
.array(z.string())
.describe(
"The URLs to scrape. Required if type is 'scrape'.",
)
.optional(),
});