mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2025-05-02 17:07:13 +00:00
Move OpenAI api calls into its own interface/Class (#162)
* Move OpenAI api calls into its own interface/Class move curate sources to be specific for each vectorDBs response for chat/query * remove comment
This commit is contained in:
parent
0a2f837fb2
commit
8929d96ed0
5 changed files with 115 additions and 184 deletions
|
@ -22,30 +22,7 @@ function toChunks(arr, size) {
|
|||
);
|
||||
}
|
||||
|
||||
function curateSources(sources = []) {
|
||||
const documents = [];
|
||||
|
||||
// Sometimes the source may or may not have a metadata property
|
||||
// in the response so we search for it explicitly or just spread the entire
|
||||
// source and check to see if at least title exists.
|
||||
for (const source of sources) {
|
||||
if (source.hasOwnProperty("metadata")) {
|
||||
const { metadata = {} } = source;
|
||||
if (Object.keys(metadata).length > 0) {
|
||||
documents.push({ ...metadata });
|
||||
}
|
||||
} else {
|
||||
if (Object.keys(source).length > 0) {
|
||||
documents.push({ ...source });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return documents;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getVectorDbClass,
|
||||
toChunks,
|
||||
curateSources,
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const { Configuration, OpenAIApi } = require("openai");
|
||||
|
||||
class OpenAi {
|
||||
constructor() {
|
||||
const config = new Configuration({
|
||||
|
@ -7,6 +8,7 @@ class OpenAi {
|
|||
const openai = new OpenAIApi(config);
|
||||
this.openai = openai;
|
||||
}
|
||||
|
||||
isValidChatModel(modelName = "") {
|
||||
const validModels = ["gpt-4", "gpt-3.5-turbo"];
|
||||
return validModels.includes(modelName);
|
||||
|
@ -79,6 +81,37 @@ class OpenAi {
|
|||
|
||||
return textResponse;
|
||||
}
|
||||
|
||||
async getChatCompletion(messages = [], { temperature = 0.7 }) {
|
||||
const model = process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo";
|
||||
const { data } = await this.openai.createChatCompletion({
|
||||
model,
|
||||
messages,
|
||||
temperature,
|
||||
});
|
||||
|
||||
if (!data.hasOwnProperty("choices")) return null;
|
||||
return data.choices[0].message.content;
|
||||
}
|
||||
|
||||
async embedTextInput(textInput) {
|
||||
const result = await this.embedChunks(textInput);
|
||||
return result?.[0] || [];
|
||||
}
|
||||
|
||||
async embedChunks(textChunks = []) {
|
||||
const {
|
||||
data: { data },
|
||||
} = await this.openai.createEmbedding({
|
||||
model: "text-embedding-ada-002",
|
||||
input: textChunks,
|
||||
});
|
||||
|
||||
return data.length > 0 &&
|
||||
data.every((embd) => embd.hasOwnProperty("embedding"))
|
||||
? data.map((embd) => embd.embedding)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -5,10 +5,10 @@ const { VectorDBQAChain } = require("langchain/chains");
|
|||
const { OpenAIEmbeddings } = require("langchain/embeddings/openai");
|
||||
const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter");
|
||||
const { storeVectorResult, cachedVectorInformation } = require("../../files");
|
||||
const { Configuration, OpenAIApi } = require("openai");
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
const { toChunks, curateSources } = require("../../helpers");
|
||||
const { toChunks } = require("../../helpers");
|
||||
const { chatPrompt } = require("../../chats");
|
||||
const { OpenAi } = require("../../openAi");
|
||||
|
||||
const Chroma = {
|
||||
name: "Chroma",
|
||||
|
@ -57,26 +57,6 @@ const Chroma = {
|
|||
embedder: function () {
|
||||
return new OpenAIEmbeddings({ openAIApiKey: process.env.OPEN_AI_KEY });
|
||||
},
|
||||
openai: function () {
|
||||
const config = new Configuration({ apiKey: process.env.OPEN_AI_KEY });
|
||||
const openai = new OpenAIApi(config);
|
||||
return openai;
|
||||
},
|
||||
getChatCompletion: async function (
|
||||
openai,
|
||||
messages = [],
|
||||
{ temperature = 0.7 }
|
||||
) {
|
||||
const model = process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo";
|
||||
const { data } = await openai.createChatCompletion({
|
||||
model,
|
||||
messages,
|
||||
temperature,
|
||||
});
|
||||
|
||||
if (!data.hasOwnProperty("choices")) return null;
|
||||
return data.choices[0].message.content;
|
||||
},
|
||||
llm: function ({ temperature = 0.7 }) {
|
||||
const model = process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo";
|
||||
return new OpenAI({
|
||||
|
@ -85,22 +65,6 @@ const Chroma = {
|
|||
temperature,
|
||||
});
|
||||
},
|
||||
embedTextInput: async function (openai, textInput) {
|
||||
const result = await this.embedChunks(openai, textInput);
|
||||
return result?.[0] || [];
|
||||
},
|
||||
embedChunks: async function (openai, chunks = []) {
|
||||
const {
|
||||
data: { data },
|
||||
} = await openai.createEmbedding({
|
||||
model: "text-embedding-ada-002",
|
||||
input: chunks,
|
||||
});
|
||||
return data.length > 0 &&
|
||||
data.every((embd) => embd.hasOwnProperty("embedding"))
|
||||
? data.map((embd) => embd.embedding)
|
||||
: null;
|
||||
},
|
||||
similarityResponse: async function (client, namespace, queryVector) {
|
||||
const collection = await client.getCollection({ name: namespace });
|
||||
const result = {
|
||||
|
@ -212,10 +176,10 @@ const Chroma = {
|
|||
const textChunks = await textSplitter.splitText(pageContent);
|
||||
|
||||
console.log("Chunks created from document:", textChunks.length);
|
||||
const openAiConnector = new OpenAi();
|
||||
const documentVectors = [];
|
||||
const vectors = [];
|
||||
const openai = this.openai();
|
||||
const vectorValues = await this.embedChunks(openai, textChunks);
|
||||
const vectorValues = await openAiConnector.embedChunks(textChunks);
|
||||
const submission = {
|
||||
ids: [],
|
||||
embeddings: [],
|
||||
|
@ -322,7 +286,7 @@ const Chroma = {
|
|||
const response = await chain.call({ query: input });
|
||||
return {
|
||||
response: response.text,
|
||||
sources: curateSources(response.sourceDocuments),
|
||||
sources: this.curateSources(response.sourceDocuments),
|
||||
message: false,
|
||||
};
|
||||
},
|
||||
|
@ -348,7 +312,8 @@ const Chroma = {
|
|||
};
|
||||
}
|
||||
|
||||
const queryVector = await this.embedTextInput(this.openai(), input);
|
||||
const openAiConnector = new OpenAi();
|
||||
const queryVector = await openAiConnector.embedTextInput(input);
|
||||
const { contextTexts, sourceDocuments } = await this.similarityResponse(
|
||||
client,
|
||||
namespace,
|
||||
|
@ -359,19 +324,24 @@ const Chroma = {
|
|||
content: `${chatPrompt(workspace)}
|
||||
Context:
|
||||
${contextTexts
|
||||
.map((text, i) => {
|
||||
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
|
||||
})
|
||||
.join("")}`,
|
||||
.map((text, i) => {
|
||||
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
|
||||
})
|
||||
.join("")}`,
|
||||
};
|
||||
const memory = [prompt, ...chatHistory, { role: "user", content: input }];
|
||||
const responseText = await this.getChatCompletion(this.openai(), memory, {
|
||||
const responseText = await openAiConnector.getChatCompletion(memory, {
|
||||
temperature: workspace?.openAiTemp ?? 0.7,
|
||||
});
|
||||
|
||||
// When we roll out own response we have separate metadata and texts,
|
||||
// so for source collection we need to combine them.
|
||||
const sources = sourceDocuments.map((metadata, i) => {
|
||||
return { metadata: { ...metadata, text: contextTexts[i] } };
|
||||
});
|
||||
return {
|
||||
response: responseText,
|
||||
sources: curateSources(sourceDocuments),
|
||||
sources: this.curateSources(sources),
|
||||
message: false,
|
||||
};
|
||||
},
|
||||
|
@ -403,6 +373,22 @@ const Chroma = {
|
|||
await client.reset();
|
||||
return { reset: true };
|
||||
},
|
||||
curateSources: function (sources = []) {
|
||||
const documents = [];
|
||||
for (const source of sources) {
|
||||
const { metadata = {} } = source;
|
||||
if (Object.keys(metadata).length > 0) {
|
||||
documents.push({
|
||||
...metadata,
|
||||
...(source.hasOwnProperty("pageContent")
|
||||
? { text: source.pageContent }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return documents;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports.Chroma = Chroma;
|
||||
|
|
|
@ -3,23 +3,9 @@ const { toChunks } = require("../../helpers");
|
|||
const { OpenAIEmbeddings } = require("langchain/embeddings/openai");
|
||||
const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter");
|
||||
const { storeVectorResult, cachedVectorInformation } = require("../../files");
|
||||
const { Configuration, OpenAIApi } = require("openai");
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
const { chatPrompt } = require("../../chats");
|
||||
|
||||
// Since we roll our own results for prompting we
|
||||
// have to manually curate sources as well.
|
||||
function curateLanceSources(sources = []) {
|
||||
const documents = [];
|
||||
for (const source of sources) {
|
||||
const { text, vector: _v, score: _s, ...metadata } = source;
|
||||
if (Object.keys(metadata).length > 0) {
|
||||
documents.push({ ...metadata, text });
|
||||
}
|
||||
}
|
||||
|
||||
return documents;
|
||||
}
|
||||
const { OpenAi } = require("../../openAi");
|
||||
|
||||
const LanceDb = {
|
||||
uri: `${
|
||||
|
@ -61,51 +47,9 @@ const LanceDb = {
|
|||
const table = await client.openTable(_namespace);
|
||||
return (await table.countRows()) || 0;
|
||||
},
|
||||
embeddingFunc: function () {
|
||||
return new lancedb.OpenAIEmbeddingFunction(
|
||||
"context",
|
||||
process.env.OPEN_AI_KEY
|
||||
);
|
||||
},
|
||||
embedTextInput: async function (openai, textInput) {
|
||||
const result = await this.embedChunks(openai, textInput);
|
||||
return result?.[0] || [];
|
||||
},
|
||||
embedChunks: async function (openai, chunks = []) {
|
||||
const {
|
||||
data: { data },
|
||||
} = await openai.createEmbedding({
|
||||
model: "text-embedding-ada-002",
|
||||
input: chunks,
|
||||
});
|
||||
return data.length > 0 &&
|
||||
data.every((embd) => embd.hasOwnProperty("embedding"))
|
||||
? data.map((embd) => embd.embedding)
|
||||
: null;
|
||||
},
|
||||
embedder: function () {
|
||||
return new OpenAIEmbeddings({ openAIApiKey: process.env.OPEN_AI_KEY });
|
||||
},
|
||||
openai: function () {
|
||||
const config = new Configuration({ apiKey: process.env.OPEN_AI_KEY });
|
||||
const openai = new OpenAIApi(config);
|
||||
return openai;
|
||||
},
|
||||
getChatCompletion: async function (
|
||||
openai,
|
||||
messages = [],
|
||||
{ temperature = 0.7 }
|
||||
) {
|
||||
const model = process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo";
|
||||
const { data } = await openai.createChatCompletion({
|
||||
model,
|
||||
messages,
|
||||
temperature,
|
||||
});
|
||||
|
||||
if (!data.hasOwnProperty("choices")) return null;
|
||||
return data.choices[0].message.content;
|
||||
},
|
||||
similarityResponse: async function (client, namespace, queryVector) {
|
||||
const collection = await client.openTable(namespace);
|
||||
const result = {
|
||||
|
@ -225,11 +169,11 @@ const LanceDb = {
|
|||
const textChunks = await textSplitter.splitText(pageContent);
|
||||
|
||||
console.log("Chunks created from document:", textChunks.length);
|
||||
const openAiConnector = new OpenAi();
|
||||
const documentVectors = [];
|
||||
const vectors = [];
|
||||
const submissions = [];
|
||||
const openai = this.openai();
|
||||
const vectorValues = await this.embedChunks(openai, textChunks);
|
||||
const vectorValues = await openAiConnector.embedChunks(textChunks);
|
||||
|
||||
if (!!vectorValues && vectorValues.length > 0) {
|
||||
for (const [i, vector] of vectorValues.entries()) {
|
||||
|
@ -287,7 +231,8 @@ const LanceDb = {
|
|||
}
|
||||
|
||||
// LanceDB does not have langchainJS support so we roll our own here.
|
||||
const queryVector = await this.embedTextInput(this.openai(), input);
|
||||
const openAiConnector = new OpenAi();
|
||||
const queryVector = await openAiConnector.embedTextInput(input);
|
||||
const { contextTexts, sourceDocuments } = await this.similarityResponse(
|
||||
client,
|
||||
namespace,
|
||||
|
@ -304,13 +249,13 @@ const LanceDb = {
|
|||
.join("")}`,
|
||||
};
|
||||
const memory = [prompt, { role: "user", content: input }];
|
||||
const responseText = await this.getChatCompletion(this.openai(), memory, {
|
||||
const responseText = await openAiConnector.getChatCompletion(memory, {
|
||||
temperature: workspace?.openAiTemp ?? 0.7,
|
||||
});
|
||||
|
||||
return {
|
||||
response: responseText,
|
||||
sources: curateLanceSources(sourceDocuments),
|
||||
sources: this.curateSources(sourceDocuments),
|
||||
message: false,
|
||||
};
|
||||
},
|
||||
|
@ -336,7 +281,8 @@ const LanceDb = {
|
|||
};
|
||||
}
|
||||
|
||||
const queryVector = await this.embedTextInput(this.openai(), input);
|
||||
const openAiConnector = new OpenAi();
|
||||
const queryVector = await openAiConnector.embedTextInput(input);
|
||||
const { contextTexts, sourceDocuments } = await this.similarityResponse(
|
||||
client,
|
||||
namespace,
|
||||
|
@ -353,13 +299,13 @@ const LanceDb = {
|
|||
.join("")}`,
|
||||
};
|
||||
const memory = [prompt, ...chatHistory, { role: "user", content: input }];
|
||||
const responseText = await this.getChatCompletion(this.openai(), memory, {
|
||||
const responseText = await openAiConnector.getChatCompletion(memory, {
|
||||
temperature: workspace?.openAiTemp ?? 0.7,
|
||||
});
|
||||
|
||||
return {
|
||||
response: responseText,
|
||||
sources: curateLanceSources(sourceDocuments),
|
||||
sources: this.curateSources(sourceDocuments),
|
||||
message: false,
|
||||
};
|
||||
},
|
||||
|
@ -391,6 +337,17 @@ const LanceDb = {
|
|||
fs.rm(`${client.uri}`, { recursive: true }, () => null);
|
||||
return { reset: true };
|
||||
},
|
||||
curateSources: function (sources = []) {
|
||||
const documents = [];
|
||||
for (const source of sources) {
|
||||
const { text, vector: _v, score: _s, ...metadata } = source;
|
||||
if (Object.keys(metadata).length > 0) {
|
||||
documents.push({ ...metadata, text });
|
||||
}
|
||||
}
|
||||
|
||||
return documents;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports.LanceDb = LanceDb;
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
const { PineconeClient } = require("@pinecone-database/pinecone");
|
||||
const { PineconeStore } = require("langchain/vectorstores/pinecone");
|
||||
const { OpenAI } = require("langchain/llms/openai");
|
||||
const { VectorDBQAChain, LLMChain } = require("langchain/chains");
|
||||
const { VectorDBQAChain } = require("langchain/chains");
|
||||
const { OpenAIEmbeddings } = require("langchain/embeddings/openai");
|
||||
const { VectorStoreRetrieverMemory } = require("langchain/memory");
|
||||
const { PromptTemplate } = require("langchain/prompts");
|
||||
const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter");
|
||||
const { storeVectorResult, cachedVectorInformation } = require("../../files");
|
||||
const { Configuration, OpenAIApi } = require("openai");
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
const { toChunks, curateSources } = require("../../helpers");
|
||||
const { toChunks } = require("../../helpers");
|
||||
const { chatPrompt } = require("../../chats");
|
||||
const { OpenAi } = require("../../openAi");
|
||||
|
||||
const Pinecone = {
|
||||
name: "Pinecone",
|
||||
|
@ -34,42 +32,6 @@ const Pinecone = {
|
|||
embedder: function () {
|
||||
return new OpenAIEmbeddings({ openAIApiKey: process.env.OPEN_AI_KEY });
|
||||
},
|
||||
openai: function () {
|
||||
const config = new Configuration({ apiKey: process.env.OPEN_AI_KEY });
|
||||
const openai = new OpenAIApi(config);
|
||||
return openai;
|
||||
},
|
||||
getChatCompletion: async function (
|
||||
openai,
|
||||
messages = [],
|
||||
{ temperature = 0.7 }
|
||||
) {
|
||||
const model = process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo";
|
||||
const { data } = await openai.createChatCompletion({
|
||||
model,
|
||||
messages,
|
||||
temperature,
|
||||
});
|
||||
|
||||
if (!data.hasOwnProperty("choices")) return null;
|
||||
return data.choices[0].message.content;
|
||||
},
|
||||
embedTextInput: async function (openai, textInput) {
|
||||
const result = await this.embedChunks(openai, textInput);
|
||||
return result?.[0] || [];
|
||||
},
|
||||
embedChunks: async function (openai, chunks = []) {
|
||||
const {
|
||||
data: { data },
|
||||
} = await openai.createEmbedding({
|
||||
model: "text-embedding-ada-002",
|
||||
input: chunks,
|
||||
});
|
||||
return data.length > 0 &&
|
||||
data.every((embd) => embd.hasOwnProperty("embedding"))
|
||||
? data.map((embd) => embd.embedding)
|
||||
: null;
|
||||
},
|
||||
llm: function ({ temperature = 0.7 }) {
|
||||
const model = process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo";
|
||||
return new OpenAI({
|
||||
|
@ -182,10 +144,10 @@ const Pinecone = {
|
|||
const textChunks = await textSplitter.splitText(pageContent);
|
||||
|
||||
console.log("Chunks created from document:", textChunks.length);
|
||||
const openAiConnector = new OpenAi();
|
||||
const documentVectors = [];
|
||||
const vectors = [];
|
||||
const openai = this.openai();
|
||||
const vectorValues = await this.embedChunks(openai, textChunks);
|
||||
const vectorValues = await openAiConnector.embedChunks(textChunks);
|
||||
|
||||
if (!!vectorValues && vectorValues.length > 0) {
|
||||
for (const [i, vector] of vectorValues.entries()) {
|
||||
|
@ -299,7 +261,7 @@ const Pinecone = {
|
|||
const response = await chain.call({ query: input });
|
||||
return {
|
||||
response: response.text,
|
||||
sources: curateSources(response.sourceDocuments),
|
||||
sources: this.curateSources(response.sourceDocuments),
|
||||
message: false,
|
||||
};
|
||||
},
|
||||
|
@ -322,7 +284,8 @@ const Pinecone = {
|
|||
"Invalid namespace - has it been collected and seeded yet?"
|
||||
);
|
||||
|
||||
const queryVector = await this.embedTextInput(this.openai(), input);
|
||||
const openAiConnector = new OpenAi();
|
||||
const queryVector = await openAiConnector.embedTextInput(input);
|
||||
const { contextTexts, sourceDocuments } = await this.similarityResponse(
|
||||
pineconeIndex,
|
||||
namespace,
|
||||
|
@ -340,17 +303,32 @@ const Pinecone = {
|
|||
};
|
||||
|
||||
const memory = [prompt, ...chatHistory, { role: "user", content: input }];
|
||||
|
||||
const responseText = await this.getChatCompletion(this.openai(), memory, {
|
||||
const responseText = await openAiConnector.getChatCompletion(memory, {
|
||||
temperature: workspace?.openAiTemp ?? 0.7,
|
||||
});
|
||||
|
||||
return {
|
||||
response: responseText,
|
||||
sources: curateSources(sourceDocuments),
|
||||
sources: this.curateSources(sourceDocuments),
|
||||
message: false,
|
||||
};
|
||||
},
|
||||
curateSources: function (sources = []) {
|
||||
const documents = [];
|
||||
for (const source of sources) {
|
||||
const { metadata = {} } = source;
|
||||
if (Object.keys(metadata).length > 0) {
|
||||
documents.push({
|
||||
...metadata,
|
||||
...(source.hasOwnProperty("pageContent")
|
||||
? { text: source.pageContent }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return documents;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports.Pinecone = Pinecone;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue