Download tests/test_llm_errors.py from elyanlabs/trashclaw: direct link, hf CLI and curl.
- Browser
- Download file 4.6 kB
-
https://huggingface.co/elyanlabs/trashclaw/resolve/main/tests/test_llm_errors.py
- Command line
-
hf download hf://elyanlabs/trashclaw/tests/test_llm_errors.py
-
curl -L -o test_llm_errors.py https://huggingface.co/elyanlabs/trashclaw/resolve/main/tests/test_llm_errors.py
4.6 kB
| # SPDX-License-Identifier: MIT | |
| """llm_request must surface server errors instead of hiding them. | |
| Before: an HTTP error status (e.g. llama-server's 400 "request exceeds the | |
| available context size") was reported as "Cannot reach llama-server" and | |
| retried twice; an error event inside the SSE stream, or a plain JSON reply | |
| from a server that ignored "stream": true, became a silent empty answer. | |
| """ | |
| import json | |
| import os | |
| import sys | |
| import threading | |
| from http.server import BaseHTTPRequestHandler, HTTPServer | |
| import pytest | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) | |
| import trashclaw | |
| def fake_server(monkeypatch): | |
| state = {"status": 200, "ctype": "text/event-stream", "body": b"", "posts": 0} | |
| class Handler(BaseHTTPRequestHandler): | |
| def log_message(self, *args): | |
| pass | |
| def do_POST(self): | |
| self.rfile.read(int(self.headers.get("Content-Length", 0))) | |
| state["posts"] += 1 | |
| body = state["body"] | |
| self.send_response(state["status"]) | |
| self.send_header("Content-Type", state["ctype"]) | |
| self.send_header("Content-Length", str(len(body))) | |
| self.end_headers() | |
| self.wfile.write(body) | |
| server = HTTPServer(("127.0.0.1", 0), Handler) | |
| thread = threading.Thread(target=server.serve_forever, daemon=True) | |
| thread.start() | |
| monkeypatch.setattr(trashclaw, "LLAMA_URL", "http://127.0.0.1:%d" % server.server_address[1]) | |
| monkeypatch.setattr(trashclaw, "LLM_RETRY_DELAY", 0) | |
| monkeypatch.setattr(trashclaw, "_INTERRUPTED", False) | |
| yield state | |
| server.shutdown() | |
| server.server_close() | |
| MSGS = [{"role": "user", "content": "hi"}] | |
| def test_http_400_reports_server_message_and_is_not_retried(fake_server): | |
| fake_server.update(status=400, ctype="application/json", body=json.dumps( | |
| {"error": {"code": 400, "message": "the request exceeds the available context size"}} | |
| ).encode()) | |
| result = trashclaw.llm_request_with_retry(MSGS) | |
| assert "error" in result | |
| assert "Cannot reach" not in result["error"] | |
| assert "HTTP 400" in result["error"] | |
| assert "exceeds the available context size" in result["error"] | |
| assert fake_server["posts"] == 1 | |
| def test_http_503_is_retried(fake_server): | |
| fake_server.update(status=503, ctype="application/json", | |
| body=b'{"error": {"message": "Loading model"}}') | |
| result = trashclaw.llm_request_with_retry(MSGS) | |
| assert "HTTP 503" in result["error"] | |
| assert "Loading model" in result["error"] | |
| assert fake_server["posts"] == trashclaw.LLM_RETRY_ATTEMPTS + 1 | |
| def test_error_event_in_stream_is_reported(fake_server): | |
| fake_server.update(body=b'data: {"error": {"message": "model crashed"}}\n\n') | |
| result = trashclaw.llm_request(MSGS) | |
| assert "error" in result | |
| assert "model crashed" in result["error"] | |
| def test_plain_json_reply_is_used(fake_server): | |
| fake_server.update(ctype="application/json", body=json.dumps({ | |
| "choices": [{"message": {"content": "hello there"}, "finish_reason": "stop"}] | |
| }).encode()) | |
| result = trashclaw.llm_request(MSGS) | |
| assert result["choices"][0]["message"]["content"] == "hello there" | |
| def test_plain_json_error_is_reported(fake_server): | |
| fake_server.update(ctype="application/json", body=b'{"error": "bad model name"}') | |
| result = trashclaw.llm_request(MSGS) | |
| assert "bad model name" in result.get("error", "") | |
| def test_normal_stream_still_works(fake_server): | |
| chunks = [ | |
| {"choices": [{"delta": {"content": "Hel"}}]}, | |
| {"choices": [{"delta": {"content": "lo"}, "finish_reason": "stop"}]}, | |
| ] | |
| body = ": keep-alive\n\n" + "".join("data: %s\n\n" % json.dumps(c) for c in chunks) + "data: [DONE]\n\n" | |
| fake_server.update(body=body.encode()) | |
| result = trashclaw.llm_request(MSGS) | |
| assert result["choices"][0]["message"]["content"] == "Hello" | |
| assert result["choices"][0]["finish_reason"] == "stop" | |
| def test_plain_json_reply_updates_stats(fake_server, monkeypatch): | |
| monkeypatch.setattr(trashclaw, "LAST_GENERATION_STATS", {}) | |
| monkeypatch.setattr(trashclaw, "SESSION_STATS", dict(trashclaw.SESSION_STATS, turns=0, | |
| total_tokens=0, total_seconds=0.0)) | |
| fake_server.update(ctype="application/json", body=json.dumps({ | |
| "choices": [{"message": {"content": "x" * 40}, "finish_reason": "stop"}] | |
| }).encode()) | |
| trashclaw.llm_request(MSGS) | |
| assert trashclaw.LAST_GENERATION_STATS["tokens"] == 10 | |
| assert trashclaw.SESSION_STATS["turns"] == 1 | |