fix: update export bundle for Azure OpenAI and add streaming diagnostics

Replace CLIProxyAPI/local proxy references with Azure OpenAI using
DefaultAzureCredential and tenant ID auth. Add Critical Pattern #8
for SSE buffering diagnostics with timestamped curl test. Add
streaming verification tasks (T6b, T15) and troubleshooting entries
for Azure AD auth, RBAC, response compression, and proxy buffering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
local
2026-04-07 01:42:38 +01:00
parent d46b179221
commit 956ec243c5
3 changed files with 256 additions and 59 deletions

View File

@@ -62,7 +62,7 @@ This feature automates extraction with AI + tool-calling validation, reducing er
## Scope
- New page at /nlxva-pricer, new MudNavLink in existing NavMenu
- New controller with 2 endpoints (chat + extract), same SSE streaming contract
- Semantic Kernel integration with OpenAI-compatible proxy
- Semantic Kernel integration with Azure OpenAI (Azure AD auth via tenant ID)
- Few-shot prompting infrastructure (instruction template + 3 examples)
- External API clients for counterparty/trade/currency validation
- Client-side markdown rendering with XSS sanitization
@@ -89,6 +89,14 @@ This feature automates extraction with AI + tool-calling validation, reducing er
With raw HttpClient we'd need to manually parse tool-call JSON, dispatch functions,
and feed results back. SK handles this loop automatically via FunctionChoiceBehavior.Auto().
## Architecture Decision: Azure OpenAI with DefaultAzureCredential
**Why:** The sandbox environment uses Azure OpenAI with an Azure AD tenant ID.
SK's `AddAzureOpenAIChatCompletion()` with `DefaultAzureCredential` integrates
with CRC's existing Azure AD auth. No API keys to manage — uses the developer's
`az login` token locally and managed identity in production. The endpoint URL
does NOT need `/v1` (Azure SDK constructs the path internally).
## Architecture Decision: SSE streaming over WebSocket
**Why:** SSE is simpler (unidirectional server→client), works through HTTP proxies,
@@ -133,10 +141,25 @@ in the Razor component. CRC's existing Fluxor infrastructure is untouched.
CRC.Server may need its CORS policy updated to allow SSE streaming (Content-Type: text/event-stream)
to the CRC.Client origin. Verify existing policy covers this.
## Risk: SSE response buffering
Two streaming hops: Azure OpenAI → CRC.Server → Browser. Buffering at any point kills
streaming UX. Common culprits: response compression middleware (`UseResponseCompression()`),
reverse proxies (NGINX, IIS), Azure API Management in front of Azure OpenAI.
Use the diagnostic stream-test endpoint (see Critical Pattern #8 in reference spec)
to verify both hops stream correctly before building the UI.
## Risk: Semantic Kernel version compatibility
CRC targets .NET 8.0. Ensure the SK NuGet package version is compatible with .NET 8.
Current stable SK packages support .NET 8+.
Current stable SK packages support .NET 8+. Also need `Microsoft.SemanticKernel.Connectors.AzureOpenAI`
and `Azure.Identity` packages.
## Risk: Azure AD token acquisition
`DefaultAzureCredential` tries multiple auth methods in sequence. On a developer machine,
it uses Azure CLI login (`az login --tenant <tenant-id>`). If the developer hasn't run
`az login`, SK will fail with an auth error at the first LLM call, not at startup.
## Risk: Large file uploads
@@ -153,7 +176,7 @@ Email HTML files are read entirely into memory (max 10MB guard). For typical sal
## Phase 1: Foundation (Server)
- [ ] **T1: Add NuGet packages** — Add `Microsoft.SemanticKernel` to CRC.Server. Add `Markdig` 1.1.1 to CRC.Client (if not already present). Verify .NET 8 compatibility.
- [ ] **T1: Add NuGet packages** — Add `Microsoft.SemanticKernel`, `Microsoft.SemanticKernel.Connectors.AzureOpenAI`, and `Azure.Identity` to CRC.Server. Add `Markdig` 1.1.1 to CRC.Client (if not already present). CRC may already have `Azure.Identity` — check first. Verify .NET 8 compatibility.
- [ ] **T2: Add shared DTOs** — Create in CRC.Shared: `NlxvaChatMessage`, `NlxvaChatRequest`, `NlxvaModelSettings`, `NlxvaExtractionRequest`, `NlxvaExtractionResult`, `TradeItem` (with `[JsonPropertyName]` snake_case), `NlxvaValidationResult`, `NlxvaCandidateMatch`. See Contracts section in reference spec for exact shapes.
@@ -163,7 +186,9 @@ Email HTML files are read entirely into memory (max 10MB guard). For typical sal
- [ ] **T5: Add FewShotService** — Create in CRC.Server/Services: `FewShotService` that loads instruction template + few-shot examples from disk. Caches ChatHistory prefix. Methods: `CloneWithEmail()`, `CloneWithEmailAndMessages()`. Register as Singleton. Copy examples/ folder to CRC.Server root.
- [ ] **T6: Register Semantic Kernel** — In CRC.Server DI: `AddOpenAIChatCompletion()` + `AddKernel()`. Base URL MUST include `/v1`. Config from `NlxvaPricer:*` keys in appsettings.json. See Critical Pattern #2.
- [ ] **T6: Register Semantic Kernel** — In CRC.Server DI: `AddAzureOpenAIChatCompletion()` with `DefaultAzureCredential` (tenant ID from config) + `AddKernel()`. Endpoint is Azure OpenAI resource URL (NO `/v1`). Use deployment name, NOT model name. Config from `NlxvaPricer:*` keys in appsettings.json. See Critical Pattern #2.
- [ ] **T6b: Verify streaming hop 1** — Add temporary `stream-test` diagnostic endpoint (see Critical Pattern #8). Run `curl -N` against it. Verify timestamps are spread across seconds (not clustered). Check for response compression middleware interference. Remove diagnostic endpoint after verification.
- [ ] **T7: Add NlxvaPricerController** — Create controller with `POST /api/nlxva-pricer/chat` and `POST /api/nlxva-pricer/extract`. Both stream SSE. Chat endpoint: builds ChatHistory from messages + optional system prompt + model settings. Extract endpoint: uses FewShotService prefix. Both import ExtractionPlugin per-request and enable `FunctionChoiceBehavior.Auto()`. See Critical Pattern #6.
@@ -183,9 +208,11 @@ Email HTML files are read entirely into memory (max 10MB guard). For typical sal
## Phase 3: Verify
- [ ] **T14: Config** — Add `NlxvaPricer` and `ExternalApis` sections to CRC.Server appsettings.json. Ensure CORS allows CRC.Client origin for SSE responses.
- [ ] **T14: Config** — Add `NlxvaPricer` (AzureOpenAIEndpoint, DeploymentName, TenantId, FewShotPath) and `ExternalApis` sections to CRC.Server appsettings.json. Ensure CORS allows CRC.Client origin for SSE responses. Ensure developer has run `az login --tenant <tenant-id>`.
- [ ] **T15: Smoke test** — Build both projects. Navigate to /nlxva-pricer. Send a chat message → verify streaming. Upload an example email HTML → verify extraction streams. Verify New Chat resets. Verify drag-drop visual feedback.
- [ ] **T15: Verify streaming end-to-end** — Run `curl -N` against `/api/nlxva-pricer/chat` to verify hop 2 (server → client) streams correctly. Check browser Network tab EventStream view for incremental token delivery. If response compression is enabled, verify SSE endpoints opt out.
- [ ] **T16: Smoke test** — Build both projects. Navigate to /nlxva-pricer. Send a chat message → verify streaming tokens appear incrementally. Upload an example email HTML → verify extraction streams. Verify New Chat resets. Verify drag-drop visual feedback.
## Implementation Notes