Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
<LangVersion>14</LangVersion>
<AnalysisMode>Recommended</AnalysisMode>
<ImplicitUsings>enable</ImplicitUsings>
<PreserveCompilationContext>true</PreserveCompilationContext>
Expand Down
34 changes: 17 additions & 17 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<PackageVersion Include="ContentFeedNuget" Version="$(ToolingPackagesVersion)" />
</ItemGroup>
<ItemGroup>
<PackageVersion Include="AspNet.Security.OAuth.GitHub" Version="9.4.1" />
<PackageVersion Include="AspNet.Security.OAuth.GitHub" Version="10.0.0" />
<PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.4.0" />
<PackageVersion Include="Azure.Identity" Version="1.17.1" />
<PackageVersion Include="Azure.Monitor.OpenTelemetry.AspNetCore" Version="1.4.0" />
Expand All @@ -26,30 +26,30 @@
<PackageVersion Include="HtmlAgilityPack" Version="1.12.4" />
<PackageVersion Include="IntelliTect.Multitool" Version="1.5.3" />
<PackageVersion Include="Mailjet.Api" Version="3.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="9.0.13" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="9.0.13" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.12" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.10" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.10" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="10.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="10.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="5.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.60.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.PgVector" Version="1.60.0-preview" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.102" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.70.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.PgVector" Version="1.70.0-preview" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.103" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="10.0.2" />
<PackageVersion Include="ModelContextProtocol" Version="0.3.0-preview.4" />
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="0.3.0-preview.4" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="System.CommandLine" Version="2.0.3" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="Octokit" Version="14.0.0" />
<PackageVersion Include="DotnetSitemapGenerator" Version="1.0.4" />
<PackageVersion Include="DotnetSitemapGenerator" Version="2.0.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace EssentialCSharp.Chat.Common.Extensions;

