diff --git a/contracts/contracts/OpenAiSimpleLlm.sol b/contracts/contracts/OpenAiSimpleLlm.sol new file mode 100644 index 0000000..753732b --- /dev/null +++ b/contracts/contracts/OpenAiSimpleLlm.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "./interfaces/IOracle.sol"; + +contract OpenAiSimpleLLM { + address private oracleAddress; // use latest: https://docs.galadriel.com/oracle-address + IOracle.Message public message; + string public response; + IOracle.OpenAiRequest private config; + + constructor(address initialOracleAddress) { + oracleAddress = initialOracleAddress; + + config = IOracle.OpenAiRequest({ + model : "gpt-4-turbo", // gpt-4-turbo gpt-4o + frequencyPenalty : 21, // > 20 for null + logitBias : "", // empty str for null + maxTokens : 1000, // 0 for null + presencePenalty : 21, // > 20 for null + responseFormat : "{\"type\":\"text\"}", + seed : 0, // null + stop : "", // null + temperature : 10, // Example temperature (scaled up, 10 means 1.0), > 20 means null + topP : 101, // Percentage 0-100, > 100 means null + tools : "", + toolChoice : "", // "none" or "auto" + user : "" // null + }); + } + + function sendMessage(string memory _message) public { + message = createTextMessage("user", _message); + IOracle(oracleAddress).createOpenAiLlmCall(0, config); + } + + // required for Oracle + function onOracleOpenAiLlmResponse( + uint runId, + IOracle.OpenAiResponse memory _response, + string memory _errorMessage + ) public { + require(msg.sender == oracleAddress, "Caller is not oracle"); + if (bytes(_errorMessage).length > 0) { + response = _errorMessage; + } else { + response = _response.content; + } + } + + // required for Oracle + function getMessageHistory( + uint /*_runId*/ + ) public view returns (IOracle.Message[] memory) { + IOracle.Message[] memory messages = new IOracle.Message[](1); + messages[0] = message; + return messages; + } + + // @notice Creates a text message with the given role and content + // @param role The role of the message + // @param content The content of the message + // @return The created message + function createTextMessage(string memory role, string memory content) private pure returns (IOracle.Message memory) { + IOracle.Message memory newMessage = IOracle.Message({ + role : role, + content : new IOracle.Content[](1) + }); + newMessage.content[0].contentType = "text"; + newMessage.content[0].value = content; + return newMessage; + } +} diff --git a/examples/package.json b/examples/package.json index 596c992..8bc6179 100644 --- a/examples/package.json +++ b/examples/package.json @@ -4,6 +4,7 @@ "description": "", "scripts": { "chat": "ts-node chat.ts", + "simpleChat": "ts-node simpleLlmChat.ts", "chat_vision": "ts-node chat_vision.ts", "chat_anthropic": "ts-node chat_anthropic.ts", "agent": "ts-node agent.ts" diff --git a/examples/simpleLlmChat.ts b/examples/simpleLlmChat.ts new file mode 100644 index 0000000..ae31639 --- /dev/null +++ b/examples/simpleLlmChat.ts @@ -0,0 +1,67 @@ +import {Contract, ethers, TransactionReceipt, Wallet} from "ethers"; +import ABI from "./abis/dAGILLM.json"; +import * as readline from 'readline'; + +require("dotenv").config() + +async function main() { + const rpcUrl = process.env.RPC_URL + if (!rpcUrl) throw Error("Missing RPC_URL in .env") + const privateKey = process.env.PRIVATE_KEY + if (!privateKey) throw Error("Missing PRIVATE_KEY in .env") + const contractAddress = process.env.SIMPLE_LLM_CONTRACT_ADDRESS + if (!contractAddress) throw Error("Missing SIMPLE_LLM_CONTRACT_ADDRESS in .env") + + const provider = new ethers.JsonRpcProvider(rpcUrl) + const wallet = new Wallet( + privateKey, provider + ) + const contract = new Contract(contractAddress, ABI, wallet) + + // The message you want to start the chat with + const message = await getUserInput() + + // Call the sendMessage function + const transactionResponse = await contract.sendMessage(message) + const receipt = await transactionResponse.wait() + console.log(`Message sent, tx hash: ${receipt.hash}`) + console.log(`Chat started with message: "${message}"`) + + // Read the LLM response on-chain + while (true) { + const response = await contract.response(); + if (response) { + console.log("Response from contract:", response); + break; + } + await new Promise(resolve => setTimeout(resolve, 2000)) + } +} + +async function getUserInput(): Promise { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }) + + const question = (query: string): Promise => { + return new Promise((resolve) => { + rl.question(query, (answer) => { + resolve(answer) + }) + }) + } + + try { + const input = await question("Message ChatGPT: ") + rl.close() + return input + } catch (err) { + console.error('Error getting user input:', err) + rl.close() + } +} + + +main() + .then(() => console.log("Done")) \ No newline at end of file