from typing import Optional from sqlalchemy import Column, Integer, String, JSON, Index, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Session from app.db import Base, async_db_query, db_query, get_id_column class AgentChat(Base): """ Agent 会话历史表。 """ id = get_id_column() # Agent 内部会话 ID,用于恢复 LangGraph 对话上下文 session_id = Column(String, nullable=False) # 前端或渠道侧传入的原始会话标识 client_session_id = Column(String) # 用户 ID user_id = Column(String) # 用户名称 username = Column(String) # 消息渠道 channel = Column(String) # 渠道来源配置名 source = Column(String) # 原聊天 ID,用于区分群聊、频道或私聊 original_chat_id = Column(String) # 会话标题 title = Column(String) # 会话预览文本 preview = Column(String) # 原始 LangChain messages,用于继续会话 agent_messages = Column(JSON) # 展示给用户的消息记录,包含文字、工具提示、附件与选择卡片 display_messages = Column(JSON) # 展示消息数量 message_count = Column(Integer, default=0) # 创建时间 created_at = Column(String) # 更新时间 updated_at = Column(String) __table_args__ = ( Index("ix_agentchat_session_user", "session_id", "user_id"), Index("ix_agentchat_user_updated", "user_id", "updated_at", "id"), Index("ix_agentchat_channel_updated", "channel", "updated_at", "id"), ) @classmethod @db_query def get_by_session( cls, db: Session, session_id: str, user_id: Optional[str] = None ) -> Optional["AgentChat"]: """ 根据会话 ID 获取 Agent 会话。 """ query = db.query(cls).filter(cls.session_id == session_id) if user_id is not None: query = query.filter(cls.user_id == user_id) return query.order_by(cls.id.desc()).first() @classmethod @async_db_query async def async_get_by_session( cls, db: AsyncSession, session_id: str, user_id: Optional[str] = None ) -> Optional["AgentChat"]: """ 异步根据会话 ID 获取 Agent 会话。 """ statement = select(cls).where(cls.session_id == session_id) if user_id is not None: statement = statement.where(cls.user_id == user_id) result = await db.execute(statement.order_by(cls.id.desc())) return result.scalars().first() @classmethod @db_query def list_by_page( cls, db: Session, page: Optional[int] = 1, count: Optional[int] = 30, user_id: Optional[str] = None, username: Optional[str] = None, ) -> list["AgentChat"]: """ 分页获取 Agent 会话历史。 """ query = db.query(cls) if user_id is not None and username is not None: query = query.filter((cls.user_id == user_id) | (cls.username == username)) elif user_id is not None: query = query.filter(cls.user_id == user_id) elif username is not None: query = query.filter(cls.username == username) return ( query.order_by(cls.updated_at.desc(), cls.id.desc()) .offset((page - 1) * count) .limit(count) .all() ) @classmethod @async_db_query async def async_list_by_page( cls, db: AsyncSession, page: Optional[int] = 1, count: Optional[int] = 30, user_id: Optional[str] = None, username: Optional[str] = None, ) -> list["AgentChat"]: """ 异步分页获取 Agent 会话历史。 """ statement = select(cls) if user_id is not None and username is not None: statement = statement.where((cls.user_id == user_id) | (cls.username == username)) elif user_id is not None: statement = statement.where(cls.user_id == user_id) elif username is not None: statement = statement.where(cls.username == username) result = await db.execute( statement.order_by(cls.updated_at.desc(), cls.id.desc()) .offset((page - 1) * count) .limit(count) ) return result.scalars().all()