Skip to content

fix(sdk): type the mixed_segment a routing detection answer carries - #821

Merged
NandhaKishorM merged 2 commits into
NandhaKishorM:mainfrom
aashish254:feat-sdk-detection-mixed
Oct 2, 2026
Merged

NandhaKishorM merged 2 commits into
NandhaKishorM:mainfrom
aashish254:feat-sdk-detection-mixed

Conversation

@aashish254

Copy link
Copy Markdown
Contributor

What

sdk/typescript (laya-client) learns the eighth field of a routing detection report.

  • src/types.ts: LanguageDetection gains mixed_segment: string | null.
  • src/response.ts: validateRoute() accepts null or a string for it when the server sends it.
  • test/types.ts: the field is readable and typed as text, not as a number, and a detection literal
    without it is a type error.
  • test/client.test.mjs: one wire test with the detection dict captured from a live answer.
result.routing?.detection?.mixed_segment;  // -> 'Ich möchte meine Bestellung stornieren, danke'

Why

laya.lang.analyse() reports mixed_segment on every branch (laya/lang.py:580,584,596,675) and it
is the field that explains a decision: when a support ticket reads as English overall because the
stack trace is longer than the customer's sentence, mixed_segment names the line that was German
and sent the state to the multilingual checkpoint. router.py:843-845 puts it in the reason string,
docs/http-api.md:191 lists it among the eight documented keys, tests/test_router.py:99 pins the
key set, and laya-ts/src/lang.ts:373 already types it as mixedSegment: string | null.

laya-client is the one reader left out: the interface stopped at seven fields, so a TypeScript
caller touching detection.mixed_segment gets TS2339: Property 'mixed_segment' does not exist on type 'LanguageDetection' and has to cast through any to read a field that is present whenever the
server reports a detection at all.

One deliberate choice: the type says required, the validator says checked-when-present. Every
branch of analyse() reports the key, so the type must not let a caller forget it exists -- that is
what makes the omission a compile error. The validator cannot require it, because a deployment that
predates that field would then fail every prediction; it follows the idiom already in this file for
validateHealth()'s withheld fields and the optional usage fields.

How it was verified

The detection dicts are not transcribed from the docs -- they were POSTed to the repo's own fixture
server (tests/sdk_server_fixture.py, tiny random weights, no downloads) with no model so the
Router really routes. Every answer carries all eight keys, and the mostly-English state names its
German line:

{"diacritic_rate": 0.0083, "is_english": false, "language": "de", "language_undecided": false,
 "mixed_segment": "Ich möchte meine Bestellung stornieren, danke", "non_latin_fraction": 0.0,
 "script": "latin", "script_profile": {"latin": 1.0}}
cd sdk/typescript
npm ci && npm test              # tests 21 / pass 21 / fail 0  (was 20 before this PR)
npm run test:integration        # tests 1  / pass 1  / fail 0

The integration test is the anchor: it deep-equals the SDK's parsed prediction against
agent.predict() from Python, so the field survives the real HTTP round-trip and matches core.

Mutation check -- each arm breaks one claim, git checkout -- restores it, and every arm must go
red for the named reason (tsc error or assertion):

arm the named red it produces
the field is not on the type at all test/types.ts(20,76): error TS2339: Property 'mixed_segment' does not exist on type 'LanguageDetection'.
the field is typed as a number test/types.ts(20,7): error TS2322: Type 'number | undefined' is not assignable to type 'string | null | undefined'.
the field is optional on the type test/types.ts(27,1): error TS2578: Unused '@ts-expect-error' directive. (the 7-key literal stops being an error)
a malformed segment reaches the caller (guard deleted) routing detection reports the segment that pulled a mostly-English state off English fails -- the rejection loop
a missing segment is a hard failure, so an old server breaks same test fails -- the older-server case
null is no longer an accepted answer same test fails -- the English-state case
any segment value is accepted same test fails -- [7, false, ['Ich'], {}] are all accepted

7/7 arms red, 0 survivors. Tree clean afterwards (git status --porcelain -> empty, each arm
restored with git checkout -- before the next).

Checklist

  • Focused on one change (split unrelated work into another PR)
  • Rebased on the latest main (branch is off 4aa6761; git diff upstream/main...HEAD is these 4 files only)
  • Tests pass locally (npm test 21/21, npm run test:integration 1/1, mutation 7/7)
  • Docs or examples updated when the public API changed -- nothing to sweep: docs/http-api.md:191
    already names all eight keys, and neither sdk/typescript/README.md nor docs/typescript-sdk.md
    enumerates the client's detection fields. No Python changed, so ruff/compileall are unaffected.

Fixes #

`laya.lang.analyse()` reports eight keys on every branch, and a live
`/v1/systemone` answer was measured sending all eight, but `LanguageDetection`
declared seven. So the field that names the line or which pulled a
mostly-English state off the English checkpoint (NandhaKishorM#384) could not be read from
TypeScript: a caller sees `is_english: false` and `language: de` with nothing
pointing at the segment the router actually acted on, and the answer to "which
of my fields is the non-English one?" was only in the free-text `reason`.

`validateRoute` checks it when the server sends it, as a null or a string, so a
deployment older than NandhaKishorM#384 stays usable while a malformed one cannot pass.
test/types.ts reads the field, refuses to type it as a number, and refuses a
detection literal that leaves it out. client.test.mjs drives a witnessed
detection through the client: the segment arrives, null is the ordinary answer,
a server that sends no such key still predicts, and four malformed ones do not.
@NandhaKishorM
NandhaKishorM merged commit 03a408f into NandhaKishorM:main Oct 2, 2026
23 checks passed
@NandhaKishorM

Copy link
Copy Markdown
Owner

Merged for 0.3.24.

One note on the merge, since #820 and #822 landed in the same pass and all three touch the same two test files: the conflict in test/client.test.mjs split each new test(...) block mid-loop around a shared closing, so a naive resolution produced an unterminated file. Both tests are in with their own closings, and the import and void [...] lists in test/types.ts are unioned rather than stacked.

The field itself is worth typing. mixed_segment is the one detection key that names why a mostly-English state left English, so a client that cannot read it can show the route but not the reason. Accepting null and tolerating its absence (a deployment predating #384) while still rejecting a number or an object is the right strictness: a display field should not fail the call, but a wrong type should not reach the display either.

Verified: SDK suite 26 tests passing, tsc clean, full Python shared list green, zero decision flips against 0.3.23.

Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants