In this chapter, we're going to implement agent memory. Right now, our agent and our conversations have no context. The agent doesn't know the history of our conversations. It doesn't understand our previous messages. Each message is technically a new project.
Let's test that out. As always ensure that you are on your main branch and feel free to synchronize the changes. The last chapter was 17 billing. Make sure you have npm run dev and ingest running. And what I want you to do is to create a very simple build a landing page.
The one we already did hundreds of times. So go ahead and build a simple landing page. And here we have a very simple landing page. What if I just send it a message, make it red? What we would expect to happen is that it would understand that my previous message was build a landing page and it will now change it to red.
But the truth is that won't happen, right? So it didn't modify the landing page. It simply updated the page to be a red themed page. If I add a calculator there, it won't understand what I mean, right? But what we want to is basically a landing page like this colored in red or if I tell it build a calculator in the hero segment I want a little calculator here instead of the rocket.
So let's go ahead and make that possible. The reason we really really need this is not for continuous conversation. The more important reason is, A, I can make mistakes. You already saw that sometimes it forgot to add useClient. And we want to allow our users to say, hey, you made a mistake.
You forgot useClient. Because if I just give it that right now, it will have no idea what I'm talking about. It has no idea that previously it created this. So let's improve that. Let's fix that.
What we're going to do is the following. We're going to go inside of our functions.ts and in here, let's go right after we do sandbox.id And let's do const previousMessages() and let's do await step.run() getPreviousMessages() it is an asynchronous arrow function like this. And then let's do const formattedMessages and give it a type of message, which you can import from Ingest AgentKit. So I can put type message here as well. And set it to be an empty array.
And now let's fetch the messages using await Prisma messages. And let's do find many where project ID is event data project ID like that and let's add order by created at descending and then for const message of messages Let's go ahead and push each message to this new array. The reason we are doing it like this is so that we have this type which cannot be broken. So formatted messages formattedMessages.push type text role if message role is assistant it will be a lowercase assistant otherwise lowercase user like this And the content is going to be message.content. And let me just see what is the problem here.
Formatted messages. Oh, it should be an array of message. My apologies. There we go. And then let's go ahead and let's return formatted messages.
There we go. So right Now it is going to have context of the entire conversation. It will know exactly what we told it. It will know exactly what it responded back to us. So now let's go ahead and let's create a const state to be create state from agent kit again.
So create state from ingest agent kit. Let's give it a type of agentState. So we already have agentState defined right here. We have summary and files. And let's go ahead and do the following.
The first object in here will be summary, make it empty, and files make them empty. And then in the second object you will have messages, which will be previous messages. And now we have the state. So now we have to add this state to a couple of places. So let's go ahead and find our network execution right here.
And let's add default state to be the state from above. And in the result, when we run it, let's add state and make it state. Or you can use the shorthand operator like this. Perfect. So let's go ahead and do this again.
Build a landing page. Let's do that. Build a landing page and let's follow the context here. As you can see, we now have a step get previous messages. And you can now see that we included all of the new, all of the older messages, even the responses and the user messages here.
So now the code agent, as you can see, has messages. So it knows exactly what we ask it to do now. So let's go ahead and wait for this result right here. We should see it any second. And you can see how it preserved the red color because that's what we asked previously.
So it already knows the context. And now this is what I'm going to say. Add a calculator in the hero segment. So I didn't tell it to create a landing page. I didn't tell it anything other than this and let's see how well it will do.
Of course, it can make a mistake even at this point. But now it has the context. It knows that add a calculator in the hero segment is the message after build a landing page. And it is a message after created a polished, fully responsive, red-themed landing page. So let's see if it was able to do this or not.
We should see any second now. And it looks like it wasn't able to do it. Let me refresh just in case. And let's see the code. Looks like we didn't add it.
So I just want to make sure that I didn't accidentally maybe do the incorrect order of loading the messages here, created at this sending, I think that should be OK. Let's try again. You didn't add the calculator component in the hero page. Add the calculator component. So in the previous example, when I tried this privately off camera, it worked for me.
So you can see that AI is sometimes a bit unpredictable, but I just want to tune it. We might even have to modify the prompt for this. We might have to tell it, you have context of all older messages, you can use older messages. But you can already see a slight improvement, right? Because right now, when we asked it to create that landing page, it made it red, right?
So it understood the context. But I have a feeling it is still not understanding entirely what we want. Let's see if this will be better. And there we go. We now have a very, very simple calculator in our landing page.
Great. So this is exactly what we asked. Let's try and make it green now, just to make sure that this is the last message it sees, right? I want, I'm okay with adding state, I just don't want to make it so that it can, it's confused about what is the latest message. So let's follow along in the running here.
So yes, make it green is the first message in the array here. Maybe that should be the opposite. I don't know. Because maybe it's now thinking that this, the last message in the array, is the newest one. And let's see the coding agent.
I think the coding agent understands the same thing maybe or not. Yeah, in here it also the last message here is build the landing page. So I keep thinking that maybe or maybe not. Yeah, you can see that it preserved the fact that it's a landing page. It added the calculator and it changed it green.
So obviously now it understands what we are doing. So I'm gonna Go ahead and add a little to do here. To do, change to ascending if AI does not understand what is the latest message. But I think that it works pretty well. I think that it understands that make it green refers to the landing page, which I scalded it for because it didn't add the calculator.
So now it has both the calculator and it is green. And I think that's exactly what we wanted. So let's try and just do one more time. Make sure to use separate component files. And while this is happening, Let's go ahead and do the following.
I want you to visit my public assets folder or the source code. You can use the link in the description or the link you can see on the screen. And in here I added a new file called additional prompts. And in here I have the response prompt and the fragment title prompt. So go ahead and copy this entire file.
Go inside of your prompt.ts. And at the end of it, or if it's easier at the top, just add those two and export both of them. So export the fragment title and export the response prompt. We're now going to use this to create two more agents, so they create better responses, and so they create a proper name here. So let's see what it did.
There we go. So it understood the context. It seems to, again, made it red. I keep thinking that the way we are loading these previous messages maybe isn't perfect. So you're going to have to tweak that a little bit.
Or Maybe you can somehow modify it so that it knows you can add it in the content. Like maybe, let's see, I'm thinking of keeping a track of the index. And then maybe we can modify the content and say first message, and then the message content. Something like that. And then maybe in here we can keep track of index, and then replace this with the index.
For example, maybe that can instruct it better to understand what's going on. But what's important is that it at least understands the last message. Right? That's what I want to make sure. So I just told it to use separate components, and that's exactly what it did.
It preserved the fact that it's a landing page, but it made it into separate components. Let's do make it green, or let's do make it yellow this time. You know, keep testing it, make sure that it's listening to you. And you can play around with changing the order here. You can use that idea that I told you.
You can even explore, you know, in just documentation about the state. Maybe in there we can find something. I will of course research off camera and in the next chapter, I will tell you more information if I find out anything new. But what I want to do now is the following. In the function here, after the network finishes, so right here, after we get the result, I want to go ahead And I want to do the following.
So I want to create an agent called FragmentTitleGenerator. And that will be CreateAgent, like this. And let me just see, I already forgot how do we create agents. So the name will be something and then the description and then the system and then model. Okay, so I will just copy this.
Let's add it all over here. So the name will be FragmentTitleGenerator. A description will be a FragmentTitleGenerator. And for the model, we can use OpenAI, and you can use a cheaper model. You can use 4.0, for example, and you can remove the default parameters here.
So in here, use a cheaper model because this will just generate text. And in here, go ahead and use the fragment title prompt, which we just added. And also now import the response prompt. So let's go ahead and use this now. After this go ahead and copy it and now this will be a response generator.
Change this to be a response generator and a response generator. And change the system here to be response prompt. And now You're going to define two outputs. The first output here will be a fragment title. Await fragment title generator dot run.
And inside of here, you will pass the result state data summary. And then you're going to copy this, and this will be response. Response generator from the same thing. And then you're going to go inside of saveResult. And for the fragment title, go ahead and do the following.
FragmentTitle.firstInTheArray.type is equal to text fragmentTitle.firstInTheArray.content, otherwise just fragment. Like this. And let me just see, dot content is this. OK, Let me just build a little function so we don't have to do this in one file. So we have fragment title output, and we have response output here.
So I'm going to just collapse this. And then in here I'm going to do const generateFragmentTitle. And I will do if FragmentTitle output.type is not equal, first in the array, of course, the text return fragment. Otherwise, let's go ahead and do if fragmentTitleGenerator first in the array.content. And let's check maybe if isArray.
Can I do this? Array.isArray, I think. Fragment.title.output. Because this can be an array, then let's return Fragment.title.output, first in the array.content.map, text, text.join. Output first and the right dot content dot map text text I'll join like this So basically, this should always return a string.
And otherwise, just return in the else here, just return the content. And let me just see. So this is a type of string here. And you can just add another safety1 fragment. OK, but this is unreachable, so I don't know why exactly.
String is not. OK, yeah, my bad. And now go ahead and copy this and call this generate response. And basically the same thing. So response output.
And in here, we're going to just set the default to be, here you go. Otherwise, it can be this. And now, when we have those two, in here we can put generate fragment title. And in the content, we can do generate response. There we go.
And this should now improve our app. So Let's see if that is true or not. So I'm going to do build a, let's actually start a new project just to make sure everything is clear. So build a calculator app like this. And let's follow the ingest to see if we're going to mess something up or not.
I mean, technically, I don't think it will ever be an array of items that we're going to have to join like this. It will almost always certainly be just this. But yeah, I guess we're trying it out. So we successfully did this fragment title, and we successfully did response generation. So let's see what it came up with.
There we go. Here's what I built for you. A snazzy calculator app with a slick responsive design. And we have a name this time for the Fragment calculator app. So that's what I wanted us to achieve, right?
If you don't want this, you don't have to use it. I mean, the app worked just fine before this. So if this, for some reason, messes up your app, you don't have to use it, of course. I just thought it would be fun to add that as well. And you can definitely write this in a better way.
I mean, starting with the fact that we can just do const title and just make it this. So let's do output and just make it this, like so. There we go. This is already better. And then you can do the same thing here, const output, change this to this, and then replace all instances here.
There we go. And you can probably also just use a single function, because it's exactly the same. So let's maybe do parse agent output. And in here, let's go ahead and see. This is a type of message or array.
So let's try value message array, like that. And then can I just use value? Const output value first in the array. Yeah, that works as well. So parseAgentOutput, then, is the only function you need.
And you can then put it maybe at the top of the file and we are later going to move it to libs. So basically parse agent output and in here it accepts the value which is a message which is an array. The message is a type of message from AgentKit. And now that we have parseAgentOutput, let's go down here. And keep the fragment title generator, keep the response generator, keep the two outputs, and remove generateResponse function.
And now what you're just going to do is parse, how did I call the function? ParseAgentOutput. And in here, you're going to parse response output. And in here, you're going to pass fragment title output. There we go.
And then you can move this function to utils here in the ingest. So export const parse agent output and import message from ingest kit here. There we go. Then you can remove it from here, find some place you're using it, and then, there we go, import from utils like this. And let's just do a sanity check here, make the calculator use glossy, glassy design, or something like that.
The only thing I'm trying to do here is again, confirm it can kind of understand the context of my previous messages and that it will give me a nice response with a name for my fragment. So we can follow again, instead of the running here, to make sure that's what's happening. There we go, fragment title generator, response generator, and let's see. Here's what I built for you, a sleek glassy design calculator app with a modern twist. Very cool.
So it understood that we just wanted a glassy design on top of our previous app. Amazing. I'm very, very satisfied with this. And I think that marks the end of this chapter. As always, you know, your app worked just fine up until this point.
So don't let this ruin your project if you don't like it. Feel free to research a bit yourself about whether this should be descending, ascending. And if this is failing for whatever reason, the fragment title generator and the response generator, you can also remove them. You don't need them. And make sure to just use a cheaper model here, simply because you can even use an entirely new model, like Gemini, Grok, whatever, because we are not calling any tools here.
So just make sure this is something cheap, so it doesn't spend your credits for no reason when it's just generating some text. Amazing. So 18-agent-memory, let's go ahead and review that. I'm opening a new branch here. 18 agent memory.
I'm going to stage all of my changes. 18 agent memory. And I'm going to commit and I'm going to publish the branch. Let's go ahead and open a pull request. And let's review our changes.
And here we have the summary. We enhanced the message handling by retrieving and formatting previous messages for improved agent interactions. We added automated generation of concise, user-friendly summary messages and short, descriptive fragment titles. We updated the process for saving results to use dynamically generated summaries and titles instead of static content. And we introduced utility functions and structured prompt templates to standardize agent outputs.
And in here we have just one comment, and that is that we should probably check if the value is actually valid. So yeah, we could add this as well. And I just noticed that we returned the text fragment as placeholder, even though we use this both for the response and for the fragment title. So I should probably add a new value, something like fallback value, something like that. We'll see.
Nevertheless, very satisfied with this one. I will research before the next chapter if there's something we can do better when it comes to adding message history but I think this is as good as we can do right now. Great! So let's go ahead and change this to main and let's synchronize our changes and we should see our new pull request being merged. Amazing!
I believe that marks the end of this chapter. Amazing, amazing job and see you