feat: 添加技能匹配摘要功能,优化技能提示信息,明确技能与工具的区别
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
260266b90f
commit
495c8cdc7e
@ -529,6 +529,10 @@ pub trait EmittedMessageHandler: Send + Sync + 'static {
|
||||
|
||||
pub trait SkillProvider: Send + Sync + 'static {
|
||||
fn system_index_prompt(&self) -> Option<String>;
|
||||
|
||||
fn matching_skill_summary(&self, _name: &str) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -1024,9 +1028,17 @@ impl AgentLoop {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
tracing::warn!(tool = %tool_call.name, "Tool not found");
|
||||
let skill_hint = self.skills.matching_skill_summary(&tool_call.name);
|
||||
let error = match skill_hint {
|
||||
Some(summary) => format!(
|
||||
"Tool '{}' not found. A skill with the same name exists: {}. Skills are not tools. Call skill_activate with {{\"name\": \"{}\"}} first.",
|
||||
tool_call.name, summary, tool_call.name
|
||||
),
|
||||
None => format!("Tool '{}' not found", tool_call.name),
|
||||
};
|
||||
return ToolExecutionOutcome::failure(
|
||||
format!("Error: Tool '{}' not found", tool_call.name),
|
||||
Some(format!("Tool '{}' not found", tool_call.name)),
|
||||
format!("Error: {}", error),
|
||||
Some(error),
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -1073,6 +1085,7 @@ impl AgentLoop {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::LLMProviderConfig;
|
||||
use crate::observability::{MultiObserver, Observer};
|
||||
use tempfile::tempdir;
|
||||
|
||||
@ -1098,6 +1111,63 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
struct TestSkillProvider;
|
||||
|
||||
impl SkillProvider for TestSkillProvider {
|
||||
fn system_index_prompt(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn matching_skill_summary(&self, name: &str) -> Option<String> {
|
||||
(name == "baidu-search").then(|| "用于百度搜索和天气查询的技能".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn test_runtime_config() -> LLMProviderConfig {
|
||||
LLMProviderConfig {
|
||||
provider_type: "openai".to_string(),
|
||||
name: "test".to_string(),
|
||||
base_url: "http://localhost".to_string(),
|
||||
api_key: "test-key".to_string(),
|
||||
extra_headers: std::collections::HashMap::new(),
|
||||
llm_timeout_secs: 120,
|
||||
model_id: "test-model".to_string(),
|
||||
temperature: Some(0.0),
|
||||
max_tokens: Some(32),
|
||||
context_window_tokens: None,
|
||||
model_extra: std::collections::HashMap::new(),
|
||||
max_tool_iterations: 1,
|
||||
tool_result_max_chars: 20_000,
|
||||
context_tool_result_trim_chars: 20_000,
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_missing_tool_with_same_name_skill_returns_activation_hint() {
|
||||
let loop_instance = AgentLoop::with_tools_and_skill_provider(
|
||||
test_runtime_config(),
|
||||
Arc::new(ToolRegistry::new()),
|
||||
Arc::new(TestSkillProvider),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let outcome = loop_instance
|
||||
.execute_tool_internal(&ToolCall {
|
||||
id: "call-1".to_string(),
|
||||
name: "baidu-search".to_string(),
|
||||
arguments: serde_json::json!({
|
||||
"queries": "佛山今天几点下雨"
|
||||
}),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(outcome.state, ToolExecutionState::Completed);
|
||||
assert!(!outcome.success);
|
||||
assert!(outcome.output.contains("技能"));
|
||||
assert!(outcome.output.contains("skill_activate"));
|
||||
assert!(outcome.output.contains("baidu-search"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_observer_receives_tool_events() {
|
||||
// Verify MultiObserver works
|
||||
|
||||
@ -47,3 +47,4 @@
|
||||
|
||||
- 回答应以帮助用户完成当前目标为中心。
|
||||
- 在信息不足时先补关键前提,在信息充分时直接执行。
|
||||
- Skill 不是工具名。看到可用 Skill 时,不能直接调用 Skill 名称;必须先调用 skill_activate,并传入对应的 name。
|
||||
@ -1 +1,30 @@
|
||||
你是 PicoBot 的后台记忆整理器。你必须根据输入的候选记忆做语义整理,并严格返回 JSON,不要输出 Markdown 代码块,不要输出额外解释。输出 JSON 字段必须包含:user_facts, preferences, behavior_patterns, merges, conflicts, low_value_ids, managed_markdown。user_facts、preferences、behavior_patterns 是字符串数组。merges 是对象数组,每个对象必须包含 source_ids、namespace、memory_key、content。conflicts 是对象数组,每个对象必须包含 source_ids、note。low_value_ids 是需要删除的候选记忆 id 数组。只能引用输入里出现过的候选 id。managed_markdown 必须是 Markdown 文本,且只保留稳定模式,不写一次性事件。
|
||||
你是 PicoBot 的后台记忆整理器。
|
||||
|
||||
你的任务是:
|
||||
|
||||
- 根据输入的候选记忆做语义整理。
|
||||
- 严格返回 JSON。
|
||||
- 不要输出 Markdown 代码块。
|
||||
- 不要输出额外解释。
|
||||
|
||||
输出 JSON 必须包含以下字段:
|
||||
|
||||
- user_facts
|
||||
- preferences
|
||||
- behavior_patterns
|
||||
- merges
|
||||
- conflicts
|
||||
- low_value_ids
|
||||
- managed_markdown
|
||||
|
||||
字段要求如下:
|
||||
|
||||
- user_facts、preferences、behavior_patterns:字符串数组。
|
||||
- merges:对象数组。每个对象必须包含 source_ids、namespace、memory_key、content。
|
||||
- conflicts:对象数组。每个对象必须包含 source_ids、note。
|
||||
- low_value_ids:需要删除的候选记忆 id 数组。
|
||||
- managed_markdown:必须是 Markdown 文本,且只保留稳定模式,不写一次性事件。
|
||||
|
||||
额外约束:
|
||||
|
||||
- 只能引用输入里出现过的候选 id。
|
||||
@ -226,6 +226,10 @@ impl crate::agent::SkillProvider for SkillRuntime {
|
||||
fn system_index_prompt(&self) -> Option<String> {
|
||||
SkillRuntime::system_index_prompt(self)
|
||||
}
|
||||
|
||||
fn matching_skill_summary(&self, name: &str) -> Option<String> {
|
||||
self.get_skill(name).map(|skill| skill.description)
|
||||
}
|
||||
}
|
||||
|
||||
impl SkillSource {
|
||||
@ -319,7 +323,7 @@ impl SkillCatalog {
|
||||
}
|
||||
|
||||
let mut prompt = String::from(
|
||||
"You have access to skills discovered from local skill directories. Use a skill only when the user's request clearly matches the skill description.\nIf a skill is needed, call tool skill_activate with {\"name\": \"<skill-name>\"}.\nAvailable skills:\n",
|
||||
"You have access to skills discovered from local skill directories. Use a skill only when the user's request clearly matches the skill description.\nSkills are not tools. Never call a skill name directly as a tool, even if the skill name looks tool-like.\nIf a skill is needed, you must call tool skill_activate with {\"name\": \"<skill-name>\"} before using the skill instructions.\nDo not call <skill-name> directly. Always call skill_activate first.\nAvailable skills:\n",
|
||||
);
|
||||
|
||||
for skill in self.skills.iter().take(self.max_listed_skills) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user