Skip to content

Commit 9c083cf

Browse files
chaynaborsevanliu048brandonskiser
authored
feat: Prompt users to select a different model when at capacity (#451)
Co-authored-by: YIFAN LIU <[email protected]> Co-authored-by: Brandon Kiser <[email protected]>
1 parent c266817 commit 9c083cf

File tree

3 files changed

+129
-50
lines changed

3 files changed

+129
-50
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ members = [
99
"tests/fig-api/fig-api-mock",
1010
"tests/figterm2",
1111
]
12+
default-members = ["crates/chat-cli"]
1213

1314
[workspace.package]
1415
authors = [

crates/chat-cli/src/cli/chat/cli/model.rs

Lines changed: 54 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -46,60 +46,66 @@ pub struct ModelArgs;
4646

4747
impl ModelArgs {
4848
pub async fn execute(self, session: &mut ChatSession) -> Result<ChatState, ChatError> {
49-
queue!(session.stderr, style::Print("\n"))?;
50-
let active_model_id = session.conversation.model.as_deref();
51-
let labels: Vec<String> = MODEL_OPTIONS
52-
.iter()
53-
.map(|opt| {
54-
if (opt.model_id.is_empty() && active_model_id.is_none()) || Some(opt.model_id) == active_model_id {
55-
format!("{} (active)", opt.name)
56-
} else {
57-
opt.name.to_owned()
58-
}
59-
})
60-
.collect();
61-
62-
let selection: Option<_> = match Select::with_theme(&crate::util::dialoguer_theme())
63-
.with_prompt("Select a model for this chat session")
64-
.items(&labels)
65-
.default(0)
66-
.interact_on_opt(&dialoguer::console::Term::stdout())
67-
{
68-
Ok(sel) => {
69-
let _ = crossterm::execute!(
70-
std::io::stdout(),
71-
crossterm::style::SetForegroundColor(crossterm::style::Color::Magenta)
72-
);
73-
sel
74-
},
75-
// Ctrl‑C -> Err(Interrupted)
76-
Err(dialoguer::Error::IO(ref e)) if e.kind() == std::io::ErrorKind::Interrupted => None,
77-
Err(e) => return Err(ChatError::Custom(format!("Failed to choose model: {e}").into())),
78-
};
49+
Ok(select_model(session)?.unwrap_or(ChatState::PromptUser {
50+
skip_printing_tools: false,
51+
}))
52+
}
53+
}
7954

80-
queue!(session.stderr, style::ResetColor)?;
55+
pub fn select_model(session: &mut ChatSession) -> Result<Option<ChatState>, ChatError> {
56+
queue!(session.stderr, style::Print("\n"))?;
57+
let active_model_id = session.conversation.model.as_deref();
58+
let labels: Vec<String> = MODEL_OPTIONS
59+
.iter()
60+
.map(|opt| {
61+
if (opt.model_id.is_empty() && active_model_id.is_none()) || Some(opt.model_id) == active_model_id {
62+
format!("{} (active)", opt.name)
63+
} else {
64+
opt.name.to_owned()
65+
}
66+
})
67+
.collect();
8168

82-
if let Some(index) = selection {
83-
let selected = &MODEL_OPTIONS[index];
84-
let model_id_str = selected.model_id.to_string();
85-
session.conversation.model = Some(model_id_str);
69+
let selection: Option<_> = match Select::with_theme(&crate::util::dialoguer_theme())
70+
.with_prompt("Select a model for this chat session")
71+
.items(&labels)
72+
.default(0)
73+
.interact_on_opt(&dialoguer::console::Term::stdout())
74+
{
75+
Ok(sel) => {
76+
let _ = crossterm::execute!(
77+
std::io::stdout(),
78+
crossterm::style::SetForegroundColor(crossterm::style::Color::Magenta)
79+
);
80+
sel
81+
},
82+
// Ctrl‑C -> Err(Interrupted)
83+
Err(dialoguer::Error::IO(ref e)) if e.kind() == std::io::ErrorKind::Interrupted => return Ok(None),
84+
Err(e) => return Err(ChatError::Custom(format!("Failed to choose model: {e}").into())),
85+
};
8686

87-
queue!(
88-
session.stderr,
89-
style::Print("\n"),
90-
style::Print(format!(" Using {}\n\n", selected.name)),
91-
style::ResetColor,
92-
style::SetForegroundColor(Color::Reset),
93-
style::SetBackgroundColor(Color::Reset),
94-
)?;
95-
}
87+
queue!(session.stderr, style::ResetColor)?;
9688

97-
execute!(session.stderr, style::ResetColor)?;
89+
if let Some(index) = selection {
90+
let selected = &MODEL_OPTIONS[index];
91+
let model_id_str = selected.model_id.to_string();
92+
session.conversation.model = Some(model_id_str);
9893

99-
Ok(ChatState::PromptUser {
100-
skip_printing_tools: false,
101-
})
94+
queue!(
95+
session.stderr,
96+
style::Print("\n"),
97+
style::Print(format!(" Using {}\n\n", selected.name)),
98+
style::ResetColor,
99+
style::SetForegroundColor(Color::Reset),
100+
style::SetBackgroundColor(Color::Reset),
101+
)?;
102102
}
103+
104+
execute!(session.stderr, style::ResetColor)?;
105+
106+
Ok(Some(ChatState::PromptUser {
107+
skip_printing_tools: false,
108+
}))
103109
}
104110

105111
/// Returns Claude 3.7 for: Amazon IDC users, FRA region users

crates/chat-cli/src/cli/chat/mod.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ use clap::{
3939
Parser,
4040
};
4141
use cli::compact::CompactStrategy;
42+
use cli::model::select_model;
4243
use context::ContextManager;
4344
pub use conversation::ConversationState;
4445
use conversation::TokenWarningLevel;
@@ -660,6 +661,12 @@ impl ChatSession {
660661
Err(ChatError::Interrupted { tool_uses: None })
661662
}
662663
},
664+
ChatState::RetryModelOverload => tokio::select! {
665+
res = self.retry_model_overload(os) => res,
666+
Ok(_) = ctrl_c_stream => {
667+
Err(ChatError::Interrupted { tool_uses: None })
668+
}
669+
},
663670
ChatState::Exit => return Ok(()),
664671
};
665672

