Tenant Model Resolution#
RAGFlow uses an asymmetric model resolution pattern when a chat or retrieval request touches a shared knowledge base:
- Embedding models → resolved against the KB owner's tenant (
kbs[0].tenant_id) - Chat, rerank, and TTS models → resolved against the dialog/request caller's tenant (
dialog.tenant_id)
This design ensures that query embeddings are always generated with the same model used to index the KB's vectors, while each user brings their own chat LLM credentials. The central service for all resolution logic is api/db/joint_services/tenant_model_service.py.
Core Resolution Functions#
All live in tenant_model_service.py:
| Function | Lines | Purpose |
|---|---|---|
resolve_model_config | 253–257 | Entry point. Tries UUID first, falls back to composite name. |
get_model_config_by_id | 322–370 | Resolves by TenantModel.id UUID; enforces cross-tenant membership. |
get_model_config_from_provider_instance | 260–319 | Resolves by composite name model@instance@provider. |
get_tenant_default_model_by_type | 163–201 | Reads tenant-level default model fields (e.g. tenant_embd_id, tenant_llm_id). |
split_model_name | 204–227 | Right-anchored parser for model_name@instance@provider composite keys. |
Model reference formats accepted as model_ref:
- UUID —
TenantModel.id(preferred post-v0.27.0) - Composite name —
{model_name}@{instance_name}@{provider_name}(legacy/fallback). Right-anchored split handles model names that contain@(e.g.text-embedding-v1@q8_0@lmstudio@LM-Studio).
The Asymmetric Pattern in Code#
The pattern is applied consistently in get_models() in dialog_service.py:
- Embedding:
embd_owner_tenant_id = kbs[0].tenant_id→resolve_model_config(embd_owner_tenant_id, EMBEDDING, kb.embd_id) - Chat:
resolve_model_config(dialog.tenant_id, CHAT, dialog.llm_id) - Rerank:
resolve_model_config(dialog.tenant_id, RERANK, dialog.rerank_id)
The same split appears in async_ask() and in the dataset search functions in dataset_api_service.py.
Cross-Tenant Access Control#
When get_model_config_by_id is called with a tenant_id that doesn't own the provider, it verifies membership via TenantService.get_joined_tenants_by_user_id(). If the caller is not a UserTenantRole.NORMAL member of the owning tenant, a LookupError is raised .
The same check applies to GET /api/v1/models?owner_tenant_id=<value>: when owner_tenant_id differs from the caller's own tenant, the endpoint verifies membership before listing models from the owner's tenant .
Dual-Column DB Schema#
After v0.27.0, downstream tables carry both legacy string model names and UUID FKs into TenantModel :
| Table | Legacy string | UUID FK |
|---|---|---|
tenant | embd_id, llm_id, … | tenant_embd_id, tenant_llm_id, … |
knowledgebase | embd_id | tenant_embd_id |
dialog | llm_id, rerank_id | tenant_llm_id, tenant_rerank_id |
get_tenant_default_model_by_type prefers the UUID FK with fallback to the string name on LookupError .
Tenant-Scoped Model Listing (UI)#
On the Agent/Pipeline canvas, OwnerTenantIdContext propagates the agent owner's user_id as owner_tenant_id to all model picker components . GET /api/v1/models?owner_tenant_id=<value> lists models from the owner's tenant (after a membership check) rather than the logged-in user's tenant. See Tenant-Scoped Model Filtering for the full component hierarchy.
Common Failure Modes#
| Symptom | Cause |
|---|---|
LookupError: Provider X not found | tenant_id doesn't own the provider and isn't a joined member of the owning tenant |
LookupError: TenantModel id=… not found | UUID FK is stale; code falls back to composite name lookup, which may also fail if the model was deleted |
| Embedding mismatch / vector dimension errors | kb.embd_id references a model in a tenant the resolver can't access |
| Model picker empty in shared agent | owner_tenant_id was not propagated or references a tenant the viewer hasn't joined |