fix(extract): disable tools for digest LLM extraction (no error_max_turns) #120

Merged
chaim merged 1 commits from worktree-digest-notools into main 2026-06-07 20:18:48 +00:00
2 changed files with 15 additions and 2 deletions

View File

@@ -82,6 +82,7 @@ async def query(
system: str | None = None,
model: str | None = None,
effort: str | None = None,
tools: str | None = None,
) -> str:
"""Send a prompt to Claude Code headless and return the text response.
@@ -104,6 +105,12 @@ async def query(
effort: Optional effort level (``low``/``medium``/``high``/``xhigh``/
``max``). When set, passed as ``--effort``. Pairs with ``model``;
an empty string is treated as "unset" (CLI default).
tools: Optional available-tools spec, passed as ``--tools``. Pass an
empty string (``""``) to disable ALL tools — for pure text→JSON
extraction the model has no reason to call a tool, and leaving
tools enabled makes it occasionally emit ``stop_reason: tool_use``
which trips ``--max-turns 1`` → ``error_max_turns`` and forces a
retry (slow). ``None`` leaves the CLI default (all tools).
Returns:
The text response from Claude.
@@ -126,6 +133,8 @@ async def query(
cmd += ["--model", model]
if effort:
cmd += ["--effort", effort]
if tools is not None: # "" → disable all tools (no tool_use → no max-turns trip)
cmd += ["--tools", tools]
size_info = f"; prompt_len={len(full_prompt):,} chars" if len(full_prompt) > 100_000 else ""
last_err = "unknown error"
@@ -204,13 +213,15 @@ async def query_json(
system: str | None = None,
model: str | None = None,
effort: str | None = None,
tools: str | None = None,
) -> dict | list | None:
"""Send a prompt and parse the response as JSON.
Uses parse_llm_json for robust parsing (handles markdown wrapping, truncation).
``model``/``effort`` are forwarded to :func:`query` (see its docstring).
``model``/``effort``/``tools`` are forwarded to :func:`query` (see its docstring).
Pure text→JSON extractors should pass ``tools=""`` to avoid ``error_max_turns``.
"""
raw = await query(prompt, timeout=timeout, system=system, model=model, effort=effort)
raw = await query(prompt, timeout=timeout, system=system, model=model, effort=effort, tools=tools)
return parse_llm_json(raw)

View File

@@ -101,6 +101,8 @@ async def extract(raw_text: str, model: str | None = None) -> dict:
result = await claude_session.query_json(
user_msg, system=DIGEST_EXTRACTION_PROMPT,
model=(model or config.DIGEST_EXTRACT_MODEL or None),
tools="", # pure text→JSON: disable tools so the model never emits
# stop_reason=tool_use and trips --max-turns (error_max_turns).
)
except Exception as e: # surfaced as warning, not swallowed silently (§6)
logger.warning("digest_metadata_extractor: query failed: %s", e)