Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.agentium.in/llms.txt

Use this file to discover all available pages before exploring further.

Tool Polish

defineTool() exposes three additional fields in v2.0 that materially improve reliability and cost.

1. strict: true — guaranteed valid arguments

import { defineTool } from "@agentium/core";
import { z } from "zod";

const lookup = defineTool({
  name: "lookupOrder",
  description: "Look up an order by ID.",
  parameters: z.object({ orderId: z.string() }),
  strict: true,                              // ← new
  execute: async ({ orderId }) => { /* ... */ },
});
When strict: true:
  • additionalProperties: false is appended to the generated JSON Schema, so the LLM cannot send junk extra keys.
  • For OpenAI-family models, strict: true flows through to the API’s structured-output mode, which guarantees the model’s tool call is valid JSON matching your schema (no retries, no parse errors).
When to use: any tool whose schema you want to enforce hard — DB queries, payment operations, anything where “the LLM made up a field” would be embarrassing. Trade-offs:
  • OpenAI strict mode disables certain features (anyOf, default values on optional fields). If your schema fails to compile in strict mode, drop the flag.
  • Anthropic / Google providers ignore the strict flag at the API level but the JSON Schema is still tightened, which the model usually respects.

2. inputExamples — N-shot demonstrations

const search = defineTool({
  name: "search",
  description: "Search the web for current information.",
  parameters: z.object({
    query: z.string(),
    timeRange: z.enum(["day", "week", "month", "year", "all"]).optional(),
  }),
  inputExamples: [
    { query: "node 22 release notes" },
    { query: "OpenAI o1 pricing", timeRange: "month" },
    { query: "rust async tutorial", timeRange: "year" },
  ],
  execute: async ({ query, timeRange }) => { /* ... */ },
});
The framework appends a formatted examples section to the tool description that the LLM sees:
Search the web for current information.

Examples:
1. {"query":"node 22 release notes"}
2. {"query":"OpenAI o1 pricing","timeRange":"month"}
3. {"query":"rust async tutorial","timeRange":"year"}
Why this works: LLMs are extraordinarily good at pattern-matching from examples. A single well-chosen example often beats two paragraphs of prose explanation. When to use:
  • Tools with enums or specific value formats (timezones, country codes, ISO dates)
  • Tools where the LLM tends to over- or under-specify (e.g. always omits an optional field that improves results)
  • New tools you don’t have months of usage data on yet
Tips:
  • 3–5 examples is the sweet spot. More doesn’t help and increases prompt cost.
  • Show variety, not similarity. The model already understands that strings can vary; show it the kinds of variation that matter.
  • Don’t include obvious “bad” examples — the model can latch onto them.

3. toModelOutput — async result transformer

const fetchOrder = defineTool({
  name: "fetchOrder",
  description: "Get order details from the warehouse system.",
  parameters: z.object({ orderId: z.string() }),
  execute: async ({ orderId }) => {
    // Real implementation returns the full order with 500 line items.
    return JSON.stringify(await db.orders.fetchFull(orderId));
  },
  toModelOutput: async (rawResult, ctx) => {
    const text = typeof rawResult === "string" ? rawResult : rawResult.content;
    const order = JSON.parse(text);
    // Shrink to just what the LLM cares about.
    return JSON.stringify({
      orderId: order.orderId,
      status: order.status,
      total: order.total,
      itemCount: order.items.length,
      firstThreeItems: order.items.slice(0, 3),
    });
  },
});
Execution order:
tool.execute  ──▶  rawResult


          toModelOutput (if set)        ──▶  transformed


        artifact auto-conversion          ──▶ pointer (if oversized)


              cache, event bus, LLM context
toModelOutput runs BEFORE the artifact auto-converter, so if your transform shrinks the output below the threshold, no pointer is created. Signature:
toModelOutput?: (
  result: string | ToolResult,
  ctx: RunContext,
) => Promise<string | ToolResult>;
ctx gives you access to userId, tenantId, sessionState, eventBus — useful for tenant-aware transforms (redact PII per tenant policy, etc.). When to use:
  • Compress / summarize / redact tool output without modifying the underlying tool
  • Convert verbose API responses to LLM-friendly shapes
  • Strip secrets that shouldn’t reach the model (API keys, internal IDs)
  • A/B test response formats without touching execute
toModelOutput vs Memory Pointers:
ScenarioUse
You know exactly what fields the LLM needstoModelOutput
You want the LLM to opt-in to expanding the data laterMemory Pointers
BothStack them: toModelOutput summarizes, then art: wraps if still too big

Putting it together

const fetchOrder = defineTool({
  name: "fetchOrder",
  description: "Get order details from the warehouse system.",
  parameters: z.object({ orderId: z.string() }),
  strict: true,
  inputExamples: [{ orderId: "ord_abc123" }, { orderId: "ord_9f3-2025" }],
  toModelOutput: async (result) => {
    const data = JSON.parse(typeof result === "string" ? result : result.content);
    return JSON.stringify({ id: data.orderId, status: data.status, itemCount: data.items.length });
  },
  execute: async ({ orderId }) => JSON.stringify(await db.orders.fetchFull(orderId)),
});
All three flags compose cleanly: strict ensures valid input, examples teach the model what valid input looks like, toModelOutput keeps the response footprint small.

See also