135 lines
3.4 KiB
Go
135 lines
3.4 KiB
Go
package agent
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"regexp"
|
|
|
|
"codelab/ai-agent/internal/conf"
|
|
"codelab/ai-agent/internal/log"
|
|
"codelab/ai-agent/web/assets"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type AgentConf struct {
|
|
// AgentID is the unique identifier for the agent. Must match pattern `^[a-z0-9-_]{2,80}$`.
|
|
AgentID string `json:"id"`
|
|
|
|
// AgentName is the human-readable name for the agent.
|
|
AgentName string `json:"name"`
|
|
|
|
// AgentDescription is a short description of the agent.
|
|
AgentDescription string `json:"desc"`
|
|
|
|
// PrimaryColor is the primary color for the agent chat page.
|
|
PrimaryColor string `json:"primaryColor"`
|
|
|
|
// SecondaryColor is the secondary color for the agent chat page.
|
|
SecondaryColor string `json:"secondaryColor"`
|
|
|
|
// AccentColor is the accent color for the agent chat page.
|
|
AccentColor string `json:"accentColor"`
|
|
|
|
Dir string `json:"-"`
|
|
}
|
|
|
|
type Agent struct {
|
|
AgentConf
|
|
Avater []byte
|
|
SystemPrompt string
|
|
InitialPrompt string
|
|
}
|
|
|
|
func (a *AgentConf) ReadAgent() *Agent {
|
|
agent := &Agent{
|
|
AgentConf: *a,
|
|
Avater: assets.DefaultAgentAvater,
|
|
SystemPrompt: "你是一个聊天机器人。你应当尽力帮助用户。",
|
|
InitialPrompt: "你好!",
|
|
}
|
|
|
|
avatar, err := os.ReadFile(path.Join(a.Dir, "avatar.webp"))
|
|
if err == nil {
|
|
agent.Avater = avatar
|
|
} else {
|
|
log.T("agent").Errf("Failed to load avatar for agent '%s': %v", a.AgentID, err)
|
|
}
|
|
|
|
systemPrompt, err := os.ReadFile(path.Join(a.Dir, "system_prompt.txt"))
|
|
if err == nil {
|
|
agent.SystemPrompt = string(systemPrompt)
|
|
} else {
|
|
log.T("agent").Errf("Failed to load system prompt for agent '%s': %v", a.AgentID, err)
|
|
}
|
|
|
|
initialPrompt, err := os.ReadFile(path.Join(a.Dir, "initial_prompt.txt"))
|
|
if err == nil {
|
|
agent.InitialPrompt = string(initialPrompt)
|
|
} else {
|
|
log.T("agent").Errf("Failed to load initial prompt for agent '%s': %v", a.AgentID, err)
|
|
}
|
|
|
|
return agent
|
|
}
|
|
|
|
func LoadAgentFromFile(agentJSONPath string) (*AgentConf, error) {
|
|
conf := &AgentConf{
|
|
AgentID: uuid.New().String(),
|
|
AgentName: conf.DefaultAgentName,
|
|
AgentDescription: conf.DefaultAgentDescription,
|
|
PrimaryColor: conf.DefaultAgentPrimaryColor,
|
|
SecondaryColor: conf.DefaultAgentSecondaryColor,
|
|
AccentColor: conf.DefaultAgentAccentColor,
|
|
Dir: path.Dir(agentJSONPath),
|
|
}
|
|
|
|
file, err := os.ReadFile(agentJSONPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = json.Unmarshal(file, conf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
agentIDPatten := `^[a-z0-9-_]{2,80}$`
|
|
if !regexp.MustCompile(agentIDPatten).MatchString(conf.AgentID) {
|
|
return nil, fmt.Errorf("invalid agent ID: '%s' (require pattern: '%s')", conf.AgentID, agentIDPatten)
|
|
}
|
|
|
|
return conf, nil
|
|
}
|
|
|
|
func LoadAllAgentsFromDir(agentsDir string) (map[string]*AgentConf, error) {
|
|
subDirs, err := os.ReadDir(agentsDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read agents directory '%s': %w", agentsDir, err)
|
|
}
|
|
|
|
agents := map[string]*AgentConf{}
|
|
for _, file := range subDirs {
|
|
if !file.IsDir() {
|
|
continue
|
|
}
|
|
|
|
agentPath := path.Join(agentsDir, file.Name(), "agent.json")
|
|
agent, err := LoadAgentFromFile(agentPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load agent from file '%s': %w", agentPath, err)
|
|
}
|
|
|
|
if agent.AgentID != file.Name() {
|
|
return nil, fmt.Errorf("Agent ID '%s' does not match directory name '%s'",
|
|
agent.AgentID, file.Name())
|
|
}
|
|
|
|
agents[agent.AgentID] = agent
|
|
}
|
|
|
|
return agents, nil
|
|
}
|