This commit is contained in:
liubiren 2026-09-04 20:15:17 +08:00
parent 5abfc83d4c
commit 714675cf5b
6 changed files with 73 additions and 19 deletions

View File

@ -396,7 +396,7 @@ def thinking(item: MessageHistoryItem) -> rx.Component:
rx.box( rx.box(
rx.cond( rx.cond(
item.is_shown, item.is_shown,
rx.text( rx.markdown(
item.content, item.content,
width="100%", width="100%",
margin="4px 0", margin="4px 0",
@ -418,6 +418,43 @@ def thinking(item: MessageHistoryItem) -> rx.Component:
) )
def tool_call(item: MessageHistoryItem) -> rx.Component:
"""
工具调用
:return: Component
"""
return rx.vstack(
# 工具调用的标题栏
rx.hstack(
rx.icon(
"square-function",
width="14px",
height="14px",
color="var(--prismui-color-3)",
),
rx.text(
item.title,
font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-3)",
class_name=rx.cond(item.is_running, "is_running", None),
),
align_items="center",
gap="4px",
margin_bottom="8px",
line_height="22px",
),
# 工具调用的内容
rx.markdown(
item.content,
width="100%",
margin="4px 0",
line_height="1.6",
font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-3)",
),
)
def output(item: MessageHistoryItem) -> rx.Component: def output(item: MessageHistoryItem) -> rx.Component:
""" """
输出 输出
@ -432,7 +469,6 @@ def output(item: MessageHistoryItem) -> rx.Component:
line_height="1.75", line_height="1.75",
word_wrap="break-word", word_wrap="break-word",
word_break="break-all", word_break="break-all",
white_space="pre-line",
), ),
"strong": lambda text: rx.text( "strong": lambda text: rx.text(
text, text,
@ -454,7 +490,6 @@ def output(item: MessageHistoryItem) -> rx.Component:
font_weight="var(--prismui-font-weight-3)", font_weight="var(--prismui-font-weight-3)",
word_wrap="break-word", word_wrap="break-word",
word_break="break-all", word_break="break-all",
white_space="pre-line",
), ),
"h2": lambda text: rx.text( "h2": lambda text: rx.text(
text, text,
@ -464,7 +499,6 @@ def output(item: MessageHistoryItem) -> rx.Component:
font_weight="var(--prismui-font-weight-3)", font_weight="var(--prismui-font-weight-3)",
word_wrap="break-word", word_wrap="break-word",
word_break="break-all", word_break="break-all",
white_space="pre-line",
), ),
"h3": lambda text: rx.text( "h3": lambda text: rx.text(
text, text,
@ -473,7 +507,6 @@ def output(item: MessageHistoryItem) -> rx.Component:
font_weight="var(--prismui-font-weight-3)", font_weight="var(--prismui-font-weight-3)",
word_wrap="break-word", word_wrap="break-word",
word_break="break-all", word_break="break-all",
white_space="pre-line",
), ),
"ul": lambda children: rx.vstack( "ul": lambda children: rx.vstack(
children, children,
@ -500,7 +533,6 @@ def output(item: MessageHistoryItem) -> rx.Component:
line_height="1.75", line_height="1.75",
word_wrap="break-word", word_wrap="break-word",
word_break="break-all", word_break="break-all",
white_space="pre-line",
), ),
align_items="center", align_items="center",
justify_content="flex-start", justify_content="flex-start",
@ -535,6 +567,7 @@ def output(item: MessageHistoryItem) -> rx.Component:
), ),
}, },
width="100%", width="100%",
margin_bottom="8px",
) )
@ -550,6 +583,7 @@ def message_history_item(item: MessageHistoryItem) -> rx.Component:
item.type, item.type,
(MessageType.USER_PROMPT, user_prompt(item)), (MessageType.USER_PROMPT, user_prompt(item)),
(MessageType.THINKING, thinking(item)), (MessageType.THINKING, thinking(item)),
(MessageType.TOOL_CALL, tool_call(item)),
(MessageType.OUTPUT, output(item)), (MessageType.OUTPUT, output(item)),
), ),
padding="0 16px", padding="0 16px",

View File

