feat(memory): 更新长期记忆工具提示,增强用户使用指导和检索流程
feat(gateway): 修改 WebSocket 处理逻辑以支持会话 ID 的字符串处理
This commit is contained in:
parent
a3ae8acde5
commit
73840c608c
@ -21,7 +21,7 @@ use std::time::Instant;
|
||||
const MAX_TOOL_RESULT_CHARS: usize = 16_000;
|
||||
/// Minimum characters to keep when truncating
|
||||
const TRUNCATION_SUFFIX_LEN: usize = 200;
|
||||
const MEMORY_TOOL_USAGE_SYSTEM_PROMPT: &str = "你可以在处理任务过程中使用长期记忆工具。读取记忆时,优先使用 memory_search:当用户的任务描述缺少相关指代,上下文存在模糊不清,执行任务需要用户长期偏好、稳定事实、历史决策、持续任务上下文时,先 search;已知 namespace/key 时可用 get;需要浏览最近记忆时可用 list。写入或修改记忆时,再使用 memory_manage。仅在遇到高价值且未来仍有用的信息时写入记忆:用户长期偏好、稳定事实、用户对你的纠正、持续任务/项目上下文、明确决策。不要保存一次性工具结果、临时列表、敏感凭证或不确定推测。写入时优先使用规范 namespace:preferences、profile、tasks、decisions,并优先调用 memory_manage(action='put');同一 namespace/key 可直接覆盖更新。检索时应提供 queries 数组,尽量同时放入中文关键词、英文别名,以及可能的 snake_case memory_key 词,例如 queries=['email', '邮件', 'email_folder_preference']。";
|
||||
const MEMORY_TOOL_USAGE_SYSTEM_PROMPT: &str = "在绝大多数请求开始时,你都应先使用长期记忆检索工具 memory_search 来召回相关上下文,然后再决定如何回答或是否需要写入记忆。默认流程是:先用 memory_search(action='search');只有在你已经明确知道 namespace 和 key 时才改用 get;只有在需要浏览最近几条记忆时才用 list。即使用户没有明确提到“记忆”或“偏好”,只要请求可能与用户长期偏好、稳定事实、历史决策、持续任务或项目上下文有关,就应先搜记忆。仅以下少数情况可跳过记忆搜索:纯寒暄、一次性简单计算、完全不依赖用户历史的直接事实问答。写入或修改记忆时,再使用 memory_manage。仅在遇到高价值且未来仍有用的信息时写入记忆:用户长期偏好、稳定事实、用户对你的纠正、持续任务/项目上下文、明确决策。不要保存一次性工具结果、临时列表、敏感凭证或不确定推测。写入时优先使用规范 namespace:preferences、profile、tasks、decisions,并优先调用 memory_manage(action='put');同一 namespace/key 可直接覆盖更新。检索时应提供 queries 数组,尽量同时放入中文关键词、英文别名,以及可能的 snake_case memory_key 词,例如 queries=['email', '邮件', 'email_folder_preference']。如果你决定跳过记忆搜索,应先确认当前请求确实属于上述少数例外,而不是因为你忘了检索。";
|
||||
const PENDING_USER_ACTION_MARKER: &str = "__PICOBOT_PENDING_USER_ACTION__";
|
||||
const DEFAULT_PENDING_ASSISTANT_MESSAGE: &str = "工具已经启动并进入等待用户操作的状态。请先完成外部操作,完成后直接告诉我继续。";
|
||||
|
||||
|
||||
@ -80,7 +80,7 @@ async fn handle_socket(ws: WebSocket, state: Arc<GatewayState>) {
|
||||
return;
|
||||
}
|
||||
|
||||
let runtime_session_id = session.lock().await.id;
|
||||
let runtime_session_id = session.lock().await.id.to_string();
|
||||
let mut current_session_id = initial_record.id.clone();
|
||||
tracing::info!(runtime_session_id = %runtime_session_id, session_id = %current_session_id, "CLI session established");
|
||||
|
||||
@ -95,7 +95,7 @@ async fn handle_socket(ws: WebSocket, state: Arc<GatewayState>) {
|
||||
let (mut ws_sender, mut ws_receiver) = ws.split();
|
||||
|
||||
let mut receiver = receiver;
|
||||
let session_id_for_sender = runtime_session_id;
|
||||
let session_id_for_sender = runtime_session_id.clone();
|
||||
tokio::spawn(async move {
|
||||
while let Some(msg) = receiver.recv().await {
|
||||
if let Ok(text) = serialize_outbound(&msg) {
|
||||
@ -114,7 +114,13 @@ async fn handle_socket(ws: WebSocket, state: Arc<GatewayState>) {
|
||||
let text = text.to_string();
|
||||
match parse_inbound(&text) {
|
||||
Ok(inbound) => {
|
||||
if let Err(e) = handle_inbound(&state, &session, &mut current_session_id, inbound).await {
|
||||
if let Err(e) = handle_inbound(
|
||||
&state,
|
||||
&session,
|
||||
&runtime_session_id,
|
||||
&mut current_session_id,
|
||||
inbound,
|
||||
).await {
|
||||
tracing::warn!(error = %e, session_id = %current_session_id, "Failed to handle inbound message");
|
||||
let _ = session
|
||||
.lock()
|
||||
@ -232,12 +238,14 @@ fn should_display_message_to_user(show_tool_results: bool, message: &ChatMessage
|
||||
async fn handle_inbound(
|
||||
state: &Arc<GatewayState>,
|
||||
session: &Arc<Mutex<Session>>,
|
||||
runtime_session_id: &str,
|
||||
current_session_id: &mut String,
|
||||
inbound: WsInbound,
|
||||
) -> Result<(), crate::agent::AgentError> {
|
||||
match inbound {
|
||||
WsInbound::UserInput { content, chat_id, .. } => {
|
||||
WsInbound::UserInput { content, chat_id, sender_id, .. } => {
|
||||
let chat_id = chat_id.unwrap_or_else(|| current_session_id.clone());
|
||||
let sender_id = resolve_ws_sender_id(sender_id.as_deref(), runtime_session_id);
|
||||
let mut session_guard = session.lock().await;
|
||||
|
||||
session_guard.ensure_persistent_session(&chat_id)?;
|
||||
@ -280,7 +288,7 @@ async fn handle_inbound(
|
||||
show_tool_results: state.config.gateway.show_tool_results,
|
||||
});
|
||||
let agent = session_guard
|
||||
.create_agent(&chat_id, None, Some(&user_message_id))?
|
||||
.create_agent(&chat_id, Some(&sender_id), Some(&user_message_id))?
|
||||
.with_emitted_message_handler(live_emitter);
|
||||
match agent.process(history).await {
|
||||
Ok(result) => {
|
||||
@ -434,10 +442,23 @@ async fn handle_inbound(
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_ws_sender_id(sender_id: Option<&str>, runtime_session_id: &str) -> String {
|
||||
sender_id
|
||||
.map(str::trim)
|
||||
.filter(|sender_id| !sender_id.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap_or_else(|| runtime_session_id.to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::agent::EmittedMessageHandler;
|
||||
use super::{WsToolCallEmitter, should_display_message_to_user, ws_outbound_from_chat_message};
|
||||
use super::{
|
||||
WsToolCallEmitter,
|
||||
resolve_ws_sender_id,
|
||||
should_display_message_to_user,
|
||||
ws_outbound_from_chat_message,
|
||||
};
|
||||
use crate::bus::ChatMessage;
|
||||
use crate::bus::message::ToolMessageState;
|
||||
use crate::providers::ToolCall;
|
||||
@ -528,6 +549,18 @@ mod tests {
|
||||
assert!(should_display_message_to_user(true, &completed));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_ws_sender_id_prefers_inbound_sender() {
|
||||
assert_eq!(resolve_ws_sender_id(Some("user-42"), "runtime-1"), "user-42");
|
||||
assert_eq!(resolve_ws_sender_id(Some(" user-42 "), "runtime-1"), "user-42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_ws_sender_id_falls_back_to_runtime_session_id() {
|
||||
assert_eq!(resolve_ws_sender_id(None, "runtime-1"), "runtime-1");
|
||||
assert_eq!(resolve_ws_sender_id(Some(" "), "runtime-1"), "runtime-1");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_ws_tool_call_emitter_hides_completed_tool_results_when_disabled() {
|
||||
let (sender, mut receiver) = mpsc::channel(4);
|
||||
|
||||
@ -23,7 +23,7 @@ impl Tool for MemoryManageTool {
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Create, update, or delete long-term user memories stored in SQLite. Supports actions: put, update, delete. Use memory_search for all retrieval, including search, get, and list. Memories are scoped to the current channel and sender, and record the originating session/message when available."
|
||||
"Create, update, or delete long-term user memories stored in SQLite. Supports actions: put, update, delete. Use memory_search as the default retrieval path before answering most requests, and use memory_search for all retrieval actions including search, get, and list. Only call this tool when you have determined that a high-value long-term memory should be created, overwritten, updated, or deleted. Memories are scoped to the current channel and sender, and record the originating session/message when available."
|
||||
}
|
||||
|
||||
fn parameters_schema(&self) -> serde_json::Value {
|
||||
|
||||
@ -23,7 +23,7 @@ impl Tool for MemorySearchTool {
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Search and read long-term user memories stored in SQLite. Use this tool when you need prior preferences, stable facts, historical decisions, or ongoing task context. This tool is read-only and supports three actions: search for multi-keyword recall, get for exact namespace/key lookup, and list for browsing recent memories. Prefer this tool over memory_manage when you only need to retrieve memory."
|
||||
"Search and read long-term user memories stored in SQLite. This is the default entry point for memory retrieval and should usually be the first memory tool you call at the start of a request, unless the request is clearly a simple greeting, a one-off calculation, or a direct fact question that does not depend on user history. Use it to recall prior preferences, stable facts, historical decisions, and ongoing task context. This tool is read-only and supports three actions: search for multi-keyword recall, get for exact namespace/key lookup, and list for browsing recent memories. Prefer this tool over memory_manage whenever you only need to retrieve memory."
|
||||
}
|
||||
|
||||
fn parameters_schema(&self) -> serde_json::Value {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user