Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

switched from brian AI to Openai #35

Merged
merged 7 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
325 changes: 151 additions & 174 deletions client/app/api/transactions/route.ts
Original file line number Diff line number Diff line change
@@ -1,218 +1,195 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */

import { NextRequest, NextResponse } from 'next/server';
import { transactionProcessor } from '@/lib/transaction';
import { LayerswapClient } from '@/lib/layerswap/client';
import type { BrianResponse, BrianTransactionData } from '@/lib/transaction/types';

const BRIAN_API_URL = 'https://api.brianknows.org/api/v0/agent';
const layerswapClient = new LayerswapClient(process.env.LAYERSWAP_API_KEY || '');

async function convertBrianResponseFormat(apiResponse: any): Promise<BrianResponse> {
const response = apiResponse.result[0];

// Construct base response
const brianResponse: BrianResponse = {
solver: response.solver,
action: response.action,
type: response.type,
extractedParams: response.extractedParams,
data: {} as BrianTransactionData
};

// Convert data based on action type
switch (response.action) {
case 'swap':
case 'transfer':
brianResponse.data = {
description: response.data?.description || '',
steps: response.data?.steps?.map((step: any) => ({
contractAddress: step.contractAddress,
entrypoint: step.entrypoint,
calldata: step.calldata
})) || [],
fromToken: response.data?.fromToken,
toToken: response.data?.toToken,
fromAmount: response.data?.fromAmount,
toAmount: response.data?.toAmount,
receiver: response.data?.receiver,
amountToApprove: response.data?.amountToApprove,
gasCostUSD: response.data?.gasCostUSD
};
break;

case 'bridge':
brianResponse.data = {
description: '',
steps: [],
bridge: {
sourceNetwork: response.extractedParams.chain,
destinationNetwork: response.extractedParams.dest_chain,
sourceToken: response.extractedParams.token1,
destinationToken: response.extractedParams.token2,
amount: parseFloat(response.extractedParams.amount),
sourceAddress: response.extractedParams.address || '',
destinationAddress: response.extractedParams.address || ''
}
};
break;

case 'deposit':
case 'withdraw':
brianResponse.data = {
description: '',
steps: [],
protocol: response.extractedParams.protocol,
fromAmount: response.extractedParams.amount,
toAmount: response.extractedParams.amount,
receiver: response.extractedParams.address || ''
};
break;

default:
throw new Error(`Unsupported action type: ${response.action}`);
}

return brianResponse;
}

