Skip to content

Comments

feat: native tool_use for Claude and OpenAI providers#335

Merged
bug-ops merged 11 commits intomainfrom
feat/m20/native-tool-use
Feb 15, 2026
Merged

feat: native tool_use for Claude and OpenAI providers#335
bug-ops merged 11 commits intomainfrom
feat/m20/native-tool-use

Conversation

@bug-ops
Copy link
Owner

@bug-ops bug-ops commented Feb 15, 2026

Summary

  • Extend LlmProvider trait with supports_tool_use() and chat_with_tools() methods
  • Add ToolDefinition, ToolUseRequest, ChatResponse types to zeph-llm
  • Add ToolUse and ToolResult variants to MessagePart for structured message parts
  • Implement native Anthropic tool_use API in ClaudeProvider
  • Implement OpenAI function calling API in OpenAiProvider
  • Add dual-mode agent loop: native tool_use path when provider supports it, legacy text extraction otherwise
  • Split system prompt into native/legacy tool instructions to prevent fenced-block fallback
  • Delegate supports_tool_use/chat_with_tools through ModelOrchestrator and SubProvider
  • Implement execute_tool_call() in ShellExecutor for structured tool invocations
  • Generate tool parameter schemas via schemars (#[derive(JsonSchema)])

Test plan

  • All 1400 unit tests pass
  • Clippy clean (zero warnings)
  • End-to-end verified: Claude returns structured tool_use content blocks, agent executes tools, sends results back, Claude produces final response
  • Legacy text extraction path still works for providers without native tool_use (Ollama, Candle)

Extend LlmProvider trait with supports_tool_use() and chat_with_tools()
default methods. Add ToolDefinition, ChatResponse, and ToolUseRequest
types to zeph-llm. Add ToolUse/ToolResult variants to MessagePart with
serde support and flatten_parts() handling.

Implement Anthropic tool_use format in ClaudeProvider with structured
content blocks for tool_use/tool_result messages. Implement OpenAI
function calling format in OpenAiProvider with tool_calls parsing and
tool role messages.

Add dual-mode agent loop: process_response() branches on
supports_tool_use() into native tool path with structured execution
loop, doom-loop detection, and context budget checks. Legacy text
extraction path unchanged for Ollama/Candle providers.

Delegate new trait methods through AnyProvider. Backward compatible
via default implementations.

Closes #254, closes #255, closes #256, closes #257, closes #258
@github-actions github-actions bot added enhancement New feature or request size/XL llm LLM provider related rust core and removed enhancement New feature or request size/XL labels Feb 15, 2026
@github-actions github-actions bot added enhancement New feature or request documentation Improvements or additions to documentation size/XL and removed enhancement New feature or request labels Feb 15, 2026
@codecov-commenter
Copy link

codecov-commenter commented Feb 15, 2026

Codecov Report

❌ Patch coverage is 52.94811% with 399 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/zeph-core/src/agent/streaming.rs 1.81% 162 Missing ⚠️
crates/zeph-llm/src/openai.rs 62.08% 91 Missing ⚠️
crates/zeph-llm/src/claude.rs 67.63% 78 Missing ⚠️
crates/zeph-llm/src/orchestrator/mod.rs 0.00% 19 Missing ⚠️
crates/zeph-llm/src/orchestrator/router.rs 0.00% 18 Missing ⚠️
crates/zeph-tools/src/shell.rs 0.00% 15 Missing ⚠️
crates/zeph-llm/src/any.rs 0.00% 10 Missing ⚠️
crates/zeph-llm/src/provider.rs 97.39% 3 Missing ⚠️
crates/zeph-core/src/agent/context.rs 85.71% 1 Missing ⚠️
crates/zeph-core/src/agent/mod.rs 50.00% 1 Missing ⚠️
... and 1 more

❌ Your patch status has failed because the patch coverage (52.94%) is below the target coverage (60.00%). You can increase the patch coverage or adjust the target coverage.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #335      +/-   ##
==========================================
- Coverage   80.28%   79.35%   -0.94%     
==========================================
  Files          99       99              
  Lines       24241    25076     +835     
==========================================
+ Hits        19463    19900     +437     
- Misses       4778     5176     +398     
Files with missing lines Coverage Δ
crates/zeph-core/src/agent/context.rs 84.32% <85.71%> (-0.04%) ⬇️
crates/zeph-core/src/agent/mod.rs 82.36% <50.00%> (ø)
crates/zeph-core/src/context.rs 97.30% <93.75%> (-0.35%) ⬇️
crates/zeph-llm/src/provider.rs 90.52% <97.39%> (+2.57%) ⬆️
crates/zeph-llm/src/any.rs 91.20% <0.00%> (-3.47%) ⬇️
crates/zeph-tools/src/shell.rs 95.58% <0.00%> (-1.75%) ⬇️
crates/zeph-llm/src/orchestrator/router.rs 58.09% <0.00%> (-12.02%) ⬇️
crates/zeph-llm/src/orchestrator/mod.rs 91.89% <0.00%> (-3.59%) ⬇️
crates/zeph-llm/src/claude.rs 79.23% <67.63%> (-4.18%) ⬇️
crates/zeph-llm/src/openai.rs 74.66% <62.08%> (-5.13%) ⬇️
... and 1 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

SubProvider and ModelOrchestrator were missing delegation for the new
trait methods, causing the agent loop to always take the legacy text
extraction path even when the underlying provider supports native
tool_use. Add debug logging for path selection.
@github-actions github-actions bot added the enhancement New feature or request label Feb 15, 2026
ShellExecutor was missing execute_tool_call() implementation, inheriting
the default that returns None. Native tool_use calls to bash were
silently producing no output. Extract command from structured params and
delegate to existing execute_inner pipeline.
When the provider supports native tool_use, the tools are passed via
the API tools parameter. The text-based tool catalog in the system
prompt was causing Claude to respond with fenced code blocks instead
of structured tool_use calls. Skip prompt-based tool instructions for
native tool_use providers.
The trait default using fn -> impl Future (RPITIT) with a body was not
being overridden by async fn implementations in provider structs,
causing the default fallback to chat() to always be called. Change the
trait definition to async fn which correctly dispatches to overrides.
…r capable providers

Split BASE_PROMPT into header/legacy/native/tail sections. When provider
supports tool_use, system prompt instructs to use structured tool mechanism
instead of fenced code blocks. This was the root cause of Claude returning
text with backtick blocks despite tools being passed via API.
Add Native Tool Use section to tools guide describing the structured
API-level tool calling mechanism for Claude and OpenAI providers.
Rename existing section to Legacy Text Extraction. Update README with
native tool use feature entry and architecture description. Add system
prompt split entry to changelog.
@bug-ops bug-ops linked an issue Feb 15, 2026 that may be closed by this pull request
5 tasks
@bug-ops bug-ops merged commit 55f086c into main Feb 15, 2026
20 checks passed
@bug-ops bug-ops deleted the feat/m20/native-tool-use branch February 15, 2026 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core documentation Improvements or additions to documentation enhancement New feature or request llm LLM provider related rust size/XL

Projects

None yet

2 participants