Skip to content

Claude and LLMs ​

bash
npm install @flattype/ai
ts
import Anthropic from "@anthropic-ai/sdk";
import { countTokens, fromModelOutput, outputInstructions, toPrompt } from "@flattype/ai";

const client = new Anthropic();

// 1. Data in: Flat Notation inside <data> tags, with a one-line legend the model reads once.
const response = await client.beta.messages.create({
  model: "claude-opus-5-5",
  max_tokens: 16000,
  betas: ["server-side-fallback-2026-07-01"],
  fallbacks: "default", // if a safety classifier declines, retry on the recommended fallback model
  messages: [{
    role: "user",
    content: `${toPrompt(orders, { name: "orders" })}\n\nTotal spent per customer?\n\n${outputInstructions([{ customer: "Alice", spent: 120 }])}`,
  }],
});

// 2. Data out: tolerant of tags, fences, prose and JSON, then validated into typed models.
const reply = response.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
const totals = fromModelOutput(reply, { model: Total, list: true }); // Total[]

// 3. Measure it with Claude's own tokenizer (count_tokens is free).
const { json, flat, saved } = await countTokens(client, orders);

Tool results shrink too, which matters most for agents that call tools over and over:

ts
import { claudeToolResult, toolResult } from "@flattype/ai";

// MCP server (@modelcontextprotocol/sdk): returns a CallToolResult with Flat Notation text
server.registerTool("list_orders", { description: "…" }, async () => toolResult(await db.orders()));

// Claude API tool loop: a tool_result block for the next user message
messages.push({ role: "user", content: [claudeToolResult(toolUse.id, await runTool(toolUse))] });
APIWhat it does
toPrompt(data, { name, legend })<data format="flat-notation"> plus a one-line legend. The block is still valid Flat Notation; put the legend in your system prompt once with legend: false
outputInstructions(example)Asks the model to answer in Flat Notation and shows the exact shape you expect
fromModelOutput(reply, { model, list, onWarning })Finds the data (last <data> block, code fence, or the text itself), skips prose lines, and accepts JSON too. Repairs go to onWarning, with their line. JSON that doesn't parse throws ModelOutputError, saying where it breaks: The data is JSON, but it is broken at line 1, column 22261: …"language" ⟶ "Galician"…
countTokens(client, data, { model }){ json, flat, saved } in real Claude tokens, minus per-message overhead. Takes any client with messages.countTokens
toolResult(data, { structured }) / claudeToolResult(id, data)MCP CallToolResult and Claude API tool_result blocks

See examples/ai-claude for a token report, a full Claude round trip, and a runnable MCP server (claude mcp add flattype-orders -- node mcp-server.mjs).

Released under the MIT License.