Ctrl K
ComunidadGuías…

tests/test_5072_splittable_under_cap_retry.py

Rama v8 · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Splittable documents below character cap can bisect after output truncation (#5072)."""
from pathlib import Path
from unittest.mock import patch

import pytest

import graphify.llm as llm
from graphify.file_slice import (
    FileSlice,
    is_splittable_text,
    read_slice_text,
    unit_source_text,
)


@pytest.mark.parametrize("ext", [".md", ".txt", ".rst"])
def test_splittable_document_under_cap_bisects_on_truncation(tmp_path, ext):
    """When a splittable document below _FILE_CHAR_CAP truncates (finish_reason='length'),
    adaptive retry should promote the raw Path unit to a full-range FileSlice and bisect it,
    completing without being marked partial (#5072).
    """
    path = tmp_path / f"dense{ext}"
    content = "# Title\n\n" + "".join(f"Paragraph {i} with some content.\n\n" for i in range(40))
    path.write_text(content, encoding="utf-8")
    text = unit_source_text(path)
    assert len(text) < llm._FILE_CHAR_CAP

    raw = llm.expand_oversized_files([path], llm._FILE_CHAR_CAP)
    assert raw == [path], "File below cap must initially stay a raw Path"

    calls = []

    def mock_extract(chunk, **kwargs):
        calls.append(chunk)
        chunk_text = "".join(
            read_slice_text(u) if isinstance(u, FileSlice) else unit_source_text(u)
            for u in chunk
        )
        if len(chunk_text) > 700:
            return {
                "nodes": [{"id": f"node_{len(calls)}", "label": "n", "source_file": str(path)}],
                "edges": [],
                "hyperedges": [],
                "finish_reason": "length",
            }
        return {
            "nodes": [{"id": f"leaf_{len(calls)}", "label": "leaf", "source_file": str(path)}],
            "edges": [],
            "hyperedges": [],
            "finish_reason": "stop",
        }

    with patch.object(llm, "extract_files_direct", side_effect=mock_extract):
        result = llm._extract_with_adaptive_retry(
            raw, "kimi", "offline", "offline", path.parent, max_depth=3
        )

    is_partial = bool(result.get("partial") or result.get("_partial_files"))
    assert not is_partial, "Splittable document under cap should bisect and recover from truncation"
    assert result["finish_reason"] == "stop"
    assert len(calls) >= 3, f"Expected at least 1 root truncated call + bisected slice calls, got {len(calls)}"
    for subcall in calls[1:]:
        assert isinstance(subcall[0], FileSlice), "Bisected unit must be a FileSlice"


def test_code_file_under_cap_does_not_bisect_on_truncation(tmp_path):
    """Code files must never be sliced or bisected; they remain whole and partial on truncation."""
    path = tmp_path / "dense.py"
    path.write_text("x = 1\n" * 100, encoding="utf-8")
    raw = [path]

    calls = []

    def mock_extract(chunk, **kwargs):
        calls.append(chunk)
        return {
            "nodes": [{"id": "code_node", "label": "n", "source_file": str(path)}],
            "edges": [],
            "hyperedges": [],
            "finish_reason": "length",
        }

    with patch.object(llm, "extract_files_direct", side_effect=mock_extract):
        result = llm._extract_with_adaptive_retry(
            raw, "kimi", "offline", "offline", path.parent, max_depth=3
        )

    is_partial = bool(result.get("partial") or result.get("_partial_files"))
    assert is_partial, "Code file must stay partial when truncated"
    assert len(calls) == 1, "Code file must not be bisected"