Nick: extract billing works

This commit is contained in:
Nicolas 2025-01-17 20:59:44 -03:00
parent ca14c651da
commit 1f6abf95e8
4 changed files with 36 additions and 12 deletions

View File

@ -77,6 +77,7 @@ export async function getACUC(
api_key: string, api_key: string,
cacheOnly = false, cacheOnly = false,
useCache = true, useCache = true,
mode?: RateLimiterMode,
): Promise<AuthCreditUsageChunk | null> { ): Promise<AuthCreditUsageChunk | null> {
const cacheKeyACUC = `acuc_${api_key}`; const cacheKeyACUC = `acuc_${api_key}`;
@ -93,9 +94,10 @@ export async function getACUC(
let retries = 0; let retries = 0;
const maxRetries = 5; const maxRetries = 5;
let rpcName = mode === RateLimiterMode.Extract ? "auth_credit_usage_chunk_extract" : "auth_credit_usage_chunk_test_21_credit_pack";
while (retries < maxRetries) { while (retries < maxRetries) {
({ data, error } = await supabase_service.rpc( ({ data, error } = await supabase_service.rpc(
"auth_credit_usage_chunk_test_21_credit_pack", rpcName,
{ input_key: api_key }, { input_key: api_key },
{ get: true }, { get: true },
)); ));
@ -203,7 +205,7 @@ export async function supaAuthenticateUser(
}; };
} }
chunk = await getACUC(normalizedApi); chunk = await getACUC(normalizedApi,false, true, mode);
if (chunk === null) { if (chunk === null) {
return { return {

View File

@ -32,7 +32,7 @@ import { ExtractStep, updateExtract } from "./extract-redis";
import { deduplicateObjectsArray } from "./helpers/deduplicate-objs-array"; import { deduplicateObjectsArray } from "./helpers/deduplicate-objs-array";
import { mergeNullValObjs } from "./helpers/merge-null-val-objs"; import { mergeNullValObjs } from "./helpers/merge-null-val-objs";
import { CUSTOM_U_TEAMS } from "./config"; import { CUSTOM_U_TEAMS } from "./config";
import { estimateCost, estimateTotalCost } from "./usage/llm-cost"; import { calculateFinalResultCost, estimateCost, estimateTotalCost } from "./usage/llm-cost";
interface ExtractServiceOptions { interface ExtractServiceOptions {
request: ExtractRequest; request: ExtractRequest;
@ -50,6 +50,7 @@ interface ExtractResult {
error?: string; error?: string;
tokenUsageBreakdown?: TokenUsage[]; tokenUsageBreakdown?: TokenUsage[];
llmUsage?: number; llmUsage?: number;
totalUrlsScraped?: number;
} }
async function analyzeSchemaAndPrompt( async function analyzeSchemaAndPrompt(
@ -178,6 +179,7 @@ export async function performExtraction(
let multiEntityCompletions: completions[] = []; let multiEntityCompletions: completions[] = [];
let multiEntityResult: any = {}; let multiEntityResult: any = {};
let singleAnswerResult: any = {}; let singleAnswerResult: any = {};
let totalUrlsScraped = 0;
// Token tracking // Token tracking
@ -238,6 +240,7 @@ export async function performExtraction(
"No valid URLs found to scrape. Try adjusting your search criteria or including more URLs.", "No valid URLs found to scrape. Try adjusting your search criteria or including more URLs.",
extractId, extractId,
urlTrace: urlTraces, urlTrace: urlTraces,
totalUrlsScraped: 0
}; };
} }
@ -334,6 +337,8 @@ export async function performExtraction(
(doc): doc is Document => doc !== null, (doc): doc is Document => doc !== null,
); );
totalUrlsScraped += multyEntityDocs.length;
let endScrape = Date.now(); let endScrape = Date.now();
await updateExtract(extractId, { await updateExtract(extractId, {
@ -529,6 +534,7 @@ export async function performExtraction(
"An unexpected error occurred. Please contact help@firecrawl.com for help.", "An unexpected error occurred. Please contact help@firecrawl.com for help.",
extractId, extractId,
urlTrace: urlTraces, urlTrace: urlTraces,
totalUrlsScraped
}; };
} }
} }
@ -580,15 +586,17 @@ export async function performExtraction(
} }
} }
singleAnswerDocs.push( const validResults = results.filter((doc): doc is Document => doc !== null);
...results.filter((doc): doc is Document => doc !== null), singleAnswerDocs.push(...validResults);
); totalUrlsScraped += validResults.length;
} catch (error) { } catch (error) {
return { return {
success: false, success: false,
error: error.message, error: error.message,
extractId, extractId,
urlTrace: urlTraces, urlTrace: urlTraces,
totalUrlsScraped
}; };
} }
@ -600,6 +608,7 @@ export async function performExtraction(
"All provided URLs are invalid. Please check your input and try again.", "All provided URLs are invalid. Please check your input and try again.",
extractId, extractId,
urlTrace: request.urlTrace ? urlTraces : undefined, urlTrace: request.urlTrace ? urlTraces : undefined,
totalUrlsScraped: 0
}; };
} }
@ -663,20 +672,23 @@ export async function performExtraction(
? await mixSchemaObjects(reqSchema, singleAnswerResult, multiEntityResult) ? await mixSchemaObjects(reqSchema, singleAnswerResult, multiEntityResult)
: singleAnswerResult || multiEntityResult; : singleAnswerResult || multiEntityResult;
const totalTokensUsed = tokenUsage.reduce((a, b) => a + b.totalTokens, 0);
const llmUsage = estimateTotalCost(tokenUsage);
const tokensToBill = calculateFinalResultCost(finalResult);
let linksBilled = links.length * 5; let linksBilled = links.length * 5;
if (CUSTOM_U_TEAMS.includes(teamId)) { if (CUSTOM_U_TEAMS.includes(teamId)) {
linksBilled = 1; linksBilled = 1;
} }
// Bill team for usage // Bill team for usage
billTeam(teamId, subId, linksBilled).catch((error) => { billTeam(teamId, subId, tokensToBill, logger, true).catch((error) => {
logger.error( logger.error(
`Failed to bill team ${teamId} for ${linksBilled} credits: ${error}`, `Failed to bill team ${teamId} for ${tokensToBill} tokens: ${error}`,
); );
}); });
const totalTokensUsed = tokenUsage.reduce((a, b) => a + b.totalTokens, 0);
const llmUsage = estimateTotalCost(tokenUsage);
// Log job with token usage // Log job with token usage
logJob({ logJob({
@ -710,6 +722,6 @@ export async function performExtraction(
warning: undefined, // TODO FIX warning: undefined, // TODO FIX
urlTrace: request.urlTrace ? urlTraces : undefined, urlTrace: request.urlTrace ? urlTraces : undefined,
llmUsage, llmUsage,
totalUrlsScraped
}; };
} }

View File

@ -8,6 +8,12 @@ interface ModelPricing {
input_cost_per_request?: number; input_cost_per_request?: number;
mode: string; mode: string;
} }
const tokenPerCharacter = 4;
const baseTokenCost = 200;
export function calculateFinalResultCost(data: any): number {
return JSON.stringify(data).length / tokenPerCharacter + baseTokenCost;
}
export function estimateTotalCost(tokenUsage: TokenUsage[]): number { export function estimateTotalCost(tokenUsage: TokenUsage[]): number {
return tokenUsage.reduce((total, usage) => { return tokenUsage.reduce((total, usage) => {

View File

@ -22,12 +22,14 @@ export async function billTeam(
subscription_id: string | null | undefined, subscription_id: string | null | undefined,
credits: number, credits: number,
logger?: Logger, logger?: Logger,
is_extract: boolean = false,
) { ) {
return withAuth(supaBillTeam, { success: true, message: "No DB, bypassed." })( return withAuth(supaBillTeam, { success: true, message: "No DB, bypassed." })(
team_id, team_id,
subscription_id, subscription_id,
credits, credits,
logger, logger,
is_extract,
); );
} }
export async function supaBillTeam( export async function supaBillTeam(
@ -35,6 +37,7 @@ export async function supaBillTeam(
subscription_id: string | null | undefined, subscription_id: string | null | undefined,
credits: number, credits: number,
__logger?: Logger, __logger?: Logger,
is_extract: boolean = false,
) { ) {
const _logger = (__logger ?? logger).child({ const _logger = (__logger ?? logger).child({
module: "credit_billing", module: "credit_billing",
@ -49,11 +52,12 @@ export async function supaBillTeam(
credits, credits,
}); });
const { data, error } = await supabase_service.rpc("bill_team", { const { data, error } = await supabase_service.rpc("bill_team_w_extract", {
_team_id: team_id, _team_id: team_id,
sub_id: subscription_id ?? null, sub_id: subscription_id ?? null,
fetch_subscription: subscription_id === undefined, fetch_subscription: subscription_id === undefined,
credits, credits,
is_extract,
}); });
if (error) { if (error) {