Repository navigation
jackson2: ignore unknown fields; core: optional API key, ApiKey without a public constructor - #18
Merged
Merged
Conversation
Jackson 2 fails on unknown properties by default and Jackson 3 doesn't, so a field added to an API response broke jackson2 clients only (seen with a local Clef server adding usage.latency_ms). Both codec tests now pin the behavior. Fixes #16 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TypeSafeClient.builder() takes no argument and Builder.apiKey(ApiKey) is optional: without a key no Authorization header is sent, which is what a local TypeSafe-compatible server (Builder.endpoint) needs. No special case for the default endpoint: a missing key there surfaces as Authentication (401). ApiKey becomes a final class with a private constructor: ApiKey.of(String) joins fromFile/fromDefaultFile/fromEnv, and the raw value is no longer exposed (only toHttpHeaderValue and the masked toString). Fixes #17 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dfa1
force-pushed
the
fix-codec-and-apikey
branch
from
October 3, 2026 12:29
3eb78bb to
88f23dc
Compare
dfa1
added a commit
that referenced
this pull request
Oct 3, 2026
TypeSafeClient.builder(ApiKey) became builder().apiKey(ApiKey) in #18. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dfa1
added a commit
that referenced
this pull request
Oct 3, 2026
mlx-community's 4-bit port of Clef-flash serves TypeSafe's /v1/systemone, so the regular client (builder().endpoint(...), no API key since #18) talks to it directly. On an M5: about 0.55 s per 3-question request, and on the 104 cached requests 95% yes/no and 88% choice agreement with jev-1.13.0 (Laya: 85%, 64%). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dfa1
added a commit
that referenced
this pull request
Oct 3, 2026
…LX (#14) * local: add LocalTypeSafeClient, in-process engines on ONNX Runtime New module typesafe-java-local: a TypeSafeClient that evaluates on ONNX Runtime from a local model directory instead of calling the API. Engines: Laya (ModernBERT + decision head, recommended) and Qwen2.5-1.5B (next-token logits). Pure-Java tokenizer and JSON; depends only on core and ONNX Runtime. Real-model tests are tagged "model" and excluded by default. JevComparison replays 104 requests against cached real-Jev answers; the `Local engines` workflow runs tests, comparison and JMH on Linux/macOS and writes the tables to the job summary. Also pins the checkstyle engine to 14.3.0: the plugin's default 9.3 can't parse Java 21 pattern matching for switch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * local: model test asserts the anger ranking only; document int8's CPU dependence On the macOS (M1) runner int8 Laya scored furious 2.02 vs calm 1.53, missing the 0.5 margin by 0.01. On the x86 runner int8 agreed with Jev less (78% vs 85% on yes/no) while fp32 matched exactly, so the how-to now says when to prefer fp32. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * local: Laya fp32 from onnx-community by default; int8 opt-in with a warning onnx-community/laya-typed-decisions-ONNX is the same export (encoder + head, dynamo, opset 18) verified against PyTorch, so download.sh replaces exporting; LayaEngine reads its layout (onnx/model.onnx, config.json "laya", bool marker_mask) as well as the flat one. Agreement with Jev is unchanged (85/64/0.19). int8 becomes opt-in: on one x86 runner it agreed less, on another it returned flat distributions. CI downloads instead of installing PyTorch; export_onnx.py only regenerates the PyTorch fixture. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * local: drop int8 Laya Dynamic int8 gave CPU-dependent answers (silently flat distributions on one x86 runner), so the module ships fp32 only: no quantize_int8.py, no laya-int8 engine, and LayaEngine reads onnx-community's layout only (config.json "laya", bool marker_mask). CI no longer needs uv. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * local: Engines enum for the test harness; JEV cache as 5 suite files Engines (LAYA, LAYA_GPU, QWEN, QWEN_GPU) carries each engine's model directory, GPU flag and loader, replacing string matching; JMH takes it as a @PARAM, the comparison and -Dengine accept "laya-gpu" or "LAYA_GPU". The 104 cached Jev answers move from one file per request to one JSON line per request in jev/<suite>.jsonl (5 files); the answers are unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * local: optional Laya fp16 (download.sh fp16) for the GPU onnx-community's fp16 export: same answers as fp32 in half the download (0.85 GB), fastest on the GPU (WebGPU/Metal), 2-3x slower on the CPU, where ONNX Runtime converts fp16 on the fly. Engines gains LAYA_FP16/LAYA_FP16_GPU; the macOS CI job downloads it and measures LAYA_FP16_GPU. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * local: JSON through JsonCodec; hf download instead of download scripts The custom Json reader/writer is gone: tokenizer.json, Laya's config and structured states go through typesafe-java's JsonCodec, found via ServiceLoader like the API client (so jackson2/jackson3 is needed at run time). Jackson's compact JSON measured no different against Jev than Python's json.dumps spacing; the tokenizer fixture is regenerated for it. Onnx.model(dir) finds the one .onnx file in dir/onnx or dir, so files keep their Hugging Face names and the bash download scripts give way to `hf download` commands (docs, CI). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * local: Clef-flash engine, with a 4-bit conversion ClefEngine runs Cloudflare's Clef-flash (Qwen3.5-9B + joint schema head) from Ollaya's ONNX graph, which reads the upstream bf16 safetensors in place; the first load hard-links them under the sha256-<hash> names the graph uses (ONNX Runtime rejects symlinks resolving outside the graph's directory). ClefEngineTest checks the port of encode_record token for token against Clef's own Python (scripts/clef/reference.py). scripts/clef/quantize_q4.py replaces the 249 projections with MatMulNBits: 20 GB -> 7.7 GB, and on an M5 2-4 s per request on WebGPU (7-13 s on CPU) instead of minutes. CoreML was tried and rejected: ONNX Runtime's CoreML EP supports neither MatMulNBits nor any 8-bit op. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * local: JevComparison builds its API client with builder().apiKey(...) TypeSafeClient.builder(ApiKey) became builder().apiKey(ApiKey) in #18. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs: run Clef-flash on a Mac with MLX mlx-community's 4-bit port of Clef-flash serves TypeSafe's /v1/systemone, so the regular client (builder().endpoint(...), no API key since #18) talks to it directly. On an M5: about 0.55 s per 3-question request, and on the 104 cached requests 95% yes/no and 88% choice agreement with jev-1.13.0 (Laya: 85%, 64%). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * local: one public client class per model LocalLayaTypeSafeClient, LocalQwenTypeSafeClient and LocalClefTypeSafeClient, each with a static load(Path), replace LocalTypeSafeClient's laya/qwen/clef factories. LocalTypeSafeClient stays as their package-private base (model check, timing, async, listModels), which the test harness still builds directly for engine variants. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * local: Qwen as the prompted-LLM baseline Measured on the same 104 cached requests: Qwen2.5-1.5B agrees with jev-1.13.0 on 80% of yes/no and 68% of choices (score error 0.26) at 2.05 s/request, against Laya's 85%/64%/0.19 at 0.19 s and Clef-flash's 95%/88%/0.07. The docs and LocalQwenTypeSafeClient's javadoc now present it as what prompting a general LLM buys, not as an option; the reference table names the classes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * local workflow: download Clef-flash's tokenizer for ClefEngineTest The parity test checks the Java prompt builder against Clef's own Python and only needs tokenizer.json (20 MB), not the weights. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * CHANGELOG: PR number on local's entries; Qwen as the baseline Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs: local's ONNX Runtime dependency, its size and platforms Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two fixes found while pointing the client at a local Clef-flash server (the MLX port's
clef_mlx.py serve, a TypeSafe-compatible/v1/systemone).jackson2: ignore unknown fields, like jackson3 (fixes #16)
Jackson 2 rejects unknown properties by default and Jackson 3 doesn't, so a field the API adds to a response (here
usage.latency_ms) brokejackson2clients only.Jackson2Codecnow disablesFAIL_ON_UNKNOWN_PROPERTIES; a new test in bothJackson2CodecTestandJackson3CodecTestcovers unknown fields at the top level, inusageand in an answer.core: optional API key; ApiKey without a public constructor (fixes #17)
TypeSafeClient.builder()takes no argument;Builder.apiKey(ApiKey)is optional. Without a key noAuthorizationheader is sent. There's no special case for the default endpoint: a missing key there surfaces asTypeSafeException.Authentication(401) on the first call.ApiKeyis a final class with a private constructor.ApiKey.of(String)joinsfromFile/fromDefaultFile/fromEnv, and the raw value is no longer exposed (onlytoHttpHeaderValue()and the maskedtoString()).builder().apiKey(key).Verified
./mvnw verifypasses.TypeSafeClient.builder().endpoint(URI.create("http://localhost:8000/v1/systemone")).build()with thejackson2codec and no key gets typed answers from the local Clef-flash MLX server (about 0.53 s per 3-question request on an Apple M5). Before these fixes, the same call needed a made-up key and failed to decode withjackson2.🤖 Generated with Claude Code