{"slug":"claude-api-tool-use","title":"Claude API: Tool Use, Agentic Loops, and Structured Outputs","tags":["claude","anthropic","tool-use","agents","llm","typescript"],"agent_summary":"Claude API tool use patterns in TypeScript — tool runner, manual agentic loops, structured outputs with output_config, streaming, and production best practices.","trigger_phrases":["Claude API tools","Claude tool use","Anthropic tool use","agentic loop","Claude structured output","Claude streaming","betaZodTool"],"runnable":false,"markdown":"\n## Overview\n\nClaude API tool use for TypeScript applications — from the simplest tool runner to full agentic loops. Uses `claude-sonnet-4-6` as the capable default; swap to `claude-opus-4-6` for complex reasoning tasks.\n\n## Model Defaults\n\n| Task | Model | Why |\n|------|-------|-----|\n| Production default | `claude-sonnet-4-6` | Cost-efficient, highly capable |\n| Complex reasoning | `claude-opus-4-6` | Maximum reasoning depth |\n| Simple tasks | `claude-haiku-4-5` | Cheapest, fast |\n\nUse exact model ID strings — never append date suffixes.\n\n## Basic API Call\n\n```typescript\nimport Anthropic from \"@anthropic-ai/sdk\";\n\nconst client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });\n\nconst response = await client.messages.create({\n  model: \"claude-sonnet-4-6\",\n  max_tokens: 2048,\n  system: \"You are a helpful assistant. Be concise.\",\n  messages: [\n    { role: \"user\", content: \"Summarize the key points of async/await in JavaScript.\" }\n  ],\n});\n\nconst text = response.content[0].type === \"text\" ? response.content[0].text : \"\";\n```\n\n## Tool Use — Tool Runner (Recommended)\n\nThe tool runner handles the agentic loop automatically:\n\n```typescript\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport { z } from \"zod\";\n\nconst client = new Anthropic();\n\nconst tools: Anthropic.Tool[] = [\n  {\n    name: \"get_weather\",\n    description: \"Get current weather for a city\",\n    input_schema: {\n      type: \"object\" as const,\n      properties: {\n        city: { type: \"string\", description: \"City name\" },\n        units: { type: \"string\", enum: [\"celsius\", \"fahrenheit\"] },\n      },\n      required: [\"city\"],\n    },\n  },\n];\n\nasync function getWeather(city: string, units = \"celsius\") {\n  // Real implementation would call a weather API\n  return { temperature: 22, condition: \"Sunny\", city, units };\n}\n\nasync function askClaude(userMessage: string) {\n  const messages: Anthropic.MessageParam[] = [\n    { role: \"user\", content: userMessage }\n  ];\n\n  while (true) {\n    const response = await client.messages.create({\n      model: \"claude-sonnet-4-6\",\n      max_tokens: 4096,\n      tools,\n      messages,\n    });\n\n    if (response.stop_reason === \"end_turn\") {\n      const textBlock = response.content.find(b => b.type === \"text\");\n      return textBlock?.text ?? \"\";\n    }\n\n    if (response.stop_reason === \"tool_use\") {\n      messages.push({ role: \"assistant\", content: response.content });\n\n      const toolResults: Anthropic.ToolResultBlockParam[] = [];\n      for (const block of response.content) {\n        if (block.type !== \"tool_use\") continue;\n        const input = block.input as { city: string; units?: string };\n        const result = await getWeather(input.city, input.units);\n        toolResults.push({\n          type: \"tool_result\",\n          tool_use_id: block.id,\n          content: JSON.stringify(result),\n        });\n      }\n      messages.push({ role: \"user\", content: toolResults });\n    }\n  }\n}\n```\n\n## Structured Outputs\n\n```typescript\n// Prefer output_config over deprecated output_format\nconst response = await client.messages.create({\n  model: \"claude-sonnet-4-6\",\n  max_tokens: 1024,\n  output_config: {\n    format: {\n      type: \"json_schema\",\n      json_schema: {\n        name: \"article_analysis\",\n        schema: {\n          type: \"object\",\n          properties: {\n            title: { type: \"string\" },\n            sentiment: { type: \"string\", enum: [\"positive\", \"negative\", \"neutral\"] },\n            keyPoints: { type: \"array\", items: { type: \"string\" } },\n            score: { type: \"number\", minimum: 0, maximum: 10 },\n          },\n          required: [\"title\", \"sentiment\", \"keyPoints\", \"score\"],\n        },\n      },\n    },\n  },\n  messages: [{ role: \"user\", content: `Analyze: ${articleText}` }],\n});\n```\n\n## Streaming (Use for Long Outputs)\n\n```typescript\n// Stream to avoid HTTP timeout on long responses\nconst stream = client.messages.stream({\n  model: \"claude-sonnet-4-6\",\n  max_tokens: 8192,\n  messages: [{ role: \"user\", content: prompt }],\n});\n\nstream.on(\"text\", (text) => process.stdout.write(text));\nconst finalMessage = await stream.finalMessage();\n// finalMessage.usage has token counts\n```\n\n## Adaptive Thinking (Opus 4.6)\n\n```typescript\nconst response = await client.messages.create({\n  model: \"claude-opus-4-6\",\n  max_tokens: 16000,\n  thinking: { type: \"adaptive\" },   // Claude decides when and how much to think\n  messages: [{ role: \"user\", content: complexProblem }],\n});\n```\n\nNever use `budget_tokens` with Opus 4.6 or Sonnet 4.6 — it is deprecated. Use `adaptive` instead.\n\n## Prompt Caching (Cost Reduction)\n\n```typescript\nconst response = await client.messages.create({\n  model: \"claude-sonnet-4-6\",\n  max_tokens: 2048,\n  system: [\n    {\n      type: \"text\",\n      text: longSystemPrompt,           // This gets cached\n      cache_control: { type: \"ephemeral\" },\n    },\n  ],\n  messages: [{ role: \"user\", content: \"Question about the document.\" }],\n});\n// Subsequent calls reuse cached system prompt — 90% cost reduction on input tokens\n```\n\n## Common Pitfalls\n\n| Mistake | Fix |\n|---------|-----|\n| Date-suffixed model IDs | Use exact IDs: `claude-sonnet-4-6` |\n| `budget_tokens` on Opus/Sonnet 4.6 | Use `thinking: {type: \"adaptive\"}` |\n| Deprecated `output_format` | Use `output_config: {format: {...}}` |\n| Prefill on Opus 4.6 | Removed — use `output_config` instead |\n| Blocking fetch for large outputs | Use streaming with `.stream()` |\n| String-matching error messages | Use typed exceptions: `Anthropic.RateLimitError` |\n","html":"<h2>Overview</h2>\n<p>Claude API tool use for TypeScript applications — from the simplest tool runner to full agentic loops. Uses <code>claude-sonnet-4-6</code> as the capable default; swap to <code>claude-opus-4-6</code> for complex reasoning tasks.</p>\n<h2>Model Defaults</h2>\n<p>| Task | Model | Why |\n|------|-------|-----|\n| Production default | <code>claude-sonnet-4-6</code> | Cost-efficient, highly capable |\n| Complex reasoning | <code>claude-opus-4-6</code> | Maximum reasoning depth |\n| Simple tasks | <code>claude-haiku-4-5</code> | Cheapest, fast |</p>\n<p>Use exact model ID strings — never append date suffixes.</p>\n<h2>Basic API Call</h2>\n<pre><code class=\"language-typescript\">import Anthropic from \"@anthropic-ai/sdk\";\n\nconst client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });\n\nconst response = await client.messages.create({\n  model: \"claude-sonnet-4-6\",\n  max_tokens: 2048,\n  system: \"You are a helpful assistant. Be concise.\",\n  messages: [\n    { role: \"user\", content: \"Summarize the key points of async/await in JavaScript.\" }\n  ],\n});\n\nconst text = response.content[0].type === \"text\" ? response.content[0].text : \"\";\n</code></pre>\n<h2>Tool Use — Tool Runner (Recommended)</h2>\n<p>The tool runner handles the agentic loop automatically:</p>\n<pre><code class=\"language-typescript\">import Anthropic from \"@anthropic-ai/sdk\";\nimport { z } from \"zod\";\n\nconst client = new Anthropic();\n\nconst tools: Anthropic.Tool[] = [\n  {\n    name: \"get_weather\",\n    description: \"Get current weather for a city\",\n    input_schema: {\n      type: \"object\" as const,\n      properties: {\n        city: { type: \"string\", description: \"City name\" },\n        units: { type: \"string\", enum: [\"celsius\", \"fahrenheit\"] },\n      },\n      required: [\"city\"],\n    },\n  },\n];\n\nasync function getWeather(city: string, units = \"celsius\") {\n  // Real implementation would call a weather API\n  return { temperature: 22, condition: \"Sunny\", city, units };\n}\n\nasync function askClaude(userMessage: string) {\n  const messages: Anthropic.MessageParam[] = [\n    { role: \"user\", content: userMessage }\n  ];\n\n  while (true) {\n    const response = await client.messages.create({\n      model: \"claude-sonnet-4-6\",\n      max_tokens: 4096,\n      tools,\n      messages,\n    });\n\n    if (response.stop_reason === \"end_turn\") {\n      const textBlock = response.content.find(b => b.type === \"text\");\n      return textBlock?.text ?? \"\";\n    }\n\n    if (response.stop_reason === \"tool_use\") {\n      messages.push({ role: \"assistant\", content: response.content });\n\n      const toolResults: Anthropic.ToolResultBlockParam[] = [];\n      for (const block of response.content) {\n        if (block.type !== \"tool_use\") continue;\n        const input = block.input as { city: string; units?: string };\n        const result = await getWeather(input.city, input.units);\n        toolResults.push({\n          type: \"tool_result\",\n          tool_use_id: block.id,\n          content: JSON.stringify(result),\n        });\n      }\n      messages.push({ role: \"user\", content: toolResults });\n    }\n  }\n}\n</code></pre>\n<h2>Structured Outputs</h2>\n<pre><code class=\"language-typescript\">// Prefer output_config over deprecated output_format\nconst response = await client.messages.create({\n  model: \"claude-sonnet-4-6\",\n  max_tokens: 1024,\n  output_config: {\n    format: {\n      type: \"json_schema\",\n      json_schema: {\n        name: \"article_analysis\",\n        schema: {\n          type: \"object\",\n          properties: {\n            title: { type: \"string\" },\n            sentiment: { type: \"string\", enum: [\"positive\", \"negative\", \"neutral\"] },\n            keyPoints: { type: \"array\", items: { type: \"string\" } },\n            score: { type: \"number\", minimum: 0, maximum: 10 },\n          },\n          required: [\"title\", \"sentiment\", \"keyPoints\", \"score\"],\n        },\n      },\n    },\n  },\n  messages: [{ role: \"user\", content: `Analyze: ${articleText}` }],\n});\n</code></pre>\n<h2>Streaming (Use for Long Outputs)</h2>\n<pre><code class=\"language-typescript\">// Stream to avoid HTTP timeout on long responses\nconst stream = client.messages.stream({\n  model: \"claude-sonnet-4-6\",\n  max_tokens: 8192,\n  messages: [{ role: \"user\", content: prompt }],\n});\n\nstream.on(\"text\", (text) => process.stdout.write(text));\nconst finalMessage = await stream.finalMessage();\n// finalMessage.usage has token counts\n</code></pre>\n<h2>Adaptive Thinking (Opus 4.6)</h2>\n<pre><code class=\"language-typescript\">const response = await client.messages.create({\n  model: \"claude-opus-4-6\",\n  max_tokens: 16000,\n  thinking: { type: \"adaptive\" },   // Claude decides when and how much to think\n  messages: [{ role: \"user\", content: complexProblem }],\n});\n</code></pre>\n<p>Never use <code>budget_tokens</code> with Opus 4.6 or Sonnet 4.6 — it is deprecated. Use <code>adaptive</code> instead.</p>\n<h2>Prompt Caching (Cost Reduction)</h2>\n<pre><code class=\"language-typescript\">const response = await client.messages.create({\n  model: \"claude-sonnet-4-6\",\n  max_tokens: 2048,\n  system: [\n    {\n      type: \"text\",\n      text: longSystemPrompt,           // This gets cached\n      cache_control: { type: \"ephemeral\" },\n    },\n  ],\n  messages: [{ role: \"user\", content: \"Question about the document.\" }],\n});\n// Subsequent calls reuse cached system prompt — 90% cost reduction on input tokens\n</code></pre>\n<h2>Common Pitfalls</h2>\n<p>| Mistake | Fix |\n|---------|-----|\n| Date-suffixed model IDs | Use exact IDs: <code>claude-sonnet-4-6</code> |\n| <code>budget_tokens</code> on Opus/Sonnet 4.6 | Use <code>thinking: {type: \"adaptive\"}</code> |\n| Deprecated <code>output_format</code> | Use <code>output_config: {format: {...}}</code> |\n| Prefill on Opus 4.6 | Removed — use <code>output_config</code> instead |\n| Blocking fetch for large outputs | Use streaming with <code>.stream()</code> |\n| String-matching error messages | Use typed exceptions: <code>Anthropic.RateLimitError</code> |</p>\n"}