kernel/profiles/
context_budget.rs1use crate::profiles::configuration::normalized_param_values;
5use crate::records::{JsonValue, ModelRecord, RuntimeId, SourceKind};
6
7pub const COMPLETION_FLOOR: i64 = 256;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum Verdict {
14 Fits {
17 clamped_max_tokens: Option<i64>,
19 },
20 Exceeds {
22 estimated: i64,
24 window: i64,
26 },
27}
28
29pub fn estimated_tokens(characters: i64) -> i64 {
32 (characters + 3) / 4
33}
34
35pub fn assess(prompt_characters: i64, window: i64, requested_max_tokens: Option<i64>) -> Verdict {
38 let estimated = estimated_tokens(prompt_characters);
39 if estimated + COMPLETION_FLOOR > window {
40 return Verdict::Exceeds { estimated, window };
41 }
42 let available = window - estimated;
43 let clamped = requested_max_tokens.unwrap_or(available).min(available);
44 Verdict::Fits {
45 clamped_max_tokens: Some(clamped),
46 }
47}
48
49pub fn effective_window(
53 record: &ModelRecord,
54 requested_context_length: Option<i64>,
55) -> Option<i64> {
56 if record.source.kind == SourceKind::builtin() {
57 return Some(4096);
58 }
59 let window = record_policy_window(record, requested_context_length)?;
60 (window > 0).then_some(window)
61}
62
63fn record_policy_window(record: &ModelRecord, requested: Option<i64>) -> Option<i64> {
64 let id = record.runtime.id.as_ref()?;
65 if *id == RuntimeId::ollama() {
66 requested.or(record.context_length)
67 } else if *id == RuntimeId::llama_cpp()
68 || *id == RuntimeId::mlx_swift()
69 || *id == RuntimeId::mlx_lm()
70 {
71 record.context_length
72 } else {
73 None
74 }
75}
76
77pub fn stored_context_length(record: &ModelRecord) -> Option<i64> {
79 normalized_param_values(record)
80 .get("context_length")
81 .and_then(JsonValue::as_i64)
82}
83
84pub fn prompt_characters(payload: &JsonValue) -> i64 {
87 let JsonValue::Object(object) = payload else {
88 return 0;
89 };
90 let mut total = 0i64;
91 if let Some(JsonValue::Array(messages)) = object.get("messages") {
92 for message in messages {
93 if let JsonValue::Object(fields) = message
94 && let Some(JsonValue::String(content)) = fields.get("content")
95 {
96 total += content.chars().count() as i64;
97 }
98 }
99 }
100 if let Some(JsonValue::String(prompt)) = object.get("prompt") {
101 total += prompt.chars().count() as i64;
102 }
103 total
104}