feat: add workspace-isolated toolhost runtime and capability-gap skill loop
This commit is contained in:
141
internal/knowledge/drafts.go
Normal file
141
internal/knowledge/drafts.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package knowledge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"laodingbot/internal/memory"
|
||||
)
|
||||
|
||||
func GenerateSkillDraft(cluster memory.CapabilityGapCluster, draftRoot string) (string, bool, error) {
|
||||
draftRoot = strings.TrimSpace(draftRoot)
|
||||
if draftRoot == "" {
|
||||
draftRoot = "./skills"
|
||||
}
|
||||
if err := os.MkdirAll(draftRoot, 0o755); err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
skillDirName := "auto_" + slugFromIntent(cluster.IntentKey)
|
||||
if skillDirName == "" {
|
||||
skillDirName = "auto_gap_skill"
|
||||
}
|
||||
dir := filepath.Join(draftRoot, skillDirName)
|
||||
file := filepath.Join(dir, "skill.md")
|
||||
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
return file, false, nil
|
||||
}
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
skillTitle := titleFromIntent(cluster.SampleIntent)
|
||||
if skillTitle == "" {
|
||||
skillTitle = "能力缺口补全技能"
|
||||
}
|
||||
content := buildDraftMarkdown(skillTitle, cluster)
|
||||
if err := os.WriteFile(file, []byte(content), 0o644); err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
return file, true, nil
|
||||
}
|
||||
|
||||
func buildDraftMarkdown(skillTitle string, cluster memory.CapabilityGapCluster) string {
|
||||
createdAt := time.Now().Format(time.RFC3339)
|
||||
return strings.TrimSpace(fmt.Sprintf(`---
|
||||
name: %s
|
||||
description: 由 capability_gap 自动生成并用于自动补全缺失能力。
|
||||
source: capability_gap
|
||||
generated_at: %s
|
||||
cluster_intent_key: %s
|
||||
cluster_reason: %s
|
||||
cluster_count: %d
|
||||
---
|
||||
|
||||
# Skill: %s
|
||||
|
||||
## 背景
|
||||
- 该技能由系统根据高频能力缺口自动生成并已纳入技能目录。
|
||||
- 最近高频缺口聚类:`+"`%s`"+`
|
||||
- 缺口原因:`+"`%s`"+`
|
||||
- 出现次数:`+"`%d`"+`
|
||||
|
||||
## 目标能力
|
||||
- 明确该类问题应如何判断是否需要调用工具。
|
||||
- 约束输入输出,避免泛化过度。
|
||||
- 在失败时提供可操作回退路径。
|
||||
|
||||
## 建议触发信号
|
||||
- 用户提问与下述意图高度相关:`+"`%s`"+`
|
||||
- 现有技能未命中,或命中后无法完成。
|
||||
|
||||
## 建议工具
|
||||
- 优先使用现有工具:`+"`shell`"+`、`+"`file`"+`。
|
||||
- 若能力不足,需要创建新工具时:
|
||||
1. 在 `+"`internal/tools/<tool_name>/`"+` 下生成 Go 代码;
|
||||
2. 在 `+"`cmd/bot/main.go`"+` 或 toolhost 注册逻辑中完成注册;
|
||||
3. 生成/补充 `+"`*_test.go`"+`;
|
||||
4. 调用 `+"`go test ./...`"+` 验证。
|
||||
|
||||
## ReAct 指南
|
||||
1. 先确认用户目标和输入约束。
|
||||
2. 判断是否可直接回答;若不行,再选择工具。
|
||||
3. 工具调用前先最小化探测范围。
|
||||
4. 工具失败时输出原因与下一步建议。
|
||||
5. 若缺少 skill:使用 `+"`file`"+` 与 `+"`shell`"+` 创建新的 `+"`skills/<skill_name>/skill.md`"+`。
|
||||
6. 若缺少 tool:生成工具代码与测试后执行 `+"`go test ./...`"+`。
|
||||
|
||||
## 输出规范
|
||||
- 结论:一句话给出当前阶段结论。
|
||||
- 依据:列出关键观察与证据。
|
||||
- 限制:说明当前不确定性。
|
||||
- 下一步:给用户可执行动作。
|
||||
`, skillTitle, createdAt, cluster.IntentKey, cluster.Reason, cluster.Count, skillTitle, cluster.IntentKey, cluster.Reason, cluster.Count, cluster.SampleIntent))
|
||||
}
|
||||
|
||||
func slugFromIntent(intent string) string {
|
||||
intent = strings.TrimSpace(strings.ToLower(intent))
|
||||
if intent == "" {
|
||||
return ""
|
||||
}
|
||||
b := strings.Builder{}
|
||||
lastDash := false
|
||||
for _, r := range intent {
|
||||
isAlphaNum := (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9')
|
||||
isCJK := r >= 0x4e00 && r <= 0x9fff
|
||||
if isAlphaNum || isCJK {
|
||||
b.WriteRune(r)
|
||||
lastDash = false
|
||||
continue
|
||||
}
|
||||
if !lastDash {
|
||||
b.WriteRune('-')
|
||||
lastDash = true
|
||||
}
|
||||
}
|
||||
out := strings.Trim(b.String(), "-")
|
||||
if out == "" {
|
||||
return ""
|
||||
}
|
||||
runes := []rune(out)
|
||||
if len(runes) > 48 {
|
||||
out = string(runes[:48])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func titleFromIntent(intent string) string {
|
||||
intent = strings.TrimSpace(intent)
|
||||
if intent == "" {
|
||||
return ""
|
||||
}
|
||||
runes := []rune(intent)
|
||||
if len(runes) > 32 {
|
||||
intent = string(runes[:32])
|
||||
}
|
||||
return intent
|
||||
}
|
||||
Reference in New Issue
Block a user