fix(aggregator): צד שלם נמחק בשקט — chunking לפי גודל + כשל שמדווח (#233)
בתיק 1069-04-26 נשלחו 310 טענות עוררים בקריאת-Claude אחת. הקריאה החזירה לא-JSON, הקוד רשם warning והחזיר [], והפעולה דיווחה status=completed עם אפס טיעונים לצד המרכזי בערר. 495 propositions_processed — כלומר "עיבדתי הכל". שתי תקלות מובחנות, שתיהן מתוקנות: 1. **הקריאה גדולה מדי.** הפיצול הפר-כתב-טענות שנוסף למשיבים (#224) הסתיר את זה במקרה — הוא שמר על קריאות קטנות — אבל עוררים וּועדה מדברים בקול אחד ואינם מפוצלים לעולם, כך שערר גדול יוצא בקריאה אחת ענקית. ההערה בקוד כבר תיעדה בדיוק את הכשל הזה אצל המשיבים; התיקון פשוט לא הוחל על העוררים. עכשיו כל צד מעל MAX_PROPS_PER_CALL=80 נצבר בכמה קריאות ומשורשר. הפיצול הוא trade-off ולא רווח חינם: כל chunk מקובץ בבידוד, ולכן צד שמתפצל עלול לקבל יותר טיעונים (וחופפים במקצת) מאשר במעבר יחיד. לאבד ליטיגנט שלם גרוע יותר, והחלופה — פרומפט קטן יותר לכל פרופוזיציה — הייתה מנוונת כל תיק כדי לתקן את הגדולים. הסדר נשמר בחיתוך, כי טענות מגיעות ממוינות לפי claim_index ושכנות שייכות בד"כ לאותו ראש-טיעון. 2. **הכשל נבלע.** החזרת [] על תשובה לא-רשימה אינה ניתנת להבחנה מ"לצד הזה אין טיעונים". עכשיו נזרקת AggregationFailed, הקורא רושם אותה ב-errors, והסטטוס יורד ל-completed_with_errors (כלל-הנדסה §6). ההודעה נוקבת בשם הצד ובמספר הפרופוזיציות, אחרת מפעיל שרואה completed_with_errors לא יודע איזה ליטיגנט נעלם. מבחני רגרסיה: chunking לא מאבד ולא מסדר-מחדש (310→4 קריאות, רצף נשמר); תשובה לא-רשימה זורקת ומזכירה את שם הצד. אם המבחן השני יחזור אי-פעם לטעון == [] — באג הבליעה הוחזר. invariants: כלל-הנדסה §6 — אין בליעה שקטה. G1 — תיקון במקור (גודל הקריאה) ולא בקריאה. G2/G12 — לא נגועים; שני השערים ירוקים. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
54
mcp-server/tests/test_argument_aggregator.py
Normal file
54
mcp-server/tests/test_argument_aggregator.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""Regression tests for argument aggregation (#233).
|
||||
|
||||
Both tests cover the same 2026-08-05 incident from different angles: the
|
||||
appellant side of 1069-04-26 sent 310 propositions in one Claude call, the call
|
||||
came back as something other than a JSON array, and the code logged a warning
|
||||
and returned ``[]``. The caller could not tell that apart from "this side has no
|
||||
arguments", so ``aggregate_claims_to_arguments`` reported ``completed`` with the
|
||||
central litigant of the appeal missing entirely.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from legal_mcp.services.argument_aggregator import (
|
||||
MAX_PROPS_PER_CALL,
|
||||
AggregationFailed,
|
||||
_aggregate_party,
|
||||
_chunk,
|
||||
)
|
||||
|
||||
|
||||
def test_chunk_preserves_every_proposition_and_their_order():
|
||||
"""Chunking must not drop or reorder — losing claims here is invisible."""
|
||||
props = [{"i": i} for i in range(310)]
|
||||
|
||||
chunks = _chunk(props, MAX_PROPS_PER_CALL)
|
||||
|
||||
assert sum(len(c) for c in chunks) == 310, "propositions were lost"
|
||||
assert [p for c in chunks for p in c] == props, "order changed"
|
||||
assert all(len(c) <= MAX_PROPS_PER_CALL for c in chunks)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_non_list_reply_raises_instead_of_dropping_the_side(monkeypatch):
|
||||
"""A malformed reply must surface, never look like an empty side.
|
||||
|
||||
This is the exact 1069-04-26 failure. If this test ever goes back to
|
||||
asserting ``== []``, the silent-drop bug has been reintroduced.
|
||||
"""
|
||||
async def _query_json(prompt, tools=""): # noqa: ARG001
|
||||
return {"error": "not a list"}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"legal_mcp.services.argument_aggregator.claude_session.query_json",
|
||||
_query_json,
|
||||
)
|
||||
|
||||
with pytest.raises(AggregationFailed) as excinfo:
|
||||
await _aggregate_party("appellant", [{"id": "x", "claim_text": "t"}])
|
||||
|
||||
# The message has to name the side, or an operator reading
|
||||
# completed_with_errors cannot tell which litigant went missing.
|
||||
assert "appellant" in str(excinfo.value)
|
||||
Reference in New Issue
Block a user