DOTNET EXPERT BLOG

Function Calling in C# with IChatClient and ASP.NET Core | Day 7

10/2/2026 4:10:29 PM Noor All Safaet Loading... 0


Our AI application can now chat, remember conversations, stream responses, and return structured data.

But it still has an important limitation.

Suppose a user asks:

How many Wireless Mouse units do we currently have in stock?

The model should not guess.

Current stock belongs to our application or database. Instead of expecting the model to know it, we want the model to request an approved C# function:

User
  ↓
AI Model
  ↓
Needs Current Stock
  ↓
C# Function
  ↓
Application Service
  ↓
Real Data
  ↓
AI Model
  ↓
Final Answer

This is function calling, also commonly called tool calling.

In Day 7 of our C# AI tutorial series, we will connect an AI model to real C# application capabilities using Microsoft.Extensions.AI.

We will learn how to:

  • create AI-callable C# functions

  • use AIFunctionFactory.Create

  • expose tools through ChatOptions.Tools

  • configure .UseFunctionInvocation()

  • connect AI tools to application services

  • validate AI-generated function arguments

  • protect authorization and tenant boundaries

  • distinguish read tools from write tools

  • avoid giving AI unrestricted database access

By the end, our AI assistant will be able to retrieve real inventory information instead of inventing it.

Previously in This Series

We have been building one practical C# AI application step by step.

Day 1: Build Your First AI Application with C# and .NET
Connected C# to an AI model.

Day 2: Understanding IChatClient
Introduced the provider-independent AI abstraction.

Day 3: Build a Reusable AI Chat Service in ASP.NET Core
Created an ASP.NET Core API with dependency injection.

Day 4: Add Conversation Memory
Added conversation IDs and multi-turn history.

Day 5: Stream AI Responses
Streamed generated content from the model to the browser.

Day 6: Structured AI Output
Converted AI responses into strongly typed C# data.

Today we move from:

Application
    ↓
Data
    ↓
AI
    ↓
Response

to:

AI
 ↓
Requests Approved Capability
 ↓
C# Function
 ↓
Application Data
 ↓
AI
 ↓
Final Response

What Is Function Calling in AI?

Function calling allows a model to request that the application execute a predefined tool.

Suppose the user asks:

What is the current stock of Wireless Mouse?

The model should not have direct database access.

Instead, our application exposes a capability such as:

GetProductStockAsync("Wireless Mouse")

The model can determine that it needs this function.

Our application executes it and might return:

{
  "productName": "Wireless Mouse",
  "currentStock": 4,
  "reorderLevel": 10
}

The model can then answer:

Wireless Mouse currently has 4 units in stock.
Its reorder level is 10, so stock is below the configured threshold.

The important point is that 4 came from our application data, not from the model's guess.

Function Calling Does Not Give AI Direct Access to C#

The model does not receive permission to execute arbitrary C# code.

The actual relationship is:

Developer
   ↓
Defines Approved Tools
   ↓
Model Sees Tool Metadata
   ↓
Model Requests a Tool
   ↓
Application Executes It

The application remains in control of which capabilities exist and what they are allowed to do.

Function Calling vs Structured Output

Day 6 introduced structured output.

Structured output answers:

What structure should the model return?

For example:

{
  "riskLevel": "High",
  "recommendedReorderQuantity": 20
}

Function calling answers:

What application capability should the model request?

For example:

GetProductStock("Wireless Mouse")

They can also work together:

Function Call
     ↓
Retrieve Real Data
     ↓
AI Analysis
     ↓
Structured Result

Function Calling vs RAG

RAG usually retrieves relevant information from a knowledge source:

Question
   ↓
Search / Retrieval
   ↓
Relevant Content
   ↓
AI Model

Function calling lets the model request an application capability:

Question
   ↓
AI Model
   ↓
C# Tool
   ↓
Application Service
   ↓
Database / API
   ↓
Tool Result
   ↓
AI Model

A real AI application may use both.

Our Day 7 Inventory Example

We will create one safe, read-only tool:

get_product_stock

The user can ask:

How many Wireless Mouse units do we have?

Our architecture becomes:

User
  ↓
Inventory Assistant
  ↓
IChatClient
  ↓
AI Model
  ↓
get_product_stock
  ↓
Inventory Service
  ↓
Real Inventory Data
  ↓
AI Model
  ↓
Final Answer

We will intentionally start with a read-only capability.

