Skip to content

Commit

Permalink
feat: tested proxy usage limits
Browse files Browse the repository at this point in the history
  • Loading branch information
anasbarg committed Sep 4, 2024
1 parent 1c179bc commit 5fda8c8
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 10 deletions.
23 changes: 23 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
services:
db:
image: postgres:14.4
environment:
POSTGRES_DB: activepieces
POSTGRES_USER: postgres
POSTGRES_PASSWORD: A79Vm5D4p2VQHOp2gd5

volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"

redis:
image: redis:7.0.7
volumes:
- redis_data:/data
ports:
- "6379:6379"

volumes:
postgres_data:
redis_data:
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const anthropic = ({
apiKey: engineToken,
baseURL: proxyUrl,
defaultHeaders: {
'X-AP-TOTAL-USAGE-BODY-PATH': 'usage.total_tokens',
'X-AP-TOTAL-USAGE-BODY-PATH': 'usage.output_tokens+usage.input_tokens',
'Authorization': `Bearer ${engineToken}`,
},
})
Expand Down
4 changes: 2 additions & 2 deletions packages/react-ui/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
target: 'http://127.0.0.1:3000',
secure: false,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
headers: {
Host: 'localhost:4200',
Host: '127.0.0.1:4200',
},
ws: true,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class CreateProxyConfig1724777988812 implements MigrationInterface {
ALTER TABLE "proxy_config"
ADD CONSTRAINT "fk_proxy_config_platform_id" FOREIGN KEY ("platformId") REFERENCES "platform"("id") ON DELETE NO ACTION ON UPDATE NO ACTION
`);
await queryRunner.query(`ALTER TABLE "project_plan" ADD COLUMN "aiTokens" integer DEFAULT 1000;`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
Expand All @@ -33,6 +34,7 @@ export class CreateProxyConfig1724777988812 implements MigrationInterface {
await queryRunner.query(`
DROP TABLE "proxy_config"
`);
await queryRunner.query(`ALTER TABLE "project_plan" DROP COLUMN "aiTokens";`);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export const ProjectPlanEntity = new EntitySchema<ProjectPlanSchema>({
tasks: {
type: Number,
},
aiTokens: {
type: Number,
default: 1000,
nullable: true
},
},
indices: [
{
Expand Down
32 changes: 32 additions & 0 deletions packages/server/api/src/app/project/usage/project-usage-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export const projectUsageService = {
increaseTasks,
getCurrentingStartPeriod,
getCurrentingEndPeriod,
increaseAITokens,
getAITokensUsage,
}

function getCurrentingStartPeriod(datetime: string): string {
Expand Down Expand Up @@ -57,6 +59,36 @@ async function getTasksUsage(projectId: string, startBillingPeriod: string): Pro
return Number(value) || 0
}


async function increaseAITokens(projectId: string, incrementBy: number): Promise<number> {
const project = await projectService.getOneOrThrow(projectId)
const startBillingPeriod = getCurrentingStartPeriod(project.created)
return incrementOrCreateAITokensRedisRecord(projectId, startBillingPeriod, incrementBy)
}

async function incrementOrCreateAITokensRedisRecord(projectId: string, startBillingPeriod: string, incrementBy: number): Promise<number> {
const environment = system.get(SharedSystemProp.ENVIRONMENT)
if (environment === ApEnvironment.TESTING) {
return 0
}
const key = constructAITokensUsageKey(projectId, startBillingPeriod)
return getRedisConnection().incrby(key, incrementBy)
}

async function getAITokensUsage(projectId: string, startBillingPeriod: string): Promise<number> {
const environment = system.get(SharedSystemProp.ENVIRONMENT)
if (environment === ApEnvironment.TESTING) {
return 0
}
const key = constructAITokensUsageKey(projectId, startBillingPeriod)
const value = await getRedisConnection().get(key)
return Number(value) || 0
}

function constructUsageKey(projectId: string, startBillingPeriod: string): string {
return `project-usage:${projectId}:${startBillingPeriod}`
}

function constructAITokensUsageKey(projectId: string, startBillingPeriod: string): string {
return `project-ai-tokens-usage:${projectId}:${startBillingPeriod}`
}
3 changes: 0 additions & 3 deletions packages/server/api/src/app/proxy/proxy-config-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ export const proxyConfigService = {
platformId,
provider,
}: GetParams): Promise<ProxyConfig> {
console.log("XXXXXXXXXX provider", provider)
console.log("XXXXXXXXXX provider", provider)
console.log("XXXXXXXXXX platformId", platformId)
const config = await repo().findOneBy({
platformId,
provider,
Expand Down
40 changes: 36 additions & 4 deletions packages/server/api/src/app/proxy/proxy-module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { PrincipalType } from '@activepieces/shared'
import { isNil, PrincipalType } from '@activepieces/shared'
import { FastifyPluginAsyncTypebox, FastifyPluginCallbackTypebox, Type } from '@fastify/type-provider-typebox'
import axios, { AxiosRequestConfig } from 'axios'
import { StatusCodes } from 'http-status-codes'
import { projectLimitsService } from 'packages/server/api/src/app/ee/project-plan/project-plan.service'
import { projectService } from 'packages/server/api/src/app/project/project-service'
import { projectUsageService } from 'packages/server/api/src/app/project/usage/project-usage-service'
import { proxyConfigService } from './proxy-config-service'
import https from 'https'

export const proxyModule: FastifyPluginAsyncTypebox = async (app) => {
await app.register(projectProxyController, { prefix: '/v1/proxy' })
Expand Down Expand Up @@ -33,6 +32,10 @@ export const projectProxyController: FastifyPluginCallbackTypebox = (
try {
const projectId = request.principal.projectId

const projectPlan = await projectLimitsService.getPlanByProjectId(projectId)

const planTokens = projectPlan?.aiTokens

const platformId = await projectService.getPlatformId(projectId)

const provider = request.params.provider
Expand All @@ -48,6 +51,10 @@ export const projectProxyController: FastifyPluginCallbackTypebox = (

const requestHeaders = structuredClone({ ...request.headers })

const tokensUsagePath = requestHeaders['x-ap-total-usage-body-path']

console.log('XXXXXXXXXX Request Headers', requestHeaders)

delete requestHeaders['authorization']
delete requestHeaders['Authorization']
delete requestHeaders['content-length']
Expand All @@ -74,6 +81,18 @@ export const projectProxyController: FastifyPluginCallbackTypebox = (

const data = await response.json()

const usage = projectPlan && tokensUsagePath ? calculateUsage(data, tokensUsagePath) : null

const totalTokensUsage = usage ? await projectUsageService.increaseAITokens(projectId, usage) : null

if (isNil(totalTokensUsage)) {
throw new Error(`Failed to calculate usage from response headers ${tokensUsagePath}`)
}

if (!isNil(planTokens) && totalTokensUsage > planTokens) {
throw new Error(`You have exceeded your plan limit of ${planTokens} tokens`)
}

await reply
.code(response.status)
.send(data);
Expand All @@ -84,4 +103,17 @@ export const projectProxyController: FastifyPluginCallbackTypebox = (
})

done()
}

const calculateUsage = (body: any, usagePath: string | string[]): number => {
const fields = typeof usagePath === "string" ? usagePath.split('+').map(field => field.trim()) : usagePath

return fields.reduce((acc, field) => {
const fieldPath = field.split('.')
const value = fieldPath.reduce((acc, field) => acc[field], body)
if (typeof value !== 'number') {
return acc
}
return acc + value
}, 0)
}
1 change: 1 addition & 0 deletions packages/shared/src/lib/project/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const ProjectPlan = Type.Object({
connections: Type.Number(),
teamMembers: Type.Number(),
tasks: Type.Number(),
aiTokens: Type.Optional(Type.Number()),
})

export type ProjectPlan = Static<typeof ProjectPlan>
Expand Down

0 comments on commit 5fda8c8

Please sign in to comment.