Skip to main content

superfluid_daemon/
inference.rs

1//! Shared inference requests, session construction, sampling and execution.
2
3pub(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        // A fork inherits its parent's class, so a continued session is
49        // compared with what it holds, not with the default.
50        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    /// Optional policy for an embedding whose omitted request parameters
78    /// predate model-published defaults. None preserves superfluid's policy.
79    pub fallback: Option<SamplingFallback>,
80}
81
82/// An embedding's defaults, below request and operator values: temperature and repetition
83/// replace the model's; truncation applies only when the model publishes none.
84#[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    /// Interpret a lone frequency_penalty as multiplicative (1 + value).
92    /// An explicit presence_penalty or repeat_penalty retains additive
93    /// frequency semantics; presence_penalty alone disables fallback repeat.
94    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    /// Set by `/v1/responses` only: the request continues a stored session
148    /// instead of starting one, and `messages` are the turn appended to it.
149    #[serde(skip)]
150    pub(crate) continues: Option<Continuation>,
151    /// The route the access log names, when another dialect runs the chat
152    /// path; chat completions when unset.
153    #[serde(skip)]
154    pub(crate) route: Option<&'static str>,
155}
156
157/// A stored session forked at `at_event`, the event after its last.
158#[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    /// Whether the scheduler timed the generation: its figure then stands,
1115    /// none included, and the events are not consulted.
1116    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    /// Adds a generation's decode as the scheduler timed it, tick end to tick
1149    /// end. Events alone cannot: a tick hands over all of its tokens at once,
1150    /// and a channel change splits one tick into several events, so the rest
1151    /// of the first tick counted against the next one's time. A generation
1152    /// that ended in its first tick adds `(0, 0.0)`: it has no decode to time,
1153    /// whatever the events of that tick say.
1154    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        // Only a codec that renders each message alone renders a turn
1649        // appended to a stored conversation as it would render it fresh.
1650        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        // The stored conversation ends after a query: a turn here follows
1657        // none of this request's user messages, or comes after the last one.
1658        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    // A turn after the last user query is a step of a tool loop.
1729    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
1738/// What a per-message codec renders first: the leading system message when
1739/// it is text only, and the tools when the request may call them. Two
1740/// requests with the same head render the same first turn.
1741pub(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 unix_now() -> u64 {
1920    std::time::SystemTime::now()
1921        .duration_since(std::time::UNIX_EPOCH)
1922        .map(|d| d.as_secs())
1923        .unwrap_or(0)
1924}
1925
1926#[cfg(test)]
1927mod access_log_rate_tests {
1928    use super::*;
1929    use std::time::Duration;
1930
1931    #[test]
1932    fn prefill_counts_only_what_was_prefilled() {
1933        let cases = [
1934            (12961u64, 0u64, 10444u64, "1241"),
1935            (14481, 12800, 5716, "294"),
1936            (15627, 14900, 5057, "144"),
1937            (16045, 15900, 2138, "68"),
1938        ];
1939        for (prompt, cached, ttft_ms, want) in cases {
1940            let got = prefill_rate(prompt, cached, Some(Duration::from_millis(ttft_ms)));
1941            assert_eq!(got, want, "prompt={prompt} cached={cached}");
1942        }
1943    }
1944
1945    #[test]
1946    fn a_fully_warm_prompt_prefilled_nothing() {
1947        assert_eq!(
1948            prefill_rate(4096, 4096, Some(Duration::from_millis(80))),
1949            "-"
1950        );
1951        assert_eq!(
1952            prefill_rate(4096, 9000, Some(Duration::from_millis(80))),
1953            "-"
1954        );
1955    }
1956
1957    #[test]
1958    fn a_fim_cache_hit_logs_its_prompt_as_cached() {
1959        let c = |cached| crate::Completion {
1960            text: String::new(),
1961            tokens: vec![1; 16],
1962            cached,
1963            expired: false,
1964            finish: superfluid_abi::finish::NONE,
1965            prompt_tokens: 21,
1966        };
1967        assert_eq!(fim_log_cached(&c(true)), 21);
1968        assert_eq!(
1969            prefill_rate(
1970                21,
1971                fim_log_cached(&c(true)),
1972                Some(Duration::from_micros(60))
1973            ),
1974            "-"
1975        );
1976        assert_eq!(fim_log_cached(&c(false)), 0);
1977    }
1978
1979    #[test]
1980    fn prefill_needs_a_ttft() {
1981        assert_eq!(prefill_rate(1024, 0, None), "-");
1982        assert_eq!(prefill_rate(1024, 0, Some(Duration::ZERO)), "-");
1983    }
1984
1985    fn run(d: &mut DecodeSpan, base: std::time::Instant, stamps: &[(u64, u64)]) {
1986        for (ms, produced) in stamps {
1987            d.stamp_at(base + Duration::from_millis(*ms), *produced);
1988        }
1989        d.finish();
1990    }
1991
1992    #[test]
1993    fn decode_is_the_inter_token_rate() {
1994        let base = std::time::Instant::now();
1995        let mut d = DecodeSpan::default();
1996        run(&mut d, base, &[(0, 1), (781, 200)]);
1997        let (tokens, span) = d.measured();
1998        assert_eq!(tokens, 199);
1999        assert_eq!(decode_rate(tokens, span), "254.8");
2000    }
2001
2002    #[test]
2003    fn decode_excludes_the_whole_first_commit() {
2004        let base = std::time::Instant::now();
2005        let mut d = DecodeSpan::default();
2006        run(&mut d, base, &[(0, 5), (1000, 100)]);
2007        assert_eq!(d.measured().0, 95, "the first commit's 5 predate the span");
2008        assert_eq!(decode_rate(d.measured().0, d.measured().1), "95.0");
2009    }
2010
2011    #[test]
2012    fn decode_spans_do_not_swallow_the_gaps_between_choices() {
2013        let base = std::time::Instant::now();
2014        let mut d = DecodeSpan::default();
2015        run(&mut d, base, &[(0, 1), (500, 51)]);
2016        run(&mut d, base, &[(4500, 1), (5000, 51)]);
2017        let (tokens, span) = d.measured();
2018        assert_eq!(tokens, 100);
2019        assert_eq!(span, Some(Duration::from_millis(1000)), "gap excluded");
2020        assert_eq!(decode_rate(tokens, span), "100.0");
2021    }
2022
2023    #[test]
2024    fn decode_counts_only_what_was_delivered() {
2025        let base = std::time::Instant::now();
2026        let mut d = DecodeSpan::default();
2027        run(&mut d, base, &[(0, 1), (500, 50)]);
2028        let (tokens, span) = d.measured();
2029        assert_eq!(tokens, 49, "the undelivered tail is usage's business");
2030        assert_eq!(decode_rate(tokens, span), "98.0");
2031    }
2032
2033    #[test]
2034    fn decode_declines_to_guess() {
2035        let base = std::time::Instant::now();
2036        let mut d = DecodeSpan::default();
2037        d.finish();
2038        assert_eq!(d.measured(), (0, None));
2039        assert_eq!(decode_rate(0, None), "-");
2040
2041        let mut one = DecodeSpan::default();
2042        run(&mut one, base, &[(0, 40)]);
2043        assert_eq!(decode_rate(one.measured().0, one.measured().1), "-");
2044
2045        assert_eq!(decode_rate(200, Some(Duration::from_millis(150))), "-");
2046        assert_eq!(decode_rate(100, None), "-");
2047    }
2048
2049    #[test]
2050    fn a_tick_split_by_a_channel_change_is_not_counted_against_the_next_tick() {
2051        // Qwen3.6-35B-A3B, 128 tokens after a 4096-token prompt: the tick that
2052        // ended the prompt also decoded and committed 19 text tokens, then 79
2053        // reasoning tokens 9 ms later; the next tick added 30 in 0.79 s.
2054        let base = std::time::Instant::now();
2055        let mut d = DecodeSpan::default();
2056        run(&mut d, base, &[(0, 19), (9, 98), (801, 128)]);
2057        assert_eq!(decode_rate(d.measured().0, d.measured().1), "136.1", "events alone overstate it");
2058        d.add_measured((30, 0.792));
2059        assert_eq!(decode_rate(d.measured().0, d.measured().1), "37.9");
2060    }
2061
2062    #[test]
2063    fn a_generation_delivered_in_one_tick_has_no_decode_rate() {
2064        let base = std::time::Instant::now();
2065        let mut d = DecodeSpan::default();
2066        run(&mut d, base, &[(0, 128)]);
2067        d.add_measured((0, 0.0));
2068        assert_eq!(decode_rate(d.measured().0, d.measured().1), "-");
2069    }
2070
2071    #[test]
2072    fn a_one_tick_generation_split_by_a_channel_change_has_no_decode_rate() {
2073        // One tick's tokens committed as two events 9 ms apart: the events
2074        // alone make a rate of the gap between them.
2075        let base = std::time::Instant::now();
2076        let mut d = DecodeSpan::default();
2077        run(&mut d, base, &[(0, 19), (9, 20)]);
2078        assert_eq!(decode_rate(d.measured().0, d.measured().1), "111.1", "events alone");
2079        d.add_measured((0, 0.0));
2080        assert_eq!(d.measured(), (0, None), "the scheduler saw no tick after the first");
2081    }
2082
2083    #[test]
2084    fn measured_decode_adds_up_over_choices_and_ignores_empty_ones() {
2085        let mut d = DecodeSpan::default();
2086        d.add_measured((63, 0.5));
2087        d.add_measured((0, 0.0));
2088        d.add_measured((63, 0.5));
2089        assert_eq!(d.measured(), (126, Some(Duration::from_secs(1))));
2090        assert_eq!(decode_rate(d.measured().0, d.measured().1), "126.0");
2091    }
2092}
2093
2094#[cfg(test)]
2095mod key_order_tests {
2096    use super::ChatRequest;
2097
2098    #[test]
2099    fn a_tool_keeps_the_key_order_it_was_sent_in() {
2100        let req: ChatRequest = serde_json::from_str(
2101            r#"{"messages":[],"tools":[{"type":"function","function":{"name":"replace_text",
2102                "parameters":{"type":"object","properties":{"path":{"type":"string"},
2103                "old_text":{"type":"string"},"new_text":{"type":"string"}}}}}]}"#,
2104        )
2105        .expect("request");
2106        let t = req.tools[0].to_string();
2107        let at = |k: &str| t.find(k).unwrap_or_else(|| panic!("{k} missing: {t}"));
2108        assert!(at("\"type\"") < at("\"function\""), "{t}");
2109        assert!(
2110            at("\"path\"") < at("\"old_text\"") && at("\"old_text\"") < at("\"new_text\""),
2111            "{t}"
2112        );
2113    }
2114}
2115
2116#[cfg(test)]
2117mod system_absorption_tests {
2118    use super::*;
2119
2120    #[test]
2121    fn assistant_reasoning_is_carried_and_other_roles_are_not() {
2122        let mut m = msg("assistant", serde_json::Value::Null);
2123        m.reasoning_content = Some("  plan  ".into());
2124        assert_eq!(reasoning_of(&m, role::ASSISTANT), Some("plan".into()));
2125        m.reasoning_content = None;
2126        m.reasoning = Some("alt".into());
2127        assert_eq!(
2128            reasoning_of(&m, role::ASSISTANT),
2129            Some("alt".into()),
2130            "the other spelling"
2131        );
2132        m.reasoning = Some("   ".into());
2133        assert_eq!(
2134            reasoning_of(&m, role::ASSISTANT),
2135            None,
2136            "blank reasoning is none"
2137        );
2138        let mut u = msg("user", serde_json::json!("hi"));
2139        u.reasoning_content = Some("x".into());
2140        assert_eq!(
2141            reasoning_of(&u, role::USER),
2142            None,
2143            "only assistant turns carry reasoning"
2144        );
2145    }
2146
2147    fn msg(role: &str, content: serde_json::Value) -> ChatMessage {
2148        ChatMessage {
2149            role: role.to_string(),
2150            content: Some(content),
2151            tool_calls: Vec::new(),
2152            tool_call_id: None,
2153            name: None,
2154            reasoning_content: None,
2155            reasoning: None,
2156        }
2157    }
2158
2159    fn image_part() -> serde_json::Value {
2160        serde_json::json!({
2161            "type": "image_url",
2162            "image_url": {"url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="}
2163        })
2164    }
2165
2166    #[test]
2167    fn text_only_system_message_is_absorbed() {
2168        let m = msg("system", serde_json::json!("you are helpful"));
2169        assert_eq!(
2170            absorbable_system_text(&m).expect("split"),
2171            Some("you are helpful".to_string()),
2172            "a text-only system message folds into the scaffold"
2173        );
2174    }
2175
2176    #[test]
2177    fn multimodal_system_message_is_left_for_the_media_loop() {
2178        let m = msg(
2179            "system",
2180            serde_json::json!([{"type": "text", "text": "look at this"}, image_part()]),
2181        );
2182        let split = split_content(&m.content).expect("split");
2183        assert_eq!(split.images.len(), 1, "fixture must carry one image part");
2184
2185        assert_eq!(
2186            absorbable_system_text(&m).expect("split"),
2187            None,
2188            "a system message with media must NOT be absorbed — its images \
2189             would be dropped on the floor"
2190        );
2191    }
2192
2193    #[test]
2194    fn audio_system_message_is_left_for_the_media_loop() {
2195        let m = msg(
2196            "system",
2197            serde_json::json!([
2198                {"type": "text", "text": "listen"},
2199                {"type": "input_audio", "input_audio": {"data": "UklGRiQAAABXQVZF", "format": "wav"}}
2200            ]),
2201        );
2202        let split = split_content(&m.content).expect("split");
2203        assert_eq!(split.images.len(), 1, "fixture must carry one audio part");
2204        assert_eq!(absorbable_system_text(&m).expect("split"), None);
2205    }
2206
2207    #[test]
2208    fn non_system_roles_are_never_absorbed() {
2209        for role in ["user", "assistant", "tool", "developer"] {
2210            let m = msg(role, serde_json::json!("hello"));
2211            assert_eq!(
2212                absorbable_system_text(&m).expect("split"),
2213                None,
2214                "{role} must not be absorbed into the system scaffold"
2215            );
2216        }
2217    }
2218}
2219
2220pub(crate) enum RunError {
2221    Daemon(DaemonError),
2222    Worker(String),
2223}
2224impl From<DaemonError> for RunError {
2225    fn from(error: DaemonError) -> Self {
2226        Self::Daemon(error)
2227    }
2228}
2229
2230pub(crate) async fn completions(
2231    daemon: Arc<Daemon>,
2232    model_name: String,
2233    req: CompletionRequest,
2234    policy: RequestPolicy,
2235    hold: Option<crate::keypolicy::ClientKey>,
2236    hooks: crate::keepalive::Hooks,
2237) -> Result<Generation, RunError> {
2238    let qos = policy.qos;
2239    let max_tokens = policy
2240        .limits
2241        .resolve(req.max_tokens, || daemon.tokenize(&req.prompt).len() as u32);
2242    let id = format!(
2243        "cmpl-superfluid-{}",
2244        next_id().rsplit('-').next().unwrap_or("0")
2245    );
2246    let created = unix_now();
2247    let model_defaults = {
2248        let d = daemon.clone();
2249        tokio::task::spawn_blocking(move || {
2250            crate::with_sync_class(Some(qos.class), || d.model_sampling_defaults())
2251        })
2252        .await
2253        .unwrap_or_default()
2254    };
2255    let mut params = params_of_for(
2256        req.temperature,
2257        req.top_p,
2258        req.top_k,
2259        req.min_p,
2260        req.seed,
2261        &model_defaults,
2262        &policy.sampling.for_daemon(&daemon),
2263    );
2264    let seed_drawn = draw_seed_if_unseeded(&mut params, req.seed);
2265    let mut extras = extras_of(
2266        req.presence_penalty,
2267        req.frequency_penalty,
2268        req.repeat_penalty,
2269        &policy.sampling,
2270        &model_defaults,
2271    );
2272    extras.seed_drawn = seed_drawn;
2273    let want_logprobs = req.logprobs.is_some();
2274    extras.want_logprobs = want_logprobs;
2275    extras.top_logprobs = req.logprobs.unwrap_or(0).min(20) as u8;
2276    extras.ignore_eos = req.ignore_eos;
2277    let echo_offset = if req.echo { req.prompt.len() } else { 0 };
2278
2279    if req.stream {
2280        let body = completion_stream(daemon, req, max_tokens, params, extras, qos, hold).await?;
2281        return Ok(Generation {
2282            kind: Kind::Completion,
2283            id,
2284            model: model_name,
2285            created,
2286            warm_header: None,
2287            body,
2288        });
2289    }
2290
2291    let daemon_lp = Arc::clone(&daemon);
2292    let model_log = model_name.clone();
2293    let out = blocking(qos, hold, move || {
2294        let mut hooks = hooks;
2295        let t0 = std::time::Instant::now();
2296        let mut decode = DecodeSpan::default();
2297        let mut produced = 0u64;
2298        let session = daemon.create(None, params)?;
2299        qos.apply(&daemon, session)?;
2300        daemon.append(session, Some(req.prompt.clone()), Vec::new())?;
2301        if hooks.is_on() {
2302            daemon.session_fits(session, None)?;
2303        }
2304        hooks.admitted(&daemon, session);
2305        hooks.check()?;
2306        let out = daemon.generate_streaming_ex(session, max_tokens, extras, |ev| {
2307            hooks.cancel_if_gone();
2308            if let EventBody::Generated { span, .. } = &ev.body {
2309                if !span.is_empty() {
2310                    produced += span.len() as u64;
2311                    decode.stamp(produced);
2312                }
2313            }
2314            Ok(())
2315        })?;
2316        let first_tok = decode.first_token();
2317        decode.finish();
2318        decode.add_measured(out.decode);
2319        let c = collect(&out.events);
2320        let usage = usage(&daemon, session, out.tokens_generated, out.warm_prefix);
2321        access_log(
2322            "POST /v1/completions",
2323            &model_log,
2324            usage.prompt.unwrap_or(0),
2325            out.warm_prefix,
2326            out.tokens_generated as u64,
2327            GenTiming {
2328                t0,
2329                first: first_tok,
2330                decode,
2331            },
2332            finish_reason(out.finish, out.tokens_generated, max_tokens, false),
2333        );
2334        let mut text = String::new();
2335        if req.echo {
2336            text.push_str(&req.prompt);
2337        }
2338        text.push_str(&c.content);
2339        text.push_str(&c.reasoning);
2340        text.push_str(&c.unparsed_tool);
2341        Ok::<_, DaemonError>((out, text, usage))
2342    })
2343    .await;
2344    let (out, text, usage) = out?;
2345    let logprobs = want_logprobs.then(|| decode_logprobs(&daemon_lp, &out.logprobs, echo_offset));
2346    let mut frame = Frame::text(
2347        text,
2348        logprobs,
2349        Some(finish_reason(
2350            out.finish,
2351            out.tokens_generated,
2352            max_tokens,
2353            false,
2354        )),
2355    );
2356    frame.usage = Some(usage);
2357    Ok(Generation {
2358        kind: Kind::Completion,
2359        id,
2360        model: model_name,
2361        created,
2362        warm_header: None,
2363        body: Body::Complete(frame),
2364    })
2365}
2366
2367#[allow(clippy::too_many_arguments)]
2368async fn completion_stream(
2369    daemon: Arc<Daemon>,
2370    req: CompletionRequest,
2371    max_tokens: u32,
2372    params: GenParams,
2373    extras: crate::scheduler::GenExtras,
2374    qos: RequestQos,
2375    hold: Option<crate::keypolicy::ClientKey>,
2376) -> Result<Body, RunError> {
2377    let want_logprobs = req.logprobs.is_some();
2378    let admitted_daemon = Arc::clone(&daemon);
2379    let (req, session) = blocking(qos, hold.clone(), move || {
2380        let session = admitted_daemon.create(None, params)?;
2381        qos.apply(&admitted_daemon, session)?;
2382        admitted_daemon.append(session, Some(req.prompt.clone()), Vec::new())?;
2383        admitted_daemon.session_fits(session, None)?;
2384        Ok((req, session))
2385    })
2386    .await?;
2387    let rx = spawn_stream(qos, hold, move |tx| {
2388        let _closed = crate::keepalive::cancel_when_closed(&daemon, session, tx);
2389        let send = |v: Frame| {
2390            tx.blocking_send(Ok(v))
2391                .map_err(|_| DaemonError::Protocol("generation consumer gone"))
2392        };
2393        let lp_q: std::rc::Rc<
2394            std::cell::RefCell<std::collections::VecDeque<crate::scheduler::TokenLogprob>>,
2395        > = std::rc::Rc::new(std::cell::RefCell::new(std::collections::VecDeque::new()));
2396        let lp_fill = std::rc::Rc::clone(&lp_q);
2397        let mut offset: usize = 0;
2398        let run = || -> Result<(), DaemonError> {
2399            if req.echo {
2400                offset = req.prompt.len();
2401                send(Frame::text(&req.prompt, None, None))?;
2402            }
2403            let out = daemon.generate_streaming_ex_lp(
2404                session,
2405                max_tokens,
2406                extras,
2407                |ev| {
2408                    if let EventBody::Generated {
2409                        text,
2410                        channel: ch,
2411                        span,
2412                        ..
2413                    } = &ev.body
2414                    {
2415                        let these: Vec<crate::scheduler::TokenLogprob> = if want_logprobs {
2416                            let mut q = lp_q.borrow_mut();
2417                            (0..span.len()).filter_map(|_| q.pop_front()).collect()
2418                        } else {
2419                            Vec::new()
2420                        };
2421                        if !text.is_empty() && *ch != channel::TOOL_CALL {
2422                            let lp = if want_logprobs {
2423                                let v = Some(decode_logprobs(&daemon, &these, offset));
2424                                offset += text.len();
2425                                v
2426                            } else {
2427                                None
2428                            };
2429                            return send(Frame::text(text, lp, None));
2430                        }
2431                    }
2432                    Ok(())
2433                },
2434                |items| {
2435                    lp_fill.borrow_mut().extend(items.iter().cloned());
2436                },
2437            )?;
2438            let fin = finish_reason(out.finish, out.tokens_generated, max_tokens, false);
2439            let mut terminal = Frame::text("", None, Some(fin));
2440            terminal.usage = Some(usage(
2441                &daemon,
2442                session,
2443                out.tokens_generated,
2444                out.warm_prefix,
2445            ));
2446            send(terminal)
2447        };
2448        run()
2449    });
2450    Ok(Body::Stream(rx))
2451}
2452pub(crate) async fn chat(
2453    daemon: Arc<Daemon>,
2454    model_name: String,
2455    req: ChatRequest,
2456    policy: RequestPolicy,
2457    hold: Option<crate::keypolicy::ClientKey>,
2458    hooks: crate::keepalive::Hooks,
2459) -> Result<Generation, RunError> {
2460    let qos = policy.qos;
2461    let explicit_max = req.max_completion_tokens.or(req.max_tokens);
2462    let id = next_id();
2463    let created = unix_now();
2464
2465    if req.stream {
2466        let body = chat_stream(daemon, model_name.clone(), req, policy, explicit_max, hold).await?;
2467        return Ok(Generation {
2468            kind: Kind::Chat,
2469            id,
2470            model: model_name,
2471            created,
2472            warm_header: None,
2473            body,
2474        });
2475    }
2476
2477    let n = req.n.unwrap_or(1).max(1);
2478    let route = req.route();
2479    let stops = req.stop.list();
2480    let model_log = model_name.clone();
2481    let out = blocking(qos, hold, move || {
2482        let mut hooks = hooks;
2483        let t0 = std::time::Instant::now();
2484        let mut decode = DecodeSpan::default();
2485        let mut ttft_first: Option<std::time::Instant> = None;
2486        let mut last_finish = "-";
2487        let model_defaults = daemon.model_sampling_defaults();
2488        let mut extras = extras_of(
2489            req.presence_penalty,
2490            req.frequency_penalty,
2491            req.repeat_penalty,
2492            &policy.sampling,
2493            &model_defaults,
2494        );
2495        let choice = ToolChoice::parse(&req.tool_choice);
2496        if choice.forces_call() {
2497            extras.open_channel = crate::wal::channel::TOOL_CALL;
2498        }
2499        let tool_schemas = crate::codec::ToolSchemas::of_values(&req.tools);
2500        extras.tool_schemas = tool_schemas.clone();
2501        let handles = RequestHandles::new(
2502            &daemon,
2503            grammar_handle_for(&daemon, &req, &choice)?,
2504            logit_bias_handle_of(&daemon, &req.logit_bias),
2505        );
2506        handles.apply(&mut extras);
2507        let want_logprobs = req.logprobs.unwrap_or(false);
2508        extras.want_logprobs = want_logprobs;
2509        extras.top_logprobs = req.top_logprobs.unwrap_or(0).min(20) as u8;
2510        extras.thinking = req.thinking();
2511        extras.ignore_eos = req.ignore_eos;
2512        let cancels = daemon.cancel_registry();
2513        let RequestPolicy {
2514            limits,
2515            sampling: op,
2516            qos,
2517        } = policy;
2518        let mut choices = Vec::with_capacity(n as usize);
2519        let mut completion_tokens = 0u64;
2520        let mut prompt_tokens = 0u64;
2521        let mut warm = 0u64;
2522        let mut warm_first: Option<u64> = None;
2523        let mut spec = (0u64, 0u64);
2524        let mut last_session = None;
2525        for i in 0..n {
2526            if i > 0 && hooks.gone() {
2527                break;
2528            }
2529            let (session, seed_drawn) = build_session(&daemon, &req, &op, &model_defaults)?;
2530            last_session = Some(session);
2531            let mut extras = extras.clone();
2532            extras.seed_drawn = seed_drawn;
2533            qos.apply(&daemon, session)?;
2534            if hooks.is_on() {
2535                daemon.session_fits(session, req.thinking())?;
2536            }
2537            hooks.admitted(&daemon, session);
2538            hooks.check()?;
2539            let max_tokens = limits.resolve(explicit_max, || prompt_len(&daemon, session));
2540            let mut acc = String::new();
2541            let mut produced = 0u64;
2542            let stops = stops.clone();
2543            let cancels = std::sync::Arc::clone(&cancels);
2544            let mut stopped = false;
2545            let out = daemon.generate_streaming_ex(session, max_tokens, extras, |ev| {
2546                hooks.cancel_if_gone();
2547                if let EventBody::Generated {
2548                    text,
2549                    channel: ch,
2550                    span,
2551                    ..
2552                } = &ev.body
2553                {
2554                    if !span.is_empty() {
2555                        produced += span.len() as u64;
2556                        decode.stamp(produced);
2557                    }
2558                    if *ch == channel::TEXT && !stops.is_empty() && !stopped {
2559                        acc.push_str(text);
2560                        if stops.iter().any(|s| acc.contains(s.as_str())) {
2561                            cancels.cancel(session);
2562                            stopped = true;
2563                        }
2564                    }
2565                }
2566                Ok(())
2567            })?;
2568            ttft_first = ttft_first.or_else(|| decode.first_token());
2569            decode.finish();
2570            decode.add_measured(out.decode);
2571            let mut c = collect(&out.events);
2572            claim_named_calls(&daemon, &mut c, &out.events, &choice);
2573            if choice.forces_call() {
2574                promote_forced_tool_call(&daemon, &mut c, session, tool_schemas.as_deref());
2575            }
2576            let (content, hit_stop) = truncate_at_stop(&c.content, &stops);
2577            c.content = content;
2578            let usage = usage(&daemon, session, out.tokens_generated, out.warm_prefix);
2579            completion_tokens += usage.completion;
2580            spec.0 += out.spec.0;
2581            spec.1 += out.spec.1;
2582            prompt_tokens = usage.prompt.unwrap_or(prompt_tokens);
2583            warm = warm.max(out.warm_prefix);
2584            warm_first.get_or_insert(out.warm_prefix);
2585            let finish = if !c.broken_calls.is_empty() {
2586                "length"
2587            } else if hit_stop {
2588                "stop"
2589            } else {
2590                finish_reason(
2591                    out.finish,
2592                    out.tokens_generated,
2593                    max_tokens,
2594                    !c.tool_calls.is_empty() && choice != ToolChoice::None,
2595                )
2596            };
2597            choices.push(Choice {
2598                index: i,
2599                message: message(&c, &choice),
2600                finish: Some(finish),
2601                logprobs: want_logprobs.then(|| decode_logprobs(&daemon, &out.logprobs, 0)),
2602                ..Choice::default()
2603            });
2604            last_finish = finish;
2605        }
2606        access_log(
2607            route,
2608            &model_log,
2609            prompt_tokens,
2610            warm_first.unwrap_or(warm),
2611            completion_tokens,
2612            GenTiming {
2613                t0,
2614                first: ttft_first,
2615                decode,
2616            },
2617            last_finish,
2618        );
2619        Ok::<_, DaemonError>((choices, prompt_tokens, completion_tokens, warm, spec, last_session))
2620    })
2621    .await;
2622    let (choices, prompt_tokens, completion_tokens, warm, spec, session) = out?;
2623    let frame = Frame {
2624        choices,
2625        usage: Some(Usage::complete(
2626            prompt_tokens,
2627            completion_tokens,
2628            Some(warm.min(prompt_tokens)),
2629        )),
2630        spec: (spec.0 > 0).then_some(spec),
2631        session,
2632        ..Frame::default()
2633    };
2634    Ok(Generation {
2635        kind: Kind::Chat,
2636        id,
2637        model: model_name,
2638        created,
2639        warm_header: Some(warm),
2640        body: Body::Complete(frame),
2641    })
2642}
2643
2644async fn chat_stream(
2645    daemon: Arc<Daemon>,
2646    model_name: String,
2647    req: ChatRequest,
2648    policy: RequestPolicy,
2649    explicit_max: Option<u32>,
2650    hold: Option<crate::keypolicy::ClientKey>,
2651) -> Result<Body, RunError> {
2652    let RequestPolicy {
2653        limits,
2654        sampling: op,
2655        qos,
2656    } = policy;
2657    let t0 = std::time::Instant::now();
2658    let admitted_daemon = Arc::clone(&daemon);
2659    let (req, session, seed_drawn, model_defaults) = blocking(qos, hold.clone(), move || {
2660        let model_defaults = admitted_daemon.model_sampling_defaults();
2661        let (session, seed_drawn) = build_session(&admitted_daemon, &req, &op, &model_defaults)?;
2662        qos.apply(&admitted_daemon, session)?;
2663        admitted_daemon.session_fits(session, req.thinking())?;
2664        Ok((req, session, seed_drawn, model_defaults))
2665    })
2666    .await?;
2667    let rx = spawn_stream(qos, hold, move |tx| {
2668        let _closed = crate::keepalive::cancel_when_closed(&daemon, session, tx);
2669        let send = |v: Frame| -> Result<(), DaemonError> {
2670            tx.blocking_send(Ok(v))
2671                .map_err(|_| DaemonError::Protocol("generation consumer gone"))
2672        };
2673        let run = || -> Result<(), DaemonError> {
2674            send(Frame::chat(
2675                Message {
2676                    assistant: true,
2677                    ..Message::default()
2678                },
2679                None,
2680            ))?;
2681            let mut tool_index = 0usize;
2682            let mut orphaned_calls = 0usize;
2683            let mut extras = extras_of(
2684                req.presence_penalty,
2685                req.frequency_penalty,
2686                req.repeat_penalty,
2687                &op,
2688                &model_defaults,
2689            );
2690            extras.seed_drawn = seed_drawn;
2691            let choice = ToolChoice::parse(&req.tool_choice);
2692            if choice.forces_call() {
2693                extras.open_channel = crate::wal::channel::TOOL_CALL;
2694            }
2695            let tool_schemas = crate::codec::ToolSchemas::of_values(&req.tools);
2696            extras.tool_schemas = tool_schemas.clone();
2697            let mut frag =
2698                crate::tool_fragment::ToolCallStream::new(if choice == ToolChoice::None {
2699                    crate::tool_fragment::ToolFragmentMode::None
2700                } else {
2701                    daemon.tool_fragment_mode()
2702                })
2703                .with_schemas(tool_schemas.clone());
2704            let mut open_call_id = String::new();
2705            let handles = RequestHandles::new(
2706                &daemon,
2707                grammar_handle_for(&daemon, &req, &choice)?,
2708                logit_bias_handle_of(&daemon, &req.logit_bias),
2709            );
2710            handles.apply(&mut extras);
2711            let want_logprobs = req.logprobs.unwrap_or(false);
2712            extras.want_logprobs = want_logprobs;
2713            extras.top_logprobs = req.top_logprobs.unwrap_or(0).min(20) as u8;
2714            extras.thinking = req.thinking();
2715            extras.ignore_eos = req.ignore_eos;
2716            let lp_q: std::rc::Rc<
2717                std::cell::RefCell<std::collections::VecDeque<crate::scheduler::TokenLogprob>>,
2718            > = std::rc::Rc::new(std::cell::RefCell::new(std::collections::VecDeque::new()));
2719            let lp_fill = std::rc::Rc::clone(&lp_q);
2720            let max_tokens = limits.resolve(explicit_max, || prompt_len(&daemon, session));
2721            let stops = req.stop.list();
2722            let cancels = daemon.cancel_registry();
2723            let delta_mode = !want_logprobs;
2724            let continuous_usage = req
2725                .stream_options
2726                .as_ref()
2727                .is_some_and(|o| o.continuous_usage_stats);
2728            struct StreamText {
2729                usage_now: Option<u32>,
2730                committed: u32,
2731                decode: DecodeSpan,
2732                content_acc: String,
2733                content_sent: usize,
2734                stopped: bool,
2735                dead: bool,
2736            }
2737            let st = std::rc::Rc::new(std::cell::RefCell::new(StreamText {
2738                usage_now: None,
2739                committed: 0,
2740                decode: DecodeSpan::default(),
2741                content_acc: String::new(),
2742                content_sent: 0,
2743                stopped: false,
2744                dead: false,
2745            }));
2746            let push_text =
2747                |s: &mut StreamText, text: &str, lp: Option<Logprobs>| -> Result<(), DaemonError> {
2748                    if s.stopped {
2749                        return Ok(());
2750                    }
2751                    if choice.forces_call() {
2752                        s.content_acc.push_str(text);
2753                        return Ok(());
2754                    }
2755                    let start = s.content_sent;
2756                    s.content_acc.push_str(text);
2757                    let tail = &s.content_acc[start..];
2758                    let mut cut: Option<usize> = None;
2759                    for stop in &stops {
2760                        if let Some(i) = tail.find(stop.as_str()) {
2761                            cut = Some(cut.map_or(i, |c| c.min(i)));
2762                        }
2763                    }
2764                    let hit = cut.is_some();
2765                    let end = match cut {
2766                        Some(i) => start + i,
2767                        None => start + tail.len() - stop_prefix_holdback(tail, &stops),
2768                    };
2769                    let piece = s.content_acc.get(start..end).unwrap_or("").to_string();
2770                    s.content_sent = s.content_sent.max(end);
2771                    if !piece.is_empty() {
2772                        let mut ck = Frame::chat(Message::content(piece), None);
2773                        if let Some(lp) = lp {
2774                            ck.choices[0].logprobs = Some(lp);
2775                        }
2776                        if let Some(n) = s.usage_now {
2777                            ck.usage = Some(Usage::partial(n));
2778                        }
2779                        send(ck)?;
2780                    }
2781                    if hit {
2782                        cancels.cancel(session);
2783                        s.stopped = true;
2784                    }
2785                    Ok(())
2786                };
2787            let st_delta = std::rc::Rc::clone(&st);
2788            let on_delta = |ch: u32, text: &str, produced: u32| {
2789                if !delta_mode || text.is_empty() {
2790                    return;
2791                }
2792                let mut s = st_delta.borrow_mut();
2793                if continuous_usage {
2794                    s.usage_now = Some(produced);
2795                }
2796                if s.dead {
2797                    return;
2798                }
2799                s.decode.stamp(produced as u64);
2800                let r = if ch == channel::REASONING {
2801                    let mut ck = Frame::chat(Message::reasoning(text), None);
2802                    if let Some(n) = s.usage_now {
2803                        ck.usage = Some(Usage::partial(n));
2804                    }
2805                    send(ck)
2806                } else {
2807                    push_text(&mut s, text, None)
2808                };
2809                if r.is_err() {
2810                    cancels.cancel(session);
2811                    s.dead = true;
2812                }
2813            };
2814            let st_ev = std::rc::Rc::clone(&st);
2815            let on_event = |ev: &crate::CommittedEvent| {
2816                match &ev.body {
2817                    EventBody::Generated {
2818                        text,
2819                        channel: ch,
2820                        span,
2821                        ..
2822                    } => {
2823                        let mut s = st_ev.borrow_mut();
2824                        let covered_by_delta = delta_mode
2825                            && (*ch == channel::TEXT || *ch == channel::REASONING)
2826                            && !text.is_empty();
2827                        s.committed += span.len() as u32;
2828                        let committed = s.committed as u64;
2829                        if !covered_by_delta && !span.is_empty() {
2830                            s.decode.stamp(committed);
2831                        }
2832                        if continuous_usage && !delta_mode {
2833                            s.usage_now = Some(s.committed);
2834                        }
2835                        let these: Vec<crate::scheduler::TokenLogprob> = if want_logprobs {
2836                            let mut q = lp_q.borrow_mut();
2837                            (0..span.len()).filter_map(|_| q.pop_front()).collect()
2838                        } else {
2839                            Vec::new()
2840                        };
2841                        if *ch == channel::TOOL_CALL {
2842                            for d in frag.feed(text) {
2843                                if d.opening {
2844                                    open_call_id = format!("call_{}", ev.event_id);
2845                                }
2846                                let json = Message::tool(&d, tool_index, &open_call_id);
2847                                send(Frame::chat(json, None))?;
2848                            }
2849                            return Ok(());
2850                        }
2851                        if text.is_empty() {
2852                            return Ok(());
2853                        }
2854                        if delta_mode {
2855                            return Ok(());
2856                        }
2857                        if *ch == channel::REASONING {
2858                            let mut ck = Frame::chat(Message::reasoning(text), None);
2859                            if let Some(n) = s.usage_now {
2860                                ck.usage = Some(Usage::partial(n));
2861                            }
2862                            return send(ck);
2863                        }
2864                        let lp = want_logprobs.then(|| decode_logprobs(&daemon, &these, 0));
2865                        push_text(&mut s, text, lp)
2866                    }
2867                    EventBody::ToolParseFailure { raw } => {
2868                        if choice != ToolChoice::None && !frag.opened() {
2869                            if let Some((name, arguments)) = daemon.named_tool_call(raw) {
2870                                let delta = Message::complete_tool(
2871                                    tool_index,
2872                                    format!("call_{}", ev.event_id),
2873                                    name,
2874                                    arguments,
2875                                );
2876                                tool_index += 1;
2877                                orphaned_calls += 1;
2878                                return send(Frame::chat(delta, None));
2879                            }
2880                        }
2881                        if frag.opened() {
2882                            orphaned_calls += 1;
2883                            tool_index += 1;
2884                        }
2885                        frag.reset();
2886                        let mut ck = Frame::chat(Message::content(raw), None);
2887                        if continuous_usage {
2888                            ck.usage = Some(Usage::partial(st_ev.borrow().committed));
2889                        }
2890                        send(ck)
2891                    }
2892                    EventBody::ToolUse { name, arguments } => {
2893                        if choice == ToolChoice::None {
2894                            let raw = format!(
2895                                "{{\"name\":{},\"arguments\":{}}}",
2896                                serde_json::Value::String(name.clone()),
2897                                arguments
2898                            );
2899                            let mut ck = Frame::chat(Message::content(raw), None);
2900                            if continuous_usage {
2901                                ck.usage = Some(Usage::partial(st_ev.borrow().committed));
2902                            }
2903                            return send(ck);
2904                        }
2905                        if frag.opened() {
2906                            for d in frag.finish(arguments) {
2907                                send(Frame::chat(
2908                                    Message::tool(&d, tool_index, &open_call_id),
2909                                    None,
2910                                ))?;
2911                            }
2912                            frag.reset();
2913                            tool_index += 1;
2914                            return Ok(());
2915                        }
2916                        let delta = Message::complete_tool(
2917                            tool_index,
2918                            format!("call_{}", ev.event_id),
2919                            name.clone(),
2920                            arguments.clone(),
2921                        );
2922                        tool_index += 1;
2923                        send(Frame::chat(delta, None))
2924                    }
2925                    _ => Ok(()),
2926                }
2927            };
2928            let on_logprobs = |items: &[crate::scheduler::TokenLogprob]| {
2929                lp_fill.borrow_mut().extend(items.iter().cloned());
2930            };
2931            let out = if delta_mode {
2932                daemon.generate_streaming_delta(
2933                    session,
2934                    max_tokens,
2935                    extras,
2936                    on_event,
2937                    on_logprobs,
2938                    on_delta,
2939                )?
2940            } else {
2941                daemon.generate_streaming_ex_lp(
2942                    session,
2943                    max_tokens,
2944                    extras,
2945                    on_event,
2946                    on_logprobs,
2947                )?
2948            };
2949            let st = st.borrow();
2950            let (mut decode, stopped) = (st.decode, st.stopped);
2951            let first_tok = decode.first_token();
2952            decode.finish();
2953            decode.add_measured(out.decode);
2954            let (content_acc, content_sent) = (st.content_acc.clone(), st.content_sent);
2955            drop(st);
2956            let mut c = collect(&out.events);
2957            if !stopped && !choice.forces_call() && content_sent < content_acc.len() {
2958                let rest = content_acc[content_sent..].to_string();
2959                let mut ck = Frame::chat(Message::content(rest), None);
2960                if continuous_usage {
2961                    ck.usage = Some(Usage::partial(out.tokens_generated));
2962                }
2963                send(ck)?;
2964            }
2965            if choice.forces_call() {
2966                c.content = content_acc.clone();
2967                promote_forced_tool_call(&daemon, &mut c, session, tool_schemas.as_deref());
2968                if tool_index > 0 {
2969                } else if let Some((id, name, args)) = c.tool_calls.first() {
2970                    send(Frame::chat(
2971                        Message::complete_tool(
2972                            tool_index,
2973                            format!("call_{id}"),
2974                            name.clone(),
2975                            args.clone(),
2976                        ),
2977                        None,
2978                    ))?;
2979                } else if !c.content.is_empty() {
2980                    let mut ck = Frame::chat(Message::content(c.content), None);
2981                    if continuous_usage {
2982                        ck.usage = Some(Usage::partial(out.tokens_generated));
2983                    }
2984                    send(ck)?;
2985                }
2986            }
2987            let fin = if stopped {
2988                "stop"
2989            } else {
2990                finish_reason(
2991                    out.finish,
2992                    out.tokens_generated,
2993                    max_tokens,
2994                    !c.tool_calls.is_empty() && choice != ToolChoice::None,
2995                )
2996            };
2997            let fin = if orphaned_calls > 0 { "length" } else { fin };
2998            let mut terminal = Frame::chat(Message::default(), Some(fin));
2999            terminal.usage = Some(usage(
3000                &daemon,
3001                session,
3002                out.tokens_generated,
3003                out.warm_prefix,
3004            ));
3005            access_log(
3006                &format!("{} (stream)", req.route()),
3007                &model_name,
3008                terminal.usage.as_ref().and_then(|u| u.prompt).unwrap_or(0),
3009                out.warm_prefix,
3010                out.tokens_generated as u64,
3011                GenTiming {
3012                    t0,
3013                    first: first_tok,
3014                    decode,
3015                },
3016                fin,
3017            );
3018            terminal.warm = Some(out.warm_prefix);
3019            terminal.spec = (out.spec.0 > 0).then_some(out.spec);
3020            terminal.session = Some(session);
3021            send(terminal)
3022        };
3023        run()
3024    });
3025
3026    Ok(Body::Stream(rx))
3027}
3028const FIM_DEFAULT_MAX_TOKENS: u32 = 128;
3029
3030#[allow(clippy::too_many_arguments)]
3031pub(crate) async fn fim_completions(
3032    daemon: Arc<Daemon>,
3033    model_name: String,
3034    req: CompletionRequest,
3035    limits: TokenLimits,
3036    sampling: SamplingOverrides,
3037    mode: u8,
3038    key: Option<crate::keypolicy::ClientKey>,
3039) -> Result<Generation, RunError> {
3040    if !daemon.fim_supported() {
3041        return Err(DaemonError::NoFimDialect.into());
3042    }
3043    let hold = key;
3044    let max_tokens = req
3045        .max_tokens
3046        .or(limits.default_max_tokens)
3047        .unwrap_or(FIM_DEFAULT_MAX_TOKENS)
3048        .max(1);
3049    let serving = daemon
3050        .fim_satellite()
3051        .unwrap_or_else(|| Arc::clone(&daemon));
3052    let model_defaults = {
3053        let d = Arc::clone(&serving);
3054        tokio::task::spawn_blocking(move || {
3055            crate::with_sync_class(Some(crate::qos::INLINE_COMPLETION), || {
3056                d.model_sampling_defaults()
3057            })
3058        })
3059        .await
3060        .unwrap_or_default()
3061    };
3062    let op = sampling.for_daemon(&serving);
3063    let mut params = params_of_for(
3064        req.temperature,
3065        req.top_p,
3066        req.top_k,
3067        req.min_p,
3068        req.seed,
3069        &model_defaults,
3070        &op,
3071    );
3072    let seed_drawn = draw_seed_if_unseeded(&mut params, req.seed);
3073    let mut extras = extras_of(
3074        req.presence_penalty,
3075        req.frequency_penalty,
3076        req.repeat_penalty,
3077        &op,
3078        &model_defaults,
3079    );
3080    extras.seed_drawn = seed_drawn;
3081    extras.ignore_eos = req.ignore_eos;
3082    let fim = crate::FimRequest {
3083        prefix: req.prompt,
3084        suffix: req.suffix.unwrap_or_default(),
3085        mode,
3086        max_tokens,
3087        params,
3088        extras,
3089    };
3090    let id = format!(
3091        "cmpl-superfluid-{}",
3092        next_id().rsplit('-').next().unwrap_or("0")
3093    );
3094    let created = unix_now();
3095    let served_by = daemon
3096        .fim_satellite_name()
3097        .unwrap_or_else(|| model_name.clone());
3098    let log_model = served_by.clone();
3099    let fin_of = move |c: &crate::Completion| {
3100        if c.expired {
3101            "length"
3102        } else {
3103            finish_reason(c.finish, c.tokens.len() as u32, max_tokens, false)
3104        }
3105    };
3106    let ext = move |c: &crate::Completion| FimMetadata {
3107        cached: c.cached,
3108        expired: c.expired,
3109        served_by: served_by.clone(),
3110    };
3111    if req.stream {
3112        let admitted_daemon = Arc::clone(&daemon);
3113        let fim = blocking(
3114            RequestQos {
3115                class: crate::qos::INLINE_COMPLETION,
3116                batch_invariant: false,
3117            },
3118            hold.clone(),
3119            move || {
3120                admitted_daemon.fim_fits(&fim)?;
3121                Ok(fim)
3122            },
3123        )
3124        .await?;
3125        let (tx, rx) = tokio::sync::mpsc::channel(64);
3126        tokio::task::spawn_blocking(move || {
3127            let _hold = hold;
3128            let t0 = std::time::Instant::now();
3129            let mut decode = DecodeSpan::default();
3130            let mut delivered = 0usize;
3131            let res = daemon.complete_streaming(&fim, |piece, produced| {
3132                decode.stamp(produced as u64);
3133                delivered = delivered.max(produced);
3134                match tx.blocking_send(Ok(Frame::text(piece, None, None))) {
3135                    Ok(()) => std::ops::ControlFlow::Continue(()),
3136                    Err(_) => std::ops::ControlFlow::Break(()),
3137                }
3138            });
3139            match res {
3140                Ok(c) => {
3141                    let fin = fin_of(&c);
3142                    let first = decode.first_token();
3143                    decode.finish();
3144                    access_log(
3145                        "POST /v1/completions (fim)",
3146                        &log_model,
3147                        c.prompt_tokens as u64,
3148                        fim_log_cached(&c),
3149                        c.tokens.len().max(delivered) as u64,
3150                        GenTiming { t0, first, decode },
3151                        fin,
3152                    );
3153                    let mut last = Frame::text("", None, Some(fin));
3154                    last.fim = Some(ext(&c));
3155                    last.usage = Some(Usage::complete(
3156                        c.prompt_tokens as u64,
3157                        c.tokens.len() as u64,
3158                        None,
3159                    ));
3160                    let _ = tx.blocking_send(Ok(last));
3161                }
3162                Err(e) => {
3163                    let client_error = matches!(
3164                        e,
3165                        DaemonError::StreamTooLong { .. } | DaemonError::NoFimDialect
3166                    );
3167                    let _ = tx.blocking_send(Err(StreamFailure::new(e, client_error)));
3168                }
3169            }
3170        });
3171        return Ok(Generation {
3172            kind: Kind::Completion,
3173            id,
3174            model: model_name,
3175            created,
3176            warm_header: None,
3177            body: Body::Stream(rx),
3178        });
3179    }
3180    let out = tokio::task::spawn_blocking(move || {
3181        let _hold = hold;
3182        let t0 = std::time::Instant::now();
3183        let mut decode = DecodeSpan::default();
3184        let c = daemon.complete_streaming(&fim, |_, produced| {
3185            decode.stamp(produced as u64);
3186            std::ops::ControlFlow::Continue(())
3187        })?;
3188        let first = decode.first_token();
3189        decode.finish();
3190        access_log(
3191            "POST /v1/completions (fim)",
3192            &log_model,
3193            c.prompt_tokens as u64,
3194            fim_log_cached(&c),
3195            c.tokens.len() as u64,
3196            GenTiming { t0, first, decode },
3197            fin_of(&c),
3198        );
3199        Ok::<_, DaemonError>(c)
3200    })
3201    .await;
3202    let c = out.map_err(|e| RunError::Worker(e.to_string()))??;
3203    let mut frame = Frame::text(&c.text, None, Some(fin_of(&c)));
3204    frame.usage = Some(Usage::complete(
3205        c.prompt_tokens as u64,
3206        c.tokens.len() as u64,
3207        None,
3208    ));
3209    frame.fim = Some(ext(&c));
3210    Ok(Generation {
3211        kind: Kind::Completion,
3212        id,
3213        model: model_name,
3214        created,
3215        warm_header: None,
3216        body: Body::Complete(frame),
3217    })
3218}
3219
3220pub(crate) fn chat_once(
3221    daemon: &Daemon,
3222    req: &ChatRequest,
3223    policy: RequestPolicy,
3224) -> Result<Frame, DaemonError> {
3225    let RequestPolicy {
3226        limits,
3227        sampling: op,
3228        qos,
3229    } = policy;
3230    let _class = crate::SyncClassScope::enter(Some(qos.class));
3231    let model_defaults = daemon.model_sampling_defaults();
3232    let mut extras = extras_of(
3233        req.presence_penalty,
3234        req.frequency_penalty,
3235        req.repeat_penalty,
3236        &op,
3237        &model_defaults,
3238    );
3239    let choice = ToolChoice::parse(&req.tool_choice);
3240    if choice.forces_call() {
3241        extras.open_channel = crate::wal::channel::TOOL_CALL;
3242    }
3243    let tool_schemas = crate::codec::ToolSchemas::of_values(&req.tools);
3244    extras.tool_schemas = tool_schemas.clone();
3245    let handles = RequestHandles::new(
3246        daemon,
3247        grammar_handle_for(daemon, req, &choice)?,
3248        logit_bias_handle_of(daemon, &req.logit_bias),
3249    );
3250    handles.apply(&mut extras);
3251    let want_logprobs = req.logprobs.unwrap_or(false);
3252    extras.want_logprobs = want_logprobs;
3253    extras.top_logprobs = req.top_logprobs.unwrap_or(0).min(20) as u8;
3254    extras.thinking = req.thinking();
3255    extras.ignore_eos = req.ignore_eos;
3256    let stops = req.stop.list();
3257    let (session, seed_drawn) = build_session(daemon, req, &op, &model_defaults)?;
3258    extras.seed_drawn = seed_drawn;
3259    qos.apply(daemon, session)?;
3260    let max_tokens = limits.resolve(req.max_completion_tokens.or(req.max_tokens), || {
3261        prompt_len(daemon, session)
3262    });
3263    let cancels = daemon.cancel_registry();
3264    let mut acc = String::new();
3265    let mut stopped = false;
3266    let out = daemon.generate_streaming_ex(session, max_tokens, extras, |ev| {
3267        if let EventBody::Generated {
3268            text, channel: ch, ..
3269        } = &ev.body
3270        {
3271            if *ch == channel::TEXT && !stops.is_empty() && !stopped {
3272                acc.push_str(text);
3273                if stops.iter().any(|s| acc.contains(s.as_str())) {
3274                    cancels.cancel(session);
3275                    stopped = true;
3276                }
3277            }
3278        }
3279        Ok(())
3280    });
3281    let out = out?;
3282    let mut c = collect(&out.events);
3283    claim_named_calls(daemon, &mut c, &out.events, &choice);
3284    if choice.forces_call() {
3285        promote_forced_tool_call(daemon, &mut c, session, tool_schemas.as_deref());
3286    }
3287    let (content, hit_stop) = truncate_at_stop(&c.content, &stops);
3288    c.content = content;
3289    let usage = usage(daemon, session, out.tokens_generated, out.warm_prefix);
3290    let finish = if !c.broken_calls.is_empty() {
3291        "length"
3292    } else if hit_stop {
3293        "stop"
3294    } else {
3295        finish_reason(
3296            out.finish,
3297            out.tokens_generated,
3298            max_tokens,
3299            !c.tool_calls.is_empty() && choice != ToolChoice::None,
3300        )
3301    };
3302    Ok(Frame {
3303        choices: vec![Choice {
3304            message: message(&c, &choice),
3305            finish: Some(finish),
3306            logprobs: want_logprobs.then(|| decode_logprobs(daemon, &out.logprobs, 0)),
3307            ..Choice::default()
3308        }],
3309        usage: Some(usage),
3310        spec: (out.spec.0 > 0).then_some(out.spec),
3311        ..Frame::default()
3312    })
3313}
3314
3315pub(crate) async fn blocking<T: Send + 'static>(
3316    qos: RequestQos,
3317    hold: Option<crate::keypolicy::ClientKey>,
3318    work: impl FnOnce() -> Result<T, DaemonError> + Send + 'static,
3319) -> Result<T, RunError> {
3320    tokio::task::spawn_blocking(move || {
3321        let _hold = hold;
3322        let _class = crate::SyncClassScope::enter(Some(qos.class));
3323        work()
3324    })
3325    .await
3326    .map_err(|e| RunError::Worker(e.to_string()))?
3327    .map_err(RunError::Daemon)
3328}
3329
3330pub(crate) fn base64_decode(data: &str) -> Result<Vec<u8>, DaemonError> {
3331    use base64::Engine;
3332    let cleaned: String = data.chars().filter(|c| !c.is_whitespace()).collect();
3333    base64::engine::general_purpose::STANDARD
3334        .decode(&cleaned)
3335        .or_else(|_| base64::engine::general_purpose::STANDARD_NO_PAD.decode(&cleaned))
3336        .map_err(|_| DaemonError::Protocol("invalid base64 image data"))
3337}
3338
3339pub(crate) fn spawn_stream<T: Send + 'static>(
3340    qos: RequestQos,
3341    hold: Option<crate::keypolicy::ClientKey>,
3342    work: impl FnOnce(&tokio::sync::mpsc::Sender<Result<T, StreamFailure>>) -> Result<(), DaemonError>
3343        + Send
3344        + 'static,
3345) -> tokio::sync::mpsc::Receiver<Result<T, StreamFailure>> {
3346    let (tx, rx) = tokio::sync::mpsc::channel(64);
3347    tokio::task::spawn_blocking(move || {
3348        let _hold = hold;
3349        let _class = crate::SyncClassScope::enter(Some(qos.class));
3350        if let Err(error) = work(&tx) {
3351            let client_error = matches!(
3352                error,
3353                DaemonError::Constraint(_)
3354                    | DaemonError::TemplateRefused(_)
3355                    | DaemonError::Unsupported(_)
3356                    | DaemonError::StreamTooLong { .. }
3357                    | DaemonError::NoFimDialect
3358            );
3359            let _ = tx.blocking_send(Err(StreamFailure::new(error, client_error)));
3360        }
3361    });
3362    rx
3363}