Tenant-Scoped Model Filtering (owner_tenant_id)#
Overview#
RAGFlow enforces tenant-level model isolation so that each user only sees the LLM/embedding/rerank models registered under their own (or a joined) tenant. The mechanism is a single optional owner_tenant_id field that propagates from a React context provider on the Pipeline/Agent canvas all the way to the GET /api/v1/models backend query.
Data Flow#
Agent/Pipeline page (index.tsx)
└─ OwnerTenantIdContext.Provider value={agentDetail.user_id}
└─ node form (e.g. ExtractorForm)
└─ useOwnerTenantId()
└─ LargeModelFormField { ownerTenantId }
└─ NextLLMSelect { ownerTenantId }
└─ LlmSettingFieldItems { ownerTenantId }
└─ LLMFormField { ownerTenantId }
└─ ModelTreeSelect { ownerTenantId }
└─ useFetchAllAddedModels(type, ownerTenantId)
└─ GET /api/v1/models?owner_tenant_id=<value>
└─ get_added_models() (models_api.py)
└─ list_tenant_added_models(target_tenant_id, ...)
Key Files#
| Layer | File | Notes |
|---|---|---|
| Context definition | web/src/pages/agent/context.ts | OwnerTenantIdContext + useOwnerTenantId() hook |
| Context provider | web/src/pages/agent/index.tsx | Wraps AgentCanvas with value={agentDetail.user_id} |
| Consumer (node form) | web/src/pages/agent/form/extractor-form/index.tsx | Calls useOwnerTenantId() and passes to LargeModelFormField |
| Form field UI | web/src/components/large-model-form-field.tsx | Forwards ownerTenantId to NextLLMSelect |
| Select component | web/src/components/llm-select/next.tsx | Forwards to LlmSettingFieldItems |
| Setting items | web/src/components/llm-setting-items/next.tsx | Forwards to LLMFormField → ModelTreeSelect |
| Model tree picker | web/src/components/model-tree-select.tsx | Calls useFetchAllAddedModels(undefined, ownerTenantId) |
| React Query hook | web/src/hooks/use-llm-request.tsx | Sets params.owner_tenant_id on the API request |
| Display label | web/src/components/llm-select/llm-label.tsx | Also calls useFetchAllAddedModels(undefined, ownerTenantId) to resolve the display name |
| Backend endpoint | api/apps/restful_apis/models_api.py | GET /api/v1/models; resolves permission before querying |
Context Provider Details#
OwnerTenantIdContext is a createContext<string | undefined>(undefined) . In agent/index.tsx, the provider is placed around AgentCanvas and sets its value to agentDetail.user_id — the user ID of the agent/pipeline owner, which doubles as their tenant_id. Any node form rendered inside the canvas calls useOwnerTenantId() to read this value.
Backend Permission Check#
In get_added_models(), when owner_tenant_id is provided and differs from the authenticated caller's tenant_id:
TenantService.get_joined_tenants_by_user_id()fetches all tenants the caller has joined.- The requested
owner_tenant_idmust appear in that set; otherwise the endpoint returns"Permission denied". - On success,
list_tenant_added_models(target_tenant_id, model_type_filter)is called with the owner's tenant ID as the scoping key .
This means a user viewing a shared agent that belongs to another tenant will see models from the owner's tenant — not their own — provided they are a member of that tenant.
Model List Anomalies from Missing/Incorrect Values#
| Symptom | Likely cause |
|---|---|
| Model picker shows your own models while editing a shared agent | ownerTenantId is undefined — context not provided or agentDetail.user_id has not loaded yet |
| Model picker is empty | owner_tenant_id passed to the API belongs to a tenant the caller has not joined — backend returns "Permission denied" and useFetchAllAddedModels gets an empty array |
| Selected model label shows wrong name or blank | LLMLabel also calls useFetchAllAddedModels with ownerTenantId; if that value is incorrect the model lookup fails silently |
| Stale models shown after tenant model changes | React Query uses the cache key [LLMApiAction.AllModels, modelType, ownerTenantId] ; a cache miss from key mismatch can serve stale results |
Debugging tip: Check the network request for
GET /api/v1/modelsin browser DevTools. The presence and value ofowner_tenant_idin the query string will immediately confirm whether context propagation succeeded.
Extension Notes#
- Other model picker variants (
ModelTreeSelectFormField,LargeModelFormFieldWithoutFilter) expose the sameownerTenantIdprop — wire it up the same way. - Adding tenant scoping to a new node form: call
useOwnerTenantId()fromweb/src/pages/agent/context.tsand pass the result to whatever LLM picker the form uses. - Backend
list_tenant_added_modelslives inapi/apps/services/models_api_service.py; it is the only place that actually queries the three-tierTenantModelProvider → TenantModelInstance → TenantModelhierarchy for a given tenant.