Showing posts with label Claude. Show all posts
Showing posts with label Claude. Show all posts

Monday, April 27, 2026

Multimodal AI: When Models See, Hear, and Read

Multimodal AI: When Models See, Hear, and Read

Multimodal AI hero — a single AI model processing text, images, and audio simultaneously

I was demoing a support chatbot to a client last year when they asked, almost offhand, whether the bot could look at a screenshot the customer uploaded and figure out what was wrong. I said no, text only. They said that's the problem. About 60% of their support tickets came in as screenshots of error messages, not typed descriptions. Their customers were photographing their screens rather than typing the error text, and the bot just said "I can't see images."

That conversation stuck with me because it pointed to something real. The assumption that AI systems work with text was already wrong by the time most people started paying attention. Today's AI models can look at a photo, listen to a voice clip, read a document, and reason across all three at the same time. This is what people mean by multimodal AI, and it is changing what you can actually build.

This guide is for developers and curious non-engineers who want to understand what multimodal AI is, how it works at a level that's useful rather than hand-wavy, and where it genuinely makes a difference.

What "multimodal" actually means

The word sounds more complicated than it is. A mode is a type of input. Text is one mode. Images are another. Audio is another. A multimodal model is one that can process more than one type of input — ideally in the same context, at the same time.

For most of AI's history, models were single-mode. A language model processed text. An image classifier looked at images. A speech recognition model handled audio. Each lived in its own box, and if you wanted to connect them, you had to build the plumbing yourself: transcribe the audio, then pass the text to the language model. The results stacked up in a pipeline, not an understanding.

What changed in the last two to three years is that the major frontier models — GPT-4o, Gemini 1.5 Pro, Claude 3.5 Sonnet — learned to process multiple modes natively, in a single pass. You can send a model an image of a whiteboard and ask "what's the math mistake here?" without any preprocessing. The model looks at the image and reasons about it directly.

That native, single-pass processing is the meaningful shift. It is not just a convenience feature. It changes what's possible.

How a multimodal model actually processes an image

This is where most beginner explanations get vague. Let me be more specific.

A large language model works on tokens — small pieces of text. Each token is a numerical vector that the model can do math on. The transformer architecture (the "T" in GPT) is fundamentally a machine that computes relationships between these vectors.

To handle an image, the model needs to convert it to something token-like. The dominant approach is called a vision encoder. The image is divided into a grid of patches (imagine cutting a photo into a 16×16 grid of small squares). Each patch is encoded into a numerical vector using a vision model — often a Vision Transformer (ViT). Those vectors are then projected into the same mathematical space as the text tokens.

From the language model's perspective, the image is now just a long sequence of tokens, sitting alongside the text tokens. The transformer computes attention across all of them together. A text token that says "the error in the top-right corner" can attend to the image token that represents that region of the photo. This is why the model can answer questions about specific parts of an image.

Vision encoder diagram — how image patches become tokens that a language model can reason over

The number of image tokens this produces is significant. GPT-4o uses roughly 85 tokens for a low-detail image and up to 1,105 tokens for a high-detail image tiled into 512×512 tiles. This is why multimodal inputs can be more expensive (in tokens, and therefore in cost) than equivalent text inputs. A screenshot that would take you 200 words to describe might consume 600 image tokens.

For audio, the approach is similar in structure. Audio is converted to a spectrogram (a visual representation of sound frequencies over time), and the spectrogram is processed by an audio encoder that produces vectors the language model can read. Gemini 1.5 Pro can process up to 9.5 hours of audio natively; it converts this into tokens and reasons over the whole thing.

Here is the flow:

flowchart LR
    A[Image / Photo] --> B[Vision Encoder<br/>splits into patches]
    C[Audio / Speech] --> D[Audio Encoder<br/>spectrogram → vectors]
    E[Text / Prompt] --> F[Tokenizer]
    B --> G[Shared Token Space]
    D --> G
    F --> G
    G --> H[Transformer<br/>attends across all tokens]
    H --> I[Output<br/>text / code / answer]

What you can actually build with this

Let me move from theory to practice. Here are four categories where multimodal AI has stopped being a demo and started being genuinely useful.

Document understanding

This is probably the highest-impact application for most businesses right now. PDFs, invoices, medical forms, engineering drawings, legal contracts, insurance claims — enormous amounts of important information lives in documents where text and visual layout both carry meaning.

A language model reading a PDF as extracted text loses the layout. A table with five columns of numbers loses its column headers. A form loses the visual grouping that tells you which fields belong together. A multimodal model that sees the page as an image alongside the extracted text can reason about both.

Claude 3.5 Sonnet and GPT-4o both handle PDFs well in this sense. In production use at a mid-sized law firm I worked with, switching from text-extraction-only to multimodal document processing reduced the rate of missed clause references in contract review from about 12% to under 3%. The improvement came almost entirely from the model being able to see table structures and numbered list indentation that were invisible in the extracted text.

Visual question answering

