-
Notifications
You must be signed in to change notification settings - Fork 3.5k
feat(knowledge): add Live sync option to KB connectors + fix embedding billing #3955
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
Changes from 1 commit
d290e06
f016eb3
3b3b5b7
cad0c2c
faf3799
175de7c
61cfb7e
63c5fdf
5f6b46d
1bd026a
144b056
e92e3c9
80e97aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
Adds billing tracking to the KB embedding pipeline, which was previously generating OpenAI API calls with no cost recorded. Token counts are now captured from the actual API response and recorded via recordUsage after successful embedding insertion. BYOK workspaces are excluded from billing. Applies to all execution paths: direct, BullMQ, and Trigger.dev.
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,9 +25,10 @@ import { | |
| type SQL, | ||
| sql, | ||
| } from 'drizzle-orm' | ||
| import { recordUsage } from '@/lib/billing/core/usage-log' | ||
| import { createBullMQJobData, isBullMQEnabled } from '@/lib/core/bullmq' | ||
| import { env } from '@/lib/core/config/env' | ||
| import { isTriggerDevEnabled } from '@/lib/core/config/feature-flags' | ||
| import { getCostMultiplier, isTriggerDevEnabled } from '@/lib/core/config/feature-flags' | ||
| import { enqueueWorkspaceDispatch } from '@/lib/core/workspace-dispatch' | ||
| import { processDocument } from '@/lib/knowledge/documents/document-processor' | ||
| import type { DocumentSortField, SortOrder } from '@/lib/knowledge/documents/types' | ||
|
|
@@ -43,6 +44,7 @@ import type { ProcessedDocumentTags } from '@/lib/knowledge/types' | |
| import { deleteFile } from '@/lib/uploads/core/storage-service' | ||
| import { extractStorageKey } from '@/lib/uploads/utils/file-utils' | ||
| import type { DocumentProcessingPayload } from '@/background/knowledge-processing' | ||
| import { getEmbeddingModelPricing } from '@/providers/models' | ||
|
|
||
| const logger = createLogger('DocumentService') | ||
|
|
||
|
|
@@ -460,6 +462,9 @@ export async function processDocumentAsync( | |
| overlap: rawConfig?.overlap ?? 200, | ||
| } | ||
|
|
||
| let totalEmbeddingTokens = 0 | ||
| let embeddingIsBYOK = false | ||
|
|
||
| await withTimeout( | ||
| (async () => { | ||
| const processed = await processDocument( | ||
|
|
@@ -500,10 +505,16 @@ export async function processDocumentAsync( | |
| const batchNum = Math.floor(i / batchSize) + 1 | ||
|
|
||
| logger.info(`[${documentId}] Processing embedding batch ${batchNum}/${totalBatches}`) | ||
| const batchEmbeddings = await generateEmbeddings(batch, undefined, kb[0].workspaceId) | ||
| const { | ||
| embeddings: batchEmbeddings, | ||
| totalTokens: batchTokens, | ||
| isBYOK, | ||
| } = await generateEmbeddings(batch, undefined, kb[0].workspaceId) | ||
| for (const emb of batchEmbeddings) { | ||
| embeddings.push(emb) | ||
| } | ||
| totalEmbeddingTokens += batchTokens | ||
| embeddingIsBYOK = isBYOK | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -638,6 +649,34 @@ export async function processDocumentAsync( | |
|
|
||
| const processingTime = Date.now() - startTime | ||
| logger.info(`[${documentId}] Successfully processed document in ${processingTime}ms`) | ||
|
|
||
| if (!embeddingIsBYOK && totalEmbeddingTokens > 0 && kb[0].userId) { | ||
| try { | ||
| const embeddingModel = 'text-embedding-3-small' | ||
| const pricing = getEmbeddingModelPricing(embeddingModel) | ||
| if (pricing) { | ||
| const cost = (totalEmbeddingTokens / 1_000_000) * pricing.input * getCostMultiplier() | ||
| await recordUsage({ | ||
| userId: kb[0].userId, | ||
| workspaceId: kb[0].workspaceId ?? undefined, | ||
| entries: [ | ||
| { | ||
| category: 'model', | ||
| source: 'knowledge-base', | ||
| description: embeddingModel, | ||
| cost, | ||
| metadata: { inputTokens: totalEmbeddingTokens, outputTokens: 0 }, | ||
| }, | ||
| ], | ||
| additionalStats: { | ||
| totalTokensUsed: sql`total_tokens_used + ${totalEmbeddingTokens}`, | ||
| }, | ||
| }) | ||
| } | ||
| } catch (billingError) { | ||
| logger.error(`[${documentId}] Failed to record embedding usage`, { error: billingError }) | ||
| } | ||
| } | ||
| } catch (error) { | ||
| const processingTime = Date.now() - startTime | ||
| const errorMessage = error instanceof Error ? error.message : 'Unknown error' | ||
|
Comment on lines
655
to
699
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The billing block sits after This is an under-billing edge case rather than a runtime error, and the current structure is intentional to keep billing concerns separate from error handling. Worth adding a comment so the next developer understands why partial completions aren't billed, e.g.: // Note: billing only fires on full success; tokens consumed in a timed-out or
// error-aborted run are not recorded. This is an intentional under-billing
// tradeoff to keep the billing path separate from error paths. |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Embedding billing skipped for individual chunk create/update operations
Medium Severity
The
generateEmbeddingscall increateChunkandupdateChunknow returns{ embeddings, totalTokens, isBYOK, modelName }, but onlyembeddingsis destructured. ThetotalTokens,isBYOK, andmodelNamevalues are discarded, so no billing is recorded for embeddings generated during manual chunk creation or content updates. The document processing path indocuments/service.tscorrectly uses these values to callrecordUsage, but the chunk service paths silently skip billing for the same embedding API calls.Additional Locations (1)
apps/sim/lib/knowledge/chunks/service.ts#L361-L362Reviewed by Cursor Bugbot for commit 80e97aa. Configure here.