@ -320,7 +320,7 @@ class ConversationState(rx.State):
# 运行并流式输出事件 # 运行并流式输出事件
stream_events = run_stream_events( stream_events = run_stream_events(
deps=dict(conversation.workflow.deps), deps=conversation.workflow.deps,
user_prompt=user_prompt, user_prompt=user_prompt,
message_history=await db_state.get_message_history( message_history=await db_state.get_message_history(
conversation_id=self.actived_conversation_id conversation_id=self.actived_conversation_id
@ -348,6 +348,7 @@ class ConversationState(rx.State):
# 若等待流式输出则将等待流式输出设置为否 # 若等待流式输出则将等待流式输出设置为否
if conversation.awaiting_stream: if conversation.awaiting_stream:
conversation.awaiting_stream = False conversation.awaiting_stream = False
print(event)
match event: match event:
# ========== 开始事件 ========== # ========== 开始事件 ==========
case PartStartEvent( case PartStartEvent(
@ -452,6 +453,8 @@ class ConversationState(rx.State):
message = conversation.messages[ message = conversation.messages[
tool_name_map_to_message_id[tool_name] tool_name_map_to_message_id[tool_name]
] ]
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
@ -516,7 +519,7 @@ class ConversationState(rx.State):
workflow = Workflow(type=type, deps=deps) workflow = Workflow(type=type, deps=deps)
# 设置用户提示词 # 设置用户提示词
self.set_user_prompt( self.set_user_prompt(
f"帮我查询并预定在 {deps.date.strftime('%Y-%m-%d')}{deps.origin_airport_code}{deps.destination_airport_code} 的航班" f"帮我查询并预定在 {workflow.deps.date.strftime('%Y-%m-%d')}{workflow.deps.origin_airport_code}{workflow.deps.destination_airport_code} 的航班"
) )
conversation.workflow = workflow conversation.workflow = workflow

View File

@ -299,8 +299,8 @@ class DatabaseState(rx.State):
) # 将 RunUsage 序列化为 JSON 字符串 ) # 将 RunUsage 序列化为 JSON 字符串
if isinstance(workflow, Workflow): if isinstance(workflow, Workflow):
record.workflow = workflow_dump_json( record.workflow = workflow_dump_json(
Workflow(**asdict(workflow)) Workflow(**workflow.model_dump(mode="json"))
) # 将 Workflow 序列化为 JSON 字符串 ) # 将 Workflow 序列化为 JSON 字符串(因 workflow 基于 pydantic 建模,故使用 model_dump 方法转为字典)
await session.commit() await session.commit()
async def create_message_record( async def create_message_record(

View File

@ -202,7 +202,7 @@ async def search_flights(ctx: RunContext[Deps]) -> list[Flight]:
return searched_flights return searched_flights
@agent.tool(name="预定航班", requires_approval=True) @agent.tool(name="预定航班")
async def book_flight(ctx: RunContext[Deps]) -> Flight | NoResult: async def book_flight(ctx: RunContext[Deps]) -> Flight | NoResult:
""" """
预定航班 预定航班
@ -211,7 +211,7 @@ async def book_flight(ctx: RunContext[Deps]) -> Flight | NoResult:
raise ModelRetry("必须先使用 search_flights 查询航班") raise ModelRetry("必须先使用 search_flights 查询航班")
if not ctx.tool_call_approved: if not ctx.tool_call_approved:
raise ApprovalRequired() raise ApprovalRequired(metadata={"content": "您要预定哪班航班?"})
# 提取到的航班号 # 提取到的航班号
extracted_flight_number = ( extracted_flight_number = (
@ -286,10 +286,6 @@ async def run_stream_events(
""" """
运行并流式输出事件 运行并流式输出事件
""" """
# 构建智能体运行事件
yield AgentRunEvent(
content=f"正在预定 {deps.date.strftime('%Y-%m-%d')}{deps.origin_airport_code}{deps.destination_airport_code} 的航班"
)
tool_names = set() tool_names = set()
async with agent.run_stream_events( async with agent.run_stream_events(
user_prompt=user_prompt, user_prompt=user_prompt,
@ -322,9 +318,9 @@ async def run_stream_events(
case "查询航班": case "查询航班":
# 查询到的航班 # 查询到的航班
searched_flights = cast(list[Flight], content) searched_flights = cast(list[Flight], content)
event.part.content = f"已查询到 **{len(searched_flights)} 班航班**\n" event.part.content = f"已查询到 **{len(searched_flights)}** 班航班:\n\n"
for searched_flight in searched_flights: for searched_flight in searched_flights:
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" 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 "预定航班": case "预定航班":
@ -334,7 +330,7 @@ async def run_stream_events(
) )
if isinstance(content, Flight): 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" 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 yield event
case AgentRunResultEvent( case AgentRunResultEvent(

View File

@ -84,4 +84,25 @@ pre, pre code {
box-shadow: 12px 0 var(--prismui-color-9), -12px 0 var(--prismui-color-5); box-shadow: 12px 0 var(--prismui-color-9), -12px 0 var(--prismui-color-5);
background: var(--prismui-color-5); background: var(--prismui-color-5);
} }
}
.is_running {
/* 使用主题变量:普通文字色 --prismui-color-3高光使用主题蓝色 --prismui-color-5 */
background: linear-gradient(
90deg,
var(--prismui-color-3) 0%,
var(--prismui-color-3) 35%,
var(--prismui-color-7) 50%,
var(--prismui-color-3) 65%,
var(--prismui-color-3) 100%
);
background-size: 200% 100%;
background-clip: text;
-webkit-background-clip: text;
color: transparent !important;
animation: shimmer 2.7s infinite linear;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
} }

Binary file not shown.