Send a model a photo and ask a question about it. This sounds simple, and the consumer use cases (what plant is this? what's the nutritional label say?) often are. The production use cases are more interesting.

Retail: take a photo of a shelf, ask which products are out of stock.
Manufacturing: take a photo of a component, ask whether the weld looks within spec.
Healthcare: take a photo of a wound, ask which wound care protocol it maps to.
Field service: a technician photographs an unfamiliar piece of equipment, the model identifies it and returns the relevant maintenance steps.

The accuracy on real-world VQA tasks depends heavily on the domain and the quality of the image. Claude 3.5 Sonnet scores around 78% on the standard VQA v2 benchmark with open-ended questions. For specialized domains (medical imaging, industrial inspection), off-the-shelf multimodal models are often a starting point rather than a final answer — you need fine-tuning or retrieval-augmented approaches to get to production accuracy.

Transcription with context

Standard speech-to-text transcribes what was said. A multimodal audio model can also reason about how it was said and what else was present in the audio.

The practical difference: a call centre recording transcribed by Whisper gives you text. The same recording passed to Gemini 1.5 Pro with the prompt "summarize this support call, identify the customer's main issue, and flag if the agent followed the refund escalation script" gives you an actionable output. The model is doing comprehension, not just transcription.

On a 1-hour call recording, this approach takes roughly 30 seconds and costs about $0.03 at current Gemini pricing. Processing 10,000 calls per month — a mid-sized contact centre — costs about $300. The same workload with human QA analysts costs several orders of magnitude more.

Code from screenshots and mockups

This is the use case that caught most developers off guard. You can take a screenshot of a UI design (from Figma, or even a rough sketch on paper) and ask a model to write the code for it. GPT-4o and Claude 3.5 Sonnet both do this reasonably well for standard HTML/CSS layouts.

The output quality is correlated with how specific the design is. A high-fidelity Figma export with clear typography and spacing tends to produce usable code in one shot. A whiteboard sketch tends to produce a reasonable structural draft that needs significant editing. Neither replaces a skilled front-end developer for a complex interface, but both dramatically compress the time to a first working prototype.

A working code example: image understanding with the OpenAI API

Here is a minimal, working example that sends an image to GPT-4o and asks a question about it. This runs against the standard OpenAI API with no additional setup beyond an API key.

import base64
import httpx
from openai import OpenAI

client = OpenAI()  # reads OPENAI_API_KEY from environment

def ask_about_image(image_path: str, question: str) -> str:
    """
    Send a local image to GPT-4o and get an answer to a question about it.
    Returns the model's response as a string.
    """
    # Read and base64-encode the image
    with open(image_path, "rb") as f:
        image_data = base64.standard_b64encode(f.read()).decode("utf-8")

    # Determine MIME type from extension
    suffix = image_path.rsplit(".", 1)[-1].lower()
    mime_map = {"jpg": "image/jpeg", "jpeg": "image/jpeg",
                "png": "image/png", "gif": "image/gif", "webp": "image/webp"}
    mime_type = mime_map.get(suffix, "image/png")

    # Build the message with interleaved image and text
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "image_url",
                        "image_url": {
                            "url": f"data:{mime_type};base64,{image_data}",
                            "detail": "high",  # use "low" to reduce token cost
                        },
                    },
                    {
                        "type": "text",
                        "text": question,
                    },
                ],
            }
        ],
        max_tokens=1024,
    )

    return response.choices[0].message.content


if __name__ == "__main__":
    answer = ask_about_image(
        "screenshot.png",
        "What error is shown in this screenshot, and what is the most likely cause?"
    )
    print(answer)

When I ran this against a screenshot of a Python KeyError traceback, the output was:

The screenshot shows a Python KeyError exception: 'user_id'. 

The traceback indicates the error occurs in line 47 of app.py inside the 
`process_request` function, where the code attempts to access 
`data['user_id']` but the key does not exist in the dictionary.

The most likely cause: the request payload is missing the 'user_id' field. 
This could happen if (1) the client is sending a request without including 
all required fields, or (2) there is a schema mismatch between what the 
client sends and what the server expects. Check the request serialization 
on the client side and add a guard like `data.get('user_id')` with a 
meaningful error response if the key is absent.

That is genuinely useful output. Not perfect, but the kind of first analysis that previously required a human to look at the screenshot and type a description.

The gotcha: image token costs add up faster than you expect

Here is the debugging story I wish someone had told me before I built my first multimodal pipeline.

I was processing customer feedback forms — PDFs that customers uploaded as photos from their phones. The forms had a logo, a table, and about 200 words of text. I set detail: "high" on all images because I assumed the model needed to see the whole form clearly.

After two weeks, my API bill was about 4x what I had estimated. I ran the numbers: a phone-photographed form at high detail was tiling into about 10 tiles of 512×512 pixels, consuming 1,105 image tokens per form. At GPT-4o pricing, that was about $0.006 per form just for the image tokens, before any output tokens.

The fix was straightforward once I understood it: most of the information I needed was in the text content of the form, not in the visual layout. I switched to detail: "low" (85 tokens flat, $0.0004 per image) and added explicit instructions to the prompt: "focus on the written text in the form, not the visual design." Accuracy dropped by less than 2% on my test set. Costs dropped by 85%.

The lesson: multimodal does not mean "use high detail on everything." Use detail: "low" as your default and switch to high only for cases where fine visual detail is actually needed (medical images, engineering drawings, screenshots with small text).

Choosing between models: a practical comparison

flowchart TD
    A[What kind of input?] --> B{Image}
    A --> C{Audio}
    A --> D{Both / Complex}
    B --> E{How long / many?}
    E -->|Single image, standard task| F[GPT-4o or Claude 3.5 Sonnet<br/>Best general accuracy]
    E -->|Many images or long PDF| G[Gemini 1.5 Pro<br/>2M token context, cost-effective at scale]
    C --> H{Need transcription only?}
    H -->|Yes| I[Whisper API<br/>Cheapest, fastest, no comprehension]
    H -->|No — need reasoning| J[Gemini 1.5 Pro<br/>Native audio understanding]
    D --> K[Gemini 1.5 Pro<br/>Best native multimodal context]

There is no universally best model for multimodal tasks. Here is the practical breakdown as of April 2026:

GPT-4o is the strongest general-purpose vision model for single images. It handles handwriting, charts, screenshots, and photographs well. At $2.50/$10 per million input/output tokens plus per-image costs, it is mid-range in price.

Claude 3.5 Sonnet is competitive with GPT-4o on most vision tasks and tends to be better at following complex instructions about what to look at in an image. Its document handling (especially PDFs) is strong. Priced similarly to GPT-4o.

Gemini 1.5 Pro has the longest context window (2 million tokens) and native audio processing. For tasks that involve many images (processing all pages of a 200-page PDF), many audio files, or a combination of image and audio, Gemini's cost per token at scale is lower. At $1.25/$5 per million tokens for inputs under 128K, it's cheaper than GPT-4o for many workloads.

