什么是 ‘Autonomous Tool Creation’:Agent 如何通过查阅文档,自主编写并封装一个全新的工具供自己使用?

在当今的AI领域,大型语言模型(LLMs)已经展现出惊人的理解、推理和生成能力。然而,它们的原始能力通常局限于文本处理。为了让AI Agent能够与外部世界进行更广泛、更精确的交互,我们赋予它们使用“工具”的能力。这些工具可以是简单的计算器,也可以是复杂的数据库查询接口或外部API调用。

传统上,这些工具是由人类专家预先编写和配置好的。但这种模式存在局限性:当Agent遇到一个需要特定能力,而现有工具箱中却没有相应工具的任务时,它将束手无策。这时,“自主工具创建”(Autonomous Tool Creation)的概念应运而生:Agent不仅能使用工具,更能根据自身需求,查阅文档,自主设计、编写、封装并集成全新的工具供自己使用。这不仅极大地扩展了Agent的能力边界,也标志着AI系统迈向真正自主学习和适应环境的关键一步。

本文将深入探讨Agent如何实现自主工具创建,从问题识别到最终的工具验证与自修正,详细阐述其背后的逻辑、技术栈与实现细节。

Agent架构与工具的基石

在深入自主工具创建之前,我们首先需要理解一个典型Agent的基本架构,以及“工具”在其中扮演的角色。

一个高级Agent通常包含以下核心组件:

  1. 大脑 (Brain/LLM):核心决策单元,通常是一个大型语言模型。它负责理解任务、进行规划、推理、调用工具、分析结果等。
  2. 记忆 (Memory):存储短期(上下文)和长期(知识库、经验)信息,帮助Agent在决策时保持连贯性和积累经验。
  3. 规划器 (Planner):将复杂任务分解为一系列子任务,并决定执行顺序和所需工具。
  4. 工具集 (Toolbox):Agent可以调用的外部功能集合。每个工具都有明确的名称、描述和参数定义。
  5. 执行器 (Executor):负责实际执行规划器指定的步骤,包括调用工具、与外部环境交互等。

什么是Agent的“工具”?

对于Agent而言,一个工具本质上是一个可执行的函数或方法,它接收Agent提供的参数,执行某个操作,并返回结果。为了让LLM能够有效地使用工具,每个工具通常需要具备以下元数据:

  • 名称 (Name):一个简洁、描述性的字符串,用于LLM在规划时引用。
  • 描述 (Description):详细说明工具的功能、用途以及它能解决的问题。这对于LLM理解何时以及如何使用工具至关重要。
  • 参数 (Arguments/Schema):一个结构化的定义,通常采用JSON Schema格式,描述了工具接收哪些参数,它们的类型、是否必需、以及简短的描述。这使得LLM能够生成符合函数调用规范的参数。

例如,一个查询天气工具的定义可能如下:

{
    "name": "get_current_weather",
    "description": "获取指定城市当前的实时天气信息。如果需要查询未来天气,请明确说明查询日期。",
    "parameters": {
        "type": "object",
        "properties": {
            "city": {
                "type": "string",
                "description": "需要查询天气的城市名称,例如 '北京','上海'。必需。"
            },
            "unit": {
                "type": "string",
                "enum": ["celsius", "fahrenheit"],
                "description": "温度单位,可选。默认为 'celsius'。",
                "default": "celsius"
            }
        },
        "required": ["city"]
    }
}

LLM在接收到用户请求后,会根据其任务目标和可用工具的描述,选择最合适的工具,并根据其参数 schema 生成调用参数。

自主工具创建的完整流程

自主工具创建是一个多阶段、迭代的过程,它模仿了人类软件工程师解决问题的思维路径。以下是其核心步骤:

1. 问题识别与目标设定

Agent面临的挑战:
Agent在执行任务时,可能会遇到现有工具无法满足需求的情况。例如:

  • 用户要求Agent执行一个特定操作,但Agent的工具箱中没有直接支持该操作的工具。
  • Agent需要与一个新的、未曾集成过的外部系统(如一个公司内部的API、一个新的SaaS服务)进行交互。
  • 现有工具效率低下或功能不全,需要一个更优化的替代品。

Agent的识别过程:
当Agent的规划器发现其现有工具无法直接解决当前问题时,它会进行以下推理:

  1. 任务分析:深入理解用户或自身任务的意图和所需能力。
  2. 工具匹配失败:遍历现有工具集,尝试匹配,但均告失败。
  3. 能力差距识别:明确指出当前能力与所需能力之间的差距。
  4. 新工具需求声明:Agent意识到需要一个全新的工具来弥补这个差距,并为这个新工具设定一个明确的目标。

示例场景:
假设Agent被赋予一个任务:“请帮我在项目管理系统中创建一个名为‘优化用户注册流程’的新任务,并分配给研发团队的负责人张三。

Agent检查其工具箱:

  • search_web: 搜索网页(无法直接创建任务)
  • send_email: 发送邮件(无法直接创建任务)
  • calculate_expression: 计算表达式(不相关)
  • … (没有与项目管理系统交互的工具)

