Skip to content

Commit 119b7c0

Browse files
authored
Sync lmstudio.js schema (2025-06-25) (#99)
* update lmstudio.js schema export * simplify not-yet-supported schema constraint patterns * ignore additional UI-centric tool calling messages * map new config keys (postponing handling the "raw" field)
1 parent 414a75f commit 119b7c0

File tree

10 files changed

+10334
-1967
lines changed

10 files changed

+10334
-1967
lines changed

sdk-schema/lms-with-inferred-unions.json

Lines changed: 3383 additions & 481 deletions
Large diffs are not rendered by default.

sdk-schema/lms.json

Lines changed: 2803 additions & 369 deletions
Large diffs are not rendered by default.

sdk-schema/lmstudio-js

Submodule lmstudio-js updated 121 files

sdk-schema/sync-sdk-schema.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import ast
2323
import builtins
2424
import json
25+
import re
2526
import shutil
2627
import subprocess
2728
import sys
@@ -410,11 +411,15 @@ def _generate_data_model_from_json_schema() -> None:
410411
raise RuntimeError(f"Failed to create {_MODEL_PATH!r}")
411412
# Generated source code post-processing:
412413
#
414+
# * Fix up miscellaneous issues the code generator currently mishandles
413415
# * Fix up typed dicts to be defined in terms of nested dicts
414416
# * Add an `__all__` definition for wildcard imports (which also
415417
# serves as a top level summary of the defined schemas)
416418
print("Post-processing generated source code...")
417-
model_source = _MODEL_PATH.read_text()
419+
# Replace unsupported regex character classes with `.`
420+
# https://github.com/python/cpython/issues/95555
421+
# https://github.com/jcrist/msgspec/issues/860
422+
model_source = re.sub(r"\\\\p\{[^}]*\}", ".", _MODEL_PATH.read_text())
418423
model_ast = ast.parse(model_source)
419424
dict_token_replacements: dict[str, str] = {}
420425
exported_names: list[str] = []

src/lmstudio/_kv_config.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ def to_kv_field(
120120
def update_client_config(
121121
self, client_config: MutableDictObject, server_value: DictObject
122122
) -> None:
123-
client_container: MutableDictObject = client_config.setdefault(self.client_key, {})
123+
client_container: MutableDictObject = client_config.setdefault(
124+
self.client_key, {}
125+
)
124126
self.server_to_client(server_value, client_container)
125127

126128

@@ -216,6 +218,7 @@ def _gpu_split_config_to_gpu_settings(
216218
**_COMMON_MODEL_LOAD_KEYS,
217219
"numExperts": ConfigField("numExperts"),
218220
"seed": CheckboxField("seed"),
221+
"offloadKVCacheToGpu": ConfigField("offloadKVCacheToGpu"),
219222
"llama": {
220223
**_COMMON_LLAMA_LOAD_KEYS,
221224
"evalBatchSize": ConfigField("evalBatchSize"),

src/lmstudio/_sdk_models/__init__.py

Lines changed: 4118 additions & 1104 deletions
Large diffs are not rendered by default.

src/lmstudio/json_api.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,8 @@ def result(self) -> T:
700700
return self._result
701701

702702
def raise_unknown_message_error(self, unknown_message: Any) -> NoReturn:
703+
# TODO: improve forward compatibility by switching this to use warnings.warn
704+
# instead of failing immediately for all unknown messages
703705
raise LMStudioUnknownMessageError(
704706
f"{self._NOTICE_PREFIX} unexpected message contents: {unknown_message!r}"
705707
)
@@ -1234,20 +1236,20 @@ def iter_message_events(
12341236
# Ignore status updates after cancellation (avoids race condition)
12351237
return
12361238
yield from self._update_prompt_processing_progress(progress)
1237-
case {
1238-
"type": "toolCallGenerationStart",
1239-
}:
1239+
case {"type": "toolCallGenerationStart"}:
12401240
self._logger.debug("Notified of pending tool call request generation.")
1241+
case {"type": "toolCallGenerationNameReceived"}:
1242+
pass # UI event, currently ignored by Python SDK
1243+
case {"type": "toolCallGenerationArgumentFragmentGenerated"}:
1244+
pass # UI event, currently ignored by Python SDK
12411245
case {
12421246
"type": "toolCallGenerationEnd",
12431247
"toolCallRequest": tool_call_request,
12441248
}:
12451249
yield PredictionToolCallEvent(
12461250
ToolCallRequest._from_api_dict(tool_call_request)
12471251
)
1248-
case {
1249-
"type": "toolCallGenerationFailed",
1250-
}:
1252+
case {"type": "toolCallGenerationFailed"}:
12511253
self._logger.warn("Tool call processing generation failed.")
12521254
yield PredictionToolCallAbortedEvent(None)
12531255
case {"type": "error", "error": {} as error}:

src/lmstudio/schemas.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,9 @@ def model_json_schema(cls) -> DictSchema:
9494

9595

9696
_CAMEL_CASE_OVERRIDES = {
97-
# This is the one key in the API that capitalizes the `V` in `KV`
97+
# `_kv_` in snake_case becomes KV in camelCase
9898
"useFp16ForKvCache": "useFp16ForKVCache",
99+
"offloadKvCacheToGpu": "offloadKVCacheToGpu",
99100
}
100101

101102
_SKIP_FIELD_RECURSION = set(

tests/test_inference.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,5 +287,5 @@ def _handle_invalid_request(
287287
assert isinstance(tool_failure_exc.__cause__, ZeroDivisionError)
288288
# If the content checks prove too flaky in practice, they can be dropped
289289
completed_response = predictions[-1].content.lower()
290-
assert "divid" in completed_response # Accepts both "divide" and "dividing"
290+
assert "divid" in completed_response # Accepts both "divide" and "dividing"
291291
assert "zero" in completed_response

tests/test_kv_config.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"llamaKCacheQuantizationType": "q8_0",
7777
"llamaVCacheQuantizationType": "f32",
7878
"numExperts": 0,
79+
"offloadKVCacheToGpu": False,
7980
"ropeFrequencyBase": 10.0,
8081
"ropeFrequencyScale": 1.5,
8182
"seed": 313,
@@ -93,6 +94,7 @@
9394
"llama_k_cache_quantization_type": "q8_0",
9495
"llama_v_cache_quantization_type": "f32",
9596
"num_experts": 0,
97+
"offload_kv_cache_to_gpu": False,
9698
"rope_frequency_base": 10.0,
9799
"rope_frequency_scale": 1.5,
98100
"seed": 313,
@@ -221,6 +223,9 @@ class LlmPredictionConfigStrict(LlmPredictionConfig, forbid_unknown_fields=True)
221223
LlmPredictionConfigStrict,
222224
)
223225

226+
# The "raw" debugging field is a special case, with TBD handling
227+
_NOT_YET_MAPPED = {"raw"}
228+
224229

225230
@pytest.mark.parametrize("config_dict,config_type", zip(CONFIG_DICTS, CONFIG_TYPES))
226231
def test_struct_field_coverage(
@@ -232,7 +237,7 @@ def test_struct_field_coverage(
232237
missing_keys = expected_keys - mapped_keys
233238
assert not missing_keys
234239
# Ensure no extra keys are mistakenly defined
235-
unknown_keys = mapped_keys - expected_keys
240+
unknown_keys = mapped_keys - expected_keys - _NOT_YET_MAPPED
236241
assert not unknown_keys
237242
# Ensure the config can be loaded
238243
config_struct = config_type._from_api_dict(config_dict)
@@ -260,7 +265,7 @@ def test_kv_stack_field_coverage(
260265
# Ensure all expected keys are covered (even those with default values)
261266
mapped_keys = keymap.keys()
262267
expected_keys = set(config_type.__struct_encode_fields__)
263-
missing_keys = expected_keys - mapped_keys
268+
missing_keys = expected_keys - mapped_keys - _NOT_YET_MAPPED
264269
assert not missing_keys
265270
# Ensure no extra keys are mistakenly defined
266271
unknown_keys = mapped_keys - expected_keys
@@ -342,6 +347,7 @@ def test_kv_stack_field_coverage(
342347
{"key": "llm.load.llama.tryMmap", "value": False},
343348
{"key": "llm.load.llama.useFp16ForKVCache", "value": True},
344349
{"key": "llm.load.numExperts", "value": 0},
350+
{"key": "llm.load.offloadKVCacheToGpu", "value": False},
345351
{"key": "llm.load.seed", "value": {"checked": True, "value": 313}},
346352
{"key": "load.gpuStrictVramCap", "value": False},
347353
]

0 commit comments

Comments
 (0)