feat(service): 实现阶段 4 业务逻辑 — Agent、Runner、Assembler、ChatService

This commit is contained in:
hhs
2026-06-10 15:13:21 +08:00
parent 658866a661
commit f532b21a7a
4 changed files with 706 additions and 0 deletions

View File

@@ -1 +1,110 @@
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)
}