Skip to content

feat: Add text-tags fallback mode for LLM tool calling (fixes #1138) - #1149

Open
manideep-malyala wants to merge 2 commits into
usestrix:mainfrom
manideep-malyala:main
Open

feat: Add text-tags fallback mode for LLM tool calling (fixes #1138)#1149
manideep-malyala wants to merge 2 commits into
usestrix:mainfrom
manideep-malyala:main

Conversation

@manideep-malyala

Copy link
Copy Markdown

The Problem

Currently, the Strix execution engine relies on native, structured API tool calling formats (e.g. strict JSON schemas). While commercial models handle this well, many local, open-source models (like Llama-3, Mistral, or Qwen via Ollama/vLLM) lack fine-tuning for structured tool calling. When passed a list of tools, they often ignore them, hallucinate arguments, or output malformed JSON directly into their conversational text block, causing Strix to crash.

The Solution

This PR addresses #1138 by introducing a robust fallback mechanism: Text-Tag Tool Dispatch.
When enabled, Strix will instruct the LLM to output tool calls as plain-text markdown tags (e.g., [TOOL: tool_name] {"args": ...} [/TOOL]). An interceptor then strips these tags from the conversation history and translates them into the native ResponseFunctionToolCall instances that the Strix engine expects.

This creates an invisible compatibility layer, allowing any generic text-generating model to seamlessly use Strix tools.

Implementation Details

  1. CLI & Config Integration:
    • Added the --llm-tool-mode=text-tags CLI argument.
    • This maps to a new tool_mode parameter in settings.llm (defaulting to "native").
  2. Dynamic System Prompting:
    • build_scope_context now threads tool_mode down to the root and leaf agents.
    • When active, system_prompt.jinja dynamically appends formatting instructions teaching the model how to use the [TOOL: ...] syntax.
  3. Model Interceptor (models.py):
    • Added the _TextTagDispatchModel wrapper class.
    • It intercepts the text output stream, uses a regex to isolate and extract the [TOOL:]...[/TOOL] blocks, and synthesizes them into native Strix SDK tool calls.
    • It cleanly handles multiple tools in a single response and strips the tags from the final text payload sent to the user UI.
  4. Synchronous Aggregation:
    • Because regex parsing across fragmented stream chunks is brittle, enabling text-tags automatically wraps the LLM provider in _NonStreamingModel. This ensures the regex always acts on a fully aggregated text block.
  5. Testing:
    • Updated tests/test_runner_root_prompt.py to account for the new tool_mode attribute in the mock assertions. The full suite of 955 tests passes.

Closes #1138

@greptile-apps

greptile-apps Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a configurable text-tag fallback for models that cannot reliably produce native structured tool calls.

  • Adds the native/text-tags setting and CLI option.
  • Threads the selected mode into root and child system prompts.
  • Adds a model wrapper intended to translate tagged assistant text into SDK function calls and disables streaming for that mode.
  • The interceptor currently reads the SDK's structured message content as a string, preventing normal tagged calls from being converted.

Confidence Score: 4/5

This PR should not merge until the interceptor parses the SDK message content parts, because its new fallback currently fails to dispatch normal tagged tool calls.

The model wrapper checks for tags against a structured content list while existing SDK consumers extract text from each content part, so the main feature silently leaves tagged responses unconverted.

Files Needing Attention: strix/config/models.py

Important Files Changed

Filename Overview
strix/config/models.py Adds text-tag translation and non-streaming wrapping, but the translator does not inspect the SDK message content parts and therefore misses normal tagged calls.
strix/agents/prompts/system_prompt.jinja Adds conditional instructions describing the text-tag protocol when the fallback mode is active.
strix/config/settings.py Adds a validated tool_mode setting with an environment-variable alias and a native default.
strix/core/runner.py Propagates the resolved tool mode into prompt context shared by root and child agents.
strix/interface/cli_args.py Adds the mode selector and exposes a non-native selection through the settings environment variable.
tests/test_runner_root_prompt.py Updates the root prompt-context assertion but does not exercise text-tag response conversion.
Prompt To Fix All With AI
### Issue 1
strix/config/models.py:503-504
**Structured message content breaks dispatch**

When a model returns the prompted `[TOOL: ...]...[/TOOL]` call in a normal SDK assistant message, `item.content` is a list of structured text parts, so this list-membership check never finds the tag inside a part's text. The response remains unchanged and no `ResponseFunctionToolCall` is synthesized, preventing the fallback mode from dispatching the tool and allowing the tool-driven scan to progress.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat: Add text-tags fallback mode for LL..." | Re-trigger Greptile

Comment thread strix/config/models.py
Comment on lines +503 to +504
if content and "[TOOL:" in content:
matches = list(TEXT_TAG_PATTERN.finditer(content))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Structured message content breaks dispatch

When a model returns the prompted [TOOL: ...]...[/TOOL] call in a normal SDK assistant message, item.content is a list of structured text parts, so this list-membership check never finds the tag inside a part's text. The response remains unchanged and no ResponseFunctionToolCall is synthesized, preventing the fallback mode from dispatching the tool and allowing the tool-driven scan to progress.

Knowledge Base Used: Agents and Prompts

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/config/models.py
Line: 503-504

Comment:
**Structured message content breaks dispatch**

When a model returns the prompted `[TOOL: ...]...[/TOOL]` call in a normal SDK assistant message, `item.content` is a list of structured text parts, so this list-membership check never finds the tag inside a part's text. The response remains unchanged and no `ResponseFunctionToolCall` is synthesized, preventing the fallback mode from dispatching the tool and allowing the tool-driven scan to progress.

**Knowledge Base Used:** [Agents and Prompts](https://app.greptile.com/strix-org-3/-/custom-context/knowledge-base/usestrix/strix/-/docs/agents-and-prompts.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in the latest commit. The interceptor now handles cases where item.content is delivered as a list of structured TextPart dictionaries by normalizing it into a flat string before executing the regex search and extraction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Text-tag tool-dispatch fallback for local backends that don't reliably emit structured tool_calls

1 participant