1 - Dapr AI Client
Learn how to create Dapr AI clients
The Dapr AI client package allows you to interact with the AI capabilities provided by the Dapr sidecar.
Note
The Dapr Conversation building block requires Dapr runtime v1.16.0 or later. Response format, prompt cache retention,
and token usage statistics require Dapr runtime v1.17.0 or later.Lifetime management
A DaprConversationClient is a version of the Dapr client that is dedicated to interacting with the Dapr Conversation
API. It can be registered alongside a DaprClient and other Dapr clients without issue.
It maintains access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar.
For best performance, create a single long-lived instance of DaprConversationClient and provide access to that shared
instance throughout your application. DaprConversationClient instances are thread-safe and intended to be shared.
This can be aided by utilizing the dependency injection functionality. The registration method supports registration
as a singleton, a scoped instance or as transient (meaning it’s recreated every time it’s injected), but also enables
registration to utilize values from an IConfiguration or other injected services in a way that’s impractical when
creating the client from scratch in each of your classes.
Avoid creating a DaprConversationClient for each operation.
Configuring DaprConversationClient via DaprConversationClientBuilder
A DaprConversationClient can be configured by invoking methods on the DaprConversationClientBuilder class before
calling .Build() to create the client itself. The settings for each DaprConversationClient are separate
and cannot be changed after calling .Build().
var daprConversationClient = new DaprConversationClientBuilder()
.UseDaprApiToken("abc123") // Specify the API token used to authenticate to other Dapr sidecars
.Build();
The DaprConversationClientBuilder contains settings for:
- The HTTP endpoint of the Dapr sidecar
- The gRPC endpoint of the Dapr sidecar
- The
JsonSerializerOptions object used to configure JSON serialization - The
GrpcChannelOptions object used to configure gRPC - The API token used to authenticate requests to the sidecar
- The factory method used to create the
HttpClient instance used by the SDK - The timeout used for the
HttpClient instance when making requests to the sidecar
The SDK will read the following environment variables to configure the default values:
DAPR_HTTP_ENDPOINT: used to find the HTTP endpoint of the Dapr sidecar, example: https://dapr-api.mycompany.comDAPR_GRPC_ENDPOINT: used to find the gRPC endpoint of the Dapr sidecar, example: https://dapr-grpc-api.mycompany.comDAPR_HTTP_PORT: if DAPR_HTTP_ENDPOINT is not set, this is used to find the HTTP local endpoint of the Dapr sidecarDAPR_GRPC_PORT: if DAPR_GRPC_ENDPOINT is not set, this is used to find the gRPC local endpoint of the Dapr sidecarDAPR_API_TOKEN: used to set the API token
Configuring gRPC channel options
Dapr’s use of CancellationToken for cancellation relies on the configuration of the gRPC channel options. If you need
to configure these options yourself, make sure to enable the ThrowOperationCanceledOnCancellation setting.
var daprConversationClient = new DaprConversationClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { ThrowOperationCanceledOnCancellation = true })
.Build();
Using cancellation with DaprConversationClient
The APIs on DaprConversationClient perform asynchronous operations and accept an optional CancellationToken parameter. This
follows a standard .NET practice for cancellable operations. Note that when cancellation occurs, there is no guarantee that
the remote endpoint stops processing the request, only that the client has stopped waiting for completion.
When an operation is canceled, it can throw an OperationCanceledException.
Configuring DaprConversationClient via dependency injection
Using the built-in extension methods for registering the DaprConversationClient in a dependency injection container can
provide the benefit of registering the long-lived service a single time, centralize complex configuration and improve
performance by ensuring similarly long-lived resources are re-purposed when possible (e.g. HttpClient instances).
There are three overloads available to give the developer the greatest flexibility in configuring the client for their
scenario. Each of these will register the IHttpClientFactory on your behalf if not already registered, and configure
the DaprConversationClientBuilder to use it when creating the HttpClient instance in order to re-use the same instance as
much as possible and avoid socket exhaustion and other issues.
In the first approach, there’s no configuration done by the developer and the DaprConversationClient is configured with the
default settings.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprConversationClient(); //Registers the `DaprConversationClient` to be injected as needed
var app = builder.Build();
Sometimes the developer will need to configure the created client using the various configuration options detailed
above. This is done through an overload that passes in the DaprConversationClientBuilder and exposes methods for configuring
the necessary options.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprConversationClient((_, daprConversationClientBuilder) => {
//Set the API token
daprConversationClientBuilder.UseDaprApiToken("abc123");
//Specify a non-standard HTTP endpoint
daprConversationClientBuilder.UseHttpEndpoint("http://dapr.my-company.com");
});
var app = builder.Build();
Finally, it’s possible that the developer may need to retrieve information from another service in order to populate
these configuration values. That value may be provided from a DaprClient instance, a vendor-specific SDK or some
local service, but as long as it’s also registered in DI, it can be injected into this configuration operation via the
last overload:
var builder = WebApplication.CreateBuilder(args);
//Register a fictional service that retrieves secrets from somewhere
builder.Services.AddSingleton<SecretService>();
builder.Services.AddDaprConversationClient((serviceProvider, daprConversationClientBuilder) => {
//Retrieve an instance of the `SecretService` from the service provider
var secretService = serviceProvider.GetRequiredService<SecretService>();
var daprApiToken = secretService.GetSecret("DaprApiToken").Value;
//Configure the `DaprConversationClientBuilder`
daprConversationClientBuilder.UseDaprApiToken(daprApiToken);
});
var app = builder.Build();
Sending a conversation request
Use ConversationInput and ConversationOptions to send prompts to your conversation component:
using Dapr.AI.Conversation;
using Dapr.AI.Conversation.ConversationRoles;
var inputs = new[]
{
new ConversationInput(new IConversationMessage[]
{
new SystemMessage { Content = [new MessageContent("You are a helpful assistant.")] },
new UserMessage { Content = [new MessageContent("Explain Dapr service invocation briefly.")] }
})
};
var options = new ConversationOptions("my-conversation-component")
{
Temperature = 0.2
};
var response = await daprConversationClient.ConverseAsync(inputs, options);
foreach (var output in response.Outputs)
{
foreach (var choice in output.Choices)
{
Console.WriteLine($"{choice.Index}: {choice.Message.Content} ({choice.FinishReason})");
}
}
Each ConversationInput contains a list of role-specific messages. The SDK supports SystemMessage,
DeveloperMessage, UserMessage, AssistantMessage, and ToolMessage. Content consists of text
MessageContent entries; this API does not expose image/audio message parts.
Each response output contains Choices, optional Model, and optional Usage. Each choice has an
Index, nullable FinishReason, and a Message with text and a list of tool calls. Do not assume the
response always contains a single text choice.
Request options and conversation context
| Option | Behavior |
|---|
ConversationComponentId | Required component name supplied to the ConversationOptions constructor. |
ContextId | Optional existing conversation context. A returned context is available as ConversationResponse.ConversationId; retention depends on the runtime/component. |
Temperature | Provider-supported creativity/predictability setting. |
ScrubPII | Requests scrubbing of response data. ConversationInput.ScrubPII separately requests scrubbing of input data. |
Metadata | Component-specific string key/value metadata. |
Parameters | Custom parameters represented as Google.Protobuf.WellKnownTypes.Any values. |
Tools, ToolChoice | Per-request tool declarations and selection policy; see tool calling. |
ResponseFormat | Optional protobuf Struct describing the requested response schema. |
PromptCacheRetention | Optional cache-retention duration where supported. |
The client sends these options to the runtime; provider support varies. A context ID is not a client-side
message history store. Keep/send any history your provider requires. ConverseAsync returns a complete
response; this client has no token-streaming API in this release.
You can provide a JSON schema to coerce responses into a specific format using ConversationOptions.ResponseFormat.
This feature requires Dapr runtime v1.17.0 or later.
using Google.Protobuf.WellKnownTypes;
var responseFormat = new Struct
{
Fields =
{
["type"] = new Value { StringValue = "object" },
["properties"] = new Value
{
StructValue = new Struct
{
Fields =
{
["answer"] = new Value
{
StructValue = new Struct
{
Fields =
{
["type"] = new Value { StringValue = "string" }
}
}
}
}
}
},
["required"] = new Value
{
ListValue = new ListValue { Values = { new Value { StringValue = "answer" } } }
}
}
};
var options = new ConversationOptions("my-conversation-component")
{
ResponseFormat = responseFormat
};
Prompt cache retention
If your conversation component supports prompt caching, you can request a cache retention window using
ConversationOptions.PromptCacheRetention. This feature requires Dapr runtime v1.17.0 or later.
var options = new ConversationOptions("my-conversation-component")
{
PromptCacheRetention = TimeSpan.FromMinutes(30)
};
Token usage statistics
The response includes optional token usage statistics when supported by the component and runtime. Usage data is
available on ConversationResponseResult.Usage and includes both totals and detailed breakdowns:
var response = await daprConversationClient.ConverseAsync(inputs, options);
var result = response.Outputs[0];
if (result.Usage is not null)
{
var totalTokens = result.Usage.TotalTokens;
var promptCachedTokens = result.Usage.PromptTokensDetails?.CachedTokens;
var reasoningTokens = result.Usage.CompletionTokensDetails?.ReasoningTokens;
}
2 - How to: Create and use Dapr AI Conversations in the .NET SDK
Learn how to create and use the Dapr Conversational AI client using the .NET SDK
Prerequisites
Installation
.NET framework support
Be advised that .NET 8 and .NET 9 reach end of support in November 2026. Support for both will be dropped
in Dapr v1.20; only .NET 10 and .NET 11 will be supported through the end of 2027.To get started with the Dapr AI .NET SDK client, install the Dapr.AI package from NuGet:
dotnet add package Dapr.AI
These guides target SDK 1.19.0 or later. Configure a conversation component and credentials for its
model provider. Import Dapr.AI.Conversation and Dapr.AI.Conversation.Extensions.
The API is experimental (DAPR_CONVERSATION).
A DaprConversationClient maintains access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar.
Dependency Injection
The AddDaprConversationClient() method registers the client with dependency injection and is the recommended approach
for using this package. This method accepts an optional options delegate for configuring the DaprConversationClient and a
ServiceLifetime argument, allowing you to specify a different lifetime for the registered services instead of the default Singleton
value.
The following example assumes all default values are acceptable and is sufficient to register the DaprConversationClient:
services.AddDaprConversationClient();
The optional configuration delegate is used to configure the DaprConversationClient by specifying options on the
DaprConversationClientBuilder as in the following example:
services.AddSingleton<DefaultOptionsProvider>();
services.AddDaprConversationClient((serviceProvider, clientBuilder) => {
//Inject a service to source a value from
var optionsProvider = serviceProvider.GetRequiredService<DefaultOptionsProvider>();
var standardTimeout = optionsProvider.GetStandardTimeout();
//Configure the value on the client builder
clientBuilder.UseTimeout(standardTimeout);
});
Manual Instantiation
Rather than using dependency injection, a DaprConversationClient can also be built using the static client builder.
For best performance, create a single long-lived instance of DaprConversationClient and provide access to that shared instance throughout
your application. DaprConversationClient instances are thread-safe and intended to be shared.
Avoid creating a DaprConversationClient per-operation.
A DaprConversationClient can be configured by invoking methods on the DaprConversationClientBuilder class before calling .Build()
to create the client. The settings for each DaprConversationClient are separate and cannot be changed after calling .Build().
var daprConversationClient = new DaprConversationClientBuilder()
.UseGrpcEndpoint("http://localhost:50001")
.Build();
See the .NET documentation here for more information about the options available when configuring the Dapr client via the builder.
Try it out
Put the Dapr AI .NET SDK to the test. Walk through the samples to see Dapr in action:
| SDK Samples | Description |
|---|
| SDK samples | Clone the SDK repo to try out some examples and get started. |
Building Blocks
This part of the .NET SDK allows you to interface with the Conversations API to send and receive messages from
large language models.
See conversation requests and responses for message roles,
options, response schemas, caching, and token usage, and
tool calling for the application-managed tool-call cycle.
3 - How to: Handle conversation tool calls in .NET
Declare conversation tools, inspect model requests, and return tool results to the model
The conversation client in SDK 1.19.0 or later can declare function tools and return model-requested
tool calls. Declaring a tool does not execute it. Your application validates the request, invokes an allowed
operation, and sends the result back.
Declare a function
using Dapr.AI.Conversation;
using Dapr.AI.Conversation.ConversationRoles;
using Dapr.AI.Conversation.Tools;
var messages = new List<IConversationMessage>
{
new UserMessage { Content = [new MessageContent("What time is it in UTC?")] }
};
var options = new ConversationOptions("conversation")
{
Tools =
[
new ToolFunction("get_utc_time")
{
Description = "Returns the current UTC time.",
Parameters = new Dictionary<string, object?>
{
["type"] = "object",
["properties"] = new Dictionary<string, object?>()
}
}
],
ToolChoice = ToolChoice.Auto
};
var response = await conversationClient.ConverseAsync(
[new ConversationInput(messages)], options, cancellationToken);
ToolFunction.Parameters describes a JSON schema, not a dictionary of argument values to execute.
ToolChoice.None, Auto, and Required select a policy, or new ToolChoice("get_utc_time") requests a
particular function by its case-sensitive name. Runtime/provider support for these policies can vary.
Select a response choice according to your application policy. Preserve its assistant tool calls in the next
request, and correlate each result with the returned tool-call ID:
if (response.Outputs.Count == 0 || response.Outputs[0].Choices.Count == 0)
{
throw new InvalidOperationException("The model returned no conversation choices.");
}
var choice = response.Outputs[0].Choices[0];
if (choice.Message.ToolCalls.Count == 0)
{
Console.WriteLine(choice.Message.Content);
return;
}
messages.Add(new AssistantMessage
{
Content = [new MessageContent(choice.Message.Content)],
ToolCalls = choice.Message.ToolCalls
});
foreach (var toolCall in choice.Message.ToolCalls)
{
if (toolCall is not CalledToolFunction call || call.Name != "get_utc_time")
{
throw new InvalidOperationException("The model requested an unsupported tool.");
}
messages.Add(new ToolMessage
{
Name = call.Name,
Id = call.Id,
Content = [new MessageContent(DateTimeOffset.UtcNow.ToString("O"))]
});
}
var followUp = await conversationClient.ConverseAsync(
[new ConversationInput(messages)],
options with { ContextId = response.ConversationId, ToolChoice = ToolChoice.None },
cancellationToken);
For tools with arguments, CalledToolFunction.JsonArguments contains the model’s JSON. Parse it into a
known schema and validate values and authorization before executing anything. Never dispatch arbitrary
method names or commands directly from model output. Put a limit on tool-call rounds and make side-effecting
tools idempotent. The example disables further tool calls in the follow-up request.
For the Microsoft abstraction and its mapping limitations, see
Microsoft.Extensions.AI integration.
4 - How to: Using Microsoft's AI extensions with Dapr's .NET Conversation SDK
Learn how to create and use Dapr with Microsoft’s AI extensions
Prerequisites
Installation
.NET framework support
Be advised that .NET 8 and .NET 9 reach end of support in November 2026. Support for both will be dropped
in Dapr v1.20; only .NET 10 and .NET 11 will be supported through the end of 2027.To get started with this SDK, install both the Dapr.AI and
Dapr.AI.Microsoft.Extensions packages from NuGet:
dotnet add package Dapr.AI
dotnet add package Dapr.AI.Microsoft.Extensions
The DaprChatClient is a Dapr-based implementation of the IChatClient interface provided in the
Microsoft.Extensions.AI.Abstractions package using Dapr’s conversation building block. It allows
developers to build against the types provided by Microsoft’s abstraction while providing the greatest conformity to the
Dapr conversation building block available. As both approaches adopt OpenAI’s API approach, these are expected to increasingly
converge over time.
Dapr Conversation Building Block
Do note that Dapr’s conversation building block is still in an alpha state, meaning that the shape of the API
is likely to change future releases. It’s the intent of this SDK package to provide an API that’s aligned with
Microsoft’s AI extensions that also maps to and conforms with the Dapr API, but the names of types and properties
may change from one release to the next, so please be aware of this possibility when using this SDK.About Microsoft.Extensions.AI
The Dapr.AI.Microsoft.Extensions package implements the Microsoft.Extensions.AI abstractions, providing a unified API for
AI services in .NET applications. Microsoft.Extensions.AI is designed to offer a consistent programming model across
different AI providers and scenarios. For detailed information about Microsoft.Extensions.AI, refer to the
official documentation.
Limited Support
Note that Microsoft’s AI extension provide many more properties and methods than Dapr’s conversation building block currently
supports. This package will only map those properties that have Dapr support and will ignore the others, so just because
it’s available in the Microsoft.Extensions.AI package doesn’t mean it’s supported by Dapr. Rely on this documentation
and the exposed XML documentation in the package to understand what is and isn’t supported.Service Registration
The DaprChatClient can be registered with the dependency injection container using several extension methods. First,
import Dapr.AI.Conversation.Extensions, Dapr.AI.Microsoft.Extensions, and Microsoft.Extensions.AI.
Register the DaprConversationClient that’s part of the Dapr.AI package from NuGet:
services.AddDaprConversationClient();
Then register the DaprChatClient with your conversation component name:
services.AddDaprChatClient("my-conversation-component", configure: null);
Pass configure: null explicitly in 1.19.0 to disambiguate overloads with optional parameters.
Configuration Options
You can confiugre the DaprChatClient using the DaprChatClientOptions though the current implementation only
provides configuration for the component name itself. This is expected to change in future releases.
services.AddDaprChatClient("my-conversation-component", options =>
{
// Configure additional options here
});
You can also configure the service lifetime (this defaults to ServiceLifetime.Scoped):
services.AddDaprChatClient("my-conversation-component", ServiceLifetime.Singleton);
For multiple components, register keyed clients:
services.AddDaprChatClient(
serviceKey: "support", conversationComponentName: "support-conversation");
services.AddDaprChatClient(
serviceKey: "catalog", conversationComponentName: "catalog-conversation");
Resolve them with [FromKeyedServices("support")] IChatClient or
GetRequiredKeyedService<IChatClient>("support"). The component-name-only overload also registers a keyed
client using the component name as its key.
Usage
Once registered, you can inject and use IChatClient in your services:
public class ChatService(IChatClient chatClient)
{
public async Task<IReadOnlyList<string>> GetResponseAsync(string message)
{
var response = await chatClient.GetResponseAsync([
new ChatMessage(ChatRole.User,
"Please write me a poem in iambic pentameter about the joys of using Dapr to develop distributed applications with .NET")
]);
return response.Messages.Select(msg => msg.Text).ToList();
}
}
Streaming Conversations
The DaprChatClient does not support streaming responses in SDK 1.19.0.
GetStreamingResponseAsync throws NotImplementedException.
The client maps tool declarations to the model and returns requested calls as FunctionCallContent.
Calling GetResponseAsync does not automatically execute a function. An application-owned tool loop or
function-invocation middleware must execute allowed functions and return results.
string GetCurrentWeather() => Random.Shared.NextDouble() > 0.5 ? "It's sunny today!" : "It's raining today!";
var toolChatOptions = new ChatOptions { Tools = [AIFunctionFactory.Create(GetCurrentWeather, "weather")] };
var toolResponse = await chatClient.GetResponseAsync("What's the weather like today?", toolChatOptions);
foreach (var toolResp in toolResponse.Messages)
{
Console.WriteLine(toolResp);
}
Error Handling
The DaprChatClient integrates with Dapr’s error handling and will throw appropriate exceptions when issues occur.
The underlying Dapr conversation component can be configured with metadata and parameters through the Dapr conversation
building block configuration. The DaprChatClient will respect these settings when making calls to the conversation component.
Mapping in SDK 1.19.0
The adapter implements a subset of IChatClient, not the complete ConversationOptions surface:
| Microsoft.Extensions.AI surface | Dapr mapping |
|---|
| User, assistant, system, and tool roles | Text-only conversation messages. Other roles throw ArgumentOutOfRangeException; non-text parts and structured tool-result/call history are not preserved by the input mapping. |
ChatOptions.Temperature | ConversationOptions.Temperature |
ChatOptions.ConversationId | ConversationOptions.ContextId |
ChatOptions.ToolMode | None, auto, required, or a named required function |
ChatOptions.Tools | Tool name, description, and AdditionalProperties mapped to function parameters; validate the schema actually sent for your tool. |
ChatOptions.AdditionalProperties | Stringified component metadata, not protobuf Parameters. |
| Response | Conversation ID, finish reason, text, returned function calls, and each raw choice in RawRepresentation. Token usage and model are not mapped to ChatResponse in this release. |
Options such as response format, prompt cache retention, and PII scrubbing are not mapped by this adapter.
Use DaprConversationClient directly when you need those capabilities or full control over the
tool-call conversation history.
Best Practices
Service Lifetime: Use ServiceLifetime.Scoped or ServiceLifetime.Singleton for the DaprChatClient registration to avoid creating multiple instances unnecessarily.
Error Handling: Always wrap calls in appropriate try-catch blocks to handle both Dapr-specific and general exceptions.
Resource Management: DaprChatClient.Dispose is a no-op in this release. DI manages the separately
registered DaprConversationClient; manually constructed applications must keep that client alive and dispose it themselves.
Configuration: Configure your Dapr conversation component properly to ensure optimal performance and reliability.