Files

396 lines
12 KiB
Markdown

# AI Agent Knowledge Analysis Design
## Overview
Implement the MVP as a server-side LangChain AI module with PostgreSQL/pgvector-backed knowledge retrieval and a structured elder AI analysis panel in the existing elder list workflow.
The MVP is not a chat assistant. It is a deterministic product workflow:
1. Authorized user opens an elder's AI analysis dialog from the elder list.
2. Server loads only the resident data families the user can read.
3. Server retrieves enabled platform shared knowledge and enabled active-organization knowledge.
4. LangChain generates a fixed structured analysis.
5. Server saves completed or failed history, writes audit logs, and returns structured JSON.
6. UI renders latest analysis, history, citations, recommendations, and restricted placeholders where permissions do not allow detail access.
## Module Boundaries
### New Module
Create `modules/ai/`:
- `modules/ai/types.ts`
- Zod-compatible validation helpers or TypeScript runtime validators for knowledge entries, analysis output, history status, risk levels, citation shapes, and data scopes.
- `modules/ai/server/config.ts`
- Reads `AI_BASE_URL`, `AI_API_KEY`, `AI_CHAT_MODEL`, `AI_EMBEDDING_MODEL`, and optional timeout/limit settings.
- `modules/ai/server/knowledge.ts`
- CRUD for knowledge entries.
- Chunking and embedding on save.
- Scope-filtered retrieval from pgvector.
- `modules/ai/server/elder-context.ts`
- Builds full resident graph with permission-aware optional data families.
- `modules/ai/server/analysis.ts`
- Runs LangChain analysis flow.
- Validates structured output.
- Saves completed/failed history.
- `modules/ai/components/KnowledgeManagementClient.tsx`
- Settings UI for manual knowledge entries.
- `modules/ai/components/ElderAiAnalysisDialog.tsx`
- Elder row dialog panel for generation, latest analysis, history, and citations.
### Existing Integration Points
- `modules/core/server/schema.ts`
- Add AI enums/tables.
- `modules/core/types.ts`
- Add permissions and exported AI-related shared types as needed.
- `modules/core/server/permissions.ts`
- Seed AI/knowledge permission definitions and default role grants.
- `app/api/ai/...`
- New API routes for knowledge and elder analysis.
- `app/(app)/app/settings/knowledge/page.tsx`
- New settings route.
- `app/(app)/app/[organizationSlug]/settings/knowledge/page.tsx`
- Scoped route re-export.
- `modules/shared/lib/navigation.ts`
- Add settings navigation item visible with `knowledge:read`.
- `modules/elders/components/EldersClient.tsx`
- Add per-row AI analysis action and dialog.
## Dependencies
Add runtime dependencies:
- `@langchain/openai`
- `@langchain/core`
- `langchain`
Add pgvector support through Drizzle/Postgres:
- Use PostgreSQL `vector` extension.
- Prefer a pgvector-capable Postgres image for local development instead of plain `postgres:17-alpine`.
- If Drizzle vector column support is insufficient for the exact version, use custom SQL migration for vector extension/index while keeping typed table metadata in schema.
Vercel AI SDK remains out of the MVP runtime path.
## Data Model
### Permissions
Add permission IDs:
- `ai:read`
- `ai:manage`
- `knowledge:read`
- `knowledge:manage`
Default grants:
- `platform_admin`: all four.
- `org_admin`: all four.
- `manager`: all four.
- `caregiver`: `ai:read`, `knowledge:read`.
- `viewer`, `family`, `resident`: none.
### Knowledge Entries
Table: `ai_knowledge_entries`
Fields:
- `id uuid primary key`
- `scope enum('platform', 'organization')`
- `organizationId uuid null references organizations(id) on delete cascade`
- `title text not null`
- `category text not null default ''`
- `tags text not null default ''`
- `body text not null`
- `status enum('enabled', 'disabled') not null default 'enabled'`
- `createdByAccountId uuid null references accounts(id)`
- `updatedByAccountId uuid null references accounts(id)`
- `createdAt timestamptz not null default now()`
- `updatedAt timestamptz not null default now()`
Rules:
- `scope='platform'` requires `organizationId IS NULL`.
- `scope='organization'` requires `organizationId` to match the active organization.
- Platform entries require platform-level authorization to manage.
- Organization entries require active organization plus `knowledge:manage`.
### Knowledge Chunks
Table: `ai_knowledge_chunks`
Fields:
- `id uuid primary key`
- `entryId uuid not null references ai_knowledge_entries(id) on delete cascade`
- `organizationId uuid null`
- `scope enum('platform', 'organization')`
- `chunkIndex integer not null`
- `content text not null`
- `embedding vector(<embedding_dimensions>) not null`
- `sourceTitle text not null`
- `sourceCategory text not null default ''`
- `createdAt timestamptz not null default now()`
Indexes:
- B-tree on `(scope, organization_id)`.
- Vector index for embedding similarity, using pgvector operator/index supported by deployment.
Embedding dimensions are tied to `AI_EMBEDDING_MODEL`; document the chosen default in env configuration.
### Elder AI Analyses
Table: `elder_ai_analyses`
Fields:
- `id uuid primary key`
- `organizationId uuid not null references organizations(id) on delete cascade`
- `elderId uuid not null references elders(id) on delete cascade`
- `actorAccountId uuid null references accounts(id)`
- `status enum('completed', 'failed') not null`
- `dataScopes text not null`
- `resultJson jsonb null`
- `citationsJson jsonb not null default '[]'::jsonb`
- `modelSummaryJson jsonb not null default '{}'::jsonb`
- `errorCategory text not null default ''`
- `errorReason text not null default ''`
- `createdAt timestamptz not null default now()`
Rules:
- Completed rows require a valid `resultJson`.
- Failed rows must not store prompt, full resident context, or raw provider errors.
- `dataScopes` stores a stable comma-separated list or JSON array; use JSONB if Drizzle ergonomics are acceptable.
## Authorization Model
### Generate Analysis
Required:
- Authenticated session.
- Active organization.
- `ai:read`.
- `elder:read`.
- Target elder belongs to active organization.
Optional data families:
- `health` only if `health:read`.
- `care` only if `care:read`.
- `family` only if `family:read`.
- `admission` only if `admission:read`.
- `facility` only if `facility:read`.
- `alert` only if `alert:read`.
- `incident` only if `incident:read`.
- `knowledge` only if `knowledge:read`.
`dataScopes` records exactly which families were included.
### View Analysis History
History list can show safe metadata for rows in the active organization and elder when the user has `ai:read` and `elder:read`.
History detail requires all permissions implied by `dataScopes`. If missing any scope, return or render a restricted placeholder and do not include `resultJson`/citations content.
### Knowledge Retrieval
Retrieval requires `knowledge:read`.
Eligible entries:
- `scope='platform'`, `status='enabled'`.
- `scope='organization'`, `organizationId = activeOrganizationId`, `status='enabled'`.
No organization-private chunks from other organizations may be retrievable.
## LangChain Flow
### Provider
Use OpenAI-compatible configuration:
- `AI_BASE_URL`
- `AI_API_KEY`
- `AI_CHAT_MODEL`
- `AI_EMBEDDING_MODEL`
`config.ts` validates required settings before generation/embedding and returns structured failures for missing configuration.
### Retrieval
1. Convert resident query/context summary into embedding.
2. Query `ai_knowledge_chunks` with pgvector similarity.
3. Filter by allowed scopes before vector ranking.
4. Return top K chunks with source metadata:
- source type: `knowledge`
- entry id
- chunk id
- title
- category
- scope
### Resident Context Assembly
Build one typed object:
- Elder basics.
- Current bed/admission context.
- Admission history.
- Health profile.
- Recent vitals.
- Chronic conditions.
- Health anomaly reviews.
- Care tasks.
- Alert triggers/rules where relevant.
- System incidents matched by elder/bed where relevant.
- Family contacts/visits/feedback.
- Retrieved knowledge snippets.
Use batch queries and `Promise.all` where independent. No `await` in loops.
### Structured Output
Output schema:
- `overallRiskLevel`: `low | medium | high | critical | unknown`
- `summary`: string
- `keyFindings`: array of:
- `category`: string
- `severity`: `info | warning | critical`
- `evidence`: string
- `citationIds`: string[]
- `recommendations`: array of:
- `priority`: `low | normal | high | urgent`
- `action`: string
- `reason`: string
- `citationIds`: string[]
- `dataGaps`: string[]
- `citations`: array of:
- `id`: string
- `sourceType`: `record | knowledge`
- `sourceId`: string
- `title`: string
- `excerpt`: string
- `confidence`: number from 0 to 1
- `modelSummary`: object with provider base URL host, chat model, embedding model, and generation timestamp, excluding API key.
Use LangChain structured output where possible. Validate again server-side before saving.
## API Design
### Elder Analysis
- `GET /api/ai/elders/[id]/analyses`
- Returns latest allowed analysis metadata/detail and history list.
- Redacts detail for records whose `dataScopes` exceed current permissions.
- `POST /api/ai/elders/[id]/analyses`
- Synchronously generates a new analysis.
- Returns `{ success: true, reason, analysis }` on completion.
- On failure, saves failed history and audit log, then returns `{ success: false, reason }`.
### Knowledge
- `GET /api/ai/knowledge`
- Requires `knowledge:read`.
- Lists platform and active organization entries visible to caller.
- `POST /api/ai/knowledge`
- Requires `knowledge:manage`.
- Creates entry, chunks, embeds, writes audit log.
- `PATCH /api/ai/knowledge/[id]`
- Requires `knowledge:manage`.
- Updates entry and rebuilds chunks/embeddings.
- `DELETE /api/ai/knowledge/[id]`
- Requires `knowledge:manage`.
- Deletes entry and cascading chunks.
All routes return the existing API response shape with `Cache-Control: no-store`.
## UI Design
### Elder List
In `EldersClient`:
- Add AI analysis button per row for users with `ai:read`.
- Use lucide icon such as `Sparkles`.
- Open `Dialog` with `width="wide"`.
- Dialog sections:
- Resident summary.
- Generate button with loading state.
- Latest analysis summary.
- Findings and recommendations.
- Data gaps.
- Citations.
- History list with restricted placeholders.
No chat input and no one-click business creation.
### Knowledge Settings Page
Route: `/settings/knowledge`
Features:
- List entries by scope/status/category/search.
- Create/edit dialog with title, body, category, tags, scope, enabled/disabled.
- Disable mutations when missing `knowledge:manage`.
- Show embedding/rebuild status through response messages; no background jobs.
## Audit And Logging
Audit actions:
- `ai.elderAnalysis.generate`
- `ai.elderAnalysis.generateFailed`
- `ai.knowledge.create`
- `ai.knowledge.update`
- `ai.knowledge.delete`
- `ai.knowledge.disable`
- `ai.knowledge.enable`
Audit records include actor, organization, target type/id, result, and sanitized reason.
Do not store:
- Full prompts.
- Full resident context snapshots.
- Raw provider errors.
- API keys.
## Failure Handling
Generation failures are classified:
- `missing_configuration`
- `provider_error`
- `timeout`
- `schema_validation_failed`
- `retrieval_failed`
- `unknown`
On failure:
1. Save failed analysis history with sanitized category/reason.
2. Write audit log.
3. Return structured API failure.
4. UI shows a concise error state and keeps prior completed history visible.
## Rollout And Compatibility
- Requires database migration for pgvector extension, AI enums, knowledge tables, chunk table, analysis table, and permissions.
- Local `compose.yaml` should use a pgvector-capable PostgreSQL image or document manual extension installation.
- Production deployment must verify `CREATE EXTENSION vector` support before migration.
- If AI env vars are missing, knowledge CRUD can still work except embedding-dependent save should fail gracefully; analysis generation should return a structured configuration failure.
## Open Trade-Offs
- Embedding dimensions depend on the selected embedding model. The implementation should define a default and document migration impact if changed.
- Synchronous generation is acceptable for MVP but can be replaced later by async jobs using the same history table status field.
- Platform shared knowledge increases utility but requires careful scope filters in every retrieval query.