Refactor message building to use standard role transformations for improved consistency

This commit is contained in:
Thorsten Sommer 2025-12-28 20:36:02 +01:00
parent 4881b1a095
commit 283a04f0b2
Signed by: tsommer
GPG Key ID: 371BBA77A02C0108
17 changed files with 59 additions and 276 deletions

View File

@ -1,4 +1,5 @@
using AIStudio.Provider; using AIStudio.Provider;
using AIStudio.Provider.OpenAI;
namespace AIStudio.Chat; namespace AIStudio.Chat;
@ -8,19 +9,44 @@ public static class ListContentBlockExtensions
/// Processes a list of content blocks by transforming them into a collection of message results asynchronously. /// Processes a list of content blocks by transforming them into a collection of message results asynchronously.
/// </summary> /// </summary>
/// <param name="blocks">The list of content blocks to process.</param> /// <param name="blocks">The list of content blocks to process.</param>
/// <param name="transformer">A function that transforms each content block into a message result asynchronously.</param> /// <param name="roleTransformer">A function that transforms each content block into a message result asynchronously.</param>
/// <returns>An asynchronous task that resolves to a list of transformed results.</returns> /// <returns>An asynchronous task that resolves to a list of transformed results.</returns>
public static async Task<IList<IMessageBase>> BuildMessages(this List<ContentBlock> blocks, Func<ContentBlock, Task<IMessageBase>> transformer) public static async Task<IList<IMessageBase>> BuildMessages(this List<ContentBlock> blocks, Func<ChatRole, string> roleTransformer)
{ {
var messages = blocks var messages = blocks
.Where(n => n.ContentType is ContentType.TEXT && !string.IsNullOrWhiteSpace((n.Content as ContentText)?.Text)) .Where(n => n.ContentType is ContentType.TEXT && !string.IsNullOrWhiteSpace((n.Content as ContentText)?.Text))
.Select(transformer) .Select(async n => new TextMessage
{
Role = roleTransformer(n.Role),
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
})
.ToList(); .ToList();
// Await all messages: // Await all messages:
await Task.WhenAll(messages); await Task.WhenAll(messages);
// Select all results: // Select all results:
return messages.Select(n => n.Result).ToList(); return messages.Select(n => n.Result).Cast<IMessageBase>().ToList();
} }
/// <summary>
/// Processes a list of content blocks using standard role transformations to create message results asynchronously.
/// </summary>
/// <param name="blocks">The list of content blocks to process.</param>
/// <returns>>An asynchronous task that resolves to a list of transformed message results.</returns>
public static async Task<IList<IMessageBase>> BuildMessagesUsingStandardRoles(this List<ContentBlock> blocks) => await blocks.BuildMessages(StandardRoleTransformer);
private static string StandardRoleTransformer(ChatRole role) => role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.AGENT => "assistant",
ChatRole.SYSTEM => "system",
_ => "user",
};
} }

View File

@ -40,24 +40,7 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
var apiParameters = this.ParseAdditionalApiParameters(); var apiParameters = this.ParseAdditionalApiParameters();
// Build the list of messages: // Build the list of messages:
var messages = await chatThread.Blocks.BuildMessages(async n => new TextMessage var messages = await chatThread.Blocks.BuildMessagesUsingStandardRoles();
{
Role = n.Role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.AGENT => "assistant",
ChatRole.SYSTEM => "system",
_ => "user",
},
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
});
// Prepare the AlibabaCloud HTTP chat request: // Prepare the AlibabaCloud HTTP chat request:
var alibabaCloudChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest var alibabaCloudChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest

View File

