使用 Gemini 3 从零开始构建 Agent 实用开发指南

看到一个 AI 代理编辑多个文件、运行命令、处理错误并迭代解决问题时,可能会觉得这像魔法一样复杂。但其实不然。构建代理的秘诀在于:它根本没有秘密

一个 Agent 的核心原理非常简单:它是一个大型语言模型(LLM)在一个循环中运行,并配备了它可以选择使用的工具

只要你会用 Python 编写循环,你就能构建一个 Agent。本指南将带你一步步完成整个过程,从简单的 API 调用到功能完备的命令行接口(CLI)代理。

Agent 的真正定义是什么?

传统的软件工作流是预设的,遵循预定义的路径(步骤 A -> 步骤 B -> 步骤 C)。而 Agent 是一个系统,它利用 LLM 动态地决定应用程序的控制流程,以达成用户的目标。

一个 Agent 通常由以下核心组件构成:

  • 模型(大脑): 推理引擎,在本例中是 Gemini 模型。它负责处理歧义、规划步骤,并决定何时需要外部帮助。
  • 工具(手和眼): 代理可以执行的功能,用于与外部世界/环境交互(例如,搜索网页、读取文件、调用 API)。
  • 上下文/记忆(工作区): 代理在任何时刻都能获取的信息。有效地管理这些信息,也就是所谓的“上下文工程(Context Engineering)”。
  • 循环(生命): 一个 while 循环,允许模型:观察 → 思考 → 行动 → 再次观察,直到任务完成。

agent

“循环”的工作流程

几乎所有 Agent 的“循环”都是一个迭代过程:

  1. 定义工具: 你使用结构化的 JSON 格式向模型描述你可用的工具(例如,get_weather)。
  2. 调用 LLM: 你将用户的提示词工具定义发送给模型。
  3. 模型决策: 模型分析请求。如果需要工具,它会返回一个结构化的工具调用请求,其中包含工具名称和参数。
  4. 执行工具(客户端责任): 客户端/应用程序代码拦截这个工具调用请求,执行实际的代码或 API 调用,并捕获结果。
  5. 响应与迭代: 你将结果(工具响应)发回给模型。模型利用这一新信息来决定下一步,可能是调用另一个工具,也可能是生成最终的回答。

构建 Agent

让我们使用 Gemini 3 Pro 和 Python SDK 一步步构建一个 Agent,从基本的文本生成逐步过渡到功能完备的 CLI 代理。

先决条件: 安装 SDK (pip install google-genai) 并设置你的 GEMINI_API_KEY 环境变量(可在 AI Studio 中获取)。

步骤 1:基础文本生成和抽象

第一步是创建与 LLM(即 Gemini 3 Pro)交互的基线。我们将创建一个简单的 Agent 类抽象来组织代码,并在后续步骤中对其进行扩展。首先从一个维护会话历史的简单聊天机器人开始。

from google import genai
from google.genai import types

class Agent:
    def __init__(self, model: str):
        self.model = model
        self.client = genai.Client()
        self.contents = [] # 用于存储会话历史

    def run(self, contents: str):
        # 1. 将用户输入添加到历史记录
        self.contents.append({"role": "user", "parts": [{"text": contents}]})

        # 2. 调用模型生成内容
        response = self.client.models.generate_content(model=self.model, contents=self.contents)
        
        # 3. 将模型的响应添加到历史记录
        self.contents.append(response.candidates[0].content)

        return response

agent = Agent(model="gemini-3-pro-preview")
response1 = agent.run(
    contents="Hello, What are top 3 cities in Germany to visit? Only return the names of the cities."
)

print(f"Model: {response1.text}")
# 输出: Berlin, Munich, Cologne 
response2 = agent.run(
    contents="Tell me something about the second city."
)

print(f"Model: {response2.text}")
# 输出: Munich is the capital of Bavaria and is known for its Oktoberfest.

请注意,这还不是一个 Agent。它是一个标准的聊天机器人。它能保持状态(记忆),但不能采取行动,没有“手和眼”。

步骤 2:赋予它手和眼(工具使用)

要开始将它转变为 Agent,我们需要工具使用函数调用能力。我们为 Agent 提供工具。这需要定义实现(Python 代码)和定义(LLM 看见的 Schema)。如果 LLM 认为某个工具将有助于解决用户的提示,它将返回一个结构化的请求来调用该函数,而不是仅仅返回文本。

我们将创建 3 个工具:read_filewrite_file 和 list_dir。工具定义是一个 JSON Schema,它定义了工具的 namedescription 和 parameters

