matrixcode_core/tools/
ask.rs1use anyhow::Result;
7use async_trait::async_trait;
8use serde_json::{Value, json};
9use std::io::{self, BufRead, Write as _};
10
11use super::{Tool, ToolDefinition};
12use crate::approval::RiskLevel;
13
14pub struct AskTool;
19
20const ASK_TOOL_DESCRIPTION: &str =
21 "当遇到不确定或多种选择时,向用户提问以获取明确指示。\
22 必须使用此工具的情况:(1) 用户请求含义模糊,(2) 存在多种可行方案需要选择,\
23 (3) 决策可能对项目产生重大影响。\
24 提供推荐方案及理由,让用户做出知情选择。\
25 不确定时切勿猜测或直接推进。";
26
27#[async_trait]
28impl Tool for AskTool {
29 fn definition(&self) -> ToolDefinition {
30 ToolDefinition {
31 name: "ask".to_string(),
32 description: ASK_TOOL_DESCRIPTION.to_string(),
33 parameters: ask_tool_schema(),
34 }
35 }
36
37 async fn execute(&self, params: Value) -> Result<String> {
38 let question = params["question"]
39 .as_str()
40 .ok_or_else(|| anyhow::anyhow!("missing 'question'"))?;
41
42 let options = params["options"].as_array();
43 let recommendation = params["recommendation"].as_object();
44
45 render_question_ui(question, options, recommendation);
47
48 let answer = read_user_answer();
50 println!();
51
52 Ok(answer)
53 }
54
55 fn risk_level(&self) -> RiskLevel {
56 RiskLevel::Safe
57 }
58}
59
60fn ask_tool_schema() -> Value {
62 json!({
63 "type": "object",
64 "properties": {
65 "question": {
66 "type": "string",
67 "description": "要向用户提问的问题,需具体清晰"
68 },
69 "options": {
70 "type": "array",
71 "description": "可选方案列表",
72 "items": {
73 "type": "object",
74 "properties": {
75 "id": {
76 "type": "string",
77 "description": "选项短标识符(如 'A'、'B'、'1'、'2')"
78 },
79 "label": {
80 "type": "string",
81 "description": "选项的可读标签"
82 },
83 "description": {
84 "type": "string",
85 "description": "该选项的简要说明"
86 }
87 },
88 "required": ["id", "label"]
89 }
90 },
91 "recommendation": {
92 "type": "object",
93 "description": "你的推荐方案及理由",
94 "properties": {
95 "option_id": {
96 "type": "string",
97 "description": "推荐选项的标识符"
98 },
99 "reason": {
100 "type": "string",
101 "description": "推荐该方案的理由"
102 }
103 },
104 "required": ["option_id", "reason"]
105 }
106 },
107 "required": ["question"]
108 })
109}
110
111fn render_question_ui(
117 question: &str,
118 options: Option<&Vec<Value>>,
119 recommendation: Option<&serde_json::Map<String, Value>>,
120) {
121 println!();
122 println!("┌─ AI 询问 ─────────────────────────────────────────");
123
124 for line in question.lines() {
126 println!("│ {}", line);
127 }
128 println!("│");
129
130 if let Some(opts) = options
132 && !opts.is_empty() {
133 render_options(opts);
134 }
135
136 if let Some(rec) = recommendation {
138 render_recommendation(rec, options);
139 }
140
141 println!("│ 请输入你的选择或补充想法:");
142 println!("└────────────────────────────────────────────────────");
143 print!("> ");
144 let _ = io::stdout().flush();
145}
146
147fn render_options(opts: &[Value]) {
149 println!("│ 可选方案:");
150 for opt in opts {
151 let id = opt["id"].as_str().unwrap_or("?");
152 let label = opt["label"].as_str().unwrap_or("未命名");
153 let desc = opt["description"].as_str();
154 if let Some(d) = desc {
155 println!("│ {}) {} - {}", id, label, d);
156 } else {
157 println!("│ {}) {}", id, label);
158 }
159 }
160 println!("│");
161}
162
163fn render_recommendation(
165 rec: &serde_json::Map<String, Value>,
166 options: Option<&Vec<Value>>,
167) {
168 let opt_id = rec["option_id"].as_str().unwrap_or("?");
169 let reason = rec["reason"].as_str().unwrap_or("无理由");
170
171 let rec_label = options
173 .and_then(|opts| opts.iter().find(|o| o["id"].as_str() == Some(opt_id)))
174 .and_then(|o| o["label"].as_str())
175 .unwrap_or(opt_id);
176
177 println!("│ 💡 推荐方案:{} ({})", rec_label, opt_id);
178 println!("│ 理由:{}", reason);
179 println!("│");
180}
181
182fn read_user_answer() -> String {
188 let stdin = io::stdin();
189 let mut line = String::new();
190 if stdin.lock().read_line(&mut line).is_err() {
191 return "无法读取回答".to_string();
192 }
193 line.trim().to_string()
194}