Fess 14.8のリリース

Fess 14.8をリリースしました。いつものように変更点を見ていきます。

#2731, #2732, #2733, #2739は検索APIに関連する修正で、従来のJSONで返していた検索APIを一新しました。新しい検索APIはOpenAPIのyamlファイルを作成したので、これを参照してください。今までのJSONの検索APIやGSAの検索APIを利用したい場合は、fess-webapp-classic-apiとfess-webapp-gsa-apiのプラグインを入れることで、利用することができます。

#2734はクロール設定の入力欄の文字数上限のデフォルト値を4000文字から10000文字まで引き上げました。Boxのデータクロールの設定で、パラメーターへの入力が4000文字を超える場合があるため、増やしました。

#2735はcrawler.document.append.filename=trueを設定することで、ファイル名をcontentフィールドに追加するようにしました。普通に検索して、ファイル名でも検索できるようにするためです。

#2736はDocker版の実行時にaudit.logもECS形式でログをコンソールに出力するようにしました。

#2737#2738は検索の実装的な修正です。FessをChatGPT PluginのRetrieverとして、動作するようなプラグインを作っているので、それに対応するために内部実装を変更しました。

#2742は利用しているOpenSearch 2.7に上げました。

#2743はコードの整理しただけです。

#2745はデータストアの設定で、キーがキャメル形式になっていたので、time_to_liveで指定できるように変更しました。

#2747はkeep_expires_docsの指定がtrueとfalseが期待する動作とは逆だったので、正しくしました。

という感じで、今回は検索APIが一新したのが大きな変更だと思います。ChatGPT Plugin対応など、このタイミングで検索API周りを整理しておかないと、今後の開発に影響がありそうだったので、整理しました。

何かあれば、フォーラムに投げてください。

opensearchpyでtimeoutが使えない

opensearchpyのドキュメントにはtimeoutを引数で渡せると書いてあるけど、使えないのを忘れてハマるので、メモ的に残す。

このIssueにもあるように、

client.search(..., timeout=120)

みたいにすると、

opensearchpy.exceptions.ConnectionError: ConnectionError(Timeout value connect was 120, but it must be an int, float or None.) caused by: ValueError(Timeout value connect was 120, but it must be an int, float or None.)

みたいな感じで、Errorが発生する。timeoutを指定したい場合は

client.search(..., params={"timeout":120})

のような感じで、paramsで渡せば指定できる。

Elasticsearchでセマンティックサーチを試す

Elasticsearchでknnクエリーを利用して、ベクトル検索を試すことができるの試してみる。まず、Elasticsearchを起動する。

git clone https://github.com/codelibs/docker-elasticsearch.git
cd docker-elasticsearch
docker compose -f compose.yaml up -d

という感じで、Dockerがあれば、http://localhost:9200でアクセスできるElasticsearchが起動できる。

ML機能はライセンスが必要なので、以下でトライアルを有効にする。

curl -XPOST -H "Content-Type:application/json" "http://localhost:9200/_license/start_trial?acknowledge=true"

ElasticsearchのML機能を利用して、テキストデータを渡されたら、ベクトルに変換してくれるようにingestを設定して、ingestでテキストからベクトルを推論するモデルを登録していく。

まずは、モデルを登録していくのが、モデルをインポートするためには、Pythonのelandを用いて、登録することになる。そのために、Pythonモジュールをインストールする。今回は、日本語を利用するので、以下のようなモジュールを入れておく。

pip install 'eland[pytorch]'
pip install fugashi
pip install unidic-lite

次はモデルのインポート。今回はtohoku/bert-base-japanese-v2を指定する。(今回は、簡単に試せるように、httpsなどのsecurityを無効な状態にしてあるので、ユーザー名などの指定が不要である)

eland_import_hub_model --url http://localhost:9200 --hub-model-id cl-tohoku/bert-base-japanese-v2 --task-type text_embedding

インポートできたら、モデルをデプロイする。

curl -XPOST -H "Content-Type:application/json" "http://localhost:9200/_ml/trained_models/cl-tohoku__bert-base-japanese-v2/deployment/_start"

ingestを登録して、テキストがベクトルに変換できるようにする。

curl -XPUT -H "Content-Type:application/json" "http://localhost:9200/_ingest/pipeline/text_embedding" -d '
{
  "description": "Text embedding pipeline",
  "processors": [
    {
      "inference": {
        "model_id": "cl-tohoku__bert-base-japanese-v2",
        "target_field": "content_vector",
        "field_map": {
          "content": "text_field"
        }
      }
    }
  ]
}
'

contentフィールドにテキスト情報を入れる。ベクトルは、content_vectorオブジェクトに入る。ベクトル自体はcontent_vector.predicted_valueに格納される。

ここまでくれば、下準備ができたので、次にデータを入れるインデックスを作る。

