fix(agent+orchestrator): 关 qwen 思考模式出干净 JSON + 修 bounce 死锁
CI / guardrails (push) Successful in 6s
CI / tests (push) Successful in 45s
CI / guardrails (pull_request) Successful in 8s
CI / tests (pull_request) Successful in 43s

BUG-A: qwen3.7-max 默认思考模式,重 impl 子任务把预算花在 reasoning_content
上不出最终 content JSON → json.loads("") 失败。task_executor._complete 两处
chat.completions.create 注入 extra_body={"enable_thinking": False}(env
AGENT_ENABLE_THINKING=1 可opt-in)。对齐 Qwen 官方结构化输出用法。

BUG-B: bounce 仅重开含 impl 文件的任务;impl 失败(0文件)+test成功时
reopens=0 → run 永远 running 空转。加兜底:reopens==0 时重开 FAILED 任务
(受 MAX_REVIEW_CYCLES 约束);仍无可重开则 accept_no_rework 走正常收敛终结。

契约测试全绿(runtime-contract/merge-smoke/workflow-e2e/contract-freeze)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
xiaohei
2026-06-22 05:44:00 +00:00
co-authored by Claude Opus 4.8
parent b0831645cf
commit cdb17a0d73
2 changed files with 39 additions and 8 deletions
+16 -1
View File
@@ -610,9 +610,19 @@ Return ONLY the JSON, no other text."""
rc = extra.get("reasoning_content") if isinstance(extra, dict) else None
return (rc or "").strip()
@staticmethod
def _thinking_extra_body() -> Optional[dict]:
"""qwen3.7-max defaults to THINKING mode: on heavy impl subtasks it spends the token
budget on `reasoning_content` and never emits the final `content` JSON, so json.loads("")
fails ("Expecting value: line 1 column 1"). Disable thinking for deterministic structured
execution. Set AGENT_ENABLE_THINKING=1 to opt back in (e.g. for non-structured analysis)."""
enabled = os.getenv("AGENT_ENABLE_THINKING", "false").strip().lower() in ("1", "true", "yes", "on")
return None if enabled else {"enable_thinking": False}
async def _complete(self, prompt: str, max_tokens: int) -> str:
"""Call the LLM with optional Jina MCP tools; handles the tool-call loop."""
extra_headers = self._model_attribution_headers()
extra_body = self._thinking_extra_body()
tools = await self._load_jina_tools()
messages = [{"role": "user", "content": prompt}]
@@ -623,6 +633,8 @@ Return ONLY the JSON, no other text."""
max_tokens=max_tokens,
extra_headers=extra_headers or None,
)
if extra_body:
kwargs["extra_body"] = extra_body
if tools:
kwargs["tools"] = tools
kwargs["tool_choice"] = "auto"
@@ -656,10 +668,13 @@ Return ONLY the JSON, no other text."""
# Fallback: ask for a final answer without tools
messages.append({"role": "user", "content": "Please provide your final answer now."})
response = await self.client.chat.completions.create(
fallback_kwargs: dict = dict(
model=self.model, messages=messages, max_tokens=max_tokens,
extra_headers=extra_headers or None,
)
if extra_body:
fallback_kwargs["extra_body"] = extra_body
response = await self.client.chat.completions.create(**fallback_kwargs)
self._record_openai_usage(response)
return self._message_text(response.choices[0].message)