public static class ServiceCollectionExtensions
{
private static readonly string[] PostgresScopes = ["https://ossrdbms-aad.database.windows.net/.default"];
private static readonly string[] _PostgresScopes = ["https://ossrdbms-aad.database.windows.net/.default"];

/// <summary>
/// Adds Azure OpenAI and related AI services to the service collection using Managed Identity
Expand Down Expand Up @@ -124,7 +124,7 @@ private static IServiceCollection AddPostgresVectorStoreWithManagedIdentity(
if (isAzurePostgres && string.IsNullOrEmpty(builder.Password))
{
// Get access token for Azure PostgreSQL using managed identity
var tokenRequestContext = new TokenRequestContext(PostgresScopes);
var tokenRequestContext = new TokenRequestContext(_PostgresScopes);
var accessToken = credential.GetToken(tokenRequestContext, default);

// Set the password to the access token
Expand Down
32 changes: 31 additions & 1 deletion EssentialCSharp.Chat.Shared/Services/AIChatService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ public class AIChatService
{
private readonly AIOptions _Options;
private readonly AzureOpenAIClient _AzureClient;
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
private readonly OpenAIResponseClient _ResponseClient;
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
private readonly AISearchService _SearchService;

public AIChatService(IOptions<AIOptions> options, AISearchService searchService, AzureOpenAIClient azureClient)
Expand All @@ -24,7 +26,9 @@ public AIChatService(IOptions<AIOptions> options, AISearchService searchService,
// Initialize Azure OpenAI client and get the Response Client from it
_AzureClient = azureClient;

#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
_ResponseClient = _AzureClient.GetOpenAIResponseClient(_Options.ChatDeploymentName);
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
}

/// <summary>
Expand All @@ -43,8 +47,10 @@ public AIChatService(IOptions<AIOptions> options, AISearchService searchService,
string? systemPrompt = null,
string? previousResponseId = null,
IMcpClient? mcpClient = null,
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
IEnumerable<ResponseTool>? tools = null,
ResponseReasoningEffortLevel? reasoningEffortLevel = null,
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
bool enableContextualSearch = false,
CancellationToken cancellationToken = default)
{
Expand All @@ -69,8 +75,10 @@ public AIChatService(IOptions<AIOptions> options, AISearchService searchService,
string? systemPrompt = null,
string? previousResponseId = null,
IMcpClient? mcpClient = null,
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
IEnumerable<ResponseTool>? tools = null,
ResponseReasoningEffortLevel? reasoningEffortLevel = null,
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
bool enableContextualSearch = false,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
{
Expand All @@ -81,12 +89,14 @@ public AIChatService(IOptions<AIOptions> options, AISearchService searchService,
var systemContext = systemPrompt ?? _Options.SystemPrompt;

// Create the streaming response using the Responses API
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
List<ResponseItem> responseItems = [ResponseItem.CreateUserMessageItem(enrichedPrompt)];
if (systemContext is not null)
{
responseItems.Add(
ResponseItem.CreateSystemMessageItem(systemContext));
}
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var streamingUpdates = _ResponseClient.CreateResponseStreamingAsync(
responseItems,
options: responseOptions,
Expand Down Expand Up @@ -132,14 +142,17 @@ private async Task<string> EnrichPromptWithContext(string prompt, bool enableCon
/// Processes streaming updates from the OpenAI Responses API, handling both regular responses and function calls
/// </summary>
private async IAsyncEnumerable<(string text, string? responseId)> ProcessStreamingUpdatesAsync(
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
IAsyncEnumerable<StreamingResponseUpdate> streamingUpdates,
ResponseCreationOptions responseOptions,
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
IMcpClient? mcpClient,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await foreach (var update in streamingUpdates.WithCancellation(cancellationToken))
{
string? responseId;
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
if (update is StreamingResponseCreatedUpdate created)
{
// Remember the response ID for later function calls
Expand Down Expand Up @@ -169,15 +182,18 @@ private async Task<string> EnrichPromptWithContext(string prompt, bool enableCon
{
yield return (string.Empty, responseId: completedUpdate.Response.Id); // Signal completion with response ID
}
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
}
}

/// <summary>
/// Executes a function call and streams the response
/// </summary>
private async IAsyncEnumerable<(string text, string? responseId)> ExecuteFunctionCallAsync(
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
FunctionCallResponseItem functionCallItem,
ResponseCreationOptions responseOptions,
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
IMcpClient mcpClient,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -217,11 +233,13 @@ private async Task<string> EnrichPromptWithContext(string prompt, bool enableCon

// Create input items with both the function call and the result
// This matches the Python pattern: append both tool_call and result
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var inputItems = new List<ResponseItem>
{
functionCallItem, // The original function call
new FunctionCallOutputResponseItem(functionCallItem.CallId, string.Join("", toolResult.Content.Where(x => x.Type == "text").OfType<TextContentBlock>().Select(x => x.Text)))
};
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

// Stream the function call response using the same processing logic
var functionResponseStream = _ResponseClient.CreateResponseStreamingAsync(
Expand All @@ -238,6 +256,7 @@ private async Task<string> EnrichPromptWithContext(string prompt, bool enableCon
/// <summary>
/// Creates response options with optional features
/// </summary>
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
private static async Task<ResponseCreationOptions> CreateResponseOptionsAsync(
string? previousResponseId = null,
IEnumerable<ResponseTool>? tools = null,
Expand All @@ -247,6 +266,7 @@ private static async Task<ResponseCreationOptions> CreateResponseOptionsAsync(
)
{
var options = new ResponseCreationOptions();
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

// Add conversation context if available
if (!string.IsNullOrEmpty(previousResponseId))
Expand All @@ -267,17 +287,21 @@ private static async Task<ResponseCreationOptions> CreateResponseOptionsAsync(
{
await foreach (McpClientTool tool in mcpClient.EnumerateToolsAsync(cancellationToken: cancellationToken))
{
options.Tools.Add(ResponseTool.CreateFunctionTool(tool.Name, tool.Description, BinaryData.FromString(tool.JsonSchema.GetRawText())));
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
options.Tools.Add(ResponseTool.CreateFunctionTool(tool.Name, functionDescription: tool.Description, strictModeEnabled: true, functionParameters: BinaryData.FromString(tool.JsonSchema.GetRawText())));
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
}
}

// Add reasoning options if specified
if (reasoningEffortLevel.HasValue)
{
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
options.ReasoningOptions = new ResponseReasoningOptions()
{
ReasoningEffortLevel = reasoningEffortLevel.Value
};
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
}

return options;
Expand All @@ -288,20 +312,24 @@ private static async Task<ResponseCreationOptions> CreateResponseOptionsAsync(
/// </summary>
private async Task<(string response, string responseId)> GetChatCompletionCore(
string prompt,
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
ResponseCreationOptions responseOptions,
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
string? systemPrompt = null,
CancellationToken cancellationToken = default)
{
// Construct the user input with system context if provided
var systemContext = systemPrompt ?? _Options.SystemPrompt;

// Create the streaming response using the Responses API
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
List<ResponseItem> responseItems = [ResponseItem.CreateUserMessageItem(prompt)];
if (systemContext is not null)
{
responseItems.Add(
ResponseItem.CreateSystemMessageItem(systemContext));
}
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

// Create the response using the Responses API
var response = await _ResponseClient.CreateResponseAsync(
Expand All @@ -315,6 +343,7 @@ private static async Task<ResponseCreationOptions> CreateResponseOptionsAsync(

foreach (var outputItem in response.Value.OutputItems)
{
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
if (outputItem is MessageResponseItem messageItem &&
messageItem.Role == MessageRole.Assistant)
{
Expand All @@ -325,6 +354,7 @@ private static async Task<ResponseCreationOptions> CreateResponseOptionsAsync(
break;
}
}
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
}

return (responseText, responseId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

Expand Down
2 changes: 1 addition & 1 deletion EssentialCSharp.Chat/EssentialCSharp.Chat.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<Version>0.0.1</Version>
</PropertyGroup>

Expand Down
4 changes: 2 additions & 2 deletions EssentialCSharp.Web.Tests/EssentialCSharp.Web.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>

<IsPackable>false</IsPackable>
<IsPublishable>false</IsPublishable>
Expand Down
Loading
Loading