Rust 写 AI CLI先把流式输出和错误处理做好一、AI CLI 的第一版不要贪多用 Rust 写 AI 命令行工具时很容易一上来就想做会话管理、插件系统、文件索引和 Agent 自动执行。实际写下来会发现第一版最重要的是两个基础能力请求模型并稳定输出结果以及在失败时给用户清楚的错误信息。没有这两个能力功能再多也只是脆弱的壳。CLI 和网页不同。用户在终端里更关注反馈是否及时、参数是否清楚、错误是否可修复。AI 接口如果一次性等完整响应返回长回答会让终端像卡住一样。流式输出能明显改善体验。Rust 在这类工具上很合适因为类型系统会逼着我们把网络错误、解析错误和配置错误分清楚。二、最小链路参数、请求、流式输出、退出码flowchart TD A[命令行参数] -- B[读取配置] B -- C[构造模型请求] C -- D[HTTP 流式响应] D -- E[逐块输出到终端] D -- F[错误分类] F -- G[退出码]第一版可以只支持一个子命令例如ask 解释这段报错。参数解析用clapHTTP 请求用reqwest错误封装用thiserror。不要急着做交互式 REPL因为 REPL 会引入历史记录、中断、上下文长度和终端兼容问题。先让一次性命令可靠再往上加。配置也要简单。API Key 可以从环境变量读取模型名和超时时间可以放在配置文件。不要把密钥写进命令历史里也不要在调试日志里打印完整请求。AI 工具再小也要从第一天尊重凭据安全。三、代码片段用枚举表达失败类型下面是一个简化的错误定义。它的意义不是优雅而是让调用方知道失败来自哪里。use thiserror::Error; #[derive(Debug, Error)] pub enum CliError { #[error(missing api key, please set AI_API_KEY)] MissingApiKey, #[error(http request failed: {0})] Http(#[from] reqwest::Error), #[error(invalid response format: {0})] InvalidResponse(String), #[error(io error: {0})] Io(#[from] std::io::Error), }如果全部用anyhow::Result也能写但学习阶段我更建议先写清楚错误枚举。这样会被迫思考哪些错误用户能修哪些错误需要重试哪些错误应该打印调试信息。CLI 工具的用户体验很大一部分来自错误信息。流式输出时还要记得及时 flush stdout。否则模型已经返回了 token终端却没有马上显示。小细节会影响工具手感。四、工程边界超时、重试和取消AI 接口一定要设置超时。网络请求没有超时就等于把终端交给不确定性。可以设置连接超时和总请求超时用户按 CtrlC 时也要能退出。后面接入 Tokio 后可以用异步任务和取消信号管理长请求。重试要谨慎。网络抖动可以重试认证失败不能重试参数错误不能重试模型限流可以指数退避。不要把所有错误都包成“再试一次”。重试如果没有边界会浪费 token也会让用户等更久。日志也要分级。默认输出只展示用户需要看的内容调试模式再打印请求 ID、耗时和错误细节。终端工具不能把日志刷得比回答还多。这个道理我也是写了几版才意识到。记得第一版上线后有用户反馈点了 ask 就卡住了。排查发现是模型服务偶尔应答十秒以上而我没有设超时。如果当时先加一个 30 秒全局超时用户就不会觉得工具死掉了。终端工具给用户的第一印象很多时候不来自功能有多强大而是失败时有交代。还有一次reqwest 的连接池在 keep-alive 下偶发 Broken Pipe加上 retry 逻辑后成功率从 95% 提到 99.7%。五、总结Rust 写 AI CLI 的第一版应聚焦最小闭环参数解析、配置读取、模型请求、流式输出和错误分类。先把基础体验做稳再扩展会话、插件和 Agent。终端工具不怕功能少怕的是失败时用户不知道该怎么办。