The model cannot change stock, delete products, update prices, or create purchase orders.

Step 1: Create the Inventory Data Model

Create:

Models/ProductStockInfo.cs

Add:

public sealed class ProductStockInfo
{
    public string ProductName { get; set; }
        = string.Empty;

    public int CurrentStock { get; set; }

    public int ReorderLevel { get; set; }
}

This represents the data our inventory service returns.

Step 2: Create the Inventory Service

AI tools should not contain database logic directly.

Create:

Services/IInventoryService.cs

Add:

public interface IInventoryService
{
    Task<ProductStockInfo?> GetProductStockAsync(
        string productName,
        CancellationToken cancellationToken = default);
}

In a real application, this service could use:

  • Entity Framework Core

  • Dapper

  • SQL Server

  • PostgreSQL

  • an ERP API

  • another internal service

For Day 7, we will use in-memory data so we can focus specifically on function calling.

Step 3: Implement the Inventory Service

Create:

Services/InventoryService.cs

Add:

public sealed class InventoryService
    : IInventoryService
{
    private static readonly List<ProductStockInfo>
        Products =
        [
            new()
            {
                ProductName = "Wireless Mouse",
                CurrentStock = 4,
                ReorderLevel = 10
            },

            new()
            {
                ProductName = "Mechanical Keyboard",
                CurrentStock = 18,
                ReorderLevel = 8
            },

            new()
            {
                ProductName = "USB-C Hub",
                CurrentStock = 0,
                ReorderLevel = 5
            }
        ];

    public Task<ProductStockInfo?> GetProductStockAsync(
        string productName,
        CancellationToken cancellationToken = default)
    {
        ProductStockInfo? product =
            Products.FirstOrDefault(
                x => string.Equals(
                    x.ProductName,
                    productName,
                    StringComparison.OrdinalIgnoreCase));

        return Task.FromResult(product);
    }
}

Later, this implementation can become:

InventoryService
      ↓
EF Core / Dapper
      ↓
PostgreSQL / SQL Server

without changing the AI-facing architecture.

Step 4: Register the Inventory Service

In Program.cs:

builder.Services.AddScoped<
    IInventoryService,
    InventoryService>();

Now the service is available through ASP.NET Core dependency injection.

Step 5: Create the AI Tool

Create:

AI/InventoryTools.cs

Add:

using System.ComponentModel;

public sealed class InventoryTools
{
    private readonly IInventoryService
        _inventoryService;

    public InventoryTools(
        IInventoryService inventoryService)
    {
        _inventoryService =
            inventoryService;
    }

    [Description(
        "Gets the current stock and reorder level for a product.")]
    public async Task<ProductStockInfo?> GetProductStockAsync(
        [Description(
            "The exact product name to look up.")]
        string productName,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(productName))
        {
            throw new ArgumentException(
                "Product name is required.",
                nameof(productName));
        }

        if (productName.Length > 200)
        {
            throw new ArgumentException(
                "Product name is too long.",
                nameof(productName));
        }

        return await _inventoryService
            .GetProductStockAsync(
                productName.Trim(),
                cancellationToken);
    }
}

The tool creates a controlled boundary:

AI
 ↓
InventoryTools
 ↓
IInventoryService
 ↓
Business Data

Notice that we validate productName.

Function arguments originate from the AI system and should be treated as untrusted input.

Why Tool Descriptions Matter

We added:

[Description(
    "Gets the current stock and reorder level for a product.")]

The model needs to understand what a tool does and when it is appropriate.

Compare:

Does inventory stuff.

with:

Gets the current stock and reorder level for a product.

Clear names and descriptions make tool selection more reliable.

Step 6: Create an AIFunction

Microsoft.Extensions.AI provides AIFunctionFactory for turning .NET methods into AI-callable functions.

For example:

AIFunction getProductStock =
    AIFunctionFactory.Create(
        _inventoryTools.GetProductStockAsync,
        name: "get_product_stock",
        description:
            "Gets current stock and reorder level for a product.");

Conceptually:

C# Method
    ↓
AIFunctionFactory
    ↓
AIFunction
    ↓
Tool Available to Model

Using an explicit AI-facing name lets us keep the normal .NET method name:

GetProductStockAsync

while exposing:

get_product_stock

to the model.

Step 7: Enable Automatic Function Invocation

Providing a function definition is only part of the process.

If the model requests:

