feat(gateway): 添加 show_tool_results 配置以控制工具结果显示
feat(session): 更新 BusToolCallEmitter 以支持工具结果显示控制 feat(ws): 更新 WsToolCallEmitter 以支持工具结果显示控制
This commit is contained in:
parent
65abf017a1
commit
302e6ef6b9
@ -356,7 +356,18 @@ impl OutboundMessage {
|
|||||||
match message.role.as_str() {
|
match message.role.as_str() {
|
||||||
"assistant" => {
|
"assistant" => {
|
||||||
if let Some(tool_calls) = &message.tool_calls {
|
if let Some(tool_calls) = &message.tool_calls {
|
||||||
tool_calls
|
let mut outbound = Vec::new();
|
||||||
|
if !message.content.trim().is_empty() {
|
||||||
|
outbound.push(Self::assistant(
|
||||||
|
channel.to_string(),
|
||||||
|
chat_id.to_string(),
|
||||||
|
message.content.clone(),
|
||||||
|
reply_to.clone(),
|
||||||
|
metadata.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
outbound.extend(tool_calls
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tool_call| {
|
.map(|tool_call| {
|
||||||
Self::tool_call(
|
Self::tool_call(
|
||||||
@ -369,7 +380,8 @@ impl OutboundMessage {
|
|||||||
metadata.clone(),
|
metadata.clone(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect()
|
);
|
||||||
|
outbound
|
||||||
} else {
|
} else {
|
||||||
vec![Self::assistant(
|
vec![Self::assistant(
|
||||||
channel.to_string(),
|
channel.to_string(),
|
||||||
@ -488,6 +500,32 @@ mod tests {
|
|||||||
assert_eq!(outbound[1].content, "### file_read\n- path: README.md");
|
assert_eq!(outbound[1].content, "### file_read\n- path: README.md");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_chat_message_keeps_assistant_content_when_tool_calls_exist() {
|
||||||
|
let message = ChatMessage::assistant_with_tool_calls(
|
||||||
|
"日报已整理完成。",
|
||||||
|
vec![ToolCall {
|
||||||
|
id: "call-1".to_string(),
|
||||||
|
name: "memory_manage".to_string(),
|
||||||
|
arguments: json!({"action": "put"}),
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
|
||||||
|
let outbound = OutboundMessage::from_chat_message(
|
||||||
|
"feishu",
|
||||||
|
"chat-1",
|
||||||
|
None,
|
||||||
|
&HashMap::new(),
|
||||||
|
&message,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(outbound.len(), 2);
|
||||||
|
assert_eq!(outbound[0].event_kind, OutboundEventKind::AssistantResponse);
|
||||||
|
assert_eq!(outbound[0].content, "日报已整理完成。");
|
||||||
|
assert_eq!(outbound[1].event_kind, OutboundEventKind::ToolCall);
|
||||||
|
assert_eq!(outbound[1].tool_name.as_deref(), Some("memory_manage"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_chat_message_includes_tool_result() {
|
fn test_from_chat_message_includes_tool_result() {
|
||||||
let message = ChatMessage::tool("call-9", "calculator", "2");
|
let message = ChatMessage::tool("call-9", "calculator", "2");
|
||||||
|
|||||||
@ -87,6 +87,7 @@ impl GatewayState {
|
|||||||
inbound.channel.clone(),
|
inbound.channel.clone(),
|
||||||
inbound.chat_id.clone(),
|
inbound.chat_id.clone(),
|
||||||
inbound.forwarded_metadata.clone(),
|
inbound.forwarded_metadata.clone(),
|
||||||
|
session_manager.show_tool_results(),
|
||||||
));
|
));
|
||||||
match session_manager.handle_message(
|
match session_manager.handle_message(
|
||||||
&inbound.channel,
|
&inbound.channel,
|
||||||
|
|||||||
@ -40,6 +40,7 @@ pub struct BusToolCallEmitter {
|
|||||||
channel_name: String,
|
channel_name: String,
|
||||||
chat_id: String,
|
chat_id: String,
|
||||||
metadata: HashMap<String, String>,
|
metadata: HashMap<String, String>,
|
||||||
|
show_tool_results: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BusToolCallEmitter {
|
impl BusToolCallEmitter {
|
||||||
@ -48,12 +49,14 @@ impl BusToolCallEmitter {
|
|||||||
channel_name: impl Into<String>,
|
channel_name: impl Into<String>,
|
||||||
chat_id: impl Into<String>,
|
chat_id: impl Into<String>,
|
||||||
metadata: HashMap<String, String>,
|
metadata: HashMap<String, String>,
|
||||||
|
show_tool_results: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bus,
|
bus,
|
||||||
channel_name: channel_name.into(),
|
channel_name: channel_name.into(),
|
||||||
chat_id: chat_id.into(),
|
chat_id: chat_id.into(),
|
||||||
metadata,
|
metadata,
|
||||||
|
show_tool_results,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,6 +64,10 @@ impl BusToolCallEmitter {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl EmittedMessageHandler for BusToolCallEmitter {
|
impl EmittedMessageHandler for BusToolCallEmitter {
|
||||||
async fn handle(&self, message: ChatMessage) {
|
async fn handle(&self, message: ChatMessage) {
|
||||||
|
if !should_display_message_to_user(self.show_tool_results, &message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for outbound in OutboundMessage::from_chat_message(
|
for outbound in OutboundMessage::from_chat_message(
|
||||||
&self.channel_name,
|
&self.channel_name,
|
||||||
&self.chat_id,
|
&self.chat_id,
|
||||||
@ -453,6 +460,10 @@ impl SessionManager {
|
|||||||
self.store.clone()
|
self.store.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn show_tool_results(&self) -> bool {
|
||||||
|
self.show_tool_results
|
||||||
|
}
|
||||||
|
|
||||||
pub fn skills(&self) -> Arc<SkillRuntime> {
|
pub fn skills(&self) -> Arc<SkillRuntime> {
|
||||||
self.skills.clone()
|
self.skills.clone()
|
||||||
}
|
}
|
||||||
@ -699,6 +710,7 @@ fn should_display_message_to_user(show_tool_results: bool, message: &ChatMessage
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::bus::MessageBus;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
@ -733,6 +745,26 @@ mod tests {
|
|||||||
assert!(should_display_message_to_user(true, &completed));
|
assert!(should_display_message_to_user(true, &completed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_bus_tool_call_emitter_hides_completed_tool_results_when_disabled() {
|
||||||
|
let bus = MessageBus::new(4);
|
||||||
|
let emitter = BusToolCallEmitter::new(
|
||||||
|
bus.clone(),
|
||||||
|
"feishu",
|
||||||
|
"chat-1",
|
||||||
|
HashMap::new(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
emitter
|
||||||
|
.handle(ChatMessage::tool("call-1", "calculator", "2"))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(tokio::time::timeout(std::time::Duration::from_millis(50), bus.consume_outbound())
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_in_chat_command_aliases() {
|
fn test_parse_in_chat_command_aliases() {
|
||||||
assert_eq!(parse_in_chat_command("/new"), Some(InChatCommand::FreshConversation));
|
assert_eq!(parse_in_chat_command("/new"), Some(InChatCommand::FreshConversation));
|
||||||
|
|||||||
@ -13,11 +13,16 @@ use super::{GatewayState, session::{Session, handle_in_chat_command}};
|
|||||||
|
|
||||||
struct WsToolCallEmitter {
|
struct WsToolCallEmitter {
|
||||||
sender: mpsc::Sender<WsOutbound>,
|
sender: mpsc::Sender<WsOutbound>,
|
||||||
|
show_tool_results: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl EmittedMessageHandler for WsToolCallEmitter {
|
impl EmittedMessageHandler for WsToolCallEmitter {
|
||||||
async fn handle(&self, message: ChatMessage) {
|
async fn handle(&self, message: ChatMessage) {
|
||||||
|
if !should_display_message_to_user(self.show_tool_results, &message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for outbound in ws_outbound_from_chat_message(&message) {
|
for outbound in ws_outbound_from_chat_message(&message) {
|
||||||
let _ = self.sender.send(outbound).await;
|
let _ = self.sender.send(outbound).await;
|
||||||
}
|
}
|
||||||
@ -162,7 +167,16 @@ fn ws_outbound_from_chat_message(message: &ChatMessage) -> Vec<WsOutbound> {
|
|||||||
match message.role.as_str() {
|
match message.role.as_str() {
|
||||||
"assistant" => {
|
"assistant" => {
|
||||||
if let Some(tool_calls) = &message.tool_calls {
|
if let Some(tool_calls) = &message.tool_calls {
|
||||||
tool_calls
|
let mut outbound = Vec::new();
|
||||||
|
if !message.content.trim().is_empty() {
|
||||||
|
outbound.push(WsOutbound::AssistantResponse {
|
||||||
|
id: message.id.clone(),
|
||||||
|
content: message.content.clone(),
|
||||||
|
role: message.role.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
outbound.extend(tool_calls
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tool_call| WsOutbound::ToolCall {
|
.map(|tool_call| WsOutbound::ToolCall {
|
||||||
id: message.id.clone(),
|
id: message.id.clone(),
|
||||||
@ -172,7 +186,8 @@ fn ws_outbound_from_chat_message(message: &ChatMessage) -> Vec<WsOutbound> {
|
|||||||
content: format_tool_call_content(&tool_call.name, &tool_call.arguments),
|
content: format_tool_call_content(&tool_call.name, &tool_call.arguments),
|
||||||
role: message.role.clone(),
|
role: message.role.clone(),
|
||||||
})
|
})
|
||||||
.collect()
|
);
|
||||||
|
outbound
|
||||||
} else {
|
} else {
|
||||||
vec![WsOutbound::AssistantResponse {
|
vec![WsOutbound::AssistantResponse {
|
||||||
id: message.id.clone(),
|
id: message.id.clone(),
|
||||||
@ -262,6 +277,7 @@ async fn handle_inbound(
|
|||||||
|
|
||||||
let live_emitter = Arc::new(WsToolCallEmitter {
|
let live_emitter = Arc::new(WsToolCallEmitter {
|
||||||
sender: session_guard.user_tx.clone(),
|
sender: session_guard.user_tx.clone(),
|
||||||
|
show_tool_results: state.config.gateway.show_tool_results,
|
||||||
});
|
});
|
||||||
let agent = session_guard
|
let agent = session_guard
|
||||||
.create_agent(&chat_id, None, Some(&user_message_id))?
|
.create_agent(&chat_id, None, Some(&user_message_id))?
|
||||||
@ -420,12 +436,14 @@ async fn handle_inbound(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{should_display_message_to_user, ws_outbound_from_chat_message};
|
use crate::agent::EmittedMessageHandler;
|
||||||
|
use super::{WsToolCallEmitter, should_display_message_to_user, ws_outbound_from_chat_message};
|
||||||
use crate::bus::ChatMessage;
|
use crate::bus::ChatMessage;
|
||||||
use crate::bus::message::ToolMessageState;
|
use crate::bus::message::ToolMessageState;
|
||||||
use crate::providers::ToolCall;
|
use crate::providers::ToolCall;
|
||||||
use crate::protocol::WsOutbound;
|
use crate::protocol::WsOutbound;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ws_outbound_from_chat_message_expands_tool_calls() {
|
fn test_ws_outbound_from_chat_message_expands_tool_calls() {
|
||||||
@ -452,6 +470,24 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ws_outbound_keeps_assistant_content_when_tool_calls_exist() {
|
||||||
|
let message = ChatMessage::assistant_with_tool_calls(
|
||||||
|
"日报已整理完成。",
|
||||||
|
vec![ToolCall {
|
||||||
|
id: "call-1".to_string(),
|
||||||
|
name: "memory_manage".to_string(),
|
||||||
|
arguments: json!({"action": "put"}),
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
|
||||||
|
let outbound = ws_outbound_from_chat_message(&message);
|
||||||
|
|
||||||
|
assert_eq!(outbound.len(), 2);
|
||||||
|
assert!(matches!(outbound[0], WsOutbound::AssistantResponse { .. }));
|
||||||
|
assert!(matches!(outbound[1], WsOutbound::ToolCall { .. }));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ws_outbound_from_chat_message_includes_tool_results() {
|
fn test_ws_outbound_from_chat_message_includes_tool_results() {
|
||||||
let message = ChatMessage::tool("call-1", "calculator", "2");
|
let message = ChatMessage::tool("call-1", "calculator", "2");
|
||||||
@ -491,4 +527,21 @@ mod tests {
|
|||||||
assert!(should_display_message_to_user(false, &pending));
|
assert!(should_display_message_to_user(false, &pending));
|
||||||
assert!(should_display_message_to_user(true, &completed));
|
assert!(should_display_message_to_user(true, &completed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_ws_tool_call_emitter_hides_completed_tool_results_when_disabled() {
|
||||||
|
let (sender, mut receiver) = mpsc::channel(4);
|
||||||
|
let emitter = WsToolCallEmitter {
|
||||||
|
sender,
|
||||||
|
show_tool_results: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
emitter
|
||||||
|
.handle(ChatMessage::tool("call-1", "calculator", "2"))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(tokio::time::timeout(std::time::Duration::from_millis(50), receiver.recv())
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user