feat(tenancy): implement workspace isolation

This commit is contained in:
Junyan Qin
2026-07-19 09:58:59 +08:00
parent 37099ddf7e
commit 8b7ce77cec
271 changed files with 31166 additions and 6513 deletions
@@ -1,147 +1,292 @@
import quart
from ....authz import Permission, has_permission
from ....context import RequestContext
from ... import group
@group.group_class('models/llm', '/api/v1/provider/models/llm')
class LLMModelsRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@self.route('', methods=['GET', 'POST'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY)
async def _() -> str:
if quart.request.method == 'GET':
provider_uuid = quart.request.args.get('provider_uuid')
if provider_uuid:
return self.success(
data={'models': await self.ap.llm_model_service.get_llm_models_by_provider(provider_uuid)}
)
return self.success(data={'models': await self.ap.llm_model_service.get_llm_models()})
elif quart.request.method == 'POST':
json_data = await quart.request.json
model_uuid = await self.ap.llm_model_service.create_llm_model(json_data)
return self.success(data={'uuid': model_uuid})
@self.route(
'',
methods=['GET'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.RESOURCE_VIEW,
)
async def _(request_context: RequestContext) -> str:
provider_uuid = quart.request.args.get('provider_uuid')
include_secret = has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE)
if provider_uuid:
models = await self.ap.llm_model_service.get_llm_models_by_provider(
request_context,
provider_uuid,
include_secret=include_secret,
)
else:
models = await self.ap.llm_model_service.get_llm_models(
request_context,
include_secret=include_secret,
)
return self.success(data={'models': models})
@self.route('/<model_uuid>', methods=['GET', 'PUT', 'DELETE'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY)
async def _(model_uuid: str) -> str:
if quart.request.method == 'GET':
model = await self.ap.llm_model_service.get_llm_model(model_uuid)
@self.route(
'',
methods=['POST'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.PROVIDER_SECRET_MANAGE,
)
async def _(request_context: RequestContext) -> str:
try:
model_uuid = await self.ap.llm_model_service.create_llm_model(
request_context,
await quart.request.json,
)
except ValueError as exc:
return self.http_status(400, -1, str(exc))
return self.success(data={'uuid': model_uuid})
if model is None:
return self.http_status(404, -1, 'model not found')
@self.route(
'/<model_uuid>',
methods=['GET'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.RESOURCE_VIEW,
)
async def _(model_uuid: str, request_context: RequestContext) -> str:
model = await self.ap.llm_model_service.get_llm_model(
request_context,
model_uuid,
include_secret=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE),
)
if model is None:
return self.http_status(404, -1, 'model not found')
return self.success(data={'model': model})
return self.success(data={'model': model})
elif quart.request.method == 'PUT':
json_data = await quart.request.json
@self.route(
'/<model_uuid>',
methods=['PUT'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.PROVIDER_SECRET_MANAGE,
)
async def _(model_uuid: str, request_context: RequestContext) -> str:
try:
await self.ap.llm_model_service.update_llm_model(
request_context,
model_uuid,
await quart.request.json,
)
except ValueError as exc:
return self.http_status(400, -1, str(exc))
return self.success()
await self.ap.llm_model_service.update_llm_model(model_uuid, json_data)
return self.success()
elif quart.request.method == 'DELETE':
await self.ap.llm_model_service.delete_llm_model(model_uuid)
return self.success()
@self.route('/<model_uuid>/test', methods=['POST'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY)
async def _(model_uuid: str) -> str:
json_data = await quart.request.json
await self.ap.llm_model_service.test_llm_model(model_uuid, json_data)
@self.route(
'/<model_uuid>',
methods=['DELETE'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.RESOURCE_MANAGE,
)
async def _(model_uuid: str, request_context: RequestContext) -> str:
await self.ap.llm_model_service.delete_llm_model(request_context, model_uuid)
return self.success()
@self.route(
'/<model_uuid>/test',
methods=['POST'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.PROVIDER_SECRET_MANAGE,
)
async def _(model_uuid: str, request_context: RequestContext) -> str:
await self.ap.llm_model_service.test_llm_model(request_context, model_uuid, await quart.request.json)
return self.success()
@group.group_class('models/embedding', '/api/v1/provider/models/embedding')
class EmbeddingModelsRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@self.route('', methods=['GET', 'POST'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY)
async def _() -> str:
if quart.request.method == 'GET':
provider_uuid = quart.request.args.get('provider_uuid')
if provider_uuid:
return self.success(
data={
'models': await self.ap.embedding_models_service.get_embedding_models_by_provider(
provider_uuid
)
}
)
return self.success(data={'models': await self.ap.embedding_models_service.get_embedding_models()})
elif quart.request.method == 'POST':
json_data = await quart.request.json
model_uuid = await self.ap.embedding_models_service.create_embedding_model(json_data)
return self.success(data={'uuid': model_uuid})
@self.route(
'',
methods=['GET'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.RESOURCE_VIEW,
)
async def _(request_context: RequestContext) -> str:
provider_uuid = quart.request.args.get('provider_uuid')
include_secret = has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE)
if provider_uuid:
models = await self.ap.embedding_models_service.get_embedding_models_by_provider(
request_context,
provider_uuid,
include_secret=include_secret,
)
else:
models = await self.ap.embedding_models_service.get_embedding_models(
request_context,
include_secret=include_secret,
)
return self.success(data={'models': models})
@self.route('/<model_uuid>', methods=['GET', 'PUT', 'DELETE'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY)
async def _(model_uuid: str) -> str:
if quart.request.method == 'GET':
model = await self.ap.embedding_models_service.get_embedding_model(model_uuid)
@self.route(
'',
methods=['POST'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.PROVIDER_SECRET_MANAGE,
)
async def _(request_context: RequestContext) -> str:
try:
model_uuid = await self.ap.embedding_models_service.create_embedding_model(
request_context,
await quart.request.json,
)
except ValueError as exc:
return self.http_status(400, -1, str(exc))
return self.success(data={'uuid': model_uuid})
if model is None:
return self.http_status(404, -1, 'model not found')
@self.route(
'/<model_uuid>',
methods=['GET'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.RESOURCE_VIEW,
)
async def _(model_uuid: str, request_context: RequestContext) -> str:
model = await self.ap.embedding_models_service.get_embedding_model(
request_context,
model_uuid,
include_secret=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE),
)
if model is None:
return self.http_status(404, -1, 'model not found')
return self.success(data={'model': model})
return self.success(data={'model': model})
elif quart.request.method == 'PUT':
json_data = await quart.request.json
@self.route(
'/<model_uuid>',
methods=['PUT'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.PROVIDER_SECRET_MANAGE,
)
async def _(model_uuid: str, request_context: RequestContext) -> str:
try:
await self.ap.embedding_models_service.update_embedding_model(
request_context,
model_uuid,
await quart.request.json,
)
except ValueError as exc:
return self.http_status(400, -1, str(exc))
return self.success()
await self.ap.embedding_models_service.update_embedding_model(model_uuid, json_data)
return self.success()
elif quart.request.method == 'DELETE':
await self.ap.embedding_models_service.delete_embedding_model(model_uuid)
return self.success()
@self.route('/<model_uuid>/test', methods=['POST'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY)
async def _(model_uuid: str) -> str:
json_data = await quart.request.json
await self.ap.embedding_models_service.test_embedding_model(model_uuid, json_data)
@self.route(
'/<model_uuid>',
methods=['DELETE'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.RESOURCE_MANAGE,
)
async def _(model_uuid: str, request_context: RequestContext) -> str:
await self.ap.embedding_models_service.delete_embedding_model(request_context, model_uuid)
return self.success()
@self.route(
'/<model_uuid>/test',
methods=['POST'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.PROVIDER_SECRET_MANAGE,
)
async def _(model_uuid: str, request_context: RequestContext) -> str:
await self.ap.embedding_models_service.test_embedding_model(
request_context, model_uuid, await quart.request.json
)
return self.success()
@group.group_class('models/rerank', '/api/v1/provider/models/rerank')
class RerankModelsRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@self.route('', methods=['GET', 'POST'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY)
async def _() -> str:
if quart.request.method == 'GET':
provider_uuid = quart.request.args.get('provider_uuid')
if provider_uuid:
return self.success(
data={
'models': await self.ap.rerank_models_service.get_rerank_models_by_provider(provider_uuid)
}
)
return self.success(data={'models': await self.ap.rerank_models_service.get_rerank_models()})
elif quart.request.method == 'POST':
json_data = await quart.request.json
model_uuid = await self.ap.rerank_models_service.create_rerank_model(json_data)
return self.success(data={'uuid': model_uuid})
@self.route(
'',
methods=['GET'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.RESOURCE_VIEW,
)
async def _(request_context: RequestContext) -> str:
provider_uuid = quart.request.args.get('provider_uuid')
include_secret = has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE)
if provider_uuid:
models = await self.ap.rerank_models_service.get_rerank_models_by_provider(
request_context,
provider_uuid,
include_secret=include_secret,
)
else:
models = await self.ap.rerank_models_service.get_rerank_models(
request_context,
include_secret=include_secret,
)
return self.success(data={'models': models})
@self.route('/<model_uuid>', methods=['GET', 'PUT', 'DELETE'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY)
async def _(model_uuid: str) -> str:
if quart.request.method == 'GET':
model = await self.ap.rerank_models_service.get_rerank_model(model_uuid)
@self.route(
'',
methods=['POST'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.PROVIDER_SECRET_MANAGE,
)
async def _(request_context: RequestContext) -> str:
try:
model_uuid = await self.ap.rerank_models_service.create_rerank_model(
request_context,
await quart.request.json,
)
except ValueError as exc:
return self.http_status(400, -1, str(exc))
return self.success(data={'uuid': model_uuid})
if model is None:
return self.http_status(404, -1, 'model not found')
return self.success(data={'model': model})
elif quart.request.method == 'PUT':
json_data = await quart.request.json
await self.ap.rerank_models_service.update_rerank_model(model_uuid, json_data)
return self.success()
elif quart.request.method == 'DELETE':
await self.ap.rerank_models_service.delete_rerank_model(model_uuid)
return self.success()
@self.route('/<model_uuid>/test', methods=['POST'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY)
async def _(model_uuid: str) -> str:
json_data = await quart.request.json
await self.ap.rerank_models_service.test_rerank_model(model_uuid, json_data)
@self.route(
'/<model_uuid>',
methods=['GET'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.RESOURCE_VIEW,
)
async def _(model_uuid: str, request_context: RequestContext) -> str:
model = await self.ap.rerank_models_service.get_rerank_model(
request_context,
model_uuid,
include_secret=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE),
)
if model is None:
return self.http_status(404, -1, 'model not found')
return self.success(data={'model': model})
@self.route(
'/<model_uuid>',
methods=['PUT'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.PROVIDER_SECRET_MANAGE,
)
async def _(model_uuid: str, request_context: RequestContext) -> str:
try:
await self.ap.rerank_models_service.update_rerank_model(
request_context,
model_uuid,
await quart.request.json,
)
except ValueError as exc:
return self.http_status(400, -1, str(exc))
return self.success()
@self.route(
'/<model_uuid>',
methods=['DELETE'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.RESOURCE_MANAGE,
)
async def _(model_uuid: str, request_context: RequestContext) -> str:
await self.ap.rerank_models_service.delete_rerank_model(request_context, model_uuid)
return self.success()
@self.route(
'/<model_uuid>/test',
methods=['POST'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.PROVIDER_SECRET_MANAGE,
)
async def _(model_uuid: str, request_context: RequestContext) -> str:
await self.ap.rerank_models_service.test_rerank_model(request_context, model_uuid, await quart.request.json)
return self.success()
@@ -1,56 +1,102 @@
import quart
from ....authz import Permission, has_permission
from ....context import RequestContext
from ... import group
@group.group_class('models/providers', '/api/v1/provider/providers')
class ModelProvidersRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@self.route('', methods=['GET', 'POST'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY)
async def _() -> str:
if quart.request.method == 'GET':
providers = await self.ap.provider_service.get_providers()
# Add model counts
for provider in providers:
counts = await self.ap.provider_service.get_provider_model_counts(provider['uuid'])
provider['llm_count'] = counts['llm_count']
provider['embedding_count'] = counts['embedding_count']
provider['rerank_count'] = counts['rerank_count']
return self.success(data={'providers': providers})
elif quart.request.method == 'POST':
json_data = await quart.request.json
provider_uuid = await self.ap.provider_service.create_provider(json_data)
return self.success(data={'uuid': provider_uuid})
@self.route(
'/<provider_uuid>', methods=['GET', 'PUT', 'DELETE'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY
'',
methods=['GET'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.RESOURCE_VIEW,
)
async def _(provider_uuid: str) -> str:
if quart.request.method == 'GET':
provider = await self.ap.provider_service.get_provider(provider_uuid)
if provider is None:
return self.http_status(404, -1, 'provider not found')
counts = await self.ap.provider_service.get_provider_model_counts(provider_uuid)
async def _(request_context: RequestContext) -> str:
providers = await self.ap.provider_service.get_providers(
request_context,
include_secret=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE),
)
for provider in providers:
counts = await self.ap.provider_service.get_provider_model_counts(request_context, provider['uuid'])
provider['llm_count'] = counts['llm_count']
provider['embedding_count'] = counts['embedding_count']
provider['rerank_count'] = counts['rerank_count']
return self.success(data={'provider': provider})
elif quart.request.method == 'PUT':
json_data = await quart.request.json
await self.ap.provider_service.update_provider(provider_uuid, json_data)
return self.success()
elif quart.request.method == 'DELETE':
try:
await self.ap.provider_service.delete_provider(provider_uuid)
return self.success()
except ValueError as e:
return self.http_status(400, -1, str(e))
return self.success(data={'providers': providers})
@self.route('/<provider_uuid>/scan-models', methods=['GET'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY)
async def _(provider_uuid: str) -> str:
@self.route(
'',
methods=['POST'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.PROVIDER_SECRET_MANAGE,
)
async def _(request_context: RequestContext) -> str:
json_data = await quart.request.json
try:
provider_uuid = await self.ap.provider_service.create_provider(request_context, json_data)
except ValueError as exc:
return self.http_status(400, -1, str(exc))
return self.success(data={'uuid': provider_uuid})
@self.route(
'/<provider_uuid>',
methods=['GET'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.RESOURCE_VIEW,
)
async def _(provider_uuid: str, request_context: RequestContext) -> str:
provider = await self.ap.provider_service.get_provider(
request_context,
provider_uuid,
include_secret=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE),
)
if provider is None:
return self.http_status(404, -1, 'provider not found')
counts = await self.ap.provider_service.get_provider_model_counts(request_context, provider_uuid)
provider['llm_count'] = counts['llm_count']
provider['embedding_count'] = counts['embedding_count']
provider['rerank_count'] = counts['rerank_count']
return self.success(data={'provider': provider})
@self.route(
'/<provider_uuid>',
methods=['PUT'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.PROVIDER_SECRET_MANAGE,
)
async def _(provider_uuid: str, request_context: RequestContext) -> str:
json_data = await quart.request.json
try:
await self.ap.provider_service.update_provider(request_context, provider_uuid, json_data)
except ValueError as exc:
return self.http_status(400, -1, str(exc))
return self.success()
@self.route(
'/<provider_uuid>',
methods=['DELETE'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.RESOURCE_MANAGE,
)
async def _(provider_uuid: str, request_context: RequestContext) -> str:
try:
await self.ap.provider_service.delete_provider(request_context, provider_uuid)
return self.success()
except ValueError as e:
return self.http_status(400, -1, str(e))
@self.route(
'/<provider_uuid>/scan-models',
methods=['GET'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.PROVIDER_SECRET_MANAGE,
)
async def _(provider_uuid: str, request_context: RequestContext) -> str:
try:
model_type = quart.request.args.get('type')
result = await self.ap.provider_service.scan_provider_models(provider_uuid, model_type)
result = await self.ap.provider_service.scan_provider_models(request_context, provider_uuid, model_type)
return self.success(data=result)
except ValueError as e:
return self.http_status(400, -1, str(e))