Skip to content

Commit 17c18a1

Browse files
committed
fix(embeddings): classify aggregated batch failures
1 parent 1050e8a commit 17c18a1

3 files changed

Lines changed: 65 additions & 8 deletions

File tree

‎apps/sim/lib/embeddings/api-error.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,18 @@ export class EmbeddingAPIError extends Error {
2020
this.isBYOK = isBYOK
2121
}
2222
}
23+
24+
/** Finds an embedding failure through bounded aggregate/cause wrappers. */
25+
export function getEmbeddingAPIError(error: unknown): EmbeddingAPIError | null {
26+
const pending = [error]
27+
const seen = new Set<unknown>()
28+
while (pending.length > 0 && seen.size < 32) {
29+
const current = pending.pop()
30+
if (!(current instanceof Error) || seen.has(current)) continue
31+
seen.add(current)
32+
if (current instanceof EmbeddingAPIError) return current
33+
if (current.cause !== undefined) pending.push(current.cause)
34+
if (current instanceof AggregateError) pending.push(...current.errors.slice(0, 32))
35+
}
36+
return null
37+
}

‎apps/sim/lib/knowledge/connectors/connector-error.test.ts‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,42 @@ import {
1010
import { ConnectorDirectoryError } from '@/connectors/source-error'
1111

1212
describe('connector failure diagnostics', () => {
13+
it('finds embedding failures through concurrent batches and nested cause wrappers', () => {
14+
const error = new Error('private outer wrapper', {
15+
cause: new AggregateError([
16+
new EmbeddingAPIError('private upstream message', 503),
17+
new Error('private inner wrapper', {
18+
cause: new AggregateError([new EmbeddingAPIError('private upstream message', 503)]),
19+
}),
20+
new Error('private sibling'),
21+
]),
22+
})
23+
expect(getConnectorFailureDiagnostic(error)).toEqual({
24+
category: 'embedding',
25+
status: 503,
26+
message: 'Embedding service request failed (HTTP 503).',
27+
})
28+
})
29+
30+
it('terminates cyclic aggregate wrappers and still finds an embedding sibling', () => {
31+
const error = new AggregateError([])
32+
error.errors.push(new EmbeddingAPIError('private upstream message', 429), error)
33+
error.cause = error
34+
expect(getConnectorFailureDiagnostic(error)).toMatchObject({
35+
category: 'embedding',
36+
status: 429,
37+
})
38+
const cycle = new AggregateError([])
39+
cycle.errors.push(cycle)
40+
expect(getConnectorFailureDiagnostic(cycle)).toBeNull()
41+
})
42+
43+
it('bounds traversal of deeply nested aggregate wrappers', () => {
44+
let error: Error = new EmbeddingAPIError('private upstream message', 503)
45+
for (let i = 0; i < 40; i++) error = new AggregateError([error])
46+
expect(getConnectorFailureDiagnostic(error)).toBeNull()
47+
})
48+
1349
it.each([401, 403, 404, 429, 502, 503])(
1450
'does not attribute a wrapped embedding HTTP %s to the source',
1551
(status) => {

‎apps/sim/lib/knowledge/connectors/connector-error.ts‎

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { findCause, getPostgresErrorCode } from '@sim/utils/errors'
22
import { DrizzleQueryError } from 'drizzle-orm/errors'
3-
import { EmbeddingAPIError } from '@/lib/embeddings/api-error'
3+
import { getEmbeddingAPIError } from '@/lib/embeddings/api-error'
44
import {
55
ConnectorDirectoryError,
66
ConnectorSourceError,
@@ -66,6 +66,19 @@ function classifyFailure(error: unknown): ConnectorFailureDiagnostic | null {
6666
if (databaseError) {
6767
return { category: 'database', message: 'Database request failed without a driver error code.' }
6868
}
69+
const embeddingError = getEmbeddingAPIError(error)
70+
if (
71+
embeddingError &&
72+
Number.isInteger(embeddingError.status) &&
73+
embeddingError.status >= 400 &&
74+
embeddingError.status <= 599
75+
) {
76+
return {
77+
category: 'embedding',
78+
status: embeddingError.status,
79+
message: `Embedding service request failed (HTTP ${embeddingError.status}).`,
80+
}
81+
}
6982
const httpError = findCause(
7083
error,
7184
(value): value is Error & { status: number } =>
@@ -78,13 +91,6 @@ function classifyFailure(error: unknown): ConnectorFailureDiagnostic | null {
7891
)
7992
if (!httpError) return null
8093
const { status } = httpError
81-
if (httpError instanceof EmbeddingAPIError) {
82-
return {
83-
category: 'embedding',
84-
status,
85-
message: `Embedding service request failed (HTTP ${status}).`,
86-
}
87-
}
8894
const category = httpError instanceof ConnectorSourceError ? httpError.category : undefined
8995
if (category === 'authorization' || (!category && (status === 401 || status === 403))) {
9096
return {

0 commit comments

Comments
 (0)