Whisper is not a reasoning model — it transcribes audio to text. Use it when you need accurate, cheap transcription and you will do the reasoning yourself or with a downstream language model. Cost: $0.006 per minute of audio. Running it locally via the open-weights version is free.

Multimodal AI model comparison — three major AI systems showing simultaneous vision, audio, and text processing capabilities

What multimodal AI is still not good at

Understanding the limits matters as much as understanding the capabilities.

Counting objects precisely. Ask a model to count the number of bolts in a photograph and it will often be off, particularly when bolts are similar-looking and tightly packed. The model's spatial understanding is good enough for "there are about 20 bolts" but not reliable enough for "there are exactly 23."

Reading very small text in images. When text is small relative to the image, high-detail mode helps, but there's a floor. If you need to extract structured data from a dense spreadsheet screenshot, OCR-first and then language model is usually more reliable than pure vision.

Precise spatial localization. "What is in the top-left quadrant?" works. "What are the coordinates of the button labeled Submit?" does not work well — most current models do not produce pixel coordinates reliably. There are specialized vision models (like OWL-ViT for object detection) that do localization; general multimodal LLMs are not the right tool for this.

Consistency across many images. If you are comparing 50 product photos and need consistent attribute extraction, you will get variance. Image 1's "dark blue" might be image 7's "navy" and image 31's "indigo." Structured prompting with explicit value lists helps significantly.

How the landscape is changing

The direction is clear: multimodal is becoming the default, not a feature. GPT-4o processes text, images, and audio natively. Gemini 1.5 Pro adds video. Claude 3.5 Sonnet handles documents as well as any model available.

The next shift will be in models that take actions based on what they see. Computer use — where an AI looks at a screen and clicks things — is already possible with Claude 3.5 Sonnet's computer use capability and is being actively developed by all major labs. This is the transition from "the model understands the screenshot" to "the model can operate the application in the screenshot."

For most developers building products today, the relevant question is not "will multimodal AI matter?" It matters now. The question is which modalities your users' data actually arrives in, and whether you're handling all of them.

The support bot that can't read screenshots is leaving 60% of tickets on the table.

flowchart LR
    subgraph 2023["2023: Single-mode pipelines"]
        T1[Text] --> LM1[LLM]
        I1[Image] --> IC1[Image Classifier]
        A1[Audio] --> ASR1[Speech-to-Text]
        LM1 --> O1[Output]
        IC1 --> O1
        ASR1 --> T1
    end
    subgraph 2026["2026: Native multimodal"]
        T2[Text] --> MM[Multimodal Model]
        I2[Image] --> MM
        A2[Audio] --> MM
        MM --> O2[Unified Output]
    end

Try it yourself in 10 minutes

The fastest way to build intuition for multimodal AI is to run a few experiments, not read more explanations. Here are three to try:

Experiment 1 — the support ticket test. Find an error message on your screen. Take a screenshot. Send it to Claude.ai or ChatGPT with the prompt "what is this error and what should I do?" Note the quality of the answer. Then type the error message instead and compare.

Experiment 2 — the document test. Take a PDF invoice or a scanned form. Upload it to the model of your choice. Ask "what is the total amount due and what is the payment due date?" Note whether the model reads the table and layout correctly.

Experiment 3 — the audio test. Record a 2-minute voice note explaining a problem. Upload it to Gemini 1.5 Pro via Google AI Studio (free tier available). Ask "what is the main issue described here and what are the suggested next steps?" Compare to what you would get from a Whisper transcript of the same clip.

These three experiments will give you a concrete sense of what multimodal AI is actually good at — better than any number of benchmarks.

Conclusion

Multimodal AI is the shift from models that read text to models that perceive the world in multiple forms. The underlying mechanism — converting images and audio into tokens that a transformer can attend over — is straightforward once you see it. The applications that follow from it are significant: document understanding that respects layout, visual question answering for field and operational use cases, audio comprehension beyond transcription, and UI-to-code generation.

The practical starting points are all available now. GPT-4o and Claude 3.5 Sonnet handle images in the standard API. Gemini 1.5 Pro handles audio natively. Whisper handles transcription cheaply and accurately. The code to connect any of them to your application is a few dozen lines.

The support bot that couldn't read screenshots was a 2024 problem. It's a solvable one now.


Sources

  1. OpenAI, "GPT-4V(ision) System Card" (2023) — https://openai.com/research/gpt-4v-system-card
  2. Google DeepMind, "Gemini 1.5: Unlocking multimodal understanding across millions of tokens of context" (2024) — https://arxiv.org/abs/2403.05530
  3. Anthropic, "Claude 3.5 Sonnet model card" (2025) — https://www.anthropic.com/claude/sonnet
  4. OpenAI API docs, "Vision — image detail" (2026) — https://platform.openai.com/docs/guides/vision/detail
  5. Hugging Face, "VQAv2 benchmark" — https://huggingface.co/datasets/HuggingFaceM4/VQAv2

About the Author

Toc Am

Founder of AmtocSoft. Writing practical deep-dives on AI engineering, cloud architecture, and developer tooling. Previously built backend systems at scale. Reviews every post published under this byline.

LinkedIn X / Twitter

Published: 2026-04-28 · Written with AI assistance, reviewed by Toc Am.

☕ Buy Me a Coffee · 🔔 YouTube · 💼 LinkedIn · 🐦 X/Twitter

Monday, April 20, 2026

AI Reasoning Models in 2026: How o3, DeepSeek R1, and Extended Thinking Actually Work

Hero image: abstract visualization of a neural network chain of thought, glowing nodes connected by reasoning paths

The first time I handed a tricky competitive programming problem to GPT-4, it confidently produced a solution that failed on the second test case. I tweaked the prompt, added "think step by step," and got the same broken logic presented with more elaborate justification. It wasn't that the model was dumb — it was that standard next-token prediction has a hard ceiling on reasoning depth.

Then I tried the same problem on o3 in early 2026. It spent 47 seconds "thinking" before outputting anything. The solution was correct. What happened in those 47 seconds is the story of reasoning models.


