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:
@@ -20,27 +20,37 @@ The agent SHALL extract a predefined set of key-value pairs from user-provided n
|
||||
|
||||
### Requirement: Predefined extraction schema
|
||||
|
||||
The system SHALL define a fixed set of known field names and types as a strongly-typed C# class. All extraction output MUST conform to this schema.
|
||||
The system SHALL define the extraction schema as a `TradeItem` class with fields: valuedate, counterparty, legal_entity, trade_id, display_ccy, pv, breakclause. Extraction output SHALL be wrapped in an `ExtractionResult` containing a `List<TradeItem>`. All extraction output MUST conform to this schema.
|
||||
|
||||
#### Scenario: Output conforms to schema
|
||||
|
||||
- **WHEN** the agent produces extracted fields
|
||||
- **THEN** every key in the output matches a field defined in the schema and values match expected types
|
||||
- **WHEN** the agent produces extracted fields from an email
|
||||
- **THEN** every item in the output is a valid TradeItem with all required fields matching expected types
|
||||
|
||||
#### Scenario: Multiple items from one email
|
||||
|
||||
- **WHEN** the agent extracts data from an email containing multiple trade legs
|
||||
- **THEN** the output ExtractionResult contains one TradeItem per trade leg
|
||||
|
||||
### Requirement: Autonomous validation via tool calling
|
||||
|
||||
The agent SHALL validate extracted fields by calling a validation tool function. The validation tool checks that all required fields are present and correctly typed.
|
||||
The agent SHALL validate extracted fields by calling external API tools exposed as Semantic Kernel functions. Validation tools include counterparty lookup, trade validation, currency validation, and schema validation. Each tool returns structured results that the agent reasons about.
|
||||
|
||||
#### Scenario: Validation passes
|
||||
|
||||
- **WHEN** the agent calls the validation tool with a complete and correct extraction
|
||||
- **WHEN** the agent calls the schema validation tool with a complete and correct ExtractionResult
|
||||
- **THEN** the tool returns a success result and the agent returns the final output to the user
|
||||
|
||||
#### Scenario: Validation fails with fixable errors
|
||||
|
||||
- **WHEN** the validation tool returns errors for missing or malformed fields
|
||||
- **WHEN** a validation tool returns errors for missing or malformed fields
|
||||
- **THEN** the agent re-reads the source text and attempts to fix the extraction without user intervention
|
||||
|
||||
#### Scenario: Counterparty disambiguation required
|
||||
|
||||
- **WHEN** the counterparty lookup tool returns multiple candidate (counterparty, legal_entity) tuples
|
||||
- **THEN** the agent presents the candidates to the user as a numbered list in the chat and waits for the user to select one before completing the extraction
|
||||
|
||||
### Requirement: Autonomous retry with iteration cap
|
||||
|
||||
The agent SHALL retry extraction autonomously up to 3 times when validation fails. After exhausting retries, the agent MUST escalate to the user.
|
||||
@@ -57,13 +67,18 @@ The agent SHALL retry extraction autonomously up to 3 times when validation fail
|
||||
|
||||
### Requirement: Human-in-the-loop clarification
|
||||
|
||||
When the agent escalates to the user, the user SHALL be able to provide the missing information in natural language, and the agent SHALL incorporate the clarification and re-attempt extraction.
|
||||
When the agent escalates to the user, the user SHALL be able to provide the missing information in natural language, and the agent SHALL incorporate the clarification and re-attempt extraction. Disambiguation of counterparty/legal_entity tuples is a specific case of human-in-the-loop clarification.
|
||||
|
||||
#### Scenario: User provides clarification
|
||||
|
||||
- **WHEN** the agent asks for clarification about missing fields and the user responds
|
||||
- **THEN** the agent incorporates the user's response into the conversation context and produces an updated extraction
|
||||
|
||||
#### Scenario: User selects counterparty from candidates
|
||||
|
||||
- **WHEN** the agent presents a numbered list of counterparty/legal_entity candidates and the user replies with a selection
|
||||
- **THEN** the agent populates the `legal_entity` field on all relevant TradeItems and proceeds with validation
|
||||
|
||||
#### Scenario: Clarification via normal chat
|
||||
|
||||
- **WHEN** the agent escalates for clarification
|
||||
|
||||
@@ -6,12 +6,27 @@ Define the streaming AI response pipeline — backend chat endpoint using Semant
|
||||
|
||||
### Requirement: Chat endpoint proxies to Responses API
|
||||
|
||||
The API backend SHALL expose `POST /api/chat` that accepts a list of messages and processes them using a Semantic Kernel chat completion service. The kernel is configured with an OpenAI connector pointed at the existing CLIProxyAPI proxy.
|
||||
The API backend SHALL expose `POST /api/chat` that accepts a `ChatRequest` containing messages, an optional system prompt, and optional model settings. The request is processed using a Semantic Kernel chat completion service. When a system prompt is provided, it SHALL be added as the first system message in the ChatHistory. When model settings are provided, non-null values SHALL be applied to the execution settings. A separate `POST /api/chat/extract` endpoint SHALL handle extraction-specific requests with few-shot prompting.
|
||||
|
||||
#### Scenario: Successful chat request
|
||||
#### Scenario: Successful chat request with system prompt
|
||||
|
||||
- **WHEN** the client sends a POST to `/api/chat` with a message list
|
||||
- **THEN** the API processes the messages through the Semantic Kernel and returns the response
|
||||
- **WHEN** the client sends a POST to `/api/chat` with messages and a system prompt
|
||||
- **THEN** the API creates a ChatHistory with the system prompt as the first message, followed by the conversation messages, and processes them through Semantic Kernel
|
||||
|
||||
#### Scenario: Successful chat request with model settings
|
||||
|
||||
- **WHEN** the client sends a POST to `/api/chat` with messages and model settings (e.g., Temperature=0.3)
|
||||
- **THEN** the API applies the settings to OpenAIPromptExecutionSettings before calling the Semantic Kernel
|
||||
|
||||
#### Scenario: Successful chat request without optional fields
|
||||
|
||||
- **WHEN** the client sends a POST to `/api/chat` with only messages (no system prompt, no settings)
|
||||
- **THEN** the API processes the request with default behavior (no system message, default execution settings)
|
||||
|
||||
#### Scenario: Extraction request routed to dedicated endpoint
|
||||
|
||||
- **WHEN** the client sends a POST to `/api/chat/extract` with email HTML
|
||||
- **THEN** the API uses the few-shot ChatHistory prefix and extraction tools instead of the general chat configuration
|
||||
|
||||
### Requirement: Streaming response delivery
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ The chat page SHALL display messages in a vertically scrolling list, with each m
|
||||
|
||||
### Requirement: Message input
|
||||
|
||||
The chat page SHALL provide a text input area at the bottom of the page where the user can type and submit messages.
|
||||
The chat page SHALL provide a text input area at the bottom of the page where the user can type and submit messages. The input area SHALL also include a file upload button for triggering email extraction.
|
||||
|
||||
#### Scenario: Submit via button
|
||||
|
||||
@@ -45,7 +45,12 @@ The chat page SHALL provide a text input area at the bottom of the page where th
|
||||
#### Scenario: Input disabled during streaming
|
||||
|
||||
- **WHEN** the assistant is currently streaming a response
|
||||
- **THEN** the input field and send button are disabled until streaming completes
|
||||
- **THEN** the input field, send button, and upload button are disabled until streaming completes
|
||||
|
||||
#### Scenario: Upload button opens file picker
|
||||
|
||||
- **WHEN** the user clicks the upload button in the input area
|
||||
- **THEN** a file picker dialog opens filtered to .html files
|
||||
|
||||
### Requirement: Thinking indicator
|
||||
|
||||
@@ -100,9 +105,24 @@ The message list SHALL automatically scroll to the newest message when a new mes
|
||||
|
||||
### Requirement: Chat page is default route
|
||||
|
||||
The chat page SHALL be the default route (`/`) of the application.
|
||||
The chat page SHALL be routed at `/sales-assistant` (or `/` with redirect). The page content SHALL be wrapped in a MudTabs container with the conversation UI in the first tab panel.
|
||||
|
||||
#### Scenario: App opens to chat
|
||||
#### Scenario: App opens to chat via redirect
|
||||
|
||||
- **WHEN** the user navigates to the root URL
|
||||
- **WHEN** the user navigates to the root URL `/`
|
||||
- **THEN** the browser redirects to `/sales-assistant` and the chat page is displayed
|
||||
|
||||
#### Scenario: Direct navigation to sales-assistant
|
||||
|
||||
- **WHEN** the user navigates to `/sales-assistant`
|
||||
- **THEN** the chat page is displayed
|
||||
|
||||
#### Scenario: Page loads with chat tab active
|
||||
|
||||
- **WHEN** the user navigates to the chat page
|
||||
- **THEN** the Chat tab is active showing the message list and input area
|
||||
|
||||
#### Scenario: Chat functionality unchanged
|
||||
|
||||
- **WHEN** the user sends a message from the Chat tab
|
||||
- **THEN** the assistant response streams in exactly as before, with the same SSE contract and rendering behavior
|
||||
|
||||
42
openspec/specs/email-upload/spec.md
Normal file
42
openspec/specs/email-upload/spec.md
Normal file
@@ -0,0 +1,42 @@
|
||||
## Purpose
|
||||
|
||||
Define the email upload UX — drag-and-drop, file picker, and upload behavior constraints during streaming.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Drag-and-drop email upload
|
||||
|
||||
The chat message area SHALL accept files dragged from the desktop or file explorer. When a supported file is dropped, the client SHALL read the file content and send it to the extraction endpoint.
|
||||
|
||||
#### Scenario: Drag HTML file onto chat
|
||||
|
||||
- **WHEN** the user drags an .html file over the message area
|
||||
- **THEN** a visual drop indicator appears (e.g., highlighted border, overlay text "Drop email here")
|
||||
|
||||
#### Scenario: Drop HTML file triggers extraction
|
||||
|
||||
- **WHEN** the user drops an .html file onto the message area
|
||||
- **THEN** the client reads the HTML content, sends it to `POST /api/chat/extract`, and streams the extraction response in the chat
|
||||
|
||||
#### Scenario: Unsupported file type rejected
|
||||
|
||||
- **WHEN** the user drops a non-.html file (e.g., .pdf, .docx)
|
||||
- **THEN** the client shows a brief error message indicating only .html files are supported
|
||||
|
||||
### Requirement: File picker upload button
|
||||
|
||||
The chat input area SHALL include an upload button (e.g., attachment icon) that opens a file picker dialog for selecting .html email files.
|
||||
|
||||
#### Scenario: Upload via file picker
|
||||
|
||||
- **WHEN** the user clicks the upload button and selects an .html file
|
||||
- **THEN** the client reads the HTML content and sends it to the extraction endpoint, same as drag-and-drop
|
||||
|
||||
### Requirement: Upload disabled during streaming
|
||||
|
||||
The upload zone and file picker SHALL be disabled while a response is streaming.
|
||||
|
||||
#### Scenario: Drop during streaming
|
||||
|
||||
- **WHEN** the user attempts to drop a file while the assistant is streaming
|
||||
- **THEN** the drop is ignored and no extraction request is sent
|
||||
51
openspec/specs/extraction-conversation-flow/spec.md
Normal file
51
openspec/specs/extraction-conversation-flow/spec.md
Normal file
@@ -0,0 +1,51 @@
|
||||
## Purpose
|
||||
|
||||
Define the extraction conversation flow — mode tracking, visual indicators, follow-up message routing, and upload message display.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Extraction mode tracking
|
||||
|
||||
The chat page SHALL track whether the current conversation is in extraction mode. Extraction mode is entered when an email is uploaded and exited when the user starts a new chat.
|
||||
|
||||
#### Scenario: Enter extraction mode on upload
|
||||
|
||||
- **WHEN** the user uploads an email file
|
||||
- **THEN** the conversation enters extraction mode and subsequent messages are routed to the extraction endpoint
|
||||
|
||||
#### Scenario: Exit extraction mode on New Chat
|
||||
|
||||
- **WHEN** the user clicks "New Chat" while in extraction mode
|
||||
- **THEN** the conversation exits extraction mode and returns to general chat routing
|
||||
|
||||
### Requirement: Extraction mode visual indicator
|
||||
|
||||
The chat page SHALL display a visual indicator when in extraction mode so the user knows their messages are part of an extraction conversation.
|
||||
|
||||
#### Scenario: Indicator shown in extraction mode
|
||||
|
||||
- **WHEN** the conversation is in extraction mode
|
||||
- **THEN** a visual indicator (e.g., chip, banner, or subtitle) is visible showing the extraction context
|
||||
|
||||
#### Scenario: Indicator hidden in general mode
|
||||
|
||||
- **WHEN** the conversation is in general chat mode
|
||||
- **THEN** no extraction indicator is shown
|
||||
|
||||
### Requirement: Follow-up messages route to extraction endpoint
|
||||
|
||||
In extraction mode, text messages typed by the user SHALL be sent to the extraction endpoint with the original email HTML and full conversation history, not to the general chat endpoint.
|
||||
|
||||
#### Scenario: User replies to disambiguation question
|
||||
|
||||
- **WHEN** the agent asks "Which legal entity?" and the user types "1"
|
||||
- **THEN** the client sends an ExtractionRequest with the original email HTML plus all messages (assistant question + user reply) to `POST /api/chat/extract`
|
||||
|
||||
### Requirement: Email upload message in chat
|
||||
|
||||
When an email is uploaded, the chat SHALL display a user message indicating the upload (e.g., showing the filename) before the extraction response streams in.
|
||||
|
||||
#### Scenario: Upload message displayed
|
||||
|
||||
- **WHEN** the user drops "trade_request.html"
|
||||
- **THEN** a user message appears in the chat like "[Uploaded: trade_request.html]" followed by the streaming extraction response
|
||||
47
openspec/specs/extraction-endpoint/spec.md
Normal file
47
openspec/specs/extraction-endpoint/spec.md
Normal file
@@ -0,0 +1,47 @@
|
||||
## Purpose
|
||||
|
||||
Define the extraction-specific API endpoint — request/response contract, few-shot ChatHistory integration, and tool isolation from the general chat endpoint.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Extraction API endpoint
|
||||
|
||||
The API SHALL expose `POST /api/chat/extract` that accepts an `ExtractionRequest` containing the email HTML content and optional follow-up conversation messages. The endpoint SHALL use the few-shot ChatHistory prefix (not the user-editable system prompt) and load extraction-specific SK plugins.
|
||||
|
||||
#### Scenario: Initial extraction request
|
||||
|
||||
- **WHEN** the client sends a POST to `/api/chat/extract` with email HTML and no follow-up messages
|
||||
- **THEN** the API assembles the few-shot ChatHistory, appends the email as the final user message, and streams the extraction response via SSE
|
||||
|
||||
#### Scenario: Follow-up disambiguation request
|
||||
|
||||
- **WHEN** the client sends a POST to `/api/chat/extract` with email HTML and follow-up messages (e.g., user selecting a counterparty)
|
||||
- **THEN** the API assembles the few-shot ChatHistory, appends the email, appends all follow-up messages, and streams the continuation response via SSE
|
||||
|
||||
#### Scenario: SSE streaming contract
|
||||
|
||||
- **WHEN** the extraction endpoint streams a response
|
||||
- **THEN** it uses the same SSE format as `/api/chat`: `data: {"text":"..."}\n\n` for deltas and `data: [DONE]\n\n` for completion
|
||||
|
||||
### Requirement: ExtractionRequest DTO
|
||||
|
||||
The system SHALL define an `ExtractionRequest` class with `EmailHtml` (string, required) and `Messages` (List<ChatMessage>, optional) for follow-up conversation context.
|
||||
|
||||
#### Scenario: First request has email only
|
||||
|
||||
- **WHEN** the user uploads an email for the first time
|
||||
- **THEN** the ExtractionRequest contains `EmailHtml` with the email content and an empty `Messages` list
|
||||
|
||||
#### Scenario: Follow-up request includes conversation
|
||||
|
||||
- **WHEN** the user replies to a disambiguation question
|
||||
- **THEN** the ExtractionRequest contains the original `EmailHtml` plus `Messages` with the full assistant/user exchange since the extraction started
|
||||
|
||||
### Requirement: Extraction endpoint uses extraction tools only
|
||||
|
||||
The extraction endpoint SHALL import only the extraction-specific SK plugins (counterparty lookup, trade validation, currency validation, schema validation). General chat tools (if any) SHALL NOT be loaded for extraction requests.
|
||||
|
||||
#### Scenario: Tool isolation
|
||||
|
||||
- **WHEN** the extraction endpoint processes a request
|
||||
- **THEN** only extraction-related KernelFunctions are available to the LLM
|
||||
67
openspec/specs/extraction-schema/spec.md
Normal file
67
openspec/specs/extraction-schema/spec.md
Normal file
@@ -0,0 +1,67 @@
|
||||
## Purpose
|
||||
|
||||
Define the TradeItem extraction schema, ExtractionResult wrapper, and mapping rules for converting sales email content into structured trade data.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: TradeItem schema
|
||||
|
||||
The system SHALL define a `TradeItem` class with the following fields representing a single trade leg extracted from a sales email:
|
||||
- `valuedate` (string, dd/MM/yyyy format)
|
||||
- `counterparty` (string, full legal name as it appears in the email)
|
||||
- `legal_entity` (string, nullable — populated after counterparty disambiguation via lookup tool)
|
||||
- `trade_id` (long, Murex trade identifier)
|
||||
- `display_ccy` (string, ISO currency code e.g. "GBP", "USD")
|
||||
- `pv` (double, present value)
|
||||
- `breakclause` (string, "Y" or "N")
|
||||
|
||||
JSON serialization SHALL use snake_case property names via `[JsonPropertyName]` attributes.
|
||||
|
||||
#### Scenario: All fields populated
|
||||
|
||||
- **WHEN** the extraction agent produces a TradeItem with all fields
|
||||
- **THEN** the JSON output contains all seven fields with snake_case keys and correct types
|
||||
|
||||
#### Scenario: Legal entity null before disambiguation
|
||||
|
||||
- **WHEN** the extraction agent produces a TradeItem before counterparty lookup
|
||||
- **THEN** the `legal_entity` field is null and all other fields are populated
|
||||
|
||||
### Requirement: ExtractionResult wrapper
|
||||
|
||||
The system SHALL define an `ExtractionResult` class containing a `List<TradeItem> Items` property. All extraction output from a single email SHALL be wrapped in this object.
|
||||
|
||||
#### Scenario: Single email with multiple trade legs
|
||||
|
||||
- **WHEN** an email contains two swaps with two legs each (4 trades total)
|
||||
- **THEN** the ExtractionResult contains an `items` array with 4 TradeItem objects
|
||||
|
||||
#### Scenario: JSON output structure
|
||||
|
||||
- **WHEN** the ExtractionResult is serialized to JSON
|
||||
- **THEN** the output has the shape `{"items": [{"valuedate": "...", ...}, ...]}`
|
||||
|
||||
### Requirement: Extraction mapping rules
|
||||
|
||||
The extraction agent SHALL follow these mapping rules when converting email content to TradeItems:
|
||||
- Each swap leg (identified by a unique Murex trade ID) becomes a separate TradeItem
|
||||
- The `valuedate` SHALL be parsed from date references in the email (e.g., "OB 27/11/2025") and formatted as dd/MM/yyyy
|
||||
- The `counterparty` SHALL be the full legal entity name as stated in the email prose
|
||||
- The `display_ccy` SHALL be derived from the currency symbol or code in the email (e.g., "£" or "PV (£)" → "GBP")
|
||||
- The `breakclause` SHALL default to "N" if not explicitly mentioned in the email
|
||||
- The `pv` SHALL be the numeric present value without formatting (no commas, no currency symbols)
|
||||
|
||||
#### Scenario: Flatten multi-leg swap into individual items
|
||||
|
||||
- **WHEN** the email contains a swap with Coupon Leg (Murex 79353083) and APD leg (Murex 79353084)
|
||||
- **THEN** the output contains two separate TradeItems, one per Murex ID
|
||||
|
||||
#### Scenario: Currency symbol to ISO code mapping
|
||||
|
||||
- **WHEN** the email shows PV values in "PV (£)" column
|
||||
- **THEN** the `display_ccy` field is set to "GBP"
|
||||
|
||||
#### Scenario: Default breakclause
|
||||
|
||||
- **WHEN** the email does not mention break clauses
|
||||
- **THEN** all TradeItems have `breakclause` set to "N"
|
||||
84
openspec/specs/extraction-tools/spec.md
Normal file
84
openspec/specs/extraction-tools/spec.md
Normal file
@@ -0,0 +1,84 @@
|
||||
## Purpose
|
||||
|
||||
Define the Semantic Kernel tool functions for extraction validation — counterparty lookup, trade validation, currency validation, schema validation, external API configuration, and error handling.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Counterparty lookup tool
|
||||
|
||||
The extraction plugin SHALL expose a `lookup_counterparty` Semantic Kernel function that accepts a counterparty name string and calls the external counterparty API. The tool SHALL return a list of candidate (counterparty, legal_entity) tuples.
|
||||
|
||||
#### Scenario: Single match found
|
||||
|
||||
- **WHEN** the tool is called with a counterparty name that matches exactly one record
|
||||
- **THEN** the tool returns a single candidate with the counterparty name and legal entity ID
|
||||
|
||||
#### Scenario: Multiple matches found (disambiguation needed)
|
||||
|
||||
- **WHEN** the tool is called with a counterparty name that matches multiple records
|
||||
- **THEN** the tool returns all matching candidates so the agent can present them to the user for selection
|
||||
|
||||
#### Scenario: No match found
|
||||
|
||||
- **WHEN** the tool is called with a counterparty name that matches no records
|
||||
- **THEN** the tool returns an empty list and an informative message so the agent can ask the user for clarification
|
||||
|
||||
### Requirement: Trade validation tool
|
||||
|
||||
The extraction plugin SHALL expose a `validate_trade` Semantic Kernel function that accepts a trade ID and calls the external trade validation API to verify the trade exists.
|
||||
|
||||
#### Scenario: Valid trade ID
|
||||
|
||||
- **WHEN** the tool is called with a known trade ID
|
||||
- **THEN** the tool returns a success result confirming the trade exists
|
||||
|
||||
#### Scenario: Invalid trade ID
|
||||
|
||||
- **WHEN** the tool is called with an unknown trade ID
|
||||
- **THEN** the tool returns an error result so the agent can flag it to the user
|
||||
|
||||
### Requirement: Currency validation tool
|
||||
|
||||
The extraction plugin SHALL expose a `validate_currency` Semantic Kernel function that accepts a currency code and calls the external currency validation API to verify it is a valid ISO currency code.
|
||||
|
||||
#### Scenario: Valid currency code
|
||||
|
||||
- **WHEN** the tool is called with "GBP"
|
||||
- **THEN** the tool returns a success result
|
||||
|
||||
#### Scenario: Invalid currency code
|
||||
|
||||
- **WHEN** the tool is called with an unrecognized code
|
||||
- **THEN** the tool returns an error with suggestions for valid codes
|
||||
|
||||
### Requirement: Schema validation tool
|
||||
|
||||
The extraction plugin SHALL expose a `validate_schema` Semantic Kernel function that accepts the full ExtractionResult JSON and validates that all required fields are present and correctly typed for every TradeItem.
|
||||
|
||||
#### Scenario: Valid extraction result
|
||||
|
||||
- **WHEN** the tool is called with a complete and correctly typed ExtractionResult JSON
|
||||
- **THEN** the tool returns a success result with no errors
|
||||
|
||||
#### Scenario: Missing required fields
|
||||
|
||||
- **WHEN** the tool is called with a TradeItem missing the `trade_id` field
|
||||
- **THEN** the tool returns a failure result listing the missing fields and which item they belong to
|
||||
|
||||
### Requirement: External API configuration
|
||||
|
||||
All external API base URLs SHALL be configurable via `appsettings.json` under an `ExternalApis` section. Each tool's HttpClient SHALL read its base URL from configuration at startup.
|
||||
|
||||
#### Scenario: Configuration at startup
|
||||
|
||||
- **WHEN** the API starts
|
||||
- **THEN** it reads external API base URLs from the `ExternalApis` configuration section and configures typed HttpClients accordingly
|
||||
|
||||
### Requirement: External API error handling
|
||||
|
||||
Each tool SHALL handle HTTP errors from external APIs gracefully, returning a structured error message that the LLM agent can reason about rather than throwing exceptions.
|
||||
|
||||
#### Scenario: External API unavailable
|
||||
|
||||
- **WHEN** a tool calls an external API that is unreachable
|
||||
- **THEN** the tool returns an error result with a descriptive message (e.g., "Counterparty API unavailable") so the agent can inform the user
|
||||
56
openspec/specs/few-shot-prompting/spec.md
Normal file
56
openspec/specs/few-shot-prompting/spec.md
Normal file
@@ -0,0 +1,56 @@
|
||||
## Purpose
|
||||
|
||||
Define the few-shot prompting infrastructure for extraction — example folder structure, instruction template, ChatHistory assembly, and evaluation folder.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Few-shot example folder structure
|
||||
|
||||
The system SHALL store few-shot examples at `examples/extraction/few-shot/` with numbered subdirectories (e.g., `01/`, `02/`). Each subdirectory SHALL contain `input.html` (the example email) and `output.json` (the expected ExtractionResult JSON).
|
||||
|
||||
#### Scenario: Example folder layout
|
||||
|
||||
- **WHEN** the application starts
|
||||
- **THEN** it reads example pairs from `examples/extraction/few-shot/` in numeric directory order
|
||||
|
||||
#### Scenario: Adding a new example
|
||||
|
||||
- **WHEN** a new subdirectory (e.g., `04/`) is added with `input.html` and `output.json`
|
||||
- **THEN** the new example is included in the few-shot ChatHistory prefix after the next application restart
|
||||
|
||||
### Requirement: Extraction instruction template
|
||||
|
||||
The system SHALL load a fixed instruction template from `examples/extraction/instruction-template.txt` that defines the extraction task, the TradeItem schema, and the mapping rules (date parsing, leg flattening, currency mapping, breakclause defaults). This template is NOT the user-editable system prompt.
|
||||
|
||||
#### Scenario: Template loaded at startup
|
||||
|
||||
- **WHEN** the application starts
|
||||
- **THEN** the instruction template is loaded from disk and used as the system message in the extraction ChatHistory
|
||||
|
||||
#### Scenario: Template content
|
||||
|
||||
- **WHEN** the instruction template is loaded
|
||||
- **THEN** it contains the TradeItem field definitions, expected JSON output format, and explicit mapping rules
|
||||
|
||||
### Requirement: ChatHistory assembly with few-shot examples
|
||||
|
||||
The system SHALL provide a `FewShotService` that assembles a reusable ChatHistory prefix at startup: the instruction template as a system message, followed by alternating User (input.html) and Assistant (output.json) messages for each example. Each extraction request SHALL clone this prefix and append the real email as the final user message.
|
||||
|
||||
#### Scenario: ChatHistory prefix structure
|
||||
|
||||
- **WHEN** the service assembles the prefix with 3 examples
|
||||
- **THEN** the ChatHistory contains: 1 system message + 3 user messages + 3 assistant messages (7 messages total)
|
||||
|
||||
#### Scenario: Prefix cached and cloned per request
|
||||
|
||||
- **WHEN** an extraction request arrives
|
||||
- **THEN** the service clones the cached prefix (not re-reading from disk) and appends the email content as a new user message
|
||||
|
||||
### Requirement: Evaluation example folder
|
||||
|
||||
The system SHALL support an `examples/extraction/evaluation/` folder for bulk examples used in offline testing. This folder is NOT loaded at startup and NOT used in the few-shot prompt.
|
||||
|
||||
#### Scenario: Evaluation folder ignored at runtime
|
||||
|
||||
- **WHEN** the application starts
|
||||
- **THEN** it does not load examples from `examples/extraction/evaluation/`
|
||||
47
openspec/specs/prompt-settings-api/spec.md
Normal file
47
openspec/specs/prompt-settings-api/spec.md
Normal file
@@ -0,0 +1,47 @@
|
||||
## Purpose
|
||||
|
||||
Define the shared data models and API contract for system prompt and model settings — ModelSettings class, ChatRequest extensions, and backend handling.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: ModelSettings shared model
|
||||
|
||||
The Shared project SHALL define a `ModelSettings` class with nullable properties: `Temperature` (double?), `TopP` (double?), `MaxTokens` (int?). Null values indicate "use server default".
|
||||
|
||||
#### Scenario: All fields null
|
||||
|
||||
- **WHEN** a ModelSettings instance has all null fields
|
||||
- **THEN** the backend uses Semantic Kernel default values for all parameters
|
||||
|
||||
#### Scenario: Partial override
|
||||
|
||||
- **WHEN** a ModelSettings instance has Temperature set but TopP and MaxTokens null
|
||||
- **THEN** only Temperature is overridden; other parameters use defaults
|
||||
|
||||
### Requirement: System prompt in chat request
|
||||
|
||||
The `ChatRequest` SHALL accept an optional `SystemPrompt` (string?) property. When present and non-empty, the backend SHALL insert it as the first system message in the ChatHistory before user/assistant messages.
|
||||
|
||||
#### Scenario: System prompt provided
|
||||
|
||||
- **WHEN** a ChatRequest includes a non-empty SystemPrompt
|
||||
- **THEN** the ChatHistory starts with a system message containing that text, followed by the conversation messages
|
||||
|
||||
#### Scenario: System prompt absent
|
||||
|
||||
- **WHEN** a ChatRequest has a null or empty SystemPrompt
|
||||
- **THEN** the ChatHistory contains only user and assistant messages (no system message)
|
||||
|
||||
### Requirement: Model settings in chat request
|
||||
|
||||
The `ChatRequest` SHALL accept an optional `Settings` (ModelSettings?) property. When present, the backend SHALL apply non-null values to `OpenAIPromptExecutionSettings` before calling the Semantic Kernel.
|
||||
|
||||
#### Scenario: Temperature override
|
||||
|
||||
- **WHEN** a ChatRequest includes Settings with Temperature = 0.5
|
||||
- **THEN** the OpenAIPromptExecutionSettings.Temperature is set to 0.5
|
||||
|
||||
#### Scenario: No settings provided
|
||||
|
||||
- **WHEN** a ChatRequest has null Settings
|
||||
- **THEN** the backend uses default OpenAIPromptExecutionSettings (only FunctionChoiceBehavior.Auto is set)
|
||||
57
openspec/specs/prompt-settings-ui/spec.md
Normal file
57
openspec/specs/prompt-settings-ui/spec.md
Normal file
@@ -0,0 +1,57 @@
|
||||
## Purpose
|
||||
|
||||
Define the UI controls for configuring system prompt and model parameters — tabbed layout, prompt editor, and settings controls.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: System prompt editor tab
|
||||
|
||||
The chat page SHALL include a "System Prompt" tab with a multi-line text area where the user can enter a system prompt. The system prompt value SHALL persist across tab switches within the same session.
|
||||
|
||||
#### Scenario: User enters a system prompt
|
||||
|
||||
- **WHEN** the user navigates to the System Prompt tab and types text
|
||||
- **THEN** the text is stored in the component state and included in the next chat request
|
||||
|
||||
#### Scenario: System prompt survives tab switch
|
||||
|
||||
- **WHEN** the user enters a system prompt, switches to the Chat tab, then switches back
|
||||
- **THEN** the system prompt text is unchanged
|
||||
|
||||
### Requirement: Model settings tab
|
||||
|
||||
The chat page SHALL include a "Model Settings" tab with controls for Temperature, TopP, and MaxTokens. Each control SHALL display its current value and allow adjustment within valid ranges.
|
||||
|
||||
#### Scenario: Temperature control
|
||||
|
||||
- **WHEN** the user adjusts the Temperature control
|
||||
- **THEN** the value is constrained to 0.0–2.0 and included in the next chat request's settings
|
||||
|
||||
#### Scenario: TopP control
|
||||
|
||||
- **WHEN** the user adjusts the TopP control
|
||||
- **THEN** the value is constrained to 0.0–1.0 and included in the next chat request's settings
|
||||
|
||||
#### Scenario: MaxTokens control
|
||||
|
||||
- **WHEN** the user sets the MaxTokens value
|
||||
- **THEN** the value is constrained to 1–4096 and included in the next chat request's settings
|
||||
|
||||
#### Scenario: Default values
|
||||
|
||||
- **WHEN** the user has not changed any model settings
|
||||
- **THEN** the controls show default values (Temperature: 1.0, TopP: 1.0, MaxTokens: empty/unset) and no overrides are sent to the API
|
||||
|
||||
### Requirement: Tabbed page layout
|
||||
|
||||
The chat page SHALL use MudTabs with three tab panels: "Chat" (the existing conversation UI), "System Prompt" (the prompt editor), and "Model Settings" (the parameter controls).
|
||||
|
||||
#### Scenario: Chat tab is default
|
||||
|
||||
- **WHEN** the page loads
|
||||
- **THEN** the Chat tab is active and the conversation UI is displayed
|
||||
|
||||
#### Scenario: Tab switching
|
||||
|
||||
- **WHEN** the user clicks a different tab
|
||||
- **THEN** the corresponding panel is displayed and the previous panel is hidden but retains its state
|
||||
42
openspec/specs/sidebar-navigation/spec.md
Normal file
42
openspec/specs/sidebar-navigation/spec.md
Normal file
@@ -0,0 +1,42 @@
|
||||
## Purpose
|
||||
|
||||
Define the collapsible sidebar drawer, its hamburger toggle, and the navigation menu with links to application pages.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Collapsible sidebar drawer
|
||||
|
||||
The application SHALL have a MudDrawer in MainLayout that contains a navigation menu. The drawer SHALL be toggleable via a hamburger icon button in the AppBar.
|
||||
|
||||
#### Scenario: Drawer visible on load
|
||||
|
||||
- **WHEN** the application loads
|
||||
- **THEN** the sidebar drawer is displayed in its default open state with navigation links visible
|
||||
|
||||
#### Scenario: Drawer toggles on hamburger click
|
||||
|
||||
- **WHEN** the user clicks the hamburger icon in the AppBar
|
||||
- **THEN** the drawer toggles between open and collapsed states
|
||||
|
||||
### Requirement: Navigation menu with Sales Assistant link
|
||||
|
||||
The sidebar drawer SHALL contain a MudNavMenu with a "Sales Assistant" navigation link that routes to `/sales-assistant`.
|
||||
|
||||
#### Scenario: Sales Assistant link present
|
||||
|
||||
- **WHEN** the drawer is open
|
||||
- **THEN** a "Sales Assistant" link with a SmartToy icon is visible in the navigation menu
|
||||
|
||||
#### Scenario: Clicking Sales Assistant navigates to chat
|
||||
|
||||
- **WHEN** the user clicks the "Sales Assistant" link
|
||||
- **THEN** the browser navigates to `/sales-assistant` and the chat page renders in MudMainContent
|
||||
|
||||
### Requirement: NavMenu is a separate component
|
||||
|
||||
The navigation menu SHALL be implemented as a separate `NavMenu.razor` component in the Layout folder, referenced from MainLayout.
|
||||
|
||||
#### Scenario: NavMenu renders inside drawer
|
||||
|
||||
- **WHEN** MainLayout renders
|
||||
- **THEN** the NavMenu component renders inside the MudDrawer with its navigation links
|
||||
Reference in New Issue
Block a user