@ -9,14 +9,11 @@ namespace AIStudio.Provider.Anthropic;
/// <param name="Messages">The chat messages.</param> /// <param name="Messages">The chat messages.</param>
/// <param name="MaxTokens">The maximum number of tokens to generate.</param> /// <param name="MaxTokens">The maximum number of tokens to generate.</param>
/// <param name="Stream">Whether to stream the chat completion.</param> /// <param name="Stream">Whether to stream the chat completion.</param>
/// <param name="System">The system prompt for the chat completion.</param>
public readonly record struct ChatRequest( public readonly record struct ChatRequest(
string Model, string Model,
IList<IMessageBase> Messages, IList<IMessageBase> Messages,
int MaxTokens, int MaxTokens,
bool Stream, bool Stream)
string System
)
{ {
// Attention: The "required" modifier is not supported for [JsonExtensionData]. // Attention: The "required" modifier is not supported for [JsonExtensionData].
[JsonExtensionData] [JsonExtensionData]

View File

@ -27,27 +27,18 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, "
if(!requestedSecret.Success) if(!requestedSecret.Success)
yield break; yield break;
// Prepare the system prompt:
var systemPrompt = new TextMessage
{
Role = "system",
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
};
// Parse the API parameters: // Parse the API parameters:
var apiParameters = this.ParseAdditionalApiParameters("system"); var apiParameters = this.ParseAdditionalApiParameters("system");
// Build the list of messages: // Build the list of messages:
var messages = await chatThread.Blocks.BuildMessages(async n => new TextMessage var messages = await chatThread.Blocks.BuildMessagesUsingStandardRoles();
{
Role = n.Role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.AGENT => "assistant",
_ => "user",
},
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
});
// Prepare the Anthropic HTTP chat request: // Prepare the Anthropic HTTP chat request:
var chatRequest = JsonSerializer.Serialize(new ChatRequest var chatRequest = JsonSerializer.Serialize(new ChatRequest
@ -55,9 +46,8 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, "
Model = chatModel.Id, Model = chatModel.Id,
// Build the messages: // Build the messages:
Messages = [..messages], Messages = [systemPrompt, ..messages],
System = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
MaxTokens = apiParameters.TryGetValue("max_tokens", out var value) && value is int intValue ? intValue : 4_096, MaxTokens = apiParameters.TryGetValue("max_tokens", out var value) && value is int intValue ? intValue : 4_096,
// Right now, we only support streaming completions: // Right now, we only support streaming completions:

View File

@ -40,24 +40,7 @@ public sealed class ProviderDeepSeek() : BaseProvider(LLMProviders.DEEP_SEEK, "h
var apiParameters = this.ParseAdditionalApiParameters(); var apiParameters = this.ParseAdditionalApiParameters();
// Build the list of messages: // Build the list of messages:
var messages = await chatThread.Blocks.BuildMessages(async n => new TextMessage var messages = await chatThread.Blocks.BuildMessagesUsingStandardRoles();
{
Role = n.Role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.AGENT => "assistant",
ChatRole.SYSTEM => "system",
_ => "user",
},
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
});
// Prepare the DeepSeek HTTP chat request: // Prepare the DeepSeek HTTP chat request:
var deepSeekChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest var deepSeekChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest

View File

@ -40,24 +40,7 @@ public class ProviderFireworks() : BaseProvider(LLMProviders.FIREWORKS, "https:/
var apiParameters = this.ParseAdditionalApiParameters(); var apiParameters = this.ParseAdditionalApiParameters();
// Build the list of messages: // Build the list of messages:
var messages = await chatThread.Blocks.BuildMessages(async n => new TextMessage var messages = await chatThread.Blocks.BuildMessagesUsingStandardRoles();
{
Role = n.Role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.AGENT => "assistant",
ChatRole.SYSTEM => "system",
_ => "user",
},
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
});
// Prepare the Fireworks HTTP chat request: // Prepare the Fireworks HTTP chat request:
var fireworksChatRequest = JsonSerializer.Serialize(new ChatRequest var fireworksChatRequest = JsonSerializer.Serialize(new ChatRequest

View File

@ -40,24 +40,7 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, "https://ch
var apiParameters = this.ParseAdditionalApiParameters(); var apiParameters = this.ParseAdditionalApiParameters();
// Build the list of messages: // Build the list of messages:
var messages = await chatThread.Blocks.BuildMessages(async n => new TextMessage var messages = await chatThread.Blocks.BuildMessagesUsingStandardRoles();
{
Role = n.Role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.AGENT => "assistant",
ChatRole.SYSTEM => "system",
_ => "user",
},
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
});
// Prepare the GWDG HTTP chat request: // Prepare the GWDG HTTP chat request:
var gwdgChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest var gwdgChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest

View File

@ -40,24 +40,7 @@ public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, "https://gener
var apiParameters = this.ParseAdditionalApiParameters(); var apiParameters = this.ParseAdditionalApiParameters();
// Build the list of messages: // Build the list of messages:
var messages = await chatThread.Blocks.BuildMessages(async n => new TextMessage var messages = await chatThread.Blocks.BuildMessagesUsingStandardRoles();
{
Role = n.Role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.AGENT => "assistant",
ChatRole.SYSTEM => "system",
_ => "user",
},
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
});
// Prepare the Google HTTP chat request: // Prepare the Google HTTP chat request:
var geminiChatRequest = JsonSerializer.Serialize(new ChatRequest var geminiChatRequest = JsonSerializer.Serialize(new ChatRequest

View File

@ -40,24 +40,7 @@ public class ProviderGroq() : BaseProvider(LLMProviders.GROQ, "https://api.groq.
var apiParameters = this.ParseAdditionalApiParameters(); var apiParameters = this.ParseAdditionalApiParameters();
// Build the list of messages: // Build the list of messages:
var messages = await chatThread.Blocks.BuildMessages(async n => new TextMessage var messages = await chatThread.Blocks.BuildMessagesUsingStandardRoles();
{
Role = n.Role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.AGENT => "assistant",
ChatRole.SYSTEM => "system",
_ => "user",
},
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
});
// Prepare the OpenAI HTTP chat request: // Prepare the OpenAI HTTP chat request:
var groqChatRequest = JsonSerializer.Serialize(new ChatRequest var groqChatRequest = JsonSerializer.Serialize(new ChatRequest

View File

@ -40,24 +40,7 @@ public sealed class ProviderHelmholtz() : BaseProvider(LLMProviders.HELMHOLTZ, "
var apiParameters = this.ParseAdditionalApiParameters(); var apiParameters = this.ParseAdditionalApiParameters();
// Build the list of messages: // Build the list of messages:
var messages = await chatThread.Blocks.BuildMessages(async n => new TextMessage var messages = await chatThread.Blocks.BuildMessagesUsingStandardRoles();
{
Role = n.Role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.AGENT => "assistant",
ChatRole.SYSTEM => "system",
_ => "user",
},
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
});
// Prepare the Helmholtz HTTP chat request: // Prepare the Helmholtz HTTP chat request:
var helmholtzChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest var helmholtzChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest

View File

@ -45,24 +45,7 @@ public sealed class ProviderHuggingFace : BaseProvider
var apiParameters = this.ParseAdditionalApiParameters(); var apiParameters = this.ParseAdditionalApiParameters();
// Build the list of messages: // Build the list of messages:
var message = await chatThread.Blocks.BuildMessages(async n => new TextMessage var message = await chatThread.Blocks.BuildMessagesUsingStandardRoles();
{
Role = n.Role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.AGENT => "assistant",
ChatRole.SYSTEM => "system",
_ => "user",
},
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
});
// Prepare the HuggingFace HTTP chat request: // Prepare the HuggingFace HTTP chat request:
var huggingfaceChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest var huggingfaceChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest

View File

@ -38,24 +38,7 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, "http
var apiParameters = this.ParseAdditionalApiParameters(); var apiParameters = this.ParseAdditionalApiParameters();
// Build the list of messages: // Build the list of messages:
var messages = await chatThread.Blocks.BuildMessages(async n => new TextMessage var messages = await chatThread.Blocks.BuildMessagesUsingStandardRoles();
{
Role = n.Role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.AGENT => "assistant",
ChatRole.SYSTEM => "system",
_ => "user",
},
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
});
// Prepare the Mistral HTTP chat request: // Prepare the Mistral HTTP chat request:
var mistralChatRequest = JsonSerializer.Serialize(new ChatRequest var mistralChatRequest = JsonSerializer.Serialize(new ChatRequest

View File

@ -90,23 +90,14 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, "https
var apiParameters = this.ParseAdditionalApiParameters("input", "store", "tools"); var apiParameters = this.ParseAdditionalApiParameters("input", "store", "tools");
// Build the list of messages: // Build the list of messages:
var messages = await chatThread.Blocks.BuildMessages(async n => new TextMessage var messages = await chatThread.Blocks.BuildMessages(role => role switch
{ {
Role = n.Role switch ChatRole.USER => "user",
{ ChatRole.AI => "assistant",
ChatRole.USER => "user", ChatRole.AGENT => "assistant",
ChatRole.AI => "assistant", ChatRole.SYSTEM => systemPromptRole,
ChatRole.AGENT => "assistant",
ChatRole.SYSTEM => systemPromptRole,
_ => "user", _ => "user",
},
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
}); });
// //

View File

@ -43,24 +43,7 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
var apiParameters = this.ParseAdditionalApiParameters(); var apiParameters = this.ParseAdditionalApiParameters();
// Build the list of messages: // Build the list of messages:
var messages = await chatThread.Blocks.BuildMessages(async n => new TextMessage var messages = await chatThread.Blocks.BuildMessagesUsingStandardRoles();
{
Role = n.Role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.AGENT => "assistant",
ChatRole.SYSTEM => "system",
_ => "user",
},
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
});
// Prepare the OpenRouter HTTP chat request: // Prepare the OpenRouter HTTP chat request:
var openRouterChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest var openRouterChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest

View File

@ -49,24 +49,7 @@ public sealed class ProviderPerplexity() : BaseProvider(LLMProviders.PERPLEXITY,
var apiParameters = this.ParseAdditionalApiParameters(); var apiParameters = this.ParseAdditionalApiParameters();
// Build the list of messages: // Build the list of messages:
var messages = await chatThread.Blocks.BuildMessages(async n => new TextMessage() var messages = await chatThread.Blocks.BuildMessagesUsingStandardRoles();
{
Role = n.Role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.AGENT => "assistant",
ChatRole.SYSTEM => "system",
_ => "user",
},
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
});
// Prepare the Perplexity HTTP chat request: // Prepare the Perplexity HTTP chat request:
var perplexityChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest var perplexityChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest

View File

@ -36,24 +36,7 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
var apiParameters = this.ParseAdditionalApiParameters(); var apiParameters = this.ParseAdditionalApiParameters();
// Build the list of messages: // Build the list of messages:
var messages = await chatThread.Blocks.BuildMessages(async n => new TextMessage var messages = await chatThread.Blocks.BuildMessagesUsingStandardRoles();
{
Role = n.Role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.AGENT => "assistant",
ChatRole.SYSTEM => "system",
_ => "user",
},
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
});
// Prepare the OpenAI HTTP chat request: // Prepare the OpenAI HTTP chat request:
var providerChatRequest = JsonSerializer.Serialize(new ChatRequest var providerChatRequest = JsonSerializer.Serialize(new ChatRequest

View File

@ -40,24 +40,7 @@ public sealed class ProviderX() : BaseProvider(LLMProviders.X, "https://api.x.ai
var apiParameters = this.ParseAdditionalApiParameters(); var apiParameters = this.ParseAdditionalApiParameters();
// Build the list of messages: // Build the list of messages:
var messages = await chatThread.Blocks.BuildMessages(async n => new TextMessage var messages = await chatThread.Blocks.BuildMessagesUsingStandardRoles();
{
Role = n.Role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.AGENT => "assistant",
ChatRole.SYSTEM => "system",
_ => "user",
},
Content = n.Content switch
{
ContentText text => await text.PrepareTextContentForAI(),
_ => string.Empty,
}
});
// Prepare the xAI HTTP chat request: // Prepare the xAI HTTP chat request:
var xChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest var xChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest