Experiments: The Intersection of NFTs and AI

SuperFriedChicken
6 min readMar 12, 2023

Since the beginning, Super Fried Chicken (SFC) has tried to do things a little different. A psychedelic theme was the base, tied in with a Fear and Loathing esque universe to explore. We have built a fully functional fight club, with live betting, weapons, armor, and other upgrades.

Now we are on to a new chapter, integrating the world of AI. With all the latest technology out there, surely AI systems could help us improve our offering.

While we use a sophisticated system to calculate the fights, our output wasn’t the most impressive. We would output a battle log that was unique for each battle, and contained a ton of phrases to keep them interesting.

However, it didn’t offer the depth we pictured in our minds. Depth of story, something to entice the reader.

ChatGPT to the rescue.

We figured, what if we seed ChatGPT the generated battle log, and have it generate an actual fight summary? This would a ton easier for users to consume, and a lot more fun too.

This ended up being super simple to do within our Rails environment. First, we added the ruby-openai gem to our Gemfile.

gem 'ruby-openai'

A quick bundle install and the gem was installed. We created an initializer at /config/initializers/openai.rb which allowed us to configure the credentials.

OpenAI.configure do |config|
config.access_token = ENV.fetch('OPENAI_API_KEY', nil)
config.organization_id = ENV.fetch('OPENAI_ORGANIZATION_ID', nil) # Optional.
end

The Fight model only needed minimal additions to leverage ChatGPT:

def fetch_chatgpt_description
return if battle_log.blank?

prefix = "Given the following log of a fantasy battle between two chickens. Please write up a small description of the fight. Ignore damage numbers which appear in parenthesis. There can be multiple paragraphs. The winner was #{winner.name}.\n\n"

response = openai_client.chat(
parameters: {
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: prefix + battle_log.join("\n") }]
}
)

text_resp = response.dig('choices', 0, 'message', 'content')
if text_resp.blank? || text_resp.length < 20
Rails.logger.error("ruby-openai response empty: #{response.inspect}")
return
end

text_resp
rescue StandardError => e
Rails.logger.error("ruby-openai gem errored out: #{e}")
nil
end

private

def openai_client
@openai_client ||= OpenAI::Client.new
end

So lets break this down, first, we can only run this code if we have an existing battle log. The battle log is merely an array of actions that happen during the fight.

We used ChatGPT’s new gpt-3.5-turbo model. This is a fast, less expensive option, and since we are only dealing with chicken fights, it seems like the right choice. You can read more about this on OpenAI’s blog at https://openai.com/blog/introducing-chatgpt-and-whisper-apis.

Then we query ChatGPT and ask it to generate a summary of the fight. This can take a short while, but since we do this in a background queue, we don’t have to worry as much about timeouts.

Once we had this working, we turned a simple battle log into an epic summary!

Here’s a sample prompt from a recent fight.

Given the following log of a fantasy battle between two chickens. Please write up a small description of the fight. Ignore damage numbers which appear in parenthesis. There can be multiple paragraphs. The winner was Smokey.

SunSpots McCock delivers a devastating critical hit that sends Smokey flying! (-16)

Smokey is losing blood… (-5)

Smokey gives SunSpots McCock the ultimate titty twister. SunSpots McCock’s feathers won’t be growing back anytime soon! (-18)

SunSpots McCock has been slashed… (-5)

SunSpots McCock rubs spice in Smokey’s eyes and then asks him which came first the chicken or the egg? Smokey’s head is beginning to explode! (-24)

Smokey has been slashed… (-5)

Smokey blows magic mushroom powder at SunSpots McCock, and everything seems like its melting! (-22)

SunSpots McCock bled… (-5)

Smokey has knocked the fuck out of SunSpots McCock!

And this was the full summary response.

The battle between SunSpots McCock and Smokey was intense and brutal. The two chickens went back and forth as they dealt deadly blows to each other. SunSpots McCock started off strong, delivering a critical hit that sent Smokey flying. Smokey was losing blood, and things were not looking good for him.

