在当今的AI领域,大型语言模型(LLMs)已经展现出惊人的理解、推理和生成能力。然而,它们的原始能力通常局限于文本处理。为了让AI Agent能够与外部世界进行更广泛、更精确的交互,我们赋予它们使用“工具”的能力。这些工具可以是简单的计算器,也可以是复杂的数据库查询接口或外部API调用。
传统上,这些工具是由人类专家预先编写和配置好的。但这种模式存在局限性:当Agent遇到一个需要特定能力,而现有工具箱中却没有相应工具的任务时,它将束手无策。这时,“自主工具创建”(Autonomous Tool Creation)的概念应运而生:Agent不仅能使用工具,更能根据自身需求,查阅文档,自主设计、编写、封装并集成全新的工具供自己使用。这不仅极大地扩展了Agent的能力边界,也标志着AI系统迈向真正自主学习和适应环境的关键一步。
本文将深入探讨Agent如何实现自主工具创建,从问题识别到最终的工具验证与自修正,详细阐述其背后的逻辑、技术栈与实现细节。
Agent架构与工具的基石
在深入自主工具创建之前,我们首先需要理解一个典型Agent的基本架构,以及“工具”在其中扮演的角色。
一个高级Agent通常包含以下核心组件:
- 大脑 (Brain/LLM):核心决策单元,通常是一个大型语言模型。它负责理解任务、进行规划、推理、调用工具、分析结果等。
- 记忆 (Memory):存储短期(上下文)和长期(知识库、经验)信息,帮助Agent在决策时保持连贯性和积累经验。
- 规划器 (Planner):将复杂任务分解为一系列子任务,并决定执行顺序和所需工具。
- 工具集 (Toolbox):Agent可以调用的外部功能集合。每个工具都有明确的名称、描述和参数定义。
- 执行器 (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的规划器发现其现有工具无法直接解决当前问题时,它会进行以下推理:
- 任务分析:深入理解用户或自身任务的意图和所需能力。
- 工具匹配失败:遍历现有工具集,尝试匹配,但均告失败。
- 能力差距识别:明确指出当前能力与所需能力之间的差距。
- 新工具需求声明:Agent意识到需要一个全新的工具来弥补这个差距,并为这个新工具设定一个明确的目标。
示例场景:
假设Agent被赋予一个任务:“请帮我在项目管理系统中创建一个名为‘优化用户注册流程’的新任务,并分配给研发团队的负责人张三。”
Agent检查其工具箱:
search_web: 搜索网页(无法直接创建任务)send_email: 发送邮件(无法直接创建任务)calculate_expression: 计算表达式(不相关)- … (没有与项目管理系统交互的工具)
Agent识别到能力差距:它没有与“项目管理系统”交互的能力。
Agent设定新工具目标:需要一个能够“在项目管理系统中创建任务”的工具。
2. 信息收集与文档查阅
这是自主工具创建中最关键的一步。Agent需要像人类开发者一样,主动去寻找并理解如何与目标系统交互。
Agent的信息收集策略:
- 上下文利用:如果用户在任务描述中提到了系统名称(如“Jira”、“Asana”或“公司内部项目管理系统”),Agent会优先使用这些信息。
- 通用搜索:如果上下文信息不足,Agent会利用其
search_web等通用搜索工具,搜索关键词,如“[系统名称] API 文档”、“[系统名称] 开发者指南”。 - 内部知识库:对于企业级Agent,可能会有访问内部Wiki、Confluence或API管理平台的工具。
文档查阅与理解:
一旦找到相关文档(通常是API文档),Agent需要执行以下操作:
- 文档下载/访问:通过HTTP请求或其他方式获取文档内容。
- 信息提取:
- 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):如果文档中包含,这对生成代码非常有帮助。
- API端点 (Endpoints):例如
- 语义理解: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的设计过程:
- 确定工具名称和描述:基于任务目标和API功能,生成清晰、简洁的工具名称和描述。
- 定义工具参数:根据API的请求Schema,将其转换为Agent可理解的工具参数定义(通常也是JSON Schema)。需要注意将API的内部字段名映射到对Agent和用户更友好的名称,并添加详细的描述。
- 规划内部逻辑:构思工具函数内部需要执行的步骤,例如:
- 构建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,提取 taskId 和 status。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能够像使用其他预置工具一样调用它,需要进行封装和集成。
封装过程:
- 动态加载:Agent需要一个机制来动态加载新生成的代码。这通常涉及将代码保存到文件,然后使用Python的
importlib或exec()函数来加载。为了安全,这通常在沙箱环境中进行。 - 工具对象化:将加载的函数封装成Agent工具框架所接受的标准格式(例如,LangChain的
Tool类,或OpenAI函数调用API的JSON描述)。这个对象包含了函数的引用、名称、描述和参数Schema。 - 添加到工具注册表:将封装好的工具对象添加到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需要具备验证和自修正的能力,这是“自主”的关键体现。
验证过程:
- 基本语法检查:在代码执行前,确保其符合Python语法。
- 单元测试生成与执行:Agent可以根据工具的描述和参数Schema,生成简单的测试用例(例如,传入有效参数、无效参数、缺失参数等),并执行这些测试。
- 试运行与结果分析:将新工具应用于一个实际的、但风险较低的任务场景(或模拟场景)。Agent执行工具,并分析其返回结果:
- 是否返回预期格式?
- 结果是否符合预期逻辑?
- 是否抛出异常?如果是,异常信息是什么?
自修正过程:
如果验证失败,Agent需要进入迭代修正循环:
- 错误诊断:LLM分析错误信息(如堆栈跟踪、API错误响应、测试用例失败信息)。它会尝试理解错误的原因,例如:
- 参数传递错误?
- API认证失败?
- API端点或请求体结构不正确?
- 对文档的理解有误?
- 代码逻辑漏洞?
- 追溯与回溯:根据诊断结果,Agent可能需要回溯到之前的步骤:
- 如果发现是文档理解问题,它会重新查阅文档(步骤2)。
- 如果发现是工具设计问题(参数定义不符),它会重新设计规范(步骤3)。
- 如果发现是代码实现问题,它会修改生成的代码(步骤4)。
- 代码修改与重新生成:LLM根据诊断结果,生成修改后的代码。
- 重新封装与验证:重复步骤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的应用范围和智能化水平。