@@ -136,6 +136,10 @@ message MetricsResponse {
136136message TokenClassifyRequest {
137137 string text = 1 ;
138138 float threshold = 2 ;
139+ // ModelIdentity names the model this request is for; see
140+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
141+ // identity supplied" and backends MUST skip the check.
142+ string ModelIdentity = 3 ;
139143}
140144
141145// TokenClassifyEntity is one detected entity span. Byte offsets are
@@ -173,6 +177,10 @@ message ScoreRequest {
173177 // candidates differ in length and the consumer wants a per-token
174178 // measure comparable across them (PMI-style scoring).
175179 bool length_normalize = 4 ;
180+ // ModelIdentity names the model this request is for; see
181+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
182+ // identity supplied" and backends MUST skip the check.
183+ string ModelIdentity = 5 ;
176184}
177185
178186// CandidateScore is one row in the ScoreResponse, matching by index
@@ -204,6 +212,10 @@ message RerankRequest {
204212 string query = 1 ;
205213 repeated string documents = 2 ;
206214 int32 top_n = 3 ;
215+ // ModelIdentity names the model this request is for; see
216+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
217+ // identity supplied" and backends MUST skip the check.
218+ string ModelIdentity = 4 ;
207219}
208220
209221message RerankResult {
@@ -336,7 +348,14 @@ message PredictOptions {
336348 // paths (core/services/nodes/file_staging_client.go), so in distributed mode
337349 // they already differ from the load-time value and comparing them would
338350 // reject valid requests. Extending identity to those RPCs needs a separate
339- // field carrying the untranslated value.
351+ // field carrying the untranslated value - which is exactly what
352+ // TTSRequest.ModelIdentity and SoundGenerationRequest.ModelIdentity are.
353+ //
354+ // Every other request message that reaches a backend through the distributed
355+ // router now carries the same ModelIdentity field, populated from the same
356+ // ModelConfig.Model. FileStagingClient rewrites Src/Dst/Voice/Model/
357+ // StartImage/EndImage/Audio and never ModelIdentity, so what the backend
358+ // compares is always what the controller sent.
340359 string ModelIdentity = 54 ;
341360
342361 // 24 was never assigned; reserve it so it is not silently reused.
@@ -510,6 +529,10 @@ message TranscriptRequest {
510529 float temperature = 8 ;
511530 repeated string timestamp_granularities = 9 ;
512531 bool stream = 10 ;
532+ // ModelIdentity names the model this request is for; see
533+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
534+ // identity supplied" and backends MUST skip the check.
535+ string ModelIdentity = 11 ;
513536}
514537
515538message TranscriptResult {
@@ -588,6 +611,10 @@ message GenerateImageRequest {
588611
589612 // Reference images for models that support them (e.g., Flux Kontext)
590613 repeated string ref_images = 12 ;
614+ // ModelIdentity names the model this request is for; see
615+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
616+ // identity supplied" and backends MUST skip the check.
617+ string ModelIdentity = 13 ;
591618}
592619
593620message GenerateVideoRequest {
@@ -607,6 +634,10 @@ message GenerateVideoRequest {
607634 // Backend-specific per-request generation parameters. Values are strings
608635 // and are validated/coerced by the selected backend.
609636 map <string , string > params = 14 ;
637+ // ModelIdentity names the model this request is for; see
638+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
639+ // identity supplied" and backends MUST skip the check.
640+ string ModelIdentity = 15 ;
610641}
611642
612643message TTSRequest {
@@ -624,10 +655,26 @@ message TTSRequest {
624655 // (e.g. Chatterbox exaggeration/cfg_weight/temperature). Values are strings and
625656 // coerced by the backend; unset leaves the backend's configured defaults.
626657 map <string , string > params = 7 ;
658+ // ModelIdentity is a SEPARATE field from `model` above and carries the
659+ // UNTRANSLATED controller-side ModelConfig.Model, so a backend can reject a
660+ // request that reached it through a stale distributed route (#10952).
661+ //
662+ // `model` cannot be reused for this: FileStagingClient.TTS/.TTSStream and the
663+ // SoundGeneration path rewrite it into a worker-local absolute path
664+ // (core/services/nodes/file_staging_client.go), while the load-time value is
665+ // untranslated. In distributed mode - exactly the configuration this guards -
666+ // the two already differ, so comparing them would reject valid requests.
667+ //
668+ // Empty means "no identity supplied" and backends MUST skip the check.
669+ string ModelIdentity = 8 ;
627670}
628671
629672message VADRequest {
630673 repeated float audio = 1 ;
674+ // ModelIdentity names the model this request is for; see
675+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
676+ // identity supplied" and backends MUST skip the check.
677+ string ModelIdentity = 2 ;
631678}
632679
633680message VADSegment {
@@ -659,6 +706,10 @@ message DiarizeRequest {
659706 float min_duration_on = 8 ; // discard segments shorter than this (seconds); 0 = backend default
660707 float min_duration_off = 9 ; // merge gaps shorter than this (seconds); 0 = backend default
661708 bool include_text = 10 ; // when the backend can emit per-segment transcript for free, ask it to populate `text`
709+ // ModelIdentity names the model this request is for; see
710+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
711+ // identity supplied" and backends MUST skip the check.
712+ string ModelIdentity = 11 ;
662713}
663714
664715message DiarizeSegment {
@@ -693,6 +744,18 @@ message SoundGenerationRequest {
693744 optional string language = 14 ;
694745 optional string timesignature = 15 ;
695746 optional bool instrumental = 17 ;
747+ // ModelIdentity is a SEPARATE field from `model` above and carries the
748+ // UNTRANSLATED controller-side ModelConfig.Model, so a backend can reject a
749+ // request that reached it through a stale distributed route (#10952).
750+ //
751+ // `model` cannot be reused for this: FileStagingClient.TTS/.TTSStream and the
752+ // SoundGeneration path rewrite it into a worker-local absolute path
753+ // (core/services/nodes/file_staging_client.go), while the load-time value is
754+ // untranslated. In distributed mode - exactly the configuration this guards -
755+ // the two already differ, so comparing them would reject valid requests.
756+ //
757+ // Empty means "no identity supplied" and backends MUST skip the check.
758+ string ModelIdentity = 18 ;
696759}
697760
698761message TokenizationResponse {
@@ -732,6 +795,10 @@ message DetectOptions {
732795 repeated float points = 3 ; // Point coordinates as [x1, y1, label1, x2, y2, label2, ...] (label: 1=pos, 0=neg)
733796 repeated float boxes = 4 ; // Box coordinates as [x1, y1, x2, y2, ...]
734797 float threshold = 5 ; // Detection confidence threshold
798+ // ModelIdentity names the model this request is for; see
799+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
800+ // identity supplied" and backends MUST skip the check.
801+ string ModelIdentity = 6 ;
735802}
736803
737804message Detection {
@@ -754,6 +821,10 @@ message SoundDetectionRequest {
754821 string src = 1 ; // audio file path (LocalAI writes the upload to disk)
755822 int32 top_k = 2 ; // number of top tags to return (0 = all classes)
756823 float threshold = 3 ; // optional: drop tags scoring below this
824+ // ModelIdentity names the model this request is for; see
825+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
826+ // identity supplied" and backends MUST skip the check.
827+ string ModelIdentity = 4 ;
757828}
758829
759830message SoundClass {
@@ -778,6 +849,10 @@ message DepthRequest {
778849 bool include_points = 7 ; // back-project to a 3D point cloud (DualDPT)
779850 float points_conf_thresh = 8 ; // keep points with confidence >= this threshold
780851 repeated string exports = 9 ; // requested exports: "glb", "colmap"
852+ // ModelIdentity names the model this request is for; see
853+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
854+ // identity supplied" and backends MUST skip the check.
855+ string ModelIdentity = 10 ;
781856}
782857
783858message DepthResponse {
@@ -809,6 +884,10 @@ message FaceVerifyRequest {
809884 string img2 = 2 ; // base64-encoded image
810885 float threshold = 3 ; // cosine-distance threshold; 0 = use backend default
811886 bool anti_spoofing = 4 ; // run MiniFASNet liveness on each image; failed liveness forces verified=false
887+ // ModelIdentity names the model this request is for; see
888+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
889+ // identity supplied" and backends MUST skip the check.
890+ string ModelIdentity = 5 ;
812891}
813892
814893message FaceVerifyResponse {
@@ -830,6 +909,10 @@ message FaceAnalyzeRequest {
830909 string img = 1 ; // base64-encoded image
831910 repeated string actions = 2 ; // subset of ["age","gender","emotion","race"]; empty = all-supported
832911 bool anti_spoofing = 3 ;
912+ // ModelIdentity names the model this request is for; see
913+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
914+ // identity supplied" and backends MUST skip the check.
915+ string ModelIdentity = 4 ;
833916}
834917
835918message FaceAnalysis {
@@ -862,6 +945,10 @@ message VoiceVerifyRequest {
862945 string audio2 = 2 ; // path to second audio clip
863946 float threshold = 3 ; // cosine-distance threshold; 0 = use backend default
864947 bool anti_spoofing = 4 ; // reserved for future AASIST bolt-on
948+ // ModelIdentity names the model this request is for; see
949+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
950+ // identity supplied" and backends MUST skip the check.
951+ string ModelIdentity = 5 ;
865952}
866953
867954message VoiceVerifyResponse {
@@ -876,6 +963,10 @@ message VoiceVerifyResponse {
876963message VoiceAnalyzeRequest {
877964 string audio = 1 ; // path to audio clip
878965 repeated string actions = 2 ; // subset of ["age","gender","emotion"]; empty = all-supported
966+ // ModelIdentity names the model this request is for; see
967+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
968+ // identity supplied" and backends MUST skip the check.
969+ string ModelIdentity = 3 ;
879970}
880971
881972message VoiceAnalysis {
@@ -894,6 +985,10 @@ message VoiceAnalyzeResponse {
894985
895986message VoiceEmbedRequest {
896987 string audio = 1 ; // path to audio clip
988+ // ModelIdentity names the model this request is for; see
989+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
990+ // identity supplied" and backends MUST skip the check.
991+ string ModelIdentity = 2 ;
897992}
898993
899994message VoiceEmbedResponse {
@@ -988,6 +1083,10 @@ message AudioTransformRequest {
9881083 string reference_path = 2 ; // optional auxiliary; empty => zero-fill
9891084 string dst = 3 ; // required, output file path
9901085 map <string , string > params = 4 ; // backend-specific tuning
1086+ // ModelIdentity names the model this request is for; see
1087+ // PredictOptions.ModelIdentity for the full rationale. Empty means "no
1088+ // identity supplied" and backends MUST skip the check.
1089+ string ModelIdentity = 5 ;
9911090}
9921091
9931092message AudioTransformResult {
0 commit comments