hermes(chat): make cluster_read robust to large node listings
Node .status.images (every cached image on the node) overflowed the size cap and left json.loads parsing a truncated blob, so a nodes query came back as a non-JSON error. The de-noise pass now summarizes that list, and genuine truncation returns the readable prefix with a narrow-your-query hint instead of an error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
This commit is contained in:
parent
4ccf2066ca
commit
ed0bc30d7c
@ -119,6 +119,11 @@ def _strip_noise(payload: Any) -> Any:
|
|||||||
if isinstance(annotations, dict):
|
if isinstance(annotations, dict):
|
||||||
for key in [k for k in annotations if "last-applied" in k]:
|
for key in [k for k in annotations if "last-applied" in k]:
|
||||||
annotations.pop(key, None)
|
annotations.pop(key, None)
|
||||||
|
status = payload.get("status")
|
||||||
|
if isinstance(status, dict) and isinstance(status.get("images"), list):
|
||||||
|
# Node .status.images is a huge blob of every cached image; it
|
||||||
|
# never helps the model and routinely overflows the size cap.
|
||||||
|
status["images"] = f"[{len(status['images'])} cached images omitted]"
|
||||||
for value in payload.values():
|
for value in payload.values():
|
||||||
_strip_noise(value)
|
_strip_noise(value)
|
||||||
elif isinstance(payload, list):
|
elif isinstance(payload, list):
|
||||||
@ -149,17 +154,20 @@ def _handle_cluster_read(args: dict[str, Any] | None = None, **kwargs: Any) -> s
|
|||||||
return json.dumps({"error": f"cluster API returned {error.code}", "detail": detail[:400]})
|
return json.dumps({"error": f"cluster API returned {error.code}", "detail": detail[:400]})
|
||||||
except Exception as error: # noqa: BLE001 - tool result, never an exception into the loop
|
except Exception as error: # noqa: BLE001 - tool result, never an exception into the loop
|
||||||
return json.dumps({"error": f"cluster API unavailable: {type(error).__name__}"})
|
return json.dumps({"error": f"cluster API unavailable: {type(error).__name__}"})
|
||||||
truncated = len(raw) > MAX_RESPONSE_BYTES
|
over_wire = len(raw) > MAX_RESPONSE_BYTES
|
||||||
|
if over_wire:
|
||||||
|
# A truncated wire body is not parseable JSON; hand back the readable
|
||||||
|
# prefix rather than failing, so a large list still yields something.
|
||||||
|
return json.dumps({"truncated": True, "reason": "response exceeded size cap; narrow with namespace/name/label_selector",
|
||||||
|
"partial": raw[: MAX_RESPONSE_BYTES - 96].decode("utf-8", "replace")})
|
||||||
try:
|
try:
|
||||||
payload = _strip_noise(json.loads(raw[:MAX_RESPONSE_BYTES] if truncated else raw))
|
payload = _strip_noise(json.loads(raw))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return json.dumps({"error": "cluster API returned non-JSON data"})
|
return json.dumps({"error": "cluster API returned non-JSON data"})
|
||||||
body = json.dumps(payload, separators=(",", ":"))
|
body = json.dumps(payload, separators=(",", ":"))
|
||||||
if len(body) > MAX_RESPONSE_BYTES:
|
if len(body) > MAX_RESPONSE_BYTES:
|
||||||
body = body[:MAX_RESPONSE_BYTES]
|
return json.dumps({"truncated": True, "reason": "result exceeded size cap after cleanup; narrow the query",
|
||||||
truncated = True
|
"partial": body[: MAX_RESPONSE_BYTES - 96]})
|
||||||
if truncated:
|
|
||||||
return json.dumps({"truncated": True, "partial": body[: MAX_RESPONSE_BYTES - 64]})
|
|
||||||
return body
|
return body
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -86,7 +86,17 @@ def test_handler_is_get_only_and_bounded(monkeypatch, tmp_path):
|
|||||||
|
|
||||||
monkeypatch.setattr(mod.urllib.request, "urlopen", huge_open)
|
monkeypatch.setattr(mod.urllib.request, "urlopen", huge_open)
|
||||||
out = json.loads(mod._handle_cluster_read({"resource": "pods"}))
|
out = json.loads(mod._handle_cluster_read({"resource": "pods"}))
|
||||||
assert out.get("truncated") is True or out.get("error")
|
assert out.get("truncated") is True and "partial" in out
|
||||||
|
|
||||||
|
def node_open(request, timeout=None, context=None):
|
||||||
|
payload = {"kind": "NodeList", "items": [{"metadata": {"name": "n"},
|
||||||
|
"status": {"images": [{"names": ["x"], "sizeBytes": 1} for _ in range(500)]}}]}
|
||||||
|
return FakeResponse(json.dumps(payload).encode())
|
||||||
|
|
||||||
|
monkeypatch.setattr(mod.urllib.request, "urlopen", node_open)
|
||||||
|
out = json.loads(mod._handle_cluster_read({"resource": "nodes"}))
|
||||||
|
assert out["kind"] == "NodeList"
|
||||||
|
assert "cached images omitted" in json.dumps(out)
|
||||||
|
|
||||||
|
|
||||||
def test_errors_never_raise(monkeypatch, tmp_path):
|
def test_errors_never_raise(monkeypatch, tmp_path):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user