fix(channel): normalize telegram history for MiniMax
This commit is contained in:
parent
7173045f1c
commit
c5834b1077
2 changed files with 135 additions and 9 deletions
|
|
@ -144,6 +144,11 @@ Optional:
|
|||
- `MINIMAX_OAUTH_REGION=global` or `cn` (defaults by provider alias)
|
||||
- `MINIMAX_OAUTH_CLIENT_ID` to override the default OAuth client id
|
||||
|
||||
Channel compatibility note:
|
||||
|
||||
- For MiniMax-backed channel conversations, runtime history is normalized to keep valid `user`/`assistant` turn order.
|
||||
- Channel-specific delivery guidance (for example Telegram attachment markers) is merged into the leading system prompt instead of being appended as a trailing `system` turn.
|
||||
|
||||
## Qwen Code OAuth Setup (config.toml)
|
||||
|
||||
Set Qwen Code OAuth mode in config:
|
||||
|
|
|
|||
|
|
@ -200,6 +200,43 @@ fn channel_delivery_instructions(channel_name: &str) -> Option<&'static str> {
|
|||
}
|
||||
}
|
||||
|
||||
fn build_channel_system_prompt(base_prompt: &str, channel_name: &str) -> String {
|
||||
if let Some(instructions) = channel_delivery_instructions(channel_name) {
|
||||
if base_prompt.is_empty() {
|
||||
instructions.to_string()
|
||||
} else {
|
||||
format!("{base_prompt}\n\n{instructions}")
|
||||
}
|
||||
} else {
|
||||
base_prompt.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_cached_channel_turns(turns: Vec<ChatMessage>) -> Vec<ChatMessage> {
|
||||
let mut normalized = Vec::with_capacity(turns.len());
|
||||
let mut expecting_user = true;
|
||||
|
||||
for turn in turns {
|
||||
match (expecting_user, turn.role.as_str()) {
|
||||
(true, "user") => {
|
||||
normalized.push(turn);
|
||||
expecting_user = false;
|
||||
}
|
||||
(false, "assistant") => {
|
||||
normalized.push(turn);
|
||||
expecting_user = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if normalized.last().map_or(false, |msg| msg.role == "user") {
|
||||
normalized.pop();
|
||||
}
|
||||
|
||||
normalized
|
||||
}
|
||||
|
||||
fn supports_runtime_model_switch(channel_name: &str) -> bool {
|
||||
matches!(channel_name, "telegram" | "discord")
|
||||
}
|
||||
|
|
@ -318,7 +355,7 @@ fn compact_sender_history(ctx: &ChannelRuntimeContext, sender_key: &str) -> bool
|
|||
let keep_from = turns
|
||||
.len()
|
||||
.saturating_sub(CHANNEL_HISTORY_COMPACT_KEEP_MESSAGES);
|
||||
let mut compacted = turns[keep_from..].to_vec();
|
||||
let mut compacted = normalize_cached_channel_turns(turns[keep_from..].to_vec());
|
||||
|
||||
for turn in &mut compacted {
|
||||
if turn.content.chars().count() > CHANNEL_HISTORY_COMPACT_CONTENT_CHARS {
|
||||
|
|
@ -327,6 +364,11 @@ fn compact_sender_history(ctx: &ChannelRuntimeContext, sender_key: &str) -> bool
|
|||
}
|
||||
}
|
||||
|
||||
if compacted.is_empty() {
|
||||
turns.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
*turns = compacted;
|
||||
true
|
||||
}
|
||||
|
|
@ -783,21 +825,19 @@ async fn process_channel_message(
|
|||
ChatMessage::user(&enriched_message),
|
||||
);
|
||||
|
||||
let mut prior_turns = ctx
|
||||
// Build history from per-sender conversation cache.
|
||||
let prior_turns_raw = ctx
|
||||
.conversation_histories
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.get(&history_key)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let prior_turns = normalize_cached_channel_turns(prior_turns_raw);
|
||||
|
||||
let mut history = vec![ChatMessage::system(ctx.system_prompt.as_str())];
|
||||
history.append(&mut prior_turns);
|
||||
|
||||
if let Some(instructions) = channel_delivery_instructions(&msg.channel) {
|
||||
history.push(ChatMessage::system(instructions));
|
||||
}
|
||||
|
||||
let system_prompt = build_channel_system_prompt(ctx.system_prompt.as_str(), &msg.channel);
|
||||
let mut history = vec![ChatMessage::system(system_prompt)];
|
||||
history.extend(prior_turns);
|
||||
let use_streaming = target_channel
|
||||
.as_ref()
|
||||
.is_some_and(|ch| ch.supports_draft_updates());
|
||||
|
|
@ -3994,6 +4034,87 @@ mod tests {
|
|||
assert!(calls[1][3].1.contains("follow up"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn process_channel_message_telegram_keeps_system_instruction_at_top_only() {
|
||||
let channel_impl = Arc::new(TelegramRecordingChannel::default());
|
||||
let channel: Arc<dyn Channel> = channel_impl.clone();
|
||||
|
||||
let mut channels_by_name = HashMap::new();
|
||||
channels_by_name.insert(channel.name().to_string(), channel);
|
||||
|
||||
let provider_impl = Arc::new(HistoryCaptureProvider::default());
|
||||
let mut histories = HashMap::new();
|
||||
histories.insert(
|
||||
"telegram_alice".to_string(),
|
||||
vec![
|
||||
ChatMessage::assistant("stale assistant"),
|
||||
ChatMessage::user("earlier user question"),
|
||||
ChatMessage::assistant("earlier assistant reply"),
|
||||
],
|
||||
);
|
||||
|
||||
let runtime_ctx = Arc::new(ChannelRuntimeContext {
|
||||
channels_by_name: Arc::new(channels_by_name),
|
||||
provider: provider_impl.clone(),
|
||||
default_provider: Arc::new("test-provider".to_string()),
|
||||
memory: Arc::new(NoopMemory),
|
||||
tools_registry: Arc::new(vec![]),
|
||||
observer: Arc::new(NoopObserver),
|
||||
system_prompt: Arc::new("test-system-prompt".to_string()),
|
||||
model: Arc::new("test-model".to_string()),
|
||||
temperature: 0.0,
|
||||
auto_save_memory: false,
|
||||
max_tool_iterations: 5,
|
||||
min_relevance_score: 0.0,
|
||||
conversation_histories: Arc::new(Mutex::new(histories)),
|
||||
provider_cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
route_overrides: Arc::new(Mutex::new(HashMap::new())),
|
||||
api_key: None,
|
||||
api_url: None,
|
||||
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
runtime_ctx.clone(),
|
||||
traits::ChannelMessage {
|
||||
id: "tg-msg-1".to_string(),
|
||||
sender: "alice".to_string(),
|
||||
reply_target: "chat-telegram".to_string(),
|
||||
content: "hello".to_string(),
|
||||
channel: "telegram".to_string(),
|
||||
timestamp: 1,
|
||||
thread_ts: None,
|
||||
},
|
||||
CancellationToken::new(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let calls = provider_impl
|
||||
.calls
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
assert_eq!(calls.len(), 1);
|
||||
assert_eq!(calls[0].len(), 4);
|
||||
|
||||
let roles = calls[0]
|
||||
.iter()
|
||||
.map(|(role, _)| role.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(roles, vec!["system", "user", "assistant", "user"]);
|
||||
assert!(
|
||||
calls[0][0]
|
||||
.1
|
||||
.contains("When responding on Telegram, include media markers"),
|
||||
"telegram delivery instruction should live in the system prompt"
|
||||
);
|
||||
assert!(!calls[0].iter().skip(1).any(|(role, _)| role == "system"));
|
||||
}
|
||||
|
||||
// ── AIEOS Identity Tests (Issue #168) ─────────────────────────
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue