144 lines
4.1 KiB
Go
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
|
|
}
|