Intriguing what's happening here.
Well, the key is in our streamObject call here and particularly in the schema, which is just down here. We have two pieces of the schema. We have a feedback property and an isGoodEnough property.
schema: z.object({feedback: z.string().optional().describe('The feedback about the most recent draft. Only return this if the draft is not good enough.',),isGoodEnough: z.boolean().describe(
The feedback is a Zod string here, which is optional. And I've specified:
"Only return this if the draft is not good enough."
Then we have an isGoodEnough boolean below saying:
"Whether the most recent draft is good enough to stop the loop."
This means that because this first draft was considered good enough, the feedback was not streamed to the front end. In fact, wasn't even generated by the LLM. All the LLM generated was an object containing isGoodEnough: true.
If we zoom down, we can see that we are streaming the feedback to the front end if it exists:
for await (const part of evaluateSlackResult.partialObjectStream) {if (part.feedback) {writer.write({type: "data-slack-message-feedback",data: part.feedback,id: feedbackId,})}}
This means that if there's no feedback, then writer.write will never be called. We grab the final object from evaluateSlackResult.object, and if the draft is good enough, break the loop:
const finalEvaluationObject = await evaluateSlackResult.object// If the draft is good enough, break the loopif (finalEvaluationObject.isGoodEnough) {break}// ...mostRecentFeedback = finalEvaluationObject.feedbackstep++
But if the draft is not good enough, then we take the feedback and we stick it in the mostRecentFeedback variable. This set up then with streamObject deciding whether or not to break the loop.
We're also handling a rare case here where it might not provide feedback:
if (!finalEvaluationObject.feedback) {throw new Error("No feedback provided by the LLM.")}mostRecentFeedback = finalEvaluationObject.feedback
Our object here, this finalEvaluationObject, has isGoodEnough boolean and feedback string or undefined. This means it could technically say isGoodEnough: false, but provide undefined as the feedback. You know, these are LLMs, they behave probabilistically. So in that rare case, we do go down and we say, if there's no feedback, we throw an error saying:
"No feedback provided by the LLM."
This setup then where the streamObject call is deciding the control flow of our program is, I think, a really, really nice way to think about mixing agentic and workflow setups. Because inside this loop we can run as much deterministic logic as we want. We can break the loop deterministically any time. Or we can hand more control over to the LLM via streamObject calls where we say if this Boolean is true, then break the loop.
I've built lots and lots of fun things using this setup.
You can even get the LLM to choose between multiple different options via z.enum and all while still streaming to the front end the entire time. Hopefully that gets your brain whirring with different ideas of things you could build with this.
Nice work, and I will see you in the next one.