// Package prompts 实现提示词库(prompt-library):内置库 embed 加载、用户覆盖链、 // 触发匹配与 system 组装。提示词是数据不是代码——文案变更走库文件 diff。 package prompts import ( "fmt" "strconv" "strings" core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git" ) // ParseModuleFile 解析一个库文件(frontmatter + 正文)。 // frontmatter 语法(刻意最小,不引 YAML 依赖): // // id: task.doc-minutes // layer: task // version: 1 // title: 会议纪要 // description: 依据上传文档生成完整会议纪要 // priority: 60 // requiresAttachments: true // outputContract: | // 多行契约文本(缩进块,支持) // triggers: // - kind: intent // keywords: 纪要, 会议记录 // - kind: command // command: /minutes func ParseModuleFile(name, content string) (core.PromptModule, error) { m := core.PromptModule{Source: string(core.SourceBuiltin)} if !strings.HasPrefix(content, "---\n") { return m, fmt.Errorf("%s: missing frontmatter", name) } end := strings.Index(content[4:], "\n---\n") if end < 0 { return m, fmt.Errorf("%s: unterminated frontmatter", name) } fm := content[4 : 4+end] m.Body = strings.TrimLeft(content[4+end+5:], "\n") lines := strings.Split(fm, "\n") var trigger *core.PromptTrigger blockKey := "" // 当前块标量(outputContract: |)归属的键 for i := 0; i < len(lines); i++ { line := lines[i] if strings.TrimSpace(line) == "" { continue } if blockKey != "" { // 块标量体:任何非空且带缩进的行并入;遇到顶格键结束块。 if line[0] == ' ' || line[0] == '\t' { appendBlock(&m, blockKey, strings.TrimSpace(line)) continue } blockKey = "" } indented := line[0] == ' ' || line[0] == '\t' trimmed := strings.TrimSpace(line) if strings.HasPrefix(trimmed, "- ") { if indented { return m, fmt.Errorf("%s: nested list unsupported: %q", name, trimmed) } k, v := splitKV(strings.TrimPrefix(trimmed, "- ")) if k != "kind" { return m, fmt.Errorf("%s: list item outside triggers: %q", name, trimmed) } m.Triggers = append(m.Triggers, core.PromptTrigger{Kind: v}) trigger = &m.Triggers[len(m.Triggers)-1] continue } k, v := splitKV(trimmed) if indented && trigger != nil { switch k { case "keywords": trigger.Keywords = splitList(v) case "command": trigger.Command = v default: return m, fmt.Errorf("%s: unknown trigger field %q", name, k) } continue } if indented { return m, fmt.Errorf("%s: unexpected indented line %q", name, trimmed) } if v == "|" { blockKey = k continue } switch k { case "id": m.ID = v case "layer": m.Layer = v case "version": m.Version = atoi(v) case "title": m.Title = v case "description": m.Description = v case "priority": m.Priority = atoi(v) case "requiresAttachments": m.RequiresAttachments = v == "true" case "outputContract": m.OutputContract = v case "triggers": // 值留空;列表项由后续 "- kind:" 行提供。 case "tools": m.Tools = splitList(v) case "model": m.Model = v case "maxTurns": m.MaxTurns = atoi(v) default: return m, fmt.Errorf("%s: unknown field %q", name, k) } } if m.ID == "" || m.Layer == "" || m.Body == "" { return m, fmt.Errorf("%s: id/layer/body required", name) } if m.Version == 0 { m.Version = 1 } return m, nil } // SubagentSpec 是子代理 profile。空字段表示沿用内置。 type SubagentSpec struct { Tools []string Model string MaxTurns int } // specFromModule 由模块字段派生 profile。未写 model 时用 lightweight; // maxTurns 为 0 表示不限制轮次。 func specFromModule(m core.PromptModule) SubagentSpec { spec := SubagentSpec{Tools: m.Tools, Model: m.Model, MaxTurns: m.MaxTurns} if spec.Model == "" { spec.Model = "lightweight" } return spec } // ParseSubagentSpec 从库文件 frontmatter 取出 tools、model、maxTurns。 // 标准模块字段可以同时出现。其他未知键报错。 func ParseSubagentSpec(content string) (SubagentSpec, error) { m, err := ParseModuleFile("subagent", content) if err != nil { return SubagentSpec{}, err } return specFromModule(m), nil } func appendBlock(m *core.PromptModule, key, line string) { switch key { case "outputContract": if m.OutputContract == "" { m.OutputContract = line } else { m.OutputContract += "\n" + line } } } // splitKV 拆 "key: value"。 func splitKV(s string) (string, string) { i := strings.Index(s, ":") if i < 0 { return s, "" } return strings.TrimSpace(s[:i]), strings.TrimSpace(s[i+1:]) } func splitList(s string) []string { var out []string for _, p := range strings.Split(s, ",") { if p = strings.TrimSpace(p); p != "" { out = append(out, p) } } return out } func atoi(s string) int { n, _ := strconv.Atoi(strings.TrimSpace(s)) return n }