mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-09-16 14:21:32 +00:00
Compare commits
5 Commits
feat/struc
...
341aae4587
Author | SHA1 | Date | |
---|---|---|---|
|
341aae4587 | ||
|
7f62907385 | ||
|
7c4aa683a2 | ||
|
b48b0eeb0e | ||
|
cddc793915 |
13
package.json
13
package.json
@@ -15,12 +15,11 @@
|
|||||||
"@headlessui/react": "^2.2.0",
|
"@headlessui/react": "^2.2.0",
|
||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
"@icons-pack/react-simple-icons": "^12.3.0",
|
"@icons-pack/react-simple-icons": "^12.3.0",
|
||||||
"@langchain/anthropic": "^0.3.24",
|
"@langchain/anthropic": "^0.3.15",
|
||||||
"@langchain/community": "^0.3.49",
|
"@langchain/community": "^0.3.36",
|
||||||
"@langchain/core": "^0.3.66",
|
"@langchain/core": "^0.3.42",
|
||||||
"@langchain/google-genai": "^0.2.15",
|
"@langchain/google-genai": "^0.1.12",
|
||||||
"@langchain/ollama": "^0.2.3",
|
"@langchain/openai": "^0.0.25",
|
||||||
"@langchain/openai": "^0.6.2",
|
|
||||||
"@langchain/textsplitters": "^0.1.0",
|
"@langchain/textsplitters": "^0.1.0",
|
||||||
"@tailwindcss/typography": "^0.5.12",
|
"@tailwindcss/typography": "^0.5.12",
|
||||||
"@xenova/transformers": "^2.17.2",
|
"@xenova/transformers": "^2.17.2",
|
||||||
@@ -32,7 +31,7 @@
|
|||||||
"drizzle-orm": "^0.40.1",
|
"drizzle-orm": "^0.40.1",
|
||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
"jspdf": "^3.0.1",
|
"jspdf": "^3.0.1",
|
||||||
"langchain": "^0.3.30",
|
"langchain": "^0.1.30",
|
||||||
"lucide-react": "^0.363.0",
|
"lucide-react": "^0.363.0",
|
||||||
"mammoth": "^1.9.1",
|
"mammoth": "^1.9.1",
|
||||||
"markdown-to-jsx": "^7.7.2",
|
"markdown-to-jsx": "^7.7.2",
|
||||||
|
@@ -223,7 +223,7 @@ export const POST = async (req: Request) => {
|
|||||||
|
|
||||||
if (body.chatModel?.provider === 'custom_openai') {
|
if (body.chatModel?.provider === 'custom_openai') {
|
||||||
llm = new ChatOpenAI({
|
llm = new ChatOpenAI({
|
||||||
apiKey: getCustomOpenaiApiKey(),
|
openAIApiKey: getCustomOpenaiApiKey(),
|
||||||
modelName: getCustomOpenaiModelName(),
|
modelName: getCustomOpenaiModelName(),
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
configuration: {
|
configuration: {
|
||||||
|
@@ -49,7 +49,7 @@ export const POST = async (req: Request) => {
|
|||||||
|
|
||||||
if (body.chatModel?.provider === 'custom_openai') {
|
if (body.chatModel?.provider === 'custom_openai') {
|
||||||
llm = new ChatOpenAI({
|
llm = new ChatOpenAI({
|
||||||
apiKey: getCustomOpenaiApiKey(),
|
openAIApiKey: getCustomOpenaiApiKey(),
|
||||||
modelName: getCustomOpenaiModelName(),
|
modelName: getCustomOpenaiModelName(),
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
configuration: {
|
configuration: {
|
||||||
|
@@ -81,7 +81,7 @@ export const POST = async (req: Request) => {
|
|||||||
if (body.chatModel?.provider === 'custom_openai') {
|
if (body.chatModel?.provider === 'custom_openai') {
|
||||||
llm = new ChatOpenAI({
|
llm = new ChatOpenAI({
|
||||||
modelName: body.chatModel?.name || getCustomOpenaiModelName(),
|
modelName: body.chatModel?.name || getCustomOpenaiModelName(),
|
||||||
apiKey:
|
openAIApiKey:
|
||||||
body.chatModel?.customOpenAIKey || getCustomOpenaiApiKey(),
|
body.chatModel?.customOpenAIKey || getCustomOpenaiApiKey(),
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
configuration: {
|
configuration: {
|
||||||
|
@@ -48,7 +48,7 @@ export const POST = async (req: Request) => {
|
|||||||
|
|
||||||
if (body.chatModel?.provider === 'custom_openai') {
|
if (body.chatModel?.provider === 'custom_openai') {
|
||||||
llm = new ChatOpenAI({
|
llm = new ChatOpenAI({
|
||||||
apiKey: getCustomOpenaiApiKey(),
|
openAIApiKey: getCustomOpenaiApiKey(),
|
||||||
modelName: getCustomOpenaiModelName(),
|
modelName: getCustomOpenaiModelName(),
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
configuration: {
|
configuration: {
|
||||||
|
@@ -49,7 +49,7 @@ export const POST = async (req: Request) => {
|
|||||||
|
|
||||||
if (body.chatModel?.provider === 'custom_openai') {
|
if (body.chatModel?.provider === 'custom_openai') {
|
||||||
llm = new ChatOpenAI({
|
llm = new ChatOpenAI({
|
||||||
apiKey: getCustomOpenaiApiKey(),
|
openAIApiKey: getCustomOpenaiApiKey(),
|
||||||
modelName: getCustomOpenaiModelName(),
|
modelName: getCustomOpenaiModelName(),
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
configuration: {
|
configuration: {
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
export const POST = async (req: Request) => {
|
export const POST = async (req: Request) => {
|
||||||
try {
|
try {
|
||||||
const body: { lat: number; lng: number; temperatureUnit: 'C' | 'F' } =
|
const body: {
|
||||||
await req.json();
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
measureUnit: 'Imperial' | 'Metric';
|
||||||
|
} = await req.json();
|
||||||
|
|
||||||
if (!body.lat || !body.lng) {
|
if (!body.lat || !body.lng) {
|
||||||
return Response.json(
|
return Response.json(
|
||||||
@@ -13,7 +16,9 @@ export const POST = async (req: Request) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`https://api.open-meteo.com/v1/forecast?latitude=${body.lat}&longitude=${body.lng}¤t=weather_code,temperature_2m,is_day,relative_humidity_2m,wind_speed_10m&timezone=auto${body.temperatureUnit === 'C' ? '' : '&temperature_unit=fahrenheit'}`,
|
`https://api.open-meteo.com/v1/forecast?latitude=${body.lat}&longitude=${body.lng}¤t=weather_code,temperature_2m,is_day,relative_humidity_2m,wind_speed_10m&timezone=auto${
|
||||||
|
body.measureUnit === 'Metric' ? '' : '&temperature_unit=fahrenheit'
|
||||||
|
}${body.measureUnit === 'Metric' ? '' : '&wind_speed_unit=mph'}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
@@ -35,13 +40,15 @@ export const POST = async (req: Request) => {
|
|||||||
windSpeed: number;
|
windSpeed: number;
|
||||||
icon: string;
|
icon: string;
|
||||||
temperatureUnit: 'C' | 'F';
|
temperatureUnit: 'C' | 'F';
|
||||||
|
windSpeedUnit: 'm/s' | 'mph';
|
||||||
} = {
|
} = {
|
||||||
temperature: data.current.temperature_2m,
|
temperature: data.current.temperature_2m,
|
||||||
condition: '',
|
condition: '',
|
||||||
humidity: data.current.relative_humidity_2m,
|
humidity: data.current.relative_humidity_2m,
|
||||||
windSpeed: data.current.wind_speed_10m,
|
windSpeed: data.current.wind_speed_10m,
|
||||||
icon: '',
|
icon: '',
|
||||||
temperatureUnit: body.temperatureUnit,
|
temperatureUnit: body.measureUnit === 'Metric' ? 'C' : 'F',
|
||||||
|
windSpeedUnit: body.measureUnit === 'Metric' ? 'm/s' : 'mph',
|
||||||
};
|
};
|
||||||
|
|
||||||
const code = data.current.weather_code;
|
const code = data.current.weather_code;
|
||||||
|
@@ -148,7 +148,9 @@ const Page = () => {
|
|||||||
const [automaticImageSearch, setAutomaticImageSearch] = useState(false);
|
const [automaticImageSearch, setAutomaticImageSearch] = useState(false);
|
||||||
const [automaticVideoSearch, setAutomaticVideoSearch] = useState(false);
|
const [automaticVideoSearch, setAutomaticVideoSearch] = useState(false);
|
||||||
const [systemInstructions, setSystemInstructions] = useState<string>('');
|
const [systemInstructions, setSystemInstructions] = useState<string>('');
|
||||||
const [temperatureUnit, setTemperatureUnit] = useState<'C' | 'F'>('C');
|
const [measureUnit, setMeasureUnit] = useState<'Imperial' | 'Metric'>(
|
||||||
|
'Metric',
|
||||||
|
);
|
||||||
const [savingStates, setSavingStates] = useState<Record<string, boolean>>({});
|
const [savingStates, setSavingStates] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -211,7 +213,9 @@ const Page = () => {
|
|||||||
|
|
||||||
setSystemInstructions(localStorage.getItem('systemInstructions')!);
|
setSystemInstructions(localStorage.getItem('systemInstructions')!);
|
||||||
|
|
||||||
setTemperatureUnit(localStorage.getItem('temperatureUnit')! as 'C' | 'F');
|
setMeasureUnit(
|
||||||
|
localStorage.getItem('measureUnit')! as 'Imperial' | 'Metric',
|
||||||
|
);
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
@@ -371,8 +375,8 @@ const Page = () => {
|
|||||||
localStorage.setItem('embeddingModel', value);
|
localStorage.setItem('embeddingModel', value);
|
||||||
} else if (key === 'systemInstructions') {
|
} else if (key === 'systemInstructions') {
|
||||||
localStorage.setItem('systemInstructions', value);
|
localStorage.setItem('systemInstructions', value);
|
||||||
} else if (key === 'temperatureUnit') {
|
} else if (key === 'measureUnit') {
|
||||||
localStorage.setItem('temperatureUnit', value.toString());
|
localStorage.setItem('measureUnit', value.toString());
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to save:', err);
|
console.error('Failed to save:', err);
|
||||||
@@ -430,22 +434,22 @@ const Page = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
Temperature Unit
|
Measurement Units
|
||||||
</p>
|
</p>
|
||||||
<Select
|
<Select
|
||||||
value={temperatureUnit ?? undefined}
|
value={measureUnit ?? undefined}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setTemperatureUnit(e.target.value as 'C' | 'F');
|
setMeasureUnit(e.target.value as 'Imperial' | 'Metric');
|
||||||
saveConfig('temperatureUnit', e.target.value);
|
saveConfig('measureUnit', e.target.value);
|
||||||
}}
|
}}
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
label: 'Celsius',
|
label: 'Metric',
|
||||||
value: 'C',
|
value: 'Metric',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Fahrenheit',
|
label: 'Imperial',
|
||||||
value: 'F',
|
value: 'Imperial',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
@@ -10,6 +10,7 @@ const WeatherWidget = () => {
|
|||||||
windSpeed: 0,
|
windSpeed: 0,
|
||||||
icon: '',
|
icon: '',
|
||||||
temperatureUnit: 'C',
|
temperatureUnit: 'C',
|
||||||
|
windSpeedUnit: 'm/s',
|
||||||
});
|
});
|
||||||
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -75,7 +76,7 @@ const WeatherWidget = () => {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
lat: location.latitude,
|
lat: location.latitude,
|
||||||
lng: location.longitude,
|
lng: location.longitude,
|
||||||
temperatureUnit: localStorage.getItem('temperatureUnit') ?? 'C',
|
measureUnit: localStorage.getItem('measureUnit') ?? 'Metric',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -95,6 +96,7 @@ const WeatherWidget = () => {
|
|||||||
windSpeed: data.windSpeed,
|
windSpeed: data.windSpeed,
|
||||||
icon: data.icon,
|
icon: data.icon,
|
||||||
temperatureUnit: data.temperatureUnit,
|
temperatureUnit: data.temperatureUnit,
|
||||||
|
windSpeedUnit: data.windSpeedUnit,
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
@@ -139,7 +141,7 @@ const WeatherWidget = () => {
|
|||||||
</span>
|
</span>
|
||||||
<span className="flex items-center text-xs text-black/60 dark:text-white/60">
|
<span className="flex items-center text-xs text-black/60 dark:text-white/60">
|
||||||
<Wind className="w-3 h-3 mr-1" />
|
<Wind className="w-3 h-3 mr-1" />
|
||||||
{data.windSpeed} km/h
|
{data.windSpeed} {data.windSpeedUnit}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-black/60 dark:text-white/60 mt-1">
|
<span className="text-xs text-black/60 dark:text-white/60 mt-1">
|
||||||
|
@@ -3,32 +3,18 @@ import {
|
|||||||
RunnableMap,
|
RunnableMap,
|
||||||
RunnableLambda,
|
RunnableLambda,
|
||||||
} from '@langchain/core/runnables';
|
} from '@langchain/core/runnables';
|
||||||
import { PromptTemplate } from '@langchain/core/prompts';
|
import { ChatPromptTemplate } from '@langchain/core/prompts';
|
||||||
import formatChatHistoryAsString from '../utils/formatHistory';
|
import formatChatHistoryAsString from '../utils/formatHistory';
|
||||||
import { BaseMessage } from '@langchain/core/messages';
|
import { BaseMessage } from '@langchain/core/messages';
|
||||||
import { StringOutputParser } from '@langchain/core/output_parsers';
|
import { StringOutputParser } from '@langchain/core/output_parsers';
|
||||||
import { searchSearxng } from '../searxng';
|
import { searchSearxng } from '../searxng';
|
||||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||||
|
import LineOutputParser from '../outputParsers/lineOutputParser';
|
||||||
|
|
||||||
const imageSearchChainPrompt = `
|
const imageSearchChainPrompt = `
|
||||||
You will be given a conversation below and a follow up question. You need to rephrase the follow-up question so it is a standalone question that can be used by the LLM to search the web for images.
|
You will be given a conversation below and a follow up question. You need to rephrase the follow-up question so it is a standalone question that can be used by the LLM to search the web for images.
|
||||||
You need to make sure the rephrased question agrees with the conversation and is relevant to the conversation.
|
You need to make sure the rephrased question agrees with the conversation and is relevant to the conversation.
|
||||||
|
Output only the rephrased query wrapped in an XML <query> element. Do not include any explanation or additional text.
|
||||||
Example:
|
|
||||||
1. Follow up question: What is a cat?
|
|
||||||
Rephrased: A cat
|
|
||||||
|
|
||||||
2. Follow up question: What is a car? How does it works?
|
|
||||||
Rephrased: Car working
|
|
||||||
|
|
||||||
3. Follow up question: How does an AC work?
|
|
||||||
Rephrased: AC working
|
|
||||||
|
|
||||||
Conversation:
|
|
||||||
{chat_history}
|
|
||||||
|
|
||||||
Follow up question: {query}
|
|
||||||
Rephrased question:
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type ImageSearchChainInput = {
|
type ImageSearchChainInput = {
|
||||||
@@ -54,12 +40,39 @@ const createImageSearchChain = (llm: BaseChatModel) => {
|
|||||||
return input.query;
|
return input.query;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
PromptTemplate.fromTemplate(imageSearchChainPrompt),
|
ChatPromptTemplate.fromMessages([
|
||||||
|
['system', imageSearchChainPrompt],
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
'<conversation>\n</conversation>\n<follow_up>\nWhat is a cat?\n</follow_up>',
|
||||||
|
],
|
||||||
|
['assistant', '<query>A cat</query>'],
|
||||||
|
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
'<conversation>\n</conversation>\n<follow_up>\nWhat is a car? How does it work?\n</follow_up>',
|
||||||
|
],
|
||||||
|
['assistant', '<query>Car working</query>'],
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
'<conversation>\n</conversation>\n<follow_up>\nHow does an AC work?\n</follow_up>',
|
||||||
|
],
|
||||||
|
['assistant', '<query>AC working</query>'],
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
'<conversation>{chat_history}</conversation>\n<follow_up>\n{query}\n</follow_up>',
|
||||||
|
],
|
||||||
|
]),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
strParser,
|
||||||
RunnableLambda.from(async (input: string) => {
|
RunnableLambda.from(async (input: string) => {
|
||||||
input = input.replace(/<think>.*?<\/think>/g, '');
|
const queryParser = new LineOutputParser({
|
||||||
|
key: 'query',
|
||||||
|
});
|
||||||
|
|
||||||
|
return await queryParser.parse(input);
|
||||||
|
}),
|
||||||
|
RunnableLambda.from(async (input: string) => {
|
||||||
const res = await searchSearxng(input, {
|
const res = await searchSearxng(input, {
|
||||||
engines: ['bing images', 'google images'],
|
engines: ['bing images', 'google images'],
|
||||||
});
|
});
|
||||||
|
@@ -3,33 +3,19 @@ import {
|
|||||||
RunnableMap,
|
RunnableMap,
|
||||||
RunnableLambda,
|
RunnableLambda,
|
||||||
} from '@langchain/core/runnables';
|
} from '@langchain/core/runnables';
|
||||||
import { PromptTemplate } from '@langchain/core/prompts';
|
import { ChatPromptTemplate } from '@langchain/core/prompts';
|
||||||
import formatChatHistoryAsString from '../utils/formatHistory';
|
import formatChatHistoryAsString from '../utils/formatHistory';
|
||||||
import { BaseMessage } from '@langchain/core/messages';
|
import { BaseMessage } from '@langchain/core/messages';
|
||||||
import { StringOutputParser } from '@langchain/core/output_parsers';
|
import { StringOutputParser } from '@langchain/core/output_parsers';
|
||||||
import { searchSearxng } from '../searxng';
|
import { searchSearxng } from '../searxng';
|
||||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||||
|
import LineOutputParser from '../outputParsers/lineOutputParser';
|
||||||
|
|
||||||
const VideoSearchChainPrompt = `
|
const videoSearchChainPrompt = `
|
||||||
You will be given a conversation below and a follow up question. You need to rephrase the follow-up question so it is a standalone question that can be used by the LLM to search Youtube for videos.
|
You will be given a conversation below and a follow up question. You need to rephrase the follow-up question so it is a standalone question that can be used by the LLM to search Youtube for videos.
|
||||||
You need to make sure the rephrased question agrees with the conversation and is relevant to the conversation.
|
You need to make sure the rephrased question agrees with the conversation and is relevant to the conversation.
|
||||||
|
Output only the rephrased query wrapped in an XML <query> element. Do not include any explanation or additional text.
|
||||||
Example:
|
`;
|
||||||
1. Follow up question: How does a car work?
|
|
||||||
Rephrased: How does a car work?
|
|
||||||
|
|
||||||
2. Follow up question: What is the theory of relativity?
|
|
||||||
Rephrased: What is theory of relativity
|
|
||||||
|
|
||||||
3. Follow up question: How does an AC work?
|
|
||||||
Rephrased: How does an AC work
|
|
||||||
|
|
||||||
Conversation:
|
|
||||||
{chat_history}
|
|
||||||
|
|
||||||
Follow up question: {query}
|
|
||||||
Rephrased question:
|
|
||||||
`;
|
|
||||||
|
|
||||||
type VideoSearchChainInput = {
|
type VideoSearchChainInput = {
|
||||||
chat_history: BaseMessage[];
|
chat_history: BaseMessage[];
|
||||||
@@ -55,12 +41,37 @@ const createVideoSearchChain = (llm: BaseChatModel) => {
|
|||||||
return input.query;
|
return input.query;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
PromptTemplate.fromTemplate(VideoSearchChainPrompt),
|
ChatPromptTemplate.fromMessages([
|
||||||
|
['system', videoSearchChainPrompt],
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
'<conversation>\n</conversation>\n<follow_up>\nHow does a car work?\n</follow_up>',
|
||||||
|
],
|
||||||
|
['assistant', '<query>How does a car work?</query>'],
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
'<conversation>\n</conversation>\n<follow_up>\nWhat is the theory of relativity?\n</follow_up>',
|
||||||
|
],
|
||||||
|
['assistant', '<query>Theory of relativity</query>'],
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
'<conversation>\n</conversation>\n<follow_up>\nHow does an AC work?\n</follow_up>',
|
||||||
|
],
|
||||||
|
['assistant', '<query>AC working</query>'],
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
'<conversation>{chat_history}</conversation>\n<follow_up>\n{query}\n</follow_up>',
|
||||||
|
],
|
||||||
|
]),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
strParser,
|
||||||
RunnableLambda.from(async (input: string) => {
|
RunnableLambda.from(async (input: string) => {
|
||||||
input = input.replace(/<think>.*?<\/think>/g, '');
|
const queryParser = new LineOutputParser({
|
||||||
|
key: 'query',
|
||||||
|
});
|
||||||
|
return await queryParser.parse(input);
|
||||||
|
}),
|
||||||
|
RunnableLambda.from(async (input: string) => {
|
||||||
const res = await searchSearxng(input, {
|
const res = await searchSearxng(input, {
|
||||||
engines: ['youtube'],
|
engines: ['youtube'],
|
||||||
});
|
});
|
||||||
@@ -92,8 +103,8 @@ const handleVideoSearch = (
|
|||||||
input: VideoSearchChainInput,
|
input: VideoSearchChainInput,
|
||||||
llm: BaseChatModel,
|
llm: BaseChatModel,
|
||||||
) => {
|
) => {
|
||||||
const VideoSearchChain = createVideoSearchChain(llm);
|
const videoSearchChain = createVideoSearchChain(llm);
|
||||||
return VideoSearchChain.invoke(input);
|
return videoSearchChain.invoke(input);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default handleVideoSearch;
|
export default handleVideoSearch;
|
||||||
|
@@ -1,41 +1,63 @@
|
|||||||
export const webSearchRetrieverPrompt = `
|
export const webSearchRetrieverPrompt = `
|
||||||
You are an AI question rephraser. You will be given a conversation and a follow-up question; rephrase it into a standalone question that another LLM can use to search the web.
|
You are an AI question rephraser. You will be given a conversation and a follow-up question, you will have to rephrase the follow up question so it is a standalone question and can be used by another LLM to search the web for information to answer it.
|
||||||
|
If it is a simple writing task or a greeting (unless the greeting contains a question after it) like Hi, Hello, How are you, etc. than a question then you need to return \`not_needed\` as the response (This is because the LLM won't need to search the web for finding information on this topic).
|
||||||
|
If the user asks some question from some URL or wants you to summarize a PDF or a webpage (via URL) you need to return the links inside the \`links\` XML block and the question inside the \`question\` XML block. If the user wants to you to summarize the webpage or the PDF you need to return \`summarize\` inside the \`question\` XML block in place of a question and the link to summarize in the \`links\` XML block.
|
||||||
|
You must always return the rephrased question inside the \`question\` XML block, if there are no links in the follow-up question then don't insert a \`links\` XML block in your response.
|
||||||
|
|
||||||
Return ONLY a JSON object that matches this schema:
|
There are several examples attached for your reference inside the below \`examples\` XML block
|
||||||
query: string // the standalone question (or "summarize")
|
|
||||||
links: string[] // URLs extracted from the user query (empty if none)
|
|
||||||
searchRequired: boolean // true if web search is needed, false for greetings/simple writing tasks
|
|
||||||
searchMode: "" | "normal" | "news" // "" when searchRequired is false; "news" if the user asks for news/articles, otherwise "normal"
|
|
||||||
|
|
||||||
Rules
|
<examples>
|
||||||
- Greetings / simple writing tasks → query:"", links:[], searchRequired:false, searchMode:""
|
1. Follow up question: What is the capital of France
|
||||||
- Summarizing a URL → query:"summarize", links:[url...], searchRequired:true, searchMode:"normal"
|
Rephrased question:\`
|
||||||
- Asking for news/articles → searchMode:"news"
|
<question>
|
||||||
|
Capital of france
|
||||||
Examples
|
</question>
|
||||||
1. Follow-up: What is the capital of France?
|
\`
|
||||||
"query":"capital of France","links":[],"searchRequired":true,"searchMode":"normal"
|
|
||||||
|
|
||||||
2. Hi, how are you?
|
2. Hi, how are you?
|
||||||
"query":"","links":[],"searchRequired":false,"searchMode":""
|
Rephrased question\`
|
||||||
|
<question>
|
||||||
|
not_needed
|
||||||
|
</question>
|
||||||
|
\`
|
||||||
|
|
||||||
3. Follow-up: What is Docker?
|
3. Follow up question: What is Docker?
|
||||||
"query":"what is Docker","links":[],"searchRequired":true,"searchMode":"normal"
|
Rephrased question: \`
|
||||||
|
<question>
|
||||||
|
What is Docker
|
||||||
|
</question>
|
||||||
|
\`
|
||||||
|
|
||||||
4. Follow-up: Can you tell me what is X from https://example.com?
|
4. Follow up question: Can you tell me what is X from https://example.com
|
||||||
"query":"what is X","links":["https://example.com"],"searchRequired":true,"searchMode":"normal"
|
Rephrased question: \`
|
||||||
|
<question>
|
||||||
|
Can you tell me what is X?
|
||||||
|
</question>
|
||||||
|
|
||||||
5. Follow-up: Summarize the content from https://example.com
|
<links>
|
||||||
"query":"summarize","links":["https://example.com"],"searchRequired":true,"searchMode":"normal"
|
https://example.com
|
||||||
|
</links>
|
||||||
|
\`
|
||||||
|
|
||||||
6. Follow-up: Latest news about AI
|
5. Follow up question: Summarize the content from https://example.com
|
||||||
"query":"latest news about AI","links":[],"searchRequired":true,"searchMode":"news"
|
Rephrased question: \`
|
||||||
|
<question>
|
||||||
|
summarize
|
||||||
|
</question>
|
||||||
|
|
||||||
|
<links>
|
||||||
|
https://example.com
|
||||||
|
</links>
|
||||||
|
\`
|
||||||
|
</examples>
|
||||||
|
|
||||||
|
Anything below is the part of the actual conversation and you need to use conversation and the follow-up question to rephrase the follow-up question as a standalone question based on the guidelines shared above.
|
||||||
|
|
||||||
<conversation>
|
<conversation>
|
||||||
{chat_history}
|
{chat_history}
|
||||||
</conversation>
|
</conversation>
|
||||||
|
|
||||||
Follow-up question: {query}
|
Follow up question: {query}
|
||||||
Rephrased question:
|
Rephrased question:
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@@ -38,7 +38,7 @@ export const loadAimlApiChatModels = async () => {
|
|||||||
chatModels[model.id] = {
|
chatModels[model.id] = {
|
||||||
displayName: model.name || model.id,
|
displayName: model.name || model.id,
|
||||||
model: new ChatOpenAI({
|
model: new ChatOpenAI({
|
||||||
apiKey: apiKey,
|
openAIApiKey: apiKey,
|
||||||
modelName: model.id,
|
modelName: model.id,
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
configuration: {
|
configuration: {
|
||||||
@@ -76,7 +76,7 @@ export const loadAimlApiEmbeddingModels = async () => {
|
|||||||
embeddingModels[model.id] = {
|
embeddingModels[model.id] = {
|
||||||
displayName: model.name || model.id,
|
displayName: model.name || model.id,
|
||||||
model: new OpenAIEmbeddings({
|
model: new OpenAIEmbeddings({
|
||||||
apiKey: apiKey,
|
openAIApiKey: apiKey,
|
||||||
modelName: model.id,
|
modelName: model.id,
|
||||||
configuration: {
|
configuration: {
|
||||||
baseURL: API_URL,
|
baseURL: API_URL,
|
||||||
|
@@ -31,7 +31,7 @@ export const loadDeepseekChatModels = async () => {
|
|||||||
chatModels[model.key] = {
|
chatModels[model.key] = {
|
||||||
displayName: model.displayName,
|
displayName: model.displayName,
|
||||||
model: new ChatOpenAI({
|
model: new ChatOpenAI({
|
||||||
apiKey: deepseekApiKey,
|
openAIApiKey: deepseekApiKey,
|
||||||
modelName: model.key,
|
modelName: model.key,
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
configuration: {
|
configuration: {
|
||||||
|
@@ -29,15 +29,12 @@ export const loadGroqChatModels = async () => {
|
|||||||
chatModels[model.id] = {
|
chatModels[model.id] = {
|
||||||
displayName: model.id,
|
displayName: model.id,
|
||||||
model: new ChatOpenAI({
|
model: new ChatOpenAI({
|
||||||
apiKey: groqApiKey,
|
openAIApiKey: groqApiKey,
|
||||||
modelName: model.id,
|
modelName: model.id,
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
configuration: {
|
configuration: {
|
||||||
baseURL: 'https://api.groq.com/openai/v1',
|
baseURL: 'https://api.groq.com/openai/v1',
|
||||||
},
|
},
|
||||||
metadata: {
|
|
||||||
'model-type': 'groq',
|
|
||||||
},
|
|
||||||
}) as unknown as BaseChatModel,
|
}) as unknown as BaseChatModel,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@@ -118,7 +118,7 @@ export const getAvailableChatModelProviders = async () => {
|
|||||||
[customOpenAiModelName]: {
|
[customOpenAiModelName]: {
|
||||||
displayName: customOpenAiModelName,
|
displayName: customOpenAiModelName,
|
||||||
model: new ChatOpenAI({
|
model: new ChatOpenAI({
|
||||||
apiKey: customOpenAiApiKey,
|
openAIApiKey: customOpenAiApiKey,
|
||||||
modelName: customOpenAiModelName,
|
modelName: customOpenAiModelName,
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
configuration: {
|
configuration: {
|
||||||
|
@@ -47,7 +47,7 @@ export const loadLMStudioChatModels = async () => {
|
|||||||
chatModels[model.id] = {
|
chatModels[model.id] = {
|
||||||
displayName: model.name || model.id,
|
displayName: model.name || model.id,
|
||||||
model: new ChatOpenAI({
|
model: new ChatOpenAI({
|
||||||
apiKey: 'lm-studio',
|
openAIApiKey: 'lm-studio',
|
||||||
configuration: {
|
configuration: {
|
||||||
baseURL: ensureV1Endpoint(endpoint),
|
baseURL: ensureV1Endpoint(endpoint),
|
||||||
},
|
},
|
||||||
@@ -83,7 +83,7 @@ export const loadLMStudioEmbeddingsModels = async () => {
|
|||||||
embeddingsModels[model.id] = {
|
embeddingsModels[model.id] = {
|
||||||
displayName: model.name || model.id,
|
displayName: model.name || model.id,
|
||||||
model: new OpenAIEmbeddings({
|
model: new OpenAIEmbeddings({
|
||||||
apiKey: 'lm-studio',
|
openAIApiKey: 'lm-studio',
|
||||||
configuration: {
|
configuration: {
|
||||||
baseURL: ensureV1Endpoint(endpoint),
|
baseURL: ensureV1Endpoint(endpoint),
|
||||||
},
|
},
|
||||||
|
@@ -6,8 +6,8 @@ export const PROVIDER_INFO = {
|
|||||||
key: 'ollama',
|
key: 'ollama',
|
||||||
displayName: 'Ollama',
|
displayName: 'Ollama',
|
||||||
};
|
};
|
||||||
import { ChatOllama } from '@langchain/ollama';
|
import { ChatOllama } from '@langchain/community/chat_models/ollama';
|
||||||
import { OllamaEmbeddings } from '@langchain/ollama';
|
import { OllamaEmbeddings } from '@langchain/community/embeddings/ollama';
|
||||||
|
|
||||||
export const loadOllamaChatModels = async () => {
|
export const loadOllamaChatModels = async () => {
|
||||||
const ollamaApiEndpoint = getOllamaApiEndpoint();
|
const ollamaApiEndpoint = getOllamaApiEndpoint();
|
||||||
|
@@ -67,7 +67,7 @@ export const loadOpenAIChatModels = async () => {
|
|||||||
chatModels[model.key] = {
|
chatModels[model.key] = {
|
||||||
displayName: model.displayName,
|
displayName: model.displayName,
|
||||||
model: new ChatOpenAI({
|
model: new ChatOpenAI({
|
||||||
apiKey: openaiApiKey,
|
openAIApiKey: openaiApiKey,
|
||||||
modelName: model.key,
|
modelName: model.key,
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
}) as unknown as BaseChatModel,
|
}) as unknown as BaseChatModel,
|
||||||
@@ -93,7 +93,7 @@ export const loadOpenAIEmbeddingModels = async () => {
|
|||||||
embeddingModels[model.key] = {
|
embeddingModels[model.key] = {
|
||||||
displayName: model.displayName,
|
displayName: model.displayName,
|
||||||
model: new OpenAIEmbeddings({
|
model: new OpenAIEmbeddings({
|
||||||
apiKey: openaiApiKey,
|
openAIApiKey: openaiApiKey,
|
||||||
modelName: model.key,
|
modelName: model.key,
|
||||||
}) as unknown as Embeddings,
|
}) as unknown as Embeddings,
|
||||||
};
|
};
|
||||||
|
@@ -24,7 +24,6 @@ import computeSimilarity from '../utils/computeSimilarity';
|
|||||||
import formatChatHistoryAsString from '../utils/formatHistory';
|
import formatChatHistoryAsString from '../utils/formatHistory';
|
||||||
import eventEmitter from 'events';
|
import eventEmitter from 'events';
|
||||||
import { StreamEvent } from '@langchain/core/tracers/log_stream';
|
import { StreamEvent } from '@langchain/core/tracers/log_stream';
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
export interface MetaSearchAgentType {
|
export interface MetaSearchAgentType {
|
||||||
searchAndAnswer: (
|
searchAndAnswer: (
|
||||||
@@ -53,17 +52,6 @@ type BasicChainInput = {
|
|||||||
query: string;
|
query: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const retrieverLLMOutputSchema = z.object({
|
|
||||||
query: z.string().describe('The query to search the web for.'),
|
|
||||||
links: z
|
|
||||||
.array(z.string())
|
|
||||||
.describe('The links to search/summarize if present'),
|
|
||||||
searchRequired: z
|
|
||||||
.boolean()
|
|
||||||
.describe('Wether there is a need to search the web'),
|
|
||||||
searchMode: z.enum(['', 'normal', 'news']).describe('The search mode.'),
|
|
||||||
});
|
|
||||||
|
|
||||||
class MetaSearchAgent implements MetaSearchAgentType {
|
class MetaSearchAgent implements MetaSearchAgentType {
|
||||||
private config: Config;
|
private config: Config;
|
||||||
private strParser = new StringOutputParser();
|
private strParser = new StringOutputParser();
|
||||||
@@ -74,71 +62,73 @@ class MetaSearchAgent implements MetaSearchAgentType {
|
|||||||
|
|
||||||
private async createSearchRetrieverChain(llm: BaseChatModel) {
|
private async createSearchRetrieverChain(llm: BaseChatModel) {
|
||||||
(llm as unknown as ChatOpenAI).temperature = 0;
|
(llm as unknown as ChatOpenAI).temperature = 0;
|
||||||
|
|
||||||
return RunnableSequence.from([
|
return RunnableSequence.from([
|
||||||
PromptTemplate.fromTemplate(this.config.queryGeneratorPrompt),
|
PromptTemplate.fromTemplate(this.config.queryGeneratorPrompt),
|
||||||
Object.assign(
|
llm,
|
||||||
Object.create(Object.getPrototypeOf(llm)),
|
this.strParser,
|
||||||
llm,
|
RunnableLambda.from(async (input: string) => {
|
||||||
).withStructuredOutput(retrieverLLMOutputSchema, {
|
const linksOutputParser = new LineListOutputParser({
|
||||||
...(llm.metadata?.['model-type'] === 'groq'
|
key: 'links',
|
||||||
? {
|
});
|
||||||
method: 'json-object',
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
}),
|
|
||||||
RunnableLambda.from(
|
|
||||||
async (input: z.infer<typeof retrieverLLMOutputSchema>) => {
|
|
||||||
let question = input.query;
|
|
||||||
const links = input.links;
|
|
||||||
|
|
||||||
if (!input.searchRequired) {
|
const questionOutputParser = new LineOutputParser({
|
||||||
return { query: '', docs: [] };
|
key: 'question',
|
||||||
|
});
|
||||||
|
|
||||||
|
const links = await linksOutputParser.parse(input);
|
||||||
|
let question = this.config.summarizer
|
||||||
|
? await questionOutputParser.parse(input)
|
||||||
|
: input;
|
||||||
|
|
||||||
|
if (question === 'not_needed') {
|
||||||
|
return { query: '', docs: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (links.length > 0) {
|
||||||
|
if (question.length === 0) {
|
||||||
|
question = 'summarize';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (links.length > 0) {
|
let docs: Document[] = [];
|
||||||
if (question.length === 0) {
|
|
||||||
question = 'summarize';
|
const linkDocs = await getDocumentsFromLinks({ links });
|
||||||
|
|
||||||
|
const docGroups: Document[] = [];
|
||||||
|
|
||||||
|
linkDocs.map((doc) => {
|
||||||
|
const URLDocExists = docGroups.find(
|
||||||
|
(d) =>
|
||||||
|
d.metadata.url === doc.metadata.url &&
|
||||||
|
d.metadata.totalDocs < 10,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!URLDocExists) {
|
||||||
|
docGroups.push({
|
||||||
|
...doc,
|
||||||
|
metadata: {
|
||||||
|
...doc.metadata,
|
||||||
|
totalDocs: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let docs: Document[] = [];
|
const docIndex = docGroups.findIndex(
|
||||||
|
(d) =>
|
||||||
|
d.metadata.url === doc.metadata.url &&
|
||||||
|
d.metadata.totalDocs < 10,
|
||||||
|
);
|
||||||
|
|
||||||
const linkDocs = await getDocumentsFromLinks({ links });
|
if (docIndex !== -1) {
|
||||||
|
docGroups[docIndex].pageContent =
|
||||||
|
docGroups[docIndex].pageContent + `\n\n` + doc.pageContent;
|
||||||
|
docGroups[docIndex].metadata.totalDocs += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const docGroups: Document[] = [];
|
await Promise.all(
|
||||||
|
docGroups.map(async (doc) => {
|
||||||
linkDocs.map((doc) => {
|
const res = await llm.invoke(`
|
||||||
const URLDocExists = docGroups.find(
|
|
||||||
(d) =>
|
|
||||||
d.metadata.url === doc.metadata.url &&
|
|
||||||
d.metadata.totalDocs < 10,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!URLDocExists) {
|
|
||||||
docGroups.push({
|
|
||||||
...doc,
|
|
||||||
metadata: {
|
|
||||||
...doc.metadata,
|
|
||||||
totalDocs: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const docIndex = docGroups.findIndex(
|
|
||||||
(d) =>
|
|
||||||
d.metadata.url === doc.metadata.url &&
|
|
||||||
d.metadata.totalDocs < 10,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (docIndex !== -1) {
|
|
||||||
docGroups[docIndex].pageContent =
|
|
||||||
docGroups[docIndex].pageContent + `\n\n` + doc.pageContent;
|
|
||||||
docGroups[docIndex].metadata.totalDocs += 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
docGroups.map(async (doc) => {
|
|
||||||
const res = await llm.invoke(`
|
|
||||||
You are a web search summarizer, tasked with summarizing a piece of text retrieved from a web search. Your job is to summarize the
|
You are a web search summarizer, tasked with summarizing a piece of text retrieved from a web search. Your job is to summarize the
|
||||||
text into a detailed, 2-4 paragraph explanation that captures the main ideas and provides a comprehensive answer to the query.
|
text into a detailed, 2-4 paragraph explanation that captures the main ideas and provides a comprehensive answer to the query.
|
||||||
If the query is \"summarize\", you should provide a detailed summary of the text. If the query is a specific question, you should answer it in the summary.
|
If the query is \"summarize\", you should provide a detailed summary of the text. If the query is a specific question, you should answer it in the summary.
|
||||||
@@ -199,50 +189,46 @@ class MetaSearchAgent implements MetaSearchAgentType {
|
|||||||
Make sure to answer the query in the summary.
|
Make sure to answer the query in the summary.
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const document = new Document({
|
const document = new Document({
|
||||||
pageContent: res.content as string,
|
pageContent: res.content as string,
|
||||||
metadata: {
|
metadata: {
|
||||||
title: doc.metadata.title,
|
title: doc.metadata.title,
|
||||||
url: doc.metadata.url,
|
url: doc.metadata.url,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
docs.push(document);
|
docs.push(document);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return { query: question, docs: docs };
|
||||||
|
} else {
|
||||||
|
question = question.replace(/<think>.*?<\/think>/g, '');
|
||||||
|
|
||||||
|
const res = await searchSearxng(question, {
|
||||||
|
language: 'en',
|
||||||
|
engines: this.config.activeEngines,
|
||||||
|
});
|
||||||
|
|
||||||
|
const documents = res.results.map(
|
||||||
|
(result) =>
|
||||||
|
new Document({
|
||||||
|
pageContent:
|
||||||
|
result.content ||
|
||||||
|
(this.config.activeEngines.includes('youtube')
|
||||||
|
? result.title
|
||||||
|
: '') /* Todo: Implement transcript grabbing using Youtubei (source: https://www.npmjs.com/package/youtubei) */,
|
||||||
|
metadata: {
|
||||||
|
title: result.title,
|
||||||
|
url: result.url,
|
||||||
|
...(result.img_src && { img_src: result.img_src }),
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return { query: question, docs: docs };
|
return { query: question, docs: documents };
|
||||||
} else {
|
}
|
||||||
question = question.replace(/<think>.*?<\/think>/g, '');
|
}),
|
||||||
|
|
||||||
const res = await searchSearxng(question, {
|
|
||||||
language: 'en',
|
|
||||||
engines:
|
|
||||||
input.searchMode === 'normal'
|
|
||||||
? this.config.activeEngines
|
|
||||||
: ['bing news'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const documents = res.results.map(
|
|
||||||
(result) =>
|
|
||||||
new Document({
|
|
||||||
pageContent:
|
|
||||||
result.content ||
|
|
||||||
(this.config.activeEngines.includes('youtube')
|
|
||||||
? result.title
|
|
||||||
: '') /* Todo: Implement transcript grabbing using Youtubei (source: https://www.npmjs.com/package/youtubei) */,
|
|
||||||
metadata: {
|
|
||||||
title: result.title,
|
|
||||||
url: result.url,
|
|
||||||
...(result.img_src && { img_src: result.img_src }),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return { query: question, docs: documents };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user