get_product_stock(
    productName = "Wireless Mouse")

something must execute that function and send its result back to the model.

Configure the IChatClient pipeline with:

.AsBuilder()
.UseFunctionInvocation()
.Build();

For example:

using Microsoft.Extensions.AI;
using OpenAI;

builder.Services.AddSingleton<IChatClient>(
    sp =>
    {
        string apiKey =
            builder.Configuration["AI:ApiKey"]
            ?? throw new InvalidOperationException(
                "AI:ApiKey is not configured.");

        string model =
            builder.Configuration["AI:Model"]
            ?? throw new InvalidOperationException(
                "AI:Model is not configured.");

        IChatClient client =
            new OpenAIClient(apiKey)
                .GetChatClient(model)
                .AsIChatClient();

        return client
            .AsBuilder()
            .UseFunctionInvocation()
            .Build();
    });

The function-invocation middleware can now handle the common tool-calling loop.

How Automatic Function Invocation Works

Conceptually:

User Message
     ↓
AI Model
     ↓
Function Requested
     ↓
Function Invocation Middleware
     ↓
AIFunction Executes
     ↓
Function Result
     ↓
AI Model
     ↓
Final Answer

Without automatic invocation, your application would need to inspect function-call content, execute the matching function, add the result to the conversation, and call the model again.

For our current scenario, middleware keeps that infrastructure out of the business service.

Step 8: Create the Inventory Assistant Service

Create:

Services/IInventoryAssistantService.cs

Add:

public interface IInventoryAssistantService
{
    Task<string> AskAsync(
        string message,
        CancellationToken cancellationToken = default);
}

Then create:

Services/InventoryAssistantService.cs

Add:

using Microsoft.Extensions.AI;

public sealed class InventoryAssistantService
    : IInventoryAssistantService
{
    private const string SystemPrompt =
        """
        You are an inventory assistant.

        Use the available inventory tool whenever
        current inventory information is required.

        Never invent current stock quantities.

        If a product cannot be found, clearly say so.

        The available inventory tools are read-only.
        Never claim that inventory has been changed.
        """;

    private readonly IChatClient _chatClient;
    private readonly InventoryTools _inventoryTools;
    private readonly ILogger<InventoryAssistantService>
        _logger;

    public InventoryAssistantService(
        IChatClient chatClient,
        InventoryTools inventoryTools,
        ILogger<InventoryAssistantService> logger)
    {
        _chatClient = chatClient;
        _inventoryTools = inventoryTools;
        _logger = logger;
    }

    public async Task<string> AskAsync(
        string message,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(message))
        {
            throw new ArgumentException(
                "Message is required.",
                nameof(message));
        }

        AIFunction getProductStock =
            AIFunctionFactory.Create(
                _inventoryTools.GetProductStockAsync,
                name: "get_product_stock",
                description:
                    "Gets current stock and reorder level for a product.");

        var options = new ChatOptions
        {
            Tools =
            [
                getProductStock
            ]
        };

        var messages =
            new List<ChatMessage>
            {
                new(
                    ChatRole.System,
                    SystemPrompt),

                new(
                    ChatRole.User,
                    message)
            };

        _logger.LogInformation(
            "Sending inventory assistant request.");

        ChatResponse response =
            await _chatClient.GetResponseAsync(
                messages,
                options,
                cancellationToken);

        return response.Text;
    }
}

This service does not know how stock is stored.

Its responsibility is coordinating:

User Request
+
AI Client
+
Available Tools

The actual inventory logic stays behind IInventoryService.

Step 9: Register the Tool and Assistant

In Program.cs:

builder.Services.AddScoped<InventoryTools>();

builder.Services.AddScoped<
    IInventoryAssistantService,
    InventoryAssistantService>();

Our dependency flow becomes:

InventoryAssistantService
        ↓
   InventoryTools
        ↓
 IInventoryService
        ↓
 Inventory Data

Step 10: Create the API Request

Create:

Models/InventoryAssistantRequest.cs

Add:

using System.ComponentModel.DataAnnotations;

public sealed class InventoryAssistantRequest
{
    [Required]
    [StringLength(
        2000,
        MinimumLength = 1)]
    public string Message { get; set; }
        = string.Empty;
}

The HTTP request is validated before it reaches the AI service.

Step 11: Create the Controller

Create:

Controllers/InventoryAssistantController.cs

Add:

