循环结构
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 → 执行工具 → 反馈结果 → 重复
- 保持完整的消息历史
- 处理终止条件和错误
- 支持并行工具调用
下一步
了解了循环结构,接下来学习状态和上下文管理:状态与上下文管理 →