mirror of
				https://github.com/ItzCrazyKns/Perplexica.git
				synced 2025-10-31 03:18:16 +00:00 
			
		
		
		
	Compare commits
	
		
			5 Commits
		
	
	
		
			feat/impro
			...
			feat/struc
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | df33229934 | ||
|  | 49fafaa096 | ||
|  | ca9b32a23b | ||
|  | 76e3ff4e02 | ||
|  | eabf3ca7d3 | 
							
								
								
									
										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: { | ||||||
|   | |||||||
| @@ -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,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,26 +74,24 @@ 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( | ||||||
|  |         Object.create(Object.getPrototypeOf(llm)), | ||||||
|         llm, |         llm, | ||||||
|       this.strParser, |       ).withStructuredOutput(retrieverLLMOutputSchema, { | ||||||
|       RunnableLambda.from(async (input: string) => { |         ...(llm.metadata?.['model-type'] === 'groq' | ||||||
|         const linksOutputParser = new LineListOutputParser({ |           ? { | ||||||
|           key: 'links', |               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', |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         const links = await linksOutputParser.parse(input); |  | ||||||
|         let question = this.config.summarizer |  | ||||||
|           ? await questionOutputParser.parse(input) |  | ||||||
|           : input; |  | ||||||
|  |  | ||||||
|         if (question === 'not_needed') { |  | ||||||
|             return { query: '', docs: [] }; |             return { query: '', docs: [] }; | ||||||
|           } |           } | ||||||
|  |  | ||||||
| @@ -207,7 +217,10 @@ class MetaSearchAgent implements MetaSearchAgentType { | |||||||
|  |  | ||||||
|             const res = await searchSearxng(question, { |             const res = await searchSearxng(question, { | ||||||
|               language: 'en', |               language: 'en', | ||||||
|             engines: this.config.activeEngines, |               engines: | ||||||
|  |                 input.searchMode === 'normal' | ||||||
|  |                   ? this.config.activeEngines | ||||||
|  |                   : ['bing news'], | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|             const documents = res.results.map( |             const documents = res.results.map( | ||||||
| @@ -228,7 +241,8 @@ class MetaSearchAgent implements MetaSearchAgentType { | |||||||
|  |  | ||||||
|             return { query: question, docs: documents }; |             return { query: question, docs: documents }; | ||||||
|           } |           } | ||||||
|       }), |         }, | ||||||
|  |       ), | ||||||
|     ]); |     ]); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user