feat(control-center): P2 document vector store — ingestion, chunking, embedding, hybrid search

- internal/controlcenter/embed: OpenAI-compatible /embeddings client
  (batch<=16, 30s timeout, 5xx/network retried once, 4xx not), Embedder
  interface, deterministic offline Stub, pgvector literal formatting
- internal/controlcenter/docs: .doc/**/*.md sync into cc_documents with
  sha256 diffing and vanished-file removal; YAML front matter -> jsonb,
  title fallback, taxonomy from first .doc dir; heading-based chunking
  with paragraph bisection over ~1800 est. tokens; chunk_hash idempotent
  inserts (unchanged chunks keep embeddings); embedding failures degrade
  to NULL vectors without failing the sync; IndexArtifact for
  cc_stage_artifacts; StartPeriodic ticker (CC_DOC_SYNC_SECONDS)
- server: GET /api/v1/docs (taxonomy/q filters), /api/v1/docs/{id},
  /api/v1/docs/search (semantic|keyword|hybrid, ts_rank+ILIKE keyword
  fallback, 0.6/0.4 max-normalized fusion, degraded flag when embedding
  unavailable); async artifact indexing hooked into agent artifacts and
  external-submit handlers
- archtest module-boundaries allowlist updated for new packages
This commit is contained in:
weichen
2026-09-17 09:47:29 +00:00
parent b11cc64845
commit 63f1fa5f87
13 changed files with 1590 additions and 6 deletions
+20
View File
@@ -16,6 +16,8 @@ import (
"time"
"chat-agent-system/internal/controlcenter/config"
"chat-agent-system/internal/controlcenter/docs"
"chat-agent-system/internal/controlcenter/embed"
"chat-agent-system/internal/controlcenter/server"
"chat-agent-system/internal/controlcenter/store"
)
@@ -77,6 +79,23 @@ func cmdServe(args []string) error {
}
srv := server.New(cfg, st)
var emb *embed.HTTPClient
if cfg.EmbeddingEndpoint != "" {
emb = embed.NewHTTP(cfg.EmbeddingEndpoint, cfg.EmbeddingAPIKey, cfg.EmbeddingModel, embed.Dim)
} else {
slog.Warn("embedding endpoint not configured; search runs keyword-only")
}
syncer := docs.New(st, cfg.RepoRoot, emb)
var embIface embed.Embedder
if emb != nil {
embIface = emb
}
srv.SetDocs(embIface, syncer)
syncCtx, stopSync := context.WithCancel(ctx)
defer stopSync()
syncer.StartPeriodic(syncCtx, time.Duration(cfg.DocSyncSecond)*time.Second)
reapCtx, stopReaper := context.WithCancel(ctx)
defer stopReaper()
srv.StartLeaseReaper(reapCtx)
@@ -86,6 +105,7 @@ func cmdServe(args []string) error {
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
<-sig
stopSync()
stopReaper()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()