A single agent answers a question with one model call, perhaps with two or three rounds of tools. The group-travel team of Nordlicht Reisen GmbH consists of seven agents (M-03), and planer calls sub-agents, which in turn call tools. One error in the control, a loop between texter and pruefer, and one request becomes dozens of calls. This lesson sets up the account so that this does not get expensive in the exercises: with a cap per run in code, with labels on every call and with a cleanup that also knows the new services of this course.

Prior knowledge. Account, groups as containers of an exercise, budget as a warning, creating resource and project, checking quota and soft delete and purge of the Foundry resource are taught by "AI-103, lesson 0.2"; they are not repeated here. That the monthly cap is code and the Azure budget only warns is stated there and in "AI track, lesson T10.3". That a span carries the AI function it belongs to is shown by "AI track, lesson T10.2". New here is the multiplied bill of the team and the labeling per tenant.

New in this lesson. The exercise group is called rg-ai500-start (M-09: rg-ai500-<umgebung> for the environments of the case, rg-ai500-<exercise> for exercises). For the exercises this applies: A run may make at most 20 model calls (assumption of the task). A mandantId has in the examples the form m- and four digits (m-0042), a laufId the form lauf- and three digits; both are made up. Later lessons use the cap and the labels; none changes them silently.

The requirement

All numbers are assumptions of the task.

No. Requirement Type Measure Weight Source
0.2-01 No run makes more than the set number of model calls. R 20 calls per run, hard limit in code Must M-11, R-09
0.2-02 Every model call carries mandantId, laufId and agent. F 3 of 3 labels Must M-11
0.2-03 The group of an exercise contains everything the exercise creates and can be deleted completely. R 1 group per exercise, check after deleting Must Costs
0.2-04 Before expensive services are created it is known what they cost by time or throughput. Q cost driver named per service, without amount Should R-09

Decision 1: how many calls may a run have?

On multi-agent flows, Learn says that they multiply the number of model calls: Every agent consumes tokens for instructions, context and tool calls, and with Magentic the consumption is hardest to predict. Learn advises measuring tokens per agent and per run and assigning each agent a model that fits its task. For the cap, a calculation on your own case is needed:

Item Calls Reason (assumption)
planer: decomposing and merging 2 one call at the start, one at the end
unterkunft, transport 6 one call each plus two tool rounds
kalkulation, recht 2 one call each (the calculating is done in code)
texter, pruefer 2 one call each
one revision (texter, pruefer) 2 at most once
Total in the regular case 14
Buffer for retries after errors 6
Cap 20

Decision note.

  • Context: A run of seven agents can multiply itself through a loop; the Azure budget reports that hours later, only by email.
  • Decision: The cap of 20 model calls per run is a counter in code. When it is reached, the run aborts and goes to the trip planner; it does not keep calculating.
  • Consequences: An error costs at most 20 calls. The number 20 is an assumption and must be confirmed with the test set (M-12).
  • Alternatives: Limiting only the quota of the deployment: it limits tokens per minute for a deployment, not calls per run (AI-103, lesson 0.2). Only the budget: warns, stops nothing.

Decision 2: counter and labels in one place

Counter and labels belong in the chat client through which every agent of the run talks, not in every agent. Every call passes through there, so no agent can forget it. The client below counts with a shared LaufZaehler, throws when the cap is exceeded and sets three labels on the span of the call (telemetry: "AI track, lesson T10.2"):

C#
public sealed class LaufZaehler(int obergrenze)
{
    private int aufrufe;
    public int Aufrufe => aufrufe;
    public int Obergrenze { get; } = obergrenze;

    public void Zaehlen()
    {
        if (Interlocked.Increment(ref aufrufe) > Obergrenze)
            throw new InvalidOperationException(
                $"Limit of {Obergrenze} model calls reached.");
    }
}

public sealed record LaufKontext(string MandantId, string LaufId, string Agent);

public sealed class GrenzClient(
    IChatClient inner, LaufZaehler zaehler, LaufKontext kontext)
    : DelegatingChatClient(inner)
{
    private static readonly ActivitySource Quelle = new("Nordlicht.Agenten");

    public override async Task<ChatResponse> GetResponseAsync(
        IEnumerable<ChatMessage> messages, ChatOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        zaehler.Zaehlen();
        using Activity? span = Quelle.StartActivity("modellaufruf");
        span?.SetTag("mandantId", kontext.MandantId);
        span?.SetTag("laufId", kontext.LaufId);
        span?.SetTag("agent", kontext.Agent);
        return await base.GetResponseAsync(messages, options, cancellationToken);
    }
}

Three things are deliberate. The counter counts before the call; the 21st does not go to the model. Interlocked keeps the count correct when unterkunft and transport run in parallel (F-01). All clients of a run share one LaufZaehler; each has its own LaufKontext with the name of its agent. The streaming path is missing in the example; a real client counts there as well.

