This commit is contained in:
parent
942c1bea71
commit
a7fead671a
|
|
@ -6,9 +6,8 @@ import reflex as rx
|
|||
from typing import Tuple
|
||||
|
||||
from application.states.models import (
|
||||
Conversation,
|
||||
Dialog,
|
||||
Thought,
|
||||
MessageHistoryItem,
|
||||
MessageType,
|
||||
ConversationHistoryItem,
|
||||
)
|
||||
from application.states import ConversationState, AuthState
|
||||
|
|
@ -32,7 +31,7 @@ def conversation_history_item(
|
|||
return rx.list.item(
|
||||
rx.vstack(
|
||||
rx.hstack(
|
||||
# 对话描述
|
||||
# 会话历史项的会话描述
|
||||
rx.text(
|
||||
item.description,
|
||||
flex=1,
|
||||
|
|
@ -51,7 +50,7 @@ def conversation_history_item(
|
|||
),
|
||||
rx.hstack(
|
||||
rx.spacer(),
|
||||
# 创建时间
|
||||
# 会话历史项的会话创建日期时间
|
||||
rx.text(
|
||||
item.created_at,
|
||||
line_height="20px",
|
||||
|
|
@ -68,7 +67,7 @@ def conversation_history_item(
|
|||
on_click=lambda: ConversationState.set_actived_conversation(item.id),
|
||||
width="100%",
|
||||
),
|
||||
# 更多按钮:点击更多按钮显示气泡卡片,可删除会话
|
||||
# 会话历史项的更多按钮
|
||||
rx.box(
|
||||
rx.popover.root(
|
||||
rx.popover.trigger(
|
||||
|
|
@ -143,9 +142,9 @@ def conversation_history_item(
|
|||
z_index="99",
|
||||
min_width="14px",
|
||||
cursor="pointer",
|
||||
opacity=rx.cond(highlight, "1", "0"),
|
||||
opacity=rx.cond(is_highlight, "1", "0"),
|
||||
transition="opacity 0.18s ease-in-out",
|
||||
pointer_events=rx.cond(highlight, "auto", "none"),
|
||||
pointer_events=rx.cond(is_highlight, "auto", "none"),
|
||||
),
|
||||
display="flex",
|
||||
position="relative",
|
||||
|
|
@ -154,12 +153,12 @@ def conversation_history_item(
|
|||
padding="16px",
|
||||
margin_bottom="8px",
|
||||
background=rx.cond(
|
||||
highlight,
|
||||
is_highlight,
|
||||
"var(--prismui-background-3)",
|
||||
"var(--prismui-background-color-1)",
|
||||
),
|
||||
border_radius="var(--prismui-border-radius-3)",
|
||||
box_shadow=rx.cond(highlight, "var(--prismui-box-shadow-7)", "none"),
|
||||
box_shadow=rx.cond(is_highlight, "var(--prismui-box-shadow-7)", "none"),
|
||||
cursor="pointer",
|
||||
style={
|
||||
"&:hover": {
|
||||
|
|
@ -175,16 +174,16 @@ def conversation_history_item(
|
|||
|
||||
|
||||
def conversation_history(
|
||||
is_shown: bool,
|
||||
is_conversation_history_shown: bool,
|
||||
) -> rx.Component:
|
||||
"""
|
||||
会话历史
|
||||
:param is_shown: 展示 / 隐藏
|
||||
:param is_conversation_history_shown: 展示 / 隐藏
|
||||
:return: Component
|
||||
"""
|
||||
return rx.box(
|
||||
rx.vstack(
|
||||
# 标题
|
||||
# 会话历史的标题
|
||||
rx.hstack(
|
||||
rx.text(
|
||||
"会话历史",
|
||||
|
|
@ -219,9 +218,9 @@ def conversation_history(
|
|||
backdrop_filter="blur(50px)",
|
||||
gap="12px",
|
||||
),
|
||||
width=rx.cond(is_shown, "25%", "0px"),
|
||||
min_width=rx.cond(is_shown, "240px", "0px"),
|
||||
max_width=rx.cond(is_shown, "380px", "0px"),
|
||||
width=rx.cond(is_conversation_history_shown, "25%", "0px"),
|
||||
min_width=rx.cond(is_conversation_history_shown, "240px", "0px"),
|
||||
max_width=rx.cond(is_conversation_history_shown, "380px", "0px"),
|
||||
height="100%",
|
||||
transition="all 0.18s ease-in-out",
|
||||
overflow="hidden",
|
||||
|
|
@ -334,55 +333,16 @@ def guidance() -> rx.Component:
|
|||
)
|
||||
|
||||
|
||||
def thought_showing(item: Tuple[int, Thought]):
|
||||
def user_prompt(item: MessageHistoryItem) -> rx.Component:
|
||||
"""
|
||||
思考展示
|
||||
:param item: 思考实例
|
||||
用户提示词
|
||||
:return: Component
|
||||
"""
|
||||
# 思考实例
|
||||
thought = item[1]
|
||||
return rx.vstack(
|
||||
rx.match(
|
||||
thought.type,
|
||||
(
|
||||
"thinking",
|
||||
rx.text(
|
||||
thought.content,
|
||||
line_height="1.6",
|
||||
font_size="var(--prismui-font-size-1)",
|
||||
color="var(--prismui-color-3)",
|
||||
),
|
||||
),
|
||||
rx.fragment(),
|
||||
),
|
||||
align_items="flex-start",
|
||||
width="100%",
|
||||
)
|
||||
|
||||
|
||||
def message_history_item(item: Tuple[str, Dialog]) -> rx.Component:
|
||||
"""
|
||||
消息历史项
|
||||
:param item: 对话实例
|
||||
:return: Component
|
||||
"""
|
||||
# 对话实例
|
||||
dialog = item[1]
|
||||
# 推理状态
|
||||
is_thinking = dialog.is_thinking
|
||||
# 思考折叠面板展开状态
|
||||
is_expanded = dialog.is_expanded
|
||||
|
||||
return rx.vstack(
|
||||
# 用户提示词
|
||||
rx.cond(
|
||||
dialog.user_prompt,
|
||||
rx.hstack(
|
||||
return rx.hstack(
|
||||
rx.spacer(),
|
||||
rx.vstack(
|
||||
rx.text(
|
||||
dialog.user_prompt,
|
||||
item.content,
|
||||
max_width="600px",
|
||||
padding="12px 16px",
|
||||
background_color="var(--prismui-background-color-3)",
|
||||
|
|
@ -418,18 +378,20 @@ def message_history_item(item: Tuple[str, Dialog]) -> rx.Component:
|
|||
gap="4px",
|
||||
width="100%",
|
||||
margin_top="8px",
|
||||
),
|
||||
rx.fragment(),
|
||||
),
|
||||
# 思考折叠面板
|
||||
rx.cond(
|
||||
dialog.thoughts,
|
||||
rx.vstack(
|
||||
# 标题
|
||||
)
|
||||
|
||||
|
||||
def thinking(item: MessageHistoryItem) -> rx.Component:
|
||||
"""
|
||||
思考
|
||||
:return: Component
|
||||
"""
|
||||
return rx.vstack(
|
||||
# 思考的标题栏
|
||||
rx.box(
|
||||
rx.hstack(
|
||||
rx.text(
|
||||
rx.cond(is_thinking, "思考中", "思考完成"),
|
||||
item.title,
|
||||
font_size="var(--prismui-font-size-1)",
|
||||
color="var(--prismui-color-3)",
|
||||
),
|
||||
|
|
@ -438,9 +400,7 @@ def message_history_item(item: Tuple[str, Dialog]) -> rx.Component:
|
|||
width="14px",
|
||||
height="14px",
|
||||
color="var(--prismui-color-3)",
|
||||
transform=rx.cond(
|
||||
is_expanded, "rotate(90deg)", "rotate(0deg)"
|
||||
),
|
||||
transform=rx.cond(item.is_shown, "rotate(90deg)", "rotate(0deg)"),
|
||||
transition="transform 0.18s ease-in-out",
|
||||
),
|
||||
align_items="center",
|
||||
|
|
@ -448,54 +408,30 @@ def message_history_item(item: Tuple[str, Dialog]) -> rx.Component:
|
|||
margin_bottom="8px",
|
||||
line_height="22px",
|
||||
),
|
||||
# 点击事件,展开/折叠思考折叠面板
|
||||
on_click=lambda: ConversationState.toggle_collapse(dialog.id),
|
||||
# 点击事件,展示 / 隐藏消息历史项
|
||||
on_click=lambda: ConversationState.toggle_message_history_item_shown(
|
||||
item.id
|
||||
),
|
||||
cursor="pointer",
|
||||
),
|
||||
# 思考
|
||||
rx.box(
|
||||
rx.box(
|
||||
rx.auto_scroll(
|
||||
rx.foreach(
|
||||
dialog.thoughts,
|
||||
thought_showing,
|
||||
),
|
||||
width="100%",
|
||||
align_self="start",
|
||||
),
|
||||
style={"overflow": "hidden", "min-height": "0"},
|
||||
),
|
||||
display="grid",
|
||||
grid_template_rows=rx.cond(is_expanded, "1fr", "0fr"),
|
||||
width="100%",
|
||||
opacity=rx.cond(is_expanded, "1", "0"),
|
||||
overflow="hidden",
|
||||
transition="grid-template-rows 0.18s ease-in-out, opacity 0.18s ease-in-out",
|
||||
# 思考的内容
|
||||
rx.text(
|
||||
item.content,
|
||||
line_height="1.6",
|
||||
font_size="var(--prismui-font-size-1)",
|
||||
color="var(--prismui-color-3)",
|
||||
),
|
||||
margin_bottom="8px",
|
||||
),
|
||||
# 若思考列表为空且正在运行则显示加载动效
|
||||
rx.cond(
|
||||
ConversationState.running_status,
|
||||
rx.box(
|
||||
rx.box(
|
||||
class_name="loadin",
|
||||
flex_shrink="0",
|
||||
),
|
||||
display="flex",
|
||||
justify_content="center",
|
||||
align_items="center",
|
||||
width="32px",
|
||||
height="18px",
|
||||
margin_bottom="8px",
|
||||
overflow="hidden",
|
||||
),
|
||||
rx.fragment(),
|
||||
),
|
||||
),
|
||||
# 结果输出
|
||||
rx.markdown(
|
||||
dialog.result_output,
|
||||
)
|
||||
|
||||
|
||||
def result_output(item: MessageHistoryItem) -> rx.Component:
|
||||
"""
|
||||
结果输出
|
||||
:return: Component
|
||||
"""
|
||||
return rx.markdown(
|
||||
item.content,
|
||||
component_map={
|
||||
"p": lambda text: rx.text(
|
||||
text,
|
||||
|
|
@ -606,10 +542,42 @@ def message_history_item(item: Tuple[str, Dialog]) -> rx.Component:
|
|||
),
|
||||
},
|
||||
width="100%",
|
||||
)
|
||||
|
||||
|
||||
def message_history_item(item: MessageHistoryItem) -> rx.Component:
|
||||
"""
|
||||
消息历史项
|
||||
:param item: 对话实例
|
||||
:return: Component
|
||||
"""
|
||||
|
||||
return rx.vstack(
|
||||
# 若等待流式输出则显示加载动效,否则根据消息类型渲染组件
|
||||
rx.cond(
|
||||
ConversationState.awaiting_stream,
|
||||
rx.box(
|
||||
rx.box(
|
||||
class_name="loadin",
|
||||
flex_shrink="0",
|
||||
),
|
||||
display="flex",
|
||||
justify_content="center",
|
||||
align_items="center",
|
||||
width="32px",
|
||||
height="18px",
|
||||
margin_bottom="8px",
|
||||
overflow="hidden",
|
||||
),
|
||||
rx.match(
|
||||
item.type,
|
||||
(MessageType.USER_PROMPT, user_prompt(item)),
|
||||
(MessageType.THINKING, thinking(item)),
|
||||
(MessageType.RESULT_OUTPUT, result_output(item)),
|
||||
),
|
||||
),
|
||||
padding="0 16px",
|
||||
width="100%",
|
||||
key=dialog.id,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -618,17 +586,15 @@ def message_history() -> rx.Component:
|
|||
消息历史
|
||||
:return: Component
|
||||
"""
|
||||
# 消息历史
|
||||
message_history = ConversationState.message_history
|
||||
return rx.box(
|
||||
# 若消息历史为空则显示引导,否则渲染消息历史项
|
||||
rx.cond(
|
||||
message_history.length() == 0,
|
||||
ConversationState.message_history.length() == 0,
|
||||
guidance(),
|
||||
rx.vstack(
|
||||
rx.auto_scroll(
|
||||
rx.foreach(
|
||||
message_history,
|
||||
ConversationState.message_history,
|
||||
message_history_item,
|
||||
),
|
||||
width="100%",
|
||||
|
|
@ -765,18 +731,19 @@ def user_prompt_sending() -> rx.Component:
|
|||
line_height="21px",
|
||||
color="var(--prismui-color-9)",
|
||||
),
|
||||
loading=ConversationState.running_status,
|
||||
# 是否禁用绑定用户提示词发送禁用状态
|
||||
disabled=ConversationState.is_user_prompt_sending_disabled,
|
||||
# 点击事件:发送用户提示词
|
||||
on_click=ConversationState.handle_user_prompt,
|
||||
# 加载状态:绑定当前会话的运行状态
|
||||
loading=ConversationState.is_running,
|
||||
# 不可点击状态:绑定用户提示词发送按钮不可点击状态
|
||||
disabled=ConversationState.is_user_prompt_sending_button_disabled,
|
||||
# 点击事件:运行
|
||||
on_click=ConversationState.run,
|
||||
display="inline-flex",
|
||||
position="relative",
|
||||
justify_content="center",
|
||||
align_items="center",
|
||||
padding="0 12px",
|
||||
background_color=rx.cond(
|
||||
ConversationState.is_user_prompt_sending_disabled,
|
||||
ConversationState.is_user_prompt_sending_button_disabled,
|
||||
"var(--prismui-color-11)",
|
||||
"var(--prismui-color-5)",
|
||||
),
|
||||
|
|
@ -787,7 +754,7 @@ def user_prompt_sending() -> rx.Component:
|
|||
white_space="nowrap",
|
||||
overflow="hidden",
|
||||
cursor=rx.cond(
|
||||
ConversationState.is_user_prompt_sending_disabled,
|
||||
ConversationState.is_user_prompt_sending_button_disabled,
|
||||
"not-allowed",
|
||||
"pointer",
|
||||
),
|
||||
|
|
@ -828,11 +795,11 @@ def user_prompt_sending() -> rx.Component:
|
|||
)
|
||||
|
||||
|
||||
def conversation_history_collapse_button(
|
||||
def conversation_history_toggle_button(
|
||||
is_conversation_history_shown: bool,
|
||||
) -> rx.Component:
|
||||
"""
|
||||
会话历史折叠面板按钮
|
||||
会话历史显示 / 隐藏按钮
|
||||
:param is_conversation_history_shown: 会话历史展示状态
|
||||
:return: Component
|
||||
"""
|
||||
|
|
@ -870,13 +837,10 @@ def conversation_page() -> rx.Component:
|
|||
"""
|
||||
会话页面:布局参考 MetaChat,从左到右分别为会话历史和工作区。其中,工作区从上到下分别为消息历史和用户提示词输入框。
|
||||
"""
|
||||
# 会话历史展示状态
|
||||
is_shown = ConversationState.is_conversation_history_shown
|
||||
|
||||
return rx.box(
|
||||
rx.hstack(
|
||||
# 会话历史
|
||||
conversation_history(is_shown),
|
||||
conversation_history(ConversationState.is_conversation_history_shown),
|
||||
# 工作区
|
||||
rx.box(
|
||||
# 对话历史
|
||||
|
|
@ -905,8 +869,10 @@ def conversation_page() -> rx.Component:
|
|||
overflow="hidden",
|
||||
transition="all 0.18s ease-in-out",
|
||||
),
|
||||
# 会话历史折叠面板按钮
|
||||
conversation_history_collapse_button(is_shown),
|
||||
# 会话历史显示 / 隐藏按钮
|
||||
conversation_history_toggle_button(
|
||||
ConversationState.is_conversation_history_shown
|
||||
),
|
||||
# 挂载事件:恢复会话状态
|
||||
on_mount=AuthState.resume_conversation_state,
|
||||
position="relative",
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ def nav_button(
|
|||
is_actived = nav_button == AuthState.activated_nav_button
|
||||
|
||||
return rx.vstack(
|
||||
# 图标
|
||||
# 导航按钮的图标
|
||||
rx.image(
|
||||
nav_buttons[nav_button]["src"],
|
||||
width="36px",
|
||||
|
|
@ -47,7 +47,7 @@ def nav_button(
|
|||
border_radius="var(--prismui-border-radius-3)",
|
||||
box_shadow=rx.cond(is_actived, "var(--prismui-box-shadow-2)", "none"),
|
||||
),
|
||||
# 标签
|
||||
# 导航按钮的文本
|
||||
rx.text(
|
||||
nav_buttons[nav_button]["text"],
|
||||
line_height="20px",
|
||||
|
|
@ -57,7 +57,7 @@ def nav_button(
|
|||
align_items="center",
|
||||
gap="4px",
|
||||
cursor="pointer",
|
||||
# 点击事件:将指定导航按钮设置为激活的导航按钮
|
||||
# 点击事件:设置激活导航按钮
|
||||
on_click=AuthState.set_activated_nav_button(nav_button),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ from application.states.models import (
|
|||
Conversation,
|
||||
Message,
|
||||
MessageType,
|
||||
Work,
|
||||
WorkType,
|
||||
WorkFlow,
|
||||
WorkFlowType,
|
||||
ConversationHistoryItem,
|
||||
usage_validate_python,
|
||||
)
|
||||
|
|
@ -149,7 +149,8 @@ class ConversationState(rx.State):
|
|||
"""
|
||||
# 获取当前数据库状态
|
||||
db_state = await self.get_db_state()
|
||||
await db_state.delete_conversation_record(conversation_id)
|
||||
# 更新会话记录
|
||||
await db_state.update_conversation_record(conversation_id, is_deleted=True)
|
||||
del self.conversations[conversation_id]
|
||||
|
||||
# 删除后,若当前用户的会话字典为空则创建会话
|
||||
|
|
@ -224,7 +225,7 @@ class ConversationState(rx.State):
|
|||
conversation.user_prompt = user_prompt.strip()
|
||||
|
||||
@rx.var
|
||||
def is_user_prompt_send_button_disabled(self) -> bool:
|
||||
def is_user_prompt_sending_button_disabled(self) -> bool:
|
||||
"""
|
||||
用户提示词发送按钮不可点击状态
|
||||
:return: bool
|
||||
|
|
@ -238,8 +239,8 @@ class ConversationState(rx.State):
|
|||
@rx.var
|
||||
def is_running(self) -> bool:
|
||||
"""
|
||||
获取当前会话的运行状态
|
||||
:return: 当前会话的运行状态(True 表示正在运行,False 表示运行完成)
|
||||
当前会话正在运行
|
||||
:return: 当前会话正在运行(True 表示正在运行,False 表示运行完成)
|
||||
"""
|
||||
# 当前会话
|
||||
conversation = self.conversations.get(self.actived_conversation_id)
|
||||
|
|
@ -247,6 +248,18 @@ class ConversationState(rx.State):
|
|||
return False
|
||||
return conversation.is_running
|
||||
|
||||
@rx.var
|
||||
def awaiting_stream(self) -> bool:
|
||||
"""
|
||||
当前会话正在等待流式输出
|
||||
:return: 当前会话正在等待流式输出(True 表示等待流式输出,False 表示已开始流式输出或已完成)
|
||||
"""
|
||||
# 当前会话
|
||||
conversation = self.conversations.get(self.actived_conversation_id)
|
||||
if not conversation:
|
||||
return False
|
||||
return conversation.awaiting_stream
|
||||
|
||||
@rx.event
|
||||
async def run(self) -> AsyncGenerator[None]:
|
||||
"""
|
||||
|
|
@ -264,13 +277,22 @@ class ConversationState(rx.State):
|
|||
|
||||
# 将正在运行设置为是
|
||||
conversation.is_running = True
|
||||
# 构建用户提示词消息实例
|
||||
message = Message(
|
||||
# 将等待流式输出设置为是
|
||||
conversation.awaiting_stream = True
|
||||
|
||||
# 获取数据库状态
|
||||
db_state = await self.get_db_state()
|
||||
|
||||
# 先创建消息记录再添加消息实例
|
||||
conversation.messages.update(
|
||||
await db_state.create_message_record(
|
||||
conversation_id=conversation.id,
|
||||
message=Message(
|
||||
type=MessageType.USER_PROMPT,
|
||||
content=(user_prompt := conversation.user_prompt),
|
||||
),
|
||||
)
|
||||
)
|
||||
# 添加至消息字典
|
||||
conversation.messages[message.id] = message
|
||||
# 清空用户提示词
|
||||
conversation.user_prompt = ""
|
||||
yield # 通知前端更新渲染
|
||||
|
|
@ -280,40 +302,32 @@ class ConversationState(rx.State):
|
|||
# 初始化工具调用唯一标识集合
|
||||
tool_call_ids: set[str] = set()
|
||||
try:
|
||||
# 获取数据库状态
|
||||
db_state = await self.get_db_state()
|
||||
# 获取消息历史列表
|
||||
message_history = await db_state.get_message_history(
|
||||
conversation_id=self.actived_conversation_id
|
||||
)
|
||||
usage = usage_validate_python(conversation.usage)
|
||||
# 匹配工作流类型
|
||||
match conversation.work_flow:
|
||||
# 预定航班
|
||||
case WorkType.BOOK_FLIGHT:
|
||||
case WorkFlowType.BOOK_FLIGHT:
|
||||
from application.workshop.book_flight import run_stream_events
|
||||
|
||||
stream_events = run_stream_events(
|
||||
work=conversation.work,
|
||||
user_prompt=user_prompt,
|
||||
message_history=message_history,
|
||||
usage=usage,
|
||||
)
|
||||
|
||||
# 非结构化对话
|
||||
case _:
|
||||
from application.workshop.unstructured_dialogue import (
|
||||
run_stream_events,
|
||||
)
|
||||
|
||||
# 运行并流式输出事件
|
||||
stream_events = run_stream_events(
|
||||
user_prompt=user_prompt,
|
||||
message_history=message_history,
|
||||
usage=usage,
|
||||
message_history=await db_state.get_message_history(
|
||||
conversation_id=self.actived_conversation_id
|
||||
),
|
||||
usage=usage_validate_python(conversation.usage),
|
||||
)
|
||||
|
||||
# 获取运行流式输出事件
|
||||
async for event in stream_events:
|
||||
# 将等待流式输出设置为否
|
||||
conversation.awaiting_stream = False
|
||||
message: Message | None = None
|
||||
match event:
|
||||
# ========== 开始事件 ==========
|
||||
case PartStartEvent(
|
||||
|
|
@ -335,24 +349,6 @@ class ConversationState(rx.State):
|
|||
# 将消息实例唯一标识与片段索引映射
|
||||
index_map_to_message_id[index] = message.id
|
||||
|
||||
# 工具调用分片开始事件
|
||||
case ToolCallPart(
|
||||
tool_name=tool_name, tool_call_id=tool_call_id
|
||||
):
|
||||
# 构建消息实例
|
||||
message = Message(
|
||||
type=MessageType.TOOL_CALL,
|
||||
title=tool_name,
|
||||
content=",",
|
||||
is_running=True,
|
||||
)
|
||||
tool_call_ids.add(tool_call_id)
|
||||
# 添加至消息字典
|
||||
dialog.thoughts[index] = Thought(
|
||||
type="tool_call",
|
||||
content="正在生成调用参数",
|
||||
)
|
||||
|
||||
# 文本分片开始事件
|
||||
case TextPart(content=content):
|
||||
# 构建消息实例
|
||||
|
|
@ -399,102 +395,67 @@ class ConversationState(rx.State):
|
|||
]
|
||||
message.is_running = False
|
||||
message.title = "思考完成"
|
||||
|
||||
# ========== 函数工具调用事件 ==========
|
||||
case FunctionToolCallEvent(tool_call_id=tool_call_id, part=part):
|
||||
# 获取分片索引
|
||||
index = tool_call_ids[tool_call_id]
|
||||
match dialog.thoughts[index].type:
|
||||
# 工具检索
|
||||
case "tool_search":
|
||||
dialog.thoughts[index].content = (
|
||||
f"正在检索 {part.args_as_json_str()}"
|
||||
)
|
||||
|
||||
# 能力加载
|
||||
case "capability_load":
|
||||
dialog.thoughts[index].content = (
|
||||
f"正在加载能力 {part.tool_name}"
|
||||
)
|
||||
|
||||
# 工具调用
|
||||
case "tool_call":
|
||||
dialog.thoughts[index].content = (
|
||||
f"正在调用工具 {part.tool_name}"
|
||||
)
|
||||
|
||||
# ========== 函数工具结果事件 ==========
|
||||
case FunctionToolResultEvent(
|
||||
tool_call_id=tool_call_id,
|
||||
content=content,
|
||||
):
|
||||
index = tool_call_ids[tool_call_id]
|
||||
match dialog.thoughts[index].type:
|
||||
# 工具检索
|
||||
case "tool_search":
|
||||
dialog.thoughts[index].content = (
|
||||
content if isinstance(content, str) else ""
|
||||
) # 暂仅考虑文本内容
|
||||
|
||||
# 能力加载
|
||||
case "capability_load":
|
||||
dialog.thoughts[index].content = f"已加载 {content}"
|
||||
|
||||
# 工具调用
|
||||
case "tool_call":
|
||||
dialog.thoughts[index].content = f"已调用 {content}"
|
||||
message.content = content
|
||||
|
||||
# ========== 智能体运行结果事件 ==========
|
||||
case AgentRunResultEvent(result=result):
|
||||
# 创建对话记录
|
||||
await db_state.create_dialog_record(
|
||||
conversation_id=self.conversation_id,
|
||||
id=dialog.id,
|
||||
user_prompt=dialog.user_prompt,
|
||||
thoughts=thoughts_to_dict(dialog.thoughts),
|
||||
result_output=dialog.result_output,
|
||||
usage=usage_to_dict(result.usage),
|
||||
# 更新使用量
|
||||
await db_state.update_conversation_record(
|
||||
conversation.id, usage=result.usage
|
||||
)
|
||||
# 创建结果记录
|
||||
await db_state.create_result_record(
|
||||
conversation_id=self.conversation_id,
|
||||
_id=dialog.id,
|
||||
# 创建运行记录
|
||||
await db_state.create_run_record(
|
||||
conversation_id=conversation.id,
|
||||
new_messages=result.new_messages(),
|
||||
)
|
||||
|
||||
if isinstance(message, Message):
|
||||
# 强制更新会话并推送前端
|
||||
self.conversations[self.actived_conversation_id] = conversation
|
||||
yield
|
||||
# 创建对话记录
|
||||
await db_state.create_message_record(
|
||||
conversation_id=conversation.id,
|
||||
message=message,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
...
|
||||
finally:
|
||||
# 将正在运行设置为否
|
||||
conversation.is_running = False
|
||||
# 将等待流式输出设置为否
|
||||
conversation.awaiting_stream = False
|
||||
|
||||
self.conversations[self.actived_conversation_id] = conversation
|
||||
yield
|
||||
|
||||
@rx.event
|
||||
async def generate_prd(self) -> None:
|
||||
async def init_work_flow(self, work_flow_type: WorkFlowType) -> None:
|
||||
"""
|
||||
预订航班
|
||||
"""
|
||||
from application.tasks.book_flight import init_task
|
||||
|
||||
# 当前会话
|
||||
conversation = self.conversations[self.conversation_id]
|
||||
# 初始化预定航班任务
|
||||
conversation.task = init_task()
|
||||
self.user_prompt = f"帮我找一班从 {conversation.task.deps.origin} 到 {conversation.task.deps.destination} 在 {conversation.task.deps.date} 的航班"
|
||||
|
||||
@rx.event
|
||||
def toggle_message_collapse(self, message_id: str) -> None:
|
||||
"""
|
||||
展开/折叠消息组件
|
||||
初始化工作流
|
||||
"""
|
||||
# 当前会话
|
||||
conversation = self.conversations.get(self.actived_conversation_id)
|
||||
if not conversation:
|
||||
return
|
||||
conversation.messages[message_id].is_expanded ^= True
|
||||
|
||||
match work_flow_type:
|
||||
# 预定航班
|
||||
case WorkFlowType.BOOK_FLIGHT:
|
||||
from application.workshop.book_flight import init_work_flow
|
||||
|
||||
# 初始化预定航班任务
|
||||
conversation.work_flow = init_work_flow()
|
||||
conversation.user_prompt = f"帮我找一班从 {conversation.work_flow.deps.origin} 到 {conversation.work_flow.deps.destination} 在 {conversation.work_flow.deps.date} 的航班"
|
||||
|
||||
@rx.event
|
||||
def toggle_message_history_item_shown(self, message_id: str) -> None:
|
||||
"""
|
||||
展示 / 隐藏消息历史项
|
||||
"""
|
||||
# 当前会话
|
||||
conversation = self.conversations.get(self.actived_conversation_id)
|
||||
if not conversation:
|
||||
return
|
||||
conversation.messages[message_id].is_shown ^= True
|
||||
|
|
|
|||
|
|
@ -5,23 +5,17 @@
|
|||
from datetime import datetime, timedelta
|
||||
from random import choices
|
||||
from typing import Any
|
||||
|
||||
from pydantic_ai import ModelMessage, ModelMessagesTypeAdapter
|
||||
from pydantic_ai import ModelMessage, ModelMessagesTypeAdapter, RunUsage
|
||||
from pydantic_ai._uuid import uuid7
|
||||
import reflex as rx
|
||||
from sqlalchemy import desc
|
||||
|
||||
from sqlmodel import Field, JSON, SQLModel, select, update
|
||||
|
||||
from application.states.models import (
|
||||
Conversation,
|
||||
TaskStatus,
|
||||
RunStatus,
|
||||
MessageType,
|
||||
Run,
|
||||
Message,
|
||||
deps_to_object,
|
||||
usage_validate_python,
|
||||
usage_limits_to_object,
|
||||
usage_dump_python,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -265,56 +259,56 @@ class DatabaseState(rx.State):
|
|||
)
|
||||
}
|
||||
|
||||
async def delete_conversation_record(self, conversation_id: str) -> None:
|
||||
async def update_conversation_record(
|
||||
self,
|
||||
conversation_id: str,
|
||||
description: str | None = None,
|
||||
is_deleted: bool | None = None,
|
||||
usage: RunUsage | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
删除会话记录(逻辑删除)
|
||||
更新会话记录
|
||||
:param conversation_id: 指定会话唯一标识
|
||||
:param description: 会话描述
|
||||
:param is_deleted: 会话已删除
|
||||
:param usage: 使用量
|
||||
:return: None
|
||||
"""
|
||||
async with rx.asession() as session:
|
||||
record = await session.get(ConversationRecord, conversation_id)
|
||||
if not record:
|
||||
return
|
||||
record.is_deleted = True
|
||||
if isinstance(description, str):
|
||||
record.description = description
|
||||
if isinstance(is_deleted, bool):
|
||||
record.is_deleted = is_deleted
|
||||
if isinstance(usage, RunUsage):
|
||||
record.usage = usage_dump_python(usage)
|
||||
await session.commit()
|
||||
|
||||
async def save_new_messages_record1(
|
||||
async def create_message_record(
|
||||
self,
|
||||
conversation_id: str,
|
||||
run_id: str,
|
||||
user_prompt: str,
|
||||
thoughts: dict[int, Any],
|
||||
result_output: str,
|
||||
usage: dict[str, Any],
|
||||
message: Message,
|
||||
) -> dict[str, Message]:
|
||||
"""
|
||||
保存新增消息
|
||||
创建消息记录
|
||||
:param conversation_id: 会话唯一标识
|
||||
:param user_prompt: 用户提示词
|
||||
:param result_output: 结果输出
|
||||
:return: 保存消息记录的唯一标识
|
||||
:param message: 消息实例
|
||||
:return: 消息实例
|
||||
"""
|
||||
async with rx.asession() as session:
|
||||
record = MessageRecord(
|
||||
id=id,
|
||||
id=message.id,
|
||||
conversation_id=conversation_id,
|
||||
user_prompt=user_prompt,
|
||||
thoughts=thoughts,
|
||||
result_output=result_output,
|
||||
usage=usage,
|
||||
type=message.type,
|
||||
title=message.title,
|
||||
content=message.content,
|
||||
)
|
||||
session.add(record)
|
||||
await session.commit()
|
||||
await session.refresh(record)
|
||||
return {
|
||||
record.id: Dialog(
|
||||
id=record.id,
|
||||
user_prompt=record.user_prompt,
|
||||
thoughts=record.thoughts,
|
||||
result_output=record.result_output,
|
||||
usage=record.usage,
|
||||
)
|
||||
}
|
||||
return {message.id: message}
|
||||
|
||||
async def get_message_history(self, conversation_id: str) -> list[ModelMessage]:
|
||||
"""
|
||||
|
|
@ -337,15 +331,13 @@ class DatabaseState(rx.State):
|
|||
)
|
||||
return message_history
|
||||
|
||||
async def save_new_messages_record(
|
||||
async def create_run_record(
|
||||
self,
|
||||
id: str,
|
||||
conversation_id: str,
|
||||
new_messages: list[ModelMessage],
|
||||
) -> None:
|
||||
"""
|
||||
保存新增消息
|
||||
:param id: 运行唯一标识
|
||||
创建运行记录
|
||||
:param conversation_id: 会话唯一标识
|
||||
:param new_messages: 新增消息
|
||||
:return: None
|
||||
|
|
@ -353,7 +345,6 @@ class DatabaseState(rx.State):
|
|||
async with rx.asession() as session:
|
||||
session.add(
|
||||
RunRecord(
|
||||
id=id,
|
||||
conversation_id=conversation_id,
|
||||
new_messages=ModelMessagesTypeAdapter.dump_json(
|
||||
new_messages
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ class Message(BaseModel):
|
|||
is_running: bool = Field(
|
||||
default=False, description="正在运行,True 表示正在运行,False 表示运行完成"
|
||||
)
|
||||
is_expanded: bool = Field(
|
||||
default=False, description="展开组件,True 表示展开,False 表示折叠"
|
||||
is_shown: bool = Field(
|
||||
default=False, description="展示组件,True 表示展示,False 表示隐藏"
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -85,12 +85,12 @@ class WorkFlow(BaseModel):
|
|||
工作流类
|
||||
"""
|
||||
|
||||
type: WorkFlowType = Field(..., description="工作类型")
|
||||
deps: Deps | None = Field(default=None, description="工作依赖项")
|
||||
usage: RunUsage = Field(default=RunUsage(), description="工作使用量")
|
||||
type: WorkFlowType = Field(..., description="工作流类型")
|
||||
deps: Deps | None = Field(default=None, description="工作流依赖项")
|
||||
usage: RunUsage = Field(default=RunUsage(), description="工作流使用量")
|
||||
usage_limits: UsageLimits | None = Field(
|
||||
default=None,
|
||||
description="任务使用量限制",
|
||||
description="工作流使用量限制",
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -109,6 +109,7 @@ class Conversation(BaseModel):
|
|||
is_running: bool = Field(
|
||||
default=False, description="正在运行,True 表示正在运行, False 表示运行结束"
|
||||
)
|
||||
awaiting_stream: bool = Field(default=False, description="等待流式输出,True 表示等待流式输出,False 表示已开始流式输出或已完成")
|
||||
|
||||
|
||||
class ConversationHistoryItem(BaseModel):
|
||||
|
|
@ -121,6 +122,23 @@ class ConversationHistoryItem(BaseModel):
|
|||
created_at: str = Field(..., description="会话创建日期时间")
|
||||
|
||||
|
||||
class MessageHistoryItem(BaseModel):
|
||||
"""
|
||||
消息历史项类
|
||||
"""
|
||||
|
||||
id: str = Field(..., description="消息唯一标识")
|
||||
type: MessageType = Field(..., description="消息类型")
|
||||
title: str = Field(default="", description="消息标题")
|
||||
content: str = Field(default="", description="消息内容")
|
||||
is_running: bool = Field(
|
||||
default=False, description="正在运行,True 表示正在运行,False 表示运行完成"
|
||||
)
|
||||
is_shown: bool = Field(
|
||||
default=False, description="展示组件,True 表示展示,False 表示隐藏"
|
||||
)
|
||||
|
||||
|
||||
# Dpes 适配器
|
||||
DepsAdapter = TypeAdapter(Deps)
|
||||
|
||||
|
|
@ -149,16 +167,18 @@ UsageAdapter = TypeAdapter(RunUsage)
|
|||
|
||||
def usage_validate_python(usage: dict[str, Any]) -> RunUsage:
|
||||
"""
|
||||
Usage 转为对象
|
||||
将 Usage 反序列化
|
||||
"""
|
||||
if not usage:
|
||||
return RunUsage()
|
||||
return UsageAdapter.validate_python(usage)
|
||||
|
||||
|
||||
def usage_to_dict(usage: RunUsage) -> dict[str, Any]:
|
||||
def usage_dump_python(usage: RunUsage) -> dict[str, Any]:
|
||||
"""
|
||||
Usage 转为字典
|
||||
将 Usage 序列化
|
||||
:param usage: 使用量
|
||||
:return: python 字典
|
||||
"""
|
||||
return UsageAdapter.dump_python(usage)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,21 +6,21 @@ import datetime
|
|||
from typing import AsyncGenerator, Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic_ai import Agent, ModelMessage, ModelRetry, RunContext, UsageLimits
|
||||
from pydantic_ai import Agent, ModelMessage, ModelRetry, RunContext, UsageLimits, RunUsage
|
||||
from pydantic_ai.run import AgentRunResultEvent
|
||||
|
||||
from application.states.models import (
|
||||
Task,
|
||||
TaskNodeResultEvent,
|
||||
TaskType,
|
||||
WorkFlow,
|
||||
WorkFlowType,
|
||||
usage_limits_to_object,
|
||||
usage_to_dict,
|
||||
usage_to_object,
|
||||
Deps,
|
||||
)
|
||||
from application.workshop.models import DEEPSEEK_V4_FLASH_MODEL, MODEL_SETTINGS
|
||||
|
||||
|
||||
class Deps(BaseModel):
|
||||
class Deps_(BaseModel):
|
||||
"""
|
||||
依赖项类
|
||||
"""
|
||||
|
|
@ -167,10 +167,9 @@ flight_info = """
|
|||
"""
|
||||
|
||||
|
||||
def init_task() -> Task:
|
||||
def init_work_flow() -> WorkFlow:
|
||||
return WorkFlow(
|
||||
type=WorkType.BOOK_FLIGHT,
|
||||
tools={"extract_flight_details": {"": ""}},
|
||||
type=WorkFlowType.BOOK_FLIGHT,
|
||||
deps=Deps(
|
||||
flight_info=flight_info,
|
||||
date=datetime.date(2025, 1, 10),
|
||||
|
|
@ -185,8 +184,6 @@ async def run_stream_events(
|
|||
usage: RunUsage,
|
||||
user_prompt: str,
|
||||
message_history: list[ModelMessage],
|
||||
work: WorkType | None = None,
|
||||
|
||||
) -> AsyncGenerator:
|
||||
result = None
|
||||
while True:
|
||||
|
|
@ -264,3 +261,73 @@ async def run_stream_events(
|
|||
)
|
||||
yield event
|
||||
return
|
||||
|
||||
|
||||
# 工具调用分片开始事件
|
||||
case ToolCallPart(
|
||||
tool_name=tool_name, tool_call_id=tool_call_id
|
||||
):
|
||||
# 构建消息实例
|
||||
message = Message(
|
||||
type=MessageType.TOOL_CALL,
|
||||
title=tool_name,
|
||||
content=",",
|
||||
is_running=True,
|
||||
)
|
||||
tool_call_ids.add(tool_call_id)
|
||||
# 添加至消息字典
|
||||
dialog.thoughts[index] = Thought(
|
||||
type="tool_call",
|
||||
content="正在生成调用参数",
|
||||
)
|
||||
|
||||
# ========== 函数工具调用事件 ==========
|
||||
case FunctionToolCallEvent(tool_call_id=tool_call_id, part=part):
|
||||
# 获取分片索引
|
||||
index = tool_call_ids[tool_call_id]
|
||||
match dialog.thoughts[index].type:
|
||||
# 工具检索
|
||||
case "tool_search":
|
||||
dialog.thoughts[index].content = (
|
||||
f"正在检索 {part.args_as_json_str()}"
|
||||
)
|
||||
|
||||
# 能力加载
|
||||
case "capability_load":
|
||||
dialog.thoughts[index].content = (
|
||||
f"正在加载能力 {part.tool_name}"
|
||||
)
|
||||
|
||||
# 工具调用
|
||||
case "tool_call":
|
||||
dialog.thoughts[index].content = (
|
||||
f"正在调用工具 {part.tool_name}"
|
||||
)
|
||||
|
||||
case _:
|
||||
continue
|
||||
|
||||
|
||||
# ========== 函数工具结果事件 ==========
|
||||
case FunctionToolResultEvent(
|
||||
tool_call_id=tool_call_id,
|
||||
content=content,
|
||||
):
|
||||
index = tool_call_ids[tool_call_id]
|
||||
match dialog.thoughts[index].type:
|
||||
# 工具检索
|
||||
case "tool_search":
|
||||
dialog.thoughts[index].content = (
|
||||
content if isinstance(content, str) else ""
|
||||
) # 暂仅考虑文本内容
|
||||
|
||||
# 能力加载
|
||||
case "capability_load":
|
||||
dialog.thoughts[index].content = f"已加载 {content}"
|
||||
|
||||
# 工具调用
|
||||
case "tool_call":
|
||||
dialog.thoughts[index].content = f"已调用 {content}"
|
||||
|
||||
case _:
|
||||
continue
|
||||
Loading…
Reference in New Issue