1010
1111from opencontractserver .annotations .compact_json import iter_page_annotations
1212from opencontractserver .constants .llm import (
13+ EXTRACT_DEFAULT_MODEL ,
14+ EXTRACT_DEFAULT_TEMPERATURE ,
1315 NONE_RESULT_AGENT_COMMITTED ,
1416 NONE_RESULT_NO_FINAL ,
1517 NONE_RESULT_TOOL_LOOP ,
@@ -106,6 +108,9 @@ def _classify_none_result(messages: Optional[list[Any]]) -> str:
106108 if most_common and most_common [0 ][1 ] >= TOOL_LOOP_THRESHOLD :
107109 return NONE_RESULT_TOOL_LOOP
108110
111+ # Falls through for both "model never produced a ModelResponse" and
112+ # "model spoke but never called a tool" — both are integration
113+ # failures from the pipeline's perspective.
109114 return NONE_RESULT_NO_FINAL
110115
111116
@@ -317,39 +322,6 @@ def sync_add_sources(datacell, sources):
317322 if not prompt :
318323 raise ValueError ("Column must have either query or match_text!" )
319324
320- # 4. Build system prompt with constraints
321- # system_prompt_parts = [
322- # "You are a precise data extraction agent.",
323- # "Extract ONLY the requested information from the document.",
324- # "If the information is not present, return None rather than guessing.",
325- # ]
326-
327- # Add must_contain_text constraint
328- # if column.must_contain_text:
329- # system_prompt_parts.append(
330- # f"\nIMPORTANT: Only extract data from sections that contain the text: '{column.must_contain_text}'"
331- # )
332- # logger.info(f"Added must_contain_text constraint: {column.must_contain_text}")
333-
334- # Add limit_to_label constraint
335- # if column.limit_to_label:
336- # system_prompt_parts.append(
337- # f"\nIMPORTANT: Only extract data from annotations labeled as: '{column.limit_to_label}'"
338- # )
339- # logger.info(f"Added limit_to_label constraint: {column.limit_to_label}")
340-
341- # system_prompt = "\n".join(system_prompt_parts)
342- # logger.info(f"System prompt: {system_prompt[:200]}...")
343-
344- # 5. Build extra context from instructions and match_text
345- # extra_context_parts = []
346-
347- # if column.instructions:
348- # extra_context_parts.append(
349- # f"Additional instructions: {column.instructions}"
350- # )
351- # logger.info(f"Added instructions: {column.instructions[:100]}...")
352-
353325 # Handle special match_text with ||| separator (few-shot examples)
354326 if column .match_text and "|||" in column .match_text :
355327 examples = [
@@ -362,14 +334,7 @@ def sync_add_sources(datacell, sources):
362334 )
363335 logger .info (f"Added { len (examples )} few-shot examples from match_text" )
364336
365- # extra_context = (
366- # "\n\n".join(extra_context_parts) if extra_context_parts else None
367- # )
368-
369- # if extra_context:
370- # logger.info(f"Extra context: {extra_context[:200]}...")
371-
372- # 6. EXTRACT! 🚀
337+ # 4. EXTRACT! 🚀
373338 logger .info (f"Starting extraction for datacell { cell_id } :" )
374339 logger .info (f" - Document ID: { document .id } " )
375340 logger .info (
@@ -388,27 +353,24 @@ def sync_add_sources(datacell, sources):
388353 # Wrap the agent call in the context manager to capture messages
389354 with capture_run_messages () as messages :
390355 # Create a temporary agent and extract
356+ # ``EXTRACT_DEFAULT_TEMPERATURE`` (=0.3) is safe ONLY while
357+ # ``EXTRACT_DEFAULT_MODEL`` is OpenAI; passing it to a
358+ # Claude model would silently regress the issue #1381
359+ # reliability fix (Anthropic structured runs need T=0).
360+ # Both constants live next to each other in
361+ # ``constants/llm.py`` so this coupling stays visible.
362+ # TODO(#1381 follow-up): when the model becomes
363+ # column-configurable, gate the temperature on the model
364+ # family in the call site rather than the constants file.
391365 result = await agents .get_structured_response_from_document (
392366 document = document .id ,
393367 corpus = corpus_id ,
394368 prompt = prompt ,
395369 target_type = output_type ,
396370 framework = AgentFramework .PYDANTIC_AI ,
397- # Low temperature for consistent extraction. Note: this
398- # function-level pin bypasses the Anthropic temperature=0
399- # override in `_structured_response_raw` (issue #1381).
400- # Safe today because the model is also pinned to
401- # `openai:gpt-4o-mini`.
402- #
403- # TODO(#1381 follow-up): if/when the model becomes
404- # column-configurable, gate this temperature on the
405- # model family — passing ``temperature=0.3`` to a
406- # Claude model silently regresses the structured-output
407- # reliability fix this PR introduced. Track removal
408- # of this pin alongside the column-model feature.
409- temperature = 0.3 ,
371+ temperature = EXTRACT_DEFAULT_TEMPERATURE ,
410372 similarity_top_k = similarity_top_k ,
411- model = "openai:gpt-4o-mini" , # Fast and reliable
373+ model = EXTRACT_DEFAULT_MODEL ,
412374 user_id = datacell .creator .id ,
413375 )
414376
0 commit comments