2026-06-09 23:53:08 +08:00
|
|
|
|
package service
|
2026-06-10 15:13:21 +08:00
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"ai-agent-scaffold-go/internal/config"
|
|
|
|
|
|
"ai-agent-scaffold-go/internal/llm"
|
|
|
|
|
|
"ai-agent-scaffold-go/internal/model"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// AssembleAll 从配置表批量组装 Agent
|
|
|
|
|
|
func AssembleAll(ctx context.Context, tables map[string]model.AiAgentConfigTable, timeout time.Duration) ([]model.RegisteredAgent, error) {
|
|
|
|
|
|
var agents []model.RegisteredAgent
|
|
|
|
|
|
for _, table := range tables {
|
|
|
|
|
|
agent, err := assembleOne(ctx, table, timeout)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("assemble %s: %w", table.AppName, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
agents = append(agents, *agent)
|
|
|
|
|
|
}
|
|
|
|
|
|
return agents, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// assembleOne 组装单个 Agent 配置
|
|
|
|
|
|
func assembleOne(ctx context.Context, table model.AiAgentConfigTable, timeout time.Duration) (*model.RegisteredAgent, error) {
|
|
|
|
|
|
apiCfg := table.Module.AiAPI
|
|
|
|
|
|
completionsURL := strings.TrimRight(apiCfg.BaseURL, "/") + "/" + strings.TrimLeft(apiCfg.CompletionsPath, "/")
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 创建 OpenAI 客户端
|
|
|
|
|
|
client := llm.NewOpenAIClient(completionsURL, apiCfg.APIKey, table.Module.ChatModel.Model, timeout)
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 创建 ChatModel(当前无外部工具,后续可扩展)
|
|
|
|
|
|
chatModel := llm.NewChatModelAdapter(client, nil)
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 构建 Agent 映射表
|
|
|
|
|
|
agentMap := map[string]model.Agent{}
|
|
|
|
|
|
for _, agentCfg := range table.Module.Agents {
|
|
|
|
|
|
agent := NewLLMAgent(agentCfg.Name, agentCfg.Instruction, agentCfg.Description, agentCfg.OutputKey, chatModel)
|
|
|
|
|
|
agentMap[agentCfg.Name] = agent
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 构建 Workflow Agent
|
|
|
|
|
|
for _, wfCfg := range table.Module.AgentWorkflows {
|
|
|
|
|
|
subs := make([]model.Agent, 0, len(wfCfg.SubAgents))
|
|
|
|
|
|
for _, subName := range wfCfg.SubAgents {
|
|
|
|
|
|
sub, ok := agentMap[subName]
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
return nil, fmt.Errorf("workflow %q references unknown agent %q", wfCfg.Name, subName)
|
|
|
|
|
|
}
|
|
|
|
|
|
subs = append(subs, sub)
|
|
|
|
|
|
}
|
|
|
|
|
|
var wfAgent model.Agent
|
|
|
|
|
|
switch wfCfg.Type {
|
|
|
|
|
|
case model.WorkflowTypeSequential:
|
|
|
|
|
|
wfAgent = NewSequentialAgent(wfCfg.Name, wfCfg.Description, subs)
|
|
|
|
|
|
case model.WorkflowTypeParallel:
|
|
|
|
|
|
wfAgent = NewParallelAgent(wfCfg.Name, wfCfg.Description, subs)
|
|
|
|
|
|
case model.WorkflowTypeLoop:
|
|
|
|
|
|
wfAgent = NewLoopAgent(wfCfg.Name, wfCfg.Description, subs, wfCfg.MaxIterations)
|
|
|
|
|
|
default:
|
|
|
|
|
|
return nil, fmt.Errorf("unknown workflow type: %s", wfCfg.Type)
|
|
|
|
|
|
}
|
|
|
|
|
|
agentMap[wfCfg.Name] = wfAgent
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 解析入口 Agent
|
|
|
|
|
|
entryName := table.Module.Runner.AgentName
|
|
|
|
|
|
entryAgent, ok := agentMap[entryName]
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
return nil, fmt.Errorf("entry agent %q not found", entryName)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 创建 Runner
|
|
|
|
|
|
runner := NewRunner(table.AppName, entryAgent)
|
|
|
|
|
|
|
|
|
|
|
|
return &model.RegisteredAgent{
|
|
|
|
|
|
AppName: table.AppName,
|
|
|
|
|
|
AgentID: table.Agent.AgentID,
|
|
|
|
|
|
AgentName: table.Agent.AgentName,
|
|
|
|
|
|
AgentDesc: table.Agent.AgentDesc,
|
|
|
|
|
|
Runner: runner,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// LoadAndAssemble 从配置文件路径列表加载并组装所有 Agent
|
|
|
|
|
|
func LoadAndAssemble(ctx context.Context, paths []string, timeout time.Duration) ([]model.RegisteredAgent, error) {
|
|
|
|
|
|
merged := make(map[string]model.AiAgentConfigTable)
|
|
|
|
|
|
for _, raw := range paths {
|
|
|
|
|
|
path := strings.TrimSpace(raw)
|
|
|
|
|
|
if path == "" {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
expanded := os.ExpandEnv(path)
|
|
|
|
|
|
tables, err := config.LoadAgentTablesFile(expanded)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
for name, table := range tables {
|
|
|
|
|
|
merged[name] = table
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(merged) == 0 {
|
|
|
|
|
|
return nil, fmt.Errorf("no agent tables loaded")
|
|
|
|
|
|
}
|
|
|
|
|
|
return AssembleAll(ctx, merged, timeout)
|
|
|
|
|
|
}
|