You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(wazuh_decoder_rule_tool): ground the RAG store in verified log samples
The RAG store indexed decoder metadata only — `decoder:… regex:… order:…` —
while retrieval queries with a raw log line. That compared a log against
OS_Regex syntax, so official decoders scored barely above unrelated feedback
rows, and every official doc carried an empty log_example. Without a sample,
a retrieved decoder can teach the model XML *style* but not the log -> regex
mapping, which is the part that matters.
ruleset/testing/tests/*.ini in the Wazuh repo already holds the samples the
project itself uses as ground truth (1986 of them, with expected decoder and
rule). They were never present locally because the repo cache pins its sparse
checkout to ruleset/decoders.
scripts/harvest_log_samples.py fetches that path, pushes every sample through
wazuh-logtest in batches, and keeps only pairs logtest confirms — recording the
decoder that actually fired and the fields it actually extracted. 1616 of 1986
verify. Two traps worth naming: JSON logs emit no `full event:` line at all, so
results are aligned positionally rather than keyed on the echoed event (keying
lost 504 samples), and a repeated `log 1 pass` key collapses into one
newline-joined value, so each line is split back out (another ~300).
The sample now leads each document's embedding text and populates log_example:
1089/1330 official docs (81%) carry a verified sample, 1472/1713 overall.
Measured with scripts/eval_rag_retrieval.py on held-out samples — any sample
indexed as a doc's log_example is excluded, since querying with a string that
is verbatim in the store measures memorisation, not retrieval. On logs that
need a real text decoder (n=578; builtin-json logs are excluded because no XML
decoder is the right answer for them):
metadata only (before) p@1 48.8% recall@3 55.2%
+ verified log example p@1 58.7% recall@3 60.0%
+ dedup (production path) p@1 66.6% recall@3 69.6%
Also fixed two latent bugs this surfaced:
* retrieve() could take down a request. `fields` metadata was truncated with
json.dumps(...)[:500], which cuts mid-element and leaves `["a", "bc` for
json.loads to raise on. Now truncated by dropping whole elements, with a
defensive parse for stores already written that way.
* /api/rag/status reported ready=false/count=0 in any worker that had not yet
served a retrieval, and kept reporting it after an out-of-process rebuild
invalidated the cached handle. It now lazy-attaches like retrieve() does.
Sibling decoders share a log sample, so top_k=3 returned the same log three
times — paying for three examples and teaching one. retrieve() over-fetches and
keeps the best-scoring doc per distinct sample.
0 commit comments