Python/agent/application/states/database.py

229 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
数据库状态
"""
from datetime import datetime
from typing import Dict, List, cast, TypeAlias
from pydantic import BaseModel, Field, 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, Dialog, RunResult
class ConversationMemory(BaseModel):
"""
会话内存模型
"""
description: str = Field(default="新会话", description="会话描述")
is_running: bool = Field(
default=False, description="运行状态True 表示运行中False 表示运行完成"
)
dialogs: Dict[str, DialogMemory] = Field(
default_factory=dict, description="对话字典"
)
created_at: datetime = Field(default_factory=datetime.now, description="创建时间")
class DialogMemory(BaseModel):
"""
对话内存模型
"""
question: str = Field(..., description="问题")
is_thinking: bool = Field(
default=False, description="思考状态True 表示思考中False 表示思考完成"
)
is_collapse_expanded: bool = Field(
default=False, description="思考折叠面板展开状态True 表示展开False 表示折叠"
)
thought_nodes: list[ThoughtNodeMemory] = Field(
default_factory=list, description="思考节点列表"
)
answer: str = Field(default="", description="回答")
class ThoughtNodeMemory(BaseModel):
"""
思考节点内存模型
"""
kind: str
content: str
# 会话字典类型别名
Conversations: TypeAlias = Dict[str, ConversationMemory]
# 思考节点内存模型类型适配器
ThoughtNodeMemoryTypeAdapter = TypeAdapter(list[ThoughtNodeMemory])
class DatabaseState(rx.State):
"""
数据库状态
"""
async def get_conversations(self, user_id: str) -> Conversations:
"""
获取会话字典
:param user_id: 用户唯一标识
:return: 会话字典
"""
conversations: Conversations = {}
async with rx.asession() as session:
result = await session.exec(
select(Conversation, Dialog)
.outerjoin(
Dialog,
cast(
ColumnElement[bool],
Conversation.id == Dialog.conversation_id,
),
)
.where(
Conversation.user_id == user_id,
Conversation.is_deleted == False,
)
.order_by(Conversation.id, Dialog.id)
)
for conversation, dialog in result.all():
conversation_memory = conversations.setdefault(
conversation.id,
ConversationMemory(
description=conversation.description,
created_at=conversation.created_at,
),
)
if not dialog:
continue
conversation_memory.dialogs[dialog.id] = DialogMemory(
question=dialog.question,
thought_nodes=ThoughtNodeMemoryTypeAdapter.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 = Conversation(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(Conversation, 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 = Dialog(
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: list[ThoughtNodeMemory],
answer: str,
) -> None:
"""
完成对话
:param dialog_id: 对话唯一标识
:param thought_nodes: 思考节点列表
:param answer: 回答
:return: None
"""
async with rx.asession() as session:
dialog = await session.get(Dialog, dialog_id)
if not dialog:
return
dialog.thought_nodes = ThoughtNodeMemoryTypeAdapter.dump_python(
thought_nodes
)
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(RunResult)
.where(RunResult.conversation_id == conversation_id)
.order_by(desc(RunResult.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(
RunResult(
conversation_id=conversation_id,
dialog_id=dialog_id,
new_messages=ModelMessagesTypeAdapter.dump_json(
new_messages
).decode(
"utf-8"
), # 序列化为 JSON 字符串
)
)
await session.commit()