AI-powered smart contracts use AI to access real-time web data and interpret natural language instructions, enhancing traditional smart contracts.
In this tutorial, we'll cover:
Note: Some knowledge of Python is assumed and needed in this tutorial.
Smart contracts have been game-changers, no doubt. They are self-executing by nature, with the terms of the agreement directly written into code. When predetermined conditions are met, they deploy on a blockchain and ensure that transactions are processed securely, transparently without needing a third party.
However, smart contracts only follow specific instructions and cannot handle unexpected situations or complex requirements not in their programming. They don't learn or adapt based on what happens over time. Also, they cannot access external data independently. They need third-party services like Oracles to feed external data to smart contracts, enabling them to react to real-world events.
This limitations of smart contracts is what GenLayer is trying to solve by creating Intelligent Contract that retains all the capabilities of traditional smart contracts but can also:
Use LLM models like GPT-4 and LLaMA to understand and process natural language instructions.
Access and use real-time data from the internet without the need for third-party tools.
GenLayer uses the Optimistic Democracy consensus method to validate transactions and operations of Intelligent Contracts. A key part of this consensus method is the Equivalence Principle. The Equivalence Principle is a specific rule or set of criteria used within the Optimistic Democracy framework to ensure accuracy and consistency when dealing with non-deterministic outputs, such as those generated by LLMs or real-time web data. As we go forward, I will explain more about the Equivalence Principle and how it works when we execute our intelligent contract.
For this blog, we are going to look at how to build a football prediction intelligent contract that can fetch real-time data from the web and process it using LLM to predict match outcomes. Sounds interesting, right?
Let's get right into it :).
Before we start building our contract, we need to set up the environment where we will run it. The GenLayer's Simulator is an interactive sandbox that we can use to build and test our Intelligent Contracts. Let’s set it up.
Go to your terminal and copy-paste the following to install GenLayer on your computer:
npm install -g genlayer
Once installed, run the init command to start the process of setting up your development environment:
genlayer init
When you run this command, it initializes the setup with 5 validators and prompts you to select your preferred LLM provider(s).
There are three options you can choose from:
OpenAI: Fastest and most reliable option for running validators)
Ollama: Free and open-source option, it may perform slower than other options
Heurist: Inference provider for open source AI models
After you have made your selection, it automatically downloads and configures the necessary Docker containers for the GenLayer simulator environment. Once the setup is complete, you can access the GenLayer Simulator at http://localhost:8080/.
Now, let’s get to building our contract!
The simulator has a code editor for writing code.
Intelligent Contracts are written in Python, making it ideal for handling data and string operations necessary for web interactions and natural language processing.
For this prediction contract, we will be retrieving our web data from the BBC Sport website and then we use an LLM to process the retrieved data to determine which team is the winning team. Let's see the step-by-step process to do this
If you want to skip the walkthrough, check the code on GitHub and go to the "Deploy Contract" section below.
First, we’ll import the libraries and modules that we will be using for our Intelligent Contract:
import json
from genvm.base.equivalence_principle import EquivalencePrinciple
from genvm.base.icontract import IContract
json
: This module is used for parsing and handling JSON data, which is a common format for data interchange.EquivalencePrinciple
: This ensures that the results are consistent and accurate across different validators. It plays a crucial role in maintaining the integrity of non-deterministic outputs by comparing results from multiple validators.IContract
: The base class for creating Intelligent Contracts on GenLayer, providing essential properties and behaviors. It ensures that the contract integrates smoothly within the GenLayer (GenVM) environment.Now, we need to define our Intelligent Contract class in this case it is Prediction Market. Our Intelligent Contract contract class inherits from IContract
. Inheriting from IContract
is necessary to ensure that the contract executes properly within the GenLayer framework:
class PredictionMarket(IContract):
Next, we are going to initialize the state of the contract and set up any necessary parameters. This step is crucial as it defines the initial conditions and properties that our contract will use throughout its execution:
class PredictionMarket(IContract):
def __init__(self, game_date: str, team1: str, team2: str):
self.has_resolved = False
self.game_date = game_date
self.resolution_url = 'https://www.bbc.com/sport/football/scores-fixtures/' + game_date
self.team1 = team1
self.team2 = team2
In this constructor, we define the following parameters:
game_date
: The date of the game formatted as 'YYYY-MM-DD'.team1
: The name of the first team participating in the match.team2
: The name of the second team participating in the match.has_resolved
: Indicates whether the game's outcome has already been resolved, preventing redundant processing.resolution_url
: The URL of the BBC Sport website from which the game results can be retrieved.
These parameters define the initial state of our contract, making it ready to process game outcomes.
Now, let's add a method to determine the outcome of the game. This method ensures that we only process the game's outcome if it hasn't been resolved yet:
async def resolve(self) -> None:
if self.has_resolved:
return "Already resolved"
final_result = {}
This method first checks if the outcome has already been determined by inspecting self.has_resolved
. This prevents redundant processing and ensures efficiency. If the game hasn't been resolved yet, we initialize final_result
to store the results. This dictionary will hold the final validated results of the game.
The Equivalence Principle is very important when writing an Intelligent Contract. When you access the web or call LLMs, inconsistencies can arise. The Equivalence Principle like we said earlier is a specific rule or set of criteria used to validate the final outputs of non-deterministic operations (web or LLM calls). This principle uses multiple validators, with one acting as the leader to propose an outcome, and others validating this outcome based on the defined criteria or rule.
Therefore, in our contract, we need to define our Equivalence Principle to prevent inconsistencies in our output from the web or when processes with LLM:
async with EquivalencePrinciple(
result=final_result,
principle="The score and the winner have to be exactly the same",
comparative=True,
) as eq:
For our prediction contract, the Equivalence Principle states that "The score and the winner have to be exactly the same." Validators will use this principle to compare their results with the leader’s result. If the result match according to the Equivalence Principle, then the final result is accepted. The comparative=True
flag indicates that both the leader and the validators perform identical tasks and compare their results to ensure consistency.
Within this Equivalence Principle, we are going to fetch the web data about the game and process it using LLM.
Moving forward, within the Equivalence Principle block we are going to retrieve the webpage content from the BBC Sport news website URL:
web_data = await eq.get_webpage(self.resolution_url)
print(web_data)
Once the data is retrieved, we are going to process it with the LLM to check the result and determine the winning team from the retrieved webpage.
To process the information from the retrieved webpage, we are going to craft prompts to send to the LLM, telling it exactly what it needs to do. When interacting with LLMs, it's important to create prompts that are clear and specific to guide the model in providing accurate and relevant responses. This is the prompt we crafted below:
task = f"""In the following web page, find the winning team in a matchup between the following teams:
Team 1: {self.team1}
Team 2: {self.team2}
Web page content:
{web_data}
End of web page data.
If it says "Kick off [time]" between the names of the two teams, it means the game hasn't started yet.
If you fail to extract the score, assume the game is not resolved yet.
Respond with the following JSON format:
{{
"score": str, // The score with numbers only, e.g., "1:2", or "-" if the game is not resolved yet
"winner": int, // The number of the winning team, 0 for draw, or -1 if the game is not yet finished
}}
"""
result = await eq.call_llm(task)
print(result)
The prompt we have crafted instructs the LLM to:
Identify the winning team and score from the retrieved web page
We also included a condition for the LLM to check if the game has not started yet. If the phrase "Kick off [time]" appears between the names of the two teams, it indicates that the game hasn't started. The LLM is instructed to recognize this scenario and understand that no result can be extracted yet.
We also included another condition to handle for the LLM to assume that the game is not resolved if it cannot extract the score. This ensures that incomplete or ongoing games are handled appropriately.
Finally, we ask the LLM to respond using a JSON format
This detailed prompt handles different scenarios and ensures that the LLM extracts and processes the required information accurately and consistently. Once the prompt is crafted, we send it to the LLM using the call_llm
method.
Once our result is obtained from the LLM, it will then be checked and validated according to the Equivalence Principle defined above: "The score and the winner have to be exactly the same.” If the result match according to the Equivalence Principle, the final result is accepted.
eq.set(result)
Note: The validators don’t validate every step in the Equivalence Principle block and only focus on the end result to reduce the need for complex validations, saves resources and simplify the contract's operations
Once the result is validated and finalized, we can now parse the result using json.loads()
. This converts the result into a format that can be easily manipulated and evaluated. From our parsed result, we will extract the winner and score:
result_json = json.loads(final_result['output'])
if result_json['winner'] > -1:
self.has_resolved = True
self.winner = result_json['winner']
self.score = result_json['score']
return result_json
If the game's outcome is determined (winner > -1), the contract's state is updated accordingly. This ensures that the final outcome is accurately recorded.
Now we are ready to deploy our contract!
Let’s see our contract in action!
In the GenLayer Simulator, click on the play button to run your contract.
In the constructor parameters section, provide the game date and the names of the two teams you want to check. For example, you might set game_date
to "2024-06-05", team1
to "Brazil", and team2
to "Jamaica".
Once the game details are set, click on Deploy
To interact with the deployed contract, go to the Execute Transactions section. Here, you can call the resolve method to process the game's result.
When the resolve method is executed:
This process ensures consistency and accuracy across the network. If the validators return "1:3" with Jamaica (Team 2) as the winner and the leader returns "1:2" with Jamaica (Team 2), the validators will reject the result.
View the logs to see detailed information about the contract interaction.
🙌 Congrats if you read all the way through!!!
The future looks bright for AI-powered smart contracts. Aside from the football prediction contract, there are other intelligent contract ideas you can build and test with the GenLayer Simulator:
There are more example ideas in the GenLayer docs on how to achieve some of the above too.