Scoping Conversation Memory0:00
A lot of the AI chat demos forget the most important, and it's also the hardest part, memory. In this episode, we are going to build ticket scoped conversations that remember the right context, and forget all of the rest. [MUSIC] With our ticket triage agent, we forced the model to return structured data, which of course it needed structured data. But when it comes to chat or conversations,
which of course it needed structured data. But when it comes to chat or conversations, we need something that is just a little bit more flexible. When it comes to something like this, the key idea is scope. We need to scope these conversations to the ticket that the conversation is about, because we don't want conversations for one ticket leaking into conversations for another ticket, that would be absolutely horrible. So each ticket gets its own boundary,
Adding Conversation ID Column0:54
another ticket, that would be absolutely horrible. So each ticket gets its own boundary, which means that anytime that we interact with the AI, we are only going to send the context that belongs to the ticket. That's going to ensure that that boundary is in place, but it also helps keep costs predictable, because we limit how much history we can send. So the first thing that we need to do is modify the tickets table, because we want to link an AI conversation to a particular ticket. So let's open up that migration,
because we want to link an AI conversation to a particular ticket. So let's open up that migration, and all we need to do is add a column. It's a string, and let's call it AI conversation ID. Now, of course, this can be null, and let's also place it after AI tags. But of course, we need to be able to drop that column. So in the down method, we will call drop column, passing in AI conversation ID. And of course, me being me, chances are good.
passing in AI conversation ID. And of course, me being me, chances are good. I mistyped that. So let's be sure that's the same conversation. OK, OK, so now those are the same. And it is very good that I test, not tested, but I checked that. So now with that in place, we can migrate our database, and we are good as far as our databases can serve. And so the next thing we need to do is make our agent that will be our ticket assistant.
Creating Ticket Assistant Agent2:23
And so the next thing we need to do is make our agent that will be our ticket assistant. Now, I don't remember if we talked about where our agents go, but whenever we create an agent, they go into the app AI agents folder. We can see that the ticket triage is there. I'm sorry about that name, but then we have our ticket assistant. So yeah, that's what we have. And the class is going to look very similar. Of course, we don't have the structured like we did for the triage agent, but we still have conversational, which we want in this case,
Of course, we don't have the structured like we did for the triage agent, but we still have conversational, which we want in this case, because this is going to be a conversational agent. We don't necessarily need has tools, so we will get rid of that. Now, we want this to be promptable, but because this is a conversational agent, we want to be able to remember the history or remember the context. In which case, there's a remembers conversations trait, which is going to handle all of that for us. Now, we can also handle that ourselves if we needed to do something special, but remembers conversations is going to have that memory built in.
Now, we can also handle that ourselves if we needed to do something special, but remembers conversations is going to have that memory built in. So we don't have to worry that much about it, which is nice. But before we move on, let's go ahead and let's include our attributes here. So first of all, we have the provider attribute, which will once again be open AI. Well, let's include the lab enum, make sure that I use the right value. So lab open AI. Yep, I did. And then we will have the use cheapest model attribute,
Yep, I did. And then we will have the use cheapest model attribute, just like we did for the triage agent. And then finally, we will have our max tokens, which in this case, let's be a little liberal here because this is a conversation agent, which means that there's going to be some history involved here. So it makes sense to have our max tokens at 1500. We might need to even bump that up higher. It just really depends upon our particular needs. So we have remembers conversations as a trait, which is great,
It just really depends upon our particular needs. So we have remembers conversations as a trait, which is great, because that's going to handle the history for us. So now we need to know what ticket that we're going to be working with. So let's just have a constructor that will have a read only property called ticket ID, because that's exactly what it is. And this will give us, you know, the ability to get the ticket, because when it comes to our instructions, which is just like what we did for the triage agent, we are going to supply the instructions that we want this agent to use every
the triage agent, we are going to supply the instructions that we want this agent to use every time that we prompt. So our prompt here is going to, well, of course, be our list of instructions, which, you know, we can start off and say you are a support assistant. Stay strictly within the current ticket. Hopefully it's not going to judge me by my spelling or my typing. And let's also say that if you are unsure, ask a clarifying question. So that's, of course, going to be the beginning part of the instructions. But that's not nearly enough because we need to supply essentially the context.
Building Ticket Context5:40
So that's, of course, going to be the beginning part of the instructions. But that's not nearly enough because we need to supply essentially the context. We need to supply all of the useful information that the AI provider is going to need to work with this particular ticket. So what we could do is build our context. It will just be simply a string that we will output here, which we could get like this to where we would call a method. Let's say that it's ticket context. And then that will get our context so that we can output it here.
Let's say that it's ticket context. And then that will get our context so that we can output it here. But the context is going to be, as I mentioned, everything. Tags, messages, a department, sentiment, everything about a ticket. We really need to supply that way the AI will have all of the information that it needs to start working with that ticket. So let's scroll down a little bit. We'll leave our messages because that is going to give us the ability to control the messages that we include if we wanted to do that. But let's get rid of the tools method because we don't need that right now.
to control the messages that we include if we wanted to do that. But let's get rid of the tools method because we don't need that right now. What we do need is our method to get the context of the ticket, which is going to return a string. So let's first of all get the ticket and we want to include several things. First of all, we want all, well, not all, but we want some of the messages. And ideally, it would be the most recent messages. So let's query to get the latest messages. Let's also limit the amount of messages. Let's say five.
Let's also limit the amount of messages. Let's say five. And then we want to include our tags so that then we will find the ticket with this ticket ID. And yeah, so we need our ticket model there so that we will check. If we don't have a ticket from this, then we can say that the ticket context unavailable because we don't have a ticket and that's going to be fine. But if we do have a ticket, we want to build our context, which, as I mentioned, is going to be everything. So first of all, let's get our tags.
which, as I mentioned, is going to be everything. So first of all, let's get our tags. We have that from our ticket and we are going to pluck the name of each ticket. And we want to turn this into a string separated with comments. Then let's get the department. But, you know, a ticket might not have a department set yet. So if we don't have a department, we'll just say in a, it's not available or not applicable. The same is going to be true for the sentiment, which is there or it's not. So again, in a, if it's not, let's also get the tags text so that if we have
The same is going to be true for the sentiment, which is there or it's not. So again, in a, if it's not, let's also get the tags text so that if we have tags, then yes, great. If not, we have none. And then we want our messages, which, of course, we will get from our tickets. But let's call the reverse method so that if I'm thinking of this correctly, it's going to have the latest message first. So then we want to map our messages because I want to turn this into a string where
So then we want to map our messages because I want to turn this into a string where the first string is the roll. The second string is the body of the message. So we'll have message roll all by the message body. And we want to implode that as well on a new line so that then we have practically everything. Well, no, we don't have everything we need. There's still some things that we need.
Well, no, we don't have everything we need. There's still some things that we need. But for right now, we can return our context, which will look like this. We'll say ticket context. And then we will have the subject, which will come from the ticket subject. Let's also have the status. Let's copy and paste that a few times. So our status will, of course, be the status from the ticket. The priority will naturally be the priority. Then we want the department, which will come from that department variable.
The priority will naturally be the priority. Then we want the department, which will come from that department variable. The same is true for the sentiment. And we want to do the same thing for the tags so that we will have our tags text. And then finally, we want our recent messages to include our messages. And that will be all of our context. But let's do this like that. And that is our context so that now for every prompt, we are telling the model that they
And that is our context so that now for every prompt, we are telling the model that they are a support assistant, we are providing the context for the ticket. And then that's it. Now, as I mentioned, with the remembers conversation trait, our agent is going to remember the conversation, which is nice. But we can also have complete internal control over the messages that are sent as part of the history.
as part of the history. And we do that by implementing the messages method. So if we wanted to, we could return our ticket message, where the ticket ID is the ticket ID that we have for this ticket, then we could get the latest and blah, blah, blah. And we could have complete and total control over how these messages are provided. Or we could just let the SDK do it for us, which I'm inclined to do that.
Implementing Chat Controller11:05
provided. Or we could just let the SDK do it for us, which I'm inclined to do that. But just know that that option is there. You just have to implement the messages method. And so now that we have our agent, we need to make a controller for our tickets chat. So let's call it ticket chat controller. And this is going to be invocable. Now I'm going to paste in the preamble of this method, because there's really nothing
Now I'm going to paste in the preamble of this method, because there's really nothing new here. So first of all, we, of course, make sure that we have a message that we want, because we need to include that with our prompt. Next we create our ticket assistant agent, and we build our prompt. All we are doing and saying that the user message is including the message from the request. Then we log our AI run.
the request. Then we log our AI run. But the main difference here is the feature key. It's ticket chat in this case. And then from there, we want to try to send this to our model. First of all, we need to check if our ticket has an AI conversation ID, then we essentially want to continue the conversation. So our agent class has a continue method. We need to include the AI conversation ID as the currently logged in user.
So our agent class has a continue method. We need to include the AI conversation ID as the currently logged in user. So not only are we going to essentially scope this conversation to the ticket, we are also scoping this conversation to the user as well. So everything is getting scoped. So whenever we continue the conversation with the given conversation ID and the user, then we will prompt with our prompt. However, if we don't have a current conversation going, then we need to start
we will prompt with our prompt. However, if we don't have a current conversation going, then we need to start one, in which first case we will use our agent calling for user passing in the currently signed in user, and we are prompting with our prompt so that then we need to update the ticket so that we can set the AI conversation ID to the response conversation ID. Now, of course, something could go horribly wrong. So we need to catch a throwable exception and we will update our run just like
Now, of course, something could go horribly wrong. So we need to catch a throwable exception and we will update our run just like we did with the triage agent so that we will set the status to failed. We want to also update the finished at attribute to now. And then we also want to include the error message based upon the exceptions get message method, and then after that, we will just throw e again. So that if we make it past the catch, then we're good. We are going to update our run using the status to be succeeded.
So that if we make it past the catch, then we're good. We are going to update our run using the status to be succeeded. We'll also set finished at to now, and then we also need to check is set response usage, because we want to log our usage, which I'm just going to place that in because we've seen that code before. But you know, that's not enough. I mean, yes, we have logged our AI run, we have run our AI prompt updated the run if
I mean, yes, we have logged our AI run, we have run our AI prompt updated the run if it failed, updated if it succeeded, and then created the usage. But I also want to update the ticket messages with first of all, the message from the user. So that will be the request user ID, the role will be user, and then the body will come from the request with that message value. But that's also not enough. I want to include the message from the AI so that the user ID is no, the role
But that's also not enough. I want to include the message from the AI so that the user ID is no, the role will say is agent, and then we want as the body, the response. So that means inside of the browser, we are going to be adding more to the conversation so that the user will send a message, it'll show up in the conversation, the AI will respond, it will show up in the conversation, kind of like how system here used the AI summary.
it will show up in the conversation, kind of like how system here used the AI summary. So the last thing I want to do here is return response, Jason, and we'll say that the status is okay, and we will include the message coming from the response. All right. So that's a controller. It's a lot of code. And ideally, we would have AI right at for us. And in today's day and age, I would have AI right at for us.
And ideally, we would have AI right at for us. And in today's day and age, I would have AI right at for us. But you know, it is what it is. And it's a lot of code, but it's very simple what it's doing. We are logging the run of the prompt, then we are actually prompting catching in case that there's an error updating if it's successful, logging the usage, and then creating messages based upon what the user supplied and what the assistant supplied. So now let's go to our routes so that we can add a route to that new controller
Adding Chat Route16:07
based upon what the user supplied and what the assistant supplied. So now let's go to our routes so that we can add a route to that new controller . Let's just copy what we have for the triage and the URL is going to end in chat instead of triage. Of course, the name is ticket chat controller. And let's give it a name of tickets, AI chat. So now that we have our agent and our controller, we need some way to actually see this in action,
So now that we have our agent and our controller, we need some way to actually see this in action, and we will implement that in the next episode.
