Files
go-tiny-claw/internal/engine/loop.go
T
zhuyongxin cefabd68dc v1.4 工具集扩展 + Windows 编码攻坚
- 新增 BashTool(bash 命令执行,30s 超时 + 自愈 + 截断)
- 新增 WriteFileTool(文件写入)
- Windows GBK 编码自动转 UTF-8(golang.org/x/text)
- OpenAIProvider 新增 API 交互日志
- dumpMessages 移除截断,完整展示上下文
- cmd/claw 三段式任务演示
2026-05-15 17:11:43 +08:00

173 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package engine
import (
"context"
"fmt"
"log"
"go-tiny-claw/internal/provider"
"go-tiny-claw/internal/schema"
"go-tiny-claw/internal/tools"
)
// AgentEngine 是微型 OS 的核心驱动
type AgentEngine struct {
provider provider.LLMProvider
registry tools.Registry
// WorkDir (工作区): 借鉴 OpenClaw 的理念,Agent 必须有一个明确的物理边界
WorkDir string
EnableThinking bool // 【新增】慢思考模式开关
}
func NewAgentEngine(p provider.LLMProvider, r tools.Registry, workDir string, enableThinking bool) *AgentEngine {
return &AgentEngine{
provider: p,
registry: r,
WorkDir: workDir,
EnableThinking: enableThinking,
}
}
func (e *AgentEngine) String() string {
return fmt.Sprintf("AgentEngine{workDir: %s, thinking: %v, registry: %s}", e.WorkDir, e.EnableThinking, e.registry)
}
// dumpMessages 打印当前上下文中的所有消息 (调试用)
func dumpMessages(msgs []schema.Message) {
for i, msg := range msgs {
content := msg.Content
// if len(content) > 80 {
// content = content[:80] + "..."
// }
log.Printf(" [%02d] %-10s | %s", i, msg.Role, content)
}
}
// dumpTools 打印当前可用的工具列表
func dumpTools(tools []schema.ToolDefinition) {
if len(tools) == 0 {
log.Printf(" [Tools] (无可用工具)\n")
return
}
for i, t := range tools {
log.Printf(" [Tool %d] %s — %s", i, t.Name, t.Description)
}
}
// internal/engine/loop.go (续)
func (e *AgentEngine) Run(ctx context.Context, userPrompt string) error {
log.Printf("[Engine] 引擎启动,锁定工作区: %s\n", e.WorkDir)
log.Printf("[Engine] 慢思考模式 (Thinking Phase): %v\n", e.EnableThinking)
contextHistory := []schema.Message{
{
Role: schema.RoleSystem,
Content: "You are go-tiny-claw, an expert coding assistant. You have full access to tools in the workspace.",
},
{
Role: schema.RoleUser,
Content: userPrompt,
},
}
turnCount := 0
const maxTurns = 10
for {
turnCount++
if turnCount > maxTurns {
log.Printf("[Engine] 已达最大轮数 (%d),强制终止。\n", maxTurns)
break
}
log.Printf("\n========== [Turn %d] 开始 ==========\n", turnCount)
dumpMessages(contextHistory)
// 获取当前挂载的所有工具定义
availableTools := e.registry.GetAvailableTools()
dumpTools(availableTools)
// ====================================================================
// Phase 1: 慢思考阶段 (Thinking) - 仅第一轮执行初始规划
// ====================================================================
if e.EnableThinking && turnCount == 1 {
log.Println("[Engine][Phase 1] 剥夺工具访问权,强制进入慢思考与规划阶段...")
// 核心机制:传入的 availableTools 为 nil!
// 大模型看不到任何 JSON Schema,被迫只能输出纯文本的思考过程。
thinkResp, err := e.provider.Generate(ctx, contextHistory, nil)
if err != nil {
return fmt.Errorf("Thinking 阶段生成失败: %w", err)
}
// 如果模型输出了思考过程,我们将其作为 Assistant 消息追加到上下文中
if thinkResp.Content != "" {
fmt.Printf("🧠 [内部思考 Trace]: %s\n", thinkResp.Content)
contextHistory = append(contextHistory, *thinkResp)
}
// 插入过渡指令:让模型知道现在可以调用工具了
contextHistory = append(contextHistory, schema.Message{
Role: schema.RoleUser,
Content: "根据你的推理,现在请使用可用的工具来完成任务。执行具体行动。",
})
}
// ====================================================================
// Phase 2: 行动阶段 (Action) - 恢复工具,顺着规划执行
// ====================================================================
log.Println("[Engine][Phase 2] 恢复工具挂载,等待模型采取行动...")
// 此时的 contextHistory 中已经包含了上一阶段模型自己的 Thinking Trace + 过渡指令。
// 模型会顺着自己的逻辑,结合恢复的 availableTools 发起精准的工具调用。
actionResp, err := e.provider.Generate(ctx, contextHistory, availableTools)
if err != nil {
return fmt.Errorf("Action 阶段生成失败: %w", err)
}
contextHistory = append(contextHistory, *actionResp)
if actionResp.Content != "" {
fmt.Printf("🤖 [对外回复]: %s\n", actionResp.Content)
}
// ====================================================================
// 退出与执行逻辑 (与上一讲保持一致)
// ====================================================================
if len(actionResp.ToolCalls) == 0 {
log.Println("[Engine] 模型未请求调用工具,任务宣告完成。")
break
}
log.Printf("[Engine] 模型请求调用 %d 个工具...\n", len(actionResp.ToolCalls))
for _, toolCall := range actionResp.ToolCalls {
log.Printf(" -> 🛠️ 执行工具: %s, 参数: %s\n", toolCall.Name, string(toolCall.Arguments))
result := e.registry.Execute(ctx, toolCall)
status := "✅ 成功"
if result.IsError {
status = "❌ 失败"
}
log.Printf(" -> 📋 ToolCall %s: %s, 结果: %s\n", toolCall.ID, status, result.Output)
if result.IsError {
log.Printf(" -> ❌ 工具执行报错: %s\n", result.Output)
} else {
log.Printf(" -> ✅ 工具执行成功 (返回 %d 字节)\n", len(result.Output))
}
// 将工具执行的观察结果追加到 Context,准备进入下一轮
observationMsg := schema.Message{
Role: schema.RoleUser,
Content: result.Output,
ToolCallID: toolCall.ID,
}
contextHistory = append(contextHistory, observationMsg)
}
}
return nil
}