LLM Function Calling in C#: Letting the Model Safely Run Your Code
A language model can only generate text. It can't query your database, check a customer's order, or call your pricing service — which makes a raw LLM useless for most business features. Function calling (also called tool calling) is the bridge: you describe your C# methods to the model, and when a user's request needs one, the model responds with "call this function with these arguments" instead of prose. Your code runs the function, returns the result, and the model turns it into an answer.
Understanding this loop — really understanding it, not just letting a framework hide it — is the difference between debugging AI features confidently and cargo-culting. Let's build it raw first, then use the shortcuts.
The loop, with no framework magic
Three phases: describe, dispatch, resume.
The things that surprise people the first time:
- The model never executes anything. It emits a request — a function name and JSON arguments. Your code is the only thing that runs; you can inspect, validate, or refuse any call.
- It's a loop, not a single round trip. The model may chain calls (look up the order → then check the courier) — hence
while, notif. Cap the iterations; a confused model can loop forever, and each lap costs tokens. - Descriptions are the API docs the model reads. Vague description → wrong or missed calls. Write them like you're onboarding a junior: what it does, when to use it, what the arguments mean.
Parsing arguments defensively
The arguments arrive as model-generated JSON. Treat them exactly like a request body from the public internet:
Return errors as structured results rather than throwing — the model reads the error and can recover ("I couldn't find that order — could you double-check the number?"), which is much better UX than a 500.
The security rules that are not optional
Function calling hands a probabilistic text generator influence over code execution. The rules my teams enforce:
- Authorization lives in your code, never in the prompt. "Only allow users to query their own orders" as a system-prompt instruction is a suggestion, not a control — prompt-injected input can override it. The tool handler must check the authenticated user's permissions itself, using identity from your auth system, not from anything the model says.
- Expose capabilities, not power. A
get_order_status(orderNumber)tool, not anexecute_sql(query)tool. Every tool should be something you'd be comfortable exposing as a public API endpoint for that same user — because effectively, it is one. - Writes require confirmation. For state-changing tools (cancel order, issue refund), have the assistant confirm with the user first, and consider a human-approval step for high-value actions. Log every invocation with arguments.
- Remember prompt injection exists. If your assistant also reads untrusted content (emails, tickets, web pages), that content can contain instructions aimed at your tools ("...and now cancel all orders"). Rules 1–3 are your defense; prompt wording is not.
The shortcut, once you understand it
The manual loop above is ~60 lines per tool. Microsoft.Extensions.AI collapses it — reflection builds the JSON schema from your method signature, and middleware runs the loop:
Use this in production — it's less code to get wrong. But the security rules transfer unchanged (the framework runs the loop; you still own validation and authorization inside the method), and when tool calls misbehave, you now know exactly what's happening underneath.
Debugging tips from the trenches
- Log the full message sequence (assistant tool-call messages and tool results). Ninety percent of "the AI is being dumb" turns out to be a bad tool description or a malformed result payload.
- If the model won't call your tool, sharpen the description and add "use the available tools; never guess" to the system prompt.
- If it calls tools too eagerly, say when not to use them ("only for questions about specific orders").
Function calling is the moment an LLM stops being a text toy and becomes an integration point in your architecture. Treat it with API-boundary seriousness and it will carry a remarkable amount of product value.
Where to go next
- Getting Reliable JSON Out of LLMs in C# — the sibling technique for data extraction.
- Building Your First AI Chatbot with Semantic Kernel — tool calling inside a full chatbot.
- Getting Started with Microsoft.Extensions.AI — the abstraction that runs the loop for you.