AI 驱动的 CLI 工具开发用 Rust 构建智能命令行 Agent一、命令行工具不该只是命令的集合它应该理解你的意图我之前用 Python 写过不少 CLI 工具argparse 一套、subcommand 一堆最后变成一个命令字典——你得记住每个子命令的参数和顺序。后来接触了 Rust 的 clap 和大模型 API突然想到如果 CLI 工具能理解自然语言呢不用记命令直接说帮我找出最近 7 天修改过的 .rs 文件工具自己解析意图、选择子命令、填充参数。这不是科幻。Rust 的强类型系统和错误处理让 CLI 工具的骨架很稳加上大模型的自然语言理解能力就能构建一个听得懂人话的命令行 Agent。这篇文章记录我从零开始踩坑的全过程。二、智能 CLI Agent 的架构设计flowchart TB A[用户输入br/自然语言或命令] -- B[输入解析层] B -- B1[传统解析br/clap 子命令] B -- B2[NLU 解析br/大模型意图识别] B1 -- C[意图路由] B2 -- C C -- C2[文件搜索意图] C -- C3[代码分析意图] C -- C4[系统操作意图] C -- C5[未知意图br/回退到大模型] C2 -- D[工具执行层] C3 -- D C4 -- D C5 -- D D -- D1[文件系统操作br/walkdir regex] D -- D2[代码解析br/tree-sitter] D -- D3[Shell 命令br/安全沙箱执行] D1 -- E[结果格式化br/rich 终端输出] D2 -- E D3 -- E E -- F[反馈学习br/记录用户修正] style B2 fill:#e3f2fd style C5 fill:#fff3e0 style F fill:#e8f5e9智能 CLI Agent 的三层架构输入解析层同时支持传统命令和自然语言、意图路由层将解析结果映射到具体工具、工具执行层安全地执行操作并格式化输出。关键设计是双通道输入——既保留传统 CLI 的精确性又支持自然语言的便利性。三、代码实现与分析3.1 项目骨架与依赖# Cargo.toml [package] name ai-cli-agent version 0.1.0 edition 2021 [dependencies] clap { version 4, features [derive] } tokio { version 1, features [full] } reqwest { version 0.12, features [json] } serde { version 1, features [derive] } serde_json 1 walkdir 2 regex 1 colored 2 indicatif 0.17 anyhow 13.2 双通道输入解析use clap::{Parser, Subcommand}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; /// AI 驱动的智能命令行工具 #[derive(Parser, Debug)] #[command(name ai, about 智能命令行 Agent)] struct Cli { #[command(subcommand)] command: OptionCommands, /// 自然语言输入当不使用子命令时 #[arg(trailing_var_arg true, allow_hyphen_values true)] natural_input: VecString, } #[derive(Subcommand, Debug)] enum Commands { /// 搜索文件 Search { /// 搜索模式支持正则 pattern: String, /// 搜索目录 #[arg(short, long, default_value .)] path: PathBuf, /// 文件类型过滤 #[arg(short, long)] file_type: OptionString, }, /// 分析代码结构 Analyze { /// 目标路径 path: PathBuf, }, /// 查看系统信息 Info, } /// 大模型返回的意图解析结果 #[derive(Debug, Serialize, Deserialize)] struct ParsedIntent { intent: String, parameters: serde_json::Value, confidence: f64, } /// NLU 解析器调用大模型解析自然语言 struct NluParser { client: reqwest::Client, api_url: String, api_key: String, } impl NluParser { fn new(api_key: String) - Self { Self { client: reqwest::Client::new(), api_url: https://api.openai.com/v1/chat/completions.to_string(), api_key, } } async fn parse(self, input: str) - anyhow::ResultParsedIntent { let system_prompt r# 你是一个命令行工具的意图解析器。将用户的自然语言输入解析为结构化意图。 支持的意图 - search: 搜索文件参数 { pattern, path, file_type } - analyze: 分析代码结构参数 { path } - info: 查看系统信息参数 {} - unknown: 无法识别的意图 返回 JSON 格式{ intent: ..., parameters: {...}, confidence: 0.0-1.0 } #; let body serde_json::json!({ model: gpt-4o-mini, messages: [ { role: system, content: system_prompt }, { role: user, content: input } ], temperature: 0.1, max_tokens: 256, }); let response self.client .post(self.api_url) .header(Authorization, format!(Bearer {}, self.api_key)) .json(body) .send() .await?; let resp_json: serde_json::Value response.json().await?; let content resp_json[choices][0][message][content] .as_str() .unwrap_or({}); let parsed: ParsedIntent serde_json::from_str(content) .unwrap_or(ParsedIntent { intent: unknown.to_string(), parameters: serde_json::Value::Null, confidence: 0.0, }); Ok(parsed) } }3.3 工具执行层use colored::*; use walkdir::WalkDir; use regex::Regex; use std::time::Instant; /// 文件搜索工具 struct FileSearcher; impl FileSearcher { /// 按模式搜索文件名 fn search_by_name( pattern: str, root: PathBuf, file_type: Optionstr, ) - VecPathBuf { let regex Regex::new(pattern).unwrap_or_else(|_| { Regex::new(regex::escape(pattern)).unwrap() }); let start Instant::now(); let mut results Vec::new(); for entry in WalkDir::new(root) .follow_links(false) .into_iter() .filter_map(|e| e.ok()) { let path entry.path(); // 文件类型过滤 if let Some(ft) file_type { if let Some(ext) path.extension() { if ext ! ft { continue; } } else { continue; } } // 文件名匹配 if let Some(name) path.file_name().and_then(|n| n.to_str()) { if regex.is_match(name) { results.push(path.to_path_buf()); } } } let elapsed start.elapsed(); println!( {} 找到 {} 个文件耗时 {:?}, ✓.green(), results.len().to_string().yellow(), elapsed, ); results } } /// 主入口 #[tokio::main] async fn main() - anyhow::Result() { let cli Cli::parse(); // 双通道路由优先处理子命令否则走 NLU match cli.command { Some(Commands::Search { pattern, path, file_type }) { let results FileSearcher::search_by_name( pattern, path, file_type.as_deref(), ); for path in results { println!( {}, path.display()); } } Some(Commands::Analyze { path }) { println!({} 分析 {}, →.blue(), path.display()); // 代码分析逻辑 } Some(Commands::Info) { println!({} 系统信息, →.blue()); println!( OS: {}, std::env::consts::OS); println!( Arch: {}, std::env::consts::ARCH); } None if !cli.natural_input.is_empty() { // 自然语言通道 let input cli.natural_input.join( ); println!({} 理解中: {}, .to_string(), input.cyan()); let api_key std::env::var(OPENAI_API_KEY) .map_err(|_| anyhow::anyhow!(请设置 OPENAI_API_KEY 环境变量))?; let parser NluParser::new(api_key); let intent parser.parse(input).await?; println!( 意图: {} (置信度: {:.0%}), intent.intent.green(), intent.confidence, ); match intent.intent.as_str() { search { let pattern intent.parameters[pattern] .as_str().unwrap_or(*); let path intent.parameters[path] .as_str() .map(PathBuf::from) .unwrap_or(PathBuf::from(.)); let file_type intent.parameters[file_type] .as_str(); let results FileSearcher::search_by_name( pattern, path, file_type, ); for p in results { println!( {}, p.display()); } } unknown { println!( {} 抱歉我没理解你的意思。试试: ai search pattern, ✗.red() ); } _ { println!({} 暂不支持该意图, ✗.red()); } } } None { println!({} 使用 ai 命令 或 ai 自然语言, 提示:.yellow()); } } Ok(()) }四、踩坑记录与架构权衡NLU 延迟问题大模型 API 调用通常需要 1-3 秒对 CLI 工具来说太慢了。我的解决方案是高频命令search、info直接用 clap 解析不走 NLU只有说不清楚的输入才走 NLU。另外可以缓存相似输入的解析结果减少 API 调用。安全沙箱的必要性如果 NLU 解析出删除所有 .tmp 文件这样的意图直接执行太危险。我的做法是所有写操作删除、移动、修改都需要用户确认且限制可操作的目录范围。Rust 的类型系统在这里帮了大忙——ReadOnlyAction和WriteAction是不同的 trait编译期就能区分。离线可用性NLU 依赖大模型 API断网就废了。我的折中方案是离线时只支持传统命令模式NLU 通道自动禁用并给出提示。长期方案是用本地小模型如 GGUF 量化的 Llama 3做 NLU但 4GB 模型的推理速度在 CPU 上还是太慢。成本控制每次 NLU 调用消耗约 200 tokens按 GPT-4o-mini 的价格约 $0.00003/次。个人用没问题但如果做成团队工具每天几百次调用就需要考虑成本。可以用更小的模型如 GPT-4o-mini做意图分类只在需要时才调用大模型。五、总结用 Rust 构建智能 CLI Agent 的核心思路是双通道输入保留传统 CLI 的精确性增加 NLU 的便利性。本文的关键实践为用 clap 处理结构化命令、用大模型 API 解析自然语言意图、用 Rust 类型系统区分读写操作保证安全、用缓存和降级策略控制延迟和成本。这不是一个成熟方案而是一个学习过程中的记录——NLU 的延迟和安全沙箱是当前最大的挑战后续会尝试本地模型和更严格的权限控制。
AI 驱动的 CLI 工具开发:用 Rust 构建智能命令行 Agent
发布时间:2026/6/17 12:06:16
AI 驱动的 CLI 工具开发用 Rust 构建智能命令行 Agent一、命令行工具不该只是命令的集合它应该理解你的意图我之前用 Python 写过不少 CLI 工具argparse 一套、subcommand 一堆最后变成一个命令字典——你得记住每个子命令的参数和顺序。后来接触了 Rust 的 clap 和大模型 API突然想到如果 CLI 工具能理解自然语言呢不用记命令直接说帮我找出最近 7 天修改过的 .rs 文件工具自己解析意图、选择子命令、填充参数。这不是科幻。Rust 的强类型系统和错误处理让 CLI 工具的骨架很稳加上大模型的自然语言理解能力就能构建一个听得懂人话的命令行 Agent。这篇文章记录我从零开始踩坑的全过程。二、智能 CLI Agent 的架构设计flowchart TB A[用户输入br/自然语言或命令] -- B[输入解析层] B -- B1[传统解析br/clap 子命令] B -- B2[NLU 解析br/大模型意图识别] B1 -- C[意图路由] B2 -- C C -- C2[文件搜索意图] C -- C3[代码分析意图] C -- C4[系统操作意图] C -- C5[未知意图br/回退到大模型] C2 -- D[工具执行层] C3 -- D C4 -- D C5 -- D D -- D1[文件系统操作br/walkdir regex] D -- D2[代码解析br/tree-sitter] D -- D3[Shell 命令br/安全沙箱执行] D1 -- E[结果格式化br/rich 终端输出] D2 -- E D3 -- E E -- F[反馈学习br/记录用户修正] style B2 fill:#e3f2fd style C5 fill:#fff3e0 style F fill:#e8f5e9智能 CLI Agent 的三层架构输入解析层同时支持传统命令和自然语言、意图路由层将解析结果映射到具体工具、工具执行层安全地执行操作并格式化输出。关键设计是双通道输入——既保留传统 CLI 的精确性又支持自然语言的便利性。三、代码实现与分析3.1 项目骨架与依赖# Cargo.toml [package] name ai-cli-agent version 0.1.0 edition 2021 [dependencies] clap { version 4, features [derive] } tokio { version 1, features [full] } reqwest { version 0.12, features [json] } serde { version 1, features [derive] } serde_json 1 walkdir 2 regex 1 colored 2 indicatif 0.17 anyhow 13.2 双通道输入解析use clap::{Parser, Subcommand}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; /// AI 驱动的智能命令行工具 #[derive(Parser, Debug)] #[command(name ai, about 智能命令行 Agent)] struct Cli { #[command(subcommand)] command: OptionCommands, /// 自然语言输入当不使用子命令时 #[arg(trailing_var_arg true, allow_hyphen_values true)] natural_input: VecString, } #[derive(Subcommand, Debug)] enum Commands { /// 搜索文件 Search { /// 搜索模式支持正则 pattern: String, /// 搜索目录 #[arg(short, long, default_value .)] path: PathBuf, /// 文件类型过滤 #[arg(short, long)] file_type: OptionString, }, /// 分析代码结构 Analyze { /// 目标路径 path: PathBuf, }, /// 查看系统信息 Info, } /// 大模型返回的意图解析结果 #[derive(Debug, Serialize, Deserialize)] struct ParsedIntent { intent: String, parameters: serde_json::Value, confidence: f64, } /// NLU 解析器调用大模型解析自然语言 struct NluParser { client: reqwest::Client, api_url: String, api_key: String, } impl NluParser { fn new(api_key: String) - Self { Self { client: reqwest::Client::new(), api_url: https://api.openai.com/v1/chat/completions.to_string(), api_key, } } async fn parse(self, input: str) - anyhow::ResultParsedIntent { let system_prompt r# 你是一个命令行工具的意图解析器。将用户的自然语言输入解析为结构化意图。 支持的意图 - search: 搜索文件参数 { pattern, path, file_type } - analyze: 分析代码结构参数 { path } - info: 查看系统信息参数 {} - unknown: 无法识别的意图 返回 JSON 格式{ intent: ..., parameters: {...}, confidence: 0.0-1.0 } #; let body serde_json::json!({ model: gpt-4o-mini, messages: [ { role: system, content: system_prompt }, { role: user, content: input } ], temperature: 0.1, max_tokens: 256, }); let response self.client .post(self.api_url) .header(Authorization, format!(Bearer {}, self.api_key)) .json(body) .send() .await?; let resp_json: serde_json::Value response.json().await?; let content resp_json[choices][0][message][content] .as_str() .unwrap_or({}); let parsed: ParsedIntent serde_json::from_str(content) .unwrap_or(ParsedIntent { intent: unknown.to_string(), parameters: serde_json::Value::Null, confidence: 0.0, }); Ok(parsed) } }3.3 工具执行层use colored::*; use walkdir::WalkDir; use regex::Regex; use std::time::Instant; /// 文件搜索工具 struct FileSearcher; impl FileSearcher { /// 按模式搜索文件名 fn search_by_name( pattern: str, root: PathBuf, file_type: Optionstr, ) - VecPathBuf { let regex Regex::new(pattern).unwrap_or_else(|_| { Regex::new(regex::escape(pattern)).unwrap() }); let start Instant::now(); let mut results Vec::new(); for entry in WalkDir::new(root) .follow_links(false) .into_iter() .filter_map(|e| e.ok()) { let path entry.path(); // 文件类型过滤 if let Some(ft) file_type { if let Some(ext) path.extension() { if ext ! ft { continue; } } else { continue; } } // 文件名匹配 if let Some(name) path.file_name().and_then(|n| n.to_str()) { if regex.is_match(name) { results.push(path.to_path_buf()); } } } let elapsed start.elapsed(); println!( {} 找到 {} 个文件耗时 {:?}, ✓.green(), results.len().to_string().yellow(), elapsed, ); results } } /// 主入口 #[tokio::main] async fn main() - anyhow::Result() { let cli Cli::parse(); // 双通道路由优先处理子命令否则走 NLU match cli.command { Some(Commands::Search { pattern, path, file_type }) { let results FileSearcher::search_by_name( pattern, path, file_type.as_deref(), ); for path in results { println!( {}, path.display()); } } Some(Commands::Analyze { path }) { println!({} 分析 {}, →.blue(), path.display()); // 代码分析逻辑 } Some(Commands::Info) { println!({} 系统信息, →.blue()); println!( OS: {}, std::env::consts::OS); println!( Arch: {}, std::env::consts::ARCH); } None if !cli.natural_input.is_empty() { // 自然语言通道 let input cli.natural_input.join( ); println!({} 理解中: {}, .to_string(), input.cyan()); let api_key std::env::var(OPENAI_API_KEY) .map_err(|_| anyhow::anyhow!(请设置 OPENAI_API_KEY 环境变量))?; let parser NluParser::new(api_key); let intent parser.parse(input).await?; println!( 意图: {} (置信度: {:.0%}), intent.intent.green(), intent.confidence, ); match intent.intent.as_str() { search { let pattern intent.parameters[pattern] .as_str().unwrap_or(*); let path intent.parameters[path] .as_str() .map(PathBuf::from) .unwrap_or(PathBuf::from(.)); let file_type intent.parameters[file_type] .as_str(); let results FileSearcher::search_by_name( pattern, path, file_type, ); for p in results { println!( {}, p.display()); } } unknown { println!( {} 抱歉我没理解你的意思。试试: ai search pattern, ✗.red() ); } _ { println!({} 暂不支持该意图, ✗.red()); } } } None { println!({} 使用 ai 命令 或 ai 自然语言, 提示:.yellow()); } } Ok(()) }四、踩坑记录与架构权衡NLU 延迟问题大模型 API 调用通常需要 1-3 秒对 CLI 工具来说太慢了。我的解决方案是高频命令search、info直接用 clap 解析不走 NLU只有说不清楚的输入才走 NLU。另外可以缓存相似输入的解析结果减少 API 调用。安全沙箱的必要性如果 NLU 解析出删除所有 .tmp 文件这样的意图直接执行太危险。我的做法是所有写操作删除、移动、修改都需要用户确认且限制可操作的目录范围。Rust 的类型系统在这里帮了大忙——ReadOnlyAction和WriteAction是不同的 trait编译期就能区分。离线可用性NLU 依赖大模型 API断网就废了。我的折中方案是离线时只支持传统命令模式NLU 通道自动禁用并给出提示。长期方案是用本地小模型如 GGUF 量化的 Llama 3做 NLU但 4GB 模型的推理速度在 CPU 上还是太慢。成本控制每次 NLU 调用消耗约 200 tokens按 GPT-4o-mini 的价格约 $0.00003/次。个人用没问题但如果做成团队工具每天几百次调用就需要考虑成本。可以用更小的模型如 GPT-4o-mini做意图分类只在需要时才调用大模型。五、总结用 Rust 构建智能 CLI Agent 的核心思路是双通道输入保留传统 CLI 的精确性增加 NLU 的便利性。本文的关键实践为用 clap 处理结构化命令、用大模型 API 解析自然语言意图、用 Rust 类型系统区分读写操作保证安全、用缓存和降级策略控制延迟和成本。这不是一个成熟方案而是一个学习过程中的记录——NLU 的延迟和安全沙箱是当前最大的挑战后续会尝试本地模型和更严格的权限控制。