// internal/agent/runtime.go
package agent
import (
"context"
"fmt"
"log/slog"
"time"
"your-project/internal/approval"
"your-project/internal/checkpoint"
"your-project/internal/fsm"
"your-project/internal/guardrails"
"your-project/internal/llm"
"your-project/internal/rbac"
"your-project/internal/tools"
)
// Runtime 是 Agent 的核心协调器
type Runtime struct {
llm llm.Client
fsm *fsm.Machine
checkpoint *checkpoint.Store
approval *approval.Manager
guardrails *guardrails.Checker
rbac *rbac.PolicyChecker
toolReg *tools.ToolRegistry
}
type Config struct {
LLM llm.Client
Checkpoint *checkpoint.Store
Approval *approval.Manager
Guardrails *guardrails.Checker
RBAC *rbac.PolicyChecker
ToolReg *tools.ToolRegistry
}
func NewRuntime(cfg Config) *Runtime {
return &Runtime{
llm: cfg.LLM,
fsm: fsm.NewMachine(),
checkpoint: cfg.Checkpoint,
approval: cfg.Approval,
guardrails: cfg.Guardrails,
rbac: cfg.RBAC,
toolReg: cfg.ToolReg,
}
}
// Request Agent 处理的单次请求
type Request struct {
SessionID string
UserID string
Message string
}
// Response Agent 的响应
type Response struct {
SessionID string
Message string
State string
TicketID string // 如果涉及工单
}
// Handle 是 Agent 的主处理循环
func (r *Runtime) Handle(ctx context.Context, req Request) (*Response, error) {
log := slog.With("session_id", req.SessionID, "user_id", req.UserID)
// ──────────────────────────────────────────
// Layer 1: Guardrails - 输入安全检查
// ──────────────────────────────────────────
log.Info("checking guardrails")
if result := r.guardrails.Check(ctx, req.Message); !result.Safe {
log.Warn("guardrails blocked input", "reason", result.Reason)
return &Response{
SessionID: req.SessionID,
Message: "抱歉,您的输入包含不允许的内容:" + result.Reason,
State: "blocked",
}, nil
}
// ──────────────────────────────────────────
// Layer 2: 恢复或初始化 Checkpoint
// ──────────────────────────────────────────
log.Info("loading checkpoint")
state, err := r.checkpoint.Load(ctx, req.SessionID)
if err != nil {
log.Info("no checkpoint found, starting fresh")
state = &checkpoint.State{
SessionID: req.SessionID,
UserID: req.UserID,
FSMState: "IDLE",
Messages: []checkpoint.Message{},
}
}
// 添加用户消息
state.Messages = append(state.Messages, checkpoint.Message{
Role: "user",
Content: req.Message,
Timestamp: time.Now(),
})
// ──────────────────────────────────────────
// Layer 3: LLM 规划 - 决定下一步行动
// ──────────────────────────────────────────
log.Info("calling LLM for planning")
// 获取用户有权限使用的工具(RBAC 过滤)
availableTools := r.toolReg.GetAvailableTools(ctx, req.UserID)
llmResp, err := r.llm.Chat(ctx, llm.ChatRequest{
Messages: convertMessages(state.Messages),
Tools: convertTools(availableTools),
SystemPrompt: buildSystemPrompt(state),
})
if err != nil {
return nil, fmt.Errorf("LLM error: %w", err)
}
// ──────────────────────────────────────────
// Layer 4: Tool Execution Loop
// ──────────────────────────────────────────
for llmResp.HasToolCall() {
toolCall := llmResp.ToolCall()
log.Info("tool call requested", "tool", toolCall.Name)
// FSM 状态转移:IDLE → TOOL_CALLING
if err := r.fsm.Transition(state.FSMState, "tool_call"); err != nil {
return &Response{
SessionID: req.SessionID,
Message: fmt.Sprintf("当前状态不允许调用工具:%v", err),
State: state.FSMState,
}, nil
}
state.FSMState = "TOOL_CALLING"
// 保存 Checkpoint(工具调用前)
if err := r.checkpoint.Save(ctx, state); err != nil {
log.Warn("checkpoint save failed", "error", err)
}
// RBAC + 工具执行(ToolRegistry 内部会检查权限)
toolResult, err := r.toolReg.ExecuteTool(ctx, req.UserID, toolCall.Name, toolCall.Arguments)
if err != nil {
// 权限不足或工具失败
log.Warn("tool execution failed", "tool", toolCall.Name, "error", err)
toolResult = fmt.Sprintf("工具执行失败:%v", err)
}
// 判断是否需要人工审批
if requiresApproval(toolCall.Name) {
log.Info("tool requires approval", "tool", toolCall.Name)
approvalReq, err := r.approval.CreateRequest(ctx, approval.CreateParams{
SessionID: req.SessionID,
UserID: req.UserID,
ToolName: toolCall.Name,
ToolArgs: toolCall.Arguments,
ToolResult: toolResult,
})
if err != nil {
return nil, fmt.Errorf("create approval: %w", err)
}
// FSM → WAITING_APPROVAL
state.FSMState = "WAITING_APPROVAL"
state.PendingApprovalID = approvalReq.ID
r.checkpoint.Save(ctx, state)
return &Response{
SessionID: req.SessionID,
Message: fmt.Sprintf(
"工单草稿已创建(ID: %s),正在等待 L2 Support 审批。审批请求 ID:%s",
extractTicketID(toolResult), approvalReq.ID,
),
State: "WAITING_APPROVAL",
}, nil
}
// 不需要审批,把工具结果加入对话
state.Messages = append(state.Messages, checkpoint.Message{
Role: "tool",
Content: fmt.Sprintf("%v", toolResult),
ToolName: toolCall.Name,
Timestamp: time.Now(),
})
// FSM 回到 IDLE,继续对话
state.FSMState = "IDLE"
// 继续让 LLM 处理工具结果
llmResp, err = r.llm.Chat(ctx, llm.ChatRequest{
Messages: convertMessages(state.Messages),
Tools: convertTools(availableTools),
SystemPrompt: buildSystemPrompt(state),
})
if err != nil {
return nil, fmt.Errorf("LLM follow-up error: %w", err)
}
}
// ──────────────────────────────────────────
// 最终:LLM 文本回复
// ──────────────────────────────────────────
state.Messages = append(state.Messages, checkpoint.Message{
Role: "assistant",
Content: llmResp.Text(),
Timestamp: time.Now(),
})
// 最终 Checkpoint
r.checkpoint.Save(ctx, state)
return &Response{
SessionID: req.SessionID,
Message: llmResp.Text(),
State: state.FSMState,
}, nil
}
// requiresApproval 判断工具是否需要人工审批
func requiresApproval(toolName string) bool {
highRiskTools := map[string]bool{
"create_ticket": true,
"delete_user": true,
"send_email": true,
}
return highRiskTools[toolName]
}
func buildSystemPrompt(state *checkpoint.State) string {
return fmt.Sprintf(`你是一个企业 IT 支持 Agent。
当前会话:%s
当前状态:%s
你只能使用提供的工具,不能执行任何系统命令。
对于高风险操作(如创建工单、发送邮件),会自动触发人工审批流程。`, state.SessionID, state.FSMState)
}