mirror of
https://git.mirrors.martin98.com/https://github.com/mendableai/firecrawl
synced 2025-08-13 05:09:03 +08:00
Revert "Merge pull request #1068 from mendableai/nsc/llm-usage-extract"
This reverts commit 406f28c04aff2ba3ae65f483627da13f02943cc3, reversing changes made to 34ad9ec25d73f37deb1e3adec2315a121ec52f0e.
This commit is contained in:
parent
406f28c04a
commit
8b17af4001
@ -77,9 +77,8 @@ 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}_${mode}`;
|
const cacheKeyACUC = `acuc_${api_key}`;
|
||||||
|
|
||||||
if (useCache) {
|
if (useCache) {
|
||||||
const cachedACUC = await getValue(cacheKeyACUC);
|
const cachedACUC = await getValue(cacheKeyACUC);
|
||||||
@ -94,13 +93,9 @@ export async function getACUC(
|
|||||||
let retries = 0;
|
let retries = 0;
|
||||||
const maxRetries = 5;
|
const maxRetries = 5;
|
||||||
|
|
||||||
let rpcName =
|
|
||||||
mode === RateLimiterMode.Extract || mode === RateLimiterMode.ExtractStatus
|
|
||||||
? "auth_credit_usage_chunk_extract"
|
|
||||||
: "auth_credit_usage_chunk_test_22_credit_pack_n_extract";
|
|
||||||
while (retries < maxRetries) {
|
while (retries < maxRetries) {
|
||||||
({ data, error } = await supabase_service.rpc(
|
({ data, error } = await supabase_service.rpc(
|
||||||
rpcName,
|
"auth_credit_usage_chunk_test_21_credit_pack",
|
||||||
{ input_key: api_key },
|
{ input_key: api_key },
|
||||||
{ get: true },
|
{ get: true },
|
||||||
));
|
));
|
||||||
@ -132,6 +127,8 @@ export async function getACUC(
|
|||||||
setCachedACUC(api_key, chunk);
|
setCachedACUC(api_key, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log(chunk);
|
||||||
|
|
||||||
return chunk;
|
return chunk;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
@ -206,7 +203,7 @@ export async function supaAuthenticateUser(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk = await getACUC(normalizedApi, false, true, mode);
|
chunk = await getACUC(normalizedApi);
|
||||||
|
|
||||||
if (chunk === null) {
|
if (chunk === null) {
|
||||||
return {
|
return {
|
||||||
@ -261,9 +258,6 @@ export async function supaAuthenticateUser(
|
|||||||
subscriptionData.plan,
|
subscriptionData.plan,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case RateLimiterMode.ExtractStatus:
|
|
||||||
rateLimiter = getRateLimiter(RateLimiterMode.ExtractStatus, token);
|
|
||||||
break;
|
|
||||||
case RateLimiterMode.CrawlStatus:
|
case RateLimiterMode.CrawlStatus:
|
||||||
rateLimiter = getRateLimiter(RateLimiterMode.CrawlStatus, token);
|
rateLimiter = getRateLimiter(RateLimiterMode.CrawlStatus, token);
|
||||||
break;
|
break;
|
||||||
|
@ -37,6 +37,5 @@ export async function extractStatusController(
|
|||||||
error: extract?.error ?? undefined,
|
error: extract?.error ?? undefined,
|
||||||
expiresAt: (await getExtractExpiry(req.params.jobId)).toISOString(),
|
expiresAt: (await getExtractExpiry(req.params.jobId)).toISOString(),
|
||||||
steps: extract.showSteps ? extract.steps : undefined,
|
steps: extract.showSteps ? extract.steps : undefined,
|
||||||
llmUsage: extract.showLLMUsage ? extract.llmUsage : undefined,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,6 @@ export async function extractController(
|
|||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
status: "processing",
|
status: "processing",
|
||||||
showSteps: req.body.__experimental_streamSteps,
|
showSteps: req.body.__experimental_streamSteps,
|
||||||
showLLMUsage: req.body.__experimental_llmUsage,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Sentry.isInitialized()) {
|
if (Sentry.isInitialized()) {
|
||||||
|
@ -226,7 +226,6 @@ export const extractV1Options = z
|
|||||||
origin: z.string().optional().default("api"),
|
origin: z.string().optional().default("api"),
|
||||||
urlTrace: z.boolean().default(false),
|
urlTrace: z.boolean().default(false),
|
||||||
__experimental_streamSteps: z.boolean().default(false),
|
__experimental_streamSteps: z.boolean().default(false),
|
||||||
__experimental_llmUsage: z.boolean().default(false),
|
|
||||||
timeout: z.number().int().positive().finite().safe().default(60000),
|
timeout: z.number().int().positive().finite().safe().default(60000),
|
||||||
})
|
})
|
||||||
.strict(strictMessage);
|
.strict(strictMessage);
|
||||||
@ -882,12 +881,3 @@ export type SearchResponse =
|
|||||||
warning?: string;
|
warning?: string;
|
||||||
data: Document[];
|
data: Document[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type TokenUsage = {
|
|
||||||
promptTokens: number;
|
|
||||||
completionTokens: number;
|
|
||||||
totalTokens: number;
|
|
||||||
step?: string;
|
|
||||||
model?: string;
|
|
||||||
};
|
|
||||||
|
@ -30,8 +30,6 @@ export type StoredExtract = {
|
|||||||
error?: any;
|
error?: any;
|
||||||
showSteps?: boolean;
|
showSteps?: boolean;
|
||||||
steps?: ExtractedStep[];
|
steps?: ExtractedStep[];
|
||||||
showLLMUsage?: boolean;
|
|
||||||
llmUsage?: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function saveExtract(id: string, extract: StoredExtract) {
|
export async function saveExtract(id: string, extract: StoredExtract) {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Document,
|
Document,
|
||||||
ExtractRequest,
|
ExtractRequest,
|
||||||
TokenUsage,
|
|
||||||
toLegacyCrawlerOptions,
|
toLegacyCrawlerOptions,
|
||||||
URLTrace,
|
URLTrace,
|
||||||
} from "../../controllers/v1/types";
|
} from "../../controllers/v1/types";
|
||||||
@ -32,7 +31,6 @@ 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 { calculateFinalResultCost, estimateCost, estimateTotalCost } from "./usage/llm-cost";
|
|
||||||
|
|
||||||
interface ExtractServiceOptions {
|
interface ExtractServiceOptions {
|
||||||
request: ExtractRequest;
|
request: ExtractRequest;
|
||||||
@ -48,9 +46,6 @@ interface ExtractResult {
|
|||||||
warning?: string;
|
warning?: string;
|
||||||
urlTrace?: URLTrace[];
|
urlTrace?: URLTrace[];
|
||||||
error?: string;
|
error?: string;
|
||||||
tokenUsageBreakdown?: TokenUsage[];
|
|
||||||
llmUsage?: number;
|
|
||||||
totalUrlsScraped?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function analyzeSchemaAndPrompt(
|
async function analyzeSchemaAndPrompt(
|
||||||
@ -62,7 +57,6 @@ async function analyzeSchemaAndPrompt(
|
|||||||
multiEntityKeys: string[];
|
multiEntityKeys: string[];
|
||||||
reasoning?: string;
|
reasoning?: string;
|
||||||
keyIndicators?: string[];
|
keyIndicators?: string[];
|
||||||
tokenUsage: TokenUsage;
|
|
||||||
}> {
|
}> {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
schema = await generateSchemaFromPrompt(prompt);
|
schema = await generateSchemaFromPrompt(prompt);
|
||||||
@ -77,10 +71,8 @@ async function analyzeSchemaAndPrompt(
|
|||||||
keyIndicators: z.array(z.string()),
|
keyIndicators: z.array(z.string()),
|
||||||
});
|
});
|
||||||
|
|
||||||
const model = "gpt-4o";
|
|
||||||
|
|
||||||
const result = await openai.beta.chat.completions.parse({
|
const result = await openai.beta.chat.completions.parse({
|
||||||
model: model,
|
model: "gpt-4o",
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "system",
|
role: "system",
|
||||||
@ -139,20 +131,12 @@ Schema: ${schemaString}\nPrompt: ${prompt}\nRelevant URLs: ${urls}`,
|
|||||||
|
|
||||||
const { isMultiEntity, multiEntityKeys, reasoning, keyIndicators } =
|
const { isMultiEntity, multiEntityKeys, reasoning, keyIndicators } =
|
||||||
checkSchema.parse(result.choices[0].message.parsed);
|
checkSchema.parse(result.choices[0].message.parsed);
|
||||||
|
return { isMultiEntity, multiEntityKeys, reasoning, keyIndicators };
|
||||||
const tokenUsage: TokenUsage = {
|
|
||||||
promptTokens: result.usage?.prompt_tokens ?? 0,
|
|
||||||
completionTokens: result.usage?.completion_tokens ?? 0,
|
|
||||||
totalTokens: result.usage?.total_tokens ?? 0,
|
|
||||||
model: model,
|
|
||||||
};
|
|
||||||
return { isMultiEntity, multiEntityKeys, reasoning, keyIndicators, tokenUsage };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type completions = {
|
type completions = {
|
||||||
extract: Record<string, any>;
|
extract: Record<string, any>;
|
||||||
numTokens: number;
|
numTokens: number;
|
||||||
totalUsage: TokenUsage;
|
|
||||||
warning?: string;
|
warning?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -179,11 +163,6 @@ 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
|
|
||||||
let tokenUsage: TokenUsage[] = [];
|
|
||||||
|
|
||||||
await updateExtract(extractId, {
|
await updateExtract(extractId, {
|
||||||
status: "processing",
|
status: "processing",
|
||||||
@ -240,7 +219,6 @@ 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
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,12 +249,9 @@ export async function performExtraction(
|
|||||||
// 1. the first one is a completion that will extract the array of items
|
// 1. the first one is a completion that will extract the array of items
|
||||||
// 2. the second one is multiple completions that will extract the items from the array
|
// 2. the second one is multiple completions that will extract the items from the array
|
||||||
let startAnalyze = Date.now();
|
let startAnalyze = Date.now();
|
||||||
const { isMultiEntity, multiEntityKeys, reasoning, keyIndicators, tokenUsage: schemaAnalysisTokenUsage } =
|
const { isMultiEntity, multiEntityKeys, reasoning, keyIndicators } =
|
||||||
await analyzeSchemaAndPrompt(links, reqSchema, request.prompt ?? "");
|
await analyzeSchemaAndPrompt(links, reqSchema, request.prompt ?? "");
|
||||||
|
|
||||||
// Track schema analysis tokens
|
|
||||||
tokenUsage.push(schemaAnalysisTokenUsage);
|
|
||||||
|
|
||||||
// console.log("\nIs Multi Entity:", isMultiEntity);
|
// console.log("\nIs Multi Entity:", isMultiEntity);
|
||||||
// console.log("\nMulti Entity Keys:", multiEntityKeys);
|
// console.log("\nMulti Entity Keys:", multiEntityKeys);
|
||||||
// console.log("\nReasoning:", reasoning);
|
// console.log("\nReasoning:", reasoning);
|
||||||
@ -337,8 +312,6 @@ 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, {
|
||||||
@ -403,8 +376,6 @@ export async function performExtraction(
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
tokenUsage.push(shouldExtractCheck.totalUsage);
|
|
||||||
|
|
||||||
if (!shouldExtractCheck.extract["extract"]) {
|
if (!shouldExtractCheck.extract["extract"]) {
|
||||||
console.log(
|
console.log(
|
||||||
`Skipping extraction for ${doc.metadata.url} as content is irrelevant`,
|
`Skipping extraction for ${doc.metadata.url} as content is irrelevant`,
|
||||||
@ -467,11 +438,6 @@ export async function performExtraction(
|
|||||||
timeoutPromise,
|
timeoutPromise,
|
||||||
])) as Awaited<ReturnType<typeof generateOpenAICompletions>>;
|
])) as Awaited<ReturnType<typeof generateOpenAICompletions>>;
|
||||||
|
|
||||||
// Track multi-entity extraction tokens
|
|
||||||
if (multiEntityCompletion) {
|
|
||||||
tokenUsage.push(multiEntityCompletion.totalUsage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log(multiEntityCompletion.extract)
|
// console.log(multiEntityCompletion.extract)
|
||||||
// if (!multiEntityCompletion.extract?.is_content_relevant) {
|
// if (!multiEntityCompletion.extract?.is_content_relevant) {
|
||||||
// console.log(`Skipping extraction for ${doc.metadata.url} as content is not relevant`);
|
// console.log(`Skipping extraction for ${doc.metadata.url} as content is not relevant`);
|
||||||
@ -534,7 +500,6 @@ 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
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -586,17 +551,15 @@ export async function performExtraction(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const validResults = results.filter((doc): doc is Document => doc !== null);
|
singleAnswerDocs.push(
|
||||||
singleAnswerDocs.push(...validResults);
|
...results.filter((doc): doc is Document => doc !== null),
|
||||||
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
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -608,7 +571,6 @@ 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
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -641,11 +603,6 @@ export async function performExtraction(
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Track single answer extraction tokens
|
|
||||||
if (singleAnswerCompletions) {
|
|
||||||
tokenUsage.push(singleAnswerCompletions.totalUsage);
|
|
||||||
}
|
|
||||||
|
|
||||||
singleAnswerResult = singleAnswerCompletions.extract;
|
singleAnswerResult = singleAnswerCompletions.extract;
|
||||||
|
|
||||||
// Update token usage in traces
|
// Update token usage in traces
|
||||||
@ -672,24 +629,19 @@ export async function performExtraction(
|
|||||||
? await mixSchemaObjects(reqSchema, singleAnswerResult, multiEntityResult)
|
? await mixSchemaObjects(reqSchema, singleAnswerResult, multiEntityResult)
|
||||||
: singleAnswerResult || multiEntityResult;
|
: singleAnswerResult || multiEntityResult;
|
||||||
|
|
||||||
|
let linksBilled = links.length * 5;
|
||||||
const totalTokensUsed = tokenUsage.reduce((a, b) => a + b.totalTokens, 0);
|
|
||||||
const llmUsage = estimateTotalCost(tokenUsage);
|
|
||||||
let tokensToBill = calculateFinalResultCost(finalResult);
|
|
||||||
|
|
||||||
|
|
||||||
if (CUSTOM_U_TEAMS.includes(teamId)) {
|
if (CUSTOM_U_TEAMS.includes(teamId)) {
|
||||||
tokensToBill = 1;
|
linksBilled = 1;
|
||||||
}
|
}
|
||||||
// Bill team for usage
|
// Bill team for usage
|
||||||
billTeam(teamId, subId, tokensToBill, logger, true).catch((error) => {
|
billTeam(teamId, subId, linksBilled).catch((error) => {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Failed to bill team ${teamId} for ${tokensToBill} tokens: ${error}`,
|
`Failed to bill team ${teamId} for ${linksBilled} credits: ${error}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Log job
|
||||||
// Log job with token usage
|
|
||||||
logJob({
|
logJob({
|
||||||
job_id: extractId,
|
job_id: extractId,
|
||||||
success: true,
|
success: true,
|
||||||
@ -702,12 +654,10 @@ export async function performExtraction(
|
|||||||
url: request.urls.join(", "),
|
url: request.urls.join(", "),
|
||||||
scrapeOptions: request,
|
scrapeOptions: request,
|
||||||
origin: request.origin ?? "api",
|
origin: request.origin ?? "api",
|
||||||
num_tokens: totalTokensUsed,
|
num_tokens: 0, // completions?.numTokens ?? 0,
|
||||||
tokens_billed: tokensToBill,
|
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
updateExtract(extractId, {
|
updateExtract(extractId, {
|
||||||
status: "completed",
|
status: "completed",
|
||||||
llmUsage,
|
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Failed to update extract ${extractId} status to completed: ${error}`,
|
`Failed to update extract ${extractId} status to completed: ${error}`,
|
||||||
@ -721,7 +671,5 @@ export async function performExtraction(
|
|||||||
extractId,
|
extractId,
|
||||||
warning: undefined, // TODO FIX
|
warning: undefined, // TODO FIX
|
||||||
urlTrace: request.urlTrace ? urlTraces : undefined,
|
urlTrace: request.urlTrace ? urlTraces : undefined,
|
||||||
llmUsage,
|
|
||||||
totalUrlsScraped
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -150,21 +150,16 @@ function filterAndProcessLinks(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RerankerResult = {
|
|
||||||
mapDocument: MapDocument[];
|
|
||||||
tokensUsed: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function rerankLinksWithLLM(
|
export async function rerankLinksWithLLM(
|
||||||
mappedLinks: MapDocument[],
|
mappedLinks: MapDocument[],
|
||||||
searchQuery: string,
|
searchQuery: string,
|
||||||
urlTraces: URLTrace[],
|
urlTraces: URLTrace[],
|
||||||
): Promise<RerankerResult> {
|
): Promise<MapDocument[]> {
|
||||||
const chunkSize = 100;
|
const chunkSize = 100;
|
||||||
const chunks: MapDocument[][] = [];
|
const chunks: MapDocument[][] = [];
|
||||||
const TIMEOUT_MS = 20000;
|
const TIMEOUT_MS = 20000;
|
||||||
const MAX_RETRIES = 2;
|
const MAX_RETRIES = 2;
|
||||||
let totalTokensUsed = 0;
|
|
||||||
|
|
||||||
// Split mappedLinks into chunks of 200
|
// Split mappedLinks into chunks of 200
|
||||||
for (let i = 0; i < mappedLinks.length; i += chunkSize) {
|
for (let i = 0; i < mappedLinks.length; i += chunkSize) {
|
||||||
@ -230,7 +225,6 @@ export async function rerankLinksWithLLM(
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
totalTokensUsed += completion.numTokens || 0;
|
|
||||||
// console.log(`Chunk ${chunkIndex + 1}: Found ${completion.extract.relevantLinks.length} relevant links`);
|
// console.log(`Chunk ${chunkIndex + 1}: Found ${completion.extract.relevantLinks.length} relevant links`);
|
||||||
return completion.extract.relevantLinks;
|
return completion.extract.relevantLinks;
|
||||||
|
|
||||||
@ -258,8 +252,5 @@ export async function rerankLinksWithLLM(
|
|||||||
.filter((link): link is MapDocument => link !== undefined);
|
.filter((link): link is MapDocument => link !== undefined);
|
||||||
|
|
||||||
// console.log(`Returning ${relevantLinks.length} relevant links`);
|
// console.log(`Returning ${relevantLinks.length} relevant links`);
|
||||||
return {
|
return relevantLinks;
|
||||||
mapDocument: relevantLinks,
|
|
||||||
tokensUsed: totalTokensUsed,
|
|
||||||
};
|
|
||||||
}
|
}
|
@ -199,19 +199,15 @@ export async function processUrl(
|
|||||||
// (link, index) => `${index + 1}. URL: ${link.url}, Title: ${link.title}, Description: ${link.description}`
|
// (link, index) => `${index + 1}. URL: ${link.url}, Title: ${link.title}, Description: ${link.description}`
|
||||||
// );
|
// );
|
||||||
|
|
||||||
const rerankerResult = await rerankLinksWithLLM(mappedLinks, searchQuery, urlTraces);
|
mappedLinks = await rerankLinksWithLLM(mappedLinks, searchQuery, urlTraces);
|
||||||
mappedLinks = rerankerResult.mapDocument;
|
|
||||||
let tokensUsed = rerankerResult.tokensUsed;
|
|
||||||
|
|
||||||
// 2nd Pass, useful for when the first pass returns too many links
|
// 2nd Pass, useful for when the first pass returns too many links
|
||||||
if (mappedLinks.length > 100) {
|
if (mappedLinks.length > 100) {
|
||||||
const rerankerResult = await rerankLinksWithLLM(
|
mappedLinks = await rerankLinksWithLLM(
|
||||||
mappedLinks,
|
mappedLinks,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
urlTraces,
|
urlTraces,
|
||||||
);
|
);
|
||||||
mappedLinks = rerankerResult.mapDocument;
|
|
||||||
tokensUsed += rerankerResult.tokensUsed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// dumpToFile(
|
// dumpToFile(
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
import { TokenUsage } from "../../../controllers/v1/types";
|
|
||||||
import { logger } from "../../../lib/logger";
|
|
||||||
import { modelPrices } from "./model-prices";
|
|
||||||
|
|
||||||
interface ModelPricing {
|
|
||||||
input_cost_per_token?: number;
|
|
||||||
output_cost_per_token?: number;
|
|
||||||
input_cost_per_request?: number;
|
|
||||||
mode: string;
|
|
||||||
}
|
|
||||||
const tokenPerCharacter = 4;
|
|
||||||
const baseTokenCost = 300;
|
|
||||||
|
|
||||||
export function calculateFinalResultCost(data: any): number {
|
|
||||||
return Math.floor((JSON.stringify(data).length / tokenPerCharacter) + baseTokenCost);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function estimateTotalCost(tokenUsage: TokenUsage[]): number {
|
|
||||||
return tokenUsage.reduce((total, usage) => {
|
|
||||||
return total + estimateCost(usage);
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function estimateCost(tokenUsage: TokenUsage): number {
|
|
||||||
let totalCost = 0;
|
|
||||||
try {
|
|
||||||
let model = tokenUsage.model ?? process.env.MODEL_NAME ?? "gpt-4o-mini";
|
|
||||||
const pricing = modelPrices[model] as ModelPricing;
|
|
||||||
|
|
||||||
if (!pricing) {
|
|
||||||
logger.error(`No pricing information found for model: ${model}`);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pricing.mode !== "chat") {
|
|
||||||
logger.error(`Model ${model} is not a chat model`);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add per-request cost if applicable (Only Perplexity supports this)
|
|
||||||
if (pricing.input_cost_per_request) {
|
|
||||||
totalCost += pricing.input_cost_per_request;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add token-based costs
|
|
||||||
if (pricing.input_cost_per_token) {
|
|
||||||
totalCost += tokenUsage.promptTokens * pricing.input_cost_per_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pricing.output_cost_per_token) {
|
|
||||||
totalCost += tokenUsage.completionTokens * pricing.output_cost_per_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Number(totalCost.toFixed(7));
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`Error estimating cost: ${error}`);
|
|
||||||
return totalCost;
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -228,7 +228,7 @@ v1Router.post(
|
|||||||
|
|
||||||
v1Router.get(
|
v1Router.get(
|
||||||
"/extract/:jobId",
|
"/extract/:jobId",
|
||||||
authMiddleware(RateLimiterMode.ExtractStatus),
|
authMiddleware(RateLimiterMode.CrawlStatus),
|
||||||
wrap(extractStatusController),
|
wrap(extractStatusController),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import OpenAI from "openai";
|
import OpenAI from "openai";
|
||||||
import { encoding_for_model } from "@dqbd/tiktoken";
|
import { encoding_for_model } from "@dqbd/tiktoken";
|
||||||
import { TiktokenModel } from "@dqbd/tiktoken";
|
import { TiktokenModel } from "@dqbd/tiktoken";
|
||||||
import { Document, ExtractOptions, TokenUsage } from "../../../controllers/v1/types";
|
import { Document, ExtractOptions } from "../../../controllers/v1/types";
|
||||||
import { Logger } from "winston";
|
import { Logger } from "winston";
|
||||||
import { EngineResultsTracker, Meta } from "..";
|
import { EngineResultsTracker, Meta } from "..";
|
||||||
import { logger } from "../../../lib/logger";
|
import { logger } from "../../../lib/logger";
|
||||||
@ -72,7 +72,7 @@ export async function generateOpenAICompletions(
|
|||||||
markdown?: string,
|
markdown?: string,
|
||||||
previousWarning?: string,
|
previousWarning?: string,
|
||||||
isExtractEndpoint?: boolean,
|
isExtractEndpoint?: boolean,
|
||||||
): Promise<{ extract: any; numTokens: number; warning: string | undefined; totalUsage: TokenUsage }> {
|
): Promise<{ extract: any; numTokens: number; warning: string | undefined }> {
|
||||||
let extract: any;
|
let extract: any;
|
||||||
let warning: string | undefined;
|
let warning: string | undefined;
|
||||||
|
|
||||||
@ -208,9 +208,6 @@ export async function generateOpenAICompletions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const promptTokens = (jsonCompletion.usage?.prompt_tokens ?? 0);
|
|
||||||
const completionTokens = (jsonCompletion.usage?.completion_tokens ?? 0);
|
|
||||||
|
|
||||||
// If the users actually wants the items object, they can specify it as 'required' in the schema
|
// If the users actually wants the items object, they can specify it as 'required' in the schema
|
||||||
// otherwise, we just return the items array
|
// otherwise, we just return the items array
|
||||||
if (
|
if (
|
||||||
@ -220,9 +217,7 @@ export async function generateOpenAICompletions(
|
|||||||
) {
|
) {
|
||||||
extract = extract?.items;
|
extract = extract?.items;
|
||||||
}
|
}
|
||||||
// num tokens (just user prompt tokenized) | deprecated
|
return { extract, warning, numTokens };
|
||||||
// totalTokens = promptTokens + completionTokens
|
|
||||||
return { extract, warning, numTokens, totalUsage: { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens, model: model } };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function performLLMExtract(
|
export async function performLLMExtract(
|
||||||
@ -287,22 +282,6 @@ Consider:
|
|||||||
3. Appropriate data types for each field
|
3. Appropriate data types for each field
|
||||||
4. Nested objects and arrays where appropriate
|
4. Nested objects and arrays where appropriate
|
||||||
|
|
||||||
Valid JSON schema, has to be simple. No crazy properties. OpenAI has to support it.
|
|
||||||
Supported types
|
|
||||||
The following types are supported for Structured Outputs:
|
|
||||||
|
|
||||||
String
|
|
||||||
Number
|
|
||||||
Boolean
|
|
||||||
Integer
|
|
||||||
Object
|
|
||||||
Array
|
|
||||||
Enum
|
|
||||||
anyOf
|
|
||||||
|
|
||||||
Formats are not supported. Min/max are not supported. Anything beyond the above is not supported. Keep it simple with types and descriptions.
|
|
||||||
Optionals are not supported.
|
|
||||||
Keep it simple. Don't create too many properties, just the ones that are needed. Don't invent properties.
|
|
||||||
Return a valid JSON schema object with properties that would capture the information requested in the prompt.`,
|
Return a valid JSON schema object with properties that would capture the information requested in the prompt.`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -23,14 +23,12 @@ 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(
|
||||||
@ -38,7 +36,6 @@ 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",
|
||||||
@ -53,12 +50,11 @@ export async function supaBillTeam(
|
|||||||
credits,
|
credits,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, error } = await supabase_service.rpc("bill_team_w_extract_3", {
|
const { data, error } = await supabase_service.rpc("bill_team", {
|
||||||
_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_param: is_extract,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -59,7 +59,6 @@ export async function logJob(job: FirecrawlJob, force: boolean = false) {
|
|||||||
num_tokens: job.num_tokens,
|
num_tokens: job.num_tokens,
|
||||||
retry: !!job.retry,
|
retry: !!job.retry,
|
||||||
crawl_id: job.crawl_id,
|
crawl_id: job.crawl_id,
|
||||||
tokens_billed: job.tokens_billed,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (force) {
|
if (force) {
|
||||||
@ -129,7 +128,6 @@ export async function logJob(job: FirecrawlJob, force: boolean = false) {
|
|||||||
origin: job.origin,
|
origin: job.origin,
|
||||||
num_tokens: job.num_tokens,
|
num_tokens: job.num_tokens,
|
||||||
retry: job.retry,
|
retry: job.retry,
|
||||||
tokens_billed: job.tokens_billed,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (job.mode !== "single_urls") {
|
if (job.mode !== "single_urls") {
|
||||||
|
@ -100,10 +100,6 @@ const RATE_LIMITS = {
|
|||||||
free: 500,
|
free: 500,
|
||||||
default: 5000,
|
default: 5000,
|
||||||
},
|
},
|
||||||
extractStatus: {
|
|
||||||
free: 500,
|
|
||||||
default: 5000,
|
|
||||||
},
|
|
||||||
testSuite: {
|
testSuite: {
|
||||||
free: 10000,
|
free: 10000,
|
||||||
default: 10000,
|
default: 10000,
|
||||||
|
@ -87,7 +87,6 @@ export interface FirecrawlJob {
|
|||||||
num_tokens?: number;
|
num_tokens?: number;
|
||||||
retry?: boolean;
|
retry?: boolean;
|
||||||
crawl_id?: string;
|
crawl_id?: string;
|
||||||
tokens_billed?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FirecrawlScrapeResponse {
|
export interface FirecrawlScrapeResponse {
|
||||||
@ -134,7 +133,6 @@ export enum RateLimiterMode {
|
|||||||
Search = "search",
|
Search = "search",
|
||||||
Map = "map",
|
Map = "map",
|
||||||
Extract = "extract",
|
Extract = "extract",
|
||||||
ExtractStatus = "extractStatus",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AuthResponse =
|
export type AuthResponse =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user