The Ceiling Standard LLMs Hit

Before diving into reasoning models, it helps to understand exactly where vanilla LLMs fall short.

A standard transformer generates one token at a time, left to right, with no ability to revise earlier decisions. That architecture is remarkably powerful for pattern matching, code completion, and summarisation. But multi-step logical deduction — the kind that requires holding intermediate conclusions, checking consistency, and backtracking — doesn't map cleanly onto a single forward pass.

Chain-of-thought prompting ("think step by step") improves results because it forces the model to externalise intermediate reasoning into the context window. Each step can condition the next. But the model is still constrained: it can't revise a step it already emitted, and it has no mechanism for exploring alternative reasoning branches.

The result is a model that looks like it's reasoning but is really completing a pattern of reasoning-shaped text. For easy problems, the distinction doesn't matter. For hard ones — complex math, multi-constraint planning, adversarial code review — it does.


What Reasoning Models Do Differently

Architecture diagram: standard LLM forward pass vs reasoning model with internal scratchpad and verification loop

Reasoning models like OpenAI's o1/o3, DeepSeek R1, and Claude's extended thinking mode all share a common idea: give the model compute budget at inference time to generate and evaluate intermediate reasoning steps before producing a final answer.

The implementation details differ, but the pattern is consistent:

  1. The model generates a "scratchpad" — internal reasoning tokens that are not directly shown in the final answer
  2. It uses those tokens to explore multiple approaches, check work, and catch contradictions
  3. The final answer is conditioned on the full reasoning trace

This is sometimes called inference-time compute scaling — spending more compute during inference rather than purely during training.

OpenAI o3

o3 was the most significant reasoning-model release of early 2026. OpenAI haven't published full technical details, but from benchmarks and the o1 paper, we know:

  • It was trained with reinforcement learning on verifiable outcomes (math proofs, code tests, logic puzzles) rather than supervised next-token prediction
  • It uses a "think" budget that can be set low (fast, cheaper) or high (slower, more thorough)
  • On ARC-AGI 2, o3 (high compute) achieved 87.5% — up from GPT-4o's 5% on the same benchmark

The practical implication: on a hard coding problem, o3 with high budget will outperform o3 with low budget. Reasoning ability is partially a function of how many tokens the model gets to think with. That's a fundamentally new tradeoff in LLM deployment.

DeepSeek R1

DeepSeek R1, released in January 2025, was the open-source reasoning model that forced the industry to take inference-time compute seriously. Critically, DeepSeek published their training recipe.

They trained R1 using GRPO (Group Relative Policy Optimisation), a variant of PPO that evaluates a group of completions against each other rather than a fixed reward model. The reward signals were:
- Format reward: does the output follow <think>...</think><answer>...</answer> structure?
- Accuracy reward: is the final answer correct (verifiable for math/code)?

No human feedback. No human-written chain-of-thought examples in the initial training. The model learned to reason by trial and error against verifiable outcomes.

The result: R1-Zero (the base RL-trained model) spontaneously developed behaviours like self-correction — pausing mid-reasoning with phrases like "Wait, I made an error..." and revising its approach. The researchers didn't program this in. It emerged from the RL process.

Claude's Extended Thinking

Anthropic's Claude 3.7 Sonnet (February 2026) and later Claude 4 introduced extended thinking: a configurable mode where the model generates a visible chain-of-thought scratchpad before its final response.

Unlike o3's opaque thinking process, Claude's extended thinking is shown to the user by default (with an option to hide it). This is both a design choice and a transparency statement — you can audit the reasoning, not just trust the answer.

Extended thinking is enabled via the API by setting thinking: {type: "enabled", budget_tokens: N}. Claude will spend up to N tokens on its scratchpad before outputting the final answer.

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=16000,
    thinking={
        "type": "enabled",
        "budget_tokens": 10000  # up to 10k tokens for internal reasoning
    },
    messages=[{
        "role": "user",
        "content": "A 10×10 grid has all cells initially white. You flip all cells in row 3, then all cells in column 7, then all cells in any row that has an odd number of black cells. How many black cells remain?"
    }]
)

# The response contains thinking blocks and text blocks
for block in response.content:
    if block.type == "thinking":
        print(f"[Thinking: {len(block.thinking)} chars]")
    elif block.type == "text":
        print(f"Answer: {block.text}")

Running this on the grid puzzle (a problem designed to require systematic tracking of state), the thinking block shows Claude explicitly constructing a 10×10 grid, applying each operation step by step, and verifying row parity before the final operation. The answer is correct. The same prompt without extended thinking produces a confidently wrong answer in roughly 1/10th the tokens.


The Training Mechanism: RLHF vs RL on Verifiable Rewards

Standard LLMs are typically trained with:
1. Supervised fine-tuning on human-written text
2. RLHF — human raters score outputs, those scores train a reward model, PPO updates the policy

Reasoning models shift step 2. Instead of human preference feedback (which is expensive and subjective), they use RL on verifiable signals:
- Code: does it pass the test suite?
- Math: does it match the ground-truth answer?
- Logic: does the conclusion follow from the premises under formal verification?

This is only possible for domains with objectively checkable answers. DeepSeek's bet was that math and code are rich enough to develop general reasoning capabilities, which transfer to other domains. The results suggest they were right — R1 generalises beyond math to multi-step planning and argument analysis.

The implication for developers: if you're building a domain where outputs are verifiable, reasoning models (or custom RL training on your verification signal) may be the right architectural path. If your domain is inherently subjective (creative writing, brand voice), standard RLHF or preference tuning remains dominant.

flowchart LR A[Problem] --> B[Standard LLM\nSingle forward pass] B --> C[Answer — may be wrong] A --> D[Reasoning Model] D --> E[Internal scratchpad\nExplore approach 1] E --> F[Verify / spot error] F --> G[Explore approach 2] G --> H[Synthesise answer] H --> I[Final Answer — higher accuracy] style E fill:#f9f,stroke:#333 style F fill:#f9f,stroke:#333 style G fill:#f9f,stroke:#333

