diff --git a/src/backend/services/ollama.ts b/src/backend/services/ollama.ts index 7592fb1..3ef22ed 100644 --- a/src/backend/services/ollama.ts +++ b/src/backend/services/ollama.ts @@ -152,7 +152,7 @@ export const askOllama = async (model: string, message: string) => { }, { role: 'user', - content: `Now answer the following question in a valid formatted JSON object without comments with both the response and transaction fields deduced from the users question. If the user's question does not initiate a transaction, let the transaction be an empty object. Adhere strictly to JSON syntax without comments. Question: ${message}. Response:`, + content: `Answer the following question in a valid formatted JSON object without comments with both the response and action fields deduced from the user's question. Adhere strictly to JSON syntax without comments. Question: ${message}. Response:`, }, ], }); diff --git a/src/backend/services/prompts.ts b/src/backend/services/prompts.ts index 9784aca..81d55a1 100644 --- a/src/backend/services/prompts.ts +++ b/src/backend/services/prompts.ts @@ -1,96 +1,90 @@ export const MOR_PROMPT = `###System: -You are MORPHEUS, an AI assistant, but you prefer to be called a SmartAgent. You respond to any question users may have and assist them in sending transactions -with metamask by creating a valid transaction object. +You are MORPHEUS, but you prefer to be called a SmartAgent. You are designed to assist users with MetaMask transactions and queries in a consistent JSON format. Your responses should always contain a "response" field for textual feedback +and an "action" field for transaction details. There are multiple action types, as detailed in the "Action Types" section. + +###Response Format: +All responses must follow this JSON structure: +{ + "response": "Textual feedback here.", + "action": { + // Action details or an empty object + } +} +Respond only in valid JSON without any comments. If the user is initiating an action, create a valid transaction JSON object from their question. If the user is not initiating an action, the "action" field should be an empty object. The object should be structured based on the type of action they wish to initiate. Keep the "response" field short, using 3 sentences maximum. + +###Action Types: +1. **Transfer**: For users wanting to transfer ETH. The user's input should provide the target address and ETH amount. + - **Format**: + { + "response": "Textual feedback here.", + "action": { + "type": "Transfer", + "targetAddress": "address", + "ethAmount": "amount" + } + } + +2. **Balance Inquiry**: For users inquiring about their ETH balance. For all Balance inquiries, the "action" field should contain only the "type" key with the value "Balance". The "response" field should be set to empty. + - **Format**: + { + "response": "", + "action": { + "type": "Balance" + } + } + +###Error Handling: +For actions requiring more information (e.g., missing ETH amount for transfers), respond with a request for the necessary details: +{ + "response": "Please provide the amount in ETH and the target address for the transfer.", + "action": {} +} -Keep your response short, using 2 or 3 sentences maximum. - -Respond in a valid JSON without comments to be consumed by an application following this pattern: -{"response", "your response goes here", "transaction", "user transaction object goes here"}. -Only respond with a JSON object without any comments, NEVER provide any text outside of the json. You respond only in a valid JSON. -If the user wants to initate a transaction with their question, create a valid transaction JSON object from the information in their question. If the user is not initating a -transaction with their question let the transaction field be an empty object. Structure the object based off the type of transaction they want to intiate. - -For Transfer transactions create a JSON transaction object without any comments even on missing data following this pattern: -{"type": "Transfer":, "targetAddress": "target address goes here", "ethAmount": "amount of eth to transfer goes here"} - -For Balance transactions create a transaction object following this pattern: -{"type": "Balance"} - -If there are comments in the format, please remove them before returning the JSON object. - -Here are examples on how to create the transaction object from the question: ###Examples: -Example 1: User is initiating a transfer transaction with their question. -Question: "transfer 43 eth to 0x223738a369F0804c091e13740D26D1269294bc1b", -Response: "{ - "response": "Of course! The transaction details are prepared for you. Please double-check the parameters before confirming on Metamask.", - "transaction": { - "type": "transfer", - "targetAddress": "0x223738a369F0804c091e13740D26D1269294bc1b", - "ethAmount": "43" - } -}" -Example 2: User is intiating a balance transaction with their question -Question: "balance?" -Response: "{ - "response": "", - "transaction": { - "type": "Balance" - } -}" - -Example 3: User is intiating a balance transaction with their question -Question: "Hey Morpheus, whats my balance?" -Response: "{ - "response": "", - "transaction": { - "type": "Balance" - } -}" - -Example 3: User is intiating a balance transaction with their question -Question: "how much eth do i have?" -Response: "{ - "response": "", - "transaction": { - "type": "Balance" - } -}" -Example 4: question does not initiate a transaction, let the transaction be an empty object. -Question: "Why is the sky blue" -Response: "{ - "response": "The sky is blue because of a thing called Rayleigh scattering. When sunlight enters the Earth\'s atmosphere, it hits air and other tiny particles. This light is made of many colors. Blue light scatters more because it travels as shorter, smaller waves. So, when we look up, we see more blue light than other colors.", - "transaction": {} -}" - - - -Example 5: question does not initiate a transaction, let the transaction be an empty object.. -Question: "What is stETH" -Response: "{ - "response": "stETH stands for staked Ether. It's a type of cryptocurrency. When people stake their Ether (ETH) in a blockchain network to support it, they get stETH in return. This shows they have ETH locked up, and they can still use stETH in other crypto activities while earning rewards.", - "transaction": {} - } - -Example 6: sufficient information in the question to create a valid transaction object. If the question does not provide enough information for a transaction, let the transaction field be an empty object. -Question: "transfer" -Response: "{ - "response": "I can certainly help you transfer ethereum, However, i needthe eth amount and target address", - "transaction": {} - } - -For Transfer transactions, ensure that there is sufficient information in the question to create a valid transaction object. If the question does not provide enough information for a transaction, do not include a transaction object in the response. +// Transfer Action +- **Transfer actions**: + - Question: "transfer 2 eth to 0x123..." + - Response: + { + "response": "Transfer prepared. Please confirm the details in MetaMask.", + "action": {"type": "Transfer", "targetAddress": "0x123...", "ethAmount": "2"} + } + +// Balance Inquiries +- **Balance inquiry**: + - Questions: "What's my balance?", "Could you tell me my current balance, please?", "how much eth I got?", "Hey Morpheus, can you show me my balance now?", "I need to see my ETH balance, can you help?" + - Response for all: + { + "response": "", + "action": {"type": "Balance"} + } + +// Insufficient Information for Transfer +- **Insufficient info for transfer**: + - Question: "I want to transfer ETH." + - Response: + { + "response": "Please provide the ETH amount and the target address for the transfer.", + "action": {} + } + +// Non-action Queries +- **Non-action query (e.g., general question)**: + - Question: "What is stETH?" + - Response: + { + "response": "stETH stands for staked Ether...", + "action": {} + } `; -export const errorHandling = `If a question is initiating a buy or transfer transaction and the user doesn't specify an amount in ETH. Gently decline to send the transaction -and request the amount to buy or transfer (depending on their transaction type) in ethereum. - -If a question is initiating a sell transaction and the user doesn't specify an amount in tokens. Gently decline to send the transaction -and request the amount to sell in tokens. +export const errorHandling = `###Error Handling: +- For buy or transfer actions without a specified ETH amount, request the missing details. +- For sell actions without a specified token amount, request the missing details. +- Never include comments within the JSON objects returned. +- Plan for detailed error messages for unsupported or incomplete action requests to guide users effectively.`; -In your response, if you do generate a transaction JSON object, never include any comments in the JSON format you return back. -`; //TODO: allow for staking MOR and swap tokens //TODO: use RAG to include a database to tokenAddresses and symbols //TODO: include chat history diff --git a/src/frontend/utils/transaction.ts b/src/frontend/utils/transaction.ts index d0cde52..2eaabb5 100644 --- a/src/frontend/utils/transaction.ts +++ b/src/frontend/utils/transaction.ts @@ -1,23 +1,19 @@ import { ethers } from 'ethers'; import { SDKProvider } from '@metamask/sdk'; -import { Transaction, TransactionParams } from './types'; +import { TransferAction, ActionParams } from './types'; -export const isTransactionInitiated = (transaction: TransactionParams) => { - return !(Object.keys(transaction).length === 0); +export const isActionInitiated = (action: ActionParams) => { + return !(Object.keys(action).length === 0); }; -export const buildTransaction = ( - transaction: TransactionParams, - account: string, - gasPrice: string, -) => { - const transactionType = transaction.type.toLowerCase(); +export const buildAction = (action: ActionParams, account: string, gasPrice: string) => { + const transactionType = action.type.toLowerCase(); - let tx: Transaction; + let tx: TransferAction; switch (transactionType) { case 'transfer': - tx = buildTransferTransaction(transaction, account, gasPrice); + tx = buildTransferTransaction(action, account, gasPrice); break; default: throw Error(`Transaction of type ${transactionType} is not yet supported`); @@ -37,16 +33,16 @@ function extractEthereumAddress(text: string): string | null { } const buildTransferTransaction = ( - transaction: TransactionParams, + action: ActionParams, account: string, gasPrice: any, -): Transaction => { +): TransferAction => { return { from: account, - to: transaction.targetAddress, + to: action.targetAddress, gas: '0x76c0', //for more complex tasks estimate this from metamast gasPrice: gasPrice, - value: '0x' + ethers.parseEther(transaction.ethAmount).toString(16), + value: '0x' + ethers.parseEther(action.ethAmount).toString(16), data: '0x000000', }; }; @@ -91,7 +87,7 @@ const estimateGasWithOverHead = (estimatedGasMaybe: string) => { export const handleTransactionRequest = async ( provider: SDKProvider | undefined, - transaction: TransactionParams, + transaction: ActionParams, account: string, question: string, ) => { @@ -115,7 +111,7 @@ export const handleTransactionRequest = async ( throw new Error('Invalid gasPrice received'); } - const builtTx = buildTransaction(transaction, account, gasPrice); + const builtTx = buildAction(transaction, account, gasPrice); const estimatedGas = await provider?.request({ method: 'eth_estimateGas', diff --git a/src/frontend/utils/types.ts b/src/frontend/utils/types.ts index bfe55ab..715635d 100644 --- a/src/frontend/utils/types.ts +++ b/src/frontend/utils/types.ts @@ -1,13 +1,13 @@ export type ModelResponse = { response: string; - transaction: TransactionParams; + action: ActionParams; }; -export type TransactionParams = { +export type ActionParams = { [key: string]: string; }; -export type Transaction = { +export type TransferAction = { from: string; to: string; gas: string; diff --git a/src/frontend/utils/utils.ts b/src/frontend/utils/utils.ts index 7d71be8..7bb130d 100644 --- a/src/frontend/utils/utils.ts +++ b/src/frontend/utils/utils.ts @@ -16,17 +16,17 @@ export const parseResponse = (jsonString: string) => { parsed = JSON.parse(jsonString); } catch (error) { new Error('Ollama error'); - return { response: 'error', transaction: {} }; + return { response: 'error', action: {} }; } } if (isModelResponse(parsed)) { - return { response: parsed.response, transaction: parsed.transaction }; + return { response: parsed.response, action: parsed.action }; } else { throw new Error('Invalid ModelResponse format'); } }; const isModelResponse = (object: any): object is ModelResponse => { - return 'response' in object && 'transaction' in object; + return 'response' in object && 'action' in object; }; diff --git a/src/frontend/views/chat.tsx b/src/frontend/views/chat.tsx index 980018b..bb6b2a8 100644 --- a/src/frontend/views/chat.tsx +++ b/src/frontend/views/chat.tsx @@ -10,12 +10,12 @@ import { OllamaChannel } from './../../events'; import { useAIMessagesContext } from '../contexts'; import { - isTransactionInitiated, + isActionInitiated, handleBalanceRequest, handleTransactionRequest, } from '../utils/transaction'; import { parseResponse } from '../utils/utils'; -import { TransactionParams } from '../utils/types'; +import { ActionParams } from '../utils/types'; const ChatView = (): JSX.Element => { const [selectedModel, setSelectedModel] = useState('llama2'); @@ -51,7 +51,7 @@ const ChatView = (): JSX.Element => { ]); }; - const checkGasCost = (balance: string, transaction: TransactionParams): boolean => { + const checkGasCost = (balance: string, transaction: ActionParams): boolean => { // calculate the max gas cost in Wei (gasPrice * gas) // User's balance in ETH as a float string const balanceInEth = parseFloat(balance); @@ -64,12 +64,12 @@ const ChatView = (): JSX.Element => { const processResponse = async ( question: string, response: string, - transaction: TransactionParams | undefined, + action: ActionParams | undefined, ) => { - if (transaction == undefined) { - transaction = {}; + if (action == undefined) { + action = {}; } - if (!isTransactionInitiated(transaction)) { + if (!isActionInitiated(action)) { updateDialogueEntries(question, response); //no additional logic in this case return; @@ -83,7 +83,7 @@ const ChatView = (): JSX.Element => { return; } - switch (transaction.type.toLowerCase()) { + switch (action.type.toLowerCase()) { case 'balance': let message: string; try { @@ -96,7 +96,7 @@ const ChatView = (): JSX.Element => { case 'transfer': try { - const builtTx = await handleTransactionRequest(provider, transaction, account, question); + const builtTx = await handleTransactionRequest(provider, action, account, question); console.log('from: ' + builtTx.params[0].from); //if gas is more than 5% of balance - check with user const balance = await handleBalanceRequest(provider, account); @@ -119,7 +119,7 @@ const ChatView = (): JSX.Element => { default: // If the transaction type is not recognized, we will not proceed with the transaction. - const errorMessage = `Error: Invalid transaction type: ${transaction.type}`; + const errorMessage = `Error: Invalid transaction type: ${action.type}`; updateDialogueEntries(question, errorMessage); } }; @@ -146,11 +146,11 @@ const ChatView = (): JSX.Element => { console.log(inference); if (inference) { - const { response, transaction } = parseResponse(inference.message.content); + const { response, action: action } = parseResponse(inference.message.content); if (response == 'error') { updateDialogueEntries(question, 'Sorry, I had a problem with your request.'); } else { - await processResponse(question, response, transaction); + await processResponse(question, response, action); } }