# Structured Outputs vs Tool Calling in .NET APIs

When you start adding AI to a .NET API, two features come up almost immediately: structured outputs and tool calling. Both let you go beyond free-form text responses. Both are supported in `Microsoft.Extensions.AI`. But they solve fundamentally different problems, and picking the wrong one for a given use case causes real pain in production.

The complete implementations for both patterns - with validation, error handling, and edge cases - are available on [Patreon](https://www.patreon.com/CodingDroplets), along with the full source code from our AI-powered .NET API builds.

If you want to see these techniques applied inside a complete running project, Chapters 6 and 11 of the [AI-Powered .NET APIs course](https://aiapis.codingdroplets.com/) cover structured outputs and tool calling end to end - in C#, against a real ASP.NET Core support-desk API.

[![AI-Powered .NET APIs](https://newsletter.codingdroplets.com/images/ai-api-course-banner-1.jpg)](https://aiapis.codingdroplets.com/)

This article is a practical comparison: what each feature does, when to reach for each one, the real trade-offs, and a clear recommendation.

## What Are Structured Outputs in .NET?

Structured outputs constrain the model's response to match a specific schema. Instead of getting back free-form text, you get a typed C# object. The model is forced - at the token-generation level - to produce valid JSON that maps to your type.

In `Microsoft.Extensions.AI`, this looks like:

```csharp
var result = await chatClient.GetStructuredResponseAsync<TicketClassification>(
    messages,
    new ChatOptions { ResponseFormat = ChatResponseFormat.ForType<TicketClassification>() }
);
```

Where `TicketClassification` might be a simple C# record:

```csharp
public record TicketClassification(
    string Category,
    SeverityLevel Severity,
    string[] Tags
);
```

The key thing to understand: structured outputs are about **shaping the model's answer**. The model is still just responding to your prompt - it is not taking any action, not calling any code, not reaching out to any service. It is just constrained to respond in a specific shape.

Use cases where structured outputs shine: ticket classification, sentiment analysis, entity extraction, document summarization into a typed object, form pre-fill from unstructured input.

## What Is Tool Calling in .NET?

Tool calling (also called function calling) is different in a critical way - the model decides to **invoke code you have registered**, gets the result, and then continues reasoning. It changes the control flow of your application.

With `AIFunctionFactory` in `Microsoft.Extensions.AI`:

```csharp
var tools = AIFunctionFactory.Create(async (string orderId) =>
{
    return await orderService.GetOrderStatusAsync(orderId);
}, "GetOrderStatus", "Retrieves the current status of an order by ID");
```

The model does not execute the function. It emits a structured request saying "I want to call `GetOrderStatus` with `orderId = '12345'`". Your code executes the function, returns the result, and the model uses that result to formulate its final response.

This is the mechanism that gives a model access to live data - databases, external APIs, internal services - without the model having any direct access to them. The model asks; your code runs; the model sees results.

Use cases: order lookup, ticket creation, knowledge base searches, real-time pricing queries, anything where the model needs data it cannot have at inference time.

## Structured Outputs vs Tool Calling: Side-by-Side

| Dimension | Structured Outputs | Tool Calling |
|---|---|---|
| What it controls | Shape of the model's answer | Which of your functions run and when |
| Direction of data flow | Model produces data into your type | Model requests data from your services |
| Network calls | None - model stays isolated | Yes - your code makes calls |
| Determinism | High - schema enforced | Medium - model decides which tools to invoke |
| Failure mode | Invalid type mapping | Tool invocation error, missing data |
| Latency | Single round-trip | Multi-round-trip (model -> tool -> model) |
| Best for | Extraction, classification | Live data, actions, agent reasoning |

### Does Tool Calling Produce Structured Data?

Yes - and this is where developers get confused. When a model calls a tool, the arguments it passes are also schema-constrained JSON. So tool calling *involves* structured data internally. But that is a mechanism, not the output. The final response to the user from a tool-calling flow is still free-form text unless you also apply a structured output schema to the final answer.

You can compose them: use tool calling to retrieve live data, then use structured outputs to enforce the shape of what the model says about that data.

## When Should You Use Structured Outputs?

Reach for structured outputs when:

- **You are extracting or classifying** - the model is your parser, and you need the result as a typed object
- **No live data is needed** - the model has enough context in the prompt or conversation history
- **Downstream code depends on the result** - you are piping the output into a database, workflow, or other system component
- **You need predictability** - schema-constrained responses are far more reliable than prompt-engineered JSON

In production, I have used structured outputs for ticket triage (incoming support messages classified into `Category`, `Severity`, `Tags` before any human sees them), invoice data extraction from email body text, and structured summarization of long documents into C# records that feed into a reporting pipeline.

The big win: you stop writing JSON parsing + validation code. The model gives you a C# object or fails with a typed exception. Recovery is simple.

## When Should You Use Tool Calling?

Reach for tool calling when:

- **The model needs data it cannot have** - current order status, user account info, live inventory
- **You want the model to take actions** - creating tickets, scheduling appointments, updating records
- **You are building an agent** - the model needs to reason, retrieve, and decide iteratively
- **The answer depends on multiple service calls** - the model determines which calls to make and in what order

The trade-off that matters most: tool calling adds latency. Each round-trip (model decides to call a tool, your code runs, result goes back to model) takes time. In our support-desk API, a single user message could trigger two or three tool calls before the model had enough context to answer. That is fine for a chat interface; it is a problem if you are inside a high-throughput processing pipeline.

Also: **never expose a destructive tool without a safety rail**. If you register a `DeleteOrder` tool, the model can and will call it when it figures out that is what the user is asking for. Validation, confirmation steps, and allow-listing are mandatory - not optional.

See our breakdown of [runaway LLM costs in .NET APIs](https://codingdroplets.com/runaway-llm-costs-dotnet-api) for why unconstrained tool-calling loops are also a significant cost risk.

## Can You Use Both in the Same Request?

Yes, and in practice, the most useful patterns combine them. A common flow in our support API:

1. Tool calling to retrieve the customer's account context (live data)
2. Tool calling to search the knowledge base for relevant articles (vector search)  
3. Structured outputs on the final response (so the answer includes a `SuggestedArticles` array alongside the text)

The `IChatClient` pipeline in `Microsoft.Extensions.AI` supports this cleanly - register tools via `ChatOptions.Tools`, set the response format, and the client handles the multi-turn conversation internally when `AutoInvokeKernelFunctions` is enabled.

## What Is the Recommendation?

**Default choice for extraction and classification: structured outputs.** They are simpler, faster (single round-trip), and more reliable. If the model already has what it needs to answer, there is no reason to introduce tool calling overhead.

**Default choice when you need live data or model-driven actions: tool calling.** There is no alternative - structured outputs cannot make the model "go get" something.

**For agent builds: tool calling is mandatory**, structured outputs optional (but useful for shaping the agent's final output).

One practical note: start with structured outputs in new projects. They are easier to test, easier to debug, and cheaper to run. Add tool calling when you hit a wall - when the model needs something it cannot have at prompt time. The [AI-Powered .NET APIs course](https://aiapis.codingdroplets.com/) follows this exact progression: Ch 6 covers structured outputs before Ch 11 introduces tool calling, because understanding extraction and classification first makes the jump to tool-driven agents much cleaner.

If you are also comparing the SDK options themselves - `Microsoft.Extensions.AI`, Semantic Kernel, Agent Framework - see our [full comparison for .NET in 2026](https://codingdroplets.com/meai-vs-semantic-kernel-vs-agent-framework-dotnet-2026).

## FAQ

### What is the difference between structured outputs and tool calling in .NET?
Structured outputs constrain the shape of the model's response into a typed C# object. Tool calling lets the model invoke functions in your code to fetch data or trigger actions before responding. Structured outputs shape answers; tool calling shapes control flow.

### Can Microsoft.Extensions.AI do both structured outputs and tool calling?
Yes. `IChatClient` supports both. Use `ChatResponseFormat.ForType<T>()` for structured outputs, and register tools via `ChatOptions.Tools` with `AIFunctionFactory`. Both can be active on the same request.

### Does tool calling add latency to .NET AI APIs?
Yes. Each tool invocation requires a round-trip: the model emits a tool call request, your code executes, the result goes back to the model, and the model continues. Complex queries can trigger multiple rounds. For latency-sensitive endpoints, minimize tool count and prefer structured outputs when the model already has the data it needs.

### When should I use structured outputs instead of prompt engineering for JSON?
Always. Prompt-engineered JSON is fragile - models occasionally emit invalid JSON, add prose before or after the JSON block, or miss fields. Schema-constrained structured outputs enforce the response format at the token level, eliminating the need for retry logic and defensive JSON parsing.

### Is tool calling safe to expose directly in a production ASP.NET Core API?
Not without guardrails. Validate all tool arguments as untrusted input (the model is not a trusted caller). Never expose destructive operations without explicit confirmation steps. Use allow-listing to restrict which tools the model can invoke per endpoint. See Microsoft's [responsible AI guidance](https://learn.microsoft.com/en-us/azure/ai-services/responsible-use-of-ai-overview) for a full checklist.

### How do structured outputs handle validation failures in .NET?
`Microsoft.Extensions.AI` throws a typed exception when deserialization fails. You should catch this at the service boundary and treat it the same way you would treat any untrusted external input failure - log the raw response for debugging, return a safe fallback, and do not surface model errors directly to API callers.

---

## About the Author

I'm Celin Daniel, Co-founder of [Coding Droplets](https://codingdroplets.com/). I've been building .NET and ASP.NET Core systems in production for 13+ years - APIs, distributed backends, enterprise platforms. Everything I write here comes from real shipping experience: patterns that held up, trade-offs that bit us, and lessons learned the hard way.

- GitHub: [codingdroplets](http://github.com/codingdroplets/)
- YouTube: [Coding Droplets](https://www.youtube.com/@CodingDroplets)
- Website: [codingdroplets.com](https://codingdroplets.com/)

