AIパーソナル・ラーニング
と実践的なガイダンス

ChatOllamaノート|生産性とRedisベースの文書データベースのための高度なRAGの実装

 

チャットオラマ LLMをベースにしたオープンソースのチャットボットです。ChatOllamaの詳細については、以下のリンクをクリックしてください。


 

ChatOllama|Ollamaベースの100%ローカルRAGアプリケーション

 

チャットオラマ 当初の目標は、100%のネイティブRAGアプリケーションをユーザーに提供することでした。

 

その成長とともに、より多くのユーザーから貴重な要望が寄せられるようになりました。現在、`ChatOllama`は以下のような複数の言語モデルをサポートしています:

 

ChatOllamaで、ユーザーは次のことができます:

  • Ollamaモデルの管理(プル/削除)
  • ナレッジベースの管理(作成/削除)
  • LLMとの自由な対話
  • 個人の知識ベースの管理
  • 個人的な知識ベースを通じてLLMとコミュニケーションを図る

 

今回は、本番用の高度なRAGを実現する方法を見ていこうと思う。RAGの基本的なテクニックと高度なテクニックを学びましたが、RAGをプロダクションに導入するためには、まだまだ気をつけなければならないことがたくさんあります。ChatOllamaで行われてきた作業と、RAGを本番に持ち込むために行われてきた準備を共有します。

 

ChatOllamaノート|RAGの高度な生産性とRedisベースの文書データベースの実装-1

ChatOllama|RAGコンストラクテッド・ラン

 

ChatOllamaプラットフォームではLangChain.jsを使ってRAGプロセスを操作します。ChatOllamaの知識ベースでは、オリジナルのRAGを超えた親ドキュメント検索が使用されています。そのアーキテクチャと実装の詳細に深く潜ってみましょう。私たちは、あなたがそれを啓発見つけることを願っています。

 

 

親ドキュメント検索ユーティリティ

 

親ドキュメント・リトリーバーを実際の製品で機能させるためには、その核となる原理を理解し、生産目的に適したストレージ・エレメントを選択する必要がある。

 

親ドキュメント検索者の基本原則

検索が必要な文書を分割する場合、しばしば相反する要件が発生する:

  • 埋め込まれたコンテンツがその意味を最も正確に表すことができるように、ドキュメントはできるだけ小さくすることを望むかもしれません。コンテンツが長すぎると、埋め込まれた情報が意味を失ってしまうかもしれません。
  • また、各段落に完全なフルテキスト環境が確保できるよう、十分な長さの文書も必要だ。

 

Powered by LangChain.js親ドキュメントレトリバーこのバランスは、文書をチャンクに分割することで達成される。検索のたびに、チャンクを抽出し、各チャンクに対応する親ドキュメントを検索し、より広い範囲のドキュメントを返します。一般的に、親文書はdoc idによってチャンクにリンクされています。この仕組みについては後で詳しく説明します。

ここでは親ドキュメントソース文書の小片を指す。

 

ビルド

親ドキュメント・リトリーバの全体的なアーキテクチャ図を見てみよう。

各チャンクは処理され、ベクトルデータに変換され、ベクトルデータベースに格納される。 これらのチャンクを取り出すプロセスは、オリジナルのRAGセットアップで行われたものと同じである。

親文書(block-1, block-2, ....block-m) が小さな部分に分割される。 ここでは2つの異なるテキスト分割方法が使用されている。親文書には大きな分割方法、小さなブロックには小さな分割方法である。各親文書には文書IDが与えられ、このIDは対応するチャンクのメタデータとして記録される。 これにより、各チャンクは、メタデータに格納された文書IDを使って、対応する親文書を見つけることができる。

親文書を検索するプロセスはこれと同じではない。類似性検索の代わりに、文書IDが提供され、対応する親文書を検索する。この場合、キー・バリュー・ストレージ・システムで十分である。

 

