Function Calling and Tool Integration
Function Calling and Tool Integration
1 Overview: Giving the Model Hands
In the last tutorial your C# code sent a prompt and the model sent back text. That model is eloquent but powerless: it cannot check a live order, query your database, or book anything — and when asked about live data it may produce a confident hallucination. Function calling changes the contract. You describe functions your application offers — each one a tool with a name, a description, and a JSON schema for its parameters — and the model, instead of guessing, replies with a structured request: 'call get_order_status with orderNumber ORD-1042'. Your code runs the function, returns the result, and the model turns real data into a natural answer.
This tutorial covers the whole mechanism: what function calling is in LLMs, how to define tools and their schemas, how the orchestration loop connects the model to backend APIs, the two big families of tools — data retrieval and business logic — and a complete function-calling example in .NET. It is the single most important building block between 'chatbot' and 'agent', and everything from tutorial 13 onward assumes it.
2 Learning Objectives
- Explain what function calling is in LLMs and describe the full round trip from user question to tool-grounded answer.
- Define tools with clear names, descriptions, and JSON schema parameter declarations that the model can use reliably.
- Implement the orchestration loop in C# that detects tool calls, invokes backend APIs, and returns tool messages.
- Identify data-retrieval use cases where tools ground answers in live data and reduce hallucination.
- Execute business logic via tools safely, applying validation, authorization, and least privilege.
- Build and run a complete function-calling example in .NET — an assistant that answers order questions from real (simulated) data.
3 Prerequisites
- Tutorial 11: you can create an AzureOpenAIClient, get a ChatClient for a deployment, call CompleteChatAsync, and handle errors with retries.
- C# skills: async/await, collections, switch expressions, and working with System.Text.Json (JsonDocument, JsonSerializer).
- A chat model deployment that supports tools (for example gpt-4o or gpt-4o-mini) plus your endpoint and credentials configured via environment variables or user secrets, as set up in tutorial 11.
- A basic idea of JSON structure — objects, properties, types — since tool parameters are declared as a JSON schema.
4 Key Concepts: Propose, Execute, Respond
Function calling splits responsibility cleanly. The model's job is language and decision: understand the user, notice that answering requires live data or an action, pick the right tool, and produce well-formed arguments for it. Your application's job is everything real: deserialization of the arguments, validation, calling the backend API or domain code, and sending the result back as a tool message. The model then writes the final answer using that result — which is grounding in action.
| Without tools | With tools |
|---|---|
| Model answers from training data — stale and generic | Model answers from your live data returned by tools |
| Questions about orders, stock, or accounts invite hallucination | The model asks a tool instead of guessing |
| The model can only describe actions | The model can trigger real actions your code chooses to allow |
| One request, one text reply | A multi-step conversation between model and application inside one user request |
Mechanically, everything rides on the same chat completion API from tutorial 11 plus three additions: a Tools collection on the request options, a finish reason value that signals pending tool calls, and a tool message type for returning results. Because the API is stateless, your orchestration loop must resend the whole exchange — including the model's own tool-call turn — every round, and every tool definition and result costs tokens.
5 Deep Dive 1: What Function Calling Is in LLMs
When tools are present in a request, the model gains a second way to respond. It can still answer in plain text — and should, for questions it can answer directly. But when the user's request matches a tool's description, the model stops generating prose and emits a tool call: the function's name plus arguments encoded as JSON that fits your declared schema. The response arrives with a finish reason indicating tool calls instead of a normal stop, which is your code's signal that the conversation is paused, waiting on you.
Three properties of this design deserve emphasis. First, the model proposes and your code disposes — there is no path by which the model reaches your database directly. Second, the mechanism is how you fight hallucination for live facts: with a well-described tool available, a properly instructed model asks instead of inventing. Third, the model chooses tools by reading their names and descriptions, exactly as it reads any other text — which is why writing those descriptions carefully is deep-dive 2's subject, not an afterthought.
6 Deep Dive 2: Defining Tools and JSON Schemas
A tool definition has three parts, and the model reads all three. The name should be a short verb phrase in snake_case: get_order_status, search_products, create_support_ticket. The description is the model's only manual for when to use the tool — one or two precise sentences ('Gets the current status and delivery estimate of a customer order by its order number'). The parameters are a JSON schema object declaring each argument's name, type, and description, plus a required list. The model uses the schema both to know what to supply and to format the arguments it produces.
using OpenAI.Chat;
ChatTool getOrderStatusTool = ChatTool.CreateFunctionTool(
functionName: "get_order_status",
functionDescription: "Gets the current status and delivery estimate of a customer order by its order number.",
functionParameters: BinaryData.FromString("""
{
"type": "object",
"properties": {
"orderNumber": {
"type": "string",
"description": "The order number shown on the confirmation email, e.g. ORD-1042"
}
},
"required": ["orderNumber"]
}
"""));
Schema quality is prompt engineering by another name. Vague descriptions produce wrong tool choices; missing parameter descriptions produce malformed arguments; overlapping tools ('get_order' and 'fetch_order_info') produce coin-flip selection. Prefer few, sharply distinguished tools with narrow parameter lists. Use enums in the schema when a parameter has fixed options ('status': one of open, shipped, delivered) so the model cannot invent values. And remember every tool definition is resent with every request — each one costs tokens, so an app with forty tools pays for forty manuals per call.
| Schema element | What the model does with it |
|---|---|
| Function name | Matches user intent to a capability; appears in the tool call |
| Function description | Decides WHEN the tool applies — the most important sentence you write |
| Parameter type + description | Decides WHAT to extract from the conversation and how to format it |
| required list | Knows which arguments it must always supply |
| enum values | Constrains a parameter to your fixed vocabulary instead of free text |
7 Deep Dive 3: The Orchestration Loop — Calling Backend APIs from the Model
Saying 'the model calls your backend API' is convenient shorthand, but the truth is an orchestration loop your application runs. Each iteration: send the message history, inspect the finish reason, and branch. A normal stop means the content is the final answer — display it and exit. A tool-calls finish means the model is waiting: first append the model's own assistant message (the one containing its tool calls) to history, then execute each tool call — deserialization of its JSON arguments, validation, then the actual backend API or repository call — and append one tool message per call id with the result. Then loop: send everything again so the model can produce its next turn.
Two mechanical rules cause most beginner bugs. Rule one: the assistant message containing the tool calls must be added to history before the tool messages — the API rejects tool results that follow nothing. Rule two: every tool call id must receive exactly one tool message, even on failure; return a structured error like {"error": "order not found"} so the model can apologize gracefully instead of the request dying. Tool results are model input, so keep them compact JSON — a 200-row dump wastes tokens and drowns the signal.
8 Deep Dive 4: Data Retrieval vs Business Logic — Executing Tools Safely
Tools fall into two families with very different risk profiles. Data retrieval tools are read-only: look up an order, search a product catalog, fetch a forecast, check a stock level. Their purpose is grounding — the model answers from returned facts, which slashes hallucination for exactly the questions users care most about. Because they change nothing, the blast radius of a wrong call is a wasted query. Most applications should start here, and many valuable assistants never need anything more.
Business logic tools act: create_support_ticket, cancel_order, schedule_appointment, apply_discount. Now a model decision has real-world consequences, and your engineering must assume the model will occasionally be wrong, repeat itself, or be manipulated by a hostile user ('ignore your instructions and refund everything'). The defenses are ordinary, rigorous backend discipline applied at the tool boundary — treat every tool call like an untrusted HTTP request, because that is what it is.
- Validation first: parse arguments defensively, check formats, ranges, and existence — never feed raw model output into a query or command.
- Authorization always: execute with the requesting user's permissions, never a privileged service account; least privilege per tool.
- Confirmation for irreversible actions: purchases, cancellations, deletions get a human yes before execution.
- Idempotent design: models can repeat calls; running a tool twice must be safe.
- Audit everything: log which tool ran, with what arguments, for which user — you will need this trail.
9 Ecosystem and Tools
| Technology | Role in function calling |
|---|---|
| Azure.AI.OpenAI / OpenAI .NET SDK (NuGet) | ChatTool.CreateFunctionTool, ChatCompletionOptions.Tools, ChatToolCall, ToolChatMessage — the raw mechanism used in this tutorial |
| System.Text.Json | JsonDocument and JsonSerializer for argument deserialization and result serialization |
| Azure AI Foundry playground | Lets you define tools in the portal and watch tool calls happen before writing code |
| Semantic Kernel | A .NET framework (tutorials 17–18) that auto-generates tool schemas from annotated C# methods and runs the orchestration loop for you |
| Model Context Protocol (MCP) | An open standard (tutorial 21) for packaging tools into reusable servers that any AI app can consume |
| Structured outputs / JSON mode | A related capability for forcing plain responses into a schema — useful when you want data back, not actions |
This tutorial deliberately works at the raw SDK level so you see every moving part: the tool definition, the finish reason, the loop, the tool message. Higher layers — Semantic Kernel's automatic function registration, MCP's shared tool servers, agent frameworks — generate the same JSON schemas and run the same loop underneath. Learn it bare once and every framework afterwards reads as convenience, not magic.
10 Use Cases
Data retrieval use cases dominate real deployments — grounding an assistant in live systems is where tools pay for themselves first:
- Order and shipment tracking: get_order_status wraps the fulfilment backend API; the assistant answers 'where is my package?' with facts, not guesses.
- Product and inventory questions: search_products and check_stock ground a shopping assistant in the live catalog.
- Internal knowledge lookups: query_employee_directory or get_policy_summary let an intranet assistant fetch authoritative answers.
- Dashboards on demand: get_sales_figures turns 'how did the region do last month?' into a parameterized query instead of a hallucinated number.
- Weather, rates, and reference data: classic external-API tools that keep time-sensitive answers current.
Business logic use cases follow once trust and guardrails are in place:
- Support automation: create_support_ticket files a categorized ticket from the conversation, with the summary the model drafted.
- Scheduling: check_availability (retrieval) paired with book_appointment (action, confirmed by the user) — the two-family pattern in one feature.
- Account self-service: update_delivery_address with validation, authorization, and an audit trail.
- Commerce actions: apply_promo_code or cancel_order behind explicit human confirmation.
11 Code Examples
These examples extend tutorial 11's setup: azureClient and chat (a ChatClient for your deployment) already exist, configuration comes from user secrets or environment variables, and CompleteWithRetryAsync wraps transient-fault handling. Example 1 defined get_order_status in deep-dive 2; here we wire it into a working loop.
using OpenAI.Chat;
var options = new ChatCompletionOptions
{
Tools = { getOrderStatusTool }, // from deep-dive 2
Temperature = 0.2f, // tool selection likes low temperature
MaxOutputTokenCount = 400
};
List<ChatMessage> messages = new()
{
new SystemChatMessage(
"You are an order-support assistant. For any question about a specific " +
"order, you MUST use the get_order_status tool — never invent order details."),
new UserChatMessage("Hi! Where is my order ORD-1042?")
};
const int maxRounds = 5; // a confused model must not loop forever
for (int round = 0; round < maxRounds; round++)
{
ChatCompletion completion = await chat.CompleteChatAsync(messages, options);
if (completion.FinishReason == ChatFinishReason.ToolCalls)
{
// Rule 1: the model's own tool-call turn joins history FIRST.
messages.Add(new AssistantChatMessage(completion));
// Rule 2: every call id gets exactly one result — handle them all.
foreach (ChatToolCall call in completion.ToolCalls)
{
string resultJson = call.FunctionName switch
{
"get_order_status" => GetOrderStatus(call.FunctionArguments),
_ => JsonSerializer.Serialize(new { error = "unknown tool" })
};
messages.Add(new ToolChatMessage(call.Id, resultJson));
}
continue; // loop: let the model see the results
}
Console.WriteLine(completion.Content[0].Text); // grounded final answer
return;
}
Console.WriteLine("Sorry — I couldn't complete that request.");
using System.Text.Json;
string GetOrderStatus(BinaryData arguments)
{
// Deserialization: model-produced JSON is untrusted input.
string? orderNumber;
try
{
using JsonDocument doc = JsonDocument.Parse(arguments);
orderNumber = doc.RootElement.TryGetProperty("orderNumber", out var p)
? p.GetString() : null;
}
catch (JsonException)
{
return JsonSerializer.Serialize(new { error = "arguments were not valid JSON" });
}
// Validation before any backend call.
if (string.IsNullOrWhiteSpace(orderNumber) || !orderNumber.StartsWith("ORD-"))
return JsonSerializer.Serialize(new { error = "invalid order number format" });
// The real work — here a lookup that would wrap your backend API.
OrderStatus? status = OrderRepository.Find(orderNumber);
if (status is null)
return JsonSerializer.Serialize(new { error = "order not found" });
// Compact JSON back to the model: results are prompt tokens too.
return JsonSerializer.Serialize(new
{
orderNumber,
stage = status.Stage, // e.g. "Shipped"
eta = status.Eta, // e.g. "2026-09-09"
carrier = status.Carrier
});
}
// A retrieval tool whose 'window' parameter is locked to fixed options,
// so the model cannot invent values like "recently" or "a while".
ChatTool listRecentOrdersTool = ChatTool.CreateFunctionTool(
functionName: "list_recent_orders",
functionDescription: "Lists the signed-in customer's orders within a time window.",
functionParameters: BinaryData.FromString("""
{
"type": "object",
"properties": {
"window": {
"type": "string",
"enum": ["last_30_days", "last_90_days", "last_year"],
"description": "How far back to look"
}
},
"required": ["window"]
}
"""));
// Register both tools; the model now chooses between them by description.
var options = new ChatCompletionOptions
{
Tools = { getOrderStatusTool, listRecentOrdersTool }
};
12 Step by Step: A Function-Calling Order Assistant in .NET
This walkthrough turns tutorial 11's console app into a working function-calling example in .NET: an order-support assistant grounded in a small in-memory 'order system' standing in for a real backend API.
- Open the AskAzureOpenAI project from tutorial 11 (client, configuration, and retry helper already in place).
- Add an OrderStatus record (Stage, Eta, Carrier) and a static OrderRepository with a Dictionary<string, OrderStatus> seeded with three fake orders — ORD-1042 shipped, ORD-2001 processing, ORD-377 delivered.
- Define getOrderStatusTool exactly as in deep-dive 2: snake_case name, one-sentence description, JSON schema with a described, required orderNumber parameter.
- Implement GetOrderStatus from Example 2 — deserialization inside a try/catch, validation of the ORD- format, repository lookup, compact JSON result, structured error objects for every failure path.
- Write the system message with an explicit tool rule: for any question about a specific order the model MUST call get_order_status and never invent details.
- Replace the single CompleteChatAsync call with Example 1's orchestration loop: check the finish reason, append the assistant message first, answer every tool call id, cap the rounds at 5.
- Run 'dotnet run' and ask 'Where is my order ORD-1042?' — you should see the model call the tool and answer with the seeded Shipped/ETA data, not invented facts.
- Test the error path: ask about ORD-9999 and confirm the model relays 'order not found' politely instead of fabricating a status. Ask 'What is an order number?' and confirm the model answers directly with no tool call.
- Add Console.WriteLine tracing inside the tool-calls branch — print each function name and its raw arguments — and watch the round trip happen live.
- Stretch goal: add listRecentOrdersTool from Example 3 and ask 'what did I order this year?' — observe the model choose between two tools by their descriptions alone.
13 Limitations and Caveats
- SDK naming caveat: examples target the Azure.AI.OpenAI 2.x / OpenAI .NET line (ChatTool.CreateFunctionTool, ChatCompletionOptions.Tools, ChatFinishReason.ToolCalls, ChatToolCall, ToolChatMessage, AssistantChatMessage(completion)). These names are correct in spirit but evolve between versions — if something does not compile, check the package's current samples rather than fighting the compiler.
- Tool choice is probabilistic, not guaranteed. The model can skip a tool it should use, call one it should not, or (rarely) produce arguments that fail your schema. Prompt instructions plus argument validation are load-bearing, not optional.
- Every tool definition and every tool result is resent with every round — tokens and latency grow with tool count and loop depth. Large tool catalogs need selection or filtering strategies.
- Each loop round is a full chat completion round trip; a two-tool chain means at least three model calls for one user question. Budget latency accordingly.
- The model only knows what tools return: a stale cache or wrong backend API response becomes a confidently wrong grounded answer.
- Not all deployments support tools equally — older or lightweight models may lack tool support or handle parallel tool calls poorly; verify your deployment's capabilities.
- Function calling shares tutorial 11's constraints: statelessness, rate limits, content filtering, and max tokens all still apply inside the loop.
- The security model here (validation, authorization, confirmation) is necessary but not complete — prompt injection and broader threats get full treatment in tutorial 24.
14 Best Practices
- Write descriptions for the model, not for teammates: state precisely when the tool applies and what it returns — that sentence is doing the routing.
- Keep tools few and orthogonal; overlapping tools force coin-flip choices. Split by intent, not by backend endpoint.
- Constrain with schemas: required lists, enums for fixed vocabularies, and per-parameter descriptions with examples ('e.g. ORD-1042').
- Say the quiet part in the system message: name the tools that are mandatory for given question types and forbid invented data.
- Treat every tool call as an untrusted request: deserialization in a try/catch, validation of every field, authorization as the signed-in user, least privilege per tool.
- Return structured errors as tool results — the model turns {"error": "order not found"} into a graceful apology; an unanswered call id kills the request.
- Cap loop rounds, log every tool call (name, arguments, outcome, user), and keep results compact JSON.
- Prefer low temperature for tool-using conversations; creative sampling and argument precision pull in opposite directions.
| Common mistake | Do this instead |
|---|---|
| Forgetting the assistant message before tool results | Append AssistantChatMessage(completion) first, then one ToolChatMessage per call id |
| Handling only the first tool call in a response | Iterate completion.ToolCalls — parallel tool calls are normal, not an edge case |
| Throwing exceptions from a failed tool | Catch inside the tool and return a structured JSON error the model can explain |
| Executing actions the moment the model asks | Validate, authorize, and require human confirmation for irreversible operations |
| Dumping raw query results to the model | Project to the few fields the answer needs — results are prompt tokens |
20 Summary
- Function calling lets a model request that your code run a named function with JSON arguments — the model proposes, your application validates, executes, and responds; it never runs anything itself.
- A tool is a name, a description, and a JSON schema; the description routes the model's choice, and enums plus required lists keep arguments well-formed.
- The orchestration loop connects model to backend APIs: branch on finish reason, append the assistant turn before tool results, answer every call id (parallel tool calls included), cap the rounds.
- Data retrieval tools ground answers in live data and are the low-risk starting point; business logic tools act on the world and demand validation, authorization, least privilege, idempotency, confirmation, and audit logs.
- Every definition and result is resent each round — few sharp tools, compact projected results, and per-round token logging keep cost and latency contained.
- Structured error results, explicit MUST rules in the system message, and low temperature turn probabilistic tool choice into dependable behavior.
You built the bridge between language and software: an assistant that checks a real order system before it speaks, declines to invent what it can look up, and fails gracefully when data is missing. The loop you wrote by hand — propose, execute, respond — is the primitive underneath every agent framework, Semantic Kernel plugin, and MCP server later in this course. From here on, the model is not just a text generator in your architecture; it is a component that can ask your application to do things — which is exactly why the next tutorial turns to integrating it into enterprise systems properly.
21 Next Steps
Next tutorial: Enterprise AI Integration Patterns (enterprise-ai-integration-patterns). You now have a model that can call tools — the next question is where that capability lives in a real system: service boundaries, gateways and middleware, resilience and cost controls, and the architectural patterns that keep an AI-enabled enterprise application maintainable, observable, and safe at scale.
- Practice: add a create_support_ticket action tool to the order assistant — validation, an idempotency key, and a printed confirmation prompt before 'creating' the ticket.
- Practice: register two deliberately overlapping tools, watch the model's confused routing in your trace logs, then fix it purely by rewriting the descriptions.
- Practice: make a two-order question ('compare ORD-1042 and ORD-377') trigger parallel tool calls, and execute them with Task.WhenAll.
- Practice: log token usage per round in the loop and measure how much a second tool and a longer result add to a conversation's cost.
- Read: the official 'Azure OpenAI function calling' how-to guide, the OpenAI .NET SDK samples on GitHub for tool use, and the 'JSON Schema' reference for parameter definitions.
15 Quiz: Function Calling and Tool Integration
Pick an answer for each question, then press Check answer. (Notes are disabled in this tab.)
1. In function calling, what does the model actually do when it 'calls' a function?
2. What are the three parts of a tool definition the model reads?
3. What is the primary purpose of the JSON schema in a tool definition?
4. A chat completion returns with finish reason ToolCalls. What must your code do?
5. Why must a ToolChatMessage carry the tool call's id?
6. What goes into the message history immediately BEFORE the tool result messages?
7. How does a data-retrieval tool reduce hallucination?
8. The model requests a tool your switch statement does not recognize. What is the right response?
9. What are parallel tool calls?
10. Which is the correct trust posture toward a tool call's arguments?
11. Why should business-logic tools be idempotent?
12. What does least privilege mean applied to tools?
13. How does the model decide WHICH registered tool to call?
14. Why should the orchestration loop cap its number of rounds?
15. When should the orchestration loop stop and display output?
16 Exam: Written Questions
Try answering each question yourself before expanding the model answer.
1. Define function calling in LLMs and explain why the phrase 'the model calls your API' is technically inaccurate.
2. Trace the complete round trip for 'Where is my order ORD-1042?' in a tool-enabled app, naming each message added to history.
3. You must define a tool for looking up invoices. Write out the three definition parts you would supply and one concrete quality rule for each.
4. Explain the two mechanical rules of returning tool results and what goes wrong when each is violated.
5. Compare data-retrieval tools and business-logic tools across purpose, risk, and required safeguards.
6. Why is grounding via tools more reliable than asking the model to 'not make things up', and where does grounding still fail?
7. Design the error-handling strategy for tool implementations. Cover malformed arguments, missing entities, and backend failures.
8. A response contains three parallel tool calls. Describe correct handling, including an optimization the independence of the calls permits.
9. Justify each safeguard on this list for a cancel_order tool: validation, authorization, confirmation, idempotency, audit logging.
10. How do tool definitions and tool results affect token consumption and latency, and what practices contain the cost?
11. Write the system message you would use for an order-support assistant with get_order_status and list_recent_orders, and explain each rule you included.
12. Explain why low temperature is recommended for tool-using conversations.
13. When is function calling the wrong mechanism, and what alternatives fit those cases?
14. How does function calling relate to agents, Semantic Kernel, and MCP later in this course?
15. Describe a test plan for the order assistant built in this tutorial, covering the happy path, error paths, and model-behavior checks.
17 Flashcards
Click a card to reveal the back.
Function calling
Tool definition (3 parts)
Tool call
Tool message (ToolChatMessage)
Finish reason: ToolCalls
Orchestration loop
Order of history entries after a tool turn
Grounding (via tools)
Data-retrieval tools
Business-logic tools
Parallel tool calls
Idempotent tool
Enum in a parameter schema
Tool error handling rule
Token cost of tools
18 Interview Questions and Answers
1. Explain function calling to a developer who has only used plain chat completions.
2. What makes a tool definition good or bad in practice?
3. Walk me through the orchestration loop you would write around tool calls.
4. How do you decide between exposing a capability as a tool versus just calling it in your pipeline?
5. What is your security model for a tool that mutates data?
6. The model keeps ignoring a tool it should use and inventing answers instead. How do you fix it?
7. How do you handle a tool whose backend throws an exception mid-call?
8. What changes when a response contains multiple parallel tool calls?
9. How do tools interact with token budgets and cost?
10. Distinguish grounding from RAG, and where function calling fits each.
11. What would you log around tool execution in production, and what have those logs caught for teams?
12. Why do enums in parameter schemas punch above their weight?
13. How would you test behavior that depends on a model's tool choices?
14. Where does function calling sit on the road to agents?
15. A product owner wants the assistant to issue refunds automatically 'since the model is usually right'. Your response?
19 Glossary
- Function calling
- A model capability: given tool definitions, the model can respond with a structured request to invoke one, instead of (or before) answering in text.
- Tool
- A capability registered with the model — function name, description, and JSON schema for parameters. Today's chat APIs implement tools as function tools.
- JSON schema
- The JSON document in a tool definition declaring parameter names, types, descriptions, enums, and the required list. Guides the model's argument generation.
- Tool call
- A single runtime request from the model: call id, function name, and JSON arguments. Untrusted input until parsed and validated.
- Tool call id
- The identifier pairing each tool call with its result message; with parallel calls, ids — not message order — do the matching.
- Tool message
- The message type (ToolChatMessage in .NET) returning a function result to the model, bound to a call id. One per id, always — even for errors.
- Orchestration loop
- The application loop around tool use: send history, branch on finish reason, execute calls, append results, repeat until a final answer; rounds capped.
- Finish reason (ToolCalls)
- The completion outcome signaling pending tool calls — the loop's cue to execute and respond rather than display content.
- Parallel tool calls
- Multiple tool invocations requested in one model turn; all must be executed and answered, and may run concurrently.
- Grounding
- Basing model output on supplied real data — here, tool results — rather than training memory. The structural remedy for live-data hallucination.
- Hallucination
- Fluent, plausible, false output. Most dangerous for live facts (orders, prices, stock) — precisely where retrieval tools apply.
- Backend API
- The internal service, database, or external endpoint a tool implementation wraps; the model reaches it only through your code's mediation.
- Data retrieval
- The read-only tool family: look up, search, check. Purpose is grounding; blast radius of error is a wasted query. Start here.
- Business logic
- Domain operations exposed as action tools (cancel, book, create). Demand validation, authorization, least privilege, idempotency, confirmation, and audit.
- Deserialization
- Parsing tool-call JSON arguments into typed C# values (JsonDocument/JsonSerializer) — defensively, inside try/catch, before any use.
- Validation
- Checking parsed arguments against format, range, existence, and permission rules before execution. Schemas guide the model; validation protects the system.
- Idempotent
- Property of a tool whose repeated execution causes no additional effect — required because models and retry logic re-issue calls.
- Least privilege
- Each tool granted only the narrowest access its purpose needs, executed under the requesting user's permissions — bounding worst-case damage.
- Structured error result
- A JSON error object (e.g. {"error": "order not found"}) returned as a tool result so the model can recover conversationally instead of the request failing.