Skip to content

Commit 3d22389

Browse files
zenshanda-ai
andcommitted
refactor(tools): unify ToolsSearchOutput and ToolsSelectOutput into ToolsOutput
- Merge ToolsSearchOutput + ToolsSelectOutput → single ToolsOutput with Vec<FunctionDefinition> tools + total_tools - Remove intermediate ToolsSearchItem struct; rank_search_items now returns names directly, definitions resolved at output boundary - Return full FunctionDefinition (not just name+description) so model can invoke tools immediately after tools_search - Default limits: search 0→10, select 0→5 with explicit MAX cap - Agent failures return ToolOutput errors instead of breaking dialogue Also: rename NativeRuntime name 'native' → 'native_shell'; return immediate task_id Output for background shell tasks Co-Authored-By: Anda Bot <noreply@anda.bot>
1 parent 520bbb5 commit 3d22389

3 files changed

Lines changed: 89 additions & 98 deletions

File tree

‎anda_engine/src/context/agent.rs‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ use std::{
4949
use super::{
5050
base::BaseCtx,
5151
engine::RemoteEngines,
52-
tool::{ToolsSelectOutput, is_tools_select_name},
52+
tool::{ToolsOutput, is_tools_select_name},
5353
};
5454
use crate::{
5555
model::{Model, Models},
@@ -1247,7 +1247,7 @@ impl CompletionRunner {
12471247
(Some(tool), None)
12481248
}
12491249
Err(err) => {
1250-
// 工具调用失败了,但我们不一定要因此终止整个对话流程,可以让 LLM 尝试纠正错误并继续对话
1250+
// 工具调用失败了,但我们不能终止整个对话流程,可以让 LLM 尝试纠正错误并继续对话
12511251
{
12521252
tool.result = Some(ToolOutput {
12531253
output: json!({ "error": format!(
@@ -1295,7 +1295,19 @@ impl CompletionRunner {
12951295
tool.result = Some(res.into_tool_output());
12961296
(Some(tool), None)
12971297
}
1298-
Err(err) => (None, Some(err.to_string())),
1298+
Err(err) => {
1299+
// agent 调用失败了,但我们不能终止整个对话流程,可以让 LLM 尝试纠正错误并继续对话
1300+
{
1301+
tool.result = Some(ToolOutput {
1302+
output: json!({ "error": format!(
1303+
"agent run failed: {}",
1304+
err
1305+
)}),
1306+
..Default::default()
1307+
});
1308+
(Some(tool), None)
1309+
}
1310+
}
12991311
}
13001312
}));
13011313
} else {
@@ -1334,7 +1346,7 @@ impl CompletionRunner {
13341346
self.accumulate(&res.usage);
13351347
if is_tools_select_name(&tool.name) {
13361348
// 从模型输出或工具调用结果中获取实际被选中的工具定义,传递给下一轮模型调用
1337-
if let Ok(selected) = serde_json::from_value::<ToolsSelectOutput>(
1349+
if let Ok(selected) = serde_json::from_value::<ToolsOutput>(
13381350
res.output
13391351
.get("content")
13401352
.cloned()

‎anda_engine/src/context/tool.rs‎

Lines changed: 56 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,20 @@ pub(crate) fn is_tools_select_name(name: &str) -> bool {
2121
pub struct ToolsSearchArgs {
2222
/// Search terms, or `*` to enumerate every available callable name.
2323
pub query: String,
24-
/// Maximum number of results to return. `0` means all.
24+
/// Maximum number of results to return. Defaults to `10`.
2525
#[serde(default)]
2626
pub limit: usize,
2727
}
2828

2929
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
30-
pub struct ToolsSearchOutput {
30+
pub struct ToolsOutput {
3131
/// Matching callable names and descriptions.
32-
pub tools: Vec<ToolsSearchItem>,
32+
pub tools: Vec<FunctionDefinition>, // tool 只要出现在上下文即可能被调用,不一定要求在请求的 tools 列表中,所以这里返回完整定义,防止大模型瞎编请求参数。
3333
/// Total number of callables to the current model turn.
34+
#[serde(default)]
3435
pub total_tools: usize,
3536
}
3637

37-
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
38-
pub struct ToolsSearchItem {
39-
/// Callable name to pass back into `tools_select` or a direct tool call.
40-
pub name: String,
41-
/// Short description exposed to the model.
42-
pub description: String,
43-
}
44-
4538
/// Searches the callable surface currently available to the model.
4639
/// The "tools_search" tool has been registered as a built-in agent with label "flash".
4740
pub struct ToolsSearch {
@@ -60,35 +53,25 @@ impl ToolsSearch {
6053
Self { tokenizer }
6154
}
6255

63-
pub fn search(
64-
&self,
65-
candidates: &[FunctionDefinition],
66-
args: &ToolsSearchArgs,
67-
) -> ToolsSearchOutput {
56+
pub fn search(&self, candidates: &[FunctionDefinition], args: &ToolsSearchArgs) -> ToolsOutput {
6857
let normalized_query = args.query.trim().to_lowercase();
69-
let tools: Vec<ToolsSearchItem> = candidates
70-
.iter()
71-
.map(|def| ToolsSearchItem {
72-
name: def.name.clone(),
73-
description: def.description.clone(),
74-
})
75-
.collect();
58+
let tools: Vec<FunctionDefinition> = candidates.iter().cloned().collect();
7659

7760
let total_tools = tools.len();
7861
if normalized_query == "*" {
79-
return ToolsSearchOutput { tools, total_tools };
62+
return ToolsOutput { tools, total_tools };
8063
}
8164

8265
let normalized_tokens: Vec<(String, usize)> =
8366
collect_tokens(&mut self.tokenizer.clone(), &normalized_query, None)
8467
.into_iter()
8568
.collect();
8669

87-
let mut tools = rank_search_items(tools, &normalized_query, &normalized_tokens, false);
88-
if args.limit > 0 {
89-
tools.truncate(args.limit);
90-
}
91-
ToolsSearchOutput { tools, total_tools }
70+
let mut tools_name =
71+
rank_search_items(&tools, &normalized_query, &normalized_tokens, false);
72+
tools_name.truncate(if args.limit == 0 { 10 } else { args.limit });
73+
let tools = select_requested_definitions(tools, &tools_name);
74+
ToolsOutput { tools, total_tools }
9275
}
9376
}
9477

@@ -116,8 +99,8 @@ impl Agent<AgentCtx> for ToolsSearch {
11699
},
117100
"limit": {
118101
"type": "integer",
119-
"description": "Maximum number of matches to return (default: 0, all matching tools).",
120-
"default": 0,
102+
"description": "Maximum number of matches to return. Defaults to `10`.",
103+
"default": 10,
121104
"minimum": 0
122105
}
123106
},
@@ -147,7 +130,7 @@ impl Agent<AgentCtx> for ToolsSearch {
147130
let definitions = ctx.definitions(None).await;
148131
if args.query.trim().is_empty() || definitions.is_empty() {
149132
return Ok(AgentOutput {
150-
content: serde_json::to_string(&ToolsSearchOutput {
133+
content: serde_json::to_string(&ToolsOutput {
151134
tools: Vec::new(),
152135
total_tools: definitions.len(),
153136
})?,
@@ -170,17 +153,11 @@ pub struct ToolsSelectArgs {
170153
/// Natural-language intent used to select tools when exact names are unknown.
171154
#[serde(default)]
172155
pub query: String,
173-
/// Maximum number of resolved definitions to load. `0` lets the selector choose a bounded set.
156+
/// Maximum number of resolved definitions to load. Defaults to `5`, and is capped at `16` to prevent overloading the next model turn.
174157
#[serde(default)]
175158
pub limit: usize,
176159
}
177160

178-
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
179-
pub struct ToolsSelectOutput {
180-
/// Resolved function definitions that should be available to the next model turn.
181-
pub tools: Vec<FunctionDefinition>,
182-
}
183-
184161
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
185162
struct ToolsSelectNamesOutput {
186163
#[serde(default)]
@@ -219,64 +196,58 @@ impl ToolsSelect {
219196
let limit = if args.limit > 0 {
220197
args.limit.min(MAX_SELECTOR_LIMIT)
221198
} else {
222-
MAX_SELECTOR_LIMIT / 2
199+
5
223200
};
224201

225-
let items: Vec<ToolsSearchItem> = definitions
226-
.iter()
227-
.map(|def| ToolsSearchItem {
228-
name: def.name.clone(),
229-
description: def.description.clone(),
230-
})
231-
.collect();
232-
233-
let candidates =
234-
self.collect_query_candidates(items, &normalized_query, MAX_SELECTOR_CANDIDATE_LIMIT);
202+
let mut candidates = self.collect_query_candidates(
203+
&definitions,
204+
&normalized_query,
205+
MAX_SELECTOR_CANDIDATE_LIMIT,
206+
);
235207

236208
if candidates.is_empty() {
237209
return Vec::new();
238210
}
239211

240212
if normalized_query.len() <= 3 {
241-
let names: Vec<String> = candidates
242-
.into_iter()
243-
.take(limit)
244-
.map(|item| item.name)
245-
.collect();
246-
return select_requested_definitions(definitions, &names);
213+
candidates.truncate(limit);
214+
return select_requested_definitions(definitions, &candidates);
247215
}
248216

249217
let mut requested =
250-
select_requested_names_with_model(ctx, &candidates, &normalized_query, limit).await;
218+
select_requested_names_with_model(ctx, &definitions, &normalized_query, limit).await;
251219
if requested.is_empty() {
252-
requested = candidates
253-
.iter()
254-
.take(limit)
255-
.map(|item| item.name.clone())
256-
.collect();
220+
candidates.truncate(limit);
221+
requested = candidates;
257222
}
258223

259224
select_requested_definitions(definitions, &requested)
260225
}
261226

262227
fn collect_query_candidates(
263228
&self,
264-
mut items: Vec<ToolsSearchItem>,
229+
items: &[FunctionDefinition],
265230
query: &str,
266231
candidate_limit: usize,
267-
) -> Vec<ToolsSearchItem> {
268-
if !query.is_empty() && query != "*" {
232+
) -> Vec<String> {
233+
let mut rt = if !query.is_empty() && query != "*" {
269234
// (lowercase token, weight)
270235
let normalized_tokens: Vec<(String, usize)> =
271236
collect_tokens(&mut self.tokenizer.clone(), query, None)
272237
.into_iter()
273238
.collect();
274239

275-
items = rank_search_items(items, query, &normalized_tokens, true);
276-
}
240+
rank_search_items(items, query, &normalized_tokens, true)
241+
} else {
242+
items
243+
.iter()
244+
.take(candidate_limit)
245+
.map(|item| item.name.clone())
246+
.collect::<Vec<_>>()
247+
};
277248

278-
items.truncate(candidate_limit);
279-
items
249+
rt.truncate(candidate_limit);
250+
rt
280251
}
281252
}
282253

@@ -313,8 +284,8 @@ impl Agent<AgentCtx> for ToolsSelect {
313284
"limit": {
314285
"type": "integer",
315286
"minimum": 0,
316-
"default": 0,
317-
"description": "Maximum number of resolved callables to inject into the next turn. `0` lets the selector choose a bounded set."
287+
"default": 5,
288+
"description": "Maximum number of resolved callables to inject into the next turn. Defaults to `5`, and is capped at `16`."
318289
}
319290
},
320291
"required": [],
@@ -348,6 +319,7 @@ impl Agent<AgentCtx> for ToolsSelect {
348319
}
349320

350321
let definitions = ctx.definitions(None).await;
322+
let total_tools = definitions.len();
351323
let tool_definitions = if !args.tools.is_empty() {
352324
select_requested_definitions(definitions, &args.tools)
353325
} else {
@@ -356,8 +328,9 @@ impl Agent<AgentCtx> for ToolsSelect {
356328
};
357329

358330
Ok(AgentOutput {
359-
content: serde_json::to_string(&ToolsSelectOutput {
331+
content: serde_json::to_string(&ToolsOutput {
360332
tools: tool_definitions,
333+
total_tools,
361334
})?,
362335
..Default::default()
363336
})
@@ -366,7 +339,7 @@ impl Agent<AgentCtx> for ToolsSelect {
366339

367340
async fn select_requested_names_with_model(
368341
ctx: &AgentCtx,
369-
candidates: &[ToolsSearchItem],
342+
candidates: &[FunctionDefinition],
370343
query: &str,
371344
limit: usize,
372345
) -> Vec<String> {
@@ -471,12 +444,12 @@ const TOKEN_NAME_MATCH_WEIGHT: usize = 100;
471444
const TOKEN_DESCRIPTION_MATCH_WEIGHT: usize = 10;
472445

473446
fn rank_search_items(
474-
items: Vec<ToolsSearchItem>,
447+
items: &[FunctionDefinition],
475448
normalized_query: &str,
476449
normalized_tokens: &[(String, usize)],
477450
fallback: bool,
478-
) -> Vec<ToolsSearchItem> {
479-
let mut candidates: Vec<(bool, usize, String, ToolsSearchItem)> = Vec::new();
451+
) -> Vec<String> {
452+
let mut candidates: Vec<(bool, usize, String)> = Vec::new();
480453
for item in items {
481454
let normalized_name = item
482455
.name
@@ -508,9 +481,9 @@ fn rank_search_items(
508481
}
509482

510483
if score > 0 {
511-
candidates.push((exact_name_match, score, normalized_name, item));
484+
candidates.push((exact_name_match, score, normalized_name));
512485
} else if fallback {
513-
candidates.push((false, 0, normalized_name, item));
486+
candidates.push((false, 0, normalized_name));
514487
}
515488
}
516489

@@ -520,7 +493,7 @@ fn rank_search_items(
520493
.then_with(|| a.2.cmp(&b.2))
521494
});
522495

523-
candidates.into_iter().map(|(_, _, _, item)| item).collect()
496+
candidates.into_iter().map(|(_, _, name)| name).collect()
524497
}
525498

526499
fn select_requested_definitions(
@@ -535,7 +508,6 @@ fn select_requested_definitions(
535508
}
536509

537510
let mut seen_requests = BTreeSet::new();
538-
let mut seen_definitions = BTreeSet::new();
539511
let mut selected = Vec::new();
540512

541513
for name in requested {
@@ -544,10 +516,8 @@ fn select_requested_definitions(
544516
continue;
545517
}
546518

547-
if let Some(def) = index.get(&lookup)
548-
&& seen_definitions.insert(def.name.clone())
549-
{
550-
selected.push(def.clone());
519+
if let Some(def) = index.remove(&lookup) {
520+
selected.push(def);
551521
}
552522
}
553523

@@ -712,15 +682,15 @@ mod tests {
712682
}
713683
}
714684

715-
async fn run_search(ctx: AgentCtx, args: ToolsSearchArgs) -> ToolsSearchOutput {
685+
async fn run_search(ctx: AgentCtx, args: ToolsSearchArgs) -> ToolsOutput {
716686
let output = ToolsSearch::new()
717687
.run(ctx, serde_json::to_string(&args).unwrap(), Vec::new())
718688
.await
719689
.unwrap();
720690
serde_json::from_str(&output.content).unwrap()
721691
}
722692

723-
async fn run_select(ctx: AgentCtx, args: ToolsSelectArgs) -> ToolsSelectOutput {
693+
async fn run_select(ctx: AgentCtx, args: ToolsSelectArgs) -> ToolsOutput {
724694
let output = ToolsSelect::new()
725695
.run(ctx, serde_json::to_string(&args).unwrap(), Vec::new())
726696
.await

0 commit comments

Comments
 (0)