fix: reasoning models reject tool_choice=required; bump to 1.0.2 (closes #503, #505) (#508)

This commit is contained in:
Ahmed Allam
2026-05-28 11:55:10 -07:00
committed by GitHub
parent f38c09a4eb
commit 5fe4cb0c55
5 changed files with 33 additions and 5 deletions
+8 -2
View File
@@ -109,13 +109,19 @@ def build_scope_context(scan_config: dict[str, Any]) -> dict[str, Any]:
def make_model_settings(
reasoning_effort: ReasoningEffort | None,
) -> ModelSettings:
# Anthropic + DeepSeek thinking reject ``tool_choice="required"`` outright
# when reasoning is enabled; OpenAI o-series accepts both but doesn't need
# the safety net. When reasoning is on we let the model self-select tools
# and rely on the system prompt + the ``_finish_tool_use_behavior`` callback
# to keep the loop converging on a lifecycle tool.
use_reasoning = reasoning_effort is not None and reasoning_effort != "none"
model_settings = ModelSettings(
parallel_tool_calls=False,
tool_choice="required",
tool_choice=None if use_reasoning else "required",
retry=DEFAULT_MODEL_RETRY,
include_usage=True,
)
if reasoning_effort is not None:
if use_reasoning:
model_settings = model_settings.resolve(
ModelSettings(reasoning=Reasoning(effort=reasoning_effort)),
)
+22 -1
View File
@@ -261,7 +261,7 @@ async def run_strix_scan(
async with coordinator._lock:
root_status = coordinator.statuses.get(root_id)
return await run_agent_loop(
result = await run_agent_loop(
agent=root_agent,
initial_input=initial_input,
run_config=run_config,
@@ -275,6 +275,27 @@ async def run_strix_scan(
event_sink=event_sink,
hooks=hooks,
)
if not interactive and result is not None:
final = getattr(result, "final_output", None)
scan_completed = False
if isinstance(final, str):
try:
parsed = json.loads(final)
scan_completed = bool(isinstance(parsed, dict) and parsed.get("scan_completed"))
except (ValueError, TypeError):
scan_completed = False
elif isinstance(final, dict):
scan_completed = bool(final.get("scan_completed"))
if not scan_completed:
logger.error(
"Scan %s ended without calling finish_scan. The agent "
"emitted a text-only turn instead of a lifecycle tool call, "
"so no executive report was written. Final output (first "
"300 chars): %r",
scan_id,
str(final)[:300],
)
return result # noqa: TRY300
except BaseException:
logger.exception("Strix scan %s failed", scan_id)
if root_id is not None: