Skip to content

fix(inference/ollama): use prompt-guided tool calling so skills work on Ollama (#3098 sub-issue 3)#3229

Merged
senamakel merged 2 commits into
tinyhumansai:mainfrom
CodeGhost21:fix/3098-ollama-prompt-guided-tools
Jun 2, 2026
Merged

fix(inference/ollama): use prompt-guided tool calling so skills work on Ollama (#3098 sub-issue 3)#3229
senamakel merged 2 commits into
tinyhumansai:mainfrom
CodeGhost21:fix/3098-ollama-prompt-guided-tools

Conversation

@CodeGhost21
Copy link
Copy Markdown
Contributor

@CodeGhost21 CodeGhost21 commented Jun 2, 2026

Summary

  • Ollama provider now reports native_tool_calling = false. The agent harness embeds tool specs in the system prompt as text and parses tool calls out of the response (ToolsPayload::PromptGuided), which works across the Ollama model zoo.
  • Adds OpenAiCompatibleProvider::with_native_tool_calling(bool) builder. Default stays true — every other provider (OpenAI, OpenHuman backend, BYOK slugs, LM Studio) is unchanged.
  • 5 new tests pin the matrix: builder behavior + Ollama opts out + LM Studio keeps native.

Problem

Sub-issue 3 of #3098 reports "Skills fail to execute" — most visibly when the user routes a channel (e.g. Telegram) through an Ollama model.

Trace:

  1. Skills today are pure markdown instruction documents (the QuickJS skill runtime was removed). "Running a skill" means the agent reads the skill body and follows the instructions inside — many skills tell the model to "use the file_read tool" or "run shell X".
  2. OpenAiCompatibleProvider::capabilities() previously hardcoded native_tool_calling: true for every OpenAI-compat endpoint, including Ollama (compatible.rs:1248-1253).
  3. Ollama rejects the tools parameter with HTTP 400 ("unsupported parameter: tools") for many models.
  4. The existing retry path (compatible.rs:1748-1775) detects this and retries with tools: None — i.e. strips tools entirely.
  5. The model produces a natural-language reply but cannot invoke any tool. From the user's perspective, the skill "didn't run" — the model talks about doing the thing instead of doing it.

PR #3217 (sub-issue 1) makes this latent bug actively visible to Telegram users: Telegram now reliably routes to Ollama when chat_provider = "ollama:…" is set, so every skill-bearing turn hits this path.

Solution

Stop pretending Ollama supports native tool schemas. Build the Ollama provider with native_tool_calling = false from the start so the agent harness uses prompt-guided text tool specs — embedded in the system prompt, parsed back out of the response. Works on every Ollama model with no upstream tools array, no 400, no silent retry-without-tools.

Concretely:

  • Add native_tool_calling: bool field on OpenAiCompatibleProvider (defaults to true, preserving every existing construction path).
  • Add with_native_tool_calling(bool) builder method.
  • Change capabilities() to return the stored flag instead of hardcoded true.
  • factory::make_ollama_provider now constructs the provider directly (no helper indirection) and chains .with_native_tool_calling(false).

LM Studio, OpenHuman backend, BYOK cloud providers (openai:…, anthropic:…, etc.), and direct OpenAiCompatibleProvider::new callers are untouched — they keep the true default. Only the explicit Ollama factory branch opts out.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy — 3 unit tests in compatible_tests.rs (default true, override false, idempotent) + 2 factory tests (ollama_provider_opts_out_of_native_tool_calling, lmstudio_provider_keeps_native_tool_calling_enabled).
  • Diff coverage ≥ 80% — every changed line is exercised: the new native_tool_calling field is read by capabilities() (3 unit tests) and the Ollama factory branch is exercised by the new factory test; the LM Studio test is the regression guard.
  • Coverage matrix updated — N/A: behavior fix on an existing capability (Ollama provider); no new feature row.
  • All affected feature IDs from the matrix are listed in the PR description under ## RelatedN/A: no matrix row added or removed.
  • No new external network dependencies introduced (mock backend used per Testing Strategy) — all new tests construct providers in-process and inspect capabilities(); no network calls.
  • Manual smoke checklist updated if this touches release-cut surfaces (docs/RELEASE-MANUAL-SMOKE.md) — N/A: not a release-cut surface; behaves identically for cloud users.
  • Linked issue closed via Closes #NNN in the ## Related section — see below (Telegram interface ignores local model selection + can't commit files or run skills via Telegram on macOS #3098 has 4 sub-issues; this PR closes sub-issue 3 only, issue stays open).

Impact

  • Desktop: zero change for the dominant cloud path. Users routing the chat / reasoning / agentic / etc. workload to Ollama see tool calls actually fire — skills, file operations, shell, schedule, etc.
  • Channels (Telegram, Discord, Slack, iMessage, Mattermost — all share the same runtime via PR fix(channels): honor chat_provider workload routing in channel runtime (#3098 sub-issue 1) #3217): same gain. The "model replies in prose instead of executing the skill" bug stops happening over Ollama.
  • Performance: marginal positive — eliminates one HTTP 400 + retry roundtrip per turn for every Ollama-routed call that previously tried tools and got rejected.
  • Security: none. Same provider, same auth, same endpoint.
  • Migration: none. Default field value preserves every non-Ollama path.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/3098-ollama-prompt-guided-tools
  • Commit SHA: 6c504c7edd00ae84f74446a9b7b119f54207b67c

Validation Run

  • pnpm --filter openhuman-app format:checkN/A: Rust-only change.
  • pnpm typecheckN/A: no TS/TSX touched.
  • Focused tests: cargo test --lib -p openhuman inference::provider → 386 passed, 0 failed, 2 ignored; cargo test --lib -p openhuman channels:: agent::harness → 1489 passed, 0 failed (regression suite). All five new tests pass.
  • Rust fmt/check (if changed): cargo fmt --check clean; cargo check --lib clean (pre-existing warnings only).
  • Tauri fmt/check (if changed): N/A: app/src-tauri not touched.

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: Ollama provider stops sending the OpenAI-style tools array on outbound chat requests. The agent harness embeds tool specs in the system prompt as text and parses tool calls out of the response — the existing prompt-guided code path.
  • User-visible effect: skills (and any workflow that depends on tool calls) actually execute when the user picks an Ollama model. Previously the model talked about the action without performing it.

Parity Contract

  • Legacy behavior preserved: Every non-Ollama provider construction site (OpenHuman backend, BYOK cloud slugs, LM Studio, direct OpenAiCompatibleProvider::new callers) inherits the true default for native_tool_calling. Behavior is byte-identical pre/post for those paths. Verified by the LM Studio regression test and by the unchanged capabilities_reports_native_tool_calling test.
  • Guard/fallback/dispatch parity checks: The existing err_supports_no_tools_retry path (compatible.rs:1748-1775) and is_native_tool_schema_unsupported detector (compatible.rs:780-795) are untouched — they remain as a safety net for any other OpenAI-compat endpoint that rejects tools.

Duplicate / Superseded PR Handling

Summary by CodeRabbit

  • New Features

    • Providers gain a configurable native tool-calling capability (opt-in/opt-out).
  • Bug Fixes

    • Ollama provider now disables native tool calling to avoid compatibility issues with its API.
  • Tests

    • Added tests verifying native tool-calling toggles and that providers report consistent capability state.

…on Ollama (tinyhumansai#3098)

Ollama's `/v1/chat/completions` OpenAI-compat endpoint rejects the
`tools` parameter with HTTP 400 "unsupported parameter: tools" for many
models. The existing retry path in `compatible.rs` detects this and
retries with `tools: None` — i.e. strips tools entirely. The model
then replies in natural language but can't actually invoke any tool.

Skills are markdown instruction documents the agent reads on demand;
many skills tell the model to "use the file_read tool" or "run shell
X". When tools are silently stripped, the model talks about doing the
thing but doesn't do it — sub-issue 3 of tinyhumansai#3098: "skills not running."

The fix: report `native_tool_calling = false` from the Ollama branch of
`OpenAiCompatibleProvider::capabilities()`. The agent harness then
embeds tool specs as text in the system prompt and parses tool calls
out of the response (the `ToolsPayload::PromptGuided` path), which any
chat model can follow — no upstream `tools` array, no 400, no silent
retry-without-tools.

Implementation:
- Add `native_tool_calling: bool` field on `OpenAiCompatibleProvider`
  (defaults to `true`, preserving every existing call site).
- Add `with_native_tool_calling(bool)` builder.
- `capabilities()` returns the stored flag instead of hardcoded `true`.
- `factory::make_ollama_provider` constructs the provider directly and
  calls `.with_native_tool_calling(false)`. LM Studio, OpenHuman
  backend, BYOK cloud providers (OpenAI/Anthropic/etc.), and direct
  `OpenAiCompatibleProvider::new` callers are unaffected — they keep
  the default `true`.

Tests:
- 3 new unit tests in `compatible_tests.rs` pin the builder behavior:
  default true, `with_native_tool_calling(false)` flips it, repeated
  calls are idempotent.
- 2 new factory tests pin the integration: `ollama:<model>` reports
  `native_tool_calling=false`; `lmstudio:<model>` keeps it `true`.

Scope: this fix benefits every Ollama user (desktop, all channels),
not just Telegram. With PR tinyhumansai#3217, Telegram now reliably routes to
Ollama when the user picks it — making this latent bug actively
visible to those users. Closes sub-issue 3 of tinyhumansai#3098.
@CodeGhost21 CodeGhost21 requested a review from a team June 2, 2026 19:24
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fe5a451a-1cc5-4a8d-b2e4-d3bbb8c23923

📥 Commits

Reviewing files that changed from the base of the PR and between 6c504c7 and 29f4f66.

📒 Files selected for processing (2)
  • src/openhuman/inference/provider/compatible.rs
  • src/openhuman/inference/provider/compatible_tests.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/openhuman/inference/provider/compatible.rs

📝 Walkthrough

Walkthrough

Adds a configurable native_tool_calling flag to OpenAiCompatibleProvider (default true) with a builder to toggle it; capabilities() and supports_native_tools() reflect the flag. make_ollama_provider now disables native tool calling for Ollama; tests cover the flag and factory behavior.

Changes

Native Tool Calling Capability Configuration

Layer / File(s) Summary
Native tool calling capability flag and builder
src/openhuman/inference/provider/compatible.rs, src/openhuman/inference/provider/compatible_tests.rs
OpenAiCompatibleProvider adds native_tool_calling: bool (default true), with_native_tool_calling(enabled: bool) builder, updates capabilities() and supports_native_tools() to use the flag; unit tests verify disabling, enabling, and idempotence.
Ollama provider explicit tool calling disable
src/openhuman/inference/provider/factory.rs
make_ollama_provider uses OpenAiCompatibleProvider::new_no_responses_fallback and calls .with_native_tool_calling(false) so Ollama does not advertise native OpenAI-style tool calling while preserving temperature wiring.
Local provider tool calling capability tests
src/openhuman/inference/provider/factory_tests.rs
Factory tests assert Ollama reports native_tool_calling=false and LM Studio reports native_tool_calling=true, with a comment about BYOK regression test location.

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • M3gA-Mind

Poem

🐰 I toggled a flag with a gentle hop,
Ollama listens when the harness won't stop,
Tools stay silent where they cause a fight,
Prompts take the lead and do it right.
Hooray for builders kept tidy and light!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: enabling prompt-guided tool calling for Ollama by disabling native_tool_calling, which allows skills to work on Ollama models.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. bug labels Jun 2, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/inference/provider/compatible.rs`:
- Around line 210-217: The supports_native_tools() accessor must reflect the new
native_tool_calling flag so both capability signals agree: update the
supports_native_tools(&self) -> bool implementation to return
self.native_tool_calling (or otherwise consult the same state used by
capabilities()) instead of always true; ensure with_native_tool_calling,
capabilities(), and supports_native_tools all use the same native_tool_calling
field so callers using the old API get the corrected behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b576c0c7-2895-40f5-a15a-e7271181f6a6

📥 Commits

Reviewing files that changed from the base of the PR and between b156392 and 6c504c7.

📒 Files selected for processing (4)
  • src/openhuman/inference/provider/compatible.rs
  • src/openhuman/inference/provider/compatible_tests.rs
  • src/openhuman/inference/provider/factory.rs
  • src/openhuman/inference/provider/factory_tests.rs

Comment thread src/openhuman/inference/provider/compatible.rs
@CodeGhost21
Copy link
Copy Markdown
Contributor Author

CI failure on the first attempt was a flake — confirmed by rerunning lanes 1 and 2 of E2E (Playwright / web) on the same commit (6c504c7edd00ae84f74446a9b7b119f54207b67c). Both lanes pass green on the rerun without any code change. Full board is now ✅.

The three tests that flaked:

Failure shape on all three: getByText('<canary>') resolved to 2 elements. That's the streaming-preview vs final-message race — the canary text is briefly visible in both the live-streaming preview bubble and the finalized assistant bubble at the moment Playwright asserts. Timing-sensitive, unrelated to this PR's code change.

This PR's diff is scoped to OpenAiCompatibleProvider::capabilities() reading the new native_tool_calling field (defaults to true, so every existing call site is unchanged) and factory::make_ollama_provider flipping it to false. The cloud chat path used by these E2E tests routes through OpenHumanBackendProviderOpenAiCompatibleProvider::new, which keeps the true default — no behavior change for those code paths. Lanes 3 + 4, all Rust/Vitest/lint/fmt/coverage jobs, and the rerun of lanes 1 + 2 all confirm this.

…tools (tinyhumansai#3098)

CodeRabbit review on PR tinyhumansai#3229 caught that `OpenAiCompatibleProvider`
overrides `Provider::supports_native_tools()` with a hardcoded `true`
(`compatible.rs:1961`), bypassing the new `native_tool_calling` field.
The harness's actual gate for native-vs-prompt-guided tool dispatch
lives at `traits.rs:415` and reads `supports_native_tools()`, not
`capabilities().native_tool_calling` directly — so the original PR's
opt-out flipped the capability signal but the agent harness still sent
a `tools` array to Ollama and got HTTP 400.

Update the override to return `self.native_tool_calling` so all three
signals (`with_native_tool_calling(...)` builder, `capabilities()`,
`supports_native_tools()`) agree. Cloud providers default to `true`
exactly as before; only the Ollama factory branch now actually routes
through the prompt-guided path the original PR intended.

Regression test pins the invariant: after
`with_native_tool_calling(false)`, both `supports_native_tools()` and
`capabilities().native_tool_calling` must return `false`.
@senamakel senamakel merged commit 476d357 into tinyhumansai:main Jun 2, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants