Python/agent/application/states/database.py

190 lines
6.1 KiB
Python

# -*- coding: utf-8 -*-
"""
数据库状态
"""
from datetime import datetime
from typing import Dict, List, cast
from pydantic import TypeAdapter
from pydantic_ai import ModelMessage, ModelMessagesTypeAdapter
import reflex as rx
from sqlalchemy import ColumnElement, desc
from sqlmodel import select
from application.models import (
Conversation,
Conversations,
Dialog,
Dialogs,
RunResults,
ThoughtNode,
)
# 思考节点内存模型类型适配器
ThoughtNodeTypeAdapter = TypeAdapter(dict[int, ThoughtNode])
class DatabaseState(rx.State):
"""
数据库状态
"""
async def get_conversations(self, user_id: str) -> Dict[str, Conversation]:
"""
获取会话字典
:param user_id: 用户唯一标识
:return: 会话字典
"""
conversations: Dict[str, Conversation] = {}
async with rx.asession() as session:
result = await session.exec(
select(Conversations, Dialogs)
.outerjoin(
Dialogs,
cast(
ColumnElement[bool],
Conversations.id == Dialogs.conversation_id,
),
)
.where(
Conversations.user_id == user_id,
Conversations.is_deleted == False,
)
.order_by(Conversations.id, Dialogs.id)
)
for conversation, dialog in result.all():
conversation_memory = conversations.setdefault(
conversation.id,
Conversation(
description=conversation.description,
created_at=conversation.created_at,
),
)
if not dialog:
continue
conversation_memory.dialogs[dialog.id] = Dialog(
question=dialog.question,
thought_nodes=ThoughtNodeTypeAdapter.validate_python(
dialog.thought_nodes
), # 反序列化为思考节点内存实例字典
answer=dialog.answer,
)
return conversations
async def create_conversation(
self, user_id: str, description: str = "新会话"
) -> tuple[str, datetime]:
"""
新建会话
:param user_id: 用户唯一标识
:param description: 会话描述
:return: 新建会话唯一标识、创建时间
"""
async with rx.asession() as session:
conversation = Conversations(user_id=user_id, description=description)
session.add(conversation)
await session.commit()
await session.refresh(conversation)
return conversation.id, conversation.created_at
async def delete_conversation(self, conversation_id: str) -> None:
"""
删除指定会话(逻辑删除)
:param conversation_id: 指定会话唯一标识
:return: None
"""
async with rx.asession() as session:
conversation = await session.get(Conversations, conversation_id)
if not conversation:
return
conversation.is_deleted = True
await session.commit()
async def create_dialog(self, conversation_id: str, question: str) -> str:
"""
新建对话
:param conversation_id: 会话唯一标识
:param question: 提问
:return: 新建对话唯一标识
"""
async with rx.asession() as session:
dialog = Dialogs(
conversation_id=conversation_id,
question=question,
)
session.add(dialog)
await session.commit()
await session.refresh(dialog)
return dialog.id
async def complete_dialog(
self,
dialog_id: str,
thought_nodes: dict[int, ThoughtNode],
answer: str,
) -> None:
"""
完成对话
:param dialog_id: 对话唯一标识
:param thought_nodes: 思考节点列表
:param answer: 回答
:return: None
"""
async with rx.asession() as session:
dialog = await session.get(Dialogs, dialog_id)
if not dialog:
return
dialog.thought_nodes = ThoughtNodeTypeAdapter.dump_python(
{k: v for k, v in thought_nodes.items()}
)
dialog.answer = answer
await session.commit()
async def get_message_history(self, conversation_id: str) -> List[ModelMessage]:
"""
获取消息历史
:param conversation_id: 会话唯一标识
:return: 消息历史
"""
message_history: List[ModelMessage] = []
async with rx.asession() as session:
result = await session.exec(
select(RunResults)
.where(RunResults.conversation_id == conversation_id)
.order_by(desc(RunResults.dialog_id))
)
for record in result.all():
message_history.extend(
ModelMessagesTypeAdapter.validate_json(record.new_messages)
)
return message_history
async def save_new_messages(
self,
conversation_id: str,
dialog_id: str,
new_messages: List[ModelMessage],
) -> None:
"""
保存新增消息
:param conversation_id: 会话唯一标识
:param dialog_id: 对话唯一标识
:param new_messages: 新增消息
:return: None
"""
async with rx.asession() as session:
session.add(
RunResults(
conversation_id=conversation_id,
dialog_id=dialog_id,
new_messages=ModelMessagesTypeAdapter.dump_json(
new_messages
).decode(
"utf-8"
), # 序列化为 JSON 字符串
)
)
await session.commit()