mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat: structured multi-agent output (#15515)
Send input now sends messages as assistant message and with this format: ``` author: /root/worker_a recipient: /root/worker_a/tester other_recipients: [] Content: bla bla bla. Actual content. Only text for now ```
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use crate::agent::inter_agent_instruction::InterAgentInstruction;
|
||||
use crate::codex::TurnContext;
|
||||
use crate::context_manager::normalize;
|
||||
use crate::event_mapping::is_contextual_user_message_content;
|
||||
@@ -204,9 +205,10 @@ impl ContextManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop the last `num_turns` user turns from this history.
|
||||
/// Drop the last `num_turns` instruction turns from this history.
|
||||
///
|
||||
/// "User turns" are identified as `ResponseItem::Message` entries whose role is `"user"`.
|
||||
/// Instruction turns are history messages that should behave like a new prompt boundary:
|
||||
/// ordinary user messages and structured assistant inter-agent instructions.
|
||||
///
|
||||
/// This mirrors thread-rollback semantics:
|
||||
/// - `num_turns == 0` is a no-op
|
||||
@@ -248,7 +250,7 @@ impl ContextManager {
|
||||
}
|
||||
|
||||
fn get_non_last_reasoning_items_tokens(&self) -> i64 {
|
||||
// Get reasoning items excluding all the ones after the last user message.
|
||||
// Get reasoning items excluding all the ones after the last instruction boundary.
|
||||
let Some(last_user_index) = self.items.iter().rposition(is_user_turn_boundary) else {
|
||||
return 0;
|
||||
};
|
||||
@@ -631,7 +633,12 @@ pub(crate) fn is_user_turn_boundary(item: &ResponseItem) -> bool {
|
||||
return false;
|
||||
};
|
||||
|
||||
role == "user" && !is_contextual_user_message_content(content)
|
||||
(role == "user" && !is_contextual_user_message_content(content))
|
||||
|| (role == "assistant" && is_inter_agent_instruction_content(content))
|
||||
}
|
||||
|
||||
fn is_inter_agent_instruction_content(content: &[ContentItem]) -> bool {
|
||||
InterAgentInstruction::is_message_content(content)
|
||||
}
|
||||
|
||||
fn user_message_positions(items: &[ResponseItem]) -> Vec<usize> {
|
||||
|
||||
@@ -38,6 +38,18 @@ fn assistant_msg(text: &str) -> ResponseItem {
|
||||
}
|
||||
}
|
||||
|
||||
fn inter_agent_assistant_msg(text: &str) -> ResponseItem {
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
role: "assistant".to_string(),
|
||||
content: vec![ContentItem::OutputText {
|
||||
text: text.to_string(),
|
||||
}],
|
||||
end_turn: None,
|
||||
phase: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_history_with_items(items: Vec<ResponseItem>) -> ContextManager {
|
||||
let mut h = ContextManager::new();
|
||||
// Use a generous but fixed token budget; tests only rely on truncation
|
||||
@@ -225,6 +237,35 @@ fn items_after_last_model_generated_tokens_are_zero_without_model_generated_item
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inter_agent_assistant_messages_are_turn_boundaries() {
|
||||
let item = inter_agent_assistant_msg(
|
||||
"author: /root\nrecipient: /root/worker\nother_recipients: []\nContent: continue",
|
||||
);
|
||||
|
||||
assert!(is_user_turn_boundary(&item));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_last_n_user_turns_treats_inter_agent_assistant_messages_as_instruction_turns() {
|
||||
let first_turn = user_input_text_msg("first");
|
||||
let first_reply = assistant_msg("done");
|
||||
let inter_agent_turn = inter_agent_assistant_msg(
|
||||
"author: /root\nrecipient: /root/worker\nother_recipients: []\nContent: continue",
|
||||
);
|
||||
let inter_agent_reply = assistant_msg("worker reply");
|
||||
let mut history = create_history_with_items(vec![
|
||||
first_turn.clone(),
|
||||
first_reply.clone(),
|
||||
inter_agent_turn,
|
||||
inter_agent_reply,
|
||||
]);
|
||||
|
||||
history.drop_last_n_user_turns(1);
|
||||
|
||||
assert_eq!(history.raw_items(), &vec![first_turn, first_reply]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn total_token_usage_includes_all_items_after_last_model_generated_item() {
|
||||
let mut history = create_history_with_items(vec![assistant_msg("already counted by API")]);
|
||||
|
||||
Reference in New Issue
Block a user