Files
Agent-System-Backend/internal/prompts/parse.go
T
weichen f1302ed5b3 Enable canonical versioned Go module consumption
Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements.

Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate.

Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable.

Refs: Versioned dependencies U1/U3; feature/dependency-workspace
2026-10-13 15:47:16 +00:00

190 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}