correct thinking modes for each model
This commit is contained in:
@@ -14,9 +14,10 @@ agent-extension data stay on this Mac.
|
|||||||
and chat data.
|
and chat data.
|
||||||
3. Choose **File > New Chat** (`⌘N`). Select a thinking profile and a shell
|
3. Choose **File > New Chat** (`⌘N`). Select a thinking profile and a shell
|
||||||
permission mode in the composer.
|
permission mode in the composer.
|
||||||
4. Enter a request and send it. **Thinking** is the DS4 default, **Direct** omits
|
4. Enter a request and send it. The choices match the selected model: DeepSeek
|
||||||
hidden reasoning, and **Think Max** appears only when that model profile has
|
offers **Direct**, **Think Low**, **Think High**, and **Think Max**; GLM 5.2
|
||||||
the required long context.
|
offers **Direct**, **Think High**, and **Think Max**; GLM 5.3 Flash always
|
||||||
|
thinks and offers **Think Low**, **Think High**, and **Think Max**.
|
||||||
|
|
||||||
The selected model is shared by the app and the optional local HTTP endpoint.
|
The selected model is shared by the app and the optional local HTTP endpoint.
|
||||||
Preferences are saved locally and generation settings are scoped by model and
|
Preferences are saved locally and generation settings are scoped by model and
|
||||||
@@ -219,6 +220,13 @@ client and do not appear in the sidebar. CORS is off by default; enable it only
|
|||||||
for trusted browser clients on this Mac. Saving changed endpoint settings
|
for trusted browser clients on this Mac. Saving changed endpoint settings
|
||||||
restarts the local listener.
|
restarts the local listener.
|
||||||
|
|
||||||
|
`reasoning_effort` accepts only the selected model's advertised values from
|
||||||
|
`GET /v1/models`: DeepSeek accepts `none`, `low`, `high`, or `max`; GLM 5.2
|
||||||
|
accepts `none`, `high`, or `max`; GLM 5.3 Flash accepts `low`, `high`, or `max`.
|
||||||
|
Omitting it uses the upstream model default: `low` for DeepSeek and `max` for
|
||||||
|
both GLM models. Unsupported values and conflicting thinking controls return a
|
||||||
|
400 error instead of being converted to another effort.
|
||||||
|
|
||||||
### Data and recovery
|
### Data and recovery
|
||||||
|
|
||||||
Application data is under
|
Application data is under
|
||||||
|
|||||||
@@ -1737,15 +1737,14 @@ impl App {
|
|||||||
.supported_reasoning_modes(self.config.model)
|
.supported_reasoning_modes(self.config.model)
|
||||||
.contains(&mode)
|
.contains(&mode)
|
||||||
{
|
{
|
||||||
self.error = Some(
|
self.error = Some(format!("{mode} is not supported by {}.", self.config.model));
|
||||||
"Think Max requires a context window of at least 393216 tokens.".into(),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
let mut config = self.config.clone();
|
let mut config = self.config.clone();
|
||||||
|
let model = config.model;
|
||||||
config
|
config
|
||||||
.model_profiles
|
.model_profiles
|
||||||
.entry(config.model)
|
.entry(model)
|
||||||
.or_default()
|
.or_insert_with(|| ModelPreferences::defaults_for(model))
|
||||||
.reasoning_mode = mode;
|
.reasoning_mode = mode;
|
||||||
match config.save(&config_path()) {
|
match config.save(&config_path()) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ impl PreferenceDraft {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn select_generation(&mut self, model: ModelChoice, mode: ReasoningMode) -> Result<(), String> {
|
fn select_generation(&mut self, model: ModelChoice, mode: ReasoningMode) -> Result<(), String> {
|
||||||
|
model.validate_reasoning_mode(mode)?;
|
||||||
self.store_generation()?;
|
self.store_generation()?;
|
||||||
self.load_generation(model, mode);
|
self.load_generation(model, mode);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -245,14 +246,18 @@ impl PreferenceDraft {
|
|||||||
let profile = self
|
let profile = self
|
||||||
.model_profiles
|
.model_profiles
|
||||||
.entry(self.acceleration_model)
|
.entry(self.acceleration_model)
|
||||||
.or_default();
|
.or_insert_with(|| ModelPreferences::defaults_for(self.acceleration_model));
|
||||||
profile.speculative = speculative;
|
profile.speculative = speculative;
|
||||||
profile.ssd = ssd;
|
profile.ssd = ssd;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_acceleration(&mut self, model: ModelChoice) {
|
fn load_acceleration(&mut self, model: ModelChoice) {
|
||||||
let profile = self.model_profiles.get(&model).cloned().unwrap_or_default();
|
let profile = self
|
||||||
|
.model_profiles
|
||||||
|
.get(&model)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| ModelPreferences::defaults_for(model));
|
||||||
let speculative = profile.speculative;
|
let speculative = profile.speculative;
|
||||||
let ssd = profile.ssd;
|
let ssd = profile.ssd;
|
||||||
self.acceleration_model = model;
|
self.acceleration_model = model;
|
||||||
@@ -301,9 +306,10 @@ impl PreferenceDraft {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn reasoning_mode_for(&self, model: ModelChoice) -> ReasoningMode {
|
pub(super) fn reasoning_mode_for(&self, model: ModelChoice) -> ReasoningMode {
|
||||||
self.model_profiles
|
self.model_profiles.get(&model).map_or_else(
|
||||||
.get(&model)
|
|| model.default_reasoning_mode(),
|
||||||
.map_or(ReasoningMode::default(), |profile| profile.reasoning_mode)
|
|profile| profile.reasoning_mode,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn runtime(&self) -> Result<RuntimePreferences, String> {
|
pub(super) fn runtime(&self) -> Result<RuntimePreferences, String> {
|
||||||
@@ -518,7 +524,7 @@ impl App {
|
|||||||
self.preference_draft
|
self.preference_draft
|
||||||
.model_profiles
|
.model_profiles
|
||||||
.entry(self.preference_draft.model)
|
.entry(self.preference_draft.model)
|
||||||
.or_default()
|
.or_insert_with(|| ModelPreferences::defaults_for(self.preference_draft.model))
|
||||||
.reasoning_mode = self.preference_draft.default_reasoning_mode;
|
.reasoning_mode = self.preference_draft.default_reasoning_mode;
|
||||||
let config = Config {
|
let config = Config {
|
||||||
model: self.preference_draft.model,
|
model: self.preference_draft.model,
|
||||||
@@ -684,7 +690,7 @@ impl App {
|
|||||||
self.preference_draft
|
self.preference_draft
|
||||||
.model_profiles
|
.model_profiles
|
||||||
.entry(self.preference_draft.model)
|
.entry(self.preference_draft.model)
|
||||||
.or_default()
|
.or_insert_with(|| ModelPreferences::defaults_for(self.preference_draft.model))
|
||||||
.reasoning_mode = mode;
|
.reasoning_mode = mode;
|
||||||
self.preference_error = None;
|
self.preference_error = None;
|
||||||
}
|
}
|
||||||
@@ -1016,7 +1022,7 @@ mod tests {
|
|||||||
assert_eq!(draft.context_tokens, "32768");
|
assert_eq!(draft.context_tokens, "32768");
|
||||||
draft.context_tokens = "456".into();
|
draft.context_tokens = "456".into();
|
||||||
draft
|
draft
|
||||||
.select_generation(ModelChoice::DeepSeekV4Flash0731, ReasoningMode::High)
|
.select_generation(ModelChoice::DeepSeekV4Flash0731, ReasoningMode::Low)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(draft.context_tokens, "123");
|
assert_eq!(draft.context_tokens, "123");
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use crate::database::{ProjectWithSessions, Session, SessionState};
|
|||||||
use crate::model::{
|
use crate::model::{
|
||||||
self, DownloadPhase, MODEL_CHOICES, ManagedArtifact, ManagedArtifactState, ModelChoice,
|
self, DownloadPhase, MODEL_CHOICES, ManagedArtifact, ManagedArtifactState, ModelChoice,
|
||||||
};
|
};
|
||||||
use crate::settings::{GIB, REASONING_MODES};
|
use crate::settings::GIB;
|
||||||
use iced::theme::{Palette, palette};
|
use iced::theme::{Palette, palette};
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
Button, Space, Svg, Tooltip, button, column, container, image, markdown, mouse_area, opaque,
|
Button, Space, Svg, Tooltip, button, column, container, image, markdown, mouse_area, opaque,
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ impl App {
|
|||||||
"Thinking mode selected when this model becomes active. Chat can switch it for later turns.",
|
"Thinking mode selected when this model becomes active. Chat can switch it for later turns.",
|
||||||
),
|
),
|
||||||
pick_list(
|
pick_list(
|
||||||
&REASONING_MODES[..],
|
self.preference_draft.model.reasoning_modes(),
|
||||||
Some(self.preference_draft.default_reasoning_mode),
|
Some(self.preference_draft.default_reasoning_mode),
|
||||||
Message::PreferenceDefaultReasoningChanged,
|
Message::PreferenceDefaultReasoningChanged,
|
||||||
)
|
)
|
||||||
@@ -462,7 +462,7 @@ impl App {
|
|||||||
row![
|
row![
|
||||||
text("Thinking mode").size(13).width(Length::Fill),
|
text("Thinking mode").size(13).width(Length::Fill),
|
||||||
pick_list(
|
pick_list(
|
||||||
&REASONING_MODES[..],
|
self.preference_draft.generation_model.reasoning_modes(),
|
||||||
Some(self.preference_draft.generation_reasoning_mode),
|
Some(self.preference_draft.generation_reasoning_mode),
|
||||||
Message::PreferenceGenerationReasoningChanged,
|
Message::PreferenceGenerationReasoningChanged,
|
||||||
)
|
)
|
||||||
@@ -513,7 +513,7 @@ impl App {
|
|||||||
text_input("Random", &self.preference_draft.seed)
|
text_input("Random", &self.preference_draft.seed)
|
||||||
.on_input(Message::PreferenceSeedChanged),
|
.on_input(Message::PreferenceSeedChanged),
|
||||||
),
|
),
|
||||||
text("Blank sampling values retain DS4's model-family defaults. Think Max needs at least 393216 context tokens.")
|
text("Blank sampling values retain the model-family defaults.")
|
||||||
.size(12),
|
.size(12),
|
||||||
text(turn.map_or_else(
|
text(turn.map_or_else(
|
||||||
|| "Effective settings will appear after valid values are entered.".to_owned(),
|
|| "Effective settings will appear after valid values are entered.".to_owned(),
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use std::path::Path;
|
|||||||
|
|
||||||
use crate::model::{MODEL_CHOICES, ModelChoice};
|
use crate::model::{MODEL_CHOICES, ModelChoice};
|
||||||
use crate::settings::{
|
use crate::settings::{
|
||||||
DEFAULT_SYSTEM_PROMPT, GenerationPreferences, REASONING_MODES, ReasoningMode,
|
DEFAULT_SYSTEM_PROMPT, GenerationPreferences, ReasoningMode, RuntimePreferences,
|
||||||
RuntimePreferences, SpeculativePreferences, SsdPreferences,
|
SpeculativePreferences, SsdPreferences,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
|
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||||
@@ -20,10 +20,15 @@ pub(crate) struct ModelPreferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ModelPreferences {
|
impl ModelPreferences {
|
||||||
fn defaults_for(model: ModelChoice) -> Self {
|
pub(crate) fn defaults_for(model: ModelChoice) -> Self {
|
||||||
let mut preferences = Self::default();
|
Self {
|
||||||
preferences.speculative.dspark_enabled = model == ModelChoice::DeepSeekV4Flash0731;
|
reasoning_mode: model.default_reasoning_mode(),
|
||||||
preferences
|
speculative: SpeculativePreferences {
|
||||||
|
dspark_enabled: model == ModelChoice::DeepSeekV4Flash0731,
|
||||||
|
..SpeculativePreferences::default()
|
||||||
|
},
|
||||||
|
ssd: SsdPreferences::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,8 +68,10 @@ impl Default for Config {
|
|||||||
.map(|model| {
|
.map(|model| {
|
||||||
(
|
(
|
||||||
model,
|
model,
|
||||||
REASONING_MODES
|
model
|
||||||
.into_iter()
|
.reasoning_modes()
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
.map(|mode| (mode, GenerationPreferences::default()))
|
.map(|mode| (mode, GenerationPreferences::default()))
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
@@ -302,9 +309,20 @@ impl Config {
|
|||||||
drop_legacy_model_settings(&mut value);
|
drop_legacy_model_settings(&mut value);
|
||||||
let migrated = migrate_deprecated_flash(&mut value);
|
let migrated = migrate_deprecated_flash(&mut value);
|
||||||
let dspark_explicit = deepseek_0731_dspark_is_explicit(&value);
|
let dspark_explicit = deepseek_0731_dspark_is_explicit(&value);
|
||||||
|
let explicit_reasoning =
|
||||||
|
MODEL_CHOICES.map(|model| (model, model_reasoning_mode_is_explicit(&value, model)));
|
||||||
let mut config: Self = serde_norway::from_value(value)
|
let mut config: Self = serde_norway::from_value(value)
|
||||||
.map_err(|error| format!("Could not read {}: {error}", path.display()))?;
|
.map_err(|error| format!("Could not read {}: {error}", path.display()))?;
|
||||||
config.fill_profile_defaults();
|
config.fill_profile_defaults();
|
||||||
|
for (model, explicit) in explicit_reasoning {
|
||||||
|
if !explicit {
|
||||||
|
config
|
||||||
|
.model_profiles
|
||||||
|
.get_mut(&model)
|
||||||
|
.expect("model profile is present")
|
||||||
|
.reasoning_mode = model.default_reasoning_mode();
|
||||||
|
}
|
||||||
|
}
|
||||||
if !dspark_explicit {
|
if !dspark_explicit {
|
||||||
config
|
config
|
||||||
.model_profiles
|
.model_profiles
|
||||||
@@ -344,6 +362,7 @@ impl Config {
|
|||||||
}
|
}
|
||||||
for (model, profiles) in &self.generation_profiles {
|
for (model, profiles) in &self.generation_profiles {
|
||||||
for (mode, generation) in profiles {
|
for (mode, generation) in profiles {
|
||||||
|
model.validate_reasoning_mode(*mode)?;
|
||||||
let mut generation = generation.clone();
|
let mut generation = generation.clone();
|
||||||
generation.system_prompt = self.system_prompt.clone();
|
generation.system_prompt = self.system_prompt.clone();
|
||||||
generation.reasoning_mode = *mode;
|
generation.reasoning_mode = *mode;
|
||||||
@@ -355,24 +374,17 @@ impl Config {
|
|||||||
for (model, preferences) in &self.model_profiles {
|
for (model, preferences) in &self.model_profiles {
|
||||||
preferences.speculative.validate(*model)?;
|
preferences.speculative.validate(*model)?;
|
||||||
preferences.ssd.validate(*model)?;
|
preferences.ssd.validate(*model)?;
|
||||||
if preferences.reasoning_mode == ReasoningMode::Max
|
model.validate_reasoning_mode(preferences.reasoning_mode)?;
|
||||||
&& !self
|
|
||||||
.supported_reasoning_modes(*model)
|
|
||||||
.contains(&ReasoningMode::Max)
|
|
||||||
{
|
|
||||||
return Err(format!(
|
|
||||||
"Default thinking mode for {model} requires its Think Max profile to use at least 393216 context tokens."
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.runtime_for(self.model).validate(self.model)?;
|
self.runtime_for(self.model).validate(self.model)?;
|
||||||
self.dev_brain.validate()
|
self.dev_brain.validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn reasoning_mode(&self, model: ModelChoice) -> ReasoningMode {
|
pub(crate) fn reasoning_mode(&self, model: ModelChoice) -> ReasoningMode {
|
||||||
self.model_profiles
|
self.model_profiles.get(&model).map_or_else(
|
||||||
.get(&model)
|
|| model.default_reasoning_mode(),
|
||||||
.map_or(ReasoningMode::default(), |profile| profile.reasoning_mode)
|
|profile| profile.reasoning_mode,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn generation_for(
|
pub(crate) fn generation_for(
|
||||||
@@ -405,8 +417,7 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn supported_reasoning_modes(&self, model: ModelChoice) -> &'static [ReasoningMode] {
|
pub(crate) fn supported_reasoning_modes(&self, model: ModelChoice) -> &'static [ReasoningMode] {
|
||||||
let max = self.generation_for(model, ReasoningMode::Max);
|
model.reasoning_modes()
|
||||||
max.supported_reasoning_modes()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill_profile_defaults(&mut self) {
|
fn fill_profile_defaults(&mut self) {
|
||||||
@@ -415,13 +426,24 @@ impl Config {
|
|||||||
.entry(model)
|
.entry(model)
|
||||||
.or_insert_with(|| ModelPreferences::defaults_for(model));
|
.or_insert_with(|| ModelPreferences::defaults_for(model));
|
||||||
let profiles = self.generation_profiles.entry(model).or_default();
|
let profiles = self.generation_profiles.entry(model).or_default();
|
||||||
for mode in REASONING_MODES {
|
for &mode in model.reasoning_modes() {
|
||||||
profiles.entry(mode).or_default();
|
profiles.entry(mode).or_default();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn model_reasoning_mode_is_explicit(value: &Value, model: ModelChoice) -> bool {
|
||||||
|
let Value::Mapping(root) = value else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
root.get(Value::String("model_profiles".into()))
|
||||||
|
.and_then(Value::as_mapping)
|
||||||
|
.and_then(|profiles| profiles.get(Value::String(model.id().into())))
|
||||||
|
.and_then(Value::as_mapping)
|
||||||
|
.is_some_and(|profile| profile.contains_key(Value::String("reasoning_mode".into())))
|
||||||
|
}
|
||||||
|
|
||||||
fn deepseek_0731_dspark_is_explicit(value: &Value) -> bool {
|
fn deepseek_0731_dspark_is_explicit(value: &Value) -> bool {
|
||||||
let Value::Mapping(root) = value else {
|
let Value::Mapping(root) = value else {
|
||||||
return false;
|
return false;
|
||||||
@@ -529,6 +551,34 @@ mod tests {
|
|||||||
fs::remove_dir_all(&directory).unwrap();
|
fs::remove_dir_all(&directory).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn model_profiles_expose_only_their_upstream_thinking_modes() {
|
||||||
|
let config = Config::default();
|
||||||
|
for model in MODEL_CHOICES {
|
||||||
|
assert_eq!(config.reasoning_mode(model), model.default_reasoning_mode());
|
||||||
|
assert_eq!(
|
||||||
|
config.supported_reasoning_modes(model),
|
||||||
|
model.reasoning_modes()
|
||||||
|
);
|
||||||
|
let profiles = config.generation_profiles.get(&model).unwrap();
|
||||||
|
assert_eq!(profiles.len(), model.reasoning_modes().len());
|
||||||
|
assert!(
|
||||||
|
model
|
||||||
|
.reasoning_modes()
|
||||||
|
.iter()
|
||||||
|
.all(|mode| profiles.contains_key(mode))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut invalid = config;
|
||||||
|
invalid
|
||||||
|
.model_profiles
|
||||||
|
.get_mut(&ModelChoice::Glm53Flash)
|
||||||
|
.unwrap()
|
||||||
|
.reasoning_mode = ReasoningMode::Direct;
|
||||||
|
assert!(invalid.validate().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn explicit_0731_dspark_opt_out_survives_the_default() {
|
fn explicit_0731_dspark_opt_out_survives_the_default() {
|
||||||
let directory =
|
let directory =
|
||||||
|
|||||||
@@ -1690,12 +1690,13 @@ fn conversation_key(system: &str, reasoning: ReasoningMode, messages: &[ChatTurn
|
|||||||
output.extend_from_slice(value.as_bytes());
|
output.extend_from_slice(value.as_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut output = b"DS4Server chat checkpoint v4".to_vec();
|
let mut output = b"DS4Server chat checkpoint v5".to_vec();
|
||||||
text(&mut output, system);
|
text(&mut output, system);
|
||||||
output.push(match reasoning {
|
output.push(match reasoning {
|
||||||
ReasoningMode::Direct => 0,
|
ReasoningMode::Direct => 0,
|
||||||
ReasoningMode::High => 1,
|
ReasoningMode::Low => 1,
|
||||||
ReasoningMode::Max => 2,
|
ReasoningMode::High => 2,
|
||||||
|
ReasoningMode::Max => 3,
|
||||||
});
|
});
|
||||||
for message in messages {
|
for message in messages {
|
||||||
output.push(u8::from(message.user));
|
output.push(u8::from(message.user));
|
||||||
|
|||||||
@@ -6,9 +6,23 @@ use super::{
|
|||||||
use crate::settings::ReasoningMode;
|
use crate::settings::ReasoningMode;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
const MAX_REASONING_PREFIX: &str = "Reasoning Effort: Absolute maximum with no shortcuts permitted.\n\
|
const HIGH_REASONING_PREFIX: &str = "Reasoning Effort: Absolute maximum with no shortcuts permitted.\n\
|
||||||
You MUST be very thorough in your thinking and comprehensively decompose the problem to resolve the root cause, rigorously stress-testing your logic against all potential paths, edge cases, and adversarial scenarios.\n\
|
You MUST be very thorough in your thinking and comprehensively decompose the problem to resolve the root cause, rigorously stress-testing your logic against all potential paths, edge cases, and adversarial scenarios.\n\
|
||||||
Explicitly write out your entire deliberation process, documenting every intermediate step, considered alternative, and rejected hypothesis to ensure absolutely no assumption is left unchecked.\n\n";
|
Explicitly write out your entire deliberation process, documenting every intermediate step, considered alternative, and rejected hypothesis to ensure absolutely no assumption is left unchecked.\n\n";
|
||||||
|
const MAX_REASONING_PREFIX: &str = "Reasoning Effort: Beyond maximum — exhaustive, relentless, and uncompromising.\n\
|
||||||
|
You MUST reason with the utmost depth and rigor, leaving absolutely nothing to chance: exhaustively decompose the problem into its most fundamental components, trace every causal chain to its root, and resolve the underlying cause rather than any surface symptom.\n\
|
||||||
|
Do not stop reasoning until you have independently verified the solution from multiple angles and are certain that no assumption remains unchecked and no error remains undiscovered.\n\n";
|
||||||
|
|
||||||
|
fn reasoning_prefix(family: ModelFamily, reasoning: ReasoningMode) -> Option<&'static str> {
|
||||||
|
match (family, reasoning) {
|
||||||
|
(ModelFamily::DeepSeek, ReasoningMode::High) => Some(HIGH_REASONING_PREFIX),
|
||||||
|
(ModelFamily::DeepSeek, ReasoningMode::Max) => Some(MAX_REASONING_PREFIX),
|
||||||
|
(ModelFamily::Glm, ReasoningMode::Low) => Some("Reasoning Effort: Low"),
|
||||||
|
(ModelFamily::Glm, ReasoningMode::High) => Some("Reasoning Effort: High"),
|
||||||
|
(ModelFamily::Glm, ReasoningMode::Max) => Some("Reasoning Effort: Max"),
|
||||||
|
(_, ReasoningMode::Direct | ReasoningMode::Low) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) struct Tokenizer {
|
pub(super) struct Tokenizer {
|
||||||
family: ModelFamily,
|
family: ModelFamily,
|
||||||
@@ -254,19 +268,11 @@ impl Tokenizer {
|
|||||||
if self.family == ModelFamily::Glm && self.sop >= 0 {
|
if self.family == ModelFamily::Glm && self.sop >= 0 {
|
||||||
output.push(self.sop);
|
output.push(self.sop);
|
||||||
}
|
}
|
||||||
match (self.family, reasoning) {
|
if let Some(prefix) = reasoning_prefix(self.family, reasoning) {
|
||||||
(ModelFamily::Glm, ReasoningMode::High | ReasoningMode::Max) => {
|
if self.family == ModelFamily::Glm {
|
||||||
output.push(self.system);
|
output.push(self.system);
|
||||||
output.extend(self.tokenize(if reasoning == ReasoningMode::Max {
|
|
||||||
"Reasoning Effort: Max"
|
|
||||||
} else {
|
|
||||||
"Reasoning Effort: High"
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
(ModelFamily::DeepSeek, ReasoningMode::Max) => {
|
output.extend(self.tokenize(prefix));
|
||||||
output.extend(self.tokenize(MAX_REASONING_PREFIX));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
if !system_prompt.is_empty() {
|
if !system_prompt.is_empty() {
|
||||||
if self.family == ModelFamily::Glm {
|
if self.family == ModelFamily::Glm {
|
||||||
@@ -843,4 +849,32 @@ mod tests {
|
|||||||
assert_eq!(next_char("", 0), 0);
|
assert_eq!(next_char("", 0), 0);
|
||||||
assert!(!cjk_at("", 0));
|
assert!(!cjk_at("", 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reasoning_prefixes_match_each_model_family_contract() {
|
||||||
|
assert_eq!(
|
||||||
|
reasoning_prefix(ModelFamily::DeepSeek, ReasoningMode::Low),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
reasoning_prefix(ModelFamily::DeepSeek, ReasoningMode::High),
|
||||||
|
Some(HIGH_REASONING_PREFIX)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
reasoning_prefix(ModelFamily::DeepSeek, ReasoningMode::Max),
|
||||||
|
Some(MAX_REASONING_PREFIX)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
reasoning_prefix(ModelFamily::Glm, ReasoningMode::Low),
|
||||||
|
Some("Reasoning Effort: Low")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
reasoning_prefix(ModelFamily::Glm, ReasoningMode::High),
|
||||||
|
Some("Reasoning Effort: High")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
reasoning_prefix(ModelFamily::Glm, ReasoningMode::Max),
|
||||||
|
Some("Reasoning Effort: Max")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -540,7 +540,12 @@ fn model_json(id: &str, model: ModelChoice, context: i32, default_tokens: i32) -
|
|||||||
"supported_parameters": [
|
"supported_parameters": [
|
||||||
"tools", "tool_choice", "max_tokens", "temperature", "top_p",
|
"tools", "tool_choice", "max_tokens", "temperature", "top_p",
|
||||||
"top_k", "min_p", "stop", "seed", "stream", "reasoning_effort"
|
"top_k", "min_p", "stop", "seed", "stream", "reasoning_effort"
|
||||||
]
|
],
|
||||||
|
"supported_reasoning_efforts": model
|
||||||
|
.reasoning_modes()
|
||||||
|
.iter()
|
||||||
|
.map(|mode| mode.as_id())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,10 +560,8 @@ fn model_alias(id: &str) -> Option<ModelChoice> {
|
|||||||
| "zai/glm-5.2-chat"
|
| "zai/glm-5.2-chat"
|
||||||
| "zai/glm-5.2-reasoner" => Some(ModelChoice::Glm52),
|
| "zai/glm-5.2-reasoner" => Some(ModelChoice::Glm52),
|
||||||
"glm-5.3-flash"
|
"glm-5.3-flash"
|
||||||
| "glm-5.3-flash-chat"
|
|
||||||
| "glm-5.3-flash-reasoner"
|
| "glm-5.3-flash-reasoner"
|
||||||
| "zai/glm-5.3-flash"
|
| "zai/glm-5.3-flash"
|
||||||
| "zai/glm-5.3-flash-chat"
|
|
||||||
| "zai/glm-5.3-flash-reasoner" => Some(ModelChoice::Glm53Flash),
|
| "zai/glm-5.3-flash-reasoner" => Some(ModelChoice::Glm53Flash),
|
||||||
_ => ModelChoice::from_id(id),
|
_ => ModelChoice::from_id(id),
|
||||||
}
|
}
|
||||||
@@ -1023,6 +1026,7 @@ mod tests {
|
|||||||
model_alias("deepseek-reasoner"),
|
model_alias("deepseek-reasoner"),
|
||||||
Some(ModelChoice::DeepSeekV4Flash0731)
|
Some(ModelChoice::DeepSeekV4Flash0731)
|
||||||
);
|
);
|
||||||
|
assert_eq!(model_alias("glm-5.3-flash-chat"), None);
|
||||||
let model = model_json(
|
let model = model_json(
|
||||||
"deepseek-reasoner",
|
"deepseek-reasoner",
|
||||||
ModelChoice::DeepSeekV4Flash0731,
|
ModelChoice::DeepSeekV4Flash0731,
|
||||||
@@ -1031,6 +1035,18 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(model["id"], "deepseek-reasoner");
|
assert_eq!(model["id"], "deepseek-reasoner");
|
||||||
assert_eq!(model["top_provider"]["max_completion_tokens"], 32_768);
|
assert_eq!(model["top_provider"]["max_completion_tokens"], 32_768);
|
||||||
|
assert_eq!(
|
||||||
|
model["supported_reasoning_efforts"],
|
||||||
|
json!(["low", "high", "max", "none"])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model_json("glm-5.2", ModelChoice::Glm52, 32_768, 50_000)["supported_reasoning_efforts"],
|
||||||
|
json!(["high", "max", "none"])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model_json("glm-5.3-flash", ModelChoice::Glm53Flash, 32_768, 50_000)["supported_reasoning_efforts"],
|
||||||
|
json!(["low", "high", "max"])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -534,7 +534,7 @@ pub(super) fn parse_chat_request(
|
|||||||
return Err((400, format!("model is not installed and verified: {model}")));
|
return Err((400, format!("model is not installed and verified: {model}")));
|
||||||
}
|
}
|
||||||
|
|
||||||
let reasoning_mode = request_reasoning(&request, requested_id)?;
|
let reasoning_mode = request_reasoning(&request, model, requested_id)?;
|
||||||
let mut generation = config.generation_for(model, reasoning_mode);
|
let mut generation = config.generation_for(model, reasoning_mode);
|
||||||
generation.max_generated_tokens = request
|
generation.max_generated_tokens = request
|
||||||
.max_completion_tokens
|
.max_completion_tokens
|
||||||
@@ -594,31 +594,43 @@ pub(super) fn parse_chat_request(
|
|||||||
|
|
||||||
fn request_reasoning(
|
fn request_reasoning(
|
||||||
request: &ChatRequest,
|
request: &ChatRequest,
|
||||||
|
model: ModelChoice,
|
||||||
model_id: &str,
|
model_id: &str,
|
||||||
) -> Result<ReasoningMode, (u16, String)> {
|
) -> Result<ReasoningMode, (u16, String)> {
|
||||||
let explicit_thinking = request
|
let explicit_thinking = request
|
||||||
.think
|
.think
|
||||||
.or_else(|| request.thinking.as_ref().and_then(thinking_enabled));
|
.or_else(|| request.thinking.as_ref().and_then(thinking_enabled));
|
||||||
let mut reasoning = match request.reasoning_effort.as_deref() {
|
let explicit_effort = match request.reasoning_effort.as_deref() {
|
||||||
Some("max") => ReasoningMode::Max,
|
Some("none") => Some(ReasoningMode::Direct),
|
||||||
Some("none") => ReasoningMode::Direct,
|
Some("low") => Some(ReasoningMode::Low),
|
||||||
Some("xhigh" | "high" | "medium" | "low" | "minimal") | None => ReasoningMode::High,
|
Some("high") => Some(ReasoningMode::High),
|
||||||
|
Some("max") => Some(ReasoningMode::Max),
|
||||||
|
None => None,
|
||||||
Some(value) => return Err((400, format!("unsupported reasoning_effort: {value}"))),
|
Some(value) => return Err((400, format!("unsupported reasoning_effort: {value}"))),
|
||||||
};
|
};
|
||||||
if explicit_thinking == Some(false)
|
let direct_alias = matches!(
|
||||||
|| (explicit_thinking.is_none()
|
model_id,
|
||||||
&& matches!(
|
"deepseek-chat"
|
||||||
model_id,
|
| "glm-5.2-chat"
|
||||||
"deepseek-chat"
|
| "glm-5.2-no-think"
|
||||||
| "glm-5.2-chat"
|
| "glm-5.2-nothink"
|
||||||
| "glm-5.2-no-think"
|
| "zai/glm-5.2-chat"
|
||||||
| "glm-5.2-nothink"
|
);
|
||||||
| "glm-5.3-flash-chat"
|
if (direct_alias && explicit_thinking == Some(true))
|
||||||
| "zai/glm-5.3-flash-chat"
|
|| ((direct_alias || explicit_thinking == Some(false))
|
||||||
))
|
&& explicit_effort.is_some_and(|mode| mode != ReasoningMode::Direct))
|
||||||
|
|| (explicit_thinking == Some(true) && explicit_effort == Some(ReasoningMode::Direct))
|
||||||
{
|
{
|
||||||
reasoning = ReasoningMode::Direct;
|
return Err((400, "conflicting thinking controls".into()));
|
||||||
}
|
}
|
||||||
|
let reasoning = if direct_alias || explicit_thinking == Some(false) {
|
||||||
|
ReasoningMode::Direct
|
||||||
|
} else {
|
||||||
|
explicit_effort.unwrap_or_else(|| model.default_reasoning_mode())
|
||||||
|
};
|
||||||
|
model
|
||||||
|
.validate_reasoning_mode(reasoning)
|
||||||
|
.map_err(|error| (400, error))?;
|
||||||
Ok(reasoning)
|
Ok(reasoning)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,3 +644,70 @@ fn thinking_enabled(value: &Value) -> Option<bool> {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn request(effort: Option<&str>, thinking: Option<bool>) -> ChatRequest {
|
||||||
|
let mut value = json!({"messages": [{"role": "user", "content": "hello"}]});
|
||||||
|
if let Some(effort) = effort {
|
||||||
|
value["reasoning_effort"] = Value::String(effort.into());
|
||||||
|
}
|
||||||
|
if let Some(thinking) = thinking {
|
||||||
|
value["thinking"] = Value::Bool(thinking);
|
||||||
|
}
|
||||||
|
decode_chat_request(value).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reasoning_requests_preserve_each_models_exact_modes() {
|
||||||
|
let default = request(None, None);
|
||||||
|
assert_eq!(
|
||||||
|
request_reasoning(&default, ModelChoice::DeepSeekV4Flash0731, "").unwrap(),
|
||||||
|
ReasoningMode::Low
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
request_reasoning(&default, ModelChoice::DeepSeekV4Pro, "").unwrap(),
|
||||||
|
ReasoningMode::Low
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
request_reasoning(&default, ModelChoice::Glm52, "").unwrap(),
|
||||||
|
ReasoningMode::Max
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
request_reasoning(&default, ModelChoice::Glm53Flash, "").unwrap(),
|
||||||
|
ReasoningMode::Max
|
||||||
|
);
|
||||||
|
|
||||||
|
for (effort, mode) in [
|
||||||
|
("none", ReasoningMode::Direct),
|
||||||
|
("low", ReasoningMode::Low),
|
||||||
|
("high", ReasoningMode::High),
|
||||||
|
("max", ReasoningMode::Max),
|
||||||
|
] {
|
||||||
|
let explicit = request(Some(effort), None);
|
||||||
|
for model in crate::model::MODEL_CHOICES {
|
||||||
|
let result = request_reasoning(&explicit, model, "");
|
||||||
|
if model.reasoning_modes().contains(&mode) {
|
||||||
|
assert_eq!(result.unwrap(), mode);
|
||||||
|
} else {
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(request_reasoning(&request(Some("medium"), None), ModelChoice::Glm52, "").is_err());
|
||||||
|
assert!(
|
||||||
|
request_reasoning(&request(None, Some(false)), ModelChoice::Glm53Flash, "").is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
request_reasoning(
|
||||||
|
&request(None, Some(true)),
|
||||||
|
ModelChoice::DeepSeekV4Flash0731,
|
||||||
|
"deepseek-chat"
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
116
src/settings.rs
116
src/settings.rs
@@ -3,13 +3,19 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub(crate) const REASONING_MODES: [ReasoningMode; 3] = [
|
const DEEPSEEK_REASONING_MODES: [ReasoningMode; 4] = [
|
||||||
|
ReasoningMode::Low,
|
||||||
ReasoningMode::High,
|
ReasoningMode::High,
|
||||||
ReasoningMode::Max,
|
ReasoningMode::Max,
|
||||||
ReasoningMode::Direct,
|
ReasoningMode::Direct,
|
||||||
];
|
];
|
||||||
const STANDARD_REASONING_MODES: [ReasoningMode; 2] = [ReasoningMode::High, ReasoningMode::Direct];
|
const GLM_52_REASONING_MODES: [ReasoningMode; 3] = [
|
||||||
const THINK_MAX_MIN_CONTEXT: i32 = 393_216;
|
ReasoningMode::High,
|
||||||
|
ReasoningMode::Max,
|
||||||
|
ReasoningMode::Direct,
|
||||||
|
];
|
||||||
|
const GLM_53_REASONING_MODES: [ReasoningMode; 3] =
|
||||||
|
[ReasoningMode::Low, ReasoningMode::High, ReasoningMode::Max];
|
||||||
const MAX_CPU_THREADS: u32 = 32;
|
const MAX_CPU_THREADS: u32 = 32;
|
||||||
pub(crate) const GIB: u64 = 1024 * 1024 * 1024;
|
pub(crate) const GIB: u64 = 1024 * 1024 * 1024;
|
||||||
/// DS4 disk KV cache defaults, from `ds4_kvstore.h` and `--kv-disk-space-mb`.
|
/// DS4 disk KV cache defaults, from `ds4_kvstore.h` and `--kv-disk-space-mb`.
|
||||||
@@ -486,6 +492,7 @@ pub(crate) enum ReasoningMode {
|
|||||||
#[serde(rename = "none")]
|
#[serde(rename = "none")]
|
||||||
Direct,
|
Direct,
|
||||||
#[default]
|
#[default]
|
||||||
|
Low,
|
||||||
High,
|
High,
|
||||||
Max,
|
Max,
|
||||||
}
|
}
|
||||||
@@ -494,12 +501,49 @@ impl fmt::Display for ReasoningMode {
|
|||||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
formatter.write_str(match self {
|
formatter.write_str(match self {
|
||||||
Self::Direct => "Direct (no thinking)",
|
Self::Direct => "Direct (no thinking)",
|
||||||
Self::High => "Thinking",
|
Self::Low => "Think Low",
|
||||||
|
Self::High => "Think High",
|
||||||
Self::Max => "Think Max",
|
Self::Max => "Think Max",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ReasoningMode {
|
||||||
|
pub(crate) fn as_id(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Direct => "none",
|
||||||
|
Self::Low => "low",
|
||||||
|
Self::High => "high",
|
||||||
|
Self::Max => "max",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModelChoice {
|
||||||
|
pub(crate) fn reasoning_modes(self) -> &'static [ReasoningMode] {
|
||||||
|
match self {
|
||||||
|
Self::DeepSeekV4Flash0731 | Self::DeepSeekV4Pro => &DEEPSEEK_REASONING_MODES,
|
||||||
|
Self::Glm52 => &GLM_52_REASONING_MODES,
|
||||||
|
Self::Glm53Flash => &GLM_53_REASONING_MODES,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn default_reasoning_mode(self) -> ReasoningMode {
|
||||||
|
match self {
|
||||||
|
Self::DeepSeekV4Flash0731 | Self::DeepSeekV4Pro => ReasoningMode::Low,
|
||||||
|
Self::Glm52 | Self::Glm53Flash => ReasoningMode::Max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn validate_reasoning_mode(self, mode: ReasoningMode) -> Result<(), String> {
|
||||||
|
if self.reasoning_modes().contains(&mode) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("{mode} is not supported by {self}."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||||
#[serde(default, deny_unknown_fields)]
|
#[serde(default, deny_unknown_fields)]
|
||||||
pub(crate) struct GenerationPreferences {
|
pub(crate) struct GenerationPreferences {
|
||||||
@@ -531,29 +575,12 @@ impl Default for GenerationPreferences {
|
|||||||
top_p: None,
|
top_p: None,
|
||||||
min_p: None,
|
min_p: None,
|
||||||
seed: None,
|
seed: None,
|
||||||
reasoning_mode: ReasoningMode::High,
|
reasoning_mode: ReasoningMode::Low,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenerationPreferences {
|
impl GenerationPreferences {
|
||||||
pub(crate) fn effective_reasoning_mode(&self) -> ReasoningMode {
|
|
||||||
if self.reasoning_mode == ReasoningMode::Max && self.context_tokens < THINK_MAX_MIN_CONTEXT
|
|
||||||
{
|
|
||||||
ReasoningMode::High
|
|
||||||
} else {
|
|
||||||
self.reasoning_mode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn supported_reasoning_modes(&self) -> &'static [ReasoningMode] {
|
|
||||||
if self.context_tokens >= THINK_MAX_MIN_CONTEXT {
|
|
||||||
&REASONING_MODES
|
|
||||||
} else {
|
|
||||||
&STANDARD_REASONING_MODES
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn validate(&self) -> Result<(), String> {
|
pub(crate) fn validate(&self) -> Result<(), String> {
|
||||||
if self.context_tokens <= 0 {
|
if self.context_tokens <= 0 {
|
||||||
return Err("Context tokens must be a positive whole number.".into());
|
return Err("Context tokens must be a positive whole number.".into());
|
||||||
@@ -587,7 +614,7 @@ impl GenerationPreferences {
|
|||||||
top_k: 0,
|
top_k: 0,
|
||||||
stops: Vec::new(),
|
stops: Vec::new(),
|
||||||
seed: self.seed,
|
seed: self.seed,
|
||||||
reasoning_mode: self.effective_reasoning_mode(),
|
reasoning_mode: self.reasoning_mode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -648,6 +675,7 @@ pub(crate) fn effective_settings(
|
|||||||
models_path: &Path,
|
models_path: &Path,
|
||||||
) -> Result<EffectiveSettings, String> {
|
) -> Result<EffectiveSettings, String> {
|
||||||
generation.validate()?;
|
generation.validate()?;
|
||||||
|
model.validate_reasoning_mode(generation.reasoning_mode)?;
|
||||||
Ok(EffectiveSettings {
|
Ok(EffectiveSettings {
|
||||||
engine: runtime.engine_settings(model, generation.context_tokens, models_path)?,
|
engine: runtime.engine_settings(model, generation.context_tokens, models_path)?,
|
||||||
turn: generation.turn_settings(model, runtime.kv_cache.settings()),
|
turn: generation.turn_settings(model, runtime.kv_cache.settings()),
|
||||||
@@ -673,11 +701,7 @@ mod tests {
|
|||||||
|
|
||||||
let glm = defaults.turn_settings(ModelChoice::Glm52, cache);
|
let glm = defaults.turn_settings(ModelChoice::Glm52, cache);
|
||||||
assert_eq!((glm.temperature, glm.top_p, glm.min_p), (1.0, 0.95, 0.0));
|
assert_eq!((glm.temperature, glm.top_p, glm.min_p), (1.0, 0.95, 0.0));
|
||||||
assert_eq!(
|
assert_eq!(defaults.reasoning_mode, ReasoningMode::Low);
|
||||||
defaults.supported_reasoning_modes(),
|
|
||||||
&[ReasoningMode::High, ReasoningMode::Direct]
|
|
||||||
);
|
|
||||||
assert_eq!(defaults.effective_reasoning_mode(), ReasoningMode::High);
|
|
||||||
|
|
||||||
let explicit = GenerationPreferences {
|
let explicit = GenerationPreferences {
|
||||||
top_p: Some(0.4),
|
top_p: Some(0.4),
|
||||||
@@ -687,39 +711,33 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let effective = explicit.turn_settings(ModelChoice::Glm52, cache);
|
let effective = explicit.turn_settings(ModelChoice::Glm52, cache);
|
||||||
assert_eq!((effective.top_p, effective.min_p), (0.4, 0.2));
|
assert_eq!((effective.top_p, effective.min_p), (0.4, 0.2));
|
||||||
assert_eq!(effective.reasoning_mode, ReasoningMode::High);
|
assert_eq!(effective.reasoning_mode, ReasoningMode::Max);
|
||||||
|
|
||||||
let long_context = GenerationPreferences {
|
|
||||||
context_tokens: THINK_MAX_MIN_CONTEXT,
|
|
||||||
reasoning_mode: ReasoningMode::Max,
|
|
||||||
..GenerationPreferences::default()
|
|
||||||
};
|
|
||||||
assert_eq!(long_context.supported_reasoning_modes(), &REASONING_MODES);
|
|
||||||
assert_eq!(long_context.effective_reasoning_mode(), ReasoningMode::Max);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reasoning_selection_reaches_every_model_turn() {
|
fn reasoning_selection_is_exact_and_model_specific() {
|
||||||
let cache = KvCachePreferences::default().settings();
|
let cache = KvCachePreferences::default().settings();
|
||||||
for model in crate::model::MODEL_CHOICES {
|
for model in crate::model::MODEL_CHOICES {
|
||||||
for mode in [ReasoningMode::Direct, ReasoningMode::High] {
|
for &mode in model.reasoning_modes() {
|
||||||
let generation = GenerationPreferences {
|
let generation = GenerationPreferences {
|
||||||
reasoning_mode: mode,
|
reasoning_mode: mode,
|
||||||
..GenerationPreferences::default()
|
..GenerationPreferences::default()
|
||||||
};
|
};
|
||||||
assert_eq!(generation.turn_settings(model, cache).reasoning_mode, mode);
|
assert_eq!(generation.turn_settings(model, cache).reasoning_mode, mode);
|
||||||
|
assert!(model.validate_reasoning_mode(mode).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
let generation = GenerationPreferences {
|
|
||||||
context_tokens: THINK_MAX_MIN_CONTEXT,
|
|
||||||
reasoning_mode: ReasoningMode::Max,
|
|
||||||
..GenerationPreferences::default()
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
generation.turn_settings(model, cache).reasoning_mode,
|
|
||||||
ReasoningMode::Max
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
ModelChoice::Glm52
|
||||||
|
.validate_reasoning_mode(ReasoningMode::Low)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
ModelChoice::Glm53Flash
|
||||||
|
.validate_reasoning_mode(ReasoningMode::Direct)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user