The labels are the key for lesson 3.4 (traces per run) and 3.5 (costs per tenant): A trace without mandantId cannot be assigned to any customer, and a chargeback without assignment does not exist. Here it is enough that the labels are set from the start.

Decision 3: costs and cleanup of the new services

The exercises of this course need more services than AI-103. Cleanup stays one group per exercise; new are the cost drivers and a second lock for names. All statements without amounts; prices are on the pricing pages of the services:

Service (lessons) Cost driver Note when deleting
Foundry resource, deployments (all) tokens per million; capacity per hour for reserved deployments soft delete, name blocked (AI-103, lesson 0.2)
Azure AI Search (1.3, 2.5) search service per hour and tier deleted with the group
Azure Cosmos DB (1.3, 2.4) provisioned throughput and storage soft delete is a preview for Cosmos DB and not used here; what is deleted is gone
Service Bus (2.10) tier, messages deleted with the group
API Management (2.7, 4.1) tier per hour soft delete 48 hours, name not reusable in that time (Learn)
Container App (2.10, 4.6) consumption by usage deleted with the group

On API Management, Learn says: A deleted instance is soft-deleted, its name is then blocked, and after 48 hours it is permanently deleted. Purging works with az apim deletedservice purge --location <region> --service-name <name> and needs rights at the subscription level. For API Management the exercises take a name with an exercise suffix.

The account from AI-103, lesson 0.2 carries the rest: Your group rg-ai500-start is created with the same script if you change four things: the group, the project (gruppenreisen instead of reiseassistent, M-09), the marking kurs=ai500 and, if the Foundry resource ai-nordlicht from AI-103 is still soft-deleted, a different name or its purge. With -WhatIf you check it first, as there.

Verification status

The code above was compiled and run locally in a throwaway project with Microsoft.Extensions.AI 10.0.0 on .NET 10 (with a fake client). That proves syntax, types and the logic of the counter, not the behavior of a model. Nothing was run against Azure; the statements on API Management and Cosmos DB are read from Learn, not run. Open: whether deleting the group soft-deletes API Management as well (Learn names the portal and the REST interface); which tiers of API Management suffice (lesson 2.7).

Exercise 1

The cap of 20 depends on the assumptions of the table. Calculate again for this change: unterkunft and transport get three tool rounds each instead of two, and the case allows two revisions. What is the total in the regular case, and is a buffer of 6 enough?

Solution 1

With three tool rounds each of the two agents needs 4 calls, together 8 instead of 6 (plus 2). Two revisions cost 4 instead of 2 calls (plus 2). The total grows from 14 to 18. With a buffer of 6 it would be 24, and the cap of 20 would not suffice. Two ways: set the cap to 24 and write that as "changes", or keep the revisions in code at 1. The cap follows from the flow rules; it is not a free value.

Exercise 2

Write a program that runs the GrenzClient with a fake client without an account: cap 2, three calls in a row. Check that the third call fails with the message of the counter and that the fake client was called only twice.

Solution 2

C#
var zaehler = new LaufZaehler(obergrenze: 2);
var kontext = new LaufKontext("m-0042", "lauf-001", "planer");
IChatClient client = new GrenzClient(new FakeClient(), zaehler, kontext);

for (int i = 0; i < 3; i++)
{
    try
    {
        await client.GetResponseAsync("Hello");
        Console.WriteLine($"Call {i + 1} ok, counter {zaehler.Aufrufe}");
    }
    catch (InvalidOperationException e)
    {
        Console.WriteLine(e.Message);
    }
}

FakeClient is an IChatClient that always answers "ok" and otherwise leaves its methods empty (template: the fake from "AI-103, lesson 2.5"). Run locally, the program reports "ok" twice with counter 1 and 2 and at the third call the message "Limit of 2 model calls reached." The counter stands at 3 afterwards, because it counts attempts. The test proves the logic of the counter, not the behavior of a model.

Common reasoning errors

  • "The budget in Azure protects me from loops." It warns hours later and stops nothing; the counter in code stops before the call.
  • "The quota of the deployment limits the run." It limits tokens per minute for a deployment, not the calls of a run.
  • "Labels can be added later." Calls without mandantId cannot be assigned to a customer afterwards.
  • "Group deleted, name free." Foundry and API Management hold names blocked for a while after deleting.

Further reading

  • https://learn.microsoft.com/azure/architecture/ai-ml/guide/ai-agent-design-patterns
  • https://learn.microsoft.com/azure/api-management/soft-delete
  • https://learn.microsoft.com/azure/ai-services/recover-purge-resources
  • https://learn.microsoft.com/azure/cosmos-db/soft-delete