feat: 实现起卦、设置与积分系统
This commit is contained in:
@@ -1,11 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from models.llm import Llm
|
||||
from models.llm_factory import LlmFactory
|
||||
from models.system_agents import SystemAgents
|
||||
from .agent_chat_message import AgentChatMessage
|
||||
from .agent_chat_session import AgentChatSession
|
||||
from .llm import Llm
|
||||
from .llm_factory import LlmFactory
|
||||
from .points_ledger import PointsLedger
|
||||
from .profile import Profile
|
||||
from .system_agents import SystemAgents
|
||||
from .user_points import UserPoints
|
||||
|
||||
__all__ = [
|
||||
"AgentChatMessage",
|
||||
"AgentChatSession",
|
||||
"Llm",
|
||||
"LlmFactory",
|
||||
"PointsLedger",
|
||||
"Profile",
|
||||
"SystemAgents",
|
||||
"UserPoints",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import (
|
||||
BigInteger,
|
||||
JSON,
|
||||
Enum as SqlEnum,
|
||||
ForeignKey,
|
||||
Integer,
|
||||
Numeric,
|
||||
String,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||
from schemas.enums import AgentChatMessageRole
|
||||
|
||||
__all__ = ["AgentChatMessage", "AgentChatMessageRole"]
|
||||
|
||||
|
||||
class AgentChatMessage(TimestampMixin, SoftDeleteMixin, Base):
|
||||
__tablename__: str = "messages"
|
||||
__table_args__: tuple[UniqueConstraint] = (
|
||||
UniqueConstraint("session_id", "seq", name="uq_messages_session_seq"),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||||
)
|
||||
session_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("sessions.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
seq: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
role: Mapped[AgentChatMessageRole] = mapped_column(
|
||||
SqlEnum(
|
||||
AgentChatMessageRole,
|
||||
name="agent_chat_message_role",
|
||||
native_enum=False,
|
||||
values_callable=lambda enum_cls: [item.value for item in enum_cls],
|
||||
),
|
||||
nullable=False,
|
||||
)
|
||||
content: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
model_code: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
||||
tool_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
input_tokens: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
output_tokens: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
cost: Mapped[Decimal] = mapped_column(Numeric(12, 6), nullable=False, default=0)
|
||||
latency_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
visibility_mask: Mapped[int] = mapped_column(
|
||||
BigInteger,
|
||||
nullable=False,
|
||||
default=0,
|
||||
)
|
||||
metadata_json: Mapped[dict[str, object] | None] = mapped_column(
|
||||
"metadata", JSON().with_variant(JSONB, "postgresql"), nullable=True
|
||||
)
|
||||
@@ -0,0 +1,75 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
JSON,
|
||||
Enum as SqlEnum,
|
||||
Integer,
|
||||
Numeric,
|
||||
String,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||
from schemas.enums import AgentChatSessionStatus, SessionType
|
||||
|
||||
__all__ = ["AgentChatSession", "AgentChatSessionStatus", "SessionType"]
|
||||
|
||||
|
||||
class AgentChatSession(TimestampMixin, SoftDeleteMixin, Base):
|
||||
__tablename__: str = "sessions"
|
||||
__table_args__ = {"extend_existing": True}
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||||
)
|
||||
user_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
session_type: Mapped[SessionType] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default=SessionType.CHAT,
|
||||
)
|
||||
job_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
nullable=True,
|
||||
)
|
||||
title: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
status: Mapped[AgentChatSessionStatus] = mapped_column(
|
||||
SqlEnum(
|
||||
AgentChatSessionStatus,
|
||||
name="agent_chat_session_status",
|
||||
native_enum=False,
|
||||
values_callable=lambda enum_cls: [item.value for item in enum_cls],
|
||||
),
|
||||
nullable=False,
|
||||
default=AgentChatSessionStatus.PENDING,
|
||||
)
|
||||
last_activity_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
nullable=False,
|
||||
)
|
||||
message_count: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, server_default=text("0")
|
||||
)
|
||||
total_tokens: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, server_default=text("0")
|
||||
)
|
||||
total_cost: Mapped[Decimal] = mapped_column(
|
||||
Numeric(12, 6), nullable=False, server_default=text("0")
|
||||
)
|
||||
state_snapshot: Mapped[dict | None] = mapped_column(
|
||||
JSON().with_variant(JSONB, "postgresql"),
|
||||
nullable=True,
|
||||
)
|
||||
@@ -0,0 +1,113 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import (
|
||||
BigInteger,
|
||||
CheckConstraint,
|
||||
Index,
|
||||
JSON,
|
||||
SmallInteger,
|
||||
String,
|
||||
UniqueConstraint,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, TimestampMixin
|
||||
|
||||
|
||||
class PointsLedger(TimestampMixin, Base):
|
||||
__tablename__ = "points_ledger"
|
||||
__table_args__ = (
|
||||
CheckConstraint("amount > 0", name="ck_points_ledger_amount_positive"),
|
||||
CheckConstraint(
|
||||
"direction in (1, -1)", name="ck_points_ledger_direction_valid"
|
||||
),
|
||||
CheckConstraint(
|
||||
"balance_after >= 0", name="ck_points_ledger_balance_after_non_negative"
|
||||
),
|
||||
CheckConstraint(
|
||||
"change_type in ('register', 'consume', 'grant', 'adjust')",
|
||||
name="ck_points_ledger_change_type",
|
||||
),
|
||||
CheckConstraint(
|
||||
"biz_type is null or biz_type = 'chat'",
|
||||
name="ck_points_ledger_biz_type",
|
||||
),
|
||||
CheckConstraint(
|
||||
"((change_type = 'register' and biz_type is null and biz_id is null) or "
|
||||
"(change_type in ('consume', 'grant', 'adjust') and biz_type = 'chat' and biz_id is not null))",
|
||||
name="ck_points_ledger_biz_binding",
|
||||
),
|
||||
CheckConstraint(
|
||||
"((change_type in ('register', 'grant') and direction = 1) or "
|
||||
"(change_type = 'consume' and direction = -1) or "
|
||||
"(change_type = 'adjust' and direction in (1, -1)))",
|
||||
name="ck_points_ledger_direction_by_change_type",
|
||||
),
|
||||
CheckConstraint(
|
||||
"jsonb_typeof(metadata) = 'object'",
|
||||
name="ck_points_ledger_metadata_object",
|
||||
),
|
||||
CheckConstraint(
|
||||
"metadata->>'schema_version' = '1' and "
|
||||
"metadata->>'operator_type' in ('user', 'system', 'admin') and "
|
||||
"coalesce(metadata->>'run_id', '') <> '' and "
|
||||
"(not (metadata ? 'ext') or jsonb_typeof(metadata->'ext') = 'object')",
|
||||
name="ck_points_ledger_metadata_common",
|
||||
),
|
||||
CheckConstraint(
|
||||
"(change_type <> 'register' or not (metadata ? 'charge'))",
|
||||
name="ck_points_ledger_metadata_register_shape",
|
||||
),
|
||||
CheckConstraint(
|
||||
"(change_type <> 'consume' or ("
|
||||
"(metadata ? 'charge') and jsonb_typeof(metadata->'charge') = 'object' and "
|
||||
"(metadata->'charge' ? 'message_id') and (metadata->'charge' ? 'message_seq') and "
|
||||
"(metadata->'charge' ? 'model_code') and (metadata->'charge' ? 'input_tokens') and "
|
||||
"(metadata->'charge' ? 'output_tokens') and (metadata->'charge' ? 'cost')))",
|
||||
name="ck_points_ledger_metadata_consume_shape",
|
||||
),
|
||||
CheckConstraint(
|
||||
"(change_type <> 'adjust' or ("
|
||||
"(metadata ? 'ext') and (metadata->'ext' ? 'ticket_id') and "
|
||||
"coalesce(metadata #>> '{ext,ticket_id}', '') <> ''))",
|
||||
name="ck_points_ledger_metadata_adjust_shape",
|
||||
),
|
||||
UniqueConstraint("user_id", "event_id", name="uq_points_ledger_user_event"),
|
||||
Index("ix_points_ledger_user_created_at", "user_id", text("created_at DESC")),
|
||||
Index("ix_points_ledger_biz_type_biz_id", "biz_type", "biz_id"),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
default=uuid.uuid4,
|
||||
)
|
||||
user_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
nullable=False,
|
||||
)
|
||||
direction: Mapped[int] = mapped_column(SmallInteger, nullable=False)
|
||||
amount: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||||
balance_after: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||||
change_type: Mapped[str] = mapped_column(String(16), nullable=False)
|
||||
biz_type: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
||||
biz_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
nullable=True,
|
||||
)
|
||||
event_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
operator_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
nullable=True,
|
||||
)
|
||||
metadata_json: Mapped[dict[str, object]] = mapped_column(
|
||||
"metadata",
|
||||
JSON().with_variant(JSONB, "postgresql"),
|
||||
nullable=False,
|
||||
server_default=text("'{}'::jsonb"),
|
||||
default=dict,
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import CheckConstraint, ForeignKey, Index, JSON, String, Text, text
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||
|
||||
|
||||
class Profile(TimestampMixin, SoftDeleteMixin, Base):
|
||||
__tablename__ = "profiles"
|
||||
__table_args__ = (
|
||||
CheckConstraint(
|
||||
"char_length(username) >= 1", name="ck_profiles_username_non_empty"
|
||||
),
|
||||
Index("ix_profiles_username", "username"),
|
||||
Index("ix_profiles_settings_gin", "settings", postgresql_using="gin"),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("auth.users.id", ondelete="CASCADE"),
|
||||
primary_key=True,
|
||||
)
|
||||
username: Mapped[str] = mapped_column(String(30), nullable=False)
|
||||
avatar_url: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
bio: Mapped[str | None] = mapped_column(String(200), nullable=True)
|
||||
settings: Mapped[dict[str, object]] = mapped_column(
|
||||
JSON().with_variant(JSONB, "postgresql"),
|
||||
nullable=False,
|
||||
server_default=text("'{}'::jsonb"),
|
||||
default=dict,
|
||||
)
|
||||
@@ -0,0 +1,58 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import BigInteger, CheckConstraint, Integer, text
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, TimestampMixin
|
||||
|
||||
|
||||
class UserPoints(TimestampMixin, Base):
|
||||
__tablename__ = "user_points"
|
||||
__table_args__ = (
|
||||
CheckConstraint("balance >= 0", name="ck_user_points_balance_non_negative"),
|
||||
CheckConstraint(
|
||||
"frozen_balance >= 0", name="ck_user_points_frozen_balance_non_negative"
|
||||
),
|
||||
CheckConstraint(
|
||||
"lifetime_earned >= 0", name="ck_user_points_lifetime_earned_non_negative"
|
||||
),
|
||||
CheckConstraint(
|
||||
"lifetime_spent >= 0", name="ck_user_points_lifetime_spent_non_negative"
|
||||
),
|
||||
CheckConstraint(
|
||||
"frozen_balance <= balance", name="ck_user_points_frozen_le_balance"
|
||||
),
|
||||
)
|
||||
|
||||
user_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
)
|
||||
balance: Mapped[int] = mapped_column(
|
||||
BigInteger,
|
||||
nullable=False,
|
||||
server_default=text("0"),
|
||||
)
|
||||
frozen_balance: Mapped[int] = mapped_column(
|
||||
BigInteger,
|
||||
nullable=False,
|
||||
server_default=text("0"),
|
||||
)
|
||||
lifetime_earned: Mapped[int] = mapped_column(
|
||||
BigInteger,
|
||||
nullable=False,
|
||||
server_default=text("0"),
|
||||
)
|
||||
lifetime_spent: Mapped[int] = mapped_column(
|
||||
BigInteger,
|
||||
nullable=False,
|
||||
server_default=text("0"),
|
||||
)
|
||||
version: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
server_default=text("0"),
|
||||
)
|
||||
Reference in New Issue
Block a user