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}
1113
1114impl DecodeSpan {
1115    pub(crate) fn stamp(&mut self, produced: u64) {
1116        self.stamp_at(std::time::Instant::now(), produced);
1117    }
1118
1119    fn stamp_at(&mut self, now: std::time::Instant, produced: u64) {
1120        if self.first.is_none() {
1121            self.first = Some(now);
1122            self.first_count = produced;
1123        }
1124        self.last = Some(now);
1125        self.last_count = produced.max(self.last_count);
1126    }
1127
1128    pub(crate) fn first_token(&self) -> Option<std::time::Instant> {
1129        self.first
1130    }
1131
1132    pub(crate) fn finish(&mut self) {
1133        if let (Some(f), Some(l)) = (self.first, self.last) {
1134            self.tokens += self.last_count.saturating_sub(self.first_count);
1135            self.elapsed += l.saturating_duration_since(f);
1136        }
1137        self.first = None;
1138        self.last = None;
1139        self.first_count = 0;
1140        self.last_count = 0;
1141    }
1142
1143    fn measured(&self) -> (u64, Option<std::time::Duration>) {
1144        if self.elapsed == std::time::Duration::ZERO {
1145            (0, None)
1146        } else {
1147            (self.tokens, Some(self.elapsed))
1148        }
1149    }
1150}
1151
1152#[derive(Clone, Copy)]
1153pub(crate) struct GenTiming {
1154    pub t0: std::time::Instant,
1155    pub first: Option<std::time::Instant>,
1156    pub decode: DecodeSpan,
1157}
1158
1159fn fim_log_cached(c: &crate::Completion) -> u64 {
1160    if c.cached {
1161        c.prompt_tokens as u64
1162    } else {
1163        0
1164    }
1165}
1166
1167fn prefill_rate(prompt: u64, cached: u64, ttft: Option<std::time::Duration>) -> String {
1168    let prefilled = prompt - cached.min(prompt);
1169    ttft.filter(|d| d.as_secs_f64() > 0.0 && prefilled > 0)
1170        .map(|d| format!("{:.0}", prefilled as f64 / d.as_secs_f64()))
1171        .unwrap_or_else(|| "-".into())
1172}
1173
1174fn decode_rate(tokens: u64, span: Option<std::time::Duration>) -> String {
1175    span.filter(|d| tokens > 0 && d.as_millis() as u64 >= tokens)
1176        .map(|d| format!("{:.1}", tokens as f64 / d.as_secs_f64()))
1177        .unwrap_or_else(|| "-".into())
1178}
1179
1180pub(crate) fn access_log(
1181    route: &str,
1182    model: &str,
1183    prompt: u64,
1184    cached: u64,
1185    completion: u64,
1186    timing: GenTiming,
1187    finish: &str,
1188) {
1189    let GenTiming { t0, first, decode } = timing;
1190    let total_ms = t0.elapsed().as_millis();
1191    let ttft = first.map(|f| f.saturating_duration_since(t0));
1192    let (decode_tokens, decode_span) = decode.measured();
1193    let prefill = prefill_rate(prompt, cached, ttft);
1194    let decode = decode_rate(decode_tokens, decode_span);
1195    tracing::info!(
1196        "<-- 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}",
1197        ttft.map(|d| d.as_millis()).unwrap_or(0),
1198    );
1199}
1200
1201pub(crate) fn extras_of(
1202    presence: Option<f32>,
1203    frequency: Option<f32>,
1204    repeat: Option<f32>,
1205    op: &SamplingOverrides,
1206    model: &ModelSamplingDefaults,
1207) -> crate::scheduler::GenExtras {
1208    let mut frequency_penalty = frequency.unwrap_or(0.0);
1209    let mut repeat_penalty = repeat_penalty_for(repeat, op, model);
1210    if op.fallback.is_some_and(|f| f.frequency_penalty_as_repeat)
1211        && repeat.is_none()
1212        && op.repeat_penalty.is_none()
1213    {
1214        if presence.is_some() {
1215            repeat_penalty = 1.0;
1216        } else if let Some(f) = frequency {
1217            repeat_penalty = 1.0 + f;
1218            frequency_penalty = 0.0;
1219        }
1220    }
1221    crate::scheduler::GenExtras {
1222        ephemeral: true,
1223        presence_penalty: presence.unwrap_or(0.0),
1224        frequency_penalty,
1225        repeat_penalty,
1226        grammar_handle: 0,
1227        logit_bias_handle: 0,
1228        want_logprobs: false,
1229        top_logprobs: 0,
1230        wants_deltas: false,
1231        open_channel: 0,
1232        thinking: None,
1233        ignore_eos: false,
1234        seed_drawn: false,
1235        tool_schemas: None,
1236    }
1237}
1238
1239pub(crate) fn logit_bias_handle_of(
1240    daemon: &Daemon,
1241    map: &Option<std::collections::HashMap<String, f32>>,
1242) -> Option<u32> {
1243    let map = map.as_ref()?;
1244    if map.is_empty() {
1245        return None;
1246    }
1247    let mut tokens = Vec::with_capacity(map.len());
1248    let mut values = Vec::with_capacity(map.len());
1249    for (k, v) in map {
1250        if let Ok(t) = k.parse::<i32>() {
1251            tokens.push(t);
1252            values.push(*v);
1253        }
1254    }
1255    if tokens.is_empty() {
1256        return None;
1257    }
1258    daemon.logit_bias_create(&tokens, &values).ok()
1259}
1260
1261#[derive(Debug, Default, Deserialize)]
1262pub(crate) struct StreamOptions {
1263    #[serde(default)]
1264    #[allow(dead_code)]
1265    pub(crate) include_usage: bool,
1266    #[serde(default)]
1267    pub(crate) continuous_usage_stats: bool,
1268}
1269
1270#[derive(Deserialize)]
1271pub(crate) struct CompletionRequest {
1272    pub(crate) model: Option<String>,
1273    #[serde(default)]
1274    pub(crate) ignore_eos: bool,
1275    #[serde(default)]
1276    pub(crate) prompt: String,
1277    #[serde(default)]
1278    pub(crate) stream: bool,
1279    #[serde(default)]
1280    pub(crate) echo: bool,
1281    pub(crate) max_tokens: Option<u32>,
1282    pub(crate) temperature: Option<f32>,
1283    pub(crate) top_p: Option<f32>,
1284    pub(crate) top_k: Option<u32>,
1285    pub(crate) seed: Option<u64>,
1286    pub(crate) presence_penalty: Option<f32>,
1287    pub(crate) frequency_penalty: Option<f32>,
1288    #[serde(alias = "repetition_penalty")]
1289    pub(crate) repeat_penalty: Option<f32>,
1290    pub(crate) min_p: Option<f32>,
1291    pub(crate) logprobs: Option<u32>,
1292    pub(crate) suffix: Option<String>,
1293    pub(crate) fim_mode: Option<String>,
1294}
1295
1296pub(crate) const DEFAULT_TOP_K: u32 = 40;
1297
1298pub(crate) const DEFAULT_REPEAT_PENALTY: f32 = 1.0;
1299
1300#[cfg(test)]
1301mod embedding_sampling_tests {
1302    use super::*;
1303
1304    fn policy() -> SamplingOverrides {
1305        SamplingOverrides {
1306            fallback: Some(SamplingFallback {
1307                temperature: 0.0, repeat_penalty: 1.07,
1308                top_p: 0.82, top_k: 17, min_p: 0.03,
1309                frequency_penalty_as_repeat: true,
1310            }),
1311            ..Default::default()
1312        }
1313    }
1314
1315    #[test]
1316    fn omitted_parameters_and_props_share_embedding_defaults() {
1317        let m = ModelSamplingDefaults::default();
1318        let op = policy();
1319        let p = advertised_generation_defaults(&m, &op);
1320        assert_eq!((p.temperature, p.top_p, p.top_k, p.min_p), (0.0, 0.82, 17, 0.03));
1321        assert_eq!(repeat_penalty_for(None, &op, &m), 1.07);
1322        let original = advertised_generation_defaults(&m, &SamplingOverrides::default());
1323        assert_eq!((original.temperature, original.top_p, original.top_k, original.min_p), (1.0, 1.0, 40, 0.0));
1324        assert_eq!(repeat_penalty_for(None, &SamplingOverrides::default(), &m), 1.0);
1325    }
1326
1327    #[test]
1328    fn model_truncation_wins_as_a_set_but_not_model_temperature_or_repeat() {
1329        for model in [
1330            ModelSamplingDefaults { top_p: Some(0.95), ..Default::default() },
1331            ModelSamplingDefaults { top_k: Some(9), ..Default::default() },
1332            ModelSamplingDefaults { min_p: Some(0.1), ..Default::default() },
1333        ] {
1334            let model = ModelSamplingDefaults { temperature: Some(0.7), repetition_penalty: Some(1.2), ..model };
1335            let p = advertised_generation_defaults(&model, &policy());
1336            assert_eq!(p.temperature, 0.0);
1337            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)));
1338            assert_eq!(repeat_penalty_for(None, &policy(), &model), 1.07);
1339        }
1340    }
1341
1342    #[test]
1343    fn caller_and_operator_values_override_embedding_without_merging_truncation() {
1344        let m = ModelSamplingDefaults { top_k: Some(90), ..Default::default() };
1345        let op = SamplingOverrides { temperature: Some(0.6), top_k: Some(12), repeat_penalty: Some(1.3), ..policy() };
1346        let p = advertised_generation_defaults(&m, &op);
1347        assert_eq!((p.temperature, p.top_p, p.top_k, p.min_p), (0.6, 1.0, 12, 0.0));
1348        assert_eq!(repeat_penalty_for(None, &op, &m), 1.3);
1349        let p = params_of_for(Some(0.2), Some(1.0), None, None, Some(42), &m, &op);
1350        assert_eq!((p.temperature, p.top_p, p.top_k, p.min_p, p.seed), (0.2, 1.0, 0, 0.0, 42));
1351        assert_eq!(repeat_penalty_for(Some(1.0), &op, &m), 1.0);
1352    }
1353
1354    #[test]
1355    fn penalty_mapping_is_opt_in_and_respects_explicit_parameters() {
1356        let m = ModelSamplingDefaults::default();
1357        for (presence, frequency, repeat, expected) in [
1358            (None, None, None, (0.0, 0.0, 1.07)),
1359            (None, Some(0.2), None, (0.0, 0.0, 1.2)),
1360            (None, Some(0.0), None, (0.0, 0.0, 1.0)),
1361            (Some(0.0), Some(0.2), None, (0.0, 0.2, 1.0)),
1362            (Some(0.3), None, None, (0.3, 0.0, 1.0)),
1363            (None, Some(0.2), Some(1.1), (0.0, 0.2, 1.1)),
1364        ] {
1365            let e = extras_of(presence, frequency, repeat, &policy(), &m);
1366            assert_eq!((e.presence_penalty, e.frequency_penalty, e.repeat_penalty), expected);
1367        }
1368        let e = extras_of(None, Some(0.2), None, &SamplingOverrides::default(), &m);
1369        assert_eq!((e.frequency_penalty, e.repeat_penalty), (0.2, 1.0));
1370    }
1371}
1372
1373pub(crate) fn advertised_generation_defaults(
1374    model: &ModelSamplingDefaults,
1375    op: &SamplingOverrides,
1376) -> GenParams {
1377    params_of_for(None, None, None, None, None, model, op)
1378}
1379
1380pub(crate) fn repeat_penalty_for(
1381    caller: Option<f32>,
1382    op: &SamplingOverrides,
1383    model: &ModelSamplingDefaults,
1384) -> f32 {
1385    caller
1386        .or(op.repeat_penalty)
1387        .or(op.fallback.map(|f| f.repeat_penalty))
1388        .or(model.repetition_penalty)
1389        .unwrap_or(DEFAULT_REPEAT_PENALTY)
1390}
1391
1392pub(crate) fn draw_seed_if_unseeded(params: &mut GenParams, client_seed: Option<u64>) -> bool {
1393    if client_seed.is_some() || params.temperature <= 0.0 {
1394        return false;
1395    }
1396    params.seed = fresh_seed();
1397    true
1398}
1399
1400fn fresh_seed() -> u64 {
1401    use std::hash::{BuildHasher, Hasher};
1402    let h = std::collections::hash_map::RandomState::new()
1403        .build_hasher()
1404        .finish();
1405    if h == 0 {
1406        1
1407    } else {
1408        h
1409    }
1410}
1411
1412pub(crate) fn params_of_for(
1413    temperature: Option<f32>,
1414    top_p: Option<f32>,
1415    top_k: Option<u32>,
1416    min_p: Option<f32>,
1417    seed: Option<u64>,
1418    model: &ModelSamplingDefaults,
1419    op: &SamplingOverrides,
1420) -> GenParams {
1421    let model_temperature = match model.do_sample {
1422        Some(false) => Some(0.0),
1423        _ => model.temperature,
1424    };
1425    let chosen = temperature.is_some() || op.temperature.is_some();
1426    let temperature = temperature
1427        .or(op.temperature)
1428        .or(op.fallback.map(|f| f.temperature))
1429        .or(model_temperature)
1430        .unwrap_or(1.0);
1431    let temperature = match op.spec_max_temperature {
1432        Some(cap) if !chosen => temperature.min(cap),
1433        _ => temperature,
1434    };
1435
1436    let caller_truncates = top_p.is_some() || top_k.is_some() || min_p.is_some();
1437    let model_truncates = model.top_p.is_some() || model.top_k.is_some() || model.min_p.is_some();
1438    let (eff_top_p, eff_top_k, eff_min_p) = if caller_truncates {
1439        (top_p, top_k, min_p)
1440    } else if op.truncates() {
1441        (op.top_p, op.top_k, op.min_p)
1442    } else if model_truncates {
1443        (model.top_p, model.top_k, model.min_p)
1444    } else if let Some(f) = op.fallback {
1445        (Some(f.top_p), Some(f.top_k), Some(f.min_p))
1446    } else if temperature > 0.0 {
1447        (None, Some(DEFAULT_TOP_K), None)
1448    } else {
1449        (None, None, None)
1450    };
1451    GenParams {
1452        temperature,
1453        top_p: eff_top_p.unwrap_or(1.0),
1454        min_p: eff_min_p.unwrap_or(0.0),
1455        top_k: eff_top_k.unwrap_or(0),
1456        seed: seed.unwrap_or(0),
1457    }
1458}
1459
1460#[derive(Deserialize, Default)]
1461pub(crate) struct ChatMessage {
1462    pub(crate) role: String,
1463    #[serde(default)]
1464    pub(crate) content: Option<serde_json::Value>,
1465    #[serde(default)]
1466    pub(crate) tool_calls: Vec<ReqToolCall>,
1467    #[serde(default)]
1468    pub(crate) tool_call_id: Option<String>,
1469    #[serde(default)]
1470    pub(crate) name: Option<String>,
1471    #[serde(default)]
1472    pub(crate) reasoning_content: Option<String>,
1473    #[serde(default)]
1474    pub(crate) reasoning: Option<String>,
1475}
1476
1477pub(crate) fn reasoning_of(m: &ChatMessage, role: u32) -> Option<String> {
1478    if role != role::ASSISTANT {
1479        return None;
1480    }
1481    m.reasoning_content
1482        .as_deref()
1483        .or(m.reasoning.as_deref())
1484        .map(str::trim)
1485        .filter(|s| !s.is_empty())
1486        .map(str::to_string)
1487}
1488
1489#[derive(Clone, Copy, PartialEq, Eq)]
1490pub(crate) enum MediaKind {
1491    Image,
1492    Audio,
1493}
1494
1495fn require_media(daemon: &Daemon, kind: MediaKind, param: &str) -> Result<(), DaemonError> {
1496    match kind {
1497        MediaKind::Image => daemon.require("modalities", "image_encode", "unsupported_input", Some(param), "does not accept images"),
1498        MediaKind::Audio => daemon.require("modalities", "gemma_audio_encode", "unsupported_input", Some(param), "does not accept audio"),
1499    }
1500}
1501
1502fn content_param(messages: &[ChatMessage], m: &ChatMessage) -> String {
1503    match messages.iter().position(|x| std::ptr::eq(x, m)) {
1504        Some(i) => format!("messages[{i}].content"),
1505        None => "messages".to_string(),
1506    }
1507}
1508
1509pub(crate) struct SplitContent {
1510    pub(crate) text: String,
1511    pub(crate) images: Vec<(String, Vec<u8>, MediaKind)>,
1512}
1513
1514pub(crate) fn absorbable_system_text(m: &ChatMessage) -> Result<Option<String>, DaemonError> {
1515    if m.role != "system" {
1516        return Ok(None);
1517    }
1518    let split = split_content(&m.content)?;
1519    Ok(split.images.is_empty().then_some(split.text))
1520}
1521
1522pub(crate) fn split_content(v: &Option<serde_json::Value>) -> Result<SplitContent, DaemonError> {
1523    let mut out = SplitContent {
1524        text: String::new(),
1525        images: Vec::new(),
1526    };
1527    match v {
1528        None => {}
1529        Some(serde_json::Value::String(s)) => out.text = s.clone(),
1530        Some(serde_json::Value::Array(parts)) => {
1531            for p in parts {
1532                match p.get("type").and_then(|t| t.as_str()) {
1533                    Some("text") => {
1534                        if let Some(t) = p.get("text").and_then(|t| t.as_str()) {
1535                            out.text.push_str(t);
1536                        }
1537                    }
1538                    Some("image_url") => {
1539                        let url = p
1540                            .get("image_url")
1541                            .and_then(|u| u.get("url"))
1542                            .and_then(|u| u.as_str())
1543                            .unwrap_or("");
1544                        let Some(rest) = url.strip_prefix("data:") else {
1545                            return Err(DaemonError::Protocol(
1546                                "image_url must be a data: URL (remote images are not fetched)",
1547                            ));
1548                        };
1549                        let Some((_, b64)) = rest.split_once(";base64,") else {
1550                            return Err(DaemonError::Protocol("image_url data URL must be base64"));
1551                        };
1552                        let bytes = base64_decode(b64)?;
1553                        out.images
1554                            .push((std::mem::take(&mut out.text), bytes, MediaKind::Image));
1555                    }
1556                    Some("input_audio") => {
1557                        let ia = p.get("input_audio");
1558                        let fmt = ia
1559                            .and_then(|a| a.get("format"))
1560                            .and_then(|f| f.as_str())
1561                            .unwrap_or("wav");
1562                        if fmt != "wav" {
1563                            return Err(DaemonError::Protocol(
1564                                "input_audio format must be wav (16-bit PCM or float32)",
1565                            ));
1566                        }
1567                        let b64 = ia
1568                            .and_then(|a| a.get("data"))
1569                            .and_then(|d| d.as_str())
1570                            .ok_or(DaemonError::Protocol("input_audio requires base64 data"))?;
1571                        let bytes = base64_decode(b64)?;
1572                        out.images
1573                            .push((std::mem::take(&mut out.text), bytes, MediaKind::Audio));
1574                    }
1575                    _ => {}
1576                }
1577            }
1578        }
1579        Some(_) => {
1580            return Err(DaemonError::Protocol(
1581                "message content must be a string or parts",
1582            ))
1583        }
1584    }
1585    Ok(out)
1586}
1587
1588#[derive(Deserialize)]
1589pub(crate) struct ReqToolCall {
1590    #[serde(default)]
1591    pub(crate) id: Option<String>,
1592    pub(crate) function: Option<ReqFunction>,
1593}
1594
1595#[derive(Deserialize)]
1596pub(crate) struct ReqFunction {
1597    pub(crate) name: String,
1598    #[serde(default)]
1599    pub(crate) arguments: serde_json::Value,
1600}
1601
1602pub(crate) fn build_session(
1603    daemon: &Daemon,
1604    req: &ChatRequest,
1605    op: &SamplingOverrides,
1606    model: &ModelSamplingDefaults,
1607) -> Result<(u64, bool), DaemonError> {
1608    if let Err(why) = daemon.validate_template_kwargs(&req.template_kwargs()) {
1609        return Err(DaemonError::Constraint(why));
1610    }
1611    let mut params = params_of_for(
1612        req.temperature,
1613        req.top_p,
1614        req.top_k,
1615        req.min_p,
1616        req.seed,
1617        model,
1618        &op.for_daemon(daemon),
1619    );
1620    let seed_drawn = draw_seed_if_unseeded(&mut params, req.seed);
1621
1622    if let Some(c) = req.continues {
1623        // Only a codec that renders each message alone renders a turn
1624        // appended to a stored conversation as it would render it fresh.
1625        if !daemon.renders_per_message() {
1626            return Err(DaemonError::Protocol(
1627                "this model's chat template renders whole conversations; a stored one is not continued by appending",
1628            ));
1629        }
1630        let session = daemon.fork(c.session, c.at_event, Some(params))?;
1631        // The stored conversation ends after a query: a turn here follows
1632        // none of this request's user messages, or comes after the last one.
1633        let last_query = req.messages.iter().rposition(|m| m.role == "user");
1634        for (i, m) in req.messages.iter().enumerate() {
1635            append_chat_message(daemon, session, req, m, last_query.is_none_or(|q| i > q))?;
1636        }
1637        return Ok((session, seed_drawn));
1638    }
1639
1640    let session = daemon.create(None, params)?;
1641
1642    if !daemon.renders_per_message() {
1643        let mut msgs: Vec<crate::codec::ChatMessage> = Vec::with_capacity(req.messages.len());
1644        for m in &req.messages {
1645            let split = split_content(&m.content)?;
1646            let role = match m.role.as_str() {
1647                "system" => role::SYSTEM,
1648                "assistant" => role::ASSISTANT,
1649                "tool" => role::TOOL,
1650                _ => role::USER,
1651            };
1652            if !split.images.is_empty() {
1653                let mut parts: Vec<crate::codec::ContentPart> = Vec::new();
1654                let trailing = split.text;
1655                for (pre, bytes, kind) in split.images {
1656                    require_media(daemon, kind, &content_param(&req.messages, m))?;
1657                    if !pre.is_empty() {
1658                        parts.push(crate::codec::ContentPart::Text(pre));
1659                    }
1660                    let hash = daemon.put_media(&bytes)?;
1661                    parts.push(match kind {
1662                        MediaKind::Image => crate::codec::ContentPart::Image { blob: hash },
1663                        MediaKind::Audio => crate::codec::ContentPart::Audio { blob: hash },
1664                    });
1665                }
1666                if !trailing.is_empty() {
1667                    parts.push(crate::codec::ContentPart::Text(trailing));
1668                }
1669                let mut msg = crate::codec::ChatMessage::new(role, String::new());
1670                msg.parts = parts;
1671                msg.reasoning = reasoning_of(m, role);
1672                msgs.push(msg);
1673                continue;
1674            }
1675            let mut msg = crate::codec::ChatMessage::new(role, split.text);
1676            for c in &m.tool_calls {
1677                let Some(f) = &c.function else { continue };
1678                let args = match &f.arguments {
1679                    serde_json::Value::String(s) => s.clone(),
1680                    other => other.to_string(),
1681                };
1682                msg.tool_calls.push(crate::codec::ToolCallMsg {
1683                    id: c.id.clone().unwrap_or_default(),
1684                    name: f.name.clone(),
1685                    arguments: args,
1686                });
1687            }
1688            msg.reasoning = reasoning_of(m, role);
1689            if role == role::TOOL {
1690                msg.tool_call_id = m.tool_call_id.clone();
1691                msg.name = m.name.clone();
1692            }
1693            msgs.push(msg);
1694        }
1695        let tools: Vec<String> = req.tools.iter().map(|t| t.to_string()).collect();
1696        daemon.append_conversation(session, &msgs, &tools, &req.template_kwargs())?;
1697        return Ok((session, seed_drawn));
1698    }
1699
1700    let (system, tool_jsons) = system_head(req)?;
1701    let rest = if system.is_some() { &req.messages[1..] } else { &req.messages[..] };
1702    daemon.append_system_full(session, system, tool_jsons, &req.template_kwargs())?;
1703    // A turn after the last user query is a step of a tool loop.
1704    let last_query = req.messages.iter().rposition(|m| m.role == "user");
1705    let skipped = req.messages.len() - rest.len();
1706    for (i, m) in rest.iter().enumerate() {
1707        let after_query = last_query.is_some_and(|q| skipped + i > q);
1708        append_chat_message(daemon, session, req, m, after_query)?;
1709    }
1710    Ok((session, seed_drawn))
1711}
1712
1713/// What a per-message codec renders first: the leading system message when
1714/// it is text only, and the tools when the request may call them. Two
1715/// requests with the same head render the same first turn.
1716pub(crate) fn system_head(req: &ChatRequest) -> Result<(Option<String>, Vec<String>), DaemonError> {
1717    let system = req.messages.first().map(absorbable_system_text).transpose()?.flatten();
1718    let tools = if !req.tools.is_empty() && ToolChoice::parse(&req.tool_choice).declares_tools() {
1719        req.tools.iter().map(|t| t.to_string()).collect()
1720    } else {
1721        Vec::new()
1722    };
1723    Ok((system, tools))
1724}
1725
1726fn append_chat_message(
1727    daemon: &Daemon,
1728    session: u64,
1729    req: &ChatRequest,
1730    m: &ChatMessage,
1731    after_query: bool,
1732) -> Result<(), DaemonError> {
1733    let split = split_content(&m.content)?;
1734    if !split.images.is_empty() {
1735        let r = match m.role.as_str() {
1736            "assistant" => role::ASSISTANT,
1737            "system" => role::SYSTEM,
1738            _ => role::USER,
1739        };
1740        let n = split.images.len();
1741        let mut trailing = split.text;
1742        for (i, (pre, bytes, kind)) in split.images.into_iter().enumerate() {
1743            require_media(daemon, kind, &content_param(&req.messages, m))?;
1744            let hash = daemon.put_media(&bytes)?;
1745            let post = if i + 1 == n {
1746                std::mem::take(&mut trailing)
1747            } else {
1748                String::new()
1749            };
1750            daemon.append_image(session, r, &hash, &pre, &post)?;
1751        }
1752        return Ok(());
1753    }
1754    let content = split.text;
1755    match m.role.as_str() {
1756        "assistant" if !m.tool_calls.is_empty() => {
1757            let calls: Vec<(String, String)> = m
1758                .tool_calls
1759                .iter()
1760                .filter_map(|c| c.function.as_ref())
1761                .map(|f| {
1762                    let args = match &f.arguments {
1763                        serde_json::Value::String(s) => s.clone(),
1764                        v => v.to_string(),
1765                    };
1766                    (f.name.clone(), args)
1767                })
1768                .collect();
1769            daemon.append_assistant_with_tool_calls(session, content, calls, after_query)?;
1770        }
1771        "assistant" => {
1772            daemon.append_message(session, role::ASSISTANT, content)?;
1773        }
1774        "system" => {
1775            daemon.append_message(session, role::SYSTEM, content)?;
1776        }
1777        "tool" => {
1778            daemon.append_tool_history(session, content)?;
1779        }
1780        _ => {
1781            daemon.append_message(session, role::USER, content)?;
1782        }
1783    }
1784    Ok(())
1785}
1786
1787pub(crate) struct Collected {
1788    pub(crate) content: String,
1789    pub(crate) reasoning: String,
1790    pub(crate) tool_calls: Vec<(u64, String, String)>,
1791    pub(crate) unparsed_tool: String,
1792    pub(crate) broken_calls: Vec<(u64, String, String)>,
1793}
1794
1795pub(crate) fn promote_forced_tool_call(
1796    daemon: &Daemon,
1797    c: &mut Collected,
1798    session: u64,
1799    schemas: Option<&crate::codec::ToolSchemas>,
1800) {
1801    if !c.tool_calls.is_empty() {
1802        return;
1803    }
1804    let raw = c.content.trim().to_string();
1805    if raw.is_empty() {
1806        return;
1807    }
1808    if let Some((name, args)) = daemon.parse_tool_call_with(&raw, schemas) {
1809        c.tool_calls.push((session, name, args));
1810        c.content.clear();
1811    }
1812}
1813
1814pub(crate) fn claim_named_calls(daemon: &Daemon, c: &mut Collected, events: &[crate::CommittedEvent], choice: &ToolChoice) {
1815    if *choice == ToolChoice::None {
1816        return;
1817    }
1818    let mut left = String::new();
1819    for e in events {
1820        if let EventBody::ToolParseFailure { raw } = &e.body {
1821            match daemon.named_tool_call(raw) {
1822                Some((name, args)) => c.broken_calls.push((e.event_id, name, args)),
1823                None => left.push_str(raw),
1824            }
1825        }
1826    }
1827    c.unparsed_tool = left;
1828}
1829
1830pub(crate) fn collect(events: &[crate::CommittedEvent]) -> Collected {
1831    let mut c = Collected {
1832        content: String::new(),
1833        reasoning: String::new(),
1834        tool_calls: Vec::new(),
1835        unparsed_tool: String::new(),
1836        broken_calls: Vec::new(),
1837    };
1838    for e in events {
1839        match &e.body {
1840            EventBody::Generated {
1841                text, channel: ch, ..
1842            } => match *ch {
1843                channel::REASONING => c.reasoning.push_str(text),
1844                channel::TOOL_CALL => {}
1845                _ => c.content.push_str(text),
1846            },
1847            EventBody::ToolUse { name, arguments } => {
1848                c.tool_calls
1849                    .push((e.event_id, name.clone(), arguments.clone()));
1850            }
1851            EventBody::ToolParseFailure { raw } => c.unparsed_tool.push_str(raw),
1852            _ => {}
1853        }
1854    }
1855    c
1856}
1857
1858pub(crate) fn finish_reason(
1859    fin: u32,
1860    produced: u32,
1861    max_tokens: u32,
1862    had_tool_calls: bool,
1863) -> &'static str {
1864    let cut = match fin {
1865        finish::LENGTH => true,
1866        finish::EOS | finish::CANCELLED => false,
1867        _ => produced >= max_tokens,
1868    };
1869    if cut {
1870        "length"
1871    } else if had_tool_calls {
1872        "tool_calls"
1873    } else {
1874        "stop"
1875    }
1876}
1877
1878pub(crate) fn prompt_len(daemon: &Daemon, session: u64) -> u32 {
1879    daemon
1880        .store()
1881        .lock()
1882        .expect("store")
1883        .session(session)
1884        .map(|s| s.tokens.len().min(u32::MAX as usize) as u32)
1885        .unwrap_or(0)
1886}
1887
1888pub(crate) fn next_id() -> String {
1889    use std::sync::atomic::{AtomicU64, Ordering};
1890    static N: AtomicU64 = AtomicU64::new(1);
1891    format!("chatcmpl-superfluid-{}", N.fetch_add(1, Ordering::Relaxed))
1892}
1893
1894pub(crate) fn unix_now() -> u64 {
1895    std::time::SystemTime::now()
1896        .duration_since(std::time::UNIX_EPOCH)
1897        .map(|d| d.as_secs())
1898        .unwrap_or(0)
1899}
1900
1901#[cfg(test)]
1902mod access_log_rate_tests {
1903    use super::*;
1904    use std::time::Duration;
1905
1906    #[test]
1907    fn prefill_counts_only_what_was_prefilled() {
1908        let cases = [
1909            (12961u64, 0u64, 10444u64, "1241"),
1910            (14481, 12800, 5716, "294"),
1911            (15627, 14900, 5057, "144"),
1912            (16045, 15900, 2138, "68"),
1913        ];
1914        for (prompt, cached, ttft_ms, want) in cases {
1915            let got = prefill_rate(prompt, cached, Some(Duration::from_millis(ttft_ms)));
1916            assert_eq!(got, want, "prompt={prompt} cached={cached}");
1917        }
1918    }
1919
1920    #[test]
1921    fn a_fully_warm_prompt_prefilled_nothing() {
1922        assert_eq!(
1923            prefill_rate(4096, 4096, Some(Duration::from_millis(80))),
1924            "-"
1925        );
1926        assert_eq!(
1927            prefill_rate(4096, 9000, Some(Duration::from_millis(80))),
1928            "-"
1929        );
1930    }
1931
1932    #[test]
1933    fn a_fim_cache_hit_logs_its_prompt_as_cached() {
1934        let c = |cached| crate::Completion {
1935            text: String::new(),
1936            tokens: vec![1; 16],
1937            cached,
1938            expired: false,
1939            finish: superfluid_abi::finish::NONE,
1940            prompt_tokens: 21,
1941        };
1942        assert_eq!(fim_log_cached(&c(true)), 21);
1943        assert_eq!(
1944            prefill_rate(
1945                21,
1946                fim_log_cached(&c(true)),
1947                Some(Duration::from_micros(60))
1948            ),
1949            "-"
1950        );
1951        assert_eq!(fim_log_cached(&c(false)), 0);
1952    }
1953
1954    #[test]
1955    fn prefill_needs_a_ttft() {
1956        assert_eq!(prefill_rate(1024, 0, None), "-");
1957        assert_eq!(prefill_rate(1024, 0, Some(Duration::ZERO)), "-");
1958    }
1959
1960    fn run(d: &mut DecodeSpan, base: std::time::Instant, stamps: &[(u64, u64)]) {
1961        for (ms, produced) in stamps {
1962            d.stamp_at(base + Duration::from_millis(*ms), *produced);
1963        }
1964        d.finish();
1965    }
1966
1967    #[test]
1968    fn decode_is_the_inter_token_rate() {
1969        let base = std::time::Instant::now();
1970        let mut d = DecodeSpan::default();
1971        run(&mut d, base, &[(0, 1), (781, 200)]);
1972        let (tokens, span) = d.measured();
1973        assert_eq!(tokens, 199);
1974        assert_eq!(decode_rate(tokens, span), "254.8");
1975    }
1976
1977    #[test]
1978    fn decode_excludes_the_whole_first_commit() {
1979        let base = std::time::Instant::now();
1980        let mut d = DecodeSpan::default();
1981        run(&mut d, base, &[(0, 5), (1000, 100)]);
1982        assert_eq!(d.measured().0, 95, "the first commit's 5 predate the span");
1983        assert_eq!(decode_rate(d.measured().0, d.measured().1), "95.0");
1984    }
1985
1986    #[test]
1987    fn decode_spans_do_not_swallow_the_gaps_between_choices() {
1988        let base = std::time::Instant::now();
1989        let mut d = DecodeSpan::default();
1990        run(&mut d, base, &[(0, 1), (500, 51)]);
1991        run(&mut d, base, &[(4500, 1), (5000, 51)]);
1992        let (tokens, span) = d.measured();
1993        assert_eq!(tokens, 100);
1994        assert_eq!(span, Some(Duration::from_millis(1000)), "gap excluded");
1995        assert_eq!(decode_rate(tokens, span), "100.0");
1996    }
1997
1998    #[test]
1999    fn decode_counts_only_what_was_delivered() {
2000        let base = std::time::Instant::now();
2001        let mut d = DecodeSpan::default();
2002        run(&mut d, base, &[(0, 1), (500, 50)]);
2003        let (tokens, span) = d.measured();
2004        assert_eq!(tokens, 49, "the undelivered tail is usage's business");
2005        assert_eq!(decode_rate(tokens, span), "98.0");
2006    }
2007
2008    #[test]
2009    fn decode_declines_to_guess() {
2010        let base = std::time::Instant::now();
2011        let mut d = DecodeSpan::default();
2012        d.finish();
2013        assert_eq!(d.measured(), (0, None));
2014        assert_eq!(decode_rate(0, None), "-");
2015
2016        let mut one = DecodeSpan::default();
2017        run(&mut one, base, &[(0, 40)]);
2018        assert_eq!(decode_rate(one.measured().0, one.measured().1), "-");
2019
2020        assert_eq!(decode_rate(200, Some(Duration::from_millis(150))), "-");
2021        assert_eq!(decode_rate(100, None), "-");
2022    }
2023}
2024
2025#[cfg(test)]
2026mod key_order_tests {
2027    use super::ChatRequest;
2028
2029    #[test]
2030    fn a_tool_keeps_the_key_order_it_was_sent_in() {
2031        let req: ChatRequest = serde_json::from_str(
2032            r#"{"messages":[],"tools":[{"type":"function","function":{"name":"replace_text",
2033                "parameters":{"type":"object","properties":{"path":{"type":"string"},
2034                "old_text":{"type":"string"},"new_text":{"type":"string"}}}}}]}"#,
2035        )
2036        .expect("request");
2037        let t = req.tools[0].to_string();
2038        let at = |k: &str| t.find(k).unwrap_or_else(|| panic!("{k} missing: {t}"));
2039        assert!(at("\"type\"") < at("\"function\""), "{t}");
2040        assert!(
2041            at("\"path\"") < at("\"old_text\"") && at("\"old_text\"") < at("\"new_text\""),
2042            "{t}"
2043        );
2044    }
2045}
2046
2047#[cfg(test)]
2048mod system_absorption_tests {
2049    use super::*;
2050
2051    #[test]
2052    fn assistant_reasoning_is_carried_and_other_roles_are_not() {
2053        let mut m = msg("assistant", serde_json::Value::Null);
2054        m.reasoning_content = Some("  plan  ".into());
2055        assert_eq!(reasoning_of(&m, role::ASSISTANT), Some("plan".into()));
2056        m.reasoning_content = None;
2057        m.reasoning = Some("alt".into());
2058        assert_eq!(
2059            reasoning_of(&m, role::ASSISTANT),
2060            Some("alt".into()),
2061            "the other spelling"
2062        );
2063        m.reasoning = Some("   ".into());
2064        assert_eq!(
2065            reasoning_of(&m, role::ASSISTANT),
2066            None,
2067            "blank reasoning is none"
2068        );
2069        let mut u = msg("user", serde_json::json!("hi"));
2070        u.reasoning_content = Some("x".into());
2071        assert_eq!(
2072            reasoning_of(&u, role::USER),
2073            None,
2074            "only assistant turns carry reasoning"
2075        );
2076    }
2077
2078    fn msg(role: &str, content: serde_json::Value) -> ChatMessage {
2079        ChatMessage {
2080            role: role.to_string(),
2081            content: Some(content),
2082            tool_calls: Vec::new(),
2083            tool_call_id: None,
2084            name: None,
2085            reasoning_content: None,
2086            reasoning: None,
2087        }
2088    }
2089
2090    fn image_part() -> serde_json::Value {
2091        serde_json::json!({
2092            "type": "image_url",
2093            "image_url": {"url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="}
2094        })
2095    }
2096
2097    #[test]
2098    fn text_only_system_message_is_absorbed() {
2099        let m = msg("system", serde_json::json!("you are helpful"));
2100        assert_eq!(
2101            absorbable_system_text(&m).expect("split"),
2102            Some("you are helpful".to_string()),
2103            "a text-only system message folds into the scaffold"
2104        );
2105    }
2106
2107    #[test]
2108    fn multimodal_system_message_is_left_for_the_media_loop() {
2109        let m = msg(
2110            "system",
2111            serde_json::json!([{"type": "text", "text": "look at this"}, image_part()]),
2112        );
2113        let split = split_content(&m.content).expect("split");
2114        assert_eq!(split.images.len(), 1, "fixture must carry one image part");
2115
2116        assert_eq!(
2117            absorbable_system_text(&m).expect("split"),
2118            None,
2119            "a system message with media must NOT be absorbed — its images \
2120             would be dropped on the floor"
2121        );
2122    }
2123
2124    #[test]
2125    fn audio_system_message_is_left_for_the_media_loop() {
2126        let m = msg(
2127            "system",
2128            serde_json::json!([
2129                {"type": "text", "text": "listen"},
2130                {"type": "input_audio", "input_audio": {"data": "UklGRiQAAABXQVZF", "format": "wav"}}
2131            ]),
2132        );
2133        let split = split_content(&m.content).expect("split");
2134        assert_eq!(split.images.len(), 1, "fixture must carry one audio part");
2135        assert_eq!(absorbable_system_text(&m).expect("split"), None);
2136    }
2137
2138    #[test]
2139    fn non_system_roles_are_never_absorbed() {
2140        for role in ["user", "assistant", "tool", "developer"] {
2141            let m = msg(role, serde_json::json!("hello"));
2142            assert_eq!(
2143                absorbable_system_text(&m).expect("split"),
2144                None,
2145                "{role} must not be absorbed into the system scaffold"
2146            );
2147        }
2148    }
2149}
2150
2151pub(crate) enum RunError {
2152    Daemon(DaemonError),
2153    Worker(String),
2154}
2155impl From<DaemonError> for RunError {
2156    fn from(error: DaemonError) -> Self {
2157        Self::Daemon(error)
2158    }
2159}
2160
2161pub(crate) async fn completions(
2162    daemon: Arc<Daemon>,
2163    model_name: String,
2164    req: CompletionRequest,
2165    policy: RequestPolicy,
2166    hold: Option<crate::keypolicy::ClientKey>,
2167    hooks: crate::keepalive::Hooks,
2168) -> Result<Generation, RunError> {
2169    let qos = policy.qos;
2170    let max_tokens = policy
2171        .limits
2172        .resolve(req.max_tokens, || daemon.tokenize(&req.prompt).len() as u32);
2173    let id = format!(
2174        "cmpl-superfluid-{}",
2175        next_id().rsplit('-').next().unwrap_or("0")
2176    );
2177    let created = unix_now();
2178    let model_defaults = {
2179        let d = daemon.clone();
2180        tokio::task::spawn_blocking(move || {
2181            crate::with_sync_class(Some(qos.class), || d.model_sampling_defaults())
2182        })
2183        .await
2184        .unwrap_or_default()
2185    };
2186    let mut params = params_of_for(
2187        req.temperature,
2188        req.top_p,
2189        req.top_k,
2190        req.min_p,
2191        req.seed,
2192        &model_defaults,
2193        &policy.sampling.for_daemon(&daemon),
2194    );
2195    let seed_drawn = draw_seed_if_unseeded(&mut params, req.seed);
2196    let mut extras = extras_of(
2197        req.presence_penalty,
2198        req.frequency_penalty,
2199        req.repeat_penalty,
2200        &policy.sampling,
2201        &model_defaults,
2202    );
2203    extras.seed_drawn = seed_drawn;
2204    let want_logprobs = req.logprobs.is_some();
2205    extras.want_logprobs = want_logprobs;
2206    extras.top_logprobs = req.logprobs.unwrap_or(0).min(20) as u8;
2207    extras.ignore_eos = req.ignore_eos;
2208    let echo_offset = if req.echo { req.prompt.len() } else { 0 };
2209
2210    if req.stream {
2211        let body = completion_stream(daemon, req, max_tokens, params, extras, qos, hold).await?;
2212        return Ok(Generation {
2213            kind: Kind::Completion,
2214            id,
2215            model: model_name,
2216            created,
2217            warm_header: None,
2218            body,
2219        });
2220    }
2221
2222    let daemon_lp = Arc::clone(&daemon);
2223    let model_log = model_name.clone();
2224    let out = blocking(qos, hold, move || {
2225        let mut hooks = hooks;
2226        let t0 = std::time::Instant::now();
2227        let mut decode = DecodeSpan::default();
2228        let mut produced = 0u64;
2229        let session = daemon.create(None, params)?;
2230        qos.apply(&daemon, session)?;
2231        daemon.append(session, Some(req.prompt.clone()), Vec::new())?;
2232        if hooks.is_on() {
2233            daemon.session_fits(session, None)?;
2234        }
2235        hooks.admitted(&daemon, session);
2236        hooks.check()?;
2237        let out = daemon.generate_streaming_ex(session, max_tokens, extras, |ev| {
2238            hooks.cancel_if_gone();
2239            if let EventBody::Generated { span, .. } = &ev.body {
2240                if !span.is_empty() {
2241                    produced += span.len() as u64;
2242                    decode.stamp(produced);
2243                }
2244            }
2245            Ok(())
2246        })?;
2247        let first_tok = decode.first_token();
2248        decode.finish();
2249        let c = collect(&out.events);
2250        let usage = usage(&daemon, session, out.tokens_generated, out.warm_prefix);
2251        access_log(
2252            "POST /v1/completions",
2253            &model_log,
2254            usage.prompt.unwrap_or(0),
2255            out.warm_prefix,
2256            out.tokens_generated as u64,
2257            GenTiming {
2258                t0,
2259                first: first_tok,
2260                decode,
2261            },
2262            finish_reason(out.finish, out.tokens_generated, max_tokens, false),
2263        );
2264        let mut text = String::new();
2265        if req.echo {
2266            text.push_str(&req.prompt);
2267        }
2268        text.push_str(&c.content);
2269        text.push_str(&c.reasoning);
2270        text.push_str(&c.unparsed_tool);
2271        Ok::<_, DaemonError>((out, text, usage))
2272    })
2273    .await;
2274    let (out, text, usage) = out?;
2275    let logprobs = want_logprobs.then(|| decode_logprobs(&daemon_lp, &out.logprobs, echo_offset));
2276    let mut frame = Frame::text(
2277        text,
2278        logprobs,
2279        Some(finish_reason(
2280            out.finish,
2281            out.tokens_generated,
2282            max_tokens,
2283            false,
2284        )),
2285    );
2286    frame.usage = Some(usage);
2287    Ok(Generation {
2288        kind: Kind::Completion,
2289        id,
2290        model: model_name,
2291        created,
2292        warm_header: None,
2293        body: Body::Complete(frame),
2294    })
2295}
2296
2297#[allow(clippy::too_many_arguments)]
2298async fn completion_stream(
2299    daemon: Arc<Daemon>,
2300    req: CompletionRequest,
2301    max_tokens: u32,
2302    params: GenParams,
2303    extras: crate::scheduler::GenExtras,
2304    qos: RequestQos,
2305    hold: Option<crate::keypolicy::ClientKey>,
2306) -> Result<Body, RunError> {
2307    let want_logprobs = req.logprobs.is_some();
2308    let admitted_daemon = Arc::clone(&daemon);
2309    let (req, session) = blocking(qos, hold.clone(), move || {
2310        let session = admitted_daemon.create(None, params)?;
2311        qos.apply(&admitted_daemon, session)?;
2312        admitted_daemon.append(session, Some(req.prompt.clone()), Vec::new())?;
2313        admitted_daemon.session_fits(session, None)?;
2314        Ok((req, session))
2315    })
2316    .await?;
2317    let rx = spawn_stream(qos, hold, move |tx| {
2318        let _closed = crate::keepalive::cancel_when_closed(&daemon, session, tx);
2319        let send = |v: Frame| {
2320            tx.blocking_send(Ok(v))
2321                .map_err(|_| DaemonError::Protocol("generation consumer gone"))
2322        };
2323        let lp_q: std::rc::Rc<
2324            std::cell::RefCell<std::collections::VecDeque<crate::scheduler::TokenLogprob>>,
2325        > = std::rc::Rc::new(std::cell::RefCell::new(std::collections::VecDeque::new()));
2326        let lp_fill = std::rc::Rc::clone(&lp_q);
2327        let mut offset: usize = 0;
2328        let run = || -> Result<(), DaemonError> {
2329            if req.echo {
2330                offset = req.prompt.len();
2331                send(Frame::text(&req.prompt, None, None))?;
2332            }
2333            let out = daemon.generate_streaming_ex_lp(
2334                session,
2335                max_tokens,
2336                extras,
2337                |ev| {
2338                    if let EventBody::Generated {
2339                        text,
2340                        channel: ch,
2341                        span,
2342                        ..
2343                    } = &ev.body
2344                    {
2345                        let these: Vec<crate::scheduler::TokenLogprob> = if want_logprobs {
2346                            let mut q = lp_q.borrow_mut();
2347                            (0..span.len()).filter_map(|_| q.pop_front()).collect()
2348                        } else {
2349                            Vec::new()
2350                        };
2351                        if !text.is_empty() && *ch != channel::TOOL_CALL {
2352                            let lp = if want_logprobs {
2353                                let v = Some(decode_logprobs(&daemon, &these, offset));
2354                                offset += text.len();
2355                                v
2356                            } else {
2357                                None
2358                            };
2359                            return send(Frame::text(text, lp, None));
2360                        }
2361                    }
2362                    Ok(())
2363                },
2364                |items| {
2365                    lp_fill.borrow_mut().extend(items.iter().cloned());
2366                },
2367            )?;
2368            let fin = finish_reason(out.finish, out.tokens_generated, max_tokens, false);
2369            let mut terminal = Frame::text("", None, Some(fin));
2370            terminal.usage = Some(usage(
2371                &daemon,
2372                session,
2373                out.tokens_generated,
2374                out.warm_prefix,
2375            ));
2376            send(terminal)
2377        };
2378        run()
2379    });
2380    Ok(Body::Stream(rx))
2381}
2382pub(crate) async fn chat(
2383    daemon: Arc<Daemon>,
2384    model_name: String,
2385    req: ChatRequest,
2386    policy: RequestPolicy,
2387    hold: Option<crate::keypolicy::ClientKey>,
2388    hooks: crate::keepalive::Hooks,
2389) -> Result<Generation, RunError> {
2390    let qos = policy.qos;
2391    let explicit_max = req.max_completion_tokens.or(req.max_tokens);
2392    let id = next_id();
2393    let created = unix_now();
2394
2395    if req.stream {
2396        let body = chat_stream(daemon, model_name.clone(), req, policy, explicit_max, hold).await?;
2397        return Ok(Generation {
2398            kind: Kind::Chat,
2399            id,
2400            model: model_name,
2401            created,
2402            warm_header: None,
2403            body,
2404        });
2405    }
2406
2407    let n = req.n.unwrap_or(1).max(1);
2408    let route = req.route();
2409    let stops = req.stop.list();
2410    let model_log = model_name.clone();
2411    let out = blocking(qos, hold, move || {
2412        let mut hooks = hooks;
2413        let t0 = std::time::Instant::now();
2414        let mut decode = DecodeSpan::default();
2415        let mut ttft_first: Option<std::time::Instant> = None;
2416        let mut last_finish = "-";
2417        let model_defaults = daemon.model_sampling_defaults();
2418        let mut extras = extras_of(
2419            req.presence_penalty,
2420            req.frequency_penalty,
2421            req.repeat_penalty,
2422            &policy.sampling,
2423            &model_defaults,
2424        );
2425        let choice = ToolChoice::parse(&req.tool_choice);
2426        if choice.forces_call() {
2427            extras.open_channel = crate::wal::channel::TOOL_CALL;
2428        }
2429        let tool_schemas = crate::codec::ToolSchemas::of_values(&req.tools);
2430        extras.tool_schemas = tool_schemas.clone();
2431        let handles = RequestHandles::new(
2432            &daemon,
2433            grammar_handle_for(&daemon, &req, &choice)?,
2434            logit_bias_handle_of(&daemon, &req.logit_bias),
2435        );
2436        handles.apply(&mut extras);
2437        let want_logprobs = req.logprobs.unwrap_or(false);
2438        extras.want_logprobs = want_logprobs;
2439        extras.top_logprobs = req.top_logprobs.unwrap_or(0).min(20) as u8;
2440        extras.thinking = req.thinking();
2441        extras.ignore_eos = req.ignore_eos;
2442        let cancels = daemon.cancel_registry();
2443        let RequestPolicy {
2444            limits,
2445            sampling: op,
2446            qos,
2447        } = policy;
2448        let mut choices = Vec::with_capacity(n as usize);
2449        let mut completion_tokens = 0u64;
2450        let mut prompt_tokens = 0u64;
2451        let mut warm = 0u64;
2452        let mut warm_first: Option<u64> = None;
2453        let mut spec = (0u64, 0u64);
2454        let mut last_session = None;
2455        for i in 0..n {
2456            if i > 0 && hooks.gone() {
2457                break;
2458            }
2459            let (session, seed_drawn) = build_session(&daemon, &req, &op, &model_defaults)?;
2460            last_session = Some(session);
2461            let mut extras = extras.clone();
2462            extras.seed_drawn = seed_drawn;
2463            qos.apply(&daemon, session)?;
2464            if hooks.is_on() {
2465                daemon.session_fits(session, req.thinking())?;
2466            }
2467            hooks.admitted(&daemon, session);
2468            hooks.check()?;
2469            let max_tokens = limits.resolve(explicit_max, || prompt_len(&daemon, session));
2470            let mut acc = String::new();
2471            let mut produced = 0u64;
2472            let stops = stops.clone();
2473            let cancels = std::sync::Arc::clone(&cancels);
2474            let mut stopped = false;
2475            let out = daemon.generate_streaming_ex(session, max_tokens, extras, |ev| {
2476                hooks.cancel_if_gone();
2477                if let EventBody::Generated {
2478                    text,
2479                    channel: ch,
2480                    span,
2481                    ..
2482                } = &ev.body
2483                {
2484                    if !span.is_empty() {
2485                        produced += span.len() as u64;
2486                        decode.stamp(produced);
2487                    }
2488                    if *ch == channel::TEXT && !stops.is_empty() && !stopped {
2489                        acc.push_str(text);
2490                        if stops.iter().any(|s| acc.contains(s.as_str())) {
2491                            cancels.cancel(session);
2492                            stopped = true;
2493                        }
2494                    }
2495                }
2496                Ok(())
2497            })?;
2498            ttft_first = ttft_first.or_else(|| decode.first_token());
2499            decode.finish();
2500            let mut c = collect(&out.events);
2501            claim_named_calls(&daemon, &mut c, &out.events, &choice);
2502            if choice.forces_call() {
2503                promote_forced_tool_call(&daemon, &mut c, session, tool_schemas.as_deref());
2504            }
2505            let (content, hit_stop) = truncate_at_stop(&c.content, &stops);
2506            c.content = content;
2507            let usage = usage(&daemon, session, out.tokens_generated, out.warm_prefix);
2508            completion_tokens += usage.completion;
2509            spec.0 += out.spec.0;
2510            spec.1 += out.spec.1;
2511            prompt_tokens = usage.prompt.unwrap_or(prompt_tokens);
2512            warm = warm.max(out.warm_prefix);
2513            warm_first.get_or_insert(out.warm_prefix);
2514            let finish = if !c.broken_calls.is_empty() {
2515                "length"
2516            } else if hit_stop {
2517                "stop"
2518            } else {
2519                finish_reason(
2520                    out.finish,
2521                    out.tokens_generated,
2522                    max_tokens,
2523                    !c.tool_calls.is_empty() && choice != ToolChoice::None,
2524                )
2525            };
2526            choices.push(Choice {
2527                index: i,
2528                message: message(&c, &choice),
2529                finish: Some(finish),
2530                logprobs: want_logprobs.then(|| decode_logprobs(&daemon, &out.logprobs, 0)),
2531                ..Choice::default()
2532            });
2533            last_finish = finish;
2534        }
2535        access_log(
2536            route,
2537            &model_log,
2538            prompt_tokens,
2539            warm_first.unwrap_or(warm),
2540            completion_tokens,
2541            GenTiming {
2542                t0,
2543                first: ttft_first,
2544                decode,
2545            },
2546            last_finish,
2547        );
2548        Ok::<_, DaemonError>((choices, prompt_tokens, completion_tokens, warm, spec, last_session))
2549    })
2550    .await;
2551    let (choices, prompt_tokens, completion_tokens, warm, spec, session) = out?;
2552    let frame = Frame {
2553        choices,
2554        usage: Some(Usage::complete(
2555            prompt_tokens,
2556            completion_tokens,
2557            Some(warm.min(prompt_tokens)),
2558        )),
2559        spec: (spec.0 > 0).then_some(spec),
2560        session,
2561        ..Frame::default()
2562    };
2563    Ok(Generation {
2564        kind: Kind::Chat,
2565        id,
2566        model: model_name,
2567        created,
2568        warm_header: Some(warm),
2569        body: Body::Complete(frame),
2570    })
2571}
2572
2573async fn chat_stream(
2574    daemon: Arc<Daemon>,
2575    model_name: String,
2576    req: ChatRequest,
2577    policy: RequestPolicy,
2578    explicit_max: Option<u32>,
2579    hold: Option<crate::keypolicy::ClientKey>,
2580) -> Result<Body, RunError> {
2581    let RequestPolicy {
2582        limits,
2583        sampling: op,
2584        qos,
2585    } = policy;
2586    let t0 = std::time::Instant::now();
2587    let admitted_daemon = Arc::clone(&daemon);
2588    let (req, session, seed_drawn, model_defaults) = blocking(qos, hold.clone(), move || {
2589        let model_defaults = admitted_daemon.model_sampling_defaults();
2590        let (session, seed_drawn) = build_session(&admitted_daemon, &req, &op, &model_defaults)?;
2591        qos.apply(&admitted_daemon, session)?;
2592        admitted_daemon.session_fits(session, req.thinking())?;
2593        Ok((req, session, seed_drawn, model_defaults))
2594    })
2595    .await?;
2596    let rx = spawn_stream(qos, hold, move |tx| {
2597        let _closed = crate::keepalive::cancel_when_closed(&daemon, session, tx);
2598        let send = |v: Frame| -> Result<(), DaemonError> {
2599            tx.blocking_send(Ok(v))
2600                .map_err(|_| DaemonError::Protocol("generation consumer gone"))
2601        };
2602        let run = || -> Result<(), DaemonError> {
2603            send(Frame::chat(
2604                Message {
2605                    assistant: true,
2606                    ..Message::default()
2607                },
2608                None,
2609            ))?;
2610            let mut tool_index = 0usize;
2611            let mut orphaned_calls = 0usize;
2612            let mut extras = extras_of(
2613                req.presence_penalty,
2614                req.frequency_penalty,
2615                req.repeat_penalty,
2616                &op,
2617                &model_defaults,
2618            );
2619            extras.seed_drawn = seed_drawn;
2620            let choice = ToolChoice::parse(&req.tool_choice);
2621            if choice.forces_call() {
2622                extras.open_channel = crate::wal::channel::TOOL_CALL;
2623            }
2624            let tool_schemas = crate::codec::ToolSchemas::of_values(&req.tools);
2625            extras.tool_schemas = tool_schemas.clone();
2626            let mut frag =
2627                crate::tool_fragment::ToolCallStream::new(if choice == ToolChoice::None {
2628                    crate::tool_fragment::ToolFragmentMode::None
2629                } else {
2630                    daemon.tool_fragment_mode()
2631                })
2632                .with_schemas(tool_schemas.clone());
2633            let mut open_call_id = String::new();
2634            let handles = RequestHandles::new(
2635                &daemon,
2636                grammar_handle_for(&daemon, &req, &choice)?,
2637                logit_bias_handle_of(&daemon, &req.logit_bias),
2638            );
2639            handles.apply(&mut extras);
2640            let want_logprobs = req.logprobs.unwrap_or(false);
2641            extras.want_logprobs = want_logprobs;
2642            extras.top_logprobs = req.top_logprobs.unwrap_or(0).min(20) as u8;
2643            extras.thinking = req.thinking();
2644            extras.ignore_eos = req.ignore_eos;
2645            let lp_q: std::rc::Rc<
2646                std::cell::RefCell<std::collections::VecDeque<crate::scheduler::TokenLogprob>>,
2647            > = std::rc::Rc::new(std::cell::RefCell::new(std::collections::VecDeque::new()));
2648            let lp_fill = std::rc::Rc::clone(&lp_q);
2649            let max_tokens = limits.resolve(explicit_max, || prompt_len(&daemon, session));
2650            let stops = req.stop.list();
2651            let cancels = daemon.cancel_registry();
2652            let delta_mode = !want_logprobs;
2653            let continuous_usage = req
2654                .stream_options
2655                .as_ref()
2656                .is_some_and(|o| o.continuous_usage_stats);
2657            struct StreamText {
2658                usage_now: Option<u32>,
2659                committed: u32,
2660                decode: DecodeSpan,
2661                content_acc: String,
2662                content_sent: usize,
2663                stopped: bool,
2664                dead: bool,
2665            }
2666            let st = std::rc::Rc::new(std::cell::RefCell::new(StreamText {
2667                usage_now: None,
2668                committed: 0,
2669                decode: DecodeSpan::default(),
2670                content_acc: String::new(),
2671                content_sent: 0,
2672                stopped: false,
2673                dead: false,
2674            }));
2675            let push_text =
2676                |s: &mut StreamText, text: &str, lp: Option<Logprobs>| -> Result<(), DaemonError> {
2677                    if s.stopped {
2678                        return Ok(());
2679                    }
2680                    if choice.forces_call() {
2681                        s.content_acc.push_str(text);
2682                        return Ok(());
2683                    }
2684                    let start = s.content_sent;
2685                    s.content_acc.push_str(text);
2686                    let tail = &s.content_acc[start..];
2687                    let mut cut: Option<usize> = None;
2688                    for stop in &stops {
2689                        if let Some(i) = tail.find(stop.as_str()) {
2690                            cut = Some(cut.map_or(i, |c| c.min(i)));
2691                        }
2692                    }
2693                    let hit = cut.is_some();
2694                    let end = match cut {
2695                        Some(i) => start + i,
2696                        None => start + tail.len() - stop_prefix_holdback(tail, &stops),
2697                    };
2698                    let piece = s.content_acc.get(start..end).unwrap_or("").to_string();
2699                    s.content_sent = s.content_sent.max(end);
2700                    if !piece.is_empty() {
2701                        let mut ck = Frame::chat(Message::content(piece), None);
2702                        if let Some(lp) = lp {
2703                            ck.choices[0].logprobs = Some(lp);
2704                        }
2705                        if let Some(n) = s.usage_now {
2706                            ck.usage = Some(Usage::partial(n));
2707                        }
2708                        send(ck)?;
2709                    }
2710                    if hit {
2711                        cancels.cancel(session);
2712                        s.stopped = true;
2713                    }
2714                    Ok(())
2715                };
2716            let st_delta = std::rc::Rc::clone(&st);
2717            let on_delta = |ch: u32, text: &str, produced: u32| {
2718                if !delta_mode || text.is_empty() {
2719                    return;
2720                }
2721                let mut s = st_delta.borrow_mut();
2722                if continuous_usage {
2723                    s.usage_now = Some(produced);
2724                }
2725                if s.dead {
2726                    return;
2727                }
2728                s.decode.stamp(produced as u64);
2729                let r = if ch == channel::REASONING {
2730                    let mut ck = Frame::chat(Message::reasoning(text), None);
2731                    if let Some(n) = s.usage_now {
2732                        ck.usage = Some(Usage::partial(n));
2733                    }
2734                    send(ck)
2735                } else {
2736                    push_text(&mut s, text, None)
2737                };
2738                if r.is_err() {
2739                    cancels.cancel(session);
2740                    s.dead = true;
2741                }
2742            };
2743            let st_ev = std::rc::Rc::clone(&st);
2744            let on_event = |ev: &crate::CommittedEvent| {
2745                match &ev.body {
2746                    EventBody::Generated {
2747                        text,
2748                        channel: ch,
2749                        span,
2750                        ..
2751                    } => {
2752                        let mut s = st_ev.borrow_mut();
2753                        let covered_by_delta = delta_mode
2754                            && (*ch == channel::TEXT || *ch == channel::REASONING)
2755                            && !text.is_empty();
2756                        s.committed += span.len() as u32;
2757                        let committed = s.committed as u64;
2758                        if !covered_by_delta && !span.is_empty() {
2759                            s.decode.stamp(committed);
2760                        }
2761                        if continuous_usage && !delta_mode {
2762                            s.usage_now = Some(s.committed);
2763                        }
2764                        let these: Vec<crate::scheduler::TokenLogprob> = if want_logprobs {
2765                            let mut q = lp_q.borrow_mut();
2766                            (0..span.len()).filter_map(|_| q.pop_front()).collect()
2767                        } else {
2768                            Vec::new()
2769                        };
2770                        if *ch == channel::TOOL_CALL {
2771                            for d in frag.feed(text) {
2772                                if d.opening {
2773                                    open_call_id = format!("call_{}", ev.event_id);
2774                                }
2775                                let json = Message::tool(&d, tool_index, &open_call_id);
2776                                send(Frame::chat(json, None))?;
2777                            }
2778                            return Ok(());
2779                        }
2780                        if text.is_empty() {
2781                            return Ok(());
2782                        }
2783                        if delta_mode {
2784                            return Ok(());
2785                        }
2786                        if *ch == channel::REASONING {
2787                            let mut ck = Frame::chat(Message::reasoning(text), None);
2788                            if let Some(n) = s.usage_now {
2789                                ck.usage = Some(Usage::partial(n));
2790                            }
2791                            return send(ck);
2792                        }
2793                        let lp = want_logprobs.then(|| decode_logprobs(&daemon, &these, 0));
2794                        push_text(&mut s, text, lp)
2795                    }
2796                    EventBody::ToolParseFailure { raw } => {
2797                        if choice != ToolChoice::None && !frag.opened() {
2798                            if let Some((name, arguments)) = daemon.named_tool_call(raw) {
2799                                let delta = Message::complete_tool(
2800                                    tool_index,
2801                                    format!("call_{}", ev.event_id),
2802                                    name,
2803                                    arguments,
2804                                );
2805                                tool_index += 1;
2806                                orphaned_calls += 1;
2807                                return send(Frame::chat(delta, None));
2808                            }
2809                        }
2810                        if frag.opened() {
2811                            orphaned_calls += 1;
2812                            tool_index += 1;
2813                        }
2814                        frag.reset();
2815                        let mut ck = Frame::chat(Message::content(raw), None);
2816                        if continuous_usage {
2817                            ck.usage = Some(Usage::partial(st_ev.borrow().committed));
2818                        }
2819                        send(ck)
2820                    }
2821                    EventBody::ToolUse { name, arguments } => {
2822                        if choice == ToolChoice::None {
2823                            let raw = format!(
2824                                "{{\"name\":{},\"arguments\":{}}}",
2825                                serde_json::Value::String(name.clone()),
2826                                arguments
2827                            );
2828                            let mut ck = Frame::chat(Message::content(raw), None);
2829                            if continuous_usage {
2830                                ck.usage = Some(Usage::partial(st_ev.borrow().committed));
2831                            }
2832                            return send(ck);
2833                        }
2834                        if frag.opened() {
2835                            for d in frag.finish(arguments) {
2836                                send(Frame::chat(
2837                                    Message::tool(&d, tool_index, &open_call_id),
2838                                    None,
2839                                ))?;
2840                            }
2841                            frag.reset();
2842                            tool_index += 1;
2843                            return Ok(());
2844                        }
2845                        let delta = Message::complete_tool(
2846                            tool_index,
2847                            format!("call_{}", ev.event_id),
2848                            name.clone(),
2849                            arguments.clone(),
2850                        );
2851                        tool_index += 1;
2852                        send(Frame::chat(delta, None))
2853                    }
2854                    _ => Ok(()),
2855                }
2856            };
2857            let on_logprobs = |items: &[crate::scheduler::TokenLogprob]| {
2858                lp_fill.borrow_mut().extend(items.iter().cloned());
2859            };
2860            let out = if delta_mode {
2861                daemon.generate_streaming_delta(
2862                    session,
2863                    max_tokens,
2864                    extras,
2865                    on_event,
2866                    on_logprobs,
2867                    on_delta,
2868                )?
2869            } else {
2870                daemon.generate_streaming_ex_lp(
2871                    session,
2872                    max_tokens,
2873                    extras,
2874                    on_event,
2875                    on_logprobs,
2876                )?
2877            };
2878            let st = st.borrow();
2879            let (mut decode, stopped) = (st.decode, st.stopped);
2880            let first_tok = decode.first_token();
2881            decode.finish();
2882            let (content_acc, content_sent) = (st.content_acc.clone(), st.content_sent);
2883            drop(st);
2884            let mut c = collect(&out.events);
2885            if !stopped && !choice.forces_call() && content_sent < content_acc.len() {
2886                let rest = content_acc[content_sent..].to_string();
2887                let mut ck = Frame::chat(Message::content(rest), None);
2888                if continuous_usage {
2889                    ck.usage = Some(Usage::partial(out.tokens_generated));
2890                }
2891                send(ck)?;
2892            }
2893            if choice.forces_call() {
2894                c.content = content_acc.clone();
2895                promote_forced_tool_call(&daemon, &mut c, session, tool_schemas.as_deref());
2896                if tool_index > 0 {
2897                } else if let Some((id, name, args)) = c.tool_calls.first() {
2898                    send(Frame::chat(
2899                        Message::complete_tool(
2900                            tool_index,
2901                            format!("call_{id}"),
2902                            name.clone(),
2903                            args.clone(),
2904                        ),
2905                        None,
2906                    ))?;
2907                } else if !c.content.is_empty() {
2908                    let mut ck = Frame::chat(Message::content(c.content), None);
2909                    if continuous_usage {
2910                        ck.usage = Some(Usage::partial(out.tokens_generated));
2911                    }
2912                    send(ck)?;
2913                }
2914            }
2915            let fin = if stopped {
2916                "stop"
2917            } else {
2918                finish_reason(
2919                    out.finish,
2920                    out.tokens_generated,
2921                    max_tokens,
2922                    !c.tool_calls.is_empty() && choice != ToolChoice::None,
2923                )
2924            };
2925            let fin = if orphaned_calls > 0 { "length" } else { fin };
2926            let mut terminal = Frame::chat(Message::default(), Some(fin));
2927            terminal.usage = Some(usage(
2928                &daemon,
2929                session,
2930                out.tokens_generated,
2931                out.warm_prefix,
2932            ));
2933            access_log(
2934                &format!("{} (stream)", req.route()),
2935                &model_name,
2936                terminal.usage.as_ref().and_then(|u| u.prompt).unwrap_or(0),
2937                out.warm_prefix,
2938                out.tokens_generated as u64,
2939                GenTiming {
2940                    t0,
2941                    first: first_tok,
2942                    decode,
2943                },
2944                fin,
2945            );
2946            terminal.warm = Some(out.warm_prefix);
2947            terminal.spec = (out.spec.0 > 0).then_some(out.spec);
2948            terminal.session = Some(session);
2949            send(terminal)
2950        };
2951        run()
2952    });
2953
2954    Ok(Body::Stream(rx))
2955}
2956const FIM_DEFAULT_MAX_TOKENS: u32 = 128;
2957
2958#[allow(clippy::too_many_arguments)]
2959pub(crate) async fn fim_completions(
2960    daemon: Arc<Daemon>,
2961    model_name: String,
2962    req: CompletionRequest,
2963    limits: TokenLimits,
2964    sampling: SamplingOverrides,
2965    mode: u8,
2966    key: Option<crate::keypolicy::ClientKey>,
2967) -> Result<Generation, RunError> {
2968    if !daemon.fim_supported() {
2969        return Err(DaemonError::NoFimDialect.into());
2970    }
2971    let hold = key;
2972    let max_tokens = req
2973        .max_tokens
2974        .or(limits.default_max_tokens)
2975        .unwrap_or(FIM_DEFAULT_MAX_TOKENS)
2976        .max(1);
2977    let serving = daemon
2978        .fim_satellite()
2979        .unwrap_or_else(|| Arc::clone(&daemon));
2980    let model_defaults = {
2981        let d = Arc::clone(&serving);
2982        tokio::task::spawn_blocking(move || {
2983            crate::with_sync_class(Some(crate::qos::INLINE_COMPLETION), || {
2984                d.model_sampling_defaults()
2985            })
2986        })
2987        .await
2988        .unwrap_or_default()
2989    };
2990    let op = sampling.for_daemon(&serving);
2991    let mut params = params_of_for(
2992        req.temperature,
2993        req.top_p,
2994        req.top_k,
2995        req.min_p,
2996        req.seed,
2997        &model_defaults,
2998        &op,
2999    );
3000    let seed_drawn = draw_seed_if_unseeded(&mut params, req.seed);
3001    let mut extras = extras_of(
3002        req.presence_penalty,
3003        req.frequency_penalty,
3004        req.repeat_penalty,
3005        &op,
3006        &model_defaults,
3007    );
3008    extras.seed_drawn = seed_drawn;
3009    extras.ignore_eos = req.ignore_eos;
3010    let fim = crate::FimRequest {
3011        prefix: req.prompt,
3012        suffix: req.suffix.unwrap_or_default(),
3013        mode,
3014        max_tokens,
3015        params,
3016        extras,
3017    };
3018    let id = format!(
3019        "cmpl-superfluid-{}",
3020        next_id().rsplit('-').next().unwrap_or("0")
3021    );
3022    let created = unix_now();
3023    let served_by = daemon
3024        .fim_satellite_name()
3025        .unwrap_or_else(|| model_name.clone());
3026    let log_model = served_by.clone();
3027    let fin_of = move |c: &crate::Completion| {
3028        if c.expired {
3029            "length"
3030        } else {
3031            finish_reason(c.finish, c.tokens.len() as u32, max_tokens, false)
3032        }
3033    };
3034    let ext = move |c: &crate::Completion| FimMetadata {
3035        cached: c.cached,
3036        expired: c.expired,
3037        served_by: served_by.clone(),
3038    };
3039    if req.stream {
3040        let admitted_daemon = Arc::clone(&daemon);
3041        let fim = blocking(
3042            RequestQos {
3043                class: crate::qos::INLINE_COMPLETION,
3044                batch_invariant: false,
3045            },
3046            hold.clone(),
3047            move || {
3048                admitted_daemon.fim_fits(&fim)?;
3049                Ok(fim)
3050            },
3051        )
3052        .await?;
3053        let (tx, rx) = tokio::sync::mpsc::channel(64);
3054        tokio::task::spawn_blocking(move || {
3055            let _hold = hold;
3056            let t0 = std::time::Instant::now();
3057            let mut decode = DecodeSpan::default();
3058            let mut delivered = 0usize;
3059            let res = daemon.complete_streaming(&fim, |piece, produced| {
3060                decode.stamp(produced as u64);
3061                delivered = delivered.max(produced);
3062                match tx.blocking_send(Ok(Frame::text(piece, None, None))) {
3063                    Ok(()) => std::ops::ControlFlow::Continue(()),
3064                    Err(_) => std::ops::ControlFlow::Break(()),
3065                }
3066            });
3067            match res {
3068                Ok(c) => {
3069                    let fin = fin_of(&c);
3070                    let first = decode.first_token();
3071                    decode.finish();
3072                    access_log(
3073                        "POST /v1/completions (fim)",
3074                        &log_model,
3075                        c.prompt_tokens as u64,
3076                        fim_log_cached(&c),
3077                        c.tokens.len().max(delivered) as u64,
3078                        GenTiming { t0, first, decode },
3079                        fin,
3080                    );
3081                    let mut last = Frame::text("", None, Some(fin));
3082                    last.fim = Some(ext(&c));
3083                    last.usage = Some(Usage::complete(
3084                        c.prompt_tokens as u64,
3085                        c.tokens.len() as u64,
3086                        None,
3087                    ));
3088                    let _ = tx.blocking_send(Ok(last));
3089                }
3090                Err(e) => {
3091                    let client_error = matches!(
3092                        e,
3093                        DaemonError::StreamTooLong { .. } | DaemonError::NoFimDialect
3094                    );
3095                    let _ = tx.blocking_send(Err(StreamFailure::new(e, client_error)));
3096                }
3097            }
3098        });
3099        return Ok(Generation {
3100            kind: Kind::Completion,
3101            id,
3102            model: model_name,
3103            created,
3104            warm_header: None,
3105            body: Body::Stream(rx),
3106        });
3107    }
3108    let out = tokio::task::spawn_blocking(move || {
3109        let _hold = hold;
3110        let t0 = std::time::Instant::now();
3111        let mut decode = DecodeSpan::default();
3112        let c = daemon.complete_streaming(&fim, |_, produced| {
3113            decode.stamp(produced as u64);
3114            std::ops::ControlFlow::Continue(())
3115        })?;
3116        let first = decode.first_token();
3117        decode.finish();
3118        access_log(
3119            "POST /v1/completions (fim)",
3120            &log_model,
3121            c.prompt_tokens as u64,
3122            fim_log_cached(&c),
3123            c.tokens.len() as u64,
3124            GenTiming { t0, first, decode },
3125            fin_of(&c),
3126        );
3127        Ok::<_, DaemonError>(c)
3128    })
3129    .await;
3130    let c = out.map_err(|e| RunError::Worker(e.to_string()))??;
3131    let mut frame = Frame::text(&c.text, None, Some(fin_of(&c)));
3132    frame.usage = Some(Usage::complete(
3133        c.prompt_tokens as u64,
3134        c.tokens.len() as u64,
3135        None,
3136    ));
3137    frame.fim = Some(ext(&c));
3138    Ok(Generation {
3139        kind: Kind::Completion,
3140        id,
3141        model: model_name,
3142        created,
3143        warm_header: None,
3144        body: Body::Complete(frame),
3145    })
3146}
3147
3148pub(crate) fn chat_once(
3149    daemon: &Daemon,
3150    req: &ChatRequest,
3151    policy: RequestPolicy,
3152) -> Result<Frame, DaemonError> {
3153    let RequestPolicy {
3154        limits,
3155        sampling: op,
3156        qos,
3157    } = policy;
3158    let _class = crate::SyncClassScope::enter(Some(qos.class));
3159    let model_defaults = daemon.model_sampling_defaults();
3160    let mut extras = extras_of(
3161        req.presence_penalty,
3162        req.frequency_penalty,
3163        req.repeat_penalty,
3164        &op,
3165        &model_defaults,
3166    );
3167    let choice = ToolChoice::parse(&req.tool_choice);
3168    if choice.forces_call() {
3169        extras.open_channel = crate::wal::channel::TOOL_CALL;
3170    }
3171    let tool_schemas = crate::codec::ToolSchemas::of_values(&req.tools);
3172    extras.tool_schemas = tool_schemas.clone();
3173    let handles = RequestHandles::new(
3174        daemon,
3175        grammar_handle_for(daemon, req, &choice)?,
3176        logit_bias_handle_of(daemon, &req.logit_bias),
3177    );
3178    handles.apply(&mut extras);
3179    let want_logprobs = req.logprobs.unwrap_or(false);
3180    extras.want_logprobs = want_logprobs;
3181    extras.top_logprobs = req.top_logprobs.unwrap_or(0).min(20) as u8;
3182    extras.thinking = req.thinking();
3183    extras.ignore_eos = req.ignore_eos;
3184    let stops = req.stop.list();
3185    let (session, seed_drawn) = build_session(daemon, req, &op, &model_defaults)?;
3186    extras.seed_drawn = seed_drawn;
3187    qos.apply(daemon, session)?;
3188    let max_tokens = limits.resolve(req.max_completion_tokens.or(req.max_tokens), || {
3189        prompt_len(daemon, session)
3190    });
3191    let cancels = daemon.cancel_registry();
3192    let mut acc = String::new();
3193    let mut stopped = false;
3194    let out = daemon.generate_streaming_ex(session, max_tokens, extras, |ev| {
3195        if let EventBody::Generated {
3196            text, channel: ch, ..
3197        } = &ev.body
3198        {
3199            if *ch == channel::TEXT && !stops.is_empty() && !stopped {
3200                acc.push_str(text);
3201                if stops.iter().any(|s| acc.contains(s.as_str())) {
3202                    cancels.cancel(session);
3203                    stopped = true;
3204                }
3205            }
3206        }
3207        Ok(())
3208    });
3209    let out = out?;
3210    let mut c = collect(&out.events);
3211    claim_named_calls(daemon, &mut c, &out.events, &choice);
3212    if choice.forces_call() {
3213        promote_forced_tool_call(daemon, &mut c, session, tool_schemas.as_deref());
3214    }
3215    let (content, hit_stop) = truncate_at_stop(&c.content, &stops);
3216    c.content = content;
3217    let usage = usage(daemon, session, out.tokens_generated, out.warm_prefix);
3218    let finish = if !c.broken_calls.is_empty() {
3219        "length"
3220    } else if hit_stop {
3221        "stop"
3222    } else {
3223        finish_reason(
3224            out.finish,
3225            out.tokens_generated,
3226            max_tokens,
3227            !c.tool_calls.is_empty() && choice != ToolChoice::None,
3228        )
3229    };
3230    Ok(Frame {
3231        choices: vec![Choice {
3232            message: message(&c, &choice),
3233            finish: Some(finish),
3234            logprobs: want_logprobs.then(|| decode_logprobs(daemon, &out.logprobs, 0)),
3235            ..Choice::default()
3236        }],
3237        usage: Some(usage),
3238        spec: (out.spec.0 > 0).then_some(out.spec),
3239        ..Frame::default()
3240    })
3241}
3242
3243pub(crate) async fn blocking<T: Send + 'static>(
3244    qos: RequestQos,
3245    hold: Option<crate::keypolicy::ClientKey>,
3246    work: impl FnOnce() -> Result<T, DaemonError> + Send + 'static,
3247) -> Result<T, RunError> {
3248    tokio::task::spawn_blocking(move || {
3249        let _hold = hold;
3250        let _class = crate::SyncClassScope::enter(Some(qos.class));
3251        work()
3252    })
3253    .await
3254    .map_err(|e| RunError::Worker(e.to_string()))?
3255    .map_err(RunError::Daemon)
3256}
3257
3258pub(crate) fn base64_decode(data: &str) -> Result<Vec<u8>, DaemonError> {
3259    use base64::Engine;
3260    let cleaned: String = data.chars().filter(|c| !c.is_whitespace()).collect();
3261    base64::engine::general_purpose::STANDARD
3262        .decode(&cleaned)
3263        .or_else(|_| base64::engine::general_purpose::STANDARD_NO_PAD.decode(&cleaned))
3264        .map_err(|_| DaemonError::Protocol("invalid base64 image data"))
3265}
3266
3267pub(crate) fn spawn_stream<T: Send + 'static>(
3268    qos: RequestQos,
3269    hold: Option<crate::keypolicy::ClientKey>,
3270    work: impl FnOnce(&tokio::sync::mpsc::Sender<Result<T, StreamFailure>>) -> Result<(), DaemonError>
3271        + Send
3272        + 'static,
3273) -> tokio::sync::mpsc::Receiver<Result<T, StreamFailure>> {
3274    let (tx, rx) = tokio::sync::mpsc::channel(64);
3275    tokio::task::spawn_blocking(move || {
3276        let _hold = hold;
3277        let _class = crate::SyncClassScope::enter(Some(qos.class));
3278        if let Err(error) = work(&tx) {
3279            let client_error = matches!(
3280                error,
3281                DaemonError::Constraint(_)
3282                    | DaemonError::TemplateRefused(_)
3283                    | DaemonError::Unsupported(_)
3284                    | DaemonError::StreamTooLong { .. }
3285                    | DaemonError::NoFimDialect
3286            );
3287            let _ = tx.blocking_send(Err(StreamFailure::new(error, client_error)));
3288        }
3289    });
3290    rx
3291}