Skip to content

Commit

Permalink
Voyage AI - Credentials, Embeddings and Rerank (FlowiseAI#2015)
Browse files Browse the repository at this point in the history
* Voyage AI - Credentials and Embeddings

* reranker and fixes for embeddings
  • Loading branch information
vinodkiran authored Mar 26, 2024
1 parent 6fd0fe6 commit 794818b
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 0 deletions.
32 changes: 32 additions & 0 deletions packages/components/credentials/VoyageAIApi.credential.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { INodeParams, INodeCredential } from '../src/Interface'

class VoyageAIApi implements INodeCredential {
label: string
name: string
version: number
description: string
inputs: INodeParams[]

constructor() {
this.label = 'Voyage AI API'
this.name = 'voyageAIApi'
this.version = 1.0
this.description =
'Refer to <a target="_blank" href="https://docs.voyageai.com/install/#authentication-with-api-keys">official guide</a> on how to get an API Key'
this.inputs = [
{
label: 'Voyage AI Endpoint',
name: 'endpoint',
type: 'string',
default: 'https://api.voyageai.com/v1/embeddings'
},
{
label: 'Voyage AI API Key',
name: 'apiKey',
type: 'password'
}
]
}
}

module.exports = { credClass: VoyageAIApi }
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { VoyageEmbeddings, VoyageEmbeddingsParams } from 'langchain/embeddings/voyage'

class VoyageAIEmbedding_Embeddings implements INode {
label: string
name: string
version: number
type: string
icon: string
category: string
description: string
baseClasses: string[]
credential: INodeParams
inputs: INodeParams[]

constructor() {
this.label = 'VoyageAI Embeddings'
this.name = 'voyageAIEmbeddings'
this.version = 1.0
this.type = 'VoyageAIEmbeddings'
this.icon = 'voyageai.png'
this.category = 'Embeddings'
this.description = 'Voyage AI API to generate embeddings for a given text'
this.baseClasses = [this.type, ...getBaseClasses(VoyageEmbeddings)]
this.credential = {
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['voyageAIApi']
}
this.inputs = [
{
label: 'Model Name',
name: 'modelName',
type: 'options',
options: [
{
label: 'voyage-2',
name: 'voyage-2',
description: 'Base generalist embedding model optimized for both latency and quality'
},
{
label: 'voyage-code-2',
name: 'voyage-code-2',
description: 'Optimized for code retrieval'
},
{
label: 'voyage-large-2',
name: 'voyage-large-2',
description: 'Powerful generalist embedding model'
},
{
label: 'voyage-lite-02-instruct',
name: 'voyage-lite-02-instruct',
description: 'Instruction-tuned for classification, clustering, and sentence textual similarity tasks'
}
],
default: 'voyage-2',
optional: true
}
]
}

async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
const modelName = nodeData.inputs?.modelName as string

const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const voyageAiApiKey = getCredentialParam('apiKey', credentialData, nodeData)
const voyageAiEndpoint = getCredentialParam('endpoint', credentialData, nodeData)

const obj: Partial<VoyageEmbeddingsParams> & { apiKey?: string } = {
apiKey: voyageAiApiKey
}

if (modelName) obj.modelName = modelName

const model = new VoyageEmbeddings(obj)
if (voyageAiEndpoint) model.apiUrl = voyageAiEndpoint
return model
}
}

module.exports = { nodeClass: VoyageAIEmbedding_Embeddings }
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import axios from 'axios'
import { Callbacks } from '@langchain/core/callbacks/manager'
import { Document } from '@langchain/core/documents'
import { BaseDocumentCompressor } from 'langchain/retrievers/document_compressors'

