B1: 项目脚手架 + 数据模型 + 租户管理 - Task 1.1: FastAPI 项目脚手架、SQLite + async SQLAlchemy - Task 1.2: 7 个数据模型 (Tenant, TenantConfig, DigitalEmployee, Conversation, Message, KnowledgeBase, Document) - Task 1.3: 租户 CRUD API + LLM 配置(含 API Key AES 加密) B2: 数字员工配置 + LLM Provider 抽象层 - Task 2.1: 数字员工 CRUD API(关联知识库) - Task 2.2: BaseLLMProvider 抽象接口 + OpenAI/Qwen Provider - Task 2.3: Provider 动态实例化 + test-provider 端点 验证: 26 个测试全部通过 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
126 lines
3.8 KiB
Python
126 lines
3.8 KiB
Python
from fastapi import HTTPException
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.models import Tenant, TenantConfig, TenantStatus
|
|
from app.providers import get_provider
|
|
from app.providers.base import BaseLLMProvider
|
|
from app.services.crypto import decrypt_api_key, encrypt_api_key
|
|
|
|
|
|
async def get_provider_for_tenant(
|
|
session: AsyncSession, tenant_id: str
|
|
) -> BaseLLMProvider:
|
|
"""根据租户配置动态实例化 Provider"""
|
|
await get_tenant(session, tenant_id)
|
|
|
|
result = await session.execute(
|
|
select(TenantConfig).where(TenantConfig.tenant_id == tenant_id)
|
|
)
|
|
config = result.scalar_one_or_none()
|
|
|
|
if not config:
|
|
raise HTTPException(status_code=404, detail="Tenant config not found")
|
|
|
|
api_key = decrypt_api_key(config.llm_api_key)
|
|
return get_provider(
|
|
provider_type=config.llm_provider,
|
|
api_key=api_key,
|
|
model=config.llm_model,
|
|
base_url=config.llm_base_url,
|
|
)
|
|
|
|
|
|
async def create_tenant(session: AsyncSession, name: str, slug: str) -> Tenant:
|
|
existing = await session.execute(select(Tenant).where(Tenant.slug == slug))
|
|
if existing.scalar_one_or_none():
|
|
raise HTTPException(status_code=400, detail="Slug already exists")
|
|
|
|
tenant = Tenant(name=name, slug=slug)
|
|
session.add(tenant)
|
|
await session.commit()
|
|
await session.refresh(tenant)
|
|
return tenant
|
|
|
|
|
|
async def get_tenant(session: AsyncSession, tenant_id: str) -> Tenant:
|
|
result = await session.execute(
|
|
select(Tenant).where(Tenant.id == tenant_id, Tenant.status != TenantStatus.deleted)
|
|
)
|
|
tenant = result.scalar_one_or_none()
|
|
if not tenant:
|
|
raise HTTPException(status_code=404, detail="Tenant not found")
|
|
return tenant
|
|
|
|
|
|
async def list_tenants(session: AsyncSession) -> list[Tenant]:
|
|
result = await session.execute(
|
|
select(Tenant).where(Tenant.status != TenantStatus.deleted)
|
|
)
|
|
return list(result.scalars().all())
|
|
|
|
|
|
async def update_tenant(
|
|
session: AsyncSession, tenant_id: str, name: str | None, slug: str | None
|
|
) -> Tenant:
|
|
tenant = await get_tenant(session, tenant_id)
|
|
|
|
if slug and slug != tenant.slug:
|
|
existing = await session.execute(select(Tenant).where(Tenant.slug == slug))
|
|
if existing.scalar_one_or_none():
|
|
raise HTTPException(status_code=400, detail="Slug already exists")
|
|
tenant.slug = slug
|
|
|
|
if name:
|
|
tenant.name = name
|
|
|
|
await session.commit()
|
|
await session.refresh(tenant)
|
|
return tenant
|
|
|
|
|
|
async def delete_tenant(session: AsyncSession, tenant_id: str) -> None:
|
|
tenant = await get_tenant(session, tenant_id)
|
|
tenant.status = TenantStatus.deleted
|
|
await session.commit()
|
|
|
|
|
|
async def update_tenant_config(
|
|
session: AsyncSession,
|
|
tenant_id: str,
|
|
llm_provider: str,
|
|
llm_api_key: str,
|
|
llm_model: str,
|
|
llm_base_url: str | None = None,
|
|
max_tokens_per_month: int = 1000000,
|
|
) -> TenantConfig:
|
|
await get_tenant(session, tenant_id)
|
|
|
|
result = await session.execute(
|
|
select(TenantConfig).where(TenantConfig.tenant_id == tenant_id)
|
|
)
|
|
config = result.scalar_one_or_none()
|
|
|
|
encrypted_key = encrypt_api_key(llm_api_key)
|
|
|
|
if config:
|
|
config.llm_provider = llm_provider
|
|
config.llm_api_key = encrypted_key
|
|
config.llm_model = llm_model
|
|
config.llm_base_url = llm_base_url
|
|
config.max_tokens_per_month = max_tokens_per_month
|
|
else:
|
|
config = TenantConfig(
|
|
tenant_id=tenant_id,
|
|
llm_provider=llm_provider,
|
|
llm_api_key=encrypted_key,
|
|
llm_model=llm_model,
|
|
llm_base_url=llm_base_url,
|
|
max_tokens_per_month=max_tokens_per_month,
|
|
)
|
|
session.add(config)
|
|
|
|
await session.commit()
|
|
await session.refresh(config)
|
|
return config
|