added multi search engine support (didnt test) WIP

This commit is contained in:
Hadi Cherkaoui
2025-02-27 21:32:26 +01:00
parent 6c218b5fee
commit 4d41243108
6 changed files with 286 additions and 68 deletions

View File

@ -8,6 +8,8 @@ import formatChatHistoryAsString from '../utils/formatHistory';
import { BaseMessage } from '@langchain/core/messages';
import { StringOutputParser } from '@langchain/core/output_parsers';
import { searchSearxng } from '../lib/searchEngines/searxng';
import { searchGooglePSE } from '../lib/searchEngines/google_pse';
import { getSearchEngineBackend } from '../config';
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
const imageSearchChainPrompt = `
@ -36,6 +38,59 @@ type ImageSearchChainInput = {
query: string;
};
async function performImageSearch(query: string) {
const searchEngine = getSearchEngineBackend();
let images = [];
switch (searchEngine) {
case 'google': {
const googleResult = await searchGooglePSE(query);
images = googleResult.originalres
.map((result) => {
// Extract image URL from multiple possible locations in Google's response
const imageSrc = result.pagemap?.cse_image?.[0]?.src ||
result.pagemap?.cse_thumbnail?.[0]?.src ||
result.image?.thumbnailLink;
if (imageSrc && result.link && result.title) {
images.push({
img_src: imageSrc,
url: result.link,
title: result.title,
// Add additional metadata if needed
source: result.displayLink,
fileFormat: result.fileFormat,
});
}
})
.filter(Boolean);
break;
}
case 'searxng': {
const searxResult = await searchSearxng(query, {
engines: ['google images', 'bing images'],
pageno: 1,
});
searxResult.results.forEach((result) => {
if (result.img_src && result.url && result.title) {
images.push({
img_src: result.img_src,
url: result.url,
title: result.title,
});
}
});
break;
}
default:
throw new Error(`Unknown search engine ${searchEngine}`);
}
return images;
}
const strParser = new StringOutputParser();
const createImageSearchChain = (llm: BaseChatModel) => {
@ -52,22 +107,7 @@ const createImageSearchChain = (llm: BaseChatModel) => {
llm,
strParser,
RunnableLambda.from(async (input: string) => {
const res = await searchSearxng(input, {
engines: ['bing images', 'google images'],
});
const images = [];
res.results.forEach((result) => {
if (result.img_src && result.url && result.title) {
images.push({
img_src: result.img_src,
url: result.url,
title: result.title,
});
}
});
const images = await performImageSearch(input);
return images.slice(0, 10);
}),
]);

View File

@ -8,6 +8,8 @@ import formatChatHistoryAsString from '../utils/formatHistory';
import { BaseMessage } from '@langchain/core/messages';
import { StringOutputParser } from '@langchain/core/output_parsers';
import { searchSearxng } from '../lib/searchEngines/searxng';
import { searchGooglePSE } from '../lib/searchEngines/google_pse';
import { getSearchEngineBackend } from '../config';
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
const VideoSearchChainPrompt = `
@ -38,27 +40,45 @@ type VideoSearchChainInput = {
const strParser = new StringOutputParser();
const createVideoSearchChain = (llm: BaseChatModel) => {
return RunnableSequence.from([
RunnableMap.from({
chat_history: (input: VideoSearchChainInput) => {
return formatChatHistoryAsString(input.chat_history);
},
query: (input: VideoSearchChainInput) => {
return input.query;
},
}),
PromptTemplate.fromTemplate(VideoSearchChainPrompt),
llm,
strParser,
RunnableLambda.from(async (input: string) => {
const res = await searchSearxng(input, {
function extractYouTubeVideoId(url: string): string | null {
const regex = /(?:v=|\/embed\/|\.be\/)([a-zA-Z0-9_-]{11})/;
const match = url.match(regex);
return match ? match[1] : null;
}
async function performVideoSearch(query: string) {
const searchEngine = getSearchEngineBackend();
const youtubeQuery = `${query} site:youtube.com`;
let videos = [];
switch (searchEngine) {
case 'google': {
const googleResult = await searchGooglePSE(youtubeQuery);
googleResult.originalres.results.forEach((result) => {
// Extract video metadata from Google PSE results
const thumbnail = result.pagemap?.cse_thumbnail?.[0]?.src
|| result.pagemap?.videoobject?.[0]?.thumbnailurl;
if (thumbnail && result.link && result.title) {
videos.push({
img_src: thumbnail,
url: result.link,
title: result.title,
// Construct iframe URL from YouTube video ID
iframe_src: result.link.includes('youtube.com/watch?v=')
? `https://www.youtube.com/embed/${result.link.split('v=')[1].split('&')[0]}`
: null,
});
}
});
break;
}
case 'searxng': {
const searxResult = await searchSearxng(query, {
engines: ['youtube'],
});
const videos = [];
res.results.forEach((result) => {
searxResult.results.forEach((result) => {
if (
result.thumbnail &&
result.url &&
@ -73,7 +93,31 @@ const createVideoSearchChain = (llm: BaseChatModel) => {
});
}
});
break;
}
default:
throw new Error(`Unknown search engine ${searchEngine}`);
}
return videos;
}
const createVideoSearchChain = (llm: BaseChatModel) => {
return RunnableSequence.from([
RunnableMap.from({
chat_history: (input: VideoSearchChainInput) => {
return formatChatHistoryAsString(input.chat_history);
},
query: (input: VideoSearchChainInput) => {
return input.query;
},
}),
PromptTemplate.fromTemplate(VideoSearchChainPrompt),
llm,
strParser,
RunnableLambda.from(async (input: string) => {
const videos = await performVideoSearch(input);
return videos.slice(0, 10);
}),
]);
@ -87,4 +131,4 @@ const handleVideoSearch = (
return VideoSearchChain.invoke(input);
};
export default handleVideoSearch;
export default handleVideoSearch;