mirror of
https://github.com/mudler/LocalAI.git
synced 2026-09-25 07:34:58 -04:00
fix(sglang): support msgspec-based ServerArgs (sglang >= 0.5.20) (#12155)
sglang 0.5.20 moved its config tier from dataclasses to msgspec.Struct (sgl-project/sglang#38753). _apply_engine_args validates engine_args keys via dataclasses.fields(ServerArgs), which raises TypeError there. That call runs on every LoadModel, so no model loads at all on the sglang backend once sglang >= 0.5.20 is installed, and the error surfaces as a generic "Unexpected <class 'TypeError'>" that does not name the cause. Introspect both shapes: msgspec structs carry their field names in __struct_fields__, so key validation and the close-match suggestion keep working, and older dataclass-based sglang stays supported. Adds a test that pins the msgspec path with a stand-in, so it is covered regardless of which sglang version is installed. Signed-off-by: pos-ei-don <1822533+pos-ei-don@users.noreply.github.com>
This commit is contained in:
1 parent
9cbfa88a5c
commit
0b09cff673
2 files changed
+47
-3
No files matched your search
@@ -136,7 +136,19 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
|
||||
raise ValueError(
|
||||
f"engine_args must be a JSON object, got {type(extra).__name__}"
|
||||
)
|
||||
valid = {f.name for f in dataclasses.fields(ServerArgs)}
|
||||
if dataclasses.is_dataclass(ServerArgs):
|
||||
valid = {f.name for f in dataclasses.fields(ServerArgs)}
|
||||
else:
|
||||
# sglang >= 0.5.20 moved the config tier from dataclasses to
|
||||
# msgspec.Struct (sgl-project/sglang#38753); msgspec keeps the
|
||||
# field names in __struct_fields__.
|
||||
valid = set(getattr(ServerArgs, "__struct_fields__", ()))
|
||||
if not valid:
|
||||
raise ValueError(
|
||||
"cannot introspect ServerArgs fields: it is neither a "
|
||||
"dataclass nor a msgspec.Struct, so engine_args cannot "
|
||||
"be validated"
|
||||
)
|
||||
for key in extra:
|
||||
if key not in valid:
|
||||
suggestion = difflib.get_close_matches(key, valid, n=1)
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
Helper-level tests run without launching the gRPC server or loading model
|
||||
weights — they only exercise the pure-Python helpers on
|
||||
``BackendServicer``. They do still require ``sglang`` to be importable
|
||||
because ``_apply_engine_args`` validates keys against
|
||||
``ServerArgs``'s dataclass fields.
|
||||
because ``_apply_engine_args`` validates keys against ``ServerArgs``
|
||||
(a dataclass up to sglang 0.5.19, a ``msgspec.Struct`` from 0.5.20 on).
|
||||
"""
|
||||
import unittest
|
||||
|
||||
@@ -77,6 +77,38 @@ class TestSglangHelpers(unittest.TestCase):
|
||||
self.assertIn("trust_remotecode", msg)
|
||||
self.assertIn("trust_remote_code", msg)
|
||||
|
||||
def test_apply_engine_args_msgspec_serverargs(self):
|
||||
"""sglang >= 0.5.20 exposes ServerArgs as a msgspec.Struct instead of a
|
||||
dataclass; the field names then live in ``__struct_fields__``.
|
||||
|
||||
Pinned with a stand-in so the msgspec path is covered no matter which
|
||||
sglang version happens to be installed.
|
||||
"""
|
||||
import json as _json
|
||||
servicer = self._servicer()
|
||||
import backend as backend_mod
|
||||
|
||||
class _StructLikeServerArgs:
|
||||
__struct_fields__ = ("model_path", "mem_fraction_static",
|
||||
"trust_remote_code")
|
||||
|
||||
original = backend_mod.ServerArgs
|
||||
backend_mod.ServerArgs = _StructLikeServerArgs
|
||||
try:
|
||||
out = servicer._apply_engine_args(
|
||||
{}, _json.dumps({"trust_remote_code": True}),
|
||||
)
|
||||
self.assertTrue(out["trust_remote_code"])
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
servicer._apply_engine_args(
|
||||
{}, _json.dumps({"mem_fraction_statik": 0.7}),
|
||||
)
|
||||
msg = str(ctx.exception)
|
||||
self.assertIn("mem_fraction_statik", msg)
|
||||
self.assertIn("mem_fraction_static", msg)
|
||||
finally:
|
||||
backend_mod.ServerArgs = original
|
||||
|
||||
def test_apply_engine_args_empty_passthrough(self):
|
||||
"""Empty / None engine_args returns the kwargs dict untouched."""
|
||||
servicer = self._servicer()
|
||||
|
||||
Reference in new issue
Block a user