OpenGIS 技能插件开发与使用:在 DeepSeek Harness 里按需加载 GIS 知识

一、OpenGIS 知识的形态:一条长链路

这里的 OpenGIS 指以开源工具为主干的 GIS 技术栈,而不是某个单一标准或产品。它包含的不是一个工具,而是一条从数据到地图的长链路。一个典型的开源 GIS 工作流会横跨这些阶段:

阶段 代表工具/库 典型任务
数据 I/O 与格式转换 GDAL/OGR、PDAL Shapefile ↔ GeoJSON、栅格裁剪、点云转换
空间分析与几何运算 GRASS GIS、JTS、NetTopologySuite、Shapely、GeoPandas 缓冲区、叠加、空间关系、投影变换
坐标参考系统 PROJ EPSG 查询、基准转换、重投影
空间数据库 PostGIS 空间索引、空间 SQL、栅格
服务发布 GeoServer、MapServer、QGIS Server WMS/WFS/WMTS/OGC API
Web 可视化 CesiumJS、OpenLayers、Leaflet、Mapbox GL JS、deck.gl 2D/3D 地图、矢量瓦片、大规模点渲染
桌面/服务端开发 GeoTools、Mapsui、SharpMap、PyQGIS、qgis_process 二次开发、批处理、桌面地图

