feat: add extraction schema, sidebar nav, few-shot prompting, and prompt settings

Overhaul extraction pipeline with new TradeItem model, conversation flow,
and dedicated extraction endpoint. Add sidebar navigation with NavMenu
component and landing page. Introduce few-shot prompting service and
tests. Add prompt settings and email upload specs. Update OpenSpec
tooling with improved export-spec and extract-feature commands. Archive
completed changes and export full specs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
local
2026-04-06 23:39:23 +01:00
parent 7a5c22593a
commit 5b027eb0db
83 changed files with 4242 additions and 296 deletions

View File

@@ -0,0 +1,75 @@
// FewShotServiceTests.cs -- Tests for the FewShotService that loads few-shot
// examples from disk and assembles ChatHistory prefixes.
using ChatAgent.Api.Services;
using ChatAgent.Shared.Models;
namespace ChatAgent.Api.Tests;
public class FewShotServiceTests
{
// Path to the examples folder from the test bin directory.
// WebApplicationFactory resolves content root to the API project,
// but for direct unit tests we resolve from the test assembly location.
private static string GetExamplesPath()
{
// Navigate from test bin/Debug/net9.0/ up to the repo root, then into examples/
var testDir = AppContext.BaseDirectory;
var repoRoot = Path.GetFullPath(Path.Combine(testDir, "..", "..", "..", "..", ".."));
return Path.Combine(repoRoot, "examples", "extraction");
}
[Fact]
public void Constructor_LoadsInstructionTemplateAndExamples()
{
var service = new FewShotService(GetExamplesPath());
// Should have: 1 system message + 3 examples × 2 messages each = 7 messages
Assert.Equal(7, service.PrefixMessageCount);
}
[Fact]
public void CloneWithEmail_AppendsEmailAsUserMessage()
{
var service = new FewShotService(GetExamplesPath());
var history = service.CloneWithEmail("<html><body>Test email</body></html>");
// Prefix (7) + 1 email user message = 8
Assert.Equal(8, history.Count);
// Last message should be the email
Assert.Equal("<html><body>Test email</body></html>", history.Last().Content);
}
[Fact]
public void CloneWithEmailAndMessages_AppendsEmailAndFollowUps()
{
var service = new FewShotService(GetExamplesPath());
var followUp = new List<ChatMessage>
{
new() { Role = "assistant", Content = "Which counterparty?" },
new() { Role = "user", Content = "Option 1" }
};
var history = service.CloneWithEmailAndMessages("<html>email</html>", followUp);
// Prefix (7) + 1 email + 2 follow-up = 10
Assert.Equal(10, history.Count);
}
[Fact]
public void CloneWithEmail_DoesNotMutatePrefix()
{
var service = new FewShotService(GetExamplesPath());
// Clone twice — second clone should not include the first email
var history1 = service.CloneWithEmail("email 1");
var history2 = service.CloneWithEmail("email 2");
Assert.Equal(8, history1.Count);
Assert.Equal(8, history2.Count);
Assert.Equal("email 1", history1.Last().Content);
Assert.Equal("email 2", history2.Last().Content);
}
}