ChatOllamaノート|RAGの高度な生産性とRedisベースの文書データベースの実装-2

親ドキュメントレトリバー

 

保管セクション

保存すべきデータには2種類ある:

  • 小規模ベクトルデータ
  • 文書IDを含む親文書の文字列データ

 

主要なベクターデータベースはすべて利用できる。ChatOllama`のうち、私はChromaを選んだ。

親ドキュメント・データには、最もポピュラーでスケーラビリティの高いキー・バリュー・ストレージ・システムであるRedisを選んだ。

 

LangChain.jsに欠けているもの

LangChain.jsは、バイトストレージとドキュメントストレージを統合する方法を数多く提供している:

 

ストレージ|🦜️🔗 ラングチェーン

データをキーと値のペアとして格納することは、高速かつ効率的であり、LLMアプリケーションの強力なツールである。ベースリポジトリ...

js.langchain.com

 

IORedis`のサポート:

 

イオレディス|🦜️🔗 ラングチェーン

この例では、RedisByteStore ベースリポジトリの統合を使用して、チャット履歴用のストレージを設定する方法を示します。

js.langchain.com

 

RedisByteStoreに欠けている部分はコレクションメカニズム

 

異なる知識ベースからのドキュメントを処理する場合、各知識ベースはコレクションコレクションはコレクションの形で整理され、同じライブラリ内のドキュメントはベクターデータに変換され、Chromaのようなベクターデータベースのいずれかに保存される。コレクション集会で

 

知識ベースを削除したいとします。確かにChromaデータベースの`コレクション`コレクションを削除することはできます。しかし、ナレッジベースのディメンションに従ってドキュメントストアをクリーンアップするにはどうすればいいでしょうか?RedisByteStoreのようなコンポーネントは`コレクション`機能をサポートしていないので、自分で実装する必要がありました。

 

チャットオラマRedisDocStore

Redis には `collection` と呼ばれる組み込みの機能はない。開発者は通常、プレフィックスキーを使用して `collection` 機能を実装します。次の図は、異なるコレクションを識別するためにプレフィックスを使用する方法を示しています:

 

ChatOllamaノート|高度なRAG生産性とRedisベースの文書データベースの実装-3

プレフィックスを使ったRedisのキー表現

 

では、どのように実装されたのかを見てみよう。レディス背景の文書保存機能の

 

キーメッセージ

  • 各 `RedisDocstore` は `namespace` パラメータで初期化される。(名前空間とコレクションの命名は現時点では標準化されていないかもしれない)。
  • キーは、get操作とset操作の両方で、最初に`名前空間`が処理される。

 

import { Document } from "@langchain/core/documents" ;
import { BaseStoreInterface } from "@langchain/core/stores" ;
import { Redis } from "ioredis".
インポート { createRedisClient } from "@/server/store/redis" ;