最佳实践: 使用 description 字段来解释何时以及如何使用该工具。模型高度依赖这些描述来理解工具的使用时机和方法。务必清晰明了。

import os
import json

# --- 工具定义(Schema) ---
read_file_definition = {
    "name": "read_file",
    "description": "Reads a file and returns its contents.",
    # ... 省略其余参数定义 ...
}

list_dir_definition = {
    "name": "list_dir",
    "description": "Lists the contents of a directory.",
    # ... 省略其余参数定义 ...
}

write_file_definition = {
    "name": "write_file",
    "description": "Writes a file with the given contents.",
    # ... 省略其余参数定义 ...
}

# --- 工具实现(Python 函数) ---
def read_file(file_path: str) -> dict:
    with open(file_path, "r") as f:
        return f.read()

def write_file(file_path: str, contents: str) -> bool:
    """Writes a file with the given contents."""
    with open(file_path, "w") as f:
        f.write(contents)
    returnTrue

def list_dir(directory_path: str) -> list[str]:
    """Lists the contents of a directory."""
    full_path = os.path.expanduser(directory_path)
    return os.listdir(full_path)

# 将定义和实现函数组合
file_tools = {
    "read_file": {"definition": read_file_definition, "function": read_file},
    "write_file": {"definition": write_file_definition, "function": write_file},
    "list_dir": {"definition": list_dir_definition, "function": list_dir},
}

# --- Agent 类集成工具 ---
from google import genai
from google.genai import types

class Agent:
    def __init__(self, model: str, tools: dict):
        self.model = model
        self.client = genai.Client()
        self.contents = []
        self.tools = tools # 存储工具

    def run(self, contents: str):
        self.contents.append({"role": "user", "parts": [{"text": contents}]})

        # 配置工具声明
        config = types.GenerateContentConfig(
            tools=[types.Tool(function_declarations=[tool["definition"] for tool in self.tools.values()])],
        )

        response = self.client.models.generate_content(model=self.model, contents=self.contents, config=config)
        self.contents.append(response.candidates[0].content)

        return response

agent = Agent(model="gemini-3-pro-preview", tools=file_tools)

response = agent.run(
    contents="Can you list my files in the current directory?"
)
print(response.function_calls)
# 输出: [FunctionCall(name='list_dir', arguments={'directory_path': '.'})]

太棒了!模型成功调用了工具。现在,我们需要将工具执行逻辑添加到 Agent 类中,并让循环将结果返回给模型。

3:闭合循环(Agent 的诞生)

一个 Agent 的意义不仅在于生成一个工具调用,而在于生成一系列工具调用,将结果返回给模型,然后模型再生成下一个工具调用,如此循环,直到任务完成。

Agent 类将处理核心循环:拦截FunctionCall,在客户端执行工具,然后将 FunctionResponse发回。我们还添加了 SystemInstruction 来指导模型的行为。

注意: Gemini 3 使用思维签名(Thought signatures)来在 API 调用中维护推理上下文。你必须将这些签名原封不动地在请求中返回给模型。

# ... 此处省略了步骤 2 的工具和工具定义代码 ...
 
from google import genai
from google.genai import types

class Agent:
    def __init__(self, model: str, tools: dict, system_instruction: str = "You are a helpful assistant."):
        # ... 初始化代码与步骤 2 相同 ...
        self.system_instruction = system_instruction

    def run(self, contents: str | list[dict[str, str]]):
        # 处理用户输入或函数响应的 parts 列表
        if isinstance(contents, list):
            self.contents.append({"role": "user", "parts": contents})
        else:
            self.contents.append({"role": "user", "parts": [{"text": contents}]})

        config = types.GenerateContentConfig(
            system_instruction=self.system_instruction,
            tools=[types.Tool(function_declarations=[tool["definition"] for tool in self.tools.values()])],
        )

        response = self.client.models.generate_content(model=self.model, contents=self.contents, config=config)
        self.contents.append(response.candidates[0].content)

        # --- 核心循环逻辑:检查工具调用 ---
        if response.function_calls:
            functions_response_parts = []
            for tool_call in response.function_calls:
                print(f"[Function Call] {tool_call}")

                # 客户端执行工具
                if tool_call.name in self.tools:
                    result = {"result": self.tools[tool_call.name]["function"](**tool_call.args)}
                else:
                    result = {"error": "Tool not found"}

                print(f"[Function Response] {result}")
                
                # 准备函数响应部分
                functions_response_parts.append({
                    "functionResponse": {"name": tool_call.name, "response": result}
                })

            # 递归调用 run(),将结果返回给模型进行下一步推理
            return self.run(functions_response_parts)
        
        return response

