In this chapter, we're going to add AI nodes to our project. So what exactly is the difference between this chapter and chapter 7 in which we've added AI providers? Well in chapter 7, we've learned how we're going to use AI within this project. In this chapter, we're going to literally create the canvas drag and drop nodes for each of those AI providers that we've added. So a quick reminder, I taught you how to add Gemini which offers a completely free API key and on top of that I told you that you can also use OpenAI, Anthropic and a million other providers that AISDK offers.
AISDK is well the SDK for AI that we are using in this project. So just a quick reminder. Let's go ahead and revisit chapter 7 right here. As you can see That's exactly what I told you. You can use Gemini, which is free, or OpenAI or Anthropic, which will set you back a minimum of $5.
If you want to use them, sure, but not required. And also a quick reminder, once you finish this project and you actually deploy it, No one will be using your AI API keys. All of your users will be using their API keys. Just to clarify that one more time. And as you can see in here, it's also marked as finished.
We set up AI SDK and we even used it within ingest. So now when we start chapter 24 we have some already added things for us. So just in case go ahead inside of your package. Jason and confirm that you have the AI package. So you should have a AISDK, Google at minimum or OpenAI and Anthropic or maybe Grok, you know, whatever you wanted to use, but you should have at least one here.
Since AISDK, Google is the free one, I will be mostly focusing on this one but I will also show you how to add Anthropic and OpenAI and I would suggest that you follow through with me and implement them as well even if you don't have an API key. In fact, if you go inside of your .environment file you will see that I don't have an anthropic API key. It's completely empty. That's perfectly fine because in the end it will be our users who are going to provide their own API keys. So just make sure that you're inside of your dot environment you have at least one API key for AI whether that is a Google Generative AI or Gemini, OpenAI or Anthropic.
Just make sure you have at least one. And inside of your package.json, make sure you have at least one AI SDK provider here and alongside that you should also have the AI package itself. Great. Quick reminder, you can use the link on the screen to visit AI studio.google.com And in here you have get API key in the sidebar and you can very quickly create a new one. Maybe you have to delete your old one.
I'm not sure how does the free tier exactly work here, but at least one API key will be completely free if that's what you need. Perfect. Just make sure you have that and now let's get to implementing this. So the first thing I want to do is I want to add the images for all three of these. So let's go ahead and prepare our public logos folder.
Then you can go ahead and visit my node base assets folder. You can go inside of images and you should find Andropic, Gemini and OpenAI. So let's go ahead and add all three inside of here. Once you add them inside of here you should have Anthropic, Gemini and last one OpenAI. Great.
Now that we have them let's go ahead and add them inside of our Prisma schema. So instead of a node type, let's go ahead and let's add Anthropic, then let's add Gemini, and let's add OpenAI. Of course, you are free to name this however you prefer, but I would highly suggest that you follow the exact same naming as I am doing so you don't cause yourself any unnecessary bugs or problems. Save this file and then as usual, let's go ahead and do npx prisma migrate dev. Once you get the prompt to enter a name feel free to add whatever you want for example ai nodes schema or AI nodes types and that will synchronize your database with the schema.
And as always, whenever you do the migration, I highly recommend restarting your next server, your ingest, And I don't think you need to restart ngrok. In fact, we are not going to need local tunnel running for this chapter. But if you want to, you can have it running. So basically, if you plan on using your Google Forms or Slack nodes, my apologies, Stripe nodes, you will need a forwarded open tunnel, local tunnel like ngrok. Great.
So just make sure that works. Go ahead and restart your Next.js app. So Everything is up to date. And now what we're going to do is well the same flow that we did before, except instead of building the trigger, we're going to be building an executor. So let's go ahead and let's copy the HTTP request inside of source, features, executions, components.
Let's go ahead and copy HTTP request and paste it here and let's rename it to Gemini. And while we are here let's also immediately do the same inside of the ingest folder, channels, copy HTTP request, paste it here and rename it Gemini. So now we have the real-time channel for Gemini. I'm going to go ahead and change this to Gemini channel name and this will be called Gemini execution. Everything else will stay the same except of course the name of the variable right here Gemini channel.
Once you have created the Gemini channel immediately go instead of ingest functions.ts and register the new Gemini channel so you don't forget to do that. Perfect. Now we can go ahead and focus back inside of the Gemini folder, which we've copied. So let's head inside of node.tsx. And basically in here, yes, we again have the data because we will need some data here.
What I would suggest is not changing anything inside. Instead, let's just focus on renaming this so it's easier to refactor it later. Gemini node data. This will be Gemini node type using the Gemini node data from above and then use the Gemini node type in here. Let's rename this to Gemini node and let's leave this as is for now.
No need to change it. Let's just focus on the base execution node and the name here. So for the name, I'm just going to set it to be Gemini. And for the icon, I'm going to go ahead and use logos Gemini.svg. And let's go ahead and fix this.
So Gemini node dot display name Gemini node. And let me just quickly check inside of our triggers do we even have that display name looks like we don't okay I was just worried that I forgot to rename that or something. If we don't have it and if we don't have any errors, I think everything is fine. Now let's go ahead and add Gemini to our node components factory, if I can call it like that. Instead of source config a node components.
Let's go ahead and do node type dot Gemini, Gemini node. You should be able to import it from add features executions components Gemini node. Perfect. And now we have to go to our node selector instead of source components, node selector, and let's go ahead and go inside of our execution nodes. Let's go ahead and duplicate this and let's add the Gemini type.
Now the Gemini type, you can make any description you want really. I'm going to use a super simple one. Use Google Gemini to generate text. Gemini and the icon is going to be forward slash logos Gemini dot SVJ. Perfect and I think that this already should be able to show you Gemini and you should be able to add Gemini.
The only problem is the dialog of course uses the HTTP request configuration. So that's what we're going to be working on now. Let's go ahead and start by changing this from HTTP request dialog to Gemini dialog. So go inside of the Gemini folder, dialogue.tsx. Again don't focus on the schema, leave that as is.
Oh yes, I also have to do this to do thing. Basically, yeah, I forgot this. The reason I thought about adding this to the body property of HTTP request node is because we now have templating. So I thought it would be a good idea to like validate JSON maybe if it's valid or not. I don't know.
Maybe we don't even need it. It's kind of depends on the user experience you want to add right. Nevertheless, let's focus on the Gemini folder now, dialogue component. And let's start by renaming the HTTP request form values to Gemini form values. Change this to use the Gemini form values as well, change this to be Gemini dialogue.
And I think inside, we should not have any more errors. Let's just change this from the title of HTTP request to be Gemini like that. And for the description we can just say configure the AI model and the prompts for this node and let's do Gemini configuration. Now let's go back inside of Gemini folder node.tsx and add Gemini node here. Let's go ahead and just import.
Yes, So let's do Gemini node and Gemini my apologies not Gemini node, Gemini dialog and Gemini form values. So I'm doing something incorrect here. This shouldn't be Gemini node. This should be Gemini dialog, of course. And this shouldn't be HTTP request form values.
This should be Gemini form values. And now you should practically solve all of your errors. You can remove the unused globe icon. And now you can see that the dialog says Gemini configuration, configure the AI model and the prompts for this node. Perfect.
Let's go ahead and continue developing this dialog right here. So I want to start by modifying the form schema. Let's go ahead and first define all the available models. So unfortunately I didn't find a type safe way to do this. So I just extracted things that work.
So this kind of might be different for you depending on which AI SDK version you're using. Just a quick reminder. So I'm using AI SDK Google 2.0.17 and AI 5.0.60. So at the time of making this tutorial, these are the available models. So even if you're not sure, you can write it exactly like this and I'm going to show you how you can see which models are available.
So the variable name can actually stay the same because every execution thingy should have the variable, right? Now let's go ahead and change the end point to be the model and the model will not be a type of string is going to be a type of a new and let's simply add available models inside. Then let's add system prompt which will be a string and it will actually be completely optional. And the last one we're going to do is user prompt. This will be a string, but it will be required.
So let's go ahead and use a shorthand user prompt is required. There we go. That's our form schema. So now in the default values, obviously, we should reflect that the model should use default values dot model or let's fall back to available models first in the array. The system prompt should use default values dot system prompt or an empty string and the user prompt should use user prompt or an empty string as well.
And then you can go ahead and copy this entire thing and do the same thing instead of a form.reset right here. Now let's go ahead and just get rid of some things we don't need. So we do not need any of these. Yes. You can.
Yeah. No need for any watch methods here. So in order to make this a bit simpler you can remove this. No need to dynamically change the description anymore. Oh actually wait, that was a cool feature wasn't it?
Maybe we can leave that. So leave watch variable name. Sorry, I just told you you don't have to do it. So just remove the other ones because this one is cool. And inside of the description then, yeah, instead of this, it will most likely be, I'm not exactly sure what will the response look like so I'm kind of guessing right now.
Let's see maybe it will just be like dot text. I'm not sure we're going to see once we get the ingest output. Now for this form field let's go ahead and remove it entirely we're not going to need it. For the end point here let's go ahead and remove that as well. And go ahead and just remove the dynamic conditional part for the form field which controls what was in the HTTP request body field.
Like this. So just like make it always available. The reason I'm leaving only this one is because it uses text area so it's the smallest amount of code we have to modify. So let's just change this to call system prompt like this. And let's go ahead and do system prompt optional to let the user know they don't have to do this And then let's go ahead and just give a little placeholder here.
For example, you are a helpful assistant and let's make this smaller like 80 pixels. And the form description will be something useful. For example, sets the behavior of the assistant and then use variables for simple values or use JSON variable to stringify objects, right? Again, this is just instructions for your users. I think you can already take a look at this if you go inside of the Gemini node, open it, we have the variable name and here we have the system prompt and this is how that description ends up rendering as.
Now let's go ahead and let's copy and paste that And let's do the same thing for the user prompt. User prompt like this. Remove the optional part here. Change this to user. And in here for example in the placeholder you can do something useful like summarize this text and then maybe use the JSON thingy to let the user know that they can use values inside.
There we go. This is how that placeholder looks like. Let's go ahead and just increase this one a bit since it's more useful. And in the form description, again, you can set something useful. The prompt to send to the AI.
Use variables for simple values or JSON variable to stringify objects. Great. So just one more field left here for now. Later we're also going to have a credential drop down so users can add their own credentials but they're not going to implement that now what we need to do now is we need to copy this form field. We need to paste it here and we need to modify the model right.
Let's go ahead and add the form item the form label will be model and let's go ahead and let's remove the entire form control here Because what we're actually going to do is we're going to use the select component. I think we should already have select imported here. So if you don't make sure you have select, select content, item, trigger, and value from components UI select. So for the select here, let's go ahead and add two values on the value change and default value. Now inside of the select we can add the actual form control and inside a very simple select trigger with full width class name and select value with placeholder select a model.
Outside of the form control let's render the select content And inside of select content we're going to iterate over our constant available models dot map model and render it in a select item with a key of model and the value of the same thing. And of course render the actual model name inside. For the form description, we can simplify it even further, the Google Gemini model to use for this completion. So let's go ahead and check it out now. There we go.
I'm just going to zoom out a little bit. So this is how it looks like. Users can now specify the exact Gemini model they want to use. They can name their variable. They can add a system prompt and they can add a user prompt.
Amazing. One thing I want to do is I want to move the variable field to the top just to be consistent because I feel like that is one of the most important values to have really. And let's make it familiar for our user to always expect it on the top of every configuration form. Where did I move it? Oh, here it is.
Variable name. Did I do this correctly? Let me go ahead and add Gemini again. Open it up. Variable name.
Perfect. That's empty. And in here I have the drop down, then I have the system prompt and then I have the user prompt. Perfect. Now let's go ahead back inside of node.tsx And in here we should kind of modify the description, right, because it makes no sense that it does this.
So instead of rendering nodeData.method, it should render nodeData.model. The problem is model is not defined. That's why that's because we have to define it right here. So let's go ahead and remove all of these and replace them with optional model, optional system prompt, and optional user prompt. Now we can go back in here and modify the description.
So now the data will be props.data and then description will be if we have node data.user prompt, Then go ahead and render the model that user selected or fall back to this one, which is the first one in the array here, I believe, yes. If you want, you can also export const available models and then do available models first one. Just make sure to import availableModels from .slash dialog. And in here let's quickly attempt to show the user prompt but let's make sure to limit how long it's going to be. So I'm just going to say node data dot user prompt dot slice 50 characters and then three dots or not configured.
So if I close this now it should say not configured. But if I go ahead and add a user prompt and click save, there we go. Gemini 1.5 flash and the user prompt right here. Perfect. So, I believe the UI part is pretty much finished at this point except we have to fix the Gemini dialog.
Let's go back instead of the Gemini dialog here and let's see what exactly is the problem here. Default values uses partial of Gemini form values here, but Gemini not data is not assignable to that. So I have some kind of a problem here. Did I forget something? Oh, variable name.
I think that's one. Variable name. Is that the problem? It is not the problem. Let me go ahead and check node.tsx in the HTTP request.
I want to see how that looks like. So variable name and point method and body. It's probably because the model needs to be a how do I do this type of key off and is it available not 100 percent sure. And then looks like that is still not working. Instead not working.
Instead of Geminate dialog here. What if I just make this required? Does that work? No. Alright.
I'm gonna go ahead and debug just a little bit and then I will tell you the conclusion about this. All right. So I didn't really find an elegant solution for this. But one easy way of doing it is well two ways one. I really don't like it's by using any That resolves the type error, right?
But another way of doing it is if You literally copy what it expects like this. That's not really the greatest of solutions but yeah, that also fixes the issue. I think the problem is because in the dialog here I'm using this as a constant when I maybe should be using this as an enum. So that's why instead of the node.tsx this is not understood correctly. This is not terribly important really.
Feel free to use any if it saves you some time. I will try to find like a more elegant solution for this. But right now I really want to focus on the executor and making this Gemini node actually work. So whichever one you prefer you can hover over it right here I think in the default values there you can see exactly what the model wants and then just add that here and add an undefined here. Let me see if I need it.
Looks like you don't need undefined because the question mark transforms it into undefined. Yes. If you know TypeScript better than me, which is quite easily possible, You can maybe try and do it with some available models thing but you can see that when I try to do it probably because of this read only. Oh yeah it expects it turns it into an array. Makes sense.
Yeah that's that's just not true. That's not what we expect here. So yes, whichever one you prefer, if you just want to use any, that's fine for now, I'm going to find a way to transform this into some enum. And then I will be able to use it like this via import. Let's go ahead and just focus on what we need to do next for now.
Great. So the UI part is now done. Now what I want to do is I want to go inside of source features. Let's go ahead and find our Gemini and let's go inside of the executor right here. So the executor is of course where the magic happens.
I'm going to go ahead and yeah, we can register a helper here. We can make it exactly the same as this. But let's just go ahead and start renaming this. So this will no longer be HTTP request data. This is now going to be Gemini data.
We're going to have a variable name, we're going to have a model, we're going to have system prompt. Let me go ahead and just make this a string and we're going to have user prompt. Now let's go ahead and add Gemini data right here. Let's go ahead and rename this executor to be Gemini executor. There we go.
Instead of publishing to the HTTP request channel, let's go ahead and change this import use Gemini and the Gemini channel. So start here. Gemini channel status is now loading. Perfect. Now let's go ahead and let's try to generate a system prompt.
So you can actually remove the entire try and catch here. So it doesn't necessarily confuse you like this and remove the extra bracket here and I'm just going to go ahead and do const system prompt data system prompt handlebars.compile data.systemprompt and pass in the context or let's fall back to you are a helpful assistant. Now I am noticing that I have a typo here, which is actually quite important. So let me go ahead and fix it. System prompt.
There we go. Now let's go ahead and add user prompt here as well. Handlebars.compile.data.userPrompt and pass the context. Great. User prompt here as well, handlebars.compile, data, user prompt and pass the context.
Great, so now we have template variables here for both our system prompt and for our user prompt. Now I'm going to add to do FetchCredential.userSelected. We currently don't have this, so the only thing we can do is we can use the ones from our environment file. That's why I made sure in the beginning of this chapter that you have that and all the other necessary things installed. So now let's remove KY entirely.
We can leave non-retriable error because we are definitely going to use it. But for now let's add create a Google Generative AI from AI SDK Google. Perfect. So Now that we have the system prompt and the user prompt and we pretend to fetch a credential, let's go ahead and actually create the Google instance using create Google generative AI, open an object inside and make the API key be, well let's make it like this const CredentialValue and for now you can use process.environment and then just use this one or whichever one... I mean, since we are doing Gemini right now, you should use the Gemini one, right?
And just pass it here. So why am I doing it in this super weird way? Well, I'm doing it so I prepare the code for later when we are going to actually fetch the credential value and then we're going to pass it here. And at that point we are no longer going to be using our own API keys nor are we going to need any API keys for AI inside of our .environment file. But just for now to test this and see if it works we need to do it this way.
Now we can open our try and catch. Instead of try let's go ahead and immediately destructure steps from await step.ai.wrap Gemini generate text. Then let's go ahead and pass the second parameter, so I'm just going to collapse them like this so it's easier to look. Generate text. Generate text like this which we don't have imported yet and then open an options.
So generate text can be imported from the global AI package like this. So generate text make sure you have added that. And now let's go ahead and define the settings. First will be the model. Which model are we going to use?
So we're using Gemini and then we're going to use data.model or here are all the options. So this package is type safe as you learned in chapter 7 and you can see that there are actually way more of them here than what I added to my available models list of course. But I kind of did it in a quick and dirty way. I would highly suggest exploring how you can extract these exact types here. If I in the meantime managed to do it myself I will of course update the code to reflect that change.
But yes in here you can see all of these that exist for your version. So using that you can also go back to the dialog inside of our newly created Gemini folder and you can see if all of these exist here. Right? If they do, all good. So just, you know, compare.
Do they exist? And you can fall back to 1.5 flash. Now let's go ahead and add system to use system prompt. Prompt to use user prompt. Experimental telemetry is enabled, set to true.
Record inputs, set to true. And record outputs, set to true. This will give Sentry access to report telemetry on our AI models, which will help us greatly to see exactly which costs occur, which models take the longest and then you will be able to recommend your users some change based on that. And now once we run this we will be able to extract the text from steps first in the array content first in the array check if the type is text and if it is go ahead and use steps.0.content0.text or simply fall back to an empty string. So let me collapse this so it looks nicer.
There we go. And now let's go ahead and publish an event. So right here. Publish Gemini channel dot status node ID and status of success. We successfully executed the AI model.
And now let's go ahead and let's return right here. So I'm not sure how you want to do this but obviously spread the context and then let's do data dot variable name and you can either directly do text or you can do AI response and then do the text inside. However you prefer. Obviously we have this error here, we will take care of that, but let's just quickly take care of the error. So go ahead and catch the error and then all you have to do is publish that error status and throw the error.
Now let's go ahead and resolve this. So we already learned how to resolve this. The problem, the trick is to do it inside of the function right here. So step.ai.wrap. Oh, actually, I think we might be able to do it in an easier way.
So let me just check. After loading, I am immediately going to check if there is no data.variable name, await, publish, GeminiChannel.status, nodeId, status of error. And let me properly wrap this. There we go. And then throw new non-retriable error Gemini node variable name is missing.
And immediately you can see if we throw an error here, if data variable name is missing, you can see that this will no longer yell at us. So we fix this. Why did it work so simple this time? The reason it worked is because all of this is in one single scope. But usually we don't do this.
But what we do is step.run. So step.run then opens another function which is a whole new scope for TypeScript so it cannot do its flow control properly because technically data which we look for right here could have been modified inside of that scope. So that's why since this is kind of a simpler example we don't have to worry about that. Let's also go ahead and do if no data.userPrompt because that is another thing we consider required. Let's go ahead and throw this Gemini node.
UserPrompt is missing. All right, And later I'm going to add to do throw if credential is missing. Great. Now that we have this ready let's see, did we miss anything? I think this is okay and I need a quick reminder.
I'm just going to take a peek at HTTP request executor. All right so we ended up making all of this optional, right? Yeah, and still this model thing is, I really dislike how I handled this. I should have either made it everywhere, a type of string or something. Yeah, technically maybe I could do that.
Maybe I could just make this Z.string like this instead of form schema, instead of inside of Gemini folder dialog.tsx. Let's just go ahead and make it required. Model is required. And then this way, if you go instead of node.tsx here, you can just simplify this to be an optional string. And as you can see, it still works.
The default values now handle this properly. Great. This is a kind of a dirty fix for now. So we are only using available models for one thing and one thing only and that is to render the select items in a loop here. Alright now that we have the executor we have to add the executor to our executions lib executor registry.
Let's go ahead and add node type dot Gemini, Gemini executor. Obviously we have some errors here because we don't have the Anthropic nor OpenAI ones. If you want an easy fix you can just repeat the same for Anthropic or for OpenAI. Just make sure to add to do, fix later and to do, fix later. So you remember that these are not valid.
All right. I think there are still a couple of things we need to do. Instead of node.tsx we are still using HTTP request channel name here. So let's quickly go instead of gemini-actions.ts, let's rename this from HTTP request, all three instances change it to Gemini Gemini token and fetch Gemini real-time token change the import here to use ingest channels Gemini and the Gemini channel and remove I mean change the two instances to use Gemini channel. That's it.
Now go back and set up node, change this to be Gemini channel name, fetch Gemini Real-time token and I think everything else should be fine. Remove fetch HTTP request real-time token and remove HTTP request channel name. And as always you can go ahead and right-click, click find in folder and search for HTTP. This is okay. This is one example where it's okay because we are using it as an example of the previous node information that you can do.
So I think everything should be fine. So how do we test this in the easiest way possible? Well let's use a manual trigger like this. Let's connect the two. Let's click save.
Let's go ahead and open Gemini. I'm going to call this Gemini. I will select 1.5 flash. You are a mathematician. User prompt, what is 2 plus 2?
And click save. Click save again. Let's go ahead and prepare our localhost 8288, so in here we can see our workflows and let's click execute workflow And let's see if we did this correctly. Something is happening here. This never...
I'm not sure if it's our real-time connection that's failing or something else. Gemini 1.5 flash is not found for API version v1 beta. Oh, okay. All right, let's see. Let me try one thing here.
Instead of executor, I mean instead of executions components Gemini executor let me try and just not even listening to the user input Can I just like choose the one that's being offered here? Does that work? Can I refresh here? Is all of this good? Looks good.
Can I just execute workflow now? Okay. Oh, this time the channel worked, but it still failed. Again, Gemini 1.5 Flash is not found for API version v1 beta. Call listmodels to see the list of available models and their supported methods.
So it could be that in the middle of my tutorial, Google AI Studio received some updates. Maybe the API tokens are new. So I'm not even sure myself which version I can use now. It shouldn't be too hard to fix really. I'm just really not sure which version I can use.
Maybe 2.0 Flash? Can I try that? Maybe they've added some limits to their tiers? Honestly, I have no idea. Yes.
So if you change to 2.0, It seems to work just fine. Let me just confirm. The result is stored inside of Gemini. AI response 2 plus 2 equals 4. Amazing.
So it officially works, but yes, something is a little bit weird here with Gemini 2.0 options. Here's what I might or might not do. We can like fall back to one that is working. And inside of our dialogue here, maybe just hide the select option. I don't know.
I'm not even sure myself now. One thing is for sure, this is a horrible way to offer users which models they can choose because we just demonstrated how quickly that can go wrong if you forget to update. So we should definitely find a way to synchronize that. But the problem is even in this kind of AI SDK Google version itself, or maybe just my API key, I'm not sure, but something here is not working as it should because it offers me 1.5 flash but when I try to use it, it fails. It also offers me 2.0 flash and when I try using it, it works.
It could also be a bug within Google AI. I am not sure. So just try some of these models until they work. You can fall back to data.model here if you want to. Just make sure that you then add this option inside.
Like that. Again, not sure how this is supposed to be working since, you know, type safety is telling us that we can use one model but obviously we cannot. So okay yeah let's go ahead and leave it like this for now and yeah this is basically how you add AI nodes. We can now do the exact same thing line for line for all other ones that we need. Anthropic, OpenAI, or a billion others that AI SDK offers.
I just did a quick research if there is a way to reliably display the models that are available. And I couldn't find a way to just read the types coming from the SDK package. So for now this is what I want to do. This is obviously broken and I cannot in good faith recommend that you write this code. So go ahead inside of dialog for your new Gemini folder, remove available models entirely.
Remove the model from the form schema and remove it from here too. Go ahead and remove it from the form reset and then go ahead and remove the entire form field for select. So I just cannot in good faith tell you to do that because it's broken. We're gonna go ahead and do it a much simpler way and then maybe later in the tutorial If I find a reliable way of doing this, I will teach you how to do it. But again, I cannot in good faith tell you to do this because it's obviously broken.
So let's go ahead inside of... Where do we go now? Node.tsx, remove available models, remove the model from Gemini node data and inside of the description generation you can just go ahead and you can just do, well you can use the one model for example inside of executor.ts that you found that works for you, like this. Like hard code what you're going to define your users to use for now, right? Because this is obviously not working reliably.
So we have to hard code inside of the executor.ts on something that will always work for us. And that's why we can safely display that here. So the user knows exactly which one we are going to use on their behalf. They will not be able to select the model because I'm really not satisfied with the way I've developed it right now. So in the executor.ts it is very important that you remove data.model here and just fall back to Gemini 2.0 flash or whatever one works for you.
Just try to get it working. Also remove the model from Gemini data here entirely. So we now no longer need that. Great. So code should actually be simpler now.
Everything should be simpler now. There we go. We just have variable name, system prompt, and user prompt. And in here we hard-coded exactly which one we are using because that is the one that's working for us. So just for fun I'm going to remove this, I'm going to add a new one right here.
I'm going to connect it and oh so this still says my API call. Let's go inside of dialog and instead of calling it MyAPICall, MyGemini, I don't know. And do I use MyAPICall anywhere else? I do. Let's change this to MyGemini, something to indicate to the user like hey this is your variable like you can do whatever you want and then let's fix this.
So this should be dot AI response because that is exactly what we do in the executor right. We return this within AI response option. Actually we did not. Not sure what is the best way of doing this. Should I just do, yeah, we can just do AI response.
I think. You can of course change this to whatever you think is a better user experience. It's you know you're free to modify this however you want. Let's just keep it as text. So Gemini.txt.
I think that is like the simplest possible one. You cannot go wrong with this. MyGemini.txt. Perfect. So I'm going to go ahead and change this to be my Gemini 2 just to see if this works.
My Gemini 2.txt. Oh, how about we try something fun? So, yes, let's call this my Gemini 2. You return only a popular HTTP, popular get API can test for fetch. Get API URL endpoint, free API URL endpoint, Give me an endpoint to list a to do by ID of one.
So hopefully it will know what I mean. We'll see. Maybe it will be a complete failure, maybe it will work. And then let's try an HTTP request here. So this will be my request, get method and can I just do a response dot text here Gemini dot text let's click Save I have no idea how this will work?
Let's click execute workflow. I'm really interested. Maybe it will be a complete failure. Maybe it will work. Failed.
Okay. Failed to parse URL. Let's see what did Gemini respond. It actually did quite well but it added some extra text. That's the problem.
So I'm just going to kind of copy what I expect and see what I expect and see if I can make it say that no formatting example no new lines nothing just URL And let's change this ID of two. So I'm just playing around to see if I can make this work because I really want to see this workflow do something. Don't worry, I'm just going to do one more attempt and then I'm going to show you how to add. Okay, failed. Looks like depending on the model, some of them don't listen to instructions.
Yes, it's still added new line slash at the end. Okay, if it didn't add that it would have worked. So now that we have this and we have this simplified model, simplified model layout with no select option, let's go ahead and do the exact same thing but for OpenAI and Anthropic. So we are going to start by going inside of our features, executions, components, Gemini, copy and paste it, rename it to OpenAI. Go inside of the OpenAI folder, go inside of node.tsx, change this to OpenAINodeData.
Use OpenAINodeData here, change this to OpenAINodeType. Use OpenAINodeType and rename this to OpenAINode. Perfect. To open AI node type. Use open AI node type and rename this to open AI node.
Perfect. Go all the way down and change this to open AI node dot display name and change the actual display name to open AI node. And you should no longer have any errors here. Great. Now let's go ahead and let's rename the logo to open AI SVJ and open AI for the name.
Perfect. Now let's go ahead inside of our node components in the source config. Let's go ahead and duplicate the Gemini one. Add OpenAI and import OpenAI node. Then go inside of the node selector, inside of source components node selector, go ahead and duplicate Gemini, Go ahead and add OpenAI, change the label to OpenAI, uses OpenAI to generate text, and use OpenAI.svg.
There we go. OpenAI node. Now let's go ahead and change the dialog, add the channel and everything else we need. So I'm going to go inside of features, openAI dialog. Everything will stay exactly the same here.
I'm going to change this from Gemini to OpenAI. So OpenAI form values, OpenAI dialog. But everything else can really stay the same. Let's just for fun change this to my open AI. This will be open AI configuration.
Description can stay exactly the same. I don't think we have to modify anything here. All of this is good enough. Instead of node.tsx let's make sure to now import OpenAI dialog, OpenAI form values. Just double check that you are doing this instead of open AI folder so you don't accidentally change your Gemini files.
Now let's go ahead and add open AI form values right here. Open AI dialog and I think automatically it should just work out of the box because they are exactly the same because they both use AI SDK which well follows the same API. We just did some slight modification to tailor it to OpenAI. Now that we have that configured, let's go ahead inside of OpenAIExecutor.ts. Change this from GeminiData to OpenAIData.
And change this finally to be open AI executor. We cannot change the channel yet because we didn't implement it. Let's change this to be a warning of open AI node. Let's go ahead and change this to open AI node. Let's go ahead and for now use your open AI API key.
If you don't have it just put an empty string like I have for Anthropic. There we go. And let's do create open AI. I have no idea if that is the correct one. I'm trying to find I think it is yes create open AI like this from a I as the K open a I you should also have that installed so a I as the K open a I make sure you add that because again even if you don't have the API keys for this, your users might.
And later, we're going to allow your users to add any API keys they want. And instead of this being a Google, this will be OpenAI. And let's change this to OpenAI generate text. And then just go ahead and change this to, I have no idea honestly which one. I am really not up to date.
I guess GPT-4? I don't know. And then you can copy GPT-4 here, go inside of OpenAI dialog and change the description, my apologies, node and change the description here to let the user know we are using GPT-4 as default because we remove the option to select the model. And I think that's it for the executor. Let me just check.
So we are using open API key, which will later be something else. The API is exactly the same. We just have to create the channel. So I'm just going to collapse everything and I'm going to go inside of ingest channels, copy and paste Gemini channel, change this to open AI. Go ahead and change this to be open AI channel name, open AI execution, open oops, open AI channel.
There we go. As simple as this. Then let's go inside of source, ingest functions.ts, add open AI channel, Execute it. Make sure you have imported OpenAI channel. Perfect.
Now that we have that, let's go inside of components, my apologies, inside of features, executions, components, OpenAI. Let's go inside of actions. Change this from Gemini token to OpenAI token. Go ahead and import OpenAI channel from channels OpenAI and do we need to modify anything? Yes.
Instead of fetch Gemini real-time token, it's going to be fetch OpenAI real-time token. Perfect. Now, we can go instead of OpenAI node, we can do fetch OpenAI real-time token, and this will now be OpenAI channel name from Ingest channels OpenAI. We can use that here and change this to fetch OpenAI real-time token. And now we should have real-time synchronization with OpenAI.
Let's go ahead and go back inside of OpenAI executor and let's go ahead and change the import from Gemini to OpenAI and replace all instances of Gemini channel with OpenAI channel. There we go. No errors anywhere in our code. To double check if we did this correctly, right click on the OpenAI folder, find in folder and search for Gemini. It looks like there is one thing left, the placeholder.
So this is inside OpenAI dialog. Change the placeholder to be myOpenAI, Like this. There we go. So how about we just quickly try this. I'm just going to simplify this.
I'm going to add OpenAI. I'm going to connect the two. Even If you don't have an API key, check if it will fail. It's going to be fun. My OpenAI, you are a mathematician.
What is 2 plus 2? Click save. Let's click save right here. One thing we forgot to do. Executor registry.
Change OpenAI to OpenAIExecutor. Looks like it is capitalized. I don't like that. So I'm going to go instead of components OpenAI executor and I will just make sure it's a lowercase like all of my other ones then I can replace these two instances I can then also remove the to-do for OpenAI. So let's go ahead and try this again.
I will click save right here. I'm going to refresh just in case. I will click execute workflow. This works. And this will actually depend on my API key, but it works.
Perfect. Yours might fail if you don't have an API key, which is perfectly expected. So don't worry. I just wanted to test if the model works. So if for whatever reason GPT-4 is not working for you, you have a list of available ones.
Well apparently available ones because we just saw with Google that was not the case. So just you know add a string and you will see a list of all available ones. So just select one and try until it works. Perfect. So that was for OpenAI and one more left and we are done with this chapter.
I know it's a lot of repeated work but I want to make sure you see every single line of code that I write. It's kind of the gist with my tutorials. I don't really skip anything. I show you every single line of the code. So Let's go ahead and copy OpenAI, paste it inside of components in the executions folder and rename it to Anthropic.
If it asks you to update imports, you can select yes. It's the simple cache thing, it might open your .next folder, just save that file, close it and close this folder. Don't worry about it. Let's go inside of Anthropic instead of Node.tsx. Let's start with renaming things.
Instead of OpenAI everything will be Anthropic. So AnthropicNodeData, AnthropicNodeType, AnthropicNode and use AnthropicNodeType here And then we have to fix this. Anthropic. Cannot find the name Anthropic. Anthropic node.
Yeah, there we go. Perfect so now that we have this let's go ahead and modify base execution node right here to use Logos Anthropic SVJ, Anthropic like this. Oh, it looks like I somehow overwritten my OpenAI. So don't do the same mistake I did. Yes, I did something very incorrect here.
Okay, a lot of mistakes now. Luckily, I don't think I went too far. Okay, what I did accidentally, I somehow deleted my OpenAI folder and I seem to have renamed it to Anthropic. So now I just reverted it back to OpenAI. So you probably don't have to do this, but I have to.
I just have to fix this. Okay. I'm so sorry. Again, copy OpenAI folder, paste it inside of components, rename it to Anthropic. There we go.
Okay, so sorry about that one. And now again, same thing I just previously did. So I'm going to replace these instances of OpenAI to Anthropic. And then I'm going to go down here and change this to Anthropic node. There we go.
Okay. That's what I wanted to do. And then in here change this to Anthropic. I think I accidentally also changed instead of my OpenAI folder instead of node, I changed this to Anthropic. This should be open AI.
So sorry about that. Instead of copy and paste I replaced it. So always be careful yourself not to do that. Instead of Anthropic after we add the node here and the logo for Anthropic we have to add it to node components. So let's go ahead and copy this, add Anthropic and simply add Anthropic node.
After that we have to go to node selector instead of source components node selector. Go ahead and copy this. Anthropic. Anthropic uses Anthropic to generate text and use Anthropic.svg. Go ahead and click plus and you will find Anthropic node.
Perfect. Now let's go ahead and let's modify the dialog here. So you can go ahead and focus on features, executions, components, anthropic, dialogue.tsx everything here will be the same. Replace instances of OpenAI with anthropic So anthropic form values and anthropic dialog. Change this variable to my anthropic for example.
Anthropic configuration, my Anthropic and I think everything else can stay the same. Perfect. Now go back into Node right here. Make sure you are importing Anthropic Dialog and Anthropic form values. Make sure you use them here and make sure you are using the Anthropic dialog.
And now it should say Anthropic configuration tailored to Anthropic. Perfect. Now let's do the channel so we can wrap it up with the executor. So inside of source, ingest channels, copy either OpenAI or Anthropic one, my apologies Gemini one, and rename it to Anthropic. Inside of here, change the instance of OpenAI or Gemini to Entropic.
Name it properly and make sure to export the constant. Entropic channel. Perfect. Immediately go inside of functions.ts and add anthropic channel. There we go.
Now that we have that, Let's go ahead inside of source, features, executions, components, Anthropic. Let's go inside of actions.ts, change this to be Anthropic, token, Import from Anthropic channel, so Anthropic channel, replace these two instances right here. Then go inside of node.tsx, change use node status to use anthropic channel name and fetch anthropic real-time token and remove the import for fetch open AI real-time token and remove the unused channel name for open AI. Then let's finally go inside of Executor here. Let's change this from OpenAI data to be Anthropic data.
Change this from OpenAI Executor to Anthropic Executor. And finally change the ingest channel to be anthropic. Anthropic channel. Replace all instances of open AI channel with Anthropic channel. Now what we have to do is we have to go aisdk anthropic, use create anthropic again if you don't have this please install it so package.json you can see my version right here now that we have create anthropic we can go ahead and create the anthropic value here create anthropic Make sure that you change the environment key to whatever it is in your Anthropic API key right here.
It can be empty like mine for example I don't have that key. Now let's go ahead and use Anthropic here, change this to Anthropic generate text and change this again to, I have no idea, Sonnet 4.5. Is that the newest one? I guess. Save that.
Then go inside of node.tsx in the Anthropic and configure the hardcoded model right here. I didn't copy it properly. It would appear so. So I'm going to copy it now and I'm going to paste right here. There we go.
So now I'm going to go ahead inside of executor registry and very simply I'm going to change this to use the entropic executor. There we go. That is how we add three different AI models. So I'm going to go ahead and remove this one. I will add this one and this one will most definitely fail because I don't have an API key.
So myentropic, you are a mathematician, what is 2 plus 2, Click save, save up there. Let's go ahead and just refresh in case it needs a refresh because we added some new channels and let's click execute workflow. What I'm hoping for is at least to see the real-time status and then a failure which is true and the error should be because of a missing API key which is exactly what it says right here. Amazing amazing job. So let's go ahead and check if that's what we intended to do.
We intended to add Gemini, OpenAI and Anthropic. My apologies for the mess with the model thing. I just really didn't like how I do it. I think it's better to remove it entirely than to have that broken version of it. And this works just fine.
It will not be hard for you to, you know, improve on this later. Think of it as a personal challenge. You already saw the code for it. So you know how I would do it. Just try and make it better, More type safe.
Maybe try and make a fetch API request to your backend using TRPC of course, and then maybe your backend can return you all the available models. I think that should work. This way they are always gonna be up to date, which means you're gonna need to have some kind of spinner for your select component. So yes a bit more complex but shouldn't be too hard for you. You came this far.
Amazing. Let's go ahead and merge this now. So 24 AI nodes. I'm going to create a new branch 24 AI nodes. Once I've created a new branch I'm going to go ahead and commit 24 files right here.
24 AI nodes. Oh so chapter 24, 24 files. Great. Let's go ahead and commit and let's publish the branch. Once we've published this branch, let's go ahead and open a pull request.
And since this was a big one, I do want to see what CodeRabbit will say, even though it will probably be a lot of repeated comments, because we added three identical things. So if it finds a mistake in one of these it will find mistake in all three. So I will just try to see the most useful comments from CodeRabbit and I will show them from you. And here we have the summary by CodeRabbit. New features.
We added three new AI execution nodes, Anthropic, Gemini, and OpenAI for enhanced workflow automation. Each AI node supports customizable system and user prompts for tailored interactions. We integrated real-time status monitoring for AI task execution within workflows. Perfect. So let's take a quick look at the sequence diagram.
And the first thing I noticed here is how it immediately understood that these three nodes are exactly the same because it didn't create a sequence diagram for any specific AI model. It created for all three of them right here. So it all starts with the AI node using React flow. Double clicking on that opens the dialog in which we can configure the model, right? Once we enter the form, we validate it using Zod Schema.
We then submit those form values and we save the entire thing. Once we manually execute or with any other trigger, since we've added Google Form and Stripe, you can use any of those, we finally call the node executor. The first thing we do for all of these are publish the loading status. We then compile handlebars templates in case user added any variables to the system prompt or the user prompt. We then initialize each AI client respectively using for now our API keys and then call the generate text and then we populate that in the context.
So what are the comments here? Well, they're actually not that bad. So in here we have a typo and we have the same typo in I mean I have I don't know if you have but I was typing container instead of contain in Anthropic executor I left open AI warnings instead of Anthropic ones. So yes, good catch by CodeRabbit here. Again, same typo for me here in some other dialogue in the Gemini one.
And then in here, it's telling us to make sure that we check if we have the credential value. This is a very good point and it is exactly what we are going to do later when we implement credentials. So this is just temporary. We are later going to actually fetch the credential and we're going to check if it doesn't exist and we are immediately going to throw an error just like this one. So good catch by cold rabbit, but we are one step ahead.
That's exactly what we're gonna be doing. In here, it suggests adding defensive checks for array access. That's a good idea. I could add these question marks. You can add them too.
So this way you won't run into any errors with accessing deeply nested objects. And I think that all the other comments repeated themselves as I said since we have three identical codes. So let's go ahead and merge this pull request, great comments by CodeRabbit here. Now let's go ahead and go back instead of our main branch. As always, make sure to synchronize the changes.
And once you've synchronized your changes, confirm that you have them in the graph right here. Here they are, 24 AI nodes. Amazing. So I believe that marks the end of this chapter. We pushed to GitHub and we reviewed our pull request.
Amazing, amazing job and see you in the next chapter.