This commit is contained in:
parent
31828a7be3
commit
1ac121e9cb
|
|
@ -1,36 +0,0 @@
|
||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 3a74f94a7ad4
|
|
||||||
Revises: d52f1ee0f9c4
|
|
||||||
Create Date: 2026-09-03 12:30:13.538072
|
|
||||||
|
|
||||||
"""
|
|
||||||
from typing import Sequence, Union
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
import sqlmodel
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision: str = '3a74f94a7ad4'
|
|
||||||
down_revision: Union[str, Sequence[str], None] = 'd52f1ee0f9c4'
|
|
||||||
branch_labels: Union[str, Sequence[str], None] = None
|
|
||||||
depends_on: Union[str, Sequence[str], None] = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
"""Upgrade schema."""
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('conversationrecord', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('workflow', sqlmodel.sql.sqltypes.AutoString(), server_default=sa.text("('')"), nullable=False))
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
"""Downgrade schema."""
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('conversationrecord', schema=None) as batch_op:
|
|
||||||
batch_op.drop_column('workflow')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
"""empty message
|
"""empty message
|
||||||
|
|
||||||
Revision ID: d52f1ee0f9c4
|
Revision ID: 3e4ccabe44c3
|
||||||
Revises:
|
Revises:
|
||||||
Create Date: 2026-08-11 14:59:37.246745
|
Create Date: 2026-09-11 14:22:48.429299
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from typing import Sequence, Union
|
from typing import Sequence, Union
|
||||||
|
|
@ -12,7 +12,7 @@ import sqlalchemy as sa
|
||||||
import sqlmodel
|
import sqlmodel
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = 'd52f1ee0f9c4'
|
revision: str = '3e4ccabe44c3'
|
||||||
down_revision: Union[str, Sequence[str], None] = None
|
down_revision: Union[str, Sequence[str], None] = None
|
||||||
branch_labels: Union[str, Sequence[str], None] = None
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
depends_on: Union[str, Sequence[str], None] = None
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
@ -37,9 +37,10 @@ def upgrade() -> None:
|
||||||
sa.Column('id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sa.Column('id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
sa.Column('user_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sa.Column('user_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
sa.Column('usage', sa.JSON(), nullable=False),
|
|
||||||
sa.Column('is_deleted', sa.Boolean(), nullable=False),
|
|
||||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||||
|
sa.Column('is_deleted', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('usage', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column('workflow', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
with op.batch_alter_table('conversationrecord', schema=None) as batch_op:
|
with op.batch_alter_table('conversationrecord', schema=None) as batch_op:
|
||||||
|
|
@ -49,9 +50,10 @@ def upgrade() -> None:
|
||||||
op.create_table('messagerecord',
|
op.create_table('messagerecord',
|
||||||
sa.Column('id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sa.Column('id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
sa.Column('conversation_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sa.Column('conversation_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
sa.Column('type', sa.Enum('USER_PROMPT', 'THINKING', 'TOOL_CALL', 'RESULT_OUTPUT', name='messagetype'), nullable=False),
|
sa.Column('type', sa.Enum('USER_PROMPT', 'THINKING', 'TOOL_CALL', 'TEXT', name='messagetype'), nullable=False),
|
||||||
sa.Column('title', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sa.Column('title', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
sa.Column('content', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sa.Column('content', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column('approval', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
with op.batch_alter_table('messagerecord', schema=None) as batch_op:
|
with op.batch_alter_table('messagerecord', schema=None) as batch_op:
|
||||||
|
|
@ -60,7 +62,7 @@ def upgrade() -> None:
|
||||||
op.create_table('runrecord',
|
op.create_table('runrecord',
|
||||||
sa.Column('id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sa.Column('id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
sa.Column('conversation_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sa.Column('conversation_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
sa.Column('new_messages', sa.JSON(), nullable=False),
|
sa.Column('new_messages', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
with op.batch_alter_table('runrecord', schema=None) as batch_op:
|
with op.batch_alter_table('runrecord', schema=None) as batch_op:
|
||||||
|
|
@ -475,9 +475,8 @@ def tool_call(item: MessageHistoryItem) -> rx.Component:
|
||||||
rx.button(
|
rx.button(
|
||||||
"发送",
|
"发送",
|
||||||
# 点击事件:审批
|
# 点击事件:审批
|
||||||
on_click=lambda: ConversationState.approve(item.id),
|
on_click=lambda: ConversationState.approve(item.id, True),
|
||||||
# 是否禁用绑定验证码发送禁用状态
|
# 是否禁用绑定验证码发送禁用状态
|
||||||
disabled=AuthState.is_captcha_code_sending_button_disabled,
|
|
||||||
margin="0 8px",
|
margin="0 8px",
|
||||||
background_color="var(--prismui-background-color-1)",
|
background_color="var(--prismui-background-color-1)",
|
||||||
line_height="16px",
|
line_height="16px",
|
||||||
|
|
@ -487,11 +486,6 @@ def tool_call(item: MessageHistoryItem) -> rx.Component:
|
||||||
"var(--prismui-color-4)",
|
"var(--prismui-color-4)",
|
||||||
"var(--prismui-color-5)",
|
"var(--prismui-color-5)",
|
||||||
),
|
),
|
||||||
cursor=rx.cond(
|
|
||||||
AuthState.is_captcha_code_sending_button_disabled,
|
|
||||||
"not-allowed",
|
|
||||||
"pointer",
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
width="100%",
|
width="100%",
|
||||||
height="45px",
|
height="45px",
|
||||||
|
|
@ -669,7 +663,7 @@ def message_history() -> rx.Component:
|
||||||
align_items="center",
|
align_items="center",
|
||||||
width="100%",
|
width="100%",
|
||||||
padding="0 16px",
|
padding="0 16px",
|
||||||
margin_bottom="8px",
|
margin="8px 0",
|
||||||
overflow="hidden",
|
overflow="hidden",
|
||||||
),
|
),
|
||||||
rx.fragment(),
|
rx.fragment(),
|
||||||
|
|
|
||||||
|
|
@ -208,8 +208,9 @@ class AuthState(rx.State):
|
||||||
"""
|
"""
|
||||||
恢复会话状态
|
恢复会话状态
|
||||||
"""
|
"""
|
||||||
# 恢复会话状态
|
# 获取会话状态
|
||||||
conversation_state = await self.get_state(ConversationState)
|
conversation_state = await self.get_state(ConversationState)
|
||||||
|
# 恢复会话状态
|
||||||
await conversation_state.resume_conversation_state(user_id=self.user_id)
|
await conversation_state.resume_conversation_state(user_id=self.user_id)
|
||||||
|
|
||||||
@rx.event
|
@rx.event
|
||||||
|
|
@ -253,8 +254,9 @@ class AuthState(rx.State):
|
||||||
# 创建用户记录
|
# 创建用户记录
|
||||||
user_id = await db_state.create_user_record(email=self.email)
|
user_id = await db_state.create_user_record(email=self.email)
|
||||||
|
|
||||||
# 恢复当前用户会话状态
|
# 获取会话状态
|
||||||
conversation_state = await self.get_state(ConversationState)
|
conversation_state = await self.get_state(ConversationState)
|
||||||
|
# 恢复当前用户的会话状态
|
||||||
await conversation_state.resume_conversation_state(user_id=user_id)
|
await conversation_state.resume_conversation_state(user_id=user_id)
|
||||||
|
|
||||||
self.user_id = user_id
|
self.user_id = user_id
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,15 @@
|
||||||
"""
|
"""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import AsyncGenerator, cast
|
from typing import AsyncGenerator, cast
|
||||||
|
|
||||||
from pydantic_ai import (
|
from pydantic_ai import (
|
||||||
DeferredToolRequests,
|
DeferredToolRequests,
|
||||||
DeferredToolResults,
|
DeferredToolResults,
|
||||||
ToolApproved,
|
ToolApproved,
|
||||||
ToolDenied,
|
ToolDenied,
|
||||||
)
|
)
|
||||||
|
|
||||||
from pydantic_ai.messages import (
|
from pydantic_ai.messages import (
|
||||||
FunctionToolCallEvent,
|
|
||||||
FunctionToolResultEvent,
|
FunctionToolResultEvent,
|
||||||
LoadCapabilityCallPart,
|
|
||||||
ToolReturnPart,
|
|
||||||
PartDeltaEvent,
|
PartDeltaEvent,
|
||||||
PartEndEvent,
|
PartEndEvent,
|
||||||
PartStartEvent,
|
PartStartEvent,
|
||||||
|
|
@ -24,13 +21,14 @@ from pydantic_ai.messages import (
|
||||||
ThinkingPart,
|
ThinkingPart,
|
||||||
ThinkingPartDelta,
|
ThinkingPartDelta,
|
||||||
ToolCallPart,
|
ToolCallPart,
|
||||||
ToolSearchCallPart,
|
ToolReturnPart,
|
||||||
)
|
)
|
||||||
from pydantic_ai.run import AgentRunResultEvent, AgentRunResult
|
from pydantic_ai.run import AgentRunResultEvent
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
from application.states.database import DatabaseState
|
from application.states.database import DatabaseState
|
||||||
from application.states.models import (
|
from application.states.models import (
|
||||||
|
Approval,
|
||||||
Conversation,
|
Conversation,
|
||||||
ConversationHistoryItem,
|
ConversationHistoryItem,
|
||||||
Message,
|
Message,
|
||||||
|
|
@ -38,11 +36,8 @@ from application.states.models import (
|
||||||
MessageType,
|
MessageType,
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowType,
|
WorkflowType,
|
||||||
Approval,
|
|
||||||
workflow_dump_json,
|
|
||||||
workflow_validate_json,
|
|
||||||
usage_validate_json,
|
|
||||||
)
|
)
|
||||||
|
from application.workshop.events import AgentPromptEvent
|
||||||
|
|
||||||
|
|
||||||
def format_conversation_created_at(created_at: datetime) -> str:
|
def format_conversation_created_at(created_at: datetime) -> str:
|
||||||
|
|
@ -280,34 +275,7 @@ class ConversationState(rx.State):
|
||||||
# 获取数据库状态
|
# 获取数据库状态
|
||||||
db_state = await self.get_db_state()
|
db_state = await self.get_db_state()
|
||||||
|
|
||||||
# 无审批实例
|
if isinstance(approval, Approval):
|
||||||
if not approval:
|
|
||||||
# 若用户提示词为空则直接返回
|
|
||||||
if not conversation.user_prompt:
|
|
||||||
return
|
|
||||||
|
|
||||||
# 将正在运行设置为是
|
|
||||||
conversation.is_running = True
|
|
||||||
# 将等待流式输出设置为是
|
|
||||||
conversation.awaiting_stream = True
|
|
||||||
yield # 通知前端更新渲染
|
|
||||||
|
|
||||||
# 先创建消息记录再添加消息实例
|
|
||||||
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.user_prompt = ""
|
|
||||||
yield # 通知前端更新渲染
|
|
||||||
|
|
||||||
# 有审批实例
|
|
||||||
else:
|
|
||||||
if not (user_prompt := approval.user_prompt):
|
if not (user_prompt := approval.user_prompt):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -315,14 +283,50 @@ class ConversationState(rx.State):
|
||||||
conversation.awaiting_stream = True
|
conversation.awaiting_stream = True
|
||||||
yield # 通知前端更新渲染
|
yield # 通知前端更新渲染
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 若用户提示词为空则直接返回
|
||||||
|
if not (user_prompt := conversation.user_prompt):
|
||||||
|
return
|
||||||
|
|
||||||
|
# 清空用户提示词
|
||||||
|
conversation.user_prompt = ""
|
||||||
|
# 将正在运行设置为是
|
||||||
|
conversation.is_running = True
|
||||||
|
# 将等待流式输出设置为是
|
||||||
|
conversation.awaiting_stream = True
|
||||||
|
# 先创建消息记录再添加消息实例
|
||||||
|
conversation.messages.update(
|
||||||
|
await db_state.create_message_record(
|
||||||
|
conversation_id=conversation.id,
|
||||||
|
message=Message(
|
||||||
|
type=MessageType.USER_PROMPT,
|
||||||
|
content=user_prompt,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
yield # 通知前端更新渲染
|
||||||
|
|
||||||
# 初始化片段索引映射为消息实例唯一标识字典
|
# 初始化片段索引映射为消息实例唯一标识字典
|
||||||
index_map_to_message_id: dict[int, str] = {}
|
index_map_to_message_id: dict[int, str] = {}
|
||||||
# 初始化工具调用片段工具名称映射为消息实例唯一标识字典
|
|
||||||
tool_name_map_to_message_id: dict[str, str] = {}
|
# 查询消息历史列表
|
||||||
|
message_history = await db_state.read_message_history(
|
||||||
|
conversation_id=self.actived_conversation_id
|
||||||
|
)
|
||||||
|
|
||||||
# 初始化工作流
|
# 初始化工作流
|
||||||
if conversation.workflow:
|
|
||||||
match conversation.workflow.type:
|
match conversation.workflow.type:
|
||||||
|
case WorkflowType.TALK:
|
||||||
|
from application.workshop.talk import (
|
||||||
|
run_stream_events,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 运行并流式输出事件
|
||||||
|
stream_events = run_stream_events(
|
||||||
|
user_prompt=user_prompt,
|
||||||
|
message_history=message_history,
|
||||||
|
usage=conversation.usage,
|
||||||
|
)
|
||||||
case WorkflowType.BOOK_FLIGHT:
|
case WorkflowType.BOOK_FLIGHT:
|
||||||
from application.workshop.book_flight import (
|
from application.workshop.book_flight import (
|
||||||
run_stream_events,
|
run_stream_events,
|
||||||
|
|
@ -332,25 +336,10 @@ class ConversationState(rx.State):
|
||||||
stream_events = run_stream_events(
|
stream_events = run_stream_events(
|
||||||
deps=conversation.workflow.deps,
|
deps=conversation.workflow.deps,
|
||||||
user_prompt=user_prompt,
|
user_prompt=user_prompt,
|
||||||
message_history=await db_state.read_message_history(
|
message_history=message_history,
|
||||||
conversation_id=self.actived_conversation_id
|
|
||||||
),
|
|
||||||
usage=conversation.usage,
|
usage=conversation.usage,
|
||||||
deferred_tool_results=deferred_tool_results,
|
deferred_tool_results=deferred_tool_results,
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
from application.workshop.talk import (
|
|
||||||
run_stream_events,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 运行并流式输出事件
|
|
||||||
stream_events = run_stream_events(
|
|
||||||
user_prompt=user_prompt,
|
|
||||||
message_history=await db_state.read_message_history(
|
|
||||||
conversation_id=self.actived_conversation_id
|
|
||||||
),
|
|
||||||
usage=conversation.usage,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 消息列表
|
# 消息列表
|
||||||
messages: list[Message] = []
|
messages: list[Message] = []
|
||||||
|
|
@ -382,7 +371,9 @@ class ConversationState(rx.State):
|
||||||
yield # 通知前端更新渲染
|
yield # 通知前端更新渲染
|
||||||
|
|
||||||
# 工具调用分片
|
# 工具调用分片
|
||||||
case ToolCallPart(tool_name=tool_name):
|
case ToolCallPart(
|
||||||
|
tool_call_id=tool_call_id, tool_name=tool_name
|
||||||
|
):
|
||||||
# 构建消息实例
|
# 构建消息实例
|
||||||
message = Message(
|
message = Message(
|
||||||
type=MessageType.TOOL_CALL,
|
type=MessageType.TOOL_CALL,
|
||||||
|
|
@ -391,8 +382,10 @@ class ConversationState(rx.State):
|
||||||
)
|
)
|
||||||
# 添加至消息字典
|
# 添加至消息字典
|
||||||
conversation.messages[message.id] = message
|
conversation.messages[message.id] = message
|
||||||
# 将消息实例唯一标识与工具调用片段工具名称映射
|
# 将消息实例唯一标识与工具调用唯一标识映射
|
||||||
tool_name_map_to_message_id[tool_name] = message.id
|
conversation.workflow.tool_call_id_map_to_message_id[
|
||||||
|
tool_call_id
|
||||||
|
] = message.id
|
||||||
yield # 通知前端更新渲染
|
yield # 通知前端更新渲染
|
||||||
|
|
||||||
# 文本分片
|
# 文本分片
|
||||||
|
|
@ -436,7 +429,7 @@ class ConversationState(rx.State):
|
||||||
):
|
):
|
||||||
match part:
|
match part:
|
||||||
# 思考分片
|
# 思考分片
|
||||||
case ThinkingPart(content=content):
|
case ThinkingPart():
|
||||||
# 获取消息实例
|
# 获取消息实例
|
||||||
message = conversation.messages[
|
message = conversation.messages[
|
||||||
index_map_to_message_id[index]
|
index_map_to_message_id[index]
|
||||||
|
|
@ -447,7 +440,7 @@ class ConversationState(rx.State):
|
||||||
messages.append(message)
|
messages.append(message)
|
||||||
|
|
||||||
# 文本分片
|
# 文本分片
|
||||||
case TextPart(content=content):
|
case TextPart():
|
||||||
# 获取消息实例
|
# 获取消息实例
|
||||||
message = conversation.messages[
|
message = conversation.messages[
|
||||||
index_map_to_message_id[index]
|
index_map_to_message_id[index]
|
||||||
|
|
@ -458,13 +451,18 @@ class ConversationState(rx.State):
|
||||||
case FunctionToolResultEvent(part=part):
|
case FunctionToolResultEvent(part=part):
|
||||||
match part:
|
match part:
|
||||||
# 工具返回分片
|
# 工具返回分片
|
||||||
case ToolReturnPart(tool_name=tool_name, content=content):
|
case ToolReturnPart(
|
||||||
|
tool_call_id=tool_call_id,
|
||||||
|
tool_name=tool_name,
|
||||||
|
content=content,
|
||||||
|
):
|
||||||
# 获取消息实例
|
# 获取消息实例
|
||||||
message = conversation.messages[
|
message = conversation.messages[
|
||||||
tool_name_map_to_message_id[tool_name]
|
conversation.workflow.tool_call_id_map_to_message_id[
|
||||||
|
tool_call_id
|
||||||
|
]
|
||||||
]
|
]
|
||||||
message.is_running = False
|
message.is_running = False
|
||||||
yield # 通知前端更新渲染
|
|
||||||
message.title = f"{tool_name}已完成"
|
message.title = f"{tool_name}已完成"
|
||||||
message.content = cast(
|
message.content = cast(
|
||||||
str, content
|
str, content
|
||||||
|
|
@ -472,26 +470,39 @@ class ConversationState(rx.State):
|
||||||
yield # 通知前端更新渲染
|
yield # 通知前端更新渲染
|
||||||
messages.append(message)
|
messages.append(message)
|
||||||
|
|
||||||
|
# ========== 智能体提示事件 ==========
|
||||||
|
case AgentPromptEvent(content=content):
|
||||||
|
# 构建消息实例
|
||||||
|
message = Message(type=MessageType.TEXT, content=content)
|
||||||
|
# 添加至消息字典
|
||||||
|
conversation.messages[message.id] = message
|
||||||
|
yield # 通知前端更新渲染
|
||||||
|
messages.append(message)
|
||||||
|
|
||||||
# ========== 智能体运行结果事件 ==========
|
# ========== 智能体运行结果事件 ==========
|
||||||
case AgentRunResultEvent(result=result):
|
case AgentRunResultEvent(result=result):
|
||||||
if isinstance(output := result.output, DeferredToolRequests):
|
if isinstance(output := result.output, DeferredToolRequests):
|
||||||
for approval_ in output.approvals:
|
for appr in output.approvals:
|
||||||
# 获取消息实例
|
# 获取消息实例
|
||||||
message = conversation.messages[
|
message = conversation.messages[
|
||||||
tool_name_map_to_message_id[approval_.tool_name]
|
conversation.workflow.tool_call_id_map_to_message_id[
|
||||||
|
tool_call_id := appr.tool_call_id
|
||||||
]
|
]
|
||||||
message.content = output.metadata[
|
]
|
||||||
tool_call_id := approval_.tool_call_id
|
message.content = output.metadata[tool_call_id]["content"]
|
||||||
]["content"]
|
|
||||||
message.approval = Approval(
|
message.approval = Approval(
|
||||||
tool_call_id=tool_call_id,
|
tool_call_id=tool_call_id,
|
||||||
)
|
)
|
||||||
|
yield # 通知前端更新渲染
|
||||||
messages.append(message)
|
messages.append(message)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# 将工作流设置为空
|
# 若工作流类型非对话则设置为对话
|
||||||
conversation.workflow = None
|
if conversation.workflow.type != WorkflowType.TALK:
|
||||||
|
conversation.workflow = Workflow()
|
||||||
# 将正在运行设置为否
|
# 将正在运行设置为否
|
||||||
conversation.is_running = False
|
conversation.is_running = False
|
||||||
|
yield # 通知前端更新渲染
|
||||||
|
|
||||||
# 更新会话记录
|
# 更新会话记录
|
||||||
await db_state.update_conversation_record(
|
await db_state.update_conversation_record(
|
||||||
|
|
@ -499,15 +510,12 @@ class ConversationState(rx.State):
|
||||||
usage=result.usage,
|
usage=result.usage,
|
||||||
workflow=conversation.workflow,
|
workflow=conversation.workflow,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 创建运行记录
|
# 创建运行记录
|
||||||
await db_state.create_run_record(
|
await db_state.create_run_record(
|
||||||
conversation_id=conversation.id,
|
conversation_id=conversation.id,
|
||||||
new_messages=result.new_messages(),
|
new_messages=result.new_messages(),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.conversations[self.actived_conversation_id] = conversation
|
|
||||||
yield # 通知前端更新渲染
|
|
||||||
|
|
||||||
# 批量创建消息记录
|
# 批量创建消息记录
|
||||||
await db_state.create_message_records(
|
await db_state.create_message_records(
|
||||||
conversation_id=conversation.id,
|
conversation_id=conversation.id,
|
||||||
|
|
@ -578,7 +586,6 @@ class ConversationState(rx.State):
|
||||||
if not conversation:
|
if not conversation:
|
||||||
return
|
return
|
||||||
|
|
||||||
# 获取审批实例
|
|
||||||
if not (message := conversation.messages.get(item_id)) or not isinstance(
|
if not (message := conversation.messages.get(item_id)) or not isinstance(
|
||||||
approval := message.approval, Approval
|
approval := message.approval, Approval
|
||||||
):
|
):
|
||||||
|
|
@ -587,14 +594,15 @@ class ConversationState(rx.State):
|
||||||
# 更新审批结果
|
# 更新审批结果
|
||||||
approval.result = result
|
approval.result = result
|
||||||
# 运行
|
# 运行
|
||||||
await self.run(
|
async for _ in self.run(
|
||||||
approval,
|
approval,
|
||||||
DeferredToolResults(
|
DeferredToolResults(
|
||||||
approvals={
|
approvals={
|
||||||
approval.tool_call_id: ToolApproved() if result else ToolDenied()
|
approval.tool_call_id: ToolApproved() if result else ToolDenied()
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
):
|
||||||
|
...
|
||||||
|
|
||||||
@rx.event
|
@rx.event
|
||||||
async def set_workflow(self, type: WorkflowType) -> None:
|
async def set_workflow(self, type: WorkflowType) -> None:
|
||||||
|
|
@ -617,6 +625,11 @@ class ConversationState(rx.State):
|
||||||
f"帮我查询并预定在 {workflow.deps.date.strftime('%Y-%m-%d')} 从 {workflow.deps.origin_airport_code} 到 {workflow.deps.destination_airport_code} 的航班"
|
f"帮我查询并预定在 {workflow.deps.date.strftime('%Y-%m-%d')} 从 {workflow.deps.origin_airport_code} 到 {workflow.deps.destination_airport_code} 的航班"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
case _:
|
||||||
|
# 初始化对话工作流
|
||||||
|
workflow = Workflow()
|
||||||
|
|
||||||
|
# 设置工作流
|
||||||
conversation.workflow = workflow
|
conversation.workflow = workflow
|
||||||
# 获取数据库状态
|
# 获取数据库状态
|
||||||
db_state = await self.get_db_state()
|
db_state = await self.get_db_state()
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@
|
||||||
"""
|
"""
|
||||||
数据库状态
|
数据库状态
|
||||||
"""
|
"""
|
||||||
|
from dataclasses import asdict
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from random import choices
|
from random import choices
|
||||||
from dataclasses import asdict
|
|
||||||
from pydantic_ai import ModelMessage, ModelMessagesTypeAdapter, RunUsage
|
from pydantic_ai import ModelMessage, ModelMessagesTypeAdapter, RunUsage
|
||||||
from pydantic_ai._uuid import uuid7
|
from pydantic_ai._uuid import uuid7
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
@ -15,12 +16,12 @@ from application.states.models import (
|
||||||
Message,
|
Message,
|
||||||
MessageType,
|
MessageType,
|
||||||
Workflow,
|
Workflow,
|
||||||
|
approval_dump_json,
|
||||||
|
approval_validate_json,
|
||||||
usage_dump_json,
|
usage_dump_json,
|
||||||
usage_validate_json,
|
usage_validate_json,
|
||||||
workflow_dump_json,
|
workflow_dump_json,
|
||||||
workflow_validate_json,
|
workflow_validate_json,
|
||||||
approval_dump_json,
|
|
||||||
approval_validate_json,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -346,7 +347,17 @@ class DatabaseState(rx.State):
|
||||||
"""
|
"""
|
||||||
async with rx.asession() as session:
|
async with rx.asession() as session:
|
||||||
for message in messages:
|
for message in messages:
|
||||||
record = MessageRecord(
|
result = await session.exec(
|
||||||
|
select(MessageRecord).where(MessageRecord.id == message.id)
|
||||||
|
)
|
||||||
|
record = result.first()
|
||||||
|
if record:
|
||||||
|
record.title = message.title
|
||||||
|
record.content = message.content
|
||||||
|
record.approval = approval_dump_json(message.approval)
|
||||||
|
else:
|
||||||
|
session.add(
|
||||||
|
MessageRecord(
|
||||||
id=message.id,
|
id=message.id,
|
||||||
conversation_id=conversation_id,
|
conversation_id=conversation_id,
|
||||||
type=message.type,
|
type=message.type,
|
||||||
|
|
@ -354,7 +365,7 @@ class DatabaseState(rx.State):
|
||||||
content=message.content,
|
content=message.content,
|
||||||
approval=approval_dump_json(message.approval),
|
approval=approval_dump_json(message.approval),
|
||||||
)
|
)
|
||||||
session.add(record)
|
)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
async def read_message_history(self, conversation_id: str) -> list[ModelMessage]:
|
async def read_message_history(self, conversation_id: str) -> list[ModelMessage]:
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ class WorkflowType(StrEnum):
|
||||||
工作类型枚举
|
工作类型枚举
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
TALK = "talk"
|
||||||
BOOK_FLIGHT = "预定航班"
|
BOOK_FLIGHT = "预定航班"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -74,8 +75,11 @@ class Workflow(BaseModel):
|
||||||
工作流类
|
工作流类
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: WorkflowType = Field(..., description="工作类型")
|
type: WorkflowType = Field(default=WorkflowType.TALK, description="工作类型")
|
||||||
deps: Any = Field(default=None, description="工作依赖项")
|
deps: Any = Field(default=None, description="工作依赖项")
|
||||||
|
tool_call_id_map_to_message_id: dict[str, str] = Field(
|
||||||
|
default_factory=dict, description="工具调用唯一标识映射为消息实例唯一标识字典"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Conversation(BaseModel):
|
class Conversation(BaseModel):
|
||||||
|
|
@ -88,7 +92,7 @@ class Conversation(BaseModel):
|
||||||
created_at: datetime = Field(..., description="会话创建日期时间")
|
created_at: datetime = Field(..., description="会话创建日期时间")
|
||||||
usage: RunUsage = Field(..., description="会话使用量")
|
usage: RunUsage = Field(..., description="会话使用量")
|
||||||
messages: dict[str, Message] = Field(default_factory=dict, description="消息字典")
|
messages: dict[str, Message] = Field(default_factory=dict, description="消息字典")
|
||||||
workflow: Workflow | None = Field(default=None, description="工作流")
|
workflow: Workflow = Field(default_factory=Workflow, description="工作,默认为对话")
|
||||||
user_prompt: str = Field(default="", description="用户提示词")
|
user_prompt: str = Field(default="", description="用户提示词")
|
||||||
is_running: bool = Field(
|
is_running: bool = Field(
|
||||||
default=False, description="正在运行,True 表示正在运行, False 表示运行结束"
|
default=False, description="正在运行,True 表示正在运行, False 表示运行结束"
|
||||||
|
|
@ -119,7 +123,7 @@ class MessageHistoryItem(Message):
|
||||||
|
|
||||||
|
|
||||||
# 工作流适配器
|
# 工作流适配器
|
||||||
WorkflowAdapter = TypeAdapter(Any)
|
WorkflowAdapter = TypeAdapter(Workflow)
|
||||||
|
|
||||||
|
|
||||||
def workflow_validate_json(workflow: str) -> Any:
|
def workflow_validate_json(workflow: str) -> Any:
|
||||||
|
|
@ -127,11 +131,11 @@ def workflow_validate_json(workflow: str) -> Any:
|
||||||
将工作流反序列化
|
将工作流反序列化
|
||||||
"""
|
"""
|
||||||
if not workflow:
|
if not workflow:
|
||||||
return None
|
return Workflow()
|
||||||
return WorkflowAdapter.validate_json(workflow)
|
return WorkflowAdapter.validate_json(workflow)
|
||||||
|
|
||||||
|
|
||||||
def workflow_dump_json(workflow: Any) -> str:
|
def workflow_dump_json(workflow: Workflow) -> str:
|
||||||
"""
|
"""
|
||||||
将工作流序列化
|
将工作流序列化
|
||||||
:param workflow: 工作流
|
:param workflow: 工作流
|
||||||
|
|
|
||||||
|
|
@ -16,24 +16,25 @@ from pydantic_ai import (
|
||||||
RunContext,
|
RunContext,
|
||||||
RunUsage,
|
RunUsage,
|
||||||
UsageLimits,
|
UsageLimits,
|
||||||
|
AgentRunResult,
|
||||||
)
|
)
|
||||||
from pydantic_ai.messages import (
|
from pydantic_ai.messages import (
|
||||||
|
PartDeltaEvent,
|
||||||
|
TextPart,
|
||||||
AgentStreamEvent,
|
AgentStreamEvent,
|
||||||
FunctionToolResultEvent,
|
FunctionToolResultEvent,
|
||||||
PartStartEvent,
|
PartStartEvent,
|
||||||
ToolCallPart,
|
ToolCallPart,
|
||||||
ToolReturnPart,
|
ToolReturnPart,
|
||||||
)
|
)
|
||||||
from pydantic_ai.run import AgentRunResult, AgentRunResultEvent
|
from pydantic_ai.run import AgentRunResultEvent
|
||||||
|
|
||||||
from application.states.models import (
|
from application.states.models import NoResult
|
||||||
NoResult,
|
|
||||||
Approval,
|
|
||||||
)
|
|
||||||
from application.workshop.models import (
|
from application.workshop.models import (
|
||||||
DEEPSEEK_V4_FLASH_MODEL,
|
DEEPSEEK_V4_FLASH_MODEL,
|
||||||
DEEPSEEK_V4_FLASH_MODEL_SETTINGS,
|
DEEPSEEK_V4_FLASH_MODEL_SETTINGS,
|
||||||
)
|
)
|
||||||
|
from application.workshop.events import AgentPromptEvent
|
||||||
|
|
||||||
|
|
||||||
class Flight(BaseModel):
|
class Flight(BaseModel):
|
||||||
|
|
@ -174,6 +175,9 @@ async def search_flights(ctx: RunContext[Deps]) -> list[Flight]:
|
||||||
**设计工具需先设计业务流程**
|
**设计工具需先设计业务流程**
|
||||||
本示例按照 查询航班 -> 推荐航班 -> 预定航班
|
本示例按照 查询航班 -> 推荐航班 -> 预定航班
|
||||||
"""
|
"""
|
||||||
|
if ctx.deps.searched_flights:
|
||||||
|
return ctx.deps.searched_flights
|
||||||
|
|
||||||
# 提取到的所有航班信息
|
# 提取到的所有航班信息
|
||||||
extracted_flights = (
|
extracted_flights = (
|
||||||
await extraction_flights_agent.run(
|
await extraction_flights_agent.run(
|
||||||
|
|
@ -204,13 +208,14 @@ async def search_flights(ctx: RunContext[Deps]) -> list[Flight]:
|
||||||
return searched_flights
|
return searched_flights
|
||||||
|
|
||||||
|
|
||||||
@agent.tool(
|
@agent.tool(name="预定航班")
|
||||||
name="预定航班",
|
|
||||||
)
|
|
||||||
async def book_flight(ctx: RunContext[Deps]) -> Flight | NoResult:
|
async def book_flight(ctx: RunContext[Deps]) -> Flight | NoResult:
|
||||||
"""
|
"""
|
||||||
预定航班
|
预定航班
|
||||||
"""
|
"""
|
||||||
|
if ctx.deps.booked_flight:
|
||||||
|
return ctx.deps.booked_flight
|
||||||
|
|
||||||
if (searched_flights := ctx.deps.searched_flights) is None:
|
if (searched_flights := ctx.deps.searched_flights) is None:
|
||||||
raise ModelRetry("必须先使用 search_flights 查询航班")
|
raise ModelRetry("必须先使用 search_flights 查询航班")
|
||||||
|
|
||||||
|
|
@ -289,7 +294,6 @@ async def run_stream_events(
|
||||||
"""
|
"""
|
||||||
运行并流式输出事件
|
运行并流式输出事件
|
||||||
"""
|
"""
|
||||||
tool_names = set()
|
|
||||||
async with agent.run_stream_events(
|
async with agent.run_stream_events(
|
||||||
user_prompt=user_prompt,
|
user_prompt=user_prompt,
|
||||||
deps=deps,
|
deps=deps,
|
||||||
|
|
@ -308,8 +312,6 @@ async def run_stream_events(
|
||||||
case ToolCallPart(tool_name=tool_name):
|
case ToolCallPart(tool_name=tool_name):
|
||||||
match tool_name:
|
match tool_name:
|
||||||
case "查询航班" | "预定航班":
|
case "查询航班" | "预定航班":
|
||||||
if tool_name not in tool_names:
|
|
||||||
tool_names.add(tool_name)
|
|
||||||
yield event
|
yield event
|
||||||
|
|
||||||
case FunctionToolResultEvent(part):
|
case FunctionToolResultEvent(part):
|
||||||
|
|
@ -327,14 +329,19 @@ async def run_stream_events(
|
||||||
event.part.content += f"{searched_flight.number} {searched_flight.airfare}$ 于 {searched_flight.date.strftime('%Y-%m-%d')} 从 {searched_flight.origin_airport_code} 到 {searched_flight.destination_airport_code}\n\n"
|
event.part.content += f"{searched_flight.number} {searched_flight.airfare}$ 于 {searched_flight.date.strftime('%Y-%m-%d')} 从 {searched_flight.origin_airport_code} 到 {searched_flight.destination_airport_code}\n\n"
|
||||||
yield event
|
yield event
|
||||||
|
|
||||||
case "预定航班":
|
# 预定航班无需业务定义
|
||||||
if isinstance(content, NoResult):
|
|
||||||
event.part.content = (
|
|
||||||
"未提取到航班号,请检查后重试"
|
|
||||||
)
|
|
||||||
if isinstance(content, Flight):
|
|
||||||
event.part.content = f"已预定航班:\n{content.number} {content.airfare}$ 于 {content.date.strftime('%Y-%m-%d')} 从 {content.origin_airport_code} 到 {content.destination_airport_code}\n"
|
|
||||||
yield event
|
|
||||||
|
|
||||||
case AgentRunResultEvent:
|
case AgentRunResultEvent(result=result):
|
||||||
|
match result:
|
||||||
|
case AgentRunResult(output=output):
|
||||||
|
match output:
|
||||||
|
case Flight():
|
||||||
|
yield AgentPromptEvent(
|
||||||
|
content=f"已预定航班:\n\n{output.number} {output.airfare}$ 于 {output.date.strftime('%Y-%m-%d')} 从 {output.origin_airport_code} 到 {output.destination_airport_code}\n\n"
|
||||||
|
)
|
||||||
|
case NoResult():
|
||||||
|
yield AgentPromptEvent(
|
||||||
|
content="未提取到航班号,请检查后重试"
|
||||||
|
)
|
||||||
|
case DeferredToolRequests():
|
||||||
yield event
|
yield event
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
智能体事件模块
|
||||||
|
"""
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from pydantic_ai.messages import CustomEvent
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class AgentPromptEvent(CustomEvent):
|
||||||
|
"""
|
||||||
|
智能体提示事件
|
||||||
|
"""
|
||||||
|
|
||||||
|
content: str
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
智能体相关模块
|
智能体模型模块
|
||||||
"""
|
"""
|
||||||
from pydantic_ai import ModelSettings
|
from pydantic_ai import ModelSettings
|
||||||
from pydantic_ai.models.openai import OpenAIChatModel
|
from pydantic_ai.models.openai import OpenAIChatModel
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue