Weaviateの初歩　Pythonでcollectionを作ってみる

[以前の記事](https://qiita.com/yasuyuki_terashi/items/a52358705720698caecc)でWeaviateをk3sクラスタ上に構築したので、コレクションを作ってみましょう。RDBで言うところのテーブルみたいなものでしょうか。

# どのようなコレクションを作るか
ER図で示すのが正しいのか分かりませんが、以下のような関係性を持たせたいと思います。
```mermaid
erDiagram
    TestA ||--o{ TestB:""

    TestA{
        text title
        text summary
    }
    TestB{
        int chapter
        text text
    }
    
```
・TestAのエンティティにはtitleとsummaryがあります。
・TestAは複数のTestBエンティティを持ちます。
・TestBのエンティティにはchapterとtextがあります。

# 事前準備
```
$ pip install weaviate-client
```
# コレクションの作成
```python:10C_define_class_TestA.py(注:うまくいかない)
import weaviate
import weaviate.classes.config as wc

weaviate_client = weaviate.connect_to_custom(
    http_host="xxx.xxx.xxx.xxx",  # URL only, no http prefix
    http_port=30020,
    http_secure=False,   # Set to True if https
    grpc_host="xxx.xxx.xxx.xxx",
    grpc_port=30021,      # Default is 50051, WCD uses 443
    grpc_secure=False,   # Edit as needed
)

weaviate_client.collections.create(
    name='TestA',
    properties=[wc.Property(name='title',data_type=wc.DataType.TEXT),
                wc.Property(name='summary',data_type=wc.DataType.TEXT)],
    references=[wc.ReferenceProperty(
            name='children',
            target_collection='TestB'
        )
    ],
    vectorizer_config=[wc.Configure.NamedVectors.text2vec_aws(
        name='textdocument_vector',
        service='bedrock',
        model='cohere.embed-multilingual-v3',
        region='us-west-2'
    )        
    ]
)

weaviate_client.close()
```


・`WeaviateClient.collections.create()`でコレクションを作成します。
・引数`properties`はリストを取ります。ここに`wc.Property`型のインスタンスを並べます。インスタンス中の`data_type`の指定は`wc.DataType`を使います。
・引数`references`はリストを取ります。ここに`wc.ReferenceProperty`型のインスタンスを並べます。インスタンス中の`target_collection`には、リファレンス先のコレクションの名前を指定します。
・ベクトル化の肝であるベクトライザーをコレクションに対して指定しています。その指定には引数`vectorizer_config`を使いますが、これに`wc.Configure.NamedVectors`以下のメソッドを指定してあげます。ここに`text2vec_openai`やら`text2vec_cohere`やらありますが、私はBedrockでcohere embeddingを使うので`text2vec_aws`を使いました。これらベクトライザーモジュール関連についての詳細は以下が詳しいと思います。英語ですが。

https://weaviate.io/developers/weaviate/modules/retriever-vectorizer-modules

**ところが**、これを実行すると以下のように**叱られてしまいました。**
```bash
$ python 10C_define_class_TestA.py
(略)
    raise UnexpectedStatusCodeError(error_msg, response=res)
weaviate.exceptions.UnexpectedStatusCodeError: Collection may not have been created properly.! Unexpected status code: 422, with response body: {'error': [{'message': "property 'children': invalid dataType: reference property to nonexistent class"}]}.
/home/yterashi/.pyenv/versions/3.12.4/lib/python3.12/site-packages/weaviate/warnings.py:303: ResourceWarning: Con004: The connection to Weaviate was not closed properly. This can lead to memory leaks.
            Please make sure to close the connection using `client.close()`.
/home/yterashi/.pyenv/versions/3.12.4/lib/python3.12/asyncio/selector_events.py:879: ResourceWarning: unclosed transport <_SelectorSocketTransport fd=10 read=idle write=<idle, bufsize=0>>
```
`reference property to nonexistent class`という一文からすると、リファレンス先のコレクションTestBがまだ存在しないのが問題のようです。存在しないコレクションへのリファレンスは作ることが出来ないみたいですね。
TestB側のコレクションを作るスクリプトは以下の通りです。
```Python:10D_define_class_TestB.py
import weaviate
import weaviate.classes.config as wc

weaviate_client = weaviate.connect_to_custom(
    http_host="xxx.xxx.xxx.xxx",  # URL only, no http prefix
    http_port=30020,
    http_secure=False,   # Set to True if https
    grpc_host="xxx.xxx.xxx.xxx",
    grpc_port=30021,      # Default is 50051, WCD uses 443
    grpc_secure=False,   # Edit as needed
)

weaviate_client.collections.create(
    name='TestB',
    properties=[wc.Property(name='chapter',data_type=wc.DataType.INT),
                wc.Property(name='text',data_type=wc.DataType.TEXT)],
    references=[wc.ReferenceProperty(
            name='parent',
            target_collection='TestA'
    )        
    ],
    vectorizer_config=[wc.Configure.NamedVectors.text2vec_aws(
        name='textdocument_vector',
        service='bedrock',
        model='cohere.embed-multilingual-v3',
        region='us-west-2'
    )        
    ]
)

weaviate_client.close()
```
やはりこちらも同じようなエラーが出てしまいました。そこで、TestA側を作るスクリプトを少し変え、一旦リファレンスを設定しない事にします。
```Python:10C_define_class_TestA.py(修正)
import weaviate
import weaviate.classes.config as wc

weaviate_client = weaviate.connect_to_custom(
    http_host="xxx.xxx.xxx.xxx",  # URL only, no http prefix
    http_port=30020,
    http_secure=False,   # Set to True if https
    grpc_host="xxx.xxx.xxx.xxx",
    grpc_port=30021,      # Default is 50051, WCD uses 443
    grpc_secure=False,   # Edit as needed
)

weaviate_client.collections.create(
    name='TestA',
    properties=[wc.Property(name='title',data_type=wc.DataType.TEXT),
                wc.Property(name='summary',data_type=wc.DataType.TEXT)],
    vectorizer_config=[wc.Configure.NamedVectors.text2vec_aws(
        name='textdocument_vector',
        service='bedrock',
        model='cohere.embed-multilingual-v3',
        region='us-west-2'
    )        
    ]
)

weaviate_client.close()
```
今度はうまくいきました。その次に、10D_define_class_TestB.pyも(こちらは変更せず)うまくいきました。TestB側のリファレンス作成がうまくいったのはコレクションTestAが既にできていたからですね。

# リファレンスを追加する
TestA側にTestBへのリファレンス追加がやり残しになってるので、これを追加しましょう。`config.add_reference`というメソッドがあります。

```Python:10E_define_reference_TestA.py
import weaviate
from weaviate.classes.config import ReferenceProperty

weaviate_client = weaviate.connect_to_custom(
    http_host="xxx.xxx.xxx.xxx",  # URL only, no http prefix
    http_port=30020,
    http_secure=False,   # Set to True if https
    grpc_host="xxx.xxx.xxx.xxx",
    grpc_port=30021,      # Default is 50051, WCD uses 443
    grpc_secure=False,   # Edit as needed
)

TestA = weaviate_client.collections.get('TestA')

try:
    TestA.config.add_reference(
        ReferenceProperty(
            name='children',
            target_collection='TestB'
        )
    )
except Exception as e:
    print(f"エラーが発生しました。{e}")

weaviate_client.close()
```
エラーも返らず、うまくいったようです。ここでcurlを使い、データベースのスキーマを得ることにしましょう。以下で、json形式でデータベース全体のスキーマを得ることが出来ます。なお、ポート番号は8080が標準ですが、私の場合k3sのnodeportの都合上30020となっています。
```bash
$ curl http://xxx.xxx.xxx.xxx:30020/v1/schema > schema.json
```
結果は以下の通りです。
<details><summary>schema.json</summary>

```json
{
    "classes": [
        {
            "class": "TestA",
            "invertedIndexConfig": {
                "bm25": {
                    "b": 0.75,
                    "k1": 1.2
                },
                "cleanupIntervalSeconds": 60,
                "stopwords": {
                    "additions": null,
                    "preset": "en",
                    "removals": null
                }
            },
            "multiTenancyConfig": {
                "autoTenantActivation": false,
                "autoTenantCreation": false,
                "enabled": false
            },
            "properties": [
                {
                    "dataType": [
                        "text"
                    ],
                    "indexFilterable": true,
                    "indexSearchable": true,
                    "moduleConfig": {
                        "text2vec-aws": {
                            "skip": false,
                            "vectorizePropertyName": false
                        }
                    },
                    "name": "title",
                    "tokenization": "word"
                },
                {
                    "dataType": [
                        "text"
                    ],
                    "indexFilterable": true,
                    "indexSearchable": true,
                    "moduleConfig": {
                        "text2vec-aws": {
                            "skip": false,
                            "vectorizePropertyName": false
                        }
                    },
                    "name": "summary",
                    "tokenization": "word"
                },
                {
                    "dataType": [
                        "TestB"
                    ],
                    "indexFilterable": true,
                    "indexSearchable": false,
                    "moduleConfig": {
                        "text2vec-aws": {
                            "skip": false,
                            "vectorizePropertyName": false
                        }
                    },
                    "name": "children"
                }
            ],
            "replicationConfig": {
                "factor": 1
            },
            "shardingConfig": {
                "actualCount": 1,
                "actualVirtualCount": 128,
                "desiredCount": 1,
                "desiredVirtualCount": 128,
                "function": "murmur3",
                "key": "_id",
                "strategy": "hash",
                "virtualPerPhysical": 128
            },
            "vectorConfig": {
                "textdocument_vector": {
                    "vectorIndexConfig": {
                        "bq": {
                            "enabled": false
                        },
                        "cleanupIntervalSeconds": 300,
                        "distance": "cosine",
                        "dynamicEfFactor": 8,
                        "dynamicEfMax": 500,
                        "dynamicEfMin": 100,
                        "ef": -1,
                        "efConstruction": 128,
                        "flatSearchCutoff": 40000,
                        "maxConnections": 64,
                        "pq": {
                            "bitCompression": false,
                            "centroids": 256,
                            "enabled": false,
                            "encoder": {
                                "distribution": "log-normal",
                                "type": "kmeans"
                            },
                            "segments": 0,
                            "trainingLimit": 100000
                        },
                        "skip": false,
                        "vectorCacheMaxObjects": 1000000000000
                    },
                    "vectorIndexType": "hnsw",
                    "vectorizer": {
                        "text2vec-aws": {
                            "model": "cohere.embed-multilingual-v3",
                            "region": "us-west-2",
                            "service": "bedrock",
                            "vectorizeClassName": true
                        }
                    }
                }
            }
        },
        {
            "class": "TestB",
            "invertedIndexConfig": {
                "bm25": {
                    "b": 0.75,
                    "k1": 1.2
                },
                "cleanupIntervalSeconds": 60,
                "stopwords": {
                    "additions": null,
                    "preset": "en",
                    "removals": null
                }
            },
            "multiTenancyConfig": {
                "autoTenantActivation": false,
                "autoTenantCreation": false,
                "enabled": false
            },
            "properties": [
                {
                    "dataType": [
                        "int"
                    ],
                    "indexFilterable": true,
                    "indexSearchable": false,
                    "moduleConfig": {
                        "text2vec-aws": {
                            "skip": false,
                            "vectorizePropertyName": false
                        }
                    },
                    "name": "chapter"
                },
                {
                    "dataType": [
                        "text"
                    ],
                    "indexFilterable": true,
                    "indexSearchable": true,
                    "moduleConfig": {
                        "text2vec-aws": {
                            "skip": false,
                            "vectorizePropertyName": false
                        }
                    },
                    "name": "text",
                    "tokenization": "word"
                },
                {
                    "dataType": [
                        "TestA"
                    ],
                    "indexFilterable": true,
                    "indexSearchable": false,
                    "moduleConfig": {
                        "text2vec-aws": {
                            "skip": false,
                            "vectorizePropertyName": false
                        }
                    },
                    "name": "parent"
                }
            ],
            "replicationConfig": {
                "factor": 1
            },
            "shardingConfig": {
                "actualCount": 1,
                "actualVirtualCount": 128,
                "desiredCount": 1,
                "desiredVirtualCount": 128,
                "function": "murmur3",
                "key": "_id",
                "strategy": "hash",
                "virtualPerPhysical": 128
            },
            "vectorConfig": {
                "textdocument_vector": {
                    "vectorIndexConfig": {
                        "bq": {
                            "enabled": false
                        },
                        "cleanupIntervalSeconds": 300,
                        "distance": "cosine",
                        "dynamicEfFactor": 8,
                        "dynamicEfMax": 500,
                        "dynamicEfMin": 100,
                        "ef": -1,
                        "efConstruction": 128,
                        "flatSearchCutoff": 40000,
                        "maxConnections": 64,
                        "pq": {
                            "bitCompression": false,
                            "centroids": 256,
                            "enabled": false,
                            "encoder": {
                                "distribution": "log-normal",
                                "type": "kmeans"
                            },
                            "segments": 0,
                            "trainingLimit": 100000
                        },
                        "skip": false,
                        "vectorCacheMaxObjects": 1000000000000
                    },
                    "vectorIndexType": "hnsw",
                    "vectorizer": {
                        "text2vec-aws": {
                            "model": "cohere.embed-multilingual-v3",
                            "region": "us-west-2",
                            "service": "bedrock",
                            "vectorizeClassName": true
                        }
                    }
                }
            }
        }
    ]
}
```
</details>
schemaがどのように定義されているか解読したい場合は、以下の資料が役に立つと思います。

https://weaviate.io/developers/weaviate/config-refs/schema

# 感想
・ベクトルDBのことはよく知らないのですが、WeaviateはベクトルDBとグラフDBのハイブリッドのようなものなんですね(グラフDBのこともよく知りませんけど)
・Weaviateを操作するGUIクライアントのようなものが無くて、Pythonスクリプトを走らせるとかしないと操作できないですね、今のところ。コマンドラインで動かすより、jupyter notebookのような環境で操作する方が楽かもしれないです(と気付いた)。
・ネット上にはPython ClientのV3とV4の資料が混在していて苦労します。

# 次の記事は
オブジェクトの挿入、取得、全削除をやってみます。

https://qiita.com/yasuyuki_terashi/items/18f6f8760cf281d845b4