feat(providers): add native tool-call API support via chat_with_tools

Add chat_with_tools() to the Provider trait with a default fallback to
chat_with_history(). Implement native tool calling in OpenRouterProvider,
reusing existing NativeChatRequest/NativeChatResponse structs. Wire the
agent loop to use native tool calls when the provider supports them,
falling back to XML-based parsing otherwise.

Changes are purely additive to traits.rs and openrouter.rs. The only
deletions (36 lines) are within run_tool_call_loop() in loop_.rs where
the LLM call section was replaced with a branching if/else for native
vs XML tool calling.

Includes 5 new tests covering:
- chat_with_tools error path (missing API key)
- NativeChatResponse deserialization (tool calls only, mixed)
- parse_native_response conversion to ChatResponse
- tools_to_openai_format schema validation
This commit is contained in:
Vernon Stinebaker 2026-02-17 12:05:08 +08:00 committed by Chummy
parent a3fc894580
commit f322360248
3 changed files with 325 additions and 33 deletions

View file

@ -170,6 +170,23 @@ pub trait Provider: Send + Sync {
async fn warmup(&self) -> anyhow::Result<()> {
Ok(())
}
/// Chat with tool definitions for native function calling support.
/// The default implementation falls back to chat_with_history and returns
/// an empty tool_calls vector (prompt-based tool use only).
async fn chat_with_tools(
&self,
messages: &[ChatMessage],
_tools: &[serde_json::Value],
model: &str,
temperature: f64,
) -> anyhow::Result<ChatResponse> {
let text = self.chat_with_history(messages, model, temperature).await?;
Ok(ChatResponse {
text: Some(text),
tool_calls: Vec::new(),
})
}
}
#[cfg(test)]