Skip to content

构建迷你 Agent

目标

从零实现一个能工作的 AI Agent,包含:

  • 与 Gemini API 交互
  • 两个工具:读文件、执行命令
  • 完整的 Agent 循环

代码约 100 行,可直接运行。

完整代码

typescript
// mini-agent.ts
import { GoogleGenerativeAI } from '@google/generative-ai'
import * as fs from 'fs/promises'
import { exec } from 'child_process'
import { promisify } from 'util'

const execAsync = promisify(exec)

// ============ 1. 定义工具 ============

const tools = {
  read_file: {
    declaration: {
      name: 'read_file',
      description: '读取指定路径的文件内容',
      parameters: {
        type: 'object',
        properties: {
          path: { type: 'string', description: '文件路径' }
        },
        required: ['path']
      }
    },
    execute: async (args: { path: string }) => {
      try {
        const content = await fs.readFile(args.path, 'utf-8')
        return { content: content.slice(0, 5000) } // 限制大小
      } catch (e: any) {
        return { error: `读取失败: ${e.message}` }
      }
    }
  },

  run_command: {
    declaration: {
      name: 'run_command',
      description: '执行 shell 命令并返回输出',
      parameters: {
        type: 'object',
        properties: {
          command: { type: 'string', description: '要执行的命令' }
        },
        required: ['command']
      }
    },
    execute: async (args: { command: string }) => {
      try {
        const { stdout, stderr } = await execAsync(args.command, {
          timeout: 30000
        })
        return { stdout, stderr }
      } catch (e: any) {
        return { error: `命令失败: ${e.message}` }
      }
    }
  }
}

// ============ 2. 初始化 Gemini ============

const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!)

const model = genAI.getGenerativeModel({
  model: 'gemini-2.5-flash',
  systemInstruction: `你是一个编程助手。可以读取文件和执行命令来帮助用户。
每次只做一件事,完成后告诉用户结果。`,
  tools: [{
    functionDeclarations: Object.values(tools).map(t => t.declaration)
  }]
})

// ============ 3. Agent 主循环 ============

const MAX_TURNS = 10

async function runAgent(userMessage: string): Promise<string> {
  const chat = model.startChat()
  let response = await chat.sendMessage(userMessage)
  let turns = 0

  while (turns < MAX_TURNS) {
    turns++
    const functionCalls = response.response.functionCalls()

    // 没有工具调用,返回文本响应
    if (!functionCalls || functionCalls.length === 0) {
      return response.response.text()
    }

    // 执行工具调用
    const results: any[] = []

    for (const call of functionCalls) {
      console.log(`[工具] ${call.name}(${JSON.stringify(call.args)})`)

      const tool = tools[call.name as keyof typeof tools]
      if (!tool) {
        results.push({
          functionResponse: {
            name: call.name,
            response: { error: `未知工具: ${call.name}` }
          }
        })
        continue
      }

      const result = await tool.execute(call.args as any)
      console.log(`[结果] ${JSON.stringify(result).slice(0, 200)}...`)

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

    // 发送工具结果,继续对话
    response = await chat.sendMessage(results)
  }

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

// ============ 4. 运行 ============

async function main() {
  const task = process.argv[2] || '列出当前目录的文件'

  console.log(`\n任务: ${task}\n`)
  console.log('---')

  const result = await runAgent(task)

  console.log('---')
  console.log(`\n回答:\n${result}`)
}

main().catch(console.error)

运行示例

bash
# 设置 API Key
export GEMINI_API_KEY=your-api-key

# 运行
npx ts-node mini-agent.ts "读取 package.json 并告诉我项目名称"

输出:

任务: 读取 package.json 并告诉我项目名称

---
[工具] read_file({"path":"package.json"})
[结果] {"content":"{\"name\":\"@google/gemini-cli\",..."}...
---

回答:
项目名称是 `@google/gemini-cli`

代码解析

1. 工具定义

每个工具包含:

  • declaration:JSON Schema 格式的定义,告诉 LLM 这个工具做什么
  • execute:实际执行逻辑
typescript
const tools = {
  read_file: {
    declaration: { /* LLM 看到的定义 */ },
    execute: async (args) => { /* 实际执行 */ }
  }
}

2. 初始化模型

typescript
const model = genAI.getGenerativeModel({
  model: 'gemini-2.5-flash',
  systemInstruction: '...', // 定义 Agent 行为
  tools: [{ functionDeclarations: [...] }] // 告诉 LLM 有哪些工具
})

3. Agent 循环

核心逻辑:

typescript
while (turns < MAX_TURNS) {
  const functionCalls = response.response.functionCalls()

  if (!functionCalls?.length) {
    return response.response.text() // 完成
  }

  // 执行工具
  const results = await executeTools(functionCalls)

  // 发送结果,继续对话
  response = await chat.sendMessage(results)
}

与 gemini-cli 的对比

特性迷你 Agentgemini-cli
代码量~100 行~10000 行
工具数量2 个10+ 个
流式输出
用户确认完整策略引擎
上下文管理Token 压缩
循环检测仅轮次限制多策略检测
错误处理基础重试、恢复
会话持久化Checkpoint

添加更多工具

扩展很简单,只需添加到 tools 对象:

typescript
const tools = {
  // 现有工具...

  write_file: {
    declaration: {
      name: 'write_file',
      description: '写入文件',
      parameters: {
        type: 'object',
        properties: {
          path: { type: 'string' },
          content: { type: 'string' }
        },
        required: ['path', 'content']
      }
    },
    execute: async (args: { path: string; content: string }) => {
      await fs.writeFile(args.path, args.content)
      return { success: true }
    }
  },

  list_directory: {
    declaration: {
      name: 'list_directory',
      description: '列出目录内容',
      parameters: {
        type: 'object',
        properties: {
          path: { type: 'string', description: '目录路径,默认当前目录' }
        }
      }
    },
    execute: async (args: { path?: string }) => {
      const files = await fs.readdir(args.path || '.')
      return { files }
    }
  }
}

小结

这个迷你 Agent 展示了 AI Agent 的核心原理:

  1. 定义工具告诉 LLM 能做什么
  2. 循环调用 LLM 直到任务完成
  3. 执行工具并把结果反馈给 LLM

虽然简单,但已经是一个能工作的 Agent。

下一步

在此基础上添加更多功能:扩展功能 →

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