export class VoyageAIRerank extends BaseDocumentCompressor {
private voyageAIAPIKey: any
private readonly VOYAGEAI_RERANK_API_URL = 'https://api.voyageai.com/v1/rerank'
private model: string = 'rerank-lite-1'
private readonly k: number

constructor(voyageAIAPIKey: string, model: string, k: number) {
super()
this.voyageAIAPIKey = voyageAIAPIKey
this.model = model
this.k = k
}
async compressDocuments(
documents: Document<Record<string, any>>[],
query: string,
_?: Callbacks | undefined
): Promise<Document<Record<string, any>>[]> {
// avoid empty api call
if (documents.length === 0) {
return []
}
const config = {
headers: {
Authorization: `Bearer ${this.voyageAIAPIKey}`,
'Content-Type': 'application/json',
Accept: 'application/json'
}
}
const data = {
model: this.model,
query: query,
documents: documents.map((doc) => doc.pageContent)
}
try {
let returnedDocs = await axios.post(this.VOYAGEAI_RERANK_API_URL, data, config)
const finalResults: Document<Record<string, any>>[] = []
returnedDocs.data.results.forEach((result: any) => {
const doc = documents[result.index]
doc.metadata.relevance_score = result.relevance_score
finalResults.push(doc)
})
return finalResults.splice(0, this.k)
} catch (error) {
return documents
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { BaseRetriever } from '@langchain/core/retrievers'
import { VectorStoreRetriever } from '@langchain/core/vectorstores'
import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression'
import { VoyageAIRerank } from './VoyageAIRerank'
import { getCredentialData, getCredentialParam, handleEscapeCharacters } from '../../../src'
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'

class VoyageAIRerankRetriever_Retrievers implements INode {
label: string
name: string
version: number
description: string
type: string
icon: string
category: string
baseClasses: string[]
inputs: INodeParams[]
credential: INodeParams
badge: string
outputs: INodeOutputsValue[]

constructor() {
this.label = 'Voyage AI Rerank Retriever'
this.name = 'voyageAIRerankRetriever'
this.version = 1.0
this.type = 'VoyageAIRerankRetriever'
this.icon = 'voyageai.png'
this.category = 'Retrievers'
this.badge = 'NEW'
this.description = 'Voyage AI Rerank indexes the documents from most to least semantically relevant to the query.'
this.baseClasses = [this.type, 'BaseRetriever']
this.credential = {
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['voyageAIApi']
}
this.inputs = [
{
label: 'Vector Store Retriever',
name: 'baseRetriever',
type: 'VectorStoreRetriever'
},
{
label: 'Model Name',
name: 'model',
type: 'options',
options: [
{
label: 'rerank-lite-1',
name: 'rerank-lite-1'
}
],
default: 'rerank-lite-1',
optional: true
},
{
label: 'Query',
name: 'query',
type: 'string',
description: 'Query to retrieve documents from retriever. If not specified, user question will be used',
optional: true,
acceptVariable: true
},
{
label: 'Top K',
name: 'topK',
description: 'Number of top results to fetch. Default to the TopK of the Base Retriever',
placeholder: '4',
type: 'number',
additionalParams: true,
optional: true
}
]
this.outputs = [
{
label: 'Voyage AI Rerank Retriever',
name: 'retriever',
baseClasses: this.baseClasses
},
{
label: 'Document',
name: 'document',
description: 'Array of document objects containing metadata and pageContent',
baseClasses: ['Document', 'json']
},
{
label: 'Text',
name: 'text',
description: 'Concatenated string from pageContent of documents',
baseClasses: ['string', 'json']
}
]
}

async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever
const model = nodeData.inputs?.model as string
const query = nodeData.inputs?.query as string
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const voyageAiApiKey = getCredentialParam('apiKey', credentialData, nodeData)
const topK = nodeData.inputs?.topK as string
const k = topK ? parseFloat(topK) : (baseRetriever as VectorStoreRetriever).k ?? 4
const output = nodeData.outputs?.output as string

const voyageAICompressor = new VoyageAIRerank(voyageAiApiKey, model, k)

const retriever = new ContextualCompressionRetriever({
baseCompressor: voyageAICompressor,
baseRetriever: baseRetriever
})

if (output === 'retriever') return retriever
else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input)
else if (output === 'text') {
let finaltext = ''

const docs = await retriever.getRelevantDocuments(query ? query : input)

for (const doc of docs) finaltext += `${doc.pageContent}\n`

return handleEscapeCharacters(finaltext, false)
}

return retriever
}
}

module.exports = { nodeClass: VoyageAIRerankRetriever_Retrievers }
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 794818b

Please sign in to comment.