OpenGIS 技能插件开发与使用:在 DeepSeek Harness 里按需加载 GIS 知识
OpenGIS 技能插件开发与使用:在 DeepSeek Harness 里按需加载 GIS 知识 - 我才是银古
面向 DeepSeek Harness(DSH)的 OpenGIS 技能插件把开源 GIS 长链路拆成 gdal、geoserver、postgis、openlayers 等按需加载的技能,并用 opengis-all 索引技能串联全流程。
一、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,把知识点连成图。
这个形态背后是三个约束:
- 目录要便宜:模型只靠 name + description 路由,描述必须写成什么时候用我;
- 正文要按需:一个 GIS 任务通常只涉及两三个工具,没必要把整条链路的细节都放进上下文;
- 引用要可解析: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 保存用户自己的配置覆盖。
启动时,配置树按以下顺序叠加:
dsh.profile.bundles中各 bundle 的 patch 层(按列表顺序);- profile 自己的
cordis.patch.yml; $DSH_HOME/cordis.patch.yml(home 级);- 启动参数
--patch指定的覆盖层。
其中 bundle 是"可安装的 patch 层":一个 npm 包通过 package.json 的 dsh.bundle.patch 声明一个或多个 patch 文件。patch 文件是顶层 YAML 数组,insert 向根或指定分组追加插件行;不带 insert 的项按 id 覆盖已有行的字段(例如 disabled、config),config 是整体替换而不是深合并。未知 id 会告警并跳过。
- 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 技能插件的最小目录结构(资产按技能分目录,每个技能可带自己的参考资料):
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 的关键字段如下:
{
"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 项:
- insert:
- id: gis-skills
name: '@your-scope/opengis-skill-pack'
locale/*.json 提供插件页展示文案,字段是 meta.title 与 meta.description;icon.svg 是插件页图标。两者不是"锦上添花":没有它们,插件在插件页里只剩包名与默认插画,排查问题时也不容易确认装的是哪个包。
{
"meta": {
"title": "OpenGIS Skills",
"description": "An OpenGIS knowledge pack loaded on demand."
}
}
最后是插件入口:
// 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):正文与资源根目录。
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 路径是同一套操作:
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 加载:
- 加载
gdal,拿到ogr2ogr的命令与参数说明; - 加载
jts或postgis,按正文里的空间谓词示例做相交判断; - 加载
geoserver,按正文发布 layer 与 style; - 加载
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,用一个最小的
ctxstub 注册 provider,断言目录字段、加载字段、取消信号、未知技能报错、目录项不带正文、definition 不带 rank。
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 插件。
来源:博客园 · cnblogs.com