@ondeinference / onde / commits / c4ae4ad

Expose tool calling support

Co-Authored-By: siGit Code <noreply@sigit.si>

Seto Elkahfi committed Sep 9, 2026 at 23:11 UTC c4ae4ad51d316e42138c4a24ed103ee320253ba9
15 files changed +113 -11
Cargo.lock
+1 -1
@@ -3508,7 +3508,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
3508
3509 [[package]]
3510 name = "onde"
3511 -version = "1.2.4"
3511 +version = "1.2.5"
3512 dependencies = [
3513 "anyhow",
3514 "cc",
Cargo.toml
+1 -1
@@ -1,6 +1,6 @@
1 [package]
2 name = "onde"
3 -version = "1.2.4"
3 +version = "1.2.5"
4 edition = "2021"
5 description = "On-device inference engine for Apple silicon."
6 license = "MIT OR Apache-2.0"
sdk/dart/CHANGELOG.md
+4
@@ -1,3 +1,7 @@
1 +## 1.2.5
2 +
3 +- Added engine-level tool-calling capability detection and tool-result history support for agent SDKs
4 +
5 ## 1.2.4
6
7 - Dependency updates across the Rust tree
sdk/dart/pubspec.yaml
+1 -1
@@ -2,7 +2,7 @@ name: onde_inference
2 description: >-
3 On-device LLM inference for Flutter & Dart. Run Qwen 2.5 models locally
4 with Metal on iOS and macOS, CPU on Android and desktop. No cloud, no API key.
5 -version: 1.2.4
5 +version: 1.2.5
6 repository: https://github.com/ondeinference/onde
7 homepage: https://ondeinference.com
8 documentation: https://ondeinference.com/sdk/dart
sdk/dart/rust/Cargo.lock
+1 -1
@@ -3604,7 +3604,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
3604
3605 [[package]]
3606 name = "onde"
3607 -version = "1.2.4"
3607 +version = "1.2.5"
3608 dependencies = [
3609 "anyhow",
3610 "cc",
sdk/kotlin/gradle.properties
+1 -1
@@ -10,7 +10,7 @@ kotlin.code.style=official
10 # Maven publishing coordinates — keep in sync with Cargo.toml [package] version
11 GROUP=com.ondeinference
12 POM_ARTIFACT_ID=onde-inference
13 -VERSION_NAME=1.2.4
13 +VERSION_NAME=1.2.5
14
15
16
sdk/react-native/CHANGELOG.md
+4
@@ -1,3 +1,7 @@
1 +## 1.2.5
2 +
3 +- Added engine-level tool-calling capability detection and tool-result history support for agent SDKs
4 +
5 ## 1.2.4
6
7 - Dependency updates across the Rust tree
sdk/react-native/package-lock.json
+2 -2
@@ -1,12 +1,12 @@
1 {
2 "name": "@ondeinference/react-native",
3 - "version": "1.2.4",
3 + "version": "1.2.5",
4 "lockfileVersion": 3,
5 "requires": true,
6 "packages": {
7 "": {
8 "name": "@ondeinference/react-native",
9 - "version": "1.2.4",
9 + "version": "1.2.5",
10 "license": "MIT OR Apache-2.0",
11 "devDependencies": {
12 "expo-modules-core": "^2.0.0",
sdk/react-native/package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "@ondeinference/react-native",
3 - "version": "1.2.4",
3 + "version": "1.2.5",
4 "description": "On-device LLM inference for React Native. Run Qwen 2.5 models locally with Metal on iOS, CPU on Android. No cloud, no API key.",
5 "main": "build/index.js",
6 "types": "build/index.d.ts",
sdk/react-native/rust/Cargo.lock
+2 -2
@@ -3420,7 +3420,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
3420
3421 [[package]]
3422 name = "onde"
3423 -version = "1.2.4"
3423 +version = "1.2.5"
3424 dependencies = [
3425 "anyhow",
3426 "cc",
@@ -3777,7 +3777,7 @@ dependencies = [
3777
3778 [[package]]
3779 name = "onde-react-native"
3780 -version = "1.2.4"
3780 +version = "1.2.5"
3781 dependencies = [
3782 "jni",
3783 "log",
sdk/react-native/rust/Cargo.toml
+1 -1
@@ -1,6 +1,6 @@
1 [package]
2 name = "onde-react-native"
3 -version = "1.2.4"
3 +version = "1.2.5"
4 edition = "2021"
5 publish = false
6
src/inference/engine.rs
+39
@@ -278,6 +278,22 @@ pub struct ChatEngine {
278 target_os = "android"
279 ))]
280 impl ChatEngine {
281 + /// Whether the currently loaded model is verified for structured tool calls.
282 + pub async fn tool_calling_support(&self) -> super::types::ToolCallingSupport {
283 + use super::types::ToolCallingSupport;
284 +
285 + let guard = self.inner.lock().await;
286 + let Some(loaded) = guard.as_ref() else {
287 + return ToolCallingSupport::Unknown;
288 + };
289 + let model_id = match &loaded.config {
290 + LoadedModelConfig::Gguf(config) => &config.model_id,
291 + LoadedModelConfig::Uqff(config) => &config.model_id,
292 + #[cfg(target_os = "macos")]
293 + LoadedModelConfig::Isq(config) => &config.model_id,
294 + };
295 + super::models::tool_calling_support(model_id)
296 + }
297 // ── Construction ─────────────────────────────────────────────────────
298
299 /// Create a new engine with no model loaded and no Onde app association.
@@ -1482,6 +1498,23 @@ impl ChatEngine {
1498 })
1499 }
1500
1501 + /// Append tool results to history without starting another inference round.
1502 + ///
1503 + /// Agent hosts use this when a turn is cancelled after the assistant has
1504 + /// requested tools. Recording a result for every request keeps the history
1505 + /// structurally valid for the next turn.
1506 + pub async fn record_tool_results(&self, results: Vec<ToolResult>) {
1507 + let mut guard = self.inner.lock().await;
1508 + if let Some(loaded) = guard.as_mut() {
1509 + for result in results {
1510 + loaded.history.push(HistoryEntry::ToolResult {
1511 + tool_call_id: result.tool_call_id,
1512 + content: result.content,
1513 + });
1514 + }
1515 + }
1516 + }
1517 +
1518 /// Stream tool execution results back to the model.
1519 ///
1520 /// Similar to [`send_tool_results`](Self::send_tool_results) but returns
@@ -2036,6 +2069,12 @@ impl ChatEngine {
2069
2070 pub async fn push_history(&self, _message: ChatMessage) {}
2071
2072 + pub async fn tool_calling_support(&self) -> super::types::ToolCallingSupport {
2073 + super::types::ToolCallingSupport::Unknown
2074 + }
2075 +
2076 + pub async fn record_tool_results(&self, _results: Vec<ToolResult>) {}
2077 +
2078 pub async fn send_message(
2079 &self,
2080 _user_message: impl Into<String>,
src/inference/mod.rs
+1
@@ -81,6 +81,7 @@ pub use types::{
81 ToolAwareResult,
82 ToolCallInfo,
83 ToolCallRequest,
84 + ToolCallingSupport,
85 ToolDefinition,
86 ToolResult,
87 UqffModelConfig,
src/inference/models.rs
+42
@@ -231,6 +231,26 @@ pub const SUPPORTED_MODELS: &[&str] = &[
231 THEBLOKE_DEEPSEEK_CODER_6_7B_INSTRUCT_GGUF,
232 ];
233
234 +/// Return Onde's verified tool-calling capability for a model repository.
235 +///
236 +/// Qwen 3 models use mistral.rs' Qwen tool parser. Qwen 2.5 Coder 7B is the
237 +/// one verified Qwen 2.5 model in the built-in catalogue that also supports
238 +/// structured tool calls. Custom repositories remain `Unknown` rather than
239 +/// being rejected solely because Onde has not catalogued them.
240 +pub fn tool_calling_support(model_id: &str) -> super::types::ToolCallingSupport {
241 + use super::types::ToolCallingSupport;
242 +
243 + if model_id.to_ascii_lowercase().contains("qwen3")
244 + || model_id == BARTOWSKI_QWEN25_CODER_7B_INSTRUCT_GGUF
245 + {
246 + ToolCallingSupport::Supported
247 + } else if SUPPORTED_MODELS.contains(&model_id) {
248 + ToolCallingSupport::Unsupported
249 + } else {
250 + ToolCallingSupport::Unknown
251 + }
252 +}
253 +
254 /// Rich metadata for a supported model, used by the frontend to display
255 /// unavailable models that can be downloaded.
256 pub struct SupportedModelInfo {
@@ -416,6 +436,28 @@ pub fn tok_model_id_for_repo(hf_repo_id: &str) -> Option<&'static str> {
436 mod tests {
437 use super::*;
438
439 + #[test]
440 + fn tool_calling_capability_is_authoritative_for_known_and_custom_models() {
441 + use super::super::types::ToolCallingSupport;
442 +
443 + assert_eq!(
444 + tool_calling_support(BARTOWSKI_QWEN3_1_7B_GGUF),
445 + ToolCallingSupport::Supported
446 + );
447 + assert_eq!(
448 + tool_calling_support(BARTOWSKI_QWEN25_CODER_7B_INSTRUCT_GGUF),
449 + ToolCallingSupport::Supported
450 + );
451 + assert_eq!(
452 + tool_calling_support(BARTOWSKI_QWEN25_1_5B_INSTRUCT_GGUF),
453 + ToolCallingSupport::Unsupported
454 + );
455 + assert_eq!(
456 + tool_calling_support("acme/custom-agent-model"),
457 + ToolCallingSupport::Unknown
458 + );
459 + }
460 +
461 /// Every supported model must have a corresponding display-metadata entry
462 /// so the model-list UI can render it.
463 #[test]
src/inference/types.rs
+12
@@ -360,6 +360,18 @@ pub struct ToolResult {
360 pub content: String,
361 }
362
363 +/// Whether the loaded model can reliably produce structured tool calls.
364 +///
365 +/// `Unknown` is used for custom model repositories that Onde does not ship in
366 +/// its built-in catalogue. Callers may choose to try those models, but should
367 +/// surface that the capability has not been verified.
368 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
369 +pub enum ToolCallingSupport {
370 + Supported,
371 + Unsupported,
372 + Unknown,
373 +}
374 +
375 // ── Streaming chunk ──────────────────────────────────────────────────────────
376
377 /// A single streaming token chunk from the inference engine.