1pub(crate) use crate::generation_output::*;
4use crate::wal::{channel, role, EventBody};
5use crate::ModelSamplingDefaults;
6use crate::{Daemon, DaemonError, GenParams};
7use superfluid_abi::finish;
8use serde::Deserialize;
9use std::sync::Arc;
10
11pub(crate) const DEFAULT_MAX_TOKENS: u32 = 2048;
12
13#[derive(Clone, Copy)]
14pub struct TokenLimits {
15 pub default_max_tokens: Option<u32>,
16 pub max_context: u32,
17}
18
19impl TokenLimits {
20 pub fn resolve(&self, explicit: Option<u32>, prompt_tokens: impl FnOnce() -> u32) -> u32 {
21 explicit
22 .or(self.default_max_tokens)
23 .unwrap_or_else(|| self.max_context.saturating_sub(prompt_tokens()).max(1))
24 }
25}
26pub(crate) const DEFAULT_MAX_CONTEXT: u32 = 8192;
27
28#[derive(Clone, Copy)]
29pub(crate) struct RequestPolicy {
30 pub(crate) limits: TokenLimits,
31 pub(crate) sampling: SamplingOverrides,
32 pub(crate) qos: RequestQos,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct RequestQos {
37 pub class: u8,
38 pub batch_invariant: bool,
39}
40
41impl RequestQos {
42 pub const AGENT: RequestQos = RequestQos {
43 class: crate::qos::FOREGROUND_AGENT,
44 batch_invariant: false,
45 };
46
47 pub(crate) fn apply(self, daemon: &Daemon, session: u64) -> Result<(), DaemonError> {
48 let held = {
51 let store = daemon.store();
52 let store = store.lock().expect("store");
53 let s = store.session(session)?;
54 RequestQos { class: s.qos_class, batch_invariant: s.batch_invariant }
55 };
56 if self != held {
57 daemon.set_qos(session, self.class, self.batch_invariant)?;
58 }
59 Ok(())
60 }
61}
62
63impl Default for RequestQos {
64 fn default() -> RequestQos {
65 RequestQos::AGENT
66 }
67}
68
69#[derive(Debug, Clone, Copy, Default, PartialEq)]
70pub struct SamplingOverrides {
71 pub temperature: Option<f32>,
72 pub top_p: Option<f32>,
73 pub top_k: Option<u32>,
74 pub min_p: Option<f32>,
75 pub repeat_penalty: Option<f32>,
76 pub spec_max_temperature: Option<f32>,
77 pub fallback: Option<SamplingFallback>,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq)]
85pub struct SamplingFallback {
86 pub temperature: f32,
87 pub repeat_penalty: f32,
88 pub top_p: f32,
89 pub top_k: u32,
90 pub min_p: f32,
91 pub frequency_penalty_as_repeat: bool,
95}
96
97impl SamplingOverrides {
98 pub fn for_daemon(&self, daemon: &Daemon) -> Self {
99 let mut o = *self;
100 if o.spec_max_temperature.is_some() && daemon.speculation().is_none() {
101 o.spec_max_temperature = None;
102 }
103 o
104 }
105
106 pub fn truncates(&self) -> bool {
107 self.top_p.is_some() || self.top_k.is_some() || self.min_p.is_some()
108 }
109}
110
111#[derive(Deserialize, Default)]
112pub(crate) struct ChatRequest {
113 pub(crate) model: Option<String>,
114 #[serde(default)]
115 pub(crate) messages: Vec<ChatMessage>,
116 #[serde(default)]
117 pub(crate) tools: Vec<serde_json::Value>,
118 #[serde(default)]
119 pub(crate) tool_choice: Option<serde_json::Value>,
120 #[serde(default)]
121 pub(crate) stream: bool,
122 #[serde(default)]
123 pub(crate) stream_options: Option<StreamOptions>,
124 pub(crate) max_completion_tokens: Option<u32>,
125 pub(crate) max_tokens: Option<u32>,
126 pub(crate) temperature: Option<f32>,
127 pub(crate) top_p: Option<f32>,
128 pub(crate) top_k: Option<u32>,
129 pub(crate) seed: Option<u64>,
130 pub(crate) presence_penalty: Option<f32>,
131 pub(crate) frequency_penalty: Option<f32>,
132 #[serde(alias = "repetition_penalty")]
133 pub(crate) repeat_penalty: Option<f32>,
134 pub(crate) min_p: Option<f32>,
135 pub(crate) n: Option<u32>,
136 #[serde(default)]
137 pub(crate) ignore_eos: bool,
138 #[serde(default)]
139 pub(crate) stop: StopField,
140 pub(crate) response_format: Option<serde_json::Value>,
141 pub(crate) logit_bias: Option<std::collections::HashMap<String, f32>>,
142 pub(crate) logprobs: Option<bool>,
143 pub(crate) top_logprobs: Option<u32>,
144 pub(crate) chat_template_kwargs: Option<serde_json::Map<String, serde_json::Value>>,
145 pub(crate) enable_thinking: Option<bool>,
146 pub(crate) reasoning_effort: Option<String>,
147 #[serde(skip)]
150 pub(crate) continues: Option<Continuation>,
151 #[serde(skip)]
154 pub(crate) route: Option<&'static str>,
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub(crate) struct Continuation {
160 pub(crate) session: u64,
161 pub(crate) at_event: u64,
162}
163
164impl ChatRequest {
165 pub(crate) fn route(&self) -> &'static str {
166 self.route.unwrap_or("POST /v1/chat/completions")
167 }
168
169 pub(crate) fn thinking(&self) -> Option<bool> {
170 self.chat_template_kwargs
171 .as_ref()
172 .and_then(|m| m.get("enable_thinking"))
173 .and_then(|v| v.as_bool())
174 .or(self.enable_thinking)
175 }
176
177 pub(crate) fn template_kwargs(&self) -> serde_json::Map<String, serde_json::Value> {
178 let mut m = self.chat_template_kwargs.clone().unwrap_or_default();
179 if let Some(t) = self.enable_thinking {
180 m.entry("enable_thinking".to_string())
181 .or_insert(serde_json::Value::Bool(t));
182 }
183 if let Some(e) = &self.reasoning_effort {
184 m.entry("reasoning_effort".to_string())
185 .or_insert(serde_json::Value::String(e.clone()));
186 }
187 m
188 }
189}
190
191pub(crate) fn schema_of(rf: &Option<serde_json::Value>) -> Option<String> {
192 let rf = rf.as_ref()?;
193 match rf.get("type").and_then(|t| t.as_str()) {
194 Some("json_object") => Some(r#"{"type":"object"}"#.to_string()),
195 Some("json_schema") => rf
196 .get("json_schema")
197 .and_then(|js| js.get("schema").or(Some(js)))
198 .map(|s| s.to_string()),
199 _ => None,
200 }
201}
202
203#[derive(Debug, Clone, PartialEq, Eq, Default)]
204pub(crate) enum ToolChoice {
205 #[default]
206 Auto,
207 None,
208 Required,
209 Function(String),
210}
211
212impl ToolChoice {
213 pub(crate) fn parse(v: &Option<serde_json::Value>) -> ToolChoice {
214 let Some(v) = v else { return ToolChoice::Auto };
215 if let Some(s) = v.as_str() {
216 return match s {
217 "none" => ToolChoice::None,
218 "required" => ToolChoice::Required,
219 _ => ToolChoice::Auto,
220 };
221 }
222 let name = v
223 .get("function")
224 .and_then(|f| f.get("name"))
225 .or_else(|| v.get("name"))
226 .and_then(|n| n.as_str());
227 match name {
228 Some(n) if !n.is_empty() => ToolChoice::Function(n.to_string()),
229 _ => ToolChoice::Auto,
230 }
231 }
232
233 pub(crate) fn forces_call(&self) -> bool {
234 matches!(self, ToolChoice::Required | ToolChoice::Function(_))
235 }
236
237 fn declares_tools(&self) -> bool {
238 !matches!(self, ToolChoice::None)
239 }
240
241 fn constrain_to<'a>(
242 &self,
243 tools: &'a [serde_json::Value],
244 ) -> Option<Vec<&'a serde_json::Value>> {
245 match self {
246 ToolChoice::Auto | ToolChoice::None => None,
247 ToolChoice::Required => Some(tools.iter().collect()),
248 ToolChoice::Function(name) => {
249 let picked: Vec<&serde_json::Value> = tools
250 .iter()
251 .filter(|t| {
252 let f = t.get("function").unwrap_or(*t);
253 f.get("name").and_then(|n| n.as_str()) == Some(name.as_str())
254 })
255 .collect();
256 (!picked.is_empty()).then_some(picked)
257 }
258 }
259 }
260}
261
262pub(crate) struct RequestHandles<'a> {
263 daemon: &'a Daemon,
264 grammar: Option<u32>,
265 bias: Option<u32>,
266}
267
268impl<'a> RequestHandles<'a> {
269 pub(crate) fn new(daemon: &'a Daemon, grammar: Option<u32>, bias: Option<u32>) -> Self {
270 RequestHandles {
271 daemon,
272 grammar,
273 bias,
274 }
275 }
276
277 pub(crate) fn apply(&self, extras: &mut crate::scheduler::GenExtras) {
278 if let Some(h) = self.grammar {
279 extras.grammar_handle = h;
280 }
281 if let Some(h) = self.bias {
282 extras.logit_bias_handle = h;
283 }
284 }
285}
286
287impl Drop for RequestHandles<'_> {
288 fn drop(&mut self) {
289 if let Some(h) = self.grammar.take() {
290 self.daemon.grammar_free(h);
291 }
292 if let Some(h) = self.bias.take() {
293 self.daemon.logit_bias_free(h);
294 }
295 }
296}
297
298pub(crate) fn grammar_handle_for(
299 daemon: &Daemon,
300 req: &ChatRequest,
301 choice: &ToolChoice,
302) -> Result<Option<u32>, DaemonError> {
303 if let ToolChoice::Function(name) = choice {
304 let declared = req.tools.iter().any(|t| {
305 let f = t.get("function").unwrap_or(t);
306 f.get("name").and_then(|n| n.as_str()) == Some(name.as_str())
307 });
308 if !declared {
309 return Err(DaemonError::Constraint(format!(
310 "tool_choice names function {name:?}, which is not among the declared tools"
311 )));
312 }
313 }
314 if !req.tools.is_empty() && *choice != ToolChoice::None {
315 let jsons: Vec<String> = match choice.constrain_to(&req.tools) {
316 Some(tools) => tools.iter().map(|t| t.to_string()).collect(),
317 None => req.tools.iter().map(|t| t.to_string()).collect(),
318 };
319 if let Some(tag) = daemon.tool_structural_tag(&jsons, choice.forces_call()) {
320 if let Ok(h) = daemon.grammar_create_structural(&tag) {
321 if h != 0 {
322 return Ok(Some(h));
323 }
324 }
325 }
326 }
327 if let Some(tools) = choice.constrain_to(&req.tools) {
328 if tools.is_empty() {
329 return Err(DaemonError::Constraint(
330 "tool_choice demands a call but the request declares no tools".to_string(),
331 ));
332 }
333 let jsons: Vec<String> = tools.iter().map(|t| t.to_string()).collect();
334 if let Some(schema) = daemon.tool_call_grammar(&jsons) {
335 if let Ok(h) = daemon.grammar_create(&schema) {
336 if h != 0 {
337 return Ok(Some(h));
338 }
339 }
340 }
341 return Err(DaemonError::Constraint(
342 "tool_choice demands a call, but no tool-call grammar could be compiled for this \
343 model's dialect on this engine"
344 .to_string(),
345 ));
346 }
347 match schema_of(&req.response_format) {
348 None if req
349 .response_format
350 .as_ref()
351 .and_then(|rf| rf.get("type"))
352 .and_then(|t| t.as_str())
353 == Some("json_schema") =>
354 {
355 Err(DaemonError::Constraint(
356 "response_format type json_schema requires a json_schema.schema block".to_string(),
357 ))
358 }
359 None => Ok(None),
360 Some(s) => {
361 if let Some(tag) = daemon.response_format_tag(&s) {
362 return match daemon.grammar_create_structural(&tag) {
363 Ok(h) if h != 0 => Ok(Some(h)),
364 _ => Err(DaemonError::Constraint(
365 "response_format grammar did not compile in this model's answer frame on this engine"
366 .to_string(),
367 )),
368 };
369 }
370 match daemon.grammar_create(&s) {
371 Ok(h) if h != 0 => Ok(Some(h)),
372 Ok(_) => Err(DaemonError::Constraint(
373 "response_format grammar did not compile on this engine (schema rejected or \
374 constrained decoding unavailable in this build)"
375 .to_string(),
376 )),
377 Err(e) => Err(DaemonError::Constraint(format!(
378 "response_format grammar could not be created: {e}"
379 ))),
380 }
381 }
382 }
383}
384
385#[derive(Deserialize, Default, Clone)]
386#[serde(untagged)]
387pub(crate) enum StopField {
388 #[default]
389 None,
390 One(String),
391 Many(Vec<String>),
392}
393
394impl StopField {
395 pub(crate) fn list(&self) -> Vec<String> {
396 match self {
397 StopField::None => Vec::new(),
398 StopField::One(s) => vec![s.clone()],
399 StopField::Many(v) => v.iter().filter(|s| !s.is_empty()).cloned().collect(),
400 }
401 }
402}
403
404fn stop_prefix_holdback(text: &str, stops: &[String]) -> usize {
405 let mut hold = 0usize;
406 for s in stops {
407 if s.is_empty() {
408 continue;
409 }
410 let max = s.len().saturating_sub(1).min(text.len());
411 for k in (1..=max).rev() {
412 if !text.is_char_boundary(text.len() - k) || !s.is_char_boundary(k) {
413 continue;
414 }
415 if text.ends_with(&s[..k]) {
416 hold = hold.max(k);
417 break;
418 }
419 }
420 }
421 hold
422}
423
424pub(crate) fn truncate_at_stop(text: &str, stops: &[String]) -> (String, bool) {
425 let mut cut = None;
426 for s in stops {
427 if let Some(i) = text.find(s.as_str()) {
428 cut = Some(cut.map_or(i, |c: usize| c.min(i)));
429 }
430 }
431 match cut {
432 Some(i) => (text[..i].to_string(), true),
433 None => (text.to_string(), false),
434 }
435}
436
437#[cfg(test)]
438mod penalty_alias_tests {
439 use super::{
440 advertised_generation_defaults, draw_seed_if_unseeded, params_of_for, repeat_penalty_for,
441 ChatRequest, CompletionRequest, SamplingOverrides, DEFAULT_REPEAT_PENALTY, DEFAULT_TOP_K,
442 };
443 use crate::GenParams;
444 use crate::ModelSamplingDefaults;
445
446 #[test]
447 fn unseeded_sampled_requests_draw_distinct_seeds() {
448 let m = ModelSamplingDefaults::default();
449 let op = SamplingOverrides::default();
450 let mut a = params_of_for(Some(0.7), None, None, None, None, &m, &op);
451 let mut b = params_of_for(Some(0.7), None, None, None, None, &m, &op);
452 assert_eq!((a.seed, b.seed), (0, 0), "params_of_for itself never draws");
453 assert!(draw_seed_if_unseeded(&mut a, None));
454 assert!(draw_seed_if_unseeded(&mut b, None));
455 assert_ne!(a.seed, 0);
456 assert_ne!(a.seed, b.seed, "two requests, two streams");
457 let mut greedy = params_of_for(Some(0.0), None, None, None, None, &m, &op);
458 assert!(!draw_seed_if_unseeded(&mut greedy, None));
459 assert_eq!(greedy.seed, 0, "greedy has no use for a seed");
460 let mut explicit = params_of_for(Some(0.7), None, None, None, Some(42), &m, &op);
461 assert!(!draw_seed_if_unseeded(&mut explicit, Some(42)));
462 assert_eq!(explicit.seed, 42, "the client's seed is honoured as is");
463 }
464
465 #[test]
466 fn repetition_penalty_is_an_alias_for_repeat_penalty() {
467 let canonical: ChatRequest =
468 serde_json::from_str(r#"{"messages":[],"repeat_penalty":1.3}"#)
469 .expect("canonical spelling");
470 let alias: ChatRequest =
471 serde_json::from_str(r#"{"messages":[],"repetition_penalty":1.3}"#)
472 .expect("alias spelling");
473 assert_eq!(canonical.repeat_penalty, Some(1.3));
474 assert_eq!(
475 alias.repeat_penalty,
476 Some(1.3),
477 "repetition_penalty was dropped"
478 );
479
480 let c_canon: CompletionRequest =
481 serde_json::from_str(r#"{"prompt":"hi","repeat_penalty":1.3}"#).expect("canonical");
482 let c_alias: CompletionRequest =
483 serde_json::from_str(r#"{"prompt":"hi","repetition_penalty":1.3}"#).expect("alias");
484 assert_eq!(c_canon.repeat_penalty, Some(1.3));
485 assert_eq!(
486 c_alias.repeat_penalty,
487 Some(1.3),
488 "repetition_penalty was dropped on /v1/completions"
489 );
490 }
491
492 fn runtime_only(
493 temperature: Option<f32>,
494 top_p: Option<f32>,
495 top_k: Option<u32>,
496 seed: Option<u64>,
497 ) -> GenParams {
498 params_of_for(
499 temperature,
500 top_p,
501 top_k,
502 None,
503 seed,
504 &ModelSamplingDefaults::default(),
505 &SamplingOverrides::default(),
506 )
507 }
508
509 #[test]
510 fn unspecified_truncation_defaults_to_top_k() {
511 let p = runtime_only(None, None, None, None);
512 assert_eq!(
513 p.top_k, DEFAULT_TOP_K,
514 "neither knob given -> default top_k"
515 );
516 assert_eq!(p.top_p, 1.0, "top_p still reports its nominal default");
517 assert_eq!(p.temperature, 1.0);
518 }
519
520 #[test]
521 fn explicit_sampling_knobs_are_never_overridden() {
522 let explicit_top_p = runtime_only(None, Some(1.0), None, None);
523 assert_eq!(
524 explicit_top_p.top_k, 0,
525 "explicit top_p disables the default"
526 );
527 assert_eq!(runtime_only(None, Some(0.9), None, None).top_k, 0);
528 assert_eq!(
529 runtime_only(None, None, Some(7), None).top_k,
530 7,
531 "explicit top_k wins"
532 );
533 assert_eq!(
534 runtime_only(None, None, Some(0), None).top_k,
535 0,
536 "explicit 0 means disabled"
537 );
538 }
539
540 #[test]
541 fn greedy_gets_no_truncation_default() {
542 assert_eq!(runtime_only(Some(0.0), None, None, None).top_k, 0);
543 }
544
545 #[test]
546 fn advertised_defaults_match_the_effective_ones() {
547 let adv = advertised_generation_defaults(
548 &ModelSamplingDefaults::default(),
549 &SamplingOverrides::default(),
550 );
551 let effective = runtime_only(None, None, None, None);
552 assert_eq!(
553 adv.top_k, effective.top_k,
554 "advertised top_k must be the effective one"
555 );
556 assert_eq!(adv.top_p, effective.top_p);
557 assert_eq!(adv.temperature, effective.temperature);
558 assert_eq!(adv.top_k, DEFAULT_TOP_K);
559 }
560
561 #[test]
562 fn round_tripping_advertised_defaults_is_stable() {
563 let adv = advertised_generation_defaults(
564 &ModelSamplingDefaults::default(),
565 &SamplingOverrides::default(),
566 );
567 let echoed = runtime_only(
568 Some(adv.temperature),
569 Some(adv.top_p),
570 Some(adv.top_k),
571 None,
572 );
573 assert_eq!(echoed.top_k, adv.top_k, "echoed top_k survives");
574 assert_eq!(echoed.top_p, adv.top_p);
575 assert_eq!(echoed.temperature, adv.temperature);
576 }
577
578 #[test]
579 fn model_defaults_beat_the_runtime_fallback() {
580 let qwen38 = ModelSamplingDefaults {
581 temperature: Some(1.0),
582 top_p: Some(0.95),
583 top_k: Some(20),
584 min_p: Some(0.0),
585 do_sample: Some(true),
586 repetition_penalty: None,
587 };
588 let p = params_of_for(
589 None,
590 None,
591 None,
592 None,
593 None,
594 &qwen38,
595 &SamplingOverrides::default(),
596 );
597 assert_eq!(p.top_k, 20, "the model asked for 20, not the fallback 40");
598 assert_eq!(p.top_p, 0.95);
599 assert_eq!(p.temperature, 1.0);
600 }
601
602 #[test]
603 fn caller_beats_the_model_defaults() {
604 let m = ModelSamplingDefaults {
605 top_k: Some(20),
606 top_p: Some(0.95),
607 ..Default::default()
608 };
609 assert_eq!(
610 params_of_for(
611 None,
612 None,
613 Some(5),
614 None,
615 None,
616 &m,
617 &SamplingOverrides::default()
618 )
619 .top_k,
620 5
621 );
622 assert_eq!(
623 params_of_for(
624 None,
625 Some(0.5),
626 None,
627 None,
628 None,
629 &m,
630 &SamplingOverrides::default()
631 )
632 .top_p,
633 0.5
634 );
635 assert_eq!(
636 params_of_for(
637 Some(0.3),
638 None,
639 None,
640 None,
641 None,
642 &m,
643 &SamplingOverrides::default()
644 )
645 .temperature,
646 0.3
647 );
648 }
649
650 #[test]
651 fn empty_model_defaults_keep_the_fallback() {
652 let p = runtime_only(None, None, None, None);
653 assert_eq!(p.top_k, DEFAULT_TOP_K);
654 }
655
656 #[test]
657 fn spec_max_temperature_caps_only_the_bundle_default() {
658 let m = ModelSamplingDefaults {
659 temperature: Some(1.0),
660 ..Default::default()
661 };
662 let op = SamplingOverrides {
663 spec_max_temperature: Some(0.6),
664 ..Default::default()
665 };
666 assert_eq!(
667 params_of_for(None, None, None, None, None, &m, &op).temperature,
668 0.6
669 );
670 assert_eq!(
671 params_of_for(Some(0.9), None, None, None, None, &m, &op).temperature,
672 0.9,
673 "caller wins"
674 );
675 let op_t = SamplingOverrides {
676 temperature: Some(0.8),
677 ..op
678 };
679 assert_eq!(
680 params_of_for(None, None, None, None, None, &m, &op_t).temperature,
681 0.8,
682 "--temperature wins"
683 );
684 let cool = ModelSamplingDefaults {
685 temperature: Some(0.3),
686 ..Default::default()
687 };
688 assert_eq!(
689 params_of_for(None, None, None, None, None, &cool, &op).temperature,
690 0.3
691 );
692 let greedy = ModelSamplingDefaults {
693 do_sample: Some(false),
694 ..Default::default()
695 };
696 assert_eq!(
697 params_of_for(None, None, None, None, None, &greedy, &op).temperature,
698 0.0
699 );
700 assert_eq!(
701 params_of_for(
702 None,
703 None,
704 None,
705 None,
706 None,
707 &ModelSamplingDefaults::default(),
708 &op
709 )
710 .temperature,
711 0.6
712 );
713 assert_eq!(advertised_generation_defaults(&m, &op).temperature, 0.6);
714 }
715
716 #[test]
717 fn do_sample_false_means_greedy() {
718 let m = ModelSamplingDefaults {
719 do_sample: Some(false),
720 ..Default::default()
721 };
722 assert_eq!(
723 params_of_for(
724 None,
725 None,
726 None,
727 None,
728 None,
729 &m,
730 &SamplingOverrides::default()
731 )
732 .temperature,
733 0.0
734 );
735 assert_eq!(
736 params_of_for(
737 Some(0.8),
738 None,
739 None,
740 None,
741 None,
742 &m,
743 &SamplingOverrides::default()
744 )
745 .temperature,
746 0.8
747 );
748 }
749
750 #[test]
751 fn model_top_p_alone_suppresses_the_fallback_top_k() {
752 let m = ModelSamplingDefaults {
753 top_p: Some(0.9),
754 ..Default::default()
755 };
756 let p = params_of_for(
757 None,
758 None,
759 None,
760 None,
761 None,
762 &m,
763 &SamplingOverrides::default(),
764 );
765 assert_eq!(p.top_p, 0.9);
766 assert_eq!(
767 p.top_k, 0,
768 "no fallback top_k when the model already truncates"
769 );
770 }
771
772 #[test]
773 fn explicit_top_p_suppresses_the_model_top_k() {
774 let qwen38 = ModelSamplingDefaults {
775 top_p: Some(0.95),
776 top_k: Some(20),
777 ..Default::default()
778 };
779 let p = params_of_for(
780 None,
781 Some(1.0),
782 None,
783 None,
784 None,
785 &qwen38,
786 &SamplingOverrides::default(),
787 );
788 assert_eq!(p.top_p, 1.0, "caller asked for the full distribution");
789 assert_eq!(p.top_k, 0, "and must not inherit the model's top_k");
790 }
791
792 #[test]
793 fn explicit_top_k_suppresses_the_model_top_p() {
794 let qwen38 = ModelSamplingDefaults {
795 top_p: Some(0.95),
796 top_k: Some(20),
797 ..Default::default()
798 };
799 let p = params_of_for(
800 None,
801 None,
802 Some(5),
803 None,
804 None,
805 &qwen38,
806 &SamplingOverrides::default(),
807 );
808 assert_eq!(p.top_k, 5);
809 assert_eq!(
810 p.top_p, 1.0,
811 "model top_p is set aside once the caller truncates"
812 );
813 }
814
815 #[test]
816 fn model_min_p_alone_is_a_truncation_claim() {
817 let m = ModelSamplingDefaults {
818 min_p: Some(0.05),
819 ..Default::default()
820 };
821 let p = params_of_for(
822 None,
823 None,
824 None,
825 None,
826 None,
827 &m,
828 &SamplingOverrides::default(),
829 );
830 assert_eq!(p.min_p, 0.05);
831 assert_eq!(
832 p.top_k, 0,
833 "no fallback top_k stacked on the model's policy"
834 );
835 }
836
837 #[test]
838 fn advertised_defaults_follow_the_model() {
839 let qwen38 = ModelSamplingDefaults {
840 top_p: Some(0.95),
841 top_k: Some(20),
842 ..Default::default()
843 };
844 let adv = advertised_generation_defaults(&qwen38, &SamplingOverrides::default());
845 assert_eq!(adv.top_k, 20);
846 assert_eq!(adv.top_p, 0.95);
847 let echoed = params_of_for(
848 Some(adv.temperature),
849 Some(adv.top_p),
850 Some(adv.top_k),
851 None,
852 None,
853 &qwen38,
854 &SamplingOverrides::default(),
855 );
856 assert_eq!(
857 echoed.top_k, 20,
858 "echoing the advertised values changes nothing"
859 );
860 assert_eq!(echoed.top_p, 0.95);
861 }
862
863 #[test]
864 fn operator_flags_sit_between_caller_and_bundle() {
865 let qwen38 = ModelSamplingDefaults {
866 temperature: Some(0.6),
867 top_p: Some(0.95),
868 top_k: Some(20),
869 min_p: Some(0.0),
870 do_sample: Some(true),
871 repetition_penalty: None,
872 };
873 let op = SamplingOverrides {
874 top_k: Some(64),
875 ..Default::default()
876 };
877
878 let p = params_of_for(None, None, None, None, None, &qwen38, &op);
879 assert_eq!(p.top_k, 64, "operator top_k overrides the bundle's");
880 assert_eq!(
881 p.top_p, 1.0,
882 "and takes the whole level: the bundle's top_p is set aside"
883 );
884 assert_eq!(
885 p.temperature, 0.6,
886 "temperature is a separate rung and still comes from the bundle"
887 );
888
889 let p = params_of_for(None, None, Some(7), None, None, &qwen38, &op);
890 assert_eq!(p.top_k, 7, "an explicit request value wins over the flag");
891
892 let p = params_of_for(
893 None,
894 None,
895 None,
896 None,
897 None,
898 &ModelSamplingDefaults::default(),
899 &op,
900 );
901 assert_eq!(p.top_k, 64);
902 }
903
904 #[test]
905 fn operator_temperature_precedence() {
906 let greedy_model = ModelSamplingDefaults {
907 do_sample: Some(false),
908 ..Default::default()
909 };
910 let op = SamplingOverrides {
911 temperature: Some(0.9),
912 ..Default::default()
913 };
914 assert_eq!(
915 params_of_for(None, None, None, None, None, &greedy_model, &op).temperature,
916 0.9,
917 "the operator outranks the checkpoint"
918 );
919 assert_eq!(
920 params_of_for(Some(0.0), None, None, None, None, &greedy_model, &op).temperature,
921 0.0,
922 "an explicit request temperature still wins"
923 );
924 }
925
926 #[test]
927 fn an_unknown_penalty_is_omitted_but_a_pinned_one_is_not() {
928 let advertise = |resolved: Option<ModelSamplingDefaults>, op: SamplingOverrides| {
929 (resolved.is_some() || op.repeat_penalty.is_some())
930 .then(|| repeat_penalty_for(None, &op, &resolved.clone().unwrap_or_default()))
931 };
932 let none = SamplingOverrides::default();
933
934 assert_eq!(advertise(None, none), None);
935
936 let op = SamplingOverrides {
937 repeat_penalty: Some(1.2),
938 ..Default::default()
939 };
940 assert_eq!(advertise(None, op), Some(1.2));
941
942 let quiet = ModelSamplingDefaults::default();
943 assert_eq!(advertise(Some(quiet), none), Some(DEFAULT_REPEAT_PENALTY));
944 let opted_out = ModelSamplingDefaults {
945 repetition_penalty: Some(1.0),
946 ..Default::default()
947 };
948 assert_eq!(advertise(Some(opted_out), none), Some(1.0));
949 }
950
951 #[test]
952 fn operator_owned_settings_survive_a_failed_bundle_lookup() {
953 let advertise = |resolved: Option<ModelSamplingDefaults>, op: SamplingOverrides| {
954 let eff = advertised_generation_defaults(&resolved.clone().unwrap_or_default(), &op);
955 let temp = (resolved.is_some() || op.temperature.is_some()).then_some(eff.temperature);
956 let trunc = (resolved.is_some() || op.truncates()).then_some((eff.top_p, eff.top_k));
957 (temp, trunc)
958 };
959
960 assert_eq!(advertise(None, SamplingOverrides::default()), (None, None));
961
962 let op = SamplingOverrides {
963 top_k: Some(64),
964 ..Default::default()
965 };
966 assert_eq!(advertise(None, op), (None, Some((1.0, 64))));
967
968 let op = SamplingOverrides {
969 temperature: Some(0.4),
970 ..Default::default()
971 };
972 assert_eq!(advertise(None, op), (Some(0.4), None));
973
974 let qwen38 = ModelSamplingDefaults {
975 temperature: Some(0.6),
976 top_p: Some(0.95),
977 top_k: Some(20),
978 min_p: Some(0.0),
979 do_sample: Some(true),
980 repetition_penalty: None,
981 };
982 let op = SamplingOverrides {
983 top_k: Some(64),
984 ..Default::default()
985 };
986 assert_eq!(advertise(Some(qwen38), op), (Some(0.6), Some((1.0, 64))));
987 }
988
989 #[test]
990 fn repeat_penalty_resolves_caller_then_operator_then_bundle_then_default() {
991 let none = SamplingOverrides::default();
992 let no_model = ModelSamplingDefaults::default();
993
994 assert_eq!(
995 repeat_penalty_for(None, &none, &no_model),
996 DEFAULT_REPEAT_PENALTY
997 );
998 assert_eq!(repeat_penalty_for(Some(1.2), &none, &no_model), 1.2);
999
1000 let model = ModelSamplingDefaults {
1001 repetition_penalty: Some(1.1),
1002 ..Default::default()
1003 };
1004 assert_eq!(
1005 repeat_penalty_for(None, &none, &model),
1006 1.1,
1007 "bundle beats the runtime default"
1008 );
1009
1010 let opted_out = ModelSamplingDefaults {
1011 repetition_penalty: Some(1.0),
1012 ..Default::default()
1013 };
1014 assert_eq!(repeat_penalty_for(None, &none, &opted_out), 1.0);
1015
1016 let op = SamplingOverrides {
1017 repeat_penalty: Some(1.2),
1018 ..Default::default()
1019 };
1020 assert_eq!(
1021 repeat_penalty_for(None, &op, &model),
1022 1.2,
1023 "flag beats the bundle"
1024 );
1025 assert_eq!(
1026 repeat_penalty_for(Some(1.0), &op, &model),
1027 1.0,
1028 "caller's 1.0 opts out"
1029 );
1030 }
1031
1032 #[test]
1033 fn echoing_advertised_min_p_is_stable() {
1034 let model = ModelSamplingDefaults {
1035 top_p: Some(0.95),
1036 top_k: Some(20),
1037 min_p: Some(0.05),
1038 ..Default::default()
1039 };
1040 let adv = advertised_generation_defaults(&model, &SamplingOverrides::default());
1041 assert_eq!(adv.min_p, 0.05, "advertised min_p comes from the model");
1042 let echoed = params_of_for(
1043 Some(adv.temperature),
1044 Some(adv.top_p),
1045 Some(adv.top_k),
1046 Some(adv.min_p),
1047 None,
1048 &model,
1049 &SamplingOverrides::default(),
1050 );
1051 assert_eq!(echoed.min_p, 0.05, "echoed min_p survives the round trip");
1052 assert_eq!(echoed.top_p, 0.95);
1053 assert_eq!(echoed.top_k, 20);
1054 }
1055}
1056
1057#[cfg(test)]
1058mod stop_tests {
1059 use super::{stop_prefix_holdback, truncate_at_stop};
1060
1061 fn stops(s: &[&str]) -> Vec<String> {
1062 s.iter().map(|x| x.to_string()).collect()
1063 }
1064
1065 #[test]
1066 fn holdback_is_the_longest_stop_prefix_at_the_tail() {
1067 let st = stops(&["END", "<|eot|>"]);
1068 assert_eq!(stop_prefix_holdback("hello", &st), 0);
1069 assert_eq!(stop_prefix_holdback("hello E", &st), 1);
1070 assert_eq!(stop_prefix_holdback("hello EN", &st), 2);
1071 assert_eq!(stop_prefix_holdback("hello END", &st), 0);
1072 assert_eq!(stop_prefix_holdback("x <|eot", &st), 5);
1073 assert_eq!(stop_prefix_holdback("a <|e", &stops(&["<|eot|>", "e"])), 3);
1074 assert_eq!(
1075 stop_prefix_holdback("héllo —", &stops(&["— fin"])),
1076 "—".len()
1077 );
1078 assert_eq!(stop_prefix_holdback("abc", &stops(&[""])), 0);
1079 }
1080
1081 #[test]
1082 fn delta_stream_never_emits_a_stop_prefix_that_the_next_delta_completes() {
1083 let st = stops(&["END"]);
1084 let mut acc = String::new();
1085 let mut sent = 0usize;
1086 let mut emitted = String::new();
1087 for delta in ["hello E", "ND world"] {
1088 acc.push_str(delta);
1089 let (mut t, hit) = truncate_at_stop(&acc, &st);
1090 if !hit {
1091 let keep = t.len() - stop_prefix_holdback(&t, &st);
1092 t.truncate(keep);
1093 }
1094 emitted.push_str(t.get(sent..).unwrap_or(""));
1095 sent = sent.max(t.len());
1096 if hit {
1097 break;
1098 }
1099 }
1100 assert_eq!(emitted, "hello ");
1101 }
1102}
1103
1104#[derive(Clone, Copy, Default)]
1105pub(crate) struct DecodeSpan {
1106 first: Option<std::time::Instant>,
1107 last: Option<std::time::Instant>,
1108 first_count: u64,
1109 last_count: u64,
1110 tokens: u64,
1111 elapsed: std::time::Duration,
1112 measured_tokens: u64,
1113 measured_elapsed: std::time::Duration,
1114 scheduler_timed: bool,
1117}
1118
1119impl DecodeSpan {
1120 pub(crate) fn stamp(&mut self, produced: u64) {
1121 self.stamp_at(std::time::Instant::now(), produced);
1122 }
1123
1124 fn stamp_at(&mut self, now: std::time::Instant, produced: u64) {
1125 if self.first.is_none() {
1126 self.first = Some(now);
1127 self.first_count = produced;
1128 }
1129 self.last = Some(now);
1130 self.last_count = produced.max(self.last_count);
1131 }
1132
1133 pub(crate) fn first_token(&self) -> Option<std::time::Instant> {
1134 self.first
1135 }
1136
1137 pub(crate) fn finish(&mut self) {
1138 if let (Some(f), Some(l)) = (self.first, self.last) {
1139 self.tokens += self.last_count.saturating_sub(self.first_count);
1140 self.elapsed += l.saturating_duration_since(f);
1141 }
1142 self.first = None;
1143 self.last = None;
1144 self.first_count = 0;
1145 self.last_count = 0;
1146 }
1147
1148 pub(crate) fn add_measured(&mut self, (tokens, secs): (u64, f64)) {
1155 self.scheduler_timed = true;
1156 if tokens > 0 && secs > 0.0 {
1157 self.measured_tokens += tokens;
1158 self.measured_elapsed += std::time::Duration::from_secs_f64(secs);
1159 }
1160 }
1161
1162 fn measured(&self) -> (u64, Option<std::time::Duration>) {
1163 if self.scheduler_timed {
1164 match self.measured_tokens {
1165 0 => (0, None),
1166 n => (n, Some(self.measured_elapsed)),
1167 }
1168 } else if self.elapsed == std::time::Duration::ZERO {
1169 (0, None)
1170 } else {
1171 (self.tokens, Some(self.elapsed))
1172 }
1173 }
1174}
1175
1176#[derive(Clone, Copy)]
1177pub(crate) struct GenTiming {
1178 pub t0: std::time::Instant,
1179 pub first: Option<std::time::Instant>,
1180 pub decode: DecodeSpan,
1181}
1182
1183fn fim_log_cached(c: &crate::Completion) -> u64 {
1184 if c.cached {
1185 c.prompt_tokens as u64
1186 } else {
1187 0
1188 }
1189}
1190
1191fn prefill_rate(prompt: u64, cached: u64, ttft: Option<std::time::Duration>) -> String {
1192 let prefilled = prompt - cached.min(prompt);
1193 ttft.filter(|d| d.as_secs_f64() > 0.0 && prefilled > 0)
1194 .map(|d| format!("{:.0}", prefilled as f64 / d.as_secs_f64()))
1195 .unwrap_or_else(|| "-".into())
1196}
1197
1198fn decode_rate(tokens: u64, span: Option<std::time::Duration>) -> String {
1199 span.filter(|d| tokens > 0 && d.as_millis() as u64 >= tokens)
1200 .map(|d| format!("{:.1}", tokens as f64 / d.as_secs_f64()))
1201 .unwrap_or_else(|| "-".into())
1202}
1203
1204pub(crate) fn access_log(
1205 route: &str,
1206 model: &str,
1207 prompt: u64,
1208 cached: u64,
1209 completion: u64,
1210 timing: GenTiming,
1211 finish: &str,
1212) {
1213 let GenTiming { t0, first, decode } = timing;
1214 let total_ms = t0.elapsed().as_millis();
1215 let ttft = first.map(|f| f.saturating_duration_since(t0));
1216 let (decode_tokens, decode_span) = decode.measured();
1217 let prefill = prefill_rate(prompt, cached, ttft);
1218 let decode = decode_rate(decode_tokens, decode_span);
1219 tracing::info!(
1220 "<-- 200 {route} model={model} prompt={prompt} cached={cached} completion={completion} ttft={}ms prefill={prefill} tok/s decode={decode} tok/s total={total_ms}ms finish={finish}",
1221 ttft.map(|d| d.as_millis()).unwrap_or(0),
1222 );
1223}
1224
1225pub(crate) fn extras_of(
1226 presence: Option<f32>,
1227 frequency: Option<f32>,
1228 repeat: Option<f32>,
1229 op: &SamplingOverrides,
1230 model: &ModelSamplingDefaults,
1231) -> crate::scheduler::GenExtras {
1232 let mut frequency_penalty = frequency.unwrap_or(0.0);
1233 let mut repeat_penalty = repeat_penalty_for(repeat, op, model);
1234 if op.fallback.is_some_and(|f| f.frequency_penalty_as_repeat)
1235 && repeat.is_none()
1236 && op.repeat_penalty.is_none()
1237 {
1238 if presence.is_some() {
1239 repeat_penalty = 1.0;
1240 } else if let Some(f) = frequency {
1241 repeat_penalty = 1.0 + f;
1242 frequency_penalty = 0.0;
1243 }
1244 }
1245 crate::scheduler::GenExtras {
1246 ephemeral: true,
1247 keep_prefix: false,
1248 presence_penalty: presence.unwrap_or(0.0),
1249 frequency_penalty,
1250 repeat_penalty,
1251 grammar_handle: 0,
1252 logit_bias_handle: 0,
1253 want_logprobs: false,
1254 top_logprobs: 0,
1255 wants_deltas: false,
1256 open_channel: 0,
1257 thinking: None,
1258 ignore_eos: false,
1259 seed_drawn: false,
1260 tool_schemas: None,
1261 }
1262}
1263
1264pub(crate) fn logit_bias_handle_of(
1265 daemon: &Daemon,
1266 map: &Option<std::collections::HashMap<String, f32>>,
1267) -> Option<u32> {
1268 let map = map.as_ref()?;
1269 if map.is_empty() {
1270 return None;
1271 }
1272 let mut tokens = Vec::with_capacity(map.len());
1273 let mut values = Vec::with_capacity(map.len());
1274 for (k, v) in map {
1275 if let Ok(t) = k.parse::<i32>() {
1276 tokens.push(t);
1277 values.push(*v);
1278 }
1279 }
1280 if tokens.is_empty() {
1281 return None;
1282 }
1283 daemon.logit_bias_create(&tokens, &values).ok()
1284}
1285
1286#[derive(Debug, Default, Deserialize)]
1287pub(crate) struct StreamOptions {
1288 #[serde(default)]
1289 #[allow(dead_code)]
1290 pub(crate) include_usage: bool,
1291 #[serde(default)]
1292 pub(crate) continuous_usage_stats: bool,
1293}
1294
1295#[derive(Deserialize)]
1296pub(crate) struct CompletionRequest {
1297 pub(crate) model: Option<String>,
1298 #[serde(default)]
1299 pub(crate) ignore_eos: bool,
1300 #[serde(default)]
1301 pub(crate) prompt: String,
1302 #[serde(default)]
1303 pub(crate) stream: bool,
1304 #[serde(default)]
1305 pub(crate) echo: bool,
1306 pub(crate) max_tokens: Option<u32>,
1307 pub(crate) temperature: Option<f32>,
1308 pub(crate) top_p: Option<f32>,
1309 pub(crate) top_k: Option<u32>,
1310 pub(crate) seed: Option<u64>,
1311 pub(crate) presence_penalty: Option<f32>,
1312 pub(crate) frequency_penalty: Option<f32>,
1313 #[serde(alias = "repetition_penalty")]
1314 pub(crate) repeat_penalty: Option<f32>,
1315 pub(crate) min_p: Option<f32>,
1316 pub(crate) logprobs: Option<u32>,
1317 pub(crate) suffix: Option<String>,
1318 pub(crate) fim_mode: Option<String>,
1319}
1320
1321pub(crate) const DEFAULT_TOP_K: u32 = 40;
1322
1323pub(crate) const DEFAULT_REPEAT_PENALTY: f32 = 1.0;
1324
1325#[cfg(test)]
1326mod embedding_sampling_tests {
1327 use super::*;
1328
1329 fn policy() -> SamplingOverrides {
1330 SamplingOverrides {
1331 fallback: Some(SamplingFallback {
1332 temperature: 0.0, repeat_penalty: 1.07,
1333 top_p: 0.82, top_k: 17, min_p: 0.03,
1334 frequency_penalty_as_repeat: true,
1335 }),
1336 ..Default::default()
1337 }
1338 }
1339
1340 #[test]
1341 fn omitted_parameters_and_props_share_embedding_defaults() {
1342 let m = ModelSamplingDefaults::default();
1343 let op = policy();
1344 let p = advertised_generation_defaults(&m, &op);
1345 assert_eq!((p.temperature, p.top_p, p.top_k, p.min_p), (0.0, 0.82, 17, 0.03));
1346 assert_eq!(repeat_penalty_for(None, &op, &m), 1.07);
1347 let original = advertised_generation_defaults(&m, &SamplingOverrides::default());
1348 assert_eq!((original.temperature, original.top_p, original.top_k, original.min_p), (1.0, 1.0, 40, 0.0));
1349 assert_eq!(repeat_penalty_for(None, &SamplingOverrides::default(), &m), 1.0);
1350 }
1351
1352 #[test]
1353 fn model_truncation_wins_as_a_set_but_not_model_temperature_or_repeat() {
1354 for model in [
1355 ModelSamplingDefaults { top_p: Some(0.95), ..Default::default() },
1356 ModelSamplingDefaults { top_k: Some(9), ..Default::default() },
1357 ModelSamplingDefaults { min_p: Some(0.1), ..Default::default() },
1358 ] {
1359 let model = ModelSamplingDefaults { temperature: Some(0.7), repetition_penalty: Some(1.2), ..model };
1360 let p = advertised_generation_defaults(&model, &policy());
1361 assert_eq!(p.temperature, 0.0);
1362 assert_eq!((p.top_p, p.top_k, p.min_p), (model.top_p.unwrap_or(1.0), model.top_k.unwrap_or(0), model.min_p.unwrap_or(0.0)));
1363 assert_eq!(repeat_penalty_for(None, &policy(), &model), 1.07);
1364 }
1365 }
1366
1367 #[test]
1368 fn caller_and_operator_values_override_embedding_without_merging_truncation() {
1369 let m = ModelSamplingDefaults { top_k: Some(90), ..Default::default() };
1370 let op = SamplingOverrides { temperature: Some(0.6), top_k: Some(12), repeat_penalty: Some(1.3), ..policy() };
1371 let p = advertised_generation_defaults(&m, &op);
1372 assert_eq!((p.temperature, p.top_p, p.top_k, p.min_p), (0.6, 1.0, 12, 0.0));
1373 assert_eq!(repeat_penalty_for(None, &op, &m), 1.3);
1374 let p = params_of_for(Some(0.2), Some(1.0), None, None, Some(42), &m, &op);
1375 assert_eq!((p.temperature, p.top_p, p.top_k, p.min_p, p.seed), (0.2, 1.0, 0, 0.0, 42));
1376 assert_eq!(repeat_penalty_for(Some(1.0), &op, &m), 1.0);
1377 }
1378
1379 #[test]
1380 fn penalty_mapping_is_opt_in_and_respects_explicit_parameters() {
1381 let m = ModelSamplingDefaults::default();
1382 for (presence, frequency, repeat, expected) in [
1383 (None, None, None, (0.0, 0.0, 1.07)),
1384 (None, Some(0.2), None, (0.0, 0.0, 1.2)),
1385 (None, Some(0.0), None, (0.0, 0.0, 1.0)),
1386 (Some(0.0), Some(0.2), None, (0.0, 0.2, 1.0)),
1387 (Some(0.3), None, None, (0.3, 0.0, 1.0)),
1388 (None, Some(0.2), Some(1.1), (0.0, 0.2, 1.1)),
1389 ] {
1390 let e = extras_of(presence, frequency, repeat, &policy(), &m);
1391 assert_eq!((e.presence_penalty, e.frequency_penalty, e.repeat_penalty), expected);
1392 }
1393 let e = extras_of(None, Some(0.2), None, &SamplingOverrides::default(), &m);
1394 assert_eq!((e.frequency_penalty, e.repeat_penalty), (0.2, 1.0));
1395 }
1396}
1397
1398pub(crate) fn advertised_generation_defaults(
1399 model: &ModelSamplingDefaults,
1400 op: &SamplingOverrides,
1401) -> GenParams {
1402 params_of_for(None, None, None, None, None, model, op)
1403}
1404
1405pub(crate) fn repeat_penalty_for(
1406 caller: Option<f32>,
1407 op: &SamplingOverrides,
1408 model: &ModelSamplingDefaults,
1409) -> f32 {
1410 caller
1411 .or(op.repeat_penalty)
1412 .or(op.fallback.map(|f| f.repeat_penalty))
1413 .or(model.repetition_penalty)
1414 .unwrap_or(DEFAULT_REPEAT_PENALTY)
1415}
1416
1417pub(crate) fn draw_seed_if_unseeded(params: &mut GenParams, client_seed: Option<u64>) -> bool {
1418 if client_seed.is_some() || params.temperature <= 0.0 {
1419 return false;
1420 }
1421 params.seed = fresh_seed();
1422 true
1423}
1424
1425fn fresh_seed() -> u64 {
1426 use std::hash::{BuildHasher, Hasher};
1427 let h = std::collections::hash_map::RandomState::new()
1428 .build_hasher()
1429 .finish();
1430 if h == 0 {
1431 1
1432 } else {
1433 h
1434 }
1435}
1436
1437pub(crate) fn params_of_for(
1438 temperature: Option<f32>,
1439 top_p: Option<f32>,
1440 top_k: Option<u32>,
1441 min_p: Option<f32>,
1442 seed: Option<u64>,
1443 model: &ModelSamplingDefaults,
1444 op: &SamplingOverrides,
1445) -> GenParams {
1446 let model_temperature = match model.do_sample {
1447 Some(false) => Some(0.0),
1448 _ => model.temperature,
1449 };
1450 let chosen = temperature.is_some() || op.temperature.is_some();
1451 let temperature = temperature
1452 .or(op.temperature)
1453 .or(op.fallback.map(|f| f.temperature))
1454 .or(model_temperature)
1455 .unwrap_or(1.0);
1456 let temperature = match op.spec_max_temperature {
1457 Some(cap) if !chosen => temperature.min(cap),
1458 _ => temperature,
1459 };
1460
1461 let caller_truncates = top_p.is_some() || top_k.is_some() || min_p.is_some();
1462 let model_truncates = model.top_p.is_some() || model.top_k.is_some() || model.min_p.is_some();
1463 let (eff_top_p, eff_top_k, eff_min_p) = if caller_truncates {
1464 (top_p, top_k, min_p)
1465 } else if op.truncates() {
1466 (op.top_p, op.top_k, op.min_p)
1467 } else if model_truncates {
1468 (model.top_p, model.top_k, model.min_p)
1469 } else if let Some(f) = op.fallback {
1470 (Some(f.top_p), Some(f.top_k), Some(f.min_p))
1471 } else if temperature > 0.0 {
1472 (None, Some(DEFAULT_TOP_K), None)
1473 } else {
1474 (None, None, None)
1475 };
1476 GenParams {
1477 temperature,
1478 top_p: eff_top_p.unwrap_or(1.0),
1479 min_p: eff_min_p.unwrap_or(0.0),
1480 top_k: eff_top_k.unwrap_or(0),
1481 seed: seed.unwrap_or(0),
1482 }
1483}
1484
1485#[derive(Deserialize, Default)]
1486pub(crate) struct ChatMessage {
1487 pub(crate) role: String,
1488 #[serde(default)]
1489 pub(crate) content: Option<serde_json::Value>,
1490 #[serde(default)]
1491 pub(crate) tool_calls: Vec<ReqToolCall>,
1492 #[serde(default)]
1493 pub(crate) tool_call_id: Option<String>,
1494 #[serde(default)]
1495 pub(crate) name: Option<String>,
1496 #[serde(default)]
1497 pub(crate) reasoning_content: Option<String>,
1498 #[serde(default)]
1499 pub(crate) reasoning: Option<String>,
1500}
1501
1502pub(crate) fn reasoning_of(m: &ChatMessage, role: u32) -> Option<String> {
1503 if role != role::ASSISTANT {
1504 return None;
1505 }
1506 m.reasoning_content
1507 .as_deref()
1508 .or(m.reasoning.as_deref())
1509 .map(str::trim)
1510 .filter(|s| !s.is_empty())
1511 .map(str::to_string)
1512}
1513
1514#[derive(Clone, Copy, PartialEq, Eq)]
1515pub(crate) enum MediaKind {
1516 Image,
1517 Audio,
1518}
1519
1520fn require_media(daemon: &Daemon, kind: MediaKind, param: &str) -> Result<(), DaemonError> {
1521 match kind {
1522 MediaKind::Image => daemon.require("modalities", "image_encode", "unsupported_input", Some(param), "does not accept images"),
1523 MediaKind::Audio => daemon.require("modalities", "gemma_audio_encode", "unsupported_input", Some(param), "does not accept audio"),
1524 }
1525}
1526
1527fn content_param(messages: &[ChatMessage], m: &ChatMessage) -> String {
1528 match messages.iter().position(|x| std::ptr::eq(x, m)) {
1529 Some(i) => format!("messages[{i}].content"),
1530 None => "messages".to_string(),
1531 }
1532}
1533
1534pub(crate) struct SplitContent {
1535 pub(crate) text: String,
1536 pub(crate) images: Vec<(String, Vec<u8>, MediaKind)>,
1537}
1538
1539pub(crate) fn absorbable_system_text(m: &ChatMessage) -> Result<Option<String>, DaemonError> {
1540 if m.role != "system" {
1541 return Ok(None);
1542 }
1543 let split = split_content(&m.content)?;
1544 Ok(split.images.is_empty().then_some(split.text))
1545}
1546
1547pub(crate) fn split_content(v: &Option<serde_json::Value>) -> Result<SplitContent, DaemonError> {
1548 let mut out = SplitContent {
1549 text: String::new(),
1550 images: Vec::new(),
1551 };
1552 match v {
1553 None => {}
1554 Some(serde_json::Value::String(s)) => out.text = s.clone(),
1555 Some(serde_json::Value::Array(parts)) => {
1556 for p in parts {
1557 match p.get("type").and_then(|t| t.as_str()) {
1558 Some("text") => {
1559 if let Some(t) = p.get("text").and_then(|t| t.as_str()) {
1560 out.text.push_str(t);
1561 }
1562 }
1563 Some("image_url") => {
1564 let url = p
1565 .get("image_url")
1566 .and_then(|u| u.get("url"))
1567 .and_then(|u| u.as_str())
1568 .unwrap_or("");
1569 let Some(rest) = url.strip_prefix("data:") else {
1570 return Err(DaemonError::Protocol(
1571 "image_url must be a data: URL (remote images are not fetched)",
1572 ));
1573 };
1574 let Some((_, b64)) = rest.split_once(";base64,") else {
1575 return Err(DaemonError::Protocol("image_url data URL must be base64"));
1576 };
1577 let bytes = base64_decode(b64)?;
1578 out.images
1579 .push((std::mem::take(&mut out.text), bytes, MediaKind::Image));
1580 }
1581 Some("input_audio") => {
1582 let ia = p.get("input_audio");
1583 let fmt = ia
1584 .and_then(|a| a.get("format"))
1585 .and_then(|f| f.as_str())
1586 .unwrap_or("wav");
1587 if fmt != "wav" {
1588 return Err(DaemonError::Protocol(
1589 "input_audio format must be wav (16-bit PCM or float32)",
1590 ));
1591 }
1592 let b64 = ia
1593 .and_then(|a| a.get("data"))
1594 .and_then(|d| d.as_str())
1595 .ok_or(DaemonError::Protocol("input_audio requires base64 data"))?;
1596 let bytes = base64_decode(b64)?;
1597 out.images
1598 .push((std::mem::take(&mut out.text), bytes, MediaKind::Audio));
1599 }
1600 _ => {}
1601 }
1602 }
1603 }
1604 Some(_) => {
1605 return Err(DaemonError::Protocol(
1606 "message content must be a string or parts",
1607 ))
1608 }
1609 }
1610 Ok(out)
1611}
1612
1613#[derive(Deserialize)]
1614pub(crate) struct ReqToolCall {
1615 #[serde(default)]
1616 pub(crate) id: Option<String>,
1617 pub(crate) function: Option<ReqFunction>,
1618}
1619
1620#[derive(Deserialize)]
1621pub(crate) struct ReqFunction {
1622 pub(crate) name: String,
1623 #[serde(default)]
1624 pub(crate) arguments: serde_json::Value,
1625}
1626
1627pub(crate) fn build_session(
1628 daemon: &Daemon,
1629 req: &ChatRequest,
1630 op: &SamplingOverrides,
1631 model: &ModelSamplingDefaults,
1632) -> Result<(u64, bool), DaemonError> {
1633 if let Err(why) = daemon.validate_template_kwargs(&req.template_kwargs()) {
1634 return Err(DaemonError::Constraint(why));
1635 }
1636 let mut params = params_of_for(
1637 req.temperature,
1638 req.top_p,
1639 req.top_k,
1640 req.min_p,
1641 req.seed,
1642 model,
1643 &op.for_daemon(daemon),
1644 );
1645 let seed_drawn = draw_seed_if_unseeded(&mut params, req.seed);
1646
1647 if let Some(c) = req.continues {
1648 if !daemon.renders_per_message() {
1651 return Err(DaemonError::Protocol(
1652 "this model's chat template renders whole conversations; a stored one is not continued by appending",
1653 ));
1654 }
1655 let session = daemon.fork(c.session, c.at_event, Some(params))?;
1656 let last_query = req.messages.iter().rposition(|m| m.role == "user");
1659 for (i, m) in req.messages.iter().enumerate() {
1660 append_chat_message(daemon, session, req, m, last_query.is_none_or(|q| i > q))?;
1661 }
1662 return Ok((session, seed_drawn));
1663 }
1664
1665 let session = daemon.create(None, params)?;
1666
1667 if !daemon.renders_per_message() {
1668 let mut msgs: Vec<crate::codec::ChatMessage> = Vec::with_capacity(req.messages.len());
1669 for m in &req.messages {
1670 let split = split_content(&m.content)?;
1671 let role = match m.role.as_str() {
1672 "system" => role::SYSTEM,
1673 "assistant" => role::ASSISTANT,
1674 "tool" => role::TOOL,
1675 _ => role::USER,
1676 };
1677 if !split.images.is_empty() {
1678 let mut parts: Vec<crate::codec::ContentPart> = Vec::new();
1679 let trailing = split.text;
1680 for (pre, bytes, kind) in split.images {
1681 require_media(daemon, kind, &content_param(&req.messages, m))?;
1682 if !pre.is_empty() {
1683 parts.push(crate::codec::ContentPart::Text(pre));
1684 }
1685 let hash = daemon.put_media(&bytes)?;
1686 parts.push(match kind {
1687 MediaKind::Image => crate::codec::ContentPart::Image { blob: hash },
1688 MediaKind::Audio => crate::codec::ContentPart::Audio { blob: hash },
1689 });
1690 }
1691 if !trailing.is_empty() {
1692 parts.push(crate::codec::ContentPart::Text(trailing));
1693 }
1694 let mut msg = crate::codec::ChatMessage::new(role, String::new());
1695 msg.parts = parts;
1696 msg.reasoning = reasoning_of(m, role);
1697 msgs.push(msg);
1698 continue;
1699 }
1700 let mut msg = crate::codec::ChatMessage::new(role, split.text);
1701 for c in &m.tool_calls {
1702 let Some(f) = &c.function else { continue };
1703 let args = match &f.arguments {
1704 serde_json::Value::String(s) => s.clone(),
1705 other => other.to_string(),
1706 };
1707 msg.tool_calls.push(crate::codec::ToolCallMsg {
1708 id: c.id.clone().unwrap_or_default(),
1709 name: f.name.clone(),
1710 arguments: args,
1711 });
1712 }
1713 msg.reasoning = reasoning_of(m, role);
1714 if role == role::TOOL {
1715 msg.tool_call_id = m.tool_call_id.clone();
1716 msg.name = m.name.clone();
1717 }
1718 msgs.push(msg);
1719 }
1720 let tools: Vec<String> = req.tools.iter().map(|t| t.to_string()).collect();
1721 daemon.append_conversation(session, &msgs, &tools, &req.template_kwargs())?;
1722 return Ok((session, seed_drawn));
1723 }
1724
1725 let (system, tool_jsons) = system_head(req)?;
1726 let rest = if system.is_some() { &req.messages[1..] } else { &req.messages[..] };
1727 daemon.append_system_full(session, system, tool_jsons, &req.template_kwargs())?;
1728 let last_query = req.messages.iter().rposition(|m| m.role == "user");
1730 let skipped = req.messages.len() - rest.len();
1731 for (i, m) in rest.iter().enumerate() {
1732 let after_query = last_query.is_some_and(|q| skipped + i > q);
1733 append_chat_message(daemon, session, req, m, after_query)?;
1734 }
1735 Ok((session, seed_drawn))
1736}
1737
1738pub(crate) fn system_head(req: &ChatRequest) -> Result<(Option<String>, Vec<String>), DaemonError> {
1742 let system = req.messages.first().map(absorbable_system_text).transpose()?.flatten();
1743 let tools = if !req.tools.is_empty() && ToolChoice::parse(&req.tool_choice).declares_tools() {
1744 req.tools.iter().map(|t| t.to_string()).collect()
1745 } else {
1746 Vec::new()
1747 };
1748 Ok((system, tools))
1749}
1750
1751fn append_chat_message(
1752 daemon: &Daemon,
1753 session: u64,
1754 req: &ChatRequest,
1755 m: &ChatMessage,
1756 after_query: bool,
1757) -> Result<(), DaemonError> {
1758 let split = split_content(&m.content)?;
1759 if !split.images.is_empty() {
1760 let r = match m.role.as_str() {
1761 "assistant" => role::ASSISTANT,
1762 "system" => role::SYSTEM,
1763 _ => role::USER,
1764 };
1765 let n = split.images.len();
1766 let mut trailing = split.text;
1767 for (i, (pre, bytes, kind)) in split.images.into_iter().enumerate() {
1768 require_media(daemon, kind, &content_param(&req.messages, m))?;
1769 let hash = daemon.put_media(&bytes)?;
1770 let post = if i + 1 == n {
1771 std::mem::take(&mut trailing)
1772 } else {
1773 String::new()
1774 };
1775 daemon.append_image(session, r, &hash, &pre, &post)?;
1776 }
1777 return Ok(());
1778 }
1779 let content = split.text;
1780 match m.role.as_str() {
1781 "assistant" if !m.tool_calls.is_empty() => {
1782 let calls: Vec<(String, String)> = m
1783 .tool_calls
1784 .iter()
1785 .filter_map(|c| c.function.as_ref())
1786 .map(|f| {
1787 let args = match &f.arguments {
1788 serde_json::Value::String(s) => s.clone(),
1789 v => v.to_string(),
1790 };
1791 (f.name.clone(), args)
1792 })
1793 .collect();
1794 daemon.append_assistant_with_tool_calls(session, content, calls, after_query)?;
1795 }
1796 "assistant" => {
1797 daemon.append_message(session, role::ASSISTANT, content)?;
1798 }
1799 "system" => {
1800 daemon.append_message(session, role::SYSTEM, content)?;
1801 }
1802 "tool" => {
1803 daemon.append_tool_history(session, content)?;
1804 }
1805 _ => {
1806 daemon.append_message(session, role::USER, content)?;
1807 }
1808 }
1809 Ok(())
1810}
1811
1812pub(crate) struct Collected {
1813 pub(crate) content: String,
1814 pub(crate) reasoning: String,
1815 pub(crate) tool_calls: Vec<(u64, String, String)>,
1816 pub(crate) unparsed_tool: String,
1817 pub(crate) broken_calls: Vec<(u64, String, String)>,
1818}
1819
1820pub(crate) fn promote_forced_tool_call(
1821 daemon: &Daemon,
1822 c: &mut Collected,
1823 session: u64,
1824 schemas: Option<&crate::codec::ToolSchemas>,
1825) {
1826 if !c.tool_calls.is_empty() {
1827 return;
1828 }
1829 let raw = c.content.trim().to_string();
1830 if raw.is_empty() {
1831 return;
1832 }
1833 if let Some((name, args)) = daemon.parse_tool_call_with(&raw, schemas) {
1834 c.tool_calls.push((session, name, args));
1835 c.content.clear();
1836 }
1837}
1838
1839pub(crate) fn claim_named_calls(daemon: &Daemon, c: &mut Collected, events: &[crate::CommittedEvent], choice: &ToolChoice) {
1840 if *choice == ToolChoice::None {
1841 return;
1842 }
1843 let mut left = String::new();
1844 for e in events {
1845 if let EventBody::ToolParseFailure { raw } = &e.body {
1846 match daemon.named_tool_call(raw) {
1847 Some((name, args)) => c.broken_calls.push((e.event_id, name, args)),
1848 None => left.push_str(raw),
1849 }
1850 }
1851 }
1852 c.unparsed_tool = left;
1853}
1854
1855pub(crate) fn collect(events: &[crate::CommittedEvent]) -> Collected {
1856 let mut c = Collected {
1857 content: String::new(),
1858 reasoning: String::new(),
1859 tool_calls: Vec::new(),
1860 unparsed_tool: String::new(),
1861 broken_calls: Vec::new(),
1862 };
1863 for e in events {
1864 match &e.body {
1865 EventBody::Generated {
1866 text, channel: ch, ..
1867 } => match *ch {
1868 channel::REASONING => c.reasoning.push_str(text),
1869 channel::TOOL_CALL => {}
1870 _ => c.content.push_str(text),
1871 },
1872 EventBody::ToolUse { name, arguments } => {
1873 c.tool_calls
1874 .push((e.event_id, name.clone(), arguments.clone()));
1875 }
1876 EventBody::ToolParseFailure { raw } => c.unparsed_tool.push_str(raw),
1877 _ => {}
1878 }
1879 }
1880 c
1881}
1882
1883pub(crate) fn finish_reason(
1884 fin: u32,
1885 produced: u32,
1886 max_tokens: u32,
1887 had_tool_calls: bool,
1888) -> &'static str {
1889 let cut = match fin {
1890 finish::LENGTH => true,
1891 finish::EOS | finish::CANCELLED => false,
1892 _ => produced >= max_tokens,
1893 };
1894 if cut {
1895 "length"
1896 } else if had_tool_calls {
1897 "tool_calls"
1898 } else {
1899 "stop"
1900 }
1901}
1902
1903pub(crate) fn prompt_len(daemon: &Daemon, session: u64) -> u32 {
1904 daemon
1905 .store()
1906 .lock()
1907 .expect("store")
1908 .session(session)
1909 .map(|s| s.tokens.len().min(u32::MAX as usize) as u32)
1910 .unwrap_or(0)
1911}
1912
1913pub(crate) fn next_id() -> String {
1914 use std::sync::atomic::{AtomicU64, Ordering};
1915 static N: AtomicU64 = AtomicU64::new(1);
1916 format!("chatcmpl-superfluid-{}", N.fetch_add(1, Ordering::Relaxed))
1917}
1918
1919pub(crate) fn next_call_gen() -> u64 {
1922 use std::sync::atomic::{AtomicU64, Ordering};
1923 static N: AtomicU64 = AtomicU64::new(1);
1924 N.fetch_add(1, Ordering::Relaxed)
1925}
1926
1927pub(crate) fn unix_now() -> u64 {
1928 std::time::SystemTime::now()
1929 .duration_since(std::time::UNIX_EPOCH)
1930 .map(|d| d.as_secs())
1931 .unwrap_or(0)
1932}
1933
1934#[cfg(test)]
1935mod access_log_rate_tests {
1936 use super::*;
1937 use std::time::Duration;
1938
1939 #[test]
1940 fn prefill_counts_only_what_was_prefilled() {
1941 let cases = [
1942 (12961u64, 0u64, 10444u64, "1241"),
1943 (14481, 12800, 5716, "294"),
1944 (15627, 14900, 5057, "144"),
1945 (16045, 15900, 2138, "68"),
1946 ];
1947 for (prompt, cached, ttft_ms, want) in cases {
1948 let got = prefill_rate(prompt, cached, Some(Duration::from_millis(ttft_ms)));
1949 assert_eq!(got, want, "prompt={prompt} cached={cached}");
1950 }
1951 }
1952
1953 #[test]
1954 fn a_fully_warm_prompt_prefilled_nothing() {
1955 assert_eq!(
1956 prefill_rate(4096, 4096, Some(Duration::from_millis(80))),
1957 "-"
1958 );
1959 assert_eq!(
1960 prefill_rate(4096, 9000, Some(Duration::from_millis(80))),
1961 "-"
1962 );
1963 }
1964
1965 #[test]
1966 fn a_fim_cache_hit_logs_its_prompt_as_cached() {
1967 let c = |cached| crate::Completion {
1968 text: String::new(),
1969 tokens: vec![1; 16],
1970 cached,
1971 expired: false,
1972 finish: superfluid_abi::finish::NONE,
1973 prompt_tokens: 21,
1974 };
1975 assert_eq!(fim_log_cached(&c(true)), 21);
1976 assert_eq!(
1977 prefill_rate(
1978 21,
1979 fim_log_cached(&c(true)),
1980 Some(Duration::from_micros(60))
1981 ),
1982 "-"
1983 );
1984 assert_eq!(fim_log_cached(&c(false)), 0);
1985 }
1986
1987 #[test]
1988 fn prefill_needs_a_ttft() {
1989 assert_eq!(prefill_rate(1024, 0, None), "-");
1990 assert_eq!(prefill_rate(1024, 0, Some(Duration::ZERO)), "-");
1991 }
1992
1993 fn run(d: &mut DecodeSpan, base: std::time::Instant, stamps: &[(u64, u64)]) {
1994 for (ms, produced) in stamps {
1995 d.stamp_at(base + Duration::from_millis(*ms), *produced);
1996 }
1997 d.finish();
1998 }
1999
2000 #[test]
2001 fn decode_is_the_inter_token_rate() {
2002 let base = std::time::Instant::now();
2003 let mut d = DecodeSpan::default();
2004 run(&mut d, base, &[(0, 1), (781, 200)]);
2005 let (tokens, span) = d.measured();
2006 assert_eq!(tokens, 199);
2007 assert_eq!(decode_rate(tokens, span), "254.8");
2008 }
2009
2010 #[test]
2011 fn decode_excludes_the_whole_first_commit() {
2012 let base = std::time::Instant::now();
2013 let mut d = DecodeSpan::default();
2014 run(&mut d, base, &[(0, 5), (1000, 100)]);
2015 assert_eq!(d.measured().0, 95, "the first commit's 5 predate the span");
2016 assert_eq!(decode_rate(d.measured().0, d.measured().1), "95.0");
2017 }
2018
2019 #[test]
2020 fn decode_spans_do_not_swallow_the_gaps_between_choices() {
2021 let base = std::time::Instant::now();
2022 let mut d = DecodeSpan::default();
2023 run(&mut d, base, &[(0, 1), (500, 51)]);
2024 run(&mut d, base, &[(4500, 1), (5000, 51)]);
2025 let (tokens, span) = d.measured();
2026 assert_eq!(tokens, 100);
2027 assert_eq!(span, Some(Duration::from_millis(1000)), "gap excluded");
2028 assert_eq!(decode_rate(tokens, span), "100.0");
2029 }
2030
2031 #[test]
2032 fn decode_counts_only_what_was_delivered() {
2033 let base = std::time::Instant::now();
2034 let mut d = DecodeSpan::default();
2035 run(&mut d, base, &[(0, 1), (500, 50)]);
2036 let (tokens, span) = d.measured();
2037 assert_eq!(tokens, 49, "the undelivered tail is usage's business");
2038 assert_eq!(decode_rate(tokens, span), "98.0");
2039 }
2040
2041 #[test]
2042 fn decode_declines_to_guess() {
2043 let base = std::time::Instant::now();
2044 let mut d = DecodeSpan::default();
2045 d.finish();
2046 assert_eq!(d.measured(), (0, None));
2047 assert_eq!(decode_rate(0, None), "-");
2048
2049 let mut one = DecodeSpan::default();
2050 run(&mut one, base, &[(0, 40)]);
2051 assert_eq!(decode_rate(one.measured().0, one.measured().1), "-");
2052
2053 assert_eq!(decode_rate(200, Some(Duration::from_millis(150))), "-");
2054 assert_eq!(decode_rate(100, None), "-");
2055 }
2056
2057 #[test]
2058 fn a_tick_split_by_a_channel_change_is_not_counted_against_the_next_tick() {
2059 let base = std::time::Instant::now();
2063 let mut d = DecodeSpan::default();
2064 run(&mut d, base, &[(0, 19), (9, 98), (801, 128)]);
2065 assert_eq!(decode_rate(d.measured().0, d.measured().1), "136.1", "events alone overstate it");
2066 d.add_measured((30, 0.792));
2067 assert_eq!(decode_rate(d.measured().0, d.measured().1), "37.9");
2068 }
2069
2070 #[test]
2071 fn a_generation_delivered_in_one_tick_has_no_decode_rate() {
2072 let base = std::time::Instant::now();
2073 let mut d = DecodeSpan::default();
2074 run(&mut d, base, &[(0, 128)]);
2075 d.add_measured((0, 0.0));
2076 assert_eq!(decode_rate(d.measured().0, d.measured().1), "-");
2077 }
2078
2079 #[test]
2080 fn a_one_tick_generation_split_by_a_channel_change_has_no_decode_rate() {
2081 let base = std::time::Instant::now();
2084 let mut d = DecodeSpan::default();
2085 run(&mut d, base, &[(0, 19), (9, 20)]);
2086 assert_eq!(decode_rate(d.measured().0, d.measured().1), "111.1", "events alone");
2087 d.add_measured((0, 0.0));
2088 assert_eq!(d.measured(), (0, None), "the scheduler saw no tick after the first");
2089 }
2090
2091 #[test]
2092 fn measured_decode_adds_up_over_choices_and_ignores_empty_ones() {
2093 let mut d = DecodeSpan::default();
2094 d.add_measured((63, 0.5));
2095 d.add_measured((0, 0.0));
2096 d.add_measured((63, 0.5));
2097 assert_eq!(d.measured(), (126, Some(Duration::from_secs(1))));
2098 assert_eq!(decode_rate(d.measured().0, d.measured().1), "126.0");
2099 }
2100}
2101
2102#[cfg(test)]
2103mod key_order_tests {
2104 use super::ChatRequest;
2105
2106 #[test]
2107 fn a_tool_keeps_the_key_order_it_was_sent_in() {
2108 let req: ChatRequest = serde_json::from_str(
2109 r#"{"messages":[],"tools":[{"type":"function","function":{"name":"replace_text",
2110 "parameters":{"type":"object","properties":{"path":{"type":"string"},
2111 "old_text":{"type":"string"},"new_text":{"type":"string"}}}}}]}"#,
2112 )
2113 .expect("request");
2114 let t = req.tools[0].to_string();
2115 let at = |k: &str| t.find(k).unwrap_or_else(|| panic!("{k} missing: {t}"));
2116 assert!(at("\"type\"") < at("\"function\""), "{t}");
2117 assert!(
2118 at("\"path\"") < at("\"old_text\"") && at("\"old_text\"") < at("\"new_text\""),
2119 "{t}"
2120 );
2121 }
2122}
2123
2124#[cfg(test)]
2125mod system_absorption_tests {
2126 use super::*;
2127
2128 #[test]
2129 fn assistant_reasoning_is_carried_and_other_roles_are_not() {
2130 let mut m = msg("assistant", serde_json::Value::Null);
2131 m.reasoning_content = Some(" plan ".into());
2132 assert_eq!(reasoning_of(&m, role::ASSISTANT), Some("plan".into()));
2133 m.reasoning_content = None;
2134 m.reasoning = Some("alt".into());
2135 assert_eq!(
2136 reasoning_of(&m, role::ASSISTANT),
2137 Some("alt".into()),
2138 "the other spelling"
2139 );
2140 m.reasoning = Some(" ".into());
2141 assert_eq!(
2142 reasoning_of(&m, role::ASSISTANT),
2143 None,
2144 "blank reasoning is none"
2145 );
2146 let mut u = msg("user", serde_json::json!("hi"));
2147 u.reasoning_content = Some("x".into());
2148 assert_eq!(
2149 reasoning_of(&u, role::USER),
2150 None,
2151 "only assistant turns carry reasoning"
2152 );
2153 }
2154
2155 fn msg(role: &str, content: serde_json::Value) -> ChatMessage {
2156 ChatMessage {
2157 role: role.to_string(),
2158 content: Some(content),
2159 tool_calls: Vec::new(),
2160 tool_call_id: None,
2161 name: None,
2162 reasoning_content: None,
2163 reasoning: None,
2164 }
2165 }
2166
2167 fn image_part() -> serde_json::Value {
2168 serde_json::json!({
2169 "type": "image_url",
2170 "image_url": {"url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="}
2171 })
2172 }
2173
2174 #[test]
2175 fn text_only_system_message_is_absorbed() {
2176 let m = msg("system", serde_json::json!("you are helpful"));
2177 assert_eq!(
2178 absorbable_system_text(&m).expect("split"),
2179 Some("you are helpful".to_string()),
2180 "a text-only system message folds into the scaffold"
2181 );
2182 }
2183
2184 #[test]
2185 fn multimodal_system_message_is_left_for_the_media_loop() {
2186 let m = msg(
2187 "system",
2188 serde_json::json!([{"type": "text", "text": "look at this"}, image_part()]),
2189 );
2190 let split = split_content(&m.content).expect("split");
2191 assert_eq!(split.images.len(), 1, "fixture must carry one image part");
2192
2193 assert_eq!(
2194 absorbable_system_text(&m).expect("split"),
2195 None,
2196 "a system message with media must NOT be absorbed — its images \
2197 would be dropped on the floor"
2198 );
2199 }
2200
2201 #[test]
2202 fn audio_system_message_is_left_for_the_media_loop() {
2203 let m = msg(
2204 "system",
2205 serde_json::json!([
2206 {"type": "text", "text": "listen"},
2207 {"type": "input_audio", "input_audio": {"data": "UklGRiQAAABXQVZF", "format": "wav"}}
2208 ]),
2209 );
2210 let split = split_content(&m.content).expect("split");
2211 assert_eq!(split.images.len(), 1, "fixture must carry one audio part");
2212 assert_eq!(absorbable_system_text(&m).expect("split"), None);
2213 }
2214
2215 #[test]
2216 fn non_system_roles_are_never_absorbed() {
2217 for role in ["user", "assistant", "tool", "developer"] {
2218 let m = msg(role, serde_json::json!("hello"));
2219 assert_eq!(
2220 absorbable_system_text(&m).expect("split"),
2221 None,
2222 "{role} must not be absorbed into the system scaffold"
2223 );
2224 }
2225 }
2226}
2227
2228pub(crate) enum RunError {
2229 Daemon(DaemonError),
2230 Worker(String),
2231}
2232impl From<DaemonError> for RunError {
2233 fn from(error: DaemonError) -> Self {
2234 Self::Daemon(error)
2235 }
2236}
2237
2238pub(crate) async fn completions(
2239 daemon: Arc<Daemon>,
2240 model_name: String,
2241 req: CompletionRequest,
2242 policy: RequestPolicy,
2243 hold: Option<crate::keypolicy::ClientKey>,
2244 hooks: crate::keepalive::Hooks,
2245) -> Result<Generation, RunError> {
2246 let qos = policy.qos;
2247 let max_tokens = policy
2248 .limits
2249 .resolve(req.max_tokens, || daemon.tokenize(&req.prompt).len() as u32);
2250 let id = format!(
2251 "cmpl-superfluid-{}",
2252 next_id().rsplit('-').next().unwrap_or("0")
2253 );
2254 let created = unix_now();
2255 let model_defaults = {
2256 let d = daemon.clone();
2257 tokio::task::spawn_blocking(move || {
2258 crate::with_sync_class(Some(qos.class), || d.model_sampling_defaults())
2259 })
2260 .await
2261 .unwrap_or_default()
2262 };
2263 let mut params = params_of_for(
2264 req.temperature,
2265 req.top_p,
2266 req.top_k,
2267 req.min_p,
2268 req.seed,
2269 &model_defaults,
2270 &policy.sampling.for_daemon(&daemon),
2271 );
2272 let seed_drawn = draw_seed_if_unseeded(&mut params, req.seed);
2273 let mut extras = extras_of(
2274 req.presence_penalty,
2275 req.frequency_penalty,
2276 req.repeat_penalty,
2277 &policy.sampling,
2278 &model_defaults,
2279 );
2280 extras.seed_drawn = seed_drawn;
2281 let want_logprobs = req.logprobs.is_some();
2282 extras.want_logprobs = want_logprobs;
2283 extras.top_logprobs = req.logprobs.unwrap_or(0).min(20) as u8;
2284 extras.ignore_eos = req.ignore_eos;
2285 let echo_offset = if req.echo { req.prompt.len() } else { 0 };
2286
2287 if req.stream {
2288 let body = completion_stream(daemon, req, max_tokens, params, extras, qos, hold).await?;
2289 return Ok(Generation {
2290 kind: Kind::Completion,
2291 id,
2292 model: model_name,
2293 created,
2294 warm_header: None,
2295 body,
2296 });
2297 }
2298
2299 let daemon_lp = Arc::clone(&daemon);
2300 let model_log = model_name.clone();
2301 let out = blocking(qos, hold, move || {
2302 let mut hooks = hooks;
2303 let t0 = std::time::Instant::now();
2304 let mut decode = DecodeSpan::default();
2305 let mut produced = 0u64;
2306 let session = daemon.create(None, params)?;
2307 qos.apply(&daemon, session)?;
2308 daemon.append(session, Some(req.prompt.clone()), Vec::new())?;
2309 if hooks.is_on() {
2310 daemon.session_fits(session, None)?;
2311 }
2312 hooks.admitted(&daemon, session);
2313 hooks.check()?;
2314 let out = daemon.generate_streaming_ex(session, max_tokens, extras, |ev| {
2315 hooks.cancel_if_gone();
2316 if let EventBody::Generated { span, .. } = &ev.body {
2317 if !span.is_empty() {
2318 produced += span.len() as u64;
2319 decode.stamp(produced);
2320 }
2321 }
2322 Ok(())
2323 })?;
2324 let first_tok = decode.first_token();
2325 decode.finish();
2326 decode.add_measured(out.decode);
2327 let c = collect(&out.events);
2328 let usage = usage(&daemon, session, out.tokens_generated, out.warm_prefix);
2329 access_log(
2330 "POST /v1/completions",
2331 &model_log,
2332 usage.prompt.unwrap_or(0),
2333 out.warm_prefix,
2334 out.tokens_generated as u64,
2335 GenTiming {
2336 t0,
2337 first: first_tok,
2338 decode,
2339 },
2340 finish_reason(out.finish, out.tokens_generated, max_tokens, false),
2341 );
2342 let mut text = String::new();
2343 if req.echo {
2344 text.push_str(&req.prompt);
2345 }
2346 text.push_str(&c.content);
2347 text.push_str(&c.reasoning);
2348 text.push_str(&c.unparsed_tool);
2349 Ok::<_, DaemonError>((out, text, usage))
2350 })
2351 .await;
2352 let (out, text, usage) = out?;
2353 let logprobs = want_logprobs.then(|| decode_logprobs(&daemon_lp, &out.logprobs, echo_offset));
2354 let mut frame = Frame::text(
2355 text,
2356 logprobs,
2357 Some(finish_reason(
2358 out.finish,
2359 out.tokens_generated,
2360 max_tokens,
2361 false,
2362 )),
2363 );
2364 frame.usage = Some(usage);
2365 Ok(Generation {
2366 kind: Kind::Completion,
2367 id,
2368 model: model_name,
2369 created,
2370 warm_header: None,
2371 body: Body::Complete(frame),
2372 })
2373}
2374
2375#[allow(clippy::too_many_arguments)]
2376async fn completion_stream(
2377 daemon: Arc<Daemon>,
2378 req: CompletionRequest,
2379 max_tokens: u32,
2380 params: GenParams,
2381 extras: crate::scheduler::GenExtras,
2382 qos: RequestQos,
2383 hold: Option<crate::keypolicy::ClientKey>,
2384) -> Result<Body, RunError> {
2385 let want_logprobs = req.logprobs.is_some();
2386 let admitted_daemon = Arc::clone(&daemon);
2387 let (req, session) = blocking(qos, hold.clone(), move || {
2388 let session = admitted_daemon.create(None, params)?;
2389 qos.apply(&admitted_daemon, session)?;
2390 admitted_daemon.append(session, Some(req.prompt.clone()), Vec::new())?;
2391 admitted_daemon.session_fits(session, None)?;
2392 Ok((req, session))
2393 })
2394 .await?;
2395 let rx = spawn_stream(qos, hold, move |tx| {
2396 let _closed = crate::keepalive::cancel_when_closed(&daemon, session, tx);
2397 let send = |v: Frame| {
2398 tx.blocking_send(Ok(v))
2399 .map_err(|_| DaemonError::Protocol("generation consumer gone"))
2400 };
2401 let lp_q: std::rc::Rc<
2402 std::cell::RefCell<std::collections::VecDeque<crate::scheduler::TokenLogprob>>,
2403 > = std::rc::Rc::new(std::cell::RefCell::new(std::collections::VecDeque::new()));
2404 let lp_fill = std::rc::Rc::clone(&lp_q);
2405 let mut offset: usize = 0;
2406 let run = || -> Result<(), DaemonError> {
2407 if req.echo {
2408 offset = req.prompt.len();
2409 send(Frame::text(&req.prompt, None, None))?;
2410 }
2411 let out = daemon.generate_streaming_ex_lp(
2412 session,
2413 max_tokens,
2414 extras,
2415 |ev| {
2416 if let EventBody::Generated {
2417 text,
2418 channel: ch,
2419 span,
2420 ..
2421 } = &ev.body
2422 {
2423 let these: Vec<crate::scheduler::TokenLogprob> = if want_logprobs {
2424 let mut q = lp_q.borrow_mut();
2425 (0..span.len()).filter_map(|_| q.pop_front()).collect()
2426 } else {
2427 Vec::new()
2428 };
2429 if !text.is_empty() && *ch != channel::TOOL_CALL {
2430 let lp = if want_logprobs {
2431 let v = Some(decode_logprobs(&daemon, &these, offset));
2432 offset += text.len();
2433 v
2434 } else {
2435 None
2436 };
2437 return send(Frame::text(text, lp, None));
2438 }
2439 }
2440 Ok(())
2441 },
2442 |items| {
2443 lp_fill.borrow_mut().extend(items.iter().cloned());
2444 },
2445 )?;
2446 let fin = finish_reason(out.finish, out.tokens_generated, max_tokens, false);
2447 let mut terminal = Frame::text("", None, Some(fin));
2448 terminal.usage = Some(usage(
2449 &daemon,
2450 session,
2451 out.tokens_generated,
2452 out.warm_prefix,
2453 ));
2454 send(terminal)
2455 };
2456 run()
2457 });
2458 Ok(Body::Stream(rx))
2459}
2460pub(crate) async fn chat(
2461 daemon: Arc<Daemon>,
2462 model_name: String,
2463 req: ChatRequest,
2464 policy: RequestPolicy,
2465 hold: Option<crate::keypolicy::ClientKey>,
2466 hooks: crate::keepalive::Hooks,
2467) -> Result<Generation, RunError> {
2468 let qos = policy.qos;
2469 let explicit_max = req.max_completion_tokens.or(req.max_tokens);
2470 let id = next_id();
2471 let created = unix_now();
2472
2473 if req.stream {
2474 let body = chat_stream(daemon, model_name.clone(), req, policy, explicit_max, hold).await?;
2475 return Ok(Generation {
2476 kind: Kind::Chat,
2477 id,
2478 model: model_name,
2479 created,
2480 warm_header: None,
2481 body,
2482 });
2483 }
2484
2485 let n = req.n.unwrap_or(1).max(1);
2486 let route = req.route();
2487 let stops = req.stop.list();
2488 let model_log = model_name.clone();
2489 let out = blocking(qos, hold, move || {
2490 let mut hooks = hooks;
2491 let t0 = std::time::Instant::now();
2492 let mut decode = DecodeSpan::default();
2493 let mut ttft_first: Option<std::time::Instant> = None;
2494 let mut last_finish = "-";
2495 let model_defaults = daemon.model_sampling_defaults();
2496 let mut extras = extras_of(
2497 req.presence_penalty,
2498 req.frequency_penalty,
2499 req.repeat_penalty,
2500 &policy.sampling,
2501 &model_defaults,
2502 );
2503 let choice = ToolChoice::parse(&req.tool_choice);
2504 if choice.forces_call() {
2505 extras.open_channel = crate::wal::channel::TOOL_CALL;
2506 }
2507 let tool_schemas = crate::codec::ToolSchemas::of_values(&req.tools);
2508 extras.tool_schemas = tool_schemas.clone();
2509 let handles = RequestHandles::new(
2510 &daemon,
2511 grammar_handle_for(&daemon, &req, &choice)?,
2512 logit_bias_handle_of(&daemon, &req.logit_bias),
2513 );
2514 handles.apply(&mut extras);
2515 let want_logprobs = req.logprobs.unwrap_or(false);
2516 extras.want_logprobs = want_logprobs;
2517 extras.top_logprobs = req.top_logprobs.unwrap_or(0).min(20) as u8;
2518 extras.thinking = req.thinking();
2519 extras.ignore_eos = req.ignore_eos;
2520 let cancels = daemon.cancel_registry();
2521 let RequestPolicy {
2522 limits,
2523 sampling: op,
2524 qos,
2525 } = policy;
2526 let mut choices = Vec::with_capacity(n as usize);
2527 let mut completion_tokens = 0u64;
2528 let mut prompt_tokens = 0u64;
2529 let mut warm = 0u64;
2530 let mut warm_first: Option<u64> = None;
2531 let mut spec = (0u64, 0u64);
2532 let mut last_session = None;
2533 for i in 0..n {
2534 if i > 0 && hooks.gone() {
2535 break;
2536 }
2537 let (session, seed_drawn) = build_session(&daemon, &req, &op, &model_defaults)?;
2538 last_session = Some(session);
2539 let mut extras = extras.clone();
2540 extras.seed_drawn = seed_drawn;
2541 qos.apply(&daemon, session)?;
2542 if hooks.is_on() {
2543 daemon.session_fits(session, req.thinking())?;
2544 }
2545 hooks.admitted(&daemon, session);
2546 hooks.check()?;
2547 let max_tokens = limits.resolve(explicit_max, || prompt_len(&daemon, session));
2548 let mut acc = String::new();
2549 let mut produced = 0u64;
2550 let stops = stops.clone();
2551 let cancels = std::sync::Arc::clone(&cancels);
2552 let mut stopped = false;
2553 let out = daemon.generate_streaming_ex(session, max_tokens, extras, |ev| {
2554 hooks.cancel_if_gone();
2555 if let EventBody::Generated {
2556 text,
2557 channel: ch,
2558 span,
2559 ..
2560 } = &ev.body
2561 {
2562 if !span.is_empty() {
2563 produced += span.len() as u64;
2564 decode.stamp(produced);
2565 }
2566 if *ch == channel::TEXT && !stops.is_empty() && !stopped {
2567 acc.push_str(text);
2568 if stops.iter().any(|s| acc.contains(s.as_str())) {
2569 cancels.cancel(session);
2570 stopped = true;
2571 }
2572 }
2573 }
2574 Ok(())
2575 })?;
2576 ttft_first = ttft_first.or_else(|| decode.first_token());
2577 decode.finish();
2578 decode.add_measured(out.decode);
2579 let mut c = collect(&out.events);
2580 claim_named_calls(&daemon, &mut c, &out.events, &choice);
2581 if choice.forces_call() {
2582 promote_forced_tool_call(&daemon, &mut c, session, tool_schemas.as_deref());
2583 }
2584 let (content, hit_stop) = truncate_at_stop(&c.content, &stops);
2585 c.content = content;
2586 let usage = usage(&daemon, session, out.tokens_generated, out.warm_prefix);
2587 completion_tokens += usage.completion;
2588 spec.0 += out.spec.0;
2589 spec.1 += out.spec.1;
2590 prompt_tokens = usage.prompt.unwrap_or(prompt_tokens);
2591 warm = warm.max(out.warm_prefix);
2592 warm_first.get_or_insert(out.warm_prefix);
2593 let finish = if !c.broken_calls.is_empty() {
2594 "length"
2595 } else if hit_stop {
2596 "stop"
2597 } else {
2598 finish_reason(
2599 out.finish,
2600 out.tokens_generated,
2601 max_tokens,
2602 !c.tool_calls.is_empty() && choice != ToolChoice::None,
2603 )
2604 };
2605 choices.push(Choice {
2606 index: i,
2607 message: message(&c, &choice),
2608 finish: Some(finish),
2609 logprobs: want_logprobs.then(|| decode_logprobs(&daemon, &out.logprobs, 0)),
2610 ..Choice::default()
2611 });
2612 last_finish = finish;
2613 }
2614 access_log(
2615 route,
2616 &model_log,
2617 prompt_tokens,
2618 warm_first.unwrap_or(warm),
2619 completion_tokens,
2620 GenTiming {
2621 t0,
2622 first: ttft_first,
2623 decode,
2624 },
2625 last_finish,
2626 );
2627 Ok::<_, DaemonError>((choices, prompt_tokens, completion_tokens, warm, spec, last_session))
2628 })
2629 .await;
2630 let (choices, prompt_tokens, completion_tokens, warm, spec, session) = out?;
2631 let frame = Frame {
2632 choices,
2633 usage: Some(Usage::complete(
2634 prompt_tokens,
2635 completion_tokens,
2636 Some(warm.min(prompt_tokens)),
2637 )),
2638 spec: (spec.0 > 0).then_some(spec),
2639 session,
2640 ..Frame::default()
2641 };
2642 Ok(Generation {
2643 kind: Kind::Chat,
2644 id,
2645 model: model_name,
2646 created,
2647 warm_header: Some(warm),
2648 body: Body::Complete(frame),
2649 })
2650}
2651
2652async fn chat_stream(
2653 daemon: Arc<Daemon>,
2654 model_name: String,
2655 req: ChatRequest,
2656 policy: RequestPolicy,
2657 explicit_max: Option<u32>,
2658 hold: Option<crate::keypolicy::ClientKey>,
2659) -> Result<Body, RunError> {
2660 let RequestPolicy {
2661 limits,
2662 sampling: op,
2663 qos,
2664 } = policy;
2665 let t0 = std::time::Instant::now();
2666 let admitted_daemon = Arc::clone(&daemon);
2667 let (req, session, seed_drawn, model_defaults) = blocking(qos, hold.clone(), move || {
2668 let model_defaults = admitted_daemon.model_sampling_defaults();
2669 let (session, seed_drawn) = build_session(&admitted_daemon, &req, &op, &model_defaults)?;
2670 qos.apply(&admitted_daemon, session)?;
2671 admitted_daemon.session_fits(session, req.thinking())?;
2672 Ok((req, session, seed_drawn, model_defaults))
2673 })
2674 .await?;
2675 let rx = spawn_stream(qos, hold, move |tx| {
2676 let _closed = crate::keepalive::cancel_when_closed(&daemon, session, tx);
2677 let send = |v: Frame| -> Result<(), DaemonError> {
2678 tx.blocking_send(Ok(v))
2679 .map_err(|_| DaemonError::Protocol("generation consumer gone"))
2680 };
2681 let run = || -> Result<(), DaemonError> {
2682 send(Frame::chat(
2683 Message {
2684 assistant: true,
2685 ..Message::default()
2686 },
2687 None,
2688 ))?;
2689 let mut extras = extras_of(
2690 req.presence_penalty,
2691 req.frequency_penalty,
2692 req.repeat_penalty,
2693 &op,
2694 &model_defaults,
2695 );
2696 extras.seed_drawn = seed_drawn;
2697 let choice = ToolChoice::parse(&req.tool_choice);
2698 if choice.forces_call() {
2699 extras.open_channel = crate::wal::channel::TOOL_CALL;
2700 }
2701 let tool_schemas = crate::codec::ToolSchemas::of_values(&req.tools);
2702 extras.tool_schemas = tool_schemas.clone();
2703 let frag =
2704 crate::tool_fragment::ToolCallStream::new(if choice == ToolChoice::None {
2705 crate::tool_fragment::ToolFragmentMode::None
2706 } else {
2707 daemon.tool_fragment_mode()
2708 })
2709 .with_schemas(tool_schemas.clone());
2710 let gen_no = next_call_gen();
2711 let handles = RequestHandles::new(
2712 &daemon,
2713 grammar_handle_for(&daemon, &req, &choice)?,
2714 logit_bias_handle_of(&daemon, &req.logit_bias),
2715 );
2716 handles.apply(&mut extras);
2717 let want_logprobs = req.logprobs.unwrap_or(false);
2718 extras.want_logprobs = want_logprobs;
2719 extras.top_logprobs = req.top_logprobs.unwrap_or(0).min(20) as u8;
2720 extras.thinking = req.thinking();
2721 extras.ignore_eos = req.ignore_eos;
2722 let lp_q: std::rc::Rc<
2723 std::cell::RefCell<std::collections::VecDeque<crate::scheduler::TokenLogprob>>,
2724 > = std::rc::Rc::new(std::cell::RefCell::new(std::collections::VecDeque::new()));
2725 let lp_fill = std::rc::Rc::clone(&lp_q);
2726 let max_tokens = limits.resolve(explicit_max, || prompt_len(&daemon, session));
2727 let stops = req.stop.list();
2728 let cancels = daemon.cancel_registry();
2729 let delta_mode = !want_logprobs;
2730 let continuous_usage = req
2731 .stream_options
2732 .as_ref()
2733 .is_some_and(|o| o.continuous_usage_stats);
2734 struct StreamText {
2735 usage_now: Option<u32>,
2736 committed: u32,
2737 decode: DecodeSpan,
2738 content_acc: String,
2739 content_sent: usize,
2740 stopped: bool,
2741 dead: bool,
2742 frag: crate::tool_fragment::ToolCallStream,
2743 tool_index: usize,
2744 open_call_id: String,
2745 orphaned_calls: usize,
2746 settled_ahead: usize,
2749 }
2750 let st = std::rc::Rc::new(std::cell::RefCell::new(StreamText {
2751 usage_now: None,
2752 committed: 0,
2753 decode: DecodeSpan::default(),
2754 content_acc: String::new(),
2755 content_sent: 0,
2756 stopped: false,
2757 dead: false,
2758 frag,
2759 tool_index: 0,
2760 open_call_id: String::new(),
2761 orphaned_calls: 0,
2762 settled_ahead: 0,
2763 }));
2764 let tool_text = |s: &mut StreamText,
2769 text: &str,
2770 event_id: Option<u64>|
2771 -> Result<(), DaemonError> {
2772 for d in s.frag.feed(text) {
2773 if d.opening {
2774 s.open_call_id = match event_id {
2775 Some(id) => format!("call_{id}"),
2776 None => format!("call_{gen_no}_{}", s.tool_index),
2777 };
2778 }
2779 send(Frame::chat(Message::tool(&d, s.tool_index, &s.open_call_id), None))?;
2780 }
2781 Ok(())
2782 };
2783 let close_call = |s: &mut StreamText| -> Result<(), DaemonError> {
2789 let content = |s: &StreamText, text: String| {
2790 let mut ck = Frame::chat(Message::content(text), None);
2791 if let Some(n) = s.usage_now {
2792 ck.usage = Some(Usage::partial(n));
2793 }
2794 ck
2795 };
2796 let raw = s.frag.body().to_string();
2797 let parsed = daemon.parse_tool_call_with(&raw, tool_schemas.as_deref());
2798 if s.frag.opened() {
2799 match parsed {
2800 Some((_, arguments)) => {
2801 for d in s.frag.finish(&arguments) {
2802 send(Frame::chat(Message::tool(&d, s.tool_index, &s.open_call_id), None))?;
2803 }
2804 s.tool_index += 1;
2805 }
2806 None => {
2807 s.tool_index += 1;
2810 s.orphaned_calls += 1;
2811 send(content(s, raw))?;
2812 }
2813 }
2814 } else {
2815 let id = format!("call_{gen_no}_{}", s.tool_index);
2818 match parsed {
2819 Some((name, arguments)) if choice != ToolChoice::None => {
2820 send(Frame::chat(Message::complete_tool(s.tool_index, id, name, arguments), None))?;
2821 s.tool_index += 1;
2822 }
2823 Some((name, arguments)) => {
2824 let text = format!(
2825 "{{\"name\":{},\"arguments\":{}}}",
2826 serde_json::Value::String(name),
2827 arguments
2828 );
2829 send(content(s, text))?;
2830 }
2831 None => {
2832 let named = if choice != ToolChoice::None { daemon.named_tool_call(&raw) } else { None };
2833 match named {
2834 Some((name, arguments)) => {
2835 send(Frame::chat(Message::complete_tool(s.tool_index, id, name, arguments), None))?;
2836 s.tool_index += 1;
2837 s.orphaned_calls += 1;
2838 }
2839 None => send(content(s, raw))?,
2840 }
2841 }
2842 }
2843 }
2844 s.frag.reset();
2845 s.settled_ahead += 1;
2846 Ok(())
2847 };
2848 let push_text =
2849 |s: &mut StreamText, text: &str, lp: Option<Logprobs>| -> Result<(), DaemonError> {
2850 if s.stopped {
2851 return Ok(());
2852 }
2853 if choice.forces_call() {
2854 s.content_acc.push_str(text);
2855 return Ok(());
2856 }
2857 let start = s.content_sent;
2858 s.content_acc.push_str(text);
2859 let tail = &s.content_acc[start..];
2860 let mut cut: Option<usize> = None;
2861 for stop in &stops {
2862 if let Some(i) = tail.find(stop.as_str()) {
2863 cut = Some(cut.map_or(i, |c| c.min(i)));
2864 }
2865 }
2866 let hit = cut.is_some();
2867 let end = match cut {
2868 Some(i) => start + i,
2869 None => start + tail.len() - stop_prefix_holdback(tail, &stops),
2870 };
2871 let piece = s.content_acc.get(start..end).unwrap_or("").to_string();
2872 s.content_sent = s.content_sent.max(end);
2873 if !piece.is_empty() {
2874 let mut ck = Frame::chat(Message::content(piece), None);
2875 if let Some(lp) = lp {
2876 ck.choices[0].logprobs = Some(lp);
2877 }
2878 if let Some(n) = s.usage_now {
2879 ck.usage = Some(Usage::partial(n));
2880 }
2881 send(ck)?;
2882 }
2883 if hit {
2884 cancels.cancel(session);
2885 s.stopped = true;
2886 }
2887 Ok(())
2888 };
2889 let st_delta = std::rc::Rc::clone(&st);
2890 let on_delta = |ch: u32, text: &str, produced: u32, closes: bool| {
2891 if !delta_mode || (text.is_empty() && !closes) {
2892 return;
2893 }
2894 let mut s = st_delta.borrow_mut();
2895 if continuous_usage {
2896 s.usage_now = Some(produced);
2897 }
2898 if s.dead {
2899 return;
2900 }
2901 s.decode.stamp(produced as u64);
2902 let r = if ch == channel::TOOL_CALL {
2903 tool_text(&mut s, text, None)
2904 .and_then(|()| if closes { close_call(&mut s) } else { Ok(()) })
2905 } else if ch == channel::REASONING {
2906 let mut ck = Frame::chat(Message::reasoning(text), None);
2907 if let Some(n) = s.usage_now {
2908 ck.usage = Some(Usage::partial(n));
2909 }
2910 send(ck)
2911 } else {
2912 push_text(&mut s, text, None)
2913 };
2914 if r.is_err() {
2915 cancels.cancel(session);
2916 s.dead = true;
2917 }
2918 };
2919 let st_ev = std::rc::Rc::clone(&st);
2920 let on_event = |ev: &crate::CommittedEvent| {
2921 match &ev.body {
2922 EventBody::Generated {
2923 text,
2924 channel: ch,
2925 span,
2926 ..
2927 } => {
2928 let mut s = st_ev.borrow_mut();
2929 let covered_by_delta = delta_mode
2930 && (*ch == channel::TEXT
2931 || *ch == channel::REASONING
2932 || *ch == channel::TOOL_CALL)
2933 && !text.is_empty();
2934 s.committed += span.len() as u32;
2935 let committed = s.committed as u64;
2936 if !covered_by_delta && !span.is_empty() {
2937 s.decode.stamp(committed);
2938 }
2939 if continuous_usage && !delta_mode {
2940 s.usage_now = Some(s.committed);
2941 }
2942 let these: Vec<crate::scheduler::TokenLogprob> = if want_logprobs {
2943 let mut q = lp_q.borrow_mut();
2944 (0..span.len()).filter_map(|_| q.pop_front()).collect()
2945 } else {
2946 Vec::new()
2947 };
2948 if *ch == channel::TOOL_CALL {
2949 if !delta_mode {
2950 tool_text(&mut s, text, Some(ev.event_id))?;
2951 }
2952 return Ok(());
2953 }
2954 if text.is_empty() {
2955 return Ok(());
2956 }
2957 if delta_mode {
2958 return Ok(());
2959 }
2960 if *ch == channel::REASONING {
2961 let mut ck = Frame::chat(Message::reasoning(text), None);
2962 if let Some(n) = s.usage_now {
2963 ck.usage = Some(Usage::partial(n));
2964 }
2965 return send(ck);
2966 }
2967 let lp = want_logprobs.then(|| decode_logprobs(&daemon, &these, 0));
2968 push_text(&mut s, text, lp)
2969 }
2970 EventBody::ToolParseFailure { raw } => {
2971 let mut s = st_ev.borrow_mut();
2972 if s.settled_ahead > 0 {
2976 s.settled_ahead -= 1;
2977 return Ok(());
2978 }
2979 let in_frag = s.frag.opened();
2980 if choice != ToolChoice::None && !in_frag {
2981 if let Some((name, arguments)) = daemon.named_tool_call(raw) {
2982 let delta = Message::complete_tool(
2983 s.tool_index,
2984 format!("call_{}", ev.event_id),
2985 name,
2986 arguments,
2987 );
2988 s.tool_index += 1;
2989 s.orphaned_calls += 1;
2990 return send(Frame::chat(delta, None));
2991 }
2992 }
2993 if in_frag {
2994 s.orphaned_calls += 1;
2995 s.tool_index += 1;
2996 }
2997 s.frag.reset();
2998 let mut ck = Frame::chat(Message::content(raw), None);
2999 if continuous_usage {
3000 ck.usage = Some(Usage::partial(s.committed));
3001 }
3002 send(ck)
3003 }
3004 EventBody::ToolUse { name, arguments } => {
3005 let mut s = st_ev.borrow_mut();
3006 if s.settled_ahead > 0 {
3007 s.settled_ahead -= 1;
3008 return Ok(());
3009 }
3010 if choice == ToolChoice::None {
3011 let raw = format!(
3012 "{{\"name\":{},\"arguments\":{}}}",
3013 serde_json::Value::String(name.clone()),
3014 arguments
3015 );
3016 let mut ck = Frame::chat(Message::content(raw), None);
3017 if continuous_usage {
3018 ck.usage = Some(Usage::partial(s.committed));
3019 }
3020 return send(ck);
3021 }
3022 if s.frag.opened() {
3023 for d in s.frag.finish(arguments) {
3024 send(Frame::chat(
3025 Message::tool(&d, s.tool_index, &s.open_call_id),
3026 None,
3027 ))?;
3028 }
3029 s.frag.reset();
3030 s.tool_index += 1;
3031 return Ok(());
3032 }
3033 let delta = Message::complete_tool(
3034 s.tool_index,
3035 format!("call_{}", ev.event_id),
3036 name.clone(),
3037 arguments.clone(),
3038 );
3039 s.tool_index += 1;
3040 send(Frame::chat(delta, None))
3041 }
3042 _ => Ok(()),
3043 }
3044 };
3045 let on_logprobs = |items: &[crate::scheduler::TokenLogprob]| {
3046 lp_fill.borrow_mut().extend(items.iter().cloned());
3047 };
3048 let out = if delta_mode {
3049 daemon.generate_streaming_delta(
3050 session,
3051 max_tokens,
3052 extras,
3053 on_event,
3054 on_logprobs,
3055 on_delta,
3056 )?
3057 } else {
3058 daemon.generate_streaming_ex_lp(
3059 session,
3060 max_tokens,
3061 extras,
3062 on_event,
3063 on_logprobs,
3064 )?
3065 };
3066 let st = st.borrow();
3067 let (mut decode, stopped) = (st.decode, st.stopped);
3068 let first_tok = decode.first_token();
3069 decode.finish();
3070 decode.add_measured(out.decode);
3071 let (content_acc, content_sent) = (st.content_acc.clone(), st.content_sent);
3072 let (tool_index, orphaned_calls) = (st.tool_index, st.orphaned_calls);
3073 drop(st);
3074 let mut c = collect(&out.events);
3075 if !stopped && !choice.forces_call() && content_sent < content_acc.len() {
3076 let rest = content_acc[content_sent..].to_string();
3077 let mut ck = Frame::chat(Message::content(rest), None);
3078 if continuous_usage {
3079 ck.usage = Some(Usage::partial(out.tokens_generated));
3080 }
3081 send(ck)?;
3082 }
3083 if choice.forces_call() {
3084 c.content = content_acc.clone();
3085 promote_forced_tool_call(&daemon, &mut c, session, tool_schemas.as_deref());
3086 if tool_index > 0 {
3087 } else if let Some((id, name, args)) = c.tool_calls.first() {
3088 send(Frame::chat(
3089 Message::complete_tool(
3090 tool_index,
3091 format!("call_{id}"),
3092 name.clone(),
3093 args.clone(),
3094 ),
3095 None,
3096 ))?;
3097 } else if !c.content.is_empty() {
3098 let mut ck = Frame::chat(Message::content(c.content), None);
3099 if continuous_usage {
3100 ck.usage = Some(Usage::partial(out.tokens_generated));
3101 }
3102 send(ck)?;
3103 }
3104 }
3105 let fin = if stopped {
3106 "stop"
3107 } else {
3108 finish_reason(
3109 out.finish,
3110 out.tokens_generated,
3111 max_tokens,
3112 !c.tool_calls.is_empty() && choice != ToolChoice::None,
3113 )
3114 };
3115 let fin = if orphaned_calls > 0 { "length" } else { fin };
3116 let mut terminal = Frame::chat(Message::default(), Some(fin));
3117 terminal.usage = Some(usage(
3118 &daemon,
3119 session,
3120 out.tokens_generated,
3121 out.warm_prefix,
3122 ));
3123 access_log(
3124 &format!("{} (stream)", req.route()),
3125 &model_name,
3126 terminal.usage.as_ref().and_then(|u| u.prompt).unwrap_or(0),
3127 out.warm_prefix,
3128 out.tokens_generated as u64,
3129 GenTiming {
3130 t0,
3131 first: first_tok,
3132 decode,
3133 },
3134 fin,
3135 );
3136 terminal.warm = Some(out.warm_prefix);
3137 terminal.spec = (out.spec.0 > 0).then_some(out.spec);
3138 terminal.session = Some(session);
3139 send(terminal)
3140 };
3141 run()
3142 });
3143
3144 Ok(Body::Stream(rx))
3145}
3146const FIM_DEFAULT_MAX_TOKENS: u32 = 128;
3147
3148#[allow(clippy::too_many_arguments)]
3149pub(crate) async fn fim_completions(
3150 daemon: Arc<Daemon>,
3151 model_name: String,
3152 req: CompletionRequest,
3153 limits: TokenLimits,
3154 sampling: SamplingOverrides,
3155 mode: u8,
3156 key: Option<crate::keypolicy::ClientKey>,
3157) -> Result<Generation, RunError> {
3158 if !daemon.fim_supported() {
3159 return Err(DaemonError::NoFimDialect.into());
3160 }
3161 let hold = key;
3162 let max_tokens = req
3163 .max_tokens
3164 .or(limits.default_max_tokens)
3165 .unwrap_or(FIM_DEFAULT_MAX_TOKENS)
3166 .max(1);
3167 let serving = daemon
3168 .fim_satellite()
3169 .unwrap_or_else(|| Arc::clone(&daemon));
3170 let model_defaults = {
3171 let d = Arc::clone(&serving);
3172 tokio::task::spawn_blocking(move || {
3173 crate::with_sync_class(Some(crate::qos::INLINE_COMPLETION), || {
3174 d.model_sampling_defaults()
3175 })
3176 })
3177 .await
3178 .unwrap_or_default()
3179 };
3180 let op = sampling.for_daemon(&serving);
3181 let mut params = params_of_for(
3182 req.temperature,
3183 req.top_p,
3184 req.top_k,
3185 req.min_p,
3186 req.seed,
3187 &model_defaults,
3188 &op,
3189 );
3190 let seed_drawn = draw_seed_if_unseeded(&mut params, req.seed);
3191 let mut extras = extras_of(
3192 req.presence_penalty,
3193 req.frequency_penalty,
3194 req.repeat_penalty,
3195 &op,
3196 &model_defaults,
3197 );
3198 extras.seed_drawn = seed_drawn;
3199 extras.ignore_eos = req.ignore_eos;
3200 let fim = crate::FimRequest {
3201 prefix: req.prompt,
3202 suffix: req.suffix.unwrap_or_default(),
3203 mode,
3204 max_tokens,
3205 params,
3206 extras,
3207 };
3208 let id = format!(
3209 "cmpl-superfluid-{}",
3210 next_id().rsplit('-').next().unwrap_or("0")
3211 );
3212 let created = unix_now();
3213 let served_by = daemon
3214 .fim_satellite_name()
3215 .unwrap_or_else(|| model_name.clone());
3216 let log_model = served_by.clone();
3217 let fin_of = move |c: &crate::Completion| {
3218 if c.expired {
3219 "length"
3220 } else {
3221 finish_reason(c.finish, c.tokens.len() as u32, max_tokens, false)
3222 }
3223 };
3224 let ext = move |c: &crate::Completion| FimMetadata {
3225 cached: c.cached,
3226 expired: c.expired,
3227 served_by: served_by.clone(),
3228 };
3229 if req.stream {
3230 let admitted_daemon = Arc::clone(&daemon);
3231 let fim = blocking(
3232 RequestQos {
3233 class: crate::qos::INLINE_COMPLETION,
3234 batch_invariant: false,
3235 },
3236 hold.clone(),
3237 move || {
3238 admitted_daemon.fim_fits(&fim)?;
3239 Ok(fim)
3240 },
3241 )
3242 .await?;
3243 let (tx, rx) = tokio::sync::mpsc::channel(64);
3244 tokio::task::spawn_blocking(move || {
3245 let _hold = hold;
3246 let t0 = std::time::Instant::now();
3247 let mut decode = DecodeSpan::default();
3248 let mut delivered = 0usize;
3249 let res = daemon.complete_streaming(&fim, |piece, produced| {
3250 decode.stamp(produced as u64);
3251 delivered = delivered.max(produced);
3252 match tx.blocking_send(Ok(Frame::text(piece, None, None))) {
3253 Ok(()) => std::ops::ControlFlow::Continue(()),
3254 Err(_) => std::ops::ControlFlow::Break(()),
3255 }
3256 });
3257 match res {
3258 Ok(c) => {
3259 let fin = fin_of(&c);
3260 let first = decode.first_token();
3261 decode.finish();
3262 access_log(
3263 "POST /v1/completions (fim)",
3264 &log_model,
3265 c.prompt_tokens as u64,
3266 fim_log_cached(&c),
3267 c.tokens.len().max(delivered) as u64,
3268 GenTiming { t0, first, decode },
3269 fin,
3270 );
3271 let mut last = Frame::text("", None, Some(fin));
3272 last.fim = Some(ext(&c));
3273 last.usage = Some(Usage::complete(
3274 c.prompt_tokens as u64,
3275 c.tokens.len() as u64,
3276 None,
3277 ));
3278 let _ = tx.blocking_send(Ok(last));
3279 }
3280 Err(e) => {
3281 let client_error = matches!(
3282 e,
3283 DaemonError::StreamTooLong { .. } | DaemonError::NoFimDialect
3284 );
3285 let _ = tx.blocking_send(Err(StreamFailure::new(e, client_error)));
3286 }
3287 }
3288 });
3289 return Ok(Generation {
3290 kind: Kind::Completion,
3291 id,
3292 model: model_name,
3293 created,
3294 warm_header: None,
3295 body: Body::Stream(rx),
3296 });
3297 }
3298 let out = tokio::task::spawn_blocking(move || {
3299 let _hold = hold;
3300 let t0 = std::time::Instant::now();
3301 let mut decode = DecodeSpan::default();
3302 let c = daemon.complete_streaming(&fim, |_, produced| {
3303 decode.stamp(produced as u64);
3304 std::ops::ControlFlow::Continue(())
3305 })?;
3306 let first = decode.first_token();
3307 decode.finish();
3308 access_log(
3309 "POST /v1/completions (fim)",
3310 &log_model,
3311 c.prompt_tokens as u64,
3312 fim_log_cached(&c),
3313 c.tokens.len() as u64,
3314 GenTiming { t0, first, decode },
3315 fin_of(&c),
3316 );
3317 Ok::<_, DaemonError>(c)
3318 })
3319 .await;
3320 let c = out.map_err(|e| RunError::Worker(e.to_string()))??;
3321 let mut frame = Frame::text(&c.text, None, Some(fin_of(&c)));
3322 frame.usage = Some(Usage::complete(
3323 c.prompt_tokens as u64,
3324 c.tokens.len() as u64,
3325 None,
3326 ));
3327 frame.fim = Some(ext(&c));
3328 Ok(Generation {
3329 kind: Kind::Completion,
3330 id,
3331 model: model_name,
3332 created,
3333 warm_header: None,
3334 body: Body::Complete(frame),
3335 })
3336}
3337
3338pub(crate) fn chat_once(
3339 daemon: &Daemon,
3340 req: &ChatRequest,
3341 policy: RequestPolicy,
3342) -> Result<Frame, DaemonError> {
3343 let RequestPolicy {
3344 limits,
3345 sampling: op,
3346 qos,
3347 } = policy;
3348 let _class = crate::SyncClassScope::enter(Some(qos.class));
3349 let model_defaults = daemon.model_sampling_defaults();
3350 let mut extras = extras_of(
3351 req.presence_penalty,
3352 req.frequency_penalty,
3353 req.repeat_penalty,
3354 &op,
3355 &model_defaults,
3356 );
3357 let choice = ToolChoice::parse(&req.tool_choice);
3358 if choice.forces_call() {
3359 extras.open_channel = crate::wal::channel::TOOL_CALL;
3360 }
3361 let tool_schemas = crate::codec::ToolSchemas::of_values(&req.tools);
3362 extras.tool_schemas = tool_schemas.clone();
3363 let handles = RequestHandles::new(
3364 daemon,
3365 grammar_handle_for(daemon, req, &choice)?,
3366 logit_bias_handle_of(daemon, &req.logit_bias),
3367 );
3368 handles.apply(&mut extras);
3369 let want_logprobs = req.logprobs.unwrap_or(false);
3370 extras.want_logprobs = want_logprobs;
3371 extras.top_logprobs = req.top_logprobs.unwrap_or(0).min(20) as u8;
3372 extras.thinking = req.thinking();
3373 extras.ignore_eos = req.ignore_eos;
3374 let stops = req.stop.list();
3375 let (session, seed_drawn) = build_session(daemon, req, &op, &model_defaults)?;
3376 extras.seed_drawn = seed_drawn;
3377 qos.apply(daemon, session)?;
3378 let max_tokens = limits.resolve(req.max_completion_tokens.or(req.max_tokens), || {
3379 prompt_len(daemon, session)
3380 });
3381 let cancels = daemon.cancel_registry();
3382 let mut acc = String::new();
3383 let mut stopped = false;
3384 let out = daemon.generate_streaming_ex(session, max_tokens, extras, |ev| {
3385 if let EventBody::Generated {
3386 text, channel: ch, ..
3387 } = &ev.body
3388 {
3389 if *ch == channel::TEXT && !stops.is_empty() && !stopped {
3390 acc.push_str(text);
3391 if stops.iter().any(|s| acc.contains(s.as_str())) {
3392 cancels.cancel(session);
3393 stopped = true;
3394 }
3395 }
3396 }
3397 Ok(())
3398 });
3399 let out = out?;
3400 let mut c = collect(&out.events);
3401 claim_named_calls(daemon, &mut c, &out.events, &choice);
3402 if choice.forces_call() {
3403 promote_forced_tool_call(daemon, &mut c, session, tool_schemas.as_deref());
3404 }
3405 let (content, hit_stop) = truncate_at_stop(&c.content, &stops);
3406 c.content = content;
3407 let usage = usage(daemon, session, out.tokens_generated, out.warm_prefix);
3408 let finish = if !c.broken_calls.is_empty() {
3409 "length"
3410 } else if hit_stop {
3411 "stop"
3412 } else {
3413 finish_reason(
3414 out.finish,
3415 out.tokens_generated,
3416 max_tokens,
3417 !c.tool_calls.is_empty() && choice != ToolChoice::None,
3418 )
3419 };
3420 Ok(Frame {
3421 choices: vec![Choice {
3422 message: message(&c, &choice),
3423 finish: Some(finish),
3424 logprobs: want_logprobs.then(|| decode_logprobs(daemon, &out.logprobs, 0)),
3425 ..Choice::default()
3426 }],
3427 usage: Some(usage),
3428 spec: (out.spec.0 > 0).then_some(out.spec),
3429 ..Frame::default()
3430 })
3431}
3432
3433pub(crate) async fn blocking<T: Send + 'static>(
3434 qos: RequestQos,
3435 hold: Option<crate::keypolicy::ClientKey>,
3436 work: impl FnOnce() -> Result<T, DaemonError> + Send + 'static,
3437) -> Result<T, RunError> {
3438 tokio::task::spawn_blocking(move || {
3439 let _hold = hold;
3440 let _class = crate::SyncClassScope::enter(Some(qos.class));
3441 work()
3442 })
3443 .await
3444 .map_err(|e| RunError::Worker(e.to_string()))?
3445 .map_err(RunError::Daemon)
3446}
3447
3448pub(crate) fn base64_decode(data: &str) -> Result<Vec<u8>, DaemonError> {
3449 use base64::Engine;
3450 let cleaned: String = data.chars().filter(|c| !c.is_whitespace()).collect();
3451 base64::engine::general_purpose::STANDARD
3452 .decode(&cleaned)
3453 .or_else(|_| base64::engine::general_purpose::STANDARD_NO_PAD.decode(&cleaned))
3454 .map_err(|_| DaemonError::Protocol("invalid base64 image data"))
3455}
3456
3457pub(crate) fn spawn_stream<T: Send + 'static>(
3458 qos: RequestQos,
3459 hold: Option<crate::keypolicy::ClientKey>,
3460 work: impl FnOnce(&tokio::sync::mpsc::Sender<Result<T, StreamFailure>>) -> Result<(), DaemonError>
3461 + Send
3462 + 'static,
3463) -> tokio::sync::mpsc::Receiver<Result<T, StreamFailure>> {
3464 let (tx, rx) = tokio::sync::mpsc::channel(64);
3465 tokio::task::spawn_blocking(move || {
3466 let _hold = hold;
3467 let _class = crate::SyncClassScope::enter(Some(qos.class));
3468 if let Err(error) = work(&tx) {
3469 let client_error = matches!(
3470 error,
3471 DaemonError::Constraint(_)
3472 | DaemonError::TemplateRefused(_)
3473 | DaemonError::Unsupported(_)
3474 | DaemonError::StreamTooLong { .. }
3475 | DaemonError::NoFimDialect
3476 );
3477 let _ = tx.blocking_send(Err(StreamFailure::new(error, client_error)));
3478 }
3479 });
3480 rx
3481}