Skip to content

Commit

Permalink
Agent using new OpenAI interface
Browse files Browse the repository at this point in the history
  • Loading branch information
kgrofelnik committed Apr 11, 2024
1 parent 4084725 commit cfb9eee
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 154 deletions.
174 changes: 86 additions & 88 deletions contracts/contracts/Agent.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,42 @@ pragma solidity ^0.8.9;
// import "hardhat/console.sol";

interface IOracle {
function createLlmCall(
uint promptId
struct OpenAiRequest {
string model;
int8 frequencyPenalty;
string logitBias;
uint32 maxTokens;
int8 presencePenalty;
string responseFormat;
uint seed;
string stop;
uint temperature;
uint topP;
string tools;
string toolChoice;
string user;
}

struct OpenAiResponse {
string id;

string content;
string functionName;
string functionArguments;

uint64 created;
string model;
string systemFingerprint;
string object;

uint32 completionTokens;
uint32 promptTokens;
uint32 totalTokens;
}

function createOpenAiLlmCall(
uint promptId,
OpenAiRequest memory request
) external returns (uint);

function createFunctionCall(
Expand Down Expand Up @@ -43,13 +77,31 @@ contract Agent {

event OracleAddressUpdated(address indexed newOracleAddress);

IOracle.OpenAiRequest private config;

constructor(
address initialOracleAddress,
string memory systemPrompt
) {
owner = msg.sender;
oracleAddress = initialOracleAddress;
prompt = systemPrompt;

config = IOracle.OpenAiRequest({
model : "gpt-4-turbo-preview",
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 : "[{\"type\":\"function\",\"function\":{\"name\":\"web_search\",\"description\":\"Search the internet\",\"parameters\":{\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\",\"description\":\"Search query\"}},\"required\":[\"query\"]}}},{\"type\":\"function\",\"function\":{\"name\":\"code_interpreter\",\"description\":\"Evaluates python code in a sandbox environment. The environment resets on every execution. You must send the whole script every time and print your outputs. Script should be pure python code that can be evaluated. It should be in python format NOT markdown. The code should NOT be wrapped in backticks. All python packages including requests, matplotlib, scipy, numpy, pandas, etc are available. Output can only be read from stdout, and stdin. Do not use things like plot.show() as it will not work. print() any output and results so you can capture the output.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"description\":\"The pure python script to be evaluated. The contents will be in main.py. It should not be in markdown format.\"}},\"required\":[\"code\"]}}}]",
toolChoice : "auto", // "none" or "auto"
user : "" // null
});
}

modifier onlyOwner() {
Expand Down Expand Up @@ -89,40 +141,44 @@ contract Agent {
uint currentId = agentRunCount;
agentRunCount = agentRunCount + 1;

IOracle(oracleAddress).createLlmCall(currentId);
IOracle(oracleAddress).createOpenAiLlmCall(currentId, config);
emit AgentRunCreated(run.owner, currentId);

return currentId;
}

function onOracleLlmResponse(
function onOracleOpenAiLlmResponse(
uint runId,
string memory response,
IOracle.OpenAiResponse memory response,
string memory errorMessage
) public onlyOracle {
AgentRun storage run = agentRuns[runId];

Message memory assistantMessage;
assistantMessage.content = response;
assistantMessage.role = "assistant";
run.messages.push(assistantMessage);
run.responsesCount++;

if (!compareStrings(errorMessage, "")) {
Message memory newMessage;
newMessage.role = "assistant";
newMessage.content = errorMessage;
run.messages.push(newMessage);
run.responsesCount++;
run.is_finished = true;
return;
}
if (run.responsesCount >= run.max_iterations) {
run.is_finished = true;
} else {
(string memory action, string memory actionInput) = findActionAndInput(response);
if (compareStrings(action, "web_search")) {
IOracle(oracleAddress).createFunctionCall(
runId,
"web_search",
actionInput
);
}
else if (containsFinalAnswer(response)) {
run.is_finished = true;
}
return;
}
if (!compareStrings(response.content, "")) {
Message memory assistantMessage;
assistantMessage.content = response.content;
assistantMessage.role = "assistant";
run.messages.push(assistantMessage);
run.responsesCount++;
}
if (!compareStrings(response.functionName, "")) {
IOracle(oracleAddress).createFunctionCall(runId, response.functionName, response.functionArguments);
return;
}
run.is_finished = true;
}

function onOracleFunctionResponse(
Expand All @@ -134,11 +190,16 @@ contract Agent {
require(
!run.is_finished, "Run is finished"
);
string memory result = response;
if (!compareStrings(errorMessage, "")) {
result = errorMessage;
}
Message memory newMessage;
newMessage.content = makeObservation(response);
newMessage.role = "user";
newMessage.content = result;
run.messages.push(newMessage);
IOracle(oracleAddress).createLlmCall(runId);
run.responsesCount++;
IOracle(oracleAddress).createOpenAiLlmCall(runId, config);
}

function getMessageHistoryContents(uint agentId) public view returns (string[] memory) {
Expand All @@ -161,70 +222,7 @@ contract Agent {
return agentRuns[runId].is_finished;
}

function findActionAndInput(string memory input) public pure returns (string memory action, string memory actionInput) {
bytes memory inputBytes = bytes(input);
uint inputLength = inputBytes.length;
uint i = 0;

// Temporary storage for byte segments
bytes memory tempBytes;

while (i < inputLength) {
// Reset tempBytes for each iteration
tempBytes = "";

// Look for "Action: " pattern
if (i + 7 < inputLength && inputBytes[i] == 'A' && inputBytes[i + 1] == 'c' && inputBytes[i + 2] == 't' && inputBytes[i + 3] == 'i' && inputBytes[i + 4] == 'o' && inputBytes[i + 5] == 'n' && inputBytes[i + 6] == ':' && inputBytes[i + 7] == ' ') {
i += 8; // Move past the "Action: " part
while (i < inputLength && inputBytes[i] != '\n') {
tempBytes = abi.encodePacked(tempBytes, inputBytes[i]);
i++;
}
action = string(tempBytes);
}
// Look for "Action Input: " pattern
else if (i + 13 < inputLength && inputBytes[i] == 'A' && inputBytes[i + 1] == 'c' && inputBytes[i + 2] == 't' && inputBytes[i + 3] == 'i' && inputBytes[i + 4] == 'o' && inputBytes[i + 5] == 'n' && inputBytes[i + 6] == ' ' && inputBytes[i + 7] == 'I' && inputBytes[i + 8] == 'n' && inputBytes[i + 9] == 'p' && inputBytes[i + 10] == 'u' && inputBytes[i + 11] == 't' && inputBytes[i + 12] == ':' && inputBytes[i + 13] == ' ') {
i += 14; // Move past the "Action Input: " part
while (i < inputLength && inputBytes[i] != '\n') {
tempBytes = abi.encodePacked(tempBytes, inputBytes[i]);
i++;
}
actionInput = string(tempBytes);
} else {
i++; // Move to the next character if no pattern is matched
}
}

return (action, actionInput);
}

function containsFinalAnswer(string memory input) public pure returns (bool) {
bytes memory inputBytes = bytes(input);
bytes memory target = bytes("Final Answer:");

if (inputBytes.length < target.length) {
return false;
}
for (uint i = 0; i <= inputBytes.length - target.length; i++) {
bool found = true;
for (uint j = 0; j < target.length; j++) {
if (inputBytes[i + j] != target[j]) {
found = false;
break;
}
}
if (found) {
return true;
}
}
return false;
}

function compareStrings(string memory a, string memory b) private pure returns (bool) {
return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b))));
}

function makeObservation(string memory response) public pure returns (string memory) {
return string(abi.encodePacked("Observation: ", response));
}
}
2 changes: 1 addition & 1 deletion contracts/scripts/deployAll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {ethers} from "hardhat";

const DALLE_PROMPT = "make an image of: \"solarpunk oil painting "
const VITAILIK_PROMPT = "You are a narrator for a text based game set in a futuristic world where the player is fighting with \"VIILIK\", a crypto dark lord hacker that looks like a hybrid of a man and a dog with 2 heads and is tattooed full of crypto logos. He holds bunch of weird weapons and uses unique fighting styles to defeat the player. \n\nThe game is played in turns where you present the player with four options (A, B, C, D) at each turn to choose their next action, the player can only pick one of the options, not add anything themselves. Generate the options short and punchy, not too verbose. Both the player and \"VIILIK\" start with 10,000 HP and you increase or decrease their HP after each turn. \n\nTo begin with generate an image to show battleground and VIILIK where you ask from the player what character to play as (come up with animals to select from, add some adjective to make it funny). Please generate according images of player's character.\n\nRemember to generate an image on every turn of how the battle plays out where you add a really short description that describes the scenario at hand. Keep a funny tone. Create funny images and immersive, try to keep a storyline. Put image description in [IMAGE] tags. Make image description as prompt for DALL-E 3. Avoid revealing future outcomes or suggesting a 'best' choice; each should seem viable to maintain the game's suspense. The game starts when user says start. Remember to keep track of VIILIK's and player's HP, IT IS ALSO possible that player's choice hurts his own HP, you decide that as a narrator based on player's choice. Minimum HP hit is 1000 and max 5000.\nShow HP on every turn like this:\nyour HP: {number}\nVIILIK HP: {number}"
const AGENT_PROMPT = "Answer the following questions as best you can. You have access to the following tools:\n\nweb_search: Use this to lookup information from google search engine.\n\nUse the following format:\n\nQuestion: the input question you must answer\n\nThought: you should always think about what to do\n\nAction: the action to take, should be web_search. \Action Input: the input to the action\nObservation: the result of the action\n... (this Thought/Action/Action Input/Observation can repeat N times)\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n\nBegin!\n"
const AGENT_PROMPT = "You are a helpful assistant";

async function main() {
const oracleAddress: string = await deployOracle();
Expand Down
125 changes: 60 additions & 65 deletions examples/abis/Agent.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,49 +81,6 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "input",
"type": "string"
}
],
"name": "containsFinalAnswer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "input",
"type": "string"
}
],
"name": "findActionAndInput",
"outputs": [
{
"internalType": "string",
"name": "action",
"type": "string"
},
{
"internalType": "string",
"name": "actionInput",
"type": "string"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
Expand Down Expand Up @@ -181,25 +138,6 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "response",
"type": "string"
}
],
"name": "makeObservation",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
Expand Down Expand Up @@ -231,17 +169,74 @@
"type": "uint256"
},
{
"internalType": "string",
"components": [
{
"internalType": "string",
"name": "id",
"type": "string"
},
{
"internalType": "string",
"name": "content",
"type": "string"
},
{
"internalType": "string",
"name": "functionName",
"type": "string"
},
{
"internalType": "string",
"name": "functionArguments",
"type": "string"
},
{
"internalType": "uint64",
"name": "created",
"type": "uint64"
},
{
"internalType": "string",
"name": "model",
"type": "string"
},
{
"internalType": "string",
"name": "systemFingerprint",
"type": "string"
},
{
"internalType": "string",
"name": "object",
"type": "string"
},
{
"internalType": "uint32",
"name": "completionTokens",
"type": "uint32"
},
{
"internalType": "uint32",
"name": "promptTokens",
"type": "uint32"
},
{
"internalType": "uint32",
"name": "totalTokens",
"type": "uint32"
}
],
"internalType": "struct IOracle.OpenAiResponse",
"name": "response",
"type": "string"
"type": "tuple"
},
{
"internalType": "string",
"name": "errorMessage",
"type": "string"
}
],
"name": "onOracleLlmResponse",
"name": "onOracleOpenAiLlmResponse",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
Expand Down

0 comments on commit cfb9eee

Please sign in to comment.