Skip to content

循环结构

Agent 循环的本质

Agent 的核心是一个循环:

while (未完成) {
  1. 发送当前状态给 LLM
  2. LLM 返回:文本 或 工具调用
  3. 如果是工具调用:执行工具,把结果加入上下文
  4. 如果是文本:检查是否完成
}

最简实现

typescript
async function agentLoop(userMessage: string) {
  const messages = [{ role: 'user', content: userMessage }]

  while (true) {
    // 1. 调用 LLM
    const response = await llm.chat(messages)

    // 2. 检查是否有工具调用
    const toolCalls = response.functionCalls()

    if (toolCalls && toolCalls.length > 0) {
      // 3. 执行工具
      for (const call of toolCalls) {
        const result = await executeTool(call.name, call.args)

        // 4. 把结果加入消息历史
        messages.push({
          role: 'function',
          name: call.name,
          content: JSON.stringify(result)
        })
      }
      // 继续循环
    } else {
      // 5. 没有工具调用,返回结果
      return response.text()
    }
  }
}

流程图

gemini-cli 的实现

gemini-cli 的 Agent 循环在 GeminiClient 类中。

源码位置packages/core/src/core/client.ts

简化的核心逻辑:

typescript
class GeminiClient {
  async runLoop(userMessage: string) {
    // 添加用户消息
    this.conversation.push({ role: 'user', content: userMessage })

    while (true) {
      // 发送给 LLM
      const response = await this.chat.send(this.conversation)

      // 处理响应
      for await (const event of this.processResponse(response)) {
        switch (event.type) {
          case 'content':
            // 文本内容,发射给 UI
            this.emit('content', event.text)
            break

          case 'toolCall':
            // 工具调用
            const result = await this.handleToolCall(event)
            this.conversation.push({
              role: 'function',
              name: event.name,
              content: JSON.stringify(result)
            })
            break

          case 'done':
            // LLM 认为完成了
            return
        }
      }
    }
  }

  async handleToolCall(call: ToolCall) {
    const tool = this.tools.find(t => t.name === call.name)

    // 检查是否需要确认
    if (tool.shouldConfirmExecute) {
      const confirm = await tool.shouldConfirmExecute(call.args)
      if (confirm) {
        const approved = await this.requestConfirmation(confirm)
        if (!approved) {
          return { error: '用户取消了操作' }
        }
      }
    }

    // 执行工具
    return tool.execute(call.args)
  }
}

关键设计点

1. 消息历史

每次调用 LLM 都需要传入完整的对话历史:

typescript
const conversation = [
  { role: 'user', content: '读取 config.json' },
  { role: 'model', functionCall: { name: 'read_file', args: {...} } },
  { role: 'function', name: 'read_file', content: '{"name":"app"}' },
  { role: 'model', content: '文件内容是...' },
  { role: 'user', content: '把 name 改成 new-app' },
  // LLM 能看到所有历史,理解上下文
]

2. 终止条件

循环何时结束?

  • LLM 返回纯文本(不请求工具调用)
  • 达到最大轮次限制
  • 用户取消
  • 发生不可恢复的错误
typescript
const MAX_TURNS = 50

async function agentLoop(userMessage: string) {
  let turns = 0

  while (turns < MAX_TURNS) {
    turns++
    const response = await llm.chat(messages)

    if (!response.functionCalls()?.length) {
      return response.text() // 正常结束
    }

    // 执行工具...
  }

  return '达到最大轮次限制' // 强制结束
}

3. 错误处理

工具执行可能失败,需要告诉 LLM:

typescript
async function executeTool(name: string, args: unknown) {
  try {
    const tool = tools.find(t => t.name === name)
    return await tool.execute(args)
  } catch (error) {
    // 错误也是有效的反馈
    return { error: `工具执行失败: ${error.message}` }
  }
}

LLM 收到错误后可能会尝试其他方法。

4. 并行工具调用

LLM 可能一次请求多个工具:

typescript
const toolCalls = response.functionCalls()

if (toolCalls.length > 1) {
  // 并行执行
  const results = await Promise.all(
    toolCalls.map(call => executeTool(call.name, call.args))
  )

  // 按顺序添加结果
  toolCalls.forEach((call, i) => {
    messages.push({
      role: 'function',
      name: call.name,
      content: JSON.stringify(results[i])
    })
  })
}

完整示例

typescript
import { GoogleGenerativeAI } from '@google/generative-ai'

const MAX_TURNS = 20

async function agentLoop(
  chat: Chat,
  tools: Map<string, Tool>,
  userMessage: string
) {
  const messages: Content[] = []
  messages.push({ role: 'user', parts: [{ text: userMessage }] })

  let turns = 0

  while (turns < MAX_TURNS) {
    turns++
    console.log(`--- Turn ${turns} ---`)

    // 调用 LLM
    const result = await chat.sendMessage(messages)
    const response = result.response
    const functionCalls = response.functionCalls()

    if (!functionCalls || functionCalls.length === 0) {
      // 完成
      return response.text()
    }

    // 执行工具调用
    const toolResults = []

    for (const call of functionCalls) {
      console.log(`执行: ${call.name}(${JSON.stringify(call.args)})`)

      const tool = tools.get(call.name)
      let result

      if (!tool) {
        result = { error: `未知工具: ${call.name}` }
      } else {
        try {
          result = await tool.execute(call.args)
        } catch (e) {
          result = { error: e.message }
        }
      }

      toolResults.push({
        functionResponse: {
          name: call.name,
          response: result
        }
      })
    }

    // 发送工具结果
    messages.push({ role: 'user', parts: toolResults })
  }

  return '达到最大轮次限制'
}

小结

  • Agent 循环:调用 LLM → 执行工具 → 反馈结果 → 重复
  • 保持完整的消息历史
  • 处理终止条件和错误
  • 支持并行工具调用

下一步

了解了循环结构,接下来学习状态和上下文管理:状态与上下文管理 →

通过实际源码学习 AI Agent 开发