feat(storage): implement unified persistence layer with database models and repositories

This commit is contained in:
rayhpeng
2026-04-20 17:04:20 +08:00
parent 3a99c4e81c
commit 38a6ec496f
39 changed files with 1924 additions and 8 deletions
@@ -0,0 +1,6 @@
from store.repositories.models.feedback import Feedback
from store.repositories.models.run import Run
from store.repositories.models.run_event import RunEvent
from store.repositories.models.thread_meta import ThreadMeta
__all__ = ["Feedback", "Run", "RunEvent", "ThreadMeta"]
@@ -0,0 +1,36 @@
from __future__ import annotations
from datetime import datetime
from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from store.persistence.base_model import DataClassBase, TimeZone, UniversalText, id_key
from store.utils import get_timezone
_tz = get_timezone()
class Feedback(DataClassBase):
"""Feedback table (create-only, no updated_time)."""
__tablename__ = "feedback"
id: Mapped[id_key] = mapped_column(init=False)
feedback_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
run_id: Mapped[str] = mapped_column(String(64), index=True)
thread_id: Mapped[str] = mapped_column(String(64), index=True)
rating: Mapped[int] = mapped_column(Integer)
user_id: Mapped[str | None] = mapped_column(String(64), default=None, index=True)
message_id: Mapped[str | None] = mapped_column(String(64), default=None)
comment: Mapped[str | None] = mapped_column(UniversalText, default=None)
created_time: Mapped[datetime] = mapped_column(
TimeZone,
init=False,
default_factory=_tz.now,
sort_order=999,
comment="Created at",
)
@@ -0,0 +1,42 @@
from __future__ import annotations
from typing import Any
from sqlalchemy import JSON, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from store.persistence.base_model import Base, UniversalText, id_key
class Run(Base):
"""Run metadata table."""
__tablename__ = "runs"
id: Mapped[id_key] = mapped_column(init=False)
run_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
thread_id: Mapped[str] = mapped_column(String(64), index=True)
assistant_id: Mapped[str | None] = mapped_column(String(64), default=None)
user_id: Mapped[str | None] = mapped_column(String(64), default=None, index=True)
status: Mapped[str] = mapped_column(String(32), default="pending", index=True)
model_name: Mapped[str | None] = mapped_column(String(128), default=None)
multitask_strategy: Mapped[str] = mapped_column(String(32), default="reject")
error: Mapped[str | None] = mapped_column(UniversalText, default=None)
follow_up_to_run_id: Mapped[str | None] = mapped_column(String(64), default=None)
meta: Mapped[dict[str, Any]] = mapped_column("metadata", JSON, default_factory=dict)
kwargs: Mapped[dict[str, Any]] = mapped_column(JSON, default_factory=dict)
total_input_tokens: Mapped[int] = mapped_column(Integer, default=0)
total_output_tokens: Mapped[int] = mapped_column(Integer, default=0)
total_tokens: Mapped[int] = mapped_column(Integer, default=0)
llm_call_count: Mapped[int] = mapped_column(Integer, default=0)
lead_agent_tokens: Mapped[int] = mapped_column(Integer, default=0)
subagent_tokens: Mapped[int] = mapped_column(Integer, default=0)
middleware_tokens: Mapped[int] = mapped_column(Integer, default=0)
message_count: Mapped[int] = mapped_column(Integer, default=0)
first_human_message: Mapped[str | None] = mapped_column(UniversalText, default=None)
last_ai_message: Mapped[str | None] = mapped_column(UniversalText, default=None)
@@ -0,0 +1,40 @@
from __future__ import annotations
from datetime import datetime
from typing import Any
from sqlalchemy import JSON, Integer, String, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column
from store.persistence.base_model import DataClassBase, TimeZone, UniversalText, id_key
from store.utils import get_timezone
_tz = get_timezone()
class RunEvent(DataClassBase):
"""Run event table."""
__tablename__ = "run_events"
__table_args__ = (
UniqueConstraint("thread_id", "seq", name="uq_run_events_thread_seq"),
{"comment": "Run event table."},
)
id: Mapped[id_key] = mapped_column(init=False)
thread_id: Mapped[str] = mapped_column(String(64), index=True)
run_id: Mapped[str] = mapped_column(String(64), index=True)
seq: Mapped[int] = mapped_column(Integer, index=True)
event_type: Mapped[str] = mapped_column(String(128), index=True)
category: Mapped[str] = mapped_column(String(64), index=True)
content: Mapped[str] = mapped_column(UniversalText, default="")
meta: Mapped[dict[str, Any]] = mapped_column("metadata", JSON, default_factory=dict)
created_at: Mapped[datetime] = mapped_column(
TimeZone,
init=False,
default_factory=_tz.now,
sort_order=999,
comment="Event timestamp",
)
@@ -0,0 +1,25 @@
from __future__ import annotations
from typing import Any
from sqlalchemy import JSON, String
from sqlalchemy.orm import Mapped, mapped_column
from store.persistence.base_model import Base, id_key
class ThreadMeta(Base):
"""Thread metadata table."""
__tablename__ = "thread_meta"
id: Mapped[id_key] = mapped_column(init=False)
thread_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
assistant_id: Mapped[str | None] = mapped_column(String(64), default=None)
user_id: Mapped[str | None] = mapped_column(String(64), default=None, index=True)
display_name: Mapped[str | None] = mapped_column(String(255), default=None)
status: Mapped[str] = mapped_column(String(32), default="idle", index=True)
meta: Mapped[dict[str, Any]] = mapped_column("metadata", JSON, default_factory=dict)