chatai/internal/agent/agent.go
2024-12-25 17:18:23 +00:00

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
}