refactor(schema): 重构数据库模型和 schema,清理废弃表
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
"""drop duplicate indexes on llm_factory and llms
|
||||
|
||||
Revision ID: 20260407_0003
|
||||
Revises: 20260407_0002
|
||||
Create Date: 2026-04-07 00:00:00
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260407_0003"
|
||||
down_revision: Union[str, Sequence[str], None] = "20260407_0002"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute("DROP INDEX IF EXISTS ix_llm_factory_name")
|
||||
op.execute("DROP INDEX IF EXISTS ix_llms_model_code")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("CREATE INDEX ix_llm_factory_name ON llm_factory(name)")
|
||||
op.execute("CREATE INDEX ix_llms_model_code ON llms(model_code)")
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
from .agent_chat_message import AgentChatMessage
|
||||
from .agent_chat_session import AgentChatSession
|
||||
from .auth_user import AuthUser
|
||||
from .invite_code import InviteCode
|
||||
from .llm import Llm
|
||||
from .llm_factory import LlmFactory
|
||||
from .points_ledger import PointsLedger
|
||||
@@ -14,6 +15,7 @@ __all__ = [
|
||||
"AgentChatMessage",
|
||||
"AgentChatSession",
|
||||
"AuthUser",
|
||||
"InviteCode",
|
||||
"Llm",
|
||||
"LlmFactory",
|
||||
"PointsLedger",
|
||||
|
||||
@@ -21,6 +21,4 @@ class Llm(TimestampMixin, SoftDeleteMixin, Base):
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
model_code: Mapped[str] = mapped_column(
|
||||
String(50), nullable=False, unique=True, index=True
|
||||
)
|
||||
model_code: Mapped[str] = mapped_column(String(50), nullable=False, unique=True)
|
||||
|
||||
@@ -15,8 +15,6 @@ class LlmFactory(TimestampMixin, SoftDeleteMixin, Base):
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||||
)
|
||||
name: Mapped[str] = mapped_column(
|
||||
String(50), nullable=False, unique=True, index=True
|
||||
)
|
||||
name: Mapped[str] = mapped_column(String(50), nullable=False, unique=True)
|
||||
request_url: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
avatar: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
@@ -34,3 +34,8 @@ class Profile(TimestampMixin, SoftDeleteMixin, Base):
|
||||
server_default=text("'{}'::jsonb"),
|
||||
default=dict,
|
||||
)
|
||||
referred_by: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("profiles.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from enum import Enum
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from schemas.agent.ui_hints import UiHintsPayload
|
||||
from schemas.domain.divination import DerivedDivinationData
|
||||
@@ -45,7 +45,6 @@ class WorkerAgentOutputLite(BaseModel):
|
||||
|
||||
status: RunStatus = RunStatus.SUCCESS
|
||||
sign_level: Literal["上上签", "中上签", "中下签", "下下签"]
|
||||
summary: str = Field(min_length=1, max_length=300)
|
||||
conclusion: list[str] = Field(min_length=1, max_length=6)
|
||||
focus_points: list[str] = Field(default_factory=list, max_length=6)
|
||||
advice: list[str] = Field(min_length=1, max_length=6)
|
||||
@@ -53,20 +52,6 @@ class WorkerAgentOutputLite(BaseModel):
|
||||
answer: str = Field(min_length=1, max_length=4000)
|
||||
error: ErrorInfo | None = None
|
||||
|
||||
# Backward-compatible shadow fields for legacy consumers.
|
||||
key_points: list[str] = Field(default_factory=list, max_length=6)
|
||||
result_type: str = Field(default="structured_payload")
|
||||
suggested_actions: list[str] = Field(default_factory=list, max_length=6)
|
||||
divination_derived: DerivedDivinationData | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def sync_compatibility_fields(self) -> WorkerAgentOutputLite:
|
||||
if not self.key_points and self.focus_points:
|
||||
self.key_points = list(self.focus_points)
|
||||
if not self.suggested_actions and self.advice:
|
||||
self.suggested_actions = list(self.advice)
|
||||
return self
|
||||
|
||||
|
||||
class WorkerAgentOutputRich(WorkerAgentOutputLite):
|
||||
ui_hints: UiHintsPayload | None = None
|
||||
@@ -75,6 +60,8 @@ class WorkerAgentOutputRich(WorkerAgentOutputLite):
|
||||
class AgentOutput(WorkerAgentOutputRich):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
divination_derived: DerivedDivinationData | None = None
|
||||
|
||||
|
||||
WorkerAgentOutput = WorkerAgentOutputLite | WorkerAgentOutputRich
|
||||
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Protocol
|
||||
from uuid import UUID
|
||||
|
||||
from core.agentscope.tools.tool_config import AgentTool
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from schemas.enums import AutomationJobStatus, ScheduleType
|
||||
|
||||
|
||||
class AutomationJobLike(Protocol):
|
||||
id: UUID
|
||||
owner_id: UUID
|
||||
bootstrap_key: str | None
|
||||
title: str
|
||||
config: dict[str, object]
|
||||
next_run_at: datetime
|
||||
timezone: str
|
||||
last_run_at: datetime | None
|
||||
status: AutomationJobStatus
|
||||
created_by: UUID | None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class ContextSource(str, Enum):
|
||||
LATEST_CHAT = "latest_chat"
|
||||
|
||||
|
||||
class ContextWindowMode(str, Enum):
|
||||
DAY = "day"
|
||||
NUMBER = "number"
|
||||
|
||||
|
||||
class MessageContextConfig(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
source: ContextSource = ContextSource.LATEST_CHAT
|
||||
window_mode: ContextWindowMode = ContextWindowMode.DAY
|
||||
window_count: int = Field(default=2, ge=1, le=200)
|
||||
|
||||
|
||||
class ScheduleRunAt(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
hour: int = Field(default=8, ge=0, le=23)
|
||||
minute: int = Field(default=0, ge=0, le=59)
|
||||
|
||||
|
||||
class ScheduleConfig(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
type: ScheduleType
|
||||
run_at: ScheduleRunAt
|
||||
weekdays: list[int] | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_weekdays(self) -> "ScheduleConfig":
|
||||
if self.type == ScheduleType.WEEKLY:
|
||||
if not self.weekdays:
|
||||
raise ValueError("weekdays is required when schedule type is weekly")
|
||||
invalid = [day for day in self.weekdays if day < 1 or day > 7]
|
||||
if invalid:
|
||||
raise ValueError("weekdays must be within 1-7")
|
||||
self.weekdays = sorted(set(self.weekdays))
|
||||
else:
|
||||
self.weekdays = None
|
||||
return self
|
||||
|
||||
|
||||
class RuntimeConfig(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
enabled_tools: list[AgentTool] = Field(default_factory=list, max_length=32)
|
||||
context: MessageContextConfig = Field(default_factory=MessageContextConfig)
|
||||
|
||||
|
||||
class AutomationJobConfig(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
enabled_tools: list[AgentTool] | None = Field(default=None, max_length=32)
|
||||
context: MessageContextConfig | None = None
|
||||
input_template: str | None = Field(default=None, min_length=1, max_length=4000)
|
||||
schedule: ScheduleConfig | None = None
|
||||
|
||||
|
||||
class AutomationJob(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
id: UUID
|
||||
owner_id: UUID
|
||||
bootstrap_key: str | None = Field(default=None, min_length=1, max_length=64)
|
||||
title: str = Field(..., min_length=1, max_length=255)
|
||||
config: AutomationJobConfig
|
||||
next_run_at: datetime
|
||||
timezone: str = Field(default="UTC", min_length=1, max_length=50)
|
||||
last_run_at: datetime | None = None
|
||||
status: AutomationJobStatus
|
||||
created_by: UUID | None = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
@classmethod
|
||||
def from_orm(cls, obj: object) -> "AutomationJob":
|
||||
return cls(
|
||||
id=getattr(obj, "id"),
|
||||
owner_id=getattr(obj, "owner_id"),
|
||||
bootstrap_key=getattr(obj, "bootstrap_key"),
|
||||
title=getattr(obj, "title"),
|
||||
config=AutomationJobConfig.model_validate(getattr(obj, "config", {}) or {}),
|
||||
next_run_at=getattr(obj, "next_run_at"),
|
||||
timezone=getattr(obj, "timezone"),
|
||||
last_run_at=getattr(obj, "last_run_at"),
|
||||
status=getattr(obj, "status"),
|
||||
created_by=getattr(obj, "created_by"),
|
||||
created_at=getattr(obj, "created_at"),
|
||||
updated_at=getattr(obj, "updated_at"),
|
||||
)
|
||||
|
||||
@property
|
||||
def is_system(self) -> bool:
|
||||
return self.bootstrap_key is not None
|
||||
@@ -1,11 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import ClassVar
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class SessionStateSnapshot(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="allow")
|
||||
|
||||
pass
|
||||
@@ -1,81 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import ClassVar, Literal, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from schemas.enums import InboxMessageStatus, InboxMessageType
|
||||
|
||||
__all__ = [
|
||||
"InboxMessageType",
|
||||
"InboxMessageStatus",
|
||||
"CalendarInviteContent",
|
||||
"CalendarUpdateContent",
|
||||
"CalendarDeleteContent",
|
||||
"FriendshipContent",
|
||||
"CalendarContent",
|
||||
"InboxMessageContent",
|
||||
"parse_calendar_content",
|
||||
]
|
||||
|
||||
|
||||
class CalendarInviteContent(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
||||
|
||||
type: Literal["invite"]
|
||||
permission: int = Field(..., description="权限: 1=view, 4=edit, 8=invite")
|
||||
action: Literal["pending"] = "pending"
|
||||
|
||||
|
||||
class CalendarUpdateContent(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
||||
|
||||
type: Literal["update"]
|
||||
title: str = Field(..., description="事件标题")
|
||||
action: Literal["updated"] = "updated"
|
||||
|
||||
|
||||
class CalendarDeleteContent(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
||||
|
||||
type: Literal["delete"]
|
||||
title: str = Field(..., description="事件标题")
|
||||
action: Literal["deleted"] = "deleted"
|
||||
|
||||
|
||||
class FriendshipContent(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
||||
|
||||
type: Literal["request"]
|
||||
message: str | None = Field(None, description="好友申请消息")
|
||||
|
||||
|
||||
CalendarContent = Union[
|
||||
CalendarInviteContent,
|
||||
CalendarUpdateContent,
|
||||
CalendarDeleteContent,
|
||||
]
|
||||
|
||||
InboxMessageContent = Union[
|
||||
CalendarInviteContent,
|
||||
CalendarUpdateContent,
|
||||
CalendarDeleteContent,
|
||||
FriendshipContent,
|
||||
]
|
||||
|
||||
|
||||
def parse_calendar_content(content: str | None) -> CalendarContent | None:
|
||||
if not content:
|
||||
return None
|
||||
try:
|
||||
data = json.loads(content)
|
||||
content_type = data.get("type")
|
||||
if content_type == "invite":
|
||||
return CalendarInviteContent(**data)
|
||||
if content_type == "update":
|
||||
return CalendarUpdateContent(**data)
|
||||
if content_type == "delete":
|
||||
return CalendarDeleteContent(**data)
|
||||
raise ValueError(f"Unknown calendar content type: {content_type}")
|
||||
except Exception:
|
||||
return None
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Literal
|
||||
from uuid import UUID
|
||||
@@ -7,6 +8,7 @@ from uuid import UUID
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from ..enums import (
|
||||
PointsBizType,
|
||||
PointsChangeType,
|
||||
PointsOperatorType,
|
||||
)
|
||||
@@ -76,3 +78,51 @@ def parse_points_ledger_metadata(
|
||||
if change_type == PointsChangeType.GRANT:
|
||||
return GrantLedgerMetadata.model_validate(payload)
|
||||
return AdjustLedgerMetadata.model_validate(payload)
|
||||
|
||||
|
||||
class ApplyPointsChangeCommand(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
user_id: UUID
|
||||
change_type: PointsChangeType
|
||||
biz_type: PointsBizType | None = None
|
||||
biz_id: UUID | None = None
|
||||
event_id: str = Field(min_length=1, max_length=64)
|
||||
amount: int = Field(gt=0)
|
||||
direction: Literal[1, -1]
|
||||
operator_id: UUID | None = None
|
||||
metadata: PointsLedgerMetadata
|
||||
occurred_at: datetime | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_change_type_contract(self) -> "ApplyPointsChangeCommand":
|
||||
if self.change_type == PointsChangeType.REGISTER:
|
||||
if (
|
||||
self.direction != 1
|
||||
or self.biz_type is not None
|
||||
or self.biz_id is not None
|
||||
):
|
||||
raise ValueError("register must use direction=1 and no biz binding")
|
||||
return self
|
||||
|
||||
if self.change_type == PointsChangeType.CONSUME:
|
||||
if (
|
||||
self.direction != -1
|
||||
or self.biz_type != PointsBizType.CHAT
|
||||
or self.biz_id is None
|
||||
):
|
||||
raise ValueError("consume must use direction=-1 and chat binding")
|
||||
return self
|
||||
|
||||
if self.change_type == PointsChangeType.GRANT:
|
||||
if (
|
||||
self.direction != 1
|
||||
or self.biz_type != PointsBizType.CHAT
|
||||
or self.biz_id is None
|
||||
):
|
||||
raise ValueError("grant must use direction=1 and chat binding")
|
||||
return self
|
||||
|
||||
if self.biz_type != PointsBizType.CHAT or self.biz_id is None:
|
||||
raise ValueError("adjust must use chat binding")
|
||||
return self
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import ClassVar, Literal
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from schemas.enums import ScheduleItemSourceType, ScheduleItemStatus
|
||||
|
||||
__all__ = [
|
||||
"AttachmentType",
|
||||
"ScheduleItemMetadataAttachment",
|
||||
"ScheduleItemMetadata",
|
||||
"ScheduleItemSourceType",
|
||||
"ScheduleItemStatus",
|
||||
]
|
||||
|
||||
|
||||
class AttachmentType(str, Enum):
|
||||
DOCUMENT = "document"
|
||||
REMINDER = "reminder"
|
||||
|
||||
|
||||
class ScheduleItemMetadataAttachment(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
||||
|
||||
name: str
|
||||
type: AttachmentType
|
||||
visible_to: list[UUID] = Field(default_factory=list)
|
||||
url: str | None = None
|
||||
note: str | None = None
|
||||
content: str | None = None
|
||||
|
||||
|
||||
class ScheduleItemMetadata(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
||||
|
||||
color: str | None = Field(default=None, pattern=r"^#[0-9A-Fa-f]{6}$")
|
||||
location: str | None = None
|
||||
notes: str | None = None
|
||||
attachments: list[ScheduleItemMetadataAttachment] = Field(default_factory=list)
|
||||
reminder_minutes: int | None = Field(default=None, ge=0, le=10080)
|
||||
version: Literal[1] = 1
|
||||
@@ -1,7 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
TodoOrder = Annotated[int, Field(ge=0)]
|
||||
@@ -1,88 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Literal
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from ..domain.points import PointsLedgerMetadata
|
||||
from ..enums import PointsBizType, PointsChangeType
|
||||
|
||||
|
||||
class UserPointsSnapshot(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
user_id: UUID
|
||||
balance: int = Field(ge=0)
|
||||
frozen_balance: int = Field(ge=0)
|
||||
lifetime_earned: int = Field(ge=0)
|
||||
lifetime_spent: int = Field(ge=0)
|
||||
version: int = Field(ge=0)
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class PointsLedgerEntry(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: UUID
|
||||
user_id: UUID
|
||||
direction: Literal[1, -1]
|
||||
amount: int = Field(gt=0)
|
||||
balance_after: int = Field(ge=0)
|
||||
change_type: PointsChangeType
|
||||
biz_type: PointsBizType | None = None
|
||||
biz_id: UUID | None = None
|
||||
event_id: str = Field(min_length=1, max_length=64)
|
||||
operator_id: UUID | None = None
|
||||
metadata: PointsLedgerMetadata = Field(validation_alias="metadata_json")
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class ApplyPointsChangeCommand(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
user_id: UUID
|
||||
change_type: PointsChangeType
|
||||
biz_type: PointsBizType | None = None
|
||||
biz_id: UUID | None = None
|
||||
event_id: str = Field(min_length=1, max_length=64)
|
||||
amount: int = Field(gt=0)
|
||||
direction: Literal[1, -1]
|
||||
operator_id: UUID | None = None
|
||||
metadata: PointsLedgerMetadata
|
||||
occurred_at: datetime | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_change_type_contract(self) -> "ApplyPointsChangeCommand":
|
||||
if self.change_type == PointsChangeType.REGISTER:
|
||||
if (
|
||||
self.direction != 1
|
||||
or self.biz_type is not None
|
||||
or self.biz_id is not None
|
||||
):
|
||||
raise ValueError("register must use direction=1 and no biz binding")
|
||||
return self
|
||||
|
||||
if self.change_type == PointsChangeType.CONSUME:
|
||||
if (
|
||||
self.direction != -1
|
||||
or self.biz_type != PointsBizType.CHAT
|
||||
or self.biz_id is None
|
||||
):
|
||||
raise ValueError("consume must use direction=-1 and chat binding")
|
||||
return self
|
||||
|
||||
if self.change_type == PointsChangeType.GRANT:
|
||||
if (
|
||||
self.direction != 1
|
||||
or self.biz_type != PointsBizType.CHAT
|
||||
or self.biz_id is None
|
||||
):
|
||||
raise ValueError("grant must use direction=1 and chat binding")
|
||||
return self
|
||||
|
||||
if self.biz_type != PointsBizType.CHAT or self.biz_id is None:
|
||||
raise ValueError("adjust must use chat binding")
|
||||
return self
|
||||
@@ -41,17 +41,22 @@ class PreferenceSettings(BaseModel):
|
||||
return normalized
|
||||
|
||||
|
||||
class NotificationSettings(BaseModel):
|
||||
allow_notifications: bool = True
|
||||
allow_vibration: bool = True
|
||||
|
||||
|
||||
class ProfileSettingsV1(BaseModel):
|
||||
version: Literal[1] = 1
|
||||
preferences: PreferenceSettings = Field(default_factory=PreferenceSettings)
|
||||
privacy: dict = Field(default_factory=dict)
|
||||
notification: dict = Field(default_factory=dict)
|
||||
privacy: dict[str, object] = Field(default_factory=dict)
|
||||
notification: NotificationSettings = Field(default_factory=NotificationSettings)
|
||||
|
||||
|
||||
ProfileSettingsUnion = ProfileSettingsV1
|
||||
|
||||
|
||||
def parse_profile_settings(raw: dict | None) -> ProfileSettingsUnion:
|
||||
def parse_profile_settings(raw: dict[str, object] | None) -> ProfileSettingsUnion:
|
||||
payload = dict(raw or {})
|
||||
payload.setdefault("version", 1)
|
||||
return ProfileSettingsV1.model_validate(payload)
|
||||
|
||||
@@ -208,15 +208,11 @@ class HistoryAgentOutput(BaseModel):
|
||||
|
||||
status: Literal["success", "failed"] | None = None
|
||||
sign_level: Literal["上上签", "中上签", "中下签", "下下签"] | None = None
|
||||
summary: str | None = None
|
||||
conclusion: list[str] = Field(default_factory=list)
|
||||
focus_points: list[str] = Field(default_factory=list)
|
||||
advice: list[str] = Field(default_factory=list)
|
||||
keywords: list[str] = Field(default_factory=list)
|
||||
answer: str | None = None
|
||||
key_points: list[str] = Field(default_factory=list)
|
||||
result_type: str | None = None
|
||||
suggested_actions: list[str] = Field(default_factory=list)
|
||||
divination_derived: DerivedDivinationData | None = None
|
||||
|
||||
|
||||
|
||||
@@ -109,11 +109,7 @@ def _extract_worker_agent_output(
|
||||
try:
|
||||
agent_output = AgentOutput.model_validate(agent_output_data)
|
||||
except Exception:
|
||||
normalized_payload = _normalize_agent_output_payload(agent_output_data)
|
||||
try:
|
||||
agent_output = AgentOutput.model_validate(normalized_payload)
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
if not agent_output:
|
||||
return None
|
||||
@@ -123,37 +119,6 @@ def _extract_worker_agent_output(
|
||||
return payload or None
|
||||
|
||||
|
||||
def _normalize_agent_output_payload(agent_output_data: Any) -> dict[str, Any] | None:
|
||||
if not isinstance(agent_output_data, dict):
|
||||
return None
|
||||
normalized = dict(agent_output_data)
|
||||
derived = normalized.get("divination_derived")
|
||||
if isinstance(derived, dict):
|
||||
normalized["divination_derived"] = _normalize_divination_derived(derived)
|
||||
return normalized
|
||||
|
||||
|
||||
def _normalize_divination_derived(value: Any) -> Any:
|
||||
if isinstance(value, dict):
|
||||
result: dict[str, Any] = {}
|
||||
for key, item in value.items():
|
||||
normalized_key = _snake_to_camel(key)
|
||||
result[normalized_key] = _normalize_divination_derived(item)
|
||||
return result
|
||||
if isinstance(value, list):
|
||||
return [_normalize_divination_derived(item) for item in value]
|
||||
return value
|
||||
|
||||
|
||||
def _snake_to_camel(value: str) -> str:
|
||||
if "_" not in value:
|
||||
return value
|
||||
parts = value.split("_")
|
||||
if not parts:
|
||||
return value
|
||||
return parts[0] + "".join(part[:1].upper() + part[1:] for part in parts[1:])
|
||||
|
||||
|
||||
def mime_to_suffix(mime_type: str) -> str:
|
||||
mapping = {
|
||||
"image/png": "png",
|
||||
|
||||
@@ -8,7 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from models.points_ledger import PointsLedger
|
||||
from models.user_points import UserPoints
|
||||
from schemas.shared.points import ApplyPointsChangeCommand
|
||||
from schemas.domain.points import ApplyPointsChangeCommand
|
||||
|
||||
|
||||
class PointsRepository:
|
||||
|
||||
@@ -8,7 +8,7 @@ from uuid import UUID, uuid4
|
||||
from core.http.errors import ApiProblemError, problem_payload
|
||||
from schemas.domain.points import ConsumeLedgerMetadata, PointsChargeSnapshot
|
||||
from schemas.enums import PointsBizType, PointsChangeType, PointsOperatorType
|
||||
from schemas.shared.points import ApplyPointsChangeCommand
|
||||
from schemas.domain.points import ApplyPointsChangeCommand
|
||||
from v1.points.repository import PointsRepository
|
||||
|
||||
RUN_POINTS_COST = 20
|
||||
|
||||
Reference in New Issue
Block a user