feat: 拆分 worker 架构并增强 assistant 安全保护
- 新增 daemon/channel/assistant/function/scheduler/logging worker 架构和 JetStream 消息协议 - 修复邮件通知误入 assistant、迁移 checksum、日志 subject 和工具调用无限循环风险 - 新增 assistant 工具轮次熔断、工具结果截断和提示词软收敛规则 - 补充邮件推送关键字、可信发件人配置、升级日志和验证测试
This commit is contained in:
+1
-1
@@ -29,7 +29,7 @@ pub async fn send_message(
|
||||
let tool_calls = choice.message.tool_calls.clone();
|
||||
|
||||
sender
|
||||
.send(QueueMessage::Aissitant(
|
||||
.send(QueueMessage::Assistant(
|
||||
channel.clone(),
|
||||
AssistantMessage {
|
||||
reasoning: Some(messsage_reasoning),
|
||||
|
||||
+68
-7
@@ -18,6 +18,7 @@ use std::collections::HashSet;
|
||||
|
||||
const CATEGORY_API: &str = "api";
|
||||
const CATEGORY_HTTP: &str = "http";
|
||||
const DEFAULT_TOOL_RESULT_MAX_CHARS: usize = 4_000;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct CapabilityInventory {
|
||||
@@ -96,7 +97,7 @@ pub async fn call_tools(
|
||||
let tool_result = if tool_result.trim().is_empty() {
|
||||
format!("工具调用失败: {tool_name} 返回空结果")
|
||||
} else {
|
||||
tool_result
|
||||
trim_tool_result(&tool_result, tool_result_max_chars())
|
||||
};
|
||||
println!(
|
||||
"工具调用完成: {}, result_len={}",
|
||||
@@ -114,6 +115,27 @@ pub async fn call_tools(
|
||||
Ok(Some(QueueMessage::Tools(channel, results)))
|
||||
}
|
||||
|
||||
fn tool_result_max_chars() -> usize {
|
||||
std::env::var("IAS_TOOL_RESULT_MAX_CHARS")
|
||||
.ok()
|
||||
.and_then(|value| value.trim().parse::<usize>().ok())
|
||||
.filter(|value| *value >= 500)
|
||||
.unwrap_or(DEFAULT_TOOL_RESULT_MAX_CHARS)
|
||||
}
|
||||
|
||||
fn trim_tool_result(value: &str, max_chars: usize) -> String {
|
||||
let mut chars = value.chars();
|
||||
let text = chars.by_ref().take(max_chars).collect::<String>();
|
||||
if chars.next().is_some() {
|
||||
format!(
|
||||
"{text}\n...(工具结果已截断,原始长度约 {} 字符)",
|
||||
value.chars().count()
|
||||
)
|
||||
} else {
|
||||
text
|
||||
}
|
||||
}
|
||||
|
||||
async fn execute_tool(
|
||||
channel: &ChannelMessage,
|
||||
tool_path: &str,
|
||||
@@ -137,7 +159,7 @@ async fn query_capabilities(channel: &ChannelMessage, tool_path: &str) -> String
|
||||
"http_capabilities": capability_briefs(&inventory.http_tools),
|
||||
"tool_call_instructions": tool_call_instructions.unwrap_or_else(|| "固定工具调用约束已在系统提示词中生效;当前没有额外动态工具调用说明。".to_string()),
|
||||
"total": inventory.api_tools.len() + inventory.http_tools.len(),
|
||||
"usage": "先用 query_capabilities 查看名称、描述和工具调用说明;需要参数时调用 query_capability_detail;执行时调用 call_capability。内部 HTTP API 统一通过 call_http_api 执行,先用 query_api_docs 查询 endpoint_id、参数和请求体。",
|
||||
"usage": "未知能力名称时先用 query_capabilities 查看名称、描述和工具调用说明;只有参数不明确时才调用 query_capability_detail;执行时调用 call_capability。内部 HTTP API 统一通过 call_http_api 执行;只有 endpoint_id、参数或请求体不明确时才用 query_api_docs 查询文档,已有足够信息时不要重复查询。",
|
||||
});
|
||||
if !inventory.warnings.is_empty() {
|
||||
result["warnings"] = serde_json::json!(inventory.warnings);
|
||||
@@ -223,7 +245,7 @@ pub fn api_capability_definitions() -> Vec<CapabilityDefinition> {
|
||||
vec![
|
||||
api_capability(
|
||||
"query_api_docs",
|
||||
"查询 iAs HTTP API 文档和功能分组。用户询问系统有哪些 HTTP API 能力、接口路径、参数、请求体、响应格式、页面数据 API、邮件 API、长期记忆 API 或控制台 API 时使用;不确定该查哪类接口时先空查询查看功能分组。",
|
||||
"查询 iAs HTTP API 文档和功能分组。仅在用户询问系统有哪些 HTTP API 能力、接口路径、参数、请求体、响应格式,或当前上下文不足以确定 endpoint_id、参数和请求体时使用;已有足够信息时不要重复查询文档。不确定该查哪类接口时先空查询查看功能分组。",
|
||||
vec![
|
||||
param(
|
||||
"query",
|
||||
@@ -253,7 +275,7 @@ pub fn api_capability_definitions() -> Vec<CapabilityDefinition> {
|
||||
),
|
||||
api_capability(
|
||||
"call_http_api",
|
||||
"按 endpoint_id 调用 iAs 内部 HTTP API。调用前应先用 query_api_docs 查询 API 文档,确认 endpoint_id、path_params、query 和 body;context 分组接口会自动补当前用户作用域身份并加内部鉴权。",
|
||||
"按 endpoint_id 调用 iAs 内部 HTTP API。调用前要确认 endpoint_id、path_params、query 和 body;如果当前上下文或本轮已有 API 文档已经足够,不要再次调用 query_api_docs。context 分组接口会自动补当前用户作用域身份并加内部鉴权。",
|
||||
vec![
|
||||
param(
|
||||
"endpoint_id",
|
||||
@@ -442,7 +464,7 @@ pub fn get_tool() -> Vec<serde_json::Value> {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "query_capabilities",
|
||||
"description": "查询可用能力的名称和描述列表。结果按 api 能力和 http 能力归类;需要完整参数信息时再调用 query_capability_detail。",
|
||||
"description": "查询可用能力的名称和描述列表。结果按 api 能力和 http 能力归类;已有足够信息时不要继续查询;只有参数不明确时才调用 query_capability_detail。",
|
||||
"parameters": {"type": "object", "properties": {}, "required": []}
|
||||
}
|
||||
}),
|
||||
@@ -450,7 +472,7 @@ pub fn get_tool() -> Vec<serde_json::Value> {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "query_capability_detail",
|
||||
"description": "查询单个能力的完整参数、分类、来源和调用格式。调用具体能力前,如果不确定参数,应先查询详情。",
|
||||
"description": "查询单个能力的完整参数、分类、来源和调用格式。只有在参数不明确、能力列表不足以填写参数时使用;不要作为每次调用具体能力前的默认步骤。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -464,7 +486,7 @@ pub fn get_tool() -> Vec<serde_json::Value> {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "call_capability",
|
||||
"description": "调用指定能力。禁止直接调用其它能力,所有 api/http 能力都必须通过本工具传入 name 和 parameters 调用;可用 text 附加执行前提示语。",
|
||||
"description": "调用指定能力。禁止直接调用其它能力,所有 api/http 能力都必须通过本工具传入 name 和 parameters 调用;不要重复调用相同能力和参数;可用 text 附加执行前提示语。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -519,6 +541,25 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_tool_descriptions_discourage_redundant_calls() {
|
||||
let descriptions = get_tool()
|
||||
.into_iter()
|
||||
.filter_map(|tool| {
|
||||
tool.get("function")
|
||||
.and_then(|function| function.get("description"))
|
||||
.and_then(Value::as_str)
|
||||
.map(ToString::to_string)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
assert!(descriptions.contains("已有足够信息时不要继续查询"));
|
||||
assert!(descriptions.contains("只有在参数不明确"));
|
||||
assert!(descriptions.contains("不要作为每次调用具体能力前的默认步骤"));
|
||||
assert!(descriptions.contains("不要重复调用相同能力和参数"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_capabilities_use_generic_http_api_call() {
|
||||
let capabilities = api_capability_definitions();
|
||||
@@ -532,5 +573,25 @@ mod tests {
|
||||
assert!(!names.contains(&"record_tool_call_instruction"));
|
||||
assert!(!names.contains(&"query_capabilities"));
|
||||
assert!(!names.contains(&"call_capability"));
|
||||
|
||||
let query_docs = capabilities
|
||||
.iter()
|
||||
.find(|capability| capability.name == "query_api_docs")
|
||||
.unwrap();
|
||||
assert!(
|
||||
query_docs
|
||||
.description
|
||||
.contains("已有足够信息时不要重复查询文档")
|
||||
);
|
||||
|
||||
let call_http = capabilities
|
||||
.iter()
|
||||
.find(|capability| capability.name == "call_http_api")
|
||||
.unwrap();
|
||||
assert!(
|
||||
call_http
|
||||
.description
|
||||
.contains("不要再次调用 query_api_docs")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user