mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-05 08:10:39 +00:00
Fix web_fetch summarizer routing to wrong LLM provider
The WebFetchSummarizer was sending requests without specifying a provider, so they always routed to the default (Anthropic). When using the OpenAI or Gemini profile, the summarizer model (e.g. gpt-4o-mini) was rejected by Anthropic with a 404. Add a `provider` field to WebFetchSummarizer so the summarization request routes to the correct provider. Also improve the error message to include the model name, and relax the parity test assertion to accept summarized content. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
13877b4e81
commit
3263d0c21e
3 changed files with 73 additions and 8 deletions
|
|
@ -150,9 +150,15 @@ fn build_summarizer(provider: &str, llm_client: Option<Client>) -> Option<crate:
|
|||
// anthropic and unknown providers
|
||||
_ => "claude-haiku-4-5-20251001",
|
||||
};
|
||||
let provider_name = match provider {
|
||||
"openai" => Some("openai".to_string()),
|
||||
"gemini" => Some("gemini".to_string()),
|
||||
_ => None,
|
||||
};
|
||||
Some(crate::tools::WebFetchSummarizer {
|
||||
client,
|
||||
model: model.into(),
|
||||
provider: provider_name,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const MAX_WEB_FETCH_BYTES: usize = 100 * 1024;
|
|||
pub struct WebFetchSummarizer {
|
||||
pub client: Client,
|
||||
pub model: String,
|
||||
pub provider: Option<String>,
|
||||
}
|
||||
|
||||
/// Returns true if the input looks like it contains HTML markup.
|
||||
|
|
@ -535,7 +536,7 @@ pub(crate) fn make_web_fetch_tool(summarizer: Option<WebFetchSummarizer>) -> Reg
|
|||
let request = Request {
|
||||
model: s.model.clone(),
|
||||
messages: vec![Message::user(summarization_prompt)],
|
||||
provider: None,
|
||||
provider: s.provider.clone(),
|
||||
tools: None,
|
||||
tool_choice: None,
|
||||
response_format: None,
|
||||
|
|
@ -547,7 +548,9 @@ pub(crate) fn make_web_fetch_tool(summarizer: Option<WebFetchSummarizer>) -> Reg
|
|||
metadata: None,
|
||||
provider_options: None,
|
||||
};
|
||||
let response = s.client.complete(&request).await.map_err(|e| format!("Summarization failed: {e}"))?;
|
||||
let response = s.client.complete(&request).await.map_err(|e| {
|
||||
format!("web_fetch summarization (model={}) failed: {e}", s.model)
|
||||
})?;
|
||||
Ok(response.text())
|
||||
}
|
||||
(Some(_), None) => {
|
||||
|
|
@ -982,6 +985,7 @@ mod tests {
|
|||
let summarizer = WebFetchSummarizer {
|
||||
client,
|
||||
model: "mock-model".into(),
|
||||
provider: None,
|
||||
};
|
||||
|
||||
let tool = make_web_fetch_tool(Some(summarizer));
|
||||
|
|
@ -1027,6 +1031,59 @@ mod tests {
|
|||
assert!(output.contains("Rust is a systems programming language"), "should contain page content, got: {output}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn web_fetch_summarizer_routes_to_specified_provider() {
|
||||
use crate::test_support::{MockErrorProvider, MockLlmProvider, text_response};
|
||||
use llm::error::{ProviderErrorDetail, ProviderErrorKind, SdkError};
|
||||
use llm::provider::ProviderAdapter;
|
||||
|
||||
// "other_provider" is the default — it rejects all requests.
|
||||
let default_provider: Arc<dyn ProviderAdapter> = Arc::new(
|
||||
MockErrorProvider {
|
||||
error: SdkError::Provider {
|
||||
kind: ProviderErrorKind::NotFound,
|
||||
detail: Box::new(ProviderErrorDetail::new(
|
||||
"model not found", "other_provider",
|
||||
)),
|
||||
},
|
||||
},
|
||||
);
|
||||
// "target_provider" has the model we actually want.
|
||||
let target_provider: Arc<dyn ProviderAdapter> = Arc::new(
|
||||
MockLlmProvider::new(vec![text_response("summarized content")]),
|
||||
);
|
||||
|
||||
let mut providers = HashMap::new();
|
||||
providers.insert("other_provider".to_string(), default_provider);
|
||||
providers.insert(target_provider.name().to_string(), target_provider);
|
||||
let client = Client::new(providers, Some("other_provider".into()), vec![]);
|
||||
|
||||
let summarizer = WebFetchSummarizer {
|
||||
client,
|
||||
model: "target-model".into(),
|
||||
provider: Some("mock".into()),
|
||||
};
|
||||
|
||||
let tool = make_web_fetch_tool(Some(summarizer));
|
||||
let env: Arc<dyn ExecutionEnvironment> = Arc::new(MockExecutionEnvironment {
|
||||
exec_result: ExecResult {
|
||||
stdout: "<html><body><p>Page content</p></body></html>".into(),
|
||||
stderr: String::new(),
|
||||
exit_code: 0,
|
||||
timed_out: false,
|
||||
duration_ms: 100,
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
let result = (tool.executor)(
|
||||
serde_json::json!({"url": "https://example.com", "prompt": "Summarize this"}),
|
||||
ToolContext { env, cancel: CancellationToken::new() },
|
||||
)
|
||||
.await;
|
||||
let output = result.expect("summarization should succeed when provider is correctly routed");
|
||||
assert_eq!(output, "summarized content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn html_to_markdown_converts_basic_html() {
|
||||
let result = html_to_markdown("<h1>Hello</h1><p>World</p>");
|
||||
|
|
|
|||
|
|
@ -8,14 +8,15 @@ use agent::{
|
|||
use llm::client::Client;
|
||||
|
||||
fn build_summarizer(provider: &str, client: &Client) -> WebFetchSummarizer {
|
||||
let summarizer_model = match provider {
|
||||
"openai" => "gpt-4o-mini",
|
||||
"gemini" => "gemini-2.0-flash",
|
||||
_ => "claude-haiku-4-5-20251001",
|
||||
let (summarizer_model, provider_name) = match provider {
|
||||
"openai" => ("gpt-4o-mini", Some("openai".to_string())),
|
||||
"gemini" => ("gemini-2.0-flash", Some("gemini".to_string())),
|
||||
_ => ("claude-haiku-4-5-20251001", None),
|
||||
};
|
||||
WebFetchSummarizer {
|
||||
client: client.clone(),
|
||||
model: summarizer_model.into(),
|
||||
provider: provider_name,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -420,9 +421,10 @@ async fn scenario_web_fetch(session: &mut Session, dir: &Path) {
|
|||
let path = dir.join("fetched.txt");
|
||||
assert!(path.exists(), "fetched.txt should have been created");
|
||||
let content = std::fs::read_to_string(&path).expect("failed to read fetched.txt");
|
||||
let lower = content.to_lowercase();
|
||||
assert!(
|
||||
content.contains("Example Domain"),
|
||||
"Expected 'Example Domain' in fetched content, got first 200 chars: {}",
|
||||
lower.contains("example domain") || lower.contains("example.com"),
|
||||
"Expected 'Example Domain' or 'example.com' in fetched content, got first 200 chars: {}",
|
||||
&content[..content.len().min(200)]
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue