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