Agent识别到能力差距:它没有与“项目管理系统”交互的能力。
Agent设定新工具目标:需要一个能够“在项目管理系统中创建任务”的工具。

2. 信息收集与文档查阅

这是自主工具创建中最关键的一步。Agent需要像人类开发者一样,主动去寻找并理解如何与目标系统交互。

Agent的信息收集策略:

  1. 上下文利用:如果用户在任务描述中提到了系统名称(如“Jira”、“Asana”或“公司内部项目管理系统”),Agent会优先使用这些信息。
  2. 通用搜索:如果上下文信息不足,Agent会利用其search_web等通用搜索工具,搜索关键词,如“[系统名称] API 文档”、“[系统名称] 开发者指南”。
  3. 内部知识库:对于企业级Agent,可能会有访问内部Wiki、Confluence或API管理平台的工具。

文档查阅与理解:

一旦找到相关文档(通常是API文档),Agent需要执行以下操作:

  1. 文档下载/访问:通过HTTP请求或其他方式获取文档内容。
  2. 信息提取
    • API端点 (Endpoints):例如 /api/v1/tasks
    • 请求方法 (HTTP Methods):GET, POST, PUT, DELETE。
    • 请求参数 (Request Parameters):路径参数、查询参数、请求体(JSON/Form Data)的结构和字段。
    • 响应格式 (Response Format):成功和失败响应的结构。
    • 认证机制 (Authentication):API Key, OAuth2, Bearer Token 等。
    • 错误码 (Error Codes):常见错误及其含义。
    • 示例代码 (Example Code):如果文档中包含,这对生成代码非常有帮助。
  3. 语义理解:LLM不仅仅是提取文本,它需要理解这些信息背后的含义,例如,哪个端点用于“创建任务”,summary字段代表“任务标题”,assignee字段需要用户ID而不是用户名。

代码示例:模拟文档查阅与信息提取

假设Agent通过搜索找到了一个名为“ProjectForge API Documentation”的网页,其中包含创建任务的API信息。

import requests
import json
from bs4 import BeautifulSoup

# 模拟Agent通过搜索获取的API文档URL
DOC_URL = "https://example.com/projectforge/api/docs" # 假设这是一个模拟的API文档URL
# 实际场景中,Agent会使用其web_search工具找到这个URL

def simulate_document_retrieval(url):
    """
    模拟Agent访问并获取API文档内容。
    在真实场景中,这可能是一个复杂的网页抓取和解析过程。
    """
    print(f"Agent: 正在访问API文档:{url}")
    # 模拟网络请求
    # response = requests.get(url)
    # response.raise_for_status() # 检查HTTP错误
    # return response.text

    # 为演示目的,我们直接提供一个模拟的文档内容
    mock_doc_content = """
    <h1>ProjectForge API Documentation</h1>
    <h2>Authentication</h2>
    All API requests require an `Authorization` header with a Bearer Token.
    Example: `Authorization: Bearer YOUR_API_TOKEN`

    <h2>Tasks API</h2>
    <h3>1. Create Task</h3>
    <p><strong>Endpoint:</strong> <code>POST /api/v1/tasks</code></p>
    <p><strong>Description:</strong> Creates a new task in the project management system.</p>
    <p><strong>Request Body (JSON):</strong></p>
    <pre><code class="language-json">
{
    "title": "string (required) - The title of the task.",
    "description": "string (optional) - Detailed description of the task.",
    "projectId": "string (required) - The ID of the project the task belongs to.",
    "assigneeId": "string (required) - The ID of the user assigned to the task.",
    "dueDate": "string (optional, YYYY-MM-DD) - The due date for the task."
}
    </code></pre>
    <p><strong>Responses:</strong></p>
    <ul>
        <li><code>201 Created</code>: Task successfully created. Returns task details.</li>
        <li><code>400 Bad Request</code>: Invalid input.</li>
        <li><code>401 Unauthorized</code>: Missing or invalid authentication token.</li>
    </ul>

    <h3>2. Get Task Details</h3>
    <p><strong>Endpoint:</strong> <code>GET /api/v1/tasks/{taskId}</code></p>
    ... (其他API信息)
    """
    return mock_doc_content

def extract_api_info_for_create_task(doc_content):
    """
    模拟Agent解析文档内容,提取创建任务所需信息。
    LLM会在这里发挥关键作用,理解非结构化文本。
    """
    soup = BeautifulSoup(doc_content, 'html.parser')

    # 提取认证信息
    auth_header_info = soup.find('h2', string='Authentication').find_next_sibling('p').text.strip()
    auth_scheme = "Bearer Token" if "Bearer Token" in auth_header_info else "Unknown"

    # 查找创建任务的API部分
    create_task_heading = soup.find('h3', string='1. Create Task')
    if not create_task_heading:
        return None

    api_details = {}
    api_details['name'] = "create_project_task"
    api_details['description'] = create_task_heading.find_next_sibling('p').text.strip()

    # 提取Endpoint和HTTP方法
    endpoint_tag = create_task_heading.find_next_sibling('p').find('code')
    if endpoint_tag:
        endpoint_text = endpoint_tag.text.strip()
        method, path = endpoint_text.split(' ', 1)
        api_details['method'] = method
        api_details['path'] = path

    # 提取请求体JSON Schema
    request_body_code = create_task_heading.find('code', class_='language-json')
    if request_body_code:
        # LLM会解析这个字符串,将其转化为结构化的JSON Schema
        # 这里我们模拟LLM的解析结果
        raw_schema_str = request_body_code.text.strip()
        # 实际LLM会通过其代码理解能力,将 "string (required) - The title..." 转换为 JSON Schema
        api_details['request_schema'] = {
            "type": "object",
            "properties": {
                "title": {"type": "string", "description": "The title of the task."},
                "description": {"type": "string", "description": "Detailed description of the task."},
                "projectId": {"type": "string", "description": "The ID of the project the task belongs to."},
                "assigneeId": {"type": "string", "description": "The ID of the user assigned to the task."},
                "dueDate": {"type": "string", "description": "The due date for the task.", "format": "date"}
            },
            "required": ["title", "projectId", "assigneeId"]
        }

    api_details['auth_scheme'] = auth_scheme

    return api_details

