🔗 feat: Custom Jina API URL for Web Search Reranking (#9236)

* 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 <danny@librechat.ai>
This commit is contained in:
Daniel Andersen 2025-09-06 14:39:20 +02:00 committed by GitHub
parent fff1f1cf27
commit 6f6a34d126
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 68 additions and 8 deletions

View file

@ -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**:

View file

@ -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",

View file

@ -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}',

View file

@ -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'),
},
},
},
},
{

View file

@ -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]) => {

View file

@ -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",

View file

@ -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

10
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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,

View file

@ -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 },
},
};

View file

@ -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(),

View file

@ -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;