perf: use the orjson codec for Valkey vector metadata (#27805)
The Valkey backend serializes chunk metadata on every insert and parses it back on every result row in `get` and `query`. Both directions now go through `JSONCodec`, which selects orjson when `ENABLE_ORJSON` is set. The stored `metadata_json` field is never matched against as text. `_build_filter_expression` only emits TAG predicates, and the TAG fields are `id`, `hash`, `file_id`, `source` and `knowledge_base_id`; `metadata_json` appears only as a return field that is immediately re-parsed. So rows written with escaped non-ASCII and rows written raw are indistinguishable to every reader, and no migration is needed. `process_metadata` already stringifies datetimes and strips null bytes and lone surrogates before the write, so the two backends cannot disagree about what is serializable here. Both read `except` clauses widen from `(json.JSONDecodeError, TypeError)` to `(ValueError, TypeError)`. The codec falls back to engineio's codec, which installs `parse_int=_safe_int` and raises a bare `ValueError` for integer literals longer than 100 characters; the narrower clause would have let that escape and abort a search instead of yielding empty metadata. `json.JSONDecodeError` is a `ValueError` subclass, so this is a strict superset. That removes the module's last use of stdlib `json`, so the import goes with it. With `ENABLE_ORJSON` unset, which is the default, `JSONCodec` is stdlib `json` and this call site behaves exactly as before.
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
# Requires Valkey core >= 9.0.1 with the valkey-search module >= 1.2.0 loaded.
|
||||
|
||||
import atexit
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import struct
|
||||
@@ -24,6 +23,7 @@ from open_webui.retrieval.vector.main import (
|
||||
VectorItem,
|
||||
)
|
||||
from open_webui.retrieval.vector.utils import process_metadata
|
||||
from open_webui.utils.json_codec import JSONCodec
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -482,7 +482,7 @@ class ValkeyClient(VectorDBBase):
|
||||
'id': item['id'],
|
||||
'vector': _vector_to_bytes(item['vector']),
|
||||
'text': item['text'],
|
||||
'metadata_json': json.dumps(metadata),
|
||||
'metadata_json': JSONCodec.dumps(metadata),
|
||||
# `or ''` prevents indexing literal 'None' as a TAG value, which would
|
||||
# poison $ne / equality queries.
|
||||
'hash': str(metadata.get('hash') or ''),
|
||||
@@ -588,8 +588,8 @@ class ValkeyClient(VectorDBBase):
|
||||
ids.append(_decode(fields.get(b'id', b'')))
|
||||
documents.append(_decode(fields.get(b'text', b'')))
|
||||
try:
|
||||
metadatas.append(json.loads(_decode(fields.get(b'metadata_json', b'{}'))))
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
metadatas.append(JSONCodec.loads(_decode(fields.get(b'metadata_json', b'{}'))))
|
||||
except (ValueError, TypeError):
|
||||
metadatas.append({})
|
||||
if limit is not None and limit > 0 and len(ids) >= limit:
|
||||
return GetResult(ids=[ids], documents=[documents], metadatas=[metadatas])
|
||||
@@ -734,8 +734,8 @@ class ValkeyClient(VectorDBBase):
|
||||
ids.append(_decode(fields.get(b'id', b'')))
|
||||
documents.append(_decode(fields.get(b'text', b'')))
|
||||
try:
|
||||
metadatas.append(json.loads(_decode(fields.get(b'metadata_json', b'{}'))))
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
metadatas.append(JSONCodec.loads(_decode(fields.get(b'metadata_json', b'{}'))))
|
||||
except (ValueError, TypeError):
|
||||
metadatas.append({})
|
||||
|
||||
if include_score:
|
||||
|
||||
Reference in New Issue
Block a user