140 lines
4.3 KiB
Go
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
|
|
}
|