From 6f6a34d126ffd9d4eacf53a32ae22021e2d09b3e Mon Sep 17 00:00:00 2001 From: Daniel Andersen Date: Sat, 6 Sep 2025 14:39:20 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=97=20feat:=20Custom=20Jina=20API=20UR?= =?UTF-8?q?L=20for=20Web=20Search=20Reranking=20(#9236)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: added support for custom JINA_API_URL * fixed tests * chore: Update @librechat/agents dependency to version 2.4.77 in package-lock.json and package.json files * fix: Update Jina API URL to use environment variable in configuration files * Refactor AppService, web.ts, and config.ts to replace hardcoded Jina API URL with an environment variable placeholder. * Ensure consistency across tests and configuration for Jina API URL. * chore: alphabetical order translation.json * fix: alphabetical order --------- Co-authored-by: Danny Avila --- README.md | 1 + api/package.json | 2 +- api/server/services/AppService.spec.js | 1 + .../SidePanel/Agents/Search/ApiKeyDialog.tsx | 8 ++++++++ client/src/hooks/Plugins/useAuthSearchTool.ts | 2 ++ client/src/locales/en/translation.json | 2 ++ librechat.example.yaml | 18 ++++++++++++++++++ package-lock.json | 10 +++++----- packages/api/package.json | 2 +- packages/api/src/web/web.spec.ts | 19 +++++++++++++++++++ packages/api/src/web/web.ts | 9 ++++++++- packages/data-provider/src/config.ts | 1 + packages/data-provider/src/types/web.ts | 1 + 13 files changed, 68 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ff8bb96c7..e30101280 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ - 🔍 **Web Search**: - Search the internet and retrieve relevant information to enhance your AI context - Combines search providers, content scrapers, and result rerankers for optimal results + - **Customizable Jina Reranking**: Configure custom Jina API URLs for reranking services - **[Learn More →](https://www.librechat.ai/docs/features/web_search)** - 🪄 **Generative UI with Code Artifacts**: diff --git a/api/package.json b/api/package.json index ffe3ac1e8..1f496b5b2 100644 --- a/api/package.json +++ b/api/package.json @@ -49,7 +49,7 @@ "@langchain/google-vertexai": "^0.2.13", "@langchain/openai": "^0.5.18", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.4.76", + "@librechat/agents": "^2.4.77", "@librechat/api": "*", "@librechat/data-schemas": "*", "@microsoft/microsoft-graph-client": "^3.0.7", diff --git a/api/server/services/AppService.spec.js b/api/server/services/AppService.spec.js index 972c685bf..6243164ed 100644 --- a/api/server/services/AppService.spec.js +++ b/api/server/services/AppService.spec.js @@ -152,6 +152,7 @@ describe('AppService', () => { webSearch: expect.objectContaining({ safeSearch: 1, jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', cohereApiKey: '${COHERE_API_KEY}', serperApiKey: '${SERPER_API_KEY}', searxngApiKey: '${SEARXNG_API_KEY}', diff --git a/client/src/components/SidePanel/Agents/Search/ApiKeyDialog.tsx b/client/src/components/SidePanel/Agents/Search/ApiKeyDialog.tsx index ceda0ab34..84d0a30a9 100644 --- a/client/src/components/SidePanel/Agents/Search/ApiKeyDialog.tsx +++ b/client/src/components/SidePanel/Agents/Search/ApiKeyDialog.tsx @@ -91,6 +91,14 @@ export default function ApiKeyDialog({ text: localize('com_ui_web_search_reranker_jina_key'), }, }, + jinaApiUrl: { + placeholder: localize('com_ui_web_search_jina_url'), + type: 'text' as const, + link: { + url: 'https://api.jina.ai/v1/rerank', + text: localize('com_ui_web_search_reranker_jina_url_help'), + }, + }, }, }, { diff --git a/client/src/hooks/Plugins/useAuthSearchTool.ts b/client/src/hooks/Plugins/useAuthSearchTool.ts index 4b9649ac2..bd5f41fe7 100644 --- a/client/src/hooks/Plugins/useAuthSearchTool.ts +++ b/client/src/hooks/Plugins/useAuthSearchTool.ts @@ -15,6 +15,7 @@ export type SearchApiKeyFormData = { firecrawlApiKey: string; firecrawlApiUrl: string; jinaApiKey: string; + jinaApiUrl: string; cohereApiKey: string; }; @@ -54,6 +55,7 @@ const useAuthSearchTool = (options?: { isEntityTool: boolean }) => { firecrawlApiKey: data.firecrawlApiKey, firecrawlApiUrl: data.firecrawlApiUrl, jinaApiKey: data.jinaApiKey, + jinaApiUrl: data.jinaApiUrl, cohereApiKey: data.cohereApiKey, }).reduce( (acc, [key, value]) => { diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 5cc1140b4..2e67a4b0b 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -1251,6 +1251,7 @@ "com_ui_web_search_cohere_key": "Enter Cohere API Key", "com_ui_web_search_firecrawl_url": "Firecrawl API URL (optional)", "com_ui_web_search_jina_key": "Enter Jina API Key", + "com_ui_web_search_jina_url": "Jina API URL (optional)", "com_ui_web_search_processing": "Processing results", "com_ui_web_search_provider": "Search Provider", "com_ui_web_search_provider_searxng": "SearXNG", @@ -1262,6 +1263,7 @@ "com_ui_web_search_reranker_cohere_key": "Get your Cohere API key", "com_ui_web_search_reranker_jina": "Jina AI", "com_ui_web_search_reranker_jina_key": "Get your Jina API key", + "com_ui_web_search_reranker_jina_url_help": "Learn about Jina Rerank API", "com_ui_web_search_scraper": "Scraper", "com_ui_web_search_scraper_firecrawl": "Firecrawl API", "com_ui_web_search_scraper_firecrawl_key": "Get your Firecrawl API key", diff --git a/librechat.example.yaml b/librechat.example.yaml index 62824f9c3..6f034910d 100644 --- a/librechat.example.yaml +++ b/librechat.example.yaml @@ -350,6 +350,24 @@ endpoints: # # See the Custom Configuration Guide for more information on Assistants Config: # # https://www.librechat.ai/docs/configuration/librechat_yaml/object_structure/assistants_endpoint +# Web Search Configuration (optional) +# webSearch: +# # Jina Reranking Configuration +# jinaApiKey: '${JINA_API_KEY}' # Your Jina API key +# jinaApiUrl: '${JINA_API_URL}' # Custom Jina API URL (optional, defaults to https://api.jina.ai/v1/rerank) +# +# # Other rerankers +# cohereApiKey: '${COHERE_API_KEY}' +# +# # Search providers +# serperApiKey: '${SERPER_API_KEY}' +# searxngInstanceUrl: '${SEARXNG_INSTANCE_URL}' +# searxngApiKey: '${SEARXNG_API_KEY}' +# +# # Content scrapers +# firecrawlApiKey: '${FIRECRAWL_API_KEY}' +# firecrawlApiUrl: '${FIRECRAWL_API_URL}' + # Memory configuration for user memories # memory: # # (optional) Disable memory functionality diff --git a/package-lock.json b/package-lock.json index 510e14168..6890e357e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,7 +64,7 @@ "@langchain/google-vertexai": "^0.2.13", "@langchain/openai": "^0.5.18", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.4.76", + "@librechat/agents": "^2.4.77", "@librechat/api": "*", "@librechat/data-schemas": "*", "@microsoft/microsoft-graph-client": "^3.0.7", @@ -21909,9 +21909,9 @@ } }, "node_modules/@librechat/agents": { - "version": "2.4.76", - "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.76.tgz", - "integrity": "sha512-DkWKpKcLgv9tA6bXJ8pSzHOA3iZRFQRt9oBjEEeW0onhEdPTmHVR3/dY5bxMKSP8rlA65M0yx1KaoLL8bhg06Q==", + "version": "2.4.77", + "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.77.tgz", + "integrity": "sha512-x7fWbbdJpy8VpIYJa7E0laBUmtgveTmTzYS8QFkXUMjzqSx7nN5ruM6rzmcodOWRXt7IrB12k4VehJ1zUnb29A==", "license": "MIT", "dependencies": { "@langchain/anthropic": "^0.3.26", @@ -51972,7 +51972,7 @@ }, "peerDependencies": { "@langchain/core": "^0.3.62", - "@librechat/agents": "^2.4.76", + "@librechat/agents": "^2.4.77", "@librechat/data-schemas": "*", "@modelcontextprotocol/sdk": "^1.17.1", "axios": "^1.8.2", diff --git a/packages/api/package.json b/packages/api/package.json index af055ef61..79e97f9f7 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -74,7 +74,7 @@ }, "peerDependencies": { "@langchain/core": "^0.3.62", - "@librechat/agents": "^2.4.76", + "@librechat/agents": "^2.4.77", "@librechat/data-schemas": "*", "@modelcontextprotocol/sdk": "^1.17.1", "axios": "^1.8.2", diff --git a/packages/api/src/web/web.spec.ts b/packages/api/src/web/web.spec.ts index 76e67a6fd..b91b53490 100644 --- a/packages/api/src/web/web.spec.ts +++ b/packages/api/src/web/web.spec.ts @@ -81,6 +81,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', cohereApiKey: '${COHERE_API_KEY}', safeSearch: SafeSearchTypes.MODERATE, }; @@ -300,6 +301,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', cohereApiKey: '${COHERE_API_KEY}', safeSearch: SafeSearchTypes.MODERATE, }; @@ -351,6 +353,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', cohereApiKey: '${COHERE_API_KEY}', safeSearch: SafeSearchTypes.MODERATE, // Specify which services to use @@ -432,6 +435,7 @@ describe('web.ts', () => { CUSTOM_FIRECRAWL_URL: 'https://custom.firecrawl.dev', CUSTOM_JINA_KEY: 'custom-jina-key', CUSTOM_COHERE_KEY: 'custom-cohere-key', + CUSTOM_JINA_URL: 'https://custom.jina.ai', }; // Initialize webSearchConfig with custom variable names @@ -442,6 +446,7 @@ describe('web.ts', () => { firecrawlApiKey: '${CUSTOM_FIRECRAWL_KEY}', firecrawlApiUrl: '${CUSTOM_FIRECRAWL_URL}', jinaApiKey: '${CUSTOM_JINA_KEY}', + jinaApiUrl: '${CUSTOM_JINA_URL}', cohereApiKey: '${CUSTOM_COHERE_KEY}', safeSearch: SafeSearchTypes.MODERATE, // Specify which services to use @@ -512,6 +517,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', cohereApiKey: '${COHERE_API_KEY}', safeSearch: SafeSearchTypes.MODERATE, }; @@ -573,6 +579,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', cohereApiKey: '${COHERE_API_KEY}', safeSearch: SafeSearchTypes.MODERATE, }; @@ -682,6 +689,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', cohereApiKey: '${COHERE_API_KEY}', safeSearch: SafeSearchTypes.MODERATE, searchProvider: 'serper' as SearchProviders, @@ -722,6 +730,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', cohereApiKey: '${COHERE_API_KEY}', safeSearch: SafeSearchTypes.MODERATE, scraperType: 'firecrawl' as ScraperTypes, @@ -762,6 +771,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', cohereApiKey: '${COHERE_API_KEY}', safeSearch: SafeSearchTypes.MODERATE, rerankerType: 'jina' as RerankerTypes, @@ -808,6 +818,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', cohereApiKey: '${COHERE_API_KEY}', safeSearch: SafeSearchTypes.MODERATE, searchProvider: 'invalid-provider' as SearchProviders, @@ -842,6 +853,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', cohereApiKey: '${COHERE_API_KEY}', safeSearch: SafeSearchTypes.MODERATE, rerankerType: 'jina' as RerankerTypes, @@ -892,6 +904,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', cohereApiKey: '${COHERE_API_KEY}', safeSearch: SafeSearchTypes.MODERATE, }; @@ -932,6 +945,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', cohereApiKey: '${COHERE_API_KEY}', safeSearch: SafeSearchTypes.MODERATE, firecrawlOptions: { @@ -991,6 +1005,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', safeSearch: SafeSearchTypes.MODERATE, scraperTimeout: 15000, // This should take priority firecrawlOptions: { @@ -1032,6 +1047,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', safeSearch: SafeSearchTypes.MODERATE, firecrawlOptions: { includeTags: ['p'], @@ -1070,6 +1086,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', safeSearch: SafeSearchTypes.MODERATE, firecrawlOptions: { timeout: 12000, // Only timeout provided @@ -1106,6 +1123,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', safeSearch: SafeSearchTypes.MODERATE, firecrawlOptions: { formats: ['html', 'markdown'], // Only formats provided @@ -1142,6 +1160,7 @@ describe('web.ts', () => { firecrawlApiKey: '${FIRECRAWL_API_KEY}', firecrawlApiUrl: '${FIRECRAWL_API_URL}', jinaApiKey: '${JINA_API_KEY}', + jinaApiUrl: '${JINA_API_URL}', safeSearch: SafeSearchTypes.MODERATE, firecrawlOptions: { timeout: 8000, diff --git a/packages/api/src/web/web.ts b/packages/api/src/web/web.ts index 6e57226a1..681a42e34 100644 --- a/packages/api/src/web/web.ts +++ b/packages/api/src/web/web.ts @@ -21,6 +21,7 @@ export function loadWebSearchConfig( const firecrawlApiKey = config?.firecrawlApiKey ?? '${FIRECRAWL_API_KEY}'; const firecrawlApiUrl = config?.firecrawlApiUrl ?? '${FIRECRAWL_API_URL}'; const jinaApiKey = config?.jinaApiKey ?? '${JINA_API_KEY}'; + const jinaApiUrl = config?.jinaApiUrl ?? '${JINA_API_URL}'; const cohereApiKey = config?.cohereApiKey ?? '${COHERE_API_KEY}'; const safeSearch = config?.safeSearch ?? SafeSearchTypes.MODERATE; @@ -28,6 +29,7 @@ export function loadWebSearchConfig( ...config, safeSearch, jinaApiKey, + jinaApiUrl, cohereApiKey, serperApiKey, searxngInstanceUrl, @@ -44,6 +46,7 @@ export type TWebSearchKeys = | 'firecrawlApiKey' | 'firecrawlApiUrl' | 'jinaApiKey' + | 'jinaApiUrl' | 'cohereApiKey'; export type TWebSearchCategories = @@ -70,7 +73,11 @@ export const webSearchAuth = { }, }, rerankers: { - jina: { jinaApiKey: 1 as const }, + jina: { + jinaApiKey: 1 as const, + /** Optional (0) */ + jinaApiUrl: 0 as const, + }, cohere: { cohereApiKey: 1 as const }, }, }; diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index d1d8e4efc..05079e708 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -708,6 +708,7 @@ export const webSearchSchema = z.object({ firecrawlApiKey: z.string().optional().default('${FIRECRAWL_API_KEY}'), firecrawlApiUrl: z.string().optional().default('${FIRECRAWL_API_URL}'), jinaApiKey: z.string().optional().default('${JINA_API_KEY}'), + jinaApiUrl: z.string().optional().default('${JINA_API_URL}'), cohereApiKey: z.string().optional().default('${COHERE_API_KEY}'), searchProvider: z.nativeEnum(SearchProviders).optional(), scraperType: z.nativeEnum(ScraperTypes).optional(), diff --git a/packages/data-provider/src/types/web.ts b/packages/data-provider/src/types/web.ts index 01cebc3df..26a9b7168 100644 --- a/packages/data-provider/src/types/web.ts +++ b/packages/data-provider/src/types/web.ts @@ -178,6 +178,7 @@ export interface SearchToolConfig extends SearchConfig, ProcessSourcesConfig, Fi logger?: Logger; safeSearch?: SafeSearchLevel; jinaApiKey?: string; + jinaApiUrl?: string; cohereApiKey?: string; rerankerType?: RerankerType; onSearchResults?: (results: SearchResult, runnableConfig?: RunnableConfig) => void;