mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-05 15:20:30 +08:00
## Summary
This PR refactors the Go knowledge-compilation ingestion pipeline
(`internal/ingestion/knowledge_compile` +
`internal/ingestion/component/knowledge_compiler`) with three related
changes:
- **Token-budget batching for LLM merge decisions.**
`LLMMergeDecider.DecideBatch` previously stuffed every `(existing,
candidate)` pair into a single LLM call, risking `max_token` overflow.
It now splits pairs into token-bounded sub-batches (budget =
`llmMaxTokens * 0.85`) via `tokenizer.NumTokensFromString`, runs them
concurrently while preserving the global pair index, and never
reindexes.
- **Process-level global compile pool.** Introduces a single vCPU-sized
goroutine pool (`pool.go`, env `KC_COMPILE_CONCURRENCY`) dedicated to
*all* knowledge-compilation stages. KNN search loop, `DecideBatch`
sub-batches, `WriteMerged`/`DeleteMerged` internals, and the
component-level (structure/mindmap) per-call pools are all unified into
it via an injected submitter. No more per-job short-lived goroutines in
`runCompilerJobs` (futures are collected then awaited on the caller).
Fan-out stays bounded by the pool worker count; these stages are
docengine-bounded / LLM-bounded, not CPU-bounded.
- **DocEngine-only deletion.** `Consumer.processBatch` deletion no
longer loads the deleted docs' products into memory. Two sequential
DocEngine calls replace the old in-memory surgery:
- `DeleteDocLevelForDocs` — one `DeleteChunks` over `doc_id IN
deletedDocIDs` (merged rows carry `doc_id == kb`, so only per-doc
products match).
- `StripMergedSources` — one `Search` of `kc_merged=1` rows filtered by
`source_doc_ids IN deletedDocIDs` (intersection pushed down to the
engine), `UpdateChunks` the source array of survivors, and
`DeleteChunks` the rows whose array became empty.
## Changes
- `internal/ingestion/knowledge_compile/pool.go` (new): global
`compilerPool` +
`runCompilerJobs`/`SubmitCompilerJob`/`SubmitCompilerJobs`.
- `internal/ingestion/knowledge_compile/consumer.go`: deletion rewritten
to the two DocEngine calls;
`mergedBase`/`toDelete`/`stripDeletedSources` removed.
- `internal/ingestion/knowledge_compile/writer.go`:
`DeleteDocLevelForDocs` + `StripMergedSources` replace
`DeleteMergedForDoc`/`DeleteMerged`.
- `internal/ingestion/knowledge_compile/reader.go`: drop
`LoadMergedBySourceDoc` + `containsString` (keep `LoadDocProducts` for
the completion branch).
- `internal/ingestion/knowledge_compile/dedup.go`: `NewLLMDeduper` takes
`llmMaxTokens`; wires `SetMaxBatchTokens`/`SetSubmitter`.
- `internal/ingestion/knowledge_compiler/{structure,merge}.go`,
`mindmap/mindmap.go`, `pool_wiring.go`: token-budget split + submitter
injection.
- Tests: `structure_test.go` (token-budget split), `dedup_test.go`,
`consumer_test.go` (tombstone + DocEngine deletion assertions) updated.
## Validation
`bash build.sh --test -race ./internal/ingestion/knowledge_compile/...
./internal/ingestion/component/knowledge_compiler/...` passes (unit
tier, no external services).
🤖 Generated with [CodeBuddy](https://www.codebuddy.ai)
---------
Co-authored-by: yuzhichang <yuzhichang@infiniflow.ai>
125 lines
4.9 KiB
Go
125 lines
4.9 KiB
Go
//
|
|
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package knowledge_compile implements the dataset-level post-processing consumer described in
|
|
// docs/develop/knowledge_compile_design.md §11 (Option E).
|
|
//
|
|
// Pipeline (KnowledgeCompiler, per document) writes compiled chunks with
|
|
// available_int=0. Its completion/deletion is recorded by appending a
|
|
// BacklogEntry to the KB's durable MySQL scheduling row
|
|
// (knowledge_compile_docs), then waking idle workers over NATS. A cluster of
|
|
// competing workers claims a closed batch (backlog -> inflight) per KB, runs
|
|
// dataset-level dedup on that batch, and writes the merged dataset-level
|
|
// products with available_int=1. The MySQL row — not the broker — is the
|
|
// scheduling system of record and the source of same-KB serialization.
|
|
package knowledge_compile
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
)
|
|
|
|
// Subjects and event types for the knowledge-compile stream. Both sit under the
|
|
// knowledge.compile.events.> prefix declared on the NATS stream/consumer.
|
|
const (
|
|
SubjectCompleted = "knowledge.compile.events.completed"
|
|
SubjectDeleted = "knowledge.compile.events.deleted"
|
|
)
|
|
|
|
// EventType enumerates the KC event kinds.
|
|
type EventType string
|
|
|
|
const (
|
|
EventTypeCompleted EventType = "doc_completed"
|
|
EventTypeDeleted EventType = "doc_deleted"
|
|
)
|
|
|
|
// KCCompileEvent is the payload published when a document's pipeline finishes
|
|
// (doc_completed) or is deleted (doc_deleted). It is intentionally decoupled
|
|
// from common.TaskMessage because the consumer reads the raw JSON body.
|
|
type KCCompileEvent struct {
|
|
TenantID string `json:"tenant_id"`
|
|
DatasetID string `json:"dataset_id"` // the KB scope
|
|
DocID string `json:"doc_id"` // the contributing document
|
|
EventType string `json:"event_type"` // EventType value
|
|
Seq uint64 `json:"seq"` // per-doc monotonic sequence, for out-of-order correction
|
|
Timestamp int64 `json:"ts"`
|
|
}
|
|
|
|
// Subject returns the NATS subject for this event.
|
|
func (e KCCompileEvent) Subject() string {
|
|
if EventType(e.EventType) == EventTypeDeleted {
|
|
return SubjectDeleted
|
|
}
|
|
return SubjectCompleted
|
|
}
|
|
|
|
// Marshal serializes the event to JSON.
|
|
func (e KCCompileEvent) Marshal() ([]byte, error) { return json.Marshal(e) }
|
|
|
|
// ParseEvent deserializes a KCCompileEvent from raw message bytes.
|
|
func ParseEvent(data []byte) (KCCompileEvent, error) {
|
|
var e KCCompileEvent
|
|
if err := json.Unmarshal(data, &e); err != nil {
|
|
return KCCompileEvent{}, err
|
|
}
|
|
return e, nil
|
|
}
|
|
|
|
// defaultPublisher is the package-level Publisher used by the publishing path
|
|
// (PublishCompleted / PublishDeleted). It is installed by Provision (called
|
|
// once by the owning Ingestor at startup). Until then, publishing is a no-op.
|
|
var defaultPublisher Publisher
|
|
|
|
// defaultClaimer is the package-level Claimer handed to the consumer workers.
|
|
// It is the same underlying instance as defaultPublisher (a *mysqlScheduler
|
|
// satisfies both roles), set together by SetScheduler.
|
|
var defaultClaimer Claimer
|
|
|
|
// SetScheduler installs the package-level Publisher and Claimer used for
|
|
// publishing and consuming. The same *mysqlScheduler satisfies both interfaces.
|
|
func SetScheduler(s Publisher) {
|
|
defaultPublisher = s
|
|
if c, ok := s.(Claimer); ok {
|
|
defaultClaimer = c
|
|
}
|
|
}
|
|
|
|
// DefaultPublisher returns the package-level Publisher used by the producer path.
|
|
func DefaultPublisher() Publisher { return defaultPublisher }
|
|
|
|
// DefaultClaimer returns the package-level Claimer used by consumer workers.
|
|
func DefaultClaimer() Claimer { return defaultClaimer }
|
|
|
|
// PublishCompleted records a doc_completed event: it appends the doc to the
|
|
// KB's durable MySQL backlog and wakes idle workers over NATS. It is a no-op
|
|
// when no Publisher has been installed (e.g. DB unavailable). A failure is
|
|
// returned so callers can log but never fail the pipeline on it.
|
|
func PublishCompleted(ctx context.Context, tenantID, datasetID, docID string, seq uint64) error {
|
|
if defaultPublisher == nil {
|
|
return nil
|
|
}
|
|
return defaultPublisher.Publish(ctx, tenantID, datasetID, docID, string(EventTypeCompleted), seq)
|
|
}
|
|
|
|
// PublishDeleted records a doc_deleted event the same way (Publish handles the
|
|
// append + notify pairing).
|
|
func PublishDeleted(ctx context.Context, tenantID, datasetID, docID string, seq uint64) error {
|
|
if defaultPublisher == nil {
|
|
return nil
|
|
}
|
|
return defaultPublisher.Publish(ctx, tenantID, datasetID, docID, string(EventTypeDeleted), seq)
|
|
}
|