D
Dev SOPKnowledge Base
Search
← All topics

Claude API Advanced Patterns: Thinking, Streaming, Batches, and Agentic Loops

Advanced Claude API patterns — adaptive thinking (claude-opus-4-6), streaming with finalMessage, Batches API at 50% cost, Files API for multi-request uploads, agentic tool use loops, RAG pipeline, and multi-provider routing via Bedrock/Vertex.

claudeanthropicllmstreamingthinkingbatchesagentic-loop
Agent trigger phrases: Claude adaptive thinking · Claude streaming · Batches API · Claude agentic loop · Claude Files API · Claude Bedrock · Claude Vertex · claude-opus-4-6 thinking · extended thinking Claude

Overview

Advanced Claude API patterns for production applications. Model defaults: always claude-opus-4-6 unless explicitly overridden. Adaptive thinking, streaming, and agentic loops are the core building blocks.

Model Reference

| Model | ID | Best For | |-------|-----|----------| | Claude Opus 4.6 | claude-opus-4-6 | Complex reasoning, production default | | Claude Sonnet 4.6 | claude-sonnet-4-6 | Cost-efficient, standard tasks | | Claude Haiku 4.5 | claude-haiku-4-5 | Simple lookups, high throughput |

Never append date suffixes to model IDs. Use the exact strings above.

Adaptive Thinking (claude-opus-4-6)

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-opus-4-6",
  max_tokens: 16_000,
  thinking: { type: "adaptive" },  // Claude decides when/how much to think
  messages: [{ role: "user", content: "Design a distributed caching strategy for a SaaS app with 1M users." }],
});

Key rules:

  • thinking: { type: "adaptive" } — correct for Opus 4.6 and Sonnet 4.6
  • budget_tokensdeprecated, do not use
  • thinking.type: "enabled" with budget_tokens — only for explicitly requested older models
  • Opus 4.6 does not support assistant message prefills — use structured outputs instead

Effort Parameter

const response = await client.messages.create({
  model: "claude-opus-4-6",
  max_tokens: 8_000,
  output_config: {
    effort: "max",    // "low" | "medium" | "high" | "max"
  },
  messages: [{ role: "user", content: "..." }],
});
  • max — Opus 4.6 only, deepest reasoning
  • high — default (equivalent to omitting)
  • low — for subagents and simple tasks

Streaming (Required for Large Outputs)

Opus 4.6 supports 128K output tokens — must stream for large max_tokens:

const stream = await client.messages.stream({
  model: "claude-opus-4-6",
  max_tokens: 32_000,
  messages: [{ role: "user", content: "Write a complete API spec for..." }],
});

// Stream events
for await (const event of stream) {
  if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
    process.stdout.write(event.delta.text);
  }
}

// Get final complete message
const message = await stream.finalMessage();

Structured Outputs

Use output_config.format (not deprecated output_format):

const response = await client.messages.parse({
  model: "claude-opus-4-6",
  max_tokens: 4_000,
  output_config: {
    format: {
      type: "json_schema",
      json_schema: {
        name: "analysis",
        schema: {
          type: "object",
          properties: {
            sentiment: { type: "string", enum: ["positive", "negative", "neutral"] },
            confidence: { type: "number", minimum: 0, maximum: 1 },
            summary: { type: "string" },
          },
          required: ["sentiment", "confidence", "summary"],
        },
      },
    },
  },
  messages: [{ role: "user", content: "Analyze this review: ..." }],
});

// Validated response
const analysis = response.content[0];

Manual Agentic Loop

const tools: Anthropic.Tool[] = [
  {
    name: "search_web",
    description: "Search the web for current information",
    input_schema: {
      type: "object",
      properties: { query: { type: "string" } },
      required: ["query"],
    },
  },
];

const messages: Anthropic.MessageParam[] = [
  { role: "user", content: "Research the latest Next.js 16 features." },
];

while (true) {
  const response = await client.messages.create({
    model: "claude-opus-4-6",
    max_tokens: 4_000,
    tools,
    messages,
  });

  // Append assistant response
  messages.push({ role: "assistant", content: response.content });

  if (response.stop_reason === "end_turn") break;

  if (response.stop_reason === "tool_use") {
    const toolResults: Anthropic.ToolResultBlockParam[] = [];

    for (const block of response.content) {
      if (block.type === "tool_use") {
        const result = await executeToolCall(block.name, block.input);
        toolResults.push({
          type: "tool_result",
          tool_use_id: block.id,
          content: JSON.stringify(result),
        });
      }
    }

    messages.push({ role: "user", content: toolResults });
  }
}

Batches API (50% Cost for Non-Latency-Sensitive Work)

const batch = await client.messages.batches.create({
  requests: [
    {
      custom_id: "classify-0",
      params: {
        model: "claude-opus-4-6",
        max_tokens: 256,
        messages: [{ role: "user", content: "Classify: 'Great product!' as positive/negative/neutral" }],
      },
    },
    {
      custom_id: "classify-1",
      params: {
        model: "claude-opus-4-6",
        max_tokens: 256,
        messages: [{ role: "user", content: "Classify: 'Terrible experience' as positive/negative/neutral" }],
      },
    },
  ],
});

console.log(`Batch ID: ${batch.id}`);

// Poll until complete
let batchResult = batch;
while (batchResult.processing_status !== "ended") {
  await new Promise(r => setTimeout(r, 10_000));
  batchResult = await client.messages.batches.retrieve(batch.id);
}

// Retrieve results
for await (const result of await client.messages.batches.results(batch.id)) {
  if (result.result.type === "succeeded") {
    console.log(result.custom_id, result.result.message.content[0]);
  }
}

Files API (Reuse Documents Across Requests)

// Upload once
const file = await client.files.create({
  file: fs.createReadStream("spec.pdf"),
});
const fileId = file.id;

// Reference in multiple requests without re-uploading
const responses = await Promise.all([
  client.messages.create({
    model: "claude-opus-4-6",
    max_tokens: 4_000,
    messages: [{
      role: "user",
      content: [
        { type: "document", source: { type: "file", file_id: fileId } },
        { type: "text", text: "Summarize the architecture section." },
      ],
    }],
  }),
  client.messages.create({
    model: "claude-opus-4-6",
    max_tokens: 4_000,
    messages: [{
      role: "user",
      content: [
        { type: "document", source: { type: "file", file_id: fileId } },
        { type: "text", text: "List all the API endpoints mentioned." },
      ],
    }],
  }),
]);

RAG Pipeline

Chunk documents (500 tokens, 50-token overlap)
→ Embed with voyage-3
→ Store in pgvector
→ Hybrid search (semantic + BM25 with Reciprocal Rank Fusion)
→ Rerank
→ Augment prompt with top-K context
→ Generate with citations
// Augment prompt with retrieved context
const context = retrievedDocs.map(doc => doc.content).join("\n\n---\n\n");

const response = await client.messages.create({
  model: "claude-opus-4-6",
  max_tokens: 4_000,
  system: `Answer questions using the provided context. Cite sources with [Source N].
  
Context:
${context}`,
  messages: [{ role: "user", content: question }],
});

Compaction (Long Conversations)

// Append response.content (not just text) to preserve compaction blocks
messages.push({ role: "assistant", content: response.content });

// Enable compaction via beta header
const client = new Anthropic({
  defaultHeaders: { "anthropic-beta": "compact-2026-01-12" },
});

Compaction automatically summarizes earlier context when approaching the 200K limit.