Files
WeKnora/internal/errors/parse_error_codes.go
T
lyingbug 6afb6ca9d5 feat(knowledge): flag documents that stop making progress (#3557)
* feat(knowledge): flag documents that stop making progress

A document stuck in processing looked the same as one still working: a
spinner that polled forever, no hint of where it stopped, and, hours
later, a generic housekeeping failure.

- The batch and spans endpoints return last_activity_at for in-flight
  rows: the later of updated_at and the newest span write. updated_at
  only moves at stage transitions; span writes keep moving while a long
  stage still works.
- After 20 minutes with no progress, the list and card views show
  "may be stuck" with a tooltip. The timeline shows the stage it
  stopped at and a stop-parsing button, plus a "last progress" stat.
  Both pollers back off to 15s while everything in flight is stalled.
- When housekeeping fails a row, the error names the stalled stage and
  the time of the last progress. It closes the row's open spans with
  TASK_STALLED (the running stage fails, the rest are cancelled), so
  the timeline shows where the run stopped instead of a spinner.

* fix(knowledge): locate stalls in finalizing and tell backlog from stuck

- Housekeeping reads the stall site from the latest attempt. It takes
  the running stages, or, when none runs (post-process closes its stage
  once enrichment is fanned out), the innermost running spans, mapped
  back to their stage. Those spans fail with TASK_STALLED and are named
  in the message; every stalled stage is listed in pipeline order. The
  other open spans are cancelled. Every closed span gets a duration, and
  the message leaves out the time when the heartbeat query failed.
- The spans tree prefers a TASK_STALLED failure for last_error over a
  later subtask failure. current_stage falls back to the stage owning a
  running subspan.
- Batch and spans responses flag rows quiet past the stall hint whose
  work is still queued (waiting_in_queue), using the sweep's own probes.
  At most 20 rows are probed per request, cached for a minute. The UI
  shows these as queued, with no stop button in the banner.

* fix(knowledge): answer stall verdicts from one shared queue scan

- Probing a quiet document for a backlog rescanned the whole queue once
  per document. When the document was absent, which is exactly the
  stuck case, the scan ran to the end, and it read a Redis error as
  "nothing queued". QueuedKnowledgeIDs now collects every referenced
  knowledge ID in one pass and returns list errors. HousekeepingService
  keeps that snapshot for a minute, shared across requests and
  single-flight. A failed scan is not cached.
- With the per-request cap of 20 gone, every quiet row on a page gets a
  verdict. The response carries it as stall_state (queued or stalled),
  replacing waiting_in_queue. The UI marks a row as stuck only on a
  "stalled" verdict. A row with no verdict, because the probe failed,
  shows as ordinary processing instead of inviting a stop.
2026-09-22 21:34:08 +08:00

68 lines
3.0 KiB
Go

// Package errors: parse stage error codes.
//
// These constants are the stable wire format the frontend uses to map a
// failure to a localized "what to do about it" message. Adding a new code
// is non-breaking; renaming an existing one requires a coordinated
// frontend release because the i18n keys are looked up by code.
//
// Codes are SCREAMING_SNAKE so they survive JSON case transforms and look
// distinct from Go identifiers in logs/dashboards.
package errors
const (
// ErrCodeDocReaderTimeout — DocReader RPC exceeded
// WEKNORA_DOCREADER_CALL_TIMEOUT (default 30m). Suggest splitting
// large files or checking docreader load.
ErrCodeDocReaderTimeout = "DOCREADER_TIMEOUT"
// ErrCodeDocReaderUnavailable — no DocReader configured for the
// requested file type / engine, or the service refused connection.
ErrCodeDocReaderUnavailable = "DOCREADER_UNAVAILABLE"
// ErrCodeDocReaderParseFailed — DocReader returned an explicit parse
// error (encoding, corrupted file, OCR engine crash, ...).
ErrCodeDocReaderParseFailed = "DOCREADER_PARSE_FAILED"
// ErrCodeChunkingFailed — text chunking step itself failed (rare;
// usually only on extreme-size inputs).
ErrCodeChunkingFailed = "CHUNKING_FAILED"
// ErrCodeEmbeddingRateLimit — embedding provider returned 429 or
// equivalent. Suggest retrying later or scaling out.
ErrCodeEmbeddingRateLimit = "EMBEDDING_RATE_LIMIT"
// ErrCodeEmbeddingProviderFail — non-rate-limit embedding error
// (auth, model not found, bad input). Usually permanent without
// config changes.
ErrCodeEmbeddingProviderFail = "EMBEDDING_PROVIDER_FAIL"
// ErrCodeVectorStoreWriteFailed — chunks embedded fine but the
// vector DB write failed (quota, connectivity, schema mismatch).
ErrCodeVectorStoreWriteFailed = "VECTORSTORE_WRITE_FAILED"
// ErrCodeMultimodalVLMFailed — a single image's OCR or caption call
// failed. Note: per-image failures don't fail the whole parent;
// see image_multimodal.go finalize-on-last-attempt.
ErrCodeMultimodalVLMFailed = "MULTIMODAL_VLM_FAILED"
// ErrCodeMultimodalAllFailed — every image task hit dead-letter.
// The parent still completes (caption / OCR is optional content),
// but stage status is marked failed so the UI can warn.
ErrCodeMultimodalAllFailed = "MULTIMODAL_ALL_FAILED"
// ErrCodeTaskTimeout — asynq retry budget exhausted. Used by the
// dead-letter callback when promoting a task failure into a stage
// failure. Distinct from DocReaderTimeout: this is the asynq-level
// timeout (whole task), not the docreader-call-level timeout.
ErrCodeTaskTimeout = "TASK_TIMEOUT"
// ErrCodeTaskStalled — housekeeping found no progress for longer than
// the stale threshold with nothing left queued, and failed the row.
ErrCodeTaskStalled = "TASK_STALLED"
// ErrCodeUnknown — fallback when a wrapped error doesn't classify.
// The full message is still recorded in error_detail so operators
// can debug; the UI shows a generic "see admin" hint.
ErrCodeUnknown = "UNKNOWN"
)