You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I've been trying to figure out how we can get our agent orchestration token usage.
We're using the MagenticOrchestration orchestration and are able to get the agent consumption trough a responsecall back and chat history. But the orchestrator itself is also doing ai calls and using tokens which are no where to be able to be retrieved.
Does anyone have any idea where to get the orchestrators token usage from after the run is done?
publicasyncTask<AiResponseResult<string>>GetAgentPlainResponse(AiAgentDtoai,stringuserContent,CancellationTokencancellationToken){varchatHistory=newChatHistory();varagentKernel=awaitCreateAgentKernel(ai,chatHistory,cancellationToken);varrunTime=newInProcessRuntime();awaitrunTime.StartAsync(cancellationToken);varresult=awaitagentKernel.InvokeAsync(userContent,runTime,cancellationToken);varoutput=awaitresult.GetValueAsync(TimeSpan.FromSeconds(300),cancellationToken);awaitrunTime.RunUntilIdleAsync();varaiResponseResult=newAiResponseResult<string>{AiModelUsed=ai.Ai.AiModelId.ToString(),ReasoningTokensUsed=0,Result=output,UsedFiles=_usedFiles,InputTokensUsed=0,OutputTokensUsed=0};returnaiResponseResult;}privateasyncTask<MagenticOrchestration>CreateAgentKernel(AiAgentDtoaiAgent,ChatHistorychatHistory,CancellationTokencancellationToken=default){varagents=newList<Agent>();foreach(varainaiAgent.AiAgentConfigurations){varaiModel=awaitaiService.GetAiModelById(a.Ai.AiModelId,cancellationToken);if(aiModel==null)thrownewAiNotFoundException($"Cannot find AI model with ID {a.Ai.AiModelId}");varchatCompletionAgentKernel=Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(aiModel.DeploymentName,azureOpenAiClient);awaitAddAiPluginsInKernelBuilder(chatCompletionAgentKernel,a.Ai,cancellationToken);varkernel=chatCompletionAgentKernel.Build();awaitAddAiPluginsInKernel(kernel,a.Ai,cancellationToken);agents.Add(newChatCompletionAgent{Name=a.Name,Kernel=kernel,Description=a.Description,Instructions=a.Ai.Prompt,Arguments=newKernelArguments(newAzureOpenAIPromptExecutionSettings{FunctionChoiceBehavior=FunctionChoiceBehavior.Auto(autoInvoke:true),Temperature=a.Ai.Temperature})});}varmanagerAiModel=awaitaiService.GetAiModelById(aiAgent.Ai.AiModelId,cancellationToken);if(managerAiModel==null)thrownewAiNotFoundException($"Cannot find AI model with ID {aiAgent.Ai.AiModelId}");varmanagerKernel=Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(managerAiModel.DeploymentName,azureOpenAiClient).Build();ValueTaskResponseCallback(ChatMessageContentresponse){chatHistory.Add(response);returnValueTask.CompletedTask;}varmanager=newStandardMagenticManager(managerKernel.GetRequiredService<IChatCompletionService>(),newAzureOpenAIPromptExecutionSettings{ResponseFormat=aiAgent.Ai.OutputSchema??"text",ChatSystemPrompt=aiAgent.Ai.Prompt}){MaximumInvocationCount=aiAgent.MaxInvocations};varorchestration=newMagenticOrchestration(manager,agents.ToArray()){ResponseCallback=ResponseCallback,Description=aiAgent.Ai.Prompt,Name=aiAgent.Name};returnorchestration;}
The StandardMagenticManager is a sealed class that uses the IChatCompletionService you hand it for its own LLM calls — PlanAsync, ReplanAsync, EvaluateTaskProgressAsync, PrepareFinalAnswerAsync, plus the internal facts/plan prompts. You can see this in StandardMagenticManager.cs: every internal call goes through this._service.GetChatMessageContentAsync(...), and the manager keeps response.Content only — the ChatMessageContent.Metadata["Usage"] is never surfaced through the orchestration API.
Two approaches that work today.
1. Decorate the IChatCompletionService you pass to the manager (cleanest)
Wrap your manager's chat service so it accumulates the Usage from every response's metadata, then read the counters after the orchestration finishes:
internalsealedclassUsageTrackingChatCompletionService(IChatCompletionServiceinner):IChatCompletionService{privatelong_promptTokens,_completionTokens,_totalTokens;publiclongPromptTokens=>Interlocked.Read(ref_promptTokens);publiclongCompletionTokens=>Interlocked.Read(ref_completionTokens);publiclongTotalTokens=>Interlocked.Read(ref_totalTokens);publicIReadOnlyDictionary<string,object?>Attributes=>inner.Attributes;publicasyncTask<IReadOnlyList<ChatMessageContent>>GetChatMessageContentsAsync(ChatHistorychatHistory,PromptExecutionSettings?executionSettings=null,Kernel?kernel=null,CancellationTokencancellationToken=default){varresults=awaitinner.GetChatMessageContentsAsync(chatHistory,executionSettings,kernel,cancellationToken).ConfigureAwait(false);foreach(varrinresults){// Azure OpenAI / OpenAI surface usage under metadata key "Usage" as OpenAI.Chat.ChatTokenUsage.if(r.Metadatais{}md&&md.TryGetValue("Usage",outvarusageObj)&&usageObjisOpenAI.Chat.ChatTokenUsageusage){Interlocked.Add(ref_promptTokens,usage.InputTokenCount);Interlocked.Add(ref_completionTokens,usage.OutputTokenCount);Interlocked.Add(ref_totalTokens,usage.TotalTokenCount);}}returnresults;}publicasyncIAsyncEnumerable<StreamingChatMessageContent>GetStreamingChatMessageContentsAsync(ChatHistorychatHistory,PromptExecutionSettings?executionSettings=null,Kernel?kernel=null,[EnumeratorCancellation]CancellationTokencancellationToken=default){awaitforeach(varchunkininner.GetStreamingChatMessageContentsAsync(chatHistory,executionSettings,kernel,cancellationToken).ConfigureAwait(false)){// The final streaming chunk carries Usage when stream_options.include_usage=true.if(chunk.Metadatais{}md&&md.TryGetValue("Usage",outvarusageObj)&&usageObjisOpenAI.Chat.ChatTokenUsageusage){Interlocked.Add(ref_promptTokens,usage.InputTokenCount);Interlocked.Add(ref_completionTokens,usage.OutputTokenCount);Interlocked.Add(ref_totalTokens,usage.TotalTokenCount);}yieldreturnchunk;}}}
Wire it in where you build the manager:
varrawManagerChat=managerKernel.GetRequiredService<IChatCompletionService>();varmanagerUsage=newUsageTrackingChatCompletionService(rawManagerChat);varmanager=newStandardMagenticManager(managerUsage,newAzureOpenAIPromptExecutionSettings{ResponseFormat=aiAgent.Ai.OutputSchema??"text",ChatSystemPrompt=aiAgent.Ai.Prompt}){MaximumInvocationCount=aiAgent.MaxInvocations};// ... run orchestration ...aiResponseResult.InputTokensUsed+=(int)managerUsage.PromptTokens;aiResponseResult.OutputTokensUsed+=(int)managerUsage.CompletionTokens;
If you also want one combined total for the whole run, wrap each agent's IChatCompletionService the same way and sum the counters — or keep the per-agent counters you already extract from ResponseCallback's Metadata["Usage"] and just add the manager's counter to them at the end.
2. Use OpenTelemetry (recommended for production)
SK already emits a span per chat completion call with the gen_ai.usage.input_tokens / gen_ai.usage.output_tokens attributes (and the gen_ai.client.token.usage metric). With SK's OTel sources enabled, every manager call and every agent call shows up as a child span under the same orchestration trace, and you can aggregate token counts across all spans of a trace without modifying the manager at all. See the Observability docs and the OTel samples under dotnet/samples/Demos/TelemetryWithAppInsights. This is what I would do if there's already an APM in the picture.
Why subclassing the manager isn't an option
StandardMagenticManager is sealed, so you can't override its internals to surface ChatMessageContent.Metadata["Usage"]. The public base MagenticManager is abstract, so you can write a manager from scratch — but for "I just want token counts" that's far more work than the decorator above.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Hey all,
I've been trying to figure out how we can get our agent orchestration token usage.
We're using the
MagenticOrchestrationorchestration and are able to get the agent consumption trough a responsecall back and chat history. But the orchestrator itself is also doing ai calls and using tokens which are no where to be able to be retrieved.Does anyone have any idea where to get the orchestrators token usage from after the run is done?
All reactions