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
29 changes: 19 additions & 10 deletions src/Indice.Features.Agents.Core/AgentsFeatureExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using OpenAI;
using static Indice.Features.Agents.Core.AgentsOptions;

namespace Microsoft.Extensions.DependencyInjection;
Expand All @@ -20,8 +21,8 @@ public static class AgentsFeatureExtensions
{
/// <summary>
/// Registers Dex core services: <see cref="AgentsOptions"/> (bound from the <c>Dex</c> configuration section),
/// <see cref="AzureOpenAIClient"/> (singleton — each pipeline step builds its own role-bound agent from it),
/// the embedding generator, the <see cref="AgentsDbContext"/> wired to SQL Server, and <see cref="IDexRunner"/>.
/// <see cref="OpenAIClient"/> (singleton — each pipeline step builds its own role-bound agent from it),
/// the embedding generator, the <see cref="AgentsDbContext"/> wired to SQL Server, and <see cref="IDexChatClient"/>.
/// </summary>
public static IServiceCollection AddAgentsCore(this IServiceCollection services, IConfiguration configuration, Action<AgentsOptions>? configureAction = null) {
var optionsBuilder = services.AddOptions<AgentsOptions>().BindConfiguration("Dex");
Expand All @@ -36,17 +37,25 @@ public static IServiceCollection AddAgentsCore(this IServiceCollection services,
agents.Value.ConfigureModelOptions?.Invoke(models);
});

services.TryAddSingleton(sp => {
services.AddSingleton(sp => {
var opts = sp.GetRequiredService<IOptions<AgentsOptions>>().Value.AzureOpenAI;
return new AzureOpenAIClient(new Uri(opts.Endpoint!), new ApiKeyCredential(opts.ApiKey!));
});
services.AddKeyedChatClient(nameof(AzureOpenAIDeployments.Reasoning), sp => {
var opts = sp.GetRequiredService<IOptions<AgentsOptions>>().Value.AzureOpenAI.Deployments;
var innerClient = sp.GetRequiredService<AzureOpenAIClient>();
return innerClient.GetChatClient(opts.Reasoning).AsIChatClient();
});
services.AddKeyedChatClient(nameof(AzureOpenAIDeployments.Fast), sp => {
var opts = sp.GetRequiredService<IOptions<AgentsOptions>>().Value.AzureOpenAI.Deployments;
var innerClient = sp.GetRequiredService<AzureOpenAIClient>();
return innerClient.GetChatClient(opts.Fast).AsIChatClient();
});

services.TryAddSingleton<IEmbeddingGenerator<string, Embedding<float>>>(sp => {
services.AddEmbeddingGenerator(sp => {
var opts = sp.GetRequiredService<IOptions<AgentsOptions>>().Value.AzureOpenAI;
var client = sp.GetRequiredService<AzureOpenAIClient>();
return client
.GetEmbeddingClient(opts.Deployments.Embedding!)
.AsIEmbeddingGenerator(opts.EmbeddingDimensions);
var innerClient = sp.GetRequiredService<AzureOpenAIClient>();
return innerClient.GetEmbeddingClient(opts.Deployments.Embedding!).AsIEmbeddingGenerator(opts.EmbeddingDimensions);
});

services.AddDbContext<AgentsDbContext>((sp, options) => {
Expand All @@ -65,12 +74,12 @@ public static IServiceCollection AddAgentsCore(this IServiceCollection services,
services.TryAddTransient<IUsageGuardService, UsageGuardService>();
services.TryAddTransient<SessionStoreChatHistoryProvider>();
services.TryAddSingleton<IPromptTemplateRenderer, FileSystemPromptTemplateRenderer>();
services.TryAddTransient<IDexRunner, DexRunner>();
services.TryAddTransient<IDexChatClient, DexChatClient>();
services.TryAddSingleton<WorkflowClaimsPrincipalSelector>(sp =>
() => null
);
services.TryAddSingleton<ISourceLinkGenerator, NoOpSourceLinkGenerator>();
services.AddDefaultDexPipeline();
services.AddAgentsDefaultPipeline();
return services;
}

Expand Down
6 changes: 3 additions & 3 deletions src/Indice.Features.Agents.Core/Data/DbMessage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Indice.Features.Agents.Core.Models;
using Microsoft.Extensions.AI;

namespace Indice.Features.Agents.Core.Data;

Expand All @@ -12,10 +12,10 @@ public class DbMessage
public Guid SessionId { get; set; }

/// <summary>Author role of this message. Persisted as the role's string value (e.g. <c>user</c>).</summary>
public ChatMessageRole Role { get; set; } = ChatMessageRole.User;
public ChatRole Role { get; set; } = ChatRole.User;

/// <summary>Message body.</summary>
public ChatMessageContent Content { get; set; } = new ();
public List<AIContent> Contents { get; set; } = [];

/// <summary>Creation timestamp.</summary>
public DateTimeOffset CreatedAt { get; set; }
Expand Down
14 changes: 5 additions & 9 deletions src/Indice.Features.Agents.Core/Data/Mappings/DbMessageMap.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
using System.Data;
using System.Text.Json;
using Indice.Configuration;
using Indice.Features.Agents.Core.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.Extensions.AI;

namespace Indice.Features.Agents.Core.Data.Mappings;

Expand All @@ -18,14 +14,14 @@ public void Configure(EntityTypeBuilder<DbMessage> builder) {
builder.HasKey(x => x.Id);
builder.Property(x => x.Role)
.HasConversion(
role => role.ToString().ToLowerInvariant(),
value => Enum.Parse<ChatMessageRole>(value, ignoreCase: true))
role => role.ToString(),
value => new ChatRole(value))
.HasMaxLength(TextSizePresets.S32)
.IsRequired();

//TODO: check whats going on here with this version. builder.ComplexProperty(b => b.Content, b => b.ToJson().IsRequired());
builder.ComplexProperty(b => b.Content, b => b.ToJson().IsRequired());
//builder.Property(x => x.Content).HasRequiredJsonConversion();
//builder.ComplexProperty(b => b.Contents, b => b.ToJson().IsRequired());
builder.Property(x => x.Contents).HasRequiredJsonConversion();

builder.Property(x => x.ModelUsed).HasMaxLength(TextSizePresets.M128);
builder.HasIndex(x => new { x.SessionId, x.CreatedAt });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.9" />
<PackageReference Include="Microsoft.Agents.AI" Version="1.13.0" />
<PackageReference Include="Microsoft.Agents.AI.Abstractions" Version="1.13.0" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" Version="1.13.0" />
<PackageReference Include="Microsoft.Agents.AI.Workflows" Version="1.13.0" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.7.0" />
<PackageReference Include="Microsoft.Extensions.AI" Version="10.8.0" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.8.0" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />

<!-- 3rd Party -->
<PackageReference Include="Duende.IdentityModel" Version="8.1.0" />
<PackageReference Include="Handlebars.Net" Version="2.1.6" />
<PackageReference Include="Markdig" Version="1.3.2" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.5.0-beta.1" />
<PackageReference Include="FluentValidation" Version="12.0.0" />
</ItemGroup>

Expand Down
49 changes: 31 additions & 18 deletions src/Indice.Features.Agents.Core/Models/ChatMessage.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
using OpenAI.Assistants;

namespace Indice.Features.Agents.Core.Models;

/// <summary>A single turn (user or assistant) in a chat session. DTO exposed at the service boundary; mirrors <see cref="Data.DbMessage"/>.</summary>
public class ChatMessage
{
/// <summary>Message identifier.</summary>
public Guid Id { get; init; }
///// <summary>A single turn (user or assistant) in a chat session. DTO exposed at the service boundary; mirrors <see cref="Data.DbMessage"/>.</summary>
//public class ChatMessage
//{
// /// <summary>Identifier of the session this turn belongs to.</summary>
// public Guid SessionId { get; init; }

// /// <summary>Identifier of the assistant message persisted for this turn.</summary>
// public Guid MessageId { get; init; }

/// <summary>Author role of this message. Serializes as the role's lowercase value (e.g. <c>user</c>).</summary>
public ChatMessageRole Role { get; init; } = ChatMessageRole.User;
// /// <summary>Author role of this message. Serializes as the role's lowercase value (e.g. <c>user</c>).</summary>
// public ChatMessageRole Role { get; init; } = ChatMessageRole.User;

/// <summary>Message body.</summary>
public ChatMessageContent Content { get; init; } = new();
// /// <summary>Message body.</summary>
// public ChatMessageContent Content { get; init; } = new();

/// <summary>Creation timestamp.</summary>
public DateTimeOffset CreatedAt { get; init; }
// /// <summary>Creation timestamp.</summary>
// public DateTimeOffset CreatedAt { get; init; }

/// <summary>References to chunks.</summary>
public List<Citation> Citations { get; set; } = [];
// /// <summary>References to chunks.</summary>
// public List<Citation> Citations { get; set; } = [];

/// <summary>References to source documents.</summary>
public List<SourceDocumentLink> Sources { get; set; } = [];
}
// /// <summary>References to source documents.</summary>
// public List<SourceDocumentLink> Sources { get; set; } = [];

// /// <summary>True when a pipeline step threw and the workflow halted. Out-of-scope is NOT a failure — its refusal text flows through <see cref="Answer"/>.</summary>
// public bool Failed { get; init; }

// /// <summary>True when the turn was blocked by a session usage limit — <see cref="Answer"/> carries the predefined limit message, nothing was persisted, and <see cref="MessageId"/> is empty.</summary>
// public bool LimitReached { get; init; }

// /// <summary>Questions used in this session so far, for a <c>used/total</c> display. <c>null</c> when the message limit is disabled.</summary>
// public int? QuestionsUsed { get; init; }

// /// <summary>Total questions allowed per session, for a <c>used/total</c> display. <c>null</c> when the message limit is disabled.</summary>
// public int? QuestionsTotal { get; init; }
//}
30 changes: 30 additions & 0 deletions src/Indice.Features.Agents.Core/Models/ChatMessageContent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Text.Json.Serialization;

namespace Indice.Features.Agents.Core.Models;

/// <summary>Represents the content of a chat message.</summary>
public class ChatMessageContent
{
/// <summary>Creates a new instance of <see cref="ChatMessageContent"/>.</summary>
public ChatMessageContent() {

}
/// <summary>Creates a new instance of <see cref="ChatMessageContent"/> with a single part.</summary>
public ChatMessageContent(string content, string contentType = "text/markdown") {
AddPart(content, contentType);
}


/// <summary>Parts of the message content.</summary>
[JsonPropertyName("parts")]
public List<ChatMessagePart> Parts { get; set; } = [];

/// <summary>
/// Adds a new part to the message content.
/// </summary>
/// <param name="value">The value of the message part.</param>
/// <param name="contentType">The content type of the message part.</param>
public void AddPart(string value, string contentType) {
Parts.Add(ChatMessagePart.FromText(value, contentType));
}
}
21 changes: 0 additions & 21 deletions src/Indice.Features.Agents.Core/Models/ChatMessageMappings.cs

This file was deleted.

27 changes: 0 additions & 27 deletions src/Indice.Features.Agents.Core/Models/ChatMessagePart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,6 @@

namespace Indice.Features.Agents.Core.Models;

/// <summary>Represents the content of a chat message.</summary>
public class ChatMessageContent
{
/// <summary>Creates a new instance of <see cref="ChatMessageContent"/>.</summary>
public ChatMessageContent() {

}
/// <summary>Creates a new instance of <see cref="ChatMessageContent"/> with a single part.</summary>
public ChatMessageContent(string content, string contentType = "text/markdown") {
AddPart(content, contentType);
}


/// <summary>Parts of the message content.</summary>
[JsonPropertyName("parts")]
public List<ChatMessagePart> Parts { get; set; } = [];

/// <summary>
/// Adds a new part to the message content.
/// </summary>
/// <param name="value">The value of the message part.</param>
/// <param name="contentType">The content type of the message part.</param>
public void AddPart(string value, string contentType) {
Parts.Add(ChatMessagePart.FromText(value, contentType));
}
}

/// <summary>Represents a part of a chat message.</summary>
public class ChatMessagePart
{
Expand Down
21 changes: 0 additions & 21 deletions src/Indice.Features.Agents.Core/Models/ChatMessageRole.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
namespace Indice.Features.Agents.Server.Endpoints;
namespace Indice.Features.Agents.Core.Models;

/// <summary>Body accepted by both <c>POST /api/my/chats</c> (creates the session inline) and <c>POST /api/my/chats/{id}/messages</c>.</summary>
public class ChatRequest
{
/// <summary>The end-user message text.</summary>
public string Text { get; init; } = string.Empty;
}

}
35 changes: 0 additions & 35 deletions src/Indice.Features.Agents.Core/Models/ChatResponse.cs

This file was deleted.

2 changes: 1 addition & 1 deletion src/Indice.Features.Agents.Core/Models/ChatStreamEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public record ChatStreamEvent
public Guid? SessionId { get; init; }

/// <summary>Identifier of the persisted assistant message; populated on <c>complete</c>.</summary>
public Guid? MessageId { get; init; }
public string? MessageId { get; init; }

/// <summary>True when a pipeline step threw and the workflow halted; populated on <c>complete</c>.</summary>
public bool? Failed { get; init; }
Expand Down
2 changes: 2 additions & 0 deletions src/Indice.Features.Agents.Core/Models/Session.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Microsoft.Extensions.AI;

namespace Indice.Features.Agents.Core.Models;

/// <summary>Detail view of a chat session, including the most recent messages.</summary>
Expand Down
Loading
Loading