# 执行模拟
doc_content = simulate_document_retrieval(DOC_URL)
extracted_info = extract_api_info_for_create_task(doc_content)

if extracted_info:
    print("nAgent: 成功从文档中提取以下API信息:")
    print(json.dumps(extracted_info, indent=4))
else:
    print("nAgent: 未能从文档中提取到创建任务的API信息。")

输出示例:

Agent: 正在访问API文档:https://example.com/projectforge/api/docs

Agent: 成功从文档中提取以下API信息:
{
    "name": "create_project_task",
    "description": "Creates a new task in the project management system.",
    "method": "POST",
    "path": "/api/v1/tasks",
    "request_schema": {
        "type": "object",
        "properties": {
            "title": {
                "type": "string",
                "description": "The title of the task."
            },
            "description": {
                "type": "string",
                "description": "Detailed description of the task."
            },
            "projectId": {
                "type": "string",
                "description": "The ID of the project the task belongs to."
            },
            "assigneeId": {
                "type": "string",
                "description": "The ID of the user assigned to the task."
            },
            "dueDate": {
                "type": "string",
                "description": "The due date for the task.",
                "format": "date"
            }
        },
        "required": [
            "title",
            "projectId",
            "assigneeId"
        ]
    },
    "auth_scheme": "Bearer Token"
}

3. 工具规范与设计

Agent利用提取到的信息,开始设计新工具的接口和内部逻辑。这一步的核心是LLM的推理和结构化输出能力。

LLM的设计过程:

  1. 确定工具名称和描述:基于任务目标和API功能,生成清晰、简洁的工具名称和描述。
  2. 定义工具参数:根据API的请求Schema,将其转换为Agent可理解的工具参数定义(通常也是JSON Schema)。需要注意将API的内部字段名映射到对Agent和用户更友好的名称,并添加详细的描述。
  3. 规划内部逻辑:构思工具函数内部需要执行的步骤,例如:
    • 构建API请求URL。
    • 准备请求头(包含认证信息)。
    • 准备请求体(根据传入参数)。
    • 发送HTTP请求。
    • 处理响应(解析JSON,检查状态码)。
    • 处理错误。
    • 返回结果。

表格:工具规范设计

