diff --git a/agent/application/pages/conversation.py b/agent/application/pages/conversation.py index e87a1c8..09a390e 100644 --- a/agent/application/pages/conversation.py +++ b/agent/application/pages/conversation.py @@ -396,7 +396,7 @@ def thinking(item: MessageHistoryItem) -> rx.Component: rx.box( rx.cond( item.is_shown, - rx.text( + rx.markdown( item.content, width="100%", 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: """ 输出 @@ -432,7 +469,6 @@ def output(item: MessageHistoryItem) -> rx.Component: line_height="1.75", word_wrap="break-word", word_break="break-all", - white_space="pre-line", ), "strong": lambda text: rx.text( text, @@ -454,7 +490,6 @@ def output(item: MessageHistoryItem) -> rx.Component: font_weight="var(--prismui-font-weight-3)", word_wrap="break-word", word_break="break-all", - white_space="pre-line", ), "h2": lambda text: rx.text( text, @@ -464,7 +499,6 @@ def output(item: MessageHistoryItem) -> rx.Component: font_weight="var(--prismui-font-weight-3)", word_wrap="break-word", word_break="break-all", - white_space="pre-line", ), "h3": lambda text: rx.text( text, @@ -473,7 +507,6 @@ def output(item: MessageHistoryItem) -> rx.Component: font_weight="var(--prismui-font-weight-3)", word_wrap="break-word", word_break="break-all", - white_space="pre-line", ), "ul": lambda children: rx.vstack( children, @@ -500,7 +533,6 @@ def output(item: MessageHistoryItem) -> rx.Component: line_height="1.75", word_wrap="break-word", word_break="break-all", - white_space="pre-line", ), align_items="center", justify_content="flex-start", @@ -535,6 +567,7 @@ def output(item: MessageHistoryItem) -> rx.Component: ), }, width="100%", + margin_bottom="8px", ) @@ -550,6 +583,7 @@ def message_history_item(item: MessageHistoryItem) -> rx.Component: item.type, (MessageType.USER_PROMPT, user_prompt(item)), (MessageType.THINKING, thinking(item)), + (MessageType.TOOL_CALL, tool_call(item)), (MessageType.OUTPUT, output(item)), ), padding="0 16px", diff --git a/agent/application/states/conversation.py b/agent/application/states/conversation.py index 897da2b..f888cd4 100644 --- a/agent/application/states/conversation.py +++ b/agent/application/states/conversation.py @@ -320,7 +320,7 @@ class ConversationState(rx.State): # 运行并流式输出事件 stream_events = run_stream_events( - deps=dict(conversation.workflow.deps), + deps=conversation.workflow.deps, user_prompt=user_prompt, message_history=await db_state.get_message_history( conversation_id=self.actived_conversation_id @@ -348,6 +348,7 @@ class ConversationState(rx.State): # 若等待流式输出则将等待流式输出设置为否 if conversation.awaiting_stream: conversation.awaiting_stream = False + print(event) match event: # ========== 开始事件 ========== case PartStartEvent( @@ -452,6 +453,8 @@ class ConversationState(rx.State): message = conversation.messages[ tool_name_map_to_message_id[tool_name] ] + message.is_running = False + yield # 通知前端更新渲染 message.title = f"{tool_name}已完成" message.content = cast( str, content @@ -516,7 +519,7 @@ class ConversationState(rx.State): workflow = Workflow(type=type, deps=deps) # 设置用户提示词 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 diff --git a/agent/application/states/database.py b/agent/application/states/database.py index 44baf05..c6bbf14 100644 --- a/agent/application/states/database.py +++ b/agent/application/states/database.py @@ -299,8 +299,8 @@ class DatabaseState(rx.State): ) # 将 RunUsage 序列化为 JSON 字符串 if isinstance(workflow, Workflow): record.workflow = workflow_dump_json( - Workflow(**asdict(workflow)) - ) # 将 Workflow 序列化为 JSON 字符串 + Workflow(**workflow.model_dump(mode="json")) + ) # 将 Workflow 序列化为 JSON 字符串(因 workflow 基于 pydantic 建模,故使用 model_dump 方法转为字典) await session.commit() async def create_message_record( diff --git a/agent/application/workshop/book_flight.py b/agent/application/workshop/book_flight.py index 85a2a9c..4cfa70a 100644 --- a/agent/application/workshop/book_flight.py +++ b/agent/application/workshop/book_flight.py @@ -202,7 +202,7 @@ async def search_flights(ctx: RunContext[Deps]) -> list[Flight]: return searched_flights -@agent.tool(name="预定航班", requires_approval=True) +@agent.tool(name="预定航班") 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 查询航班") if not ctx.tool_call_approved: - raise ApprovalRequired() + raise ApprovalRequired(metadata={"content": "您要预定哪班航班?"}) # 提取到的航班号 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() async with agent.run_stream_events( user_prompt=user_prompt, @@ -322,9 +318,9 @@ async def run_stream_events( case "查询航班": # 查询到的航班 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: - 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 case "预定航班": @@ -334,7 +330,7 @@ async def run_stream_events( ) 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 case AgentRunResultEvent( diff --git a/agent/assets/styles.css b/agent/assets/styles.css index 015f25f..54e044a 100644 --- a/agent/assets/styles.css +++ b/agent/assets/styles.css @@ -84,4 +84,25 @@ pre, pre code { box-shadow: 12px 0 var(--prismui-color-9), -12px 0 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; } } \ No newline at end of file diff --git a/agent/database.db b/agent/database.db index 5f320ec..63c6843 100644 Binary files a/agent/database.db and b/agent/database.db differ