ADR-0021: Domain-Specific Models Architecture
ADR-0021: Domain-Specific Models Architecture
Date: 2026-01-23 Status: Proposed Author: Rovo (Architect Agent) Reviewers: Codex (Gatekeeper), George (User) Context: Phase T - Multi-Model Strategy for Data Source Isolation
1. Context
Business Need
Users need specialized AI assistants for different data sources:
- Jorvis-Meteo — Weather data only (Open-Meteo API)
- Jorvis-Wikipedia — Knowledge queries only (WikiData)
- Jorvis-Sports — Sports data only (TheSportsDB)
- Jorvis-Movies — Movie data only (TMDb)
- Jorvis-Viorica — Google Sheets only (specific spreadsheet)
- Jorvis-Wizly — SQL analytics (Wizly database)
- Jorvis-AdventureWorks — SQL analytics (AdventureWorks demo DB)
- Jorvis-Chinook — SQL analytics (Chinook music DB)
- Jorvis-Help — Documentation assistant (no data access)
Current Architecture
Open WebUI → model="jorvis-cloud" → Jorvis API → Graph Orchestrator
↓
All tools available
All DBs accessible
Problem
- No model isolation — user can accidentally query wrong data source
- Tool overload — LLM sees all tools, increases hallucination risk
- No specialized system prompts — generic responses
- Single model in UI — confusing for users with multiple use cases
Existing Assets (Ready to Use)
| Component | Location | Status |
|---|---|---|
| MCP Weather Tool | src/modules/mcp/tools/weather.tool.ts | ✅ Exists |
| MCP WikiData Tool | src/modules/mcp/tools/wikidata.tool.ts | ✅ Exists |
| MCP SportsDB Tool | src/modules/mcp/tools/sportsdb.tool.ts | ✅ Exists |
| MCP TMDb Tool | src/modules/mcp/tools/tmdb.tool.ts | ✅ Exists |
| MCP Google Sheets Tool | src/modules/mcp/tools/google-sheets.tool.ts | ✅ Exists |
| MCP Excel Tool | src/modules/mcp/tools/excel.tool.ts | ✅ Exists |
| Graph ToolRouter | src/ai/graph/nodes/tool-router.node.ts | ✅ Exists |
| Graph ToolExecution | src/ai/graph/nodes/tool-execution.node.ts | ✅ Exists |
| DefaultToolRegistry | src/ai/graph/tools/default-tool-registry.ts | ✅ Exists |
| OpenAI-Compat Service | src/internal/openai-compat.service.ts | ✅ Exists |
Clarification: MCP tools are part of the MCP server stack and are not automatically wired into the Jorvis API tool execution path. Domain-specific models require explicit bridging (see Task‑T‑3) or direct registration in the runtime tool registry used by the API.
Gap Analysis
| Component | Current | Required |
|---|---|---|
| Model Registry | ❌ None | JSON config with model definitions |
| listModels() | Returns hardcoded 2 models | Returns registry models |
| Tool Filtering | ❌ None | allowedTools whitelist per model |
| Forced Tool | ❌ None | forcedTool for single-purpose models |
| SQL Gate | ❌ None | Block SQL for tool-only models |
| GraphTools Bridge | ❌ None | MCP tools → Graph tools adapter |
2. Decision
Architecture: Model-as-DataSource
Each Open WebUI model maps to a specific data source with enforced isolation:
┌─────────────────────────────────────────────────────────────────────┐
│ Open WebUI │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Jorvis- │ │ Jorvis- │ │ Jorvis- │ │ Jorvis- │ │ Jorvis- │ │
│ │ Meteo │ │ Wikipedia│ │ Wizly │ │ Viorica │ │ Help │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │ │
└───────┼────────────┼────────────┼────────────┼────────────┼─────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ Jorvis API (OpenAI-Compat) │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Model Registry │ │
│ │ ┌─────────────────────────────────────────────────────────┐│ │
│ │ │ jorvis-meteo: routing=tool-only, forcedTool=weather ││ │
│ │ │ jorvis-wikipedia: routing=tool-only, forcedTool=wikidata││ │
│ │ │ jorvis-wizly: routing=sql-only, brand=wizly ││ │
│ │ │ jorvis-viorica: routing=tool-only, forcedTool=sheets ││ │
│ │ │ jorvis-help: routing=doc-only ││ │
│ │ └─────────────────────────────────────────────────────────┘│ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Graph Orchestrator (with Policy) │ │
│ │ │ │
│ │ ToolRouterNode ──► if forcedTool → skip routing │ │
│ │ DataExecutionNode ──► if tool-only → skip SQL │ │
│ │ ToolExecutionNode ──► check allowedTools whitelist │ │
│ │ DocumentContextNode ──► for doc-only models │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
3. Model Registry Schema
File: config/models/jorvis-models.yaml
version: "1.0"
models:
# === TOOL-ONLY MODELS (API Data Sources) ===
jorvis-meteo:
displayName: "Jorvis-Meteo"
description: "Weather forecasts and historical data from Open-Meteo"
icon: "🌤️"
routing: tool-only
forcedTool: weather
allowedTools: [weather]
provider: gemini
providerModel: gemini-2.5-flash
systemPrompt: |
You are Jorvis-Meteo, a weather assistant.
You ONLY answer questions about weather using the Open-Meteo API.
For non-weather questions, politely redirect users to other Jorvis models.
jorvis-wikipedia:
displayName: "Jorvis-Wikipedia"
description: "Knowledge queries from WikiData"
icon: "📚"
routing: tool-only
forcedTool: wikidata
allowedTools: [wikidata]
provider: gemini
providerModel: gemini-2.5-flash
systemPrompt: |
You are Jorvis-Wikipedia, a knowledge assistant.
You answer factual questions using WikiData.
Cite sources when possible.
jorvis-sports:
displayName: "Jorvis-Sports"
description: "Sports data, scores, and statistics"
icon: "⚽"
routing: tool-only
forcedTool: sportsdb
allowedTools: [sportsdb]
provider: gemini
providerModel: gemini-2.5-flash
systemPrompt: |
You are Jorvis-Sports, a sports data assistant.
You provide information about sports events, teams, players, and scores.
jorvis-movies:
displayName: "Jorvis-Movies"
description: "Movie and TV show information from TMDb"
icon: "🎬"
routing: tool-only
forcedTool: tmdb
allowedTools: [tmdb]
provider: gemini
providerModel: gemini-2.5-flash
systemPrompt: |
You are Jorvis-Movies, a movie and TV assistant.
You provide information about films, actors, and TV shows using TMDb.
jorvis-viorica:
displayName: "Jorvis-Viorica"
description: "Analytics for Viorica Google Spreadsheet"
icon: "📊"
routing: tool-only
forcedTool: google-sheets
allowedTools: [google-sheets]
provider: gemini
providerModel: gemini-2.5-flash
config:
sheetId: "1l5qrcke9u6h5EkeltP4gTIApO4uK5kl864gCWU09CEo"
defaultGid: "2137851665"
systemPrompt: |
You are Jorvis-Viorica, an assistant for the Viorica spreadsheet.
You can read and analyze data from this specific Google Sheet.
Always confirm which sheet/tab the user wants to query.
# === NOTE: EXCEL SUPPORT ===
# "Jorvis-Auto" is NOT a separate model. Excel processing is provided via
# the 'excel' tool available to 'jorvis-cloud' (routing: auto).
# === SQL-ONLY MODELS (Database Sources) ===
jorvis-wizly:
displayName: "Jorvis-Wizly"
description: "Business intelligence for Wizly database"
icon: "📈"
routing: sql-only
brand: wizly
allowedTools: [schema_explorer]
provider: gemini
providerModel: gemini-2.5-flash
systemPrompt: |
You are Jorvis-Wizly, a BI assistant for the Wizly database.
You generate SQL queries to answer business questions.
Always explain your SQL logic and validate results.
jorvis-adventureworks:
displayName: "Jorvis-AdventureWorks"
description: "Demo analytics for AdventureWorks sample DB"
icon: "�icing"
routing: sql-only
brand: adventureworks
allowedTools: [schema_explorer]
provider: gemini
providerModel: gemini-2.5-flash
systemPrompt: |
You are Jorvis-AdventureWorks, an analytics assistant.
You work with the AdventureWorks sample database (sales, products, customers).
jorvis-chinook:
displayName: "Jorvis-Chinook"
description: "Demo analytics for Chinook music DB"
icon: "🎵"
routing: sql-only
brand: chinook
allowedTools: [schema_explorer]
provider: gemini
providerModel: gemini-2.5-flash
systemPrompt: |
You are Jorvis-Chinook, a music database assistant.
You work with the Chinook database (artists, albums, tracks, invoices).
# === DOC-ONLY MODELS (No Data Access) ===
jorvis-help:
displayName: "Jorvis-Help"
description: "Documentation and help for Jorvis platform"
icon: "❓"
routing: doc-only
allowedTools: []
provider: gemini
providerModel: gemini-2.5-flash
knowledgeBase: jorvis-docs
systemPrompt: |
You are Jorvis-Help, the documentation assistant for the Jorvis platform.
You know about:
- Jorvis architecture and capabilities
- Available models and their purposes
- Configuration and deployment
- Development workflows and ADRs
You do NOT execute SQL or call external APIs.
If users need data, direct them to the appropriate Jorvis model.
# === GENERAL MODEL (Backward Compatibility) ===
jorvis-cloud:
displayName: "Jorvis Cloud"
description: "General-purpose assistant with all capabilities"
icon: "🧠"
routing: auto
allowedTools: [weather, wikidata, sportsdb, tmdb, google-sheets, schema_explorer, web_search, calculator]
provider: gemini
providerModel: gemini-2.5-flash
systemPrompt: |
You are Jorvis, a versatile AI assistant.
You can query databases, call APIs, and answer general questions.
4. Implementation Plan
Phase T-1: Model Registry & listModels (2 days)
Files to create:
config/models/jorvis-models.yaml— Model definitions
Files to modify:
src/internal/openai-compat.service.ts:- Add
ModelRegistryServiceinjection - Update
listModels()to return registry models - Update
resolveProvider()to read from registry
- Add
New service:
// src/config/model-registry.service.ts
@Injectable()
export class ModelRegistryService {
private models: Map<string, ModelConfig>;
getModel(modelId: string): ModelConfig | undefined;
listModels(): ModelConfig[];
getRoutingPolicy(modelId: string): RoutingPolicy;
}
Acceptance Criteria:
- Open WebUI shows all 10 models in dropdown
- Each model has correct icon and description
-
GET /v1/modelsreturns full list
Phase T-2: GraphTools Bridge (2 days)
Problem: MCP tools exist but aren't registered in Graph's DefaultToolRegistry.
Files to create:
src/ai/graph/tools/mcp-tool-adapter.ts— Adapter to wrap MCP tools as GraphTools
Files to modify:
src/ai/graph/tools/default-tool-registry.ts:- Register MCP tools via adapter
- Add weather, wikidata, sportsdb, tmdb, google-sheets
Code pattern:
// MCP to Graph adapter
export class McpToolAdapter implements GraphTool {
constructor(private mcpTool: McpTool) {}
get name() { return this.mcpTool.name; }
get description() { return this.mcpTool.description; }
async execute(input: unknown) {
return this.mcpTool.execute(input);
}
}
Acceptance Criteria:
- All MCP tools accessible via Graph ToolRouter
- Tools return correct results
- Unit tests pass
Phase T-3: Routing Policy Enforcement (3 days)
Files to modify:
src/ai/graph/graph.types.ts:
export interface GraphState {
// ... existing fields
routingPolicy?: RoutingPolicy;
}
export interface RoutingPolicy {
type: 'auto' | 'sql-only' | 'tool-only' | 'doc-only';
forcedTool?: string;
allowedTools?: string[];
brand?: string;
}
src/ai/graph/nodes/tool-router.node.ts:
async execute(state: GraphState) {
// NEW: Check for forced tool
if (state.routingPolicy?.forcedTool) {
return {
toolCall: {
toolName: state.routingPolicy.forcedTool,
input: { query: state.question },
},
nextNode: 'ToolExecutionNode',
};
}
// Existing routing logic...
}
src/ai/graph/nodes/data-execution.node.ts:
async execute(state: GraphState) {
// NEW: Block SQL for tool-only/doc-only
if (state.routingPolicy?.type === 'tool-only' ||
state.routingPolicy?.type === 'doc-only') {
this.logger.warn('SQL blocked by routing policy');
return { nextNode: 'SynthesisNode' };
}
// Existing SQL execution...
}
src/ai/graph/nodes/tool-execution.node.ts:
async execute(state: GraphState) {
// NEW: Check tool whitelist
const allowedTools = state.routingPolicy?.allowedTools;
if (allowedTools && !allowedTools.includes(state.toolCall.toolName)) {
this.logger.warn(`Tool ${state.toolCall.toolName} not in whitelist`);
return {
error: `Tool not available for this model`,
nextNode: 'SynthesisNode'
};
}
// Existing execution...
}
src/question/conversation-question.service.ts:
// Pass routing policy to Graph
const state: GraphState = {
question,
routingPolicy: modelConfig.getRoutingPolicy(),
// ...
};
Acceptance Criteria:
- jorvis-meteo ONLY calls weather tool
- jorvis-wizly ONLY executes SQL
- jorvis-help returns doc-only responses
- Cross-model tool calls blocked
Phase T-4: System Prompts & Knowledge Base (2 days)
Files to modify:
-
src/internal/openai-compat.service.ts:- Inject system prompt from model config
- Pass to ConversationQuestionService
-
src/ai/graph/nodes/document-context.node.ts:- Filter by
knowledgeBasefield for doc-only models
- Filter by
For jorvis-help:
- Index project documentation into vector store
- Configure DocumentContextNode to use jorvis-docs collection
Acceptance Criteria:
- Each model has correct system prompt
- jorvis-help answers questions about Jorvis
- System prompts visible in transparency trace
Phase T-5: Open WebUI Registration (1 day)
Files to modify:
scripts/register-openwebui-model.sh:- Read from
config/models/jorvis-models.yaml - Register all models with correct metadata
- Read from
Script enhancement:
#!/bin/bash
# Parse YAML and register each model
for model in $(yq '.models | keys | .[]' config/models/jorvis-models.yaml); do
displayName=$(yq ".models.$model.displayName" config/models/jorvis-models.yaml)
description=$(yq ".models.$model.description" config/models/jorvis-models.yaml)
curl -X POST "$WEBUI_URL/api/models" \
-H "Authorization: Bearer $WEBUI_TOKEN" \
-d "{\"id\": \"$model\", \"name\": \"$displayName\", \"description\": \"$description\"}"
done
Acceptance Criteria:
- All 10 models visible in Open WebUI
- Icons and descriptions correct
- Models selectable and functional
Phase T-6: Testing & Documentation (2 days)
Test scenarios:
| Model | Test Query | Expected Behavior |
|---|---|---|
| jorvis-meteo | "What's weather in Kyiv?" | Calls weather tool ONLY |
| jorvis-meteo | "SELECT * FROM users" | Politely refuses, no SQL |
| jorvis-wikipedia | "Who is Taras Shevchenko?" | Calls wikidata tool |
| jorvis-wizly | "Show top 10 customers" | Executes SQL ONLY |
| jorvis-wizly | "What's weather?" | Politely refuses |
| jorvis-help | "How to deploy Jorvis?" | Returns docs, no API calls |
| jorvis-viorica | "Show sales by month" | Calls sheets with correct ID |
Documentation:
- Update
docs/operations/LOCAL_RUNTIME.md - Create
docs/guides/domain-specific-models.md - Update
docs/architecture/CAPABILITIES_OVERVIEW.md
Acceptance Criteria:
- All test scenarios pass
- Documentation complete
- E2E tests added
5. Task Breakdown for Executor
Task-T-1: Model Registry Service
Priority: P0 Estimate: 1 day Dependencies: None
Deliverables:
- Create
config/models/jorvis-models.yaml - Create
src/config/model-registry.service.ts - Create
src/config/model-registry.module.ts - Unit tests for registry service
Task-T-2: OpenAI-Compat Integration
Priority: P0 Estimate: 1 day Dependencies: Task-T-1
Deliverables:
- Update
listModels()in openai-compat.service.ts - Update
resolveProvider()to use registry - Pass
routingPolicyto ConversationQuestionService - Integration tests
Task-T-3: GraphTools Bridge (MCP Adapter)
Priority: P0 Estimate: 2 days Dependencies: None (parallel with T-1, T-2)
Deliverables:
- Create
src/ai/graph/tools/mcp-tool-adapter.ts - Update
default-tool-registry.ts - Register: weather, wikidata, sportsdb, tmdb, google-sheets
- Unit tests for each adapter
Task-T-4: Routing Policy Enforcement
Priority: P0 Estimate: 2 days Dependencies: Task-T-1, Task-T-2
Deliverables:
- Add
routingPolicyto GraphState - Update ToolRouterNode (forcedTool)
- Update DataExecutionNode (sql-only gate)
- Update ToolExecutionNode (allowedTools)
- Integration tests
Task-T-5: System Prompts Integration
Priority: P1 Estimate: 1 day Dependencies: Task-T-2
Deliverables:
- Inject system prompts from model config
- Pass to LLM adapter
- Verify in transparency trace
Task-T-6: Jorvis-Help Knowledge Base
Priority: P1 Estimate: 1 day Dependencies: Task-T-4
Deliverables:
- Index Jorvis docs into vector store
- Configure DocumentContextNode for doc-only
- Test jorvis-help responses
Task-T-7: Open WebUI Registration Script
Priority: P1 Estimate: 0.5 day Dependencies: Task-T-1
Deliverables:
- Update
register-openwebui-model.sh - Auto-register all models from YAML
- Manual verification
Task-T-8: E2E Testing & Documentation
Priority: P1 Estimate: 1.5 days Dependencies: All above
Deliverables:
- E2E test suite for all models
- User documentation
- Architecture docs update
6. Risks & Mitigations
| Risk | Impact | Mitigation |
|---|---|---|
| MCP tools not compatible with Graph | High | Create adapter pattern (Task-T-3) |
| System prompt injection | Medium | Sanitize in openai-compat service |
| Performance with many models | Low | Lazy load model configs |
| Open WebUI caching | Medium | Add cache invalidation endpoint |
7. Consequences
Positive
- Clear separation of concerns per data source
- Reduced hallucinations (tool filtering)
- Better UX (users pick right model)
- Easier debugging (model → single data source)
Negative
- More models to maintain
- User must know which model to pick
- Increased config complexity
Mitigations
- Clear naming convention (Jorvis-{DataSource})
- Helpful descriptions in Open WebUI
- jorvis-cloud as fallback for general queries
8. References
- ADR-0018: Voice Pipeline Local Architecture
- ADR-0019: Local LLM Strategy
- ADR-0020: GraphRAG Implementation Strategy
analytics-platform/src/modules/mcp/tools/— Existing MCP toolsanalytics-platform/src/ai/graph/tools/— Graph tool system
9. Approval
Status: Proposed — Awaiting Review
| Reviewer | Status | Date |
|---|---|---|
| Codex (Gatekeeper) | ⏳ Pending | |
| George (User) | ⏳ Pending |
This ADR was created by Rovo (Architect) for Phase T (Domain-Specific Models).