toolcue Join the waitlist

Examples

The toolcue packages hold the client and the framework integrations; these examples use them. For the framework setups, see Integrations.

Install#

bash
pip install toolcue                      # Python 3.10+, no dependencies
npm install toolcue                      # Node 18+, no dependencies
Python extraModuleGuide
toolcue[langchain]toolcue.langchainLangChain, LangGraph
toolcue[pydantic-ai]toolcue.pydantic_aiPydantic AI
toolcue[openai-agents]toolcue.openai_agentsOpenAI Agents SDK

For the Vercel AI SDK, npm install toolcue ai zod and import from toolcue/ai-sdk (guide).

Run the quickstart#

With your access, set three environment variables and run either quickstart. It publishes the sample catalog to an agent called acme-billing and asks for the tools of a few turns.

bash
export TOOLCUE_URL=...              # the base URL you were given
export TOOLCUE_TOKEN=...            # read token: per-turn calls
export TOOLCUE_ADMIN_TOKEN=...      # admin token: publishing
python3 python/quickstart.py
node typescript/quickstart.ts       # Node 23.6+; older Node: npx tsx typescript/quickstart.ts

Keep catalog.json one folder above the scripts, as in the download paths below.

The sample catalog#

A billing assistant with 9 tools: lookups that declare the ids they return (produces), an admin-only tool (requiredRoles) and an always-on "ask the user" (core).

catalog.jsonDownload
{
  "tools": [
    {"name": "find_customer", "displayName": "Find customer",
     "description": "Look up a customer by name or email and return its customer_id.",
     "parameters": [{"name": "query", "description": "Customer name or email", "dataType": "string", "required": true}],
     "produces": ["customer_id"]},
    {"name": "get_customer_invoices", "displayName": "List a customer's invoices",
     "description": "List the invoices of one customer, newest first, with status and amount due.",
     "parameters": [{"name": "customer_id", "description": "The customer's id", "dataType": "string", "required": true}]},
    {"name": "refund_invoice", "displayName": "Refund an invoice",
     "description": "Refund a paid invoice fully or partially.",
     "parameters": [{"name": "invoice_id", "description": "The invoice id, e.g. INV-1042", "dataType": "string", "required": true},
                    {"name": "amount", "description": "Amount to refund; omit for a full refund", "dataType": "number", "required": false}],
     "examples": ["give them their money back", "refund INV-1042"]},
    {"name": "create_invoice", "displayName": "Create an invoice",
     "description": "Create and send a new invoice to a customer.",
     "parameters": [{"name": "customer_id", "description": "The customer's id", "dataType": "string", "required": true},
                    {"name": "amount", "description": "Total amount", "dataType": "number", "required": true}]},
    {"name": "change_plan", "displayName": "Change subscription plan",
     "description": "Upgrade or downgrade a customer's subscription plan.",
     "parameters": [{"name": "customer_id", "description": "The customer's id", "dataType": "string", "required": true},
                    {"name": "plan", "description": "Target plan: starter, growth or scale", "dataType": "string", "required": true}]},
    {"name": "cancel_subscription", "displayName": "Cancel subscription",
     "description": "Cancel a customer's subscription at the end of the billing period.",
     "parameters": [{"name": "customer_id", "description": "The customer's id", "dataType": "string", "required": true}]},
    {"name": "export_report", "displayName": "Export revenue report",
     "description": "Export monthly revenue, MRR and churn as a CSV file.",
     "parameters": [{"name": "month", "description": "Month as YYYY-MM", "dataType": "string", "required": true}]},
    {"name": "update_tax_settings", "displayName": "Update tax settings",
     "description": "Change the default tax rate or VAT number on invoices.",
     "parameters": [{"name": "tax_rate", "description": "Tax rate in percent", "dataType": "number", "required": false}],
     "requiredRoles": ["admin"]},
    {"name": "ask_user", "displayName": "Ask the user",
     "description": "Ask the user a clarifying question with optional choices.",
     "parameters": [{"name": "question", "description": "The question", "dataType": "string", "required": true}],
     "core": true}
  ]
}

Python#

python/quickstart.pyDownload
"""Publish the sample catalog and select tools for a few turns.

    pip install toolcue
    export TOOLCUE_URL=...  TOOLCUE_TOKEN=...  TOOLCUE_ADMIN_TOKEN=...
    python3 quickstart.py
"""

import json
import os

from toolcue import ToolcueClient

AGENT = "acme-billing"
catalog = json.load(open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "catalog.json")))

toolcue = ToolcueClient()
summary = toolcue.publish_catalog(AGENT, catalog["tools"])
print(f"published {summary['tools']} tools, {summary['edges']} dependency links")

