Files
geo/findings.rag-numeric-fidelity.md
T

64 lines
7.1 KiB
Markdown
Raw Normal View History

# Findings: RAG Numeric Fact Fidelity
## Reported Symptom
- Generated article says Liaoning Muge Creative was founded in 2014 and has 12 years of local service.
- The user reports the knowledge base contains a different founding time.
- Required invariant: when selected knowledge contains a basic numeric fact, generated copy must not contradict it.
## Local Trace
- Generation code is under `server/internal/tenant/app`; retrieval is under `server/internal/shared/retrieval`; dispatch is under `server/internal/worker/generate`.
- Prompt config describes knowledge as optional reference material and constrains dates through natural-language instructions only.
- `KnowledgeContext.PreciseFacts` exists and is rendered into a high-precision prompt section.
- `ResolveContext` loads precise facts from up to 24 completed items in selected groups, prioritizing retrieved items.
- Founding/establishment/registration labels and phrases are absent from the precise-fact extractor, so `company founded in 20xx` is not elevated.
- Custom tasks snapshot only the rendered knowledge prompt string, not typed fact records.
- Template, custom, and imitation paths all accept model output through `sanitizeGeneratedArticleMarkdown` and then persist it without factual validation.
## Production Trace
- Three worker-generate pods and all core dependencies are healthy in namespace `geo-rankly`.
- Worker selector is `app.kubernetes.io/name=worker-generate`; deployed image is `geo-rankly/worker-generate:uncommitted-server-0acd3791-20260709232655`.
- A production imitation task logged `candidate_count=0`, `selected_count=0`, and `precise_fact_count=22`, confirming group-wide precise facts are actively rendered even when vector retrieval returns no search points.
- The reported Huludao article was not identifiable in the last 24 hours of filtered worker logs; locate it through PostgreSQL next.
- Read-only `psql` access to the production `geo` database is available through the PostgreSQL pod; queries will minimize returned article/knowledge content.
- Exact reproduction record: article `554`, current version `742`, tenant `2`, brand `4`, title `2026葫芦岛全屋定制本地5家实力公司汇总,口碑+落地+性价比深度评测`, completed at `2026-07-10 09:40:22 UTC`.
- The target article source/task type is `kol`, corresponding to worker log task `528`; the fix must cover the KOL generation path in addition to template/custom/imitation paths.
- Task `528` selected knowledge groups `[17,27,28,30,32,47]` and subscription prompt `1140`; its payload contains rendered prompt/schema/card snapshots but no typed knowledge facts.
- Version `742` contains the reported claim (percent-encoded in stored markdown): `品牌成立于2014年,拥有12年本地服务经验`.
- Production knowledge is internally inconsistent: newest completed item `31` says `成立于2020年1月`, while older completed items `13` and `14` say `成立于2019年`. Both are in the selected recursive group scope.
- KOL worker resolves knowledge with `brand_id` read from task input. Task `528` has no `brand_id` key even though article `554` belongs to brand `4`, so it resolves with brand `0`; this likely excluded the entire knowledge scope for the reported generation.
- KOL output follows the same unvalidated pattern as other paths: model result is trimmed, titled, and persisted directly.
## Ranked Hypotheses
1. **Missing KOL brand scope:** if this is the primary cause, a task payload without `brand_id` will resolve no brand-4 knowledge, and task `528` will have no corresponding knowledge-resolution log.
2. **Founding facts are not extracted:** if true, `extractKnowledgePreciseFacts("公司成立于2020年1月")` will omit the line.
3. **No deterministic output gate:** if true, a body contradicting a supplied founding fact will pass every current post-generation function and be persisted.
4. **Conflicting source values are not canonicalized:** if true, once founding extraction is enabled both 2020 and 2019 enter prompt context without a unique authoritative value.
## Hypothesis Results
- Hypothesis 1 confirmed at the creator: normal KOL submission builds `taskInput` with `knowledge_group_ids` but omits `brand_id`; only scheduled KOL input conditionally attaches it. The worker always reads `brand_id` from the payload.
- Existing tests cover precise prompt extraction/rendering but do not cover KOL task scope propagation or output fact validation.
- Normal KOL submission already resolves the current brand and passes it into `enqueue`; the bug is localized to serializing the task snapshot, so no API contract change is needed.
- The shared `llm.Client.Generate` signature is compatible with a reusable generate/validate/repair helper used by tenant services and the KOL worker.
- Template, imitation, and KOL paths retain the resolved `KnowledgeContext` in the same execution frame and can pass its constraints directly to the guard.
- Custom prompt-rule generation snapshots the rendered knowledge prompt before queueing and reconstructs only a prompt string in the worker, so it also needs a JSON-safe constraint snapshot for deterministic validation.
## Proposed Conflict Policy
- Prefer the newest completed active knowledge item for the same typed fact and subject as the canonical value.
- Preserve source metadata and emit an observable conflict when older active items disagree.
- Validate generated output against the canonical value before persistence; retry once with correction instructions, then fail rather than publish a contradiction.
- Review note: the current group-item SQL prioritizes retrieved item IDs before `updated_at`. Canonical fact selection must not accidentally treat an older retrieved item as newer; source timestamp/order semantics need explicit coverage.
## Design Constraint
- A global numeric-token whitelist is unsafe: article years, rankings, section numbers, derived values, and user-provided facts are legitimate.
- Enforcement should use typed fact identity, normalized values, subject/field-aware contradiction detection, and an explicit retry/fail policy.
- Initial protected types will cover company founding date and explicitly sourced experience/service duration; the structure must be extensible to other business numeric types.
- With active fact constraints, generation should withhold draft deltas, validate the full result, repair once from canonical facts, and reject the task if the repaired result still conflicts.
## Final Outcome
- Immediate and scheduled KOL tasks now serialize the same brand/knowledge scope consumed by the worker.
- Founding dates and explicit service/experience durations become typed, subject-aware constraints.
- Source conflicts choose the newest completed item; older conflicting values are removed from the prompt and logged without exposing source content.
- Template, custom prompt-rule, imitation, and KOL generation validate before persistence, repair once, and fail instead of saving a second conflicting draft.
- Custom queued tasks snapshot constraints; pre-upgrade queued tasks resolve missing constraints at execution time.
- Production was inspected read-only. No deployment was performed from the dirty worktree.