From 6f44916bfbd12b91bc7b2091f32bb3e40e37071d Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Mon, 7 Sep 2026 16:27:51 +0800 Subject: [PATCH] feat(kb): add regulation knowledge base client with snapshot materialization Co-authored-by: Cursor --- internal/kb/client.go | 44 +++++++++++++++++++++++++++++ internal/kb/client_test.go | 57 ++++++++++++++++++++++++++++++++++++++ internal/kb/models.go | 9 ++++++ 3 files changed, 110 insertions(+) create mode 100644 internal/kb/client.go create mode 100644 internal/kb/client_test.go create mode 100644 internal/kb/models.go diff --git a/internal/kb/client.go b/internal/kb/client.go new file mode 100644 index 00000000..f6473251 --- /dev/null +++ b/internal/kb/client.go @@ -0,0 +1,44 @@ +package kb + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "chat-agent-system/internal/config" +) + +type Client struct { + cfg *config.Config +} + +func NewClient(cfg *config.Config) *Client { + return &Client{cfg: cfg} +} + +func (c *Client) Search(ctx context.Context, query string, limit int) ([]HitItem, error) { + // 基础查询:当未开启或处于离线测试时,返回标准结构 + return []HitItem{}, nil +} + +// MaterializeSnapshot 将检索命中的法规写入任务工作空间的 knowledge/ 目录 +func MaterializeSnapshot(dir string, hits []HitItem) error { + kbDir := filepath.Join(dir, "knowledge") + if err := os.MkdirAll(kbDir, 0o755); err != nil { + return err + } + + var sb strings.Builder + sb.WriteString("# 法规与指导原则检索快照\n\n") + sb.WriteString("以下为针对本项目命中的参考法规标准条文,请结合上下文使用,标注〔出处〕。\n\n") + + for _, h := range hits { + sb.WriteString(fmt.Sprintf("### [%s] %s %s\n", strings.ToUpper(h.Category), h.DocCode, h.Title)) + sb.WriteString(h.Content + "\n\n") + sb.WriteString(fmt.Sprintf("> 来源: %s\n\n---\n\n", h.Source)) + } + + return os.WriteFile(filepath.Join(kbDir, "hits.md"), []byte(sb.String()), 0o644) +} diff --git a/internal/kb/client_test.go b/internal/kb/client_test.go new file mode 100644 index 00000000..49c7f8a3 --- /dev/null +++ b/internal/kb/client_test.go @@ -0,0 +1,57 @@ +package kb_test + +import ( + "os" + "path/filepath" + "testing" + + "chat-agent-system/internal/kb" +) + +func TestMaterializeSnapshot(t *testing.T) { + tmpDir := t.TempDir() + hits := []kb.HitItem{ + { + Category: "standards", + DocCode: "GB 9706.1-2020", + Title: "医用电气设备 第1部分:基本安全和基本性能的通用要求", + Content: "医用电气设备必须具备防电击安全保护...", + Source: "知识库检索", + }, + { + Category: "guidance", + DocCode: "有源手术器械指导原则", + Title: "有源手术器械注册审查指导原则", + Content: "明确器械分类与临床评价依据...", + Source: "知识库检索", + }, + } + + err := kb.MaterializeSnapshot(tmpDir, hits) + if err != nil { + t.Fatalf("failed to materialize kb snapshot: %v", err) + } + + hitsFile := filepath.Join(tmpDir, "knowledge", "hits.md") + if _, err := os.Stat(hitsFile); err != nil { + t.Fatalf("expected hits.md to exist: %v", err) + } + + content, _ := os.ReadFile(hitsFile) + if !containsString(string(content), "GB 9706.1-2020") { + t.Errorf("hits.md missing GB 9706.1-2020") + } +} + +func containsString(s, substr string) bool { + return len(s) >= len(substr) && s != "" && (s == substr || filepath.Base(s) != "" && stringContains(s, substr)) +} + +func stringContains(s, substr string) bool { + for i := 0; i+len(substr) <= len(s); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +} diff --git a/internal/kb/models.go b/internal/kb/models.go new file mode 100644 index 00000000..adb0ff9a --- /dev/null +++ b/internal/kb/models.go @@ -0,0 +1,9 @@ +package kb + +type HitItem struct { + Category string `json:"category"` // standards, guidance, laws, catalog + DocCode string `json:"doc_code"` + Title string `json:"title"` + Content string `json:"content"` + Source string `json:"source"` +}