Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pr_split/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
" the diff will be sent to that host"
)
LLM_OUTPUT_INCOMPLETE = "LLM output incomplete (status: {status}, reason: {reason})"
LLM_UNEXPECTED_STOP = "LLM stopped with stop_reason={stop_reason} (keys in tool input: {keys})"
CHUNK_RETRY = "Chunk {index}/{total} failed (attempt {attempt}), retrying: {error}"
INVALID_HUNK_INDEX = (
"Group '{group}': invalid hunk index {index} for {file} (max: {max}), skipping"
Expand Down
5 changes: 4 additions & 1 deletion pr_split/planner/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,12 @@ def _call_anthropic(system: str, user: str, *, settings: Settings) -> RawToolOut
if isinstance(block, BetaToolUseBlock) and isinstance(block.input, dict):
keys = list(block.input.keys())
break
logger.warning(logs.LLM_OUTPUT_TRUNCATED.format(stop_reason=stop_reason, keys=keys))
if stop_reason == "max_tokens":
logger.warning(logs.LLM_OUTPUT_TRUNCATED.format(stop_reason=stop_reason, keys=keys))
raise LLMError(ErrorMsg.LLM_OUTPUT_TRUNCATED(detail=f"stop_reason={stop_reason}"))
# Any other stop reason is not a truncation; if a tool block is
# present the plan is complete, otherwise the loop below reports it.
logger.debug(logs.LLM_UNEXPECTED_STOP.format(stop_reason=stop_reason, keys=keys))
for block in response.content:
if isinstance(block, BetaToolUseBlock) and block.name == SPLIT_TOOL_NAME:
return RawToolOutput(groups=_extract_raw_output(block.input))
Expand Down
19 changes: 19 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1337,6 +1337,25 @@ def test_failed_status_falls_back_to_error_code(self, mock_cls: MagicMock) -> No
_call_openai("sys", "usr", settings=_make_settings(Provider.OPENAI))


class TestTruncationWarningScope:
@patch("pr_split.planner.client.logger")
@patch("pr_split.planner.client.anthropic.Anthropic")
def test_end_turn_with_tool_block_does_not_warn_truncated(
self, mock_cls: MagicMock, mock_logger: MagicMock
) -> None:
from anthropic.types.beta import BetaToolUseBlock

block = BetaToolUseBlock(
id="tu_1", type="tool_use", name=SPLIT_TOOL_NAME, input={"groups": _SAMPLE_RAW_GROUPS}
)
mock_cls.return_value.beta.messages.create.return_value = SimpleNamespace(
stop_reason="end_turn", content=[block]
)
result = _call_anthropic("sys", "usr", settings=_make_settings(Provider.ANTHROPIC))
assert result["groups"] == _SAMPLE_RAW_GROUPS
assert not any("truncated" in str(c.args[0]) for c in mock_logger.warning.call_args_list)


class TestMergeChunkGroupsSameFile:
def test_same_file_in_later_chunk_is_merged_into_one_assignment(self) -> None:
first = Group(
Expand Down
Loading