Files
video-factory/shortdrama/agent/react_agent.go
T
2026-07-24 13:05:57 +08:00

144 lines
4.1 KiB
Go

package agent
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/gogf/gf/v2/frame/g"
)
// ReActAgent 实现 ReAct 模式的智能体
type ReActAgent struct {
cfg *ModelConfig
tools []*ToolInfo
systemPrompt string
maxStep int
}
// NewReActAgent 创建 ReAct 智能体
func NewReActAgent(cfg *ModelConfig, tools []*ToolInfo, systemPrompt string, maxStep int) *ReActAgent {
return &ReActAgent{
cfg: cfg,
tools: tools,
systemPrompt: systemPrompt,
maxStep: maxStep,
}
}
// Run 执行 ReAct 循环
// 标准流程: 思考 → 行动(调用工具) → 观察(工具结果) → 重复 → 最终回答
func (a *ReActAgent) Run(ctx context.Context, userInput string) (string, error) {
messages := []*ChatMessage{
{Role: RoleSystem, Content: a.systemPrompt},
{Role: RoleUser, Content: userInput},
}
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)
startTime := time.Now()
result, err := CallChatModel(ctx, a.cfg, &ChatRequest{
Messages: messages,
MaxTokens: a.cfg.MaxTokens,
Temperature: a.cfg.Temperature,
Tools: a.tools,
})
elapsed := time.Since(startTime)
if err != nil {
return "", fmt.Errorf("step %d: model call failed: %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, &ChatMessage{
Role: RoleAssistant,
Content: result.Content,
ToolCalls: result.ToolCalls,
})
// 无工具调用 → 最终回答
if len(result.ToolCalls) == 0 {
g.Log().Infof(ctx, "ReAct step %d/%d: 无工具调用,返回最终结果", step+1, a.maxStep)
return result.Content, nil
}
// 执行每个工具调用
for _, tc := range result.ToolCalls {
tool := a.findTool(tc.Name)
if tool == nil {
g.Log().Warningf(ctx, "ReAct step %d: unknown tool: %s", step+1, tc.Name)
messages = append(messages, &ChatMessage{
Role: RoleTool,
Content: fmt.Sprintf("未知工具: %s", tc.Name),
Name: tc.Name,
ToolCallID: tc.ID,
})
continue
}
if tc.Arguments == "" {
g.Log().Warningf(ctx, "ReAct step %d: tool %s arguments empty, skipping", step+1, tc.Name)
messages = append(messages, &ChatMessage{
Role: RoleTool,
Content: "工具参数为空",
Name: tc.Name,
ToolCallID: tc.ID,
})
continue
}
var args map[string]any
if err := json.Unmarshal([]byte(tc.Arguments), &args); err != nil {
g.Log().Warningf(ctx, "ReAct step %d: argument parse failed: %v", step+1, err)
messages = append(messages, &ChatMessage{
Role: RoleTool,
Content: fmt.Sprintf("参数解析失败: %v", err),
Name: tc.Name,
ToolCallID: tc.ID,
})
continue
}
g.Log().Infof(ctx, "ReAct step %d: 调用工具 %s, 参数: %s", step+1, tc.Name, tc.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: tool %s execution failed (elapsed %v): %v", step+1, tc.Name, toolElapsed, err)
} else {
truncated := output
if len(truncated) > 200 {
truncated = truncated[:200] + "..."
}
g.Log().Infof(ctx, "ReAct step %d: 工具 %s 执行成功 (耗时 %v), 结果长度=%d", step+1, tc.Name, toolElapsed, len(output))
}
messages = append(messages, &ChatMessage{
Role: RoleTool,
Content: output,
Name: tc.Name,
ToolCallID: tc.ID,
})
}
}
g.Log().Errorf(ctx, "ReAct reached max steps %d, generation incomplete", a.maxStep)
return "", fmt.Errorf("max steps reached %d, generation incomplete", a.maxStep)
}
func (a *ReActAgent) findTool(name string) *ToolInfo {
for _, t := range a.tools {
if t.Name == name {
return t
}
}
return nil
}