Files
iAs/src/tools/definitions.rs
T
wunianxiao 23c7fbac62 feat: 为全部 39 个 Rust 源文件添加系统性中文注释
为整个项目添加了详尽的中文文档注释,覆盖所有模块:

入口与架构:
- main.rs — 架构图、数据流、4 个关键设计决策
- cli.rs — 所有 CLI 子命令中文化
- Cargo.toml — 每个依赖的作用说明
- channel/mod.rs — 渠道标识设计意图

核心守护进程与消息队列:
- daemon.rs — 三消费者架构图、审批流
- queue/* — 公平轮转算法、路由调度架构

LLM 层:
- types.rs — Role/Message/ToolCall 等每个字段含义
- provider.rs — Provider trait 设计 + SSE 解析
- conversation.rs — 工具循环流程图、摘要机制
- deepseek.rs — API 请求格式

上下文管理:
- context/types.rs — ChatSession 消息生命周期
- context/builder.rs — Token 预算管理策略
- context/tools.rs — MemoryStore 双写策略

工具系统:
- tools/mod.rs — 两层元工具架构图
- tools/approval.rs — 审批流程
- tools/definitions.rs — 声明式工具三要素
- tools/subprocess.rs — 执行流程 + 缓存策略

数据库与状态:
- db/* — 5 张表用途、回退策略
- state.rs — 文件存储回退方案
- scheduler.rs — SKIP LOCKED 防重复

已废弃模块:
- ipc.rs — 旧 UDS 协议
- worker.rs — 旧 vs 新架构对比
2026-06-09 20:52:24 +08:00

394 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! ## JSON 工具定义格式
//!
//! 本模块定义了一种"声明式工具"格式 —— 只需要写一个 JSON 文件就能注册一个新工具,
//! 无需改 Rust 代码、重新编译。
//!
//! ### 设计理念:像 curl + jq 一样描述工具
//!
//! ```json
//! {
//! "name": "stocks",
//! "description": "股票行情查询",
//! "url": "https://api.example.com/stocks/{{code}}",
//! "method": "GET",
//! "headers": { "Authorization": "${env:API_KEY}" },
//! "extract": "data.items",
//! "fields": ["name", "price"]
//! }
//! ```
//!
//! ### 三要素
//! 1. **URL** — API 端点模板(支持 `{{var}}` 和 `${param:name:type}` 传参)
//! 2. **取节点** — `extract` 字段指定 JSON 点路径(类似 jq)
//! 3. **选字段** — `fields` 字段只保留需要的字段
//!
//! ### 参数推导
//! 自动从 URL/Body/Headers 中推导参数定义(无需手动写 parameters 字段),
//! 也支持显式 `parameters` 声明以获得更精确的类型描述。
use std::collections::HashMap;
/// HTTP 方法
#[derive(Debug, Clone, PartialEq)]
pub enum HttpMethod {
Get,
Post,
Put,
Delete,
}
#[allow(dead_code)]
impl HttpMethod {
pub fn as_str(&self) -> &'static str {
match self {
HttpMethod::Get => "GET",
HttpMethod::Post => "POST",
HttpMethod::Put => "PUT",
HttpMethod::Delete => "DELETE",
}
}
}
impl Default for HttpMethod {
fn default() -> Self {
HttpMethod::Get
}
}
impl<'de> serde::Deserialize<'de> for HttpMethod {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.to_uppercase().as_str() {
"GET" => Ok(HttpMethod::Get),
"POST" => Ok(HttpMethod::Post),
"PUT" => Ok(HttpMethod::Put),
"DELETE" => Ok(HttpMethod::Delete),
other => Err(serde::de::Error::custom(format!(
"不支持的 HTTP 方法: {} (支持: GET/POST/PUT/DELETE)",
other
))),
}
}
}
/// ## JSON 工具定义(ToolDef
///
/// 描述一个通过 HTTP API 调用的外部工具。
///
/// ### 字段说明
/// * `name` — 工具名称,用作 `call_capability` 的 name
/// * `description` — 工具描述(给 LLM 看的)
/// * `url` — URL 模板,支持 `{{var}}` 和 `${param:name:type}` 两种传参
/// * `method` — HTTP 方法(GET/POST/PUT/DELETE),默认 GET
/// * `headers` — HTTP 请求头,值可包含 `${env:KEY}`(环境变量)、
/// `${cmd:command}`(命令输出)、`${param:name:type}`(参数值)
/// * `body` — POST/PUT 的 JSON body 模板,同样支持变量替换
/// * `parameters` — 显式参数声明(可选,未声明时自动推导)
/// * `extract` — JSON 提取路径,如 "data.items"
/// * `fields` — 筛选要保留的字段
/// * `timeout` — 超时秒数,默认 30
#[derive(Debug, Clone, serde::Deserialize)]
pub struct ToolDef {
/// 工具名称,用作 call_capability 的 name
pub name: String,
/// 工具描述
pub description: String,
/// URL 模板,如 "https://api.example.com/stock/{{code}}"
/// 支持 {{var}} 和 ${param:name:type} 两种传参形式
pub url: String,
/// HTTP 方法,默认 GET
#[serde(default)]
pub method: HttpMethod,
/// HTTP 头。值可包含 ${env:KEY}、${cmd:command}、${param:name:type}
#[serde(default, skip_serializing_if = "Option::is_none")]
pub headers: Option<HashMap<String, String>>,
/// POST/PUT 的 JSON body 模板。字符串值可包含 {{var}} 和 ${param:name:type}
#[serde(default, skip_serializing_if = "Option::is_none")]
pub body: Option<serde_json::Value>,
/// 显式参数声明(可选)
/// 格式:{ "name": { "type": "string", "description": "..." } }
/// 未声明的参数自动从 ${param:name:type} 和 {{var}} 推导
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parameters: Option<serde_json::Value>,
/// JSON 提取路径(点路径),如 "data.items"
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extract: Option<String>,
/// 筛选字段
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fields: Option<Vec<String>>,
/// 超时秒数,默认 30
#[serde(default = "default_timeout")]
pub timeout: u64,
}
const fn default_timeout() -> u64 {
30
}
impl ToolDef {
/// 从文件加载
pub fn from_file(path: &std::path::Path) -> Result<Self, String> {
let text = std::fs::read_to_string(path)
.map_err(|e| format!("读取 {} 失败: {}", path.display(), e))?;
serde_json::from_str(&text)
.map_err(|e| format!("解析 {} 失败: {}", path.display(), e))
}
/// 生成 SkillSpec
///
/// 参数推导优先级:
/// 1. 显式 parameters 字段(精确控制类型和描述)
/// 2. 从 URL/Body/Headers 中扫描 `${param:name:type}` 推导
/// 3. 从 {{var}} 推导(全视为 string 类型,兼容旧格式)
pub fn to_spec(&self) -> super::types::SkillSpec {
let params = self.infer_parameters();
super::types::SkillSpec {
name: self.name.clone(),
description: self.description.clone(),
risk_level: super::types::RiskLevel::Low,
parameters: params,
timeout_secs: self.timeout,
}
}
/// 推导参数定义
fn infer_parameters(&self) -> serde_json::Value {
// 1. 显式 parameters 优先
if let Some(ref p) = self.parameters {
let p = p.clone();
// 如果已经是完整 schema(含 type/properties),直接返回
if p.get("type").is_some() {
return p;
}
// 否则认为是属性定义 { name: { type, description }, ... }
// 包装为完整 schema
let required: Vec<String> = p.as_object()
.map(|o| o.keys().cloned().collect())
.unwrap_or_default();
return serde_json::json!({
"type": "object",
"properties": p,
"required": required
});
}
// 2. 从模板中扫描 ${param:name:type} 和 {{var}}
let mut param_map: serde_json::Map<String, serde_json::Value> = serde_json::Map::new();
Self::collect_params(&self.url, &mut param_map);
if let Some(ref body) = self.body {
Self::collect_params_from_json(body, &mut param_map);
}
if let Some(ref headers) = self.headers {
for v in headers.values() {
Self::collect_params(v, &mut param_map);
}
}
let required: Vec<String> = param_map.keys().cloned().collect();
serde_json::json!({
"type": "object",
"properties": param_map,
"required": required
})
}
/// 从字符串中收集参数(识别 ${param:name:type} 和 {{var}}
fn collect_params(s: &str, map: &mut serde_json::Map<String, serde_json::Value>) {
// 匹配 ${param:name:type} 和 ${param:name:type=default}
let mut pos = 0;
while let Some(start) = s[pos..].find("${param:") {
let rel = pos + start + 8; // skip "${param:"
let rest = &s[rel..];
if let Some(end) = rest.find('}') {
let segment = &rest[..end];
let parts: Vec<&str> = segment.split(':').collect();
let name = parts[0].trim().to_string();
if name.is_empty() {
pos = rel + end + 1;
continue;
}
if !map.contains_key(&name) {
let param_type = parts.get(1).copied().unwrap_or("string");
let description = parts.get(2).copied().unwrap_or(name.as_str());
let entry = serde_json::json!({
"type": param_type,
"description": description
});
map.insert(name.clone(), entry);
}
pos = rel + end + 1;
continue;
}
pos = rel;
}
// 兼容旧格式 {{var}}
Self::extract_vars(s, map);
}
/// 从 JSON 值中递归收集参数
fn collect_params_from_json(
v: &serde_json::Value,
map: &mut serde_json::Map<String, serde_json::Value>,
) {
match v {
serde_json::Value::String(s) => Self::collect_params(s, map),
serde_json::Value::Object(obj) => {
for val in obj.values() {
Self::collect_params_from_json(val, map);
}
}
serde_json::Value::Array(arr) => {
for item in arr {
Self::collect_params_from_json(item, map);
}
}
_ => {}
}
}
/// 兼容旧格式 {{var}} — 全视为 string 类型
fn extract_vars(s: &str, map: &mut serde_json::Map<String, serde_json::Value>) {
let mut remaining = s;
while let Some(start) = remaining.find("{{") {
remaining = &remaining[start + 2..];
if let Some(end) = remaining.find("}}") {
let name = remaining[..end].trim().to_string();
if !name.is_empty() && !name.starts_with('#') && !map.contains_key(&name) {
map.insert(
name.clone(),
serde_json::json!({"type": "string", "description": name}),
);
}
remaining = &remaining[end + 2..];
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_minimal() {
let json = r#"{
"name": "ping",
"description": "Ping test",
"url": "https://httpbin.org/get"
}"#;
let def: ToolDef = serde_json::from_str(json).unwrap();
assert_eq!(def.name, "ping");
assert_eq!(def.method, HttpMethod::Get);
assert_eq!(def.timeout, 30);
}
#[test]
fn test_parse_post() {
let json = r#"{
"name": "translate",
"description": "翻译",
"url": "https://api.example.com/translate?text={{content}}",
"method": "POST",
"headers": { "Authorization": "${env:TR_KEY}" },
"body": { "q": "{{content}}", "target": "zh" },
"extract": "data.result",
"fields": ["text"],
"timeout": 60
}"#;
let def: ToolDef = serde_json::from_str(json).unwrap();
assert_eq!(def.method, HttpMethod::Post);
assert_eq!(def.timeout, 60);
assert_eq!(def.extract.as_deref(), Some("data.result"));
}
#[test]
fn test_infer_parameters_from_url() {
let json = r#"{
"name": "test",
"description": "test",
"url": "https://api.example.com/{{city}}/weather?days={{days}}"
}"#;
let def: ToolDef = serde_json::from_str(json).unwrap();
let params = def.infer_parameters();
let props = params["properties"].as_object().unwrap();
assert!(props.contains_key("city"));
assert!(props.contains_key("days"));
let required = params["required"].as_array().unwrap();
assert!(required.iter().any(|v| v == "city"));
assert!(required.iter().any(|v| v == "days"));
}
#[test]
fn test_infer_parameters_from_body() {
let json = r#"{
"name": "test",
"description": "test",
"url": "https://api.example.com/x",
"body": { "q": "{{text}}", "target": "{{lang}}" }
}"#;
let def: ToolDef = serde_json::from_str(json).unwrap();
let params = def.infer_parameters();
let props = params["properties"].as_object().unwrap();
assert!(props.contains_key("text"));
assert!(props.contains_key("lang"));
}
#[test]
fn test_method_uppercase_flexibility() {
let json = r#"{"name":"t","description":"t","url":"u","method":"post"}"#;
let def: ToolDef = serde_json::from_str(json).unwrap();
assert_eq!(def.method, HttpMethod::Post);
}
#[test]
fn test_infer_parameters_from_param_syntax() {
let json = r#"{
"name": "weather",
"description": "天气查询",
"url": "https://api.example.com/weather/${param:city:string}",
"headers": { "X-Key": "${param:api_key:string}" }
}"#;
let def: ToolDef = serde_json::from_str(json).unwrap();
let params = def.infer_parameters();
let props = params["properties"].as_object().unwrap();
assert!(props.contains_key("city"), "应有 city 参数");
assert_eq!(props["city"]["type"], "string", "city 类型应为 string");
assert!(props.contains_key("api_key"), "应有 api_key 参数");
assert_eq!(props["api_key"]["type"], "string", "api_key 类型应为 string");
let required = params["required"].as_array().unwrap();
assert!(required.iter().any(|v| v == "city"));
}
#[test]
fn test_infer_parameters_explicit_priority() {
let json = r#"{
"name": "my_tool",
"description": "test",
"url": "https://api.example.com/${param:city:string}",
"parameters": {
"city": { "type": "string", "description": "城市名称" },
"days": { "type": "integer", "description": "天数" }
}
}"#;
let def: ToolDef = serde_json::from_str(json).unwrap();
let params = def.infer_parameters();
// 显式 parameters 覆盖了自动推导
let props = params["properties"].as_object().unwrap();
assert!(props.contains_key("days"), "显式声明的 days 应在");
assert_eq!(props["days"]["type"], "integer");
// 且自动 wrap 为完整 schema
assert_eq!(params["type"], "object");
}
}