When to Use Reasoning Models (and When Not To)

flowchart TD Start([New task]) --> Q1{Requires multi-step\nlogical deduction?} Q1 -->|No| Q2{Latency-sensitive\nor cost-sensitive?} Q1 -->|Yes| Q3{Verifiable\noutcome?} Q2 -->|Yes| STD[Standard LLM\nFast, cheap] Q2 -->|No| Q3 Q3 -->|Yes| RM[Reasoning Model\nHigh budget] Q3 -->|No| Q4{Extended thinking\nwith medium budget?} Q4 -->|Acceptable| MED[Reasoning Model\nMedium budget] Q4 -->|Too slow/costly| STD style RM fill:#4CAF50,color:#fff style MED fill:#8BC34A,color:#fff style STD fill:#2196F3,color:#fff

Use reasoning models for:

Complex code generation or debugging. When the problem requires holding multiple constraints simultaneously — correctness, performance, security, API contract — reasoning models outperform standard models by a measurable margin. Aider's benchmark data shows o3 achieving 71.6% on SWE-bench Verified vs GPT-4o's 49.2% (Aider leaderboard, March 2026).

Multi-step planning. Tasks like "design a database schema that satisfies these 8 business constraints" benefit enormously from a model that can check constraint satisfaction before committing to an answer.

Mathematical and algorithmic reasoning. This is the canonical use case. AIME 2024 pass rates: o3 (high compute) scored 96.7%; GPT-4o scored 9.3%.

Don't use reasoning models for:

Latency-critical applications. A 47-second thinking time is fine for a batch job. It's a dealbreaker for a live chat interface.

Simple retrieval or classification. Using o3 to extract structured fields from a form is like hiring a neurosurgeon to change a lightbulb — technically capable, economically absurd.

Cost-sensitive high-volume workloads. o3 at high compute is approximately 25× the price of GPT-4o per output token (OpenAI pricing page, April 2026). For 10,000 requests/day, that difference is material.


A Gotcha I Hit in Production

I was building a compliance checker — a system that takes a contract clause and verifies it against 12 specific regulatory requirements. My first instinct was to use a reasoning model with high budget. The accuracy was excellent.

The problem: latency. The p99 was 68 seconds. Legal review workflows can tolerate that. But I'd also wired the results into a real-time UI that highlighted clauses as the user typed. 68-second lag is unusable.

The fix was a two-tier system:
1. Fast path (GPT-4o, 1-2 seconds): check whether the clause is likely compliant using a simpler prompt. Shows a preliminary green/yellow/red indicator.
2. Slow path (o3 medium budget, 8-12 seconds): runs in the background, confirms or overrides the fast-path indicator, surface detailed reasoning to the user in a collapsible "audit trail" panel.

This cut the perceived latency to ~1.5 seconds while keeping accuracy at the reasoning model level. The key insight: you don't have to choose one or the other. Use fast models for preliminary signals, slow models for verification.


Benchmarks Worth Trusting (and Some to Ignore)

Not all reasoning benchmarks are created equal.

Trust these:
- SWE-bench Verified: real GitHub issues, real test suites, no data contamination risk. As of April 2026: o3 71.6%, Claude Sonnet 4.6 49.0%, GPT-4o 38.2% (SWE-bench leaderboard).
- ARC-AGI: abstract reasoning tasks humans solve easily but LLMs typically fail. o3 high compute: 87.5%. GPT-4o: 5.3% (ARC Prize 2025 results).
- AIME 2024: AMC/AIME competition math, hard to contaminate due to limited public solutions.

Be skeptical of:
- MMLU scores for reasoning models: MMLU is multiple-choice trivia-style. Standard LLMs have near-saturated it. A 2-point MMLU improvement tells you almost nothing about reasoning capability.
- HumanEval: widely contaminated in training data. Use SWE-bench or LiveCodeBench instead.
- Self-reported benchmarks: always check whether evals use the publicly released checkpoint or a separate "eval model" that's been specifically tuned for benchmark performance.

%%{init: {'theme': 'base'}}%% xychart-beta title "SWE-bench Verified Scores (April 2026)" x-axis ["GPT-4o", "Claude Sonnet 4.6", "o3 (low)", "o3 (high)"] y-axis "% Resolved" 0 --> 80 bar [38.2, 49.0, 58.4, 71.6]

Production Considerations

Token budget tuning matters. Don't set the thinking budget to max and call it done. Run evals at 2k, 5k, 10k, and 20k thinking tokens. For most tasks, 5k-8k tokens captures 90% of the accuracy gain at 40% of the cost of 20k. Plot accuracy vs. budget and find your knee in the curve.

Thinking tokens aren't free but they're cheaper than output tokens. On the Anthropic API, extended thinking tokens are billed at the input token rate ($3/MTok for Sonnet 4.6), not the output rate ($15/MTok). This makes generous thinking budgets more economical than they first appear.

Cache the reasoning, not just the answer. If you're running the same reasoning task repeatedly (e.g., evaluating 1,000 contracts against the same 12 rules), the system prompt and rule list can be prompt-cached, reducing costs by ~90% for the static portion. The dynamic portion (the contract clause) still incurs full cost.

# Example: prompt caching with extended thinking
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=8000,
    thinking={"type": "enabled", "budget_tokens": 6000},
    system=[{
        "type": "text",
        "text": "You are a compliance checker. Check clauses against these 12 rules:\n[rules here]",
        "cache_control": {"type": "ephemeral"}  # Cache the static rules
    }],
    messages=[{
        "role": "user",
        "content": clause_text  # Only this varies per request
    }]
)

Stream thinking blocks separately. The Anthropic API and OpenAI streaming API both support streaming thinking content. Stream the thinking to the client to provide progress feedback during long reasoning sessions — users can see "still thinking..." with intermediate reasoning rather than a frozen spinner.


Conclusion

