Report llama context limit errors clearly
This commit is contained in:
+19
-1
@@ -19,6 +19,24 @@ class APIError(ValueError):
|
|||||||
def __init__(self,message,status=400,code='invalid_request_error'):
|
def __init__(self,message,status=400,code='invalid_request_error'):
|
||||||
super().__init__(message);self.status=status;self.code=code
|
super().__init__(message);self.status=status;self.code=code
|
||||||
|
|
||||||
|
def chat_upstream_error(status, raw):
|
||||||
|
"""Expose known, safe llama.cpp limits without reflecting request content."""
|
||||||
|
try:
|
||||||
|
detail=json.loads(raw)
|
||||||
|
error=detail.get('error',{}) if isinstance(detail,dict) else {}
|
||||||
|
if error.get('type')=='exceed_context_size_error':
|
||||||
|
needed=error.get('n_prompt_tokens');available=error.get('n_ctx')
|
||||||
|
if all(type(value) is int and 0<value<=2097152 for value in (needed,available)):
|
||||||
|
needed_text=f'{needed:,}'.replace(',','.');available_text=f'{available:,}'.replace(',','.')
|
||||||
|
return APIError(
|
||||||
|
f'Die Anfrage benötigt {needed_text} Kontext-Tokens, das aktive Profil bietet {available_text}. '
|
||||||
|
'Kontext im Profil erhöhen oder die Anfrage verkürzen.',
|
||||||
|
400,'context_length_exceeded')
|
||||||
|
except (ValueError,TypeError,AttributeError):
|
||||||
|
pass
|
||||||
|
return APIError('llama.cpp hat die Anfrage abgelehnt. Kontext und Anfrageparameter prüfen.',
|
||||||
|
status if 400<=status<600 else 502,'upstream_error')
|
||||||
|
|
||||||
class Endpoint:
|
class Endpoint:
|
||||||
def __init__(self,root,profiles,worker,scheduler,images,credentials,management_port):
|
def __init__(self,root,profiles,worker,scheduler,images,credentials,management_port):
|
||||||
self.root=Path(root);self.profiles=profiles;self.worker=worker;self.scheduler=scheduler;self.images=images;self.tts=None;self.stt=None;self.video=None;self.credentials=credentials
|
self.root=Path(root);self.profiles=profiles;self.worker=worker;self.scheduler=scheduler;self.images=images;self.tts=None;self.stt=None;self.video=None;self.credentials=credentials
|
||||||
@@ -287,7 +305,7 @@ class APIHandler(BaseHTTPRequestHandler):
|
|||||||
conn.request('POST','/v1/chat/completions',body=json.dumps(body).encode(),headers={'Content-Type':'application/json','Authorization':'Bearer '+key})
|
conn.request('POST','/v1/chat/completions',body=json.dumps(body).encode(),headers={'Content-Type':'application/json','Authorization':'Bearer '+key})
|
||||||
response=conn.getresponse()
|
response=conn.getresponse()
|
||||||
if response.status!=200:
|
if response.status!=200:
|
||||||
response.read(65536);raise APIError('llama.cpp hat die Anfrage abgelehnt. Kontext und Anfrageparameter prüfen.',response.status if 400<=response.status<600 else 502,'upstream_error')
|
raise chat_upstream_error(response.status,response.read(65536))
|
||||||
if not data.get('stream'):
|
if not data.get('stream'):
|
||||||
raw=response.read(16*1024*1024+1)
|
raw=response.read(16*1024*1024+1)
|
||||||
if len(raw)>16*1024*1024:raise InferenceError('Modellantwort überschreitet 16 MiB.')
|
if len(raw)>16*1024*1024:raise InferenceError('Modellantwort überschreitet 16 MiB.')
|
||||||
|
|||||||
+14
-1
@@ -11,7 +11,7 @@ from pathlib import Path
|
|||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer
|
from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
from endpoint import Endpoint
|
from endpoint import Endpoint,chat_upstream_error
|
||||||
from inference import Scheduler,InferenceError
|
from inference import Scheduler,InferenceError
|
||||||
|
|
||||||
class FakeWorker:
|
class FakeWorker:
|
||||||
@@ -32,6 +32,19 @@ class Upstream(BaseHTTPRequestHandler):
|
|||||||
else:
|
else:
|
||||||
body=json.dumps(dict(id='test',model=data['model'],choices=[{'message':{'role':'assistant','content':'synthetic'}}])).encode();self.send_response(200);self.send_header('Content-Length',str(len(body)));self.end_headers();self.wfile.write(body)
|
body=json.dumps(dict(id='test',model=data['model'],choices=[{'message':{'role':'assistant','content':'synthetic'}}])).encode();self.send_response(200);self.send_header('Content-Length',str(len(body)));self.end_headers();self.wfile.write(body)
|
||||||
|
|
||||||
|
class UpstreamErrorTests(unittest.TestCase):
|
||||||
|
def test_context_limit_is_actionable_without_echoing_upstream_text(self):
|
||||||
|
raw=json.dumps({'error':{'type':'exceed_context_size_error','message':'private request text',
|
||||||
|
'n_prompt_tokens':13523,'n_ctx':8192}}).encode()
|
||||||
|
error=chat_upstream_error(400,raw)
|
||||||
|
self.assertEqual(error.code,'context_length_exceeded')
|
||||||
|
self.assertIn('13.523',str(error));self.assertIn('8.192',str(error))
|
||||||
|
self.assertNotIn('private request text',str(error))
|
||||||
|
def test_unknown_upstream_errors_stay_generic(self):
|
||||||
|
error=chat_upstream_error(400,b'{"error":{"message":"private request text"}}')
|
||||||
|
self.assertEqual(error.code,'upstream_error')
|
||||||
|
self.assertNotIn('private request text',str(error))
|
||||||
|
|
||||||
class EndpointTests(unittest.TestCase):
|
class EndpointTests(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.tmp=tempfile.TemporaryDirectory();self.worker=FakeWorker();self.worker.http=ThreadingHTTPServer(('127.0.0.1',0),Upstream);self.worker.http.worker=self.worker;threading.Thread(target=self.worker.http.serve_forever,daemon=True).start()
|
self.tmp=tempfile.TemporaryDirectory();self.worker=FakeWorker();self.worker.http=ThreadingHTTPServer(('127.0.0.1',0),Upstream);self.worker.http.worker=self.worker;threading.Thread(target=self.worker.http.serve_forever,daemon=True).start()
|
||||||
|
|||||||
Reference in New Issue
Block a user