Skip to content

AI scenarios

Parallel tools

Bit.BlazorUI.AI Scripted transport

Two calls in flight at once, each with its own card, status, and timing.

Live demo

Compare two cities: the model asks for the weather in both at once.

\n**Two calls in one round**: ```razor @implements IAsyncDisposable
``` ```csharp private BitAiChatSession? session; private readonly string[] suggestions = ["Compare the weather in Oslo and Lisbon"]; [Inject] private IBitAiToolRegistry Tools { get; set; } = default!; [Inject] private BitAiChatSessionFactory SessionFactory { get; set; } = default!; // A transport of its own, with the registered tools and the session options configured in Program.cs // (SessionFactory.Defaults), which is where the limit of six rounds comes from. protected override void OnInitialized() => session = new BitAiChatSession(AiDemoToolTransports.CallsTwoTools(), options: SessionFactory.Defaults, tools: Tools); public ValueTask DisposeAsync() => session?.DisposeAsync() ?? ValueTask.CompletedTask; ``` `Program.cs`: ```csharp builder.Services.AddBitBlazorUIAIServices(ai => { ai.UseTransport(AiDemoTransports.Echo()); // The tool both calls ask for (its types are on the Tool calling scenario). ai.AddTool(BitAiTool.Create( "get_weather", "Look up the current weather for a city.", AiDemoJsonContext.Default.WeatherArgs, AiDemoJsonContext.Default.WeatherResult, async (args, token) => { await Task.Delay(700, token); return Weather(args.City); }, definition => definition with { Title = "Weather", Icon = BitIconName.Globe, Risk = BitAiToolRisk.ReadOnly })); // How many rounds of tools one turn may take before the session stops asking. ai.ConfigureSessions(options => options with { MaxToolRoundtrips = 6 }); }); static WeatherResult Weather(string city) => city.ToLowerInvariant() switch { "oslo" => new WeatherResult("Oslo", -3, "Snow", 81), "lisbon" => new WeatherResult("Lisbon", 17, "Clear", 54), "auckland" => new WeatherResult("Auckland", 22, "Showers", 72), _ => new WeatherResult(city, 14, "Cloudy", 66), }; ``` `AiDemoToolTransports.cs`: ```csharp // AiScriptedTransport and AiScript are the demo's helpers - see the Basic chat scenario. public static IBitAiTransport CallsTwoTools() => new AiScriptedTransport((request, token) => AiScript.Stream(token, async yielder => { await yielder(new BitAiMessageStartedEvent()); if (AiScript.HasToolResultThisTurn(request) is false) { // Two calls in one round, each with its arguments complete from the start. await yielder(new BitAiToolCallStartedEvent(BitAiId.NewToolCallId(), "get_weather") { Arguments = AiScript.Json("""{"city":"Oslo"}"""), }); await yielder(new BitAiToolCallStartedEvent(BitAiId.NewToolCallId(), "get_weather") { Arguments = AiScript.Json("""{"city":"Lisbon"}"""), }); await yielder(new BitAiMessageCompletedEvent(BitAiFinishReason.ToolCalls)); return; } const string answer = "Oslo is cold and grey; Lisbon is mild and clear."; await foreach (var evt in AiScript.Write(answer, 4, 12, token)) await yielder(evt); await yielder(new BitAiMessageCompletedEvent()); })); ```

How it works

The transport asks for both calls in one response: two BitAiToolCallStartedEvents, each with its Arguments complete from the start - which makes the delta and completion events unnecessary - then BitAiFinishReason.ToolCalls. The session runs every call of the round concurrently, each through its own approval check, and waits for all of them before asking again; the transport then finds both results on the request and answers from them. Each card updates on its own, because each BitAiToolCallPart raises its own change event and only its own view re-renders - which is where a serial tool UI falls apart.

A declined or failed call does not stop the others. Its failure is recorded on that call and sent back to the model with the rest. BitAiChatSessionOptions.MaxToolRoundtrips caps how many rounds of tools one turn may take - ten by default, six for the sessions of this site, set with ai.ConfigureSessions. This page builds its session over a transport of its own, so it hands it those configured options (BitAiChatSessionFactory.Defaults) together with the tool registry. A model still asking for tools when the limit is reached gets no more: the reply ends with an error part saying the limit was hit.

Components used

Feedback

Found a mistake, a gap, or something that could be clearer? Every page and every component is one click from its source.