1. 为什么地球观测需要Agent而不是简单给个工作流
这几年做遥感数据处理的人,大概都有同一种感受:单景影像的预处理、指数计算、分类提取这些活儿,已经越来越“熟”了。流程就是标准化流水线,从辐射定标、大气校正到特征提取、结果导出,每一步都有成熟工具。可一旦任务变成“开放性”的——比如“帮我看看这片区域近三个月地表温度异常的原因”“结合地形和气象数据评估某条河流改道风险”——事情就变得麻烦。麻烦不在于数据多难拿,而在于这类任务是高度非确定的,你没法提前写死一个流程,更没法保证每一步都有现成工具可调。
OpenEarth-Agent这个方向,本质就是在回答一个问题:当地球观测任务进入开放环境,一个能自己调用工具的智能体,能不能更进一步,在自己缺工具的时候把工具造出来。它的核心设计思路,是从工具调用(tool calling)进化到工具创建(tool creation)。这两个词看着只差一步,实际是完全不同的做事逻辑。
工具调用的前提是“库里有什么,我调什么”。工具创建的逻辑则是“我需要什么,我造什么”。放到地球观测场景里,这个区别来得非常具体。比如我接到一个任务:基于时序光学影像判断某农田区域的灌溉事件发生频次。传统做法是:先找云掩膜工具、再写个植被指数脚本、再用变化检测算法。但如果研究区是那种少见的条播作物,或者观测期正好赶上间歇性多云,标准工具的假设条件就不成立了。此时一个只会调用工具的Agent会卡住,而一个能创建工具的Agent可以自己写一个针对条播行向的纹理滤波算子,丢进沙箱里验证,再把结果注册成新工具供后续复用。
1.1 从固定工序到自适应工作流
遥感处理的一个传统思维是工序化:你定义输入、定义中间步骤、定义输出,然后把它们串起来。但开放环境下的地球观测任务,输入本身就不稳定。举例来说,同一景影像,可能是光学传感器拍的,也可能是SAR;分辨率大不相同;坐标参考可能不一致;云区分布千变万化。你以为写好的处理逻辑能够应对这些变化,实际上每次都会冒出新情况。
这时候Agent的价值在于“决策”而不是“执行”。它需要像一个有经验的分析师,先观察数据特征,再判断流程怎么走。而工具创建机制,则让Agent不再受限于“只会在已有函数集合里挑函数”。它是一个站在更高维度的工作方式——先理解任务目标,再审视手头有什么工具,如果发现工具空缺,就尝试自己补上。
这种自适应工作流设计上有一个关键点:必须把“任务规划”和“工具执行”完全解耦。任务规划层只负责生成计划,工具执行层只负责具体计算。如果两层耦合在一起,Agent一旦遇到工具缺失就会全线崩溃;解耦之后,工具缺失只是一个需要处理的分支状态,Agent可以从这个状态转入“工具创建”的子流程。
1.2 工具调用已不满足开放场景的三个理由
有不少人会觉得,先把工具调用做好就不错了,为什么要一步跨到工具创建。我梳理了三个理由,都是实际干活中会碰到的。
第一,长尾需求覆盖不了。地学任务千奇百怪,官方SDK和开源库覆盖的是高频操作,比如读取栅格、计算NDVI、影像裁剪。但很多研究任务面向特定区域、特定作物、特定地质现象,这些场景需要的是定制算子。如果只依赖已有工具,Agent回答不了长尾问题。
第二,工具组合不等于能力涌现。你可以让Agent把五个工具串起来得到一个新功能,但串起来的本质还是“已有能力的排列组合”。工具创建不一样,它产生的是新的能力——比如写一个新的损失函数来适应特殊噪声分布。这种能力不是现有工具组合能等价实现的。
第三,数据格式和口径不可控。开放环境拿到的数据,命名方式乱七八糟,单位混用,投影不统一。Agent如果只调用现成工具,就必须花大量时间在做格式适配。但工具创建的机制下,Agent可以为这批特定格式的数据生成一个专门的读取和标准化工具。这不仅更快,而且这个工具可以留档,下一次遇到相似格式直接复用。
1.3 工具创建要解决的痛点:已有工具覆盖不了的长尾
我在实际测试中观察到一个现象:标准数据集上的任务,工具调用能力的利用率很高;但切换到开放数据之后,Agent的调用成功率断崖式下降。原因很直接:标准数据集的字段名、位深、坐标系都是预设好的,而这些信息恰恰是开放数据最不可控的部分。
工具创建机制解决的痛点就在这里。它允许Agent动态扩展自己的函数库,把一次性解决方案沉淀为可复用的新工具。这个过程不是让Agent学会某个遥感算法,而是让它学会“怎样快速构建一个遥感算法工具”的能力——前者解决一个问题,后者解决一类问题。
2. 系统整体设计:模型、编排、工具三层解耦
把OpenEarth-Agent拆开看,它其实是一个三层架构:模型层、编排层、工具层。这三层职责分离,各自独立演化。
模型层一般是一个对话式大语言模型,也可以是带视觉能力的多模态模型。它负责理解任务诉求、生成处理方案、编写工具代码。编排层是核心调度器,它维护一个任务状态机,负责拆解任务、分派执行、收集结果、判断何时需要创建新工具。工具层则是工具注册表和沙箱执行环境,承载已有工具和新生成工具的运行。
这三层解耦的巧妙之处在于:模型层不需要知道所有工具的细节,它只需要知道“工具是什么、能干什么、怎么调用”;编排层不关心工具具体内部实现,它只负责调度和决策;工具层不关心任务高层语义,它只需要提供确定性的计算能力。
2.1 任务规划层:意图分解与可验证子目标
任务规划是整个系统里最难做的一块。一个开放任务经模型理解后,往往要拆解成多个子目标,而每个子目标都需要有明确的验证标准。比如任务“评估某区域洪水淹没区域的变化”,拆解出来可能是:获取时序影像、自动识别水域、计算淹没范围、评估变化趋势。其中“获取时序影像”是可执行的,“自动识别水域”是可执行的,但“变化趋势评估”则是偏分析性的,需要前面步骤的结果作为输入。
这个拆解过程必须形成可验证的子目标,否则Agent不知道自己做完了没有。我的实践经验是,子目标的验证标准一定要可量化,不要写“分析影像”,而要写“输出逐景水体掩膜GeoTIFF,空间分辨率不低于30米,缺失数据低于5%”。这样编排层在执行完每个子目标后,可以对照验证标准决定是否进入下一步。
2.2 工具管理层:已有工具的注册、描述与检索
工具管理层的设计,决定了Agent能否在正确的时间找到正确的工具。我建议每个已注册工具至少包含以下字段:
- 工具名称:唯一标识,短且无歧义
- 功能描述:用自然语言写清楚适用场景,越具体越好
- 输入输出规格:数据格式、波段、坐标系、分辨率要求
- 使用限制:哪些条件下不适用,有哪些已知边界
- 依赖信息:需要哪些Python包、是否依赖外部服务
检索策略上,一般先用语义检索召回候选工具,再用规则和元数据过滤。这里有一个很容易踩的坑:如果工具描述写得含糊,语义检索会把一堆不相关的工具都召回,编排层就要花大量时间试错。所以工具描述宁可啰嗦,不能模糊。
2.3 工具创建层:从代码生成到注册上架
工具创建层的流程,可以理解为一条小型的“从需求到上线”的开发流水线,只不过开发者变成了Agent。需求来自哪里?来自任务规划和工具检索之间的缺口。当编排层发现没有任何已注册工具能完成当前子目标时,它不会报错退出,而是进入创建流程。
创建流程的第一步是生成工具规格说明,第二步是让代码生成模块基于规格写代码,第三步是沙箱执行验证,第四步是把通过验证的工具注册进工具库。这一步是整个系统最有创新价值的部分。传统Agent遇到工具缺失时,只能告诉用户“当前能力不足”,而OpenEarth-Agent则是把“能力不足”转化为“能力建设”,相当于一个自动化的能力演进机制。
3. 工具创建的三个实现阶段
工具创建看着很先进,拆开实现其实是可以落地的三个阶段。每个阶段都需要仔细设计,任何一个环节出问题,整个创建流程都会失败。
3.1 阶段一:工具需求发现与规格提取
工具需求不能凭空生成,一定是从某个具体的执行受阻中产生的。比如编排层在执行“提取研究区不透水面”这个子目标时,检索到三个工具:一个做NDVI计算、一个做地表覆盖分类、一个做矢量裁剪。看起来好像够用,但真正执行时发现:NDVI工具依赖的是多光谱数据,而实际数据是SAR影像,波段对不上。此时工具调用失效,编排层就捕获到一个工具缺口:需要“从SAR数据提取不透水面特征”的工具。
这个阶段的关键是实现自动的“规格提取”:从任务上下文、数据元数据、失效原因三个维度生成新工具的规格说明。数据元数据尤其重要,因为它能告诉代码生成模块,可用数据有哪些波段、坐标系、分辨率。没有这些信息,生成的代码很可能只是一段漂亮但无法运行的伪代码。
规格提取的质量决定工具能否一次写对。我的做法是这样:规格说明里至少包含输入变量表、输出描述、处理步骤、边界条件四项。边界条件一定要写全,比如输入影像全为云区时怎么办、坐标系不一致时是否需要重投影、输出单位是什么。边界条件写全了,生成的代码才具备健壮性。
3.2 阶段二:代码生成与本地沙箱验证
代码生成阶段依赖一个关键的设计决策:生成的代码必须是无依赖自由函数,至少逻辑核心部分要如此。所谓无依赖,是指这个函数接收numpy数组和基础参数,返回处理结果,不直接读取文件、不调用外部API。这样做的好处是验证速度快、失败原因容易排查、IO边界清晰。
沙箱验证要做两步。第一步是编译检查,确认代码语法正确、函数签名与规格一致。第二步是逻辑验证,用样本数据跑一遍,对比输出是否符合预期。逻辑验证不能只看结果有没有输出,还要做数值合理性检查。比如不透水面提取的结果,比例值应该在0到1之间;掩膜数据应该是整型;坐标范围不能偏离输入影像的范围。
这里我建议在沙箱环境中加一个资源限制:内存上限、运行时间上限、CPU核数限制。原因是遥感数据往往很大,一个不小心写了个死循环或者超O(n^2)复杂度的算法,能直接把沙箱拖垮。加了资源限制之后,运行超时的候选工具直接判为“未通过验证”,不会影响整个系统的稳定性。
3.3 阶段三:结果注册与下一次复用
工具验证通过之后,最关键的一步就是注册。注册不是把代码存个档那么简单,它需要一个质检环节。我对注册的定义是:只有通过验证且有明确描述的工具才允许写入工具库。描述包括功能描述、参数说明、适用数据范围、失败模式说明。
注册完成后,这个新工具立即进入工具检索索引,此后这套数据区域内再遇到类似任务,Agent可以直接调用它,而不需要重新创建。这就形成了“创建一次、反复受益”的效果。随着任务数量的增加,工具库会持续膨胀,但检索层会把最匹配的工具优先召回,所以工具库规模变大并不会显著拖慢检索速度。
这个阶段的注意事项是:不能把所有创建的“一次性脚本”都注册为长期工具。有些脚本高度特化,只能处理某一种特定格式的输入,注册进库里反而会干扰后续检索。一个实用策略是给新工具打上置信度标签,首次创建的工具默认为“实验性工具”,必须经过三次以上成功调用才能转正为“正式工具”。这个机制能有效避免工具库被低质量代码污染。
4. 开放环境下的鲁棒性设计
地球观测数据的不可预测性,是每个做系统的人最先感受到的现实。影像可能缺波段,矢量数据可能有自相交错误,坐标系可能不统一。OpenEarth-Agent如果想在开放环境真正可用,鲁棒性设计是最不能省的部分。
4.1 数据异常检测与工具失败回退
我比较推崇的一个设计原则是:所有工具调用都必须包一层结果检查,而不是默认工具返回了正确结果。结果检查包括三方面:输出是否为空、输出维度是否与规格一致、数值范围是否合理。任何一个不符合,都判定为工具执行失败。
工具失败之后,编排层不能原地崩溃,而要有一个回退策略。回退策略可以按优先级展开:第一,检查是否存在参数不同的替代调用方式;第二,检索是否存在功能相似的备用工具;第三,启动工具创建流程;第四,如果以上都不行,才把这个子目标标记为“无法完成”并如实向用户反馈。这四层回退机制,能把任务执行的成功率提升一个量级。
4.2 多工具组合调度与依赖关系管理
地球观测任务很少是单个工具就能做完的,更多的情况是多个工具协同。例如做时序变化检测,一般链路是:影像预处理 -> 辐射归一化 -> 指数计算 -> 变化向量分析 -> 输出专题图。编排层需要管理工具间的依赖关系,确保每个工具的输入是由上一个工具的输出转化而来。
工具间依赖管理的一个关键点是:输出与输入之间的格式适配。很多时候上一个工具输出的栅格单位是反射率,而下一个工具期望的输入是辐亮度,如果不做转换直接丢进去,结果就错了。这里比较好的设计是引入“数据状态检查”:每个工具执行前,先检查输入数据的状态是否符合工具要求。不符合时,启动一个格式转换子流程,而不是直接执行。
格式转换本身也可以作为一个工具来创建。遇到新的数据格式,Agent可以写一个转换工具,转换逻辑验证通过后注册进工具库。这样一来,系统越用越顺手,越来越多的数据格式被打通,工具库的形态也会从“单一处理工具”变成“处理+转换+适配全覆盖”的综合工具集。
4.3 元工具与工具演化:工具创建后形成的生态
我在项目设计中最看重的一个能力是元工具,也就是“工具的生成工具”。工作流里,元工具的职责是:根据任务规格自动生成新的工具函数。这个元工具本身也是一个可以被调用的工具,只是它的输出不是地理分析结果,而是“一个可以通过验证的新工具”。
这种设计带来一个很有意思的生态效应:工具库不再是静态的,而是会随着任务流不断演化。新工具注入工具库,工具库反过来增强后续任务的处理能力。时间拉长以后,系统对某个特定区域的适配能力会越来越好,因为它的工具库已经围绕该区域的常见任务形态沉淀了大量专有工具。
这种演化形态特别适合长期观测场景。比如某个固定研究区的生态监测项目,每个月都有新的影像数据进来,每个季节都有不同的分析任务。传统做法是每年都要人工去适配新的分析需求,而工具演化机制让这个适配过程自动化:第一年系统还需要频繁创建新工具,第二年创建次数显著减少,第三年绝大部分任务都可以直接从工具库检索得到解决方案。
5. 实测效果:从工具调用到工具创建的收益量化
判断一个架构设计好不好,最终还是要落到数据上。我在模拟项目中做了多组对照测试:一组只允许工具调用,不允许工具创建;另一组启用完整工具创建能力。测试任务集包括水体提取、不透水面估算、植被覆盖度变化检测、特定地物目标识别等。
5.1 任务成功率提升
在标准数据集上,两组的成功率差距不算大,工具调用组的成功率能达到80%左右,完整组的成功率在85%上下。但切换到开放数据后,差距一下就拉开了。工具调用组的成功率骤降到40%上下,大量任务卡在工具不匹配或数据格式不对上。启用工具创建后,成功率能稳定在70%以上,而且随着工具库积淀,这个数字还会持续上升。
成功率之外,还有一个更值得关注的维度:人工干预次数。工具调用组在开放数据上的平均任务需要4到6次人工干预,几乎每个环节都要人盯着。工具创建组在前几次任务时干预次数较高,但同一类型任务跑过几遍之后,干预次数显著降到了1次以下。这说明工具创建机制带来的不只是单次任务成功率的提升,更是整个系统的自我完善能力。
5.2 参数覆盖与配置策略建议
从我实测的配置来看,有四个参数对整体效果影响最大:任务拆解粒度、工具描述长度、沙箱资源配额、工具注册转正阈值。
任务拆解粒度决定了Agent能把一个大任务细化到什么程度。粒度太粗,工具选择的精度低;粒度太细,步骤数量膨胀,错误率累积。我的经验是把每个子目标控制在“一个工具能完成”的粒度,如果某个子目标仍然需要多个逻辑步骤,就继续往下拆。
工具描述长度直接影响检索准确率。我试过用一句话描述工具,检索召回率惨不忍睹;也试过写三百字的长描述,召回精度明显提升,但描述本身的生成成本也高了。折中方案是写八十字到一百五十字的结构化描述,涵盖适用场景、输入规格、输出规格、限制条件。
沙箱资源配额方面,我建议内存给2GB以内,单工具运行时间最长300秒。超过这个阈值,大概率是算法有问题或者死循环,直接判失败比等它跑完更划算。
工具转正阈值我建议默认3次成功调用。低于这个阈值时工具保留在“实验性”状态,只在精确匹配时才会被调用,避免干扰正常检索。三次成功之后转正,进入常规候选池。这个机制我实测下来能有效降低工具库噪声。
下面是我整理的参数建议表:
| 配置项 | 推荐值 | 备注 |
|---|---|---|
| 子目标粒度 | 单工具可完成 | 过大则拆解失败率上升,过小则步骤爆炸 |
| 工具描述长度 | 80-150字 | 必须覆盖功能、输入输出、限制条件 |
| 沙箱内存上限 | 2GB | 超过说明算法不可控 |
| 单工具运行时长 | 300秒 | 超时直接判定失败 |
| 实验工具转正阈值 | 3次成功调用 | 防止低质量工具污染工具库 |
5.3 从零搭建一个最小工具创建循环
要复现OpenEarth-Agent的核心能力,其实不需要重写一个大型系统。我在这节给你一个最小的工具创建循环,用现成的语言模型API和Python环境就能跑通。核心逻辑是三步:识别工具缺口、让模型生成代码、验证并注册。
我用一个简化场景来演示:假设需要从光学影像计算一个自定义植被指数,指数公式是(近红外 - 红色) / (近红外 + 红色 + 0.01),但现有工具库里没有这个函数。Agent需要自己创建一个工具来满足需求。
环境准备:Python 3.10以上,安装numpy、rasterio,以及一个可调用的大模型API客户端库。数据结构上,用一个字典模拟工具注册表,里面存函数名、描述、函数对象。
第一步是让Agent发现自己缺工具。这个检测逻辑可以很简单:把任务需求描述转为向量,与工具库中所有工具的描述做相似度比对。如果最高相似度低于某个阈值(比如0.5),就判定为工具缺失。为了快速演示,可以直接用规则判断:如果任务描述里包含“自定义指数”且工具库里没有对应函数名,就触发创建流程。
第二步是生成工具代码。把工具规格说明发给语言模型,要求生成一个纯NumPy实现的无依赖函数。prompt模板我建议这样写:
task_spec = """ 请根据以下规格生成一个Python函数: - 函数名:custom_vegetation_index - 输入:red (np.ndarray), nir (np.ndarray) - 输出:指数结果 (np.ndarray) - 公式:指数 = (nir - red) / (nir + red + 0.01) - 要求:处理除零情况;函数内不做文件读写 """模型返回的代码存到一个待验证缓冲区。第三步是验证:用随机生成的red和nir数组跑一遍函数,检查输出形状是否正确、值域是否合理、是否出现NaN。验证通过后,把函数对象注册进工具注册表。
下面这个示例代码把上述第三步的核心逻辑写出来了,可以直接跑:
import numpy as np tools = {} def validate_and_register(tool_code, name, desc, inputs, outputs): # 将生成的代码编译为函数对象 exec(tool_code, globals()) fn = globals().get(name) if fn is None: return False, "函数未生成" # 构造测试数据 test_in = [np.random.rand(256, 256).astype(np.float32) for _ in inputs] # 执行函数 try: res = fn(*test_in) except Exception as e: return False, f"执行异常: {e}" # 验证输出条件 if res.shape != test_in[0].shape: return False, "输出形状不一致" if np.isnan(res).any(): return False, "输出包含NaN" # 注册 tools[name] = {"desc": desc, "fn": fn} return True, "注册成功" # 模拟模型返回的代码 generated_code = """ def custom_vegetation_index(red, nir): denom = nir + red + 0.01 return (nir - red) / denom """ ok, msg = validate_and_register( tool_code=generated_code, name="custom_vegetation_index", desc="计算自定义植被指数 (NIR-Red)/(NIR+Red+0.01)", inputs=["red", "nir"], outputs=["index"] ) print(ok, msg)这个最小循环可能还达不到生产级的要求,但它能让你理解整个机制的核心骨架:需求、生成、验证、注册。生产系统里新增的,无非是更复杂的规格提取、更强的代码生成策略、更严格的沙箱和更大的工具库。
6. 调试经验与常见坑
工具创建说起来漂亮,真做起来坑很多。我在实验早期踩过不少,挑几个典型的说说。
6.1 生成代码“看起来对”但数值错误
这种现象非常常见:模型生成代码时逻辑很完整,但忽略了某个关键的物理约束。比如在水体提取工具里,模型生成的阈值分割函数是写对了,但阈值是面向反射率0到1区间的,而实际输入是放大一万倍的辐亮度值。结果就是整幅影像全被标记为水体。
我的解决方案是:在规格提取阶段强制加入数据范围检查,并把数据范围写进prompt里。这样代码生成模块看到的是“输入范围0-10000”,而不是普通的“输入数组”。数值类错误发生率能降低一大截。
6.2 工具描述写得不好导致检索召回混乱
工具库小的时候,描述随便写写也没关系。工具库一旦超过二十个,描述质量直接影响检索准确率。我遇到过工具库里同时有“计算NDVI”和“计算SAVI”两个工具,描述分别是“算植被指数”和“计算土壤调节植被指数”。结果任务说“给我一个抗土壤背景的植被指数”,检索模块把两个工具都召回了,Agent选了NDVI——这就错了。
后来我规定工具描述必须包含三要素:适用数据条件、物理或数学含义、适用场景。SAVI工具的描述改成“在土壤裸露背景下计算SAVI指数,输入近红外和红光波段反射率,输出范围约-1到1,适用于植被覆盖度低、土壤背景干扰强的区域”。这个描述一改,检索准确率立刻上来了。
6.3 工具注册后找不到
还有一种情况:工具明明注册成功了,但后面的任务就是检索不到它。排查下来发现,是索引没有实时更新。注册表写入是新函数对象,但检索用的向量索引是旧版本,没有包含新注册工具的描述向量。
解决办法是在注册流程最后强制触发一次索引重建。对于几十到几百个工具的小规模工具库,重建索引开销很低,完全可以每次注册后全量重建。工具库大到上万级别时,再考虑增量索引。
6.4 安全隔离:给工具创建加一道边界
最后说说安全边界。让Agent自动生成代码并执行,听起来美好,但也意味着系统要承担代码质量风险和安全风险。我在实际设计时做了两层约束。
第一层是沙箱隔离。生成的代码必须在独立进程中运行,不允许直接访问宿主机文件系统。可以用容器技术做这层隔离。第二层是API白名单。生成的工具内不允许访问网络接口,如果确实需要外部数据,必须通过预先定义好的受控接口获取。
这两层约束看起来限制了Agent的能力,实际上反而提升了可靠性。代码生成模型出错是常态,隔离层能把这些错误控制在无害范围内。而白名单机制,则把网络访问的决策权收回到系统设计者手中,避免模型生成不可控的代码行为。
7. 后续扩展的空间
OpenEarth-Agent这个方向,目前的重点是对已有卫星观测数据的分析处理。往后走,它完全可以延伸到更广的领域:把无人机航测数据接入工具链,让Agent创建处理无人机影像拼接的工具;把地面物联网传感数据纳入进来,让Agent在时序数据上自主构建异常检测工具;甚至向多模态方向扩展,让Agent同时处理文本报告、卫星影像、地面照片,统一调度。
我在实际工作中越来越确认一点:工具创建不只是一个技术机制,更是一种系统设计哲学。以往我们总想预先建立一个无所不包的工具库,然后用人力去维护和扩展它。工具创建机制的启示是:与其追求静态的工具完备性,不如让系统具备动态生成能力。这个思路放到任何一个细分领域的技术系统里都成立。
最后分享一个实操层面的建议:不要一上来就尝试实现一个完整的工具创建框架。先把工具调用的闭环做扎实,再尝试引入“单工具自动创建”能力,最后再逐步放开到多工具、复杂任务。路线走得稳,系统才能真正立起来。