Files
video-factory/shortdrama/agent/react_agent.go
T
2026-06-18 16:01:39 +08:00

140 lines
4.3 KiB
Go

package agent
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/schema"
"github.com/gogf/gf/v2/frame/g"
)
// ReActAgent 实现 ReAct 模式的智能体
type ReActAgent struct {
model model.ChatModel
tools []*ToolInfo
systemPrompt string
maxStep int
}
// NewReActAgent 创建 ReAct 智能体
func NewReActAgent(ctx context.Context, chatModel model.ChatModel, tools []*ToolInfo, systemPrompt string, maxStep int) *ReActAgent {
return &ReActAgent{
model: chatModel,
tools: tools,
systemPrompt: systemPrompt,
maxStep: maxStep,
}
}
// Run 执行 ReAct 循环
// 标准流程: 思考 → 行动(调用工具) → 观察(工具结果) → 重复 → 最终回答
func (a *ReActAgent) Run(ctx context.Context, userInput string) (string, error) {
messages := []*schema.Message{
schema.SystemMessage(a.systemPrompt),
schema.UserMessage(userInput),
}
// 构建 toolInfos 传给模型
toolInfos := make([]*schema.ToolInfo, 0, len(a.tools))
for _, t := range a.tools {
toolInfos = append(toolInfos, t.ToEinoToolInfo())
}
g.Log().Infof(ctx, "ReAct 开始执行,maxStep=%d, tools=%d", a.maxStep, len(a.tools))
for step := 0; step < a.maxStep; step++ {
g.Log().Infof(ctx, "ReAct step %d/%d: 调用模型...", step+1, a.maxStep)
// 1. LLM 思考并决定行动
startTime := time.Now()
result, err := a.model.Generate(ctx, messages, model.WithTools(toolInfos))
elapsed := time.Since(startTime)
if err != nil {
return "", fmt.Errorf("step %d: 模型调用失败: %w", step, err)
}
g.Log().Infof(ctx, "ReAct step %d/%d: 模型返回 (耗时 %v), content_len=%d, ToolCalls=%d",
step+1, a.maxStep, elapsed, len(result.Content), len(result.ToolCalls))
messages = append(messages, result)
// 2. 检查是否有工具调用
if len(result.ToolCalls) == 0 {
// 没有工具调用 → 最终回答
g.Log().Infof(ctx, "ReAct step %d/%d: 无工具调用,返回最终结果 (content长度=%d)", step+1, a.maxStep, len(result.Content))
return result.Content, nil
}
// 3. 执行每个工具调用
for _, tc := range result.ToolCalls {
tool := a.findTool(tc.Function.Name)
if tool == nil {
g.Log().Warningf(ctx, "ReAct step %d: 未知工具: %s", step+1, tc.Function.Name)
toolResultMsg := &schema.Message{
Role: schema.Tool,
Content: fmt.Sprintf("未知工具: %s", tc.Function.Name),
ToolName: tc.Function.Name,
ToolCallID: tc.ID,
}
messages = append(messages, toolResultMsg)
continue
}
// 解析参数
var args map[string]any
if err := json.Unmarshal([]byte(tc.Function.Arguments), &args); err != nil {
g.Log().Warningf(ctx, "ReAct step %d: 参数解析失败: %v", step+1, err)
toolResultMsg := &schema.Message{
Role: schema.Tool,
Content: fmt.Sprintf("参数解析失败: %v", err),
ToolName: tc.Function.Name,
ToolCallID: tc.ID,
}
messages = append(messages, toolResultMsg)
continue
}
g.Log().Infof(ctx, "ReAct step %d: 调用工具 %s, 参数: %s", step+1, tc.Function.Name, tc.Function.Arguments)
// 执行工具
toolStart := time.Now()
output, err := tool.Func(ctx, args)
toolElapsed := time.Since(toolStart)
if err != nil {
output = fmt.Sprintf("工具执行失败: %v", err)
g.Log().Warningf(ctx, "ReAct step %d: 工具 %s 执行失败 (耗时 %v): %v", step+1, tc.Function.Name, toolElapsed, err)
} else {
truncated := output
if len(truncated) > 200 {
truncated = truncated[:200] + "..."
}
g.Log().Infof(ctx, "ReAct step %d: 工具 %s 执行成功 (耗时 %v), 结果长度=%d, 预览: %s", step+1, tc.Function.Name, toolElapsed, len(output), truncated)
}
// 4. 观察工具结果
toolResultMsg := &schema.Message{
Role: schema.Tool,
Content: output,
Name: tc.Function.Name,
ToolCallID: tc.ID,
}
messages = append(messages, toolResultMsg)
}
}
g.Log().Errorf(ctx, "ReAct 达到最大步骤数 %d,生成未完成", a.maxStep)
return "", fmt.Errorf("达到最大步骤数 %d,生成未完成", a.maxStep)
}
func (a *ReActAgent) findTool(name string) *ToolInfo {
for _, t := range a.tools {
if t.Name == name {
return t
}
}
return nil
}