12
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Flowiseのカスタムノードを作る

Last updated at Posted at 2024-12-22

1.はじめに

OSSで利用できるローコードAIエージェント開発ツール Flowise ですが、すこし凝った事を実現しようとすると、用意されているノードだけでは不十分な事があります。

そういった場合に、自分でカスタムノードを作ることができるので、その対応方法をまとめす。

2.作成するもの

Flowiseでは様々なベクトルストアノード実装(ElasticsearchやOpenSearch等)が用意されていますが、Azure AI Searchに対応したノードがありませんでした。

この記事ではAzure AI Searchに対応したベクトルストアノードを作成します。

3.準備

3.1 Flowiseをリポジトリから取得

Githubの公式リポジトリからフォークします。

3.2 pnpmをインストール

Flowiseではパッケージマネージャにpnpmを用いているので、これをインストールします。

$ npm i -g pnpm

4.カスタムノードの作成

カスタムノードといっても、LangChainがAzure AI Searchに対応しているベクトルストアのクラスを用意しているので、これを利用するカスタムノードを作るだけです。

Flowiseでは大きく3つのパッケージがあります。

  • components ・・・各ノード
  • server ・・・サーバー側
  • ui ・・・ビュー全般

今回はノードを追加するのでcomponents 配下のサブパッケージに変更を加えます。主なノードはLangChainのコンセプトと対応しているため

  • LangChainのコンポーネント(VectorStoreDocumentLoader等)をつくる
  • FlowiseのNodeでラップする

という流れになります。

4.1 必要なパッケージのインストール

Azure AI Searchへアクセスするために、@azure/search-documentsパッケージをnpmを通じてインストールします。

4.2 カスタムノードのソース

各ノードはpackages/components/nodes 配下にあるので、ここにカスタムノードを作成します。
今回はvectorestoresにあるOpenSearchの実装をマルマル真似ていきます。
LangChainのcommunityパッケージにすでにAzure AI Searchのベクトルストアが存在するので、今回はこれをそのまま使います。
もし独自のデータストアなどを利用したい場合はLangChainのVectorStoreクラスを継承したものを自作する形になります。

ポイントは

  • INodeインターフェイスを実装する
  • inputsフィールドにパラメータを定義する
  • initメソッドをオーバライドし、そこでLangChainVectorStoreクラスあるいはVectorStoreRetriverクラス を生成して返す(今回のサンプルではそれらを継承したAzureAISearchVectorStoreクラス)
/packages/nodes/vectorstores/AzureAISearch/AzureAISearch.ts
class AzureAISearch_VectorStores implements INode {
    label: string
    name: string
    version: number
    description: string
    author: string
    type: string
    icon: string
    category: string
    badge: string
    baseClasses: string[]
    inputs: INodeParams[]
    outputs: INodeOutputsValue[]
    credential: INodeParams

    constructor() {
        //ノードの属性を設定する
        this.label = 'Azure AISearch'
        this.name = 'azureAISearch'
        this.version = 1.0
        this.type = 'AzureAISearch'
        this.icon = 'Azure.svg'
        this.category = 'Vector Stores'
        this.description = `Upsert embedded data and perform similarity search upon query using Azure AISearch`
        this.author = '<作成者>'
        this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever']
        this.credential = {
            label: 'Connect Credential',
            name: 'credential',
            type: 'credential',
            credentialNames: ['azureAISearchUrl']
        }
        //FlowiseのWebUIに表示される各パラメータの定義
        this.inputs = [
            {
                label: 'Document',
                name: 'document',
                type: 'Document',
                list: true,
                optional: true
            },
            {
                label: 'Embeddings',
                name: 'embeddings',
                type: 'Embeddings'
            },
            {
                label: 'Index Name',
                name: 'indexName',
                type: 'string'
            },
            {
                label: 'Top K',
                name: 'topK',
                description: 'Number of top results to fetch. Default to 4',
                placeholder: '4',
                type: 'number',
                additionalParams: true,
                optional: true
            },
            {
                label: 'Search Type',
                name: 'searchType',
                type: 'options',
                description: 'Search type, Default to similarity',
                options: [
                    { label: 'Similarity', name: 'similarity', description: '類似度検索' },
                    { label: 'Similarity_hybrid', name: 'similarity_hybrid', description: '類似度検索ハイブリッド' },
                    { label: 'semantic_hybrid', name: 'semantic_hybrid', description: 'セマンティック検索ハイブリッド' }
                ],
                additionalParams: true,
                optional: true
            }
        ]
        //ノードの出力の定義。
        this.outputs = [
            {
                label: 'AzureAISearch Retriever',
                name: 'retriever',
                baseClasses: this.baseClasses
            },
            {
                label: 'AzureAISearch Vector Store',
                name: 'vectorStore',
                baseClasses: [this.type, ...getBaseClasses(AzureAISearchVectorStore)]
            }
        ]
    }