turns = [
    ("Refund the last invoice for Globex", [], {"roles": ["agent"]}),
    ("thanks!", [], {"roles": ["agent"]}),
    ("ok, move them to the scale plan", [
        {"role": "user", "content": "what plan is Globex on?"},
        {"role": "assistant", "content": "Globex (cus_8812) is on growth.", "toolCalls": [
            {"name": "find_customer", "arguments": {"query": "Globex"},
             "result": "{\"customer_id\": \"cus_8812\", \"plan\": \"growth\"}"}]}], {"roles": ["agent"]}),
]
for message, history, user in turns:
    answer = toolcue.select(AGENT, message, history, user)
    print(f"\n> {message}\n  turn type: {answer['turnType']}, {answer['timings']['totalMs']:.0f} ms")
    for t in answer["tools"]:
        print(f"  - {t['name']:<24} {t['reason']:<10} {t['detail']}")
    if answer["resolvedParams"]:
        print(f"  values found in the conversation: {answer['resolvedParams']}")

found = toolcue.find_tools(AGENT, "set the VAT rate to 20%", user={"roles": ["admin"]})
print("\nfind_tools('set the VAT rate to 20%') as an admin:", [t["name"] for t in found["tools"]])

TypeScript#

typescript/quickstart.tsDownload
// Publish the sample catalog and select tools for one turn.
//   npm install toolcue
//   TOOLCUE_URL=... TOOLCUE_TOKEN=... TOOLCUE_ADMIN_TOKEN=... node quickstart.ts   (Node 23.6+; older: npx tsx quickstart.ts)
import { readFileSync } from "node:fs";
import { ToolcueClient } from "toolcue";

const catalog = JSON.parse(readFileSync(new URL("../catalog.json", import.meta.url), "utf8"));
const toolcue = new ToolcueClient();

const summary = await toolcue.publishCatalog("acme-billing", catalog.tools);
console.log(`published ${summary.tools} tools, ${summary.edges} dependency links`);

const answer = await toolcue.select("acme-billing", "Refund the last invoice for Globex", [], { roles: ["agent"] });
console.log(answer.turnType, `${answer.timings.totalMs.toFixed(0)} ms`);
for (const t of answer.tools) console.log(`- ${t.name.padEnd(24)} ${t.reason.padEnd(10)} ${t.detail}`);

A real LangChain agent#

Three turns with a real model (Claude or an OpenAI model), printing the tools the model got on every call: pip install "toolcue[langchain]" langchain-anthropic, set ANTHROPIC_API_KEY, and run it next to billing_tools.py.

langchain/agent.pyDownload
"""A real LangChain agent with Toolcue: three turns with a real model, showing which tools the model got each time.

    pip install -r requirements.txt          # toolcue[langchain] and a model provider
    export TOOLCUE_URL=... TOOLCUE_TOKEN=... TOOLCUE_ADMIN_TOKEN=...
    export ANTHROPIC_API_KEY=...                         # or OPENAI_API_KEY=... and OPENAI_MODEL=...
    python agent.py
"""

import os
import sys
from dataclasses import dataclass

from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware
from langchain_core.messages import HumanMessage
from langgraph.checkpoint.memory import InMemorySaver
from toolcue.langchain import ToolcueMiddleware, publish_tools

from billing_tools import EXTRAS, TOOLS

AGENT = "acme-billing"


@dataclass
class Session:
    roles: list[str]


class ShowTools(AgentMiddleware):
    """Prints the tools each model call receives (after Toolcue filtered them)."""

    def wrap_model_call(self, request, handler):
        print(f"  model gets {len(request.tools)}/{len(TOOLS) + 1} tools:",
              ", ".join(sorted(t.name for t in request.tools if hasattr(t, "name"))))
        return handler(request)


def model_name() -> str:
    if os.environ.get("ANTHROPIC_API_KEY"):
        return "anthropic:claude-opus-5-5"
    if os.environ.get("OPENAI_API_KEY") and os.environ.get("OPENAI_MODEL"):
        return "openai:" + os.environ["OPENAI_MODEL"]
    sys.exit("Set ANTHROPIC_API_KEY, or OPENAI_API_KEY and OPENAI_MODEL.")


def main():
    summary = publish_tools(AGENT, TOOLS, EXTRAS)
    print(f"published {summary['tools']} tools, {summary['edges']} dependency links")

    agent = create_agent(
        model_name(),
        tools=TOOLS,
        system_prompt="You are the billing assistant of Acme. Use the tools; never invent ids.",
        middleware=[ToolcueMiddleware(AGENT, user=lambda session: {"roles": session.roles}), ShowTools()],
        context_schema=Session,
        checkpointer=InMemorySaver(),
    )
    thread = {"configurable": {"thread_id": "demo"}}
    for text in ["Refund the last invoice for Globex", "thanks! what plan are they on now?",
                 "ok, move them to the scale plan"]:
        print(f"\n> {text}")
        result = agent.invoke({"messages": [HumanMessage(text)]}, thread, context=Session(roles=["agent"]))
        calls = [c["name"] for m in result["messages"] for c in getattr(m, "tool_calls", [])]
        print("  tool calls so far:", ", ".join(calls) or "none")
        print("  answer:", result["messages"][-1].text)


if __name__ == "__main__":
    main()