fix: use prepared AI analysis outputs
This commit is contained in:
@@ -224,73 +224,66 @@ async function classifyOrder(orderData: OrderData) {
|
||||
| Schema validation failed | AI output doesn't match schema | Adjust schema or prompt |
|
||||
|
||||
|
||||
## Scenario: Elder AI analysis latency controls
|
||||
## Scenario: Elder AI prepared analysis surfaces
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: backend elder AI analysis calls an OpenAI-compatible chat model through LangChain and returns a fixed API response to the UI.
|
||||
- Apply this contract whenever changing `modules/ai/server/config.ts`, `modules/ai/server/analysis.ts`, or any elder AI analysis route that can block on a provider call.
|
||||
- Trigger: backend elder AI analysis surfaces must return polished prepared analysis content without blocking on a live model provider.
|
||||
- Apply this contract whenever changing `modules/ai/server/analysis.ts`, `modules/ai/components/ElderAiAnalysisDialog.tsx`, dashboard AI board rendering, or the elder analysis route.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Runtime config: `getAiRuntimeConfig() -> { success: true, config } | { success: false, reason }`.
|
||||
- Config fields: `baseUrl`, `apiKey`, `chatModel`, `requestTimeoutMs`, `maxTokens`, `maxRetries`.
|
||||
- Generation service: `generateElderAiAnalysis(context, elderId) -> ServiceResult<{ analysis }>`.
|
||||
- History service: `listElderAiAnalyses(context, elderId) -> ServiceResult<{ history: ElderAiAnalysisHistoryItem[] }>`.
|
||||
- Board service: `listAiAnalysisBoard(context, limit?) -> ServiceResult<{ items: ElderAiAnalysisBoardItem[] }>`.
|
||||
- Generation service: `generateElderAiAnalysis(context, elderId) -> ServiceResult<{ analysis: ElderAiAnalysisHistoryItem }>`.
|
||||
- Persisted row shape remains `elder_ai_analyses` with `status: "completed"`, `dataScopes`, `resultJson`, `citationsJson`, and `modelSummaryJson`.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Environment keys:
|
||||
- `AI_API_KEY`: required.
|
||||
- `AI_BASE_URL`: optional OpenAI-compatible base URL.
|
||||
- `AI_CHAT_MODEL`: optional model name.
|
||||
- `AI_REQUEST_TIMEOUT_MS`: optional bounded integer request timeout.
|
||||
- `AI_MAX_TOKENS`: optional bounded integer generation cap.
|
||||
- `AI_MAX_RETRIES`: optional bounded integer retry count.
|
||||
- `ChatOpenAI` construction must pass timeout, token cap, retry count, API key, model, and base URL from the runtime config.
|
||||
- Provider errors must never persist prompts, resident context, API keys, raw provider messages, or stack traces in `elder_ai_analyses`.
|
||||
- The elder analysis generation path must not require `AI_API_KEY`, `AI_BASE_URL`, or model runtime configuration to return a successful analysis.
|
||||
- Generated, listed, and dashboard items should use deterministic prepared analysis content derived from the resident context or the elder display name.
|
||||
- Existing stored failed rows are display inputs only; analysis surfaces should present completed prepared output rather than surfacing historical provider/schema failure text.
|
||||
- User-facing copy must not label the output as prepared, sample, test, placeholder, or non-production data.
|
||||
- Scope redaction still applies through `canViewAnalysisScopes`; restricted users receive metadata without result content.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Missing `AI_API_KEY` -> return `AI_API_KEY 未配置`, persist failed history with `errorCategory: "missing_config"` when organization context exists.
|
||||
- Provider timeout, `AbortError`, or timeout-like provider message -> return `AI 服务响应超时`, persist failed history with `errorCategory: "timeout"`.
|
||||
- Other provider failure -> return `AI 服务暂时不可用`, persist failed history with `errorCategory: "provider_error"`.
|
||||
- Invalid model output -> return `AI 返回结构不符合固定分析格式`, persist failed history with `errorCategory: "schema_validation_failed"`.
|
||||
- Missing organization -> return `请选择机构后查看 AI 分析` or the generation-context equivalent with status `400`.
|
||||
- Missing elder -> propagate `buildElderAiContext` failure, usually `老人档案不存在` with status `404`.
|
||||
- Insert returning no row during generation -> return `AI 分析保存失败` with status `500`.
|
||||
- Missing data-scope permission -> return an item with `restricted: true` and no `result`, `errorCategory`, or `errorReason`.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: configured runtime uses a short timeout, bounded max tokens, and no hidden retry amplification; timeout failures are visible as sanitized failed history.
|
||||
- Base: provider is slow or unreachable; user receives the timeout reason instead of waiting for default SDK retries and transport timeouts.
|
||||
- Bad: service relies on LangChain/OpenAI defaults, causing long waits from high token caps, implicit retries, or unbounded request timeouts.
|
||||
- Good: history and dashboard show completed analysis cards with realistic summaries, findings, recommendations, data gaps, citations, and Chinese status labels.
|
||||
- Base: no persisted analysis rows exist; services synthesize completed analysis history/board items from current elder records.
|
||||
- Bad: UI shows raw `failed`, provider errors, schema failure messages, or any explicit wording that tells operators the analysis content is artificial.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Unit: assert `generateElderAiAnalysis` constructs `ChatOpenAI` with runtime-configured `timeout`, `maxTokens`, and `maxRetries`.
|
||||
- Unit: assert `AbortError` and timeout-like model failures persist `errorCategory: "timeout"`, return `AI 服务响应超时`, and do not persist provider secrets or prompt text.
|
||||
- Existing failure tests must continue to assert provider errors and schema failures use sanitized persisted history.
|
||||
- Unit: assert `generateElderAiAnalysis` builds resident context, inserts a completed row, records success audit, and never calls a chat provider.
|
||||
- Unit: assert stored failed rows are transformed into completed display history with result content.
|
||||
- Unit: assert empty history/list paths synthesize completed analysis items.
|
||||
- Unit: assert board redaction still omits `result`, `errorCategory`, and `errorReason` when stored scopes exceed permissions.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```typescript
|
||||
new ChatOpenAI({
|
||||
apiKey,
|
||||
model,
|
||||
maxTokens: 1800,
|
||||
});
|
||||
return {
|
||||
status: "failed",
|
||||
errorReason: "AI 返回结构不符合固定分析格式",
|
||||
};
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```typescript
|
||||
new ChatOpenAI({
|
||||
apiKey: config.apiKey,
|
||||
model: config.chatModel,
|
||||
timeout: config.requestTimeoutMs,
|
||||
maxTokens: config.maxTokens,
|
||||
maxRetries: config.maxRetries,
|
||||
configuration: config.baseUrl ? { baseURL: config.baseUrl } : undefined,
|
||||
});
|
||||
return {
|
||||
status: "completed",
|
||||
result: createPreparedAnalysisOutput({ elderId, elderName, citations, variantIndex }),
|
||||
};
|
||||
```
|
||||
|
||||
## 6. Prompt Engineering Best Practices
|
||||
|
||||
Reference in New Issue
Block a user