async function getBrianTransactionData(prompt: string, address: string, chainId: string, messages: any[]): Promise<BrianResponse> {
import { NextResponse, NextRequest } from "next/server";
import { transactionProcessor } from "@/lib/transaction";
import type { BrianResponse } from "@/lib/transaction/types";
import {
TRANSACTION_INTENT_PROMPT,
transactionIntentPromptTemplate,
} from "@/prompts/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { ChatOpenAI } from "@langchain/openai";

const llm = new ChatOpenAI({
model: "gpt-4o-mini",
apiKey: process.env.OPENAI_API_KEY,
});

async function getTransactionIntentFromOpenAI(
prompt: string,
address: string,
chainId: string,
messages: any[]
): Promise<BrianResponse> {
try {
const response = await fetch(`${BRIAN_API_URL}/transaction`, {
method: 'POST',
headers: {
'X-Brian-Api-Key': process.env.BRIAN_API_KEY || '',
'Content-Type': 'application/json',
},
body: JSON.stringify({
prompt,
address,
chainId: chainId.toString(),
messages
}),
const conversationHistory = messages
.map((msg) => `${msg.role}: ${msg.content}`)
.join("\n");

const formattedPrompt = await transactionIntentPromptTemplate.format({
TRANSACTION_INTENT_PROMPT,
prompt,
chainId,
conversationHistory,
});

const data = await response.json();
console.log('Brian API Response:', JSON.stringify(data, null, 2));

// Special handling for intent recognition errors that still have extracted params
if (!response.ok && data.error && data.extractedParams) {
// If we have extracted params, we can still proceed
if (data.extractedParams[0]) {
// Convert to expected format
return {
solver: "Brian-Starknet",
action: data.extractedParams[0].action as 'bridge',
type: "write",
extractedParams: data.extractedParams[0],
data: {
description: '',
steps: [],
bridge: {
sourceNetwork: data.extractedParams[0].chain,
destinationNetwork: data.extractedParams[0].dest_chain,
sourceToken: data.extractedParams[0].token1,
destinationToken: data.extractedParams[0].token2,
amount: parseFloat(data.extractedParams[0].amount),
sourceAddress: address,
destinationAddress: data.extractedParams[0].address
}
}
};
}
}

if (!response.ok) {
throw new Error(data.error || `API request failed with status ${response.status}`);
}
const jsonOutputParser = new StringOutputParser();
const response = await llm.pipe(jsonOutputParser).invoke(formattedPrompt);
const intentData = JSON.parse(response);

if (!data.result?.[0]) {
throw new Error('Invalid response format from Brian API');
if (!intentData.isTransactionIntent) {
throw new Error("Not a transaction-related prompt");
}

const brianResponse = data.result[0] as BrianResponse;

// Add connected wallet address to params
if (brianResponse.extractedParams) {
brianResponse.extractedParams.connectedAddress = address;
}
const intentresponse: BrianResponse = {
solver: intentData.solver || "OpenAI-Intent-Recognizer",
action: intentData.action,
type: "write",
extractedParams: {
action: intentData.extractedParams.action,
token1: intentData.extractedParams.token1 || "",
token2: intentData.extractedParams.token2 || "",
chain: intentData.extractedParams.chain || "",
amount: intentData.extractedParams.amount || "",
protocol: intentData.extractedParams.protocol || "",
address: intentData.extractedParams.address || address,
dest_chain: intentData.extractedParams.dest_chain || "",
destinationChain: intentData.extractedParams.dest_chain || "",
destinationAddress:
intentData.extractedParams.destinationAddress || address,
},
data: {
description: "",
steps: [],
...(["swap", "transfer"].includes(intentData.action)
? {
fromToken: {
symbol: intentData.extractedParams.token1 || "",
address: intentData.extractedParams.address || "",
decimals: 1, // default, adjust if needed
},
toToken: {
symbol: intentData.extractedParams.token2 || "",
address: intentData.extractedParams.address || "",
decimals: 1, // default, adjust if needed
},
fromAmount: intentData.extractedParams.amount,
toAmount: intentData.extractedParams.amount,
receiver: intentData.extractedParams.address,
}
: {}),
...(intentData.action === "bridge"
? {
bridge: {
sourceNetwork: intentData.extractedParams.chain || "",
destinationNetwork: intentData.extractedParams.dest_chain || "",
sourceToken: intentData.extractedParams.token1 || "",
destinationToken: intentData.extractedParams.token2 || "",
amount: parseFloat(intentData.extractedParams.amount || "0"),
sourceAddress: address,
destinationAddress:
intentData.extractedParams.destinationAddress || address,
},
}
: {}),
...(["deposit", "withdraw"].includes(intentData.action)
? {
protocol: intentData.extractedParams.protocol,
fromAmount: intentData.extractedParams.amount,
toAmount: intentData.extractedParams.amount,
receiver: intentData.extractedParams.address,
}
: {}),
},
};

return brianResponse;
return intentresponse;
} catch (error) {
console.error('Error fetching transaction data:', error);
console.error("Error fetching transaction intent:", error);
throw error;
}
}


export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { prompt, address, messages = [], chainId = '4012' } = body;
const { prompt, address, messages = [], chainId = "4012" } = body;

if (!prompt || !address) {
return NextResponse.json(
{ error: 'Missing required parameters (prompt or address)' },
{ error: "Missing required parameters (prompt or address)" },
{ status: 400 }
);
}

try {
const brianResponse = await getBrianTransactionData(prompt, address, chainId, messages);
console.log('Processed Brian Response:', JSON.stringify(brianResponse, null, 2));

// Always pass connected address to handlers
if (brianResponse.extractedParams) {
brianResponse.extractedParams.connectedAddress = address;
}

const processedTx = await transactionProcessor.processTransaction(brianResponse);
console.log('Processed Transaction:', JSON.stringify(processedTx, null, 2));
const transactionIntent = await getTransactionIntentFromOpenAI(
prompt,
address,
chainId,
messages
);
console.log(
"Processed Transaction Intent from OPENAI:",
JSON.stringify(transactionIntent, null, 2)
);

// For deposit and withdraw, always use connected address as receiver
if (['deposit', 'withdraw'].includes(brianResponse.action)) {
const processedTx = await transactionProcessor.processTransaction(
transactionIntent
);
console.log(
"Processed Transaction:",
JSON.stringify(processedTx, null, 2)
);

if (["deposit", "withdraw"].includes(transactionIntent.action)) {
processedTx.receiver = address;
}

return NextResponse.json({
result: [{
data: {
description: processedTx.description,
transaction: {
type: processedTx.action,
data: {
transactions: processedTx.transactions,
fromToken: processedTx.fromToken,
toToken: processedTx.toToken,
fromAmount: processedTx.fromAmount,
toAmount: processedTx.toAmount,
receiver: processedTx.receiver,
gasCostUSD: processedTx.estimatedGas,
solver: processedTx.solver,
protocol: processedTx.protocol,
bridge: processedTx.bridge
}
}
result: [
{
data: {
description: processedTx.description,
transaction: {
type: processedTx.action,
data: {
transactions: processedTx.transactions,
fromToken: processedTx.fromToken,
toToken: processedTx.toToken,
fromAmount: processedTx.fromAmount,
toAmount: processedTx.toAmount,
receiver: processedTx.receiver,
gasCostUSD: processedTx.estimatedGas,
solver: processedTx.solver,
protocol: processedTx.protocol,
bridge: processedTx.bridge,
},
},
},
conversationHistory: messages,
},
conversationHistory: messages
}]
],
});
} catch (error) {
console.error('Transaction processing error:', error);
console.error("Transaction processing error:", error);
return NextResponse.json(
{
error: error instanceof Error ? error.message : 'Transaction processing failed',
details: error instanceof Error ? error.stack : undefined
{
error:
error instanceof Error
? error.message
: "Transaction processing failed",
details: error instanceof Error ? error.stack : undefined,
},
{ status: 400 }
);
}
} catch (error) {
console.error('Request processing error:', error);
console.error("Request processing error:", error);
return NextResponse.json(
{ error: 'Internal server error' },
{ error: "Internal server error" },
{ status: 500 }
);
}
}
}
Loading