using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/inventory-assistant")]
public sealed class InventoryAssistantController
    : ControllerBase
{
    private readonly IInventoryAssistantService
        _assistantService;

    public InventoryAssistantController(
        IInventoryAssistantService assistantService)
    {
        _assistantService =
            assistantService;
    }

    [HttpPost("ask")]
    public async Task<ActionResult<object>> AskAsync(
        [FromBody]
        InventoryAssistantRequest request,
        CancellationToken cancellationToken)
    {
        string answer =
            await _assistantService.AskAsync(
                request.Message,
                cancellationToken);

        return Ok(new
        {
            answer
        });
    }
}

The endpoint is:

POST /api/inventory-assistant/ask

Step 12: Test Function Calling

Send:

{
  "message": "How many Wireless Mouse units do we currently have?"
}

The flow becomes:

User Question
      ↓
AI Model
      ↓
Needs Current Stock
      ↓
get_product_stock
      ↓
InventoryService
      ↓
CurrentStock = 4
ReorderLevel = 10
      ↓
AI Model
      ↓
Final Answer

A possible API response is:

{
  "answer": "Wireless Mouse currently has 4 units in stock. Its reorder level is 10, so stock is below the configured threshold."
}

The wording can vary.

The important fact is that the stock quantity came from our application.

What Happens Behind the Scenes?

The function-calling cycle contains several steps.

1. The Application Sends the Message and Tool

The model receives the user's question together with metadata for:

get_product_stock

2. The Model Requests the Tool

Conceptually, it requests:

{
  "function": "get_product_stock",
  "arguments": {
    "productName": "Wireless Mouse"
  }
}

3. The Application Executes the Approved Function

The invocation middleware matches the request to our AIFunction.

That eventually calls:

GetProductStockAsync(
    "Wireless Mouse")

4. The Tool Returns Real Data

For example:

{
  "productName": "Wireless Mouse",
  "currentStock": 4,
  "reorderLevel": 10
}

5. The Model Generates the Final Answer

The tool result becomes available to the model, which can explain it naturally.

With automatic function invocation configured, this loop can happen behind one application-level:

GetResponseAsync(...)

call.

Why the AI Should Not Query the Database Directly

A tempting architecture is:

AI
 ↓
Database

A better boundary is:

AI
 ↓
Approved Tool
 ↓
Application Service
 ↓
Authorization / Business Rules
 ↓
Database

The application should remain responsible for:

  • data access

  • authorization

  • tenant isolation

  • validation

  • business rules

  • logging

  • auditing

The model receives capabilities, not unrestricted infrastructure access.

Treat Tool Arguments as Untrusted Input

The model may generate:

productName = "Wireless Mouse"

but AI-generated arguments should still be validated.

Our tool checks:

string.IsNullOrWhiteSpace(productName)

and:

productName.Length > 200

Other tools may need validation for:

  • identifiers

  • quantities

  • dates

  • financial amounts

  • search filters

  • URLs

  • status transitions

Function calling does not replace application validation.

Keep Security Context Outside the Model

Suppose this becomes a multi-tenant inventory application.

The model might request:

get_product_stock(productId = 123)

The application should obtain the current tenant from trusted authentication context.

Do not rely on the model to provide:

TenantId
UserId
Role
Permission

A database query should conceptually enforce:

Authenticated Tenant
        +
Product ID
        ↓
Authorized Query

The same principle applies to user permissions.

A model's request to execute a tool is not authorization.

Prefer Stable IDs When Appropriate

Product names are convenient for our tutorial.

Production systems often have stable identifiers such as:

ProductId
SKU
WarehouseId

For example:

GetProductStockAsync(
    Guid productId,
    Guid warehouseId)

can be less ambiguous than a product-name lookup.

If the user only knows the name, the tool workflow could be:

search_products
      ↓
Product ID
      ↓
get_product_stock

This becomes useful as the tool set grows.

Read Tools vs Write Tools

Our current tool only reads information:

get_product_stock

Read-only tools are a good starting point.

Other examples include:

search_products
get_order_status
get_invoice_details
get_customer_balance

Write tools have greater consequences:

create_purchase_order
update_product_price
cancel_order
issue_refund

For write operations, a stronger flow is:

AI Requests Action
       ↓
Validate Arguments
       ↓
Check Authorization
       ↓
Check Business Rules
       ↓
Approval if Required
       ↓
Execute