@@ -776,15 +783,49 @@ impl ChatSession {
776783
},
777784
ApiClientError::QuotaBreach { message, .. } => (message, Report::from(err), true),
778785
ApiClientError::ModelOverloadedError { request_id, .. } => {
786+
if self.interactive {
787+
execute!(
788+
self.stderr,
789+
style::SetAttribute(Attribute::Bold),
790+
style::SetForegroundColor(Color::Red),
791+
style::Print(
792+
"\nThe model you've selected is temporarily unavailable. Please select a different model.\n"
793+
),
794+
style::SetAttribute(Attribute::Reset),
795+
style::SetForegroundColor(Color::Reset),
796+
)?;
797+
798+
if let Some(id) = request_id {
799+
self.conversation
800+
.append_transcript(format!("Model unavailable (Request ID: {})", id));
801+
}
802+
803+
self.inner = Some(ChatState::RetryModelOverload);
804+
805+
return Ok(());
806+
}
807+
808+
// non-interactive throws this error
809+
let model_instruction = "Please relaunch with '--model <model_id>' to use a different model.";
779810
let err = format!(
780-
"The model you've selected is temporarily unavailable. Please use '/model' to select a different model and try again.{}\n\n",
811+
"The model you've selected is temporarily unavailable. {}{}\n\n",
812+
model_instruction,
781813
match request_id {
782814
Some(id) => format!("\n Request ID: {}", id),
783815
None => "".to_owned(),
784816
}
785817
);
786818
self.conversation.append_transcript(err.clone());
787-
("Amazon Q is having trouble responding right now", eyre!(err), true)
819+
execute!(
820+
self.stderr,
821+
style::SetAttribute(Attribute::Bold),
822+
style::SetForegroundColor(Color::Red),
823+
style::Print("Amazon Q is having trouble responding right now:\n"),
824+
style::Print(format!(" {}\n", err.clone())),
825+
style::SetAttribute(Attribute::Reset),
826+
style::SetForegroundColor(Color::Reset),
827+
)?;
828+
("Amazon Q is having trouble responding right now", eyre!(err), false)
788829
},
789830
ApiClientError::MonthlyLimitReached { .. } => {
790831
let subscription_status = get_subscription_status(os).await;
@@ -935,6 +976,8 @@ enum ChatState {
935976
/// Parameters for how to perform the compaction request.
936977
strategy: CompactStrategy,
937978
},
979+
/// Retry the current request if we encounter a model overloaded error.
980+
RetryModelOverload,
938981
/// Exit the chat.
939982
Exit,
940983
}
@@ -2086,6 +2129,35 @@ impl ChatSession {
20862129
Ok(ChatState::ExecuteTools)
20872130
}
20882131

2132+
async fn retry_model_overload(&mut self, os: &mut Os) -> Result<ChatState, ChatError> {
2133+
match select_model(self) {
2134+
Ok(Some(_)) => (),
2135+
Ok(None) => {
2136+
// User did not select a model, so reset the current request state.
2137+
self.conversation.enforce_conversation_invariants();
2138+
self.conversation.reset_next_user_message();
2139+
self.pending_tool_index = None;
2140+
return Ok(ChatState::PromptUser {
2141+
skip_printing_tools: false,
2142+
});
2143+
},
2144+
Err(err) => return Err(err),
2145+
}
2146+
2147+
let conv_state = self
2148+
.conversation
2149+
.as_sendable_conversation_state(os, &mut self.stderr, true)
2150+
.await?;
2151+
2152+
if self.interactive {
2153+
self.spinner = Some(Spinner::new(Spinners::Dots, "Thinking...".to_owned()));
2154+
}
2155+
2156+
Ok(ChatState::HandleResponseStream(
2157+
os.client.send_message(conv_state).await?,
2158+
))
2159+
}
2160+
20892161
/// Apply program context to tools that Q may not have.
20902162
// We cannot attach this any other way because Tools are constructed by deserializing
20912163
// output from Amazon Q.

0 commit comments

Comments
 (0)