vocs 2.7.2 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/internal/embedding.d.ts.map +1 -1
- package/dist/internal/embedding.js +5 -1
- package/dist/internal/embedding.js.map +1 -1
- package/dist/internal/mdx.d.ts +15 -0
- package/dist/internal/mdx.d.ts.map +1 -1
- package/dist/internal/mdx.js +140 -0
- package/dist/internal/mdx.js.map +1 -1
- package/dist/internal/promise.d.ts +10 -0
- package/dist/internal/promise.d.ts.map +1 -0
- package/dist/internal/promise.js +30 -0
- package/dist/internal/promise.js.map +1 -0
- package/dist/internal/reranker.d.ts.map +1 -1
- package/dist/internal/reranker.js +5 -1
- package/dist/internal/reranker.js.map +1 -1
- package/dist/internal/vector-store.d.ts.map +1 -1
- package/dist/internal/vector-store.js +5 -1
- package/dist/internal/vector-store.js.map +1 -1
- package/dist/mdx.d.ts.map +1 -1
- package/dist/mdx.js +3 -0
- package/dist/mdx.js.map +1 -1
- package/dist/react/internal/Benchmarks.mdx.d.ts +9 -0
- package/dist/react/internal/Benchmarks.mdx.d.ts.map +1 -0
- package/dist/react/internal/Benchmarks.mdx.js +6 -0
- package/dist/react/internal/Benchmarks.mdx.js.map +1 -0
- package/dist/server/openapi/assets.generated.js +3 -3
- package/dist/server/openapi/assets.generated.js.map +1 -1
- package/dist/styles/markdown.css +93 -0
- package/package.json +1 -1
- package/src/internal/embedding.ts +21 -12
- package/src/internal/mdx.test.ts +195 -0
- package/src/internal/mdx.ts +150 -0
- package/src/internal/promise.test.ts +90 -0
- package/src/internal/promise.ts +41 -0
- package/src/internal/reranker.ts +22 -13
- package/src/internal/vector-store.ts +18 -9
- package/src/mdx.tsx +2 -0
- package/src/react/internal/Benchmarks.mdx.tsx +30 -0
- package/src/server/openapi/assets.generated.ts +3 -3
- package/src/styles/markdown.css +93 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import * as promise from './promise.js'
|
|
3
|
+
|
|
4
|
+
afterEach(() => vi.useRealTimers())
|
|
5
|
+
|
|
6
|
+
describe('withRetry', () => {
|
|
7
|
+
it('retries failures', async () => {
|
|
8
|
+
const fn = vi
|
|
9
|
+
.fn<() => Promise<string>>()
|
|
10
|
+
.mockRejectedValueOnce(new Error('failed'))
|
|
11
|
+
.mockRejectedValueOnce(new Error('failed'))
|
|
12
|
+
.mockResolvedValueOnce('ok')
|
|
13
|
+
|
|
14
|
+
await expect(promise.withRetry(fn, { baseMs: 0 })).resolves.toBe('ok')
|
|
15
|
+
expect(fn).toHaveBeenCalledTimes(3)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('throws after exhausting retries', async () => {
|
|
19
|
+
const error = new Error('failed')
|
|
20
|
+
const fn = vi.fn().mockRejectedValue(error)
|
|
21
|
+
|
|
22
|
+
await expect(promise.withRetry(fn, { baseMs: 0 })).rejects.toBe(error)
|
|
23
|
+
expect(fn).toHaveBeenCalledTimes(3)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('only retries matching failures', async () => {
|
|
27
|
+
const error = new Error('invalid request')
|
|
28
|
+
const fn = vi.fn().mockRejectedValue(error)
|
|
29
|
+
|
|
30
|
+
await expect(
|
|
31
|
+
promise.withRetry(fn, {
|
|
32
|
+
baseMs: 0,
|
|
33
|
+
shouldRetry: (error) => error instanceof TypeError,
|
|
34
|
+
}),
|
|
35
|
+
).rejects.toBe(error)
|
|
36
|
+
expect(fn).toHaveBeenCalledOnce()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('stops retrying when aborted', async () => {
|
|
40
|
+
const controller = new AbortController()
|
|
41
|
+
const fn = vi.fn().mockImplementation(async () => {
|
|
42
|
+
controller.abort(new Error('aborted'))
|
|
43
|
+
throw new Error('failed')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
await expect(
|
|
47
|
+
promise.withRetry(fn, {
|
|
48
|
+
baseMs: 0,
|
|
49
|
+
signal: controller.signal,
|
|
50
|
+
}),
|
|
51
|
+
).rejects.toThrow('failed')
|
|
52
|
+
expect(fn).toHaveBeenCalledOnce()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('cancels the retry delay when aborted', async () => {
|
|
56
|
+
const controller = new AbortController()
|
|
57
|
+
const error = new Error('aborted')
|
|
58
|
+
const fn = vi.fn().mockRejectedValue(new Error('failed'))
|
|
59
|
+
const retry = promise.withRetry(fn, {
|
|
60
|
+
baseMs: 100,
|
|
61
|
+
signal: controller.signal,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
setTimeout(() => controller.abort(error), 0)
|
|
65
|
+
|
|
66
|
+
await expect(retry).rejects.toBe(error)
|
|
67
|
+
expect(fn).toHaveBeenCalledOnce()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('backs off exponentially', async () => {
|
|
71
|
+
vi.useFakeTimers()
|
|
72
|
+
const fn = vi
|
|
73
|
+
.fn<() => Promise<string>>()
|
|
74
|
+
.mockRejectedValueOnce(new Error('failed'))
|
|
75
|
+
.mockRejectedValueOnce(new Error('failed'))
|
|
76
|
+
.mockResolvedValueOnce('ok')
|
|
77
|
+
const retry = promise.withRetry(fn, { baseMs: 100 })
|
|
78
|
+
|
|
79
|
+
await vi.advanceTimersByTimeAsync(99)
|
|
80
|
+
expect(fn).toHaveBeenCalledOnce()
|
|
81
|
+
await vi.advanceTimersByTimeAsync(1)
|
|
82
|
+
expect(fn).toHaveBeenCalledTimes(2)
|
|
83
|
+
await vi.advanceTimersByTimeAsync(199)
|
|
84
|
+
expect(fn).toHaveBeenCalledTimes(2)
|
|
85
|
+
await vi.advanceTimersByTimeAsync(1)
|
|
86
|
+
|
|
87
|
+
await expect(retry).resolves.toBe('ok')
|
|
88
|
+
expect(fn).toHaveBeenCalledTimes(3)
|
|
89
|
+
})
|
|
90
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export async function withRetry<value>(
|
|
2
|
+
fn: () => Promise<value>,
|
|
3
|
+
options: withRetry.Options = {},
|
|
4
|
+
): Promise<value> {
|
|
5
|
+
const { baseMs = 250, retries = 2, shouldRetry = () => true, signal } = options
|
|
6
|
+
for (let attempt = 0; ; attempt++) {
|
|
7
|
+
try {
|
|
8
|
+
return await fn()
|
|
9
|
+
} catch (error) {
|
|
10
|
+
if (signal?.aborted || attempt >= retries || !shouldRetry(error)) throw error
|
|
11
|
+
await wait(baseMs * 2 ** attempt, signal)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export declare namespace withRetry {
|
|
17
|
+
type Options = {
|
|
18
|
+
baseMs?: number | undefined
|
|
19
|
+
retries?: number | undefined
|
|
20
|
+
shouldRetry?: ((error: unknown) => boolean) | undefined
|
|
21
|
+
signal?: AbortSignal | null | undefined
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function wait(delay: number, signal?: AbortSignal | null): Promise<void> {
|
|
26
|
+
if (signal?.aborted) throw signal.reason
|
|
27
|
+
await new Promise<void>((resolve, reject) => {
|
|
28
|
+
const timeout = setTimeout(done, delay)
|
|
29
|
+
signal?.addEventListener('abort', aborted, { once: true })
|
|
30
|
+
|
|
31
|
+
function aborted() {
|
|
32
|
+
clearTimeout(timeout)
|
|
33
|
+
reject(signal?.reason)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function done() {
|
|
37
|
+
signal?.removeEventListener('abort', aborted)
|
|
38
|
+
resolve()
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
}
|
package/src/internal/reranker.ts
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* private `_localRetriever` config — never in serializable config sent to the browser.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import * as promise from './promise.js'
|
|
19
|
+
|
|
18
20
|
export type RerankContext = {
|
|
19
21
|
/** Optional abort signal to cancel the request. */
|
|
20
22
|
signal?: AbortSignal | undefined
|
|
@@ -111,20 +113,27 @@ export function cloudflare(options: cloudflare.Options = {}): Adapter {
|
|
|
111
113
|
if (!apiToken)
|
|
112
114
|
throw new Error('[vocs] Reranker.cloudflare: missing `apiToken` (or CLOUDFLARE_API_TOKEN).')
|
|
113
115
|
const url = `${baseUrl.replace(/\/$/, '')}/accounts/${accountId}/ai/run/${model}`
|
|
114
|
-
const response = await
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
const response = await promise.withRetry(
|
|
117
|
+
() =>
|
|
118
|
+
fetch(url, {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: {
|
|
121
|
+
Authorization: `Bearer ${apiToken}`,
|
|
122
|
+
'Content-Type': 'application/json',
|
|
123
|
+
...headers,
|
|
124
|
+
},
|
|
125
|
+
body: JSON.stringify({
|
|
126
|
+
query,
|
|
127
|
+
contexts: documents.map((text) => ({ text })),
|
|
128
|
+
...(context.topK ? { top_k: context.topK } : {}),
|
|
129
|
+
}),
|
|
130
|
+
signal: context.signal ?? null,
|
|
131
|
+
}),
|
|
132
|
+
{
|
|
133
|
+
shouldRetry: (error) => error instanceof TypeError,
|
|
134
|
+
signal: context.signal,
|
|
120
135
|
},
|
|
121
|
-
|
|
122
|
-
query,
|
|
123
|
-
contexts: documents.map((text) => ({ text })),
|
|
124
|
-
...(context.topK ? { top_k: context.topK } : {}),
|
|
125
|
-
}),
|
|
126
|
-
signal: context.signal ?? null,
|
|
127
|
-
})
|
|
136
|
+
)
|
|
128
137
|
if (!response.ok)
|
|
129
138
|
throw new Error(
|
|
130
139
|
`[vocs] cloudflare rerank failed (${response.status}): ${await safeText(response)}`,
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* runtime, so no index artifact ships with the server bundle.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import * as promise from './promise.js'
|
|
18
|
+
|
|
17
19
|
export type Format = 'float32' | 'int8'
|
|
18
20
|
|
|
19
21
|
export type Adapter = StaticAdapter | RemoteAdapter
|
|
@@ -170,16 +172,23 @@ export function cloudflare(options: cloudflare.Options = {}): RemoteAdapter {
|
|
|
170
172
|
'[vocs] VectorStore.cloudflare: missing `apiToken` (or CLOUDFLARE_API_TOKEN).',
|
|
171
173
|
)
|
|
172
174
|
const url = `${baseUrl.replace(/\/$/, '')}/accounts/${accountId}/vectorize/v2/indexes${path}`
|
|
173
|
-
return await
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
return await promise.withRetry(
|
|
176
|
+
() =>
|
|
177
|
+
fetch(url, {
|
|
178
|
+
method: init.method ?? 'GET',
|
|
179
|
+
headers: {
|
|
180
|
+
Authorization: `Bearer ${apiToken}`,
|
|
181
|
+
...(init.contentType ? { 'Content-Type': init.contentType } : {}),
|
|
182
|
+
...headers,
|
|
183
|
+
},
|
|
184
|
+
...(init.body !== undefined ? { body: init.body } : {}),
|
|
185
|
+
signal: init.signal ?? null,
|
|
186
|
+
}),
|
|
187
|
+
{
|
|
188
|
+
shouldRetry: (error) => error instanceof TypeError,
|
|
189
|
+
signal: init.signal,
|
|
179
190
|
},
|
|
180
|
-
|
|
181
|
-
signal: init.signal ?? null,
|
|
182
|
-
})
|
|
191
|
+
)
|
|
183
192
|
}
|
|
184
193
|
|
|
185
194
|
/** Creates the index when missing; validates dimensions when present. */
|
package/src/mdx.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MDXComponents } from 'mdx/types.js'
|
|
2
2
|
|
|
3
3
|
import { Callout } from './react/Callout.js'
|
|
4
|
+
import { Benchmarks } from './react/internal/Benchmarks.mdx.js'
|
|
4
5
|
import { Changelog } from './react/internal/Changelog.mdx.js'
|
|
5
6
|
import { CodeBlock } from './react/internal/CodeBlock.mdx.js'
|
|
6
7
|
import { CodeGroup } from './react/internal/CodeGroup.mdx.js'
|
|
@@ -36,6 +37,7 @@ export const components: MDXComponents = {
|
|
|
36
37
|
}
|
|
37
38
|
>,
|
|
38
39
|
) {
|
|
40
|
+
if ('data-v-benchmarks' in props) return <Benchmarks {...props} />
|
|
39
41
|
if ('data-v-changelog' in props) return <Changelog {...props} />
|
|
40
42
|
if ('data-v-code-group' in props) return <CodeGroup {...props} />
|
|
41
43
|
if ('data-v-file-tree' in props) return <FileTree {...props} />
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type * as React from 'react'
|
|
2
|
+
|
|
3
|
+
export function Benchmarks(props: Benchmarks.Props) {
|
|
4
|
+
const { children, legend, scalar: _scalar, ...rest } = props
|
|
5
|
+
return (
|
|
6
|
+
<div {...rest} data-v data-v-benchmarks>
|
|
7
|
+
{children}
|
|
8
|
+
{legend !== 'false' && (
|
|
9
|
+
<footer data-v-benchmarks-legend>
|
|
10
|
+
<span>Faster</span>
|
|
11
|
+
<span data-v-benchmarks-legend-scale />
|
|
12
|
+
<span>Slower</span>
|
|
13
|
+
<span data-v-benchmarks-legend-baseline>
|
|
14
|
+
<span data-v-benchmarks-legend-swatch />
|
|
15
|
+
baseline
|
|
16
|
+
</span>
|
|
17
|
+
</footer>
|
|
18
|
+
)}
|
|
19
|
+
</div>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export declare namespace Benchmarks {
|
|
24
|
+
type Props = React.PropsWithChildren<
|
|
25
|
+
React.ComponentProps<'div'> & {
|
|
26
|
+
legend?: string | undefined
|
|
27
|
+
scalar?: string | undefined
|
|
28
|
+
}
|
|
29
|
+
>
|
|
30
|
+
}
|