属性 设计考量 示例值
工具名称 简洁、反映核心功能,不与现有工具冲突。 create_projectforge_task
工具描述 详细说明工具作用、何时使用、返回什么。对LLM决策至关重要。 在ProjectForge项目管理系统中创建一个新的任务。你需要提供任务标题、所属项目ID和负责人ID。可选地,可以提供任务描述和截止日期。
参数Schema 基于API请求体和Agent使用习惯。考虑参数类型、是否必填、枚举值、语义描述。JSON Schema格式。 json<br>{<br> "type": "object",<br> "properties": {<br> "title": {<br> "type": "string",<br> "description": "新任务的标题,例如 '修复登录页bug'。",<br> "minLength": 1<br> },<br> "projectId": {<br> "type": "string",<br> "description": "任务所属项目的唯一标识符(ID)。",<br> "pattern": "^[0-9a-fA-F]{24}$" // 假设是MongoDB ObjectId<br> },<br> "assigneeId": {<br> "type": "string",<br> "description": "负责该任务的用户唯一标识符(ID)。",<br> "pattern": "^[0-9a-fA-F]{24}$"<br> },<br> "description": {<br> "type": "string",<br> "description": "任务的详细描述。",<br> "nullable": true<br> },<br> "dueDate": {<br> "type": "string",<br> "format": "date",<br> "description": "任务的截止日期,格式为 YYYY-MM-DD。",<br> "nullable": true<br> }<br> },<br> "required": ["title", "projectId", "assigneeId"]<br>}<br>
内部逻辑 如何将Agent参数转换为API请求,如何处理响应。 1. 获取API Token。
2. 构造请求头 (Authorization: Bearer <token>, Content-Type: application/json)。
3. 构造请求体:将 title, projectId, assigneeId, description, dueDate 映射到 API 所需的 title, projectId, assigneeId, description, dueDate 字段。
4. 发送 POST 请求到 /api/v1/tasks
5. 检查响应状态码 (201)。
6. 解析响应JSON,提取 taskIdstatus
7. 返回结构化结果。

代码示例:LLM输出工具规范(Python函数签名及参数Schema)

Agent通过LLM输出一个Python函数定义及其参数说明,这通常是LLM生成代码的前奏。

# 假设这是LLM根据前面提取的API信息和设计考量生成的输出
tool_spec_output_from_llm = """
```python
def create_projectforge_task(title: str, projectId: str, assigneeId: str, description: Optional[str] = None, dueDate: Optional[str] = None) -> Dict:
    """
    在ProjectForge项目管理系统中创建一个新的任务。
    你需要提供任务标题、所属项目ID和负责人ID。可选地,可以提供任务描述和截止日期。

    Args:
        title (str): 新任务的标题,例如 '修复登录页bug'。
        projectId (str): 任务所属项目的唯一标识符(ID)。
        assigneeId (str): 负责该任务的用户唯一标识符(ID)。
        description (Optional[str]): 任务的详细描述。默认为 None。
        dueDate (Optional[str]): 任务的截止日期,格式为 YYYY-MM-DD。默认为 None。

    Returns:
        Dict: 包含新创建任务的ID和状态的字典,例如 {"taskId": "...", "status": "created"}。
              如果创建失败,会抛出异常。
    """
    # 这里是待填充的工具逻辑代码
    pass

# 工具参数的JSON Schema表示,供Agent的工具调用机制使用
TOOL_PARAMETER_SCHEMA = {
    "type": "object",
    "properties": {
        "title": {
            "type": "string",
            "description": "新任务的标题,例如 '修复登录页bug'。",
            "minLength": 1
        },
        "projectId": {
            "type": "string",
            "description": "任务所属项目的唯一标识符(ID)。"
        },
        "assigneeId": {
            "type": "string",
            "description": "负责该任务的用户唯一标识符(ID)。"
        },
        "description": {
            "type": "string",
            "description": "任务的详细描述。",
            "nullable": True
        },
        "dueDate": {
            "type": "string",
            "format": "date",
            "description": "任务的截止日期,格式为 YYYY-MM-DD。",
            "nullable": True
        }
    },
    "required": ["title", "projectId", "assigneeId"]
}

"""
print(tool_spec_output_from_llm)


### 4. 代码生成

在确定了工具规范后,Agent的LLM会利用其代码生成能力,将设计转化为可执行的Python代码。这一步需要LLM具备对编程语言、常用库(如`requests`用于HTTP请求)、JSON处理、异常处理等方面的深刻理解。

**LLM的代码生成过程:**

1.  **导入必要库**:根据功能需求,导入如 `requests`, `json`, `os` 等模块。
2.  **实现函数体**:
    *   **获取配置**:Agent可能需要从环境变量、配置文件或其记忆中获取API基础URL和认证Token。
    *   **构建请求**:根据API文档和工具参数,构造完整的URL、请求头和请求体。
    *   **发送请求**:使用HTTP客户端库发送请求。
    *   **处理响应**:检查HTTP状态码。如果成功,解析响应JSON并提取关键信息;如果失败,根据错误码和响应体构造有意义的异常或错误信息。
    *   **异常处理**:捕获网络错误、API返回的业务错误,并进行适当处理。
3.  **返回结果**:将API响应转换为Agent易于理解和后续处理的格式。

**代码示例:LLM生成完整的Python工具函数**

假设Agent已经获取了 `PROJECTFORGE_API_BASE_URL` 和 `PROJECTFORGE_API_TOKEN`。

```python
import requests
import json
import os
from typing import Optional, Dict

# 假设Agent通过其配置或环境变量获取这些值
PROJECTFORGE_API_BASE_URL = os.getenv("PROJECTFORGE_API_BASE_URL", "https://api.example.com/projectforge")
PROJECTFORGE_API_TOKEN = os.getenv("PROJECTFORGE_API_TOKEN", "your_mock_api_token")

def create_projectforge_task(
    title: str,
    projectId: str,
    assigneeId: str,
    description: Optional[str] = None,
    dueDate: Optional[str] = None
) -> Dict:
    """
    在ProjectForge项目管理系统中创建一个新的任务。
    你需要提供任务标题、所属项目ID和负责人ID。可选地,可以提供任务描述和截止日期。

    Args:
        title (str): 新任务的标题,例如 '修复登录页bug'。
        projectId (str): 任务所属项目的唯一标识符(ID)。
        assigneeId (str): 负责该任务的用户唯一标识符(ID)。
        description (Optional[str]): 任务的详细描述。默认为 None。
        dueDate (Optional[str]): 任务的截止日期,格式为 YYYY-MM-DD。默认为 None。

    Returns:
        Dict: 包含新创建任务的ID和状态的字典,例如 {"taskId": "...", "status": "created"}。
              如果创建失败,会抛出异常。
    """
    headers = {
        "Authorization": f"Bearer {PROJECTFORGE_API_TOKEN}",
        "Content-Type": "application/json"
    }

    payload = {
        "title": title,
        "projectId": projectId,
        "assigneeId": assigneeId
    }
    if description:
        payload["description"] = description
    if dueDate:
        payload["dueDate"] = dueDate

    api_url = f"{PROJECTFORGE_API_BASE_URL}/api/v1/tasks"

    try:
        print(f"Agent: 正在向 {api_url} 发送 POST 请求...")
        print(f"Agent: 请求头: {headers}")
        print(f"Agent: 请求体: {json.dumps(payload, indent=2)}")

        # 模拟API响应
        if PROJECTFORGE_API_TOKEN == "your_mock_api_token": # 模拟成功
            if not all([title, projectId, assigneeId]):
                raise ValueError("Required fields (title, projectId, assigneeId) are missing for mock.")
            mock_task_id = f"task_{abs(hash(title+projectId+assigneeId)) % 100000}"
            mock_response_data = {"taskId": mock_task_id, "status": "created", "title": title}
            print(f"Agent: 模拟API响应 (201 Created): {json.dumps(mock_response_data)}")
            return mock_response_data

        # 真实场景下的请求
        # response = requests.post(api_url, headers=headers, json=payload)
        # response.raise_for_status()  # 这会为 4xx/5xx 状态码抛出 HTTPError

        # return response.json()

    except requests.exceptions.HTTPError as e:
        error_details = {"status_code": e.response.status_code, "detail": e.response.text}
        print(f"Agent: API请求失败 (HTTPError): {error_details}")
        raise RuntimeError(f"Failed to create task: HTTP Error {e.response.status_code} - {e.response.text}")
    except requests.exceptions.ConnectionError as e:
        print(f"Agent: API请求失败 (ConnectionError): {e}")
        raise RuntimeError(f"Failed to connect to ProjectForge API: {e}")
    except json.JSONDecodeError as e:
        print(f"Agent: API响应解析失败 (JSONDecodeError): {e}")
        raise RuntimeError(f"Failed to parse API response: {e}")
    except Exception as e:
        print(f"Agent: 创建任务时发生未知错误: {e}")
        raise RuntimeError(f"An unexpected error occurred: {e}")

# 将生成的函数和其参数Schema结合,以便后续封装
generated_tool_function = create_projectforge_task
generated_tool_schema = {
    "name": "create_projectforge_task",
    "description": "在ProjectForge项目管理系统中创建一个新的任务。你需要提供任务标题、所属项目ID和负责人ID。可选地,可以提供任务描述和截止日期。",
    "parameters": {
        "type": "object",
        "properties": {
            "title": {
                "type": "string",
                "description": "新任务的标题,例如 '修复登录页bug'。",
                "minLength": 1
            },
            "projectId": {
                "type": "string",
                "description": "任务所属项目的唯一标识符(ID)。"
            },
            "assigneeId": {
                "type": "string",
                "description": "负责该任务的用户唯一标识符(ID)。"
            },
            "description": {
                "type": "string",
                "description": "任务的详细描述。",
                "nullable": True
            },
            "dueDate": {
                "type": "string",
                "format": "date",
                "description": "任务的截止日期,格式为 YYYY-MM-DD。",
                "nullable": True
            }
        },
        "required": ["title", "projectId", "assigneeId"]
    }
}

print("nAgent: 成功生成并导入 `create_projectforge_task` 工具函数及其Schema。")

5. 封装与集成

生成的Python函数只是一个独立的模块。为了让Agent能够像使用其他预置工具一样调用它,需要进行封装和集成。

封装过程:

  1. 动态加载:Agent需要一个机制来动态加载新生成的代码。这通常涉及将代码保存到文件,然后使用Python的 importlibexec() 函数来加载。为了安全,这通常在沙箱环境中进行。
  2. 工具对象化:将加载的函数封装成Agent工具框架所接受的标准格式(例如,LangChain的 Tool 类,或OpenAI函数调用API的JSON描述)。这个对象包含了函数的引用、名称、描述和参数Schema。
  3. 添加到工具注册表:将封装好的工具对象添加到Agent可用的工具列表中,使其成为Agent规划器和执行器的一部分。

代码示例:封装新工具并添加到Agent工具集

# 假设这是Agent的工具封装和注册机制

class AgentTool:
    """
    Agent工具的通用封装类。
    """
    def __init__(self, name: str, description: str, func, parameters: Dict):
        self.name = name
        self.description = description
        self.func = func
        self.parameters = parameters

    def __call__(self, *args, **kwargs):
        """使工具对象可直接调用其内部函数。"""
        return self.func(*args, **kwargs)

class AgentToolbox:
    """
    Agent的工具箱,管理所有可用工具。
    """
    def __init__(self):
        self._tools = {} # {tool_name: AgentTool_instance}

    def register_tool(self, tool: AgentTool):
        """注册一个新工具到工具箱。"""
        if tool.name in self._tools:
            print(f"警告: 工具 '{tool.name}' 已存在,将被覆盖。")
        self._tools[tool.name] = tool
        print(f"Agent: 工具 '{tool.name}' 已成功注册。")

    def get_tool(self, name: str) -> Optional[AgentTool]:
        """根据名称获取工具。"""
        return self._tools.get(name)

    def list_tools(self) -> Dict[str, Dict]:
        """列出所有工具的名称和描述。"""
        return {name: {"description": tool.description, "parameters": tool.parameters} for name, tool in self._tools.items()}

# 实例化Agent的工具箱
agent_toolbox = AgentToolbox()

# 模拟Agent将生成的函数封装并注册
new_tool = AgentTool(
    name=generated_tool_schema["name"],
    description=generated_tool_schema["description"],
    func=generated_tool_function,
    parameters=generated_tool_schema["parameters"]
)

agent_toolbox.register_tool(new_tool)

# Agent现在可以通过名称访问和调用新工具了
print("nAgent: 当前可用工具:")
for tool_name, tool_meta in agent_toolbox.list_tools().items():
    print(f"- {tool_name}: {tool_meta['description']}")

# 模拟Agent尝试使用新工具
print("nAgent: 尝试使用新创建的工具 'create_projectforge_task'...")
try:
    task_result = agent_toolbox.get_tool("create_projectforge_task")(
        title="优化用户注册流程",
        projectId="65c7a5b3d9e0f1g2h3i4j5k6", # 模拟的项目ID
        assigneeId="65c7a5b3d9e0f1g2h3i4j5l7", # 模拟的负责人ID
        description="分析现有注册流程,找出瓶颈并提出改进方案。",
        dueDate="2023-12-31"
    )
    print(f"nAgent: 任务创建成功!结果: {task_result}")
    # 真实Agent会将此结果存入记忆或用于后续决策
except Exception as e:
    print(f"nAgent: 任务创建失败: {e}")

输出示例:

Agent: 工具 'create_projectforge_task' 已成功注册。

Agent: 当前可用工具:
- create_projectforge_task: 在ProjectForge项目管理系统中创建一个新的任务。你需要提供任务标题、所属项目ID和负责人ID。可选地,可以提供任务描述和截止日期。

Agent: 尝试使用新创建的工具 'create_projectforge_task'...
Agent: 正在向 https://api.example.com/projectforge/api/v1/tasks 发送 POST 请求...
Agent: 请求头: {'Authorization': 'Bearer your_mock_api_token', 'Content-Type': 'application/json'}
Agent: 请求体: {
  "title": "优化用户注册流程",
  "projectId": "65c7a5b3d9e0f1g2h3i4j5k6",
  "assigneeId": "65c7a5b3d9e0f1g2h3i4j5l7",
  "description": "分析现有注册流程,找出瓶颈并提出改进方案。",
  "dueDate": "2023-12-31"
}
Agent: 模拟API响应 (201 Created): {"taskId": "task_48601", "status": "created", "title": "优化用户注册流程"}

Agent: 任务创建成功!结果: {'taskId': 'task_48601', 'status': 'created', 'title': '优化用户注册流程'}

6. 验证与自修正

工具的创建并非一蹴而就。即使LLM生成了代码,也可能存在bug、逻辑错误或对文档的误解。因此,Agent需要具备验证和自修正的能力,这是“自主”的关键体现。

验证过程:

  1. 基本语法检查:在代码执行前,确保其符合Python语法。
  2. 单元测试生成与执行:Agent可以根据工具的描述和参数Schema,生成简单的测试用例(例如,传入有效参数、无效参数、缺失参数等),并执行这些测试。
  3. 试运行与结果分析:将新工具应用于一个实际的、但风险较低的任务场景(或模拟场景)。Agent执行工具,并分析其返回结果:
    • 是否返回预期格式?
    • 结果是否符合预期逻辑?
    • 是否抛出异常?如果是,异常信息是什么?

自修正过程:

如果验证失败,Agent需要进入迭代修正循环:

  1. 错误诊断:LLM分析错误信息(如堆栈跟踪、API错误响应、测试用例失败信息)。它会尝试理解错误的原因,例如:
    • 参数传递错误?
    • API认证失败?
    • API端点或请求体结构不正确?
    • 对文档的理解有误?
    • 代码逻辑漏洞?
  2. 追溯与回溯:根据诊断结果,Agent可能需要回溯到之前的步骤:
    • 如果发现是文档理解问题,它会重新查阅文档(步骤2)。
    • 如果发现是工具设计问题(参数定义不符),它会重新设计规范(步骤3)。
    • 如果发现是代码实现问题,它会修改生成的代码(步骤4)。
  3. 代码修改与重新生成:LLM根据诊断结果,生成修改后的代码。
  4. 重新封装与验证:重复步骤5和步骤6,直到工具通过验证。

代码示例:模拟验证失败与LLM的自修正尝试

假设Agent的 PROJECTFORGE_API_TOKEN 配置错误。

# 假设PROJECTFORGE_API_TOKEN被错误地设置为一个无效值,或者API模拟返回401
# PROJECTFORGE_API_TOKEN = "invalid_token" # 模拟Token错误

# 重新定义一个模拟的create_projectforge_task函数,使其在特定条件下失败
def create_projectforge_task_with_mock_failure(
    title: str,
    projectId: str,
    assigneeId: str,
    description: Optional[str] = None,
    dueDate: Optional[str] = None
) -> Dict:
    # 模拟API请求和错误响应
    print(f"Agent: (验证阶段) 正在向 API 发送 POST 请求...")
    if PROJECTFORGE_API_TOKEN == "invalid_token": # 模拟认证失败
        print("Agent: (验证阶段) 模拟API响应 (401 Unauthorized): {'error': 'Invalid API Token'}")
        raise RuntimeError("Failed to create task: HTTP Error 401 - {'error': 'Invalid API Token'}")
    elif not projectId.startswith("proj_"): # 模拟项目ID格式错误
        print("Agent: (验证阶段) 模拟API响应 (400 Bad Request): {'error': 'Invalid Project ID format'}")
        raise RuntimeError("Failed to create task: HTTP Error 400 - {'error': 'Invalid Project ID format'}")
    else: # 模拟成功
        mock_task_id = f"task_{abs(hash(title+projectId+assigneeId)) % 100000}"
        mock_response_data = {"taskId": mock_task_id, "status": "created", "title": title}
        print(f"Agent: (验证阶段) 模拟API响应 (201 Created): {json.dumps(mock_response_data)}")
        return mock_response_data

# 注册一个模拟会失败的工具
failed_tool = AgentTool(
    name="create_projectforge_task_failed",
    description="一个模拟会失败的ProjectForge任务创建工具。",
    func=create_projectforge_task_with_mock_failure,
    parameters=generated_tool_schema["parameters"] # 使用相同的参数Schema
)
agent_toolbox.register_tool(failed_tool)

print("nAgent: 进行工具的验证测试...")
try:
    # 模拟一个失败的调用,例如由于projectId格式错误
    agent_toolbox.get_tool("create_projectforge_task_failed")(
        title="测试失败任务",
        projectId="wrong_project_format", # 故意传递错误格式的projectId
        assigneeId="65c7a5b3d9e0f1g2h3i4j5l7"
    )
except Exception as e:
    print(f"nAgent: 验证测试失败!捕获到错误: {e}")
    error_message = str(e)

    # LLM的自修正阶段
    print("nAgent: 错误诊断与自修正开始...")
    llm_error_analysis_prompt = f"""
    我尝试使用刚刚创建的 'create_projectforge_task_failed' 工具,但它失败了,错误信息如下:
    {error_message}

    请分析这个错误信息,判断可能的原因是什么,并提出修改建议。
    具体分析:
    1. 错误类型和关键信息是什么?
    2. 这个错误指向工具的哪个部分(参数、API调用、认证、逻辑)?
    3. 你会如何修改工具的代码或其参数Schema来解决这个问题?
    """

    # 模拟LLM的分析和建议
    llm_analysis_output = f"""
    LLM分析:
    1. 错误类型是 RuntimeError,关键信息是 "Failed to create task: HTTP Error 400 - {{'error': 'Invalid Project ID format'}}"。这表明API服务器返回了400 Bad Request,并明确指出'Invalid Project ID format'。
    2. 这个错误指向工具调用时传入的 `projectId` 参数不符合API的预期格式。
    3. 修改建议:
       a. **更新工具的参数Schema**:在 `projectId` 的 `properties` 中添加 `pattern` 字段,以正则表达式 `^proj_[a-zA-Z0-9]+$` (假设正确格式是 `proj_` 开头)来限制输入,并在 `description` 中明确说明期望的格式。
       b. **在工具代码中添加客户端验证**:在发送API请求之前,对 `projectId` 进行格式检查,如果格式不符,提前抛出有意义的错误,避免不必要的API调用。
       c. **Agent自身在调用工具前进行参数验证**:Agent在调用工具前,应根据工具的参数Schema进行预验证,以减少错误。
    """
    print(llm_analysis_output)

    # 假设LLM根据分析,更新了projectId的参数Schema
    corrected_schema = generated_tool_schema.copy()
    corrected_schema["parameters"]["properties"]["projectId"]["pattern"] = "^proj_[a-zA-Z0-9]+$"
    corrected_schema["parameters"]["properties"]["projectId"]["description"] += " 格式应为 'proj_...'。"

    print("nAgent: 根据LLM建议,已更新工具的参数Schema。")
    print(json.dumps(corrected_schema["parameters"], indent=4))

    # 实际Agent会根据修正建议,重新生成代码或更新工具定义,然后再次进行验证
    # 这里我们模拟一个修正后的成功调用
    print("nAgent: (模拟修正后) 重新尝试使用工具...")
    try:
        agent_toolbox.get_tool("create_projectforge_task_failed")( # 实际上会是新注册的修正版工具
            title="修正后任务",
            projectId="proj_valid_id_123", # 传递正确格式的projectId
            assigneeId="65c7a5b3d9e0f1g2h3i4j5l7"
        )
        print("nAgent: 修正后的工具调用成功!")
    except Exception as e:
        print(f"nAgent: 修正后的工具调用仍然失败: {e}")

输出示例:

Agent: 工具 'create_projectforge_task_failed' 已成功注册。

Agent: 进行工具的验证测试...
Agent: (验证阶段) 正在向 API 发送 POST 请求...
Agent: (验证阶段) 模拟API响应 (400 Bad Request): {'error': 'Invalid Project ID format'}

Agent: 验证测试失败!捕获到错误: Failed to create task: HTTP Error 400 - {'error': 'Invalid Project ID format'}

Agent: 错误诊断与自修正开始...

LLM分析:
1. 错误类型是 RuntimeError,关键信息是 "Failed to create task: HTTP Error 400 - {'error': 'Invalid Project ID format'}}"。这表明API服务器返回了400 Bad Request,并明确指出'Invalid Project ID format'。
2. 这个错误指向工具调用时传入的 `projectId` 参数不符合API的预期格式。
3. 修改建议:
   a. **更新工具的参数Schema**:在 `projectId` 的 `properties` 中添加 `pattern` 字段,以正则表达式 `^proj_[a-zA-Z0-9]+$` (假设正确格式是 `proj_` 开头)来限制输入,并在 `description` 中明确说明期望的格式。
   b. **在工具代码中添加客户端验证**:在发送API请求之前,对 `projectId` 进行格式检查,如果格式不符,提前抛出有意义的错误,避免不必要的API调用。
   c. **Agent自身在调用工具前进行参数验证**:Agent在调用工具前,应根据工具的参数Schema进行预验证,以减少错误。

Agent: 根据LLM建议,已更新工具的参数Schema。
{
    "type": "object",
    "properties": {
        "title": {
            "type": "string",
            "description": "新任务的标题,例如 '修复登录页bug'。",
            "minLength": 1
        },
        "projectId": {
            "type": "string",
            "description": "任务所属项目的唯一标识符(ID)。 格式应为 'proj_...'。",
            "pattern": "^proj_[a-zA-Z0-9]+$"
        },
        "assigneeId": {
            "type": "string",
            "description": "负责该任务的用户唯一标识符(ID)。"
        },
        "description": {
            "type": "string",
            "description": "任务的详细描述。",
            "nullable": true
        },
        "dueDate": {
            "type": "string",
            "format": "date",
            "description": "任务的截止日期,格式为 YYYY-MM-DD。",
            "nullable": true
        }
    },
    "required": [
        "title",
        "projectId",
        "assigneeId"
    ]
}

Agent: (模拟修正后) 重新尝试使用工具...
Agent: (验证阶段) 正在向 API 发送 POST 请求...
Agent: (验证阶段) 模拟API响应 (201 Created): {"taskId": "task_15785", "status": "created", "title": "修正后任务"}

Agent: 修正后的工具调用成功!

自主工具创建的架构考量

实现上述自主工具创建流程,需要Agent具备一些关键的架构能力:

  • 强大的LLM核心:能够进行复杂的文本理解、推理、规划、代码生成和错误分析。这是整个流程的“大脑”。
  • 富文本/网页解析能力:能够从HTML、Markdown、PDF等多种格式的文档中提取结构化和非结构化信息。
  • 安全沙箱执行环境:由于Agent生成并执行代码,必须在一个隔离的、受限制的环境中运行,以防止潜在的安全风险(如恶意代码执行、资源滥用)。
  • 动态模块加载机制:能够运行时加载、卸载和更新Python模块或函数。
  • 持久化记忆与知识库: Agent需要存储查阅过的文档、生成的工具代码、测试结果和修正历史,以便在未来的任务中复用或进行更高级的自省。
  • 与现有工具链的无缝集成:新工具一旦创建,应能立即被Agent的规划器识别并调用,与现有工具无异。

挑战与未来展望

尽管自主工具创建潜力巨大,但仍面临诸多挑战:

  • 可靠性与幻觉:LLM在代码生成和文档理解方面仍可能产生“幻觉”,生成错误的代码或误解文档意图,导致工具不可靠。
  • 安全性:在生产环境中运行Agent生成的代码,其安全风险不容忽视。沙箱技术虽然重要,但仍需严密监控和人工审查机制。
  • 效率与成本:自主工具创建涉及多次迭代、搜索、代码生成和验证,这会消耗大量的计算资源和时间。
  • 复杂性管理:创建的工具如果依赖于其他新创建的工具,或涉及复杂的业务逻辑,其维护和调试将变得异常困难。
  • 泛化能力:目前Agent更多是基于现有API文档创建包装器工具。如何让Agent创建出真正具有创新性、解决全新问题模式的工具,是未来的重要方向。
  • 调试与可解释性:当工具出现问题时,Agent如何清晰地解释其诊断过程和修正逻辑,以及人类如何介入调试,都是亟待解决的问题。

展望未来,随着LLM能力的不断提升,以及Agent架构的日益完善,自主工具创建将成为AI系统不可或缺的能力。它将使Agent能够更快速地适应新环境、集成新服务、解决新问题,从而在自动化、软件开发、科学研究等领域发挥出前所未有的作用。最终,我们可能会看到Agent不仅能编写代码,更能设计系统、优化架构,甚至自主构建出更高级的AI系统,开启一个全新的智能时代。

扩展Agent能力边界的基石

自主工具创建是Agent从被动执行者转向主动问题解决者的关键飞跃。它赋予Agent识别需求、自主学习、编码实现、集成应用和自我完善的完整生命周期能力,极大地拓宽了AI Agent的应用范围和智能化水平。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注