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

חילוץ-המטא-דאטה של יומון הוא טקסט→JSON טהור, אבל ה-claude CLI רץ עם tools
זמינים, ו-Sonnet לפעמים פולט stop_reason=tool_use → פוגע ב---max-turns 1 →
error_max_turns → retry (איטי). מבזבז זמן רב בגיבוי-המוני.

- claude_session.query/query_json: פרמטר חדש `tools` → מועבר כ---tools.
  "" = ביטוי כל ה-tools (אין tool_use → אין max-turns trip). None = ברירת-CLI.
- digest_metadata_extractor.extract: מעביר tools="".

אומת: extract על יומון 5160 ב-Sonnet+tools="" → num_turns=1, JSON תקין, ללא
error_max_turns. claude_session נשאר local-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 20:18:29 +00:00
parent 879bb6c074
commit d3b5c563ce
2 changed files with 15 additions and 2 deletions

View File

@@ -82,6 +82,7 @@ async def query(
system: str | None = None, system: str | None = None,
model: str | None = None, model: str | None = None,
effort: str | None = None, effort: str | None = None,
tools: str | None = None,
) -> str: ) -> str:
"""Send a prompt to Claude Code headless and return the text response. """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``/ effort: Optional effort level (``low``/``medium``/``high``/``xhigh``/
``max``). When set, passed as ``--effort``. Pairs with ``model``; ``max``). When set, passed as ``--effort``. Pairs with ``model``;
an empty string is treated as "unset" (CLI default). 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: Returns:
The text response from Claude. The text response from Claude.
@@ -126,6 +133,8 @@ async def query(
cmd += ["--model", model] cmd += ["--model", model]
if effort: if effort:
cmd += ["--effort", 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 "" size_info = f"; prompt_len={len(full_prompt):,} chars" if len(full_prompt) > 100_000 else ""
last_err = "unknown error" last_err = "unknown error"
@@ -204,13 +213,15 @@ async def query_json(
system: str | None = None, system: str | None = None,
model: str | None = None, model: str | None = None,
effort: str | None = None, effort: str | None = None,
tools: str | None = None,
) -> dict | list | None: ) -> dict | list | None:
"""Send a prompt and parse the response as JSON. """Send a prompt and parse the response as JSON.
Uses parse_llm_json for robust parsing (handles markdown wrapping, truncation). 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) 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( result = await claude_session.query_json(
user_msg, system=DIGEST_EXTRACTION_PROMPT, user_msg, system=DIGEST_EXTRACTION_PROMPT,
model=(model or config.DIGEST_EXTRACT_MODEL or None), 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) except Exception as e: # surfaced as warning, not swallowed silently (§6)
logger.warning("digest_metadata_extractor: query failed: %s", e) logger.warning("digest_metadata_extractor: query failed: %s", e)