Skip to content

Commit 5ed94de

Browse files
authored
feat: added support for custom JINA_API_URL (#15)
* added jinaApiUrl / JINA_API_URL * test: improve jina-reranker test reliability and type safety - Fix TypeScript linting errors in jina-reranker.test.ts - Improve type casting for private property access - Fix environment variable handling in tests - Replace require import with ES6 import - Add proper null checks for createReranker return value * Revert "test: improve jina-reranker test reliability and type safety" This reverts commit 4ee8448.
1 parent f3cd7ad commit 5ed94de

File tree

4 files changed

+140
-5
lines changed

4 files changed

+140
-5
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { JinaReranker } from './rerankers';
2+
import { createDefaultLogger } from './utils';
3+
4+
describe('JinaReranker', () => {
5+
const mockLogger = createDefaultLogger();
6+
7+
describe('constructor', () => {
8+
it('should use default API URL when no apiUrl is provided', () => {
9+
const reranker = new JinaReranker({
10+
apiKey: 'test-key',
11+
logger: mockLogger,
12+
});
13+
14+
// Access private property for testing
15+
const apiUrl = (reranker as any).apiUrl;
16+
expect(apiUrl).toBe('https://api.jina.ai/v1/rerank');
17+
});
18+
19+
it('should use custom API URL when provided', () => {
20+
const customUrl = 'https://custom-jina-endpoint.com/v1/rerank';
21+
const reranker = new JinaReranker({
22+
apiKey: 'test-key',
23+
apiUrl: customUrl,
24+
logger: mockLogger,
25+
});
26+
27+
const apiUrl = (reranker as any).apiUrl;
28+
expect(apiUrl).toBe(customUrl);
29+
});
30+
31+
it('should use environment variable JINA_API_URL when available', () => {
32+
const originalEnv = process.env.JINA_API_URL;
33+
process.env.JINA_API_URL = 'https://env-jina-endpoint.com/v1/rerank';
34+
35+
const reranker = new JinaReranker({
36+
apiKey: 'test-key',
37+
logger: mockLogger,
38+
});
39+
40+
const apiUrl = (reranker as any).apiUrl;
41+
expect(apiUrl).toBe('https://env-jina-endpoint.com/v1/rerank');
42+
43+
// Restore original environment
44+
if (originalEnv) {
45+
process.env.JINA_API_URL = originalEnv;
46+
} else {
47+
delete process.env.JINA_API_URL;
48+
}
49+
});
50+
51+
it('should prioritize explicit apiUrl over environment variable', () => {
52+
const originalEnv = process.env.JINA_API_URL;
53+
process.env.JINA_API_URL = 'https://env-jina-endpoint.com/v1/rerank';
54+
55+
const customUrl = 'https://explicit-jina-endpoint.com/v1/rerank';
56+
const reranker = new JinaReranker({
57+
apiKey: 'test-key',
58+
apiUrl: customUrl,
59+
logger: mockLogger,
60+
});
61+
62+
const apiUrl = (reranker as any).apiUrl;
63+
expect(apiUrl).toBe(customUrl);
64+
65+
// Restore original environment
66+
if (originalEnv) {
67+
process.env.JINA_API_URL = originalEnv;
68+
} else {
69+
delete process.env.JINA_API_URL;
70+
}
71+
});
72+
});
73+
74+
describe('rerank method', () => {
75+
it('should log the API URL being used', async () => {
76+
const customUrl = 'https://test-jina-endpoint.com/v1/rerank';
77+
const reranker = new JinaReranker({
78+
apiKey: 'test-key',
79+
apiUrl: customUrl,
80+
logger: mockLogger,
81+
});
82+
83+
const logSpy = jest.spyOn(mockLogger, 'debug');
84+
85+
try {
86+
await reranker.rerank('test query', ['document1', 'document2'], 2);
87+
} catch (error) {
88+
// Expected to fail due to missing API key, but we can check the log
89+
}
90+
91+
expect(logSpy).toHaveBeenCalledWith(
92+
expect.stringContaining(`Reranking 2 chunks with Jina using API URL: ${customUrl}`)
93+
);
94+
95+
logSpy.mockRestore();
96+
});
97+
});
98+
});
99+
100+
describe('createReranker', () => {
101+
const { createReranker } = require('./rerankers');
102+
103+
it('should create JinaReranker with jinaApiUrl when provided', () => {
104+
const customUrl = 'https://custom-jina-endpoint.com/v1/rerank';
105+
const reranker = createReranker({
106+
rerankerType: 'jina',
107+
jinaApiKey: 'test-key',
108+
jinaApiUrl: customUrl,
109+
});
110+
111+
expect(reranker).toBeInstanceOf(JinaReranker);
112+
const apiUrl = (reranker as any).apiUrl;
113+
expect(apiUrl).toBe(customUrl);
114+
});
115+
116+
it('should create JinaReranker with default URL when jinaApiUrl is not provided', () => {
117+
const reranker = createReranker({
118+
rerankerType: 'jina',
119+
jinaApiKey: 'test-key',
120+
});
121+
122+
expect(reranker).toBeInstanceOf(JinaReranker);
123+
const apiUrl = (reranker as any).apiUrl;
124+
expect(apiUrl).toBe('https://api.jina.ai/v1/rerank');
125+
});
126+
});

src/tools/search/rerankers.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,28 @@ export abstract class BaseReranker {
2828
}
2929

3030
export class JinaReranker extends BaseReranker {
31+
private apiUrl: string;
32+
3133
constructor({
3234
apiKey = process.env.JINA_API_KEY,
35+
apiUrl = process.env.JINA_API_URL || 'https://api.jina.ai/v1/rerank',
3336
logger,
3437
}: {
3538
apiKey?: string;
39+
apiUrl?: string;
3640
logger?: t.Logger;
3741
}) {
3842
super(logger);
3943
this.apiKey = apiKey;
44+
this.apiUrl = apiUrl;
4045
}
4146

4247
async rerank(
4348
query: string,
4449
documents: string[],
4550
topK: number = 5
4651
): Promise<t.Highlight[]> {
47-
this.logger.debug(`Reranking ${documents.length} chunks with Jina`);
52+
this.logger.debug(`Reranking ${documents.length} chunks with Jina using API URL: ${this.apiUrl}`);
4853

4954
try {
5055
if (this.apiKey == null || this.apiKey === '') {
@@ -61,7 +66,7 @@ export class JinaReranker extends BaseReranker {
6166
};
6267

6368
const response = await axios.post<t.JinaRerankerResponse | undefined>(
64-
'https://api.jina.ai/v1/rerank',
69+
this.apiUrl,
6570
requestData,
6671
{
6772
headers: {
@@ -201,17 +206,18 @@ export class InfinityReranker extends BaseReranker {
201206
export const createReranker = (config: {
202207
rerankerType: t.RerankerType;
203208
jinaApiKey?: string;
209+
jinaApiUrl?: string;
204210
cohereApiKey?: string;
205211
logger?: t.Logger;
206212
}): BaseReranker | undefined => {
207-
const { rerankerType, jinaApiKey, cohereApiKey, logger } = config;
213+
const { rerankerType, jinaApiKey, jinaApiUrl, cohereApiKey, logger } = config;
208214

209215
// Create a default logger if none is provided
210216
const defaultLogger = logger || createDefaultLogger();
211217

212218
switch (rerankerType.toLowerCase()) {
213219
case 'jina':
214-
return new JinaReranker({ apiKey: jinaApiKey, logger: defaultLogger });
220+
return new JinaReranker({ apiKey: jinaApiKey, apiUrl: jinaApiUrl, logger: defaultLogger });
215221
case 'cohere':
216222
return new CohereReranker({
217223
apiKey: cohereApiKey,
@@ -226,7 +232,7 @@ export const createReranker = (config: {
226232
defaultLogger.warn(
227233
`Unknown reranker type: ${rerankerType}. Defaulting to InfinityReranker.`
228234
);
229-
return new JinaReranker({ apiKey: jinaApiKey, logger: defaultLogger });
235+
return new JinaReranker({ apiKey: jinaApiKey, apiUrl: jinaApiUrl, logger: defaultLogger });
230236
}
231237
};
232238

src/tools/search/tool.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ export const createSearchTool = (
349349
firecrawlOptions,
350350
scraperTimeout,
351351
jinaApiKey,
352+
jinaApiUrl,
352353
cohereApiKey,
353354
onSearchResults: _onSearchResults,
354355
onGetHighlights,
@@ -395,6 +396,7 @@ export const createSearchTool = (
395396
const selectedReranker = createReranker({
396397
rerankerType,
397398
jinaApiKey,
399+
jinaApiUrl,
398400
cohereApiKey,
399401
logger,
400402
});

src/tools/search/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ export interface SearchToolConfig
148148
logger?: Logger;
149149
safeSearch?: SafeSearchLevel;
150150
jinaApiKey?: string;
151+
jinaApiUrl?: string;
151152
cohereApiKey?: string;
152153
rerankerType?: RerankerType;
153154
scraperTimeout?: number;

0 commit comments

Comments
 (0)