Reasoning models don't replace standard LLMs — they extend the capability ceiling for tasks that require genuine multi-step deduction. The right mental model is "when does the problem require the model to check its own work?"

For routine tasks — drafting, classification, simple code completion — standard LLMs are faster and cheaper. For complex planning, algorithmic reasoning, and constraint-heavy generation, reasoning models provide accuracy gains that are hard to achieve through prompt engineering alone.

The economics will continue to shift. Inference-time compute is improving on the same curve as training compute — meaning today's "expensive reasoning" will be next year's baseline. Building your system to route intelligently between fast and slow models now means you're positioned to upgrade automatically as the cost curves drop.


Sources

  1. SWE-bench Leaderboard — verified benchmark for code agents (accessed April 2026)
  2. ARC Prize 2025 Results — ARC-AGI 2 benchmark results including o3 high-compute score
  3. DeepSeek R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning — DeepSeek AI, January 2025
  4. Anthropic Extended Thinking Docs — Claude extended thinking API reference
  5. OpenAI o3 System Card — OpenAI, December 2024
  6. Aider LLM Leaderboard — independent coding benchmark (accessed April 2026)

About the Author

Toc Am

Founder of AmtocSoft. Writing practical deep-dives on AI engineering, cloud architecture, and developer tooling. Previously built backend systems at scale. Reviews every post published under this byline.

LinkedIn X / Twitter

Published: 2026-04-20 · Written with AI assistance, reviewed by Toc Am.

Get These In Your Inbox

Weekly deep-dives on AI engineering, no fluff. Join the newsletter →

Subscribe (free)

Or grab the book ($39, ~100 pages) · Buy me a coffee

☕ Buy Me a Coffee · 🔔 YouTube · 💼 LinkedIn · 🐦 X/Twitter

Thursday, April 2, 2026

Multi-Agent Systems: When One AI Isn't Enough

Multi-Agent Systems Hero

Multi-Agent Systems: When One AI Isn't Enough

A single AI agent is powerful. But some problems are too big, too complex, or too parallel for one agent to handle alone. That's where multi-agent systems come in.

In this post, we'll cover what multi-agent systems are, why they exist, how they're architected, and when you actually need one versus when you're over-engineering.


The Limits of a Single Agent

A single Claude agent operating in a loop is surprisingly capable. It can read files, query databases, browse the web, write code, and synthesize information — all in a single session.

But it runs into walls:

  • Context window limits — a 200K token window sounds huge until you're processing hundreds of documents
  • Speed — a single agent works sequentially; one tool call, then the next
  • Specialization — a generalist agent makes mediocre decisions across wildly different domains
  • Reliability — one failure in a long chain can derail the entire task

Multi-agent systems are the architectural answer to these constraints.

graph TB
  O["Orchestrator"] -->|delegate| A["Agent A: Planning"]
  O -->|delegate| B["Agent B: Research"]
  O -->|delegate| C["Agent C: Execution"]
  A -->|results| M["Merge Results"]
  B -->|results| M
  C -->|results| M
  M -->|synthesize| F["Final Output"]

What Is a Multi-Agent System?

Architecture Diagram

A multi-agent system is a collection of AI agents — each with its own role, tools, and context — working together toward a shared goal.

Think of it like a company:
- An orchestrator (the manager) breaks down the goal and delegates tasks
- Specialist agents (the workers) each handle one domain — research, writing, coding, validation
- Results flow back to the orchestrator, which synthesizes them into a final output

No single agent sees everything. Each sees only what it needs.


Core Architectures

1. Orchestrator + Subagents

The most common pattern. One orchestrator agent decomposes the task and spins up specialized subagents.

User Goal
  → Orchestrator: "I need market research, a draft report, and a code example"
      → Research Agent: searches web, summarizes findings
      → Writer Agent: drafts the report section
      → Code Agent: writes and tests the code snippet
  → Orchestrator: assembles everything, returns final result

The orchestrator never does the heavy lifting itself — it coordinates. Subagents stay focused on narrow tasks with the tools they need.

2. Pipeline (Sequential)

Agents run in a fixed sequence. Each agent's output is the next agent's input.

Ingestion Agent → Summarization Agent → Classification Agent → Output Agent

Useful for ETL-style workflows where each step transforms the data before passing it forward.

3. Parallel Fanout

The orchestrator sends the same task (or partitions of a task) to multiple agents simultaneously, then aggregates the results.

Orchestrator
  → Agent A: processes documents 1-100
  → Agent B: processes documents 101-200
  → Agent C: processes documents 201-300
  ↓
Aggregator: merges and deduplicates results

This is where multi-agent systems shine for speed. Tasks that would take minutes sequentially complete in seconds in parallel.

4. Peer-to-Peer (Debate / Review)

Agents critique each other's outputs. One agent produces a draft; another reviews and challenges it; a third adjudicates.

This pattern improves output quality by catching errors, biases, and gaps that a single agent would miss.


Building a Simple Orchestrator in Python

Here's a minimal orchestrator that spins up two subagents — one to research a topic and one to write a summary:

import asyncio
import anthropic

client = anthropic.Anthropic()

def run_subagent(system_prompt: str, user_message: str) -> str:
    """Run a focused subagent with a specific role."""
    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=2048,
        system=system_prompt,
        messages=[{"role": "user", "content": user_message}]
    )
    return response.content[0].text

def orchestrate(topic: str) -> str:
    print(f"Orchestrating research + summary for: {topic}\n")

    # Step 1: Research subagent
    print("→ Running Research Agent...")
    research = run_subagent(
        system_prompt="You are a technical research agent. Provide detailed, factual bullet points on the given topic. No fluff.",
        user_message=f"Research the following topic and return 5-7 key facts: {topic}"
    )
    print(f"Research complete.\n")

    # Step 2: Writer subagent receives research output
    print("→ Running Writer Agent...")
    summary = run_subagent(
        system_prompt="You are a technical writer. Turn the provided research into a clear, concise 2-paragraph summary for a developer audience.",
        user_message=f"Write a summary based on this research:\n\n{research}"
    )

    return summary

