New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversational Interface #262
Conversation
For simplicity of merging in the changes from `main`, due to heavy conflicts, this squashes all back-end commits down. * conversational flow, take 2 * update prompts and history * wip * Clean up conversational logic, fix several bugs * Fix field name in Answer API request * Fix termination sequence * Fix state machine logic * Tie selected snippet to explanation prompt, per iteration * Run cargo fmt * Fix clippy lints * Send snippets along during explanation phase Co-authored-by: Andre Bogus <andre@bloop.ai>
…lect returns a single integer
…o conversational-flow-fe
This allows multiple independent conversations per user.
…o conversational-flow-fe
This reverts commit 4b10f01.
second time is the charm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there are serious code improvements that should be made before we can review this in earnest, otherwise as an outsider this will be very hard to pick up.
| /// This gets the prior conversation. Be sure to drop the borrow before calling | ||
| /// [`add_conversation_entry`], lest we deadlock. | ||
| pub fn with_prior_conversation<T>( | ||
| &self, | ||
| user_id: &str, | ||
| f: impl Fn(&[(String, String)]) -> T, | ||
| ) -> T { | ||
| self.prior_conversational_store | ||
| .get(user_id) | ||
| .map(|r| f(&r.value()[..])) | ||
| .unwrap_or_else(|| f(&[])) | ||
| } | ||
|
|
||
| /// add a new conversation entry to the store | ||
| pub fn add_conversation_entry(&self, user_id: String, query: String) { | ||
| match self.prior_conversational_store.entry(user_id) { | ||
| Entry::Occupied(mut o) => o.get_mut().push((query, String::new())), | ||
| Entry::Vacant(v) => { | ||
| v.insert(vec![(query, String::new())]); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// extend the last answer for the session by the given fragment | ||
| pub fn extend_conversation_answer(&self, user_id: String, fragment: &str) { | ||
| if let Entry::Occupied(mut o) = self.prior_conversational_store.entry(user_id) { | ||
| if let Some((_, ref mut answer)) = o.get_mut().last_mut() { | ||
| answer.push_str(fragment); | ||
| } else { | ||
| error!("No answer to add {fragment} to"); | ||
| } | ||
| } else { | ||
| error!("We should not answer if there is no question. Fragment {fragment}"); | ||
| } | ||
| } | ||
|
|
||
| /// clear the conversation history for a user | ||
| pub fn purge_prior_conversation(&self, user_id: &str) { | ||
| self.prior_conversational_store.remove(user_id); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of leaving these in Application, I'd strongly prefer a dedicated component that wraps and contains this functionality. These are very low-level functions that do not logically belong in a high-level component such as Application.
| /// This gets the prior conversation. Be sure to drop the borrow before calling | ||
| /// [`add_conversation_entry`], lest we deadlock. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit of a foot-gun. Can we instead use an Arc<Vec<Arc<(String, String)>>> here, which would make access to members of DashMap safe? You can then update the record with the result from make_mut: https://doc.rust-lang.org/std/sync/struct.Arc.html#method.make_mut
| @@ -122,6 +123,8 @@ impl Application { | |||
| env | |||
| }; | |||
|
|
|||
| let prior_conversational_store = Arc::new(DashMap::new()); | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Arc::default() would work just as well, but as mentioned below, it would be good to wrap this in an struct.
| @@ -1,5 +1,5 @@ | |||
| use std::{ | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The size of this file is making it very hard to decipher what's exactly happening here.
It would be really helpful to move most of this:
- out of the webserver
- into smaller modules
- keep a super high-level, easy-to-review function call flow in the webserver. (1 small function )
| break grown_text; | ||
| .push(Stage::new("start", &query).with_time(stop_watch.lap())); | ||
|
|
||
| loop { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an unbounded loop inside a very long function, which make this logic inherently hard to reason about. It would be good to either make this shorter, or change this to loop to something with explicit bounds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactoring comments noted in: https://linear.app/bloopai/issue/BLO-406/clean-up-answer-endpoint


This adds support for a chat interface. With this bloop supports follow up queries which reference previous questions and answers in the conversational history. These are rendered in a chat window on the right-hand-side of the search results.
This bundles a host of changes, including:
OpenAIOpenAI's 'message' based prompt-formatThe abstractions in the
answer.rsmodule will be re-worked in a future PR.