v0.12.0
Compatibility pass for mxchat-basic 3.2.8, which makes the Custom Provider
/ Azure OpenAI embedding path work end-to-end (both knowledge-base indexing
and live queries route through the configured endpoint). That activates a path
our dimension auto-detection didn't cover: mxchat stamps the active model as
custom:<id>, a value absent from every embedding registry, so vectors from a
non-1536-dim local model (nomic-embed-text 768, bge-large 1024, Azure
text-embedding-3-large 3072, …) were sized against a silent 1536 default.
No schema migration. Built-in models are unaffected. Safe drop-in from 0.11.1.
Added
- Empirical dimension probe for Custom / Azure OpenAI providers. When
MxChat is embedding through a custom endpoint, the Vector schema admin
section now shows a Detect dimension button. It embeds a probe string via
mxchat-basic's ownMxChat_Utils::generate_embedding_custom()(honouring the
exact Base URL / auth scheme / model the operator configured), measures the
returned vector, and caches the result keyed to the active model in
mxchat_duckdb_custom_embedding_dim. New AJAX handler
wp_ajax_mxchat_duckdb_detect_dimension(nonce +manage_optionsgated). MxChat_DuckDB_Options::is_custom_embedding_model(),
active_embedding_model(),store_probed_custom_dim(), and
probed_custom_dim()helpers.
Changed
detect_embedding_dim()recognisescustom:<id>models. For custom
providers it returns the probed dimension when one was measured against the
current model, falling back to a 1536 placeholder (clearly labelled in the
UI as such) otherwise — instead of silently treating the custom model as a
1536-dim built-in. A stale probe from a previously-configured custom model is
ignored once the model id changes. Built-in models still resolve through
mxchat-basic's registry exactly as before.
Notes
- The insert-time dimension guard in
Vector_Storealready rejected mismatched
vectors loudly, so this was never silent corruption — but operators on local
embedding stacks (the plugin's core audience) no longer have to hand-compute
the dimension.
Performance & correctness
- Metrics no longer write
wp_optionson every query.observe_latency()/
record()are on the hottest path (a cache hit still recorded a counter),
and each call did a read-modify-write of the metrics option plus an O(n)
window trim. They now append to a request-scoped buffer that is merged into
the option in a single write onshutdown. This drops metrics writes from
N-per-request to 1-per-request, removes a per-querywp_optionswrite from
the cache-hit path, and moves the window trim off the hot path. It also
shrinks the lost-update race (concurrent read-modify-writes dropping samples)
from per-query to per-request.snapshot()merges the pending buffer so
within-request reads stay consistent. - Compactor sweeps stale-generation query-cache transients. The O(1)
cache-generation bump makes superseded_transient_mxd_q_*rows unreachable
but never deletes them; on write-heavy installs they could accumulate in
wp_optionsfaster than WordPress' expired-transient GC reclaims them. The
daily compactor now deletes every query-cache transient that isn't the
current generation. It runs before the sync-freshness guard, so busy
installs (which early-return on "last sync too recent") still get the sweep.
No-op under an external object cache. - Hardened DuckDB CLI JSON parsing.
execute_cli()previously did a single
json_decodeand silently returned[](no rows, no error) on anything but
one clean JSON value — so concatenated result sets (init-SQL output + the
real query) or NDJSON could swallow real results. Parsing now falls back to
the last top-level array, then to line-by-line NDJSON, before giving up. - Memoised CLI-binary autodetection for the request (a mirrored install
builds two backends, each of which would otherwise re-stat the candidate
paths and re-spawncommand -v), and hoisted the transient-error class/anchor
lists to constants.
Added (extensibility)
mxchat_duckdb_filter_fieldsfilter — map extra Pinecone-style filter
field names to physical columns (for add-ons storing custom per-vector
metadata). Column names are validated against/^[a-zA-Z0-9_]+$/before they
touch SQL, so a bad entry is dropped rather than becoming an injection vector.
See docs/HOOKS.md.
CI
- Fixed the
composer auditjob. It rancomposer install --no-devthen a
plaincomposer audit, which errored with "No installed packages found"
because the plugin has zero runtime dependencies. It now audits the locked
production set directly (composer audit --locked --no-dev --abandoned=report):
a no-op that stays green today and trips the instant a vulnerable production
dependency is introduced.