The AI can request an action, but the application remains the authority.

Return Clear Tool Results

Our tutorial returns:

ProductStockInfo?

and uses null when the product does not exist.

For larger applications, an explicit result contract can be clearer:

public sealed class ProductStockLookupResult
{
    public bool Found { get; set; }

    public string? ProductName { get; set; }

    public int? CurrentStock { get; set; }

    public int? ReorderLevel { get; set; }
}

A missing product can then return:

{
  "found": false,
  "productName": null,
  "currentStock": null,
  "reorderLevel": null
}

This makes tool results easier for both application code and the model to interpret.

Adding Multiple AI Tools

Our assistant currently exposes one capability.

Later we could add:

search_products
get_product_stock
get_recent_sales
get_supplier_information

Then:

var options = new ChatOptions
{
    Tools =
    [
        searchProducts,
        getProductStock,
        getRecentSales
    ]
};

A question such as:

Is Wireless Mouse selling quickly,
and should we reorder it?

might require both stock and recent-sales information.

However, more tools are not automatically better.

Prefer a focused tool set with clear, non-overlapping responsibilities.

Function Calling with Conversation Memory

Day 4 introduced conversation memory.

Consider:

User:
What is the stock of Wireless Mouse?

Assistant:
There are 4 units.

User:
What about USB-C Hub?

Conversation history helps the model understand what:

What about...

means.

The tool provides the current data.

So:

Conversation Memory
        +
Function Calling
        ↓
Context-Aware Assistant

Memory provides conversational context.

Tools provide application capabilities.

Function Calling with Streaming and Structured Output

The concepts from previous days can also be combined.

With streaming:

User
 ↓
Model
 ↓
Tool Call
 ↓
Tool Result
 ↓
Model
 ↓
Streaming Response

With structured output:

User
 ↓
Tool Call
 ↓
Real Data
 ↓
AI Analysis
 ↓
Structured C# Result

For Day 7, we keep the final response simple so the function-calling mechanics remain easy to understand.

Function Calling Is a Building Block for Agents

Function calling gives the model access to approved capabilities.

More advanced agent systems can add:

  • multiple tools

  • workflow state

  • repeated tool use

  • planning

  • memory

  • approvals

  • checkpoints

  • background execution

So function calling is an important building block, but it does not automatically make every application an autonomous agent.

Handle Tool Errors Carefully

Suppose the inventory database becomes unavailable.

Avoid exposing raw exceptions to the model if they contain internal details such as:

  • database server names

  • connection information

  • file paths

  • internal service names

Log appropriate technical details internally.

Expose a controlled result such as:

Inventory information is temporarily unavailable.

This keeps internal infrastructure details out of AI-facing responses.

Avoid Overpowered Tools

Prefer:

get_product_stock

over generic capabilities such as:

execute_sql
run_command
delete_any_record
call_any_url

Narrow tools are easier to:

  • validate

  • authorize

  • test

  • audit

  • monitor

The goal is not to give the model maximum power.

The goal is to give it the minimum capability required for the task.

Suggested Project Structure After Day 7

Our project can now look like:

CSharpAiApi
│
├── AI
│   └── InventoryTools.cs
│
├── Controllers
│   ├── AiController.cs
│   ├── InventoryAiController.cs
│   └── InventoryAssistantController.cs
│
├── Models
│   ├── AiChatResult.cs
│   ├── AiConversation.cs
│   ├── AiStreamContext.cs
│   ├── ChatRequest.cs
│   ├── InventoryAnalysisRequest.cs
│   ├── InventoryAnalysisResult.cs
│   ├── InventoryAssistantRequest.cs
│   ├── InventoryRiskLevel.cs
│   ├── ProductStockInfo.cs
│   └── StockStatus.cs
│
├── Services
│   ├── IAiChatService.cs
│   ├── AiChatService.cs
│   ├── IConversationStore.cs
│   ├── InMemoryConversationStore.cs
│   ├── IInventoryAiService.cs
│   ├── InventoryAiService.cs
│   ├── IInventoryAssistantService.cs
│   ├── InventoryAssistantService.cs
│   ├── IInventoryService.cs
│   └── InventoryService.cs
│
├── Program.cs
└── appsettings.json

The important architectural boundary is:

AI
 ↓
Approved Tools
 ↓
Application Services
 ↓
Business Data

Common Function Calling Problems

The Model Never Calls the Function

