From f7cf15bc15187e60852b1236233c19168d72732e Mon Sep 17 00:00:00 2001 From: siddharth Date: Wed, 22 Jan 2025 21:37:29 +0530 Subject: [PATCH] refactor: tools in seprate files --- apps/lana-chat/app/api/chat/route.ts | 147 ++-------------- .../api/chat/tools/get-credit-facilities.ts | 159 +++++++++++++++++ .../chat/tools/get-credit-facility-details.ts | 164 +++++++++++++++++ .../tools/get-customer-credit-facility.ts | 165 ++++++++++++++++++ .../api/chat/tools/get-customer-details.ts | 89 ++++++++++ 5 files changed, 587 insertions(+), 137 deletions(-) create mode 100644 apps/lana-chat/app/api/chat/tools/get-credit-facilities.ts create mode 100644 apps/lana-chat/app/api/chat/tools/get-credit-facility-details.ts create mode 100644 apps/lana-chat/app/api/chat/tools/get-customer-credit-facility.ts create mode 100644 apps/lana-chat/app/api/chat/tools/get-customer-details.ts diff --git a/apps/lana-chat/app/api/chat/route.ts b/apps/lana-chat/app/api/chat/route.ts index 2c419cae2f..f68c472c1c 100644 --- a/apps/lana-chat/app/api/chat/route.ts +++ b/apps/lana-chat/app/api/chat/route.ts @@ -1,76 +1,18 @@ import { openai } from "@ai-sdk/openai"; -import { streamText, tool } from "ai"; -import { z } from "zod"; -import { - getCustomerCreditFacility, - getCustomerDetails, -} from "./get-customer-details"; - -import { - CreditFacilitiesFilterBy, - CreditFacilitiesSortBy, - CollateralizationState, - CreditFacilityStatus, - SortDirection, -} from "@/lib/graphql/generated"; -import { - getCreditFacilities, - getCreditFacilityById, -} from "./get-facility-details"; +import { streamText } from "ai"; +import { getCustomerDetailsTool } from "./tools/get-customer-details"; +import { getCustomerCreditFacilitiesTool } from "./tools/get-customer-credit-facility"; +import { getCreditFacilitiesTool } from "./tools/get-credit-facilities"; +import { getCreditFacilityDetailsTool } from "./tools/get-credit-facility-details"; const systemPrompt = ` You are an assistant exclusively designed to help users explore and understand data from a banking and Bitcoin-focused application. You must: 1. Focus solely on the banking and functions/tools response and there data, including accounts, credit facilities, transactions, approvals, and Bitcoin. 2. Use tables to present lists and structured data clearly, when applicable. - 4. Highlight Bitcoin amounts in satoshis and USD when relevant. - 5. Support users in queries, pagination, filtering, and sorting based on the schema. + 3. Support users in queries, pagination, filtering, and sorting based on the schema. You must not answer questions or provide assistance unrelated to this schema or the app.`; -const CollateralizationStateSchema = z - .nativeEnum(CollateralizationState) - .describe(`Collateralization states`); - -const CreditFacilityStatusSchema = z - .nativeEnum(CreditFacilityStatus) - .describe(`Facility statuses`); - -const CreditFacilitiesFilterBySchema = z - .nativeEnum(CreditFacilitiesFilterBy) - .describe(`Filter by fields`); - -const CreditFacilitiesSortBySchema = z - .nativeEnum(CreditFacilitiesSortBy) - .describe(`Sort by fields`); - -const SortDirectionSchema = z - .nativeEnum(SortDirection) - .describe(`Sort directions`); - -const CreditFacilitiesFilterSchema = z - .object({ - collateralizationState: CollateralizationStateSchema.optional().describe( - "Filter facilities by collateralization state" - ), - field: CreditFacilitiesFilterBySchema.describe( - "Required field to filter facilities by" - ), - status: CreditFacilityStatusSchema.optional().describe( - "Filter facilities by status" - ), - }) - .nullish() - .describe("Optional filters for credit facilities query"); - -const CreditFacilitiesSortSchema = z.object({ - by: CreditFacilitiesSortBySchema.optional().describe( - "Field to sort facilities by" - ), - direction: SortDirectionSchema.optional().describe( - "Sort direction (ascending/descending)" - ), -}); - export async function POST(req: Request) { const { messages } = await req.json(); @@ -80,79 +22,10 @@ export async function POST(req: Request) { messages, maxSteps: 5, tools: { - getCustomerDetails: tool({ - type: "function", - description: - "Retrieve general information about a customer by their email address. This includes account balances, deposit history, withdrawal history, KYC document statuses, and basic customer information. **Note**: This does not include any details about the customer's credit facility.", - parameters: z.object({ - email: z - .string() - .describe( - "The email address of the customer whose general details (excluding credit facility) are being requested." - ), - }), - execute: async ({ email }) => { - return getCustomerDetails({ email }); - }, - }), - getCustomerCreditFacilities: tool({ - type: "function", - description: - "Retrieve details about the customer's credit facility, including the facility amount, transactions, balance information, collateral, and associated terms. Use this tool to get credit-specific information about the customer.", - parameters: z.object({ - email: z - .string() - .describe( - "The email address of the customer whose credit facility details are being requested." - ), - }), - execute: async ({ email }) => { - return getCustomerCreditFacility({ email }); - }, - }), - getCreditFacilities: tool({ - type: "function", - description: - "Retrieve list of credit facilities with filtering and sorting. When using filter with status or collateralizationState, you must also specify field as 'STATUS' or 'COLLATERALIZATION_STATE' respectively. Limited to 5 facilities per request.", - parameters: z.object({ - first: z - .number() - .min(1) - .max(5) - .describe("Number of facilities to fetch (5 max: cannot exceed 5)"), - after: z - .string() - .optional() - .describe("Pagination cursor. Null/undefined for first page"), - sort: CreditFacilitiesSortSchema.optional(), - filter: CreditFacilitiesFilterSchema.optional(), - }), - execute: async ({ first, after, sort, filter }) => { - console.log({ first, after, sort, filter }); - return getCreditFacilities({ - first, - after, - sort, - filter, - }); - }, - }), - getCreditFacilityDetails: tool({ - type: "function", - description: `Retrieve comprehensive details for a single credit facility. USE ONLY: when complete facility details are asked.`, - parameters: z.object({ - id: z - .string() - .uuid() - .describe( - "UUID of the credit facility to fetch detailed information for" - ), - }), - execute: async ({ id }) => { - console.log(id); - return getCreditFacilityById({ id }); - }, - }), + getCustomerDetails: getCustomerDetailsTool, + getCustomerCreditFacilities: getCustomerCreditFacilitiesTool, + getCreditFacilities: getCreditFacilitiesTool, + getCreditFacilityDetails: getCreditFacilityDetailsTool, }, }); diff --git a/apps/lana-chat/app/api/chat/tools/get-credit-facilities.ts b/apps/lana-chat/app/api/chat/tools/get-credit-facilities.ts new file mode 100644 index 0000000000..0cdbe617db --- /dev/null +++ b/apps/lana-chat/app/api/chat/tools/get-credit-facilities.ts @@ -0,0 +1,159 @@ +import { z } from "zod"; +import { tool } from "ai"; +import { gql } from "@apollo/client"; +import { getClient } from "../client"; +import { + CreditFacilitiesDocument, + CreditFacilitiesQuery, + CreditFacilitiesQueryVariables, + CreditFacilitiesFilterBy, + CreditFacilitiesSortBy, + CollateralizationState, + CreditFacilityStatus, + SortDirection, +} from "@/lib/graphql/generated"; + +const CollateralizationStateSchema = z + .nativeEnum(CollateralizationState) + .describe(`Collateralization states`); + +const CreditFacilityStatusSchema = z + .nativeEnum(CreditFacilityStatus) + .describe(`Facility statuses`); + +const CreditFacilitiesFilterBySchema = z + .nativeEnum(CreditFacilitiesFilterBy) + .describe(`Filter by fields`); + +const CreditFacilitiesSortBySchema = z + .nativeEnum(CreditFacilitiesSortBy) + .describe(`Sort by fields`); + +const SortDirectionSchema = z + .nativeEnum(SortDirection) + .describe(`Sort directions`); + +const CreditFacilitiesFilterSchema = z + .object({ + collateralizationState: CollateralizationStateSchema.optional().describe( + "Filter facilities by collateralization state" + ), + field: CreditFacilitiesFilterBySchema.describe( + "Required field to filter facilities by" + ), + status: CreditFacilityStatusSchema.optional().describe( + "Filter facilities by status" + ), + }) + .nullish() + .describe("Optional filters for credit facilities query"); + +const CreditFacilitiesSortSchema = z.object({ + by: CreditFacilitiesSortBySchema.optional().describe( + "Field to sort facilities by" + ), + direction: SortDirectionSchema.optional().describe( + "Sort direction (ascending/descending)" + ), +}); + +gql` + query CreditFacilities( + $first: Int! + $after: String + $sort: CreditFacilitiesSort + $filter: CreditFacilitiesFilter + ) { + creditFacilities( + first: $first + after: $after + sort: $sort + filter: $filter + ) { + edges { + cursor + node { + id + creditFacilityId + collateralizationState + createdAt + status + facilityAmount + collateral + currentCvl { + disbursed + total + } + balance { + collateral { + btcBalance + } + outstanding { + usdBalance + } + } + customer { + customerId + email + } + } + } + pageInfo { + endCursor + hasNextPage + } + } + } +`; + +export const getCreditFacilities = async ( + variables: CreditFacilitiesQueryVariables +) => { + try { + const response = await getClient().query< + CreditFacilitiesQuery, + CreditFacilitiesQueryVariables + >({ + query: CreditFacilitiesDocument, + variables, + }); + + if (!response.data) { + return { error: "Data not found" }; + } + + return response; + } catch (error) { + if (error instanceof Error) { + return { error: error.message }; + } + return { error: "An unknown error occurred" }; + } +}; + +export const getCreditFacilitiesTool = tool({ + type: "function", + description: + "Retrieve list of credit facilities with filtering and sorting. When using filter with status or collateralizationState, you must also specify field as 'STATUS' or 'COLLATERALIZATION_STATE' respectively. Limited to 5 facilities per request.", + parameters: z.object({ + first: z + .number() + .min(1) + .max(5) + .describe("Number of facilities to fetch (5 max: cannot exceed 5)"), + after: z + .string() + .optional() + .describe("Pagination cursor. Null/undefined for first page"), + sort: CreditFacilitiesSortSchema.optional(), + filter: CreditFacilitiesFilterSchema.optional(), + }), + execute: async ({ first, after, sort, filter }) => { + return getCreditFacilities({ + first, + after, + sort, + filter, + }); + }, +}); diff --git a/apps/lana-chat/app/api/chat/tools/get-credit-facility-details.ts b/apps/lana-chat/app/api/chat/tools/get-credit-facility-details.ts new file mode 100644 index 0000000000..e0e3671c7d --- /dev/null +++ b/apps/lana-chat/app/api/chat/tools/get-credit-facility-details.ts @@ -0,0 +1,164 @@ +import { z } from "zod"; +import { tool } from "ai"; +import { gql } from "@apollo/client"; +import { getClient } from "../client"; +import { + GetCreditFacilityDetailsDocument, + GetCreditFacilityDetailsQuery, + GetCreditFacilityDetailsQueryVariables, +} from "@/lib/graphql/generated"; + +gql` + query GetCreditFacilityDetails($id: UUID!) { + creditFacility(id: $id) { + creditFacilityId + activatedAt + expiresAt + createdAt + collateralizationState + facilityAmount + collateral + creditFacilityTerms { + annualRate + accrualInterval + incurrenceInterval + oneTimeFeeRate + liquidationCvl + marginCallCvl + initialCvl + duration { + period + units + } + } + status + currentCvl { + total + disbursed + } + transactions { + ... on CreditFacilityIncrementalPayment { + cents + recordedAt + txId + } + ... on CreditFacilityCollateralUpdated { + satoshis + recordedAt + action + txId + } + ... on CreditFacilityOrigination { + cents + recordedAt + txId + } + ... on CreditFacilityCollateralizationUpdated { + state + collateral + outstandingInterest + outstandingDisbursal + recordedAt + price + } + ... on CreditFacilityDisbursalExecuted { + cents + recordedAt + txId + } + ... on CreditFacilityInterestAccrued { + cents + recordedAt + txId + days + } + } + disbursals { + disbursalId + amount + createdAt + status + } + balance { + facilityRemaining { + usdBalance + } + disbursed { + total { + usdBalance + } + outstanding { + usdBalance + } + dueOutstanding { + usdBalance + } + } + interest { + total { + usdBalance + } + outstanding { + usdBalance + } + dueOutstanding { + usdBalance + } + } + outstanding { + usdBalance + } + dueOutstanding { + usdBalance + } + collateral { + btcBalance + } + } + customer { + email + } + } + } +`; + +export const getCreditFacilityById = async ( + variables: GetCreditFacilityDetailsQueryVariables +) => { + try { + const response = await getClient().query< + GetCreditFacilityDetailsQuery, + GetCreditFacilityDetailsQueryVariables + >({ + query: GetCreditFacilityDetailsDocument, + variables, + }); + + if (!response.data) { + return { error: "Data not found" }; + } + + return response; + } catch (error) { + if (error instanceof Error) { + return { error: error.message }; + } + return { error: "An unknown error occurred" }; + } +}; + +export const getCreditFacilityDetailsTool = tool({ + type: "function", + description: `Retrieve comprehensive details for a single credit facility. USE ONLY: when complete facility details are asked.`, + parameters: z.object({ + id: z + .string() + .uuid() + .describe( + "UUID of the credit facility to fetch detailed information for" + ), + }), + execute: async ({ id }) => { + return getCreditFacilityById({ id }); + }, +}); diff --git a/apps/lana-chat/app/api/chat/tools/get-customer-credit-facility.ts b/apps/lana-chat/app/api/chat/tools/get-customer-credit-facility.ts new file mode 100644 index 0000000000..2097f86583 --- /dev/null +++ b/apps/lana-chat/app/api/chat/tools/get-customer-credit-facility.ts @@ -0,0 +1,165 @@ +import { z } from "zod"; +import { tool } from "ai"; +import { gql } from "@apollo/client"; +import { getClient } from "../client"; +import { + GetCustomerCreditFacilityByEmailDocument, + GetCustomerCreditFacilityByEmailQuery, + GetCustomerCreditFacilityByEmailQueryVariables, +} from "@/lib/graphql/generated"; + +gql` + query GetCustomerCreditFacilityByEmail($email: String!) { + customerByEmail(email: $email) { + customerId + creditFacilities { + creditFacilityId + activatedAt + expiresAt + createdAt + collateralizationState + facilityAmount + collateral + canBeCompleted + status + transactions { + ... on CreditFacilityIncrementalPayment { + cents + recordedAt + txId + } + ... on CreditFacilityCollateralUpdated { + satoshis + recordedAt + action + txId + } + ... on CreditFacilityOrigination { + cents + recordedAt + txId + } + ... on CreditFacilityCollateralizationUpdated { + state + collateral + outstandingInterest + outstandingDisbursal + recordedAt + price + } + ... on CreditFacilityDisbursalExecuted { + cents + recordedAt + txId + } + ... on CreditFacilityInterestAccrued { + cents + recordedAt + txId + days + } + } + disbursals { + disbursalId + amount + createdAt + status + } + currentCvl { + total + disbursed + } + creditFacilityTerms { + annualRate + accrualInterval + incurrenceInterval + oneTimeFeeRate + liquidationCvl + marginCallCvl + initialCvl + duration { + period + units + } + } + balance { + facilityRemaining { + usdBalance + } + disbursed { + total { + usdBalance + } + outstanding { + usdBalance + } + dueOutstanding { + usdBalance + } + } + interest { + total { + usdBalance + } + outstanding { + usdBalance + } + dueOutstanding { + usdBalance + } + } + outstanding { + usdBalance + } + dueOutstanding { + usdBalance + } + collateral { + btcBalance + } + } + } + } + } +`; + +export const getCustomerCreditFacility = async ( + variables: GetCustomerCreditFacilityByEmailQueryVariables +) => { + try { + const response = await getClient().query< + GetCustomerCreditFacilityByEmailQuery, + GetCustomerCreditFacilityByEmailQueryVariables + >({ + query: GetCustomerCreditFacilityByEmailDocument, + variables, + }); + + if (!response.data?.customerByEmail) { + return { error: "Customer not found" }; + } + + return response; + } catch (error) { + if (error instanceof Error) { + return { error: error.message }; + } + return { error: "An unknown error occurred" }; + } +}; + +export const getCustomerCreditFacilitiesTool = tool({ + type: "function", + description: + "Retrieve details about the customer's credit facility, including the facility amount, transactions, balance information, collateral, and associated terms. Use this tool to get credit-specific information about the customer.", + parameters: z.object({ + email: z + .string() + .describe( + "The email address of the customer whose credit facility details are being requested." + ), + }), + execute: async ({ email }) => { + return getCustomerCreditFacility({ email }); + }, +}); diff --git a/apps/lana-chat/app/api/chat/tools/get-customer-details.ts b/apps/lana-chat/app/api/chat/tools/get-customer-details.ts new file mode 100644 index 0000000000..f17cd1d44c --- /dev/null +++ b/apps/lana-chat/app/api/chat/tools/get-customer-details.ts @@ -0,0 +1,89 @@ +import { z } from "zod"; +import { tool } from "ai"; +import { gql } from "@apollo/client"; +import { getClient } from "../client"; +import { + GetCustomerByEmailDocument, + GetCustomerByEmailQuery, + GetCustomerByEmailQueryVariables, +} from "@/lib/graphql/generated"; + +gql` + query GetCustomerByEmail($email: String!) { + customerByEmail(email: $email) { + customerId + status + level + createdAt + email + telegramId + applicantId + depositAccount { + depositAccountId + balance { + pending + settled + } + withdrawals { + amount + createdAt + reference + status + withdrawalId + } + deposits { + amount + createdAt + reference + depositId + } + } + documents { + documentId + status + filename + } + } + } +`; + +export const getCustomerDetails = async ( + variables: GetCustomerByEmailQueryVariables +) => { + try { + const response = await getClient().query< + GetCustomerByEmailQuery, + GetCustomerByEmailQueryVariables + >({ + query: GetCustomerByEmailDocument, + variables, + }); + + if (!response.data?.customerByEmail) { + return { error: "Customer not found" }; + } + + return response; + } catch (error) { + if (error instanceof Error) { + return { error: error.message }; + } + return { error: "An unknown error occurred" }; + } +}; + +export const getCustomerDetailsTool = tool({ + type: "function", + description: + "Retrieve general information about a customer by their email address. This includes account balances, deposit history, withdrawal history, KYC document statuses, and basic customer information. **Note**: This does not include any details about the customer's credit facility.", + parameters: z.object({ + email: z + .string() + .describe( + "The email address of the customer whose general details (excluding credit facility) are being requested." + ), + }), + execute: async ({ email }) => { + return getCustomerDetails({ email }); + }, +});