mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat(core) ModelInfo.model_instructions_template (#9597)
## Summary #9555 is the start of a rename, so I'm starting to standardize here. Sets up `model_instructions` templating with a strongly-typed object for injecting a personality block into the model instructions. ## Testing - [x] Added tests - [x] Ran locally
This commit is contained in:
committed by
GitHub
Unverified
parent
a489b64cb5
commit
96a72828be
@@ -27,6 +27,7 @@ fn preset_to_info(preset: &ModelPreset, priority: i32) -> ModelInfo {
|
||||
priority,
|
||||
upgrade: preset.upgrade.as_ref().map(|u| u.into()),
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_instructions_template: None,
|
||||
supports_reasoning_summaries: false,
|
||||
support_verbosity: false,
|
||||
default_verbosity: None,
|
||||
|
||||
@@ -77,6 +77,7 @@ async fn models_client_hits_models_endpoint() {
|
||||
priority: 1,
|
||||
upgrade: None,
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_instructions_template: None,
|
||||
supports_reasoning_summaries: false,
|
||||
support_verbosity: false,
|
||||
default_verbosity: None,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
danger-unless-trusted
|
||||
@@ -285,7 +285,7 @@ impl Codex {
|
||||
.base_instructions
|
||||
.clone()
|
||||
.or_else(|| conversation_history.get_base_instructions().map(|s| s.text))
|
||||
.unwrap_or_else(|| model_info.base_instructions.clone());
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.model_personality));
|
||||
|
||||
// TODO (aibrahim): Consolidate config.model and config.model_reasoning_effort into config.collaboration_mode
|
||||
// to avoid extracting these fields separately and constructing CollaborationMode here.
|
||||
@@ -3741,7 +3741,7 @@ mod tests {
|
||||
base_instructions: config
|
||||
.base_instructions
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.base_instructions.clone()),
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.model_personality)),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.approval_policy.clone(),
|
||||
sandbox_policy: config.sandbox_policy.clone(),
|
||||
@@ -3816,7 +3816,7 @@ mod tests {
|
||||
base_instructions: config
|
||||
.base_instructions
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.base_instructions.clone()),
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.model_personality)),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.approval_policy.clone(),
|
||||
sandbox_policy: config.sandbox_policy.clone(),
|
||||
@@ -4075,7 +4075,7 @@ mod tests {
|
||||
base_instructions: config
|
||||
.base_instructions
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.base_instructions.clone()),
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.model_personality)),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.approval_policy.clone(),
|
||||
sandbox_policy: config.sandbox_policy.clone(),
|
||||
@@ -4179,7 +4179,7 @@ mod tests {
|
||||
base_instructions: config
|
||||
.base_instructions
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.base_instructions.clone()),
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.model_personality)),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.approval_policy.clone(),
|
||||
sandbox_policy: config.sandbox_policy.clone(),
|
||||
|
||||
@@ -11,7 +11,6 @@ use crate::config::types::Notifications;
|
||||
use crate::config::types::OtelConfig;
|
||||
use crate::config::types::OtelConfigToml;
|
||||
use crate::config::types::OtelExporterKind;
|
||||
use crate::config::types::Personality;
|
||||
use crate::config::types::SandboxWorkspaceWrite;
|
||||
use crate::config::types::ShellEnvironmentPolicy;
|
||||
use crate::config::types::ShellEnvironmentPolicyToml;
|
||||
@@ -43,6 +42,7 @@ use codex_app_server_protocol::Tools;
|
||||
use codex_app_server_protocol::UserSavedConfig;
|
||||
use codex_protocol::config_types::AltScreenMode;
|
||||
use codex_protocol::config_types::ForcedLoginMethod;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
use codex_protocol::config_types::TrustLevel;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
use crate::config_loader::RequirementSource;
|
||||
pub use codex_protocol::config_types::AltScreenMode;
|
||||
pub use codex_protocol::config_types::Personality;
|
||||
pub use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::collections::BTreeMap;
|
||||
@@ -628,14 +629,6 @@ impl Default for ShellEnvironmentPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum Personality {
|
||||
Friendly,
|
||||
#[default]
|
||||
Pragmatic,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -477,6 +477,42 @@ model_instructions_file = "child.txt"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cli_override_model_instructions_file_sets_base_instructions() -> std::io::Result<()> {
|
||||
let tmp = tempdir()?;
|
||||
let codex_home = tmp.path().join("home");
|
||||
tokio::fs::create_dir_all(&codex_home).await?;
|
||||
tokio::fs::write(codex_home.join(CONFIG_TOML_FILE), "").await?;
|
||||
|
||||
let cwd = tmp.path().join("work");
|
||||
tokio::fs::create_dir_all(&cwd).await?;
|
||||
|
||||
let instructions_path = tmp.path().join("instr.md");
|
||||
tokio::fs::write(&instructions_path, "cli override instructions").await?;
|
||||
|
||||
let cli_overrides = vec![(
|
||||
"model_instructions_file".to_string(),
|
||||
TomlValue::String(instructions_path.to_string_lossy().to_string()),
|
||||
)];
|
||||
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home)
|
||||
.cli_overrides(cli_overrides)
|
||||
.harness_overrides(ConfigOverrides {
|
||||
cwd: Some(cwd),
|
||||
..ConfigOverrides::default()
|
||||
})
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
config.base_instructions.as_deref(),
|
||||
Some("cli override instructions")
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn project_layer_is_added_when_dot_codex_exists_without_config_toml() -> std::io::Result<()> {
|
||||
let tmp = tempdir()?;
|
||||
|
||||
@@ -86,8 +86,9 @@ impl ContextManager {
|
||||
// This is a coarse lower bound, not a tokenizer-accurate count.
|
||||
pub(crate) fn estimate_token_count(&self, turn_context: &TurnContext) -> Option<i64> {
|
||||
let model_info = turn_context.client.get_model_info();
|
||||
let base_instructions = model_info.base_instructions.as_str();
|
||||
let base_tokens = i64::try_from(approx_token_count(base_instructions)).unwrap_or(i64::MAX);
|
||||
let personality = turn_context.client.config().model_personality;
|
||||
let base_instructions = model_info.get_model_instructions(personality);
|
||||
let base_tokens = i64::try_from(approx_token_count(&base_instructions)).unwrap_or(i64::MAX);
|
||||
|
||||
let items_tokens = self.items.iter().fold(0i64, |acc, item| {
|
||||
acc + match item {
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::Verbosity;
|
||||
use codex_protocol::openai_models::ApplyPatchToolType;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelInstructionsTemplate;
|
||||
use codex_protocol::openai_models::ModelVisibility;
|
||||
use codex_protocol::openai_models::PersonalityMessages;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::openai_models::ReasoningEffortPreset;
|
||||
use codex_protocol::openai_models::TruncationMode;
|
||||
use codex_protocol::openai_models::TruncationPolicyConfig;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::config::types::Personality;
|
||||
use crate::truncate::approx_bytes_for_tokens;
|
||||
use tracing::warn;
|
||||
|
||||
@@ -21,7 +25,12 @@ const GPT_5_CODEX_INSTRUCTIONS: &str = include_str!("../../gpt_5_codex_prompt.md
|
||||
const GPT_5_1_INSTRUCTIONS: &str = include_str!("../../gpt_5_1_prompt.md");
|
||||
const GPT_5_2_INSTRUCTIONS: &str = include_str!("../../gpt_5_2_prompt.md");
|
||||
const GPT_5_1_CODEX_MAX_INSTRUCTIONS: &str = include_str!("../../gpt-5.1-codex-max_prompt.md");
|
||||
|
||||
const GPT_5_2_CODEX_INSTRUCTIONS: &str = include_str!("../../gpt-5.2-codex_prompt.md");
|
||||
const GPT_5_2_CODEX_INSTRUCTIONS_TEMPLATE: &str =
|
||||
include_str!("../../templates/model_instructions/gpt-5.2-codex_instructions_template.md");
|
||||
const PERSONALITY_FRIENDLY: &str = include_str!("../../templates/personalities/friendly.md");
|
||||
const PERSONALITY_PRAGMATIC: &str = include_str!("../../templates/personalities/pragmatic.md");
|
||||
|
||||
pub(crate) const CONTEXT_WINDOW_272K: i64 = 272_000;
|
||||
|
||||
@@ -45,6 +54,7 @@ macro_rules! model_info {
|
||||
priority: 99,
|
||||
upgrade: None,
|
||||
base_instructions: BASE_INSTRUCTIONS.to_string(),
|
||||
model_instructions_template: None,
|
||||
supports_reasoning_summaries: false,
|
||||
support_verbosity: false,
|
||||
default_verbosity: None,
|
||||
@@ -87,22 +97,10 @@ pub(crate) fn with_config_overrides(mut model: ModelInfo, config: &Config) -> Mo
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(base_instructions) = &config.base_instructions {
|
||||
model.base_instructions = base_instructions.clone();
|
||||
} else if model.slug.contains("gpt-5.2-codex")
|
||||
&& let Some(personality) = &config.model_personality
|
||||
{
|
||||
let template = include_str!(
|
||||
"../../templates/model_instructions/gpt-5.2-codex_instructions_template.md"
|
||||
);
|
||||
let personality_message = match personality {
|
||||
Personality::Friendly => include_str!("../../templates/personalities/friendly.md"),
|
||||
Personality::Pragmatic => {
|
||||
include_str!("../../templates/personalities/pragmatic.md")
|
||||
}
|
||||
};
|
||||
let instructions = template.replace("{{ personality_message }}", personality_message);
|
||||
model.base_instructions = instructions;
|
||||
model.model_instructions_template = None;
|
||||
}
|
||||
model
|
||||
}
|
||||
@@ -205,6 +203,16 @@ pub(crate) fn find_model_info_for_slug(slug: &str) -> ModelInfo {
|
||||
truncation_policy: TruncationPolicyConfig::tokens(10_000),
|
||||
context_window: Some(CONTEXT_WINDOW_272K),
|
||||
supported_reasoning_levels: supported_reasoning_level_low_medium_high_xhigh(),
|
||||
model_instructions_template: Some(ModelInstructionsTemplate {
|
||||
template: GPT_5_2_CODEX_INSTRUCTIONS_TEMPLATE.to_string(),
|
||||
personality_messages: Some(PersonalityMessages(BTreeMap::from([(
|
||||
Personality::Friendly,
|
||||
PERSONALITY_FRIENDLY.to_string(),
|
||||
), (
|
||||
Personality::Pragmatic,
|
||||
PERSONALITY_PRAGMATIC.to_string(),
|
||||
)]))),
|
||||
}),
|
||||
)
|
||||
} else if slug.starts_with("gpt-5.1-codex-max") {
|
||||
model_info!(
|
||||
|
||||
@@ -48,6 +48,7 @@ mod models_etag_responses;
|
||||
mod otel;
|
||||
mod pending_input;
|
||||
mod permissions_messages;
|
||||
mod personality;
|
||||
mod prompt_caching;
|
||||
mod quota_exceeded;
|
||||
mod read_file;
|
||||
|
||||
@@ -174,6 +174,7 @@ fn test_remote_model(slug: &str, priority: i32) -> ModelInfo {
|
||||
priority,
|
||||
upgrade: None,
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_instructions_template: None,
|
||||
supports_reasoning_summaries: false,
|
||||
support_verbosity: false,
|
||||
default_verbosity: None,
|
||||
|
||||
@@ -4,20 +4,31 @@ use core_test_support::load_default_config_for_test;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
|
||||
const BASE_INSTRUCTIONS_TEMPLATE: &str = include_str!(
|
||||
"../../templates/model_instructions/gpt-5.2-codex_instructions_template.md"
|
||||
);
|
||||
const FRIENDLY_PERSONALITY: &str = include_str!("../../templates/personalities/friendly.md");
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn model_personality_updates_base_instructions() {
|
||||
async fn model_personality_does_not_mutate_base_instructions_without_template() {
|
||||
let codex_home = TempDir::new().expect("create temp dir");
|
||||
let mut config = load_default_config_for_test(&codex_home).await;
|
||||
config.model_personality = Some(Personality::Friendly);
|
||||
|
||||
let model_info = ModelsManager::construct_model_info_offline("gpt-5.2-codex", &config);
|
||||
let expected =
|
||||
BASE_INSTRUCTIONS_TEMPLATE.replace("{{ personality_message }}", FRIENDLY_PERSONALITY);
|
||||
|
||||
assert_eq!(model_info.base_instructions, expected);
|
||||
let model_info = ModelsManager::construct_model_info_offline("gpt-5.1", &config);
|
||||
assert_eq!(
|
||||
model_info.get_model_instructions(config.model_personality),
|
||||
model_info.base_instructions
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn base_instructions_override_disables_personality_template() {
|
||||
let codex_home = TempDir::new().expect("create temp dir");
|
||||
let mut config = load_default_config_for_test(&codex_home).await;
|
||||
config.model_personality = Some(Personality::Friendly);
|
||||
config.base_instructions = Some("override instructions".to_string());
|
||||
|
||||
let model_info = ModelsManager::construct_model_info_offline("gpt-5.2-codex", &config);
|
||||
|
||||
assert_eq!(model_info.base_instructions, "override instructions");
|
||||
assert_eq!(
|
||||
model_info.get_model_instructions(config.model_personality),
|
||||
"override instructions"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> {
|
||||
priority: 1,
|
||||
upgrade: None,
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_instructions_template: None,
|
||||
supports_reasoning_summaries: false,
|
||||
support_verbosity: false,
|
||||
default_verbosity: None,
|
||||
@@ -312,6 +313,7 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> {
|
||||
priority: 1,
|
||||
upgrade: None,
|
||||
base_instructions: remote_base.to_string(),
|
||||
model_instructions_template: None,
|
||||
supports_reasoning_summaries: false,
|
||||
support_verbosity: false,
|
||||
default_verbosity: None,
|
||||
@@ -782,6 +784,7 @@ fn test_remote_model_with_policy(
|
||||
priority,
|
||||
upgrade: None,
|
||||
base_instructions: "base instructions".to_string(),
|
||||
model_instructions_template: None,
|
||||
supports_reasoning_summaries: false,
|
||||
support_verbosity: false,
|
||||
default_verbosity: None,
|
||||
|
||||
@@ -65,6 +65,27 @@ pub enum SandboxMode {
|
||||
DangerFullAccess,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Display,
|
||||
JsonSchema,
|
||||
TS,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum Personality {
|
||||
Friendly,
|
||||
Pragmatic,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS, Default,
|
||||
)]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
@@ -7,10 +8,14 @@ use serde::Serialize;
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::Display;
|
||||
use strum_macros::EnumIter;
|
||||
use tracing::warn;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::config_types::Personality;
|
||||
use crate::config_types::Verbosity;
|
||||
|
||||
const PERSONALITY_PLACEHOLDER: &str = "{{ personality_message }}";
|
||||
|
||||
/// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -180,6 +185,8 @@ pub struct ModelInfo {
|
||||
pub priority: i32,
|
||||
pub upgrade: Option<ModelInfoUpgrade>,
|
||||
pub base_instructions: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub model_instructions_template: Option<ModelInstructionsTemplate>,
|
||||
pub supports_reasoning_summaries: bool,
|
||||
pub support_verbosity: bool,
|
||||
pub default_verbosity: Option<Verbosity>,
|
||||
@@ -206,8 +213,49 @@ impl ModelInfo {
|
||||
.map(|context_window| (context_window * 9) / 10)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_model_instructions(&self, personality: Option<Personality>) -> String {
|
||||
if let Some(personality) = personality
|
||||
&& let Some(template) = &self.model_instructions_template
|
||||
&& template.has_personality_placeholder()
|
||||
&& let Some(personality_messages) = &template.personality_messages
|
||||
&& let Some(personality_message) = personality_messages.0.get(&personality)
|
||||
{
|
||||
template
|
||||
.template
|
||||
.replace(PERSONALITY_PLACEHOLDER, personality_message.as_str())
|
||||
} else if let Some(personality) = personality {
|
||||
warn!(
|
||||
model = %self.slug,
|
||||
%personality,
|
||||
"Model personality requested but model_instructions_template is invalid, falling back to base instructions."
|
||||
);
|
||||
self.base_instructions.clone()
|
||||
} else {
|
||||
self.base_instructions.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A strongly-typed template for assembling model instructions. If populated and valid, will override
|
||||
/// base_instructions.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TS, JsonSchema)]
|
||||
pub struct ModelInstructionsTemplate {
|
||||
pub template: String,
|
||||
pub personality_messages: Option<PersonalityMessages>,
|
||||
}
|
||||
|
||||
impl ModelInstructionsTemplate {
|
||||
fn has_personality_placeholder(&self) -> bool {
|
||||
self.template.contains(PERSONALITY_PLACEHOLDER)
|
||||
}
|
||||
}
|
||||
|
||||
// serializes as a dictionary from personality to message
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, TS, JsonSchema)]
|
||||
#[serde(transparent)]
|
||||
pub struct PersonalityMessages(pub BTreeMap<Personality, String>);
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TS, JsonSchema)]
|
||||
pub struct ModelInfoUpgrade {
|
||||
pub model: String,
|
||||
@@ -335,3 +383,67 @@ fn nearest_effort(target: ReasoningEffort, supported: &[ReasoningEffort]) -> Rea
|
||||
.min_by_key(|candidate| (effort_rank(*candidate) - target_rank).abs())
|
||||
.unwrap_or(target)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn test_model(template: Option<ModelInstructionsTemplate>) -> ModelInfo {
|
||||
ModelInfo {
|
||||
slug: "test-model".to_string(),
|
||||
display_name: "Test Model".to_string(),
|
||||
description: None,
|
||||
default_reasoning_level: None,
|
||||
supported_reasoning_levels: vec![],
|
||||
shell_type: ConfigShellToolType::ShellCommand,
|
||||
visibility: ModelVisibility::List,
|
||||
supported_in_api: true,
|
||||
priority: 1,
|
||||
upgrade: None,
|
||||
base_instructions: "base".to_string(),
|
||||
model_instructions_template: template,
|
||||
supports_reasoning_summaries: false,
|
||||
support_verbosity: false,
|
||||
default_verbosity: None,
|
||||
apply_patch_tool_type: None,
|
||||
truncation_policy: TruncationPolicyConfig::bytes(10_000),
|
||||
supports_parallel_tool_calls: false,
|
||||
context_window: None,
|
||||
auto_compact_token_limit: None,
|
||||
effective_context_window_percent: 95,
|
||||
experimental_supported_tools: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn personality_messages() -> PersonalityMessages {
|
||||
PersonalityMessages(BTreeMap::from([(
|
||||
Personality::Friendly,
|
||||
"friendly".to_string(),
|
||||
)]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_model_instructions_uses_template_when_placeholder_present() {
|
||||
let model = test_model(Some(ModelInstructionsTemplate {
|
||||
template: "Hello {{ personality_message }}".to_string(),
|
||||
personality_messages: Some(personality_messages()),
|
||||
}));
|
||||
|
||||
let instructions = model.get_model_instructions(Some(Personality::Friendly));
|
||||
|
||||
assert_eq!(instructions, "Hello friendly");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_model_instructions_falls_back_when_placeholder_missing() {
|
||||
let model = test_model(Some(ModelInstructionsTemplate {
|
||||
template: "Hello there".to_string(),
|
||||
personality_messages: Some(personality_messages()),
|
||||
}));
|
||||
|
||||
let instructions = model.get_model_instructions(Some(Personality::Friendly));
|
||||
|
||||
assert_eq!(instructions, "base");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user