However, Smokey was not one to give up easily. He fought back with a vengeance, giving SunSpots McCock the ultimate titty twister that left him featherless. SunSpots McCock tried to counterattack with a slash, but he wasn’t fast enough, and Smokey managed to dodge and retaliate.

SunSpots McCock then resorted to dirty tactics, rubbing spice into Smokey’s eyes and taunting him with the age-old question of which came first, the chicken or the egg. Smokey’s head began to explode, and it seemed like he was at his end.

But just when things looked their bleakest for Smokey, he managed to turn the tables once again. He blew magic mushroom powder at SunSpots McCock, causing everything to melt and giving him a momentary advantage. SunSpots McCock tried to fight back, but he was no match for Smokey’s final blow, which knocked him out completely.

In the end, Smokey emerged victorious, battered and bruised but ultimately triumphant. The battle had been a fierce one, but Smokey had proven himself to be the stronger warrior.

This was indeed a huge improvement, but we weren’t done. We have been playing around with text-to-speech API’s for a long time. Finally we had good enough content that audio was the next logical step.

We needed announcers.

Enter ElevenLabs.

ElevenLabs specializes in audio AI solutions. After looking around for a bit, we decided to integrate their API into our text-to-speech pipeline. The code to integrate their API was straightforward to add.

def attach_summary_audio!
return if chatgpt_description.blank?
return unless ENV.fetch('ELEVENLABS_API_KEY', nil)

voice = %w[lJaDTYOMKl4XYTBLu4x9 aoEs5VXeWdWHFIA3VBWb]

uri = URI.parse("https://api.elevenlabs.io/v1/text-to-speech/#{voice.sample}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 240

headers = {
'accept' => 'audio/mpeg',
'xi-api-key' => ENV.fetch('ELEVENLABS_API_KEY'),
'Content-Type' => 'application/json'
}

data = {
text: chatgpt_description,
voice_settings: {
stability: 0.5,
similarity_boost: 0.75
}
}

request = Net::HTTP::Post.new(uri.request_uri, headers)
request.body = data.to_json

response = http.request(request)

return unless response.code == '200'

summary_audio.attach(io: StringIO.new(response.body), filename: 'summary.mp3')
rescue StandardError => e
Rails.logger.error("elevenlabs errored out: #{e}")
nil
end

First we make sure we have a description from ChatGPT and a valid API key for ElevenLabs. Then we build up a https request, and pass in the ChatGPT description with a few voice settings.

If all goes well, about 30–60 seconds later we have an audio file of the fight!

ActiveStorage is used to save the files, that way in development they are saved locally, and in production they are saved to cloud storage.

ElevenLabs also has advanced voice training functionality, so we trained two voices, and love our new announcers!

You can hear how it all turned out by listening to the SunSpots McCock and Smokey fight at https://superfriedchicken.xyz/fights/9361.

Errors will Happen.

While these new features are exciting, there is one undeniable downside, OpenAI and ElevenLabs need to be up when the fights are calculated. Luckily, we have found their APIs to be quite reliable.

If an API fails, we simply degrade the fight experience. Fights will continue to complete, and fighters will still be able to get their tendies, but they just might not get a fancy audio commentary, or a fight summary at all.

Worst case, they see a battle log, just like they did before AI was implemented.

API Calls not Required.

One pattern we wanted to note, was building arrays inside of ChatGPT and then using them within your code. This allows you to have multiple options for a string/template, and avoid API calls at runtime.

We improved the tweets on our fight feed by having ChatGPT generate us an array of tweet templates, and choosing between those. We went from having 1 phrase, to 30 in just a few minutes, and now it doesn’t look nearly as repetitive.

Future Experiments.

AI workflows are just beginning to develop, and we are just starting to see the incredible improvements that are possible. Our characters and worlds will come alive like never before.

Stay tuned for our next post about incorporating visual AI tools into SFC. Get a deep dive into El Pollo Triptaminas, our next collection coming out soon.

If you like our crazy little project, and want to learn more, or if you just want to fight some cocks, come visit us at https://superfriedchicken.xyz.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

SuperFriedChicken
SuperFriedChicken

Written by SuperFriedChicken

5999 Super F#$%eD UP Chickens running wild on the ETH Blockchain

No responses yet

Write a response