1. 从“大而全”到“小而美”:为什么我们需要一个极简的 Agent 框架?
最近在折腾 AI Agent 开发,发现一个挺有意思的现象:无论是开源社区还是商业产品,大家似乎都在追求“功能齐全”。一个 Agent 框架,恨不得把能想到的所有工具都集成进去——从网页搜索、文件读写、数据库操作,到调用各种云服务 API,甚至还有复杂的多模态处理。这当然有它的好处,开箱即用,看起来非常强大。但实际用起来,尤其是在一些特定的、资源受限的场景下,比如在树莓派(Raspberry Pi)上部署,或者想快速验证一个核心想法时,这种“大而全”的框架就显得有些笨重了。
我最近深度拆解了一个非常有意思的项目,它的核心思想就四个字:极简主义。这个项目,我们姑且称之为“Pi-Harness”,它整个 Agent 的“工具套件”(Tool Harness)只提供了 4 个最基础的工具。初看可能会觉得“这能干啥?”,但仔细研究其源码后,我发现这种设计背后蕴含着对 Agent 本质的深刻理解,以及对开发者体验和系统可控性的极致追求。它就像一把精密的瑞士军刀,虽然只有几个核心功能,但每一个都设计得恰到好处,组合起来能解决绝大多数基础问题,并且为无限扩展留足了空间。
这和我们常见的“工具箱”式框架形成了鲜明对比。后者给你一个装满上百种工具的仓库,你每次用可能都得翻找半天,而前者则告诉你:“看,这是锤子、螺丝刀、钳子和卷尺。用它们,你可以造出任何你想要的工具,甚至造出新的工具来。” 今天,我就来详细拆解这个只有 4 个工具的极简 Agent Harness,看看它如何通过最少的“原子操作”,构建起一个灵活、可控且高效的 Agent 系统。这对于我们理解 Agent 的底层运行机制,以及设计自己的轻量级应用,有着非常大的启发。
2. 核心四件套:拆解 Pi-Harness 的四个基础工具
这个极简框架的魔力,就藏在这四个看似简单的工具里。它们不是随意选择的,而是经过深思熟虑,覆盖了 Agent 与外部世界交互的最基本维度。让我们逐一拆解。
2.1 Tool 1:execute_python- 代码执行的万能钥匙
这是整个框架中最强大、最核心的工具。它的作用很简单:接收一段 Python 代码字符串,在一个受控的、沙盒化的环境中执行它,并返回执行结果(标准输出、返回值或错误信息)。
为什么这是第一个工具?因为 Python 本身就是一种极其强大的“元工具”。有了执行任意 Python 代码的能力,Agent 理论上就能做任何事情:数学计算、数据处理、调用系统命令(在沙盒允许的范围内)、操作文件、甚至通过网络请求调用其他 API。这个工具将 Agent 的“行动能力”从有限的预定义函数,扩展到了近乎无限的编程领域。
源码实现要点:
- 沙盒环境:这是安全的重中之重。框架绝不会使用
eval()或简单的exec()。它会创建一个独立的命名空间(namespace),并预先置入一些安全的模块(如math,datetime,json,re等),同时严格禁止导入危险模块(如os,sys,subprocess的某些功能)。更高级的实现可能会使用ast(抽象语法树)在代码执行前进行静态分析,过滤掉危险的函数调用和导入语句。 - 超时控制:必须设置执行超时(例如 30 秒),防止 Agent 陷入死循环或执行耗时过长的计算,耗尽资源。
- 结果捕获:需要重定向
stdout和stderr,将打印信息捕获并作为结果的一部分返回。同时,也要获取最后一条表达式的值作为返回值。 - 错误处理:妥善处理代码语法错误、运行时异常,并将清晰的错误信息返回给 Agent,让它能“理解”哪里出错了,从而调整策略。
# 一个极度简化的概念性代码示例,非真实源码 def execute_python(code: str) -> dict: """ 在安全沙盒中执行 Python 代码。 返回格式:{'success': bool, 'output': str, 'result': Any, 'error': str} """ safe_globals = {'__builtins__': {}} # 限制内置函数 safe_globals.update({ 'math': math, 'json': json, 'datetime': datetime, # ... 其他安全模块 }) local_vars = {} # 1. 尝试编译,检查语法 try: compiled_code = compile(code, '<string>', 'exec') except SyntaxError as e: return {'success': False, 'output': '', 'result': None, 'error': f'SyntaxError: {e}'} # 2. 设置超时和输出捕获 import io, sys old_stdout = sys.stdout sys.stdout = io.StringIO() try: # 使用 signal 或 threading 实现超时(此处略去复杂实现) exec(compiled_code, safe_globals, local_vars) output = sys.stdout.getvalue() # 尝试获取最后一个表达式的值(非可靠方法,仅示意) result = local_vars.get('_', None) return {'success': True, 'output': output, 'result': result, 'error': ''} except Exception as e: return {'success': False, 'output': sys.stdout.getvalue(), 'result': None, 'error': f'RuntimeError: {e}'} finally: sys.stdout = old_stdout注意:生产级的沙盒实现极其复杂,上述代码仅为教学示意。直接使用
exec非常危险,真实项目会使用更严格的隔离技术,如PyPy沙盒、Docker容器,或专门的安全执行服务。
2.2 Tool 2:read_file与write_file- 持久化存储的基石
这是两个工具,但通常成对出现,因为它们共同解决了信息的持久化问题。read_file读取指定路径文件的内容,write_file向指定路径写入内容。
为什么需要它们?Agent 的对话是短暂的,记忆是有限的。如果 Agent 无法读写文件,那么它的所有“工作成果”都会在会话结束后消失。这两个工具赋予了 Agent“记忆”和“产出”的能力。它可以读取配置文件、读取之前任务保存的中间结果、将最终答案写入报告,甚至管理自己的任务列表。
源码实现要点:
- 路径限制(安全围栏):绝对不能允许 Agent 任意读写文件系统。框架会定义一个“工作根目录”(如
./workspace),所有文件操作都必须限制在这个目录及其子目录下。传入的文件路径会先被解析,确保是相对路径,并且没有..等试图向上跳转目录的符号链接攻击。 - 文件类型与大小限制:可以限制只能读写文本文件(如
.txt,.json,.py),并设置最大文件大小(如 10MB),防止 Agent 意外读取一个巨大的二进制文件拖慢系统,或写满磁盘。 - 编码处理:明确指定文件编码(如 UTF-8),避免乱码问题。
- 原子化操作:
write_file应该是一个“全有或全无”的操作。通常的实现是先写入一个临时文件,确认无误后再移动(move)到目标位置,这样可以避免在写入过程中程序崩溃导致文件损坏。
import os import pathlib WORKSPACE_ROOT = pathlib.Path('./workspace').resolve() def validate_path(user_path: str) -> pathlib.Path: """将用户提供的路径解析并限制在工作空间内""" # 转换为绝对路径(相对于工作空间) full_path = (WORKSPACE_ROOT / user_path).resolve() # 安全检查:确保解析后的路径仍然在工作空间根目录下 if not str(full_path).startswith(str(WORKSPACE_ROOT)): raise PermissionError(f'Access denied: {user_path} is outside the workspace.') return full_path def read_file(file_path: str) -> str: try: safe_path = validate_path(file_path) with open(safe_path, 'r', encoding='utf-8') as f: return f.read() except FileNotFoundError: return f"Error: File '{file_path}' not found in workspace." except IsADirectoryError: return f"Error: '{file_path}' is a directory, not a file." except Exception as e: return f"Error reading file: {e}" def write_file(file_path: str, content: str) -> str: try: safe_path = validate_path(file_path) # 确保目标目录存在 safe_path.parent.mkdir(parents=True, exist_ok=True) # 原子写入:先写临时文件,再重命名 temp_path = safe_path.with_suffix(safe_path.suffix + '.tmp') with open(temp_path, 'w', encoding='utf-8') as f: f.write(content) temp_path.replace(safe_path) # 原子操作 return f"Successfully wrote to '{file_path}'." except Exception as e: return f"Error writing file: {e}"2.3 Tool 3:http_request- 连接外部世界的桥梁
这个工具允许 Agent 发送 HTTP/HTTPS 请求(GET, POST, PUT, DELETE 等),并获取响应。这是 Agent 与绝大多数现代 Web 服务、API 进行交互的唯一方式。
为什么它是不可或缺的?在当今的互联网生态中,一个不能联网的 Agent 能力是极其有限的。通过http_request,Agent 可以:
- 获取实时信息:查询天气、股票、新闻(虽然不能直接浏览任意网页,但可以调用特定的数据 API)。
- 调用外部服务:使用云平台的 AI 模型(如另一个专长不同的 LLM)、数据库服务、支付接口等。
- 集成现有系统:与你公司内部的 RESTful API 对接,成为现有业务流程的智能助手。
源码实现要点:
- 封装与简化:底层使用
requests或aiohttp库,但暴露给 Agent 的接口要极其简单。通常只需要url、method、headers、body(JSON 或表单数据)这几个关键参数。 - 超时与重试:必须设置合理的连接超时和读取超时,并可能实现简单的重试逻辑(对 5xx 错误),以提高鲁棒性。
- 安全策略:
- URL 过滤/白名单:这是防止 Agent 滥用或攻击内部网络的关键。可以配置一个允许访问的域名或 IP 地址白名单。在生产环境中,禁止访问内网地址(如
10.x.x.x,192.168.x.x,127.0.0.1)是基本要求。 - 请求头控制:可以自动注入或强制覆盖某些请求头,比如设置一个合理的
User-Agent,或者移除 Agent 可能试图设置的敏感头。 - 大小限制:限制请求体和响应体的大小,防止内存耗尽。
- URL 过滤/白名单:这是防止 Agent 滥用或攻击内部网络的关键。可以配置一个允许访问的域名或 IP 地址白名单。在生产环境中,禁止访问内网地址(如
- 结果标准化:将 HTTP 响应的状态码、头部(过滤掉敏感信息如
Set-Cookie)和正文(文本或 JSON)整理成一个结构化的字典返回给 Agent,方便它解析。
import requests from urllib.parse import urlparse ALLOWED_DOMAINS = ['api.openweathermap.org', 'official.data.api.example.com'] # 示例白名单 def http_request(method: str, url: str, headers: dict = None, body: dict = None) -> dict: """ 发送 HTTP 请求。 返回格式:{'status_code': int, 'headers': dict, 'body': str, 'error': str} """ # 1. URL 安全检查 parsed_url = urlparse(url) if parsed_url.hostname not in ALLOWED_DOMAINS: return {'status_code': None, 'headers': {}, 'body': '', 'error': f'Domain {parsed_url.hostname} is not allowed.'} # 2. 准备请求 req_headers = {'User-Agent': 'Pi-Harness-Agent/1.0'} if headers: req_headers.update(headers) # 3. 发送请求(带超时) try: response = requests.request( method=method.upper(), url=url, headers=req_headers, json=body, # 自动处理 JSON timeout=(10, 30) # (连接超时, 读取超时) ) # 4. 处理响应 resp_headers = dict(response.headers) # 过滤掉可能过大的或敏感的头部 resp_headers.pop('Set-Cookie', None) resp_headers.pop('Server', None) # 尝试解析 JSON,否则返回文本 try: resp_body = response.json() except: resp_body = response.text[:5000] # 限制返回长度 return { 'status_code': response.status_code, 'headers': resp_headers, 'body': resp_body, 'error': '' } except requests.exceptions.Timeout: return {'status_code': None, 'headers': {}, 'body': '', 'error': 'Request timeout.'} except Exception as e: return {'status_code': None, 'headers': {}, 'body': '', 'error': f'Request failed: {e}'}2.4 Tool 4:ask_user- 关键决策的“刹车”与导航
这个工具最容易被忽略,但却是构建安全、可控、协作型 Agent的灵魂。它的功能是:让 Agent 在遇到不确定、高风险或需要人类明确指示的情况时,暂停执行,向用户(人类)提出一个问题,并等待用户的回复。
为什么它如此重要?LLM 再强大,也是基于概率的模型,会“幻觉”,会误解模糊指令,会执行高风险操作。ask_user工具提供了几个关键价值:
- 安全阀:当 Agent 即将执行一个具有潜在破坏性的操作(如删除文件、发送重要邮件、进行支付)时,它可以先询问用户确认。
- 消歧器:当用户指令模糊(如“处理一下那个报告”),Agent 可以反问:“您指的是
workspace/report_2023.pdf这个文件吗?您希望我如何‘处理’它?是总结内容,还是修改格式?” - 人机协作:将人类纳入循环(Human-in-the-loop)。对于一些需要创造性、主观判断或专业知识的步骤,Agent 可以主动寻求人类的帮助,而不是硬着头皮给出一个可能质量不高的答案。
- 资源节约:避免 Agent 在错误的方向上浪费大量的 API 调用和计算资源。
源码实现要点:这个工具的实现不涉及复杂的外部调用,核心在于框架流程的控制。
- 中断与等待:当 Agent 调用
ask_user(question=”...”)时,框架的主循环必须暂停,将控制权交还给外部的“运行时环境”(可能是一个命令行界面、一个 Web 服务器或一个消息队列的消费者)。 - 信息传递:框架需要将这个问题(以及可能相关的上下文,如当前任务 ID)呈现给用户。
- 恢复执行:在收到用户的回复后,框架需要将回复内容作为该工具调用的“结果”返回给 Agent,然后恢复 Agent 的执行循环。
- 超时处理:需要设置一个等待用户回复的超时时间。如果超时,可以提供一个默认回复(如“用户未响应,取消操作”或“按原计划继续”),这取决于具体业务逻辑。
# 这是一个框架层面的概念,而非一个独立的函数 # 在 Agent 执行循环中: def run_agent_loop(agent, initial_input): context = initial_input while not agent.is_finished(context): # Agent 决定下一步行动,可能返回一个工具调用请求 action = agent.decide_next_action(context) if action['type'] == 'ask_user': question = action['question'] # 1. 暂停循环,将问题抛给外部系统 # 例如,通过一个回调函数、一个消息队列或设置一个状态标志 user_response = await external_interface.ask_user(question, timeout=300) # 等待5分钟 # 2. 将用户回复作为工具执行结果,更新 Agent 上下文 tool_result = { 'success': True if user_response else False, 'output': user_response or 'User did not respond.', 'result': user_response } context = agent.process_tool_result(context, action['tool_name'], tool_result) elif action['type'] == 'call_tool': # ... 处理其他工具调用 pass3. 极简设计的哲学:少即是多的力量
拥有了这四个工具,一个 Agent 能做什么?答案是:非常多,而且非常可控。这种极简设计背后,是一套清晰的哲学。
1. 正交性与完备性:这四个工具几乎是正交的(相互独立),但又共同构成了一个功能完备的基础集。
execute_python提供了计算与逻辑能力。read/write_file提供了状态持久化能力。http_request提供了外部通信能力。ask_user提供了安全与协作能力。 它们覆盖了 Agent 与环境交互的四个核心方面:处理信息、保存信息、获取新信息、在关键节点引入人类智慧。用这四样“原材料”,Agent 可以组合出复杂的技能。
2. 安全优先的架构:“能力越大,责任越大”。一个全功能的框架很难做全面的安全审计。而 Pi-Harness 将攻击面压缩到了极致:
execute_python的沙盒是唯一需要重兵把守的关口。read/write_file被严格限制在工作目录。http_request可以通过白名单严格控制。ask_user本身就是安全阀。 这种设计让安全审计和风险控制变得可行且清晰。你知道风险点在哪里,并且每个点都有明确的防护策略。
3. 无限的扩展性:这正是极简主义的精髓——它不限制你,而是为你搭建了一个可扩展的基石。如果你需要让 Agent 操作数据库,你不需要修改框架核心。你只需要让 Agent使用execute_python来运行一段调用了sqlite3或psycopg2库的代码,或者让 Agent通过http_request调用一个你预先写好的、安全的数据库操作 API 端点。 框架本身保持极小、极稳定。所有特定的、复杂的业务能力,都通过这四种基础工具的组合或外部服务的调用来实现。这符合 Unix 哲学:“只做一件事,并把它做好”。框架只做好“安全地调度和运行这四类基础工具”这一件事。
4. 对开发者友好:对于框架的开发者而言,维护成本极低。核心逻辑清晰,没有层出不穷的新工具需要集成和测试。 对于 Agent 的开发者(使用者)而言,他们需要理解的 API 只有四个,学习曲线平缓。他们可以更专注于 Prompt 工程和任务流程设计,而不是在浩如烟海的工具文档中迷失。
4. 实战推演:用四件套解决一个复杂任务
让我们通过一个具体的例子,看看这个只有 4 个工具的 Agent 如何协作完成一个看似复杂的任务。
任务描述:“监控我工作目录下logs/文件夹中今天的日志文件(假设是app_YYYY-MM-DD.log),找出所有ERROR级别的日志条目,将它们汇总成一个 JSON 格式的报告,并通过 HTTP POST 发送到我的监控 Webhook 地址https://my-monitor.com/api/alert,最后把发送成功的报告保存到reports/目录下。”
Agent 的思考与行动链(ReAct 模式):
- Thought: 用户想分析日志并发送报告。我需要先找到今天的日志文件。我可以使用
execute_python来列出文件并筛选。Action:execute_python
import os, datetime, glob today = datetime.date.today().strftime('%Y-%m-%d') log_pattern = f'logs/app_{today}.log' log_files = glob.glob(log_pattern) log_filesObservation:['logs/app_2023-10-27.log'](假设文件存在)
Thought: 找到了文件。现在需要读取它并过滤出 ERROR 行。用
read_file读取内容,再用execute_python处理。Action:read_filefile_path:logs/app_2023-10-27.logObservation:[文件内容,多行文本]Action:
execute_python
# 假设上一步的读取结果存储在变量 `log_content` 中(框架会处理上下文传递) lines = log_content.split('\n') error_lines = [line for line in lines if 'ERROR' in line] # 简单结构化一下 report_data = { 'date': today, 'log_file': 'logs/app_2023-10-27.log', 'error_count': len(error_lines), 'errors': error_lines } import json report_json = json.dumps(report_data, indent=2) report_jsonObservation:{"date": "2023-10-27", "log_file": "...", "error_count": 5, "errors": ["...", "..."]}(JSON字符串)
Thought: 报告已生成。现在需要发送到 Webhook。这是一个外部 HTTP 调用,使用
http_request。但这是发送警报,我需要先让用户确认吗?根据任务上下文,用户明确要求发送,所以可以直接执行。但为了绝对安全,我可以设计成在发送前询问。这里假设我们直接发送。Action:http_requestmethod:POSTurl:https://my-monitor.com/api/alertheaders:{'Content-Type': 'application/json'}body:[上一步生成的 report_data 字典]Observation:{'status_code': 200, 'body': '{"status": "ok"}', 'error': ''}Thought: 发送成功(状态码200)。最后需要将报告保存到本地。使用
write_file。Action:write_filefile_path:reports/error_report_2023-10-27.jsoncontent:[第3步生成的 report_json 字符串]Observation:Successfully wrote to 'reports/error_report_2023-10-27.json'.Thought: 所有步骤已完成。任务结束。
可以看到,整个流程完全由四个基础工具组合完成。execute_python承担了主要的逻辑处理和数据处理工作,read_file/write_file负责数据存取,http_request负责外部通信。整个过程中,Agent 展现了良好的问题分解和工具调用规划能力。
5. 从极简到生产:扩展模式与最佳实践
一个只有 4 个工具的框架适合原型验证和特定场景,但要投入生产环境,我们还需要围绕它建立一些模式和最佳实践。
1. 工具的组合与封装(“宏工具”):虽然基础工具只有四个,但我们可以通过 Prompt 工程,让 Agent 学会调用一系列基础工具来完成一个高级操作。更进一步,我们可以在框架层之上,构建一个“工具封装层”。例如,我们可以预先用 Python 写一个函数query_database(sql),然后通过execute_python来调用它。对于 Agent 来说,它只是在执行一段代码,但实际上我们为它封装了一个安全的、可复用的“数据库查询工具”。我们可以将这类常用封装整理成一个工具库脚本,让 Agent 在需要时导入和执行。
2. 状态管理与记忆:极简框架通常不提供复杂的记忆管理。这就需要我们在设计任务流程时考虑状态持久化。一个常见的模式是:让 Agent 将关键的中间状态、对话历史或任务列表,以结构化的格式(如 JSON)通过write_file定期保存到工作目录的某个特定文件(如agent_state.json)中。每次 Agent 启动或新轮次开始时,先通过read_file读取这个状态文件,从而恢复上下文。这实现了简单的“长期记忆”。
3. 错误处理与重试逻辑:框架提供的工具会返回错误信息,但 Agent 本身需要具备处理错误的能力。这需要通过 Prompt 来教导 LLM:“当你调用http_request收到 5xx 错误时,可以等待几秒后重试;当read_file返回文件不存在时,可以检查路径或询问用户。” 更复杂的错误恢复策略,也可以封装在通过execute_python调用的自定义函数里。
4. 性能与资源监控:在生产环境运行 Agent,尤其是允许执行任意 Python 代码时,必须严密监控。
- 超时:为每个工具调用设置严格的超时,特别是
execute_python。 - 资源限制:可以使用操作系统级别的工具(如
ulimit,cgroups)或容器技术,限制单个 Agent 进程的内存和 CPU 使用量。 - 审计日志:记录 Agent 调用的每一个工具、传入的参数和返回的结果(可脱敏)。这对于调试、分析和安全审计至关重要。
5. 提示工程(Prompt Engineering)是关键:在这个框架下,Agent 的能力上限很大程度上取决于你如何设计系统提示词(System Prompt)。你需要清晰地告诉 LLM:
- 你有哪四个工具,每个工具的精确用途、输入格式和输出格式。
- 你鼓励它如何使用这些工具(例如,“当你需要计算或处理数据时,优先考虑使用
execute_python”)。 - 你要求它在特定情况下必须使用某个工具(例如,“在删除任何文件或进行网络支付前,必须使用
ask_user获得明确确认”)。 - 工作目录的结构是怎样的,文件的命名规范是什么。 一份精心编写的提示词,是激活这个极简框架潜力的“咒语”。
6. 对比与反思:何时选择极简,何时需要“全家桶”?
拆解完这个极简设计,我们有必要将其与功能丰富的框架(如 LangChain、AutoGPT 等)进行对比,以便做出合适的技术选型。
| 特性维度 | 极简 Pi-Harness (4-Tool) | 全功能框架 (如 LangChain) |
|---|---|---|
| 核心哲学 | 提供原子能力,组合无限可能。 | 提供预制组件,快速搭建应用。 |
| 学习成本 | 极低。只需掌握4个工具的API。 | 较高。需要学习大量概念(Chain, Agent, Tool, Memory等)和数百个工具。 |
| 灵活性 | 极高。理论上可实现任何能通过代码、IO、网络和人工交互完成的任务。 | 中等。受限于已集成的工具和组件,超出范围需要自行开发集成。 |
| 可控性与安全性 | 高。攻击面小,安全策略清晰,易于审计和加固。 | 较低。功能复杂,攻击面广,安全审计难度大。 |
| 开发效率(初始) | 较低。需要从零开始组合工具,设计流程。 | 高。对于常见任务(文档QA、摘要等),有现成链条可用。 |
| 开发效率(复杂定制) | 高。无需理解框架深层逻辑,直接通过代码和提示词定制。 | 较低。可能需要深入框架源码,遵循其复杂的设计模式。 |
| 部署与资源占用 | 极轻量。依赖少,启动快,适合边缘设备。 | 较重。依赖多,启动慢,资源消耗大。 |
| 适用场景 | 1.资源受限环境(树莓派、边缘计算)。 2.对安全和可控性要求极高的场景。 3.研究和实验Agent 核心机制。 4.高度定制化的专用 Agent。 | 1.快速原型验证和概念演示。 2.构建标准化的、常见的AI 应用(如客服机器人、内容生成)。 3.利用丰富生态,避免重复造轮子。 |
我的个人体会是:
这个只有 4 个工具的极简 Agent Harness,更像是一个“元框架”或“内核”。它强迫你去思考 Agent 的本质是什么——是一个能够使用基础工具来解决问题的推理引擎。它把复杂性从框架转移到了 Prompt 设计和任务规划上。
在最近的一个物联网数据分析项目中,我选择了这种极简模式。项目需要在多个低算力的网关设备上运行,负责收集本地传感器数据,进行简单的异常检测,并在需要时上报。全功能框架的依赖和开销是无法接受的。而使用这个四工具框架,我写了一个不到 200 行的核心循环,配合一个精心设计的提示词,就让 Agent 可靠地完成了文件读取(传感器日志)、数据清洗(execute_python调用pandas)、判断决策,并通过http_request上报结果。整个系统非常轻量、稳定,且由于工具极少,出问题时排查起来一目了然。
当然,如果你的目标是快速搭建一个功能丰富的聊天机器人,需要连接知识库、搜索引擎和多种第三方 API,那么直接使用 LangChain 这样的“全家桶”无疑是更高效的选择。它用复杂性换来了便利性。
最后,再分享一个从这次源码拆解中学到的小技巧:在设计你自己的工具时,无论简单还是复杂,输出标准化至关重要。看看这四件套,每个工具都返回一个结构化的字典,包含success、output/result、error等字段。这种一致性极大地简化了 Agent(LLM)对工具调用结果的理解和后续决策的逻辑处理。当你扩展自定义工具时,请务必遵循这个约定。