feat: 优化 todo_write 工具的 merge 参数描述,调整默认值逻辑并添加测试
This commit is contained in:
parent
4742a6aed9
commit
bf1549b88b
@ -27,8 +27,8 @@ const TODO_WRITE_INSTRUCTIONS: &str = r#"
|
|||||||
- 不需要为简单的单步操作(如回答一个问题、读取一个文件)创建 todo
|
- 不需要为简单的单步操作(如回答一个问题、读取一个文件)创建 todo
|
||||||
|
|
||||||
### merge 参数
|
### merge 参数
|
||||||
- `merge: false`(默认):全量替换 — 只传入需要追踪的 todo,不在列表中的项将被移除
|
- `merge: true`(默认,推荐):增量更新 — 只传入需要添加或更新的项,未提及的项保持不变。**绝大多数情况使用默认即可**
|
||||||
- `merge: true`(推荐):增量更新 — 只传入需要添加或更新的项,未提及的项保持不变。**绝大多数情况应该使用 merge=true**
|
- `merge: false`:全量替换 — 只传入需要追踪的 todo,不在列表中的项将被移除
|
||||||
|
|
||||||
### 状态语义
|
### 状态语义
|
||||||
- `pending` — 尚未开始
|
- `pending` — 尚未开始
|
||||||
|
|||||||
@ -26,6 +26,7 @@ pub(crate) fn ws_outbound_from_chat_message(message: &ChatMessage) -> Vec<WsOutb
|
|||||||
topic_id: None,
|
topic_id: None,
|
||||||
timestamp: None,
|
timestamp: None,
|
||||||
reasoning_content: message.reasoning_content.clone(),
|
reasoning_content: message.reasoning_content.clone(),
|
||||||
|
user_message_id: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ pub(crate) fn ws_outbound_from_chat_message(message: &ChatMessage) -> Vec<WsOutb
|
|||||||
topic_id: None,
|
topic_id: None,
|
||||||
timestamp: None,
|
timestamp: None,
|
||||||
reasoning_content: tc_reasoning.clone(),
|
reasoning_content: tc_reasoning.clone(),
|
||||||
|
user_message_id: None,
|
||||||
}));
|
}));
|
||||||
outbound
|
outbound
|
||||||
} else {
|
} else {
|
||||||
@ -54,6 +56,7 @@ pub(crate) fn ws_outbound_from_chat_message(message: &ChatMessage) -> Vec<WsOutb
|
|||||||
topic_id: None,
|
topic_id: None,
|
||||||
timestamp: None,
|
timestamp: None,
|
||||||
reasoning_content: message.reasoning_content.clone(),
|
reasoning_content: message.reasoning_content.clone(),
|
||||||
|
user_message_id: None,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,9 +77,9 @@ impl Tool for TodoWriteTool {
|
|||||||
|
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Manage a structured task list for tracking work within the current conversation. \
|
"Manage a structured task list for tracking work within the current conversation. \
|
||||||
Two modes: merge=false (default, full replacement — omitted items are removed); \
|
Two modes: merge=true (default, incremental — only send the items you want to add/update, \
|
||||||
merge=true (incremental — only send the items you want to add/update, \
|
previously existing items are preserved); \
|
||||||
previously existing items are preserved). \
|
merge=false (full replacement — omitted items are removed). \
|
||||||
Use when you have 3+ distinct steps to track. \
|
Use when you have 3+ distinct steps to track. \
|
||||||
Rules: only ONE in_progress at a time, complete work before marking completed, \
|
Rules: only ONE in_progress at a time, complete work before marking completed, \
|
||||||
every item requires an id (generate a short random string for new items)."
|
every item requires an id (generate a short random string for new items)."
|
||||||
@ -91,7 +91,7 @@ impl Tool for TodoWriteTool {
|
|||||||
"properties": {
|
"properties": {
|
||||||
"merge": {
|
"merge": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "false (default): full replacement — todos not in the list are removed. true: incremental — only send items you want to add or update, existing items not mentioned are preserved."
|
"description": "true (default): incremental — only send items you want to add or update, existing items not mentioned are preserved. false: full replacement — todos not in the list are removed."
|
||||||
},
|
},
|
||||||
"todos": {
|
"todos": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@ -156,7 +156,7 @@ impl Tool for TodoWriteTool {
|
|||||||
let merge_mode = args
|
let merge_mode = args
|
||||||
.get("merge")
|
.get("merge")
|
||||||
.and_then(|v| v.as_bool())
|
.and_then(|v| v.as_bool())
|
||||||
.unwrap_or(false);
|
.unwrap_or(true);
|
||||||
|
|
||||||
// 3. 读锁获取旧状态
|
// 3. 读锁获取旧状态
|
||||||
let old_items = {
|
let old_items = {
|
||||||
@ -754,11 +754,12 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// 只传入一个任务(任务B 被移除)
|
// 全量替换:只传入一个任务(任务B 被移除)
|
||||||
let result = tool
|
let result = tool
|
||||||
.execute_with_context(
|
.execute_with_context(
|
||||||
&context,
|
&context,
|
||||||
json!({
|
json!({
|
||||||
|
"merge": false,
|
||||||
"todos": [
|
"todos": [
|
||||||
{"id": "i1", "content": "任务A", "status": "in_progress"}
|
{"id": "i1", "content": "任务A", "status": "in_progress"}
|
||||||
]
|
]
|
||||||
@ -1054,11 +1055,12 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// merge=false(默认)— 只传一个,另一个被删
|
// 显式指定 merge=false — 只传一个,另一个被删
|
||||||
let result = tool
|
let result = tool
|
||||||
.execute_with_context(
|
.execute_with_context(
|
||||||
&context,
|
&context,
|
||||||
json!({
|
json!({
|
||||||
|
"merge": false,
|
||||||
"todos": [
|
"todos": [
|
||||||
{"id": "p1", "content": "任务A", "status": "in_progress"}
|
{"id": "p1", "content": "任务A", "status": "in_progress"}
|
||||||
]
|
]
|
||||||
@ -1140,4 +1142,45 @@ mod tests {
|
|||||||
assert!(!result.success);
|
assert!(!result.success);
|
||||||
assert!(result.error.unwrap().contains("missing or empty 'id'"));
|
assert!(result.error.unwrap().contains("missing or empty 'id'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_default_is_merge_mode() {
|
||||||
|
let state = test_state();
|
||||||
|
let tool = TodoWriteTool::new(state.clone());
|
||||||
|
let context = test_context();
|
||||||
|
|
||||||
|
// 先创建 2 个 todo
|
||||||
|
let _ = tool
|
||||||
|
.execute_with_context(
|
||||||
|
&context,
|
||||||
|
json!({
|
||||||
|
"todos": [
|
||||||
|
{"id": "x1", "content": "任务A", "status": "pending"},
|
||||||
|
{"id": "x2", "content": "任务B", "status": "pending"}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// 不传 merge 参数,只更新一项 — 默认应为 merge=true,旧项保留
|
||||||
|
let result = tool
|
||||||
|
.execute_with_context(
|
||||||
|
&context,
|
||||||
|
json!({
|
||||||
|
"todos": [
|
||||||
|
{"id": "x1", "content": "任务A", "status": "in_progress"}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
let output: serde_json::Value = serde_json::from_str(&result.output).unwrap();
|
||||||
|
let todos = output["current_todos"].as_array().unwrap();
|
||||||
|
assert_eq!(todos.len(), 2); // 默认 merge,旧项保留
|
||||||
|
let task_a = todos.iter().find(|t| t["id"] == "x1").unwrap();
|
||||||
|
assert_eq!(task_a["status"], "in_progress");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -502,10 +502,10 @@ function App() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 过滤无实质内容的 merged_tool(无结果且非等待中)
|
// 过滤无实质内容的 merged_tool:result 到达后才显示保留;calling/pending 有 callContent 也保留
|
||||||
return result.filter(msg => {
|
return result.filter(msg => {
|
||||||
if (msg.type !== 'merged_tool') return true
|
if (msg.type !== 'merged_tool') return true
|
||||||
if (msg.status === 'pending') return true
|
if (msg.status === 'calling' || msg.status === 'pending') return true
|
||||||
return !!(msg.resultContent && msg.resultContent.trim())
|
return !!(msg.resultContent && msg.resultContent.trim())
|
||||||
})
|
})
|
||||||
}, [messages])
|
}, [messages])
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user