feat(daemon-beta t5/t6): rag_ingest_card 收卡 workflow(零LLM零credential)+direct 迴圈接萃取器——本地萃卡只上卡、原文不出機;e2e 測試驗「原文不洩上雲」+失敗重試語意
This commit is contained in:
@@ -42,10 +42,16 @@ type DirectConfig struct {
|
||||
Namespace string `json:"namespace"` // 租戶 namespace(必填),如 demo
|
||||
APIKey string `json:"api_key"` // X-Arcrun-API-Key(空=沿用 namespace,demo 慣例)
|
||||
Library string `json:"library"` // 藏書地圖歸庫鍵(空=kb)
|
||||
IngestWF string `json:"ingest_workflow"` // 直送萃取 workflow 名(空=rag_ingest_direct)
|
||||
IngestWF string `json:"ingest_workflow"` // 直送萃取 workflow 名(空=rag_ingest_direct;extractor 模式不用)
|
||||
RemovedWF string `json:"removed_workflow"` // 下架 workflow 名(空=rag_takedown_direct;吃 {page_name,path})
|
||||
PollSec int `json:"poll_interval_sec"` // 輪詢間隔秒(空/0=5)
|
||||
MaxRemoved float64 `json:"max_removed_ratio"` // 大量刪除防呆門檻(空/0=0.4)
|
||||
// —— 四步定稿(daemon-beta t3/t4/t6):本地萃卡模式 ——
|
||||
Extractor string `json:"extractor,omitempty"` // "claude"|"gemma";空=舊制(內容直送雲端萃)
|
||||
ClaudeBin string `json:"claude_bin,omitempty"` // claude 執行檔(空=PATH 找 claude)
|
||||
GeminiAPIKey string `json:"gemini_api_key,omitempty"` // gemma 路的用戶 key
|
||||
LLMModel string `json:"llm_model,omitempty"` // gemma 路模型(空=gemma-4-31b-it)
|
||||
CardIngestWF string `json:"card_ingest_workflow,omitempty"` // 收卡 workflow(空=rag_ingest_card)
|
||||
PollSec int `json:"poll_interval_sec"` // 輪詢間隔秒(空/0=5)
|
||||
MaxRemoved float64 `json:"max_removed_ratio"` // 大量刪除防呆門檻(空/0=0.4)
|
||||
}
|
||||
|
||||
// LoadDirectConfig 讀設定檔並補預設值 + 基本驗證。
|
||||
@@ -86,6 +92,15 @@ func LoadDirectConfig(path string) (*DirectConfig, error) {
|
||||
if c.RemovedWF == "" {
|
||||
c.RemovedWF = "rag_takedown_direct"
|
||||
}
|
||||
if c.CardIngestWF == "" {
|
||||
c.CardIngestWF = "rag_ingest_card"
|
||||
}
|
||||
if c.LLMModel == "" {
|
||||
c.LLMModel = defaultLLMModel
|
||||
}
|
||||
if c.Extractor != "" && c.Extractor != "claude" && c.Extractor != "gemma" {
|
||||
return nil, fmt.Errorf("extractor 只能是 claude / gemma(或留空走舊制),got %q", c.Extractor)
|
||||
}
|
||||
if c.PollSec <= 0 {
|
||||
c.PollSec = 5
|
||||
}
|
||||
@@ -253,6 +268,52 @@ func runDirectOnceRoot(cfg *DirectConfig, root string, dryRun bool) ([]DirectRes
|
||||
results = append(results, res)
|
||||
continue
|
||||
}
|
||||
if cfg.Extractor != "" {
|
||||
// 四步定稿:本地萃卡 → 每張卡 POST rag_ingest_card(原文不出機)
|
||||
var cards []string
|
||||
var xerr error
|
||||
switch cfg.Extractor {
|
||||
case "claude":
|
||||
cards, xerr = ExtractWithClaude(cfg.ClaudeBin, absRoot, ev.Path)
|
||||
case "gemma":
|
||||
cards, xerr = ExtractWithGemma(cfg.GeminiAPIKey, cfg.LLMModel, absRoot, ev.Path)
|
||||
}
|
||||
if xerr != nil {
|
||||
res.Status, res.Error = "failed", "本地萃取失敗:"+xerr.Error()
|
||||
results = append(results, res)
|
||||
exit = 1
|
||||
continue
|
||||
}
|
||||
ok := true
|
||||
for _, cardRel := range cards {
|
||||
cardData, cerr := os.ReadFile(filepath.Join(absRoot, filepath.FromSlash(cardRel)))
|
||||
if cerr != nil {
|
||||
res.Status, res.Error = "failed", "讀卡片失敗:"+cerr.Error()
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
status, _, perr := cfg.postJSON(cfg.triggerURL(cfg.CardIngestWF), map[string]any{
|
||||
"page_name": pageNameOf(cardRel),
|
||||
"path": cardRel,
|
||||
"card_content": string(cardData),
|
||||
"library": cfg.Library,
|
||||
})
|
||||
res.HTTPStatus = status
|
||||
if perr != nil {
|
||||
res.Status, res.Error = "failed", perr.Error()
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
res.Status = "ingested"
|
||||
m.MarkIngested(ev.Path, ev.SourceHash, now)
|
||||
} else {
|
||||
exit = 1
|
||||
}
|
||||
results = append(results, res)
|
||||
continue
|
||||
}
|
||||
status, _, perr := cfg.postJSON(cfg.triggerURL(cfg.IngestWF), map[string]any{
|
||||
"page_name": pageNameOf(ev.Path),
|
||||
"path": ev.Path,
|
||||
|
||||
Reference in New Issue
Block a user