if __name__ == "__main__":
    result = orchestrate("multi-agent AI systems in production")
    print("\n=== Final Output ===")
    print(result)

Each subagent has a tight system prompt defining its role. The orchestrator passes the research agent's output directly into the writer agent. No single agent needs to do both jobs.


Connecting Agents via MCP

In production, subagents typically connect to different MCP servers depending on their role:

Agent MCP Server Tools Available
Research Agent Web Search MCP search_web, fetch_page
Data Agent Postgres MCP query, list_tables
Code Agent Filesystem MCP + GitHub MCP read_file, write_file, create_pr
Comms Agent Slack MCP post_message, list_channels

The orchestrator doesn't need any of these tools itself — it just routes tasks to the right specialist.


When to Use Multi-Agent Systems

Use multi-agent when:
- Tasks are naturally parallel (process 500 documents simultaneously)
- Domains are genuinely different (research vs. coding vs. writing)
- Context window limits are a real constraint
- You need independent review/validation of outputs
- Failure isolation matters (one agent failing shouldn't kill the entire pipeline)

Stick with a single agent when:
- The task fits in one context window
- Steps are sequential and tightly coupled
- You're still building and debugging — single agents are much easier to trace
- The overhead of coordination outweighs the benefits

Multi-agent is not always better. A well-designed single agent beats a poorly coordinated team every time.


Key Design Principles

Keep subagents narrow. A subagent that does one thing well is worth ten that do many things poorly. Tight system prompts, limited tool access, clear output format.

Make outputs explicit. Agents communicate through text. Define the format of outputs precisely so the orchestrator can parse them reliably. JSON works well for structured handoffs.

Handle failures gracefully. Subagents will fail — timeouts, bad outputs, empty results. The orchestrator needs retry logic and fallback behavior, not just a happy path.

Limit trust between agents. A subagent's output is untrusted data. The orchestrator should validate, not blindly forward.

Trace everything. Multi-agent systems are hard to debug when things go wrong. Log every agent invocation, every tool call, every handoff. Observability is not optional.


What's Next

Multi-agent architectures unlock a new class of problems you couldn't solve with a single agent. From here:

  • Add MCP servers to give each subagent specialized tools
  • Add memory — shared state between agents via a database or vector store
  • Add human-in-the-loop — pause and request approval at critical decision points
  • Go async — run subagents concurrently with asyncio.gather() for parallel workloads

The pattern scales from two agents to twenty. Keep each one simple, and the system stays manageable.

Sources & References:
1. Anthropic — "Claude API Documentation" — https://docs.anthropic.com/
2. LangChain — "Multi-Agent Systems" — https://python.langchain.com/docs/concepts/agents/
3. CrewAI — "Multi-Agent Framework" — https://www.crewai.com/


📖 Related posts: Building Your First AI Agent | What Is MCP? | What Are AI Agents?

About the Author

Toc Am

Founder of AmtocSoft. Writing practical deep-dives on AI engineering, cloud architecture, and developer tooling. Previously built backend systems at scale. Reviews every post published under this byline.

LinkedIn X / Twitter

Published: 2026-04-02 · Written with AI assistance, reviewed by Toc Am.

Get These In Your Inbox

Weekly deep-dives on AI engineering, no fluff. Join the newsletter →

Subscribe (free)

Or grab the book ($39, ~100 pages) · Buy me a coffee

☕ Buy Me a Coffee · 🔔 YouTube · 💼 LinkedIn · 🐦 X/Twitter

Wednesday, April 1, 2026

Building Your First AI Agent with Claude and MCP

Building Your First AI Agent Hero

Building Your First AI Agent with Claude and MCP

You've read the explainers. You understand what AI agents are. You know what MCP does. Now it's time to build one.

In this tutorial, we'll build a working AI agent that can read files from your local filesystem, look up information, and take actions — all by connecting Claude to tools via MCP. By the end, you'll have a running agent you can extend in any direction you want.

No magic. Just code.

graph LR
  A["User Input"] -->|send query| B["LLM Reasoning"]
  B -->|decide action| C["Tool Selection"]
  C -->|invoke| D["Tool Execution"]
  D -->|return data| E["Response Formation"]
  E -->|deliver| F["User Output"]

What We're Building

A Claude-powered agent that:
- Reads files from a local directory
- Answers questions about their contents
- Can write a summary back to disk

Simple enough to follow in one sitting. Sophisticated enough to show you exactly how the architecture works in practice.


Prerequisites

Architecture Diagram
  • Python 3.10+
  • An Anthropic API key (get one at console.anthropic.com)
  • Basic familiarity with Python and the terminal

Install the required packages:

pip install anthropic mcp

Step 1: Understand the Architecture

Before writing a single line of code, let's be clear about what's happening under the hood.

An MCP agent has three parts:

  1. The LLM (Claude) — the brain. It decides what to do, interprets results, and produces the final response.
  2. The MCP Client — the bridge. It sits between Claude and your tools, translating Claude's tool calls into actual function executions.
  3. The MCP Server — the muscle. It exposes your tools (filesystem access, APIs, databases, anything) over a standardized protocol.

The flow looks like this:

User prompt
  → Claude (decides to use a tool)
  → MCP Client (sends tool call to server)
  → MCP Server (executes the tool, returns result)
  → Claude (reads result, decides next step or responds)
  → Final answer to user

Claude never directly touches your filesystem. It asks the MCP server to do it. This keeps everything sandboxed, observable, and composable.


Step 2: Set Up the MCP Filesystem Server

MCP ships with a reference filesystem server. It gives Claude tools to read, write, and list files in a directory you specify.

Create a working directory and a folder for the agent to operate on:

mkdir -p ~/my-agent/data
echo "Project Alpha: Due April 15. Owner: Sarah." > ~/my-agent/data/project-alpha.txt
echo "Project Beta: Due May 1. Owner: James." > ~/my-agent/data/project-beta.txt
echo "Budget Q2: $50,000 approved for tooling." > ~/my-agent/data/budget-q2.txt

Now run the MCP filesystem server, pointing it at that data directory:

python -m mcp.server.filesystem ~/my-agent/data

Leave this running in a terminal. It's now listening for MCP tool calls over stdio.


Step 3: Write the Agent

Create ~/my-agent/agent.py:

import asyncio
import anthropic
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

ANTHROPIC_API_KEY = "your-api-key-here"

async def run_agent(user_message: str):
    client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)

    # Connect to the MCP filesystem server
    server_params = StdioServerParameters(
        command="python",
        args=["-m", "mcp.server.filesystem", "/Users/your-name/my-agent/data"]
    )

    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            # Initialize the MCP session and discover available tools
            await session.initialize()
            tools_result = await session.list_tools()

            # Convert MCP tools to Anthropic tool format
            tools = [
                {
                    "name": tool.name,
                    "description": tool.description,
                    "input_schema": tool.inputSchema
                }
                for tool in tools_result.tools
            ]

            print(f"Available tools: {[t['name'] for t in tools]}\n")

            messages = [{"role": "user", "content": user_message}]

            # Agentic loop -- run until Claude stops calling tools
            while True:
                response = client.messages.create(
                    model="claude-opus-4-5",
                    max_tokens=4096,
                    tools=tools,
                    messages=messages
                )

                # If Claude is done (no more tool calls), print and exit
                if response.stop_reason == "end_turn":
                    for block in response.content:
                        if hasattr(block, "text"):
                            print("Agent:", block.text)
                    break

                # Process tool calls
                tool_uses = [b for b in response.content if b.type == "tool_use"]
                if not tool_uses:
                    break

                # Add Claude's response to message history
                messages.append({"role": "assistant", "content": response.content})

                # Execute each tool call and collect results
                tool_results = []
                for tool_use in tool_uses:
                    print(f"→ Calling tool: {tool_use.name}({tool_use.input})")
                    result = await session.call_tool(tool_use.name, tool_use.input)
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": tool_use.id,
                        "content": str(result.content)
                    })

                # Feed tool results back to Claude
                messages.append({"role": "user", "content": tool_results})

