prettier
This commit is contained in:
parent
5b1aaee605
commit
3b737a078a
63 changed files with 1132 additions and 1853 deletions
|
|
@ -1,24 +1,16 @@
|
|||
import { BaseMessage } from '@langchain/core/messages';
|
||||
import {
|
||||
PromptTemplate,
|
||||
ChatPromptTemplate,
|
||||
MessagesPlaceholder,
|
||||
} from '@langchain/core/prompts';
|
||||
import {
|
||||
RunnableSequence,
|
||||
RunnableMap,
|
||||
RunnableLambda,
|
||||
} from '@langchain/core/runnables';
|
||||
import { StringOutputParser } from '@langchain/core/output_parsers';
|
||||
import { Document } from '@langchain/core/documents';
|
||||
import { searchSearxng } from '../lib/searxng';
|
||||
import type { StreamEvent } from '@langchain/core/tracers/log_stream';
|
||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import type { Embeddings } from '@langchain/core/embeddings';
|
||||
import formatChatHistoryAsString from '../utils/formatHistory';
|
||||
import eventEmitter from 'events';
|
||||
import computeSimilarity from '../utils/computeSimilarity';
|
||||
import logger from '../utils/logger';
|
||||
import { BaseMessage } from "@langchain/core/messages";
|
||||
import { PromptTemplate, ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
|
||||
import { RunnableSequence, RunnableMap, RunnableLambda } from "@langchain/core/runnables";
|
||||
import { StringOutputParser } from "@langchain/core/output_parsers";
|
||||
import { Document } from "@langchain/core/documents";
|
||||
import { searchSearxng } from "../lib/searxng";
|
||||
import type { StreamEvent } from "@langchain/core/tracers/log_stream";
|
||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||
import type { Embeddings } from "@langchain/core/embeddings";
|
||||
import formatChatHistoryAsString from "../utils/formatHistory";
|
||||
import eventEmitter from "events";
|
||||
import computeSimilarity from "../utils/computeSimilarity";
|
||||
import logger from "../utils/logger";
|
||||
|
||||
const basicAcademicSearchRetrieverPrompt = `
|
||||
You will be given a conversation below and a follow up question. You need to rephrase the follow-up question if needed so it is a standalone question that can be used by the LLM to search the web for information.
|
||||
|
|
@ -65,34 +57,16 @@ const basicAcademicSearchResponsePrompt = `
|
|||
|
||||
const strParser = new StringOutputParser();
|
||||
|
||||
const handleStream = async (
|
||||
stream: AsyncGenerator<StreamEvent, unknown, unknown>,
|
||||
emitter: eventEmitter,
|
||||
) => {
|
||||
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
||||
for await (const event of stream) {
|
||||
if (
|
||||
event.event === 'on_chain_end' &&
|
||||
event.name === 'FinalSourceRetriever'
|
||||
) {
|
||||
emitter.emit(
|
||||
'data',
|
||||
JSON.stringify({ type: 'sources', data: event.data.output }),
|
||||
);
|
||||
if (event.event === "on_chain_end" && event.name === "FinalSourceRetriever") {
|
||||
emitter.emit("data", JSON.stringify({ type: "sources", data: event.data.output }));
|
||||
}
|
||||
if (
|
||||
event.event === 'on_chain_stream' &&
|
||||
event.name === 'FinalResponseGenerator'
|
||||
) {
|
||||
emitter.emit(
|
||||
'data',
|
||||
JSON.stringify({ type: 'response', data: event.data.chunk }),
|
||||
);
|
||||
if (event.event === "on_chain_stream" && event.name === "FinalResponseGenerator") {
|
||||
emitter.emit("data", JSON.stringify({ type: "response", data: event.data.chunk }));
|
||||
}
|
||||
if (
|
||||
event.event === 'on_chain_end' &&
|
||||
event.name === 'FinalResponseGenerator'
|
||||
) {
|
||||
emitter.emit('end');
|
||||
if (event.event === "on_chain_end" && event.name === "FinalResponseGenerator") {
|
||||
emitter.emit("end");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -108,22 +82,17 @@ const createBasicAcademicSearchRetrieverChain = (llm: BaseChatModel) => {
|
|||
llm,
|
||||
strParser,
|
||||
RunnableLambda.from(async (input: string) => {
|
||||
if (input === 'not_needed') {
|
||||
return { query: '', docs: [] };
|
||||
if (input === "not_needed") {
|
||||
return { query: "", docs: [] };
|
||||
}
|
||||
|
||||
const res = await searchSearxng(input, {
|
||||
language: 'en',
|
||||
engines: [
|
||||
'arxiv',
|
||||
'google scholar',
|
||||
'internetarchivescholar',
|
||||
'pubmed',
|
||||
],
|
||||
language: "en",
|
||||
engines: ["arxiv", "google scholar", "internetarchivescholar", "pubmed"],
|
||||
});
|
||||
|
||||
const documents = res.results.map(
|
||||
(result) =>
|
||||
result =>
|
||||
new Document({
|
||||
pageContent: result.content,
|
||||
metadata: {
|
||||
|
|
@ -139,36 +108,22 @@ const createBasicAcademicSearchRetrieverChain = (llm: BaseChatModel) => {
|
|||
]);
|
||||
};
|
||||
|
||||
const createBasicAcademicSearchAnsweringChain = (
|
||||
llm: BaseChatModel,
|
||||
embeddings: Embeddings,
|
||||
) => {
|
||||
const basicAcademicSearchRetrieverChain =
|
||||
createBasicAcademicSearchRetrieverChain(llm);
|
||||
const createBasicAcademicSearchAnsweringChain = (llm: BaseChatModel, embeddings: Embeddings) => {
|
||||
const basicAcademicSearchRetrieverChain = createBasicAcademicSearchRetrieverChain(llm);
|
||||
|
||||
const processDocs = async (docs: Document[]) => {
|
||||
return docs
|
||||
.map((_, index) => `${index + 1}. ${docs[index].pageContent}`)
|
||||
.join('\n');
|
||||
return docs.map((_, index) => `${index + 1}. ${docs[index].pageContent}`).join("\n");
|
||||
};
|
||||
|
||||
const rerankDocs = async ({
|
||||
query,
|
||||
docs,
|
||||
}: {
|
||||
query: string;
|
||||
docs: Document[];
|
||||
}) => {
|
||||
const rerankDocs = async ({ query, docs }: { query: string; docs: Document[] }) => {
|
||||
if (docs.length === 0) {
|
||||
return docs;
|
||||
}
|
||||
|
||||
const docsWithContent = docs.filter(
|
||||
(doc) => doc.pageContent && doc.pageContent.length > 0,
|
||||
);
|
||||
const docsWithContent = docs.filter(doc => doc.pageContent && doc.pageContent.length > 0);
|
||||
|
||||
const [docEmbeddings, queryEmbedding] = await Promise.all([
|
||||
embeddings.embedDocuments(docsWithContent.map((doc) => doc.pageContent)),
|
||||
embeddings.embedDocuments(docsWithContent.map(doc => doc.pageContent)),
|
||||
embeddings.embedQuery(query),
|
||||
]);
|
||||
|
||||
|
|
@ -184,7 +139,7 @@ const createBasicAcademicSearchAnsweringChain = (
|
|||
const sortedDocs = similarity
|
||||
.sort((a, b) => b.similarity - a.similarity)
|
||||
.slice(0, 15)
|
||||
.map((sim) => docsWithContent[sim.index]);
|
||||
.map(sim => docsWithContent[sim.index]);
|
||||
|
||||
return sortedDocs;
|
||||
};
|
||||
|
|
@ -194,41 +149,35 @@ const createBasicAcademicSearchAnsweringChain = (
|
|||
query: (input: BasicChainInput) => input.query,
|
||||
chat_history: (input: BasicChainInput) => input.chat_history,
|
||||
context: RunnableSequence.from([
|
||||
(input) => ({
|
||||
input => ({
|
||||
query: input.query,
|
||||
chat_history: formatChatHistoryAsString(input.chat_history),
|
||||
}),
|
||||
basicAcademicSearchRetrieverChain
|
||||
.pipe(rerankDocs)
|
||||
.withConfig({
|
||||
runName: 'FinalSourceRetriever',
|
||||
runName: "FinalSourceRetriever",
|
||||
})
|
||||
.pipe(processDocs),
|
||||
]),
|
||||
}),
|
||||
ChatPromptTemplate.fromMessages([
|
||||
['system', basicAcademicSearchResponsePrompt],
|
||||
new MessagesPlaceholder('chat_history'),
|
||||
['user', '{query}'],
|
||||
["system", basicAcademicSearchResponsePrompt],
|
||||
new MessagesPlaceholder("chat_history"),
|
||||
["user", "{query}"],
|
||||
]),
|
||||
llm,
|
||||
strParser,
|
||||
]).withConfig({
|
||||
runName: 'FinalResponseGenerator',
|
||||
runName: "FinalResponseGenerator",
|
||||
});
|
||||
};
|
||||
|
||||
const basicAcademicSearch = (
|
||||
query: string,
|
||||
history: BaseMessage[],
|
||||
llm: BaseChatModel,
|
||||
embeddings: Embeddings,
|
||||
) => {
|
||||
const basicAcademicSearch = (query: string, history: BaseMessage[], llm: BaseChatModel, embeddings: Embeddings) => {
|
||||
const emitter = new eventEmitter();
|
||||
|
||||
try {
|
||||
const basicAcademicSearchAnsweringChain =
|
||||
createBasicAcademicSearchAnsweringChain(llm, embeddings);
|
||||
const basicAcademicSearchAnsweringChain = createBasicAcademicSearchAnsweringChain(llm, embeddings);
|
||||
|
||||
const stream = basicAcademicSearchAnsweringChain.streamEvents(
|
||||
{
|
||||
|
|
@ -236,28 +185,20 @@ const basicAcademicSearch = (
|
|||
query: query,
|
||||
},
|
||||
{
|
||||
version: 'v1',
|
||||
version: "v1",
|
||||
},
|
||||
);
|
||||
|
||||
handleStream(stream, emitter);
|
||||
} catch (err) {
|
||||
emitter.emit(
|
||||
'error',
|
||||
JSON.stringify({ data: 'An error has occurred please try again later' }),
|
||||
);
|
||||
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
||||
logger.error(`Error in academic search: ${err}`);
|
||||
}
|
||||
|
||||
return emitter;
|
||||
};
|
||||
|
||||
const handleAcademicSearch = (
|
||||
message: string,
|
||||
history: BaseMessage[],
|
||||
llm: BaseChatModel,
|
||||
embeddings: Embeddings,
|
||||
) => {
|
||||
const handleAcademicSearch = (message: string, history: BaseMessage[], llm: BaseChatModel, embeddings: Embeddings) => {
|
||||
const emitter = basicAcademicSearch(message, history, llm, embeddings);
|
||||
return emitter;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
import {
|
||||
RunnableSequence,
|
||||
RunnableMap,
|
||||
RunnableLambda,
|
||||
} from '@langchain/core/runnables';
|
||||
import { PromptTemplate } from '@langchain/core/prompts';
|
||||
import formatChatHistoryAsString from '../utils/formatHistory';
|
||||
import { BaseMessage } from '@langchain/core/messages';
|
||||
import { StringOutputParser } from '@langchain/core/output_parsers';
|
||||
import { searchSearxng } from '../lib/searxng';
|
||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import { RunnableSequence, RunnableMap, RunnableLambda } from "@langchain/core/runnables";
|
||||
import { PromptTemplate } from "@langchain/core/prompts";
|
||||
import formatChatHistoryAsString from "../utils/formatHistory";
|
||||
import { BaseMessage } from "@langchain/core/messages";
|
||||
import { StringOutputParser } from "@langchain/core/output_parsers";
|
||||
import { searchSearxng } from "../lib/searxng";
|
||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||
|
||||
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.
|
||||
|
|
@ -53,12 +49,12 @@ const createImageSearchChain = (llm: BaseChatModel) => {
|
|||
strParser,
|
||||
RunnableLambda.from(async (input: string) => {
|
||||
const res = await searchSearxng(input, {
|
||||
engines: ['bing images', 'google images'],
|
||||
engines: ["bing images", "google images"],
|
||||
});
|
||||
|
||||
const images = [];
|
||||
|
||||
res.results.forEach((result) => {
|
||||
res.results.forEach(result => {
|
||||
if (result.img_src && result.url && result.title) {
|
||||
images.push({
|
||||
img_src: result.img_src,
|
||||
|
|
@ -73,10 +69,7 @@ const createImageSearchChain = (llm: BaseChatModel) => {
|
|||
]);
|
||||
};
|
||||
|
||||
const handleImageSearch = (
|
||||
input: ImageSearchChainInput,
|
||||
llm: BaseChatModel,
|
||||
) => {
|
||||
const handleImageSearch = (input: ImageSearchChainInput, llm: BaseChatModel) => {
|
||||
const imageSearchChain = createImageSearchChain(llm);
|
||||
return imageSearchChain.invoke(input);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,24 +1,16 @@
|
|||
import { BaseMessage } from '@langchain/core/messages';
|
||||
import {
|
||||
PromptTemplate,
|
||||
ChatPromptTemplate,
|
||||
MessagesPlaceholder,
|
||||
} from '@langchain/core/prompts';
|
||||
import {
|
||||
RunnableSequence,
|
||||
RunnableMap,
|
||||
RunnableLambda,
|
||||
} from '@langchain/core/runnables';
|
||||
import { StringOutputParser } from '@langchain/core/output_parsers';
|
||||
import { Document } from '@langchain/core/documents';
|
||||
import { searchSearxng } from '../lib/searxng';
|
||||
import type { StreamEvent } from '@langchain/core/tracers/log_stream';
|
||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import type { Embeddings } from '@langchain/core/embeddings';
|
||||
import formatChatHistoryAsString from '../utils/formatHistory';
|
||||
import eventEmitter from 'events';
|
||||
import computeSimilarity from '../utils/computeSimilarity';
|
||||
import logger from '../utils/logger';
|
||||
import { BaseMessage } from "@langchain/core/messages";
|
||||
import { PromptTemplate, ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
|
||||
import { RunnableSequence, RunnableMap, RunnableLambda } from "@langchain/core/runnables";
|
||||
import { StringOutputParser } from "@langchain/core/output_parsers";
|
||||
import { Document } from "@langchain/core/documents";
|
||||
import { searchSearxng } from "../lib/searxng";
|
||||
import type { StreamEvent } from "@langchain/core/tracers/log_stream";
|
||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||
import type { Embeddings } from "@langchain/core/embeddings";
|
||||
import formatChatHistoryAsString from "../utils/formatHistory";
|
||||
import eventEmitter from "events";
|
||||
import computeSimilarity from "../utils/computeSimilarity";
|
||||
import logger from "../utils/logger";
|
||||
|
||||
const basicRedditSearchRetrieverPrompt = `
|
||||
You will be given a conversation below and a follow up question. You need to rephrase the follow-up question if needed so it is a standalone question that can be used by the LLM to search the web for information.
|
||||
|
|
@ -65,34 +57,16 @@ const basicRedditSearchResponsePrompt = `
|
|||
|
||||
const strParser = new StringOutputParser();
|
||||
|
||||
const handleStream = async (
|
||||
stream: AsyncGenerator<StreamEvent, unknown, unknown>,
|
||||
emitter: eventEmitter,
|
||||
) => {
|
||||
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
||||
for await (const event of stream) {
|
||||
if (
|
||||
event.event === 'on_chain_end' &&
|
||||
event.name === 'FinalSourceRetriever'
|
||||
) {
|
||||
emitter.emit(
|
||||
'data',
|
||||
JSON.stringify({ type: 'sources', data: event.data.output }),
|
||||
);
|
||||
if (event.event === "on_chain_end" && event.name === "FinalSourceRetriever") {
|
||||
emitter.emit("data", JSON.stringify({ type: "sources", data: event.data.output }));
|
||||
}
|
||||
if (
|
||||
event.event === 'on_chain_stream' &&
|
||||
event.name === 'FinalResponseGenerator'
|
||||
) {
|
||||
emitter.emit(
|
||||
'data',
|
||||
JSON.stringify({ type: 'response', data: event.data.chunk }),
|
||||
);
|
||||
if (event.event === "on_chain_stream" && event.name === "FinalResponseGenerator") {
|
||||
emitter.emit("data", JSON.stringify({ type: "response", data: event.data.chunk }));
|
||||
}
|
||||
if (
|
||||
event.event === 'on_chain_end' &&
|
||||
event.name === 'FinalResponseGenerator'
|
||||
) {
|
||||
emitter.emit('end');
|
||||
if (event.event === "on_chain_end" && event.name === "FinalResponseGenerator") {
|
||||
emitter.emit("end");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -108,17 +82,17 @@ const createBasicRedditSearchRetrieverChain = (llm: BaseChatModel) => {
|
|||
llm,
|
||||
strParser,
|
||||
RunnableLambda.from(async (input: string) => {
|
||||
if (input === 'not_needed') {
|
||||
return { query: '', docs: [] };
|
||||
if (input === "not_needed") {
|
||||
return { query: "", docs: [] };
|
||||
}
|
||||
|
||||
const res = await searchSearxng(input, {
|
||||
language: 'en',
|
||||
engines: ['reddit'],
|
||||
language: "en",
|
||||
engines: ["reddit"],
|
||||
});
|
||||
|
||||
const documents = res.results.map(
|
||||
(result) =>
|
||||
result =>
|
||||
new Document({
|
||||
pageContent: result.content ? result.content : result.title,
|
||||
metadata: {
|
||||
|
|
@ -134,36 +108,22 @@ const createBasicRedditSearchRetrieverChain = (llm: BaseChatModel) => {
|
|||
]);
|
||||
};
|
||||
|
||||
const createBasicRedditSearchAnsweringChain = (
|
||||
llm: BaseChatModel,
|
||||
embeddings: Embeddings,
|
||||
) => {
|
||||
const basicRedditSearchRetrieverChain =
|
||||
createBasicRedditSearchRetrieverChain(llm);
|
||||
const createBasicRedditSearchAnsweringChain = (llm: BaseChatModel, embeddings: Embeddings) => {
|
||||
const basicRedditSearchRetrieverChain = createBasicRedditSearchRetrieverChain(llm);
|
||||
|
||||
const processDocs = async (docs: Document[]) => {
|
||||
return docs
|
||||
.map((_, index) => `${index + 1}. ${docs[index].pageContent}`)
|
||||
.join('\n');
|
||||
return docs.map((_, index) => `${index + 1}. ${docs[index].pageContent}`).join("\n");
|
||||
};
|
||||
|
||||
const rerankDocs = async ({
|
||||
query,
|
||||
docs,
|
||||
}: {
|
||||
query: string;
|
||||
docs: Document[];
|
||||
}) => {
|
||||
const rerankDocs = async ({ query, docs }: { query: string; docs: Document[] }) => {
|
||||
if (docs.length === 0) {
|
||||
return docs;
|
||||
}
|
||||
|
||||
const docsWithContent = docs.filter(
|
||||
(doc) => doc.pageContent && doc.pageContent.length > 0,
|
||||
);
|
||||
const docsWithContent = docs.filter(doc => doc.pageContent && doc.pageContent.length > 0);
|
||||
|
||||
const [docEmbeddings, queryEmbedding] = await Promise.all([
|
||||
embeddings.embedDocuments(docsWithContent.map((doc) => doc.pageContent)),
|
||||
embeddings.embedDocuments(docsWithContent.map(doc => doc.pageContent)),
|
||||
embeddings.embedQuery(query),
|
||||
]);
|
||||
|
||||
|
|
@ -179,8 +139,8 @@ const createBasicRedditSearchAnsweringChain = (
|
|||
const sortedDocs = similarity
|
||||
.sort((a, b) => b.similarity - a.similarity)
|
||||
.slice(0, 15)
|
||||
.filter((sim) => sim.similarity > 0.3)
|
||||
.map((sim) => docsWithContent[sim.index]);
|
||||
.filter(sim => sim.similarity > 0.3)
|
||||
.map(sim => docsWithContent[sim.index]);
|
||||
|
||||
return sortedDocs;
|
||||
};
|
||||
|
|
@ -190,69 +150,55 @@ const createBasicRedditSearchAnsweringChain = (
|
|||
query: (input: BasicChainInput) => input.query,
|
||||
chat_history: (input: BasicChainInput) => input.chat_history,
|
||||
context: RunnableSequence.from([
|
||||
(input) => ({
|
||||
input => ({
|
||||
query: input.query,
|
||||
chat_history: formatChatHistoryAsString(input.chat_history),
|
||||
}),
|
||||
basicRedditSearchRetrieverChain
|
||||
.pipe(rerankDocs)
|
||||
.withConfig({
|
||||
runName: 'FinalSourceRetriever',
|
||||
runName: "FinalSourceRetriever",
|
||||
})
|
||||
.pipe(processDocs),
|
||||
]),
|
||||
}),
|
||||
ChatPromptTemplate.fromMessages([
|
||||
['system', basicRedditSearchResponsePrompt],
|
||||
new MessagesPlaceholder('chat_history'),
|
||||
['user', '{query}'],
|
||||
["system", basicRedditSearchResponsePrompt],
|
||||
new MessagesPlaceholder("chat_history"),
|
||||
["user", "{query}"],
|
||||
]),
|
||||
llm,
|
||||
strParser,
|
||||
]).withConfig({
|
||||
runName: 'FinalResponseGenerator',
|
||||
runName: "FinalResponseGenerator",
|
||||
});
|
||||
};
|
||||
|
||||
const basicRedditSearch = (
|
||||
query: string,
|
||||
history: BaseMessage[],
|
||||
llm: BaseChatModel,
|
||||
embeddings: Embeddings,
|
||||
) => {
|
||||
const basicRedditSearch = (query: string, history: BaseMessage[], llm: BaseChatModel, embeddings: Embeddings) => {
|
||||
const emitter = new eventEmitter();
|
||||
|
||||
try {
|
||||
const basicRedditSearchAnsweringChain =
|
||||
createBasicRedditSearchAnsweringChain(llm, embeddings);
|
||||
const basicRedditSearchAnsweringChain = createBasicRedditSearchAnsweringChain(llm, embeddings);
|
||||
const stream = basicRedditSearchAnsweringChain.streamEvents(
|
||||
{
|
||||
chat_history: history,
|
||||
query: query,
|
||||
},
|
||||
{
|
||||
version: 'v1',
|
||||
version: "v1",
|
||||
},
|
||||
);
|
||||
|
||||
handleStream(stream, emitter);
|
||||
} catch (err) {
|
||||
emitter.emit(
|
||||
'error',
|
||||
JSON.stringify({ data: 'An error has occurred please try again later' }),
|
||||
);
|
||||
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
||||
logger.error(`Error in RedditSearch: ${err}`);
|
||||
}
|
||||
|
||||
return emitter;
|
||||
};
|
||||
|
||||
const handleRedditSearch = (
|
||||
message: string,
|
||||
history: BaseMessage[],
|
||||
llm: BaseChatModel,
|
||||
embeddings: Embeddings,
|
||||
) => {
|
||||
const handleRedditSearch = (message: string, history: BaseMessage[], llm: BaseChatModel, embeddings: Embeddings) => {
|
||||
const emitter = basicRedditSearch(message, history, llm, embeddings);
|
||||
return emitter;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { RunnableSequence, RunnableMap } from '@langchain/core/runnables';
|
||||
import ListLineOutputParser from '../lib/outputParsers/listLineOutputParser';
|
||||
import { PromptTemplate } from '@langchain/core/prompts';
|
||||
import formatChatHistoryAsString from '../utils/formatHistory';
|
||||
import { BaseMessage } from '@langchain/core/messages';
|
||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import { ChatOpenAI } from '@langchain/openai';
|
||||
import { RunnableSequence, RunnableMap } from "@langchain/core/runnables";
|
||||
import ListLineOutputParser from "../lib/outputParsers/listLineOutputParser";
|
||||
import { PromptTemplate } from "@langchain/core/prompts";
|
||||
import formatChatHistoryAsString from "../utils/formatHistory";
|
||||
import { BaseMessage } from "@langchain/core/messages";
|
||||
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
|
||||
const suggestionGeneratorPrompt = `
|
||||
You are an AI suggestion generator for an AI powered search engine. You will be given a conversation below. You need to generate 4-5 suggestions based on the conversation. The suggestion should be relevant to the conversation that can be used by the user to ask the chat model for more information.
|
||||
|
|
@ -28,14 +28,13 @@ type SuggestionGeneratorInput = {
|
|||
};
|
||||
|
||||
const outputParser = new ListLineOutputParser({
|
||||
key: 'suggestions',
|
||||
key: "suggestions",
|
||||
});
|
||||
|
||||
const createSuggestionGeneratorChain = (llm: BaseChatModel) => {
|
||||
return RunnableSequence.from([
|
||||
RunnableMap.from({
|
||||
chat_history: (input: SuggestionGeneratorInput) =>
|
||||
formatChatHistoryAsString(input.chat_history),
|
||||
chat_history: (input: SuggestionGeneratorInput) => formatChatHistoryAsString(input.chat_history),
|
||||
}),
|
||||
PromptTemplate.fromTemplate(suggestionGeneratorPrompt),
|
||||
llm,
|
||||
|
|
@ -43,10 +42,7 @@ const createSuggestionGeneratorChain = (llm: BaseChatModel) => {
|
|||
]);
|
||||
};
|
||||
|
||||
const generateSuggestions = (
|
||||
input: SuggestionGeneratorInput,
|
||||
llm: BaseChatModel,
|
||||
) => {
|
||||
const generateSuggestions = (input: SuggestionGeneratorInput, llm: BaseChatModel) => {
|
||||
(llm as ChatOpenAI).temperature = 0;
|
||||
const suggestionGeneratorChain = createSuggestionGeneratorChain(llm);
|
||||
return suggestionGeneratorChain.invoke(input);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
import {
|
||||
RunnableSequence,
|
||||
RunnableMap,
|
||||
RunnableLambda,
|
||||
} from '@langchain/core/runnables';
|
||||
import { PromptTemplate } from '@langchain/core/prompts';
|
||||
import formatChatHistoryAsString from '../utils/formatHistory';
|
||||
import { BaseMessage } from '@langchain/core/messages';
|
||||
import { StringOutputParser } from '@langchain/core/output_parsers';
|
||||
import { searchSearxng } from '../lib/searxng';
|
||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import { RunnableSequence, RunnableMap, RunnableLambda } from "@langchain/core/runnables";
|
||||
import { PromptTemplate } from "@langchain/core/prompts";
|
||||
import formatChatHistoryAsString from "../utils/formatHistory";
|
||||
import { BaseMessage } from "@langchain/core/messages";
|
||||
import { StringOutputParser } from "@langchain/core/output_parsers";
|
||||
import { searchSearxng } from "../lib/searxng";
|
||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||
|
||||
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.
|
||||
|
|
@ -53,18 +49,13 @@ const createVideoSearchChain = (llm: BaseChatModel) => {
|
|||
strParser,
|
||||
RunnableLambda.from(async (input: string) => {
|
||||
const res = await searchSearxng(input, {
|
||||
engines: ['youtube'],
|
||||
engines: ["youtube"],
|
||||
});
|
||||
|
||||
const videos = [];
|
||||
|
||||
res.results.forEach((result) => {
|
||||
if (
|
||||
result.thumbnail &&
|
||||
result.url &&
|
||||
result.title &&
|
||||
result.iframe_src
|
||||
) {
|
||||
res.results.forEach(result => {
|
||||
if (result.thumbnail && result.url && result.title && result.iframe_src) {
|
||||
videos.push({
|
||||
img_src: result.thumbnail,
|
||||
url: result.url,
|
||||
|
|
@ -79,10 +70,7 @@ const createVideoSearchChain = (llm: BaseChatModel) => {
|
|||
]);
|
||||
};
|
||||
|
||||
const handleVideoSearch = (
|
||||
input: VideoSearchChainInput,
|
||||
llm: BaseChatModel,
|
||||
) => {
|
||||
const handleVideoSearch = (input: VideoSearchChainInput, llm: BaseChatModel) => {
|
||||
const VideoSearchChain = createVideoSearchChain(llm);
|
||||
return VideoSearchChain.invoke(input);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,24 +1,16 @@
|
|||
import { BaseMessage } from '@langchain/core/messages';
|
||||
import {
|
||||
PromptTemplate,
|
||||
ChatPromptTemplate,
|
||||
MessagesPlaceholder,
|
||||
} from '@langchain/core/prompts';
|
||||
import {
|
||||
RunnableSequence,
|
||||
RunnableMap,
|
||||
RunnableLambda,
|
||||
} from '@langchain/core/runnables';
|
||||
import { StringOutputParser } from '@langchain/core/output_parsers';
|
||||
import { Document } from '@langchain/core/documents';
|
||||
import { searchSearxng } from '../lib/searxng';
|
||||
import type { StreamEvent } from '@langchain/core/tracers/log_stream';
|
||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import type { Embeddings } from '@langchain/core/embeddings';
|
||||
import formatChatHistoryAsString from '../utils/formatHistory';
|
||||
import eventEmitter from 'events';
|
||||
import computeSimilarity from '../utils/computeSimilarity';
|
||||
import logger from '../utils/logger';
|
||||
import { BaseMessage } from "@langchain/core/messages";
|
||||
import { PromptTemplate, ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
|
||||
import { RunnableSequence, RunnableMap, RunnableLambda } from "@langchain/core/runnables";
|
||||
import { StringOutputParser } from "@langchain/core/output_parsers";
|
||||
import { Document } from "@langchain/core/documents";
|
||||
import { searchSearxng } from "../lib/searxng";
|
||||
import type { StreamEvent } from "@langchain/core/tracers/log_stream";
|
||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||
import type { Embeddings } from "@langchain/core/embeddings";
|
||||
import formatChatHistoryAsString from "../utils/formatHistory";
|
||||
import eventEmitter from "events";
|
||||
import computeSimilarity from "../utils/computeSimilarity";
|
||||
import logger from "../utils/logger";
|
||||
|
||||
const basicSearchRetrieverPrompt = `
|
||||
You will be given a conversation below and a follow up question. You need to rephrase the follow-up question if needed so it is a standalone question that can be used by the LLM to search the web for information.
|
||||
|
|
@ -65,34 +57,16 @@ const basicWebSearchResponsePrompt = `
|
|||
|
||||
const strParser = new StringOutputParser();
|
||||
|
||||
const handleStream = async (
|
||||
stream: AsyncGenerator<StreamEvent, unknown, unknown>,
|
||||
emitter: eventEmitter,
|
||||
) => {
|
||||
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
||||
for await (const event of stream) {
|
||||
if (
|
||||
event.event === 'on_chain_end' &&
|
||||
event.name === 'FinalSourceRetriever'
|
||||
) {
|
||||
emitter.emit(
|
||||
'data',
|
||||
JSON.stringify({ type: 'sources', data: event.data.output }),
|
||||
);
|
||||
if (event.event === "on_chain_end" && event.name === "FinalSourceRetriever") {
|
||||
emitter.emit("data", JSON.stringify({ type: "sources", data: event.data.output }));
|
||||
}
|
||||
if (
|
||||
event.event === 'on_chain_stream' &&
|
||||
event.name === 'FinalResponseGenerator'
|
||||
) {
|
||||
emitter.emit(
|
||||
'data',
|
||||
JSON.stringify({ type: 'response', data: event.data.chunk }),
|
||||
);
|
||||
if (event.event === "on_chain_stream" && event.name === "FinalResponseGenerator") {
|
||||
emitter.emit("data", JSON.stringify({ type: "response", data: event.data.chunk }));
|
||||
}
|
||||
if (
|
||||
event.event === 'on_chain_end' &&
|
||||
event.name === 'FinalResponseGenerator'
|
||||
) {
|
||||
emitter.emit('end');
|
||||
if (event.event === "on_chain_end" && event.name === "FinalResponseGenerator") {
|
||||
emitter.emit("end");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -108,16 +82,16 @@ const createBasicWebSearchRetrieverChain = (llm: BaseChatModel) => {
|
|||
llm,
|
||||
strParser,
|
||||
RunnableLambda.from(async (input: string) => {
|
||||
if (input === 'not_needed') {
|
||||
return { query: '', docs: [] };
|
||||
if (input === "not_needed") {
|
||||
return { query: "", docs: [] };
|
||||
}
|
||||
|
||||
const res = await searchSearxng(input, {
|
||||
language: 'en',
|
||||
language: "en",
|
||||
});
|
||||
|
||||
const documents = res.results.map(
|
||||
(result) =>
|
||||
result =>
|
||||
new Document({
|
||||
pageContent: result.content,
|
||||
metadata: {
|
||||
|
|
@ -133,35 +107,22 @@ const createBasicWebSearchRetrieverChain = (llm: BaseChatModel) => {
|
|||
]);
|
||||
};
|
||||
|
||||
const createBasicWebSearchAnsweringChain = (
|
||||
llm: BaseChatModel,
|
||||
embeddings: Embeddings,
|
||||
) => {
|
||||
const createBasicWebSearchAnsweringChain = (llm: BaseChatModel, embeddings: Embeddings) => {
|
||||
const basicWebSearchRetrieverChain = createBasicWebSearchRetrieverChain(llm);
|
||||
|
||||
const processDocs = async (docs: Document[]) => {
|
||||
return docs
|
||||
.map((_, index) => `${index + 1}. ${docs[index].pageContent}`)
|
||||
.join('\n');
|
||||
return docs.map((_, index) => `${index + 1}. ${docs[index].pageContent}`).join("\n");
|
||||
};
|
||||
|
||||
const rerankDocs = async ({
|
||||
query,
|
||||
docs,
|
||||
}: {
|
||||
query: string;
|
||||
docs: Document[];
|
||||
}) => {
|
||||
const rerankDocs = async ({ query, docs }: { query: string; docs: Document[] }) => {
|
||||
if (docs.length === 0) {
|
||||
return docs;
|
||||
}
|
||||
|
||||
const docsWithContent = docs.filter(
|
||||
(doc) => doc.pageContent && doc.pageContent.length > 0,
|
||||
);
|
||||
const docsWithContent = docs.filter(doc => doc.pageContent && doc.pageContent.length > 0);
|
||||
|
||||
const [docEmbeddings, queryEmbedding] = await Promise.all([
|
||||
embeddings.embedDocuments(docsWithContent.map((doc) => doc.pageContent)),
|
||||
embeddings.embedDocuments(docsWithContent.map(doc => doc.pageContent)),
|
||||
embeddings.embedQuery(query),
|
||||
]);
|
||||
|
||||
|
|
@ -176,9 +137,9 @@ const createBasicWebSearchAnsweringChain = (
|
|||
|
||||
const sortedDocs = similarity
|
||||
.sort((a, b) => b.similarity - a.similarity)
|
||||
.filter((sim) => sim.similarity > 0.5)
|
||||
.filter(sim => sim.similarity > 0.5)
|
||||
.slice(0, 15)
|
||||
.map((sim) => docsWithContent[sim.index]);
|
||||
.map(sim => docsWithContent[sim.index]);
|
||||
|
||||
return sortedDocs;
|
||||
};
|
||||
|
|
@ -188,43 +149,35 @@ const createBasicWebSearchAnsweringChain = (
|
|||
query: (input: BasicChainInput) => input.query,
|
||||
chat_history: (input: BasicChainInput) => input.chat_history,
|
||||
context: RunnableSequence.from([
|
||||
(input) => ({
|
||||
input => ({
|
||||
query: input.query,
|
||||
chat_history: formatChatHistoryAsString(input.chat_history),
|
||||
}),
|
||||
basicWebSearchRetrieverChain
|
||||
.pipe(rerankDocs)
|
||||
.withConfig({
|
||||
runName: 'FinalSourceRetriever',
|
||||
runName: "FinalSourceRetriever",
|
||||
})
|
||||
.pipe(processDocs),
|
||||
]),
|
||||
}),
|
||||
ChatPromptTemplate.fromMessages([
|
||||
['system', basicWebSearchResponsePrompt],
|
||||
new MessagesPlaceholder('chat_history'),
|
||||
['user', '{query}'],
|
||||
["system", basicWebSearchResponsePrompt],
|
||||
new MessagesPlaceholder("chat_history"),
|
||||
["user", "{query}"],
|
||||
]),
|
||||
llm,
|
||||
strParser,
|
||||
]).withConfig({
|
||||
runName: 'FinalResponseGenerator',
|
||||
runName: "FinalResponseGenerator",
|
||||
});
|
||||
};
|
||||
|
||||
const basicWebSearch = (
|
||||
query: string,
|
||||
history: BaseMessage[],
|
||||
llm: BaseChatModel,
|
||||
embeddings: Embeddings,
|
||||
) => {
|
||||
const basicWebSearch = (query: string, history: BaseMessage[], llm: BaseChatModel, embeddings: Embeddings) => {
|
||||
const emitter = new eventEmitter();
|
||||
|
||||
try {
|
||||
const basicWebSearchAnsweringChain = createBasicWebSearchAnsweringChain(
|
||||
llm,
|
||||
embeddings,
|
||||
);
|
||||
const basicWebSearchAnsweringChain = createBasicWebSearchAnsweringChain(llm, embeddings);
|
||||
|
||||
const stream = basicWebSearchAnsweringChain.streamEvents(
|
||||
{
|
||||
|
|
@ -232,28 +185,20 @@ const basicWebSearch = (
|
|||
query: query,
|
||||
},
|
||||
{
|
||||
version: 'v1',
|
||||
version: "v1",
|
||||
},
|
||||
);
|
||||
|
||||
handleStream(stream, emitter);
|
||||
} catch (err) {
|
||||
emitter.emit(
|
||||
'error',
|
||||
JSON.stringify({ data: 'An error has occurred please try again later' }),
|
||||
);
|
||||
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
||||
logger.error(`Error in websearch: ${err}`);
|
||||
}
|
||||
|
||||
return emitter;
|
||||
};
|
||||
|
||||
const handleWebSearch = (
|
||||
message: string,
|
||||
history: BaseMessage[],
|
||||
llm: BaseChatModel,
|
||||
embeddings: Embeddings,
|
||||
) => {
|
||||
const handleWebSearch = (message: string, history: BaseMessage[], llm: BaseChatModel, embeddings: Embeddings) => {
|
||||
const emitter = basicWebSearch(message, history, llm, embeddings);
|
||||
return emitter;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,23 +1,15 @@
|
|||
import { BaseMessage } from '@langchain/core/messages';
|
||||
import {
|
||||
PromptTemplate,
|
||||
ChatPromptTemplate,
|
||||
MessagesPlaceholder,
|
||||
} from '@langchain/core/prompts';
|
||||
import {
|
||||
RunnableSequence,
|
||||
RunnableMap,
|
||||
RunnableLambda,
|
||||
} from '@langchain/core/runnables';
|
||||
import { StringOutputParser } from '@langchain/core/output_parsers';
|
||||
import { Document } from '@langchain/core/documents';
|
||||
import { searchSearxng } from '../lib/searxng';
|
||||
import type { StreamEvent } from '@langchain/core/tracers/log_stream';
|
||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import type { Embeddings } from '@langchain/core/embeddings';
|
||||
import formatChatHistoryAsString from '../utils/formatHistory';
|
||||
import eventEmitter from 'events';
|
||||
import logger from '../utils/logger';
|
||||
import { BaseMessage } from "@langchain/core/messages";
|
||||
import { PromptTemplate, ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
|
||||
import { RunnableSequence, RunnableMap, RunnableLambda } from "@langchain/core/runnables";
|
||||
import { StringOutputParser } from "@langchain/core/output_parsers";
|
||||
import { Document } from "@langchain/core/documents";
|
||||
import { searchSearxng } from "../lib/searxng";
|
||||
import type { StreamEvent } from "@langchain/core/tracers/log_stream";
|
||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||
import type { Embeddings } from "@langchain/core/embeddings";
|
||||
import formatChatHistoryAsString from "../utils/formatHistory";
|
||||
import eventEmitter from "events";
|
||||
import logger from "../utils/logger";
|
||||
|
||||
const basicWolframAlphaSearchRetrieverPrompt = `
|
||||
You will be given a conversation below and a follow up question. You need to rephrase the follow-up question if needed so it is a standalone question that can be used by the LLM to search the web for information.
|
||||
|
|
@ -64,34 +56,16 @@ const basicWolframAlphaSearchResponsePrompt = `
|
|||
|
||||
const strParser = new StringOutputParser();
|
||||
|
||||
const handleStream = async (
|
||||
stream: AsyncGenerator<StreamEvent, unknown, unknown>,
|
||||
emitter: eventEmitter,
|
||||
) => {
|
||||
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
||||
for await (const event of stream) {
|
||||
if (
|
||||
event.event === 'on_chain_end' &&
|
||||
event.name === 'FinalSourceRetriever'
|
||||
) {
|
||||
emitter.emit(
|
||||
'data',
|
||||
JSON.stringify({ type: 'sources', data: event.data.output }),
|
||||
);
|
||||
if (event.event === "on_chain_end" && event.name === "FinalSourceRetriever") {
|
||||
emitter.emit("data", JSON.stringify({ type: "sources", data: event.data.output }));
|
||||
}
|
||||
if (
|
||||
event.event === 'on_chain_stream' &&
|
||||
event.name === 'FinalResponseGenerator'
|
||||
) {
|
||||
emitter.emit(
|
||||
'data',
|
||||
JSON.stringify({ type: 'response', data: event.data.chunk }),
|
||||
);
|
||||
if (event.event === "on_chain_stream" && event.name === "FinalResponseGenerator") {
|
||||
emitter.emit("data", JSON.stringify({ type: "response", data: event.data.chunk }));
|
||||
}
|
||||
if (
|
||||
event.event === 'on_chain_end' &&
|
||||
event.name === 'FinalResponseGenerator'
|
||||
) {
|
||||
emitter.emit('end');
|
||||
if (event.event === "on_chain_end" && event.name === "FinalResponseGenerator") {
|
||||
emitter.emit("end");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -107,17 +81,17 @@ const createBasicWolframAlphaSearchRetrieverChain = (llm: BaseChatModel) => {
|
|||
llm,
|
||||
strParser,
|
||||
RunnableLambda.from(async (input: string) => {
|
||||
if (input === 'not_needed') {
|
||||
return { query: '', docs: [] };
|
||||
if (input === "not_needed") {
|
||||
return { query: "", docs: [] };
|
||||
}
|
||||
|
||||
const res = await searchSearxng(input, {
|
||||
language: 'en',
|
||||
engines: ['wolframalpha'],
|
||||
language: "en",
|
||||
engines: ["wolframalpha"],
|
||||
});
|
||||
|
||||
const documents = res.results.map(
|
||||
(result) =>
|
||||
result =>
|
||||
new Document({
|
||||
pageContent: result.content,
|
||||
metadata: {
|
||||
|
|
@ -134,13 +108,10 @@ const createBasicWolframAlphaSearchRetrieverChain = (llm: BaseChatModel) => {
|
|||
};
|
||||
|
||||
const createBasicWolframAlphaSearchAnsweringChain = (llm: BaseChatModel) => {
|
||||
const basicWolframAlphaSearchRetrieverChain =
|
||||
createBasicWolframAlphaSearchRetrieverChain(llm);
|
||||
const basicWolframAlphaSearchRetrieverChain = createBasicWolframAlphaSearchRetrieverChain(llm);
|
||||
|
||||
const processDocs = (docs: Document[]) => {
|
||||
return docs
|
||||
.map((_, index) => `${index + 1}. ${docs[index].pageContent}`)
|
||||
.join('\n');
|
||||
return docs.map((_, index) => `${index + 1}. ${docs[index].pageContent}`).join("\n");
|
||||
};
|
||||
|
||||
return RunnableSequence.from([
|
||||
|
|
@ -148,7 +119,7 @@ const createBasicWolframAlphaSearchAnsweringChain = (llm: BaseChatModel) => {
|
|||
query: (input: BasicChainInput) => input.query,
|
||||
chat_history: (input: BasicChainInput) => input.chat_history,
|
||||
context: RunnableSequence.from([
|
||||
(input) => ({
|
||||
input => ({
|
||||
query: input.query,
|
||||
chat_history: formatChatHistoryAsString(input.chat_history),
|
||||
}),
|
||||
|
|
@ -157,49 +128,41 @@ const createBasicWolframAlphaSearchAnsweringChain = (llm: BaseChatModel) => {
|
|||
return docs;
|
||||
})
|
||||
.withConfig({
|
||||
runName: 'FinalSourceRetriever',
|
||||
runName: "FinalSourceRetriever",
|
||||
})
|
||||
.pipe(processDocs),
|
||||
]),
|
||||
}),
|
||||
ChatPromptTemplate.fromMessages([
|
||||
['system', basicWolframAlphaSearchResponsePrompt],
|
||||
new MessagesPlaceholder('chat_history'),
|
||||
['user', '{query}'],
|
||||
["system", basicWolframAlphaSearchResponsePrompt],
|
||||
new MessagesPlaceholder("chat_history"),
|
||||
["user", "{query}"],
|
||||
]),
|
||||
llm,
|
||||
strParser,
|
||||
]).withConfig({
|
||||
runName: 'FinalResponseGenerator',
|
||||
runName: "FinalResponseGenerator",
|
||||
});
|
||||
};
|
||||
|
||||
const basicWolframAlphaSearch = (
|
||||
query: string,
|
||||
history: BaseMessage[],
|
||||
llm: BaseChatModel,
|
||||
) => {
|
||||
const basicWolframAlphaSearch = (query: string, history: BaseMessage[], llm: BaseChatModel) => {
|
||||
const emitter = new eventEmitter();
|
||||
|
||||
try {
|
||||
const basicWolframAlphaSearchAnsweringChain =
|
||||
createBasicWolframAlphaSearchAnsweringChain(llm);
|
||||
const basicWolframAlphaSearchAnsweringChain = createBasicWolframAlphaSearchAnsweringChain(llm);
|
||||
const stream = basicWolframAlphaSearchAnsweringChain.streamEvents(
|
||||
{
|
||||
chat_history: history,
|
||||
query: query,
|
||||
},
|
||||
{
|
||||
version: 'v1',
|
||||
version: "v1",
|
||||
},
|
||||
);
|
||||
|
||||
handleStream(stream, emitter);
|
||||
} catch (err) {
|
||||
emitter.emit(
|
||||
'error',
|
||||
JSON.stringify({ data: 'An error has occurred please try again later' }),
|
||||
);
|
||||
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
||||
logger.error(`Error in WolframAlphaSearch: ${err}`);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
import { BaseMessage } from '@langchain/core/messages';
|
||||
import {
|
||||
ChatPromptTemplate,
|
||||
MessagesPlaceholder,
|
||||
} from '@langchain/core/prompts';
|
||||
import { RunnableSequence } from '@langchain/core/runnables';
|
||||
import { StringOutputParser } from '@langchain/core/output_parsers';
|
||||
import type { StreamEvent } from '@langchain/core/tracers/log_stream';
|
||||
import eventEmitter from 'events';
|
||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import type { Embeddings } from '@langchain/core/embeddings';
|
||||
import logger from '../utils/logger';
|
||||
import { BaseMessage } from "@langchain/core/messages";
|
||||
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
|
||||
import { RunnableSequence } from "@langchain/core/runnables";
|
||||
import { StringOutputParser } from "@langchain/core/output_parsers";
|
||||
import type { StreamEvent } from "@langchain/core/tracers/log_stream";
|
||||
import eventEmitter from "events";
|
||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||
import type { Embeddings } from "@langchain/core/embeddings";
|
||||
import logger from "../utils/logger";
|
||||
|
||||
const writingAssistantPrompt = `
|
||||
You are Perplexica, an AI model who is expert at searching the web and answering user's queries. You are currently set on focus mode 'Writing Assistant', this means you will be helping the user write a response to a given query.
|
||||
|
|
@ -18,25 +15,13 @@ Since you are a writing assistant, you would not perform web searches. If you th
|
|||
|
||||
const strParser = new StringOutputParser();
|
||||
|
||||
const handleStream = async (
|
||||
stream: AsyncGenerator<StreamEvent, unknown, unknown>,
|
||||
emitter: eventEmitter,
|
||||
) => {
|
||||
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
||||
for await (const event of stream) {
|
||||
if (
|
||||
event.event === 'on_chain_stream' &&
|
||||
event.name === 'FinalResponseGenerator'
|
||||
) {
|
||||
emitter.emit(
|
||||
'data',
|
||||
JSON.stringify({ type: 'response', data: event.data.chunk }),
|
||||
);
|
||||
if (event.event === "on_chain_stream" && event.name === "FinalResponseGenerator") {
|
||||
emitter.emit("data", JSON.stringify({ type: "response", data: event.data.chunk }));
|
||||
}
|
||||
if (
|
||||
event.event === 'on_chain_end' &&
|
||||
event.name === 'FinalResponseGenerator'
|
||||
) {
|
||||
emitter.emit('end');
|
||||
if (event.event === "on_chain_end" && event.name === "FinalResponseGenerator") {
|
||||
emitter.emit("end");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -44,14 +29,14 @@ const handleStream = async (
|
|||
const createWritingAssistantChain = (llm: BaseChatModel) => {
|
||||
return RunnableSequence.from([
|
||||
ChatPromptTemplate.fromMessages([
|
||||
['system', writingAssistantPrompt],
|
||||
new MessagesPlaceholder('chat_history'),
|
||||
['user', '{query}'],
|
||||
["system", writingAssistantPrompt],
|
||||
new MessagesPlaceholder("chat_history"),
|
||||
["user", "{query}"],
|
||||
]),
|
||||
llm,
|
||||
strParser,
|
||||
]).withConfig({
|
||||
runName: 'FinalResponseGenerator',
|
||||
runName: "FinalResponseGenerator",
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -72,16 +57,13 @@ const handleWritingAssistant = (
|
|||
query: query,
|
||||
},
|
||||
{
|
||||
version: 'v1',
|
||||
version: "v1",
|
||||
},
|
||||
);
|
||||
|
||||
handleStream(stream, emitter);
|
||||
} catch (err) {
|
||||
emitter.emit(
|
||||
'error',
|
||||
JSON.stringify({ data: 'An error has occurred please try again later' }),
|
||||
);
|
||||
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
||||
logger.error(`Error in writing assistant: ${err}`);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,16 @@
|
|||
import { BaseMessage } from '@langchain/core/messages';
|
||||
import {
|
||||
PromptTemplate,
|
||||
ChatPromptTemplate,
|
||||
MessagesPlaceholder,
|
||||
} from '@langchain/core/prompts';
|
||||
import {
|
||||
RunnableSequence,
|
||||
RunnableMap,
|
||||
RunnableLambda,
|
||||
} from '@langchain/core/runnables';
|
||||
import { StringOutputParser } from '@langchain/core/output_parsers';
|
||||
import { Document } from '@langchain/core/documents';
|
||||
import { searchSearxng } from '../lib/searxng';
|
||||
import type { StreamEvent } from '@langchain/core/tracers/log_stream';
|
||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import type { Embeddings } from '@langchain/core/embeddings';
|
||||
import formatChatHistoryAsString from '../utils/formatHistory';
|
||||
import eventEmitter from 'events';
|
||||
import computeSimilarity from '../utils/computeSimilarity';
|
||||
import logger from '../utils/logger';
|
||||
import { BaseMessage } from "@langchain/core/messages";
|
||||
import { PromptTemplate, ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
|
||||
import { RunnableSequence, RunnableMap, RunnableLambda } from "@langchain/core/runnables";
|
||||
import { StringOutputParser } from "@langchain/core/output_parsers";
|
||||
import { Document } from "@langchain/core/documents";
|
||||
import { searchSearxng } from "../lib/searxng";
|
||||
import type { StreamEvent } from "@langchain/core/tracers/log_stream";
|
||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||
import type { Embeddings } from "@langchain/core/embeddings";
|
||||
import formatChatHistoryAsString from "../utils/formatHistory";
|
||||
import eventEmitter from "events";
|
||||
import computeSimilarity from "../utils/computeSimilarity";
|
||||
import logger from "../utils/logger";
|
||||
|
||||
const basicYoutubeSearchRetrieverPrompt = `
|
||||
You will be given a conversation below and a follow up question. You need to rephrase the follow-up question if needed so it is a standalone question that can be used by the LLM to search the web for information.
|
||||
|
|
@ -65,34 +57,16 @@ const basicYoutubeSearchResponsePrompt = `
|
|||
|
||||
const strParser = new StringOutputParser();
|
||||
|
||||
const handleStream = async (
|
||||
stream: AsyncGenerator<StreamEvent, unknown, unknown>,
|
||||
emitter: eventEmitter,
|
||||
) => {
|
||||
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
||||
for await (const event of stream) {
|
||||
if (
|
||||
event.event === 'on_chain_end' &&
|
||||
event.name === 'FinalSourceRetriever'
|
||||
) {
|
||||
emitter.emit(
|
||||
'data',
|
||||
JSON.stringify({ type: 'sources', data: event.data.output }),
|
||||
);
|
||||
if (event.event === "on_chain_end" && event.name === "FinalSourceRetriever") {
|
||||
emitter.emit("data", JSON.stringify({ type: "sources", data: event.data.output }));
|
||||
}
|
||||
if (
|
||||
event.event === 'on_chain_stream' &&
|
||||
event.name === 'FinalResponseGenerator'
|
||||
) {
|
||||
emitter.emit(
|
||||
'data',
|
||||
JSON.stringify({ type: 'response', data: event.data.chunk }),
|
||||
);
|
||||
if (event.event === "on_chain_stream" && event.name === "FinalResponseGenerator") {
|
||||
emitter.emit("data", JSON.stringify({ type: "response", data: event.data.chunk }));
|
||||
}
|
||||
if (
|
||||
event.event === 'on_chain_end' &&
|
||||
event.name === 'FinalResponseGenerator'
|
||||
) {
|
||||
emitter.emit('end');
|
||||
if (event.event === "on_chain_end" && event.name === "FinalResponseGenerator") {
|
||||
emitter.emit("end");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -108,17 +82,17 @@ const createBasicYoutubeSearchRetrieverChain = (llm: BaseChatModel) => {
|
|||
llm,
|
||||
strParser,
|
||||
RunnableLambda.from(async (input: string) => {
|
||||
if (input === 'not_needed') {
|
||||
return { query: '', docs: [] };
|
||||
if (input === "not_needed") {
|
||||
return { query: "", docs: [] };
|
||||
}
|
||||
|
||||
const res = await searchSearxng(input, {
|
||||
language: 'en',
|
||||
engines: ['youtube'],
|
||||
language: "en",
|
||||
engines: ["youtube"],
|
||||
});
|
||||
|
||||
const documents = res.results.map(
|
||||
(result) =>
|
||||
result =>
|
||||
new Document({
|
||||
pageContent: result.content ? result.content : result.title,
|
||||
metadata: {
|
||||
|
|
@ -134,36 +108,22 @@ const createBasicYoutubeSearchRetrieverChain = (llm: BaseChatModel) => {
|
|||
]);
|
||||
};
|
||||
|
||||
const createBasicYoutubeSearchAnsweringChain = (
|
||||
llm: BaseChatModel,
|
||||
embeddings: Embeddings,
|
||||
) => {
|
||||
const basicYoutubeSearchRetrieverChain =
|
||||
createBasicYoutubeSearchRetrieverChain(llm);
|
||||
const createBasicYoutubeSearchAnsweringChain = (llm: BaseChatModel, embeddings: Embeddings) => {
|
||||
const basicYoutubeSearchRetrieverChain = createBasicYoutubeSearchRetrieverChain(llm);
|
||||
|
||||
const processDocs = async (docs: Document[]) => {
|
||||
return docs
|
||||
.map((_, index) => `${index + 1}. ${docs[index].pageContent}`)
|
||||
.join('\n');
|
||||
return docs.map((_, index) => `${index + 1}. ${docs[index].pageContent}`).join("\n");
|
||||
};
|
||||
|
||||
const rerankDocs = async ({
|
||||
query,
|
||||
docs,
|
||||
}: {
|
||||
query: string;
|
||||
docs: Document[];
|
||||
}) => {
|
||||
const rerankDocs = async ({ query, docs }: { query: string; docs: Document[] }) => {
|
||||
if (docs.length === 0) {
|
||||
return docs;
|
||||
}
|
||||
|
||||
const docsWithContent = docs.filter(
|
||||
(doc) => doc.pageContent && doc.pageContent.length > 0,
|
||||
);
|
||||
const docsWithContent = docs.filter(doc => doc.pageContent && doc.pageContent.length > 0);
|
||||
|
||||
const [docEmbeddings, queryEmbedding] = await Promise.all([
|
||||
embeddings.embedDocuments(docsWithContent.map((doc) => doc.pageContent)),
|
||||
embeddings.embedDocuments(docsWithContent.map(doc => doc.pageContent)),
|
||||
embeddings.embedQuery(query),
|
||||
]);
|
||||
|
||||
|
|
@ -179,8 +139,8 @@ const createBasicYoutubeSearchAnsweringChain = (
|
|||
const sortedDocs = similarity
|
||||
.sort((a, b) => b.similarity - a.similarity)
|
||||
.slice(0, 15)
|
||||
.filter((sim) => sim.similarity > 0.3)
|
||||
.map((sim) => docsWithContent[sim.index]);
|
||||
.filter(sim => sim.similarity > 0.3)
|
||||
.map(sim => docsWithContent[sim.index]);
|
||||
|
||||
return sortedDocs;
|
||||
};
|
||||
|
|
@ -190,41 +150,35 @@ const createBasicYoutubeSearchAnsweringChain = (
|
|||
query: (input: BasicChainInput) => input.query,
|
||||
chat_history: (input: BasicChainInput) => input.chat_history,
|
||||
context: RunnableSequence.from([
|
||||
(input) => ({
|
||||
input => ({
|
||||
query: input.query,
|
||||
chat_history: formatChatHistoryAsString(input.chat_history),
|
||||
}),
|
||||
basicYoutubeSearchRetrieverChain
|
||||
.pipe(rerankDocs)
|
||||
.withConfig({
|
||||
runName: 'FinalSourceRetriever',
|
||||
runName: "FinalSourceRetriever",
|
||||
})
|
||||
.pipe(processDocs),
|
||||
]),
|
||||
}),
|
||||
ChatPromptTemplate.fromMessages([
|
||||
['system', basicYoutubeSearchResponsePrompt],
|
||||
new MessagesPlaceholder('chat_history'),
|
||||
['user', '{query}'],
|
||||
["system", basicYoutubeSearchResponsePrompt],
|
||||
new MessagesPlaceholder("chat_history"),
|
||||
["user", "{query}"],
|
||||
]),
|
||||
llm,
|
||||
strParser,
|
||||
]).withConfig({
|
||||
runName: 'FinalResponseGenerator',
|
||||
runName: "FinalResponseGenerator",
|
||||
});
|
||||
};
|
||||
|
||||
const basicYoutubeSearch = (
|
||||
query: string,
|
||||
history: BaseMessage[],
|
||||
llm: BaseChatModel,
|
||||
embeddings: Embeddings,
|
||||
) => {
|
||||
const basicYoutubeSearch = (query: string, history: BaseMessage[], llm: BaseChatModel, embeddings: Embeddings) => {
|
||||
const emitter = new eventEmitter();
|
||||
|
||||
try {
|
||||
const basicYoutubeSearchAnsweringChain =
|
||||
createBasicYoutubeSearchAnsweringChain(llm, embeddings);
|
||||
const basicYoutubeSearchAnsweringChain = createBasicYoutubeSearchAnsweringChain(llm, embeddings);
|
||||
|
||||
const stream = basicYoutubeSearchAnsweringChain.streamEvents(
|
||||
{
|
||||
|
|
@ -232,28 +186,20 @@ const basicYoutubeSearch = (
|
|||
query: query,
|
||||
},
|
||||
{
|
||||
version: 'v1',
|
||||
version: "v1",
|
||||
},
|
||||
);
|
||||
|
||||
handleStream(stream, emitter);
|
||||
} catch (err) {
|
||||
emitter.emit(
|
||||
'error',
|
||||
JSON.stringify({ data: 'An error has occurred please try again later' }),
|
||||
);
|
||||
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
||||
logger.error(`Error in youtube search: ${err}`);
|
||||
}
|
||||
|
||||
return emitter;
|
||||
};
|
||||
|
||||
const handleYoutubeSearch = (
|
||||
message: string,
|
||||
history: BaseMessage[],
|
||||
llm: BaseChatModel,
|
||||
embeddings: Embeddings,
|
||||
) => {
|
||||
const handleYoutubeSearch = (message: string, history: BaseMessage[], llm: BaseChatModel, embeddings: Embeddings) => {
|
||||
const emitter = basicYoutubeSearch(message, history, llm, embeddings);
|
||||
return emitter;
|
||||
};
|
||||
|
|
|
|||
22
src/app.ts
22
src/app.ts
|
|
@ -1,10 +1,10 @@
|
|||
import { startWebSocketServer } from './websocket';
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import http from 'http';
|
||||
import routes from './routes';
|
||||
import { getPort } from './config';
|
||||
import logger from './utils/logger';
|
||||
import { startWebSocketServer } from "./websocket";
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import http from "http";
|
||||
import routes from "./routes";
|
||||
import { getPort } from "./config";
|
||||
import logger from "./utils/logger";
|
||||
|
||||
const port = getPort();
|
||||
|
||||
|
|
@ -12,15 +12,15 @@ const app = express();
|
|||
const server = http.createServer(app);
|
||||
|
||||
const corsOptions = {
|
||||
origin: '*',
|
||||
origin: "*",
|
||||
};
|
||||
|
||||
app.use(cors(corsOptions));
|
||||
app.use(express.json());
|
||||
|
||||
app.use('/api', routes);
|
||||
app.get('/api', (_, res) => {
|
||||
res.status(200).json({ status: 'ok' });
|
||||
app.use("/api", routes);
|
||||
app.get("/api", (_, res) => {
|
||||
res.status(200).json({ status: "ok" });
|
||||
});
|
||||
|
||||
server.listen(port, () => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import toml from '@iarna/toml';
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import toml from "@iarna/toml";
|
||||
|
||||
const configFileName = 'config.toml';
|
||||
const configFileName = "config.toml";
|
||||
|
||||
interface Config {
|
||||
GENERAL: {
|
||||
|
|
@ -24,14 +24,11 @@ type RecursivePartial<T> = {
|
|||
};
|
||||
|
||||
const loadConfig = () =>
|
||||
toml.parse(
|
||||
fs.readFileSync(path.join(__dirname, `../${configFileName}`), 'utf-8'),
|
||||
) as unknown as Config;
|
||||
toml.parse(fs.readFileSync(path.join(__dirname, `../${configFileName}`), "utf-8")) as unknown as Config;
|
||||
|
||||
export const getPort = () => loadConfig().GENERAL.PORT;
|
||||
|
||||
export const getSimilarityMeasure = () =>
|
||||
loadConfig().GENERAL.SIMILARITY_MEASURE;
|
||||
export const getSimilarityMeasure = () => loadConfig().GENERAL.SIMILARITY_MEASURE;
|
||||
|
||||
export const getOpenaiApiKey = () => loadConfig().API_KEYS.OPENAI;
|
||||
|
||||
|
|
@ -47,23 +44,16 @@ export const updateConfig = (config: RecursivePartial<Config>) => {
|
|||
for (const key in currentConfig) {
|
||||
if (!config[key]) config[key] = {};
|
||||
|
||||
if (typeof currentConfig[key] === 'object' && currentConfig[key] !== null) {
|
||||
if (typeof currentConfig[key] === "object" && currentConfig[key] !== null) {
|
||||
for (const nestedKey in currentConfig[key]) {
|
||||
if (
|
||||
!config[key][nestedKey] &&
|
||||
currentConfig[key][nestedKey] &&
|
||||
config[key][nestedKey] !== ''
|
||||
) {
|
||||
if (!config[key][nestedKey] && currentConfig[key][nestedKey] && config[key][nestedKey] !== "") {
|
||||
config[key][nestedKey] = currentConfig[key][nestedKey];
|
||||
}
|
||||
}
|
||||
} else if (currentConfig[key] && config[key] !== '') {
|
||||
} else if (currentConfig[key] && config[key] !== "") {
|
||||
config[key] = currentConfig[key];
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, `../${configFileName}`),
|
||||
toml.stringify(config),
|
||||
);
|
||||
fs.writeFileSync(path.join(__dirname, `../${configFileName}`), toml.stringify(config));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||
import Database from 'better-sqlite3';
|
||||
import * as schema from './schema';
|
||||
import { drizzle } from "drizzle-orm/better-sqlite3";
|
||||
import Database from "better-sqlite3";
|
||||
import * as schema from "./schema";
|
||||
|
||||
const sqlite = new Database('data/db.sqlite');
|
||||
const sqlite = new Database("data/db.sqlite");
|
||||
const db = drizzle(sqlite, {
|
||||
schema: schema,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import { text, integer, sqliteTable } from 'drizzle-orm/sqlite-core';
|
||||
import { text, integer, sqliteTable } from "drizzle-orm/sqlite-core";
|
||||
|
||||
export const messages = sqliteTable('messages', {
|
||||
id: integer('id').primaryKey(),
|
||||
content: text('content').notNull(),
|
||||
chatId: text('chatId').notNull(),
|
||||
messageId: text('messageId').notNull(),
|
||||
role: text('type', { enum: ['assistant', 'user'] }),
|
||||
metadata: text('metadata', {
|
||||
mode: 'json',
|
||||
export const messages = sqliteTable("messages", {
|
||||
id: integer("id").primaryKey(),
|
||||
content: text("content").notNull(),
|
||||
chatId: text("chatId").notNull(),
|
||||
messageId: text("messageId").notNull(),
|
||||
role: text("type", { enum: ["assistant", "user"] }),
|
||||
metadata: text("metadata", {
|
||||
mode: "json",
|
||||
}),
|
||||
});
|
||||
|
||||
export const chats = sqliteTable('chats', {
|
||||
id: text('id').primaryKey(),
|
||||
title: text('title').notNull(),
|
||||
createdAt: text('createdAt').notNull(),
|
||||
focusMode: text('focusMode').notNull(),
|
||||
export const chats = sqliteTable("chats", {
|
||||
id: text("id").primaryKey(),
|
||||
title: text("title").notNull(),
|
||||
createdAt: text("createdAt").notNull(),
|
||||
focusMode: text("focusMode").notNull(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { Embeddings, type EmbeddingsParams } from '@langchain/core/embeddings';
|
||||
import { chunkArray } from '@langchain/core/utils/chunk_array';
|
||||
import { Embeddings, type EmbeddingsParams } from "@langchain/core/embeddings";
|
||||
import { chunkArray } from "@langchain/core/utils/chunk_array";
|
||||
|
||||
export interface HuggingFaceTransformersEmbeddingsParams
|
||||
extends EmbeddingsParams {
|
||||
export interface HuggingFaceTransformersEmbeddingsParams extends EmbeddingsParams {
|
||||
modelName: string;
|
||||
|
||||
model: string;
|
||||
|
|
@ -14,13 +13,10 @@ export interface HuggingFaceTransformersEmbeddingsParams
|
|||
stripNewLines?: boolean;
|
||||
}
|
||||
|
||||
export class HuggingFaceTransformersEmbeddings
|
||||
extends Embeddings
|
||||
implements HuggingFaceTransformersEmbeddingsParams
|
||||
{
|
||||
modelName = 'Xenova/all-MiniLM-L6-v2';
|
||||
export class HuggingFaceTransformersEmbeddings extends Embeddings implements HuggingFaceTransformersEmbeddingsParams {
|
||||
modelName = "Xenova/all-MiniLM-L6-v2";
|
||||
|
||||
model = 'Xenova/all-MiniLM-L6-v2';
|
||||
model = "Xenova/all-MiniLM-L6-v2";
|
||||
|
||||
batchSize = 512;
|
||||
|
||||
|
|
@ -41,12 +37,9 @@ export class HuggingFaceTransformersEmbeddings
|
|||
}
|
||||
|
||||
async embedDocuments(texts: string[]): Promise<number[][]> {
|
||||
const batches = chunkArray(
|
||||
this.stripNewLines ? texts.map((t) => t.replace(/\n/g, ' ')) : texts,
|
||||
this.batchSize,
|
||||
);
|
||||
const batches = chunkArray(this.stripNewLines ? texts.map(t => t.replace(/\n/g, " ")) : texts, this.batchSize);
|
||||
|
||||
const batchRequests = batches.map((batch) => this.runEmbedding(batch));
|
||||
const batchRequests = batches.map(batch => this.runEmbedding(batch));
|
||||
const batchResponses = await Promise.all(batchRequests);
|
||||
const embeddings: number[][] = [];
|
||||
|
||||
|
|
@ -61,22 +54,17 @@ export class HuggingFaceTransformersEmbeddings
|
|||
}
|
||||
|
||||
async embedQuery(text: string): Promise<number[]> {
|
||||
const data = await this.runEmbedding([
|
||||
this.stripNewLines ? text.replace(/\n/g, ' ') : text,
|
||||
]);
|
||||
const data = await this.runEmbedding([this.stripNewLines ? text.replace(/\n/g, " ") : text]);
|
||||
return data[0];
|
||||
}
|
||||
|
||||
private async runEmbedding(texts: string[]) {
|
||||
const { pipeline } = await import('@xenova/transformers');
|
||||
const { pipeline } = await import("@xenova/transformers");
|
||||
|
||||
const pipe = await (this.pipelinePromise ??= pipeline(
|
||||
'feature-extraction',
|
||||
this.model,
|
||||
));
|
||||
const pipe = await (this.pipelinePromise ??= pipeline("feature-extraction", this.model));
|
||||
|
||||
return this.caller.call(async () => {
|
||||
const output = await pipe(texts, { pooling: 'mean', normalize: true });
|
||||
const output = await pipe(texts, { pooling: "mean", normalize: true });
|
||||
return output.tolist();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { BaseOutputParser } from '@langchain/core/output_parsers';
|
||||
import { BaseOutputParser } from "@langchain/core/output_parsers";
|
||||
|
||||
interface LineListOutputParserArgs {
|
||||
key?: string;
|
||||
}
|
||||
|
||||
class LineListOutputParser extends BaseOutputParser<string[]> {
|
||||
private key = 'questions';
|
||||
private key = "questions";
|
||||
|
||||
constructor(args?: LineListOutputParserArgs) {
|
||||
super();
|
||||
|
|
@ -13,30 +13,29 @@ class LineListOutputParser extends BaseOutputParser<string[]> {
|
|||
}
|
||||
|
||||
static lc_name() {
|
||||
return 'LineListOutputParser';
|
||||
return "LineListOutputParser";
|
||||
}
|
||||
|
||||
lc_namespace = ['langchain', 'output_parsers', 'line_list_output_parser'];
|
||||
lc_namespace = ["langchain", "output_parsers", "line_list_output_parser"];
|
||||
|
||||
async parse(text: string): Promise<string[]> {
|
||||
const regex = /^(\s*(-|\*|\d+\.\s|\d+\)\s|\u2022)\s*)+/;
|
||||
const startKeyIndex = text.indexOf(`<${this.key}>`);
|
||||
const endKeyIndex = text.indexOf(`</${this.key}>`);
|
||||
const questionsStartIndex =
|
||||
startKeyIndex === -1 ? 0 : startKeyIndex + `<${this.key}>`.length;
|
||||
const questionsStartIndex = startKeyIndex === -1 ? 0 : startKeyIndex + `<${this.key}>`.length;
|
||||
const questionsEndIndex = endKeyIndex === -1 ? text.length : endKeyIndex;
|
||||
const lines = text
|
||||
.slice(questionsStartIndex, questionsEndIndex)
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter((line) => line.trim() !== '')
|
||||
.map((line) => line.replace(regex, ''));
|
||||
.split("\n")
|
||||
.filter(line => line.trim() !== "")
|
||||
.map(line => line.replace(regex, ""));
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
getFormatInstructions(): string {
|
||||
throw new Error('Not implemented.');
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai';
|
||||
import { ChatOllama } from '@langchain/community/chat_models/ollama';
|
||||
import { OllamaEmbeddings } from '@langchain/community/embeddings/ollama';
|
||||
import { HuggingFaceTransformersEmbeddings } from './huggingfaceTransformer';
|
||||
import {
|
||||
getGroqApiKey,
|
||||
getOllamaApiEndpoint,
|
||||
getOpenaiApiKey,
|
||||
} from '../config';
|
||||
import logger from '../utils/logger';
|
||||
import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai";
|
||||
import { ChatOllama } from "@langchain/community/chat_models/ollama";
|
||||
import { OllamaEmbeddings } from "@langchain/community/embeddings/ollama";
|
||||
import { HuggingFaceTransformersEmbeddings } from "./huggingfaceTransformer";
|
||||
import { getGroqApiKey, getOllamaApiEndpoint, getOpenaiApiKey } from "../config";
|
||||
import logger from "../utils/logger";
|
||||
|
||||
export const getAvailableChatModelProviders = async () => {
|
||||
const openAIApiKey = getOpenaiApiKey();
|
||||
|
|
@ -18,25 +14,25 @@ export const getAvailableChatModelProviders = async () => {
|
|||
|
||||
if (openAIApiKey) {
|
||||
try {
|
||||
models['openai'] = {
|
||||
'GPT-3.5 turbo': new ChatOpenAI({
|
||||
models["openai"] = {
|
||||
"GPT-3.5 turbo": new ChatOpenAI({
|
||||
openAIApiKey,
|
||||
modelName: 'gpt-3.5-turbo',
|
||||
modelName: "gpt-3.5-turbo",
|
||||
temperature: 0.7,
|
||||
}),
|
||||
'GPT-4': new ChatOpenAI({
|
||||
"GPT-4": new ChatOpenAI({
|
||||
openAIApiKey,
|
||||
modelName: 'gpt-4',
|
||||
modelName: "gpt-4",
|
||||
temperature: 0.7,
|
||||
}),
|
||||
'GPT-4 turbo': new ChatOpenAI({
|
||||
"GPT-4 turbo": new ChatOpenAI({
|
||||
openAIApiKey,
|
||||
modelName: 'gpt-4-turbo',
|
||||
modelName: "gpt-4-turbo",
|
||||
temperature: 0.7,
|
||||
}),
|
||||
'GPT-4 omni': new ChatOpenAI({
|
||||
"GPT-4 omni": new ChatOpenAI({
|
||||
openAIApiKey,
|
||||
modelName: 'gpt-4o',
|
||||
modelName: "gpt-4o",
|
||||
temperature: 0.7,
|
||||
}),
|
||||
};
|
||||
|
|
@ -47,45 +43,45 @@ export const getAvailableChatModelProviders = async () => {
|
|||
|
||||
if (groqApiKey) {
|
||||
try {
|
||||
models['groq'] = {
|
||||
'LLaMA3 8b': new ChatOpenAI(
|
||||
models["groq"] = {
|
||||
"LLaMA3 8b": new ChatOpenAI(
|
||||
{
|
||||
openAIApiKey: groqApiKey,
|
||||
modelName: 'llama3-8b-8192',
|
||||
modelName: "llama3-8b-8192",
|
||||
temperature: 0.7,
|
||||
},
|
||||
{
|
||||
baseURL: 'https://api.groq.com/openai/v1',
|
||||
baseURL: "https://api.groq.com/openai/v1",
|
||||
},
|
||||
),
|
||||
'LLaMA3 70b': new ChatOpenAI(
|
||||
"LLaMA3 70b": new ChatOpenAI(
|
||||
{
|
||||
openAIApiKey: groqApiKey,
|
||||
modelName: 'llama3-70b-8192',
|
||||
modelName: "llama3-70b-8192",
|
||||
temperature: 0.7,
|
||||
},
|
||||
{
|
||||
baseURL: 'https://api.groq.com/openai/v1',
|
||||
baseURL: "https://api.groq.com/openai/v1",
|
||||
},
|
||||
),
|
||||
'Mixtral 8x7b': new ChatOpenAI(
|
||||
"Mixtral 8x7b": new ChatOpenAI(
|
||||
{
|
||||
openAIApiKey: groqApiKey,
|
||||
modelName: 'mixtral-8x7b-32768',
|
||||
modelName: "mixtral-8x7b-32768",
|
||||
temperature: 0.7,
|
||||
},
|
||||
{
|
||||
baseURL: 'https://api.groq.com/openai/v1',
|
||||
baseURL: "https://api.groq.com/openai/v1",
|
||||
},
|
||||
),
|
||||
'Gemma 7b': new ChatOpenAI(
|
||||
"Gemma 7b": new ChatOpenAI(
|
||||
{
|
||||
openAIApiKey: groqApiKey,
|
||||
modelName: 'gemma-7b-it',
|
||||
modelName: "gemma-7b-it",
|
||||
temperature: 0.7,
|
||||
},
|
||||
{
|
||||
baseURL: 'https://api.groq.com/openai/v1',
|
||||
baseURL: "https://api.groq.com/openai/v1",
|
||||
},
|
||||
),
|
||||
};
|
||||
|
|
@ -98,14 +94,14 @@ export const getAvailableChatModelProviders = async () => {
|
|||
try {
|
||||
const response = await fetch(`${ollamaEndpoint}/api/tags`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const { models: ollamaModels } = (await response.json()) as any;
|
||||
|
||||
models['ollama'] = ollamaModels.reduce((acc, model) => {
|
||||
models["ollama"] = ollamaModels.reduce((acc, model) => {
|
||||
acc[model.model] = new ChatOllama({
|
||||
baseUrl: ollamaEndpoint,
|
||||
model: model.model,
|
||||
|
|
@ -118,7 +114,7 @@ export const getAvailableChatModelProviders = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
models['custom_openai'] = {};
|
||||
models["custom_openai"] = {};
|
||||
|
||||
return models;
|
||||
};
|
||||
|
|
@ -131,14 +127,14 @@ export const getAvailableEmbeddingModelProviders = async () => {
|
|||
|
||||
if (openAIApiKey) {
|
||||
try {
|
||||
models['openai'] = {
|
||||
'Text embedding 3 small': new OpenAIEmbeddings({
|
||||
models["openai"] = {
|
||||
"Text embedding 3 small": new OpenAIEmbeddings({
|
||||
openAIApiKey,
|
||||
modelName: 'text-embedding-3-small',
|
||||
modelName: "text-embedding-3-small",
|
||||
}),
|
||||
'Text embedding 3 large': new OpenAIEmbeddings({
|
||||
"Text embedding 3 large": new OpenAIEmbeddings({
|
||||
openAIApiKey,
|
||||
modelName: 'text-embedding-3-large',
|
||||
modelName: "text-embedding-3-large",
|
||||
}),
|
||||
};
|
||||
} catch (err) {
|
||||
|
|
@ -150,14 +146,14 @@ export const getAvailableEmbeddingModelProviders = async () => {
|
|||
try {
|
||||
const response = await fetch(`${ollamaEndpoint}/api/tags`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const { models: ollamaModels } = (await response.json()) as any;
|
||||
|
||||
models['ollama'] = ollamaModels.reduce((acc, model) => {
|
||||
models["ollama"] = ollamaModels.reduce((acc, model) => {
|
||||
acc[model.model] = new OllamaEmbeddings({
|
||||
baseUrl: ollamaEndpoint,
|
||||
model: model.model,
|
||||
|
|
@ -170,15 +166,15 @@ export const getAvailableEmbeddingModelProviders = async () => {
|
|||
}
|
||||
|
||||
try {
|
||||
models['local'] = {
|
||||
'BGE Small': new HuggingFaceTransformersEmbeddings({
|
||||
modelName: 'Xenova/bge-small-en-v1.5',
|
||||
models["local"] = {
|
||||
"BGE Small": new HuggingFaceTransformersEmbeddings({
|
||||
modelName: "Xenova/bge-small-en-v1.5",
|
||||
}),
|
||||
'GTE Small': new HuggingFaceTransformersEmbeddings({
|
||||
modelName: 'Xenova/gte-small',
|
||||
"GTE Small": new HuggingFaceTransformersEmbeddings({
|
||||
modelName: "Xenova/gte-small",
|
||||
}),
|
||||
'Bert Multilingual': new HuggingFaceTransformersEmbeddings({
|
||||
modelName: 'Xenova/bert-base-multilingual-uncased',
|
||||
"Bert Multilingual": new HuggingFaceTransformersEmbeddings({
|
||||
modelName: "Xenova/bert-base-multilingual-uncased",
|
||||
}),
|
||||
};
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import axios from 'axios';
|
||||
import { getSearxngApiEndpoint } from '../config';
|
||||
import axios from "axios";
|
||||
import { getSearxngApiEndpoint } from "../config";
|
||||
|
||||
interface SearxngSearchOptions {
|
||||
categories?: string[];
|
||||
|
|
@ -19,19 +19,16 @@ interface SearxngSearchResult {
|
|||
iframe_src?: string;
|
||||
}
|
||||
|
||||
export const searchSearxng = async (
|
||||
query: string,
|
||||
opts?: SearxngSearchOptions,
|
||||
) => {
|
||||
export const searchSearxng = async (query: string, opts?: SearxngSearchOptions) => {
|
||||
const searxngURL = getSearxngApiEndpoint();
|
||||
|
||||
const url = new URL(`${searxngURL}/search?format=json`);
|
||||
url.searchParams.append('q', query);
|
||||
url.searchParams.append("q", query);
|
||||
|
||||
if (opts) {
|
||||
Object.keys(opts).forEach((key) => {
|
||||
Object.keys(opts).forEach(key => {
|
||||
if (Array.isArray(opts[key])) {
|
||||
url.searchParams.append(key, opts[key].join(','));
|
||||
url.searchParams.append(key, opts[key].join(","));
|
||||
return;
|
||||
}
|
||||
url.searchParams.append(key, opts[key]);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import express from 'express';
|
||||
import logger from '../utils/logger';
|
||||
import db from '../db/index';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { chats, messages } from '../db/schema';
|
||||
import express from "express";
|
||||
import logger from "../utils/logger";
|
||||
import db from "../db/index";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { chats, messages } from "../db/schema";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', async (_, res) => {
|
||||
router.get("/", async (_, res) => {
|
||||
try {
|
||||
let chats = await db.query.chats.findMany();
|
||||
|
||||
|
|
@ -14,19 +14,19 @@ router.get('/', async (_, res) => {
|
|||
|
||||
return res.status(200).json({ chats: chats });
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: 'An error has occurred.' });
|
||||
res.status(500).json({ message: "An error has occurred." });
|
||||
logger.error(`Error in getting chats: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:id', async (req, res) => {
|
||||
router.get("/:id", async (req, res) => {
|
||||
try {
|
||||
const chatExists = await db.query.chats.findFirst({
|
||||
where: eq(chats.id, req.params.id),
|
||||
});
|
||||
|
||||
if (!chatExists) {
|
||||
return res.status(404).json({ message: 'Chat not found' });
|
||||
return res.status(404).json({ message: "Chat not found" });
|
||||
}
|
||||
|
||||
const chatMessages = await db.query.messages.findMany({
|
||||
|
|
@ -35,7 +35,7 @@ router.get('/:id', async (req, res) => {
|
|||
|
||||
return res.status(200).json({ chat: chatExists, messages: chatMessages });
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: 'An error has occurred.' });
|
||||
res.status(500).json({ message: "An error has occurred." });
|
||||
logger.error(`Error in getting chat: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
|
@ -47,18 +47,15 @@ router.delete(`/:id`, async (req, res) => {
|
|||
});
|
||||
|
||||
if (!chatExists) {
|
||||
return res.status(404).json({ message: 'Chat not found' });
|
||||
return res.status(404).json({ message: "Chat not found" });
|
||||
}
|
||||
|
||||
await db.delete(chats).where(eq(chats.id, req.params.id)).execute();
|
||||
await db
|
||||
.delete(messages)
|
||||
.where(eq(messages.chatId, req.params.id))
|
||||
.execute();
|
||||
await db.delete(messages).where(eq(messages.chatId, req.params.id)).execute();
|
||||
|
||||
return res.status(200).json({ message: 'Chat deleted successfully' });
|
||||
return res.status(200).json({ message: "Chat deleted successfully" });
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: 'An error has occurred.' });
|
||||
res.status(500).json({ message: "An error has occurred." });
|
||||
logger.error(`Error in deleting chat: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,18 +1,10 @@
|
|||
import express from 'express';
|
||||
import {
|
||||
getAvailableChatModelProviders,
|
||||
getAvailableEmbeddingModelProviders,
|
||||
} from '../lib/providers';
|
||||
import {
|
||||
getGroqApiKey,
|
||||
getOllamaApiEndpoint,
|
||||
getOpenaiApiKey,
|
||||
updateConfig,
|
||||
} from '../config';
|
||||
import express from "express";
|
||||
import { getAvailableChatModelProviders, getAvailableEmbeddingModelProviders } from "../lib/providers";
|
||||
import { getGroqApiKey, getOllamaApiEndpoint, getOpenaiApiKey, updateConfig } from "../config";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', async (_, res) => {
|
||||
router.get("/", async (_, res) => {
|
||||
const config = {};
|
||||
|
||||
const [chatModelProviders, embeddingModelProviders] = await Promise.all([
|
||||
|
|
@ -20,29 +12,25 @@ router.get('/', async (_, res) => {
|
|||
getAvailableEmbeddingModelProviders(),
|
||||
]);
|
||||
|
||||
config['chatModelProviders'] = {};
|
||||
config['embeddingModelProviders'] = {};
|
||||
config["chatModelProviders"] = {};
|
||||
config["embeddingModelProviders"] = {};
|
||||
|
||||
for (const provider in chatModelProviders) {
|
||||
config['chatModelProviders'][provider] = Object.keys(
|
||||
chatModelProviders[provider],
|
||||
);
|
||||
config["chatModelProviders"][provider] = Object.keys(chatModelProviders[provider]);
|
||||
}
|
||||
|
||||
for (const provider in embeddingModelProviders) {
|
||||
config['embeddingModelProviders'][provider] = Object.keys(
|
||||
embeddingModelProviders[provider],
|
||||
);
|
||||
config["embeddingModelProviders"][provider] = Object.keys(embeddingModelProviders[provider]);
|
||||
}
|
||||
|
||||
config['openaiApiKey'] = getOpenaiApiKey();
|
||||
config['ollamaApiUrl'] = getOllamaApiEndpoint();
|
||||
config['groqApiKey'] = getGroqApiKey();
|
||||
config["openaiApiKey"] = getOpenaiApiKey();
|
||||
config["ollamaApiUrl"] = getOllamaApiEndpoint();
|
||||
config["groqApiKey"] = getGroqApiKey();
|
||||
|
||||
res.status(200).json(config);
|
||||
});
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
router.post("/", async (req, res) => {
|
||||
const config = req.body;
|
||||
|
||||
const updatedConfig = {
|
||||
|
|
@ -57,7 +45,7 @@ router.post('/', async (req, res) => {
|
|||
|
||||
updateConfig(updatedConfig);
|
||||
|
||||
res.status(200).json({ message: 'Config updated' });
|
||||
res.status(200).json({ message: "Config updated" });
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
import express from 'express';
|
||||
import handleImageSearch from '../agents/imageSearchAgent';
|
||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import { getAvailableChatModelProviders } from '../lib/providers';
|
||||
import { HumanMessage, AIMessage } from '@langchain/core/messages';
|
||||
import logger from '../utils/logger';
|
||||
import express from "express";
|
||||
import handleImageSearch from "../agents/imageSearchAgent";
|
||||
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||
import { getAvailableChatModelProviders } from "../lib/providers";
|
||||
import { HumanMessage, AIMessage } from "@langchain/core/messages";
|
||||
import logger from "../utils/logger";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
router.post("/", async (req, res) => {
|
||||
try {
|
||||
const { query, chat_history: raw_chat_history, chat_model_provider, chat_model } = req.body;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const chat_history = raw_chat_history.map((msg: any) => {
|
||||
if (msg.role === 'user') {
|
||||
if (msg.role === "user") {
|
||||
return new HumanMessage(msg.content);
|
||||
} else if (msg.role === 'assistant') {
|
||||
} else if (msg.role === "assistant") {
|
||||
return new AIMessage(msg.content);
|
||||
}
|
||||
});
|
||||
|
|
@ -31,7 +31,7 @@ router.post('/', async (req, res) => {
|
|||
}
|
||||
|
||||
if (!llm) {
|
||||
res.status(500).json({ message: 'Invalid LLM model selected' });
|
||||
res.status(500).json({ message: "Invalid LLM model selected" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ router.post('/', async (req, res) => {
|
|||
|
||||
res.status(200).json({ images });
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: 'An error has occurred.' });
|
||||
res.status(500).json({ message: "An error has occurred." });
|
||||
logger.error(`Error in image search: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import express from 'express';
|
||||
import imagesRouter from './images';
|
||||
import videosRouter from './videos';
|
||||
import configRouter from './config';
|
||||
import modelsRouter from './models';
|
||||
import suggestionsRouter from './suggestions';
|
||||
import chatsRouter from './chats';
|
||||
import express from "express";
|
||||
import imagesRouter from "./images";
|
||||
import videosRouter from "./videos";
|
||||
import configRouter from "./config";
|
||||
import modelsRouter from "./models";
|
||||
import suggestionsRouter from "./suggestions";
|
||||
import chatsRouter from "./chats";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use('/images', imagesRouter);
|
||||
router.use('/videos', videosRouter);
|
||||
router.use('/config', configRouter);
|
||||
router.use('/models', modelsRouter);
|
||||
router.use('/suggestions', suggestionsRouter);
|
||||
router.use('/chats', chatsRouter);
|
||||
router.use("/images", imagesRouter);
|
||||
router.use("/videos", videosRouter);
|
||||
router.use("/config", configRouter);
|
||||
router.use("/models", modelsRouter);
|
||||
router.use("/suggestions", suggestionsRouter);
|
||||
router.use("/chats", chatsRouter);
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
import express from 'express';
|
||||
import logger from '../utils/logger';
|
||||
import {
|
||||
getAvailableChatModelProviders,
|
||||
getAvailableEmbeddingModelProviders,
|
||||
} from '../lib/providers';
|
||||
import express from "express";
|
||||
import logger from "../utils/logger";
|
||||
import { getAvailableChatModelProviders, getAvailableEmbeddingModelProviders } from "../lib/providers";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
router.get("/", async (req, res) => {
|
||||
try {
|
||||
const [chatModelProviders, embeddingModelProviders] = await Promise.all([
|
||||
getAvailableChatModelProviders(),
|
||||
|
|
@ -16,7 +13,7 @@ router.get('/', async (req, res) => {
|
|||
|
||||
res.status(200).json({ chatModelProviders, embeddingModelProviders });
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: 'An error has occurred.' });
|
||||
res.status(500).json({ message: "An error has occurred." });
|
||||
logger.error(err.message);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
import express from 'express';
|
||||
import generateSuggestions from '../agents/suggestionGeneratorAgent';
|
||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import { getAvailableChatModelProviders } from '../lib/providers';
|
||||
import { HumanMessage, AIMessage } from '@langchain/core/messages';
|
||||
import logger from '../utils/logger';
|
||||
import express from "express";
|
||||
import generateSuggestions from "../agents/suggestionGeneratorAgent";
|
||||
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||
import { getAvailableChatModelProviders } from "../lib/providers";
|
||||
import { HumanMessage, AIMessage } from "@langchain/core/messages";
|
||||
import logger from "../utils/logger";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
router.post("/", async (req, res) => {
|
||||
try {
|
||||
const { chat_history: raw_chat_history, chat_model, chat_model_provider } = req.body;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const chat_history = raw_chat_history.map((msg: any) => {
|
||||
if (msg.role === 'user') {
|
||||
if (msg.role === "user") {
|
||||
return new HumanMessage(msg.content);
|
||||
} else if (msg.role === 'assistant') {
|
||||
} else if (msg.role === "assistant") {
|
||||
return new AIMessage(msg.content);
|
||||
}
|
||||
});
|
||||
|
|
@ -31,7 +31,7 @@ router.post('/', async (req, res) => {
|
|||
}
|
||||
|
||||
if (!llm) {
|
||||
res.status(500).json({ message: 'Invalid LLM model selected' });
|
||||
res.status(500).json({ message: "Invalid LLM model selected" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ router.post('/', async (req, res) => {
|
|||
|
||||
res.status(200).json({ suggestions: suggestions });
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: 'An error has occurred.' });
|
||||
res.status(500).json({ message: "An error has occurred." });
|
||||
logger.error(`Error in generating suggestions: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
import express from 'express';
|
||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import { getAvailableChatModelProviders } from '../lib/providers';
|
||||
import { HumanMessage, AIMessage } from '@langchain/core/messages';
|
||||
import logger from '../utils/logger';
|
||||
import handleVideoSearch from '../agents/videoSearchAgent';
|
||||
import express from "express";
|
||||
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||
import { getAvailableChatModelProviders } from "../lib/providers";
|
||||
import { HumanMessage, AIMessage } from "@langchain/core/messages";
|
||||
import logger from "../utils/logger";
|
||||
import handleVideoSearch from "../agents/videoSearchAgent";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
router.post("/", async (req, res) => {
|
||||
try {
|
||||
const { query, chat_history: raw_chat_history, chat_model_provider, chat_model } = req.body;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const chat_history = raw_chat_history.map((msg: any) => {
|
||||
if (msg.role === 'user') {
|
||||
if (msg.role === "user") {
|
||||
return new HumanMessage(msg.content);
|
||||
} else if (msg.role === 'assistant') {
|
||||
} else if (msg.role === "assistant") {
|
||||
return new AIMessage(msg.content);
|
||||
}
|
||||
});
|
||||
|
|
@ -31,7 +31,7 @@ router.post('/', async (req, res) => {
|
|||
}
|
||||
|
||||
if (!llm) {
|
||||
res.status(500).json({ message: 'Invalid LLM model selected' });
|
||||
res.status(500).json({ message: "Invalid LLM model selected" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ router.post('/', async (req, res) => {
|
|||
|
||||
res.status(200).json({ videos });
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: 'An error has occurred.' });
|
||||
res.status(500).json({ message: "An error has occurred." });
|
||||
logger.error(`Error in video search: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import dot from 'compute-dot';
|
||||
import cosineSimilarity from 'compute-cosine-similarity';
|
||||
import { getSimilarityMeasure } from '../config';
|
||||
import dot from "compute-dot";
|
||||
import cosineSimilarity from "compute-cosine-similarity";
|
||||
import { getSimilarityMeasure } from "../config";
|
||||
|
||||
const computeSimilarity = (x: number[], y: number[]): number => {
|
||||
const similarityMeasure = getSimilarityMeasure();
|
||||
|
||||
if (similarityMeasure === 'cosine') {
|
||||
if (similarityMeasure === "cosine") {
|
||||
return cosineSimilarity(x, y);
|
||||
} else if (similarityMeasure === 'dot') {
|
||||
} else if (similarityMeasure === "dot") {
|
||||
return dot(x, y);
|
||||
}
|
||||
|
||||
throw new Error('Invalid similarity measure');
|
||||
throw new Error("Invalid similarity measure");
|
||||
};
|
||||
|
||||
export default computeSimilarity;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import { BaseMessage } from '@langchain/core/messages';
|
||||
import { BaseMessage } from "@langchain/core/messages";
|
||||
|
||||
const formatChatHistoryAsString = (history: BaseMessage[]) => {
|
||||
return history
|
||||
.map((message) => `${message._getType()}: ${message.content}`)
|
||||
.join('\n');
|
||||
return history.map(message => `${message._getType()}: ${message.content}`).join("\n");
|
||||
};
|
||||
|
||||
export default formatChatHistoryAsString;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,14 @@
|
|||
import winston from 'winston';
|
||||
import winston from "winston";
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
level: "info",
|
||||
transports: [
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple(),
|
||||
),
|
||||
format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: 'app.log',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.json(),
|
||||
),
|
||||
filename: "app.log",
|
||||
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,41 +1,28 @@
|
|||
import { WebSocket } from 'ws';
|
||||
import { handleMessage } from './messageHandler';
|
||||
import {
|
||||
getAvailableEmbeddingModelProviders,
|
||||
getAvailableChatModelProviders,
|
||||
} from '../lib/providers';
|
||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import type { Embeddings } from '@langchain/core/embeddings';
|
||||
import type { IncomingMessage } from 'http';
|
||||
import logger from '../utils/logger';
|
||||
import { ChatOpenAI } from '@langchain/openai';
|
||||
import { WebSocket } from "ws";
|
||||
import { handleMessage } from "./messageHandler";
|
||||
import { getAvailableEmbeddingModelProviders, getAvailableChatModelProviders } from "../lib/providers";
|
||||
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||
import type { Embeddings } from "@langchain/core/embeddings";
|
||||
import type { IncomingMessage } from "http";
|
||||
import logger from "../utils/logger";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
|
||||
export const handleConnection = async (
|
||||
ws: WebSocket,
|
||||
request: IncomingMessage,
|
||||
) => {
|
||||
export const handleConnection = async (ws: WebSocket, request: IncomingMessage) => {
|
||||
try {
|
||||
const searchParams = new URL(request.url, `http://${request.headers.host}`)
|
||||
.searchParams;
|
||||
const searchParams = new URL(request.url, `http://${request.headers.host}`).searchParams;
|
||||
|
||||
const [chatModelProviders, embeddingModelProviders] = await Promise.all([
|
||||
getAvailableChatModelProviders(),
|
||||
getAvailableEmbeddingModelProviders(),
|
||||
]);
|
||||
|
||||
const chatModelProvider =
|
||||
searchParams.get('chatModelProvider') ||
|
||||
Object.keys(chatModelProviders)[0];
|
||||
const chatModel =
|
||||
searchParams.get('chatModel') ||
|
||||
Object.keys(chatModelProviders[chatModelProvider])[0];
|
||||
const chatModelProvider = searchParams.get("chatModelProvider") || Object.keys(chatModelProviders)[0];
|
||||
const chatModel = searchParams.get("chatModel") || Object.keys(chatModelProviders[chatModelProvider])[0];
|
||||
|
||||
const embeddingModelProvider =
|
||||
searchParams.get('embeddingModelProvider') ||
|
||||
Object.keys(embeddingModelProviders)[0];
|
||||
searchParams.get("embeddingModelProvider") || Object.keys(embeddingModelProviders)[0];
|
||||
const embeddingModel =
|
||||
searchParams.get('embeddingModel') ||
|
||||
Object.keys(embeddingModelProviders[embeddingModelProvider])[0];
|
||||
searchParams.get("embeddingModel") || Object.keys(embeddingModelProviders[embeddingModelProvider])[0];
|
||||
|
||||
let llm: BaseChatModel | undefined;
|
||||
let embeddings: Embeddings | undefined;
|
||||
|
|
@ -43,18 +30,16 @@ export const handleConnection = async (
|
|||
if (
|
||||
chatModelProviders[chatModelProvider] &&
|
||||
chatModelProviders[chatModelProvider][chatModel] &&
|
||||
chatModelProvider != 'custom_openai'
|
||||
chatModelProvider != "custom_openai"
|
||||
) {
|
||||
llm = chatModelProviders[chatModelProvider][chatModel] as
|
||||
| BaseChatModel
|
||||
| undefined;
|
||||
} else if (chatModelProvider == 'custom_openai') {
|
||||
llm = chatModelProviders[chatModelProvider][chatModel] as BaseChatModel | undefined;
|
||||
} else if (chatModelProvider == "custom_openai") {
|
||||
llm = new ChatOpenAI({
|
||||
modelName: chatModel,
|
||||
openAIApiKey: searchParams.get('openAIApiKey'),
|
||||
openAIApiKey: searchParams.get("openAIApiKey"),
|
||||
temperature: 0.7,
|
||||
configuration: {
|
||||
baseURL: searchParams.get('openAIBaseURL'),
|
||||
baseURL: searchParams.get("openAIBaseURL"),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -63,35 +48,29 @@ export const handleConnection = async (
|
|||
embeddingModelProviders[embeddingModelProvider] &&
|
||||
embeddingModelProviders[embeddingModelProvider][embeddingModel]
|
||||
) {
|
||||
embeddings = embeddingModelProviders[embeddingModelProvider][
|
||||
embeddingModel
|
||||
] as Embeddings | undefined;
|
||||
embeddings = embeddingModelProviders[embeddingModelProvider][embeddingModel] as Embeddings | undefined;
|
||||
}
|
||||
|
||||
if (!llm || !embeddings) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'error',
|
||||
data: 'Invalid LLM or embeddings model selected, please refresh the page and try again.',
|
||||
key: 'INVALID_MODEL_SELECTED',
|
||||
type: "error",
|
||||
data: "Invalid LLM or embeddings model selected, please refresh the page and try again.",
|
||||
key: "INVALID_MODEL_SELECTED",
|
||||
}),
|
||||
);
|
||||
ws.close();
|
||||
}
|
||||
|
||||
ws.on(
|
||||
'message',
|
||||
async (message) =>
|
||||
await handleMessage(message.toString(), ws, llm, embeddings),
|
||||
);
|
||||
ws.on("message", async message => await handleMessage(message.toString(), ws, llm, embeddings));
|
||||
|
||||
ws.on('close', () => logger.debug('Connection closed'));
|
||||
ws.on("close", () => logger.debug("Connection closed"));
|
||||
} catch (err) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'error',
|
||||
data: 'Internal server error.',
|
||||
key: 'INTERNAL_SERVER_ERROR',
|
||||
type: "error",
|
||||
data: "Internal server error.",
|
||||
key: "INTERNAL_SERVER_ERROR",
|
||||
}),
|
||||
);
|
||||
ws.close();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { initServer } from './websocketServer';
|
||||
import http from 'http';
|
||||
import { initServer } from "./websocketServer";
|
||||
import http from "http";
|
||||
|
||||
export const startWebSocketServer = (
|
||||
server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>,
|
||||
) => {
|
||||
export const startWebSocketServer = (server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>) => {
|
||||
initServer(server);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import { EventEmitter, WebSocket } from 'ws';
|
||||
import { BaseMessage, AIMessage, HumanMessage } from '@langchain/core/messages';
|
||||
import handleWebSearch from '../agents/webSearchAgent';
|
||||
import handleAcademicSearch from '../agents/academicSearchAgent';
|
||||
import handleWritingAssistant from '../agents/writingAssistant';
|
||||
import handleWolframAlphaSearch from '../agents/wolframAlphaSearchAgent';
|
||||
import handleYoutubeSearch from '../agents/youtubeSearchAgent';
|
||||
import handleRedditSearch from '../agents/redditSearchAgent';
|
||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import type { Embeddings } from '@langchain/core/embeddings';
|
||||
import logger from '../utils/logger';
|
||||
import db from '../db';
|
||||
import { chats, messages } from '../db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import crypto from 'crypto';
|
||||
import { EventEmitter, WebSocket } from "ws";
|
||||
import { BaseMessage, AIMessage, HumanMessage } from "@langchain/core/messages";
|
||||
import handleWebSearch from "../agents/webSearchAgent";
|
||||
import handleAcademicSearch from "../agents/academicSearchAgent";
|
||||
import handleWritingAssistant from "../agents/writingAssistant";
|
||||
import handleWolframAlphaSearch from "../agents/wolframAlphaSearchAgent";
|
||||
import handleYoutubeSearch from "../agents/youtubeSearchAgent";
|
||||
import handleRedditSearch from "../agents/redditSearchAgent";
|
||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||
import type { Embeddings } from "@langchain/core/embeddings";
|
||||
import logger from "../utils/logger";
|
||||
import db from "../db";
|
||||
import { chats, messages } from "../db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import crypto from "crypto";
|
||||
|
||||
type Message = {
|
||||
messageId: string;
|
||||
|
|
@ -37,30 +37,25 @@ const searchHandlers = {
|
|||
redditSearch: handleRedditSearch,
|
||||
};
|
||||
|
||||
const handleEmitterEvents = (
|
||||
emitter: EventEmitter,
|
||||
ws: WebSocket,
|
||||
messageId: string,
|
||||
chatId: string,
|
||||
) => {
|
||||
let recievedMessage = '';
|
||||
const handleEmitterEvents = (emitter: EventEmitter, ws: WebSocket, messageId: string, chatId: string) => {
|
||||
let recievedMessage = "";
|
||||
let sources = [];
|
||||
|
||||
emitter.on('data', (data) => {
|
||||
emitter.on("data", data => {
|
||||
const parsedData = JSON.parse(data);
|
||||
if (parsedData.type === 'response') {
|
||||
if (parsedData.type === "response") {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'message',
|
||||
type: "message",
|
||||
data: parsedData.data,
|
||||
messageId: messageId,
|
||||
}),
|
||||
);
|
||||
recievedMessage += parsedData.data;
|
||||
} else if (parsedData.type === 'sources') {
|
||||
} else if (parsedData.type === "sources") {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'sources',
|
||||
type: "sources",
|
||||
data: parsedData.data,
|
||||
messageId: messageId,
|
||||
}),
|
||||
|
|
@ -68,15 +63,15 @@ const handleEmitterEvents = (
|
|||
sources = parsedData.data;
|
||||
}
|
||||
});
|
||||
emitter.on('end', () => {
|
||||
ws.send(JSON.stringify({ type: 'messageEnd', messageId: messageId }));
|
||||
emitter.on("end", () => {
|
||||
ws.send(JSON.stringify({ type: "messageEnd", messageId: messageId }));
|
||||
|
||||
db.insert(messages)
|
||||
.values({
|
||||
content: recievedMessage,
|
||||
chatId: chatId,
|
||||
messageId: messageId,
|
||||
role: 'assistant',
|
||||
role: "assistant",
|
||||
metadata: JSON.stringify({
|
||||
createdAt: new Date(),
|
||||
...(sources && sources.length > 0 && { sources }),
|
||||
|
|
@ -84,41 +79,36 @@ const handleEmitterEvents = (
|
|||
})
|
||||
.execute();
|
||||
});
|
||||
emitter.on('error', (data) => {
|
||||
emitter.on("error", data => {
|
||||
const parsedData = JSON.parse(data);
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'error',
|
||||
type: "error",
|
||||
data: parsedData.data,
|
||||
key: 'CHAIN_ERROR',
|
||||
key: "CHAIN_ERROR",
|
||||
}),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const handleMessage = async (
|
||||
message: string,
|
||||
ws: WebSocket,
|
||||
llm: BaseChatModel,
|
||||
embeddings: Embeddings,
|
||||
) => {
|
||||
export const handleMessage = async (message: string, ws: WebSocket, llm: BaseChatModel, embeddings: Embeddings) => {
|
||||
try {
|
||||
const parsedWSMessage = JSON.parse(message) as WSMessage;
|
||||
const parsedMessage = parsedWSMessage.message;
|
||||
|
||||
const id = crypto.randomBytes(7).toString('hex');
|
||||
const id = crypto.randomBytes(7).toString("hex");
|
||||
|
||||
if (!parsedMessage.content)
|
||||
return ws.send(
|
||||
JSON.stringify({
|
||||
type: 'error',
|
||||
data: 'Invalid message format',
|
||||
key: 'INVALID_FORMAT',
|
||||
type: "error",
|
||||
data: "Invalid message format",
|
||||
key: "INVALID_FORMAT",
|
||||
}),
|
||||
);
|
||||
|
||||
const history: BaseMessage[] = parsedWSMessage.history.map((msg) => {
|
||||
if (msg[0] === 'human') {
|
||||
const history: BaseMessage[] = parsedWSMessage.history.map(msg => {
|
||||
if (msg[0] === "human") {
|
||||
return new HumanMessage({
|
||||
content: msg[1],
|
||||
});
|
||||
|
|
@ -129,16 +119,11 @@ export const handleMessage = async (
|
|||
}
|
||||
});
|
||||
|
||||
if (parsedWSMessage.type === 'message') {
|
||||
if (parsedWSMessage.type === "message") {
|
||||
const handler = searchHandlers[parsedWSMessage.focusMode];
|
||||
|
||||
if (handler) {
|
||||
const emitter = handler(
|
||||
parsedMessage.content,
|
||||
history,
|
||||
llm,
|
||||
embeddings,
|
||||
);
|
||||
const emitter = handler(parsedMessage.content, history, llm, embeddings);
|
||||
|
||||
handleEmitterEvents(emitter, ws, id, parsedMessage.chatId);
|
||||
|
||||
|
|
@ -164,7 +149,7 @@ export const handleMessage = async (
|
|||
content: parsedMessage.content,
|
||||
chatId: parsedMessage.chatId,
|
||||
messageId: id,
|
||||
role: 'user',
|
||||
role: "user",
|
||||
metadata: JSON.stringify({
|
||||
createdAt: new Date(),
|
||||
}),
|
||||
|
|
@ -173,9 +158,9 @@ export const handleMessage = async (
|
|||
} else {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'error',
|
||||
data: 'Invalid focus mode',
|
||||
key: 'INVALID_FOCUS_MODE',
|
||||
type: "error",
|
||||
data: "Invalid focus mode",
|
||||
key: "INVALID_FOCUS_MODE",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
@ -183,9 +168,9 @@ export const handleMessage = async (
|
|||
} catch (err) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'error',
|
||||
data: 'Invalid message format',
|
||||
key: 'INVALID_FORMAT',
|
||||
type: "error",
|
||||
data: "Invalid message format",
|
||||
key: "INVALID_FORMAT",
|
||||
}),
|
||||
);
|
||||
logger.error(`Failed to handle message: ${err}`);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
import { WebSocketServer } from 'ws';
|
||||
import { handleConnection } from './connectionManager';
|
||||
import http from 'http';
|
||||
import { getPort } from '../config';
|
||||
import logger from '../utils/logger';
|
||||
import { WebSocketServer } from "ws";
|
||||
import { handleConnection } from "./connectionManager";
|
||||
import http from "http";
|
||||
import { getPort } from "../config";
|
||||
import logger from "../utils/logger";
|
||||
|
||||
export const initServer = (
|
||||
server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>,
|
||||
) => {
|
||||
export const initServer = (server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>) => {
|
||||
const port = getPort();
|
||||
const wss = new WebSocketServer({ server });
|
||||
|
||||
wss.on('connection', handleConnection);
|
||||
wss.on("connection", handleConnection);
|
||||
|
||||
logger.info(`WebSocket server started on port ${port}`);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue