[prompts] Better user_instructions handling (#1836)

## Summary
Our recent change in #1737 can sometimes lead to the model confusing
AGENTS.md context as part of the message. But a little prompting and
formatting can help fix this!

## Testing
- Ran locally with a few different prompts to verify the model
behaves well.
- Updated unit tests
This commit is contained in:
Dylan
2025-08-04 18:55:57 -07:00
committed by GitHub
parent f58401e203
commit 063083af15
5 changed files with 22 additions and 4 deletions

View File

@@ -9,6 +9,8 @@ You MUST adhere to the following criteria when executing the task:
- Analyzing code for vulnerabilities is allowed.
- Showing user code and tool call details is allowed.
- User instructions may overwrite the _CODING GUIDELINES_ section in this developer message.
- `user_instructions` are not part of the user's request, but guidance for how to complete the task.
- Do not cite `user_instructions` back to the user unless a specific piece is relevant.
- Do not use \`ls -R\`, \`find\`, or \`grep\` - these are slow in large repos. Use \`rg\` and \`rg --files\`.
- Use \`apply_patch\` to edit files: {"command":["apply_patch","*** Begin Patch\\n*** Update File: path/to/file.py\\n@@ def example():\\n- pass\\n+ return 123\\n*** End Patch"]}
- If completing the user's task requires writing or modifying files:

View File

@@ -40,7 +40,7 @@ pub(crate) async fn stream_chat_completions(
let full_instructions = prompt.get_full_instructions(model);
messages.push(json!({"role": "system", "content": full_instructions}));
if let Some(instr) = &prompt.user_instructions {
if let Some(instr) = &prompt.get_formatted_user_instructions() {
messages.push(json!({"role": "user", "content": instr}));
}

View File

@@ -144,11 +144,11 @@ impl ModelClient {
};
let mut input_with_instructions = Vec::with_capacity(prompt.input.len() + 1);
if let Some(ui) = &prompt.user_instructions {
if let Some(ui) = prompt.get_formatted_user_instructions() {
input_with_instructions.push(ResponseItem::Message {
id: None,
role: "user".to_string(),
content: vec![ContentItem::InputText { text: ui.clone() }],
content: vec![ContentItem::InputText { text: ui }],
});
}
input_with_instructions.extend(prompt.input.clone());

View File

@@ -17,6 +17,10 @@ use tokio::sync::mpsc;
/// with this content.
const BASE_INSTRUCTIONS: &str = include_str!("../prompt.md");
/// wraps user instructions message in a tag for the model to parse more easily.
const USER_INSTRUCTIONS_START: &str = "<user_instructions>\n\n";
const USER_INSTRUCTIONS_END: &str = "\n\n</user_instructions>";
/// API request payload for a single model turn.
#[derive(Default, Debug, Clone)]
pub struct Prompt {
@@ -49,6 +53,12 @@ impl Prompt {
}
Cow::Owned(sections.join("\n"))
}
pub(crate) fn get_formatted_user_instructions(&self) -> Option<String> {
self.user_instructions
.as_ref()
.map(|ui| format!("{USER_INSTRUCTIONS_START}{ui}{USER_INSTRUCTIONS_END}"))
}
}
#[derive(Debug)]

View File

@@ -376,7 +376,13 @@ async fn includes_user_instructions_message_in_request() {
request_body["input"][0]["content"][0]["text"]
.as_str()
.unwrap()
.starts_with("be nice")
.starts_with("<user_instructions>\n\nbe nice")
);
assert!(
request_body["input"][0]["content"][0]["text"]
.as_str()
.unwrap()
.ends_with("</user_instructions>")
);
}