agent = Agent(
    model="gemini-3-pro-preview", 
    tools=file_tools, 
    system_instruction="You are a helpful Coding Assistant. Respond like you are Linus Torvalds."
)

response = agent.run(
    contents="Can you list my files in the current directory?"
)
print(response.text)
# 输出: [Function Call] id=None args={'directory_path': '.'} name='list_dir'
# [Function Response] {'result': ['.venv', ... ]}
# Linus: There. Your current directory contains: `LICENSE`, ...

刚刚我们成功地构建了第一个功能完备的 Agent。

4:多轮 CLI 代理

现在,我们可以将 Agent 运行在一个简单的命令行接口(CLI)循环中。只需很少的代码,就能创建出高度智能的行为。

# ... 此处省略了 Agent、工具和工具定义代码 ...
 
agent = Agent(
    model="gemini-3-pro-preview", 
    tools=file_tools, 
    system_instruction="You are a helpful Coding Assistant. Respond like you are Linus Torvalds."
)

print("Agent ready. Ask it to check files in this directory.")
whileTrue:
    user_input = input("You: ")
    if user_input.lower() in ['exit', 'quit']:
        break

    response = agent.run(user_input)
    print(f"Linus: {response.text}\n")

Agent 工程的最佳实践

构建循环很容易;但让它可靠、透明和可控却很难。以下是源自顶尖行业实践的关键工程原则,按功能区域分组。

1. 工具定义与人体工程学

你的工具是模型的接口。不要仅仅包装你现有的内部 API。如果一个工具对人类来说很复杂,那么对模型来说也一样:

  • 清晰命名: 使用一目了然的名称,例如 search_customer_database,而不是 cust_db_v2_query
  • 精确描述: Gemini 会阅读函数的文档字符串(docstrings)来理解何时以及如何使用工具。花时间仔细编写这些内容,这本质上是工具的“提示工程”。
  • 返回有意义的错误: 不要返回 50 行的 Java 堆栈跟踪。如果工具失败,返回一个清晰的字符串,如 错误:文件未找到。您想找 'data.csv' 吗? 这样能让 Agent 进行自我修正。
  • 容忍模糊输入: 如果模型经常猜错文件路径,请更新你的工具来处理相对路径或模糊输入,而不是仅仅报错。

2. 上下文工程

模型有一个有限的“注意力预算”。管理哪些信息进入上下文对于性能和成本至关重要。

  • 不要“倾倒”数据: 不要让工具返回整个 10MB 的数据库表。与其创建 get_all_users(),不如创建 search_users(query: str)
  • 即时加载: 不要预加载所有数据(传统的 RAG),而是使用即时策略。Agent 应该维护轻量级的标识符(文件路径、ID),并仅在需要时使用工具动态加载内容。
  • 压缩: 对于运行时间很长的 Agent,可以总结历史记录,移除旧的上下文或开启新会话。
  • Agent 记忆: 允许 Agent 维护一个存储在上下文窗口之外的笔记或便签本,仅在相关时才将它们拉回。

3. 不要过度工程化

构建复杂的多代理系统是很诱人的。但请不要这样做。

  • 首先最大化单个 Agent 的能力: 不要立即构建复杂的系统。Gemini 具备很强的能力,可以在一个提示中处理数十个工具。
  • 逃逸舱口: 确保循环可以被停止,例如设置 max_iterations 中断(比如 15 轮)。
  • 护栏和系统指令: 使用 system_instruction 来为模型设定硬性规则(例如,“您被严格禁止提供超过 50 美元的退款”),或者使用外部分类器。
  • 人机协作(Human-in-the-loop): 对于敏感操作(如 send_email 或 execute_code),暂停循环并要求用户确认后才能实际执行工具。
  • 优先考虑透明度和调试: 记录工具调用及其参数。分析模型的推理过程有助于发现问题,并随着时间推移改进 Agent。

构建 Agent 不再是魔法,而是一个实用的工程任务。正如我们所示,你可以在不到 100 行代码内构建出一个可工作的原型。虽然理解这些基本原理很关键,但不要拘泥于一遍又一遍地重复编写相同的模式。AI 社区已经创建了优秀的开源库,可以帮助你更快地构建更复杂、更健壮的 Agent。

来源:AI AGENT领域

THE END