diff --git a/src/llm_core.py b/src/llm_core.py index 77afbd1..1e3d12a 100644 --- a/src/llm_core.py +++ b/src/llm_core.py @@ -562,11 +562,18 @@ def _build_anthropic_headers(headers): return h def _parse_anthropic_response(data: dict) -> str: - """Extract text from Anthropic response.""" - for block in data.get("content", []): - if block.get("type") == "text": - return block.get("text", "") - return "" + """Extract text from an Anthropic response. + + The Messages API `content` is an array that can hold more than one text + block (e.g. text split around a tool_use block, or citation-segmented + text). Concatenate them all instead of returning only the first, which + silently dropped the rest of the reply. + """ + return "".join( + block.get("text", "") + for block in data.get("content", []) + if isinstance(block, dict) and block.get("type") == "text" + ) def _sanitize_llm_messages(messages: List[Dict]) -> List[Dict]: diff --git a/tests/test_anthropic_response_parse.py b/tests/test_anthropic_response_parse.py new file mode 100644 index 0000000..e41c9bb --- /dev/null +++ b/tests/test_anthropic_response_parse.py @@ -0,0 +1,27 @@ +"""Tests for _parse_anthropic_response (src/llm_core.py).""" + +from src.llm_core import _parse_anthropic_response + + +def test_concatenates_multiple_text_blocks(): + # Regression: only the first text block was returned, dropping the rest. + data = {"content": [ + {"type": "text", "text": "Part A "}, + {"type": "tool_use", "id": "t1", "name": "x", "input": {}}, + {"type": "text", "text": "Part B"}, + ]} + assert _parse_anthropic_response(data) == "Part A Part B" + + +def test_skips_non_text_blocks(): + data = {"content": [ + {"type": "thinking", "thinking": "..."}, + {"type": "text", "text": "answer"}, + ]} + assert _parse_anthropic_response(data) == "answer" + + +def test_single_block_and_empty(): + assert _parse_anthropic_response({"content": [{"type": "text", "text": "hi"}]}) == "hi" + assert _parse_anthropic_response({"content": []}) == "" + assert _parse_anthropic_response({}) == ""