Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions flocks/session/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -1338,11 +1338,19 @@ def _build_tool_catalog_prompt(self, agent: AgentInfo) -> Optional[str]:
)

def _should_use_text_tool_call_mode(self) -> bool:
# MiniMax models exposed through ThreatBook-managed gateways do not
# forward the OpenAI ``tool_calls`` field on their streaming chunks
# (observed on ``threatbook-cn-llm`` 2026-04: first ``ChoiceDelta``
# only contains ``content`` / ``role``). Without this opt-in the
# model can never request a tool and ends every turn with
# ``finish_reason=stop`` and zero tool calls. Force the MiniMax XML
# text-call protocol for these provider/model pairs so tools work.
model_lower = (self.model_id or "").lower()
provider_lower = (self.provider_id or "").lower()
minimax_text_tool_call_providers = {
"custom-threatbook-internal",
"custom-tb-inner",
"threatbook-cn-llm",
}
return (
"minimax" in model_lower
Expand Down
32 changes: 32 additions & 0 deletions tests/session/test_runner_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,38 @@ def test_enabled_for_custom_tb_inner_minimax(self):
)
assert runner._should_use_text_tool_call_mode() is True

def test_enabled_for_threatbook_cn_llm_minimax(self):
# Regression: threatbook-cn-llm gateway strips the OpenAI tool_calls
# field for MiniMax models, so the XML text-call protocol must be
# forced or every turn ends with finish_reason=stop and zero tools.
session = _make_session("ses_minimax_threatbook_cn_llm")
runner = SessionRunner(
session=session,
provider_id="threatbook-cn-llm",
model_id="minimax-m2.7",
)
assert runner._should_use_text_tool_call_mode() is True

def test_enabled_for_threatbook_cn_llm_minimax_case_insensitive(self):
session = _make_session("ses_minimax_threatbook_cn_llm_case")
runner = SessionRunner(
session=session,
provider_id="ThreatBook-CN-LLM",
model_id="MiniMax-M2.5",
)
assert runner._should_use_text_tool_call_mode() is True

def test_disabled_for_threatbook_cn_llm_non_minimax(self):
# Other models routed through the same gateway (e.g. qwen, GLM) keep
# the standard OpenAI native function-calling path.
session = _make_session("ses_threatbook_cn_llm_qwen")
runner = SessionRunner(
session=session,
provider_id="threatbook-cn-llm",
model_id="qwen3.6-plus",
)
assert runner._should_use_text_tool_call_mode() is False

def test_disabled_for_other_models(self):
session = _make_session("ses_normal_mode")
runner = SessionRunner(
Expand Down
Loading