diff --git a/Proxytrace.Api.Tests/AgentCallsControllerHistogramTests.cs b/Proxytrace.Api.Tests/AgentCallsControllerHistogramTests.cs index 4492bad3e..1ede439cf 100644 --- a/Proxytrace.Api.Tests/AgentCallsControllerHistogramTests.cs +++ b/Proxytrace.Api.Tests/AgentCallsControllerHistogramTests.cs @@ -12,7 +12,7 @@ using Proxytrace.Application.Streaming; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Session; using Nordstein.Core.Testing; diff --git a/Proxytrace.Api.Tests/AgentCallsControllerProposalsTests.cs b/Proxytrace.Api.Tests/AgentCallsControllerProposalsTests.cs index f38516b72..da2120839 100644 --- a/Proxytrace.Api.Tests/AgentCallsControllerProposalsTests.cs +++ b/Proxytrace.Api.Tests/AgentCallsControllerProposalsTests.cs @@ -162,7 +162,7 @@ private static AgentCallsController ResolveController( services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), - services.GetRequiredService(), + services.GetRequiredService(), guard, NullLogger.Instance, services.GetRequiredService(), diff --git a/Proxytrace.Api.Tests/AgentCallsControllerSummaryTests.cs b/Proxytrace.Api.Tests/AgentCallsControllerSummaryTests.cs index 34c2c40d0..2a3d9a44d 100644 --- a/Proxytrace.Api.Tests/AgentCallsControllerSummaryTests.cs +++ b/Proxytrace.Api.Tests/AgentCallsControllerSummaryTests.cs @@ -11,7 +11,7 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; using Proxytrace.Domain.AuditLog; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Session; using Nordstein.Core.Testing; diff --git a/Proxytrace.Api.Tests/AgentCallsControllerTests.cs b/Proxytrace.Api.Tests/AgentCallsControllerTests.cs index a2b2f2044..e07ec9bbb 100644 --- a/Proxytrace.Api.Tests/AgentCallsControllerTests.cs +++ b/Proxytrace.Api.Tests/AgentCallsControllerTests.cs @@ -13,9 +13,8 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Nordstein.Core.Testing; namespace Proxytrace.Api.Tests; @@ -278,9 +277,9 @@ private async Task SeedAgentInNewProjectAsync(IServiceProvider services, services.GetRequiredService()($"P-{name}-{Guid.NewGuid():N}", endpoint, []), CancellationToken); - var template = services.GetRequiredService()( + var template = services.GetRequiredService()( $"T-{name}", "You are a test agent."); - var parameters = services.GetRequiredService()(null, null, null, null, null); + var parameters = services.GetRequiredService()(null, null, null, null, null); return await services.GetRequiredService().AddAsync( services.GetRequiredService()( @@ -477,7 +476,7 @@ private static AgentCallsController ResolveController( services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), - services.GetRequiredService(), + services.GetRequiredService(), guard, NullLogger.Instance, services.GetRequiredService(), diff --git a/Proxytrace.Api.Tests/AgentsControllerTests.cs b/Proxytrace.Api.Tests/AgentsControllerTests.cs index d4b6f8280..e640886d7 100644 --- a/Proxytrace.Api.Tests/AgentsControllerTests.cs +++ b/Proxytrace.Api.Tests/AgentsControllerTests.cs @@ -173,8 +173,8 @@ public async Task UpdateEndpoint_SwapsEndpoint() services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), - services.GetRequiredService(), - services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService(), Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance, services.GetRequiredService()); } diff --git a/Proxytrace.Api.Tests/AnomaliesControllerTests.cs b/Proxytrace.Api.Tests/AnomaliesControllerTests.cs index 4f56cc5ba..1a5e05fd3 100644 --- a/Proxytrace.Api.Tests/AnomaliesControllerTests.cs +++ b/Proxytrace.Api.Tests/AnomaliesControllerTests.cs @@ -7,10 +7,9 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.CustomAnomaly; -using Proxytrace.Domain.Message; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Messages; using Nordstein.Core.Testing; namespace Proxytrace.Api.Tests; diff --git a/Proxytrace.Api.Tests/CustomAnomalyDetectorsControllerTests.cs b/Proxytrace.Api.Tests/CustomAnomalyDetectorsControllerTests.cs index e20839b19..e957d5a85 100644 --- a/Proxytrace.Api.Tests/CustomAnomalyDetectorsControllerTests.cs +++ b/Proxytrace.Api.Tests/CustomAnomalyDetectorsControllerTests.cs @@ -324,8 +324,8 @@ private async Task GetProject(IServiceProvider services) private async Task CreateAgentIn(IServiceProvider services, IProject project) { var createAgent = services.GetRequiredService(); - var createPrompt = services.GetRequiredService(); - var createParameters = services.GetRequiredService(); + var createPrompt = services.GetRequiredService(); + var createParameters = services.GetRequiredService(); var agent = createAgent( name: $"Agent {Guid.NewGuid():N}", systemPrompt: createPrompt("agent", "You help."), @@ -364,8 +364,8 @@ private static CustomAnomalyDetectorsController ResolveController( services.GetRequiredService>(), services.GetRequiredService(), services.GetRequiredService(), - services.GetRequiredService(), - services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService(), services.GetRequiredService(), accessGuard ?? services.GetRequiredService(), Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance); diff --git a/Proxytrace.Api.Tests/EvaluatorTestBenchControllerTests.cs b/Proxytrace.Api.Tests/EvaluatorTestBenchControllerTests.cs index 930f15a73..a12904306 100644 --- a/Proxytrace.Api.Tests/EvaluatorTestBenchControllerTests.cs +++ b/Proxytrace.Api.Tests/EvaluatorTestBenchControllerTests.cs @@ -6,13 +6,13 @@ using Proxytrace.Api.Dto.TestRuns; using Proxytrace.Domain; using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Evaluation; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Inference; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.TestCase; using Proxytrace.Domain.TestResult; diff --git a/Proxytrace.Api.Tests/Mcp/SuiteToolsTests.cs b/Proxytrace.Api.Tests/Mcp/SuiteToolsTests.cs index 795b1fd94..517ec544f 100644 --- a/Proxytrace.Api.Tests/Mcp/SuiteToolsTests.cs +++ b/Proxytrace.Api.Tests/Mcp/SuiteToolsTests.cs @@ -9,7 +9,7 @@ using Proxytrace.Domain.AgentCall; using Proxytrace.Domain.AuditLog; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.Project; using Proxytrace.Domain.TestCase; using Proxytrace.Domain.TestSuite; diff --git a/Proxytrace.Api.Tests/Module.cs b/Proxytrace.Api.Tests/Module.cs index 0e9914e09..97b45a681 100644 --- a/Proxytrace.Api.Tests/Module.cs +++ b/Proxytrace.Api.Tests/Module.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using Proxytrace.Domain.Notifications; using Proxytrace.Domain.AuditLog; using Autofac; @@ -7,6 +8,7 @@ using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.User; using Proxytrace.Storage; diff --git a/Proxytrace.Api.Tests/ProposalsControllerTests.cs b/Proxytrace.Api.Tests/ProposalsControllerTests.cs index 12c136634..7f6a804b5 100644 --- a/Proxytrace.Api.Tests/ProposalsControllerTests.cs +++ b/Proxytrace.Api.Tests/ProposalsControllerTests.cs @@ -383,12 +383,12 @@ private async Task SeedProposalInNewProjectAsync( var agent = await services.GetRequiredService().AddAsync( services.GetRequiredService()( $"A-{name}", - services.GetRequiredService()( + services.GetRequiredService()( $"T-{name}", "You are a test agent."), [], endpoint, project, - services.GetRequiredService()( + services.GetRequiredService()( null, null, null, null, null)), CancellationToken); diff --git a/Proxytrace.Api.Tests/TestRunDtoMapperTests.cs b/Proxytrace.Api.Tests/TestRunDtoMapperTests.cs index e2f3d67ac..55cabeaa0 100644 --- a/Proxytrace.Api.Tests/TestRunDtoMapperTests.cs +++ b/Proxytrace.Api.Tests/TestRunDtoMapperTests.cs @@ -3,9 +3,9 @@ using Proxytrace.Api.Dto.TestRuns; using Proxytrace.Domain; using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.TestCase; using Proxytrace.Domain.TestResult; diff --git a/Proxytrace.Api.Tests/TestRunsControllerTests.cs b/Proxytrace.Api.Tests/TestRunsControllerTests.cs index a37690c1a..8d8ccfe02 100644 --- a/Proxytrace.Api.Tests/TestRunsControllerTests.cs +++ b/Proxytrace.Api.Tests/TestRunsControllerTests.cs @@ -10,9 +10,10 @@ using NSubstitute; using Proxytrace.Domain; using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.TestRun; using Proxytrace.Domain.TestRunGroup; diff --git a/Proxytrace.Api.Tests/TestSuitesControllerTests.cs b/Proxytrace.Api.Tests/TestSuitesControllerTests.cs index dfc3b1ab8..a78fb1fcc 100644 --- a/Proxytrace.Api.Tests/TestSuitesControllerTests.cs +++ b/Proxytrace.Api.Tests/TestSuitesControllerTests.cs @@ -674,8 +674,8 @@ private async Task CreateAgentInFreshProject(IServiceProvider services) { var project = await services.GetRequiredService>().CreateAsync(CancellationToken); var endpoint = await services.GetRequiredService>().GetOrCreateAsync(CancellationToken); - var prompt = await services.GetRequiredService>().CreateAsync(CancellationToken); - var modelParameters = await services.GetRequiredService>().CreateAsync(CancellationToken); + var prompt = await services.GetRequiredService>().CreateAsync(CancellationToken); + var modelParameters = await services.GetRequiredService>().CreateAsync(CancellationToken); return await services.GetRequiredService().CreateWithInitialVersionAsync( name: "Suite agent", systemPrompt: prompt, diff --git a/Proxytrace.Api/Auth/ISigningKeyProvider.cs b/Proxytrace.Api/Auth/ISigningKeyProvider.cs index c3eff997f..88f662fd5 100644 --- a/Proxytrace.Api/Auth/ISigningKeyProvider.cs +++ b/Proxytrace.Api/Auth/ISigningKeyProvider.cs @@ -3,4 +3,4 @@ namespace Proxytrace.Api.Auth; internal interface ISigningKeyProvider { string EnsureSigningKey(string? configured); -} \ No newline at end of file +} diff --git a/Proxytrace.Api/Controllers/AgentCallsController.cs b/Proxytrace.Api/Controllers/AgentCallsController.cs index 8f37ad604..23d5c2dd9 100644 --- a/Proxytrace.Api/Controllers/AgentCallsController.cs +++ b/Proxytrace.Api/Controllers/AgentCallsController.cs @@ -18,11 +18,10 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; using Proxytrace.Domain.AuditLog; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Nordstein.Core.Domain.Paging; using Proxytrace.Domain.Session; -using Proxytrace.Domain.Usage; namespace Proxytrace.Api.Controllers; @@ -459,11 +458,11 @@ public async Task> Seed( var conversation = Conversation.Create(); if (!string.IsNullOrEmpty(request.SystemContent)) - conversation = conversation.WithSystemMessage(new SystemMessage([Proxytrace.Domain.Message.Content.FromText(request.SystemContent)])); - conversation = conversation.With(new UserMessage([Proxytrace.Domain.Message.Content.FromText(request.UserContent)])); + conversation = conversation.WithSystemMessage(new SystemMessage([Nordstein.Core.AI.Messages.Content.FromText(request.SystemContent)])); + conversation = conversation.With(new UserMessage([Nordstein.Core.AI.Messages.Content.FromText(request.UserContent)])); var assistantMessage = new AssistantMessage( - [Proxytrace.Domain.Message.Content.FromText(request.AssistantContent)], + [Nordstein.Core.AI.Messages.Content.FromText(request.AssistantContent)], (request.ToolNames ?? []).Select((name, i) => new ToolRequest($"seed-{i}", name, "{}")).ToList()); var usage = new TokenUsage((ulong)request.InputTokens, (ulong)request.OutputTokens); ICompletion completion = createCompletion( diff --git a/Proxytrace.Api/Controllers/AgentsController.cs b/Proxytrace.Api/Controllers/AgentsController.cs index e2a717ae9..a641c214b 100644 --- a/Proxytrace.Api/Controllers/AgentsController.cs +++ b/Proxytrace.Api/Controllers/AgentsController.cs @@ -11,10 +11,11 @@ using Proxytrace.Domain.AgentCall; using Proxytrace.Domain.AgentVersion; using Proxytrace.Domain.AuditLog; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.ModelEndpoint; using Nordstein.Core.Domain.Paging; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; namespace Proxytrace.Api.Controllers; diff --git a/Proxytrace.Api/Controllers/CustomAnomalyDetectorsController.cs b/Proxytrace.Api/Controllers/CustomAnomalyDetectorsController.cs index 9c88ab97d..396fd148b 100644 --- a/Proxytrace.Api/Controllers/CustomAnomalyDetectorsController.cs +++ b/Proxytrace.Api/Controllers/CustomAnomalyDetectorsController.cs @@ -10,9 +10,10 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.AuditLog; using Proxytrace.Domain.CustomAnomaly; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Licensing; diff --git a/Proxytrace.Api/Controllers/EvaluatorTestBenchController.cs b/Proxytrace.Api/Controllers/EvaluatorTestBenchController.cs index d69945b60..d4810c60c 100644 --- a/Proxytrace.Api/Controllers/EvaluatorTestBenchController.cs +++ b/Proxytrace.Api/Controllers/EvaluatorTestBenchController.cs @@ -3,9 +3,9 @@ using Proxytrace.Api.Auth; using Proxytrace.Api.Dto.Evaluators; using Proxytrace.Api.Dto.TestRuns; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.TestCase; using Proxytrace.Domain.TestResult; using Proxytrace.Domain.TestSuite; @@ -178,7 +178,7 @@ public async Task> Run( TimeSpan latency; if (request.ActualResponseOverride is not null) { - actual = new AssistantMessage([Domain.Message.Content.FromText(request.ActualResponseOverride)], []); + actual = new AssistantMessage([Nordstein.Core.AI.Messages.Content.FromText(request.ActualResponseOverride)], []); var latest = await testResults.GetLatestByTestCaseAsync(request.TestCaseId, cancellationToken); latency = latest?.Latency ?? TimeSpan.FromMilliseconds(1); } diff --git a/Proxytrace.Api/Controllers/ProposalsController.cs b/Proxytrace.Api/Controllers/ProposalsController.cs index b281a2471..5012a541b 100644 --- a/Proxytrace.Api/Controllers/ProposalsController.cs +++ b/Proxytrace.Api/Controllers/ProposalsController.cs @@ -14,7 +14,7 @@ using Proxytrace.Domain.TestRun; using Proxytrace.Domain.TestRunGroup; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Proxytrace.Licensing; namespace Proxytrace.Api.Controllers; diff --git a/Proxytrace.Api/Controllers/TestCasesController.cs b/Proxytrace.Api/Controllers/TestCasesController.cs index 2f4ee4822..205dcca39 100644 --- a/Proxytrace.Api/Controllers/TestCasesController.cs +++ b/Proxytrace.Api/Controllers/TestCasesController.cs @@ -5,7 +5,7 @@ using Proxytrace.Api.Dto.TestSuites; using Proxytrace.Domain; using Proxytrace.Domain.AuditLog; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.TestCase; using Proxytrace.Domain.TestSuite; diff --git a/Proxytrace.Api/Controllers/TestRunsController.cs b/Proxytrace.Api/Controllers/TestRunsController.cs index edf0c2b5f..9a48ee3e0 100644 --- a/Proxytrace.Api/Controllers/TestRunsController.cs +++ b/Proxytrace.Api/Controllers/TestRunsController.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using System.Text.Json; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/Proxytrace.Api/Controllers/TheoriesController.cs b/Proxytrace.Api/Controllers/TheoriesController.cs index f45479573..3bf7191da 100644 --- a/Proxytrace.Api/Controllers/TheoriesController.cs +++ b/Proxytrace.Api/Controllers/TheoriesController.cs @@ -14,7 +14,7 @@ using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.OptimizationTheory; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Proxytrace.Licensing; namespace Proxytrace.Api.Controllers; diff --git a/Proxytrace.Api/Dockerfile b/Proxytrace.Api/Dockerfile index f8086140e..88302dee3 100644 --- a/Proxytrace.Api/Dockerfile +++ b/Proxytrace.Api/Dockerfile @@ -41,7 +41,6 @@ COPY ["Proxytrace.Application/Proxytrace.Application.csproj", "Proxytrace.Applic COPY ["Proxytrace.Domain/Proxytrace.Domain.csproj", "Proxytrace.Domain/"] COPY ["Proxytrace.Infrastructure/Proxytrace.Infrastructure.csproj", "Proxytrace.Infrastructure/"] COPY ["Proxytrace.Messaging/Proxytrace.Messaging.csproj", "Proxytrace.Messaging/"] -COPY ["Proxytrace.Serialization/Proxytrace.Serialization.csproj", "Proxytrace.Serialization/"] COPY ["Proxytrace.Storage/Proxytrace.Storage.csproj", "Proxytrace.Storage/"] RUN dotnet restore "Proxytrace.Api/Proxytrace.Api.csproj" -a $TARGETARCH COPY . . diff --git a/Proxytrace.Api/Dto/AgentCalls/AgentCallDtoMapper.cs b/Proxytrace.Api/Dto/AgentCalls/AgentCallDtoMapper.cs index 3ded6b8ca..3ada571e9 100644 --- a/Proxytrace.Api/Dto/AgentCalls/AgentCallDtoMapper.cs +++ b/Proxytrace.Api/Dto/AgentCalls/AgentCallDtoMapper.cs @@ -2,7 +2,7 @@ using Proxytrace.Api.Dto.Inference; using Proxytrace.Api.Dto.Tools; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; namespace Proxytrace.Api.Dto.AgentCalls; diff --git a/Proxytrace.Api/Dto/Inference/ModelParametersDto.cs b/Proxytrace.Api/Dto/Inference/ModelParametersDto.cs index 83c87b34b..f1721bd03 100644 --- a/Proxytrace.Api/Dto/Inference/ModelParametersDto.cs +++ b/Proxytrace.Api/Dto/Inference/ModelParametersDto.cs @@ -1,4 +1,4 @@ -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; namespace Proxytrace.Api.Dto.Inference; diff --git a/Proxytrace.Api/Dto/TestRuns/TestRunDtoMapper.cs b/Proxytrace.Api/Dto/TestRuns/TestRunDtoMapper.cs index 162d22747..58e3409f0 100644 --- a/Proxytrace.Api/Dto/TestRuns/TestRunDtoMapper.cs +++ b/Proxytrace.Api/Dto/TestRuns/TestRunDtoMapper.cs @@ -1,6 +1,7 @@ +using Nordstein.Core.AI.Clients; using System.Text.Json; using Proxytrace.Domain.Evaluation; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.TestResult; using Proxytrace.Domain.TestRun; diff --git a/Proxytrace.Api/Dto/TestSuites/TestSuiteDtoMapper.cs b/Proxytrace.Api/Dto/TestSuites/TestSuiteDtoMapper.cs index e4bca7aaf..bb2550bb0 100644 --- a/Proxytrace.Api/Dto/TestSuites/TestSuiteDtoMapper.cs +++ b/Proxytrace.Api/Dto/TestSuites/TestSuiteDtoMapper.cs @@ -1,5 +1,5 @@ using Proxytrace.Domain.Statistics.TestRun; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.TestSuite; namespace Proxytrace.Api.Dto.TestSuites; diff --git a/Proxytrace.Api/Dto/Tools/ToolDtoMapper.cs b/Proxytrace.Api/Dto/Tools/ToolDtoMapper.cs index a9a4785d1..8f8b2bd3c 100644 --- a/Proxytrace.Api/Dto/Tools/ToolDtoMapper.cs +++ b/Proxytrace.Api/Dto/Tools/ToolDtoMapper.cs @@ -1,6 +1,6 @@ using System.Text.Json; using Proxytrace.Api.Dto.Agents; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Api.Dto.Tools; diff --git a/Proxytrace.Api/Evaluators/EvaluatorBuilder.cs b/Proxytrace.Api/Evaluators/EvaluatorBuilder.cs index 09cbd9efd..311df6e3c 100644 --- a/Proxytrace.Api/Evaluators/EvaluatorBuilder.cs +++ b/Proxytrace.Api/Evaluators/EvaluatorBuilder.cs @@ -2,8 +2,9 @@ using Proxytrace.Api.Dto.Evaluators; using Proxytrace.Domain.Agent; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Licensing; using Proxytrace.Licensing.Exceptions; diff --git a/Proxytrace.Api/Mcp/Tools/TheoryTools.cs b/Proxytrace.Api/Mcp/Tools/TheoryTools.cs index 816ec2d56..0cb911044 100644 --- a/Proxytrace.Api/Mcp/Tools/TheoryTools.cs +++ b/Proxytrace.Api/Mcp/Tools/TheoryTools.cs @@ -12,7 +12,7 @@ using Proxytrace.Domain.OptimizationTheory; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Proxytrace.Licensing; namespace Proxytrace.Api.Mcp.Tools; diff --git a/Proxytrace.Api/Program.cs b/Proxytrace.Api/Program.cs index 9d6906a3f..6536a51ea 100644 --- a/Proxytrace.Api/Program.cs +++ b/Proxytrace.Api/Program.cs @@ -319,4 +319,4 @@ private FixedWindowRateLimiterOptions Limits(string key, int permitLimit, TimeSp }; } } -} \ No newline at end of file +} diff --git a/Proxytrace.Application.Tests/AgentCallCleanupServiceTests.cs b/Proxytrace.Application.Tests/AgentCallCleanupServiceTests.cs index 020a760f0..5d1060168 100644 --- a/Proxytrace.Application.Tests/AgentCallCleanupServiceTests.cs +++ b/Proxytrace.Application.Tests/AgentCallCleanupServiceTests.cs @@ -201,4 +201,4 @@ await agentCallRepository.Received(1) Arg.Is(x => DateTimeOffset.UtcNow - cap - x < TimeSpan.FromSeconds(10)), Arg.Any()); } -} \ No newline at end of file +} diff --git a/Proxytrace.Application.Tests/AgentCallIngestorTests.cs b/Proxytrace.Application.Tests/AgentCallIngestorTests.cs index 4fb0da75c..c4b670fe8 100644 --- a/Proxytrace.Application.Tests/AgentCallIngestorTests.cs +++ b/Proxytrace.Application.Tests/AgentCallIngestorTests.cs @@ -16,9 +16,10 @@ using Proxytrace.Domain.AgentVersion; using Proxytrace.Domain.CustomAnomaly; using Nordstein.Core.Domain.Exceptions; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.Project; using Proxytrace.Domain.Session; diff --git a/Proxytrace.Application.Tests/AgentNameGeneratorTests.cs b/Proxytrace.Application.Tests/AgentNameGeneratorTests.cs index cd0248bf2..2572357a0 100644 --- a/Proxytrace.Application.Tests/AgentNameGeneratorTests.cs +++ b/Proxytrace.Application.Tests/AgentNameGeneratorTests.cs @@ -1,15 +1,17 @@ +using Nordstein.Core.AI.Clients; using AwesomeAssertions; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using NSubstitute.ExceptionExtensions; using Proxytrace.Application.Agent; using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Nordstein.Core.Testing; namespace Proxytrace.Application.Tests; @@ -45,7 +47,7 @@ private static AgentNameGenerator Build( endpoint: Arg.Any(), name: Arg.Any(), isSystemAgent: Arg.Any(), - modelParameters: Arg.Any(), + modelParameters: Arg.Any(), cancellationToken: Arg.Any()).Returns(agent); return new AgentNameGenerator(prompts, agentRepo, NullLogger.Instance); @@ -89,7 +91,7 @@ await agentRepo.Received(1).GetOrCreateAsync( endpoint: project.SystemEndpoint, name: "agent_name_generator", isSystemAgent: true, - modelParameters: Arg.Any(), + modelParameters: Arg.Any(), cancellationToken: Arg.Any()); } diff --git a/Proxytrace.Application.Tests/AgentVersionMatcherTests.cs b/Proxytrace.Application.Tests/AgentVersionMatcherTests.cs index 610cf74b8..194f280b9 100644 --- a/Proxytrace.Application.Tests/AgentVersionMatcherTests.cs +++ b/Proxytrace.Application.Tests/AgentVersionMatcherTests.cs @@ -5,8 +5,9 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Nordstein.Core.Testing; namespace Proxytrace.Application.Tests; diff --git a/Proxytrace.Application.Tests/CannedJsonAgent.cs b/Proxytrace.Application.Tests/CannedJsonAgent.cs index 59d59dadf..a23b4a663 100644 --- a/Proxytrace.Application.Tests/CannedJsonAgent.cs +++ b/Proxytrace.Application.Tests/CannedJsonAgent.cs @@ -1,14 +1,15 @@ +using Nordstein.Core.AI.Clients; using NSubstitute; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentVersion; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Inference; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; -using Proxytrace.Serialization; +using Nordstein.Core.AI.Tools; +using Nordstein.Core.AI.Serialization; namespace Proxytrace.Application.Tests; diff --git a/Proxytrace.Application.Tests/CustomAnomaly/CustomAnomalyReviewServiceTests.cs b/Proxytrace.Application.Tests/CustomAnomaly/CustomAnomalyReviewServiceTests.cs index e0e2e5532..020b66caf 100644 --- a/Proxytrace.Application.Tests/CustomAnomaly/CustomAnomalyReviewServiceTests.cs +++ b/Proxytrace.Application.Tests/CustomAnomaly/CustomAnomalyReviewServiceTests.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using System.Net; using AwesomeAssertions; using Autofac; @@ -9,13 +10,13 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.CustomAnomaly; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Notification; using Proxytrace.Licensing; -using Proxytrace.Serialization; +using Nordstein.Core.AI.Serialization; using Nordstein.Core.Testing; namespace Proxytrace.Application.Tests.CustomAnomaly; @@ -210,7 +211,7 @@ private static void Register( builder.RegisterInstance(license).As(); builder.RegisterInstance(broadcaster).As(); builder.RegisterInstance(notifications).As(); - builder.RegisterInstance((agent, _, skipIngestion) => clients.Resolve(agent, skipIngestion)); + builder.RegisterInstance((agent, _, skipIngestion) => clients.Resolve(agent, skipIngestion)); } private static CustomAnomalyReviewService ResolveService(IServiceProvider services) diff --git a/Proxytrace.Application.Tests/Demo/DemoSeedingTests.cs b/Proxytrace.Application.Tests/Demo/DemoSeedingTests.cs index f0d5582ea..c743afc73 100644 --- a/Proxytrace.Application.Tests/Demo/DemoSeedingTests.cs +++ b/Proxytrace.Application.Tests/Demo/DemoSeedingTests.cs @@ -6,7 +6,7 @@ using Proxytrace.Domain.AgentCall; using Proxytrace.Domain.Evaluator; using Proxytrace.Domain.Kiosk; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Notification; using Proxytrace.Domain.OptimizationProposal; @@ -14,7 +14,7 @@ using Proxytrace.Domain.TestRun; using Proxytrace.Domain.TestRunGroup; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.User; using Nordstein.Core.Testing; diff --git a/Proxytrace.Application.Tests/ModelSwitchTheoryValidatorTests.cs b/Proxytrace.Application.Tests/ModelSwitchTheoryValidatorTests.cs index b773dffef..97aad87b9 100644 --- a/Proxytrace.Application.Tests/ModelSwitchTheoryValidatorTests.cs +++ b/Proxytrace.Application.Tests/ModelSwitchTheoryValidatorTests.cs @@ -13,7 +13,7 @@ using Proxytrace.Domain.TestRun; using Proxytrace.Domain.TestRunGroup; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; using Nordstein.Core.Testing; namespace Proxytrace.Application.Tests; diff --git a/Proxytrace.Application.Tests/Module.cs b/Proxytrace.Application.Tests/Module.cs index dc2a3b758..80a302c95 100644 --- a/Proxytrace.Application.Tests/Module.cs +++ b/Proxytrace.Application.Tests/Module.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using Autofac; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; @@ -10,6 +11,7 @@ using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Storage; using Nordstein.Core.Testing; @@ -29,7 +31,6 @@ protected override void Load(ContainerBuilder builder) // they are registered afterwards. builder.RegisterModule(); builder.RegisterModule(); - builder.RegisterModule(); builder.RegisterStub(); builder.RegisterStub(); diff --git a/Proxytrace.Application.Tests/OpenAiCallParserTests.cs b/Proxytrace.Application.Tests/OpenAiCallParserTests.cs index b45991d89..e00cebaf3 100644 --- a/Proxytrace.Application.Tests/OpenAiCallParserTests.cs +++ b/Proxytrace.Application.Tests/OpenAiCallParserTests.cs @@ -3,8 +3,8 @@ using Microsoft.Extensions.DependencyInjection; using Proxytrace.Application.Ingestion.Internal; using Proxytrace.Domain; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelProvider; using Nordstein.Core.Testing; diff --git a/Proxytrace.Application.Tests/Optimization/ProposalAdoptionTests.cs b/Proxytrace.Application.Tests/Optimization/ProposalAdoptionTests.cs index da88c702d..eeb2b1293 100644 --- a/Proxytrace.Application.Tests/Optimization/ProposalAdoptionTests.cs +++ b/Proxytrace.Application.Tests/Optimization/ProposalAdoptionTests.cs @@ -9,10 +9,11 @@ using Proxytrace.Domain.AgentVersion; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.OptimizationProposal; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestRun; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Nordstein.Core.Testing; namespace Proxytrace.Application.Tests.Optimization; diff --git a/Proxytrace.Application.Tests/Optimization/UpdateSystemPromptOptimizerIntegrationTests.cs b/Proxytrace.Application.Tests/Optimization/UpdateSystemPromptOptimizerIntegrationTests.cs index 6e9f62567..a015c773d 100644 --- a/Proxytrace.Application.Tests/Optimization/UpdateSystemPromptOptimizerIntegrationTests.cs +++ b/Proxytrace.Application.Tests/Optimization/UpdateSystemPromptOptimizerIntegrationTests.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using Autofac; using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; @@ -10,12 +11,13 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.Evaluation; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.Model; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; using Proxytrace.Domain.OptimizationTheory; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestCase; @@ -23,7 +25,7 @@ using Proxytrace.Domain.TestRun; using Proxytrace.Domain.TestRunGroup; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Proxytrace.Domain.Kiosk; using Nordstein.Core.Testing; @@ -263,7 +265,7 @@ public async Task SupportAgent_MissingGreeting_ProposesPolitePrompt() agent.Project.Returns(project); agent.SystemPrompt.Returns(systemPrompt); agent.Tools.Returns(new List()); - agent.ModelParameters.Returns(Substitute.For()); + agent.ModelParameters.Returns(Substitute.For()); var suite = Substitute.For(); suite.Agent.Returns(agent); @@ -362,4 +364,4 @@ internal sealed record TestCaseData( string Expected, string Actual, string? FailReason); -} \ No newline at end of file +} diff --git a/Proxytrace.Application.Tests/Optimization/UpdateToolDefinitionOptimizerIntegrationTests.cs b/Proxytrace.Application.Tests/Optimization/UpdateToolDefinitionOptimizerIntegrationTests.cs index fe9fdc40f..01c9f6175 100644 --- a/Proxytrace.Application.Tests/Optimization/UpdateToolDefinitionOptimizerIntegrationTests.cs +++ b/Proxytrace.Application.Tests/Optimization/UpdateToolDefinitionOptimizerIntegrationTests.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using Autofac; using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; @@ -10,12 +11,13 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.Evaluation; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.Model; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; using Proxytrace.Domain.OptimizationTheory; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestCase; @@ -23,7 +25,7 @@ using Proxytrace.Domain.TestRun; using Proxytrace.Domain.TestRunGroup; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Proxytrace.Domain.Kiosk; using Nordstein.Core.Testing; @@ -310,7 +312,7 @@ public async Task SendEmailTool_UnclearFormat_ProposesImprovedDescription() agent.Project.Returns(project); agent.SystemPrompt.Returns(systemPrompt); agent.Tools.Returns(tools); - agent.ModelParameters.Returns(Substitute.For()); + agent.ModelParameters.Returns(Substitute.For()); var suite = Substitute.For(); suite.Agent.Returns(agent); diff --git a/Proxytrace.Application.Tests/Playground/PlaygroundServiceTests.cs b/Proxytrace.Application.Tests/Playground/PlaygroundServiceTests.cs index 67370161f..b6bb2daf8 100644 --- a/Proxytrace.Application.Tests/Playground/PlaygroundServiceTests.cs +++ b/Proxytrace.Application.Tests/Playground/PlaygroundServiceTests.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using AwesomeAssertions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging.Abstractions; @@ -6,11 +7,11 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Nordstein.Core.Domain.Exceptions; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.Model; using Proxytrace.Domain.ModelEndpoint; -using Proxytrace.Domain.Tools; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Tools; +using Nordstein.Core.AI.Completions; using Nordstein.Core.Testing; namespace Proxytrace.Application.Tests.Playground; diff --git a/Proxytrace.Application.Tests/Proxytrace.Application.Tests.csproj b/Proxytrace.Application.Tests/Proxytrace.Application.Tests.csproj index 858d1e83b..d0472bc23 100644 --- a/Proxytrace.Application.Tests/Proxytrace.Application.Tests.csproj +++ b/Proxytrace.Application.Tests/Proxytrace.Application.Tests.csproj @@ -34,7 +34,6 @@ - diff --git a/Proxytrace.Application.Tests/Search/SystemEntitySearchExclusionTests.cs b/Proxytrace.Application.Tests/Search/SystemEntitySearchExclusionTests.cs index 95e409166..3e38b31d5 100644 --- a/Proxytrace.Application.Tests/Search/SystemEntitySearchExclusionTests.cs +++ b/Proxytrace.Application.Tests/Search/SystemEntitySearchExclusionTests.cs @@ -6,9 +6,8 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Nordstein.Core.Testing; namespace Proxytrace.Application.Tests.Search; diff --git a/Proxytrace.Application.Tests/SystemPromptTheoryValidatorTests.cs b/Proxytrace.Application.Tests/SystemPromptTheoryValidatorTests.cs index 60072cca0..c0fa92857 100644 --- a/Proxytrace.Application.Tests/SystemPromptTheoryValidatorTests.cs +++ b/Proxytrace.Application.Tests/SystemPromptTheoryValidatorTests.cs @@ -5,10 +5,11 @@ using Proxytrace.Application.TestRun; using Proxytrace.Domain.Agent; using Proxytrace.Domain.Evaluation; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.OptimizationProposal; using Proxytrace.Domain.OptimizationTheory; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestCase; @@ -16,7 +17,7 @@ using Proxytrace.Domain.TestRun; using Proxytrace.Domain.TestRunGroup; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Nordstein.Core.Testing; namespace Proxytrace.Application.Tests; diff --git a/Proxytrace.Application.Tests/TestCase/ConversationTranscriptTests.cs b/Proxytrace.Application.Tests/TestCase/ConversationTranscriptTests.cs index 3f7b0133c..e88ab7791 100644 --- a/Proxytrace.Application.Tests/TestCase/ConversationTranscriptTests.cs +++ b/Proxytrace.Application.Tests/TestCase/ConversationTranscriptTests.cs @@ -4,10 +4,11 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; using Proxytrace.Domain.AgentVersion; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Application.Tests.TestCase; diff --git a/Proxytrace.Application.Tests/TestCase/ProposalValidatorTests.cs b/Proxytrace.Application.Tests/TestCase/ProposalValidatorTests.cs index a36ff093b..1286f8e41 100644 --- a/Proxytrace.Application.Tests/TestCase/ProposalValidatorTests.cs +++ b/Proxytrace.Application.Tests/TestCase/ProposalValidatorTests.cs @@ -4,9 +4,9 @@ using Proxytrace.Application.TestCase.Internal; using Proxytrace.Domain.AgentCall; using Proxytrace.Domain.AgentVersion; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Application.Tests.TestCase; diff --git a/Proxytrace.Application.Tests/TestCase/TestCaseSynthesisServiceTests.cs b/Proxytrace.Application.Tests/TestCase/TestCaseSynthesisServiceTests.cs index 2ea45debe..61adb4458 100644 --- a/Proxytrace.Application.Tests/TestCase/TestCaseSynthesisServiceTests.cs +++ b/Proxytrace.Application.Tests/TestCase/TestCaseSynthesisServiceTests.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; using NSubstitute; @@ -6,14 +7,14 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; using Proxytrace.Domain.AgentVersion; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Inference; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; -using Proxytrace.Serialization; +using Nordstein.Core.AI.Tools; +using Nordstein.Core.AI.Serialization; using Nordstein.Core.Testing; namespace Proxytrace.Application.Tests.TestCase; diff --git a/Proxytrace.Application.Tests/TestRunnerServiceTests.cs b/Proxytrace.Application.Tests/TestRunnerServiceTests.cs index 238a421ac..ebf500f6e 100644 --- a/Proxytrace.Application.Tests/TestRunnerServiceTests.cs +++ b/Proxytrace.Application.Tests/TestRunnerServiceTests.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using System.Collections.Concurrent; using System.Reflection; using Autofac; @@ -11,17 +12,16 @@ using Proxytrace.Application.TestRun; using Proxytrace.Domain; using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Evaluation; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.TestCase; using Proxytrace.Domain.TestResult; using Proxytrace.Domain.TestRun; using Proxytrace.Domain.TestRunGroup; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Usage; using Proxytrace.Licensing; using Nordstein.Core.Testing; diff --git a/Proxytrace.Application.Tests/UpdateSystemPromptOptimizerTests.cs b/Proxytrace.Application.Tests/UpdateSystemPromptOptimizerTests.cs index dce552e11..81ddd7276 100644 --- a/Proxytrace.Application.Tests/UpdateSystemPromptOptimizerTests.cs +++ b/Proxytrace.Application.Tests/UpdateSystemPromptOptimizerTests.cs @@ -8,11 +8,12 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.Evaluation; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.OptimizationProposal; using Proxytrace.Domain.OptimizationTheory; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestCase; @@ -20,8 +21,8 @@ using Proxytrace.Domain.TestRun; using Proxytrace.Domain.TestRunGroup; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Tools; -using Proxytrace.Serialization; +using Nordstein.Core.AI.Tools; +using Nordstein.Core.AI.Serialization; using Nordstein.Core.Testing; namespace Proxytrace.Application.Tests; @@ -259,7 +260,7 @@ public static OptimizerFixture Build(IServiceProvider services, string cannedRes agent.Project.Returns(project); agent.SystemPrompt.Returns(systemPrompt); agent.Tools.Returns(new List()); - agent.ModelParameters.Returns(Substitute.For()); + agent.ModelParameters.Returns(Substitute.For()); var suite = Substitute.For(); suite.Agent.Returns(agent); @@ -282,7 +283,7 @@ public static OptimizerFixture Build(IServiceProvider services, string cannedRes Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), + Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(systemAgent)); diff --git a/Proxytrace.Application.Tests/UpdateToolDefinitionOptimizerTests.cs b/Proxytrace.Application.Tests/UpdateToolDefinitionOptimizerTests.cs index c42f88886..f7914110d 100644 --- a/Proxytrace.Application.Tests/UpdateToolDefinitionOptimizerTests.cs +++ b/Proxytrace.Application.Tests/UpdateToolDefinitionOptimizerTests.cs @@ -10,19 +10,20 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.Evaluation; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.OptimizationProposal; using Proxytrace.Domain.OptimizationTheory; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.TestCase; using Proxytrace.Domain.TestResult; using Proxytrace.Domain.TestRun; using Proxytrace.Domain.TestRunGroup; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Tools; -using Proxytrace.Serialization; +using Nordstein.Core.AI.Tools; +using Nordstein.Core.AI.Serialization; using Nordstein.Core.Testing; namespace Proxytrace.Application.Tests; @@ -305,7 +306,7 @@ public static OptimizerFixture Build(IServiceProvider services, string cannedRes agent.Project.Returns(project); agent.SystemPrompt.Returns(systemPrompt); agent.Tools.Returns(initialTools); - agent.ModelParameters.Returns(Substitute.For()); + agent.ModelParameters.Returns(Substitute.For()); var suite = Substitute.For(); suite.Agent.Returns(agent); @@ -328,7 +329,7 @@ public static OptimizerFixture Build(IServiceProvider services, string cannedRes Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), + Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(systemAgent)); diff --git a/Proxytrace.Application/Agent/AgentNameGenerator.cs b/Proxytrace.Application/Agent/AgentNameGenerator.cs index e58c1f3d5..900975c5b 100644 --- a/Proxytrace.Application/Agent/AgentNameGenerator.cs +++ b/Proxytrace.Application/Agent/AgentNameGenerator.cs @@ -1,9 +1,10 @@ using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; namespace Proxytrace.Application.Agent; @@ -58,4 +59,4 @@ private async Task GetGeneratorAgent(IProject project, CancellationToken tools: [], isSystemAgent: true, cancellationToken: cancellationToken); -} \ No newline at end of file +} diff --git a/Proxytrace.Application/Cleanup/AgentCallCleanupConfiguration.cs b/Proxytrace.Application/Cleanup/AgentCallCleanupConfiguration.cs index 977d7629c..c54f47c11 100644 --- a/Proxytrace.Application/Cleanup/AgentCallCleanupConfiguration.cs +++ b/Proxytrace.Application/Cleanup/AgentCallCleanupConfiguration.cs @@ -4,4 +4,4 @@ public sealed record AgentCallCleanupConfiguration { public int RetentionDurationDays { get; init; } = 30; public int CleanupIntervalHours { get; init; } = 6; -} \ No newline at end of file +} diff --git a/Proxytrace.Application/Cleanup/Internal/AgentCallCleanupService.cs b/Proxytrace.Application/Cleanup/Internal/AgentCallCleanupService.cs index 797783407..9a4c143d0 100644 --- a/Proxytrace.Application/Cleanup/Internal/AgentCallCleanupService.cs +++ b/Proxytrace.Application/Cleanup/Internal/AgentCallCleanupService.cs @@ -88,4 +88,4 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken) await CleanOnceAsync(cancellationToken); } } -} \ No newline at end of file +} diff --git a/Proxytrace.Application/CustomAnomaly/Internal/CustomAnomalyReviewService.cs b/Proxytrace.Application/CustomAnomaly/Internal/CustomAnomalyReviewService.cs index c7feb0a1f..bf805d73d 100644 --- a/Proxytrace.Application/CustomAnomaly/Internal/CustomAnomalyReviewService.cs +++ b/Proxytrace.Application/CustomAnomaly/Internal/CustomAnomalyReviewService.cs @@ -6,7 +6,7 @@ using Proxytrace.Application.Streaming; using Proxytrace.Domain.AgentCall; using Proxytrace.Domain.CustomAnomaly; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.Notification; using Proxytrace.Licensing; diff --git a/Proxytrace.Application/Demo/Scenarios/CoreSeedScenario.cs b/Proxytrace.Application/Demo/Scenarios/CoreSeedScenario.cs index cb38021cb..9bb595883 100644 --- a/Proxytrace.Application/Demo/Scenarios/CoreSeedScenario.cs +++ b/Proxytrace.Application/Demo/Scenarios/CoreSeedScenario.cs @@ -2,17 +2,16 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Kiosk; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.Model; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Tools; using Proxytrace.Domain.User; // ReSharper disable InconsistentNaming diff --git a/Proxytrace.Application/Demo/Scenarios/EvaluatorSeedScenario.cs b/Proxytrace.Application/Demo/Scenarios/EvaluatorSeedScenario.cs index 402e38ef4..ce7450754 100644 --- a/Proxytrace.Application/Demo/Scenarios/EvaluatorSeedScenario.cs +++ b/Proxytrace.Application/Demo/Scenarios/EvaluatorSeedScenario.cs @@ -2,7 +2,8 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; namespace Proxytrace.Application.Demo.Scenarios; diff --git a/Proxytrace.Application/Demo/Scenarios/OptimizationProposalSeedScenario.cs b/Proxytrace.Application/Demo/Scenarios/OptimizationProposalSeedScenario.cs index 1833d1e5b..53169d2b0 100644 --- a/Proxytrace.Application/Demo/Scenarios/OptimizationProposalSeedScenario.cs +++ b/Proxytrace.Application/Demo/Scenarios/OptimizationProposalSeedScenario.cs @@ -3,7 +3,7 @@ using Proxytrace.Domain.OptimizationProposal; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestRun; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Application.Demo.Scenarios; diff --git a/Proxytrace.Application/Demo/Scenarios/OptimizationTheorySeedScenario.cs b/Proxytrace.Application/Demo/Scenarios/OptimizationTheorySeedScenario.cs index f10af8b5a..9672a37d5 100644 --- a/Proxytrace.Application/Demo/Scenarios/OptimizationTheorySeedScenario.cs +++ b/Proxytrace.Application/Demo/Scenarios/OptimizationTheorySeedScenario.cs @@ -6,7 +6,7 @@ using Proxytrace.Domain.OptimizationTheory; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Application.Demo.Scenarios; diff --git a/Proxytrace.Application/Demo/Scenarios/OutlierTraceSeedScenario.cs b/Proxytrace.Application/Demo/Scenarios/OutlierTraceSeedScenario.cs index 125f014fb..d4767e41a 100644 --- a/Proxytrace.Application/Demo/Scenarios/OutlierTraceSeedScenario.cs +++ b/Proxytrace.Application/Demo/Scenarios/OutlierTraceSeedScenario.cs @@ -3,12 +3,10 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Inference; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; -using Proxytrace.Domain.Tools; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Application.Demo.Scenarios; diff --git a/Proxytrace.Application/Demo/Scenarios/StatisticsBackfillScenario.cs b/Proxytrace.Application/Demo/Scenarios/StatisticsBackfillScenario.cs index fc32ef1ca..3e0e69a95 100644 --- a/Proxytrace.Application/Demo/Scenarios/StatisticsBackfillScenario.cs +++ b/Proxytrace.Application/Demo/Scenarios/StatisticsBackfillScenario.cs @@ -4,15 +4,13 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Inference; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.TestResult; using Proxytrace.Domain.TestRun; using Proxytrace.Domain.TestRunGroup; -using Proxytrace.Domain.Tools; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Application.Demo.Scenarios; diff --git a/Proxytrace.Application/Demo/Scenarios/TestRunSeedScenario.cs b/Proxytrace.Application/Demo/Scenarios/TestRunSeedScenario.cs index 038e3e9f2..b6ec1435f 100644 --- a/Proxytrace.Application/Demo/Scenarios/TestRunSeedScenario.cs +++ b/Proxytrace.Application/Demo/Scenarios/TestRunSeedScenario.cs @@ -2,13 +2,13 @@ using Proxytrace.Domain; using Proxytrace.Domain.Evaluation; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.TestResult; using Proxytrace.Domain.TestRun; using Proxytrace.Domain.TestRunGroup; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; namespace Proxytrace.Application.Demo.Scenarios; diff --git a/Proxytrace.Application/Demo/Scenarios/TestSuiteSeedScenario.cs b/Proxytrace.Application/Demo/Scenarios/TestSuiteSeedScenario.cs index bd895ade2..7b18ca48b 100644 --- a/Proxytrace.Application/Demo/Scenarios/TestSuiteSeedScenario.cs +++ b/Proxytrace.Application/Demo/Scenarios/TestSuiteSeedScenario.cs @@ -1,7 +1,7 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.TestCase; using Proxytrace.Domain.TestSuite; diff --git a/Proxytrace.Application/Evaluator/Internal/DefaultEvaluatorProvisioner.cs b/Proxytrace.Application/Evaluator/Internal/DefaultEvaluatorProvisioner.cs index 453fbb326..02392ba95 100644 --- a/Proxytrace.Application/Evaluator/Internal/DefaultEvaluatorProvisioner.cs +++ b/Proxytrace.Application/Evaluator/Internal/DefaultEvaluatorProvisioner.cs @@ -1,7 +1,8 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; namespace Proxytrace.Application.Evaluator.Internal; diff --git a/Proxytrace.Application/Ingestion/Internal/AgentCallProcessor.cs b/Proxytrace.Application/Ingestion/Internal/AgentCallProcessor.cs index 706b34d0a..407306a92 100644 --- a/Proxytrace.Application/Ingestion/Internal/AgentCallProcessor.cs +++ b/Proxytrace.Application/Ingestion/Internal/AgentCallProcessor.cs @@ -9,8 +9,9 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; using Proxytrace.Domain.AgentVersion; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Nordstein.Core.Domain.Exceptions; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.Session; using Proxytrace.Licensing; diff --git a/Proxytrace.Application/Ingestion/Internal/AgentVersionMatcher.cs b/Proxytrace.Application/Ingestion/Internal/AgentVersionMatcher.cs index 1eb3b1ac3..25ddb8943 100644 --- a/Proxytrace.Application/Ingestion/Internal/AgentVersionMatcher.cs +++ b/Proxytrace.Application/Ingestion/Internal/AgentVersionMatcher.cs @@ -1,8 +1,9 @@ using Proxytrace.Domain.AgentCall; using Proxytrace.Domain.AgentVersion; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Quickenshtein; namespace Proxytrace.Application.Ingestion.Internal; diff --git a/Proxytrace.Application/Ingestion/Internal/IOpenAiCallParser.cs b/Proxytrace.Application/Ingestion/Internal/IOpenAiCallParser.cs index d943f2e41..206042da8 100644 --- a/Proxytrace.Application/Ingestion/Internal/IOpenAiCallParser.cs +++ b/Proxytrace.Application/Ingestion/Internal/IOpenAiCallParser.cs @@ -1,10 +1,9 @@ using System.Net; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Inference; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Application.Ingestion.Internal; diff --git a/Proxytrace.Application/Ingestion/Internal/OpenAiCallParser.cs b/Proxytrace.Application/Ingestion/Internal/OpenAiCallParser.cs index 9d00c1a6d..1cf4b13a9 100644 --- a/Proxytrace.Application/Ingestion/Internal/OpenAiCallParser.cs +++ b/Proxytrace.Application/Ingestion/Internal/OpenAiCallParser.cs @@ -1,13 +1,11 @@ using System.Net; using System.Text; using System.Text.Json; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Inference; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; -using Proxytrace.Domain.Tools; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Application.Ingestion.Internal; diff --git a/Proxytrace.Application/Optimization/Internal/Adoption/ProposalAdoptionMatcher.cs b/Proxytrace.Application/Optimization/Internal/Adoption/ProposalAdoptionMatcher.cs index c9da98085..e376f585b 100644 --- a/Proxytrace.Application/Optimization/Internal/Adoption/ProposalAdoptionMatcher.cs +++ b/Proxytrace.Application/Optimization/Internal/Adoption/ProposalAdoptionMatcher.cs @@ -1,7 +1,7 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentVersion; using Proxytrace.Domain.OptimizationProposal; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Application.Optimization.Internal.Adoption; diff --git a/Proxytrace.Application/Optimization/Internal/Evidence/IOptimizerEvidenceBuilder.cs b/Proxytrace.Application/Optimization/Internal/Evidence/IOptimizerEvidenceBuilder.cs index 1dc9a61f7..cde9e1de1 100644 --- a/Proxytrace.Application/Optimization/Internal/Evidence/IOptimizerEvidenceBuilder.cs +++ b/Proxytrace.Application/Optimization/Internal/Evidence/IOptimizerEvidenceBuilder.cs @@ -5,4 +5,4 @@ namespace Proxytrace.Application.Optimization.Internal.Evidence; internal interface IOptimizerEvidenceBuilder { OptimizerEvidence Build(ITestRun run); -} \ No newline at end of file +} diff --git a/Proxytrace.Application/Optimization/Internal/Evidence/OptimizerEvidence.cs b/Proxytrace.Application/Optimization/Internal/Evidence/OptimizerEvidence.cs index a076e2a61..d49d6aad7 100644 --- a/Proxytrace.Application/Optimization/Internal/Evidence/OptimizerEvidence.cs +++ b/Proxytrace.Application/Optimization/Internal/Evidence/OptimizerEvidence.cs @@ -1,7 +1,7 @@ using System.Text.Json; using Proxytrace.Domain.Agent; using Proxytrace.Domain.TestResult; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Application.Optimization.Internal.Evidence; @@ -76,4 +76,4 @@ private static object ToResultPayload(ITestResult result) return null; } } -} \ No newline at end of file +} diff --git a/Proxytrace.Application/Optimization/Internal/UpdateSystemPromptOptimizer.cs b/Proxytrace.Application/Optimization/Internal/UpdateSystemPromptOptimizer.cs index 959e23941..7b5aff7f7 100644 --- a/Proxytrace.Application/Optimization/Internal/UpdateSystemPromptOptimizer.cs +++ b/Proxytrace.Application/Optimization/Internal/UpdateSystemPromptOptimizer.cs @@ -4,9 +4,10 @@ using Proxytrace.Domain.Statistics.TestRun; using Proxytrace.Application.TestRun; using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.OptimizationTheory; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestRun; diff --git a/Proxytrace.Application/Optimization/Internal/UpdateToolDefinitionOptimizer.cs b/Proxytrace.Application/Optimization/Internal/UpdateToolDefinitionOptimizer.cs index 19edd212f..eb67c9f0d 100644 --- a/Proxytrace.Application/Optimization/Internal/UpdateToolDefinitionOptimizer.cs +++ b/Proxytrace.Application/Optimization/Internal/UpdateToolDefinitionOptimizer.cs @@ -4,14 +4,15 @@ using Proxytrace.Domain.Statistics.TestRun; using Proxytrace.Application.TestRun; using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.OptimizationTheory; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestRun; using Proxytrace.Domain.TestRunGroup; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Application.Optimization.Internal; diff --git a/Proxytrace.Application/Optimization/Internal/Validation/SystemPromptTheoryValidator.cs b/Proxytrace.Application/Optimization/Internal/Validation/SystemPromptTheoryValidator.cs index 4f378beff..0b19ac08d 100644 --- a/Proxytrace.Application/Optimization/Internal/Validation/SystemPromptTheoryValidator.cs +++ b/Proxytrace.Application/Optimization/Internal/Validation/SystemPromptTheoryValidator.cs @@ -3,6 +3,7 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.OptimizationProposal; using Proxytrace.Domain.OptimizationTheory; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.TestRun; diff --git a/Proxytrace.Application/Playground/Internal/PlaygroundService.cs b/Proxytrace.Application/Playground/Internal/PlaygroundService.cs index 6500b7b38..5d630a3d4 100644 --- a/Proxytrace.Application/Playground/Internal/PlaygroundService.cs +++ b/Proxytrace.Application/Playground/Internal/PlaygroundService.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using System.ComponentModel.DataAnnotations; using System.Runtime.CompilerServices; using System.Text.Json; @@ -6,10 +7,10 @@ using Proxytrace.Application.ErrorLog; using Proxytrace.Domain; using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; -using Proxytrace.Domain.Tools; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Tools; +using Nordstein.Core.AI.Completions; namespace Proxytrace.Application.Playground.Internal; diff --git a/Proxytrace.Application/Search/Internal/LuceneIndexWriter.cs b/Proxytrace.Application/Search/Internal/LuceneIndexWriter.cs index fb68bed8b..2a7655cde 100644 --- a/Proxytrace.Application/Search/Internal/LuceneIndexWriter.cs +++ b/Proxytrace.Application/Search/Internal/LuceneIndexWriter.cs @@ -133,4 +133,4 @@ public AcquiredReader(SearcherManager manager, IndexSearcher searcher) public void Dispose() => manager.Release(searcher); } -} \ No newline at end of file +} diff --git a/Proxytrace.Application/Search/Internal/Mappers/AbstractDocumentMapper.cs b/Proxytrace.Application/Search/Internal/Mappers/AbstractDocumentMapper.cs index 97004772e..6024c4898 100644 --- a/Proxytrace.Application/Search/Internal/Mappers/AbstractDocumentMapper.cs +++ b/Proxytrace.Application/Search/Internal/Mappers/AbstractDocumentMapper.cs @@ -54,4 +54,4 @@ public async IAsyncEnumerable BuildAllForProjectAsync(Guid projectId, } protected abstract Document? GetDocument(TDomainEntity entity); -} \ No newline at end of file +} diff --git a/Proxytrace.Application/Search/Internal/Mappers/TestCaseDocumentMapper.cs b/Proxytrace.Application/Search/Internal/Mappers/TestCaseDocumentMapper.cs index 07c36a1cf..d8822aa4c 100644 --- a/Proxytrace.Application/Search/Internal/Mappers/TestCaseDocumentMapper.cs +++ b/Proxytrace.Application/Search/Internal/Mappers/TestCaseDocumentMapper.cs @@ -3,7 +3,7 @@ using Lucene.Net.Documents; using Microsoft.Extensions.Logging; using Proxytrace.Domain; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.Search; using Proxytrace.Domain.TestCase; using Proxytrace.Domain.TestSuite; diff --git a/Proxytrace.Application/Statistics/TestRun/Internal/TestRunStatsProjector.cs b/Proxytrace.Application/Statistics/TestRun/Internal/TestRunStatsProjector.cs index 9d425036d..79b5eace7 100644 --- a/Proxytrace.Application/Statistics/TestRun/Internal/TestRunStatsProjector.cs +++ b/Proxytrace.Application/Statistics/TestRun/Internal/TestRunStatsProjector.cs @@ -4,7 +4,7 @@ using Proxytrace.Domain; using Proxytrace.Domain.TestResult; using Proxytrace.Domain.TestRun; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; namespace Proxytrace.Application.Statistics.TestRun.Internal; diff --git a/Proxytrace.Application/Streaming/ITraceBroadcaster.cs b/Proxytrace.Application/Streaming/ITraceBroadcaster.cs index 9fcbe7580..254453409 100644 --- a/Proxytrace.Application/Streaming/ITraceBroadcaster.cs +++ b/Proxytrace.Application/Streaming/ITraceBroadcaster.cs @@ -32,4 +32,4 @@ public interface ITraceBroadcaster ChannelReader Subscribe(CancellationToken cancellationToken); void Publish(TraceCreatedEvent evt); -} \ No newline at end of file +} diff --git a/Proxytrace.Application/TestCase/Internal/ConversationTranscript.cs b/Proxytrace.Application/TestCase/Internal/ConversationTranscript.cs index 9993032a2..2576889ee 100644 --- a/Proxytrace.Application/TestCase/Internal/ConversationTranscript.cs +++ b/Proxytrace.Application/TestCase/Internal/ConversationTranscript.cs @@ -1,7 +1,7 @@ using System.Text; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Message; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Messages; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Application.TestCase.Internal; diff --git a/Proxytrace.Application/TestCase/Internal/TestCaseSynthesisService.cs b/Proxytrace.Application/TestCase/Internal/TestCaseSynthesisService.cs index 6a43caf85..99868f4cb 100644 --- a/Proxytrace.Application/TestCase/Internal/TestCaseSynthesisService.cs +++ b/Proxytrace.Application/TestCase/Internal/TestCaseSynthesisService.cs @@ -1,10 +1,13 @@ +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Clients; using System.Text; using System.Text.Json; using JetBrains.Annotations; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.TestSuite; @@ -85,7 +88,7 @@ public async Task SynthesizeAsync( Conversation modelConversation = BuildModelConversation(transcript, destination, priorRounds, instruction); using IModelClient client = synthesizer.CreateClient(); - ModelOptions options = ModelOptions.FromAgent(synthesizer, synthesizer.Endpoint.Model) with + ModelOptions options = ModelOptionsFactory.FromAgent(synthesizer, synthesizer.Endpoint.Model) with { Sampling = new ModelSamplingParameters(ReasoningEffort: ReasoningBudget), }; diff --git a/Proxytrace.Application/TestRun/Internal/TestRunnerService.cs b/Proxytrace.Application/TestRun/Internal/TestRunnerService.cs index caec799ba..94f4637a3 100644 --- a/Proxytrace.Application/TestRun/Internal/TestRunnerService.cs +++ b/Proxytrace.Application/TestRun/Internal/TestRunnerService.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using System.Collections.Concurrent; using System.Diagnostics; using System.Threading.Channels; @@ -9,7 +10,7 @@ using Nordstein.Core.Common.Async; using Proxytrace.Domain; using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Evaluation; using Proxytrace.Domain.Evaluator; using Proxytrace.Domain.ModelEndpoint; diff --git a/Proxytrace.Application/Tracey/Internal/TraceyAgentProvisioner.cs b/Proxytrace.Application/Tracey/Internal/TraceyAgentProvisioner.cs index 6491a5cc8..3dad67c38 100644 --- a/Proxytrace.Application/Tracey/Internal/TraceyAgentProvisioner.cs +++ b/Proxytrace.Application/Tracey/Internal/TraceyAgentProvisioner.cs @@ -1,6 +1,7 @@ using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; namespace Proxytrace.Application.Tracey.Internal; diff --git a/Proxytrace.Client.Sample/Configuration.cs b/Proxytrace.Client.Sample/Configuration.cs index d36ffba6a..e76fc070b 100644 --- a/Proxytrace.Client.Sample/Configuration.cs +++ b/Proxytrace.Client.Sample/Configuration.cs @@ -4,4 +4,4 @@ public record Configuration { public required string Endpoint { get; init; } public required string ApiKey { get; init; } -} \ No newline at end of file +} diff --git a/Proxytrace.Client.Sample/Module.cs b/Proxytrace.Client.Sample/Module.cs index 61c5883f0..76996d391 100644 --- a/Proxytrace.Client.Sample/Module.cs +++ b/Proxytrace.Client.Sample/Module.cs @@ -32,4 +32,4 @@ protected override void Load(ContainerBuilder builder) .As() .SingleInstance(); } -} \ No newline at end of file +} diff --git a/Proxytrace.Domain.Tests/AgentCallSummaryTests.cs b/Proxytrace.Domain.Tests/AgentCallSummaryTests.cs index 2520dbbe2..3ff4001c4 100644 --- a/Proxytrace.Domain.Tests/AgentCallSummaryTests.cs +++ b/Proxytrace.Domain.Tests/AgentCallSummaryTests.cs @@ -2,7 +2,7 @@ using NSubstitute; using Proxytrace.Domain.AgentCall; using Proxytrace.Domain.ModelEndpoint; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; namespace Proxytrace.Domain.Tests; diff --git a/Proxytrace.Domain.Tests/AgentCallValidationTests.cs b/Proxytrace.Domain.Tests/AgentCallValidationTests.cs index df976b95e..5baca31f8 100644 --- a/Proxytrace.Domain.Tests/AgentCallValidationTests.cs +++ b/Proxytrace.Domain.Tests/AgentCallValidationTests.cs @@ -3,10 +3,9 @@ using Microsoft.Extensions.DependencyInjection; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; -using Proxytrace.Domain.Usage; namespace Proxytrace.Domain.Tests; diff --git a/Proxytrace.Domain.Tests/AgentValidationTests.cs b/Proxytrace.Domain.Tests/AgentValidationTests.cs index bd3b43488..148f97bc8 100644 --- a/Proxytrace.Domain.Tests/AgentValidationTests.cs +++ b/Proxytrace.Domain.Tests/AgentValidationTests.cs @@ -1,11 +1,12 @@ using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Domain.Tests; diff --git a/Proxytrace.Domain.Tests/AssistantMessageGetDisplayTextTests.cs b/Proxytrace.Domain.Tests/AssistantMessageGetDisplayTextTests.cs deleted file mode 100644 index f3c778185..000000000 --- a/Proxytrace.Domain.Tests/AssistantMessageGetDisplayTextTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -using AwesomeAssertions; -using Proxytrace.Domain.Message; - -namespace Proxytrace.Domain.Tests; - -/// -/// GetDisplayText feeds the evaluator playground's reference / candidate panes. A tool-call-only -/// assistant turn must render the tool call, not an empty string — otherwise the panes show -/// "—" / blank (issue #234). Unlike it omits the role prefix. -/// -[TestClass] -public sealed class AssistantMessageGetDisplayTextTests -{ - [TestMethod] - public void GetDisplayText_WithToolRequestAndNoText_RendersToolCallNameAndArguments() - { - var message = new AssistantMessage( - [], - [new ToolRequest("call-1", "forecast_trend", """{"metric":"revenue","horizon_days":90}""")]); - - var text = message.GetDisplayText(); - - text.Should().NotBeNullOrWhiteSpace(); - text.Should().Contain("[tool call]"); - text.Should().Contain("forecast_trend"); - text.Should().Contain("revenue"); - text.Should().Contain("horizon_days"); - } - - [TestMethod] - public void GetDisplayText_WithTextAndToolRequest_RendersBoth() - { - var message = new AssistantMessage( - [Content.FromText("Let me forecast that.")], - [new ToolRequest("call-1", "forecast_trend", "{}")]); - - var text = message.GetDisplayText(); - - text.Should().Contain("Let me forecast that."); - text.Should().Contain("[tool call]"); - text.Should().Contain("forecast_trend"); - } - - [TestMethod] - public void GetDisplayText_WithoutToolRequests_ReturnsPlainText() - { - var message = new AssistantMessage([Content.FromText("plain answer")], []); - - message.GetDisplayText().Should().Be("plain answer"); - } - - [TestMethod] - public void GetDisplayText_WithNoContentAndNoToolRequests_ReturnsEmpty() - { - var message = new AssistantMessage([], []); - - message.GetDisplayText().Should().BeEmpty(); - } -} diff --git a/Proxytrace.Domain.Tests/AssistantMessageToStringTests.cs b/Proxytrace.Domain.Tests/AssistantMessageToStringTests.cs deleted file mode 100644 index 984f28758..000000000 --- a/Proxytrace.Domain.Tests/AssistantMessageToStringTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -using AwesomeAssertions; -using Proxytrace.Domain.Message; - -namespace Proxytrace.Domain.Tests; - -/// -/// ToString feeds the agentic evaluator's prompt verbatim. A tool-call-only assistant message -/// must not stringify to an empty body, or the judge reports "the response is empty". -/// -[TestClass] -public sealed class AssistantMessageToStringTests -{ - [TestMethod] - public void ToString_WithToolRequestAndNoText_RendersToolCallNameAndArguments() - { - var message = new AssistantMessage( - [], - [new ToolRequest("call-1", "forecast_trend", """{"metric":"revenue","horizon_days":90}""")]); - - var text = message.ToString(); - - text.Should().NotBeNullOrWhiteSpace(); - text.Should().Contain("forecast_trend"); - text.Should().Contain("revenue"); - text.Should().Contain("horizon_days"); - } - - [TestMethod] - public void ToString_WithTextAndToolRequest_RendersBoth() - { - var message = new AssistantMessage( - [Content.FromText("Let me forecast that.")], - [new ToolRequest("call-1", "forecast_trend", "{}")]); - - var text = message.ToString(); - - text.Should().Contain("Let me forecast that."); - text.Should().Contain("forecast_trend"); - } - - [TestMethod] - public void ToString_WithoutToolRequests_IsUnchanged() - { - var message = new AssistantMessage([Content.FromText("plain answer")], []); - - message.ToString().Should().Be("Assistant: plain answer"); - } - - [TestMethod] - public void ToString_ToolMessage_RendersPayloadWithIdInlineNotOnItsOwnLine() - { - var message = new ToolMessage([Content.FromText("call-1"), Content.FromText("forecast complete")]); - - message.ToString().Should().Be("Tool (id: call-1): forecast complete"); - } -} diff --git a/Proxytrace.Domain.Tests/ContentSerializationTests.cs b/Proxytrace.Domain.Tests/ContentSerializationTests.cs deleted file mode 100644 index 4e935920b..000000000 --- a/Proxytrace.Domain.Tests/ContentSerializationTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -using AwesomeAssertions; -using Microsoft.Extensions.DependencyInjection; -using Nordstein.Core.Common.Serialization; -using Proxytrace.Domain.Message; - -namespace Proxytrace.Domain.Tests; - -[TestClass] -public sealed class ContentSerializationTests : DomainTest -{ - [TestMethod] - public void Serialize_TextContent_RoundTrips() - { - IServiceProvider services = GetServices(); - var serializer = services.GetRequiredService(); - var content = Content.FromText("hello"); - - var restored = serializer.DeserializeRequired(serializer.Serialize(content)); - - restored.Should().Be(content); - } - - [TestMethod] - public void Serialize_ImageContent_RoundTripsMediaType() - { - // Regression: the converter used to drop BinaryData.MediaType on write, so a stored image - // content came back without a media type and failed Content validation on reload. - IServiceProvider services = GetServices(); - var serializer = services.GetRequiredService(); - var content = Content.FromImage(BinaryData.FromBytes([1, 2, 3], "image/png")); - - var restored = serializer.DeserializeRequired(serializer.Serialize(content)); - - restored.Should().Be(content); - restored.Kind.Should().Be(ContentKind.Image); - restored.Data.Should().NotBeNull(); - restored.Data.MediaType.Should().Be("image/png"); - } - - [TestMethod] - public void Equals_SameBytesDifferentMediaType_AreNotEqual() - { - var png = Content.FromImage(BinaryData.FromBytes([1, 2, 3], "image/png")); - var jpeg = Content.FromImage(BinaryData.FromBytes([1, 2, 3], "image/jpeg")); - - png.Should().NotBe(jpeg); - } -} diff --git a/Proxytrace.Domain.Tests/ConversationResolvedToolCallCountTests.cs b/Proxytrace.Domain.Tests/ConversationResolvedToolCallCountTests.cs deleted file mode 100644 index 4a93e5423..000000000 --- a/Proxytrace.Domain.Tests/ConversationResolvedToolCallCountTests.cs +++ /dev/null @@ -1,122 +0,0 @@ -using AwesomeAssertions; -using Proxytrace.Domain.Message; - -namespace Proxytrace.Domain.Tests; - -/// -/// is what tells a decision point apart from a -/// summary: a conversation captured at the last call of an agent's tool loop already holds every -/// tool call the agent made and every result it got, so a test case built on it can only grade the -/// closing message. These tests pin the counting rule that distinction rests on. -/// -[TestClass] -public sealed class ConversationResolvedToolCallCountTests -{ - [TestMethod] - public void ResolvedToolCallCount_PlainConversation_IsZero() - { - var conversation = new Conversation([User("Can I still return this?")]); - - conversation.ResolvedToolCallCount.Should().Be(0); - } - - [TestMethod] - public void ResolvedToolCallCount_UnansweredToolCall_IsZero() - { - // The agent asked for a tool but the result has not come back — the next model call is - // still the tool call itself, not a summary of it. - var conversation = new Conversation( - [ - User("Can I still return order 20114?"), - AssistantCalling("call-1", "lookup_order") - ]); - - conversation.ResolvedToolCallCount.Should().Be(0); - } - - [TestMethod] - public void ResolvedToolCallCount_DecisionPoint_CountsOnlyTheAnsweredCalls() - { - // The shape of a usable regression case: the lookup came back, and the very next thing the - // model produces is the decision. One call resolved, the decision still open. - var conversation = new Conversation( - [ - User("Can I still return order 20114?"), - AssistantCalling("call-1", "lookup_order"), - ToolResult("call-1", """{"delivered_days_ago":45}""") - ]); - - conversation.ResolvedToolCallCount.Should().Be(1); - } - - [TestMethod] - public void ResolvedToolCallCount_CompletedToolLoop_CountsEveryAnsweredCall() - { - // The shape that cannot be corrected: every call the agent made, including the harmful - // one, already succeeded in the input. - var conversation = new Conversation( - [ - User("Maria promised me a refund."), - AssistantCalling("call-1", "lookup_order"), - ToolResult("call-1", """{"delivered_days_ago":45}"""), - AssistantCalling("call-2", "start_return"), - ToolResult("call-2", """{"error":"return_not_available"}"""), - AssistantCalling("call-3", "issue_refund"), - ToolResult("call-3", """{"refund_id":"REF-21108","percent":100}""") - ]); - - conversation.ResolvedToolCallCount.Should().Be(3); - } - - [TestMethod] - public void ResolvedToolCallCount_ToolResultForAnotherCall_IsNotCounted() - { - // Pairing is by tool-call id, not by position — an orphaned result must not make an - // unanswered call look answered. - var conversation = new Conversation( - [ - User("Can I still return order 20114?"), - AssistantCalling("call-1", "lookup_order"), - ToolResult("some-other-call", """{"delivered_days_ago":45}""") - ]); - - conversation.ResolvedToolCallCount.Should().Be(0); - } - - [TestMethod] - public void ResolvedToolCallCount_MalformedToolMessage_DoesNotThrow() - { - // A ToolMessage missing its result slot is invalid, but reading a descriptive count must - // not be the thing that fails — ToolMessage.Id would throw here. - var conversation = new Conversation( - [ - User("Can I still return order 20114?"), - AssistantCalling("call-1", "lookup_order"), - new ToolMessage([Content.FromText("call-1")]) - ]); - - conversation.ResolvedToolCallCount.Should().Be(1); - } - - [TestMethod] - public void ResolvedToolCallCount_EmptyToolMessage_DoesNotThrow() - { - var conversation = new Conversation( - [ - User("Can I still return order 20114?"), - AssistantCalling("call-1", "lookup_order"), - new ToolMessage([]) - ]); - - conversation.ResolvedToolCallCount.Should().Be(0); - } - - private static UserMessage User(string text) - => new([Content.FromText(text)]); - - private static AssistantMessage AssistantCalling(string id, string toolName) - => new([], [new ToolRequest(id, toolName, "{}")]); - - private static ToolMessage ToolResult(string id, string payload) - => new(new ToolResponse(id, [Content.FromText(payload)], success: true, error: null)); -} diff --git a/Proxytrace.Domain.Tests/DomainTest.cs b/Proxytrace.Domain.Tests/DomainTest.cs index e576e0266..b1f9bd34c 100644 --- a/Proxytrace.Domain.Tests/DomainTest.cs +++ b/Proxytrace.Domain.Tests/DomainTest.cs @@ -12,4 +12,4 @@ protected async Task GetOrCreate(IServiceProvider var generator = services.GetRequiredService>(); return await generator.GetOrCreateAsync(CancellationToken); } -} \ No newline at end of file +} diff --git a/Proxytrace.Domain.Tests/EvaluatorBehaviorTests.cs b/Proxytrace.Domain.Tests/EvaluatorBehaviorTests.cs index 78d1cce37..3e2d32188 100644 --- a/Proxytrace.Domain.Tests/EvaluatorBehaviorTests.cs +++ b/Proxytrace.Domain.Tests/EvaluatorBehaviorTests.cs @@ -6,7 +6,7 @@ using Proxytrace.Domain.Evaluation; using Proxytrace.Domain.Evaluator; using Proxytrace.Domain.Evaluator.Internal; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.Project; using Proxytrace.Domain.TestCase; using Proxytrace.Domain.TestResult; diff --git a/Proxytrace.Domain.Tests/MessageGetTextTests.cs b/Proxytrace.Domain.Tests/MessageGetTextTests.cs deleted file mode 100644 index 006f038c7..000000000 --- a/Proxytrace.Domain.Tests/MessageGetTextTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -using AwesomeAssertions; -using Proxytrace.Domain.Message; - -namespace Proxytrace.Domain.Tests; - -[TestClass] -public sealed class MessageGetTextTests -{ - [TestMethod] - public void GetText_UserMessage_ConcatenatesAllTextContents() - { - var message = new UserMessage([Content.FromText("Hello "), Content.FromText("world")]); - - message.GetText().Should().Be("Hello world"); - } - - [TestMethod] - public void GetText_AssistantMessage_ConcatenatesAllTextContents() - { - var message = new AssistantMessage([Content.FromText("part 1 "), Content.FromText("part 2")], []); - - message.GetText().Should().Be("part 1 part 2"); - } - - [TestMethod] - public void GetText_SystemMessage_ConcatenatesAllTextContents() - { - var message = new SystemMessage("system prompt"); - - message.GetText().Should().Be("system prompt"); - } - - [TestMethod] - public void GetText_ToolMessage_ReturnsPayloadAndSkipsLeadingId() - { - var message = new ToolMessage([Content.FromText("call-1"), Content.FromText("payload")]); - - message.GetText().Should().Be("payload"); - } - - [TestMethod] - public void GetText_ToolMessageWithIdOnly_ReturnsEmpty() - { - var message = new ToolMessage([Content.FromText("call-1")]); - - message.GetText().Should().BeEmpty(); - } -} diff --git a/Proxytrace.Domain.Tests/ModelEndpointCostTests.cs b/Proxytrace.Domain.Tests/ModelEndpointCostTests.cs index 820c57277..577f89596 100644 --- a/Proxytrace.Domain.Tests/ModelEndpointCostTests.cs +++ b/Proxytrace.Domain.Tests/ModelEndpointCostTests.cs @@ -3,7 +3,7 @@ using Proxytrace.Domain.Model; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; namespace Proxytrace.Domain.Tests; diff --git a/Proxytrace.Domain.Tests/ModelParametersValidationTests.cs b/Proxytrace.Domain.Tests/ModelParametersValidationTests.cs deleted file mode 100644 index 3fd83caf5..000000000 --- a/Proxytrace.Domain.Tests/ModelParametersValidationTests.cs +++ /dev/null @@ -1,116 +0,0 @@ -using AwesomeAssertions; -using Microsoft.Extensions.DependencyInjection; -using Proxytrace.Domain.Inference; -using Nordstein.Core.Testing; - -namespace Proxytrace.Domain.Tests; - -[TestClass] -public sealed class ModelParametersValidationTests : BaseTest -{ - [TestMethod] - public void Create_WithValidParameters_Succeeds() - { - IServiceProvider services = GetServices(); - var factory = services.GetRequiredService(); - - var parameters = factory( - temperature: 0.7, - topP: 0.9, - frequencyPenalty: 0.5, - presencePenalty: -0.5, - maxTokens: 256, - n: 1); - - parameters.MaxTokens.Should().Be(256); - parameters.N.Should().Be(1); - } - - [TestMethod] - public void Create_WithEmptyParameters_Succeeds() - { - IServiceProvider services = GetServices(); - var factory = services.GetRequiredService(); - - var act = () => factory(); - - act.Should().NotThrow(); - } - - [TestMethod] - public void Create_WithNegativeMaxTokens_Throws() - { - IServiceProvider services = GetServices(); - var factory = services.GetRequiredService(); - - var act = () => factory(maxTokens: -5); - - act.Should().Throw(); - } - - [TestMethod] - public void Create_WithZeroN_Throws() - { - IServiceProvider services = GetServices(); - var factory = services.GetRequiredService(); - - var act = () => factory(n: 0); - - act.Should().Throw(); - } - - [TestMethod] - public void Create_WithNaNTemperature_Throws() - { - IServiceProvider services = GetServices(); - var factory = services.GetRequiredService(); - - var act = () => factory(temperature: double.NaN); - - act.Should().Throw(); - } - - [TestMethod] - public void Create_WithNegativeTemperature_Throws() - { - IServiceProvider services = GetServices(); - var factory = services.GetRequiredService(); - - var act = () => factory(temperature: -0.1); - - act.Should().Throw(); - } - - [TestMethod] - public void Create_WithTopPAboveOne_Throws() - { - IServiceProvider services = GetServices(); - var factory = services.GetRequiredService(); - - var act = () => factory(topP: 1.5); - - act.Should().Throw(); - } - - [TestMethod] - public void Create_WithInfinitePresencePenalty_Throws() - { - IServiceProvider services = GetServices(); - var factory = services.GetRequiredService(); - - var act = () => factory(presencePenalty: double.PositiveInfinity); - - act.Should().Throw(); - } - - [TestMethod] - public void Create_WithFrequencyPenaltyOutOfRange_Throws() - { - IServiceProvider services = GetServices(); - var factory = services.GetRequiredService(); - - var act = () => factory(frequencyPenalty: 3.0); - - act.Should().Throw(); - } -} diff --git a/Proxytrace.Domain.Tests/Module.cs b/Proxytrace.Domain.Tests/Module.cs index 6c32ab24a..04c480ad3 100644 --- a/Proxytrace.Domain.Tests/Module.cs +++ b/Proxytrace.Domain.Tests/Module.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using Autofac; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; diff --git a/Proxytrace.Domain.Tests/OptimizationContentHashStabilityTests.cs b/Proxytrace.Domain.Tests/OptimizationContentHashStabilityTests.cs index 77320fb43..e8d0e7b14 100644 --- a/Proxytrace.Domain.Tests/OptimizationContentHashStabilityTests.cs +++ b/Proxytrace.Domain.Tests/OptimizationContentHashStabilityTests.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Nordstein.Core.Common.Serialization; using Proxytrace.Domain.Proposal; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Nordstein.Core.Testing; namespace Proxytrace.Domain.Tests; diff --git a/Proxytrace.Domain.Tests/PromptTemplateTests.cs b/Proxytrace.Domain.Tests/PromptTemplateTests.cs deleted file mode 100644 index 0600dfbb8..000000000 --- a/Proxytrace.Domain.Tests/PromptTemplateTests.cs +++ /dev/null @@ -1,548 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using AwesomeAssertions; -using Microsoft.Testing.Platform.Services; -using Nordstein.Core.Common.Validation; -using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Prompt.Internal; - -namespace Proxytrace.Domain.Tests; - -[TestClass] -public class PromptTemplateTests : DomainTest -{ - [TestMethod] - public void Constructor_WithValidTemplate_ShouldCreateInstance() - { - // Arrange - var services = GetServices(); - var factory = services.GetRequiredService(); - const string template = "Hello {{name}}!"; - - // Act - var promptTemplate = factory("test", template); - - // Assert - promptTemplate.Template.Should().Be(template); - promptTemplate.Variables.Should().ContainSingle().Which.Should().Be("name"); - } - - [TestMethod] - public void Variables_WithNoVariables_ShouldReturnEmptySet() - { - // Arrange - const string template = "Hello world!"; - - // Act - var promptTemplate = new PromptTemplate("test", template); - - // Assert - promptTemplate.Variables.Should().BeEmpty(); - } - - [TestMethod] - public void Variables_WithOldTemplateVariable_ShouldReturnEmptySet() - { - // Arrange - const string template = "Hello {name}!"; - - // Act - var promptTemplate = new PromptTemplate("test", template); - - // Assert - promptTemplate.Variables.Should().BeEmpty(); - } - - [TestMethod] - public void Variables_WithSingleVariable_ShouldExtractVariable() - { - // Arrange - const string template = "Hello {{name}}!"; - - // Act - var promptTemplate = new PromptTemplate("test", template); - - // Assert - promptTemplate.Variables.Should().ContainSingle().Which.Should().Be("name"); - } - - [TestMethod] - public void Variables_WithMultipleVariables_ShouldExtractAllVariables() - { - // Arrange - const string template = "Hello {{firstName}} {{lastName}}! You are {{age}} years old."; - - // Act - var promptTemplate = new PromptTemplate("test", template); - - // Assert - promptTemplate.Variables.Should().HaveCount(3); - promptTemplate.Variables.Should().Contain("firstName"); - promptTemplate.Variables.Should().Contain("lastName"); - promptTemplate.Variables.Should().Contain("age"); - } - - [TestMethod] - public void Variables_WithDuplicateVariables_ShouldReturnUniqueVariables() - { - // Arrange - const string template = "Hello {{name}}! Nice to meet you, {{name}}."; - - // Act - var promptTemplate = new PromptTemplate("test", template); - - // Assert - promptTemplate.Variables.Should().ContainSingle().Which.Should().Be("name"); - } - - [TestMethod] - public void Variables_WithValidVariableNames_ShouldExtractAllValidVariables() - { - // Arrange - const string template = - "Test {{var1}} {{var_2}} {{var-3}} {{VarCamelCase}} {{VAR_UPPER}} {{var123}} {{_underscore}} {{-hyphen}}"; - - // Act - var promptTemplate = new PromptTemplate("test", template); - - // Assert - promptTemplate.Variables.Should().HaveCount(8); - promptTemplate.Variables.Should().Contain("var1"); - promptTemplate.Variables.Should().Contain("var_2"); - promptTemplate.Variables.Should().Contain("var-3"); - promptTemplate.Variables.Should().Contain("VarCamelCase"); - promptTemplate.Variables.Should().Contain("VAR_UPPER"); - promptTemplate.Variables.Should().Contain("var123"); - promptTemplate.Variables.Should().Contain("_underscore"); - promptTemplate.Variables.Should().Contain("-hyphen"); - } - - [TestMethod] - public void Variables_WithInvalidVariableNames_ShouldIgnoreInvalidVariables() - { - // Arrange - const string template = - "Valid: {{validVar}} Invalid: {{var with space}} {{var@symbol}} {{var.dot}} {{var!exclamation}} {{}}"; - - // Act - var promptTemplate = new PromptTemplate("test", template); - - // Assert - promptTemplate.Variables.Should().ContainSingle().Which.Should().Be("validVar"); - } - - [TestMethod] - [DataRow("""{{"value"}}""")] - [DataRow("""{{"key":"value"}}""")] - [DataRow(""" - {{ - "key":"value" - }} - """)] - public void Variables_WithJsonString_ShouldNotContainVariables(string template) - { - // Act - var promptTemplate = new PromptTemplate("test", template); - - // Assert - promptTemplate.Variables.Should().BeEmpty(); - } - - [TestMethod] - public void Variables_WithWhitespaceInBraces_ShouldIgnoreWhitespaceVariables() - { - // Arrange - const string template = "Valid: {{name}} Invalid: {{ name }} {{name }} {{ name}} {{first name}} {{last name}}"; - - // Act - var promptTemplate = new PromptTemplate("test", template); - - // Assert - promptTemplate.Variables.Should().ContainSingle().Which.Should().Be("name"); - } - - [TestMethod] - public void Variables_WithSpecialCharactersInBraces_ShouldIgnoreInvalidVariables() - { - // Arrange - const string template = "Test {{valid}} {{in@valid}} {{in.valid}} {{in!valid}} {{in valid}} {{in,valid}} {{in;valid}}"; - - // Act - var promptTemplate = new PromptTemplate("test", template); - - // Assert - promptTemplate.Variables.Should().ContainSingle().Which.Should().Be("valid"); - } - - [TestMethod] - public void Variables_WithNestedBraces_ShouldHandleCorrectly() - { - // Arrange - const string template = "Test {{outer{{inner}}}}"; - - // Act - var promptTemplate = new PromptTemplate("test", template); - - // Assert - promptTemplate.Variables.Should().ContainSingle().Which.Should().Be("inner"); - } - - [TestMethod] - public void Variables_WithEmptyBraces_ShouldIgnoreEmptyBraces() - { - // Arrange - const string template = "Test {{}} {{valid}} {{ }}"; - - // Act - var promptTemplate = new PromptTemplate("test", template); - - // Assert - promptTemplate.Variables.Should().ContainSingle().Which.Should().Be("valid"); - } - - [TestMethod] - public void Render_WithNoVariables_ShouldReturnOriginalTemplate() - { - // Arrange - const string template = "Hello world!"; - var promptTemplate = new PromptTemplate("test", template); - - // Act - var result = promptTemplate.Render(new Dictionary()); - - // Assert - result.Should().NotBeNull(); - result.ToPromptString().Should().Be(template); - result.Name.Should().Be(promptTemplate.Name); - } - - [TestMethod] - public void Render_WithSingleVariable_ShouldReplaceVariable() - { - // Arrange - const string template = "Hello {{name}}!"; - var promptTemplate = new PromptTemplate("test", template); - var values = new Dictionary { ["name"] = "John" }; - - // Act - var result = promptTemplate.Render(values); - - // Assert - result.ToPromptString().Should().Be("Hello John!"); - } - - [TestMethod] - public void Render_WithMultipleVariables_ShouldReplaceAllVariables() - { - // Arrange - const string template = "Hello {{firstName}} {{lastName}}! You are {{age}} years old."; - var promptTemplate = new PromptTemplate("test", template); - var values = new Dictionary - { - ["firstName"] = "John", - ["lastName"] = "Doe", - ["age"] = "30" - }; - - // Act - var result = promptTemplate.Render(values); - - // Assert - result.ToPromptString().Should().Be("Hello John Doe! You are 30 years old."); - } - - [TestMethod] - public void Render_WithDuplicateVariables_ShouldReplaceAllOccurrences() - { - // Arrange - const string template = "Hello {{name}}! Nice to meet you, {{name}}."; - var promptTemplate = new PromptTemplate("test", template); - var values = new Dictionary { ["name"] = "Alice" }; - - // Act - var result = promptTemplate.Render(values); - - // Assert - result.ToPromptString().Should().Be("Hello Alice! Nice to meet you, Alice."); - } - - [TestMethod] - public void Render_WithExtraValues_ShouldSucceed() - { - // Arrange - const string template = "Hello {{name}}!"; - var promptTemplate = new PromptTemplate("test", template); - var values = new Dictionary - { - ["name"] = "John", - ["extra"] = "value" - }; - - // Act - var result = promptTemplate.Render(values); - - // Assert - result.ToPromptString().Should().Be("Hello John!"); - } - - [TestMethod] - public void Render_WithMissingVariable_ShouldThrowArgumentException() - { - // Arrange - const string template = "Hello {{name}}!"; - var promptTemplate = new PromptTemplate("test", template); - - // Act & Assert - FluentActions.Invoking(() => promptTemplate.Render(new Dictionary())) - .Should() - .Throw() - .WithMessage("*Value for variable 'name' was not provided.*"); - } - - [TestMethod] - public void Render_WithPartiallyMissingVariables_ShouldThrowArgumentException() - { - // Arrange - const string template = "Hello {{firstName}} {{lastName}}!"; - var promptTemplate = new PromptTemplate("test", template); - var values = new Dictionary { ["firstName"] = "John" }; - - // Act & Assert - FluentActions.Invoking(() => promptTemplate.Render(values)) - .Should() - .Throw() - .WithMessage("*Value for variable 'lastName' was not provided*"); - } - - [TestMethod] - public void Render_WithEmptyStringValue_ShouldReplaceWithEmptyString() - { - // Arrange - const string template = "Hello {{name}}!"; - var promptTemplate = new PromptTemplate("test", template); - var values = new Dictionary { ["name"] = "" }; - - // Act - var result = promptTemplate.Render(values); - - // Assert - result.ToPromptString().Should().Be("Hello !"); - } - - [TestMethod] - public void Render_WithSpecialCharactersInValue_ShouldHandleCorrectly() - { - // Arrange - const string template = "Test {{value}}"; - var promptTemplate = new PromptTemplate("test", template); - var values = new Dictionary { ["value"] = "special@#$%^&*()chars" }; - - // Act - var result = promptTemplate.Render(values); - - // Assert - result.ToPromptString().Should().Be("Test special@#$%^&*()chars"); - } - - [TestMethod] - public void Render_WithBracesInValue_ShouldHandleCorrectly() - { - // Arrange - const string template = "Test {{value}}"; - var promptTemplate = new PromptTemplate("test", template); - var values = new Dictionary { ["value"] = "{braces} in value" }; - - // Act - var result = promptTemplate.Render(values); - - // Assert - result.ToPromptString().Should().Be("Test {braces} in value"); - } - - [TestMethod] - public void PromptTemplate_WithComplexTemplate_ShouldWorkCorrectly() - { - // Arrange - const string template = """ - You are a {{role}} assistant. - User: {{userInput}} - Context: {{context}} - Please provide a {{responseType}} response. - Remember to be {{tone}} and {{helpful}}. - """; - var promptTemplate = new PromptTemplate("test", template); - var values = new Dictionary - { - ["role"] = "helpful AI", - ["userInput"] = "What is the weather?", - ["context"] = "Weather inquiry", - ["responseType"] = "detailed", - ["tone"] = "friendly", - ["helpful"] = "informative" - }; - - // Act - var result = promptTemplate.Render(values); - - // Assert - var actual = result.ToPromptString(); - var expected = """ - You are a helpful AI assistant. - User: What is the weather? - Context: Weather inquiry - Please provide a detailed response. - Remember to be friendly and informative. - """; - actual.Should().Be(expected); - promptTemplate.Variables.Should().HaveCount(6); - } - - [TestMethod] - public void PromptTemplate_WithUnicodeCharacters_ShouldHandleCorrectly() - { - // Arrange - const string template = "Hello {{name}}! 你好 {{greeting}} 🎉"; - var promptTemplate = new PromptTemplate("test", template); - var values = new Dictionary - { - ["name"] = "世界", - ["greeting"] = "مرحبا" - }; - - // Act - var result = promptTemplate.Render(values); - - // Assert - result.ToPromptString().Should().Be("Hello 世界! 你好 مرحبا 🎉"); - } - - [TestMethod] - public void PromptTemplate_WithLongTemplate_ShouldHandleCorrectly() - { - // Arrange - var template = string.Join(" ", Enumerable.Repeat("Word {{var}}", 1000)); - var promptTemplate = new PromptTemplate("test", template); - var values = new Dictionary { ["var"] = "test" }; - - // Act - var result = promptTemplate.Render(values); - - // Assert - result.ToPromptString().Should().Contain("Word test"); - promptTemplate.Variables.Should().ContainSingle().Which.Should().Be("var"); - } - - [TestMethod] - public void PromptTemplate_WithVariableAtTemplateStart_ShouldWorkCorrectly() - { - // Arrange - const string template = "{{greeting}} world!"; - var promptTemplate = new PromptTemplate("test", template); - var values = new Dictionary { ["greeting"] = "Hello" }; - - // Act - var result = promptTemplate.Render(values); - - // Assert - result.ToPromptString().Should().Be("Hello world!"); - } - - [TestMethod] - public void PromptTemplate_WithVariableAtTemplateEnd_ShouldWorkCorrectly() - { - // Arrange - const string template = "Hello {{name}}"; - var promptTemplate = new PromptTemplate("test", template); - var values = new Dictionary { ["name"] = "world!" }; - - // Act - var result = promptTemplate.Render(values); - - // Assert - result.ToPromptString().Should().Be("Hello world!"); - } - - [TestMethod] - public void PromptTemplate_WithOnlyVariable_ShouldWorkCorrectly() - { - // Arrange - const string template = "{{content}}"; - var promptTemplate = new PromptTemplate("test", template); - var values = new Dictionary { ["content"] = "Complete content" }; - - // Act - var result = promptTemplate.Render(values); - - // Assert - result.ToPromptString().Should().Be("Complete content"); - } - - [TestMethod] - public void Variables_WithNumbersOnly_ShouldThrow() - { - // Arrange - const string template = "Test {{123}} {{valid}}"; - - // Act - var promptTemplate = new PromptTemplate("test", template); - - // Assert - // validation should fail — a purely numeric variable name has no letters - FluentActions.Invoking(() => promptTemplate.Validate()) - .Should() - .Throw() - .WithMessage("*must be greater than 0*"); - } - - [TestMethod] - public void Variables_WithSingleLetter_ShouldBeValid() - { - // Arrange — a one-letter variable name is legal and must not be rejected. - const string template = "Value: {{x}}"; - var promptTemplate = new PromptTemplate("test", template); - - // Act - var rendered = promptTemplate.Render(new Dictionary { ["x"] = "42" }); - - // Assert - rendered.ToPromptString().Should().Be("Value: 42"); - } - - [TestMethod] - public void Variables_WithUnderscoreAndHyphenCombinations_ShouldBeValid() - { - // Arrange - const string template = "Test {{var_-name}} {{-_var}} {{__var__}} {{--var--}}"; - - // Act - var promptTemplate = new PromptTemplate("test", template); - - // Assert - promptTemplate.Variables.Should().HaveCount(4); - promptTemplate.Variables.Should().Contain("var_-name"); - promptTemplate.Variables.Should().Contain("-_var"); - promptTemplate.Variables.Should().Contain("__var__"); - promptTemplate.Variables.Should().Contain("--var--"); - } - - [TestMethod] - public void Equals_SeparatelyConstructedIdenticalTemplates_AreEqual() - { - // Regression: the synthesized record equality compared the Variables HashSet by - // reference, so two identically-constructed templates were never equal — which silently - // disabled the "has the prompt actually changed?" dedup in Agent.ChangeSystemMessage. - var a = new PromptTemplate("test", "Hello {{name}}!"); - var b = new PromptTemplate("test", "Hello {{name}}!"); - - a.Should().Be(b); - a.GetHashCode().Should().Be(b.GetHashCode()); - } - - [TestMethod] - public void Equals_DifferentTemplateText_AreNotEqual() - { - var a = new PromptTemplate("test", "Hello {{name}}!"); - var b = new PromptTemplate("test", "Goodbye {{name}}!"); - - a.Should().NotBe(b); - } -} \ No newline at end of file diff --git a/Proxytrace.Domain.Tests/PromptTests.cs b/Proxytrace.Domain.Tests/PromptTests.cs deleted file mode 100644 index 79119a6c0..000000000 --- a/Proxytrace.Domain.Tests/PromptTests.cs +++ /dev/null @@ -1,221 +0,0 @@ -using AwesomeAssertions; -using Microsoft.Extensions.DependencyInjection; -using Proxytrace.Domain.Prompt; -using Nordstein.Core.Testing; - -// ReSharper disable NullableWarningSuppressionIsUsed - -namespace Proxytrace.Domain.Tests; - -[TestClass] -public class PromptTests : BaseTest -{ - private IPrompt CreatePrompt(string name, string content, IServiceProvider services) - { - var factory = services.GetRequiredService(); - var template = factory(name, content); - return template.Render(); - } - - [TestMethod] - public void Append_ShouldCombineNamesWithUnderscore() - { - // Arrange - var services = GetServices(); - var prompt1 = CreatePrompt("FirstPrompt", "First content", services); - var prompt2 = CreatePrompt("SecondPrompt", "Second content", services); - - // Act - IPrompt result = prompt1.Append(prompt2); - - // Assert - result.Name.Should().Be("FirstPrompt_SecondPrompt"); - } - - [TestMethod] - public void Append_ShouldCombinePromptStringsWithNewLine() - { - // Arrange - var services = GetServices(); - var prompt1 = CreatePrompt("Prompt1", "First content", services); - var prompt2 = CreatePrompt("Prompt2", "Second content", services); - - // Act - IPrompt result = prompt1.Append(prompt2); - - // Assert - string expected = string.Join(Environment.NewLine, "First content", "Second content"); - result.ToPromptString().Should().Be(expected); - } - - [TestMethod] - public void Append_ShouldReturnNewInstance() - { - // Arrange - var services = GetServices(); - var prompt1 = CreatePrompt("Prompt1", "Content1", services); - var prompt2 = CreatePrompt("Prompt2", "Content2", services); - - // Act - IPrompt result = prompt1.Append(prompt2); - - // Assert - result.Should().NotBeSameAs(prompt1); - result.Should().NotBeSameAs(prompt2); - } - - [TestMethod] - public void Append_ShouldNotModifyOriginalPrompts() - { - // Arrange - var services = GetServices(); - var prompt1 = CreatePrompt("Original1", "Content1", services); - var prompt2 = CreatePrompt("Original2", "Content2", services); - - // Act - _ = prompt1.Append(prompt2); - - // Assert - prompt1.Name.Should().Be("Original1"); - prompt1.ToPromptString().Should().Be("Content1"); - prompt2.Name.Should().Be("Original2"); - prompt2.ToPromptString().Should().Be("Content2"); - } - - [TestMethod] - public void Append_WithMultipleAppends_ShouldChainCorrectly() - { - // Arrange - var services = GetServices(); - var prompt1 = CreatePrompt("First", "Content1", services); - var prompt2 = CreatePrompt("Second", "Content2", services); - var prompt3 = CreatePrompt("Third", "Content3", services); - - // Act - IPrompt result = prompt1.Append(prompt2).Append(prompt3); - - // Assert - result.Name.Should().Be("First_Second_Third"); - string expectedContent = string.Join(Environment.NewLine, - "Content1", - "Content2", - "Content3"); - result.ToPromptString().Should().Be(expectedContent); - } - - [TestMethod] - public void Append_WithMultiLineContent_ShouldPreserveFormatting() - { - // Arrange - var services = GetServices(); - var prompt1 = CreatePrompt("Prompt1", "Line1\nLine2", services); - var prompt2 = CreatePrompt("Prompt2", "Line3\nLine4", services); - - // Act - IPrompt result = prompt1.Append(prompt2); - - // Assert - result.ToPromptString().Should().Contain("Line1\nLine2"); - result.ToPromptString().Should().Contain("Line3\nLine4"); - } - - [TestMethod] - public void Constructor_WithValidParameters_ShouldCreatePrompt() - { - // Arrange & Act - var services = GetServices(); - var prompt = CreatePrompt("TestName", "Test content", services); - - // Assert - prompt.Should().NotBeNull(); - prompt.Name.Should().Be("TestName"); - prompt.ToPromptString().Should().Be("Test content"); - } - - [TestMethod] - public void Constructor_WithNullName_ShouldThrowArgumentException() - { - // Act & Assert - var services = GetServices(); - var factory = services.GetRequiredService(); - FluentActions.Invoking(() => factory.DynamicInvoke(null, "Content")) - .Should() - .Throw(); - } - - [TestMethod] - public void Constructor_WithEmptyName_ShouldThrowArgumentException() - { - // Act & Assert - var services = GetServices(); - FluentActions.Invoking(() => CreatePrompt(string.Empty, "Content", services)) - .Should() - .Throw(); - } - - [TestMethod] - public void Constructor_WithWhitespaceName_ShouldThrowArgumentException() - { - // Act & Assert - var services = GetServices(); - FluentActions.Invoking(() => CreatePrompt(" ", "Content", services)) - .Should() - .Throw(); - } - - [TestMethod] - public void Constructor_WithNullPromptString_ShouldThrowArgumentException() - { - // Act & Assert - var services = GetServices(); - var factory = services.GetRequiredService(); - FluentActions.Invoking(() => factory.DynamicInvoke("Name", null)) - .Should() - .Throw(); - } - - [TestMethod] - public void Constructor_WithEmptyPromptString_ShouldThrowArgumentException() - { - // Act & Assert - var services = GetServices(); - FluentActions.Invoking(() => CreatePrompt("Name", string.Empty, services)) - .Should() - .Throw(); - } - - [TestMethod] - public void Constructor_WithWhitespaceName_ThrowsArgumentExceptionNotArgumentNull() - { - // The ctor guards non-null whitespace with ArgumentException (ArgumentNullException — - // which derives from ArgumentException — is the wrong type for a present-but-blank value). - var act = () => new Proxytrace.Domain.Prompt.Internal.Prompt(" ", "content"); - - act.Should().Throw() - .Which.Should().NotBeOfType(); - } - - [TestMethod] - public void Constructor_WithWhitespacePromptString_ThrowsArgumentExceptionNotArgumentNull() - { - var act = () => new Proxytrace.Domain.Prompt.Internal.Prompt("name", " "); - - act.Should().Throw() - .Which.Should().NotBeOfType(); - } - - [TestMethod] - public void ToPromptString_ShouldReturnOriginalContent() - { - // Arrange - var services = GetServices(); - const string content = "This is the prompt content"; - var prompt = CreatePrompt("TestPrompt", content, services); - - // Act - string result = prompt.ToPromptString(); - - // Assert - result.Should().Be(content); - } -} \ No newline at end of file diff --git a/Proxytrace.Domain.Tests/ProposalHasherTests.cs b/Proxytrace.Domain.Tests/ProposalHasherTests.cs index d96685530..554f89d66 100644 --- a/Proxytrace.Domain.Tests/ProposalHasherTests.cs +++ b/Proxytrace.Domain.Tests/ProposalHasherTests.cs @@ -6,7 +6,7 @@ using Proxytrace.Domain.OptimizationProposal; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestRun; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Nordstein.Core.Testing; namespace Proxytrace.Domain.Tests; diff --git a/Proxytrace.Domain.Tests/ResourcesPromptRepositoryTests.cs b/Proxytrace.Domain.Tests/ResourcesPromptRepositoryTests.cs index 3ca79782d..b776ffa09 100644 --- a/Proxytrace.Domain.Tests/ResourcesPromptRepositoryTests.cs +++ b/Proxytrace.Domain.Tests/ResourcesPromptRepositoryTests.cs @@ -1,6 +1,7 @@ using System.Resources; using Autofac; using Microsoft.Testing.Platform.Services; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Nordstein.Core.Testing; @@ -43,4 +44,4 @@ public async Task GetAsync_TemplateDoesNotExist_ReturnsNull() // Assert Assert.IsNull(template); } -} \ No newline at end of file +} diff --git a/Proxytrace.Domain.Tests/SerializationProbeTests.cs b/Proxytrace.Domain.Tests/SerializationProbeTests.cs index 9c9c1adc2..8d40a722a 100644 --- a/Proxytrace.Domain.Tests/SerializationProbeTests.cs +++ b/Proxytrace.Domain.Tests/SerializationProbeTests.cs @@ -1,7 +1,7 @@ using Autofac; using Nordstein.Core.Common.Serialization; -using Proxytrace.Domain.Message; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Messages; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Domain.Tests; diff --git a/Proxytrace.Domain.Tests/TestCaseGetSummaryTests.cs b/Proxytrace.Domain.Tests/TestCaseGetSummaryTests.cs index e24175a07..75614f90b 100644 --- a/Proxytrace.Domain.Tests/TestCaseGetSummaryTests.cs +++ b/Proxytrace.Domain.Tests/TestCaseGetSummaryTests.cs @@ -1,6 +1,6 @@ using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.TestCase; using Nordstein.Core.Testing; @@ -62,7 +62,7 @@ public void GetSummary_CustomMaxLength_IsRespected() testCase.GetSummary(maxLength: 10).Should().Be(new string('y', 10) + "…"); } - private ITestCase BuildTestCase(params Message.Message[] messages) + private ITestCase BuildTestCase(params Message[] messages) { var services = GetServices(); var factory = services.GetRequiredService(); diff --git a/Proxytrace.Domain.Tests/TestCaseValidationTests.cs b/Proxytrace.Domain.Tests/TestCaseValidationTests.cs index f868d8486..db009c591 100644 --- a/Proxytrace.Domain.Tests/TestCaseValidationTests.cs +++ b/Proxytrace.Domain.Tests/TestCaseValidationTests.cs @@ -1,7 +1,7 @@ using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.TestCase; using Nordstein.Core.Testing; diff --git a/Proxytrace.Domain.Tests/TestResultValidationTests.cs b/Proxytrace.Domain.Tests/TestResultValidationTests.cs index 76e5a6c1b..85f2ea2f1 100644 --- a/Proxytrace.Domain.Tests/TestResultValidationTests.cs +++ b/Proxytrace.Domain.Tests/TestResultValidationTests.cs @@ -1,8 +1,8 @@ using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Evaluation; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.TestCase; using Proxytrace.Domain.TestResult; using Nordstein.Core.Testing; diff --git a/Proxytrace.Domain.Tests/TokenUsageTests.cs b/Proxytrace.Domain.Tests/TokenUsageTests.cs deleted file mode 100644 index f871532e6..000000000 --- a/Proxytrace.Domain.Tests/TokenUsageTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -using AwesomeAssertions; -using Proxytrace.Domain.Usage; - -namespace Proxytrace.Domain.Tests; - -[TestClass] -public sealed class TokenUsageTests -{ - [TestMethod] - public void Minus_SubtractsPerTokenKind() - { - var a = new TokenUsage(100, 50, 20); - var b = new TokenUsage(40, 10, 5); - - var result = a - b; - - result.Should().Be(new TokenUsage(60, 40, 15)); - } - - [TestMethod] - public void Minus_LargerSubtrahend_ClampsAtZero() - { - var a = new TokenUsage(10, 10, 10); - var b = new TokenUsage(20, 5, 30); - - var result = a - b; - - result.Should().Be(new TokenUsage(0, 5, 0)); - } - - [TestMethod] - public void Minus_WithNullOperand_ReturnsNull() - { - // Regression: `null - b` used to return b itself as the "difference". - var usage = new TokenUsage(100, 50, 20); - - (null - usage).Should().BeNull(); - (usage - null).Should().BeNull(); - ((TokenUsage?)null - null).Should().BeNull(); - } - - [TestMethod] - public void Plus_WithNullOperand_ReturnsOther() - { - var usage = new TokenUsage(100, 50, 20); - - (null + usage).Should().Be(usage); - (usage + null).Should().Be(usage); - } -} diff --git a/Proxytrace.Domain.Tests/ToolMessageTests.cs b/Proxytrace.Domain.Tests/ToolMessageTests.cs deleted file mode 100644 index 1d2e8271d..000000000 --- a/Proxytrace.Domain.Tests/ToolMessageTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using AwesomeAssertions; -using Proxytrace.Domain.Message; - -namespace Proxytrace.Domain.Tests; - -[TestClass] -public sealed class ToolMessageTests -{ - [TestMethod] - public void Validate_MultiResultToolMessage_HasNoFailures() - { - // The ToolMessage(ToolResponse) factory prepends the id slot to every result, so a - // two-result response produces three content slots. This must still validate. - var message = new ToolMessage(BuildMultiResultResponse()); - - message.Contents.Should().HaveCount(3); // id slot + two result slots - - var failures = message - .Validate(new ValidationContext(message)) - .Where(result => result != ValidationResult.Success) - .ToList(); - - failures.Should().BeEmpty(); - } - - [TestMethod] - public void GetText_MultiResultToolMessage_ConcatenatesAllResults() - { - var message = new ToolMessage(BuildMultiResultResponse()); - - message.GetText().Should().Be("first resultsecond result"); - } - - [TestMethod] - public void GetText_MultiResultToolMessage_MatchesDeconstruct() - { - var message = new ToolMessage(BuildMultiResultResponse()); - - var (id, contents) = message.Deconstruct(); - var fromDeconstruct = string.Concat(contents.Select(content => content.Text ?? "")); - - id.Should().Be("call-1"); - contents.Should().HaveCount(2); - message.GetText().Should().Be(fromDeconstruct); - } - - private static ToolResponse BuildMultiResultResponse() - => new( - id: "call-1", - results: [Content.FromText("first result"), Content.FromText("second result")], - success: true, - error: null); -} diff --git a/Proxytrace.Domain.Tests/ToolRequestMatchTests.cs b/Proxytrace.Domain.Tests/ToolRequestMatchTests.cs deleted file mode 100644 index 7f34ff25a..000000000 --- a/Proxytrace.Domain.Tests/ToolRequestMatchTests.cs +++ /dev/null @@ -1,116 +0,0 @@ -using AwesomeAssertions; -using Proxytrace.Domain.Message; - -namespace Proxytrace.Domain.Tests; - -[TestClass] -public sealed class ToolRequestMatchTests -{ - [TestMethod] - public void Matches_IgnoresTheToolCallId() - { - // Expected-output ids are minted locally (TestSuiteDtoMapper uses Guid.NewGuid()); - // the actual id comes from the provider. Comparing them could never match. - var expected = new ToolRequest("local-1", "get_order", """{"order_id":"91"}"""); - var actual = new ToolRequest("call_abc123", "get_order", """{"order_id":"91"}"""); - - ToolRequestMatch.Matches(expected, actual).Should().BeTrue(); - } - - [TestMethod] - public void Matches_IgnoresKeyOrderAndWhitespace() - { - var expected = new ToolRequest("a", "issue_refund", """{"order_id":"91","amount":40}"""); - var actual = new ToolRequest("b", "issue_refund", "{\n \"amount\": 40,\n \"order_id\": \"91\"\n}"); - - ToolRequestMatch.Matches(expected, actual).Should().BeTrue(); - } - - [TestMethod] - public void Matches_ComparesNumbersByValue() - { - var expected = new ToolRequest("a", "issue_refund", """{"amount":40}"""); - var actual = new ToolRequest("b", "issue_refund", """{"amount":40.0}"""); - - ToolRequestMatch.Matches(expected, actual).Should().BeTrue(); - } - - [TestMethod] - public void Matches_WithUnparseableArguments_FallsBackToTrimmedStringEquality() - { - var expected = new ToolRequest("a", "note", " not json "); - var actual = new ToolRequest("b", "note", "not json"); - - ToolRequestMatch.Matches(expected, actual).Should().BeTrue(); - } - - [TestMethod] - public void Matches_WithDifferentArgumentValue_IsFalse() - { - var expected = new ToolRequest("a", "issue_refund", """{"order_id":"91"}"""); - var actual = new ToolRequest("b", "issue_refund", """{"order_id":"92"}"""); - - ToolRequestMatch.Matches(expected, actual).Should().BeFalse(); - } - - [TestMethod] - public void Matches_WithDifferentToolName_IsFalse() - { - var expected = new ToolRequest("a", "get_order", "{}"); - var actual = new ToolRequest("b", "delete_order", "{}"); - - ToolRequestMatch.Matches(expected, actual).Should().BeFalse(); - } - - [TestMethod] - public void Differences_WithSameCallsInAnotherOrder_IsEmpty() - { - // Parallel tool calls carry no meaningful order. - ToolRequest[] expected = - [ - new("a", "get_order", """{"id":1}"""), - new("b", "get_customer", """{"id":7}"""), - ]; - ToolRequest[] actual = - [ - new("x", "get_customer", """{"id":7}"""), - new("y", "get_order", """{"id":1}"""), - ]; - - ToolRequestMatch.Differences(expected, actual).Should().BeEmpty(); - } - - [TestMethod] - public void Differences_WithWrongArgument_NamesBothSides() - { - ToolRequest[] expected = [new("a", "issue_refund", """{"order_id":"91"}""")]; - ToolRequest[] actual = [new("b", "issue_refund", """{"order_id":"92"}""")]; - - ToolRequestMatch.Differences(expected, actual).Should().ContainSingle() - .Which.Should().Contain("issue_refund").And.Contain("91").And.Contain("92"); - } - - [TestMethod] - public void Differences_WithMissingCall_SaysItWasNotCalled() - { - ToolRequest[] expected = [new("a", "get_order", "{}")]; - - ToolRequestMatch.Differences(expected, []).Should().ContainSingle() - .Which.Should().Be("Expected tool 'get_order' but it was not called"); - } - - [TestMethod] - public void Differences_WithExtraCall_ReportsItAsUnexpected() - { - ToolRequest[] actual = [new("b", "delete_order", "{}")]; - - ToolRequestMatch.Differences([], actual).Should().ContainSingle() - .Which.Should().Be("Unexpected tool 'delete_order({})'"); - } - - [TestMethod] - public void Differences_WithNoToolsOnEitherSide_IsEmpty() - { - ToolRequestMatch.Differences([], []).Should().BeEmpty(); - } -} diff --git a/Proxytrace.Domain/Agent/IAgent.cs b/Proxytrace.Domain/Agent/IAgent.cs index f271b611e..69e2de4d8 100644 --- a/Proxytrace.Domain/Agent/IAgent.cs +++ b/Proxytrace.Domain/Agent/IAgent.cs @@ -1,24 +1,24 @@ +using Nordstein.Core.AI.Clients; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Prompts; +using Nordstein.Core.AI.Tools; using Proxytrace.Domain.AgentVersion; -using Proxytrace.Domain.Inference; -using Proxytrace.Domain.Message; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; -using Proxytrace.Domain.Prompt; using Proxytrace.Domain.Search; -using Proxytrace.Domain.Tools; namespace Proxytrace.Domain.Agent; /// -/// Represents an AI agent. Identity is the (project, name) pair. The agent's prompt and tool-set +/// Represents an AI agent. Identity is the (project, name) pair. The generic agent contract +/// (name, system prompt, tools, model parameters, system-message rendering) comes from +/// ; this adds the product concerns: version +/// history, endpoint binding, tenancy, search, and archiving. The agent's prompt and tool-set /// live on its history; is the version /// currently in effect (latest, unless pinned). /// -public interface IAgent : IDomainEntity, ISearchable, IArchivable +public interface IAgent : Nordstein.Core.AI.Agents.IAgent, IDomainEntity, ISearchable, IArchivable { - /// Short human-readable name generated from the system message at creation time. - string Name { get; } - /// /// The endpoint the agent completes against. /// @@ -31,17 +31,6 @@ public interface IAgent : IDomainEntity, ISearchable, IArchivable /// IAgentVersion CurrentVersion { get; } - /// The system prompt of . - IPromptTemplate SystemPrompt { get; } - - /// The tools of . - IReadOnlyList Tools { get; } - - /// - /// Sampling and decoding parameters last seen for this agent. Not part of the version identity. - /// - IModelParameters ModelParameters { get; } - /// /// Whether the agent is a built-in agent (e.g. for prompt optimization). /// @@ -101,7 +90,4 @@ Task ChangeEndpoint( Task ChangeModelParameters( IModelParameters modelParameters, CancellationToken cancellationToken = default); - - SystemMessage CreateSystemMessage( - IReadOnlyDictionary? variables = null); } diff --git a/Proxytrace.Domain/Agent/IAgentGenerator.cs b/Proxytrace.Domain/Agent/IAgentGenerator.cs index 67efe6a46..916a5ceb1 100644 --- a/Proxytrace.Domain/Agent/IAgentGenerator.cs +++ b/Proxytrace.Domain/Agent/IAgentGenerator.cs @@ -7,4 +7,4 @@ Task CreateAsync( string? systemPrompt = null, bool isSystemAgent = false, CancellationToken cancellationToken = default); -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Agent/IAgentNameGenerator.cs b/Proxytrace.Domain/Agent/IAgentNameGenerator.cs index 0e72e66aa..97490eb02 100644 --- a/Proxytrace.Domain/Agent/IAgentNameGenerator.cs +++ b/Proxytrace.Domain/Agent/IAgentNameGenerator.cs @@ -1,4 +1,5 @@ using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; namespace Proxytrace.Domain.Agent; diff --git a/Proxytrace.Domain/Agent/IAgentRepository.cs b/Proxytrace.Domain/Agent/IAgentRepository.cs index 80f863c9f..7a997a5e3 100644 --- a/Proxytrace.Domain/Agent/IAgentRepository.cs +++ b/Proxytrace.Domain/Agent/IAgentRepository.cs @@ -1,9 +1,10 @@ using Proxytrace.Domain.AgentVersion; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Domain.Agent; diff --git a/Proxytrace.Domain/Agent/Internal/Agent.cs b/Proxytrace.Domain/Agent/Internal/Agent.cs index 87752c6dd..2ead16558 100644 --- a/Proxytrace.Domain/Agent/Internal/Agent.cs +++ b/Proxytrace.Domain/Agent/Internal/Agent.cs @@ -4,19 +4,21 @@ using Nordstein.Core.Common.Async; using Nordstein.Core.Common.Validation; using Proxytrace.Domain.AgentVersion; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Clients; +using Nordstein.Core.AI.Completions; using Nordstein.Core.Domain; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Domain.Agent.Internal; internal record Agent : DomainEntity, IAgent { - private readonly IModelClient.Factory modelClientFactory; + private readonly ModelClientFactory modelClientFactory; private readonly ILogger logger; private readonly IAgentVersion.CreateNew createVersion; private readonly Lazy versionRepository; @@ -55,7 +57,7 @@ public Agent( IModelParameters modelParameters, bool isSystemAgent, IRepository repository, - IModelClient.Factory modelClientFactory, + ModelClientFactory modelClientFactory, IAgentVersion.CreateNew createVersion, Lazy versionRepository, Lazy agentRepository, @@ -88,7 +90,7 @@ public Agent( IAgentVersion currentVersion, IDomainEntityData existing, IRepository repository, - IModelClient.Factory modelClientFactory, + ModelClientFactory modelClientFactory, IAgentVersion.CreateNew createVersion, Lazy versionRepository, Lazy agentRepository, @@ -174,7 +176,7 @@ public Task ChangeModelParameters( : ApplyAsync(this with { ModelParameters = modelParameters }, cancellationToken); public SystemMessage CreateSystemMessage(IReadOnlyDictionary? variables = null) - => Message.Message.CreateSystemMessage(SystemPrompt, variables); + => Message.CreateSystemMessage(SystemPrompt, variables); public override IEnumerable Validate(ValidationContext validationContext) { diff --git a/Proxytrace.Domain/Agent/Internal/AgentGenerator.cs b/Proxytrace.Domain/Agent/Internal/AgentGenerator.cs index 24f83799c..209ec1740 100644 --- a/Proxytrace.Domain/Agent/Internal/AgentGenerator.cs +++ b/Proxytrace.Domain/Agent/Internal/AgentGenerator.cs @@ -1,8 +1,9 @@ using Nordstein.Core.Common.Random; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Nordstein.Core.Domain; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; namespace Proxytrace.Domain.Agent.Internal; diff --git a/Proxytrace.Domain/AgentCall/AgentCallSummary.cs b/Proxytrace.Domain/AgentCall/AgentCallSummary.cs index 3f999b82c..7042482c7 100644 --- a/Proxytrace.Domain/AgentCall/AgentCallSummary.cs +++ b/Proxytrace.Domain/AgentCall/AgentCallSummary.cs @@ -1,5 +1,5 @@ using Proxytrace.Domain.ModelEndpoint; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; namespace Proxytrace.Domain.AgentCall; diff --git a/Proxytrace.Domain/AgentCall/IAgentCall.cs b/Proxytrace.Domain/AgentCall/IAgentCall.cs index c3ccd428a..1880b30ba 100644 --- a/Proxytrace.Domain/AgentCall/IAgentCall.cs +++ b/Proxytrace.Domain/AgentCall/IAgentCall.cs @@ -1,9 +1,8 @@ using System.Net; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentVersion; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Inference; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Search; diff --git a/Proxytrace.Domain/AgentCall/IAgentCallGenerator.cs b/Proxytrace.Domain/AgentCall/IAgentCallGenerator.cs index 6f026b752..460012acb 100644 --- a/Proxytrace.Domain/AgentCall/IAgentCallGenerator.cs +++ b/Proxytrace.Domain/AgentCall/IAgentCallGenerator.cs @@ -3,4 +3,4 @@ namespace Proxytrace.Domain.AgentCall; public interface IAgentCallGenerator : IDomainEntityGenerator { Task CreateAsync(DateTimeOffset createdAt, CancellationToken cancellationToken = default); -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/AgentCall/Internal/AgentCall.cs b/Proxytrace.Domain/AgentCall/Internal/AgentCall.cs index fb08ee06c..3d42652ea 100644 --- a/Proxytrace.Domain/AgentCall/Internal/AgentCall.cs +++ b/Proxytrace.Domain/AgentCall/Internal/AgentCall.cs @@ -2,10 +2,9 @@ using System.Net; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentVersion; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Nordstein.Core.Domain; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; @@ -52,7 +51,7 @@ public AgentCall( HttpStatus = httpStatus; FinishReason = finishReason; ErrorMessage = errorMessage; - ModelParameters = modelParameters ?? Inference.Internal.ModelParameters.Empty; + ModelParameters = modelParameters ?? IModelParameters.Empty; ConversationId = conversationId; SessionId = sessionId; OutlierFlags = outlierFlags; diff --git a/Proxytrace.Domain/AgentCall/Internal/AgentCallGenerator.cs b/Proxytrace.Domain/AgentCall/Internal/AgentCallGenerator.cs index d3436a4ac..60b74263c 100644 --- a/Proxytrace.Domain/AgentCall/Internal/AgentCallGenerator.cs +++ b/Proxytrace.Domain/AgentCall/Internal/AgentCallGenerator.cs @@ -2,9 +2,9 @@ using Nordstein.Core.Common.Random; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentVersion; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Nordstein.Core.Domain; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; namespace Proxytrace.Domain.AgentCall.Internal; diff --git a/Proxytrace.Domain/AgentVersion/IAgentVersion.cs b/Proxytrace.Domain/AgentVersion/IAgentVersion.cs index ebc1201f5..03f5a07ce 100644 --- a/Proxytrace.Domain/AgentVersion/IAgentVersion.cs +++ b/Proxytrace.Domain/AgentVersion/IAgentVersion.cs @@ -1,6 +1,7 @@ using Proxytrace.Domain.Agent; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Domain.AgentVersion; diff --git a/Proxytrace.Domain/AgentVersion/IAgentVersionFingerprinter.cs b/Proxytrace.Domain/AgentVersion/IAgentVersionFingerprinter.cs index f5aee0a3c..7baff6738 100644 --- a/Proxytrace.Domain/AgentVersion/IAgentVersionFingerprinter.cs +++ b/Proxytrace.Domain/AgentVersion/IAgentVersionFingerprinter.cs @@ -1,5 +1,6 @@ +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Domain.AgentVersion; diff --git a/Proxytrace.Domain/AgentVersion/IAgentVersionRepository.cs b/Proxytrace.Domain/AgentVersion/IAgentVersionRepository.cs index c4296b039..e21d4525b 100644 --- a/Proxytrace.Domain/AgentVersion/IAgentVersionRepository.cs +++ b/Proxytrace.Domain/AgentVersion/IAgentVersionRepository.cs @@ -1,7 +1,8 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Domain.AgentVersion; diff --git a/Proxytrace.Domain/AgentVersion/Internal/AgentVersion.cs b/Proxytrace.Domain/AgentVersion/Internal/AgentVersion.cs index 18ef0101c..8b6f0afcd 100644 --- a/Proxytrace.Domain/AgentVersion/Internal/AgentVersion.cs +++ b/Proxytrace.Domain/AgentVersion/Internal/AgentVersion.cs @@ -3,8 +3,9 @@ using Nordstein.Core.Common.Validation; using Proxytrace.Domain.Agent; using Nordstein.Core.Domain; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Domain.AgentVersion.Internal; diff --git a/Proxytrace.Domain/AgentVersion/Internal/AgentVersionFingerprinter.cs b/Proxytrace.Domain/AgentVersion/Internal/AgentVersionFingerprinter.cs index c33713ce3..fcb58822a 100644 --- a/Proxytrace.Domain/AgentVersion/Internal/AgentVersionFingerprinter.cs +++ b/Proxytrace.Domain/AgentVersion/Internal/AgentVersionFingerprinter.cs @@ -2,8 +2,9 @@ using System.Security.Cryptography; using System.Text; using System.Text.Json; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Domain.AgentVersion.Internal; diff --git a/Proxytrace.Domain/AgentVersion/Internal/AgentVersionGenerator.cs b/Proxytrace.Domain/AgentVersion/Internal/AgentVersionGenerator.cs index 25314d6f0..6e2edc9d4 100644 --- a/Proxytrace.Domain/AgentVersion/Internal/AgentVersionGenerator.cs +++ b/Proxytrace.Domain/AgentVersion/Internal/AgentVersionGenerator.cs @@ -1,6 +1,7 @@ using Nordstein.Core.Common.Random; using Proxytrace.Domain.Agent; using Nordstein.Core.Domain; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; namespace Proxytrace.Domain.AgentVersion.Internal; diff --git a/Proxytrace.Domain/ApiKey/IApiKey.cs b/Proxytrace.Domain/ApiKey/IApiKey.cs index bb068dd8d..da0dd66b9 100644 --- a/Proxytrace.Domain/ApiKey/IApiKey.cs +++ b/Proxytrace.Domain/ApiKey/IApiKey.cs @@ -58,4 +58,4 @@ public interface IApiKey : IDomainEntity /// Factory delegate for reconstituting an existing API key from persistence. public delegate IApiKey CreateExisting(string name, string keyHash, string keyPrefix, IProject project, IModelProvider provider, ApiKeyScopes scopes, IUser owner, IDomainEntityData existing); -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/ApiKey/IApiKeyRepository.cs b/Proxytrace.Domain/ApiKey/IApiKeyRepository.cs index 738a9fb9f..9e3d43810 100644 --- a/Proxytrace.Domain/ApiKey/IApiKeyRepository.cs +++ b/Proxytrace.Domain/ApiKey/IApiKeyRepository.cs @@ -31,4 +31,4 @@ public interface IApiKeyRepository : IRepository /// nothing that could be replayed. /// Task> GetKeyNamesByOwnerAsync(Guid ownerId, CancellationToken cancellationToken = default); -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Completion/ICompletion.cs b/Proxytrace.Domain/Completion/ICompletion.cs deleted file mode 100644 index 7ad8a248d..000000000 --- a/Proxytrace.Domain/Completion/ICompletion.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Proxytrace.Domain.Message; -using Proxytrace.Domain.Usage; - -namespace Proxytrace.Domain.Completion; - -public interface ICompletion : IDomainObject -{ - public delegate ICompletion Create( - AssistantMessage response, - TokenUsage? usage, - TimeSpan latency); - - AssistantMessage Response { get; } - TokenUsage? Usage { get; } - TimeSpan Latency { get; } -} \ No newline at end of file diff --git a/Proxytrace.Domain/Completion/Internal/Completion.cs b/Proxytrace.Domain/Completion/Internal/Completion.cs deleted file mode 100644 index b13cfdd2c..000000000 --- a/Proxytrace.Domain/Completion/Internal/Completion.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Proxytrace.Domain.Message; -using Proxytrace.Domain.Usage; - -namespace Proxytrace.Domain.Completion.Internal; - -internal sealed record Completion : ICompletion -{ - public AssistantMessage Response { get; } - public TokenUsage? Usage { get; } - public TimeSpan Latency { get; } - - public Completion(AssistantMessage response, TokenUsage? usage, TimeSpan latency) - { - Response = response; - Usage = usage; - Latency = latency; - } - - public IEnumerable Validate(ValidationContext validationContext) - { - foreach (var validationResult in Response.Validate(validationContext)) - { - yield return validationResult; - } - - foreach (var validationResult in Usage?.Validate(validationContext) ?? []) - { - yield return validationResult; - } - - if (Latency < TimeSpan.Zero) - { - yield return new ValidationResult("Latency must not be negative.", [nameof(Latency)]); - } - } -} \ No newline at end of file diff --git a/Proxytrace.Domain/Completion/Internal/CompletionGenerator.cs b/Proxytrace.Domain/Completion/Internal/CompletionGenerator.cs deleted file mode 100644 index 5d8105510..000000000 --- a/Proxytrace.Domain/Completion/Internal/CompletionGenerator.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; -using Proxytrace.Domain.Message; -using Proxytrace.Domain.Usage; - -namespace Proxytrace.Domain.Completion.Internal; - -internal class CompletionGenerator : DomainObjectGenerator -{ - private readonly IDomainObjectGenerator messageGenerator; - private readonly IDomainObjectGenerator usageGenerator; - private readonly ICompletion.Create factory; - - public CompletionGenerator( - IDomainObjectGenerator messageGenerator, - IDomainObjectGenerator usageGenerator, - ICompletion.Create factory, - IRandom random) : base(random) - { - this.messageGenerator = messageGenerator; - this.usageGenerator = usageGenerator; - this.factory = factory; - } - - public override async Task CreateAsync(CancellationToken cancellationToken = default) - { - AssistantMessage message = await messageGenerator.CreateAsync(cancellationToken); - TokenUsage usage = await usageGenerator.CreateAsync(cancellationToken); - return factory(message, usage, random.TimeSpan()); - } -} diff --git a/Proxytrace.Domain/Evaluation/EvaluationExtensions.cs b/Proxytrace.Domain/Evaluation/EvaluationExtensions.cs index 529abb0e6..d10850ae1 100644 --- a/Proxytrace.Domain/Evaluation/EvaluationExtensions.cs +++ b/Proxytrace.Domain/Evaluation/EvaluationExtensions.cs @@ -26,4 +26,4 @@ public static bool IsErrored(this IEvaluation evaluation) } return (EvaluationScore)Math.Round(scored.Average(b => (double)b)); } -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Evaluation/EvaluationScore.cs b/Proxytrace.Domain/Evaluation/EvaluationScore.cs index 1208aedd2..a459a52d4 100644 --- a/Proxytrace.Domain/Evaluation/EvaluationScore.cs +++ b/Proxytrace.Domain/Evaluation/EvaluationScore.cs @@ -7,4 +7,4 @@ public enum EvaluationScore : byte Acceptable = 3, Good = 4, Excellent = 5, -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Evaluation/IEvaluation.cs b/Proxytrace.Domain/Evaluation/IEvaluation.cs index fbaab9c7a..530340d92 100644 --- a/Proxytrace.Domain/Evaluation/IEvaluation.cs +++ b/Proxytrace.Domain/Evaluation/IEvaluation.cs @@ -1,5 +1,5 @@ using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; namespace Proxytrace.Domain.Evaluation; diff --git a/Proxytrace.Domain/Evaluation/Internal/Evaluation.cs b/Proxytrace.Domain/Evaluation/Internal/Evaluation.cs index f05a3a005..035c14122 100644 --- a/Proxytrace.Domain/Evaluation/Internal/Evaluation.cs +++ b/Proxytrace.Domain/Evaluation/Internal/Evaluation.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using Nordstein.Core.Common.Validation; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; namespace Proxytrace.Domain.Evaluation.Internal; diff --git a/Proxytrace.Domain/Evaluator/IAgenticEvaluator.cs b/Proxytrace.Domain/Evaluator/IAgenticEvaluator.cs index 4a9642e3f..08204ba00 100644 --- a/Proxytrace.Domain/Evaluator/IAgenticEvaluator.cs +++ b/Proxytrace.Domain/Evaluator/IAgenticEvaluator.cs @@ -11,4 +11,4 @@ public interface IAgenticEvaluator : IEvaluator public delegate IAgenticEvaluator CreateExisting( IAgent agent, IDomainEntityData existing); -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Evaluator/IEvaluatorGenerator.cs b/Proxytrace.Domain/Evaluator/IEvaluatorGenerator.cs index 5a3e7c5f6..6c3c361cb 100644 --- a/Proxytrace.Domain/Evaluator/IEvaluatorGenerator.cs +++ b/Proxytrace.Domain/Evaluator/IEvaluatorGenerator.cs @@ -3,4 +3,4 @@ namespace Proxytrace.Domain.Evaluator; public interface IEvaluatorGenerator : IDomainEntityGenerator { Task CreateAsync(EvaluatorKind kind, CancellationToken cancellationToken = default); -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Evaluator/IExactMatchEvaluator.cs b/Proxytrace.Domain/Evaluator/IExactMatchEvaluator.cs index 1cd72c3b9..83a74e436 100644 --- a/Proxytrace.Domain/Evaluator/IExactMatchEvaluator.cs +++ b/Proxytrace.Domain/Evaluator/IExactMatchEvaluator.cs @@ -8,4 +8,4 @@ public interface IExactMatchEvaluator : IEvaluator public delegate IExactMatchEvaluator CreateExisting( IProject project, IDomainEntityData existing); -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Evaluator/IJsonSchemaMatchEvaluator.cs b/Proxytrace.Domain/Evaluator/IJsonSchemaMatchEvaluator.cs index 6ffaa2329..dfa9b283c 100644 --- a/Proxytrace.Domain/Evaluator/IJsonSchemaMatchEvaluator.cs +++ b/Proxytrace.Domain/Evaluator/IJsonSchemaMatchEvaluator.cs @@ -17,4 +17,4 @@ public delegate IJsonSchemaMatchEvaluator CreateExisting( string jsonSchema, IProject project, IDomainEntityData existing); -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Evaluator/INumericMatchEvaluator.cs b/Proxytrace.Domain/Evaluator/INumericMatchEvaluator.cs index c26a5650e..e4bf56662 100644 --- a/Proxytrace.Domain/Evaluator/INumericMatchEvaluator.cs +++ b/Proxytrace.Domain/Evaluator/INumericMatchEvaluator.cs @@ -21,4 +21,4 @@ public delegate INumericMatchEvaluator CreateExisting( Regex ExtractionPattern { get; } decimal Tolerance { get; } -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Evaluator/Internal/AgenticEvaluator.cs b/Proxytrace.Domain/Evaluator/Internal/AgenticEvaluator.cs index cae2256c4..a1b87a990 100644 --- a/Proxytrace.Domain/Evaluator/Internal/AgenticEvaluator.cs +++ b/Proxytrace.Domain/Evaluator/Internal/AgenticEvaluator.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics; @@ -6,11 +7,11 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.Evaluation; using Nordstein.Core.Domain; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; using Proxytrace.Domain.TestResult; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; namespace Proxytrace.Domain.Evaluator.Internal; @@ -120,7 +121,7 @@ private UserMessage BuildEvaluationMessage(ITestResult testResult) "{testResult.ActualResponse}" """; - return Message.Message.CreateUserMessage(content); + return Message.CreateUserMessage(content); } public override IEnumerable Validate(ValidationContext validationContext) diff --git a/Proxytrace.Domain/Evaluator/Internal/ExactMatchEvaluator.cs b/Proxytrace.Domain/Evaluator/Internal/ExactMatchEvaluator.cs index c31191c6d..15ab72507 100644 --- a/Proxytrace.Domain/Evaluator/Internal/ExactMatchEvaluator.cs +++ b/Proxytrace.Domain/Evaluator/Internal/ExactMatchEvaluator.cs @@ -3,7 +3,7 @@ using JetBrains.Annotations; using Proxytrace.Domain.Evaluation; using Nordstein.Core.Domain; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.Project; using Proxytrace.Domain.TestResult; diff --git a/Proxytrace.Domain/IProjectSpecific.cs b/Proxytrace.Domain/IProjectSpecific.cs index 262aff83c..dd7aa9530 100644 --- a/Proxytrace.Domain/IProjectSpecific.cs +++ b/Proxytrace.Domain/IProjectSpecific.cs @@ -8,4 +8,4 @@ namespace Proxytrace.Domain; public interface IProjectSpecific { IProject Project { get; } -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Inference/IModelParameters.cs b/Proxytrace.Domain/Inference/IModelParameters.cs deleted file mode 100644 index 6204de61d..000000000 --- a/Proxytrace.Domain/Inference/IModelParameters.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Proxytrace.Domain.Inference; - -/// -/// Sampling and decoding parameters extracted from an OpenAI-compatible chat completion request. -/// All fields are nullable; means the client did not specify the value. -/// -public interface IModelParameters : IDomainObject -{ - public delegate IModelParameters Create( - double? temperature = null, - double? topP = null, - string? reasoningEffort = null, - double? frequencyPenalty = null, - double? presencePenalty = null, - int? maxTokens = null, - long? seed = null, - IReadOnlyList? stop = null, - int? n = null); - - double? Temperature { get; } - double? TopP { get; } - string? ReasoningEffort { get; } - double? FrequencyPenalty { get; } - double? PresencePenalty { get; } - int? MaxTokens { get; } - long? Seed { get; } - IReadOnlyList? Stop { get; } - int? N { get; } -} diff --git a/Proxytrace.Domain/Inference/Internal/ModelParameters.cs b/Proxytrace.Domain/Inference/Internal/ModelParameters.cs deleted file mode 100644 index 400d3c6ec..000000000 --- a/Proxytrace.Domain/Inference/Internal/ModelParameters.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Nordstein.Core.Common.Validation; - -namespace Proxytrace.Domain.Inference.Internal; - -internal sealed record ModelParameters : IModelParameters -{ - public double? Temperature { get; } - public double? TopP { get; } - public string? ReasoningEffort { get; } - public double? FrequencyPenalty { get; } - public double? PresencePenalty { get; } - public int? MaxTokens { get; } - public long? Seed { get; } - public IReadOnlyList? Stop { get; } - public int? N { get; } - - public static IModelParameters Empty { get; } = new ModelParameters(); - - public ModelParameters( - double? temperature = null, - double? topP = null, - string? reasoningEffort = null, - double? frequencyPenalty = null, - double? presencePenalty = null, - int? maxTokens = null, - long? seed = null, - IReadOnlyList? stop = null, - int? n = null) - { - Temperature = temperature; - TopP = topP; - ReasoningEffort = reasoningEffort; - FrequencyPenalty = frequencyPenalty; - PresencePenalty = presencePenalty; - MaxTokens = maxTokens; - Seed = seed; - Stop = stop; - N = n; - } - - public bool Equals(ModelParameters? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return Temperature == other.Temperature - && TopP == other.TopP - && ReasoningEffort == other.ReasoningEffort - && FrequencyPenalty == other.FrequencyPenalty - && PresencePenalty == other.PresencePenalty - && MaxTokens == other.MaxTokens - && Seed == other.Seed - && N == other.N - && (Stop ?? []).SequenceEqual(other.Stop ?? []); - } - - public override int GetHashCode() - { - // Fold Stop's elements (Equals uses SequenceEqual on it); hashing the list reference would - // give equal-content instances different hashes and break the Equals/GetHashCode contract. - var hash = new HashCode(); - hash.Add(Temperature); - hash.Add(TopP); - hash.Add(ReasoningEffort); - hash.Add(FrequencyPenalty); - hash.Add(PresencePenalty); - hash.Add(MaxTokens); - hash.Add(Seed); - hash.Add(N); - foreach (string stop in Stop ?? []) - { - hash.Add(stop); - } - return hash.ToHashCode(); - } - - public IEnumerable Validate(ValidationContext validationContext) - { - // These parameters are also embedded on AgentCall, which is CAPTURED traffic, so bounds - // stay deliberately lenient: reject only clearly-invalid values (NaN/Infinity, negatives, - // non-positive counts) rather than tight provider-specific ranges. - if (Temperature is { } temperature && (!double.IsFinite(temperature) || temperature < 0)) - yield return new ValidationResult( - $"{nameof(Temperature)} must be a finite, non-negative value.", [nameof(Temperature)]); - - if (TopP is { } topP && (!double.IsFinite(topP) || topP is < 0 or > 1)) - yield return new ValidationResult( - $"{nameof(TopP)} must be a finite value between 0 and 1.", [nameof(TopP)]); - - if (FrequencyPenalty is { } frequencyPenalty && - (!double.IsFinite(frequencyPenalty) || frequencyPenalty is < -2 or > 2)) - yield return new ValidationResult( - $"{nameof(FrequencyPenalty)} must be a finite value between -2 and 2.", [nameof(FrequencyPenalty)]); - - if (PresencePenalty is { } presencePenalty && - (!double.IsFinite(presencePenalty) || presencePenalty is < -2 or > 2)) - yield return new ValidationResult( - $"{nameof(PresencePenalty)} must be a finite value between -2 and 2.", [nameof(PresencePenalty)]); - - if (MaxTokens is { } maxTokens) - yield return Validation.GreaterThan(maxTokens, 0, nameof(MaxTokens)); - - if (N is { } n) - yield return Validation.GreaterThan(n, 0, nameof(N)); - } -} diff --git a/Proxytrace.Domain/Inference/Internal/ModelParametersGenerator.cs b/Proxytrace.Domain/Inference/Internal/ModelParametersGenerator.cs deleted file mode 100644 index b6ebd0545..000000000 --- a/Proxytrace.Domain/Inference/Internal/ModelParametersGenerator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Nordstein.Core.Common.Async; -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; - -namespace Proxytrace.Domain.Inference.Internal; - -internal sealed class ModelParametersGenerator : DomainObjectGenerator -{ - private readonly IModelParameters.Create factory; - - public ModelParametersGenerator( - IRandom random, - IModelParameters.Create factory) : base(random) - { - this.factory = factory; - } - - public override Task CreateAsync(CancellationToken cancellationToken = default) - => factory( - temperature: random.Double(min: 0.7, max: 0.9), - topP: random.Double(min: 0.7, max: 0.9), - reasoningEffort: random.Any(["none", "low", "medium", "high"]), - frequencyPenalty: random.Double(min: 0.0, max: 0.5), - presencePenalty: random.Double(min: 0.0, max: 0.5), - maxTokens: random.Int(min: 50, max: 200) - ).ToTaskResult(); -} diff --git a/Proxytrace.Domain/Message/AssistantMessage.cs b/Proxytrace.Domain/Message/AssistantMessage.cs deleted file mode 100644 index fbf0d1e49..000000000 --- a/Proxytrace.Domain/Message/AssistantMessage.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Proxytrace.Domain.Message; - -/// -/// A message from the assistant, which may include tool requests. -/// -public sealed record AssistantMessage : Message -{ - /// - /// The tool requests made by the assistant in this message. - /// - public IReadOnlyList ToolRequests { get; } - - /// - /// Initializes a new instance of the class with the specified contents and tool requests. - /// - /// The contents of the message. - /// The tool requests made by the assistant. - public AssistantMessage( - IReadOnlyList contents, - IReadOnlyList toolRequests) - : base(Role.Assistant, contents) - { - ToolRequests = [..toolRequests]; - } - - /// - public override IEnumerable Validate(ValidationContext validationContext) - { - foreach (var result in base.Validate(validationContext)) - { - yield return result; - } - - foreach (var result in ToolRequests.SelectMany(tr => tr.Validate(validationContext))) - { - yield return result; - } - } - - /// - /// Returns the message text with any tool requests rendered as - /// [tool call] Name(Arguments) lines appended. Unlike - /// — which only concatenates and therefore returns an empty - /// string for a turn whose sole content is a tool call — this always yields a non-empty, - /// human-readable representation of a tool-call-only assistant turn. Intended for display - /// (e.g. the evaluator playground reference / candidate panes), not for evaluation. - /// - public string GetDisplayText() - { - var text = GetText(); - if (ToolRequests.Count == 0) - { - return text; - } - - var toolCalls = string.Join( - Environment.NewLine, - ToolRequests.Select(tr => $"[tool call] {tr.Name}({tr.Arguments})")); - - return string.IsNullOrWhiteSpace(text) - ? toolCalls - : $"{text}{Environment.NewLine}{toolCalls}"; - } - - public string GetTextResponse() - { - if (ToolRequests.Any()) - { - throw new InvalidOperationException("Cannot get text response from an AssistantMessage that contains tool requests."); - } - - return Contents.Any(x => x.Kind != ContentKind.Text) - ? throw new InvalidOperationException("Cannot get text response from an AssistantMessage that contains non-text content.") - : string.Join(string.Empty, Contents.Select(x => x.Text)); - } - - /// - public bool Equals(AssistantMessage? other) - => other is not null - && base.Equals(other) - && ToolRequests.SequenceEqual(other.ToolRequests); - - /// - public override int GetHashCode() - { - var hash = new HashCode(); - hash.Add(base.GetHashCode()); - foreach (ToolRequest toolRequest in ToolRequests) - { - hash.Add(toolRequest); - } - return hash.ToHashCode(); - } - - public override string ToString() - { - if (ToolRequests.Count == 0) - { - return base.ToString(); - } - - var toolCalls = string.Join( - Environment.NewLine, - ToolRequests.Select(tr => $"[tool call] {tr.Name}({tr.Arguments})")); - - return string.IsNullOrWhiteSpace(GetText()) - ? $"{Role}: {toolCalls}" - : $"{base.ToString()}{Environment.NewLine}{toolCalls}"; - } -} \ No newline at end of file diff --git a/Proxytrace.Domain/Message/Content.cs b/Proxytrace.Domain/Message/Content.cs deleted file mode 100644 index 9707ba03e..000000000 --- a/Proxytrace.Domain/Message/Content.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Nordstein.Core.Common.Validation; - -namespace Proxytrace.Domain.Message; - -public sealed record Content : IDomainObject -{ - public ContentKind Kind - => Data != null ? ContentKind.Image : ContentKind.Text; - - public string? Text { get; } - - public BinaryData? Data { get; } - - private Content( - string? text, - BinaryData? data) - { - Text = text; - Data = data; - } - - public static Content FromText(string text) - => new(text, null); - - public static Content FromImage(BinaryData data) - => new(null, data); - - public IEnumerable Validate(ValidationContext validationContext) - { - if (Text.NotNullOrWhiteSpace() && Data != null) - { - yield return new ValidationResult( - $"Content cannot have both text and data. Text: '{Text}', Data length: {Data.Length}", - [nameof(Text), nameof(Data)]); - yield break; - } - - if (Text.NullOrWhiteSpace() && Data is null) - { - yield return new ValidationResult( - "Content must have either text or data.", - [nameof(Text), nameof(Data)]); - yield break; - } - - if (Kind is ContentKind.Text) - { - yield return Validation.NotNullOrWhiteSpace(Text); - yield return Validation.NotEmpty(Text); - } - - if (Kind is ContentKind.Image) - { - yield return Validation.NotNull(Data); - yield return Validation.NotNullOrWhiteSpace(Data?.MediaType); - } - } - - /// - public bool Equals(Content? other) - => other is not null && - Equals(Kind, other.Kind) && - Equals(Text, other.Text) && - Equals(Data?.MediaType, other.Data?.MediaType) && - ((Data == null && other.Data == null) || - (Data?.ToArray() ?? []).SequenceEqual(other.Data?.ToArray() ?? [])); - - /// - public override int GetHashCode() - // Hash Data by length, not reference: Equals compares the bytes, so equal content (same bytes - // ⇒ same length) hashes equally; differing bytes of equal length may collide, which is allowed. - => HashCode.Combine((int)Kind, Text, Data?.Length, Data?.MediaType); - - public override string ToString() - => Kind switch - { - ContentKind.Text => Text, - ContentKind.Image => $"Image: MediaType='{Data?.MediaType}', Size={Data?.Length} bytes", - _ => "Unknown content" - } ?? string.Empty; -} \ No newline at end of file diff --git a/Proxytrace.Domain/Message/ContentKind.cs b/Proxytrace.Domain/Message/ContentKind.cs deleted file mode 100644 index 24e5b8721..000000000 --- a/Proxytrace.Domain/Message/ContentKind.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Proxytrace.Domain.Message; - -/// -/// Type of content of a -/// -public enum ContentKind -{ - /// - /// Text content. - /// - Text, - - /// - /// Image content. - /// - Image -} \ No newline at end of file diff --git a/Proxytrace.Domain/Message/Conversation.cs b/Proxytrace.Domain/Message/Conversation.cs deleted file mode 100644 index 13f6fd871..000000000 --- a/Proxytrace.Domain/Message/Conversation.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using JetBrains.Annotations; - -namespace Proxytrace.Domain.Message; - -/// -/// Represents a conversation consisting of a sequence of messages. -/// -public sealed record Conversation : IDomainObject -{ - // Immutable backing list: assigned once in the constructor and never mutated. Every "mutation" - // (With/WithSystemMessage/WithoutSystemMessage) returns a NEW Conversation, so a captured - // instance can never change underfoot — important because this is a content-folding record - // (Equals/GetHashCode fold the messages) used as a value object. - private readonly IReadOnlyList messages; - - /// - /// The messages in the conversation - /// - public IReadOnlyList Messages - => messages; - - public SystemMessage? SystemMessage - => Messages.FirstOrDefault(x => x.Role == Role.System) as SystemMessage; - - /// - /// How many of the assistant's tool calls in this conversation already have their result - /// recorded — calls that were both made and came back. - /// - /// This is what tells a *decision point* apart from a *summary*. An agent turn that uses tools - /// is several upstream calls, each capturing the conversation so far; the last one already holds - /// every tool call the agent made and every result it got, so the only thing a model can still - /// produce from it is the closing message. Every choice the agent made was decided in an earlier - /// call. A test case built on such a conversation therefore scores the wording of the summary, - /// never the decision — see docs/optimization-loop.md. - /// - public int ResolvedToolCallCount - { - get - { - // A ToolMessage carries its tool-call id in the first content slot. Read that slot - // directly instead of going through ToolMessage.Id, which throws on a malformed - // message — a descriptive count is not worth failing a whole read over. - HashSet resolved = - [ - .. Messages.OfType() - .Select(tool => tool.Contents.Count > 0 ? tool.Contents[0].Text : null) - .OfType() - ]; - return Messages.OfType() - .SelectMany(assistant => assistant.ToolRequests) - .Count(request => resolved.Contains(request.Id)); - } - } - - /// - /// Initializes a new instance of the class with the specified messages. - /// - /// The messages in the conversation. - public Conversation( - IReadOnlyList messages) - { - // Defensive copy so a caller mutating the passed-in list cannot mutate this value object. - this.messages = messages.ToArray(); - } - - /// - /// Creates a new empty conversation - /// - public static Conversation Create() - => new([]); - - /// - /// Returns a new conversation with appended. - /// - [Pure] - public Conversation With(Message message) - { - if (message.Role == Role.System) - { - throw new InvalidOperationException("System messages must be added using WithSystemMessage"); - } - return new Conversation([..messages, message]); - } - - /// - /// Returns a new conversation with prepended. - /// - [Pure] - public Conversation WithSystemMessage(SystemMessage systemMessage) - { - if (messages.Any(x => x.Role == Role.System)) - { - throw new InvalidOperationException("Conversation already contains a system message"); - } - return new Conversation([systemMessage, ..messages]); - } - - /// - /// Returns the Conversation without the system message - /// - [Pure] - public Conversation WithoutSystemMessage() - => new(Messages.Where(x => x.Role != Role.System).ToArray()); - - /// - public IEnumerable Validate(ValidationContext validationContext) - { - return Messages.SelectMany(x => x.Validate(validationContext)); - } - - /// - public bool Equals(Conversation? other) - => other is not null && - Messages.SequenceEqual(other.Messages); - - /// - public override int GetHashCode() - { - // Fold the messages' elements (Equals uses SequenceEqual); hashing the list reference would - // give equal-content instances different hashes and break the Equals/GetHashCode contract. - var hash = new HashCode(); - foreach (Message message in messages) - { - hash.Add(message); - } - return hash.ToHashCode(); - } - - /// - /// Replaces any existing system Prompt with this system prompt - /// - [Pure] - public static Conversation ReplaceSystemMessage(Conversation conversation, SystemMessage systemMessage) - => conversation.WithoutSystemMessage().WithSystemMessage(systemMessage); - - public override string ToString() - => string.Join(Environment.NewLine, messages.Select(x => x.ToString())); -} \ No newline at end of file diff --git a/Proxytrace.Domain/Message/Internal/AssistantMessageGenerator.cs b/Proxytrace.Domain/Message/Internal/AssistantMessageGenerator.cs deleted file mode 100644 index 712a1ae86..000000000 --- a/Proxytrace.Domain/Message/Internal/AssistantMessageGenerator.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; - -namespace Proxytrace.Domain.Message.Internal; - -internal class AssistantMessageGenerator : DomainObjectGenerator -{ - private readonly IDomainObjectGenerator contentGenerator; - - public AssistantMessageGenerator( - IDomainObjectGenerator contentGenerator, - IRandom random) : base(random) - { - this.contentGenerator = contentGenerator; - } - - public override async Task CreateAsync(CancellationToken cancellationToken = default) - { - var content = await contentGenerator.CreateAsync(cancellationToken); - return new AssistantMessage([content], []); - } -} diff --git a/Proxytrace.Domain/Message/Internal/ContentGenerator.cs b/Proxytrace.Domain/Message/Internal/ContentGenerator.cs deleted file mode 100644 index a099cc646..000000000 --- a/Proxytrace.Domain/Message/Internal/ContentGenerator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Nordstein.Core.Common.Async; -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; - -namespace Proxytrace.Domain.Message.Internal; - -internal class ContentGenerator : DomainObjectGenerator -{ - public ContentGenerator(IRandom random) : base(random) - { - } - - public override Task CreateAsync(CancellationToken cancellationToken = default) - => Content.FromText(random.String()).ToTaskResult(); -} diff --git a/Proxytrace.Domain/Message/Internal/ContentJsonConverter.cs b/Proxytrace.Domain/Message/Internal/ContentJsonConverter.cs deleted file mode 100644 index fc79ed59b..000000000 --- a/Proxytrace.Domain/Message/Internal/ContentJsonConverter.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using JetBrains.Annotations; - -namespace Proxytrace.Domain.Message.Internal; - -[UsedImplicitly] -internal sealed class ContentJsonConverter : JsonConverter -{ - public override Content Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - using var doc = JsonDocument.ParseValue(ref reader); - var root = doc.RootElement; - - if (root.TryGetProperty("Text", out var textProp) && textProp.ValueKind == JsonValueKind.String) - { - var text = textProp.GetString() ?? string.Empty; - return Content.FromText(text); - } - - if (root.TryGetProperty("Data", out var dataProp) && dataProp.ValueKind == JsonValueKind.String) - { - // Restore the media type written alongside the bytes — without it the round-tripped - // content fails Content.Validate (image content requires a media type). - string? mediaType = root.TryGetProperty("MediaType", out var mediaTypeProp) - && mediaTypeProp.ValueKind == JsonValueKind.String - ? mediaTypeProp.GetString() - : null; - var bytes = Convert.FromBase64String(dataProp.GetString() ?? string.Empty); - return Content.FromImage(BinaryData.FromBytes(bytes, mediaType)); - } - - throw new JsonException("Cannot deserialize Content: missing Text or Data property"); - } - - public override void Write(Utf8JsonWriter writer, Content value, JsonSerializerOptions options) - { - writer.WriteStartObject(); - if (value.Text != null) - { - writer.WriteString("Text", value.Text); - } - if (value.Data != null) - { - writer.WriteString("Data", Convert.ToBase64String(value.Data.ToArray())); - if (value.Data.MediaType != null) - { - writer.WriteString("MediaType", value.Data.MediaType); - } - } - writer.WriteEndObject(); - } -} \ No newline at end of file diff --git a/Proxytrace.Domain/Message/Internal/ConversationGenerator.cs b/Proxytrace.Domain/Message/Internal/ConversationGenerator.cs deleted file mode 100644 index 6a8546f35..000000000 --- a/Proxytrace.Domain/Message/Internal/ConversationGenerator.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; - -namespace Proxytrace.Domain.Message.Internal; - -internal class ConversationGenerator : DomainObjectGenerator -{ - private readonly IDomainObjectGenerator userMessageGenerator; - private readonly IDomainObjectGenerator assistantMessageGenerator; - - public ConversationGenerator( - IRandom random, - IDomainObjectGenerator userMessageGenerator, - IDomainObjectGenerator assistantMessageGenerator) : base(random) - { - this.userMessageGenerator = userMessageGenerator; - this.assistantMessageGenerator = assistantMessageGenerator; - } - - public override async Task CreateAsync(CancellationToken cancellationToken = default) - { - var userMessage = await userMessageGenerator.CreateAsync(cancellationToken); - var assistantMessage = await assistantMessageGenerator.CreateAsync(cancellationToken); - return Conversation.Create() - .With(userMessage) - .With(assistantMessage); - } -} diff --git a/Proxytrace.Domain/Message/Internal/SystemMessageGenerator.cs b/Proxytrace.Domain/Message/Internal/SystemMessageGenerator.cs deleted file mode 100644 index 4b3a7d601..000000000 --- a/Proxytrace.Domain/Message/Internal/SystemMessageGenerator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Nordstein.Core.Common.Async; -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; - -namespace Proxytrace.Domain.Message.Internal; - -internal class SystemMessageGenerator : DomainObjectGenerator -{ - public SystemMessageGenerator(IRandom random) : base(random) - { - } - - public override Task CreateAsync(CancellationToken cancellationToken = default) - => new SystemMessage(random.String()).ToTaskResult(); -} diff --git a/Proxytrace.Domain/Message/Internal/ToolMessageGenerator.cs b/Proxytrace.Domain/Message/Internal/ToolMessageGenerator.cs deleted file mode 100644 index 35a706883..000000000 --- a/Proxytrace.Domain/Message/Internal/ToolMessageGenerator.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; - -namespace Proxytrace.Domain.Message.Internal; - -internal class ToolMessageGenerator : DomainObjectGenerator -{ - private readonly IDomainObjectGenerator toolResponseGenerator; - - public ToolMessageGenerator( - IRandom random, - IDomainObjectGenerator toolResponseGenerator) : base(random) - { - this.toolResponseGenerator = toolResponseGenerator; - } - - public override async Task CreateAsync(CancellationToken cancellationToken = default) - { - var response = await toolResponseGenerator.CreateAsync(cancellationToken); - return new ToolMessage(response); - } -} diff --git a/Proxytrace.Domain/Message/Internal/ToolRequestGenerator.cs b/Proxytrace.Domain/Message/Internal/ToolRequestGenerator.cs deleted file mode 100644 index 534d6b745..000000000 --- a/Proxytrace.Domain/Message/Internal/ToolRequestGenerator.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Nordstein.Core.Common.Async; -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; - -namespace Proxytrace.Domain.Message.Internal; - -internal class ToolRequestGenerator : DomainObjectGenerator -{ - public ToolRequestGenerator(IRandom random) : base(random) - { - } - - public override Task CreateAsync(CancellationToken cancellationToken = default) - => new ToolRequest( - id: random.Guid().ToString(), - name: random.String(), - arguments: "{}") - .ToTaskResult(); -} diff --git a/Proxytrace.Domain/Message/Internal/ToolResponseGenerator.cs b/Proxytrace.Domain/Message/Internal/ToolResponseGenerator.cs deleted file mode 100644 index c7c2296f5..000000000 --- a/Proxytrace.Domain/Message/Internal/ToolResponseGenerator.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; - -namespace Proxytrace.Domain.Message.Internal; - -internal class ToolResponseGenerator : DomainObjectGenerator -{ - private readonly IDomainObjectGenerator toolRequestGenerator; - private readonly IDomainObjectGenerator contentGenerator; - - public ToolResponseGenerator( - IRandom random, - IDomainObjectGenerator toolRequestGenerator, - IDomainObjectGenerator contentGenerator) : base(random) - { - this.toolRequestGenerator = toolRequestGenerator; - this.contentGenerator = contentGenerator; - } - - public override async Task CreateAsync(CancellationToken cancellationToken = default) - { - var request = await toolRequestGenerator.CreateAsync(cancellationToken); - var result = await contentGenerator.CreateAsync(cancellationToken); - return new ToolResponse(request, [result]); - } -} diff --git a/Proxytrace.Domain/Message/Internal/UserMessageGenerator.cs b/Proxytrace.Domain/Message/Internal/UserMessageGenerator.cs deleted file mode 100644 index 6a52083a6..000000000 --- a/Proxytrace.Domain/Message/Internal/UserMessageGenerator.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; - -namespace Proxytrace.Domain.Message.Internal; - -internal class UserMessageGenerator : DomainObjectGenerator -{ - private readonly IDomainObjectGenerator contentGenerator; - - public UserMessageGenerator( - IRandom random, - IDomainObjectGenerator contentGenerator) : base(random) - { - this.contentGenerator = contentGenerator; - } - - public override async Task CreateAsync(CancellationToken cancellationToken = default) - { - var content = await contentGenerator.CreateAsync(cancellationToken); - return new UserMessage([content]); - } -} diff --git a/Proxytrace.Domain/Message/Message.cs b/Proxytrace.Domain/Message/Message.cs deleted file mode 100644 index 81ab8a94f..000000000 --- a/Proxytrace.Domain/Message/Message.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Text.Json.Serialization; -using Proxytrace.Domain.Prompt; - -namespace Proxytrace.Domain.Message; - -/// -/// Represents a message consisting of a role and associated content. -/// -[JsonPolymorphic(TypeDiscriminatorPropertyName = nameof(Role))] -[JsonDerivedType(typeof(UserMessage), nameof(Role.User))] -[JsonDerivedType(typeof(AssistantMessage), nameof(Role.Assistant))] -[JsonDerivedType(typeof(SystemMessage), nameof(Role.System))] -[JsonDerivedType(typeof(ToolMessage), nameof(Role.Tool))] -public abstract record Message : IDomainObject -{ - /// - /// The role of the message - /// - [JsonIgnore] - public Role Role { get; } - - /// - /// The content of the message - /// - public IReadOnlyList Contents { get; } - - /// - /// Initializes a new instance of the class with the specified role and contents. - /// - /// The role of the message. - /// The contents of the message. - protected Message( - Role role, - IReadOnlyList contents) - { - Role = role; - Contents = contents; - } - - /// - /// Creates a new message with the - /// - public static UserMessage CreateUserMessage(string content) - => CreateUserMessage([Content.FromText(content)]); - - /// - /// Creates a new message with the - /// - /// TODO: Remove this overload - public static SystemMessage CreateSystemMessage(string systemPrompt) - => new(systemPrompt); - - public static SystemMessage CreateSystemMessage( - IPromptTemplate template, - IReadOnlyDictionary? variables = null) - => new(template.Render(variables).ToPromptString()); - - /// - /// Creates a new message with the - /// - public static UserMessage CreateUserMessage(IReadOnlyList content) - => new(content); - - /// - /// Creates a new message with the given and tool requests. - /// - public static AssistantMessage CreateAssistantMessage( - IReadOnlyList contents, - IReadOnlyList toolRequests) - => new(contents, toolRequests); - - /// - /// Creates a new message with the given . - /// - public static ToolMessage CreateToolMessage(ToolResponse response) - => new(response); - - /// - /// Returns the concatenated text content of this message. - /// - public virtual string GetText() - => string.Concat(Contents.Select(c => c.Text ?? "")); - - /// - public virtual IEnumerable Validate(ValidationContext validationContext) - => Contents.SelectMany(content => content.Validate(validationContext)); - - /// - public virtual bool Equals(Message? other) - => other is not null && - Role == other.Role && - Contents.SequenceEqual(other.Contents); - - /// - public override int GetHashCode() - { - // Fold the elements, not the list reference, so a value-equal message (Equals uses - // SequenceEqual) hashes equally — otherwise the Equals/GetHashCode contract is violated. - var hash = new HashCode(); - hash.Add(Role); - foreach (Content content in Contents) - { - hash.Add(content); - } - return hash.ToHashCode(); - } - - public override string ToString() - => $"{Role}: {string.Join(Environment.NewLine, Contents.Select(c => c.ToString()))}"; -} \ No newline at end of file diff --git a/Proxytrace.Domain/Message/Role.cs b/Proxytrace.Domain/Message/Role.cs deleted file mode 100644 index f8264752e..000000000 --- a/Proxytrace.Domain/Message/Role.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Proxytrace.Domain.Message; - -/// -/// The role of a message in the conversation -/// -public enum Role -{ - /// - /// The system message, usually the initial prompt - /// - System, - - /// - /// Message from the user - /// - User, - - /// - /// Message from the assistant (the model) - /// - Assistant, - - /// - /// The response from a tool request - /// - Tool, -} \ No newline at end of file diff --git a/Proxytrace.Domain/Message/SystemMessage.cs b/Proxytrace.Domain/Message/SystemMessage.cs deleted file mode 100644 index dba3eb5f6..000000000 --- a/Proxytrace.Domain/Message/SystemMessage.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Text.Json.Serialization; -using JetBrains.Annotations; - -namespace Proxytrace.Domain.Message; - -/// -/// A system message, typically used to set the behavior of the AI assistant. -/// -public sealed record SystemMessage : Message -{ - /// - /// Initializes a new instance of the class with the specified contents. - /// - /// The contents of the system message. - [UsedImplicitly] - [JsonConstructor] - public SystemMessage(IReadOnlyList contents) - : base(Role.System, contents) - { - } - - /// - /// Initializes a new instance of the class with the specified prompt and output format. - /// - /// The prompt to use. - public SystemMessage(string prompt) - : base(Role.System, [Content.FromText(prompt)]) - { - } - - /// - public override IEnumerable Validate(ValidationContext validationContext) - { - // base (Message.Validate) already cascades into each Content; layer only the - // SystemMessage-specific "must be text" rule on top. - foreach (var result in base.Validate(validationContext)) - { - yield return result; - } - - foreach (Content content in Contents) - { - if (content.Kind != ContentKind.Text) - { - yield return new ValidationResult( - $"SystemMessage content must be of kind Text. Found content with kind {content.Kind}.", - [nameof(Contents)]); - } - } - } - - /// - public override string ToString() - => base.ToString(); -} \ No newline at end of file diff --git a/Proxytrace.Domain/Message/ToolMessage.cs b/Proxytrace.Domain/Message/ToolMessage.cs deleted file mode 100644 index b60c68015..000000000 --- a/Proxytrace.Domain/Message/ToolMessage.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Text.Json.Serialization; -using Nordstein.Core.Common.Validation; - -namespace Proxytrace.Domain.Message; - -/// -/// A message representing a tool response. -/// -public sealed record ToolMessage : Message -{ - /// - /// The unique identifier of the tool response. - /// - public string Id => Deconstruct().Id; - - /// - /// Initializes a new instance of the class from a . - /// - /// The tool response to represent. - public ToolMessage(ToolResponse response) - : this(response.Id, GetResponseContent(response)) - { - } - - private static IReadOnlyList GetResponseContent(ToolResponse response) - { - if (response.Success) - { - var results = response.Results.ToList(); - if (results.Count == 0) - { - results.Add(Content.FromText("Tool executed successfully. No result returned.")); - } - return results; - } - - return [ - Content.FromText($""" - Status: Failure - Error: {response.Error?.Message ?? "Unknown error"} - """) - ]; - } - - /// - /// Initializes a new instance of the class with the specified id and response. - /// - /// The unique identifier of the tool response. - /// The response content. - private ToolMessage(string id, IReadOnlyList contents) - : this( - [ - Content.FromText(id), - ..contents - ]) - { - } - - /// - /// Initializes a new instance of the class with the specified contents. - /// - /// The contents of the tool message. - [JsonConstructor] - public ToolMessage(IReadOnlyList contents) - : base(Role.Tool, contents) - { - } - - /// - /// Deconstructs the ToolMessage into its Id and Response components. - /// - public (string Id, IReadOnlyList Contents) Deconstruct() - { - if (Contents.Count < 2) - { - throw new InvalidOperationException("Invalid ToolMessage content."); - } - - var id = Contents[0].Text; - return id.NullOrWhiteSpace() - ? throw new InvalidOperationException("Invalid ToolMessage content.") - : (id, Contents.Skip(1).ToList()); - } - - /// - /// - /// Skips the leading content slot (which carries the tool-call id rather than payload text) - /// and concatenates the remaining slots, so multi-result tool messages round-trip - /// consistently with . - /// - public override string GetText() - => string.Concat(Contents.Skip(1).Select(content => content.Text ?? "")); - - public override IEnumerable Validate(ValidationContext validationContext) - { - foreach (var result in base.Validate(validationContext)) - { - yield return result; - } - - // A valid tool message is the id slot plus at least one result slot. The - // ToolMessage(ToolResponse) factory can emit more than one result slot, so the - // invariant is "at least two items", not "exactly two". - if (Contents.Count < 2) - { - yield return new ValidationResult( - $"{nameof(Contents)} must have at least 2 items", [nameof(Contents)]); - yield break; - } - - yield return Validation.NotNullOrWhiteSpace(Contents[0].Text); - } - - public override string ToString() - { - var id = Contents.Count > 0 ? Contents[0].Text : null; - var payload = GetText(); - return string.IsNullOrWhiteSpace(id) - ? $"{Role}: {payload}" - : $"{Role} (id: {id}): {payload}"; - } -} \ No newline at end of file diff --git a/Proxytrace.Domain/Message/ToolRequest.cs b/Proxytrace.Domain/Message/ToolRequest.cs deleted file mode 100644 index cceb7715c..000000000 --- a/Proxytrace.Domain/Message/ToolRequest.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Nordstein.Core.Common.Validation; - -namespace Proxytrace.Domain.Message; - -/// -/// Request to call a tool -/// -public sealed record ToolRequest : IDomainObject -{ - /// - /// The id of the tool request - /// - public string Id { get; } - - /// - /// The name of the tool to call - /// - public string Name { get; } - - /// - /// The arguments to pass to the tool - /// - public string Arguments { get; } - - /// - /// Initializes a new instance of the class with the specified id, name, and arguments. - /// - /// The id of the tool request. - /// The name of the tool to call. - /// The arguments to pass to the tool. - public ToolRequest(string id, string name, string arguments) - { - Id = id; - Name = name; - Arguments = arguments; - } - - /// - public IEnumerable Validate(ValidationContext validationContext) - { - yield return Validation.NotNullOrWhiteSpace(Id); - yield return Validation.NotNullOrWhiteSpace(Name); - } -} \ No newline at end of file diff --git a/Proxytrace.Domain/Message/ToolRequestMatch.cs b/Proxytrace.Domain/Message/ToolRequestMatch.cs deleted file mode 100644 index ba6411e9a..000000000 --- a/Proxytrace.Domain/Message/ToolRequestMatch.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System.Globalization; -using System.Text; -using System.Text.Json; - -namespace Proxytrace.Domain.Message; - -/// -/// Compares an expected tool request against an actual one for evaluation. -/// -/// Deliberately ignores : an expected output's ids are minted locally -/// when the case is stored, while the actual ids come from the provider, so an id-sensitive -/// comparison could never match. Arguments are compared as canonical JSON (key order- and -/// whitespace-insensitive, numbers by value) and fall back to trimmed string equality when either -/// side is not JSON. -/// -public static class ToolRequestMatch -{ - /// True when both requests name the same tool with equivalent arguments. - public static bool Matches(ToolRequest expected, ToolRequest actual) - => string.Equals(expected.Name, actual.Name, StringComparison.Ordinal) - && ArgumentsMatch(expected.Arguments, actual.Arguments); - - /// - /// Human-readable differences between the expected and actual tool calls, compared as an - /// UNORDERED multiset — parallel tool calls carry no meaningful order, and the same two calls - /// emitted the other way round is not a defect. An empty result means they match. - /// - public static IReadOnlyList Differences( - IReadOnlyList expected, - IReadOnlyList actual) - { - List unmatched = [.. actual]; - List differences = []; - - foreach (ToolRequest want in expected) - { - int exact = unmatched.FindIndex(candidate => Matches(want, candidate)); - if (exact >= 0) - { - unmatched.RemoveAt(exact); - continue; - } - - // Same tool, different arguments is the interesting failure — report both sides rather - // than the useless pair "expected X, never called" + "unexpected X". - int sameName = unmatched.FindIndex( - candidate => string.Equals(want.Name, candidate.Name, StringComparison.Ordinal)); - if (sameName >= 0) - { - differences.Add($"Expected tool '{Describe(want)}' but got '{Describe(unmatched[sameName])}'"); - unmatched.RemoveAt(sameName); - continue; - } - - differences.Add($"Expected tool '{want.Name}' but it was not called"); - } - - differences.AddRange(unmatched.Select(extra => $"Unexpected tool '{Describe(extra)}'")); - return differences; - } - - private static string Describe(ToolRequest request) - => $"{request.Name}({request.Arguments.Trim()})"; - - /// "40.0" → "40", "0.50" → "0.5", "100" → "100" (an integer has nothing to trim). - private static string TrimTrailingZeros(string number) - => number.Contains('.', StringComparison.Ordinal) - ? number.TrimEnd('0').TrimEnd('.') - : number; - - private static bool ArgumentsMatch(string expected, string actual) - => TryCanonicalize(expected, out string? canonicalExpected) - && TryCanonicalize(actual, out string? canonicalActual) - ? string.Equals(canonicalExpected, canonicalActual, StringComparison.Ordinal) - : string.Equals(expected.Trim(), actual.Trim(), StringComparison.Ordinal); - - private static bool TryCanonicalize(string json, out string? canonical) - { - canonical = null; - if (string.IsNullOrWhiteSpace(json)) - { - return false; - } - - try - { - using JsonDocument document = JsonDocument.Parse(json); - using var buffer = new MemoryStream(); - using (var writer = new Utf8JsonWriter(buffer)) - { - WriteCanonical(document.RootElement, writer); - } - canonical = Encoding.UTF8.GetString(buffer.ToArray()); - return true; - } - catch (JsonException) - { - return false; - } - } - - private static void WriteCanonical(JsonElement element, Utf8JsonWriter writer) - { - switch (element.ValueKind) - { - case JsonValueKind.Object: - writer.WriteStartObject(); - foreach (JsonProperty property in element.EnumerateObject() - .OrderBy(property => property.Name, StringComparer.Ordinal)) - { - writer.WritePropertyName(property.Name); - WriteCanonical(property.Value, writer); - } - writer.WriteEndObject(); - break; - case JsonValueKind.Array: - writer.WriteStartArray(); - foreach (JsonElement item in element.EnumerateArray()) - { - WriteCanonical(item, writer); - } - writer.WriteEndArray(); - break; - case JsonValueKind.Number: - // Normalize 40 / 40.0 / 4e1 to one representation. Note decimal PRESERVES scale — - // 40m and 40.0m are distinct representations and write differently — so the - // trailing zeros have to go explicitly. A number too large or precise for decimal - // keeps its raw text: still stable, just not value-normalized. - if (element.TryGetDecimal(out decimal number)) - { - writer.WriteRawValue(TrimTrailingZeros(number.ToString(CultureInfo.InvariantCulture))); - } - else - { - writer.WriteRawValue(element.GetRawText()); - } - break; - default: - element.WriteTo(writer); - break; - } - } -} diff --git a/Proxytrace.Domain/Message/ToolResponse.cs b/Proxytrace.Domain/Message/ToolResponse.cs deleted file mode 100644 index 365c0467b..000000000 --- a/Proxytrace.Domain/Message/ToolResponse.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Text.Json.Serialization; -using Nordstein.Core.Common.Validation; - -namespace Proxytrace.Domain.Message; - -/// -/// The response from a tool -/// -public sealed record ToolResponse : IDomainObject -{ - /// - /// The id of the tool call - /// - public string Id { get; } - - /// - /// The response from the tool, if successful - /// - public IReadOnlyList Results { get; } - - /// - /// The error from the tool, if unsuccessful - /// - public Exception? Error { get; } - - /// - /// Whether the tool call was successful - /// - public bool Success { get; } - - /// - /// The result of a successful tool call - /// - public ToolResponse( - ToolRequest request, - IReadOnlyList results) : this( - id: request.Id, - results: results, - success: true, - error: null) - { - } - - /// - /// The result of a tool call - /// - [JsonConstructor] - public ToolResponse( - string id, - IReadOnlyList results, - bool success, - Exception? error) - { - Id = id; - Results = results; - Success = success; - Error = error; - } - - /// - /// The result of a failed tool call - /// - public ToolResponse(ToolRequest request, Exception error) : this( - id: request.Id, - results: [], - success: false, - error: error) - { - } - - /// - public bool Equals(ToolResponse? other) - => other is not null && - Id == other.Id && - Results.SequenceEqual(other.Results) && - Equals(Error, other.Error) && - Success == other.Success; - - /// - public override int GetHashCode() - { - var hash = new HashCode(); - hash.Add(Id); - foreach (Content result in Results) - { - hash.Add(result); - } - hash.Add(Error); - hash.Add(Success); - return hash.ToHashCode(); - } - - public IEnumerable Validate(ValidationContext validationContext) - { - yield return Validation.NotNullOrWhiteSpace(Id); - if (Success) - { - yield return Validation.Null(Error); - } - else - { - yield return Validation.NotNull(Error); - } - } -} \ No newline at end of file diff --git a/Proxytrace.Domain/Message/UserMessage.cs b/Proxytrace.Domain/Message/UserMessage.cs deleted file mode 100644 index 7a192cb58..000000000 --- a/Proxytrace.Domain/Message/UserMessage.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Proxytrace.Domain.Message; - -/// -/// A message from the user to the agent. -/// -public sealed record UserMessage : Message -{ - /// - /// Initializes a new instance of the class with the specified contents. - /// - /// The contents of the user message. - public UserMessage(IReadOnlyList contents) : base(Role.User, contents) - { - } - - /// - /// adds additional content to the message and returns a new instance. - /// - public UserMessage Add(Content content) - => new ([..Contents, content]); - - public override string ToString() - => base.ToString(); -} \ No newline at end of file diff --git a/Proxytrace.Domain/Model/IModel.cs b/Proxytrace.Domain/Model/IModel.cs index 47a9e9930..53bb4f6aa 100644 --- a/Proxytrace.Domain/Model/IModel.cs +++ b/Proxytrace.Domain/Model/IModel.cs @@ -15,4 +15,4 @@ public interface IModel : IDomainEntity /// Factory delegate for reconstituting an existing model from persistence. public delegate IModel CreateExisting(string name, IDomainEntityData existing); -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Model/IModelRepository.cs b/Proxytrace.Domain/Model/IModelRepository.cs index 4a9929907..6802941e2 100644 --- a/Proxytrace.Domain/Model/IModelRepository.cs +++ b/Proxytrace.Domain/Model/IModelRepository.cs @@ -3,4 +3,4 @@ namespace Proxytrace.Domain.Model; public interface IModelRepository : IRepository { Task GetOrCreateAsync(string name, CancellationToken cancellationToken = default); -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/ModelEndpoint/AgentExtensions.cs b/Proxytrace.Domain/ModelEndpoint/AgentExtensions.cs index 8f50d3897..6f634c5e4 100644 --- a/Proxytrace.Domain/ModelEndpoint/AgentExtensions.cs +++ b/Proxytrace.Domain/ModelEndpoint/AgentExtensions.cs @@ -1,5 +1,6 @@ -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Clients; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; namespace Proxytrace.Domain.ModelEndpoint; @@ -38,4 +39,4 @@ public static Task CompleteAsync( cancellationToken); return completion.Response; } -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/ModelEndpoint/IModelClient.cs b/Proxytrace.Domain/ModelEndpoint/IModelClient.cs deleted file mode 100644 index b6274be48..000000000 --- a/Proxytrace.Domain/ModelEndpoint/IModelClient.cs +++ /dev/null @@ -1,141 +0,0 @@ -using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; -using Proxytrace.Domain.Model; -using Proxytrace.Domain.Tools; -using Proxytrace.Domain.Usage; - -namespace Proxytrace.Domain.ModelEndpoint; - -public record ModelOptions( - string ModelName, - IReadOnlyList Tools, - ModelSamplingParameters? Sampling = null) -{ - - public static ModelOptions FromModel(IModel model) - => new( - ModelName: model.Name, - Tools: []); - - public static ModelOptions FromAgent(IAgent agent, IModel model) - => new( - ModelName: model.Name, - Tools: agent.Tools); -} - -/// -/// Per-request sampling overrides. Every member is optional; a leaves the -/// provider's own default in place rather than sending a value. -/// -/// -/// -/// Added because the playground offered these controls, mapped them through its request DTO, and -/// then had nowhere to put them — carried only the model name and tools, -/// so every one of them was dropped before the request left the process. Changing temperature in the -/// playground did nothing at all, silently. -/// -/// -/// Support is per-provider: an OpenAI-compatible backend may reject or ignore individual fields -/// (reasoning models, for instance, refuse temperature). Nothing is validated here — the -/// provider's own error is the honest answer, and it now actually reaches the user instead of the -/// value being discarded locally. -/// -/// -/// There is deliberately no choice-count (n) member. It can be put on the wire — the OpenAI -/// SDK's JsonPatch escape hatch reaches fields it exposes no property for — but nothing -/// downstream can use the answer: StreamingChatCompletionUpdate carries no choice index, so -/// every completion's tokens arrive flattened into one indistinguishable stream. Asking for more -/// than one completion would bill for N and render them interleaved into a single garbled message, -/// so the parameter is not offered at all. See issue #496. -/// -/// -public record ModelSamplingParameters( - double? Temperature = null, - double? TopP = null, - double? FrequencyPenalty = null, - double? PresencePenalty = null, - int? MaxOutputTokens = null, - long? Seed = null, - IReadOnlyList? StopSequences = null, - string? ReasoningEffort = null) -{ - /// True when no override is set, so the request carries the provider's defaults. - public bool IsEmpty - => Temperature is null - && TopP is null - && FrequencyPenalty is null - && PresencePenalty is null - && MaxOutputTokens is null - && Seed is null - && (StopSequences is null || StopSequences.Count == 0) - && string.IsNullOrWhiteSpace(ReasoningEffort); -} - -public record TypedCompletion(TOutput? Response, TokenUsage? Usage, TimeSpan Latency); - -/// -/// A read-only snapshot of the exact request a would send to the model: -/// the resolved model name, the messages (system prompt already merged in), and the tool definitions. -/// Built without contacting the provider, so it can be inspected for debugging. -/// -public record ModelRequestPreview( - string Model, - IReadOnlyList Messages, - IReadOnlyList Tools); - -public record RequestMessagePreview( - string Role, - string? Content, - IReadOnlyList ToolCalls, - string? ToolCallId); - -public record RequestToolCallPreview(string Id, string Name, string Arguments); - -public record RequestToolPreview(string Name, string Description, string JsonSchema); - -/// -/// A model client bound to a single agent + endpoint. Implementations own a disposable provider -/// transport, so callers MUST dispose the client (a using) once done — it is created per -/// call via and its underlying chat-client transport is not freed for them. -/// -public interface IModelClient : IDisposable -{ - public delegate IModelClient Factory( - IAgent agent, - IModelEndpoint? customEndpoint = null, - bool skipIngestion = false); - - Task CompleteAsync( - Conversation conversation, - ModelOptions? options = null, - IReadOnlyDictionary? promptVariables = null, - CancellationToken cancellationToken = default); - - Task> CompleteAsync( - Conversation conversation, - ModelOptions? options = null, - IReadOnlyDictionary? promptVariables = null, - CancellationToken cancellationToken = default); - - /// - /// Reconstructs the request that - /// would send — model, messages (with the agent's system prompt merged in), and tools — without - /// contacting the provider. Defaults the tools to the agent's toolset, mirroring a real run. - /// - ModelRequestPreview BuildRequestPreview( - Conversation conversation, - ModelOptions? options = null, - IReadOnlyDictionary? promptVariables = null); - - /// - /// Streams a single completion turn. Caller supplies the system message explicitly so the - /// agent's stored system prompt can be overridden (Playground use case). - /// Always runs with skipIngestion behaviour (no IAgentCall recorded). - /// - IAsyncEnumerable StreamAsync( - SystemMessage systemMessage, - Conversation conversation, - ModelOptions? options = null, - CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/Proxytrace.Domain/ModelEndpoint/IModelEndpoint.cs b/Proxytrace.Domain/ModelEndpoint/IModelEndpoint.cs index 19821920e..8bc6cb7aa 100644 --- a/Proxytrace.Domain/ModelEndpoint/IModelEndpoint.cs +++ b/Proxytrace.Domain/ModelEndpoint/IModelEndpoint.cs @@ -1,6 +1,6 @@ using Proxytrace.Domain.Model; using Proxytrace.Domain.ModelProvider; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; namespace Proxytrace.Domain.ModelEndpoint; @@ -57,4 +57,4 @@ public delegate IModelEndpoint CreateExisting( /// Calculates cost associated with this endpoint for a given usage /// decimal? CalculateCost(TokenUsage usage); -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/ModelEndpoint/Internal/ModelEndpoint.cs b/Proxytrace.Domain/ModelEndpoint/Internal/ModelEndpoint.cs index 0a48e2cf5..77f579a31 100644 --- a/Proxytrace.Domain/ModelEndpoint/Internal/ModelEndpoint.cs +++ b/Proxytrace.Domain/ModelEndpoint/Internal/ModelEndpoint.cs @@ -3,7 +3,7 @@ using Nordstein.Core.Domain; using Proxytrace.Domain.Model; using Proxytrace.Domain.ModelProvider; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; namespace Proxytrace.Domain.ModelEndpoint.Internal; diff --git a/Proxytrace.Domain/ModelEndpoint/ModelClientFactory.cs b/Proxytrace.Domain/ModelEndpoint/ModelClientFactory.cs new file mode 100644 index 000000000..2464be96e --- /dev/null +++ b/Proxytrace.Domain/ModelEndpoint/ModelClientFactory.cs @@ -0,0 +1,13 @@ +using Nordstein.Core.AI.Clients; + +namespace Proxytrace.Domain.ModelEndpoint; + +/// +/// Creates a bound to an agent and endpoint. Product-side seam: +/// carries the concerns the generic contract deliberately omits — the agent binding, an +/// endpoint override, and whether the call is recorded as a trace. +/// +public delegate IModelClient ModelClientFactory( + Agent.IAgent agent, + IModelEndpoint? customEndpoint = null, + bool skipIngestion = false); diff --git a/Proxytrace.Domain/ModelEndpoint/ModelOptionsFactory.cs b/Proxytrace.Domain/ModelEndpoint/ModelOptionsFactory.cs new file mode 100644 index 000000000..aa670dc4c --- /dev/null +++ b/Proxytrace.Domain/ModelEndpoint/ModelOptionsFactory.cs @@ -0,0 +1,19 @@ +using Nordstein.Core.AI.Clients; +using Proxytrace.Domain.Model; + +namespace Proxytrace.Domain.ModelEndpoint; + +/// +/// Builds from product model/agent bindings — the product-side +/// replacement for the statics that could not move to Core (they reference ). +/// +public static class ModelOptionsFactory +{ + /// Options for a bare model completion: the model's name, no tools. + public static ModelOptions FromModel(IModel model) + => new(ModelName: model.Name, Tools: []); + + /// Options for an agent completion: the model's name and the agent's tools. + public static ModelOptions FromAgent(Agent.IAgent agent, IModel model) + => new(ModelName: model.Name, Tools: agent.Tools); +} diff --git a/Proxytrace.Domain/ModelEndpoint/ModelStreamUpdate.cs b/Proxytrace.Domain/ModelEndpoint/ModelStreamUpdate.cs deleted file mode 100644 index 6c27d7770..000000000 --- a/Proxytrace.Domain/ModelEndpoint/ModelStreamUpdate.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Proxytrace.Domain.Message; -using Proxytrace.Domain.Usage; - -namespace Proxytrace.Domain.ModelEndpoint; - -/// -/// A streaming chunk from . -/// One of: , , . -/// -public abstract record ModelStreamUpdate; - -public sealed record TextDelta(string Text) : ModelStreamUpdate; - -public sealed record ToolRequested(ToolRequest Request) : ModelStreamUpdate; - -public sealed record Completed( - TokenUsage? Usage, - TimeSpan Latency, - string? FinishReason) : ModelStreamUpdate; diff --git a/Proxytrace.Domain/ModelProvider/IModelProvider.cs b/Proxytrace.Domain/ModelProvider/IModelProvider.cs index 1de8cf175..fe884b56c 100644 --- a/Proxytrace.Domain/ModelProvider/IModelProvider.cs +++ b/Proxytrace.Domain/ModelProvider/IModelProvider.cs @@ -44,4 +44,4 @@ public delegate IModelProvider CreateExisting( IDomainEntityData existing); IProviderClient CreateClient(); -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/ModelProvider/ModelProviderKind.cs b/Proxytrace.Domain/ModelProvider/ModelProviderKind.cs index 29aae8963..8796f6f34 100644 --- a/Proxytrace.Domain/ModelProvider/ModelProviderKind.cs +++ b/Proxytrace.Domain/ModelProvider/ModelProviderKind.cs @@ -8,4 +8,4 @@ public enum ModelProviderKind Unknown = 0, OpenAi = 2, OpenAiCompatible = 3, -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Module.cs b/Proxytrace.Domain/Module.cs index 76338bcc3..3b5019a02 100644 --- a/Proxytrace.Domain/Module.cs +++ b/Proxytrace.Domain/Module.cs @@ -4,15 +4,15 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentVersion; using Proxytrace.Domain.AgentVersion.Internal; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; using Proxytrace.Domain.Evaluator.Internal; -using Proxytrace.Domain.Message.Internal; +using Nordstein.Core.AI.Messages.Internal; using Proxytrace.Domain.OptimizationProposal.Internal; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.Prompt.Internal; -using Proxytrace.Domain.Tools.Internal; namespace Proxytrace.Domain; @@ -34,14 +34,7 @@ protected override void Load(ContainerBuilder builder) builder.Properties[RegisteredKey] = true; builder.RegisterModule(new Nordstein.Core.Domain.Module(typeof(Module).Assembly)); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); + builder.RegisterModule(); builder.RegisterType() .AsImplementedInterfaces(); diff --git a/Proxytrace.Domain/OptimizationProposal/IToolUpdateProposal.cs b/Proxytrace.Domain/OptimizationProposal/IToolUpdateProposal.cs index 0e454387d..5b5ea67e9 100644 --- a/Proxytrace.Domain/OptimizationProposal/IToolUpdateProposal.cs +++ b/Proxytrace.Domain/OptimizationProposal/IToolUpdateProposal.cs @@ -1,7 +1,7 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestRun; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Domain.OptimizationProposal; diff --git a/Proxytrace.Domain/OptimizationProposal/Internal/ToolUpdateProposal.cs b/Proxytrace.Domain/OptimizationProposal/Internal/ToolUpdateProposal.cs index 2572bf93f..d1ae734fc 100644 --- a/Proxytrace.Domain/OptimizationProposal/Internal/ToolUpdateProposal.cs +++ b/Proxytrace.Domain/OptimizationProposal/Internal/ToolUpdateProposal.cs @@ -4,7 +4,7 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestRun; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Domain.OptimizationProposal.Internal; diff --git a/Proxytrace.Domain/OptimizationTheory/IToolUpdateTheory.cs b/Proxytrace.Domain/OptimizationTheory/IToolUpdateTheory.cs index 837f46169..5f1daba99 100644 --- a/Proxytrace.Domain/OptimizationTheory/IToolUpdateTheory.cs +++ b/Proxytrace.Domain/OptimizationTheory/IToolUpdateTheory.cs @@ -1,7 +1,7 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Domain.OptimizationTheory; diff --git a/Proxytrace.Domain/OptimizationTheory/Internal/ToolUpdateTheory.cs b/Proxytrace.Domain/OptimizationTheory/Internal/ToolUpdateTheory.cs index ff9a1251c..8f7fd282f 100644 --- a/Proxytrace.Domain/OptimizationTheory/Internal/ToolUpdateTheory.cs +++ b/Proxytrace.Domain/OptimizationTheory/Internal/ToolUpdateTheory.cs @@ -6,7 +6,7 @@ using Proxytrace.Domain.OptimizationProposal; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Domain.OptimizationTheory.Internal; diff --git a/Proxytrace.Domain/Prompt/IPrompt.cs b/Proxytrace.Domain/Prompt/IPrompt.cs deleted file mode 100644 index 00481c08d..000000000 --- a/Proxytrace.Domain/Prompt/IPrompt.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Diagnostics.Contracts; - -namespace Proxytrace.Domain.Prompt; - -/// -/// A prompt that can be sent to a language model. -/// It is rendered from a and a set of values. -/// -public interface IPrompt : IDomainObject -{ - /// - /// The template from which this prompt was rendered. - /// - string Name { get; } - - /// - /// Appends the prompt to this prompt, creating a new prompt that combines the templates and values of both prompts. - /// - [Pure] - IPrompt Append(IPrompt other); - - /// - /// Gets the rendered prompt string that can be sent to a language model. - /// - string ToPromptString(); -} \ No newline at end of file diff --git a/Proxytrace.Domain/Prompt/IPromptTemplate.cs b/Proxytrace.Domain/Prompt/IPromptTemplate.cs deleted file mode 100644 index 8c5007995..000000000 --- a/Proxytrace.Domain/Prompt/IPromptTemplate.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Proxytrace.Domain.Prompt; - -/// -/// A template for a LLM prompt that can be rendered with a set of values. -/// -public interface IPromptTemplate : IDomainObject -{ - public delegate IPromptTemplate Create(string name, string template); - - /// - /// The name of the prompt template. - /// - public string Name { get; } - - /// - /// The template string with variables in {curly braces}. - /// - string Template { get; } - - /// - /// The names of the variables in the template. - /// - IReadOnlyCollection Variables { get; } - - /// - /// Replaces the variables in the template with the given - /// and returns a rendered . - /// - IPrompt Render(IReadOnlyDictionary? values = null); -} \ No newline at end of file diff --git a/Proxytrace.Domain/Prompt/IPromptTemplateRepository.cs b/Proxytrace.Domain/Prompt/IPromptTemplateRepository.cs index e65c04997..c65380042 100644 --- a/Proxytrace.Domain/Prompt/IPromptTemplateRepository.cs +++ b/Proxytrace.Domain/Prompt/IPromptTemplateRepository.cs @@ -1,4 +1,5 @@ -namespace Proxytrace.Domain.Prompt; +using Nordstein.Core.AI.Prompts; +namespace Proxytrace.Domain.Prompt; /// /// Repository for @@ -21,4 +22,4 @@ public class PromptNotFoundException : Exception { public PromptNotFoundException(string name) : base($"Prompt with name '{name}' not found.") { } -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Prompt/Internal/Prompt.cs b/Proxytrace.Domain/Prompt/Internal/Prompt.cs deleted file mode 100644 index 507f0324e..000000000 --- a/Proxytrace.Domain/Prompt/Internal/Prompt.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Diagnostics.Contracts; -using Nordstein.Core.Common.Validation; - -namespace Proxytrace.Domain.Prompt.Internal; - -/// -internal record Prompt : IPrompt -{ - /// - public string Name { get; } - - /// - /// The rendered prompt string. - /// - private readonly string promptString; - - public Prompt( - string name, - string promptString) - { - // Routes through the framework guard: ArgumentNullException for null, ArgumentException for - // a non-null but empty/whitespace value (the previous ArgumentNullException was the wrong - // type for whitespace). - ArgumentException.ThrowIfNullOrWhiteSpace(name); - ArgumentException.ThrowIfNullOrWhiteSpace(promptString); - - - Name = name; - this.promptString = promptString; - } - - /// - public string ToPromptString() - => promptString; - - /// - [Pure] - public IPrompt Append(IPrompt other) - => new Prompt( - name: $"{Name}_{other.Name}", - promptString: string.Join(Environment.NewLine, ToPromptString(), other.ToPromptString())); - - public IEnumerable Validate(ValidationContext validationContext) - { - yield return Validation.NotNullOrWhiteSpace(Name); - yield return Validation.NotNullOrWhiteSpace(promptString); - } -} diff --git a/Proxytrace.Domain/Prompt/Internal/PromptGenerator.cs b/Proxytrace.Domain/Prompt/Internal/PromptGenerator.cs deleted file mode 100644 index d6ed07f3e..000000000 --- a/Proxytrace.Domain/Prompt/Internal/PromptGenerator.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; - -namespace Proxytrace.Domain.Prompt.Internal; - -internal sealed class PromptGenerator : DomainObjectGenerator -{ - private readonly IDomainObjectGenerator templateGenerator; - - public PromptGenerator( - IRandom random, - IDomainObjectGenerator templateGenerator) : base(random) - { - this.templateGenerator = templateGenerator; - } - - public override async Task CreateAsync(CancellationToken cancellationToken = default) - { - IPromptTemplate template = await templateGenerator.CreateAsync(cancellationToken); - return template.Render(); - } -} diff --git a/Proxytrace.Domain/Prompt/Internal/PromptTemplate.cs b/Proxytrace.Domain/Prompt/Internal/PromptTemplate.cs deleted file mode 100644 index 153e0bd74..000000000 --- a/Proxytrace.Domain/Prompt/Internal/PromptTemplate.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Text.RegularExpressions; -using Nordstein.Core.Common.Validation; - -namespace Proxytrace.Domain.Prompt.Internal; - -/// -internal record PromptTemplate : IPromptTemplate -{ - private static readonly Regex VariableRegex = new(@"\{\{([a-zA-Z0-9_-]+)\}\}", RegexOptions.Compiled); - - /// - public string Name { get; } - - /// - public string Template { get; } - - /// - public IReadOnlyCollection Variables { get; } - - public PromptTemplate( - string name, - string template) - { - Name = name; - Template = template; - Variables = ExtractVariables(template); - } - - /// - public IPrompt Render(IReadOnlyDictionary? values = null) - { - this.Validate(); - string renderedTemplate = Template; - values ??= new Dictionary(); - - // Replace each variable with its value - foreach (string variable in Variables) - { - if (!values.TryGetValue(variable, out string? value)) - { - throw new ArgumentException($"Value for variable '{variable}' was not provided."); - } - renderedTemplate = renderedTemplate.Replace($"{{{{{variable}}}}}", value); - } - - var prompt = new Prompt(Name, renderedTemplate); - prompt.Validate(); - return prompt; - } - - private IReadOnlyCollection ExtractVariables(string template) - { - HashSet variables = []; - foreach (Match match in VariableRegex.Matches(template)) - { - string variableName = match.Groups[1].Value; - variables.Add(variableName); - } - return variables; - } - - /// - public virtual bool Equals(PromptTemplate? other) - => other is not null - && Name == other.Name - && Template == other.Template; - - /// - public override int GetHashCode() - // Name + Template fully determine the template (Variables is derived from Template), so - // they alone define value equality. The synthesized record members would compare the - // Variables HashSet by reference, making two identically-constructed templates unequal — - // which silently disabled every "has the prompt actually changed?" dedup check - // (e.g. Agent.ChangeSystemMessage). - => HashCode.Combine(Name, Template); - - public IEnumerable Validate(ValidationContext validationContext) - { - yield return Validation.NotNullOrWhiteSpace(Name); - yield return Validation.NotNullOrWhiteSpace(Template); - foreach (var variable in Variables) - { - yield return Validation.NotNullOrWhiteSpace(variable); - // Require at least one letter (rejects purely numeric/underscore names like {{1}}); - // single-letter names such as {{x}} are valid and must pass. - var letterCount = variable.Count(char.IsLetter); - yield return Validation.GreaterThan(letterCount, 0); - } - } -} \ No newline at end of file diff --git a/Proxytrace.Domain/Prompt/Internal/PromptTemplateGenerator.cs b/Proxytrace.Domain/Prompt/Internal/PromptTemplateGenerator.cs deleted file mode 100644 index b3f5e9efa..000000000 --- a/Proxytrace.Domain/Prompt/Internal/PromptTemplateGenerator.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Nordstein.Core.Common.Async; -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; - -namespace Proxytrace.Domain.Prompt.Internal; - -internal sealed class PromptTemplateGenerator : DomainObjectGenerator -{ - private readonly IPromptTemplate.Create factory; - - public PromptTemplateGenerator( - IRandom random, - IPromptTemplate.Create factory) : base(random) - { - this.factory = factory; - } - - public override Task CreateAsync(CancellationToken cancellationToken = default) - => factory(name: random.String(), template: random.String()) - .ToTaskResult(); -} diff --git a/Proxytrace.Domain/Prompt/Internal/ResourcesPromptRepository.cs b/Proxytrace.Domain/Prompt/Internal/ResourcesPromptRepository.cs index b2f6e8d7c..6ff5db22c 100644 --- a/Proxytrace.Domain/Prompt/Internal/ResourcesPromptRepository.cs +++ b/Proxytrace.Domain/Prompt/Internal/ResourcesPromptRepository.cs @@ -1,4 +1,5 @@ -using System.Resources; +using Nordstein.Core.AI.Prompts; +using System.Resources; using Nordstein.Core.Common.Validation; namespace Proxytrace.Domain.Prompt.Internal; @@ -9,10 +10,14 @@ namespace Proxytrace.Domain.Prompt.Internal; internal class ResourcesPromptRepository : IPromptTemplateRepository { private readonly IReadOnlyCollection resources; + private readonly IPromptTemplate.Create createTemplate; - public ResourcesPromptRepository(IReadOnlyCollection resources) + public ResourcesPromptRepository( + IReadOnlyCollection resources, + IPromptTemplate.Create createTemplate) { this.resources = resources; + this.createTemplate = createTemplate; } /// @@ -40,8 +45,8 @@ public async Task GetAsync(string name, CancellationToken cance return Task.FromResult(null); } - IPromptTemplate template = new PromptTemplate(name, templateContent); + IPromptTemplate template = createTemplate(name, templateContent); template.Validate(); return Task.FromResult(template); } -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Proposal/IProposal.cs b/Proxytrace.Domain/Proposal/IProposal.cs index acc9d14db..9bd30baa3 100644 --- a/Proxytrace.Domain/Proposal/IProposal.cs +++ b/Proxytrace.Domain/Proposal/IProposal.cs @@ -6,4 +6,4 @@ public interface IProposal : IDomainEntity Priority Priority { get; } string Description { get; } -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Proposal/OptimizationContentHash.cs b/Proxytrace.Domain/Proposal/OptimizationContentHash.cs index d76edbd31..e3e051690 100644 --- a/Proxytrace.Domain/Proposal/OptimizationContentHash.cs +++ b/Proxytrace.Domain/Proposal/OptimizationContentHash.cs @@ -2,7 +2,7 @@ using System.Text; using Nordstein.Core.Common.Serialization; using Proxytrace.Domain.OptimizationProposal; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Domain.Proposal; diff --git a/Proxytrace.Domain/Proposal/Priority.cs b/Proxytrace.Domain/Proposal/Priority.cs index 2781f0a21..4af7d730f 100644 --- a/Proxytrace.Domain/Proposal/Priority.cs +++ b/Proxytrace.Domain/Proposal/Priority.cs @@ -6,4 +6,4 @@ public enum Priority Medium = 1, High = 2, Critical = 3, -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Proxytrace.Domain.csproj b/Proxytrace.Domain/Proxytrace.Domain.csproj index 4642cf113..41a99c305 100644 --- a/Proxytrace.Domain/Proxytrace.Domain.csproj +++ b/Proxytrace.Domain/Proxytrace.Domain.csproj @@ -21,6 +21,7 @@ + diff --git a/Proxytrace.Domain/Search/ISearchable.cs b/Proxytrace.Domain/Search/ISearchable.cs index 780efce46..29fb7d889 100644 --- a/Proxytrace.Domain/Search/ISearchable.cs +++ b/Proxytrace.Domain/Search/ISearchable.cs @@ -3,4 +3,4 @@ namespace Proxytrace.Domain.Search; public interface ISearchable : IProjectSpecific { SearchKind SearchKind { get; } -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Statistics/StatisticsBucket.cs b/Proxytrace.Domain/Statistics/StatisticsBucket.cs index 39a887db1..41c6bd5ea 100644 --- a/Proxytrace.Domain/Statistics/StatisticsBucket.cs +++ b/Proxytrace.Domain/Statistics/StatisticsBucket.cs @@ -116,4 +116,4 @@ public static long BucketCount(this StatisticsBucket bucket, DateTimeOffset from long lastIndex = (long)Math.Floor(to.ToUniversalTime().ToUnixTimeMilliseconds() / width); return lastIndex - firstIndex + 1; } -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/Statistics/StatisticsRecords.cs b/Proxytrace.Domain/Statistics/StatisticsRecords.cs index 82c93c4f2..90cb07f16 100644 --- a/Proxytrace.Domain/Statistics/StatisticsRecords.cs +++ b/Proxytrace.Domain/Statistics/StatisticsRecords.cs @@ -1,6 +1,6 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; namespace Proxytrace.Domain.Statistics; diff --git a/Proxytrace.Domain/Statistics/TestRun/TestRunStats.cs b/Proxytrace.Domain/Statistics/TestRun/TestRunStats.cs index 0c61513fd..efcf6cd52 100644 --- a/Proxytrace.Domain/Statistics/TestRun/TestRunStats.cs +++ b/Proxytrace.Domain/Statistics/TestRun/TestRunStats.cs @@ -1,4 +1,4 @@ -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; namespace Proxytrace.Domain.Statistics.TestRun; @@ -32,4 +32,4 @@ public record Filter( IReadOnlyCollection? SuiteIds = null, DateTimeOffset? From = null, DateTimeOffset? To = null); -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/TestCase/ITestCase.cs b/Proxytrace.Domain/TestCase/ITestCase.cs index 5bce392f8..8e24a902d 100644 --- a/Proxytrace.Domain/TestCase/ITestCase.cs +++ b/Proxytrace.Domain/TestCase/ITestCase.cs @@ -1,5 +1,5 @@ using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; namespace Proxytrace.Domain.TestCase; diff --git a/Proxytrace.Domain/TestCase/Internal/TestCase.cs b/Proxytrace.Domain/TestCase/Internal/TestCase.cs index 1333c3a44..5a283d189 100644 --- a/Proxytrace.Domain/TestCase/Internal/TestCase.cs +++ b/Proxytrace.Domain/TestCase/Internal/TestCase.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using Proxytrace.Domain.AgentCall; using Nordstein.Core.Domain; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; namespace Proxytrace.Domain.TestCase.Internal; diff --git a/Proxytrace.Domain/TestCase/Internal/TestCaseGenerator.cs b/Proxytrace.Domain/TestCase/Internal/TestCaseGenerator.cs index ffda4d67f..af8b7072b 100644 --- a/Proxytrace.Domain/TestCase/Internal/TestCaseGenerator.cs +++ b/Proxytrace.Domain/TestCase/Internal/TestCaseGenerator.cs @@ -1,6 +1,6 @@ using Nordstein.Core.Common.Random; using Nordstein.Core.Domain; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; namespace Proxytrace.Domain.TestCase.Internal; diff --git a/Proxytrace.Domain/TestResult/ITestResult.cs b/Proxytrace.Domain/TestResult/ITestResult.cs index ce0821638..c5b821817 100644 --- a/Proxytrace.Domain/TestResult/ITestResult.cs +++ b/Proxytrace.Domain/TestResult/ITestResult.cs @@ -1,8 +1,7 @@ -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Evaluation; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.TestCase; -using Proxytrace.Domain.Usage; namespace Proxytrace.Domain.TestResult; diff --git a/Proxytrace.Domain/TestResult/ITestResultGenerator.cs b/Proxytrace.Domain/TestResult/ITestResultGenerator.cs index ce3d62ac0..163cf7e4d 100644 --- a/Proxytrace.Domain/TestResult/ITestResultGenerator.cs +++ b/Proxytrace.Domain/TestResult/ITestResultGenerator.cs @@ -5,4 +5,4 @@ namespace Proxytrace.Domain.TestResult; public interface ITestResultGenerator : IDomainEntityGenerator { Task CreateAsync(ITestCase testCase, CancellationToken cancellationToken = default); -} \ No newline at end of file +} diff --git a/Proxytrace.Domain/TestResult/Internal/TestResult.cs b/Proxytrace.Domain/TestResult/Internal/TestResult.cs index 5e8714ea8..3226a0858 100644 --- a/Proxytrace.Domain/TestResult/Internal/TestResult.cs +++ b/Proxytrace.Domain/TestResult/Internal/TestResult.cs @@ -1,10 +1,9 @@ using System.ComponentModel.DataAnnotations; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Evaluation; using Nordstein.Core.Domain; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.TestCase; -using Proxytrace.Domain.Usage; namespace Proxytrace.Domain.TestResult.Internal; diff --git a/Proxytrace.Domain/TestResult/Internal/TestResultGenerator.cs b/Proxytrace.Domain/TestResult/Internal/TestResultGenerator.cs index 225d4f58c..d59f9bf79 100644 --- a/Proxytrace.Domain/TestResult/Internal/TestResultGenerator.cs +++ b/Proxytrace.Domain/TestResult/Internal/TestResultGenerator.cs @@ -1,6 +1,6 @@ using Nordstein.Core.Common.Async; using Nordstein.Core.Common.Random; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Evaluation; using Nordstein.Core.Domain; using Proxytrace.Domain.TestCase; diff --git a/Proxytrace.Domain/TestRun/TestRunTotals.cs b/Proxytrace.Domain/TestRun/TestRunTotals.cs index 761f7d577..95f60d554 100644 --- a/Proxytrace.Domain/TestRun/TestRunTotals.cs +++ b/Proxytrace.Domain/TestRun/TestRunTotals.cs @@ -1,5 +1,5 @@ using Proxytrace.Domain.TestResult; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; namespace Proxytrace.Domain.TestRun; diff --git a/Proxytrace.Domain/Tools/IToolArgument.cs b/Proxytrace.Domain/Tools/IToolArgument.cs deleted file mode 100644 index 0686f44e9..000000000 --- a/Proxytrace.Domain/Tools/IToolArgument.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace Proxytrace.Domain.Tools; - -/// -/// A tool argument -/// -public interface IToolArgument : IDomainObject -{ - /// - /// The name of the argument. - /// - string Name { get; } - - /// - /// The description of the argument. - /// - string? Description { get; } - - /// - /// Whether the argument is required. - /// - bool IsRequired { get; } - - /// - /// The type of the argument. - /// - Type Type { get; } - - /// - /// The default value of the argument, if any. - /// - public object? DefaultValue { get; } - - /// - /// The JSON schema definition of the argument. - /// - string JsonSchema { get; } -} \ No newline at end of file diff --git a/Proxytrace.Domain/Tools/Internal/ToolArgumentGenerator.cs b/Proxytrace.Domain/Tools/Internal/ToolArgumentGenerator.cs deleted file mode 100644 index 3a3b3502d..000000000 --- a/Proxytrace.Domain/Tools/Internal/ToolArgumentGenerator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Text.Json; -using Nordstein.Core.Common.Async; -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; - -namespace Proxytrace.Domain.Tools.Internal; - -internal class ToolArgumentGenerator : DomainObjectGenerator -{ - public ToolArgumentGenerator(IRandom random) : base(random) - { - } - - public override Task CreateAsync(CancellationToken cancellationToken = default) - { - var schema = JsonSerializer.SerializeToElement(new - { - type = "string", - description = random.String() - }); - return ((IToolArgument)new JsonToolArgument( - name: random.String(), - isRequired: random.Bool(), - json: schema)) - .ToTaskResult(); - } -} diff --git a/Proxytrace.Domain/Tools/Internal/ToolArgumentsGenerator.cs b/Proxytrace.Domain/Tools/Internal/ToolArgumentsGenerator.cs deleted file mode 100644 index 27e5b3fde..000000000 --- a/Proxytrace.Domain/Tools/Internal/ToolArgumentsGenerator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Nordstein.Core.Common.Async; -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; - -namespace Proxytrace.Domain.Tools.Internal; - -internal class ToolArgumentsGenerator : DomainObjectGenerator -{ - public ToolArgumentsGenerator(IRandom random) : base(random) - { - } - - public override Task CreateAsync(CancellationToken cancellationToken = default) - => ToolArguments.None.ToTaskResult(); -} diff --git a/Proxytrace.Domain/Tools/Internal/ToolArgumentsJsonConverter.cs b/Proxytrace.Domain/Tools/Internal/ToolArgumentsJsonConverter.cs deleted file mode 100644 index 7b467ccfb..000000000 --- a/Proxytrace.Domain/Tools/Internal/ToolArgumentsJsonConverter.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using JetBrains.Annotations; - -namespace Proxytrace.Domain.Tools.Internal; - -/// -/// Serialises as a JSON Schema object so that tool definitions -/// round-trip cleanly through the storage layer without exposing the internal IToolArgument -/// type hierarchy (which contains a System.Type property that System.Text.Json cannot handle). -/// -[UsedImplicitly] -internal sealed class ToolArgumentsJsonConverter : JsonConverter -{ - public override ToolArguments Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - using var doc = JsonDocument.ParseValue(ref reader); - return ToolArguments.FromJsonSchema(doc.RootElement); - } - - public override void Write(Utf8JsonWriter writer, ToolArguments value, JsonSerializerOptions options) - { - using var doc = JsonDocument.Parse(value.JsonSchema); - doc.RootElement.WriteTo(writer); - } -} diff --git a/Proxytrace.Domain/Tools/Internal/ToolSpecificationGenerator.cs b/Proxytrace.Domain/Tools/Internal/ToolSpecificationGenerator.cs deleted file mode 100644 index 237c35162..000000000 --- a/Proxytrace.Domain/Tools/Internal/ToolSpecificationGenerator.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; - -namespace Proxytrace.Domain.Tools.Internal; - -internal class ToolSpecificationGenerator : DomainObjectGenerator -{ - private readonly IDomainObjectGenerator toolArgumentsGenerator; - - public ToolSpecificationGenerator( - IRandom random, - IDomainObjectGenerator toolArgumentsGenerator) : base(random) - { - this.toolArgumentsGenerator = toolArgumentsGenerator; - } - - public override async Task CreateAsync(CancellationToken cancellationToken = default) - { - var arguments = await toolArgumentsGenerator.CreateAsync(cancellationToken); - return new ToolSpecification( - name: random.String(), - description: random.String(), - arguments: arguments); - } -} diff --git a/Proxytrace.Domain/Tools/JsonToolArgument.cs b/Proxytrace.Domain/Tools/JsonToolArgument.cs deleted file mode 100644 index 2f82408cb..000000000 --- a/Proxytrace.Domain/Tools/JsonToolArgument.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Text.Json; -using Nordstein.Core.Common.Validation; - -namespace Proxytrace.Domain.Tools; - -/// -/// A tool argument defined by a JSON schema. -/// -internal sealed record JsonToolArgument : IToolArgument -{ - /// - public string Name { get; } - - /// - public bool IsRequired { get; } - - /// - public Type Type - => typeof(object); - - /// - public string? Description { get; } - - /// - public object? DefaultValue - => null; - - /// - public string JsonSchema { get; } - - /// - /// Creates a new instance of - /// - public JsonToolArgument(string name, bool isRequired, JsonElement json) - { - Name = name; - IsRequired = isRequired; - JsonSchema = json.GetRawText(); - - if (json.TryGetProperty("description", out JsonElement descriptionElement) - && descriptionElement.ValueKind == JsonValueKind.String) - { - Description = descriptionElement.GetString(); - } - } - - /// - public IEnumerable Validate(ValidationContext validationContext) - { - yield return Validation.NotNullOrWhiteSpace(Name); - yield return Validation.NotNullOrWhiteSpace(JsonSchema); - yield return Validation.Json(JsonSchema); - } -} \ No newline at end of file diff --git a/Proxytrace.Domain/Tools/ToolArguments.cs b/Proxytrace.Domain/Tools/ToolArguments.cs deleted file mode 100644 index 46128d2d2..000000000 --- a/Proxytrace.Domain/Tools/ToolArguments.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System.Collections; -using System.ComponentModel.DataAnnotations; -using System.Text.Json; -using Nordstein.Core.Common.Validation; - -namespace Proxytrace.Domain.Tools -{ - /// - /// The arguments a tool accepts - /// - public sealed record ToolArguments : - IDomainObject, - IEnumerable - { - /// - /// No arguments - /// - public static readonly ToolArguments None - = new(); - - /// - /// The arguments the tool accepts - /// - public IReadOnlyList Arguments { get; } - - /// - /// The JSON schema describing the arguments the tool accepts - /// - public string JsonSchema { get; } - - /// - /// The number of arguments - /// - public int Count - => Arguments.Count; - - /// - /// Indexer to get argument metadata by index - /// - public IToolArgument this[int index] - => Arguments[index]; - - /// - /// Creates a new instance of - /// - /// - public ToolArguments(params IReadOnlyList arguments) - { - Arguments = arguments; - JsonSchema = ToJsonSchema(arguments); - } - - /// - public bool Equals(ToolArguments? other) - => other is not null - && JsonSchema == other.JsonSchema - && Arguments.SequenceEqual(other.Arguments); - - /// - public override int GetHashCode() - { - // Fold the arguments' elements (Equals uses SequenceEqual); hashing the list reference - // would give equal-content instances different hashes and break the - // Equals/GetHashCode contract — the same idiom as Message/Conversation/ModelParameters. - var hash = new HashCode(); - hash.Add(JsonSchema); - foreach (IToolArgument argument in Arguments) - { - hash.Add(argument); - } - return hash.ToHashCode(); - } - - /// - /// Generates a JSON schema from the provided tool argument metadata - /// - private static string ToJsonSchema(IReadOnlyCollection argumentMetadata) - { - // Create the properties object for the JSON schema - var properties = new Dictionary(); - var required = new List(); - - foreach (IToolArgument metadata in argumentMetadata) - { - // ignore CancellationToken parameters - if (metadata.Type == typeof(CancellationToken)) - { - continue; - } - - // Deserialize the JSON schema string to an object to avoid double-serialization - properties[metadata.Name] = JsonSerializer.Deserialize(metadata.JsonSchema); - - // Add to required list if the argument is required - if (metadata.IsRequired) - { - required.Add(metadata.Name); - } - } - - var schema = new - { - type = "object", - properties, - required = required.ToArray() - }; - - var schemaString = JsonSerializer.Serialize(schema, new JsonSerializerOptions - { - WriteIndented = true - }); - return schemaString; - } - - /// - public IEnumerable Validate(ValidationContext validationContext) - => Validation.Json(JsonSchema).AsEnumerable() - .Concat(Arguments.SelectMany(argument => argument.Validate(validationContext))); - - /// - public IEnumerator GetEnumerator() - => Arguments.GetEnumerator(); - - /// - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); - - /// - /// Parses a JSON schema string into a instance - /// - public static ToolArguments FromJsonSchema(string jsonSchemaString) - { - using JsonDocument document = JsonDocument.Parse(jsonSchemaString); - return FromJsonSchema(document.RootElement); - } - - /// - /// Parses a JSON schema into a instance - /// - public static ToolArguments FromJsonSchema(JsonElement jsonSchema) - { - // Get the properties object - if (!jsonSchema.TryGetProperty("properties", out JsonElement properties)) - { - return None; - } - - // Get the required array (if it exists) - var requiredFields = new HashSet(); - if (jsonSchema.TryGetProperty("required", out JsonElement required) - && required.ValueKind == JsonValueKind.Array) - { - foreach (JsonElement item in required.EnumerateArray()) - { - if (item.ValueKind == JsonValueKind.String) - { - requiredFields.Add(item.GetString() ?? string.Empty); - } - } - } - - IReadOnlyList arguments = properties.EnumerateObject().Select(property => - { - var name = property.Name; - var isRequired = requiredFields.Contains(name); - return new JsonToolArgument(name, isRequired, property.Value); - }).ToList(); - - ToolArguments result = new ToolArguments(arguments); - result.Validate(); - return result; - } - - } -} \ No newline at end of file diff --git a/Proxytrace.Domain/Tools/ToolSpecification.cs b/Proxytrace.Domain/Tools/ToolSpecification.cs deleted file mode 100644 index 8d41aa267..000000000 --- a/Proxytrace.Domain/Tools/ToolSpecification.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Nordstein.Core.Common.Validation; - -namespace Proxytrace.Domain.Tools; - -/// -/// Describes an available AI tool -/// -public sealed record ToolSpecification : IDomainObject -{ - /// - /// The name of the tool. Must be unique within the agent's toolset. - /// - public string Name { get; } - - /// - /// The description of the tool. This will be shown to the agent when deciding which tool to use. - /// - public string Description { get; } - - /// - /// The arguments the tool accepts - /// - public ToolArguments Arguments { get; } - - /// - /// Initializes a new instance of the class with the specified name, description, and arguments. - /// - public ToolSpecification(string name, string description, ToolArguments arguments) - { - Name = name; - Description = description; - Arguments = arguments; - } - - public IEnumerable Validate(ValidationContext validationContext) - { - yield return Validation.NotNullOrWhiteSpace(Name); - yield return Validation.NotNullOrWhiteSpace(Description); - foreach (var result in Arguments.Validate(validationContext)) - { - yield return result; - } - } -} \ No newline at end of file diff --git a/Proxytrace.Domain/Usage/Internal/TokenUsageGenerator.cs b/Proxytrace.Domain/Usage/Internal/TokenUsageGenerator.cs deleted file mode 100644 index 9b31c88c2..000000000 --- a/Proxytrace.Domain/Usage/Internal/TokenUsageGenerator.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Nordstein.Core.Common.Async; -using Nordstein.Core.Common.Random; -using Nordstein.Core.Domain; - -namespace Proxytrace.Domain.Usage.Internal; - -internal class TokenUsageGenerator : DomainObjectGenerator -{ - public TokenUsageGenerator(IRandom random) : base(random) - { - } - - public override Task CreateAsync(CancellationToken cancellationToken = default) - => new TokenUsage( - inputTokenCount: (ulong)random.Int(min: 0, max: 1000), - outputTokenCount: (ulong)random.Int(min: 0, max: 1000)) - .ToTaskResult(); -} diff --git a/Proxytrace.Domain/Usage/TokenUsage.cs b/Proxytrace.Domain/Usage/TokenUsage.cs deleted file mode 100644 index cdcd6473a..000000000 --- a/Proxytrace.Domain/Usage/TokenUsage.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Proxytrace.Domain.Usage; - -/// -/// Represents the token usage statistics for a language model interaction. -/// -public sealed record TokenUsage : IDomainObject -{ - /// - /// The number of input tokens (prompt) used. - /// - public ulong InputTokenCount { get; init; } - - /// - /// The number of output tokens (response) generated. - /// - public ulong OutputTokenCount { get; init; } - - /// - /// The number of input tokens served from the provider's cache. This is a subset of - /// (cached ≤ input), priced at the cheaper cached rate. - /// - public ulong CachedInputTokenCount { get; init; } - - /// - /// No tokens used - /// - public static TokenUsage None => new TokenUsage(0, 0); - - /// - /// Initializes a new instance of the class with zero input and output tokens. - /// - public TokenUsage() : this(inputTokenCount: 0,outputTokenCount: 0) - { - } - - /// - /// Initializes a new instance of the class with the specified input and output token counts. - /// - /// The number of input tokens. - /// The number of output tokens. - public TokenUsage(ulong inputTokenCount, ulong outputTokenCount) - : this(inputTokenCount, outputTokenCount, cachedInputTokenCount: 0) - { - } - - /// - /// Initializes a new instance of the class, including the cached subset - /// of the input tokens. - /// - /// The total number of input tokens (cached portion included). - /// The number of output tokens. - /// The cached subset of the input tokens. - public TokenUsage(ulong inputTokenCount, ulong outputTokenCount, ulong cachedInputTokenCount) - { - InputTokenCount = inputTokenCount; - OutputTokenCount = outputTokenCount; - CachedInputTokenCount = cachedInputTokenCount; - } - - /// - /// Overloads the - operator to subtract one TokenUsage from another, per token kind. - /// Counts are clamped at zero so a larger subtrahend can never wrap the unsigned result. - /// When either operand is unknown (null) the difference is unknown, so null propagates — - /// notably null - b must not surface b itself as the "difference". - /// - public static TokenUsage? operator -(TokenUsage? a, TokenUsage? b) - => a == null || b == null - ? null - : new( - a.InputTokenCount > b.InputTokenCount ? a.InputTokenCount - b.InputTokenCount : 0, - a.OutputTokenCount > b.OutputTokenCount ? a.OutputTokenCount - b.OutputTokenCount : 0, - a.CachedInputTokenCount > b.CachedInputTokenCount ? a.CachedInputTokenCount - b.CachedInputTokenCount : 0); - - /// - /// Overloads the + operator to add two TokenUsage instances. - /// - public static TokenUsage? operator +(TokenUsage? a, TokenUsage? b) - => a == null || b == null - ? a ?? b - : new( - a.InputTokenCount + b.InputTokenCount, - a.OutputTokenCount + b.OutputTokenCount, - a.CachedInputTokenCount + b.CachedInputTokenCount); - - public static TokenUsage? Create(ulong? inputTokenCount, ulong? outputTokenCount) - => Create(inputTokenCount, outputTokenCount, cachedInputTokenCount: 0); - - public static TokenUsage? Create(ulong? inputTokenCount, ulong? outputTokenCount, ulong? cachedInputTokenCount) - => inputTokenCount.HasValue && outputTokenCount.HasValue - ? new TokenUsage(inputTokenCount.Value, outputTokenCount.Value, cachedInputTokenCount ?? 0) - : null; - - /// - public IEnumerable Validate(ValidationContext validationContext) - { - yield break; - } -} \ No newline at end of file diff --git a/Proxytrace.Infrastructure.Tests/ModelClientTests.cs b/Proxytrace.Infrastructure.Tests/ModelClientTests.cs index b47179e71..0e0d53865 100644 --- a/Proxytrace.Infrastructure.Tests/ModelClientTests.cs +++ b/Proxytrace.Infrastructure.Tests/ModelClientTests.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using System.ClientModel; using System.ClientModel.Primitives; using System.Net; @@ -14,13 +15,13 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.Model; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Proxytrace.Infrastructure.Internal; -using Proxytrace.Serialization; +using Nordstein.Core.AI.Serialization; using Nordstein.Core.Testing; namespace Proxytrace.Infrastructure.Tests; @@ -1084,7 +1085,7 @@ public void Constructor_WithUnknownProviderKind_ThrowsNotSupportedException() { var services = GetServices(); var endpoint = MakeEndpoint(kind: ModelProviderKind.Unknown); - var factory = services.GetRequiredService(); + var factory = services.GetRequiredService(); FluentActions .Invoking(() => factory(MakeAgent(endpoint))) @@ -1096,7 +1097,7 @@ public void Constructor_WithOpenAiProviderKind_DoesNotThrow() { var services = GetServices(); var endpoint = MakeEndpoint(kind: ModelProviderKind.OpenAi); - var factory = services.GetRequiredService(); + var factory = services.GetRequiredService(); FluentActions .Invoking(() => factory(MakeAgent(endpoint))) @@ -1110,7 +1111,7 @@ public void Constructor_WithOpenAiCompatibleProviderKind_DoesNotThrow() var endpoint = MakeEndpoint( kind: ModelProviderKind.OpenAiCompatible, endpointUrl: "https://openrouter.ai/api/v1"); - var factory = services.GetRequiredService(); + var factory = services.GetRequiredService(); FluentActions .Invoking(() => factory(MakeAgent(endpoint))) diff --git a/Proxytrace.Infrastructure.Tests/Module.cs b/Proxytrace.Infrastructure.Tests/Module.cs index 49e4ab442..4970e8f42 100644 --- a/Proxytrace.Infrastructure.Tests/Module.cs +++ b/Proxytrace.Infrastructure.Tests/Module.cs @@ -9,4 +9,4 @@ protected override void Load(ContainerBuilder builder) base.Load(builder); builder.RegisterModule(); } -} \ No newline at end of file +} diff --git a/Proxytrace.Infrastructure.Tests/Proxytrace.Infrastructure.Tests.csproj b/Proxytrace.Infrastructure.Tests/Proxytrace.Infrastructure.Tests.csproj index 73eaf66cc..b1a5b4247 100644 --- a/Proxytrace.Infrastructure.Tests/Proxytrace.Infrastructure.Tests.csproj +++ b/Proxytrace.Infrastructure.Tests/Proxytrace.Infrastructure.Tests.csproj @@ -28,7 +28,6 @@ - diff --git a/Proxytrace.Infrastructure/Internal/ChatClientExtensions.cs b/Proxytrace.Infrastructure/Internal/ChatClientExtensions.cs index c5e36d84f..4dd6c49a0 100644 --- a/Proxytrace.Infrastructure/Internal/ChatClientExtensions.cs +++ b/Proxytrace.Infrastructure/Internal/ChatClientExtensions.cs @@ -1,7 +1,8 @@ +using Nordstein.Core.AI.Clients; using System.Text; using System.Text.Json; using Microsoft.Extensions.AI; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; // Aliased rather than imported: OpenAI.Chat also defines a ChatMessage, which would collide with // Microsoft.Extensions.AI's own throughout this file. using OpenAiChatOptions = OpenAI.Chat.ChatCompletionOptions; @@ -149,4 +150,4 @@ private static void ApplyReasoningEffort(ChatOptions chatOptions, string? effort new OpenAiChatOptions { ReasoningEffortLevel = new OpenAiReasoningEffort(effort) }; #pragma warning restore OPENAI001 } -} \ No newline at end of file +} diff --git a/Proxytrace.Infrastructure/Internal/LiteLlmCatalogResolver.cs b/Proxytrace.Infrastructure/Internal/LiteLlmCatalogResolver.cs index e85540660..c2b6577c8 100644 --- a/Proxytrace.Infrastructure/Internal/LiteLlmCatalogResolver.cs +++ b/Proxytrace.Infrastructure/Internal/LiteLlmCatalogResolver.cs @@ -178,4 +178,4 @@ private bool IsRetrySuppressed() obj.TryGetProperty(name, out JsonElement el) && el.ValueKind == JsonValueKind.Number ? el.GetDecimal() : null; -} \ No newline at end of file +} diff --git a/Proxytrace.Infrastructure/Internal/ModelClient.cs b/Proxytrace.Infrastructure/Internal/ModelClient.cs index 84c5fd9e4..3e1ae2bd1 100644 --- a/Proxytrace.Infrastructure/Internal/ModelClient.cs +++ b/Proxytrace.Infrastructure/Internal/ModelClient.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using System.ClientModel; using System.ClientModel.Primitives; using System.Diagnostics; @@ -10,12 +11,11 @@ using Proxytrace.Domain.Kiosk; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; -using Proxytrace.Domain.Usage; -using Proxytrace.Serialization; +using Nordstein.Core.AI.Serialization; namespace Proxytrace.Infrastructure.Internal; @@ -132,7 +132,7 @@ public ModelRequestPreview BuildRequestPreview( IReadOnlyDictionary? promptVariables = null) { SystemMessage systemMessage = agent.CreateSystemMessage(promptVariables); - options ??= ModelOptions.FromAgent(agent, endpoint.Model); + options ??= ModelOptionsFactory.FromAgent(agent, endpoint.Model); conversation = Conversation.ReplaceSystemMessage(conversation, systemMessage); var messages = conversation.Messages.Select(ToPreviewMessage).ToList(); @@ -180,7 +180,7 @@ private async Task CompleteAsync( "This instance of ModelClient is not functional."); } - options ??= ModelOptions.FromAgent(agent, endpoint.Model); + options ??= ModelOptionsFactory.FromAgent(agent, endpoint.Model); conversation = Conversation.ReplaceSystemMessage(conversation, systemMessage); ICompletion? completion = null; @@ -307,7 +307,7 @@ public async IAsyncEnumerable StreamAsync( ModelOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { - options ??= ModelOptions.FromAgent(agent, endpoint.Model); + options ??= ModelOptionsFactory.FromAgent(agent, endpoint.Model); conversation = Conversation.ReplaceSystemMessage(conversation, systemMessage); Stopwatch sw = Stopwatch.StartNew(); @@ -382,4 +382,4 @@ internal static OpenAIClientOptions BuildClientOptions(IModelEndpoint endpoint) NetworkTimeout = NetworkTimeout, RetryPolicy = new ClientRetryPolicy(maxRetries: MaxRetries), }; -} \ No newline at end of file +} diff --git a/Proxytrace.Infrastructure/Internal/ProviderClient.cs b/Proxytrace.Infrastructure/Internal/ProviderClient.cs index 0ae8c0755..ece7c3140 100644 --- a/Proxytrace.Infrastructure/Internal/ProviderClient.cs +++ b/Proxytrace.Infrastructure/Internal/ProviderClient.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using System.ClientModel; using System.ClientModel.Primitives; using System.Net; diff --git a/Proxytrace.Infrastructure/Module.cs b/Proxytrace.Infrastructure/Module.cs index fbc3786d5..5d6ba7ceb 100644 --- a/Proxytrace.Infrastructure/Module.cs +++ b/Proxytrace.Infrastructure/Module.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using Autofac; using Autofac.Core; using Microsoft.Extensions.Configuration; @@ -14,7 +15,6 @@ protected override void Load(ContainerBuilder builder) { base.Load(builder); builder.RegisterModule(); - builder.RegisterModule(); builder.RegisterType() .As() @@ -60,4 +60,4 @@ protected override void Load(ContainerBuilder builder) .SingleInstance() .IfNotRegistered(typeof(KioskEndpointOptions)); } -} \ No newline at end of file +} diff --git a/Proxytrace.Infrastructure/Proxytrace.Infrastructure.csproj b/Proxytrace.Infrastructure/Proxytrace.Infrastructure.csproj index 4346eca4f..75c27cde6 100644 --- a/Proxytrace.Infrastructure/Proxytrace.Infrastructure.csproj +++ b/Proxytrace.Infrastructure/Proxytrace.Infrastructure.csproj @@ -8,7 +8,6 @@ - diff --git a/Proxytrace.Proxy.Api/Dockerfile b/Proxytrace.Proxy.Api/Dockerfile index d7b8691b8..0a05cc536 100644 --- a/Proxytrace.Proxy.Api/Dockerfile +++ b/Proxytrace.Proxy.Api/Dockerfile @@ -38,7 +38,6 @@ COPY ["Proxytrace.Domain/Proxytrace.Domain.csproj", "Proxytrace.Domain/"] COPY ["Proxytrace.Infrastructure/Proxytrace.Infrastructure.csproj", "Proxytrace.Infrastructure/"] COPY ["Proxytrace.Licensing/Proxytrace.Licensing.csproj", "Proxytrace.Licensing/"] COPY ["Proxytrace.Messaging/Proxytrace.Messaging.csproj", "Proxytrace.Messaging/"] -COPY ["Proxytrace.Serialization/Proxytrace.Serialization.csproj", "Proxytrace.Serialization/"] COPY ["Proxytrace.Storage/Proxytrace.Storage.csproj", "Proxytrace.Storage/"] RUN dotnet restore "Proxytrace.Proxy.Api/Proxytrace.Proxy.Api.csproj" -a $TARGETARCH COPY . . diff --git a/Proxytrace.Proxy.Api/Internal/UnusedAgentNameGenerator.cs b/Proxytrace.Proxy.Api/Internal/UnusedAgentNameGenerator.cs index c5265e049..b734bf943 100644 --- a/Proxytrace.Proxy.Api/Internal/UnusedAgentNameGenerator.cs +++ b/Proxytrace.Proxy.Api/Internal/UnusedAgentNameGenerator.cs @@ -1,5 +1,6 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; namespace Proxytrace.Proxy.Api.Internal; diff --git a/Proxytrace.Serialization.Tests/JsonOutputParserTests.cs b/Proxytrace.Serialization.Tests/JsonOutputParserTests.cs deleted file mode 100644 index c07bf7b7c..000000000 --- a/Proxytrace.Serialization.Tests/JsonOutputParserTests.cs +++ /dev/null @@ -1,735 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Runtime.Serialization; -using System.Text.Json; -using AwesomeAssertions; -using JetBrains.Annotations; -using Microsoft.Testing.Platform.Services; -using Nordstein.Core.Common.Validation; -using Nordstein.Core.Testing; -using JsonSerializer = System.Text.Json.JsonSerializer; -using Proxytrace.Serialization.Internal; - -// ReSharper disable PropertyCanBeMadeInitOnly.Global -// ReSharper disable MemberCanBePrivate.Global -// ReSharper disable UnusedAutoPropertyAccessor.Global - -namespace Proxytrace.Serialization.Tests; - -[TestClass] -public class JsonOutputParserTests : BaseTest -{ - public class SimpleModel - { - public string Name { get; set; } = string.Empty; - public int Age { get; set; } - public bool IsActive { get; set; } - } - - [UsedImplicitly] - public class ModelWithValidation : IValidatableObject - { - public string RequiredField { get; set; } = string.Empty; - - [Range(1, 100)] public int Age { get; set; } - - public void Validate() - { - - } - - public IEnumerable Validate(ValidationContext validationContext) - { - yield return Validation.NotNullOrWhiteSpace(RequiredField); - yield return Validation.InRange(Age, 1, 100); - } - } - - public enum TestEnum - { - FirstValue, - SecondValue, - ThirdValue - } - - [UsedImplicitly] - public class ModelWithEnum - { - public TestEnum Status { get; set; } - public string Description { get; set; } = string.Empty; - } - - public class ComplexModel - { - public SimpleModel? NestedModel { get; set; } - public List Items { get; set; } = []; - public Dictionary Scores { get; set; } = new(); - public TestEnum Status { get; set; } - } - - [UsedImplicitly] - public class NullableModel - { - public string? OptionalString { get; set; } - public int? OptionalInt { get; set; } - public DateTime? OptionalDate { get; set; } - } - - [UsedImplicitly] - public class ModelWithDescription - { - [System.ComponentModel.Description("The action that was requested to be performed")] - public string RequestedAction { get; set; } = string.Empty; - - [System.ComponentModel.Description("The unique identifier for the actor")] - public Guid ActorId { get; set; } - - [System.ComponentModel.Description("The status of the operation")] - public TestEnum Status { get; set; } - } - - private IOutputFormat Format() => - GetServices().GetRequiredService()(typeof(T)); - - [TestMethod] - public void SchemaDefinition_SimpleModel_ReturnsValidJsonSchema() - { - // Arrange & Act - string schema = Format().As().Schema; - - // Assert - schema.Should().NotBeNullOrWhiteSpace(); - - // Verify it's valid JSON - JsonDocument.Parse(schema); - - // Verify schema contains expected properties - schema.Should().Contain("Name"); - schema.Should().Contain("Age"); - schema.Should().Contain("IsActive"); - } - - [TestMethod] - public void SchemaDefinition_ModelWithEnum_ContainsEnumValues() - { - // Arrange & Act - string schema = Format().As().Schema; - - // Assert - schema.Should().NotBeNullOrWhiteSpace(); - - // Verify schema contains enum values in camelCase - schema.Should().Contain("firstValue"); - schema.Should().Contain("secondValue"); - schema.Should().Contain("thirdValue"); - } - - [TestMethod] - public void SchemaDefinition_ComplexModel_ContainsNestedStructures() - { - // Arrange & Act - string schema = Format().As().Schema; - - // Assert - schema.Should().NotBeNullOrWhiteSpace(); - JsonDocument.Parse(schema); // Verify valid JSON - - schema.Should().Contain("NestedModel"); - schema.Should().Contain("Items"); - schema.Should().Contain("Scores"); - schema.Should().Contain("Status"); - } - - [TestMethod] - public void SchemaDefinition_NullableModel_HandlesNullableProperties() - { - // Arrange & Act - string schema = Format().As().Schema; - - // Assert - schema.Should().NotBeNullOrWhiteSpace(); - JsonDocument.Parse(schema); // Verify valid JSON - - schema.Should().Contain("OptionalString"); - schema.Should().Contain("OptionalInt"); - schema.Should().Contain("OptionalDate"); - } - - [TestMethod] - public void SchemaDefinition_SameTypeMultipleCalls_ReturnsSameSchema() - { - // Arrange & Act - string schema1 = Format().As().Schema; - string schema2 = Format().As().Schema; - - // Assert - schema1.Should().Be(schema2); - } - - [TestMethod] - public async Task ParseAsync_ValidSimpleJson_ReturnsDeserializedObject() - { - // Arrange - string json = """{"name":"John","age":30,"isActive":true}"""; - - // Act - var result = await Format().ParseAsync(json); - - // Assert - result.Should().NotBeNull(); - result.Name.Should().Be("John"); - result.Age.Should().Be(30); - result.IsActive.Should().BeTrue(); - } - - [TestMethod] - public async Task ParseAsync_ValidJsonWithWhitespace_ReturnsDeserializedObject() - { - // Arrange - string json = """ - { - "name": "Jane", - "age": 25, - "isActive": false - } - """; - - // Act - var result = await Format().ParseAsync(json); - - // Assert - result.Should().NotBeNull(); - result.Name.Should().Be("Jane"); - result.Age.Should().Be(25); - result.IsActive.Should().BeFalse(); - } - - [TestMethod] - public async Task ParseAsync_EnumAsString_ParsesCorrectly() - { - // Arrange - string json = """{"status":"firstValue","description":"Test"}"""; - - // Act - var result = await Format().ParseAsync(json); - - // Assert - result.Should().NotBeNull(); - result.Status.Should().Be(TestEnum.FirstValue); - result.Description.Should().Be("Test"); - } - - [TestMethod] - public async Task ParseAsync_EnumAsInteger_ParsesCorrectly() - { - // Arrange - string json = """{"status":1,"description":"Test"}"""; - - // Act - var result = await Format().ParseAsync(json); - - // Assert - result.Should().NotBeNull(); - result.Status.Should().Be(TestEnum.SecondValue); - result.Description.Should().Be("Test"); - } - - [TestMethod] - public async Task ParseAsync_ComplexModel_ParsesCorrectly() - { - // Arrange - string json = """ - { - "nestedModel": {"name":"Nested","age":20,"isActive":true}, - "items": ["item1","item2","item3"], - "scores": {"math":95,"science":87}, - "status": "secondValue" - } - """; - - // Act - var result = await Format().ParseAsync(json); - - // Assert - result.Should().NotBeNull(); - result.NestedModel.Should().NotBeNull(); - result.NestedModel.Name.Should().Be("Nested"); - result.NestedModel.Age.Should().Be(20); - result.NestedModel.IsActive.Should().BeTrue(); - result.Items.Should().BeEquivalentTo("item1", "item2", "item3"); - result.Scores.Should().ContainKey("math").WhoseValue.Should().Be(95); - result.Scores.Should().ContainKey("science").WhoseValue.Should().Be(87); - result.Status.Should().Be(TestEnum.SecondValue); - } - - [TestMethod] - public async Task ParseAsync_NullableProperties_ParsesCorrectly() - { - // Arrange - string json = """{"optionalString":"test","optionalInt":42,"optionalDate":"2023-01-01T00:00:00Z"}"""; - - // Act - var result = await Format().ParseAsync(json); - - // Assert - result.Should().NotBeNull(); - result.OptionalString.Should().Be("test"); - result.OptionalInt.Should().Be(42); - result.OptionalDate.Should().Be(new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - } - - [TestMethod] - [DataRow(null)] - [DataRow("")] - [DataRow(" ")] - [DataRow("null")] - [DataRow("\"null\"")] - [DataRow("\"null\"\r\n")] - public async Task ParseAsync_NullableListWithNull_ReturnNull(string? data) - { - var result = await Format>().ParseAsync>(data); - result.Should().BeNull(); - } - - [TestMethod] - public async Task ParseAsync_NullablePropertiesWithNulls_ParsesCorrectly() - { - // Arrange - string json = """{"optionalString":null,"optionalInt":null,"optionalDate":null}"""; - - // Act - var result = await Format().ParseAsync(json); - - // Assert - result.Should().NotBeNull(); - result.OptionalString.Should().BeNull(); - result.OptionalInt.Should().BeNull(); - result.OptionalDate.Should().BeNull(); - } - - [TestMethod] - public async Task ParseAsync_ValidModelWithValidation_PassesValidation() - { - // Arrange - string json = """{"requiredField":"Valid Value","age":25}"""; - - // Act - var result = await Format().ParseAsync(json); - - // Assert - result.Should().NotBeNull(); - result.RequiredField.Should().Be("Valid Value"); - result.Age.Should().Be(25); - } - - [TestMethod] - public async Task ParseAsync_JsonWithCodeBlockMarkdown_TrimsArtifacts() - { - // Arrange - string jsonWithArtifacts = """ - ```json - {"name":"John","age":30,"isActive":true} - ``` - """; - - // Act - var result = await Format().ParseAsync(jsonWithArtifacts); - - // Assert - result.Should().NotBeNull(); - result.Name.Should().Be("John"); - result.Age.Should().Be(30); - result.IsActive.Should().BeTrue(); - } - - [TestMethod] - public async Task ParseAsync_JsonWithLanguageSpecifier_TrimsArtifacts() - { - // Arrange - string jsonWithArtifacts = """ - ```csharp - {"name":"John","age":30,"isActive":true} - ``` - """; - - // Act - var result = await Format().ParseAsync(jsonWithArtifacts); - - // Assert - result.Should().NotBeNull(); - result.Name.Should().Be("John"); - } - - [TestMethod] - public async Task ParseAsync_JsonWithCommonPrefixes_TrimsArtifacts() - { - var testCases = new[] - { - """json: {"name":"John","age":30,"isActive":true}""", - """Output: {"name":"John","age":30,"isActive":true}""", - """Result: {"name":"John","age":30,"isActive":true}""", - """Response: {"name":"John","age":30,"isActive":true}""" - }; - - foreach (string testCase in testCases) - { - // Act - var result = await Format().ParseAsync(testCase); - - // Assert - result.Should().NotBeNull(); - result.Name.Should().Be("John"); - result.Age.Should().Be(30); - result.IsActive.Should().BeTrue(); - } - } - - [TestMethod] - [DataRow("28c50307-b609-4c23-ae7d-c5e51f636a82")] - public async Task ParseAsync_Guid_works(string input) - { - // Act - Guid? result = await Format().ParseAsync(input); - - // Assert - result.Should().NotBeNull(); - result.Should().Be(Guid.Parse(input)); - } - - [TestMethod] - public async Task ParseAsync_JsonWithOnlyOpeningBackticks_TrimsArtifacts() - { - // Arrange - string jsonWithArtifacts = """```{"name":"John","age":30,"isActive":true}"""; - - // Act - var result = await Format().ParseAsync(jsonWithArtifacts); - - // Assert - result.Should().NotBeNull(); - result.Name.Should().Be("John"); - } - - [TestMethod] - public async Task ParseAsync_JsonWithOnlyClosingBackticks_TrimsArtifacts() - { - // Arrange - string jsonWithArtifacts = """{"name":"John","age":30,"isActive":true}```"""; - - // Act - var result = await Format().ParseAsync(jsonWithArtifacts); - - // Assert - result.Should().NotBeNull(); - result.Name.Should().Be("John"); - } - - [TestMethod] - public async Task ParseAsync_EmptyString_ReturnsNull() - { - var result = await Format().ParseAsync(string.Empty); - result.Should().BeNull(); - } - - [TestMethod] - public async Task ParseAsync_WhitespaceOnly_ReturnsEmptyAfterTrimming() - { - var result = await Format().ParseAsync(" \n\t "); - result.Should().BeNull(); - } - - [TestMethod] - public async Task ParseAsync_InvalidJson_ThrowsSerializationException() - { - // Structurally balanced but meaningless — nothing suggests the model was cut off, so there - // is nothing to recover and the failure must surface. - string invalidJson = """{"name":"John","age":}"""; - - // Act & Assert - await Format().Invoking(f => f.ParseAsync(invalidJson)) - .Should() - .ThrowAsync(); - } - - [TestMethod] - public async Task ParseAsync_JsonReturningNull_ReturnNull() - { - var result = await Format().ParseAsync("null"); - result.Should().BeNull(); - } - - [TestMethod] - public async Task ParseAsync_ModelWithValidationErrors_ThrowsValidationException() - { - // Arrange - string invalidJson = """{"requiredField":"","age":150}"""; - - // Act & Assert - await Format().Invoking(f => f.ParseAsync(invalidJson)) - .Should() - .ThrowAsync(); - } - - [TestMethod] - public async Task ParseAsync_ModelWithValidationEmptyRequiredField_ThrowsValidationException() - { - // Arrange - string invalidJson = """{"requiredField":"","age":25}"""; - - // Act & Assert - await Format().Invoking(f => f.ParseAsync(invalidJson)) - .Should().ThrowAsync(); - } - - [TestMethod] - public async Task ParseAsync_ModelWithValidationInvalidAge_ThrowsValidationException() - { - // Arrange - string invalidJson = """{"requiredField":"Valid","age":0}"""; - - // Act & Assert - await Format().Invoking(f => f.ParseAsync(invalidJson)) - .Should().ThrowAsync(); - } - - [TestMethod] - public async Task ParseAsync_IncorrectJsonStructure_ThrowsSerializationException() - { - // Arrange - string wrongStructure = """["array","instead","of","object"]"""; - - // Act & Assert - await Format().Invoking(f => f.ParseAsync(wrongStructure)) - .Should().ThrowAsync(); - } - - [TestMethod] - public async Task ParseAsync_InvalidEnumValue_ThrowsSerializationException() - { - // Arrange - string invalidEnum = """{"status":"invalidValue","description":"Test"}"""; - - // Act & Assert - await Format().Invoking(f => f.ParseAsync(invalidEnum)) - .Should().ThrowAsync(); - } - - [TestMethod] - public void SchemaDefinition_ModelWithDescription_ContainsDescriptions() - { - // Arrange & Act - string schema = Format().As().Schema; - - // Assert - schema.Should().NotBeNullOrWhiteSpace(); - - // Verify it's valid JSON - JsonDocument.Parse(schema); - - // Verify schema contains description attributes - schema.Should().Contain("The action that was requested to be performed"); - schema.Should().Contain("The unique identifier for the actor"); - schema.Should().Contain("The status of the operation"); - - // Verify the properties exist - schema.Should().Contain("RequestedAction"); - schema.Should().Contain("ActorId"); - schema.Should().Contain("Status"); - } - - [TestMethod] - public async Task ParseAsync_ModelWithDescription_SuccessfullyParsed() - { - // Arrange - Guid testGuid = Guid.NewGuid(); - string json = $$"""{"requestedAction":"TestAction","actorId":"{{testGuid}}","status":"secondValue"}"""; - - // Act - var result = await Format().ParseAsync(json); - - // Assert - result.Should().NotBeNull(); - result.RequestedAction.Should().Be("TestAction"); - result.ActorId.Should().Be(testGuid); - result.Status.Should().Be(TestEnum.SecondValue); - } - - [TestMethod] - public async Task ParseAsync_WithCancellationToken_RespectsToken() - { - // Arrange - string json = """{"name":"John","age":30,"isActive":true}"""; - using var cts = new CancellationTokenSource(); - - // Act - var result = await Format().ParseAsync(json, cts.Token); - - // Assert - result.Should().NotBeNull(); - result.Name.Should().Be("John"); - } - - [TestMethod] - public async Task ParseAsync_WithCancelledToken_ThrowsOperationCancelledException() - { - // Arrange - string json = """{"name":"John","age":30,"isActive":true}"""; - var cts = new CancellationTokenSource(); - await cts.CancelAsync(); - - // Act & Assert - await Format().Invoking(f => f.ParseAsync(json, cts.Token)) - .Should() - .ThrowAsync(); - } - - [TestMethod] - public async Task ParseAsync_LargeJson_ParsesSuccessfully() - { - // Arrange - var items = Enumerable.Range(1, 1000).Select(i => $"item{i}").ToList(); - var scores = Enumerable.Range(1, 100).ToDictionary(i => $"subject{i}", i => i * 10); - - var model = new ComplexModel - { - NestedModel = new SimpleModel { Name = "Large Test", Age = 50, IsActive = true }, - Items = items, - Scores = scores, - Status = TestEnum.ThirdValue - }; - - string json = JsonSerializer.Serialize(model); - - // Act - var result = await Format().ParseAsync(json); - - // Assert - result.Should().NotBeNull(); - result.Items.Should().HaveCount(1000); - result.Scores.Should().HaveCount(100); - result.NestedModel.Should().NotBeNull(); - result.Status.Should().Be(TestEnum.ThirdValue); - } - - [TestMethod] - public async Task ParseAsync_UnicodeCharacters_ParsesCorrectly() - { - // Arrange - string json = """{"name":"João 你好 🌟","age":30,"isActive":true}"""; - - // Act - var result = await Format().ParseAsync(json); - - // Assert - result.Should().NotBeNull(); - result.Name.Should().Be("João 你好 🌟"); - result.Age.Should().Be(30); - result.IsActive.Should().BeTrue(); - } - - [TestMethod] - public async Task ParseAsync_EscapedCharacters_ParsesCorrectly() - { - // Arrange - string json = """{"name":"Line1\nLine2\tTabbed\"Quoted\"","age":30,"isActive":true}"""; - - // Act - var result = await Format().ParseAsync(json); - - // Assert - result.Should().NotBeNull(); - result.Name.Should().Be("Line1\nLine2\tTabbed\"Quoted\""); - } - - [TestMethod] - public void GetFormat_MultipleTypesAndCalls_ReturnsCorrectSchemas() - { - string schema1 = Format().As().Schema; - string schema2 = Format().As().Schema; - string schema3 = Format().As().Schema; - schema1.Should().Be(schema2); - schema1.Should().NotBe(schema3); - } - - // ── truncated model output ──────────────────────────────────────────────── - // - // A judge that talks past its output budget stops mid-token: the JSON arrives with an - // unterminated string and unclosed braces. Everything before the cut is intact, and the field - // that carries the answer is usually written before the long prose that ran out of room — so the - // answer is recovered rather than discarded with its tail. - - [TestMethod] - public async Task ParseAsync_CutOffMidString_RecoversTheFieldsWrittenBeforeTheCut() - { - // Verbatim shape of the real truncation from issue #453: a complete verdict followed by a - // reasoning string that simply stops — no closing quote, no closing brace. - string json = """{"status":"firstValue","description":"The response clearly addresses the"""; - - var result = await Format().ParseAsync(json); - - result.Should().NotBeNull(); - result.Status.Should().Be(TestEnum.FirstValue); - result.Description.Should().Be("The response clearly addresses the"); - } - - [TestMethod] - public async Task ParseAsync_CutOffMidPropertyName_DropsTheIncompleteMemberAndKeepsTheRest() - { - // Closing the open brace cannot rescue a half-written key, so the incomplete member is - // dropped and the members that did complete are kept. - string json = """{"name":"John","age":30,"isAct"""; - - var result = await Format().ParseAsync(json); - - result.Should().NotBeNull(); - result.Name.Should().Be("John"); - result.Age.Should().Be(30); - result.IsActive.Should().BeFalse(); - } - - [TestMethod] - public async Task ParseAsync_CutOffInsideNestedObject_ClosesEveryOpenContainer() - { - string json = """{"items":["a","b"],"nestedModel":{"name":"Jane","age":25"""; - - var result = await Format().ParseAsync(json); - - result.Should().NotBeNull(); - result.Items.Should().Equal("a", "b"); - result.NestedModel.Should().NotBeNull(); - result.NestedModel.Name.Should().Be("Jane"); - } - - [TestMethod] - public async Task ParseAsync_CutOffAfterAPropertyColon_KeepsTheCompletedMembers() - { - string json = """{"name":"John","age":30,"isActive":"""; - - var result = await Format().ParseAsync(json); - - result.Should().NotBeNull(); - result.Name.Should().Be("John"); - result.Age.Should().Be(30); - } - - [TestMethod] - public async Task ParseAsync_MalformedBeyondTruncation_StillThrows() - { - // Repair is only for output that was cut off. Genuinely broken JSON must not be papered - // over into some object that the model never said. - string json = """{"name" "John" 30 true}"""; - - await FluentActions - .Invoking(() => Format().ParseAsync(json)) - .Should().ThrowAsync(); - } - - [TestMethod] - public async Task SerializeDictionaryArguments() - { - string json = """{"query":"DFS algorithm"}"""; - var serializer = GetServices().GetRequiredService(); - var expected = new Dictionary { { "query", "DFS algorithm" } }; - var actual = await serializer.DeserializeAsync>(json); - actual.Should().BeEquivalentTo(expected); - } -} diff --git a/Proxytrace.Serialization.Tests/Module.cs b/Proxytrace.Serialization.Tests/Module.cs deleted file mode 100644 index 1e21105b0..000000000 --- a/Proxytrace.Serialization.Tests/Module.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Autofac; - -namespace Proxytrace.Serialization.Tests; - -public class Module : Autofac.Module -{ - protected override void Load(ContainerBuilder builder) - { - base.Load(builder); - builder.RegisterModule(); - } -} \ No newline at end of file diff --git a/Proxytrace.Serialization.Tests/ObjectToInferredTypesConverterTests.cs b/Proxytrace.Serialization.Tests/ObjectToInferredTypesConverterTests.cs deleted file mode 100644 index 1e167366a..000000000 --- a/Proxytrace.Serialization.Tests/ObjectToInferredTypesConverterTests.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System.Text.Json; -using JsonSerializer = System.Text.Json.JsonSerializer; -using AwesomeAssertions; -using Proxytrace.Serialization.Internal; -using Nordstein.Core.Testing; - -namespace Proxytrace.Serialization.Tests; - -[TestClass] -public sealed class ObjectToInferredTypesConverterTests : BaseTest -{ - private static readonly JsonSerializerOptions Options = new() - { - Converters = { new ObjectToInferredTypesConverter() }, - }; - - private static object? Read(string json) => - JsonSerializer.Deserialize(json, Options); - - [TestMethod] - public void Read_TrueLiteral_ReturnsBool() - => Read("true").Should().Be(true); - - [TestMethod] - public void Read_FalseLiteral_ReturnsBool() - => Read("false").Should().Be(false); - - [TestMethod] - public void Read_NullLiteral_ReturnsNull() - => Read("null").Should().BeNull(); - - [TestMethod] - public void Read_Integer_ReturnsInt() - { - var result = Read("42"); - result.Should().BeOfType().And.Be(42); - } - - [TestMethod] - public void Read_LargeNumber_ReturnsLong() - { - long value = (long)int.MaxValue + 1; - var result = Read(value.ToString()); - result.Should().BeOfType().And.Be(value); - } - - [TestMethod] - public void Read_Decimal_ReturnsDouble() - { - var result = Read("3.14"); - result.Should().BeOfType().And.Be(3.14); - } - - [TestMethod] - public void Read_DateTimeString_ReturnsDateTime() - { - var result = Read("\"2026-05-13T12:00:00Z\""); - result.Should().BeOfType(); - } - - [TestMethod] - public void Read_GuidString_ReturnsGuid() - { - var id = Guid.NewGuid(); - var result = Read($"\"{id}\""); - result.Should().BeOfType().And.Be(id); - } - - [TestMethod] - public void Read_PlainString_ReturnsString() - { - var result = Read("\"hello\""); - result.Should().BeOfType().And.Be("hello"); - } - - [TestMethod] - public void Read_Object_ReturnsDictionary() - { - var result = Read("""{"name":"alice","age":30,"active":true}"""); - - var dict = result.Should().BeOfType>().Subject; - dict["name"].Should().Be("alice"); - dict["age"].Should().Be(30); - dict["active"].Should().Be(true); - } - - [TestMethod] - public void Read_Array_ReturnsList() - { - var result = Read("""[1, "two", true, null]"""); - - var list = result.Should().BeOfType>().Subject; - list.Should().HaveCount(4); - list[0].Should().Be(1); - list[1].Should().Be("two"); - list[2].Should().Be(true); - list[3].Should().BeNull(); - } - - [TestMethod] - public void Read_NestedObject_RecursesIntoDictionaries() - { - var result = Read("""{"outer":{"inner":7}}"""); - var outer = result.Should().BeOfType>().Subject; - var inner = outer["outer"].Should().BeOfType>().Subject; - inner["inner"].Should().Be(7); - } - - [TestMethod] - public void Read_NestedArray_RecursesIntoLists() - { - var result = Read("""[[1,2],[3]]"""); - var outer = result.Should().BeOfType>().Subject; - outer[0].Should().BeOfType>().Subject.Should().HaveCount(2); - outer[1].Should().BeOfType>().Subject.Should().HaveCount(1); - } - - [TestMethod] - public void Write_SerializesUnderlyingType() - { - var dict = new Dictionary { ["k"] = 42 }; - var json = JsonSerializer.Serialize(dict, Options); - json.Should().Contain("\"k\":42"); - } - - [TestMethod] - public void Read_ObjectWithMissingValue_ThrowsJsonException() - { - var act = () => Read("""{"name"}"""); - act.Should().Throw(); - } -} diff --git a/Proxytrace.Serialization.Tests/Proxytrace.Serialization.Tests.csproj b/Proxytrace.Serialization.Tests/Proxytrace.Serialization.Tests.csproj deleted file mode 100644 index 0abb69081..000000000 --- a/Proxytrace.Serialization.Tests/Proxytrace.Serialization.Tests.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - net10.0 - enable - enable - false - true - - - - - - - - - - - - - - - diff --git a/Proxytrace.Serialization.Tests/StringOutputFormatTests.cs b/Proxytrace.Serialization.Tests/StringOutputFormatTests.cs deleted file mode 100644 index b93e98b50..000000000 --- a/Proxytrace.Serialization.Tests/StringOutputFormatTests.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using AwesomeAssertions; -using Proxytrace.Serialization.Internal; -using Nordstein.Core.Testing; - -namespace Proxytrace.Serialization.Tests; - -[TestClass] -public sealed class StringOutputFormatTests : BaseTest -{ - [TestMethod] - public void ToPromptString_ReturnsNull() - { - var format = new StringOutputFormat(); - format.ToPromptString().Should().BeNull(); - } - - [TestMethod] - public async Task ParseAsync_TargetingString_ReturnsRawInput() - { - var format = new StringOutputFormat(); - var result = await format.ParseAsync("hello world", CancellationToken); - - result.Should().Be("hello world"); - } - - [TestMethod] - public async Task ParseAsync_NullInput_ReturnsNull() - { - var format = new StringOutputFormat(); - var result = await format.ParseAsync(null, CancellationToken); - - result.Should().BeNull(); - } - - [TestMethod] - public async Task ParseAsync_TargetingNonString_Throws() - { - var format = new StringOutputFormat(); - - await format.Invoking(f => f.ParseAsync("123", CancellationToken)) - .Should().ThrowAsync(); - } - - [TestMethod] - public void Validate_ReturnsNoErrors() - { - var format = new StringOutputFormat(); - var results = format.Validate(new ValidationContext(format)).ToArray(); - - results.Should().BeEmpty(); - } -} diff --git a/Proxytrace.Serialization/IOutputFormat.cs b/Proxytrace.Serialization/IOutputFormat.cs deleted file mode 100644 index ad155a807..000000000 --- a/Proxytrace.Serialization/IOutputFormat.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Proxytrace.Serialization; - -/// -/// A format for the output of an agent -/// -public interface IOutputFormat : IValidatableObject -{ - /// - /// Creates a from a json schema string - /// - delegate IOutputFormat Create(Type type); - - /// - /// Parses model output to and validates it - /// - Task ParseAsync(string? output, CancellationToken cancellationToken = default); - - /// - /// Returns an instruction that tells the model how to format its output - /// - string? ToPromptString(); -} \ No newline at end of file diff --git a/Proxytrace.Serialization/ISerializer.cs b/Proxytrace.Serialization/ISerializer.cs deleted file mode 100644 index 51eb7ceb6..000000000 --- a/Proxytrace.Serialization/ISerializer.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Proxytrace.Serialization; - -/// -/// The serializer interface -/// -public interface ISerializer -{ - /// - /// Serializes to a string - /// - string Serialize(object? input, bool writeIndented = false); - - /// - /// Parses model output to - /// - Task DeserializeAsync( - string output, - CancellationToken cancellationToken = default); - - /// - /// Parses model output to - /// - TOutput? Deserialize(string value); -} \ No newline at end of file diff --git a/Proxytrace.Serialization/Internal/JsonOutputFormat.cs b/Proxytrace.Serialization/Internal/JsonOutputFormat.cs deleted file mode 100644 index b29df5741..000000000 --- a/Proxytrace.Serialization/Internal/JsonOutputFormat.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Text.Json; -using System.Text.Json.Nodes; -using System.Text.Json.Schema; -using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; -using Nordstein.Core.Common.Validation; - -namespace Proxytrace.Serialization.Internal; - -/// -internal record JsonOutputFormat : IOutputFormat -{ - internal delegate JsonOutputFormat Create(Type type); - - private readonly ISerializer serializer; - - /// - /// The JSON schema definition that the output must adhere to - /// - public string Schema { get; } - - public JsonOutputFormat( - Type type, - ISerializer serializer) - { - this.serializer = serializer; - - JsonSerializerOptions jsonSerializerOptions = new() - { - TypeInfoResolver = new DefaultJsonTypeInfoResolver(), - PropertyNameCaseInsensitive = true - }; - jsonSerializerOptions.Converters.Add( - new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, allowIntegerValues: true)); - - JsonSchemaExporterOptions jsonSchemaExporterOptions1 = new() - { - TreatNullObliviousAsNonNullable = false, - TransformSchemaNode = (context, schema) => - { - if (context.TypeInfo.Type.IsEnum) - { - var enumNames = Enum.GetNames(context.TypeInfo.Type); - var enumArray = new JsonArray(); - foreach (var enumName in enumNames) - { - enumArray.Add(JsonNamingPolicy.CamelCase.ConvertName(enumName)); - } - schema["enum"] = enumArray; - } - - var descriptionAttribute = context.PropertyInfo?.AttributeProvider - ?.GetCustomAttributes(typeof(DescriptionAttribute), inherit: true) - .OfType() - .FirstOrDefault(); - - if (descriptionAttribute != null && !string.IsNullOrWhiteSpace(descriptionAttribute.Description)) - { - schema["description"] = descriptionAttribute.Description; - } - - return schema; - } - }; - - Schema = jsonSerializerOptions.GetJsonSchemaAsNode( - type, - jsonSchemaExporterOptions1).ToString(); - } - - /// - public string ToPromptString() - => $"Respond only in JSON format that adheres to the following JSON schema definition:\n{Schema}"; - - public async Task ParseAsync(string? output, CancellationToken cancellationToken = default) - { - string? cleanedOutput = TrimArtifacts(output); - if (cleanedOutput is null) - return default; - - try - { - var deserialized = await serializer.DeserializeAsync(cleanedOutput, cancellationToken); - return deserialized - ?? throw new InvalidOperationException($"Failed to deserialize model output to {typeof(TOutput).FullName}"); - } - catch (Exception) when (!cancellationToken.IsCancellationRequested) - { - // A model that runs out of output budget stops mid-token, leaving an unterminated string - // and unclosed braces. Everything written before the cut is still good — and the fields - // that carry the answer are usually written before the long prose that ran out of room — - // so close what was left open and parse that rather than losing the whole response. - TOutput? salvaged = await TryParseTruncatedAsync(cleanedOutput, cancellationToken); - if (salvaged is not null) - return salvaged; - - throw; - } - } - - private async Task TryParseTruncatedAsync(string output, CancellationToken cancellationToken) - { - foreach (string candidate in TruncatedJsonRepair.Candidates(output)) - { - try - { - var deserialized = await serializer.DeserializeAsync(candidate, cancellationToken); - if (deserialized is not null) - return deserialized; - } - catch (Exception) when (!cancellationToken.IsCancellationRequested) - { - // This repair didn't parse either — try the next, more aggressive one. - } - } - - return default; - } - - public IEnumerable Validate(ValidationContext validationContext) - { - yield return Validation.NotNullOrWhiteSpace(Schema); - yield return Validation.Json(Schema); - } - - private string? TrimArtifacts(string? output) - { - if (string.IsNullOrWhiteSpace(output)) - return null; - - string cleaned = output.Trim(); - - if (cleaned.StartsWith("```")) - { - int firstNewline = cleaned.IndexOf('\n'); - cleaned = firstNewline > 0 - ? new string(cleaned.Skip(firstNewline + 1).ToArray()).Trim() - : new string(cleaned.Skip(3).ToArray()).Trim(); - } - - if (cleaned.EndsWith("```")) - cleaned = cleaned[..^3].Trim(); - - string[] commonPrefixes = ["json:", "Output:", "Result:", "Response:"]; - foreach (string prefix in commonPrefixes) - { - if (cleaned.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) - { - cleaned = cleaned[prefix.Length..].Trim(); - break; - } - } - - cleaned = cleaned.Trim('\"', '\''); - - return string.Equals(cleaned, "null", StringComparison.OrdinalIgnoreCase) - ? null - : cleaned; - } -} \ No newline at end of file diff --git a/Proxytrace.Serialization/Internal/JsonSerializer.cs b/Proxytrace.Serialization/Internal/JsonSerializer.cs deleted file mode 100644 index c94b88423..000000000 --- a/Proxytrace.Serialization/Internal/JsonSerializer.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Runtime.Serialization; -using System.Text.Encodings.Web; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; -using Nordstein.Core.Common.Validation; - -namespace Proxytrace.Serialization.Internal; - - /// -internal class JsonSerializer : ISerializer -{ - private readonly JsonSerializerOptions serializerOptions; - - public JsonSerializer() : this([]) { } - - public JsonSerializer(IEnumerable converters) - { - JsonSerializerOptions jsonSerializerOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - WriteIndented = true, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - PropertyNameCaseInsensitive = true, - TypeInfoResolver = new DefaultJsonTypeInfoResolver(), - }; - - jsonSerializerOptions.Converters.Add(new ObjectToInferredTypesConverter()); - jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, allowIntegerValues: true)); - - foreach (var converter in converters) - { - jsonSerializerOptions.Converters.Add(converter); - } - - serializerOptions = jsonSerializerOptions; - } - - /// - public string Serialize(object? input, bool writeIndented = false) - { - if (input is null) - { - return string.Empty; - } - - JsonSerializerOptions options = new JsonSerializerOptions(serializerOptions) - { - WriteIndented = writeIndented - }; - // UnsafeRelaxedJsonEscaping (set on the options above) already emits non-ASCII - // characters unescaped, so no post-processing is needed. The previous \uXXXX - // un-escaping pass corrupted output by turning mandatory control-character escapes - // (e.g. a newline escape inside a string) into raw control characters, producing invalid JSON. - return System.Text.Json.JsonSerializer.Serialize(input, input.GetType(), options); - } - - /// - public async Task DeserializeAsync(string value, CancellationToken cancellationToken = default) - { - try - { - return typeof(TOutput) switch - { - var t when t == typeof(string) => - (TOutput)(object)value, - var t when t == typeof(Guid) => - (TOutput?)(object?)ParseGuid(value), - _ => await ParseObject(value, cancellationToken) - - }; - } - catch(Exception ex) - { - if (ex is OperationCanceledException) - { - throw; - } - throw new SerializationException($"Failed to deserialize model output to {typeof(TOutput).FullName} from {value}", ex); - } - } - - /// - public TOutput? Deserialize(string value) - { - if(typeof(TOutput) == typeof(string)) - { - return (TOutput)(object)value; - } - - TOutput? deserialized = System.Text.Json.JsonSerializer.Deserialize(value, serializerOptions); - if (deserialized is IValidatableObject validatable) - { - validatable.Validate(); - } - return deserialized; - } - - private async Task ParseObject(string data, CancellationToken cancellationToken) - { - using MemoryStream outputStream = new(System.Text.Encoding.UTF8.GetBytes(data)); - TOutput? deserialized = await System.Text.Json.JsonSerializer.DeserializeAsync(outputStream, serializerOptions, - cancellationToken: cancellationToken); - - if (deserialized is IValidatableObject validatable) - { - validatable.Validate(); - } - - return deserialized; - } - - private Guid? ParseGuid(string data) - => Guid.TryParse(data, out Guid result) - ? result - : null; -} \ No newline at end of file diff --git a/Proxytrace.Serialization/Internal/ObjectToInferredTypesConverter.cs b/Proxytrace.Serialization/Internal/ObjectToInferredTypesConverter.cs deleted file mode 100644 index 7de7725cc..000000000 --- a/Proxytrace.Serialization/Internal/ObjectToInferredTypesConverter.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Proxytrace.Serialization.Internal; - -/// -/// A JSON converter that deserializes object? properties to their inferred types instead of JsonElement. -/// This prevents JsonElement from appearing in deserialized results when dealing with Dictionary<string, object?> or similar types. -/// -internal class ObjectToInferredTypesConverter : JsonConverter -{ - /// - public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return reader.TokenType switch - { - JsonTokenType.True => true, - JsonTokenType.False => false, - JsonTokenType.Number when reader.TryGetInt32(out int intValue) => intValue, - JsonTokenType.Number when reader.TryGetInt64(out long longValue) => longValue, - JsonTokenType.Number => reader.GetDouble(), - JsonTokenType.String when reader.TryGetDateTime(out DateTime datetime) => datetime, - JsonTokenType.String when reader.TryGetGuid(out Guid guid) => guid, - JsonTokenType.String => reader.GetString(), - JsonTokenType.Null => null, - JsonTokenType.StartObject => ReadObject(ref reader, options), - JsonTokenType.StartArray => ReadArray(ref reader, options), - _ => JsonDocument.ParseValue(ref reader).RootElement.Clone() - }; - } - - /// - public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) - => System.Text.Json.JsonSerializer.Serialize(writer, value, value.GetType(), options); - - private object ReadObject(ref Utf8JsonReader reader, JsonSerializerOptions options) - { - var dictionary = new Dictionary(); - - while (reader.Read()) - { - if (reader.TokenType == JsonTokenType.EndObject) - { - return dictionary; - } - - if (reader.TokenType != JsonTokenType.PropertyName) - { - throw new JsonException("Expected property name"); - } - - string propertyName = reader.GetString() ?? throw new JsonException("Property name cannot be null"); - reader.Read(); - object? value = Read(ref reader, typeof(object), options); - dictionary[propertyName] = value; - } - - throw new JsonException("Expected end of object"); - } - - private object ReadArray(ref Utf8JsonReader reader, JsonSerializerOptions options) - { - var list = new List(); - - while (reader.Read()) - { - if (reader.TokenType == JsonTokenType.EndArray) - { - return list; - } - - object? value = Read(ref reader, typeof(object), options); - list.Add(value); - } - - throw new JsonException("Expected end of array"); - } -} - diff --git a/Proxytrace.Serialization/Internal/StringOutputFormat.cs b/Proxytrace.Serialization/Internal/StringOutputFormat.cs deleted file mode 100644 index 08d2ec043..000000000 --- a/Proxytrace.Serialization/Internal/StringOutputFormat.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Proxytrace.Serialization.Internal; - -/// -internal record StringOutputFormat : IOutputFormat -{ - /// - public string? ToPromptString() - // not relevant (we don't parse string output) - => null; - - public Task ParseAsync( - string? output, - CancellationToken cancellationToken = default) - => typeof(TOutput) != typeof(string) - ? throw new InvalidOperationException($"StringOutputFormat can only parse to string, not {typeof(TOutput).FullName}") - : Task.FromResult((TOutput?)(object?)output); - - public IEnumerable Validate(ValidationContext validationContext) - { - yield break; - } -} \ No newline at end of file diff --git a/Proxytrace.Serialization/Internal/TruncatedJsonRepair.cs b/Proxytrace.Serialization/Internal/TruncatedJsonRepair.cs deleted file mode 100644 index f7c72c0c5..000000000 --- a/Proxytrace.Serialization/Internal/TruncatedJsonRepair.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Text; - -namespace Proxytrace.Serialization.Internal; - -/// -/// Repairs JSON that a model stopped writing part-way through. A model that runs out of output -/// budget cuts off mid-token, so the document arrives with an unterminated string and unclosed -/// braces — invalid as a whole, even though everything before the cut is intact and usually carries -/// the fields that matter (an evaluator's Score ahead of its long Reasoning prose, for -/// instance). Discarding the answer over its tail throws away a usable verdict. -/// -internal static class TruncatedJsonRepair -{ - /// - /// Returns progressively more aggressive repairs of , best first, for the - /// caller to try in order — each is only a candidate, so the caller decides whether it parses. - /// Empty when the document is already balanced (nothing suggests truncation). - /// - public static IEnumerable Candidates(string json) - { - if (string.IsNullOrWhiteSpace(json)) - yield break; - - var closers = new Stack(); - bool inString = false; - bool escaped = false; - int lastMemberBreak = -1; - string lastMemberBreakClosers = string.Empty; - - for (int i = 0; i < json.Length; i++) - { - char c = json[i]; - - if (inString) - { - if (escaped) - escaped = false; - else if (c == '\\') - escaped = true; - else if (c == '"') - inString = false; - continue; - } - - switch (c) - { - case '"': - inString = true; - break; - case '{': - closers.Push('}'); - break; - case '[': - closers.Push(']'); - break; - case '}': - case ']': - if (closers.Count > 0) - closers.Pop(); - break; - case ',': - // The last point at which the document was structurally complete apart from its - // open containers — where the second candidate cuts back to. A Stack enumerates - // top-down, which is exactly the order the containers have to be closed in. - lastMemberBreak = i; - lastMemberBreakClosers = new string(closers.ToArray()); - break; - } - } - - if (!inString && closers.Count == 0) - yield break; - - // Close what the model left open. Enough whenever the cut fell inside a value — the common - // case, since the longest field is the one that runs out of room. - var closed = new StringBuilder(json); - if (inString) - { - // A trailing lone backslash would escape the quote we are about to add. - if (escaped) - closed.Length--; - closed.Append('"'); - } - - foreach (char closer in closers) - closed.Append(closer); - - yield return closed.ToString(); - - // The cut fell in a place closing cannot rescue (mid-key, after a colon, inside a partial - // escape sequence). Drop the incomplete member entirely and keep the ones before it. - if (lastMemberBreak >= 0) - yield return json[..lastMemberBreak] + lastMemberBreakClosers; - } -} diff --git a/Proxytrace.Serialization/Module.cs b/Proxytrace.Serialization/Module.cs deleted file mode 100644 index 63c786a6d..000000000 --- a/Proxytrace.Serialization/Module.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Autofac; -using JetBrains.Annotations; -using Proxytrace.Serialization.Internal; - -namespace Proxytrace.Serialization; - -/// -/// Sets up serialization services -/// -[UsedImplicitly] -public sealed class Module : Autofac.Module -{ - /// - /// Add serialization services - /// - protected override void Load(ContainerBuilder builder) - { - base.Load(builder); - builder.RegisterType().As(); - builder.RegisterType().AsSelf(); - builder.RegisterType().AsSelf(); - - builder.Register(c => - { - var ctx = c.Resolve(); - return type => - { - if (type == typeof(string)) - return ctx.Resolve(); - return ctx.Resolve(new TypedParameter(typeof(Type), type)); - }; - }).AsSelf(); - } -} diff --git a/Proxytrace.Serialization/Proxytrace.Serialization.csproj b/Proxytrace.Serialization/Proxytrace.Serialization.csproj deleted file mode 100644 index 5e6e6c70c..000000000 --- a/Proxytrace.Serialization/Proxytrace.Serialization.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - net10.0 - enable - enable - 13 - true - - - - - - - - - - - - - - - - diff --git a/Proxytrace.Storage.Tests/AgentCallListQueryTests.cs b/Proxytrace.Storage.Tests/AgentCallListQueryTests.cs index bf024eb54..ba4315a65 100644 --- a/Proxytrace.Storage.Tests/AgentCallListQueryTests.cs +++ b/Proxytrace.Storage.Tests/AgentCallListQueryTests.cs @@ -4,10 +4,9 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; -using Proxytrace.Domain.Usage; using Nordstein.Core.Testing; namespace Proxytrace.Storage.Tests; @@ -57,7 +56,7 @@ public async Task GetFilteredList_PreviewMatchesFirstUserMessage() var call = await gen.CreateAsync(CancellationToken); var expectedPreview = call.Request.Messages - .OfType() + .OfType() .FirstOrDefault()?.GetText(); var (items, _) = await repo.GetFilteredListAsync(new AgentCallFilter(), 1, 50, CancellationToken); diff --git a/Proxytrace.Storage.Tests/AgentCallPreviewBackfillTests.cs b/Proxytrace.Storage.Tests/AgentCallPreviewBackfillTests.cs index c5c52e1d6..bb93dd316 100644 --- a/Proxytrace.Storage.Tests/AgentCallPreviewBackfillTests.cs +++ b/Proxytrace.Storage.Tests/AgentCallPreviewBackfillTests.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Proxytrace.Domain; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Storage.Internal; using Proxytrace.Storage.Internal.Entities.AgentCall; using Nordstein.Core.Testing; diff --git a/Proxytrace.Storage.Tests/AgentCallPreviewTests.cs b/Proxytrace.Storage.Tests/AgentCallPreviewTests.cs index ce46adc0e..af209b813 100644 --- a/Proxytrace.Storage.Tests/AgentCallPreviewTests.cs +++ b/Proxytrace.Storage.Tests/AgentCallPreviewTests.cs @@ -1,5 +1,5 @@ using AwesomeAssertions; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Storage.Internal.Entities.AgentCall; namespace Proxytrace.Storage.Tests; diff --git a/Proxytrace.Storage.Tests/AgentCallStatsQueriesTests.cs b/Proxytrace.Storage.Tests/AgentCallStatsQueriesTests.cs index 56f9f9771..91421ac62 100644 --- a/Proxytrace.Storage.Tests/AgentCallStatsQueriesTests.cs +++ b/Proxytrace.Storage.Tests/AgentCallStatsQueriesTests.cs @@ -5,13 +5,12 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Inference; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Usage; using Nordstein.Core.Testing; namespace Proxytrace.Storage.Tests; diff --git a/Proxytrace.Storage.Tests/AgentCallSummaryQueryTests.cs b/Proxytrace.Storage.Tests/AgentCallSummaryQueryTests.cs index df81c984a..0ccf81082 100644 --- a/Proxytrace.Storage.Tests/AgentCallSummaryQueryTests.cs +++ b/Proxytrace.Storage.Tests/AgentCallSummaryQueryTests.cs @@ -4,12 +4,11 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.Model; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; -using Proxytrace.Domain.Usage; using Nordstein.Core.Testing; namespace Proxytrace.Storage.Tests; diff --git a/Proxytrace.Storage.Tests/AgentCallToolBackfillTests.cs b/Proxytrace.Storage.Tests/AgentCallToolBackfillTests.cs index 6bcd6c22e..08f6fffb6 100644 --- a/Proxytrace.Storage.Tests/AgentCallToolBackfillTests.cs +++ b/Proxytrace.Storage.Tests/AgentCallToolBackfillTests.cs @@ -7,9 +7,8 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Storage.Internal; using Proxytrace.Storage.Internal.Entities.AgentCall; using Nordstein.Core.Testing; diff --git a/Proxytrace.Storage.Tests/AgentCallToolTests.cs b/Proxytrace.Storage.Tests/AgentCallToolTests.cs index 902d37865..dae1d8077 100644 --- a/Proxytrace.Storage.Tests/AgentCallToolTests.cs +++ b/Proxytrace.Storage.Tests/AgentCallToolTests.cs @@ -5,13 +5,12 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Inference; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.Project; -using Proxytrace.Domain.Usage; using Proxytrace.Storage.Internal.Entities.AgentCall; using Nordstein.Core.Testing; diff --git a/Proxytrace.Storage.Tests/AgentFingerprintTests.cs b/Proxytrace.Storage.Tests/AgentFingerprintTests.cs index 986853ccd..bca0f95b7 100644 --- a/Proxytrace.Storage.Tests/AgentFingerprintTests.cs +++ b/Proxytrace.Storage.Tests/AgentFingerprintTests.cs @@ -3,11 +3,12 @@ using Microsoft.Extensions.DependencyInjection; using Proxytrace.Domain; using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Nordstein.Core.Testing; namespace Proxytrace.Storage.Tests; diff --git a/Proxytrace.Storage.Tests/AnomalyStatsQueriesTests.cs b/Proxytrace.Storage.Tests/AnomalyStatsQueriesTests.cs index 1cf9e93eb..bd2443ebe 100644 --- a/Proxytrace.Storage.Tests/AnomalyStatsQueriesTests.cs +++ b/Proxytrace.Storage.Tests/AnomalyStatsQueriesTests.cs @@ -5,9 +5,8 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Nordstein.Core.Testing; namespace Proxytrace.Storage.Tests; diff --git a/Proxytrace.Storage.Tests/EvaluationStatBackfillTests.cs b/Proxytrace.Storage.Tests/EvaluationStatBackfillTests.cs index 3a864953c..7cbe9a5e7 100644 --- a/Proxytrace.Storage.Tests/EvaluationStatBackfillTests.cs +++ b/Proxytrace.Storage.Tests/EvaluationStatBackfillTests.cs @@ -4,13 +4,12 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; using Proxytrace.Domain; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Evaluation; using Proxytrace.Domain.Evaluator; using Proxytrace.Domain.Statistics; using Proxytrace.Domain.TestCase; using Proxytrace.Domain.TestResult; -using Proxytrace.Domain.Usage; using Proxytrace.Storage.Internal; using Proxytrace.Storage.Internal.Entities.TestResult; using Nordstein.Core.Testing; diff --git a/Proxytrace.Storage.Tests/EvaluatorStatsQueriesTests.cs b/Proxytrace.Storage.Tests/EvaluatorStatsQueriesTests.cs index 128a74b31..fafd1335b 100644 --- a/Proxytrace.Storage.Tests/EvaluatorStatsQueriesTests.cs +++ b/Proxytrace.Storage.Tests/EvaluatorStatsQueriesTests.cs @@ -3,13 +3,12 @@ using Microsoft.Extensions.DependencyInjection; using Proxytrace.Domain.Statistics; using Proxytrace.Domain; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Evaluation; using Proxytrace.Domain.Evaluator; using Proxytrace.Domain.Project; using Proxytrace.Domain.TestCase; using Proxytrace.Domain.TestResult; -using Proxytrace.Domain.Usage; using Proxytrace.Storage.Internal.Entities.Evaluator; using Proxytrace.Storage.Internal.Entities.TestResult; using Nordstein.Core.Testing; diff --git a/Proxytrace.Storage.Tests/Module.cs b/Proxytrace.Storage.Tests/Module.cs index f41db98f2..4d9ad538e 100644 --- a/Proxytrace.Storage.Tests/Module.cs +++ b/Proxytrace.Storage.Tests/Module.cs @@ -1,4 +1,5 @@ using Autofac; +using Nordstein.Core.AI.Clients; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; using Nordstein.Core.Testing; @@ -24,4 +25,4 @@ protected override void Load(ContainerBuilder builder) builder.RegisterStub(); builder.RegisterStub(); } -} \ No newline at end of file +} diff --git a/Proxytrace.Storage.Tests/OptimizationProposalRepositoryScopeTests.cs b/Proxytrace.Storage.Tests/OptimizationProposalRepositoryScopeTests.cs index bf0c130b5..faffc8e94 100644 --- a/Proxytrace.Storage.Tests/OptimizationProposalRepositoryScopeTests.cs +++ b/Proxytrace.Storage.Tests/OptimizationProposalRepositoryScopeTests.cs @@ -2,10 +2,11 @@ using Microsoft.Extensions.DependencyInjection; using Proxytrace.Domain; using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.OptimizationProposal; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestRun; diff --git a/Proxytrace.Storage.Tests/OutlierBaselineQueriesTests.cs b/Proxytrace.Storage.Tests/OutlierBaselineQueriesTests.cs index 7c89c486a..66ec37dcb 100644 --- a/Proxytrace.Storage.Tests/OutlierBaselineQueriesTests.cs +++ b/Proxytrace.Storage.Tests/OutlierBaselineQueriesTests.cs @@ -5,9 +5,8 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Nordstein.Core.Testing; namespace Proxytrace.Storage.Tests; diff --git a/Proxytrace.Storage.Tests/ProjectScopedRepositoryTests.cs b/Proxytrace.Storage.Tests/ProjectScopedRepositoryTests.cs index 61e0f680c..1faad3331 100644 --- a/Proxytrace.Storage.Tests/ProjectScopedRepositoryTests.cs +++ b/Proxytrace.Storage.Tests/ProjectScopedRepositoryTests.cs @@ -2,10 +2,11 @@ using Microsoft.Extensions.DependencyInjection; using Proxytrace.Domain; using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.OptimizationProposal; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.Proposal; using Proxytrace.Domain.TestCase; diff --git a/Proxytrace.Storage.Tests/SessionTraceRemovalQueryTests.cs b/Proxytrace.Storage.Tests/SessionTraceRemovalQueryTests.cs index 9632577a4..329f0a7d4 100644 --- a/Proxytrace.Storage.Tests/SessionTraceRemovalQueryTests.cs +++ b/Proxytrace.Storage.Tests/SessionTraceRemovalQueryTests.cs @@ -4,10 +4,9 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.Session; -using Proxytrace.Domain.Usage; using Nordstein.Core.Testing; namespace Proxytrace.Storage.Tests; diff --git a/Proxytrace.Storage.Tests/TestCaseRepositoryTests.cs b/Proxytrace.Storage.Tests/TestCaseRepositoryTests.cs index cce1cada8..699637177 100644 --- a/Proxytrace.Storage.Tests/TestCaseRepositoryTests.cs +++ b/Proxytrace.Storage.Tests/TestCaseRepositoryTests.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Proxytrace.Domain; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.TestCase; using Nordstein.Core.Testing; diff --git a/Proxytrace.Storage.Tests/TestDomainEntities.cs b/Proxytrace.Storage.Tests/TestDomainEntities.cs index 944b3bc4c..c61b20a21 100644 --- a/Proxytrace.Storage.Tests/TestDomainEntities.cs +++ b/Proxytrace.Storage.Tests/TestDomainEntities.cs @@ -126,4 +126,4 @@ public Task Repository_UpsertNonExistingAsync_AddsEntity(Type storedEntityType) [DynamicData(nameof(GetStoredEntityTypes))] public Task Repository_RemoveAsync_RemovesEntity(Type storedEntityType) => GetTestCases(storedEntityType).Repository_RemoveAsync_RemovesEntity(); -} \ No newline at end of file +} diff --git a/Proxytrace.Storage.Tests/TestResultRepositoryTests.cs b/Proxytrace.Storage.Tests/TestResultRepositoryTests.cs index 87c11e688..2fd05ea4d 100644 --- a/Proxytrace.Storage.Tests/TestResultRepositoryTests.cs +++ b/Proxytrace.Storage.Tests/TestResultRepositoryTests.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Proxytrace.Domain; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Evaluation; using Proxytrace.Domain.Evaluator; using Proxytrace.Domain.TestCase; diff --git a/Proxytrace.Storage.Tests/TestRunRepositoryTests.cs b/Proxytrace.Storage.Tests/TestRunRepositoryTests.cs index ea9ad1645..1a31d9150 100644 --- a/Proxytrace.Storage.Tests/TestRunRepositoryTests.cs +++ b/Proxytrace.Storage.Tests/TestRunRepositoryTests.cs @@ -2,9 +2,10 @@ using Microsoft.Extensions.DependencyInjection; using Proxytrace.Domain; using Proxytrace.Domain.Agent; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.TestResult; using Proxytrace.Domain.TestRun; diff --git a/Proxytrace.Storage.Tests/TestRunStatsStoreTests.cs b/Proxytrace.Storage.Tests/TestRunStatsStoreTests.cs index 2995e65da..4c9b2404d 100644 --- a/Proxytrace.Storage.Tests/TestRunStatsStoreTests.cs +++ b/Proxytrace.Storage.Tests/TestRunStatsStoreTests.cs @@ -4,7 +4,7 @@ using Proxytrace.Domain.Statistics.TestRun; using Proxytrace.Domain; using Proxytrace.Domain.TestRun; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; using Nordstein.Core.Testing; namespace Proxytrace.Storage.Tests; diff --git a/Proxytrace.Storage/Internal/Entities/Agent/AgentConfig.cs b/Proxytrace.Storage/Internal/Entities/Agent/AgentConfig.cs index 4b5130c01..d0dc607db 100644 --- a/Proxytrace.Storage/Internal/Entities/Agent/AgentConfig.cs +++ b/Proxytrace.Storage/Internal/Entities/Agent/AgentConfig.cs @@ -5,7 +5,7 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentVersion; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; using Proxytrace.Storage.Internal.Entities.Inference; diff --git a/Proxytrace.Storage/Internal/Entities/Agent/AgentRepository.cs b/Proxytrace.Storage/Internal/Entities/Agent/AgentRepository.cs index 033a53e6c..c1ed6c01f 100644 --- a/Proxytrace.Storage/Internal/Entities/Agent/AgentRepository.cs +++ b/Proxytrace.Storage/Internal/Entities/Agent/AgentRepository.cs @@ -7,11 +7,12 @@ using Proxytrace.Domain.AgentVersion; using Nordstein.Core.Domain.Events; using Nordstein.Core.Domain.Exceptions; -using Proxytrace.Domain.Inference; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Proxytrace.Storage.Internal.Entities.AgentVersion; namespace Proxytrace.Storage.Internal.Entities.Agent; diff --git a/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallConfig.cs b/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallConfig.cs index 0e6da58c4..80e85f11f 100644 --- a/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallConfig.cs +++ b/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallConfig.cs @@ -7,11 +7,9 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; using Proxytrace.Domain.AgentVersion; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Inference; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; -using Proxytrace.Domain.Usage; using Proxytrace.Storage.Internal.Entities.Agent; using Proxytrace.Storage.Internal.Entities.AgentVersion; using Proxytrace.Storage.Internal.Entities.Inference; diff --git a/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallEntity.cs b/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallEntity.cs index 21d920c11..df5bffbb9 100644 --- a/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallEntity.cs +++ b/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallEntity.cs @@ -1,5 +1,5 @@ using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Storage.Internal.Entities.Inference; namespace Proxytrace.Storage.Internal.Entities.AgentCall; diff --git a/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallPreview.cs b/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallPreview.cs index 041509179..108118f44 100644 --- a/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallPreview.cs +++ b/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallPreview.cs @@ -1,5 +1,5 @@ using System.Text.RegularExpressions; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; namespace Proxytrace.Storage.Internal.Entities.AgentCall; diff --git a/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallRepository.cs b/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallRepository.cs index 07ac0ac24..89d6db324 100644 --- a/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallRepository.cs +++ b/Proxytrace.Storage/Internal/Entities/AgentCall/AgentCallRepository.cs @@ -11,7 +11,7 @@ using Nordstein.Core.Domain.Paging; using Proxytrace.Domain.Project; using Proxytrace.Domain.Search; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; using Proxytrace.Storage.Internal.Entities.Agent; using Proxytrace.Storage.Internal.Entities.AgentVersion; using Proxytrace.Storage.Internal.Entities.Model; diff --git a/Proxytrace.Storage/Internal/Entities/AgentVersion/AgentVersionConfig.cs b/Proxytrace.Storage/Internal/Entities/AgentVersion/AgentVersionConfig.cs index d966a2ca8..a4598e2c7 100644 --- a/Proxytrace.Storage/Internal/Entities/AgentVersion/AgentVersionConfig.cs +++ b/Proxytrace.Storage/Internal/Entities/AgentVersion/AgentVersionConfig.cs @@ -2,8 +2,9 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Nordstein.Core.Common.Serialization; using Proxytrace.Domain.AgentVersion; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Proxytrace.Storage.Internal.Entities.Agent; using Proxytrace.Storage.Internal.Entities.Project; diff --git a/Proxytrace.Storage/Internal/Entities/AgentVersion/AgentVersionEntity.cs b/Proxytrace.Storage/Internal/Entities/AgentVersion/AgentVersionEntity.cs index c4b1c6e72..01094970a 100644 --- a/Proxytrace.Storage/Internal/Entities/AgentVersion/AgentVersionEntity.cs +++ b/Proxytrace.Storage/Internal/Entities/AgentVersion/AgentVersionEntity.cs @@ -1,5 +1,5 @@ using Proxytrace.Domain.AgentVersion; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; using Proxytrace.Storage.Internal.Entities.Agent; namespace Proxytrace.Storage.Internal.Entities.AgentVersion; diff --git a/Proxytrace.Storage/Internal/Entities/AgentVersion/AgentVersionRepository.cs b/Proxytrace.Storage/Internal/Entities/AgentVersion/AgentVersionRepository.cs index 2e0e5d0a7..d9f2e854b 100644 --- a/Proxytrace.Storage/Internal/Entities/AgentVersion/AgentVersionRepository.cs +++ b/Proxytrace.Storage/Internal/Entities/AgentVersion/AgentVersionRepository.cs @@ -5,8 +5,9 @@ using Proxytrace.Domain.AgentVersion; using Nordstein.Core.Domain.Events; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Storage.Internal.Entities.AgentVersion; diff --git a/Proxytrace.Storage/Internal/Entities/Inference/ModelParametersData.cs b/Proxytrace.Storage/Internal/Entities/Inference/ModelParametersData.cs index 02cdf0463..b4ba9be0e 100644 --- a/Proxytrace.Storage/Internal/Entities/Inference/ModelParametersData.cs +++ b/Proxytrace.Storage/Internal/Entities/Inference/ModelParametersData.cs @@ -1,7 +1,7 @@ namespace Proxytrace.Storage.Internal.Entities.Inference; /// -/// Storage value object for serializing as JSON. +/// Storage value object for serializing as JSON. /// internal record ModelParametersData( double? Temperature, diff --git a/Proxytrace.Storage/Internal/Entities/OptimizationProposal/OptimizationProposalData.cs b/Proxytrace.Storage/Internal/Entities/OptimizationProposal/OptimizationProposalData.cs index ffc22dc67..1c664eaef 100644 --- a/Proxytrace.Storage/Internal/Entities/OptimizationProposal/OptimizationProposalData.cs +++ b/Proxytrace.Storage/Internal/Entities/OptimizationProposal/OptimizationProposalData.cs @@ -1,4 +1,4 @@ -using Proxytrace.Domain.Tools; +using Nordstein.Core.AI.Tools; namespace Proxytrace.Storage.Internal.Entities.OptimizationProposal; diff --git a/Proxytrace.Storage/Internal/Entities/TestCase/TestCaseConfig.cs b/Proxytrace.Storage/Internal/Entities/TestCase/TestCaseConfig.cs index 94fdf61cf..0684256c8 100644 --- a/Proxytrace.Storage/Internal/Entities/TestCase/TestCaseConfig.cs +++ b/Proxytrace.Storage/Internal/Entities/TestCase/TestCaseConfig.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Nordstein.Core.Common.Async; using Nordstein.Core.Common.Serialization; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.TestCase; namespace Proxytrace.Storage.Internal.Entities.TestCase; diff --git a/Proxytrace.Storage/Internal/Entities/TestCase/TestCaseEntity.cs b/Proxytrace.Storage/Internal/Entities/TestCase/TestCaseEntity.cs index 61654b2b2..2349af77c 100644 --- a/Proxytrace.Storage/Internal/Entities/TestCase/TestCaseEntity.cs +++ b/Proxytrace.Storage/Internal/Entities/TestCase/TestCaseEntity.cs @@ -1,4 +1,4 @@ -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.TestCase; namespace Proxytrace.Storage.Internal.Entities.TestCase; diff --git a/Proxytrace.Storage/Internal/Entities/TestResult/TestResultConfig.cs b/Proxytrace.Storage/Internal/Entities/TestResult/TestResultConfig.cs index 66aa9ded2..320d19730 100644 --- a/Proxytrace.Storage/Internal/Entities/TestResult/TestResultConfig.cs +++ b/Proxytrace.Storage/Internal/Entities/TestResult/TestResultConfig.cs @@ -6,10 +6,10 @@ using Proxytrace.Domain.Evaluation; using Proxytrace.Domain.Evaluation.Internal; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.TestCase; using Proxytrace.Domain.TestResult; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; using Proxytrace.Storage.Internal.Entities.TestCase; namespace Proxytrace.Storage.Internal.Entities.TestResult; diff --git a/Proxytrace.Storage/Internal/Entities/TestResult/TestResultEntity.cs b/Proxytrace.Storage/Internal/Entities/TestResult/TestResultEntity.cs index 3139cd0e2..3b4585391 100644 --- a/Proxytrace.Storage/Internal/Entities/TestResult/TestResultEntity.cs +++ b/Proxytrace.Storage/Internal/Entities/TestResult/TestResultEntity.cs @@ -1,5 +1,5 @@ using Proxytrace.Domain.Evaluation; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.TestResult; namespace Proxytrace.Storage.Internal.Entities.TestResult; diff --git a/Proxytrace.Storage/Internal/InMemoryConfiguration.cs b/Proxytrace.Storage/Internal/InMemoryConfiguration.cs index 5a21e17e4..99119050d 100644 --- a/Proxytrace.Storage/Internal/InMemoryConfiguration.cs +++ b/Proxytrace.Storage/Internal/InMemoryConfiguration.cs @@ -12,4 +12,4 @@ internal record InMemoryConfiguration : StorageConfiguration /// The name of the in-memory storage instance /// public string Name { get; } = Guid.NewGuid().ToString(); -} \ No newline at end of file +} diff --git a/Proxytrace.Storage/Internal/Statistics/AgentCallStatsQueries.cs b/Proxytrace.Storage/Internal/Statistics/AgentCallStatsQueries.cs index f5f05b2a9..c020a739c 100644 --- a/Proxytrace.Storage/Internal/Statistics/AgentCallStatsQueries.cs +++ b/Proxytrace.Storage/Internal/Statistics/AgentCallStatsQueries.cs @@ -4,7 +4,7 @@ using Proxytrace.Domain.Statistics; using Proxytrace.Domain.AgentCall; using Proxytrace.Domain.ModelEndpoint; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; using Proxytrace.Storage.Internal.Entities.Agent; using Proxytrace.Storage.Internal.Entities.AgentCall; using Proxytrace.Storage.Internal.Entities.AgentVersion; diff --git a/Proxytrace.Storage/Internal/Statistics/TestRunStatsStore.cs b/Proxytrace.Storage/Internal/Statistics/TestRunStatsStore.cs index 14dd9eafd..4ce7a953f 100644 --- a/Proxytrace.Storage/Internal/Statistics/TestRunStatsStore.cs +++ b/Proxytrace.Storage/Internal/Statistics/TestRunStatsStore.cs @@ -3,7 +3,7 @@ using Proxytrace.Domain.Statistics; using Proxytrace.Domain.Statistics.TestRun; using Proxytrace.Domain; -using Proxytrace.Domain.Usage; +using Nordstein.Core.AI.Completions; using Proxytrace.Storage.Internal.Entities.Statistics; namespace Proxytrace.Storage.Internal.Statistics; diff --git a/Proxytrace.Storage/StorageConfiguration.cs b/Proxytrace.Storage/StorageConfiguration.cs index ed49263fa..51548af26 100644 --- a/Proxytrace.Storage/StorageConfiguration.cs +++ b/Proxytrace.Storage/StorageConfiguration.cs @@ -31,4 +31,4 @@ public static StorageConfiguration Postgres( ConnectionString = connectionString, CryptographyKeyBase64 = cryptographyKeyBase64 }; -} \ No newline at end of file +} diff --git a/Proxytrace.Storage/StorageDbContextFactory.cs b/Proxytrace.Storage/StorageDbContextFactory.cs index dd6852cc0..cbad09b09 100644 --- a/Proxytrace.Storage/StorageDbContextFactory.cs +++ b/Proxytrace.Storage/StorageDbContextFactory.cs @@ -8,6 +8,7 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.ModelProvider; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Domain.Security; diff --git a/Proxytrace.sln b/Proxytrace.sln index f83b88a96..888e062ae 100644 --- a/Proxytrace.sln +++ b/Proxytrace.sln @@ -20,10 +20,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Proxytrace.Infrastructure", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Proxytrace.Application.Tests", "Proxytrace.Application.Tests\Proxytrace.Application.Tests.csproj", "{29B94DF9-6328-4856-B2FF-51D25EB95102}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Proxytrace.Serialization", "Proxytrace.Serialization\Proxytrace.Serialization.csproj", "{6E3CF061-7720-46F5-B3D9-122F45CCFE4D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Proxytrace.Serialization.Tests", "Proxytrace.Serialization.Tests\Proxytrace.Serialization.Tests.csproj", "{3B49F4B7-8CFA-4193-8D7B-E9E4756AF9FD}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Proxytrace.Infrastructure.Tests", "Proxytrace.Infrastructure.Tests\Proxytrace.Infrastructure.Tests.csproj", "{AEE1CE68-1506-40FF-9CFE-82A2FC10875B}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DockerCompose", "DockerCompose", "{4B473322-9224-4BE0-AC70-8A0172C8E431}" @@ -176,30 +172,6 @@ Global {29B94DF9-6328-4856-B2FF-51D25EB95102}.Release|x64.Build.0 = Release|Any CPU {29B94DF9-6328-4856-B2FF-51D25EB95102}.Release|x86.ActiveCfg = Release|Any CPU {29B94DF9-6328-4856-B2FF-51D25EB95102}.Release|x86.Build.0 = Release|Any CPU - {6E3CF061-7720-46F5-B3D9-122F45CCFE4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6E3CF061-7720-46F5-B3D9-122F45CCFE4D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6E3CF061-7720-46F5-B3D9-122F45CCFE4D}.Debug|x64.ActiveCfg = Debug|Any CPU - {6E3CF061-7720-46F5-B3D9-122F45CCFE4D}.Debug|x64.Build.0 = Debug|Any CPU - {6E3CF061-7720-46F5-B3D9-122F45CCFE4D}.Debug|x86.ActiveCfg = Debug|Any CPU - {6E3CF061-7720-46F5-B3D9-122F45CCFE4D}.Debug|x86.Build.0 = Debug|Any CPU - {6E3CF061-7720-46F5-B3D9-122F45CCFE4D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6E3CF061-7720-46F5-B3D9-122F45CCFE4D}.Release|Any CPU.Build.0 = Release|Any CPU - {6E3CF061-7720-46F5-B3D9-122F45CCFE4D}.Release|x64.ActiveCfg = Release|Any CPU - {6E3CF061-7720-46F5-B3D9-122F45CCFE4D}.Release|x64.Build.0 = Release|Any CPU - {6E3CF061-7720-46F5-B3D9-122F45CCFE4D}.Release|x86.ActiveCfg = Release|Any CPU - {6E3CF061-7720-46F5-B3D9-122F45CCFE4D}.Release|x86.Build.0 = Release|Any CPU - {3B49F4B7-8CFA-4193-8D7B-E9E4756AF9FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3B49F4B7-8CFA-4193-8D7B-E9E4756AF9FD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3B49F4B7-8CFA-4193-8D7B-E9E4756AF9FD}.Debug|x64.ActiveCfg = Debug|Any CPU - {3B49F4B7-8CFA-4193-8D7B-E9E4756AF9FD}.Debug|x64.Build.0 = Debug|Any CPU - {3B49F4B7-8CFA-4193-8D7B-E9E4756AF9FD}.Debug|x86.ActiveCfg = Debug|Any CPU - {3B49F4B7-8CFA-4193-8D7B-E9E4756AF9FD}.Debug|x86.Build.0 = Debug|Any CPU - {3B49F4B7-8CFA-4193-8D7B-E9E4756AF9FD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3B49F4B7-8CFA-4193-8D7B-E9E4756AF9FD}.Release|Any CPU.Build.0 = Release|Any CPU - {3B49F4B7-8CFA-4193-8D7B-E9E4756AF9FD}.Release|x64.ActiveCfg = Release|Any CPU - {3B49F4B7-8CFA-4193-8D7B-E9E4756AF9FD}.Release|x64.Build.0 = Release|Any CPU - {3B49F4B7-8CFA-4193-8D7B-E9E4756AF9FD}.Release|x86.ActiveCfg = Release|Any CPU - {3B49F4B7-8CFA-4193-8D7B-E9E4756AF9FD}.Release|x86.Build.0 = Release|Any CPU {AEE1CE68-1506-40FF-9CFE-82A2FC10875B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AEE1CE68-1506-40FF-9CFE-82A2FC10875B}.Debug|Any CPU.Build.0 = Debug|Any CPU {AEE1CE68-1506-40FF-9CFE-82A2FC10875B}.Debug|x64.ActiveCfg = Debug|Any CPU diff --git a/core b/core index 5ffeb440c..3120a0924 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 5ffeb440c5cc2a03acfeefed926237f0c8918a6f +Subproject commit 3120a0924de4ae5b0df865258ca96aa101b249a1 diff --git a/deploy/allinone/Dockerfile b/deploy/allinone/Dockerfile index b8339b404..f09b52fd2 100644 --- a/deploy/allinone/Dockerfile +++ b/deploy/allinone/Dockerfile @@ -62,7 +62,6 @@ COPY ["Proxytrace.Application/Proxytrace.Application.csproj", "Proxytrace.Applic COPY ["Proxytrace.Domain/Proxytrace.Domain.csproj", "Proxytrace.Domain/"] COPY ["Proxytrace.Infrastructure/Proxytrace.Infrastructure.csproj", "Proxytrace.Infrastructure/"] COPY ["Proxytrace.Messaging/Proxytrace.Messaging.csproj", "Proxytrace.Messaging/"] -COPY ["Proxytrace.Serialization/Proxytrace.Serialization.csproj", "Proxytrace.Serialization/"] COPY ["Proxytrace.Storage/Proxytrace.Storage.csproj", "Proxytrace.Storage/"] RUN dotnet restore "Proxytrace.Api/Proxytrace.Api.csproj" -a $TARGETARCH \ && dotnet restore "Proxytrace.Proxy.Api/Proxytrace.Proxy.Api.csproj" -a $TARGETARCH diff --git a/docs/architecture.md b/docs/architecture.md index 23db142ec..d82a92868 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -3,8 +3,8 @@ Strict layered dependency flow — each layer may only depend on layers below it: ``` -Proxytrace.Api → Proxytrace.Application → Proxytrace.Domain → Nordstein.Core.Domain → Nordstein.Core.Common - → Proxytrace.Infrastructure → Proxytrace.Domain + Proxytrace.Serialization → Nordstein.Core.Domain/Common +Proxytrace.Api → Proxytrace.Application → Proxytrace.Domain → Nordstein.Core.AI/Domain → Nordstein.Core.Common + → Proxytrace.Infrastructure → Proxytrace.Domain → Nordstein.Core.AI/Domain/Common → Proxytrace.Storage → Proxytrace.Domain + Nordstein.Core.Domain ``` @@ -12,7 +12,7 @@ Proxytrace.Api → Proxytrace.Application → Proxytrace.Domain → Nordst > `Nordstein.Core.Common` (and their test-side sibling `Nordstein.Core.Testing`) are > product-agnostic and live under [`core/`](../core/) as > their own solution and separate repository, on their way to NuGet packages. The dependency -> arrow only ever points *into* them — Core knows nothing about agents, traces or projects, and +> arrow only ever points *into* them — Core knows nothing about traces or projects (the *generic* agent vocabulary lives in `Nordstein.Core.AI`), and > a change that would teach it belongs in `Proxytrace.Domain` instead. Consuming projects > declare `` rather than a direct reference; see > [`code-reuse.md`](code-reuse.md). Core's own internals — package layering, the Autofac @@ -21,7 +21,7 @@ Proxytrace.Api → Proxytrace.Application → Proxytrace.Domain → Nordst > [`core/docs/domain.md`](../core/docs/domain.md); any change under `core/` follows > [`core/CLAUDE.md`](../core/CLAUDE.md) and updates those docs, not this page. -> **`Proxytrace.Storage` references only `Domain` and `Nordstein.Core.Domain`** (+ `Serialization`/`Common` transitively) — it does +> **`Proxytrace.Storage` references only `Domain` and `Nordstein.Core.Domain`** (+ `Nordstein.Core.AI`/`Common` transitively) — it does > **not** reference `Application`. Most secondary-port **interfaces** that `Storage` implements live in > `Domain` alongside the pure DTOs they expose: the statistics/test-run readers + writer > (`Domain.Statistics`, with `TestRunStats` and the result @@ -33,7 +33,7 @@ Proxytrace.Api → Proxytrace.Application → Proxytrace.Domain → Nordst > `ISecretIndexer` and its persisted scheme remain in `Domain.Security`. The **implementations** (`Internal/*`), > hosted services, and the audit *capture* pipeline stay in `Application` (issue #270). > -> `Proxytrace.Infrastructure` depends only on `Domain` + `Serialization` (it has **no** reference to +> `Proxytrace.Infrastructure` depends only on `Domain` (+ `Nordstein.Core.AI` transitively; it has **no** reference to > `Application`): the kiosk option records it needs (`KioskOptions`, `KioskEndpointOptions`) live in > `Proxytrace.Domain.Kiosk`, and it now also hosts the at-rest secret seam's Data Protection-backed > **implementation** + DI module (`Proxytrace.Infrastructure.Security.SecretProtectionModule`) — the @@ -46,7 +46,7 @@ Proxytrace.Api → Proxytrace.Application → Proxytrace.Domain → Nordst - **Proxytrace.Application** — Use-case orchestration: ingestion (`OpenAiCallParser`, `AgentCallIngestor`), test running (`TestRunnerService`), optimization, test-case synthesis (`TestCase/ITestCaseSynthesisService` — proposes cases from a captured conversation), SSE broadcasters (`TraceBroadcaster`, `TestResultBroadcaster`, `ProposalBroadcaster`), demo data seeding (`IDatabaseInitializer`) - **Proxytrace.Domain** — Proxytrace business entities, value objects, specialized repositories, and business rules. Pure C#, no I/O. - **Proxytrace.Infrastructure** — External service integration. `ModelClient` wraps `Microsoft.Extensions.AI` + the OpenAI SDK to invoke LLMs. -- **Proxytrace.Serialization** — JSON serializers and output formats (`ISerializer`, `IOutputFormat`, `ObjectToInferredTypesConverter`). +- **Nordstein.Core.AI** ([`core/`](../core/)) — Product-agnostic AI foundation: message/conversation types, tool specifications, prompt templates, completions, the versionless `IAgent`/`IModelClient` contracts, and structured output parsing (`IOutputFormat`, `ITextSerializer`). See [`core/docs/ai.md`](../core/docs/ai.md). - **Proxytrace.Storage** — EF Core entities, configurations, mappers, migrations. Provider auto-detected (SQLite / PostgreSQL / SQL Server). - **Nordstein.Core.Common** ([`core/`](../core/)) — Product-agnostic utilities: validation helpers, async/type extensions, DI extensions, clock and randomness seams, secret-protection/hash contracts, hosting defaults. - **Nordstein.Core.Domain** ([`core/`](../core/)) — Product-agnostic domain foundation: entity/object contracts and bases, repositories, transactions, generators, paging, persistence exceptions, entity events, and consuming-assembly discovery. @@ -84,7 +84,7 @@ Your Agent ──► Proxytrace.Proxy.Api ──► Upstream LLM provider ## Dependency Injection (Autofac) -DI is wired with Autofac. Each project ships a `Module : Autofac.Module` (`Proxytrace.Domain.Module`, `Proxytrace.Application.Module`, `Proxytrace.Storage.Module`, `Proxytrace.Infrastructure.Module`, `Proxytrace.Serialization.Module`, `Nordstein.Core.Common.Module`, `Nordstein.Core.Domain.Module`, `Proxytrace.Api.Module`, `Proxytrace.Proxy.Module` (pipeline lib), `Proxytrace.Proxy.Api.Module` (standalone host), `Nordstein.Core.Testing.Module`). `Proxytrace.Domain.Module` passes its assembly to `Nordstein.Core.Domain.Module`, which discovers entities and generators without Core assuming where product types live. `Proxytrace.Storage.Module` still discovers EF configurations and repositories in the product storage assembly. The API serves the compiled React app from `wwwroot/` in production. +DI is wired with Autofac. Each project ships a `Module : Autofac.Module` (`Proxytrace.Domain.Module`, `Proxytrace.Application.Module`, `Proxytrace.Storage.Module`, `Proxytrace.Infrastructure.Module`, `Nordstein.Core.AI.Module`, `Nordstein.Core.Common.Module`, `Nordstein.Core.Domain.Module`, `Proxytrace.Api.Module`, `Proxytrace.Proxy.Module` (pipeline lib), `Proxytrace.Proxy.Api.Module` (standalone host), `Nordstein.Core.Testing.Module`). `Proxytrace.Domain.Module` passes its assembly to `Nordstein.Core.Domain.Module`, which discovers entities and generators without Core assuming where product types live. `Proxytrace.Storage.Module` still discovers EF configurations and repositories in the product storage assembly. The API serves the compiled React app from `wwwroot/` in production. **Bridging to `IServiceCollection`.** Modules that need Microsoft-DI extension methods (`AddHttpClient`, `AddMemoryCache`, …) call `builder.RegisterServiceCollection(services => …)`, which fills a fresh `ServiceCollection` and `Populate`s it into Autofac. Those extension methods share their plumbing through `TryAdd`/`TryAddEnumerable`, which dedupes only **within one collection** — so every caller re-adds it and `Populate` faithfully registers each copy. Four modules calling `AddHttpClient` (Api, Application, Licensing, Proxy) therefore put four `IHttpMessageHandlerBuilderFilter`s in the container, and each one's logging handler wrapped every outgoing request: one upstream LLM call, logged four times ([#451](https://github.com/NordsteinSoftware/Proxytrace/issues/451)). `RegisterServiceCollection` now drops descriptors whose (service, implementation, lifetime) triple an earlier call already populated into the same container — an identical type-based registration can never mean two different things, while genuine multi-registrations use distinct implementation types and instance/factory descriptors are left alone. diff --git a/docs/code-reuse.md b/docs/code-reuse.md index 95245889f..def543e42 100644 --- a/docs/code-reuse.md +++ b/docs/code-reuse.md @@ -25,9 +25,16 @@ the decisions still open before anything is published. | `Nordstein.Core.Testing` | `BaseTest` and the MSTest + AwesomeAssertions + NSubstitute baseline | | `Nordstein.Core.Licensing` | The generic license engine: JWT verification (issuer/audience/keys injected), runtime activation, the resolved `LicenseSnapshot`, and the periodic server check with the offline-grace state machine. Tier/feature/limit vocabulary is supplied by the product through `ILicenseTierPolicy` (string names that double as the JWT claim values) | | `Nordstein.Core.Storage` | `NordsteinDbContext` base, `Entity`/`IEntity`/`IArchivableEntity`, `AbstractRepository`/`ArchivableRepository`, the `AmbientDbContext`/`ITransaction` seam, `IMapper`, `EntityCache`/`EntityCacheVersions`, `AbstractEntityConfiguration`, `LikePattern`, the concurrency-token helpers, and `StorageFoundationModule` (assembly-scoped entity/config/repository/cache discovery). Provider-neutral — depends only on EF Core Relational | - -The reusable domain contracts, **the storage foundation**, and **the licensing engine** are now -extracted. What stays in the product is the provider- and schema-specific storage code: the concrete +| `Nordstein.Core.AI` | The generic AI vocabulary: LLM message/conversation types, tool specifications, prompt templates (`IPromptTemplate`/`IPrompt`), completions/`TokenUsage`/`IModelParameters`, the versionless `IAgent` and `IModelClient` contracts with their option records, structured model-output parsing (`IOutputFormat`, `ITextSerializer`, truncated-JSON repair), and the Autofac `Module` wiring generators, JSON converters, serializer, and output formats. See [`core/docs/ai.md`](../core/docs/ai.md) | + +The reusable domain contracts, **the storage foundation**, **the licensing engine**, and **the AI +foundation** (`Nordstein.Core.AI` — messages, tools, prompts, completions, the versionless agent +and model-client contracts, output parsing; the former `Proxytrace.Serialization` project is +retired into it) are now extracted. For AI, the product keeps everything that ties an agent to +Proxytrace: `Proxytrace.Domain.Agent.IAgent` extends the Core contract with version history, +endpoint binding, tenancy, search and archiving; `ModelClientFactory`/`ModelOptionsFactory` are +the product seams over the Core client contract; the OpenAI `ModelClient` implementation, +providers/endpoints/pricing, and the prompt-template repository stay product-side. What stays in the product is the provider- and schema-specific storage code: the concrete `StorageDbContext` (a thin `NordsteinDbContext` subclass), the `StorageConfiguration` provider switch (Npgsql / in-memory), the migrations assembly and its snapshot, the concrete entities/configurations/repositories, the statistics/query stores, and the backfills. The foundation @@ -142,4 +149,8 @@ tier policy, enums and trust root): hooks, as an `@nordstein/ui` npm package. Same question, different registry; do it after the backend split rather than alongside it. -Never extracted: anything that knows what an agent, a trace, an evaluator or a test run is. +Never extracted: anything that knows what a trace, a project, an evaluator or a test run is. The +*generic* agent vocabulary (messages, tools, prompts, completions, the versionless agent and +model-client contracts, output parsing) lives in `Nordstein.Core.AI`; what stays product-side is +everything that ties an agent to this product — tenancy, version history, endpoints and pricing, +the OpenAI `ModelClient` implementation, ingestion, and search. diff --git a/perf/Proxytrace.Benchmarks/BenchFixture.cs b/perf/Proxytrace.Benchmarks/BenchFixture.cs index bd55f0421..ebce8b44b 100644 --- a/perf/Proxytrace.Benchmarks/BenchFixture.cs +++ b/perf/Proxytrace.Benchmarks/BenchFixture.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using Autofac; using Autofac.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection; @@ -6,9 +7,8 @@ using Nordstein.Core.Common.DependencyInjection; using Nordstein.Core.Common.Serialization; using Proxytrace.Domain; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Inference; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; using Proxytrace.Storage; @@ -45,7 +45,6 @@ protected override void Load(ContainerBuilder builder) // Domain generators + the serializer come from Storage.Module's transitive Domain/Common // modules; Application services aren't needed to mint value objects, so skip them. builder.RegisterModule(new Storage.Module(_ => StorageConfiguration.InMemory(), registerApplicationServices: false)); - builder.RegisterModule(); builder.RegisterStub(); builder.RegisterStub(); } diff --git a/perf/Proxytrace.Benchmarks/Proxytrace.Benchmarks.csproj b/perf/Proxytrace.Benchmarks/Proxytrace.Benchmarks.csproj index b377406cd..82d8d5764 100644 --- a/perf/Proxytrace.Benchmarks/Proxytrace.Benchmarks.csproj +++ b/perf/Proxytrace.Benchmarks/Proxytrace.Benchmarks.csproj @@ -19,7 +19,6 @@ - diff --git a/perf/Proxytrace.Benchmarks/SerializationBenchmarks.cs b/perf/Proxytrace.Benchmarks/SerializationBenchmarks.cs index e1895c2fb..b32eb3598 100644 --- a/perf/Proxytrace.Benchmarks/SerializationBenchmarks.cs +++ b/perf/Proxytrace.Benchmarks/SerializationBenchmarks.cs @@ -1,7 +1,7 @@ using Autofac; using BenchmarkDotNet.Attributes; using Nordstein.Core.Common.Serialization; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; namespace Proxytrace.Benchmarks; diff --git a/perf/Proxytrace.PerfHarness/Bootstrap/PerfModule.cs b/perf/Proxytrace.PerfHarness/Bootstrap/PerfModule.cs index 179c3e371..873759489 100644 --- a/perf/Proxytrace.PerfHarness/Bootstrap/PerfModule.cs +++ b/perf/Proxytrace.PerfHarness/Bootstrap/PerfModule.cs @@ -1,3 +1,4 @@ +using Nordstein.Core.AI.Clients; using Autofac; using Autofac.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection; @@ -9,6 +10,7 @@ using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; using Proxytrace.Domain.Project; +using Nordstein.Core.AI.Prompts; using Proxytrace.Domain.Prompt; using Proxytrace.Storage; using Nordstein.Core.Testing; @@ -62,7 +64,6 @@ protected override void Load(ContainerBuilder builder) // were pulled in transitively via Storage.Module). builder.RegisterModule(); builder.RegisterModule(); - builder.RegisterModule(); // --- substitute the infrastructure seams the data/ingestion graph depends on --- builder.RegisterStub(); diff --git a/perf/Proxytrace.PerfHarness/Proxytrace.PerfHarness.csproj b/perf/Proxytrace.PerfHarness/Proxytrace.PerfHarness.csproj index f0e68b375..057d018dd 100644 --- a/perf/Proxytrace.PerfHarness/Proxytrace.PerfHarness.csproj +++ b/perf/Proxytrace.PerfHarness/Proxytrace.PerfHarness.csproj @@ -19,7 +19,6 @@ - diff --git a/perf/Proxytrace.PerfHarness/Scenarios/IngestionThroughputScenario.cs b/perf/Proxytrace.PerfHarness/Scenarios/IngestionThroughputScenario.cs index 5aa7b5a7a..aefae525c 100644 --- a/perf/Proxytrace.PerfHarness/Scenarios/IngestionThroughputScenario.cs +++ b/perf/Proxytrace.PerfHarness/Scenarios/IngestionThroughputScenario.cs @@ -5,9 +5,8 @@ using Proxytrace.Domain; using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; -using Proxytrace.Domain.Completion; -using Proxytrace.Domain.Inference; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Completions; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.Project; using Proxytrace.Domain.Session; diff --git a/perf/Proxytrace.PerfHarness/Seeding/PerfDataSeeder.cs b/perf/Proxytrace.PerfHarness/Seeding/PerfDataSeeder.cs index 380e67316..b685e45ab 100644 --- a/perf/Proxytrace.PerfHarness/Seeding/PerfDataSeeder.cs +++ b/perf/Proxytrace.PerfHarness/Seeding/PerfDataSeeder.cs @@ -7,10 +7,9 @@ using Proxytrace.Domain.Agent; using Proxytrace.Domain.AgentCall; using Proxytrace.Domain.AgentVersion; -using Proxytrace.Domain.Completion; +using Nordstein.Core.AI.Completions; using Proxytrace.Domain.Evaluator; -using Proxytrace.Domain.Inference; -using Proxytrace.Domain.Message; +using Nordstein.Core.AI.Messages; using Proxytrace.Domain.Model; using Proxytrace.Domain.ModelEndpoint; using Proxytrace.Domain.ModelProvider; @@ -20,7 +19,6 @@ using Proxytrace.Domain.TestRun; using Proxytrace.Domain.TestRunGroup; using Proxytrace.Domain.TestSuite; -using Proxytrace.Domain.Usage; using Proxytrace.PerfHarness.Bootstrap; namespace Proxytrace.PerfHarness.Seeding;