feat: implement instruction assembly and project instruction providers with tests
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
runtimeinstruction "cs-agent/internal/ai/runtime/instruction"
|
||||
einoagents "cs-agent/internal/ai/runtime/internal/impl/agents"
|
||||
einocallbacks "cs-agent/internal/ai/runtime/internal/impl/callbacks"
|
||||
"cs-agent/internal/ai/runtime/registry"
|
||||
@@ -18,7 +19,7 @@ import (
|
||||
type AgentFactory struct {
|
||||
chatModelFactory *ChatModelFactory
|
||||
toolFactory *ToolFactory
|
||||
instructionService *InstructionService
|
||||
instructionService *runtimeinstruction.Service
|
||||
handlerService *AgentHandlerService
|
||||
}
|
||||
|
||||
@@ -55,7 +56,7 @@ func NewAgentFactory() *AgentFactory {
|
||||
return &AgentFactory{
|
||||
chatModelFactory: NewChatModelFactory(),
|
||||
toolFactory: NewToolFactory(),
|
||||
instructionService: NewInstructionService(nil, nil, nil, nil, nil),
|
||||
instructionService: runtimeinstruction.NewService(nil, nil, nil, nil, nil),
|
||||
handlerService: NewAgentHandlerService(nil),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
package factory
|
||||
|
||||
import "strings"
|
||||
|
||||
const defaultGovernanceInstruction = `
|
||||
你正在一个有明确工程约束的客服系统中工作。
|
||||
执行时必须严格遵守当前注入的项目规则、Agent 规则和技能规则。
|
||||
如果存在工具白名单限制,只能调用当前允许的工具;信息不足时优先追问,不要伪造事实或跳过必要确认。
|
||||
`
|
||||
|
||||
type InstructionAssembler struct {
|
||||
governanceInstruction string
|
||||
}
|
||||
|
||||
type InstructionAssemblerInput struct {
|
||||
AgentInstruction string
|
||||
GovernanceInstruction string
|
||||
SkillInstruction string
|
||||
ToolAppendices []string
|
||||
ProjectInstruction string
|
||||
}
|
||||
|
||||
// InstructionAssemblySummary 描述 instruction 各组成部分的来源摘要。
|
||||
type InstructionAssemblySummary struct {
|
||||
SectionTitles []string
|
||||
HasProjectRule bool
|
||||
HasGovernanceRule bool
|
||||
HasAgentRule bool
|
||||
HasSkillRule bool
|
||||
HasToolRule bool
|
||||
}
|
||||
|
||||
// InstructionAssemblyResult 为 instruction 装配结果。
|
||||
type InstructionAssemblyResult struct {
|
||||
Text string
|
||||
Summary InstructionAssemblySummary
|
||||
}
|
||||
|
||||
func NewInstructionAssembler() *InstructionAssembler {
|
||||
return &InstructionAssembler{
|
||||
governanceInstruction: strings.TrimSpace(defaultGovernanceInstruction),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *InstructionAssembler) Build(input InstructionAssemblerInput) string {
|
||||
return a.Assemble(input).Text
|
||||
}
|
||||
|
||||
// Assemble 构建最终 instruction 文本及其来源摘要。
|
||||
func (a *InstructionAssembler) Assemble(input InstructionAssemblerInput) InstructionAssemblyResult {
|
||||
parts := make([]string, 0, 5)
|
||||
summary := InstructionAssemblySummary{SectionTitles: make([]string, 0, 5)}
|
||||
projectInstruction := strings.TrimSpace(input.ProjectInstruction)
|
||||
if projectInstruction == "" {
|
||||
projectInstruction = strings.TrimSpace(DefaultProjectInstruction)
|
||||
}
|
||||
if projectInstruction != "" {
|
||||
parts = append(parts, buildInstructionSection("项目级规则", projectInstruction))
|
||||
summary.HasProjectRule = true
|
||||
summary.SectionTitles = append(summary.SectionTitles, "项目级规则")
|
||||
}
|
||||
governanceInstruction := strings.TrimSpace(input.GovernanceInstruction)
|
||||
if governanceInstruction == "" && a != nil {
|
||||
governanceInstruction = strings.TrimSpace(a.governanceInstruction)
|
||||
}
|
||||
if governanceInstruction != "" {
|
||||
parts = append(parts, buildInstructionSection("系统治理规则", governanceInstruction))
|
||||
summary.HasGovernanceRule = true
|
||||
summary.SectionTitles = append(summary.SectionTitles, "系统治理规则")
|
||||
}
|
||||
if agentInstruction := strings.TrimSpace(input.AgentInstruction); agentInstruction != "" {
|
||||
parts = append(parts, buildInstructionSection("Agent 规则", agentInstruction))
|
||||
summary.HasAgentRule = true
|
||||
summary.SectionTitles = append(summary.SectionTitles, "Agent 规则")
|
||||
}
|
||||
if skillInstruction := strings.TrimSpace(input.SkillInstruction); skillInstruction != "" {
|
||||
parts = append(parts, buildInstructionSection("当前技能上下文", skillInstruction))
|
||||
summary.HasSkillRule = true
|
||||
summary.SectionTitles = append(summary.SectionTitles, "当前技能上下文")
|
||||
}
|
||||
if appendix := buildToolAppendix(input.ToolAppendices); appendix != "" {
|
||||
parts = append(parts, buildInstructionSection("工具补充规则", appendix))
|
||||
summary.HasToolRule = true
|
||||
summary.SectionTitles = append(summary.SectionTitles, "工具补充规则")
|
||||
}
|
||||
return InstructionAssemblyResult{
|
||||
Text: strings.TrimSpace(strings.Join(parts, "\n\n")),
|
||||
Summary: summary,
|
||||
}
|
||||
}
|
||||
|
||||
func buildInstructionSection(title, body string) string {
|
||||
title = strings.TrimSpace(title)
|
||||
body = strings.TrimSpace(body)
|
||||
if body == "" {
|
||||
return ""
|
||||
}
|
||||
if title == "" {
|
||||
return body
|
||||
}
|
||||
return title + ":\n" + body
|
||||
}
|
||||
|
||||
func buildToolAppendix(input []string) string {
|
||||
if len(input) == 0 {
|
||||
return ""
|
||||
}
|
||||
parts := make([]string, 0, len(input))
|
||||
for _, item := range input {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
parts = append(parts, item)
|
||||
}
|
||||
return strings.TrimSpace(strings.Join(parts, "\n\n"))
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package factory
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInstructionAssemblerRespectsProvidedSources(t *testing.T) {
|
||||
result := NewInstructionAssembler().Assemble(InstructionAssemblerInput{
|
||||
ProjectInstruction: "project-rule",
|
||||
GovernanceInstruction: "governance-rule",
|
||||
AgentInstruction: "agent-rule",
|
||||
SkillInstruction: "skill-rule",
|
||||
ToolAppendices: []string{"tool-rule-1", "tool-rule-2"},
|
||||
})
|
||||
if !strings.Contains(result.Text, "项目级规则:\nproject-rule") {
|
||||
t.Fatalf("missing project instruction: %s", result.Text)
|
||||
}
|
||||
if !strings.Contains(result.Text, "系统治理规则:\ngovernance-rule") {
|
||||
t.Fatalf("missing governance instruction: %s", result.Text)
|
||||
}
|
||||
if !strings.Contains(result.Text, "当前技能上下文:\nskill-rule") {
|
||||
t.Fatalf("missing skill instruction: %s", result.Text)
|
||||
}
|
||||
if !strings.Contains(result.Text, "工具补充规则:\ntool-rule-1") {
|
||||
t.Fatalf("missing tool appendix: %s", result.Text)
|
||||
}
|
||||
if !result.Summary.HasProjectRule || !result.Summary.HasGovernanceRule || !result.Summary.HasAgentRule || !result.Summary.HasSkillRule || !result.Summary.HasToolRule {
|
||||
t.Fatalf("unexpected summary: %#v", result.Summary)
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package factory
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
runtimetooling "cs-agent/internal/ai/runtime/tooling"
|
||||
"cs-agent/internal/models"
|
||||
"cs-agent/internal/pkg/toolx"
|
||||
)
|
||||
|
||||
type ProjectInstructionProvider struct {
|
||||
fileName string
|
||||
}
|
||||
|
||||
// TODO 这个要读取AGENTS.md文件,后面考虑还要不要
|
||||
func NewProjectInstructionProvider() *ProjectInstructionProvider {
|
||||
return &ProjectInstructionProvider{fileName: "AGENTS.md"}
|
||||
}
|
||||
|
||||
func (p *ProjectInstructionProvider) Resolve() string {
|
||||
if text := p.loadFromFile(); text != "" {
|
||||
return text
|
||||
}
|
||||
return strings.TrimSpace(DefaultProjectInstruction)
|
||||
}
|
||||
|
||||
func (p *ProjectInstructionProvider) loadFromFile() string {
|
||||
path := p.resolvePath()
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(data))
|
||||
}
|
||||
|
||||
func (p *ProjectInstructionProvider) resolvePath() string {
|
||||
fileName := "AGENTS.md"
|
||||
if p != nil && strings.TrimSpace(p.fileName) != "" {
|
||||
fileName = strings.TrimSpace(p.fileName)
|
||||
}
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
dir := wd
|
||||
for {
|
||||
candidate := filepath.Join(dir, fileName)
|
||||
if stat, statErr := os.Stat(candidate); statErr == nil && !stat.IsDir() {
|
||||
return candidate
|
||||
}
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == dir {
|
||||
return ""
|
||||
}
|
||||
dir = parent
|
||||
}
|
||||
}
|
||||
|
||||
type ToolAppendixProvider struct{}
|
||||
|
||||
func NewToolAppendixProvider() *ToolAppendixProvider {
|
||||
return &ToolAppendixProvider{}
|
||||
}
|
||||
|
||||
type GovernanceInstructionProvider struct{}
|
||||
|
||||
func NewGovernanceInstructionProvider() *GovernanceInstructionProvider {
|
||||
return &GovernanceInstructionProvider{}
|
||||
}
|
||||
|
||||
func (p *GovernanceInstructionProvider) Resolve() string {
|
||||
return strings.TrimSpace(defaultGovernanceInstruction)
|
||||
}
|
||||
|
||||
type SkillInstructionProvider struct{}
|
||||
|
||||
func NewSkillInstructionProvider() *SkillInstructionProvider {
|
||||
return &SkillInstructionProvider{}
|
||||
}
|
||||
|
||||
func (p *SkillInstructionProvider) Resolve(selectedSkill *models.SkillDefinition) string {
|
||||
return buildSelectedSkillActivationInstruction(selectedSkill)
|
||||
}
|
||||
|
||||
func (p *ToolAppendixProvider) Build(toolDefinitions []runtimetooling.MCPToolDefinition, extraToolCodes map[string]string) []string {
|
||||
appendixParts := make([]string, 0, 1)
|
||||
toolCodes := make([]string, 0, len(toolDefinitions)+len(extraToolCodes))
|
||||
for _, item := range toolDefinitions {
|
||||
toolCodes = append(toolCodes, item.ToolCode)
|
||||
}
|
||||
for _, item := range extraToolCodes {
|
||||
toolCodes = append(toolCodes, item)
|
||||
}
|
||||
appendixParts = append(appendixParts, toolx.BuildToolAppendicesForCodes(len(toolDefinitions) > 0, toolCodes)...)
|
||||
return appendixParts
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package factory
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProjectInstructionProviderResolveFromAgentsFile(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
nestedDir := filepath.Join(tmpDir, "nested", "child")
|
||||
if err := os.MkdirAll(nestedDir, 0o755); err != nil {
|
||||
t.Fatalf("mkdir failed: %v", err)
|
||||
}
|
||||
content := "# AGENTS.md\n\nfrom temp file"
|
||||
if err := os.WriteFile(filepath.Join(tmpDir, "AGENTS.md"), []byte(content), 0o644); err != nil {
|
||||
t.Fatalf("write file failed: %v", err)
|
||||
}
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("getwd failed: %v", err)
|
||||
}
|
||||
defer func() { _ = os.Chdir(wd) }()
|
||||
if err := os.Chdir(nestedDir); err != nil {
|
||||
t.Fatalf("chdir failed: %v", err)
|
||||
}
|
||||
got := NewProjectInstructionProvider().Resolve()
|
||||
if !strings.Contains(got, "from temp file") {
|
||||
t.Fatalf("expected provider to load AGENTS.md from file, got: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectInstructionProviderFallbacksToDefault(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("getwd failed: %v", err)
|
||||
}
|
||||
defer func() { _ = os.Chdir(wd) }()
|
||||
if err := os.Chdir(tmpDir); err != nil {
|
||||
t.Fatalf("chdir failed: %v", err)
|
||||
}
|
||||
got := NewProjectInstructionProvider().Resolve()
|
||||
if !strings.Contains(got, "本文件定义本项目内 AI Agent 的强制开发规则") {
|
||||
t.Fatalf("expected fallback project instruction, got: %s", got)
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package factory
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
runtimetooling "cs-agent/internal/ai/runtime/tooling"
|
||||
"cs-agent/internal/models"
|
||||
)
|
||||
|
||||
type InstructionService struct {
|
||||
assembler *InstructionAssembler
|
||||
projectInstructionProvider *ProjectInstructionProvider
|
||||
governanceInstructionProvider *GovernanceInstructionProvider
|
||||
skillInstructionProvider *SkillInstructionProvider
|
||||
toolAppendixProvider *ToolAppendixProvider
|
||||
}
|
||||
|
||||
func NewInstructionService(
|
||||
assembler *InstructionAssembler,
|
||||
projectProvider *ProjectInstructionProvider,
|
||||
governanceProvider *GovernanceInstructionProvider,
|
||||
skillProvider *SkillInstructionProvider,
|
||||
toolProvider *ToolAppendixProvider,
|
||||
) *InstructionService {
|
||||
if assembler == nil {
|
||||
assembler = NewInstructionAssembler()
|
||||
}
|
||||
if projectProvider == nil {
|
||||
projectProvider = NewProjectInstructionProvider()
|
||||
}
|
||||
if governanceProvider == nil {
|
||||
governanceProvider = NewGovernanceInstructionProvider()
|
||||
}
|
||||
if skillProvider == nil {
|
||||
skillProvider = NewSkillInstructionProvider()
|
||||
}
|
||||
if toolProvider == nil {
|
||||
toolProvider = NewToolAppendixProvider()
|
||||
}
|
||||
return &InstructionService{
|
||||
assembler: assembler,
|
||||
projectInstructionProvider: projectProvider,
|
||||
governanceInstructionProvider: governanceProvider,
|
||||
skillInstructionProvider: skillProvider,
|
||||
toolAppendixProvider: toolProvider,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *InstructionService) Build(
|
||||
aiAgent *models.AIAgent,
|
||||
selectedSkill *models.SkillDefinition,
|
||||
toolDefinitions []runtimetooling.MCPToolDefinition,
|
||||
extraToolCodes map[string]string,
|
||||
) InstructionAssemblyResult {
|
||||
baseInstruction := ""
|
||||
if aiAgent != nil {
|
||||
baseInstruction = strings.TrimSpace(aiAgent.SystemPrompt)
|
||||
}
|
||||
projectInstruction := ""
|
||||
governanceInstruction := ""
|
||||
skillInstruction := ""
|
||||
toolAppendices := make([]string, 0)
|
||||
if s != nil && s.projectInstructionProvider != nil {
|
||||
projectInstruction = s.projectInstructionProvider.Resolve()
|
||||
}
|
||||
if s != nil && s.governanceInstructionProvider != nil {
|
||||
governanceInstruction = s.governanceInstructionProvider.Resolve()
|
||||
}
|
||||
if s != nil && s.skillInstructionProvider != nil {
|
||||
skillInstruction = s.skillInstructionProvider.Resolve(selectedSkill)
|
||||
}
|
||||
if s != nil && s.toolAppendixProvider != nil {
|
||||
toolAppendices = s.toolAppendixProvider.Build(toolDefinitions, extraToolCodes)
|
||||
}
|
||||
assembler := NewInstructionAssembler()
|
||||
if s != nil && s.assembler != nil {
|
||||
assembler = s.assembler
|
||||
}
|
||||
return assembler.Assemble(InstructionAssemblerInput{
|
||||
AgentInstruction: baseInstruction,
|
||||
GovernanceInstruction: governanceInstruction,
|
||||
SkillInstruction: skillInstruction,
|
||||
ToolAppendices: toolAppendices,
|
||||
ProjectInstruction: projectInstruction,
|
||||
})
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package factory
|
||||
|
||||
// DefaultProjectInstruction 为当前项目统一注入的全局项目规则。
|
||||
//
|
||||
// 默认回退到内置常量;运行时优先由 ProjectInstructionProvider 读取仓库中的 AGENTS.md。
|
||||
// TODO 这个内容不合适,需要重新整理内容,内容需要面向客服系统
|
||||
const DefaultProjectInstruction = `# AGENTS.md
|
||||
|
||||
本文件定义本项目内 AI Agent 的强制开发规则。除非用户明确要求偏离,否则必须遵循。
|
||||
|
||||
## 1. 基本原则
|
||||
|
||||
- 适用范围:仓库根目录及所有子目录
|
||||
- 优先级:用户明确指令 > 本文件 > 默认实现习惯
|
||||
- 若与用户要求冲突:先执行用户要求,并在变更说明中标注偏离点
|
||||
|
||||
## 2. 固定技术栈
|
||||
|
||||
- 后端:Golang + Iris + GORM + github.com/mlogclub/simple
|
||||
- 数据库:同时兼容 SQLite 和 MySQL
|
||||
- 前端:Next.js(App Router) + React + shadcn/ui + Tailwind CSS
|
||||
- 前端包管理器:pnpm
|
||||
|
||||
## 3. 后端分层
|
||||
|
||||
必须遵循单向依赖:models -> repositories -> services -> controllers
|
||||
|
||||
- models:只定义实体和表映射
|
||||
- repositories:只封装数据访问
|
||||
- services:负责业务规则、事务编排、聚合逻辑
|
||||
- controllers:只做参数解析、权限校验、service 调用、响应封装
|
||||
|
||||
禁止:
|
||||
|
||||
- controller 直接调用 repository
|
||||
- 直接将 GORM model 返回前端
|
||||
- 在 models/repositories 中写业务编排
|
||||
|
||||
## 4. simple 使用约定
|
||||
|
||||
- DB 初始化后必须执行:sqls.SetDB(db)
|
||||
- 查询条件优先使用:sqls.Cnd
|
||||
- 参数绑定优先使用:web/params
|
||||
- HTTP 响应统一使用:web.JsonData、web.JsonPageData、web.JsonError
|
||||
|
||||
## 5. 数据库兼容规则
|
||||
|
||||
- 字段类型使用兼容集合:varchar、text、int、bigint、datetime
|
||||
- 主键统一使用 int64
|
||||
- 避免数据库私有语法和方言特性
|
||||
|
||||
## 6. 接口与 DTO
|
||||
|
||||
- DTO 分离:request / response 分开定义
|
||||
- JSON 字段统一使用 camelCase
|
||||
- 禁止透传底层 SQL 错误
|
||||
- controller 入参使用 request DTO
|
||||
- controller 出参使用 response DTO
|
||||
- 禁止直接返回 models 到前端
|
||||
|
||||
## 7. Go 代码规范
|
||||
|
||||
- 日志统一使用标准库 log/slog
|
||||
- 新增 Go 代码统一使用 any,禁止新增 interface{}
|
||||
- 修改 Go 代码后必须执行 gofmt
|
||||
|
||||
## 8. 前端规范
|
||||
|
||||
- 前端目录:web
|
||||
- 框架:Next.js 16 + App Router
|
||||
- 基础组件优先使用 shadcn/ui
|
||||
- 前端业务接口统一通过 web/lib/api/* 发起,禁止页面里散落裸 fetch
|
||||
- 新增或修改前端页面后至少执行:cd web && pnpm typecheck
|
||||
|
||||
## 9. 提交前检查清单
|
||||
|
||||
每次修改后至少确认:
|
||||
|
||||
1. 没有跨层调用或反向依赖
|
||||
2. 写操作有明确事务边界
|
||||
3. 返回仍符合统一 JsonResult 结构
|
||||
4. 兼容 SQLite 与 MySQL
|
||||
5. 补充了必要测试,至少覆盖 service 核心路径
|
||||
6. Go 改动已执行 gofmt
|
||||
7. 前端改动至少通过 pnpm lint 或 pnpm typecheck(在 web 目录)`
|
||||
@@ -1,85 +0,0 @@
|
||||
package factory
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
runtimetooling "cs-agent/internal/ai/runtime/tooling"
|
||||
"cs-agent/internal/models"
|
||||
"cs-agent/internal/pkg/toolx"
|
||||
)
|
||||
|
||||
func buildSelectedSkillActivationInstruction(skill *models.SkillDefinition) string {
|
||||
if skill == nil {
|
||||
return ""
|
||||
}
|
||||
lines := []string{
|
||||
"当前命中的专项技能:",
|
||||
fmt.Sprintf("- code: %s", strings.TrimSpace(skill.Code)),
|
||||
fmt.Sprintf("- name: %s", strings.TrimSpace(skill.Name)),
|
||||
}
|
||||
if desc := strings.TrimSpace(skill.Description); desc != "" {
|
||||
lines = append(lines, fmt.Sprintf("- description: %s", desc))
|
||||
}
|
||||
lines = append(lines, "", "执行要求:", "- 本轮优先处理该技能范围内的问题。", fmt.Sprintf("- 需要专项处理细节时,优先调用 %s 工具加载该技能说明后再继续。", toolx.BuiltinSkill.Name), "- 如果关键信息不足,先向用户追问。", "- 不得调用当前技能未授权的工具。")
|
||||
return strings.TrimSpace(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func buildSelectedSkillDocument(skill *models.SkillDefinition, toolDefinitions []runtimetooling.MCPToolDefinition) string {
|
||||
if skill == nil {
|
||||
return ""
|
||||
}
|
||||
lines := []string{
|
||||
"当前命中的专项技能:",
|
||||
fmt.Sprintf("- code: %s", strings.TrimSpace(skill.Code)),
|
||||
fmt.Sprintf("- name: %s", strings.TrimSpace(skill.Name)),
|
||||
}
|
||||
if desc := strings.TrimSpace(skill.Description); desc != "" {
|
||||
lines = append(lines, fmt.Sprintf("- description: %s", desc))
|
||||
}
|
||||
if content := strings.TrimSpace(skill.Instruction); content != "" {
|
||||
lines = append(lines, "", "技能说明:", content)
|
||||
}
|
||||
if examples := parseJSONStringArray(skill.Examples); len(examples) > 0 {
|
||||
lines = append(lines, "", "典型示例问法:")
|
||||
for _, item := range examples {
|
||||
lines = append(lines, "- "+item)
|
||||
}
|
||||
}
|
||||
if len(toolDefinitions) > 0 {
|
||||
lines = append(lines, "", "当前技能允许使用的工具:")
|
||||
for _, item := range toolDefinitions {
|
||||
if strings.TrimSpace(item.ToolCode) == "" {
|
||||
continue
|
||||
}
|
||||
line := "- " + strings.TrimSpace(item.ToolCode)
|
||||
if title := strings.TrimSpace(item.Title); title != "" {
|
||||
line += " | " + title
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
lines = append(lines, "", "执行要求:", "- 优先遵循该技能说明完成任务。", "- 如果关键信息不足,先向用户追问。", "- 不得调用当前技能未授权的工具。")
|
||||
return strings.TrimSpace(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func parseJSONStringArray(raw string) []string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
var ret []string
|
||||
if err := json.Unmarshal([]byte(raw), &ret); err != nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]string, 0, len(ret))
|
||||
for _, item := range ret {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
out = append(out, item)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
runtimeinstruction "cs-agent/internal/ai/runtime/instruction"
|
||||
runtimetooling "cs-agent/internal/ai/runtime/tooling"
|
||||
"cs-agent/internal/models"
|
||||
|
||||
@@ -25,7 +26,7 @@ func newSelectedSkillBackend(selectedSkill *models.SkillDefinition, toolDefiniti
|
||||
return nil, fmt.Errorf("selected skill code is empty")
|
||||
}
|
||||
description := strings.TrimSpace(selectedSkill.Description)
|
||||
content := buildSelectedSkillDocument(selectedSkill, toolDefinitions)
|
||||
content := runtimeinstruction.BuildSelectedSkillDocument(selectedSkill, toolDefinitions)
|
||||
return &selectedSkillBackend{
|
||||
frontMatter: einoskill.FrontMatter{
|
||||
Name: skillName,
|
||||
|
||||
@@ -3,6 +3,7 @@ package factory
|
||||
import (
|
||||
"strings"
|
||||
|
||||
runtimeinstruction "cs-agent/internal/ai/runtime/instruction"
|
||||
einocallbacks "cs-agent/internal/ai/runtime/internal/impl/callbacks"
|
||||
"cs-agent/internal/ai/runtime/registry"
|
||||
runtimetooling "cs-agent/internal/ai/runtime/tooling"
|
||||
@@ -10,7 +11,7 @@ import (
|
||||
"cs-agent/internal/pkg/toolx"
|
||||
)
|
||||
|
||||
func buildInstructionTraceSummary(summary InstructionAssemblySummary) einocallbacks.InstructionTraceSummary {
|
||||
func buildInstructionTraceSummary(summary runtimeinstruction.AssemblySummary) einocallbacks.InstructionTraceSummary {
|
||||
return einocallbacks.InstructionTraceSummary{
|
||||
SectionTitles: append([]string(nil), summary.SectionTitles...),
|
||||
HasProjectRule: summary.HasProjectRule,
|
||||
|
||||
@@ -2,10 +2,12 @@ package factory
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
runtimeinstruction "cs-agent/internal/ai/runtime/instruction"
|
||||
)
|
||||
|
||||
func TestBuildInstructionTraceSummary(t *testing.T) {
|
||||
got := buildInstructionTraceSummary(InstructionAssemblySummary{
|
||||
got := buildInstructionTraceSummary(runtimeinstruction.AssemblySummary{
|
||||
SectionTitles: []string{"项目级规则", "当前技能上下文"},
|
||||
HasProjectRule: true,
|
||||
HasGovernanceRule: true,
|
||||
|
||||
Reference in New Issue
Block a user