export class RedisDocstore implements BaseStoreInterface.
{
名前空間:文字列;
_client: Redis.

コンストラクタ(namespace: string) {.
this._namespace = namespace;
this._client = createRedisClient();
}

serializeDocument(doc: Document): string {.
JSON.stringify(doc)を返す;
}

deserializeDocument(jsonString: string): ドキュメント {.
const obj = JSON.parse(jsonString);
return new Document(obj);
}

getNamespacedKey(key: string): string {.
${this._namespace}:${key}`を返します;
}

getKeys(): Promise {.
return new Promise((resolve, reject) => {.
const stream = this._client.scanStream({ match: this._namespace + '*' });

const keys: string[] = [];
stream.on('data', (resultKeys) => {.
keys.push(....resultKeys);
});

stream.on('end', () => {.
resolve(keys)。
});

stream.on('error', (err) => {.
reject(err);
});
});
}

addText(key: string, value: string) {.
this._client.set(this.getNamespacedKey(key), value);
}

async search(search: string): Promise {.
const result = await this._client.get(this.getNamespacedKey(search));
もし(!result) {。
throw new Error(`ID ${search} not found.`);
} else {
const document = this.deserializeDocument(result);
ドキュメントを返す;
}
}

/**
* :: ストアに新しい文書を追加する。
* param text ドキュメントIDをキーとし、ドキュメントそのものを値とするオブジェクト。
* Void を返す
*/
async add(texts: Record): Promise {.
for (Object.entries(texts)のconst [key, value]) {。
console.log(`ストアに${key}を追加:${this.serializeDocument(value)}`);
}

const keys = [....awaitこの.getKeys()];
const overlapping = Object.keys(texts).filter((x) => keys.includes(x));

if (overlapping.length > 0) {.
throw new Error(`Trying to add id that already exist: ${overlapping}`);
}

for (Object.entries(texts)のconst [key, value]) {。
this.addText(key, this.serialiseDocument(value)); this.
}
}

async mget(keys: string[]): Promise {.
return Promise.all(keys.map((key) => {)
const document = this.search(key);
ドキュメントを返す;
}));
}

async mset(keyValuePairs: [string, Document][]): Promise {.
await Promise.all()
keyValuePairs.map(([key, value]) => this.add({ [key]: value }))
);
}

async mdelete(_keys: string[]): Promise {.
throw new Error("実装されていません。");
}

// eslint-next-lineを無効にする require-yield
async *yieldKeys(_prefix?: string): AsyncGenerator {.
throw new Error("実装されていません");
}

非同期deleteAll(): Promise { {.
return new Promise((resolve, reject) => {.
レット カーソル = '0';

const scanCallback = (err, result) => {.
もし (err) {
reject(err);
を返す;
}

const [nextCursor, keys] = result;

// プレフィックスに一致するキーを削除する
キー.forEach((キー) => {)
this._client.del(key);
});

// カーソルが'0'の場合、すべてのキーを反復処理したことになる。
if (nextCursor === '0') {.
resolve()。
} else {
// 次のカーソルでスキャンを続ける
this._client.scan(nextCursor, 'MATCH', `${this._namespace}:*`, scanCallback);
}
};

// 最初のSCAN操作を開始する
this._client.scan(cursor, 'MATCH', `${this._namespace}:*`, scanCallback);
});
}
}

 

このコンポーネントは ParentDocumentRetriever とシームレスに使用できます:

retriever = 新しいParentDocumentRetriever({)
vectorstore: chromaClient.
docstore: 新しいRedisDocstore(collectionName)、
parentSplitter: new RecursiveCharacterTextSplitter({)
chunkOverlap: 200、
チャンクサイズ:1000
}),
childSplitter: new RecursiveCharacterTextSplitter({)
チャンクオーバーラップ:50
チャンクサイズ:200
}),
childK:20歳。
親K:5.
});

私たちは現在、`Chroma`と`Redis`とともに、高度なRAG、Parent Document Retrieverのためのスケーラブルなストレージソリューションを手に入れました。

AIイージー・ラーニング

AIを始めるための素人ガイド

AIツールの活用方法を、低コスト・ゼロベースから学ぶことができます。AIはオフィスソフトと同様、誰にとっても必須のスキルです。 AIをマスターすれば、就職活動で有利になり、今後の仕事や勉強の労力も半減します。

詳細を見る
無断転載を禁じます:チーフAIシェアリングサークル " ChatOllamaノート|生産性とRedisベースの文書データベースのための高度なRAGの実装

チーフAIシェアリングサークル

チーフAIシェアリングサークルは、AI学習に焦点を当て、包括的なAI学習コンテンツ、AIツール、実践指導を提供しています。私たちの目標は、高品質のコンテンツと実践的な経験の共有を通じて、ユーザーがAI技術を習得し、AIの無限の可能性を一緒に探求することです。AI初心者でも上級者でも、知識を得てスキルを向上させ、イノベーションを実現するための理想的な場所です。

お問い合わせ
ja日本語