111 lines
3.3 KiB
Go
111 lines
3.3 KiB
Go
package service
|
||
|
||
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)
|
||
}
|