Transcription
Today, we're going to build an evaluator optimizer workflow. Basically, this is a workflow where we have two LLM calls that are going in a loop until they complete the job or our evaluator is satisfied with the result that the generator provided. And we're going to build a writing reviewer workflow where we first write an article, then the reviewer reviews the article and gives feedback back to the writer. The writer rewrites the article based on the feedback, provides it back to the evaluator, and it goes in a loop until it completes the job, until the evaluator is satisfied with the result. It's probably going to be a long video because it's a live coding tutorial. I'm going to build this application from scratch. I'm going to face a lot of issues, a lot of uh bugs along the way. I'm going to fix them. And this whole application is built on top of Next.js and MRA. And I'm still learning Maestra. So I still have a few like those gray areas that I need to clarify, and I need to debug, and I need to like think through. So if you're interested in that, stick around and let's get into it.
So, I already initialized the project. It's a basic Next.js application with integrated Maestra. All I did is ran the command to initialize the Next.js application, basically this one. And I pasted this comment in to implement the Maestra into the Next.js. First, like first, what I like to do is to think through the components that we need to implement this. So obviously, it's going to be a workflow. Um, let's actually draw maybe it's on Excal. It's a very simple workflow where we have our writer and we have our reviewer. But at first glance, it won't be as simple as that because we need to actually loop around it. So we would need to, how do I do it? We would need to loop back from the reviewer to the writer and then go again to the writer. So I think it's going to be like, at least how I'm thinking about it, it's going to be two separate workflows. First is this one that writes and reviews, and the other one is going to be like a parent workflow that controls the loop, and that controls what goes into this workflow, and that controls the exit state as well. So, as I said, we're using Maestra. So if we go to workflows, control flow, it's probably going to be do until or do while, same thing basically. Yeah. And here we're going to define our parent workflow that will control that if, for example, if for example, the reviewer gave the is approved to true, then we're going to exit, or maybe like based on the number of iterations, if the number of iterations is equals to 10, then we're going to exit as well, something along those lines. So we have two workflows, and this parent workflow, and this parent workflow will just control the loop.
Let's jump into the code and just uh create this. Uh, we will start by defining the workflow. Let's create a folder actually, workflows. We will define our reviewer, reviewer workflow. Let's start by defining steps for the writer and reviewer first. So let's define the writer first. Writer, writer step. Let's define input and output schema for this. We would need to install Zod as well. Let's maybe move the schema as a separate variable actually. So let's call it writer input schema. Let's for now just define the topic. Topic is uh what the user will want us to generate for the article. It's going to be a string and it's going to be a required field. Writer's input schema. The output schema of this is going to be our article. Let's also forward the topic that was provided to the next step. Maybe it's going to be useful for us. Output, output schema. Here we just need to get our input data, which is topic. Now we need to define an agent for writing our article. I'm going to delete this. Just uh agent, agent is going to be new agent uh from Maestra from here. Here we need to provide the model, the name, writer agent, instructions. Let's see, high quality, engaging, various topics. Let's that works at least for now. We can improve it later if needed. Now let's define the model. I already connected the OpenRouter API key, so I can use OpenRouter directly from the string. If I do OpenRouter, let's say 5 mini. Now let's just export it. Now let's go to our index here. Remove this. Add that here. Let's define our writer. Right. Writer agent. This one. Now let's go back to our steps and our workflow. The reason why it yells at us because we don't provide, we didn't provide any return article and return. Now we need to get Maestra from here. Actually, maybe make it a bit bigger. We have Maestra here. Const writer agent. Yeah. Maestra get agent writer agent. Now all we need to do is to actually write the article. So let's do con response equals await writer agent for now. Let's do generate, but we also will implement the front-end part of this application. So we would need to stream down to the front-end as well, but that's going to be a separate thing. For now, let's just write the workflows and test them in the Maestra dashboard. First parameter here is a prompt, so let's please write an article about response.ext, right? Yep. Spawn.ext.
Now, let's define our reviewer. Input schema is going to be, input schema is going to be, I think, yeah, it's going to be writer output schema, probably. Yeah, let's just return the topic, article, or like forward to the next step, article, then approved whether or not, or whether or not our reviewer approved the article, and then the feedback. Just also have to be, doesn't have to be optional. Let's also provide ID to the step. Get all the input data. Now we need to define another agent, which is going to be review agent. So basically, what this prompt says is that, um, it has to evaluate the article and provide feedback, structured output here. I think I can. Why not? Or I need to output. So we need to define the structured output, not here, but separately in our workflow. Good to know. Okay. Now let's, uh, it's reviewer agent. Let's define it in our Maestra instance here. Now we have our reviewer. Let's just get it. Con reviewer Maestra. Yep. Reviewer. Please review the following article on the topic. Let's do it in XML format, just for better boundaries, I guess. Um, and now we can here define structured output, I believe. Need to define the object, structured output. Yeah. So here we can define it. It's going to be an option, an object, and we can define schema. It's going to be again Zod object. Let's just for now define it like this. So approved, whether the article approved or not. Feedback, detailed feedback on the article. That should be good. Now let's return something. Approved, not going to be structured output, but object.
Now we have our bare bone of the workflow. Let's define the workflow itself. Con uh reviewer writer, let's say writer reviewer workflow, create workflow ID, input schema is going to be writer input schema, the output schema is going to be reviewer output schema, because we only have two steps to define steps in our workflow, then we need to define, then it's going to be writer, then reviewer step, and then we need to commit, commit like this. Let's export that workflow and import that into our into our Maestra instance. So we've implemented our like basic workflow. Let's uh quickly test that. Let's remove tools from here. Let's just run Maestra. Let's go to the admin panel. Here we have our two agents, different models. I just test that topic, how to improve sleep, for example. So it's going, we can see we cannot see the trace for now because it didn't output anything. We can see the input as well here. So we finished writing the article. Let's see the output. Sleep is luxury. Yep. It wrote the article. Now it reviewed it. Approved. True. That's probably not the best that our reviewer just approved it in one step. We would need to tinker the prompt a bit more to make it more like strict, maybe.
Now let's implement the looping because now it's just one step, one uh iteration, but we want to go into loops to like after the reviewer processed the article, we want to provide feedback back to the writer. Writer want to act on this feedback and give it back to the reviewer. Then reviewer will decide whether or not we want to continue uh writing it or like rewriting it, or we can like exit. For that, let's go to the code again. Let's define a new workflow. Let's call it loop writer reviewer workflow. Let's for now just export all the types, all the schemas from this workflow because we probably would need them in the loop workflow. So let's import, let's export them. Now let's define the workflow, the loop workflow. It's going to be the same create workflow. But now we would need to change uh some of the input schemas and output schemas because we need to keep track of what was the previous article, what was the previous feedback. Um, so let's define the input schema here. Let's call it const loop input schema. And here we would need to, let me think, we need to have a current, current article. We need to, of course, we need to have uh a topic. Here we would need to have approved, right? Because we are going in loops, we need to know the output or like decision of the previous iteration and act on it. So let's define that approved topic and and probably like previous feedback or something, previous. Again, I'm doing it on the spot. So if something is doesn't make sense, we'll figure it out later. Let's define loop article input schema like this. And the output schema here, I think, is going to be just a output schema of our reviewer. Just do that output schema, define import. Let's now export that.
So now we need to define the loop itself. Let's define do until. I think do until is the good thing here that you can also provide a workflow here, not only this step. Let's actually provide our workflow writer reviewer workflow here. So we are going to do this workflow until it's going to be in the sync function. Here we can get the input data and I believe also iterations because it's also important. Uh, let's start with iteration count because we don't want this to be like indefinitely. We want to actually stop it somewhere. If, for example, some one of the AI starts hallucinating or something, we want to exit it. Let's define the iteration count. Iteration count, iteration count is more or equal to 10, then we'll just return false. Oh, no, the other way, we need to return true. So we need to return true to exit the do until as well. Let's import that. Let's also here, it probably yells at us because the output schema of this one doesn't match the input schema of this because we're going in loops. Um, those have to match. We'll fix that in a minute, but for now, let's just Okay, let's actually maybe fix it right now. So now we need to change the writer input to be our loop going to be loop writer writer writer workflow. We're going to define that. So the output of our reviewer has to be the same as the input of our loop because I want to provide it to the next iteration and it has to match. So let's actually maybe rename that to just multiple like this. So almost everything is just going to be this one schema. Yep. Now, so we would need to change the name. Reimp import that. Now they all match and we can proceed with our loop. So here in input data, if our input data, oh, it's any type. Why is that? Why is it any type? I've provided this. H interesting. Why is it any type? Maybe I'm doing something wrong. H interesting. Okay, let's for now just proceed. Input data, approved. Let's return true if the our reviewer decided to approve the article, then we're just returning true, which means that just exit. Okay, that should be done, I think. So, so we are providing to our loop this topic, current article is going to be initially an empty string, feedback and approved. Yeah, we need to update the writer. Now need to go to our workflow and update the writer, right? Because now we have also previous feedback and the reviewer. We need to update the writer. Maybe the output of this one should also be just the loop article schema. Yeah, I think so. So, we want to provide all the all the same things to the next reviewer. I think it makes sense. Okay. If there is a previous feedback and article and current article, then what we need to do is to rewrite the article and not the write initial, not write the new one. Let's define this agent again. You're rewriting an article based on the reviewer feedback topic. Original article, reviewer feedback. Please rewrite. Yeah, that makes sense. Again, let me just do my OCD thing. I'm going to just wrap everything in. At some point, I just need to use the library that will help me with formatting for the prompts because that's that's a mess for sure. Okay, now we have our response. Um, our writer rewrote the article and we need to return it. Approved. Approved. Let's just forward everything. Topic still the same. Topic, current article is going to be response.ext. Previous feedback is going to be previous feedback. Here we also need to fix the input, which is going to be just loop. Article schema. Output here is going to be article, topic, approved, feedback. Just going to be the loop again. It's going to be the loop again. Need to do here the same thing. Current article. I probably didn't. Yeah, I need to here return there. So here again we need to define if if we have previous feedback here, we have current article, then we need to rewrite. So if we have our previous feedback and we have current article, that means that we need to take into account previous feedback as well. So we would need to change, maybe we can just update it here. If there is a previous feedback and we have previous article, we have current article, original article, and that's that. Yeah, that's that's getting ugly. That's okay. Let's return now. Current article, pon feedback. That should be it. Now we go in loops. We provide the feedback. We write the article. We provide the feedback. If our feedback is not, if our article is not approved by the reviewer, then we're continuing. And if current it, if iteration count is less than 10, then we're also continuing. So that should work. Import that. Write review workflow. Let's restart the dev server. Can access review write reviewer workflow before initialization. Oh, interesting. H interesting. Interesting. What if we put it in one file? Will that work? So now it works. It's good to know. So if you have like a workflow that uses another workflow, they have to be in a single file, I guess, based on what you what I just experienced. Of course, all those things like input data here and do until being any and the issue that I just faced with workflows, I will share it with the Maestra team and maybe they will fix it or clarify it for me, and so I can help you understand it better next time. But it is what it is right now.
So now let's actually test the workflow itself. We should have two workflows, loop and writer. Here we have our nested graph. So, it's the first time I see this as well. Topic, article, proof. I'll just write topic, how to improve sleep. It's probably going to take a while to complete. H. Why does it take so long? Maybe because I'm using OpenAI's model. Yeah, I think so. It was because uh the latency of OpenAI's model is really bad right now. I'm actually curious to see. It's like 11 seconds as usual. And it's still going. So it's still writing the article and this input, we can see here that we have approved false and topic how to improve sleep and the output is our current article, topic and approved. We'll see what's going to happen next. Input. Yeah, because it's, yeah, because it's approved true, it just exited. Okay. Um, let's maybe update the prompt quickly. The reviewer, let's try again. Let's update the page. Uh, how to improve improve sleep. This time it should be a bit faster though. I'm not sure what's happening now because now nothing is happening. Okay, now it's going. It wrote the article. Let's see, see the input, current article. Let me double check quickly. I don't, we don't need that. I just want to be sure that we're providing everything correctly. False. Okay. Input. Okay. Now we have our feedback. Something is wrong with the timer here. I don't see the output here. Now I'm looking at it. It doesn't make sense. We need to, I just do the same thing here as we did with, um, the writer. The with the reviewer. Let's just do if there is a previous feedback, then what we need to do is to provide original article. We need, we don't need that. H interesting. So we didn't see, we didn't see the feedback right from the second iteration from the writer because we are providing the input data. For some reason, it always respond, it responds without it, responds without the previous feedback back though we have it. Why is it article? Why is it not current article? Wait a second. Review step. Oh, wait, wait, wait, wait, wait, wait, wait. It should warn me. Oh, because it's, this can be undefined. So it was my bad. Okay. Uh, good debugging. So I forgot. So I returned from the reviewer step. I I returned just the article, which was the previous schema. And we need to return the article with a different key. It should be current article, not the article. And it didn't yell at us because uh the article, current article can be undefined. It's optional. That's why it didn't yell at us. We do that. And we do that. Something broke. Previous feedback is declared here. Oh, we and we have feedback. See, TypeScript does help. That was just my dumb ass missing this. Okay, let's try again. How to improve sleep. Try to. Okay, now it's expecting it. It's a good thing or not. I think it still should be optional to be honest. Yeah, let's run it now. Then it stopped. Writer step input. I feel like I'm breaking something. Okay, now it did work. We have our current. We have our current article. We have our for some reason we have two runs here. So, this one is still going. Okay, let me maybe restart this one. Invalid string, string length. I don't think it's related to that. I think it's something with here, right? Like pal logger. Let me maybe remove that. Maybe it will help. Agent with name weather agent is not defined. Excuse me. Where do I say anything about weather agent? It's getting weird. Let me restart that. Maybe I need to delete the Maestra folder. Maybe it cached something. Can I test it without it? Okay, it just, it works. But this one is strange. Okay, let's try to write a topic. How to improve sleep. [snorts] Input false. Nested. Let's look at the nested. Completed it. Input previous proved false. Let's see the input. Now we have approved false. The output proved false. So, okay. So, it worked. It just was confused with the interface, right? So, it it's our last step. It's hard to see here, but we're going to implement UI for this to see it better. We for sure would need to improve prompts as well a bit more. So writer step does our that's our [clears throat] last iteration. We have here writer step uh which is approved false, current article and previous feedback, article, feedback. Okay, this actually very interesting. Okay, now we know that it works.
Now we would need to connect our workflows from Maestra to the front end. In our case, it's going to be Next.js. Now let's first implement API route. Next.js API route. Just uh do this route.ts. Let's do post like this. Now we can import our Maestra cons. We have our loop. For now, we're just going to return it. and uh set up the streaming first here. Then we're going to go to the workflow and fix fix the streaming so that we can stream the response to the front end. We need to get the topic. Yes, AI gets it. Now we need to create a run, I believe, for the workflow. Await workflow run create run, I think. Yep, I messed this up. Run ID, resources. Okay, here we have that const result await run. Is there a stream or something? Yeah, it should be stream input data. Here we provide our topic, which is our first initial topic that user writes. Right now, we have here our stream. I think I'm going to use uh ak to connect it to the front end. Now what we need to do is just return create UI message you. Oh, here I I need to first, I need to actually install the AI SDK. Npm install AI. And then we need to do provide stream to AI SDK stream to AI SDK stream. But I need to double check that. Let's go to let's go to the Maestra documentation SDK. I think it's a separate package, I believe, at least. Yeah, it's a separate package. Maestra AI SDK. So, let's install that as well. Pnpm install Maestra SDK to AI. Yeah, it's two AI SDK format. First, we need to provide the result. Let's call it stream. Stream. Let's provide the stream from workflow. So, here now we have our route. Uh, now we can from the front end send the request and get the stream back. But because we haven't defined any streams and we are not streaming any agent uh any generation from the agent to the front end because we are not streaming any responses from the agents. We're just generating them in one chunk, we won't get any stream of course. So for that, we need to get the writer and I'm going to do it uh I'm going to do it my way where I'm defining the writer and I'm defining custom data parts for the better type safety. I'm going to explain along the way like what it actually means. But what we need to do now here, we need to instead of generate, we need to do stream. And here we need to do writer.right. Right. First, I think we need to actually define types for our writer, for our custom data that we're going to send with the workflow. I think we would also need to pass the iteration count to the like to the workflow because we want to have a unique ID for each of our for each of our writers that stream custom data to the front end. So, we probably need to provide that as well. Let's for now just uh define the types uh for our workflow because you cannot infer types from the workflows. So we have to define them separately. We need to define types for our custom data as well. So let's just call it types or index. I don't know, it's workflow. Now let's define the type. Uh, it's going to be for our writer first. So it's going to be like writer event data or something. Here we're going to have a status which is streaming or completed, and we have our article. Now let's define the type for reviewer. It's going to be the status, approved, feedback. Yeah, we need to have an iteration count to like know and associate each feedback with each uh article on the front end. And yeah, maybe it's, I don't know, not the right way to do it. Just something from the top of my head right now. Maybe after this video, I will create another one when where I'm going to review it and give some feedback here. But for now, let's do as I'm thinking right now at the moment. So we need to provide an iteration, iteration number. How are we going to increase that? That's a good question. I think only our writer should should increase that if we get our iteration count, iteration count, I think it should be always actually, we should always have that, shouldn't be optional, I think, makes sense, yeah, actually I think it shouldn't be optional. Let's just remove optional. Let's just do iteration count + one here. We also need to provide iteration count. That's going to be just iteration count. So on each writer, first step means that we started the loop. We're going to increase it by one. So by default, it's going to be zero, then one. I can even like provide the default like this. I don't know, maybe it's not the right way to it. Let's let's try to make it work, then improve it. So we added the iteration count to our workflow. Now we know and we can associate each feedback and each um article on the front end. So we can easily show them side by side. For example, now we're going to go through chunks where each chunk is basically part of the object. In our case, we need to provide ID, which is reviewer feedback. And here we should provide the iteration as well as an ID because this ID has to be a unique ID. Also, we need to provide type, u. I'm going to explain later why we need this, but for now, let's just define data, reviewer feedback data, like this. Yes. And also we need to provide, let's go to the types. Let's move this status type into a separate one. Now let's copy this and do here the data. We need to specify the type for this data because um it has to be typed. We we will use it on the front end. So we need to do as, or the better way is probably to do satisfies. Now here we also need to provide iteration count. Hopefully I didn't miss that iteration count. No, it's correct. See here, that's why we used satisfies instead of as. So if I had here as, I think it, yeah, it just doesn't yell at us. Satisfies at least yells when type is mismatched. Here we can provide the iteration count and here to get the final object, we need to await the object like this, response final object, final object like this. Now we're providing it to the next step. Here we're defining streaming, but we would probably need to define. And after that, we also need to provide to the writer that we completed the streaming like this. Okay, we for some reason started from review. Let's go to the writer and change the stream from generate to stream. Here we [music] need to do this again. We need to get the writer and we need to do writer write. We cannot just do chunk because it's going to be a new string every time. So we need to create an article content variable and do article plus, yeah, like this and use an article content here. After that, again, we need to do writer write completed traation. Yep. Like this. And we need to do satisfies, not reviewer, but writer. For some reason, writer steps, step yells at us. Let's see. Yeah, here we have promise string as text. So we need to do con uh response final, do it like this. And now we should be able to stream the data from the back end to the front end, each iteration. I don't think we need to define any write, by the way. Do we have writer here? We have. So we can actually write here as well. I don't think why we would need that. Maybe to specify how many iterations we've completed, but maybe not for now. Let's do it maybe later. I think we mostly completed the workflow part on the back end here. I think we all we need to do now is to connect the front end to the back end. And we're going to do it with ESDK, of course. So let's jump right into it.
So I found a bug in our writer step here. We previously did only iteration count plus one only when we were returning, but we also need to do it for all the other like IDs and other places where we're using it. So I fixed that. I moved it into a separate variable and applied it everywhere here in the step. Where I like to start when I'm working with front end is, um, start by implementing all the supporting like hooks that we would need. In our case, it would be a workflow hook to interact between with our server. We're going to call it like, as I said, we are going to use a SDK for this. And you might think, why would we use a SDK here in the first place? Because they only provide use chat hook, and we don't have any chat here. The reason for that is that they're providing very like easy way to connect between front end and the back end and, um, how they're handling your streams and all the statuses that comes with it, also type safety. So let's, uh, first [music] we need to install it. Need to install ak react package. Now our use chat, we would need to define a transport because we would need to process messages before we are sending them to the front to the back end, and we we won't send messages. We we're going to send just a prompt that the user wrote in the field. So we need to define new default chat transport. I believe here we can define prepare send message request. So this function will be triggered every time we send a message. And here in the return, we need to define the body. So what we're going to send, um, let's hold on for a minute. We need to first define the API. In our case, it's API slash write review. If we go to our endpoint here, we're getting our topic from the request. So we would need to pass down the topic like this. So now we need to get the topic. Uh, for this, let's just get first [music] the last message, which is going to be messages dot. After that, let's rename that last message. Const topic equals to last message. Parts do find an empty string and pass this topic into our party. So all good. We now will get the stream. What we want to do and what we want to have here is custom message type because we want some type safety across our workflow. Okay, let's add type safety here. For this, let's go to our types and we need to define a new UI message. Let's define our custom UI message for our use chat. Let's name it workflow or, um, write UI message, for example. Then we need to import UI message from SDK. The first parameter is metadata. We don't need that. We need data parts. So let's define that here. We would need to define the name of our data, and we've already established that in our workflows. So if we go to workflow here, we've defined this type uh data writer article. You'll notice that I'm only writing only adding this writer article and not data uh prefix because um UI message and the SDK has this data prefix automatically. So all we need to do is to write this write article and here we would need to define the writer event that, that's why we've defined this type previously, and that's why we've associated this type with the data as well. And the same for the reviewer. We have here reviewer feedback. Let's add that. That's it. Now just let's export that. Now we can use that in our hook right here. And now in our messages, if we look, let's um, just try to get something messages here as a type, you will notice that we have our [music] custom data parts from our workflow inside of them. So if we, for example, get our writer article part here, we have our data, which is article, iteration count, and status, and based on that, we can like render UI or something like this. Now, all we need to do is to define a few more functions or a few more variables in our hook to not just return everything that use chat returns, but but return only like a few necessary things to make it uh, at least a bit more cleaner than it is right now. So let's say workflow. So let's say workflow, and workflow is going to be messages. Let's actually maybe do it as use memo. So what we know is that the workflow is basically the last assistant message in our message history. So let's get messages. If we don't have that, let's just return null here. Specify messages. Uh, also we need to also now we need to define a send function, which is just going to be with send message do user text. We'll just define text, which is going to be our article topic. So maybe let's also return like status, chat status, to see whether or not it's streaming and stuff like this. So we will return send. I think I think that's enough for now. Role doesn't exist. Interesting role. Oh, we can just define the text. Okay, I agree. It does feel a bit hacky that we're defining chat, but we are not using chat but using workflows. But it handles like most of the stuff and it's like 49 lines of code. So, it should be fine. And that's why we're doing two AISDK format is because Maestra has its own format of streaming of streams. And we need to transform this to ESDK so that we can use it on the front end with like use chat hook and other primitives from ESDK.
Let's quickly craft the UI for this. I'm just going to use v0.zero for this. And, uh, before even building this project, I decided just to go back and forth with clot to decide to come up with the UI for this. So I'm just going to copy this. Just going to paste that in. I think I have $1 should be enough. And let's just go to Enthropic UI skills article. This one. Just a small hug to make our UI a bit better. What's happening here? So, we're just going to copy this. Let's just run it and wait until it's done. Ooh, that that looks fancy. To be honest, I like how it looks. So, I really like how it looks. Let's try to just write. Of course, we don't have any AI connection here. You can see the writing, the analytics, the review. Don't have a score yet. We won't have it, but it's okay. We have our writer reviewer. And once once it's done, we have completed and approved. You should be able to go between iterations to see the results. And here you see your topic that you pasted in. So let's just add those pages to our project. All I'm going to do is click those three buttons here, copy this, open the terminal, and paste that in. So now what we can do actually is to use our AI our AI agent that we have that we all have in our editors and just ask it to connect those two. So let's run this prompt. It should be fairly easy for Alus to figure this out because we have defined all the data and we have the hook to get all the like necessary information for our workflow. We've defined all the types. So, all it should do is just connect two pieces and it should be really good at it. Oh, here we go. No, don't do that. Define the iteration data. So, I don't think we need this writer parts, the viewer parts. We have our map grouped by iteration count. Okay, that makes sense. A lot of if statements. That looks nasty. So, because we ran the vzero command to need all the components into our project, it because we have this src directory and decided to put it in root. Let's just move this thing here. Okay, I fixed the issue with imports. I just had to change the paths here because for some reason VZ decided to put all the components in root and not in src folder. Interesting. We only have one part. Oh, wait. I see it here, but I don't see it in custom data parts. So, a bit more debugging here. H interesting. It doesn't write to the writer. Wait, could it be because we're using the loop? Maybe it's not forwarding the data. Something is wrong with our loop workflow because it doesn't forward forward the writer data, I guess. I don't know. So for some reason when I'm using the loop reviewer work it for some reason doesn't do it doesn't write to our writer but when I'm doing the writer review workflow so the nested workflow like one in iteration then it worked stream to this type after investigating this issue I decided to open an issue on GitHub and word the legend explained to me why it happened if you use write or write we wrap it in a top chunk and our AISDK implementation only looks at the data chunks in the top level workflow by using custom so writer custom we do not wrap the chunk so to fix our problem all we need to do is to go to the our workflow here here I already fixed that I tried it and it worked so we need to change from right to custom and the reason why we need to use custom is because we are using the nested workflow if you are not using nested workflows then you should just do like write basically like this. I didn't find any documentation about this so I should probably mention it to them. But after we change it to custom, if we look at our project, if we look at our project and let's say paste this in, we will see that we are streaming in the writer writer response. After that, we're streaming the reviewer response. And after our reviewer finished, we are now streaming again a new response, which is the second iteration. and reviewer as well. So it will go in loops until it's done. We still need to fix iterations here. So again, I I think like we have some issues with our prompt and it will go most likely indefinitely until it hits the the maximum amount of iterations, but it's really easy to fix. You should probably just update the prompt and it will work. If you want to, it can be your like homework to fix that. I think we can consider it like completed, like 90% of the project is completed and and if you want to, you can and if you want to, you can complete those small things on your own. I think we learned a lot. Uh, if you found this helpful, let me know in the comments.