Tool use (Claude / OpenAI / Vercel / Mastra / LangChain)
The SDK ships an asTool() helper that returns ready-to-use tool
definitions for every major function-calling API: Anthropic, OpenAI
Chat Completions, OpenAI Agents SDK, Vercel AI SDK, Mastra, and
LangChain / LangGraph.
Anthropic Claude
Section titled “Anthropic Claude”import Anthropic from "@anthropic-ai/sdk";import { PerSQL } from "@persql/sdk";
const persql = new PerSQL({ token: process.env.PERSQL_TOKEN! });const db = persql.database("acme/orders");const tool = db.asTool("query_orders").anthropic;
const anthropic = new Anthropic();const response = await anthropic.messages.create({ model: "claude-opus-4-7", max_tokens: 1024, tools: [tool], messages: [{ role: "user", content: "How many orders shipped last week?" }],});
for (const block of response.content) { if (block.type === "tool_use" && block.name === tool.name) { const result = await db.runTool( block.input as { sql: string; params?: unknown[] } ); // Pass `result.data` back to the model in the next turn. }}OpenAI
Section titled “OpenAI”import OpenAI from "openai";import { PerSQL } from "@persql/sdk";
const persql = new PerSQL({ token: process.env.PERSQL_TOKEN! });const db = persql.database("acme/orders");const tool = db.asTool("query_orders").openai;
const openai = new OpenAI();const completion = await openai.chat.completions.create({ model: "gpt-5", messages: [{ role: "user", content: "How many orders shipped last week?" }], tools: [tool],});
const call = completion.choices[0].message.tool_calls?.[0];if (call?.type === "function" && call.function.name === tool.function.name) { const args = JSON.parse(call.function.arguments) as { sql: string; params?: unknown[]; }; const result = await db.runTool(args);}Vercel AI SDK
Section titled “Vercel AI SDK”import { streamText } from "ai";
const tool = db.asTool("query_orders");const result = streamText({ model: openai("gpt-5"), prompt: "How many orders shipped last week?", tools: tool.aiSdk(),});Mastra
Section titled “Mastra”import { createTool } from "@mastra/core";
const { query_orders } = db.asTool("query_orders").mastra();const tool = createTool(query_orders);LangChain / LangGraph
Section titled “LangChain / LangGraph”import { DynamicStructuredTool } from "@langchain/core/tools";
const [{ name, description, schema, invoke }] = db.asTool("query_orders").langchain();const tool = new DynamicStructuredTool({ name, description, schema, func: invoke });invoke is also usable directly inside a custom LangGraph node — pass
it as the node’s handler if you’d rather skip the wrapper class.
OpenAI Agents SDK
Section titled “OpenAI Agents SDK”import { Agent } from "@openai/agents";
const [orderTool] = db.asTool("query_orders").openaiAgents();const agent = new Agent({ name: "data", tools: [orderTool] });Typed per-table tools (asTools)
Section titled “Typed per-table tools (asTools)”asTool() exposes one fat SQL function. For LLMs that pick tools by
intent rather than write SQL, use asTools() — it emits a bundle
covering the full agent surface:
Per-table CRUD (one set per user table):
select_<table>(where?, orderBy?, limit?)count_<table>(where?)describe_<table>()insert_<table>(values)update_<table>(where, set)—whereis requireddelete_<table>(where)—whereis required
Discovery — what an agent should call before writing SQL:
describe_database()— schema graph + stored semantic descriptionssearch_schema(q, limit?)— natural-language ranked searchschema_doctor()— lint LLM-hostile patterns
Safety primitives — pre-flight risky writes:
propose_mutation(sql, params?, ttlSec?)— EXPLAIN-validate, return a single-useexecutionTokenapply_mutation(executionToken)— redeem the token
Telemetry / branches:
recent_queries(since?, status?, pageSize?)— engine query logclaim_branch(purpose?, ttlSec?, role?)— fresh isolated branch + scoped token in one call (admin-only)branches_list / branches_create / branches_delete / branches_preview_merge / branches_merge— full branch CRUD
Plus the fallback sql_query(sql, params) for joins, aggregates,
and DDL the typed tools can’t express.
const tools = await db.asTools();
const reply = await anthropic.messages.create({ model: "claude-opus-4-7", tools: tools.anthropic, messages: [...],});
for (const block of reply.content) { if (block.type === "tool_use") { const result = await tools.run(block.name, block.input); }}Smaller models hit per-tool inputs at much higher correctness than
they write SQL. The fat sql_query tool is still in the bundle for
joins, aggregates, and DDL the typed tools can’t express.
The token’s role still gates execution server-side — a readonly
token calling insert_* gets a 403. update_* and delete_*
require a non-empty where (use sql_query for unfiltered writes).
Schema introspection
Section titled “Schema introspection”Most agents work better when they can discover the schema. Expose it as a second tool:
import { z } from "zod";
const tools = [ db.asTool("query_database").anthropic, { name: "list_tables", description: `List user-defined tables in the "${db.namespace}/${db.slug}" database.`, input_schema: { type: "object", properties: {} }, },];
// In your tool dispatcher:if (toolName === "list_tables") { return await db.tables();}