Check that:

  • the selected model supports tool calling

  • the tool is included in ChatOptions.Tools

  • the tool name and description are clear

  • .UseFunctionInvocation() is configured for automatic invocation

  • the provider adapter supports the capability

  • the user's request actually requires the tool

A model does not need to call a tool simply because one is available.

The Function Is Requested but Not Executed

Providing tools and automatically executing them are separate concerns.

For automatic invocation, verify that the client pipeline includes:

.AsBuilder()
.UseFunctionInvocation()
.Build();

Otherwise, your application may need to process function-call content manually.

The Model Invents Current Data

The system prompt should explicitly say:

Use the inventory tool whenever current inventory information is required.

Never invent current stock quantities.

More importantly, business decisions should rely on verified application data rather than unsupported model statements.

Invalid Function Arguments

Always validate arguments before passing them to your application services.

AI-generated input should not bypass the same boundaries you would apply to other external input.

Production Checklist

Before using function calling in production, review:

  • tool names and descriptions

  • argument validation

  • result contracts

  • authentication

  • authorization

  • tenant isolation

  • read vs write operations

  • business rules

  • approval requirements

  • error handling

  • timeout and cancellation

  • invocation limits

  • logging and auditing

  • sensitive-data exposure

  • provider and model support

  • AI usage and cost

Function calling is powerful precisely because it connects model decisions to real application capabilities.

Keep that boundary controlled.

What We Built Today

Before Day 7:

Application Data
       ↓
AI
       ↓
Response

Now:

User
 ↓
AI Model
 ↓
Tool Request
 ↓
AIFunction
 ↓
C# Application Service
 ↓
Business Data
 ↓
Tool Result
 ↓
AI Model
 ↓
Final Response

We added:

  • IInventoryService

  • InventoryService

  • InventoryTools

  • AIFunction

  • AIFunctionFactory.Create

  • ChatOptions.Tools

  • .UseFunctionInvocation()

  • InventoryAssistantService

  • function-argument validation

  • read-only tool boundaries

  • authorization and tenant-isolation principles

Our AI application can now use real C# capabilities without receiving unrestricted access to the system.

Day 7 Checklist

Before moving to Day 8, make sure you understand:

  • what AI function calling means

  • what AIFunction represents

  • what AIFunctionFactory.Create does

  • how ChatOptions.Tools exposes capabilities

  • what .UseFunctionInvocation() provides

  • how tool results return to the model

  • why AI-generated arguments require validation

  • why application services should sit behind AI tools

  • why the model should not directly access the database

  • why tenant and user identity must come from trusted application context

  • why a tool request is not authorization

  • the difference between read and write tools

  • how function calling differs from structured output and RAG

  • why narrow tools are safer than generic infrastructure access

What's Next: Day 8

Our inventory tool currently reads from:

private static readonly List<ProductStockInfo>

That is useful for learning function calling, but real applications store business data in databases.

In Day 8: Connect AI to a Database in C# with ASP.NET Core and EF Core, we will replace the temporary list with real database access.

The architecture will become:

User
 ↓
AI
 ↓
Function Call
 ↓
C# Tool
 ↓
Application Service
 ↓
Entity Framework Core
 ↓
Database
 ↓
Real Business Data
 ↓
AI
 ↓
Final Response

We will cover:

  • EF Core integration

  • product entities

  • DbContext

  • asynchronous queries

  • safe read-only AI data access

  • dependency injection

  • tenant filtering

  • avoiding arbitrary AI-generated SQL

  • query performance

  • keeping AI outside the data-access layer

That will connect our assistant to real application data while preserving the architecture and security boundaries established today.

Final Thoughts

Function calling changes what an AI application can do.

Before Day 7, the model could only work with information already provided in its request.

Now it can request an approved capability:

get_product_stock

But it still does not receive unrestricted database or application access.

We maintain a controlled path:

AI
 ↓
Approved Tool
 ↓
Validated Input
 ↓
Application Service
 ↓
Business Data

The central principle is simple:

The model can request a capability. The application remains the authority.

Day 1 connected C# to an AI model.

Day 2 introduced IChatClient.

Day 3 built a reusable ASP.NET Core architecture.

Day 4 added conversation memory.

Day 5 added response streaming.

Day 6 introduced structured output.

Day 7 connected the model to real C# application capabilities through function calling.

Next, we will connect those capabilities to a real database with Entity Framework Core.

Comments 0