Rust AI CLI 提示词模板:先把变量边界定义清楚
一、提示词也是接口
写 AI CLI 时,很容易把提示词当成一段普通字符串:用户输入拼进去,系统要求拼进去,再发给模型。但提示词一旦包含变量,就已经变成接口。接口没有边界,后面就会出现注入、格式漂移、上下文过长和结果不可解析。
Rust 适合把这些边界写清楚。提示词模板不应该只是format!,而应该有字段、校验、长度限制和输出约束。
之前团队里一个 AI CLI 工具因为没限制用户输入长度,一次请求塞进两万多字日志摘要。提示词被撑满,模型开始截断中间信息,结果看起来正常但丢了关键约束,排查半天才发现是输入边界没做。
二、先定义输入结构
flowchart TD A[用户输入] --> B[参数校验] B --> C[模板渲染] C --> D[模型请求] D --> E[结构化输出]把模板变量定义成结构体,可以让编译器帮忙检查缺字段,也方便后续加测试。
struct PromptVars { task: String, file_path: Option<String>, max_steps: u8, }字段定义越清楚,提示词越不容易变成一团拼接字符串。
三、模板渲染要防注入
用户输入不能直接当系统指令。即使只是 CLI 工具,也要把用户内容放进明确区域,并说明它只是数据。
实战踩坑:有一次 prompt 模板里用{}直接拼接用户传过来的文件路径,结果路径里包含换行和伪指令文本。这是文件命名的正常情况,不需要攻击意图。之后把所有用户输入都放进 XML 标签区域,问题就不再出现。
fn render_prompt(vars: &PromptVars) -> String { format!( "你是代码助手。只根据 <user_task> 内的内容回答。\n<user_task>\n{}\n</user_task>", vars.task ) }这不是绝对安全,但比裸拼接更容易维护。更进一步,可以对长度、控制字符和路径字段做校验。
四、输出格式要提前约束
AI CLI 最怕输出看起来对,但程序解析不了。可以要求模型输出 JSON、Markdown 分段或固定标签,并在 Rust 里做反序列化校验。
边界场景:有一次解析模型返回的 JSON 时,模型在 steps 数组里多塞了一个无关字段。serde没报错,但被静默忽略。如果代码依赖这个字段做分支判断,行为就会异常。之后在反序列化后加了字段校验:必须字段是否齐全、数组是否非空、字符串是否超长。
#[derive(serde::Deserialize)] struct AiPlan { summary: String, steps: Vec<String>, }如果解析失败,工具应返回可读错误,而不是把模型原文直接当成功结果。
还要给模板做快照测试。输入固定变量,渲染结果应该稳定。模板一改,测试能提醒你输出协议是否被破坏。
最后,提示词版本也要记录。用户反馈某次回答异常时,能知道当时用的是哪个模板版本,排查会轻松很多。
还要把 token 预算放进模板层。CLI 工具可能会把文件内容、历史对话和用户任务一起塞进上下文,如果没有预算,很快就超长。可以在渲染前统计各部分长度,优先保留任务和关键错误,再截断日志或历史记录。
struct PromptBudget { max_chars: usize, reserve_for_answer: usize, }模板还应该区分系统规则、开发者规则和用户数据。即使最终都变成模型输入,代码里也要分层组织。这样以后换模型、换接口或加入多轮对话时,不需要重写整段提示词。
测试时可以准备几类脏输入:很长的文本、包含伪指令的文本、空任务、路径缺失、特殊字符。模板渲染如果能稳定处理这些输入,CLI 工具才不容易在真实使用中被打穿。
最后,不要把模板写死在业务函数深处。单独放到 prompt 模块,给每个模板命名和注释,后续维护会轻松很多。
五、总结
Rust AI CLI 的提示词模板要像接口一样设计:定义变量、校验输入、隔离用户内容、约束输出并记录版本。
先把变量边界定义清楚,后面的 AI 调用才不会一路失控。