feat: 人设模板系统、节点调度标签、pydantic-settings收敛、错误处理增强
新增persona_template表和CRUD API,BaseIndividualModel增加node_affinity和template_origin_id字段, WorkerCluster支持多集群Ray资源调度,环境变量收敛到pydantic-settings统一校验, 数据库异常转换为结构化BusinessError/RetryableError,系统节点支持custom_system_prompt。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
|
||||
from sqlalchemy.exc import IntegrityError, OperationalError
|
||||
from pydantic import ValidationError
|
||||
from kilostar.utils.error import UserNotExistError
|
||||
from kilostar.utils.error import UserNotExistError, BusinessError, RetryableError
|
||||
|
||||
from kilostar.utils.logger import get_logger
|
||||
|
||||
@@ -31,14 +31,16 @@ def database_exception(func):
|
||||
logger.error(f"对象校验失败:{e}")
|
||||
raise e
|
||||
except IntegrityError as e:
|
||||
logger.error(f"数据库完整性错误 (如重复记录): {e}")
|
||||
raise e
|
||||
logger.warning(f"数据库完整性冲突: {e.orig}")
|
||||
err = BusinessError(str(e.orig))
|
||||
err.http_status = 409
|
||||
err.code = "conflict"
|
||||
raise err from e
|
||||
except OperationalError as e:
|
||||
logger.error(f"数据库连接异常: {e}")
|
||||
raise e
|
||||
except UserNotExistError as e:
|
||||
logger.error(f"更改密码失败,用户不存在:{e}")
|
||||
raise e
|
||||
raise RetryableError(f"数据库暂时不可用,请稍后重试: {e}") from e
|
||||
except (UserNotExistError, BusinessError):
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.exception(f"未预期的数据库错误: {e}")
|
||||
raise e
|
||||
|
||||
@@ -34,6 +34,7 @@ from kilostar.core.postgres_database.model.mcp_server import MCPServerModel
|
||||
from kilostar.core.postgres_database.model.tool_config import ToolConfigModel
|
||||
from kilostar.core.postgres_database.model.custom_toolset import CustomToolsetModel
|
||||
from kilostar.core.postgres_database.model.system_event_log import SystemEventLog
|
||||
from kilostar.core.postgres_database.model.persona_template import PersonaTemplate
|
||||
|
||||
# 兼容旧代码的别名
|
||||
Provider = ProviderModel
|
||||
@@ -63,5 +64,6 @@ __all__ = [
|
||||
"ToolConfigModel",
|
||||
"CustomToolsetModel",
|
||||
"SystemEventLog",
|
||||
"PersonaTemplate",
|
||||
"AgentType",
|
||||
]
|
||||
|
||||
@@ -43,6 +43,12 @@ class BaseIndividualModel(BaseDataModel):
|
||||
owner_id: Mapped[str] = mapped_column(String(64), index=True)
|
||||
|
||||
agent_type: Mapped[str] = mapped_column(String(32))
|
||||
node_affinity: Mapped[str] = mapped_column(String(32), nullable=False, default="cpu")
|
||||
template_origin_id: Mapped[Optional[str]] = mapped_column(
|
||||
ForeignKey("persona_template.template_id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
|
||||
__mapper_args__ = {"polymorphic_on": "agent_type", "polymorphic_identity": "base"}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
from typing import List, Optional
|
||||
from sqlalchemy import String, Text, Boolean, text
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from .base import BaseDataModel
|
||||
|
||||
|
||||
class PersonaTemplate(BaseDataModel):
|
||||
__tablename__ = "persona_template"
|
||||
|
||||
template_id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
|
||||
description: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
||||
system_prompt: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
||||
agent_type: Mapped[str] = mapped_column(String(32), nullable=False, default="ordinary")
|
||||
provider_title: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
model_id: Mapped[Optional[str]] = mapped_column(String(100))
|
||||
tools: Mapped[Optional[List[str]]] = mapped_column(
|
||||
JSONB, default=list, server_default=text("'[]'::jsonb")
|
||||
)
|
||||
tags: Mapped[Optional[List[str]]] = mapped_column(
|
||||
JSONB, default=list, server_default=text("'[]'::jsonb")
|
||||
)
|
||||
is_builtin: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||||
owner_id: Mapped[Optional[str]] = mapped_column(String(64), index=True)
|
||||
@@ -0,0 +1,73 @@
|
||||
from sqlalchemy import select
|
||||
from ulid import ULID
|
||||
|
||||
from kilostar.core.postgres_database.model.persona_template import PersonaTemplate
|
||||
from kilostar.core.postgres_database.database_exception import database_exception
|
||||
|
||||
|
||||
class PersonaTemplateDatabase:
|
||||
def __init__(self, async_session_maker):
|
||||
self.async_session_maker = async_session_maker
|
||||
|
||||
@database_exception
|
||||
async def add_template(self, **kwargs) -> PersonaTemplate:
|
||||
async with self.async_session_maker() as session:
|
||||
tpl = PersonaTemplate(template_id=str(ULID()), **kwargs)
|
||||
session.add(tpl)
|
||||
await session.commit()
|
||||
await session.refresh(tpl)
|
||||
return tpl
|
||||
|
||||
@database_exception
|
||||
async def get_template(self, template_id: str):
|
||||
async with self.async_session_maker() as session:
|
||||
result = await session.execute(
|
||||
select(PersonaTemplate).where(PersonaTemplate.template_id == template_id)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
@database_exception
|
||||
async def list_templates(self, owner_id: str = None, include_builtin: bool = True):
|
||||
async with self.async_session_maker() as session:
|
||||
stmt = select(PersonaTemplate)
|
||||
if owner_id and include_builtin:
|
||||
from sqlalchemy import or_
|
||||
stmt = stmt.where(
|
||||
or_(PersonaTemplate.owner_id == owner_id, PersonaTemplate.is_builtin == True)
|
||||
)
|
||||
elif owner_id:
|
||||
stmt = stmt.where(PersonaTemplate.owner_id == owner_id)
|
||||
elif include_builtin:
|
||||
stmt = stmt.where(PersonaTemplate.is_builtin == True)
|
||||
result = await session.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
|
||||
@database_exception
|
||||
async def update_template(self, template_id: str, **kwargs):
|
||||
async with self.async_session_maker() as session:
|
||||
result = await session.execute(
|
||||
select(PersonaTemplate).where(PersonaTemplate.template_id == template_id)
|
||||
)
|
||||
tpl = result.scalar_one_or_none()
|
||||
if not tpl:
|
||||
return None
|
||||
for k, v in kwargs.items():
|
||||
if v is not None:
|
||||
setattr(tpl, k, v)
|
||||
session.add(tpl)
|
||||
await session.commit()
|
||||
await session.refresh(tpl)
|
||||
return tpl
|
||||
|
||||
@database_exception
|
||||
async def delete_template(self, template_id: str) -> bool:
|
||||
async with self.async_session_maker() as session:
|
||||
result = await session.execute(
|
||||
select(PersonaTemplate).where(PersonaTemplate.template_id == template_id)
|
||||
)
|
||||
tpl = result.scalar_one_or_none()
|
||||
if not tpl:
|
||||
return False
|
||||
await session.delete(tpl)
|
||||
await session.commit()
|
||||
return True
|
||||
Reference in New Issue
Block a user