    //@ts-ignore
    vectorStoreMethods = {
        //FlowiseのWebUIからUpsertされたときの処理。
        async upsert(nodeData: INodeData, options: ICommonObject): Promise<Partial<IndexingResult>> {
            const docs = nodeData.inputs?.document as Document[]
            const embeddings = nodeData.inputs?.embeddings as Embeddings
            const indexName = nodeData.inputs?.indexName as string

            const credentialData = await getCredentialData(nodeData.credential ?? '', options)
            const endpoint = getCredentialParam('endpoint', credentialData, nodeData)
            const key = getCredentialParam('apikey', credentialData, nodeData)

            const flattenDocs = docs && docs.length ? flatten(docs) : []
            const finalDocs = []
            for (let i = 0; i < flattenDocs.length; i += 1) {
                if (flattenDocs[i] && flattenDocs[i].pageContent) {
                    finalDocs.push(new Document(flattenDocs[i]))
                }
            }

            try {
                await AzureAISearchVectorStore.fromDocuments(finalDocs, embeddings, {
                    endpoint,
                    key,
                    indexName
                })
                return { numAdded: finalDocs.length, addedDocs: finalDocs }
            } catch (e) {
                throw new Error(e)
            }
        }
    }
    //ノードの初期化処理
    async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
        const embeddings = nodeData.inputs?.embeddings as Embeddings
        const indexName = nodeData.inputs?.indexName as string
        const output = nodeData.outputs?.output as string
        const topK = nodeData.inputs?.topK as string
        const k = topK ? parseFloat(topK) : 4
        const searchTypeStr = nodeData.inputs?.searchType as string
        let searchType: AzureAISearchQueryType
        if (searchTypeStr == 'similarity') {
            searchType = AzureAISearchQueryType.Similarity
        } else if (searchTypeStr == 'similarity_hybrid') {
            searchType = AzureAISearchQueryType.SimilarityHybrid
        } else if (searchTypeStr == 'semantic_hybrid') {
            searchType = AzureAISearchQueryType.SemanticHybrid
        } else {
            searchType = AzureAISearchQueryType.Similarity
        }

        const credentialData = await getCredentialData(nodeData.credential ?? '', options)
        const endpoint = getCredentialParam('endpoint', credentialData, nodeData)
        const key = getCredentialParam('apikey', credentialData, nodeData)

        const vectorStore = new AzureAISearchVectorStore(embeddings, {
            endpoint,
            key,
            indexName,
            search: {
                type: searchType
            }
        })

        if (output === 'retriever') {
            const retriever = vectorStore.asRetriever(k)
            return retriever
        } else if (output === 'vectorStore') {
            ;(vectorStore as any).k = k
            return vectorStore
        }
        return vectorStore
    }
}

module.exports = { nodeClass: AzureAISearch_VectorStores }

4.3 クレデンシャルのソース

Azure AI Searchにアクセスするための認証情報を保存するクラスです。

/packages/credentials/AzureAiSearchUrl.credential.ts
import { INodeParams, INodeCredential } from '../src/Interface'

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

    constructor() {
        this.label = 'Azure AISearch'
        this.name = 'azureAISearchUrl'
        this.version = 1.0
        this.inputs = [
            {
                label: 'Azure AISearch Endpoint',
                name: 'endpoint',
                type: 'string'
            },
            {
                label: 'API Key',
                name: 'apikey',
                type: 'string',
                placeholder: '<AZUREAISEARCH_APIKEY>',
                optional: true
            }
        ]
    }
}

module.exports = { credClass: AzureAISearchUrl }

4.4 ビルド

ビルドします。

$ pnpm install
$ pnpm build

5. 実行

環境変数SHOW_COMMUNITY_NODESをtrueにして、サーバーを起動させます。

$ SHOW_COMMUNITY_NODES=true pnpm start

追加したカスタムノードが一覧に表れてフローが作れれば成功です。

(図1)Azure AI Searchベクトルストア カスタムノードでフローを作った図
Flowise1.png

(図2)Azure AI Searchベクトルストア カスタムノードの追加設定
Flowise2.png

6.さいごに

LangChainの知識がある程度あれば、比較的容易にFlowiseを拡張することができました。
Flowiseを使えば、手軽にRAGシステムやAIエージェントシステムなどをグラフィカルに手早く作るることができるので、手早く実証実験などを行う事ができます。

一方、この手のローコードツールにありがちな 凝ったことができない再利用性が悪いという問題もカスタムノードを実装することである程度軽減できます。

業務独自のデータソースや汎用機との接続が行えるノードなどを作ることで、生成AIでできることの幅が広がりそうです。

12
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?