Files
microsoft--semantic-kernel/docs/decisions/0008-support-generic-llm-request-settings.md
wehub-resource-sync b957a53def
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:21:23 +08:00

234 lines
11 KiB
Markdown

---
# These are optional elements. Feel free to remove any of them.
status: accepted
contact: markwallace-microsoft
date: 2023-9-15
deciders: shawncal
consulted: stephentoub, lemillermicrosoft, dmytrostruk
informed:
---
# Refactor to support generic LLM request settings
## Context and Problem Statement
The Semantic Kernel abstractions package includes a number of classes (`CompleteRequestSettings`, `ChatRequestSettings`, `PromptTemplateConfig.CompletionConfig`) which are used to support:
1. Passing LLM request settings when invoking an AI service
2. Deserialization of LLM requesting settings when loading the `config.json` associated with a Semantic Function
The problem with these classes is they include OpenAI specific properties only. A developer can only pass OpenAI specific requesting settings which means:
1. Settings may be passed that have no effect e.g., passing `MaxTokens` to Huggingface
2. Settings that do not overlap with the OpenAI properties cannot be sent e.g., Oobabooga supports additional parameters e.g., `do_sample`, `typical_p`, ...
Link to issue raised by the implementer of the Oobabooga AI service: <https://github.com/microsoft/semantic-kernel/issues/2735>
## Decision Drivers
- Semantic Kernel abstractions must be AI Service agnostic i.e., remove OpenAI specific properties.
- Solution must continue to support loading Semantic Function configuration (which includes AI request settings) from `config.json`.
- Provide good experience for developers e.g., must be able to program with type safety, intellisense, etc.
- Provide a good experience for implementors of AI services i.e., should be clear how to define the appropriate AI Request Settings abstraction for the service they are supporting.
- Semantic Kernel implementation and sample code should avoid specifying OpenAI specific request settings in code that is intended to be used with multiple AI services.
- Semantic Kernel implementation and sample code must be clear if an implementation is intended to be OpenAI specific.
## Considered Options
- Use `dynamic` to pass request settings
- Use `object` to pass request settings
- Define a base class for AI request settings which all implementations must extend
Note: Using generics was discounted during an earlier investigation which Dmytro conducted.
## Decision Outcome
**Proposed:** Define a base class for AI request settings which all implementations must extend.
## Pros and Cons of the Options
### Use `dynamic` to pass request settings
The `IChatCompletion` interface would look like this:
```csharp
public interface IChatCompletion : IAIService
{
ChatHistory CreateNewChat(string? instructions = null);
Task<IReadOnlyList<IChatResult>> GetChatCompletionsAsync(
ChatHistory chat,
dynamic? requestSettings = null,
CancellationToken cancellationToken = default);
IAsyncEnumerable<IChatStreamingResult> GetStreamingChatCompletionsAsync(
ChatHistory chat,
dynamic? requestSettings = null,
CancellationToken cancellationToken = default);
}
```
Developers would have the following options to specify the requesting settings for a semantic function:
```csharp
// Option 1: Use an anonymous type
await kernel.InvokeSemanticFunctionAsync("Hello AI, what can you do for me?", requestSettings: new { MaxTokens = 256, Temperature = 0.7 });
// Option 2: Use an OpenAI specific class
await kernel.InvokeSemanticFunctionAsync(prompt, requestSettings: new OpenAIRequestSettings() { MaxTokens = 256, Temperature = 0.7 });
// Option 3: Load prompt template configuration from a JSON payload
string configPayload = @"{
""schema"": 1,
""description"": ""Say hello to an AI"",
""type"": ""completion"",
""completion"": {
""max_tokens"": 60,
""temperature"": 0.5,
""top_p"": 0.0,
""presence_penalty"": 0.0,
""frequency_penalty"": 0.0
}
}";
var templateConfig = JsonSerializer.Deserialize<PromptTemplateConfig>(configPayload);
var func = kernel.CreateSemanticFunction(prompt, config: templateConfig!, "HelloAI");
await kernel.RunAsync(func);
```
PR: <https://github.com/microsoft/semantic-kernel/pull/2807>
- Good, SK abstractions contain no references to OpenAI specific request settings
- Neutral, because anonymous types can be used which allows a developer to pass in properties that may be supported by multiple AI services e.g., `temperature` or combine properties for different AI services e.g., `max_tokens` (OpenAI) and `max_new_tokens` (Oobabooga).
- Bad, because it's not clear to developers what they should pass when creating a semantic function
- Bad, because it's not clear to implementors of a chat/text completion service what they should accept or how to add service specific properties.
- Bad, there is no compiler type checking for code paths where the dynamic argument has not been resolved which will impact code quality. Type issues manifest as `RuntimeBinderException`'s and may be difficult to troubleshoot. Special care needs to be taken with return types e.g., may be necessary to specify an explicit type rather than just `var` again to avoid errors such as `Microsoft.CSharp.RuntimeBinder.RuntimeBinderException : Cannot apply indexing with [] to an expression of type 'object'`
### Use `object` to pass request settings
The `IChatCompletion` interface would look like this:
```csharp
public interface IChatCompletion : IAIService
{
ChatHistory CreateNewChat(string? instructions = null);
Task<IReadOnlyList<IChatResult>> GetChatCompletionsAsync(
ChatHistory chat,
object? requestSettings = null,
CancellationToken cancellationToken = default);
IAsyncEnumerable<IChatStreamingResult> GetStreamingChatCompletionsAsync(
ChatHistory chat,
object? requestSettings = null,
CancellationToken cancellationToken = default);
}
```
The calling pattern is the same as for the `dynamic` case i.e. use either an anonymous type, an AI service specific class e.g., `OpenAIRequestSettings` or load from JSON.
PR: <https://github.com/microsoft/semantic-kernel/pull/2819>
- Good, SK abstractions contain no references to OpenAI specific request settings
- Neutral, because anonymous types can be used which allows a developer to pass in properties that may be supported by multiple AI services e.g., `temperature` or combine properties for different AI services e.g., `max_tokens` (OpenAI) and `max_new_tokens` (Oobabooga).
- Bad, because it's not clear to developers what they should pass when creating a semantic function
- Bad, because it's not clear to implementors of a chat/text completion service what they should accept or how to add service specific properties.
- Bad, code is needed to perform type checks and explicit casts. The situation is slightly better than for the `dynamic` case.
### Define a base class for AI request settings which all implementations must extend
The `IChatCompletion` interface would look like this:
```csharp
public interface IChatCompletion : IAIService
{
ChatHistory CreateNewChat(string? instructions = null);
Task<IReadOnlyList<IChatResult>> GetChatCompletionsAsync(
ChatHistory chat,
AIRequestSettings? requestSettings = null,
CancellationToken cancellationToken = default);
IAsyncEnumerable<IChatStreamingResult> GetStreamingChatCompletionsAsync(
ChatHistory chat,
AIRequestSettings? requestSettings = null,
CancellationToken cancellationToken = default);
}
```
`AIRequestSettings` is defined as follows:
```csharp
public class AIRequestSettings
{
/// <summary>
/// Service identifier.
/// </summary>
[JsonPropertyName("service_id")]
[JsonPropertyOrder(1)]
public string? ServiceId { get; set; } = null;
/// <summary>
/// Extra properties
/// </summary>
[JsonExtensionData]
public Dictionary<string, object>? ExtensionData { get; set; }
}
```
Developers would have the following options to specify the requesting settings for a semantic function:
```csharp
// Option 1: Invoke the semantic function and pass an OpenAI specific instance
var result = await kernel.InvokeSemanticFunctionAsync(prompt, requestSettings: new OpenAIRequestSettings() { MaxTokens = 256, Temperature = 0.7 });
Console.WriteLine(result.Result);
// Option 2: Load prompt template configuration from a JSON payload
string configPayload = @"{
""schema"": 1,
""description"": ""Say hello to an AI"",
""type"": ""completion"",
""completion"": {
""max_tokens"": 60,
""temperature"": 0.5,
""top_p"": 0.0,
""presence_penalty"": 0.0,
""frequency_penalty"": 0.0
}
}";
var templateConfig = JsonSerializer.Deserialize<PromptTemplateConfig>(configPayload);
var func = kernel.CreateSemanticFunction(prompt, config: templateConfig!, "HelloAI");
await kernel.RunAsync(func);
```
It would also be possible to use the following pattern:
```csharp
this._summarizeConversationFunction = kernel.CreateSemanticFunction(
SemanticFunctionConstants.SummarizeConversationDefinition,
skillName: nameof(ConversationSummarySkill),
description: "Given a section of a conversation, summarize conversation.",
requestSettings: new AIRequestSettings()
{
ExtensionData = new Dictionary<string, object>()
{
{ "Temperature", 0.1 },
{ "TopP", 0.5 },
{ "MaxTokens", MaxTokens }
}
});
```
The caveat with this pattern is, assuming a more specific implementation of `AIRequestSettings` uses JSON serialization/deserialization to hydrate an instance from the base `AIRequestSettings`, this will only work if all properties are supported by the default JsonConverter e.g.,
- If we have `MyAIRequestSettings` which includes a `Uri` property. The implementation of `MyAIRequestSettings` would make sure to load a URI converter so that it can serialize/deserialize the settings correctly.
- If the settings for `MyAIRequestSettings` are sent to an AI service which relies on the default JsonConverter then a `NotSupportedException` exception will be thrown.
PR: <https://github.com/microsoft/semantic-kernel/pull/2829>
- Good, SK abstractions contain no references to OpenAI specific request settings
- Good, because it is clear to developers what they should pass when creating a semantic function and it is easy to discover what service specific request setting implementations exist.
- Good, because it is clear to implementors of a chat/text completion service what they should accept and how to extend the base abstraction to add service specific properties.
- Neutral, because `ExtensionData` can be used which allows a developer to pass in properties that may be supported by multiple AI services e.g., `temperature` or combine properties for different AI services e.g., `max_tokens` (OpenAI) and `max_new_tokens` (Oobabooga).