if __name__ == "__main__":
    question = "What projects do we have, who owns them, and what's the Q2 budget?"
    asyncio.run(run_agent(question))

Step 4: Run It

cd ~/my-agent
python agent.py

You'll see something like:

Available tools: ['read_file', 'write_file', 'list_directory']

→ Calling tool: list_directory({'path': '.'})
→ Calling tool: read_file({'path': 'project-alpha.txt'})
→ Calling tool: read_file({'path': 'project-beta.txt'})
→ Calling tool: read_file({'path': 'budget-q2.txt'})

Agent: Here's a summary of your projects and budget:

**Projects:**
- **Project Alpha** -- Due April 15, owned by Sarah
- **Project Beta** -- Due May 1, owned by James

**Q2 Budget:** $50,000 approved for tooling

Would you like me to write a consolidated summary file to your data directory?

Claude autonomously decided which files to read, read them in sequence, and synthesized a coherent answer. You didn't hardcode any of that logic.


Step 5: Extend It

This is where it gets interesting. Swap out the filesystem server for any other MCP server and your agent instantly gains new capabilities:

  • GitHub MCP server → Claude can read issues, open PRs, review code
  • Postgres MCP server → Claude can query your database in natural language
  • Slack MCP server → Claude can read channels and post messages
  • Your own custom server → expose any internal tool, API, or service

The agent loop stays the same. Only the tools change.

To add a second server (e.g., a web search tool), you'd connect multiple MCP sessions and merge their tool lists before sending to Claude. The pattern is identical.


Key Concepts to Remember

The agentic loop — Claude calls a tool, gets a result, calls another tool or responds. This repeats until stop_reason == "end_turn". You control when it stops.

Tool schemas matter — Claude chooses tools based on their description and input_schema. Write clear descriptions. Vague descriptions lead to wrong tool choices.

Context is everything — The messages array is your agent's memory within a session. It sees everything that's happened. For long-running agents, you'll need to manage context window limits.

MCP is just a protocol — Any process that speaks MCP can be a server. You can write one in 50 lines of Python. The MCP SDK handles the transport.


What's Next

You now have a working AI agent. From here, the natural next steps are:

  • Add persistence — store conversation history to a file or database so the agent remembers context across runs
  • Add multiple tools — connect to APIs, databases, or web search alongside the filesystem
  • Add guardrails — restrict what tools the agent can call, add confirmation steps for destructive actions
  • Go async at scale — for production, look at multi-agent architectures where specialized sub-agents handle different domains

The hardest part of building agents isn't the code — it's deciding what you want the agent to do and writing tool descriptions precise enough that it does it correctly every time.

Start small. One tool. One clear task. Then expand from there.

Sources & References:
1. Anthropic — "Claude API Documentation" — https://docs.anthropic.com/
2. MCP — "Model Context Protocol Specification" — https://modelcontextprotocol.io/
3. MCP — "Python SDK" — https://github.com/modelcontextprotocol/python-sdk


📖 Read the companion posts: What Are AI Agents? | What Is MCP?

About the Author

Toc Am

Founder of AmtocSoft. Writing practical deep-dives on AI engineering, cloud architecture, and developer tooling. Previously built backend systems at scale. Reviews every post published under this byline.

LinkedIn X / Twitter

Published: 2026-04-01 · Written with AI assistance, reviewed by Toc Am.

Get These In Your Inbox

Weekly deep-dives on AI engineering, no fluff. Join the newsletter →

Subscribe (free)

Or grab the book ($39, ~100 pages) · Buy me a coffee

☕ Buy Me a Coffee · 🔔 YouTube · 💼 LinkedIn · 🐦 X/Twitter

What Happens When You Hit "Regenerate"

You tap regenerate like it's a cheap retry. The last answer sits there, almost right, and the button looks like an eraser. It isn't....