Open AI powered NPCs you can have conversations with

Codewithroblox
5 min readJan 29, 2023

--

With the help of OpenAI API, it’s now possible to give your NPCs their own unique personalities and the ability to engage in meaningful conversations with players.

In this guide, we will walk you through the steps necessary to implement this cutting-edge technology into your Roblox game.

Youtube walkthrough available here

1 Get your API key:
Make an account here so you can get an API key. To use the model we will submit our users’ message as HTTP requests to the Open AI API. It’s not important to know what an HTTP request for this tutorial. Just know it’s a type of message that we’re sending off to a server, and getting information back. In this case we send our users’ messages and get OpenAi’s messages as a response. The API key is just used to authenticate these messages. (You’ll see later)

2 Create a script called “ChatAI” inside the NPC you wish to speak to:

local char = script.Parent
local name = char.Name
local rs = game:GetService("ReplicatedStorage")
local TextService = game:GetService("TextService")
local chatRe = rs:WaitForChild("ChatRE") -- get chat remote event
local color = Color3.fromRGB(255, 170, 255) -- what color our npcs text will be in chat so it stands out
local busy = false --boolean for debounce
local OpenAIScript = require(game:GetService("ReplicatedStorage"):WaitForChild("OpenAI"))--module script with API call

game.Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function(character)
local humanoid = character:WaitForChild("Humanoid")

player.Chatted:Connect(function(msg) --Fires when Chat:Chat() is called.
local match = string.match(msg,name) --Is our NPCs name in the string
if match and busy == false then
local plrPosition = humanoid.RootPart.Position
local charPosition = char.Humanoid.RootPart.Position
if (plrPosition - charPosition).magnitude <30 then --checks player distance to npc
busy = true
local newStr, replaced = string.gsub(msg, name, "") --remove NPC name from the string we're submitting to openAI
--#SPECIAL NOTE#
local return_msg = OpenAIScript.GetMessage(character.Name, newStr,char.Value.Value)--submit the message to openAI
--cleaning the string
if string.match(return_msg, "Dumb Monk:") then
return_msg = return_msg:gsub("^.*Dumb Monk:", "")-- Sometimes response will start with "Character name: [response]" This removes everything before the response in that case
end

local stripped_msg =return_msg:gsub("^%?", "", 1)-- Sometimes response will add a "?" if user input doesn't have one. This line removes it
stripped_msg = string.gsub(return_msg, "^%s+", "")--remove additional space at begining and end of string
stripped_msg = string.gsub(return_msg, "%s+$", "")
chatRe:FireAllClients(char,player.Name..": "..stripped_msg,color)--send NPC's character and message with player NPC's name concatted
busy = false
end
end
end)
end)

end)

This creates an event listener that fires every time a player submits a message, checks if our NPC is mentioned, checks if the player is within 30 studs then sends the message over to our “OpenAI” module script (created on step 4). The message is then saved in the “return_msg” variable (the line below the comment “— #SPECIAL NOTE#”). The next couple of lines format the strings, and then we fire our remote event with the “FireAllClients” event (explained more on step 7).

3 Add a string value to your NPC to identify it:
Mouse over your NPC in the explorer window > click the grey plus sign that appears next to its name > type string > click string variable, in the value section write a string that we can use to identify our character. You’ll see how this is used in step 4. Note that this value gets passed into the “GetMessage” function on the line below the comment “ — #SPECIAL NOTE#”

4 Create a module script called “OpenAI” in ReplicatedStorage with the following code:

local module = {}
local TextService = game:GetService("TextService")
local http = game:GetService("HttpService")
local apiModule = require(game:GetService("ServerScriptService").ApiModule)
local apiKey = apiModule:getApiKey()
local endpoint = "https://api.openai.com/v1/engines/text-davinci-003/completions"--select your model

local prompts = {
dumb = "I'm Dumb Monk, the sage of terrible advice. Got a problem? I'll give you some terrible advice that will make it worse.",
smart = "I'm Wise Monk, filled with ancient wisdom from deep meditation. Share your problem and I'll give you some deep philosophical answers."
}

local data = {
prompt = "",
temperature = 0.5,
max_tokens = 100,
}

function module.GetMessage(charName,message,id)
data.prompt = prompts[id] .. charName..": " .. message --select prompt using NPC id
local response = http:RequestAsync({
Url = endpoint,
Method = "POST",
Headers = {
["Content-Type"] = "application/json",
["Authorization"] = "Bearer " .. apiKey
},
Body = http:JSONEncode(data),
})--sends message to the openAI api and saves it as "response"
local json = http:JSONDecode(response.Body) --decodes response from JSON format
print(json)
return json.choices[1].text --selects the response text from with the http response
end

return module

Here we create the “GetMessage” function that gets called below the comment “ — #SPECIAL NOTE#” in our first script. It takes the player’s name, message, and the NPCs Value variable as parameters. In the first line of the function we create the message that we’re going to submit to openAI, this is called a prompt. We create our prompt by using the NPC ID (“dumb”) to query the “prompts” dictionary for a string. This string contains a description of the characters personality, which we then concatenate to our player’s message so that the AI model knows to reply to the player’s message in the style of the NPC they spoke to. Use the documentation’s prompt guide to assist you.
“Data” table includes our prompt and some settings that we send to open ai.
The endpoint lets us select which model we want. Chose Davinci-003 because it’s the best.
The Method and Headers are just settings require by OpenAi’s API (these will always be the same for OpenAi). Note that we pass our API key in the authorization section.

5 create a module script called “ApiModule” in the serverscriptservice with the following code:

local apiKey = "YOUR API KEY"
local module = {}

function module.getApiKey()
return apiKey
end

return module

Get your API key by logging into openAI where you created your account in step 1. Click your name in the top right screen and click view API keys in the menu that drops down. It should look something like:
“sk-XXXXXXXXX1Q5CKXXXXXXXXXEMX0byj8xAhXXXXXXXPXXXXX”
Paste that in where it says “YOUR API KEY”

6 Create a remote event called “ChatRE” in the ReplicatedStorage
7 Create a local script called “ApiModule” in starterplayer > starterplayerscripts with the following code:

local rs = game:GetService("ReplicatedStorage")
local chatRe = rs:WaitForChild("ChatRE")
local starterGui = game:GetService("StarterGui")
local chatFont = Enum.Font.SourceSansBold
local chatSize = 18
local chatService = game:GetService("Chat")


local function sendChatMsg(npcChar,msg, chatColor)
repeat
wait(.1)
local success = pcall(function()
starterGui:SetCore("ChatMakeSystemMessage",{--send message to chat gui
Text = msg,
Color = chatColor,
Font = chatFont,
TextSize = chatSize})
chatService:Chat(npcChar.Head,msg)--put message over NPC's Head
end)
until success
end

chatRe.OnClientEvent:Connect(sendChatMsg)--fire from remote event in NPC's ChatAi script

The comments explain this one. The remote event is fired by the “FireAllClients” event in the “ChatAi” script from step 2

--

--