mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-09-13 21:11:32 +00:00
Compare commits
9 Commits
v1.11.0-rc
...
feat/struc
Author | SHA1 | Date | |
---|---|---|---|
|
df33229934 | ||
|
49fafaa096 | ||
|
ca9b32a23b | ||
|
76e3ff4e02 | ||
|
eabf3ca7d3 | ||
|
94e6db10bb | ||
|
26e1d5fec3 | ||
|
66be87b688 | ||
|
f7b4e32218 |
13
package.json
13
package.json
@@ -15,11 +15,12 @@
|
|||||||
"@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.15",
|
"@langchain/anthropic": "^0.3.24",
|
||||||
"@langchain/community": "^0.3.36",
|
"@langchain/community": "^0.3.49",
|
||||||
"@langchain/core": "^0.3.42",
|
"@langchain/core": "^0.3.66",
|
||||||
"@langchain/google-genai": "^0.1.12",
|
"@langchain/google-genai": "^0.2.15",
|
||||||
"@langchain/openai": "^0.0.25",
|
"@langchain/ollama": "^0.2.3",
|
||||||
|
"@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",
|
||||||
@@ -31,7 +32,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.1.30",
|
"langchain": "^0.3.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({
|
||||||
openAIApiKey: getCustomOpenaiApiKey(),
|
apiKey: getCustomOpenaiApiKey(),
|
||||||
modelName: getCustomOpenaiModelName(),
|
modelName: getCustomOpenaiModelName(),
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
configuration: {
|
configuration: {
|
||||||
|
@@ -36,6 +36,7 @@ export const GET = async (req: Request) => {
|
|||||||
{
|
{
|
||||||
engines: ['bing news'],
|
engines: ['bing news'],
|
||||||
pageno: 1,
|
pageno: 1,
|
||||||
|
language: 'en',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
).results;
|
).results;
|
||||||
@@ -49,7 +50,11 @@ export const GET = async (req: Request) => {
|
|||||||
data = (
|
data = (
|
||||||
await searchSearxng(
|
await searchSearxng(
|
||||||
`site:${articleWebsites[Math.floor(Math.random() * articleWebsites.length)]} ${topics[Math.floor(Math.random() * topics.length)]}`,
|
`site:${articleWebsites[Math.floor(Math.random() * articleWebsites.length)]} ${topics[Math.floor(Math.random() * topics.length)]}`,
|
||||||
{ engines: ['bing news'], pageno: 1 },
|
{
|
||||||
|
engines: ['bing news'],
|
||||||
|
pageno: 1,
|
||||||
|
language: 'en',
|
||||||
|
},
|
||||||
)
|
)
|
||||||
).results;
|
).results;
|
||||||
}
|
}
|
||||||
|
@@ -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({
|
||||||
openAIApiKey: getCustomOpenaiApiKey(),
|
apiKey: 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(),
|
||||||
openAIApiKey:
|
apiKey:
|
||||||
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({
|
||||||
openAIApiKey: getCustomOpenaiApiKey(),
|
apiKey: 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({
|
||||||
openAIApiKey: getCustomOpenaiApiKey(),
|
apiKey: getCustomOpenaiApiKey(),
|
||||||
modelName: getCustomOpenaiModelName(),
|
modelName: getCustomOpenaiModelName(),
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
configuration: {
|
configuration: {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
export const POST = async (req: Request) => {
|
export const POST = async (req: Request) => {
|
||||||
try {
|
try {
|
||||||
const body: { lat: number; lng: number } = await req.json();
|
const body: { lat: number; lng: number; temperatureUnit: 'C' | 'F' } =
|
||||||
|
await req.json();
|
||||||
|
|
||||||
if (!body.lat || !body.lng) {
|
if (!body.lat || !body.lng) {
|
||||||
return Response.json(
|
return Response.json(
|
||||||
@@ -12,7 +13,7 @@ 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`,
|
`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'}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
@@ -33,12 +34,14 @@ export const POST = async (req: Request) => {
|
|||||||
humidity: number;
|
humidity: number;
|
||||||
windSpeed: number;
|
windSpeed: number;
|
||||||
icon: string;
|
icon: string;
|
||||||
|
temperatureUnit: 'C' | 'F';
|
||||||
} = {
|
} = {
|
||||||
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,
|
||||||
};
|
};
|
||||||
|
|
||||||
const code = data.current.weather_code;
|
const code = data.current.weather_code;
|
||||||
|
@@ -148,6 +148,7 @@ 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 [savingStates, setSavingStates] = useState<Record<string, boolean>>({});
|
const [savingStates, setSavingStates] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -210,6 +211,8 @@ const Page = () => {
|
|||||||
|
|
||||||
setSystemInstructions(localStorage.getItem('systemInstructions')!);
|
setSystemInstructions(localStorage.getItem('systemInstructions')!);
|
||||||
|
|
||||||
|
setTemperatureUnit(localStorage.getItem('temperatureUnit')! as 'C' | 'F');
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -368,6 +371,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') {
|
||||||
|
localStorage.setItem('temperatureUnit', value.toString());
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to save:', err);
|
console.error('Failed to save:', err);
|
||||||
@@ -416,13 +421,35 @@ const Page = () => {
|
|||||||
) : (
|
) : (
|
||||||
config && (
|
config && (
|
||||||
<div className="flex flex-col space-y-6 pb-28 lg:pb-8">
|
<div className="flex flex-col space-y-6 pb-28 lg:pb-8">
|
||||||
<SettingsSection title="Appearance">
|
<SettingsSection title="Preferences">
|
||||||
<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">
|
||||||
Theme
|
Theme
|
||||||
</p>
|
</p>
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col space-y-1">
|
||||||
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
|
Temperature Unit
|
||||||
|
</p>
|
||||||
|
<Select
|
||||||
|
value={temperatureUnit ?? undefined}
|
||||||
|
onChange={(e) => {
|
||||||
|
setTemperatureUnit(e.target.value as 'C' | 'F');
|
||||||
|
saveConfig('temperatureUnit', e.target.value);
|
||||||
|
}}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: 'Celsius',
|
||||||
|
value: 'C',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Fahrenheit',
|
||||||
|
value: 'F',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
<SettingsSection title="Automatic Search">
|
<SettingsSection title="Automatic Search">
|
||||||
@@ -516,7 +543,7 @@ const Page = () => {
|
|||||||
<SettingsSection title="System Instructions">
|
<SettingsSection title="System Instructions">
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
<Textarea
|
<Textarea
|
||||||
value={systemInstructions}
|
value={systemInstructions ?? undefined}
|
||||||
isSaving={savingStates['systemInstructions']}
|
isSaving={savingStates['systemInstructions']}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setSystemInstructions(e.target.value);
|
setSystemInstructions(e.target.value);
|
||||||
|
@@ -9,7 +9,9 @@ const WeatherWidget = () => {
|
|||||||
humidity: 0,
|
humidity: 0,
|
||||||
windSpeed: 0,
|
windSpeed: 0,
|
||||||
icon: '',
|
icon: '',
|
||||||
|
temperatureUnit: 'C',
|
||||||
});
|
});
|
||||||
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -73,6 +75,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',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,6 +94,7 @@ const WeatherWidget = () => {
|
|||||||
humidity: data.humidity,
|
humidity: data.humidity,
|
||||||
windSpeed: data.windSpeed,
|
windSpeed: data.windSpeed,
|
||||||
icon: data.icon,
|
icon: data.icon,
|
||||||
|
temperatureUnit: data.temperatureUnit,
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
@@ -125,7 +129,7 @@ const WeatherWidget = () => {
|
|||||||
className="h-10 w-auto"
|
className="h-10 w-auto"
|
||||||
/>
|
/>
|
||||||
<span className="text-base font-semibold text-black dark:text-white">
|
<span className="text-base font-semibold text-black dark:text-white">
|
||||||
{data.temperature}°C
|
{data.temperature}°{data.temperatureUnit}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col justify-between flex-1 h-full py-1">
|
<div className="flex flex-col justify-between flex-1 h-full py-1">
|
||||||
|
@@ -1,63 +1,41 @@
|
|||||||
export const webSearchRetrieverPrompt = `
|
export const webSearchRetrieverPrompt = `
|
||||||
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.
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
There are several examples attached for your reference inside the below \`examples\` XML block
|
Return ONLY a JSON object that matches this schema:
|
||||||
|
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"
|
||||||
|
|
||||||
<examples>
|
Rules
|
||||||
1. Follow up question: What is the capital of France
|
- Greetings / simple writing tasks → query:"", links:[], searchRequired:false, searchMode:""
|
||||||
Rephrased question:\`
|
- Summarizing a URL → query:"summarize", links:[url...], searchRequired:true, searchMode:"normal"
|
||||||
<question>
|
- Asking for news/articles → searchMode:"news"
|
||||||
Capital of france
|
|
||||||
</question>
|
Examples
|
||||||
\`
|
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?
|
||||||
Rephrased question\`
|
"query":"","links":[],"searchRequired":false,"searchMode":""
|
||||||
<question>
|
|
||||||
not_needed
|
|
||||||
</question>
|
|
||||||
\`
|
|
||||||
|
|
||||||
3. Follow up question: What is Docker?
|
3. Follow-up: What is Docker?
|
||||||
Rephrased question: \`
|
"query":"what is Docker","links":[],"searchRequired":true,"searchMode":"normal"
|
||||||
<question>
|
|
||||||
What is Docker
|
|
||||||
</question>
|
|
||||||
\`
|
|
||||||
|
|
||||||
4. Follow up question: Can you tell me what is X from https://example.com
|
4. Follow-up: Can you tell me what is X from https://example.com?
|
||||||
Rephrased question: \`
|
"query":"what is X","links":["https://example.com"],"searchRequired":true,"searchMode":"normal"
|
||||||
<question>
|
|
||||||
Can you tell me what is X?
|
|
||||||
</question>
|
|
||||||
|
|
||||||
<links>
|
5. Follow-up: Summarize the content from https://example.com
|
||||||
https://example.com
|
"query":"summarize","links":["https://example.com"],"searchRequired":true,"searchMode":"normal"
|
||||||
</links>
|
|
||||||
\`
|
|
||||||
|
|
||||||
5. Follow up question: Summarize the content from https://example.com
|
6. Follow-up: Latest news about AI
|
||||||
Rephrased question: \`
|
"query":"latest news about AI","links":[],"searchRequired":true,"searchMode":"news"
|
||||||
<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({
|
||||||
openAIApiKey: apiKey,
|
apiKey: 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({
|
||||||
openAIApiKey: apiKey,
|
apiKey: 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({
|
||||||
openAIApiKey: deepseekApiKey,
|
apiKey: deepseekApiKey,
|
||||||
modelName: model.key,
|
modelName: model.key,
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
configuration: {
|
configuration: {
|
||||||
|
@@ -29,12 +29,15 @@ export const loadGroqChatModels = async () => {
|
|||||||
chatModels[model.id] = {
|
chatModels[model.id] = {
|
||||||
displayName: model.id,
|
displayName: model.id,
|
||||||
model: new ChatOpenAI({
|
model: new ChatOpenAI({
|
||||||
openAIApiKey: groqApiKey,
|
apiKey: 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({
|
||||||
openAIApiKey: customOpenAiApiKey,
|
apiKey: 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({
|
||||||
openAIApiKey: 'lm-studio',
|
apiKey: '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({
|
||||||
openAIApiKey: 'lm-studio',
|
apiKey: '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/community/chat_models/ollama';
|
import { ChatOllama } from '@langchain/ollama';
|
||||||
import { OllamaEmbeddings } from '@langchain/community/embeddings/ollama';
|
import { OllamaEmbeddings } from '@langchain/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({
|
||||||
openAIApiKey: openaiApiKey,
|
apiKey: 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({
|
||||||
openAIApiKey: openaiApiKey,
|
apiKey: openaiApiKey,
|
||||||
modelName: model.key,
|
modelName: model.key,
|
||||||
}) as unknown as Embeddings,
|
}) as unknown as Embeddings,
|
||||||
};
|
};
|
||||||
|
@@ -24,6 +24,7 @@ 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: (
|
||||||
@@ -52,6 +53,17 @@ 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();
|
||||||
@@ -62,73 +74,71 @@ 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),
|
||||||
llm,
|
Object.assign(
|
||||||
this.strParser,
|
Object.create(Object.getPrototypeOf(llm)),
|
||||||
RunnableLambda.from(async (input: string) => {
|
llm,
|
||||||
const linksOutputParser = new LineListOutputParser({
|
).withStructuredOutput(retrieverLLMOutputSchema, {
|
||||||
key: 'links',
|
...(llm.metadata?.['model-type'] === 'groq'
|
||||||
});
|
? {
|
||||||
|
method: 'json-object',
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
}),
|
||||||
|
RunnableLambda.from(
|
||||||
|
async (input: z.infer<typeof retrieverLLMOutputSchema>) => {
|
||||||
|
let question = input.query;
|
||||||
|
const links = input.links;
|
||||||
|
|
||||||
const questionOutputParser = new LineOutputParser({
|
if (!input.searchRequired) {
|
||||||
key: 'question',
|
return { query: '', docs: [] };
|
||||||
});
|
|
||||||
|
|
||||||
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';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let docs: Document[] = [];
|
if (links.length > 0) {
|
||||||
|
if (question.length === 0) {
|
||||||
const linkDocs = await getDocumentsFromLinks({ links });
|
question = 'summarize';
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const docIndex = docGroups.findIndex(
|
let docs: Document[] = [];
|
||||||
(d) =>
|
|
||||||
d.metadata.url === doc.metadata.url &&
|
|
||||||
d.metadata.totalDocs < 10,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (docIndex !== -1) {
|
const linkDocs = await getDocumentsFromLinks({ links });
|
||||||
docGroups[docIndex].pageContent =
|
|
||||||
docGroups[docIndex].pageContent + `\n\n` + doc.pageContent;
|
|
||||||
docGroups[docIndex].metadata.totalDocs += 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(
|
const docGroups: Document[] = [];
|
||||||
docGroups.map(async (doc) => {
|
|
||||||
const res = await llm.invoke(`
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -189,46 +199,50 @@ 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: documents };
|
return { query: question, docs: docs };
|
||||||
}
|
} 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