Skip to main content

lmcpp/client/types/
generation_settings.rs

1use bon::Builder;
2use cmdstruct::Arg;
3use serde::{Deserialize, Serialize};
4
5/// Represents the prompt for a completion request, which can be provided as text or tokens.
6#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
7#[serde(untagged)]
8pub enum Prompt {
9    Tokens(TokenIds),
10    Text(String),
11}
12
13impl From<String> for Prompt {
14    fn from(text: String) -> Self {
15        Prompt::Text(text)
16    }
17}
18
19impl From<&String> for Prompt {
20    fn from(text: &String) -> Self {
21        Prompt::Text(text.to_owned())
22    }
23}
24
25impl From<&str> for Prompt {
26    fn from(text: &str) -> Self {
27        Prompt::Text(text.into())
28    }
29}
30
31impl<T> From<T> for Prompt
32where
33    T: Into<TokenIds>,
34{
35    fn from(t: T) -> Self {
36        Prompt::Tokens(t.into())
37    }
38}
39/// Wrapper around a vector of token-IDs.
40///
41/// This transparent new-type lets callers pass `Vec<u32>`, `Vec<u64>` or
42/// `Vec<usize>`.
43#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
44#[serde(transparent)]
45pub struct TokenIds(pub Vec<u64>);
46
47impl From<Vec<u32>> for TokenIds {
48    #[inline]
49    fn from(v: Vec<u32>) -> Self {
50        TokenIds(v.into_iter().map(|n| n as u64).collect())
51    }
52}
53
54impl From<Vec<u64>> for TokenIds {
55    #[inline]
56    fn from(v: Vec<u64>) -> Self {
57        TokenIds(v)
58    }
59}
60
61impl From<Vec<usize>> for TokenIds {
62    #[inline]
63    fn from(v: Vec<usize>) -> Self {
64        TokenIds(v.into_iter().map(|n| n as u64).collect())
65    }
66}
67
68/// Low-level image data structure for multimodal models.
69#[derive(Clone, Serialize, Debug, Deserialize)]
70pub struct ImageData {
71    /// Raw base64 image bytes.
72    pub data: String,
73    /// Identifier referenced in the prompt (e.g. `[img-12]` in prompt corresponds to `id = 12`).
74    pub id: i64,
75}
76
77/// Specifies a LoRA (Low-Rank Adaptation) adapter to apply.
78#[derive(Clone, Serialize, Debug, Deserialize)]
79pub struct LoraAdapter {
80    pub id: i64,
81    pub scale: f32,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct GenerationSettings {
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub n_predict: Option<isize>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub grammar: Option<String>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub stop: Option<Vec<String>>,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub n_ctx: Option<usize>,
94
95    // ── Other fields seen in the server payload but not documented in the API ──
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub chat_format: Option<String>,
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub grammar_lazy: Option<bool>,
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub grammar_triggers: Option<Vec<String>>,
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub ignore_eos: Option<bool>,
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub lora: Option<Vec<LoraAdapter>>,
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub max_tokens: Option<isize>,
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub n_discard: Option<u64>,
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub post_sampling_probs: Option<bool>,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub preserved_tokens: Option<Vec<usize>>,
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub reasoning_format: Option<String>,
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub reasoning_in_content: Option<bool>,
118
119    // dotted keys → flat field names with serde renames
120    #[serde(rename = "speculative.n_max", skip_serializing_if = "Option::is_none")]
121    pub speculative_n_max: Option<u32>,
122    #[serde(rename = "speculative.n_min", skip_serializing_if = "Option::is_none")]
123    pub speculative_n_min: Option<u32>,
124    #[serde(rename = "speculative.p_min", skip_serializing_if = "Option::is_none")]
125    pub speculative_p_min: Option<f32>,
126
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub stream: Option<bool>,
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub thinking_forced_open: Option<bool>,
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub timings_per_token: Option<bool>,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub top_n_sigma: Option<f32>,
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub xtc_probability: Option<f32>,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub xtc_threshold: Option<f32>,
139    #[serde(flatten)]
140    pub sampling: SamplingParams,
141}
142
143// ──────────────────────────────────────────────────────────────────────────────
144//  Sampling parameters (all knobs that influence *how* tokens are chosen)
145// ──────────────────────────────────────────────────────────────────────────────
146#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
147#[builder(on(String, into))]
148#[builder(derive(Debug, Clone))]
149pub struct SamplingParams {
150    // ───────────────────────── basic samplers ────────────────────────────────
151    /// Temperature to control randomness in sampling.
152    ///
153    /// Higher values (e.g. `1.0` and above) produce more random output, while
154    /// lower values (e.g. `0.2`) make the output more deterministic and focused.
155    /// When using temperature, recommended values are typically between
156    /// `0.1` and `2.0`.  
157    /// *Default:* `0.8`.  
158    /// *Note:* If a negative temperature is given, the model will instead use
159    /// greedy sampling, i.e. always pick the highest‑probability token (you can
160    /// still request probabilities via `n_probs` in that case).
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub temperature: Option<f32>,
163
164    /// Limit the next token selection to the K most probable tokens.
165    ///
166    /// The top-K sampler will only consider this many most-likely tokens at each generation step. A smaller `top_k` means the model has fewer options (making it more deterministic), while a larger `top_k` (or 0) means essentially no restriction.
167    /// Default: `40`. (Set `0` to disable top-K filtering entirely.)
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub top_k: Option<u32>,
170
171    /// Limit the next token selection to a cumulative probability.
172    ///
173    /// The top-p (nucleus) sampler considers only the smallest set of tokens whose combined probability mass exceeds this threshold `p`. This dynamically limits the vocabulary considered at each step to a subset that sums to `p`. For example, `0.95` means ~95% of probable tokens are considered.
174    /// Default: `0.95`. (Set `1.0` to disable top-p filtering.)
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub top_p: Option<f32>,
177
178    /// Adjust the "typical probability" sampler threshold (locally typical sampling).
179    ///
180    /// This controls locally typical sampling with parameter `p`. When set below 1.0, at each step the model will prefer tokens whose probability is closer to the expected distribution (entropy) of the remaining options. A value of `1.0` disables this mechanism (no effect, since 100% typical).
181    /// Default: `1.0` (disabled).
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub typical_p: Option<f32>,
184
185    /// Ensure a minimum probability for tokens to be considered (min-p sampler).
186    ///
187    /// This sets a floor on token probabilities relative to the most likely token. At each step, any token with probability less than `min_p * (probability of best token)` will be excluded.
188    /// Default: `0.05`. (If set to `0.0`, the min-p filter is disabled.)
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub min_p: Option<f32>,
191
192    // ──────────────────────── dynamic temperature ────────────────────────────
193    /// Dynamic temperature range for sampling.
194    ///
195    /// If set, the effective temperature will be randomly chosen for each token within ± this range of the base `temperature`. For example, if `temperature = 0.8` and `dynatemp_range = 0.1`, then the actual temperature for each token will be in [0.7, 0.9].
196    /// Default: `0.0` (disabled, no dynamic variation in temperature).
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub dynatemp_range: Option<f32>,
199
200    /// Dynamic temperature exponent.
201    ///
202    /// This exponent modifies the distribution from which dynamic temperatures are drawn (when `dynatemp_range` is used). It influences how biased the random temperature selection is towards the edges or center of the range.
203    /// Default: `1.0` (linear distribution).
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub dynatemp_exponent: Option<f32>,
206
207    // ───────────────────── repetition / presence penalties ───────────────────
208    /// Repetition penalty factor.
209    ///
210    /// When greater than `1.0`, the model will penalize tokens that have already appeared, reducing their likelihood (discouraging repetition). A value of `1.0` means no repetition penalty (disabled), and values below `1.0` would *increase* the likelihood of repeats (not commonly used).
211    /// Default: `1.1` (slightly discouraging repetition).
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub repeat_penalty: Option<f32>,
214
215    /// Last-N repetition penalty context length.
216    ///
217    /// Only the last `repeat_last_n` tokens are considered when applying the repetition penalty. For example, if set to 64, the model will look at the 64 most recent tokens to penalize repeats. If set to `0`, repetition penalty is disabled entirely. If set to `-1`, the model's full context window is used.
218    /// Default: `64`.
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub repeat_last_n: Option<i64>,
221
222    /// Presence penalty (OpenAI-style).
223    ///
224    /// A positive presence penalty reduces the probability of any token that has already appeared in the text, regardless of frequency. It encourages the model to talk about new topics.
225    /// Default: `0.0` (no presence penalty).
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub presence_penalty: Option<f32>,
228
229    /// Frequency penalty (OpenAI-style).
230    ///
231    /// A positive frequency penalty reduces the probability of tokens in proportion to how often they have already appeared in the text. This helps prevent the model from repeating the same token frequently.
232    /// Default: `0.0` (no frequency penalty).
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub frequency_penalty: Option<f32>,
235
236    // ──────────────────────── DRY (“don’t repeat yourself”) ──────────────────
237    /// DRY (Don't Repeat Yourself) penalty multiplier.
238    ///
239    /// This enables an alternative repetition penalty mechanism. A multiplier > 0 activates DRY sampling: when the model starts to generate a sequence that repeats a recent sequence, it will incur an additional penalty. The penalty applied is determined by this multiplier and grows with the length of the repeating sequence (see `dry_base` and `dry_allowed_length`).
240    /// Default: `0.0` (DRY penalty disabled).
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub dry_multiplier: Option<f32>,
243
244    /// DRY penalty base value for exponential growth.
245    ///
246    /// When a repeating sequence is detected and exceeds the allowed length, the penalty is calculated exponentially as `multiplier * (dry_base)^(repetition_length - dry_allowed_length)`.
247    /// Default: `1.75`.
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub dry_base: Option<f32>,
250
251    /// Allowed repetition length for DRY penalty.
252    ///
253    /// Sequences of tokens can repeat up to this length without penalty. Once a repetition exceeds this length, the DRY penalty will start increasing exponentially.
254    /// Default: `2` (the penalty starts applying once a sequence of 3 or more tokens repeats).
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub dry_allowed_length: Option<u64>,
257
258    /// DRY penalty window (last-N tokens to consider for repetition).
259    ///
260    /// Determines how far back the model looks for repeated sequences when applying the DRY penalty. A value of `-1` means the entire context is considered. `0` disables DRY repetition checking entirely.
261    /// Default: `-1` (use the full context for detecting repeats).
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub dry_penalty_last_n: Option<i64>,
264
265    /// Sequence breakers for DRY sampling.
266    ///
267    /// A list of strings that, when encountered, will break the sequence for the purpose of DRY repetition detection. In other words, sequences separated by any of these "breakers" are not considered continuous for repetition penalty. By the breakers are newline (`"\n"`), colon (`":"`), double quote (`"\""`), and asterisk (`"*"`), which helps reset repetition at sentence or list boundaries, etc.
268    /// Providing a custom list will replace the defaults. You can also specify `["none"]` to indicate that no sequence breakers should be used at all.
269    /// Default: `["\n", ":", "\"", "*"]`.
270    #[serde(skip_serializing_if = "Option::is_none")]
271    pub dry_sequence_breakers: Option<Vec<String>>,
272
273    // ──────────────────────────── XTC sampling ───────────────────────────────
274    /// XTC (eXtreme Token Compression) removal probability.
275    ///
276    /// XTC sampling randomly removes low-probability tokens to potentially improve generation quality. This setting is the probability of applying the removal at each step. For example, `0.1` means a 10% chance that low-probability tokens (below the threshold) will be removed from consideration at each token generation step.
277    /// Default: `0.0` (XTC disabled).
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub xtc_probability: Option<f32>,
280
281    /// XTC removal probability threshold.
282    ///
283    /// If XTC is active (see `xtc_probability`), this value is the minimum probability a token must have to *avoid* being removed. Tokens with probability below this threshold are candidates for removal when the XTC mechanism triggers.
284    /// **Note:** If `xtc_threshold` is above `0.5`, XTC will effectively be disabled (since the threshold would be too high to remove any token in practice).
285    /// Default: `0.1`.
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub xtc_threshold: Option<f32>,
288
289    // ─────────────────────────── Mirostat sampling ───────────────────────────
290    /// Enable Mirostat sampling for dynamic perplexity control.
291    ///
292    /// Mirostat is an adaptive sampling algorithm that adjusts token selection to maintain a target perplexity (information content) in the generated text.
293    /// - Set to `1` to enable Mirostat (version 1.0), or `2` to enable Mirostat 2.0.
294    /// - If Mirostat is enabled, other sampling methods like top-K, top-p, and typical-p are ignored (Mirostat takes control of the token selection process).
295    /// Default: `0` (disabled).
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub mirostat: Option<u8>,
298
299    /// Mirostat target entropy (tau).
300    ///
301    /// This is the target entropy value for Mirostat sampling (often denoted as τ). It roughly corresponds to the desired perplexity of the model's responses. A higher value means allowing more uncertainty (higher entropy).
302    /// Default: `5.0`.
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub mirostat_tau: Option<f32>,
305
306    /// Mirostat learning rate (eta).
307    ///
308    /// This controls how quickly Mirostat adjusts its sampling to reach the target entropy. A smaller value makes the adjustments more gradual, while a larger value makes it adapt more aggressively.
309    /// Default: `0.1`.
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub mirostat_eta: Option<f32>,
312
313    // ───────────────────────── fine‑grained controls ─────────────────────────
314    /// Modify the likelihood of specific tokens appearing in the completion.
315    ///
316    /// This parameter allows fine-grained control over token selection by adjusting logits. It expects an array of pairs `[[token, bias], ...]` where `token` can be specified by its integer ID or as a string, and `bias` is a float value (or `false`). The bias is added to the model's logit for that token before sampling.
317    /// - A positive bias increases the token's probability (e.g. `1.0` adds a moderate boost, while `100` would make a token nearly guaranteed to be selected if possible).
318    /// - A negative bias decreases the token's probability (e.g. `-1.0` makes it less likely, and a very large negative like `-100` can effectively ban the token).
319    /// - You can also use a boolean `false` as the bias to explicitly forbid a token (equivalent to a very large negative bias).
320    /// **Examples:** `[[15043, 1.0]]` might increase the likelihood of the token with ID 15043 (which could be the word "Hello"), whereas `[[15043, false]]` would prevent that token from ever being generated. You can target sequences by providing text: e.g. `[["Hello, World!", -0.5]]` will reduce the likelihood of the exact sequence "Hello, World!" (by applying a penalty to each token in that sequence), similar to a specialized presence penalty for that phrase.
321    /// Default: `[]` (no bias adjustments; all tokens are considered with their default probabilities).
322    #[serde(skip_serializing_if = "Option::is_none")]
323    pub logit_bias: Option<Vec<Vec<serde_json::Value>>>,
324
325    /// Return top-N token probabilities with each generated token.
326    ///
327    /// If set to a value > 0, the response will include an array of the top N tokens (and their probabilities or log-probs) for each step of generation, in addition to the generated text. For example, if `n_probs = 5`, for each token generated the model will output the top 5 candidate tokens it considered and their probabilities at that step.
328    /// *Note:* When `temperature < 0` (greedy sampling) or other samplers are in effect, these probabilities are still computed from the raw model logits via a softmax, ignoring the filtering of those samplers.
329    /// Default: `0` (no probability info returned).
330    #[serde(skip_serializing_if = "Option::is_none")]
331    pub n_probs: Option<u32>,
332
333    /// Ensure samplers keep a minimum number of tokens.
334    ///
335    /// If > 0, this guarantees that each sampler in the chain will always leave at least this many token options. For instance, setting `min_keep = 1` ensures no sampler ever eliminates all tokens; setting `min_keep = 5` ensures at least 5 tokens remain possible after each sampling stage.
336    /// This can help avoid situations where aggressive sampling filters (like very low top_p combined with top_k) might remove all candidates.
337    /// Default: `0` (no minimum enforced beyond each sampler's own threshold).
338    #[serde(skip_serializing_if = "Option::is_none")]
339    pub min_keep: Option<u32>,
340
341    // ───────────────────── sampler chain & reproducibility ───────────────────
342    /// Sampler identifiers, encoded the same way the CLI/server expects.
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub samplers: Option<Vec<Sampler>>,
345
346    /// Random seed for generation.
347    ///
348    /// If you want reproducible results, set this to a specific integer. Use `-1` to randomize on each request.
349    /// Default: `-1` (random seed, different each run).
350    #[serde(skip_serializing_if = "Option::is_none")]
351    pub seed: Option<i64>,
352}
353
354/// Custom sampler ordering for generation.
355///
356/// By the model applies samplers in a specific chain (penalties, then dry run, top-K, typical-P, top-P, min-P, XTC, temperature). This field allows you to override that order or choose a subset of samplers. Provide an array of sampler names in the exact order you want them applied.
357/// Available sampler names include: `"dry"`, `"top_k"`, `"typ_p"`, `"top_p"`, `"min_p"`, `"xtc"`, `"temperature"`, and `"penalties"` (which covers repeat and presence/frequency penalties). If a sampler name is omitted from the list, that sampling method will not be used. If a name appears multiple times, that sampler will be applied multiple times in sequence.
358/// Default order (using all samplers) is: `["dry", "top_k", "typ_p", "top_p", "min_p", "xtc", "temperature"]`.
359#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
360#[serde(rename_all = "snake_case")]
361pub enum Sampler {
362    Dry,
363    TopK,
364    TypP,
365    TopP,
366    MinP,
367    Xtc,
368    Temperature,
369    Penalties,
370    /// Any sampler we don’t recognise yet (future-proofing).
371    #[serde(other)]
372    Unknown,
373}
374
375impl std::fmt::Display for Sampler {
376    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377        f.write_str(match self {
378            Sampler::Dry => "dry",
379            Sampler::TopK => "top_k",
380            Sampler::TypP => "typ_p",
381            Sampler::TopP => "top_p",
382            Sampler::MinP => "min_p",
383            Sampler::Xtc => "xtc",
384            Sampler::Temperature => "temperature",
385            Sampler::Penalties => "penalties",
386            Sampler::Unknown => "unknown",
387        })
388    }
389}
390
391/// Pooling strategy for embedding vectors. Applicable if `embeddings_only` mode
392/// is used. Options: `"none"` (no pooling, possibly return per-token embeddings),
393/// `"mean"` (average all token embeddings), `"cls"` (use the first token's embedding,
394/// e.g., [CLS] token), `"last"` (use the last token's embedding), `"rank"` (use a
395/// specialized pooling for reranker models).  
396///
397/// *The lowercase Serde mapping means `"None"` serializes as `"none"`*,
398/// which is exactly what the llama.cpp server expects.
399#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
400#[serde(rename_all = "lowercase")]
401pub enum Pooling {
402    None,
403    Mean,
404    Cls,
405    Last,
406    Rank, // used by reranker models
407    /// Any value we don’t model yet (future-proofing).
408    #[serde(other)]
409    Unknown,
410}
411
412impl Arg for Pooling {
413    fn append_arg(&self, command: &mut std::process::Command) {
414        command.arg(format!("{}", self));
415    }
416}
417
418impl std::fmt::Display for Pooling {
419    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
420        // Write the exact lowercase string the server flag expects.
421        f.write_str(match self {
422            Pooling::None => "none",
423            Pooling::Mean => "mean",
424            Pooling::Cls => "cls",
425            Pooling::Last => "last",
426            Pooling::Rank => "rank",
427            Pooling::Unknown => "unknown",
428        })
429    }
430}