Skip to content

Commit c1cf235

Browse files
committed
fix retain zero retry initial call
1 parent 37fa0ad commit c1cf235

2 files changed

Lines changed: 30 additions & 2 deletions

File tree

hindsight-api-slim/hindsight_api/engine/retain/fact_extraction.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,10 +1362,11 @@ async def _extract_facts_from_chunk(
13621362
llm_max_retries = (
13631363
config.retain_llm_max_retries if config.retain_llm_max_retries is not None else config.llm_max_retries
13641364
)
1365+
extraction_attempts = max(1, llm_max_retries)
13651366
last_error: Exception | None = None
13661367

13671368
usage = TokenUsage() # Track cumulative usage across retries
1368-
for attempt in range(llm_max_retries):
1369+
for attempt in range(extraction_attempts):
13691370
try:
13701371
initial_backoff = (
13711372
config.retain_llm_initial_backoff
@@ -1672,7 +1673,7 @@ def get_value(field_name):
16721673
# If we exhausted all retries, raise the last error or a descriptive fallback
16731674
if last_error is not None:
16741675
raise last_error
1675-
raise RuntimeError(f"Fact extraction failed after {llm_max_retries} attempts: LLM did not return valid JSON")
1676+
raise RuntimeError(f"Fact extraction failed after {extraction_attempts} attempts: LLM did not return valid JSON")
16761677

16771678

16781679
async def _extract_facts_with_auto_split(

hindsight-api-slim/tests/test_fact_extraction_retry.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,33 @@ async def test_retain_llm_max_retries_overrides_global():
263263
assert llm_config.call.call_count == 5
264264

265265

266+
@pytest.mark.asyncio
267+
async def test_zero_retain_retry_budget_still_makes_initial_call():
268+
"""A zero transport retry budget must not skip fact extraction."""
269+
from hindsight_api.engine.retain.fact_extraction import _extract_facts_from_chunk
270+
271+
config = _make_config(llm_max_retries=10, retain_llm_max_retries=0)
272+
llm_config = _make_llm_config(mock_response={"facts": [], "entities": []})
273+
274+
with patch(
275+
"hindsight_api.engine.retain.fact_extraction._build_extraction_prompt_and_schema",
276+
return_value=("system prompt", MagicMock()),
277+
):
278+
await _extract_facts_from_chunk(
279+
chunk="Bob likes Python.",
280+
chunk_index=0,
281+
total_chunks=1,
282+
event_date=datetime(2024, 1, 1, tzinfo=timezone.utc),
283+
context="",
284+
llm_config=llm_config,
285+
config=config,
286+
agent_name="agent",
287+
)
288+
289+
llm_config.call.assert_awaited_once()
290+
assert llm_config.call.await_args.kwargs["max_retries"] == 0
291+
292+
266293
@pytest.mark.asyncio
267294
async def test_none_event_date_with_empty_facts_no_crash():
268295
"""

0 commit comments

Comments
 (0)