tool出力制限の修正
This commit is contained in:
@@ -650,10 +650,33 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||
}
|
||||
};
|
||||
|
||||
// Cap `content` byte-size before it enters history. This is the
|
||||
// single chokepoint that protects the next LLM request from
|
||||
// blowing past the provider's per-minute input-token limit; no
|
||||
// individual tool is trusted to self-limit.
|
||||
// Phase 3: Apply post_tool_call interceptor
|
||||
for tool_result in &mut results {
|
||||
if let Some((tool_call, meta, tool)) = call_info_map.get(&tool_result.tool_use_id) {
|
||||
let mut info = ToolResultInfo {
|
||||
call: tool_call.clone(),
|
||||
result: tool_result.clone(),
|
||||
meta: meta.clone(),
|
||||
tool: tool.clone(),
|
||||
};
|
||||
|
||||
match self.interceptor.post_tool_call(&mut info).await {
|
||||
PostToolAction::Continue => {}
|
||||
PostToolAction::Abort(reason) => {
|
||||
self.last_run_interrupted = true;
|
||||
return Err(WorkerError::Aborted(reason));
|
||||
}
|
||||
}
|
||||
// Reflect interceptor-modified results
|
||||
*tool_result = info.result;
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 4: Cap `content` byte-size before it enters history.
|
||||
// Runs *after* post_tool_call so interceptors (audit, logging,
|
||||
// classification) still observe the full content, and any
|
||||
// content they inject is also truncated — closing the last gap
|
||||
// before the data reaches the next LLM request.
|
||||
if let Some(limits) = self.tool_output_limits.as_ref() {
|
||||
for tool_result in &mut results {
|
||||
let Some(content) = tool_result.content.as_mut() else {
|
||||
@@ -677,28 +700,6 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: Apply post_tool_call interceptor
|
||||
for tool_result in &mut results {
|
||||
if let Some((tool_call, meta, tool)) = call_info_map.get(&tool_result.tool_use_id) {
|
||||
let mut info = ToolResultInfo {
|
||||
call: tool_call.clone(),
|
||||
result: tool_result.clone(),
|
||||
meta: meta.clone(),
|
||||
tool: tool.clone(),
|
||||
};
|
||||
|
||||
match self.interceptor.post_tool_call(&mut info).await {
|
||||
PostToolAction::Continue => {}
|
||||
PostToolAction::Abort(reason) => {
|
||||
self.last_run_interrupted = true;
|
||||
return Err(WorkerError::Aborted(reason));
|
||||
}
|
||||
}
|
||||
// Reflect interceptor-modified results
|
||||
*tool_result = info.result;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ToolExecutionResult::Completed(results))
|
||||
}
|
||||
|
||||
|
||||
@@ -80,8 +80,12 @@ pub struct WorkerManifest {
|
||||
pub max_turns: Option<NonZeroU32>,
|
||||
#[serde(default)]
|
||||
pub temperature: Option<f32>,
|
||||
/// Byte-size caps applied to tool `content` before it reaches the
|
||||
/// conversation history. The section is optional in TOML — when
|
||||
/// omitted, `ToolOutputLimits::default()` (16KB default cap, no
|
||||
/// per-tool overrides) is applied so truncation is on by default.
|
||||
#[serde(default)]
|
||||
pub tool_output: Option<ToolOutputLimits>,
|
||||
pub tool_output: ToolOutputLimits,
|
||||
}
|
||||
|
||||
/// Byte-size caps applied to tool execution `content` before it enters
|
||||
@@ -411,9 +415,11 @@ permission = "write"
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn omitted_tool_output_is_none() {
|
||||
fn omitted_tool_output_falls_back_to_default_16k() {
|
||||
let manifest = PodManifest::from_toml(MINIMAL_REQUIRED).unwrap();
|
||||
assert!(manifest.worker.tool_output.is_none());
|
||||
let limits = &manifest.worker.tool_output;
|
||||
assert_eq!(limits.default_max_bytes, 16 * 1024);
|
||||
assert!(limits.per_tool.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -428,7 +434,7 @@ permission = "write"
|
||||
Grep = 4096\n",
|
||||
);
|
||||
let manifest = PodManifest::from_toml(&toml).unwrap();
|
||||
let limits = manifest.worker.tool_output.unwrap();
|
||||
let limits = &manifest.worker.tool_output;
|
||||
assert_eq!(limits.default_max_bytes, 8192);
|
||||
assert_eq!(limits.limit_for("Read"), 32768);
|
||||
assert_eq!(limits.limit_for("Grep"), 4096);
|
||||
@@ -436,14 +442,14 @@ permission = "write"
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_output_default_max_bytes_is_16k() {
|
||||
fn empty_tool_output_section_uses_default_max_bytes() {
|
||||
let toml = MINIMAL_REQUIRED.replace(
|
||||
"[worker]\n",
|
||||
"[worker]\n\
|
||||
[worker.tool_output]\n",
|
||||
);
|
||||
let manifest = PodManifest::from_toml(&toml).unwrap();
|
||||
let limits = manifest.worker.tool_output.unwrap();
|
||||
let limits = &manifest.worker.tool_output;
|
||||
assert_eq!(limits.default_max_bytes, 16 * 1024);
|
||||
assert!(limits.per_tool.is_empty());
|
||||
}
|
||||
|
||||
@@ -846,9 +846,9 @@ pub fn apply_worker_manifest<C: LlmClient>(worker: &mut Worker<C>, wm: &WorkerMa
|
||||
}
|
||||
worker.set_request_config(config);
|
||||
worker.set_max_turns(wm.max_turns.map(|n| n.get()));
|
||||
worker.set_tool_output_limits(wm.tool_output.as_ref().map(|limits| ToolOutputLimits {
|
||||
default_max_bytes: limits.default_max_bytes,
|
||||
per_tool: limits.per_tool.clone(),
|
||||
worker.set_tool_output_limits(Some(ToolOutputLimits {
|
||||
default_max_bytes: wm.tool_output.default_max_bytes,
|
||||
per_tool: wm.tool_output.per_tool.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user