curl -XPUT -H "Content-Type:application/json" "http://localhost:9200/docs" -d '
{
  "settings": {
    "index": {
      "number_of_shards": 1,
      "number_of_replicas": 0
    }
  },
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "standard"
      },
      "content_vector.model_id": {
        "type": "keyword"
      },
      "content_vector.predicted_value": {
        "type": "dense_vector",
        "dims": 768,
        "index": true,
        "similarity": "l2_norm"
      }
    }
  }
}
'

検証なので、contentにデータを入れるシンプルなインデックスです。このインデックスにデータを入れrます。テスト用のデータは、ChatGPTに10件生成してもらったので、それを入れます。

curl -XPOST -H "Content-Type: application/x-ndjson" "http://localhost:9200/_bulk?pipeline=text_embedding" -d '
{"index": {"_index": "docs"}}
{"content": "明日の天気は晴れの予報です。"}
{"index": {"_index": "docs"}}
{"content": "彼女が好きな色はピンクです。"}
{"index": {"_index": "docs"}}
{"content": "日本語を勉強しています。"}
{"index": {"_index": "docs"}}
{"content": "新しいレストランがオープンしました。"}
{"index": {"_index": "docs"}}
{"content": "今日は疲れたので早く寝ます。"}
{"index": {"_index": "docs"}}
{"content": "おいしいコーヒーを飲みました。"}
{"index": {"_index": "docs"}}
{"content": "新しいスマートフォンが発売されます。"}
{"index": {"_index": "docs"}}
{"content": "友達と映画を見に行きました。"}
{"index": {"_index": "docs"}}
{"content": "最近、本を読む習慣を始めました。"}
{"index": {"_index": "docs"}}
{"content": "子供たちが公園で遊んでいます。"}
'

シンプルなデータなので、面白みにかけますが、検索は以下のリクエストを投げると、結果が帰ってきます。

curl -XPOST -H "Content-Type: application/json" "http://localhost:9200/docs/_search" -d '
{
  "knn": {
    "field": "content_vector.predicted_value",
    "query_vector_builder": {
      "text_embedding": {
        "model_id": "cl-tohoku__bert-base-japanese-v2",
        "model_text": "彼女が好きな色は?"
      }
    },
    "k": 3,
    "num_candidates": 10
  },
  "_source": [
    "content"
  ]
}
'

「彼女が好きな色はピンクです。」が1件目で取得できると思いますが、2件目以降は類似度が高い順に返ってくるだけです。

という感じですが、詳しくはElasticsearchのドキュメントを参照するのが良いと思います。

No module named ‘torch._six’

Dollyを試してみようと思い、

git clone https://github.com/databrickslabs/dolly.git
cd dolly
pip install -r requirements.txt

をして、Getting Started with Response Generationを実行してみたけど、

ModuleNotFoundError: No module named 'torch._six'

という感じで怒られる…。ぐぐってみると、._sixを消せば良いとのことなので、

sed -i "s/._six//" /usr/local/lib/python3.9/dist-packages/deepspeed/runtime/utils.py
sed -i "s/._six//" /usr/local/lib/python3.9/dist-packages/deepspeed/runtime/zero/stage_1_and_2.py

みたいな感じで、対象箇所を修正。でも、40Gのメモリーを積んだGPUでも、

from transformers import pipeline

from transformers import pipeline

instruct_pipeline = pipeline(model="databricks/dolly-v2-12b", trust_remote_code=True, device_map="auto")

の方はメモリー不足と言われる…。(厳しい時代だな…)

なので、

import torch
from transformers import pipeline

instruct_pipeline = pipeline(model="databricks/dolly-v2-12b", torch_dtype=torch.bfloat16, trust_remote_code=True, device_map="auto")

を実行したら、処理できた。でも、24Gのメモリーを使っているっぽい…。Colab ProのGPUプレミアム設定で確認した感じです。

Dolly自体については、ChatGPTと比べると、現時点では、これから頑張っていく感じなのかなみたいな感じかな。

OpenAPI対応

Fessでは、検索APIを提供していますが、今まで放置してきたOpenAPI対応に取り組むことにしました。既存のAPIをベースにopenapi.yamlを作れば良いかなと思っていたのですが、いろいろと作業していると、サジェストAPIとの整合性もほぼないですし、統一性がないので、思い切って、一新することにしました。

今回作成したOpenAPIのyamlファイルは、openapi-user.yamlという感じです。現時点では、足りない情報とかもある気はしますが、ちょっとずつ対応していこうと思います。ということで、14.8からはこのAPIがデフォルトになります。

で、今までのJSON APIやGSA APIはどうするか?ということですが、本体からは取り除いて、プラグイン化して、利用するようにしようと思います。なので、過去と互換性が必要であれば、プラグインを追加して、対応するみたいな感じを想定しています。

という感じで、次のバージョンからOpenAPIのyamlが提供される予定です。