每一块都值得写一本手册。对一个 Agent 来说,正确的形态不是把所有手册合并成一篇,而是:

  • 一个工具(或一组强相关工具)一个技能 :gdal、geoserver、postgis、openlayers......
  • 一个索引技能串起全流程 (例如 opengis-all):告诉模型阶段怎么排、每步该加载哪个技能;
  • 技能正文 + 参考资料 :正文负责怎么选、怎么用、坑在哪,reference/*.md 负责命令速查、API 清单、参数细节;
  • 技能之间用相对链接 :../geoserver/SKILL.md、reference/vector-tools.md,把知识点连成图。

这个形态背后是三个约束:

  1. 目录要便宜:模型只靠 name + description 路由,描述必须写成什么时候用我;
  2. 正文要按需:一个 GIS 任务通常只涉及两三个工具,没必要把整条链路的细节都放进上下文;
  3. 引用要可解析:GIS 知识是网状的(GDAL 会提到 PROJ,GeoServer 会提到 PostGIS),相对链接 + 资源根让跨技能跳转成立。

这也是为什么它适合做成 DSH 插件:插件只提供知识目录与按需加载,不注册工具、不抢上下文;工具链本身仍由模型通过 bash、文件系统等通用工具去执行。

二、DSH 的三层模型:profile、bundle、插件

DSH 启动的是 profile 。profile 位于 $DSH_HOME/profiles/<name>,本身是一个 npm 项目:package.json 记录依赖与有序的 dsh.profile.bundles,同级 cordis.patch.yml 保存用户自己的配置覆盖。

启动时,配置树按以下顺序叠加:

  1. dsh.profile.bundles 中各 bundle 的 patch 层(按列表顺序);
  2. profile 自己的 cordis.patch.yml;
  3. $DSH_HOME/cordis.patch.yml(home 级);
  4. 启动参数 --patch 指定的覆盖层。

其中 bundle 是"可安装的 patch 层":一个 npm 包通过 package.json 的 dsh.bundle.patch 声明一个或多个 patch 文件。patch 文件是顶层 YAML 数组,insert 向根或指定分组追加插件行;不带 insert 的项按 id 覆盖已有行的字段(例如 disabled、config),config 是整体替换而不是深合并。未知 id 会告警并跳过。

yaml 复制代码
- insert:
    - id: gis-skills
      name: '@your-scope/opengis-skill-pack'

插件行(entry)里,id 是稳定的行标识,name 是要加载的模块,可以是包标识符、绝对路径或 file URL;相对路径按 patch 文件所在目录解析。config 把配置传给插件,disabled 用于停用。

插件 则是被行加载的 Cordis 模块:导出 name、可选的 inject 和 apply(ctx)。inject 声明依赖的服务,Cordis 会等这些服务就绪后再执行 apply;apply 里注册的东西(服务、监听器、工具、技能提供方)都归调用方 fiber 所有,插件卸载时自动清理。DSH 的启动策略是:可选行加载失败只告警并继续,必需行失败才终止启动。这对插件作者有直接影响:技能插件挂掉通常不会拖垮整个宿主,但它的技能会静默缺席,所以错误信息必须足够可诊断。

bundle 与插件的关系通常有两种:

  • 同包兼任 :bundle 的 patch 插入自己,包的 main 入口就是插件实现。适合"包本身就是能力"的项目。
  • 纯 patch 载体 :bundle 只负责接线,patch 插入另一个依赖包。DSH 官方的徽章技能 bundle 就是这种形态:它自己的入口只有 export {},patch 行指向另一个提供技能的包。

前者简单,后者能让一个能力包被多个组合复用。选哪种,取决于你希望"安装单位"和"实现单位"是否一致。

兼容性方面,bundle 或插件在 engines.dsh 中声明的 DSH 版本范围会在安装前 和启动时 检查;没有 engines.dsh 时退回检查 peerDependencies 中的 DSH 包范围。不满足的 bundle 会被跳过并记录诊断;确需例外时,profile 的 compatibility.json 可以按"精确包版本 + 精确运行时版本"授权豁免,但这是显式的风险确认,不应成为常态。

三、最小 OpenGIS 技能插件:包清单与 bundle 接线

一个 OpenGIS 技能插件的最小目录结构(资产按技能分目录,每个技能可带自己的参考资料):

text 复制代码
opengis-skill-pack/
├─ package.json
├─ cordis.patch.yml
├─ lib/index.js
├─ assets/gdal/SKILL.md
├─ assets/gdal/reference/vector-tools.md
├─ assets/geoserver/SKILL.md
├─ assets/openlayers/SKILL.md
├─ locale/en.json
├─ locale/zh.json
└─ icon.svg

package.json 的关键字段如下:

json 复制代码
{
  "name": "@your-scope/opengis-skill-pack",
  "version": "0.1.0",
  "type": "module",
  "main": "lib/index.js",
  "exports": {
    ".": "./lib/index.js",
    "./cordis.patch.yml": "./cordis.patch.yml",
    "./locale/*.json": "./locale/*.json",
    "./icon": "./icon.svg",
    "./package.json": "./package.json"
  },
  "icon": "./icon.svg",
  "engines": {
    "dsh": ">=0.2.0 <0.3.0",
    "node": ">=22"
  },
  "dsh": {
    "manifestVersion": 1,
    "bundle": { "patch": "./cordis.patch.yml" }
  },
  "files": [
    "lib",
    "assets",
    "cordis.patch.yml",
    "locale/*.json",
    "icon.svg",
    "README.md"
  ]
}

逐项解释:

  • type + main:插件是 ESM 模块,入口导出 name/inject/apply。
  • exports:除了主入口,patch、locale、icon 也要能被解析;DSH 读取插件展示信息时会遵循 Node exports。
  • engines.dsh:声明兼容的 DSH 版本范围;engines.node 约束开发/运行环境。若插件 import 宿主包,还应把对应包写进 peerDependencies。
  • dsh.bundle.patch:声明 bundle 的 patch 文件,可以是字符串,也可以是按顺序应用的文件列表。
  • dsh.manifestVersion:可选的清单格式版本,当前为 1;不声明时相关校验按未声明处理。
  • files:npm 发布白名单。只放运行时真正需要的文件,开发脚本与测试目录不进入安装包。

cordis.patch.yml 只有一个 insert 项:

yaml 复制代码
- insert:
    - id: gis-skills
      name: '@your-scope/opengis-skill-pack'

locale/*.json 提供插件页展示文案,字段是 meta.title 与 meta.description;icon.svg 是插件页图标。两者不是"锦上添花":没有它们,插件在插件页里只剩包名与默认插画,排查问题时也不容易确认装的是哪个包。

json 复制代码
{
  "meta": {
    "title": "OpenGIS Skills",
    "description": "An OpenGIS knowledge pack loaded on demand."
  }
}

最后是插件入口:

js 复制代码
// lib/index.js
export const name = "opengis-skill-pack";
export const inject = ["skills"];

export function apply(ctx) {
  // 在这里注册技能提供方,下一节展开
  ctx.skills.registerProvider(() => provider);
}

inject: ["skills"] 是这里唯一的硬依赖。默认的 Web / Desktop profile 已经挂载了技能注册表与 skill 工具;如果是在自建 profile 里跑,需要确认有 @deepseek-ai/dsh-skill(注册表)与 @deepseek-ai/dsh-tool-skill(目录与工具)。缺了注册表,这一行会一直等待服务,技能自然不会出现。

四、注册技能:OpenGIS provider 协议与按需加载

DSH 的技能子系统是分层的:

  • @deepseek-ai/dsh-skill 提供注册表,把多个来源的 skill 合并成一份目录,并负责重名裁决、校验与按需加载;
  • @deepseek-ai/dsh-skill-filesystem 是一个内置提供方,从项目/用户目录发现 SKILL.md;
  • @deepseek-ai/dsh-tool-skill 是消费方,把目录渲染给模型,并提供 skill 工具与用户 /name 调用。

我们的插件是"另一个提供方"。提供方只有两个动作:

  • list() 返回目录项(candidate):名称、描述、调用策略、来源、rank、资源根目录等元数据;
  • get(candidate, { signal }) 返回加载结果(definition):正文与资源根目录。
js 复制代码
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
// readSkillCatalog() / parseSkillFile() 是示例包内的本地工具函数,负责扫描资产与解析 frontmatter

export const name = "opengis-skill-pack";
export const inject = ["skills"];

export function apply(ctx) {
  const root = fileURLToPath(new URL("../assets/", import.meta.url));
  const skills = readSkillCatalog(root);            // 只解析 frontmatter
  const byName = new Map(skills.map((s) => [s.name, s]));
  const candidates = skills.map((skill) => ({
    name: skill.name,
    description: skill.description,
    invocation: { modelInvocable: true, userInvocable: true },
    provider: "opengis",
    source: "opengis-skill-pack",
    rank: 600,
    path: skill.path,
    locator: skill.path,
    resourceBase: { kind: "directory", path: skill.directory.replaceAll("\\", "/") }
  }));

  ctx.skills.registerProvider(() => ({
    name: "opengis",
    list: () => Promise.resolve(candidates),
    async get(candidate, { signal }) {
      const skill = byName.get(candidate.name);
      if (skill === undefined) throw new Error("unknown skill: " + candidate.name);
      const raw = await readFile(skill.path, { encoding: "utf8", signal });
      const parsed = parseSkillFile(raw, skill.path);  // name + description + 正文
      if (parsed.name !== candidate.name) {
        throw new Error(skill.path + " 的 name 与目录项不一致");
      }
      return {
        name: parsed.name,
        description: parsed.description,
        content: parsed.content,
        resourceBase: candidate.resourceBase,
        provider: "opengis",
        source: "opengis-skill-pack"
      };
    }
  }));
}

这段代码背后有几个值得说明的协议约束。

1)candidate 与 definition 的字段不对称。

candidate 携带 rank、source、provider、locator,用于宿主裁决与来源追踪;definition 不带 rank------技能一旦加载,就不再参与优先级比较。candidate 也绝不能带正文,否则目录就变成了全量正文索引,按需加载失去意义。把这两条写进测试,比写在注释里可靠。

2)加载永远读当前文件。

注册表不缓存 definition,每次 get 都向提供方要当前正文。好处是:改技能正文后,下次加载就是新内容,不需要清缓存。代价是每次加载一次 I/O,对几十 KB 的 Markdown 可以忽略。signal 要透传给 readFile,否则已取消的请求仍会读完整份文件。

3)名称漂移要显式失败。

发现与加载之间文件可能被改。如果 get 解析出的 name 与 candidate 不一致,必须抛错;宿主会因此让目录缓存失效、重新发现,而不是把错名内容交给模型。

4)rank 与重名裁决。

注册表在同一个作用域层内按 rank 升序、提供方注册顺序、提供方内部顺序裁决重名;更近的层会遮蔽更远的层。内置文件系统提供方的根按 100--500 排序,随包内置技能是 600。一个随包 OpenGIS 知识库通常就用 600:用户项目里的同名 skill(rank 更低)会优先覆盖它,符合"本地覆盖内置"的直觉。若想让自己的技能优先,需要更低的 rank,但要清楚这会改变宿主目录的裁决结果。

5)调用策略决定谁能看见。

modelInvocable 控制它是否进入模型目录、能否通过 skill 工具加载;userInvocable 控制用户能否用 /name 调用。两者可以独立组合。不是所有 skill 都适合暴露给模型------一个只给用户手动触发的流程,可以设为 modelInvocable: false。

6)目录只带名称与描述。

模型看到的是持久化的 <system-reminder> 目录,每个 skill 只有名称与规范化后的描述,描述默认截断到 500 字符(可由消费方配置);whenToUse、来源、提供方都不会出现在目录里。这直接决定了插件作者应该把路由信息全部压进 description:写得含糊,技能就不会被选中。同时,加载后的正文没有大小上限,资源也只是"指引"而不是附件------正文里要明确告诉模型"什么时候去读哪个参考文件",而不是假设工具会自动展开它。

GIS 技能尤其吃描述质量:gdal 的描述不该写「GDAL 是地理空间数据处理库」,而该写「Use when converting, reprojecting, mosaicking or analyzing raster/vector data via CLI」;gdal(命令行)、gdal-api(编程接口)、qgis-process(QGIS 批处理)三个技能如果描述都写「处理 GIS 数据」,模型就无法区分。描述是路由协议,不是简介。

7)提供方失败不会让宿主崩,但会让技能缺席。

注册表会隔离提供方的发现失败:失败的提供方被跳过,消费方尽量保留上一份可用目录。反过来,格式错误的 candidate 会在校验阶段快速失败。因此提供方要做两件事:apply 时把资产校验做完(坏资产早失败),list 只返回内存中的候选(快);把读盘和解析留给 get。

8)目录失效是显式的。

注册表没有 TTL。提供方工厂可以拿到 invalidate(),在资产变化时主动让目录失效;静态随包资产通常不需要,重载插件或新开会话即可。这也是为什么"升级插件后要重载并新开会话"不是多余步骤。

成本账

这套 OpenGIS 技能包有三十余个技能、五十余万字符的 Markdown。如果只把 name + description 放进目录,大约 8,000 字符、2,000 token 量级;正文留到命中后再加载。目录常驻上下文,所以描述长度是硬成本:技能越多、描述越长,每一轮都要为目录付费。这也是为什么 OpenGIS 知识型插件的第一设计目标不是"写得多全",而是"索引多便宜"。

五、使用:安装、启用与一次 GIS 任务

安装

Web / Desktop 的侧栏插件 页是主要入口。添加插件接受四类 spec:

  • npm 包名(可带版本、标签或别名);
  • Git 仓库地址;
  • tarball;
  • 本地绝对路径。

安装前,宿主会先解析 spec(包名走 registry 查询,本地路径读 package.json),确认它是不是包、有没有 bundle patch,并在 pnpm 真正执行前检查 DSH 版本兼容性;没有 dsh.bundle.patch 的普通依赖会被拒绝,因为它不是插件。安装动作本身是在 profile 目录里执行 pnpm;构建脚本被 pnpm 拦下时,页面会要求单独批准。安装成功不等于激活成功,页面会在安装完成后给出"立即启用",或保留已安装但关闭。

CLI 路径是同一套操作:

bash 复制代码
dsh plugin --profile web add @your-scope/opengis-skill-pack

它把参数转发给 profile 目录里的 pnpm。desktop profile 由 Electron 应用独占,CLI 会拒绝管理它的插件;桌面端有内置的命令运行时来做同样的事。

本地开发时,直接把本地绝对路径加进 profile 最方便:它引用源码目录,改完代码重新构建并按重载策略生效,不需要每次发版。正式分发再走 npm 包与版本号。

启用与生效

插件页里的 bundle 开关改变 profile 的有序 dsh.profile.bundles 列表:关闭只是取消选择,不卸载依赖;重新开启会追加到列表末尾,可能改变层优先级。单个插件行的开关则写入 profile cordis.patch.yml 的 disabled 覆盖,不会篡改 bundle 自己的 patch。

生效方式取决于 profile:

  • 启用 dsh-hmr 的 profile(随附 Web 模板默认如此)会重新组合配置树并重载受影响的插件;
  • 没有 HMR 的 profile 需要重启进程;
  • 技能目录在会话的步骤边界装配。对于没有 watcher、也不调用 invalidate() 的静态提供方,新开会话是最可靠的刷新方式;升级包之后同样应重载并新开会话。

会话内

启用并新开会话后,模型会在首个请求前收到一份持久目录(<system-reminder> + <available_skills>),列出每个技能的名称与截断后的描述。之后:

  • 模型用 skill 工具、以精确名称加载技能;用户也可以在输入框里直接输入 /name,把同一份指令注入当前步骤;
  • 加载结果是一段 <skill_content>,包含资源指引与 <skill_instructions>。资源根是目录时,模型会看到 "Base directory for this skill: ",并被告知相对路径要基于它解析;
  • 正文作为工具结果进入历史,后续步骤会继续携带,直到压缩;正文修改不会改写已经产生的结果;
  • 如果名称无效、技能不存在或不可模型调用,工具会返回明确错误(invalid skill name / unknown or no longer available / not available for model invocation)。

所以"用"插件其实只有三步:装、启用、新开会话;剩下的都是模型与目录协议在工作。

一次 GIS 任务

用户输入:把 data/roads.shp 转成 GeoJSON,和 existing/pipes.shp 做一次相交检查,发布成 WMS,再在网页上叠加底图。

目录里相关的条目通常只有几条:gdal(格式转换与命令行)、jts 或 postgis(空间关系)、geoserver(服务发布)、openlayers(网页叠加)。模型按描述选中它们,逐个用 skill 加载:

  1. 加载 gdal,拿到 ogr2ogr 的命令与参数说明;
  2. 加载 jts 或 postgis,按正文里的空间谓词示例做相交判断;
  3. 加载 geoserver,按正文发布 layer 与 style;
  4. 加载 openlayers,拿到 WMS 图层与底图的接入写法。

正文里的 ../geoserver/SKILL.md 与 reference/vector-tools.md 都相对加载结果的 Base directory 解析;需要细节时再读参考资料。整个过程中,其余二十多个技能的正文都没有进入上下文------这就是整包分发、按技能加载的意义。

排查

现象 先查什么
插件页看不到包 包有没有 dsh.bundle.patch;装的是不是 bundle;spec 是否被 inspect 拒绝
bundle 启用了但目录没有 插件行是否真的 insert;provider apply 是否报错(可选行失败只告警);profile 有没有技能注册表与 skill 工具
技能目录为空 inject: ["skills"] 的服务是否就绪;是否还在旧会话;是否被兼容性检查跳过
工具报 invalid skill name 调用 skill 时传的名字不是合法 kebab-case
目录里没有某个技能 frontmatter 的 name 不合法、与目录项不一致,或资产解析失败
同名技能不是自己的版本 rank 与层优先级:项目/用户目录的 skill 可能覆盖随包技能
相对资源找不到 resourceBase 是否是绝对路径、是否统一正斜杠;资源文件是"指引",模型要自己读
改了正文没生效 提供方是否重新加载/新会话;目录 digest 不看正文,旧工具结果不会变
装完启动报缺服务 inject 声明的服务在 profile 里不存在;entry 会等待或失败

调试组合后的配置有两个直接手段:dsh --dump-config 打印最终插件树,dsh --dump-config-schema 打印各插件的配置 schema。插件页与宿主日志会给出 bundle 跳过、兼容性拒绝、apply 失败的原因。

六、开发配套:校验、测试与发布

OpenGIS 技能插件虽然是"知识包",发布检查一点不能少。建议至少覆盖:

  • 包清单校验 :dsh.bundle.patch 指向真实文件;exports 每个目标可解析;engines 范围合法;files 包含运行所需资产、不包含开发脚本与测试目录。
  • bundle patch 结构校验:按行检查是否存在启用的 insert 行指向本包。不要用"字符串包含"------被注释掉的行也会命中。
  • 展示元数据校验 :locale 文件名是合法语言 id、meta.title/description 非空;icon 存在、体积在限制内、内容像 SVG。
  • 技能资产校验 :每个 <skill>/SKILL.md 可解析、name 唯一且 kebab-case;相对链接全部可解析且不越界;文本文件无 BOM/CR 等跨平台污染。
  • GIS 内容校验:命令示例与参数无法可靠地自动执行,至少要做代码块语法检查、命令拼写抽查与版本标注;跨技能引用与参考文件必须全部可解析。
  • 技能目录测试 :不启动 DSH,用一个最小的 ctx stub 注册 provider,断言目录字段、加载字段、取消信号、未知技能报错、目录项不带正文、definition 不带 rank。
js 复制代码
let provider;
const ctx = {
  skills: {
    registerProvider(create) {
      provider = create({ signal: new AbortController().signal, invalidate() {} });
    }
  }
};
apply(ctx);

const candidates = await provider.list({ signal: new AbortController().signal });
assert.equal(candidates.length, skills.length);
assert.equal(Object.hasOwn(candidates[0], "content"), false);

再往上就是发布门禁与 CI:npm pack --dry-run 确认实际进入 tarball 的文件;在 Linux 与 Windows 两个平台跑同一套校验与测试(路径分隔符、CRLF、BOM 这些问题只在另一个平台暴露);CI 的动作固定到提交 SHA,避免构建链本身成为供应链缺口。DSH 版本兼容性建议做成矩阵:engines.dsh 声明你验证过的范围,CI 至少覆盖边界版本。

七、知识快照:让 OpenGIS 知识离线可用且可验证

OpenGIS 技能插件常见的一个选择是:运行时去上游拉取,还是随包分发快照。随包分发的理由很实际:运行时不需要网络、Git 或认证;安装结果可复现;知识版本与包版本绑定;升级是一次显式动作。代价是知识不会自动最新,需要维护一条同步流水线。

同步不是 cp -r:

  • 字节可复现:同一个 commit 在 Windows 上 checkout 可能是 CRLF、在 Linux 上是 LF。文本统一规范成 LF(含 NUL 的二进制原样保留),发布校验再反向拒绝文本里的 CR 字节。
  • 来源可证明:清单记录上游 commit,但只接受"源目录本身就是 Git 检出根"的输入。否则一个嵌套在别的仓库里的副本会把外层仓库 HEAD 记成上游,后续所有基于它的校验都会"正确地验证一个错误的前提"。
  • 完整性清单:每个文件一个 SHA-256,键是相对资产根的 POSIX 路径;再加 schema 版本、hash 算法、技能列表与 commit。这样发布校验不依赖上游 checkout,也能发现文件被手改、缺失或夹带。
  • 文档由数据生成:技能数量、技能表格这类信息从资产生成,用 HTML 注释标记界定生成区;校验器比对生成结果,避免"文档和资产各说各话"。

清单防的是手滑与漂移,不是恶意篡改------资产与清单被同时修改时它无法发现;要升级到防篡改,需要签名或外部可信源。另外,随包再分发上游知识还要记录许可证与署名,并随包携带 LICENSE/NOTICE;"可复现"最终要能证明的不只是字节,还有再分发权利。

八、踩坑清单

现象 根因 防线
插件页看不到 缺 dsh.bundle.patch,或装成了普通依赖 清单校验 + inspect
启用后技能目录为空 provider apply 失败被当作可选行告警 apply 期资产校验、错误带路径、启动日志
目录里描述被截断 消费方默认 500 字符上限 描述写短、把细节放正文
前台能 /name、模型看不到 modelInvocable: false 明确调用策略并按组合测试
同名 skill 被覆盖 rank / 层优先级 显式选择 rank,测试同名场景
资源链接在 Windows 失效 resourceBase 带反斜杠 统一正斜杠 + 测试断言
改了正文不生效 definition 不缓存但旧结果仍在;静态提供方无 watcher 重载/新会话;正文变更不改目录 digest
升级后还是旧技能 包是静态文件快照 重载 + 新会话
安装被拒 engines.dsh 不兼容;构建脚本未批准 声明兼容范围;按提示批准脚本或授权豁免
加载报 unknown 名称不一致 / 被禁用 / 提供方被跳过 校验 name、检查插件日志
技能切得太细 一个命令行工具一个技能,目录膨胀 按工具/任务链路切分,用索引技能串流程
描述像百科,模型不选 只写是什么,没写何时用 用 Use when ... 写触发条件与任务边界
跨技能引用断链 技能改名/移动,../x/SKILL.md 失效 发布前链接校验 + 测试

九、边界与取舍

  • 知识型插件不提供工具。 它不注册工具、不注入系统提示段、不写会话状态,只向技能目录贡献条目。这样它能和工具型插件自由组合,而不是互相抢占上下文。
  • 领域知识不等于执行能力。 插件只回答怎么做,不执行 GDAL/GeoServer;真正的命令执行、环境准备和数据访问仍由通用工具与用户环境负责。
  • 零宿主内部依赖。 只用 Node 内置模块,插件与宿主之间只有 ctx.skills 一个接口。代价是拿不到 BUNDLED_SKILL_RANK 之类的常量与类型,需要自己维护(并用校验和测试锁住);收益是宿主升级时需要适配的面积很小。
  • list 快、get 慢。 apply 时把资产解析成目录,list 只返回内存数组;读盘留给 get。如果技能数量到几千,需要另做索引与失效策略。
  • 不做 TTL 与 watcher。 静态 OpenGIS 知识包用重载/新会话刷新,简单可靠;需要热更新的场景应当接提供方的 invalidate(),或者直接用 dsh-skill-filesystem 那类带 watcher 的提供方。
  • 目录能力有边界。 模型只看到名称与截断描述,whenToUse 不会出现;技能正文没有大小上限;资源不会被自动读取。这些约束决定了知识必须组织成"短描述 + 分层参考文档"。

结语

把 OpenGIS 这条长链路交给 Agent,表面上是提示词工程,实质上是软件工程:bundle 负责接线与启停,Cordis 负责生命周期,技能注册表负责目录与按需加载,提供方负责资产与引用,校验与测试负责让这些知识在升级、跨平台和多人协作时不至于悄悄坏掉。把这五层各自守好,一个 OpenGIS 技能包才真正成为一个可安装、可启用、可排查、可升级的 DSH 插件。

项目地址:https://github.com/znlgis/dsh-opengis-skill

相关推荐
sbjdhjd5 小时前
从签名重放到 Agent Harness:一堂安全课程如何把前端逆向、MCP 与工程约束串起来
网络安全·ai·github·agent·webshell·mcp·harness
奇牙coding5 小时前
Codex CLI 升级后多文件编辑静默失效:config.yaml 枚举变更排查与修复指南
java·前端·javascript·ai
全栈练习生5 小时前
AI安全与红队测试
python·ai
七夜zippoe6 小时前
Spring AI 是什么?Java 工程师的 AI 开发第一站
java·人工智能·spring·ai·开发
海带紫菜菠萝汤6 小时前
PostgreSQL 19 延期一个月:AI 把找 bug 变便宜了,修 bug 还是人工价
人工智能·ai·程序员·架构·大模型
运维开发王义杰6 小时前
AI 视频怎么选格式?从 MP4、MP3 到编码、剪辑与流媒体
ai
老牛日常随笔7 小时前
把ElevenLabs装进电脑:VoiceStudio如何用35K Star把语音克隆从云端搬到本地
人工智能·ai·电脑
运维开发王义杰7 小时前
Kokoro 和微软 TTS 怎么选?先分清本地模型与在线服务
ai
ALINX技术博客7 小时前
ALINX 亮相 2026 工博会,聚焦 FPGA 行业应用场景
ai·fpga开发·射频工程