Skip to content

Commit c9a5666

Browse files
fix: telemetry events in q chat, other refactoring and cleanup (#693)
1 parent d281002 commit c9a5666

File tree

3 files changed

+109
-231
lines changed

3 files changed

+109
-231
lines changed

crates/q_cli/src/cli/chat/conversation_state.rs

Lines changed: 43 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use eyre::{
88
bail,
99
};
1010
use fig_api_client::model::{
11+
AssistantResponseMessage,
1112
ChatMessage,
1213
ConversationState as FigConversationState,
1314
EnvState,
@@ -28,9 +29,14 @@ use fig_settings::history::{
2829
OrderBy,
2930
};
3031
use fig_util::Shell;
32+
use rand::distributions::{
33+
Alphanumeric,
34+
DistString,
35+
};
3136
use regex::Regex;
3237
use tracing::{
3338
error,
39+
info,
3440
warn,
3541
};
3642

@@ -66,18 +72,21 @@ const MAX_CONVERSATION_STATE_HISTORY_LEN: usize = 25;
6672
/// Tracks state related to an ongoing conversation.
6773
#[derive(Debug, Clone)]
6874
pub struct ConversationState {
69-
pub conversation_id: Option<String>,
75+
/// Randomly generated on creation.
76+
conversation_id: String,
7077
/// The next user message to be sent as part of the conversation. Required to be [Some] before
7178
/// calling [Self::as_sendable_conversation_state].
72-
pub next_message: Option<Message>,
73-
pub history: VecDeque<Message>,
79+
pub next_message: Option<UserInputMessage>,
80+
pub history: VecDeque<ChatMessage>,
7481
tools: Vec<Tool>,
7582
}
7683

7784
impl ConversationState {
7885
pub fn new(tool_config: ToolConfiguration) -> Self {
86+
let conversation_id = Alphanumeric.sample_string(&mut rand::thread_rng(), 9);
87+
info!(?conversation_id, "Generated new conversation id");
7988
Self {
80-
conversation_id: None,
89+
conversation_id,
8190
next_message: None,
8291
history: VecDeque::new(),
8392
tools: tool_config
@@ -126,30 +135,32 @@ impl ConversationState {
126135
}
127136
}
128137

129-
let msg = Message(ChatMessage::UserInputMessage(UserInputMessage {
138+
let msg = UserInputMessage {
130139
content: input,
131140
user_input_message_context: Some(user_input_message_context),
132141
user_intent: None,
133-
}));
142+
};
134143
self.next_message = Some(msg);
135144
}
136145

137-
pub fn push_assistant_message(&mut self, message: Message) {
146+
pub fn push_assistant_message(&mut self, message: AssistantResponseMessage) {
138147
debug_assert!(self.next_message.is_none(), "next_message should not exist");
139148
if let Some(next_message) = self.next_message.as_ref() {
140149
warn!(?next_message, "next_message should not exist");
141150
}
142-
self.history.push_back(message);
151+
self.history.push_back(ChatMessage::AssistantResponseMessage(message));
143152
}
144153

145-
/// Returns the conversation id, if available.
146-
pub fn conversation_id(&self) -> Option<&str> {
147-
self.conversation_id.as_deref()
154+
/// Returns the conversation id.
155+
pub fn conversation_id(&self) -> &str {
156+
self.conversation_id.as_ref()
148157
}
149158

150159
/// Returns the message id associated with the last assistant message, if present.
160+
///
161+
/// This is equivalent to `utterance_id` in the Q API.
151162
pub fn message_id(&self) -> Option<&str> {
152-
self.history.iter().last().and_then(|m| match &m.0 {
163+
self.history.iter().last().and_then(|m| match &m {
153164
ChatMessage::AssistantResponseMessage(m) => m.message_id.as_deref(),
154165
ChatMessage::UserInputMessage(_) => None,
155166
})
@@ -167,11 +178,11 @@ impl ConversationState {
167178
},
168179
..Default::default()
169180
};
170-
let msg = Message(ChatMessage::UserInputMessage(UserInputMessage {
181+
let msg = UserInputMessage {
171182
content: String::new(),
172183
user_input_message_context: Some(user_input_message_context),
173184
user_intent: None,
174-
}));
185+
};
175186
self.next_message = Some(msg);
176187
}
177188

@@ -197,11 +208,11 @@ impl ConversationState {
197208
},
198209
..Default::default()
199210
};
200-
let msg = Message(ChatMessage::UserInputMessage(UserInputMessage {
211+
let msg = UserInputMessage {
201212
content: deny_input,
202213
user_input_message_context: Some(user_input_message_context),
203214
user_intent: None,
204-
}));
215+
};
205216
self.next_message = Some(msg);
206217
}
207218

@@ -219,33 +230,20 @@ impl ConversationState {
219230

220231
// Updating `self` so that the current next_message is moved to history.
221232
let mut last_message = self.next_message.take().unwrap();
222-
match &mut last_message.0 {
223-
ChatMessage::UserInputMessage(msg) => {
224-
if let Some(ctx) = &mut msg.user_input_message_context {
225-
ctx.tools.take();
226-
}
227-
},
228-
ChatMessage::AssistantResponseMessage(_) => (),
233+
if let Some(ctx) = &mut last_message.user_input_message_context {
234+
// Don't include the tool spec in all user messages in the history.
235+
ctx.tools.take();
229236
}
230-
self.history.push_back(last_message);
237+
self.history.push_back(ChatMessage::UserInputMessage(last_message));
231238

232239
FigConversationState {
233-
conversation_id: curr_state.conversation_id,
234-
user_input_message: curr_state
235-
.next_message
236-
.and_then(|m| match m.0 {
237-
ChatMessage::AssistantResponseMessage(_) => None,
238-
ChatMessage::UserInputMessage(user_input_message) => Some(user_input_message),
239-
})
240-
.expect("no user input message available"),
241-
history: Some(curr_state.history.into_iter().map(|m| m.0).collect()),
240+
conversation_id: Some(curr_state.conversation_id),
241+
user_input_message: curr_state.next_message.expect("no user input message available"),
242+
history: Some(curr_state.history.into()),
242243
}
243244
}
244245
}
245246

246-
#[derive(Debug, Clone)]
247-
pub struct Message(pub ChatMessage);
248-
249247
impl From<InvokeOutput> for ToolResultContentBlock {
250248
fn from(value: InvokeOutput) -> Self {
251249
match value.output {
@@ -554,16 +552,16 @@ mod tests {
554552
async fn test_conversation_state_history_handling() {
555553
let mut conversation_state = ConversationState::new(load_tools().unwrap());
556554

555+
// First, build a large conversation history. We need to ensure that the order is always
556+
// User -> Assistant -> User -> Assistant ...and so on.
557557
conversation_state.append_new_user_message("start".to_string()).await;
558-
for i in 0..=20 {
558+
for i in 0..=100 {
559559
let _ = conversation_state.as_sendable_conversation_state();
560-
conversation_state.push_assistant_message(Message(ChatMessage::AssistantResponseMessage(
561-
AssistantResponseMessage {
562-
message_id: None,
563-
content: i.to_string(),
564-
tool_uses: None,
565-
},
566-
)));
560+
conversation_state.push_assistant_message(AssistantResponseMessage {
561+
message_id: None,
562+
content: i.to_string(),
563+
tool_uses: None,
564+
});
567565
conversation_state.append_new_user_message(i.to_string()).await;
568566
}
569567

@@ -577,7 +575,7 @@ mod tests {
577575
let last_msg = s.history.as_ref().unwrap().iter().last().unwrap();
578576
match last_msg {
579577
ChatMessage::AssistantResponseMessage(assistant_response_message) => {
580-
assert_eq!(assistant_response_message.content, "20");
578+
assert_eq!(assistant_response_message.content, "100");
581579
},
582580
other @ ChatMessage::UserInputMessage(_) => {
583581
panic!("Last message should be from the assistant, instead found {:?}", other)

0 commit comments

Comments
 (0)