ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

WorkBuddy GIS空间分析自动化实战:MCP协议与Skill编码落地指南

WorkBuddy GIS空间分析自动化实战:MCP协议与Skill编码落地指南 1. 这不是一份“指南”而是一份真实办公现场的作战手记WorkBuddy 这个名字最近在技术圈和效率圈反复刷屏但很多人点开官网、下载安装、打开界面后第一反应是这到底是个啥它不像传统办公软件那样有明确的“文档”“表格”“演示”三大件也不像编程工具那样有清晰的编辑器、调试器、终端三件套。它更像一个刚被塞进你电脑里的、有点腼腆但潜力巨大的新同事——你得先搞清楚它擅长什么、怕什么、怎么跟它说人话它才能真正帮你干活。我从去年底开始把 WorkBuddy 接入日常研发流程从最初用它自动整理每日站会纪要到后来让它接管整个项目周报生成、API文档初稿撰写、甚至辅助做代码审查意见草拟中间踩过坑、改过三次工作流、重写过七版 Skill 脚本。这篇《WorkBuddy 行业应用指南》不讲概念、不列功能菜单只分享一件我用它完整闭环落地的真实任务用 WorkBuddy 自动化完成 GIS 空间分析报告的生成与分发。这个任务背后藏着 MCP 协议如何真正落地、Skill 如何被“喂养”出业务理解力、以及一个 AI 办公工具从“能用”到“敢用”的关键分水岭。如果你正卡在“装好了但不知道干啥”、“写了 Skill 但跑不通”、“MCP 配置了一堆但没效果”的阶段这篇文章里每一个参数、每一行日志、每一次失败重试都是我亲手试出来的答案。2. 为什么选 GIS 空间分析报告作为突破口2.1 这个任务完美暴露了传统办公工具的“失语症”GIS地理信息系统空间分析报告是我们团队每月固定产出的交付物。典型流程是从 PostgreSQLPostGIS 数据库中提取某区域的地块数据、人口热力数据、交通可达性数据用 QGIS 或 ArcGIS Pro 做缓冲区分析、叠加分析、密度计算导出结果图层和统计表格再手动粘贴进 Word 模板配上文字说明最后 PDF 导出、邮件发送给规划部门。整个过程耗时约 4–6 小时其中 80% 的时间花在“搬运”上复制坐标范围、核对字段名、调整图例位置、检查页眉页脚是否错位。这不是技术问题而是典型的“信息搬运工”困境——数据在系统里人在系统外工具在中间当哑巴。提示很多团队误以为 AI 办公就是让 AI 写 PPT。但真正的价值起点是让 AI 成为那个“不说话但永远在线”的搬运工把散落在数据库、Excel、PDF、邮件附件里的碎片信息按业务规则自动拼成一张完整的图。2.2 WorkBuddy 的三层能力刚好切中要害我之所以选这个任务试 WorkBuddy是因为它同时调用了平台最核心的三个能力层缺一不可MCPModel Control Protocol协议层这是 WorkBuddy 的“神经系统”。它不是简单地调用一个大模型 API而是定义了一套标准化的指令集告诉模型“你现在是 GIS 分析专家你要用 PostGIS 语法写查询输出必须是 GeoJSON 格式坐标系强制为 EPSG:4326”。MCP 把模糊的“帮我分析一下”转化成精确的“执行以下 SQL 并返回指定结构”。我在配置 MCP 时发现官方文档里写的tool_call示例太理想化真实场景中必须加一层“参数校验中间件”——比如用户输入的“朝阳区”必须先通过行政区划 API 转成 WKT 多边形否则直接丢给 PostGIS 会报错。Skill 编码层Skill 编码193/247这是 WorkBuddy 的“肌肉记忆”。Skill 不是 Python 脚本而是一套带上下文感知的声明式逻辑块。比如skill_gis_buffer_analysis这个 Skill它的核心不是写ST_Buffer(geom, 500)而是定义“输入一个行政区名称 缓冲距离输出一个包含原始边界、缓冲区、交集面积的 GeoJSON FeatureCollection失败兜底返回错误码 可视化建议如‘请确认该区域在数据库中有对应记录’”。我最初用 Skill 编码193 写的版本在测试时总卡在坐标系转换环节后来翻开源社区 issue 才发现PostGIS 的ST_Transform函数在不同版本中对 SRID 的处理逻辑不一致必须在 Skill 中显式指定ST_Transform(geom, 4326)而非依赖默认值。工作台Workbench集成层这是 WorkBuddy 的“办公桌”。它把 MCP 指令、Skill 执行、外部工具QGIS CLI、GDAL、邮件服务串成一条流水线。关键在于“状态透出”——每一步执行完WorkBuddy 必须把中间产物如生成的 GeoJSON 文件路径、QGIS 渲染的日志、PDF 生成时间戳实时推送到前端 UI。我第一次部署时工作台页面一直显示“正在处理…”但后台日志早显示成功排查三天才发现是 WorkBuddy 默认的 WebSocket 心跳超时设为 30 秒而 QGIS 渲染一张高分辨率地图平均耗时 42 秒必须在workbuddy.yaml里把websocket.timeout改成60。2.3 它避开了新手最容易栽的三个“概念陷阱”很多用户一上来就想做“全自动会议纪要”结果两周都跑不通本质是掉进了抽象陷阱陷阱一“AI 会自己找数据”WorkBuddy 不会主动连你的数据库。它需要你明确配置 MCP 的data_source字段且必须提供带最小权限的只读账号。我们生产环境的 PostGIS 实例启用了 SSL 双向认证WorkBuddy 的 JDBC 连接字符串必须写成jdbc:postgresql://host:5432/gisdb?sslmodeverify-fullsslcert/path/to/client.crtsslkey/path/to/client.keysslrootcert/path/to/root.crt漏掉任何一个参数连接就静默失败。陷阱二“Skill 就是写代码”Skill 编码247 的语法看着像 YAML但它有严格的执行时序约束。比如output_format: geojson这一行必须放在steps块之后、error_handling之前否则解析器会报invalid order: output_format before steps。这个错误不报在控制台只出现在 WorkBuddy 后台的skill_validation.log里而且日志级别默认是 WARN得手动改成 DEBUG 才能看到。陷阱三“工作台就是可视化界面”工作台的 UI 组件如地图预览框、进度条、下载按钮不是静态 HTML而是由 Skill 的ui_schema动态渲染的。我最初以为只要返回 GeoJSON 就能自动渲染地图结果页面一片空白。后来查源码发现WorkBuddy 的地图组件只认FeatureCollection结构且properties里必须包含layer_name和legend_info两个键否则拒绝渲染。这属于“约定大于配置”的隐性规则文档里根本没提。3. 实战拆解从零搭建 GIS 报告自动化流水线3.1 环境准备与最小可行验证MVP在动手写 Skill 之前我坚持先做三件事避免后期返工验证 MCP 基础通信链路不用 WorkBuddy 界面直接用curl模拟一次最简 MCP 请求curl -X POST http://localhost:8080/mcp/v1/invoke \ -H Content-Type: application/json \ -d { tool: postgres_query, parameters: { query: SELECT ST_AsGeoJSON(ST_Envelope(ST_Collect(geom))) FROM districts WHERE name 朝阳区; } }如果返回{status:success,result:{\type\:\Polygon\,\coordinates\:[[[...]]]}}说明 MCP 层通了。如果返回{status:error,message:Connection refused}那问题一定在数据库连接配置而不是 Skill 逻辑。建立 Skill 开发沙盒WorkBuddy 官方推荐用 VS Code WorkBuddy Extension但我发现它对 Skill 编码247 的语法高亮支持很差。最终我用 PyCharm 新建一个纯文本工程手动配置文件关联.skill文件 →YAML语法再加一个File Watcher插件保存时自动执行workbuddy-cli validate --file ./gis_buffer.skill。这个 CLI 工具是 WorkBuddy 服务端自带的但文档里藏得很深在/opt/workbuddy/bin/目录下。设计“失败必现”的测试用例我写了三个必测用例每天早上 CI 流水线自动跑用不存在的行政区名称如“宇宙区”触发 Skill 的error_handling分支检查是否返回标准错误结构输入距离为负数如-100验证 Skill 是否在input_validation阶段拦截而非让 PostGIS 报错强制断开数据库网络观察 WorkBuddy 是否在 15 秒内返回timeout状态而非卡死。注意WorkBuddy 的 Skill 编译缓存很顽固。修改.skill文件后必须执行workbuddy-cli reload-skill --name gis_buffer_analysis否则前端永远加载旧版本。这个命令没有回显成功与否只能看后台日志里有没有Reloaded skill: gis_buffer_analysis这行。3.2 Skill 编码247 核心逻辑实现附逐行注释以下是gis_buffer_analysis.skill的核心部分我保留了所有调试用的log步骤因为它们在真实排障中救了我三次# Skill 编码247 规范要求每个 Skill 必须有唯一 name 和 version name: gis_buffer_analysis version: 1.3.0 description: 生成指定区域的缓冲区分析报告输出 GeoJSON PDF # 输入参数定义严格类型校验避免运行时错误 input_schema: type: object properties: district_name: type: string description: 行政区全称如朝阳区 min_length: 2 max_length: 20 buffer_distance: type: number description: 缓冲距离米 minimum: 10 maximum: 5000 multipleOf: 10 # 输出结构定义强制约定确保下游组件可解析 output_schema: type: object properties: geojson_path: type: string description: 生成的 GeoJSON 文件绝对路径 pdf_path: type: string description: 生成的 PDF 报告绝对路径 report_summary: type: object properties: area_km2: type: number description: 缓冲区总面积平方公里 intersect_count: type: integer description: 与原始区域相交的地块数量 # MCP 工具调用链按业务逻辑顺序编排每步都有超时和重试 steps: # Step 1: 查询行政区边界WKT 格式 - id: query_district_boundary tool: postgres_query parameters: query: | SELECT ST_AsText(ST_Transform(geom, 4326)) FROM districts WHERE name {{ input.district_name }}; timeout: 10 retry: 2 log: Step 1: 查询 {{ input.district_name }} 边界 WKT # Step 2: 用 PostGIS 计算缓冲区关键必须指定 SRID - id: calculate_buffer tool: postgres_query parameters: query: | WITH boundary AS ( SELECT ST_GeomFromText({{ step.query_district_boundary.result }}, 4326) AS geom ) SELECT ST_AsGeoJSON( ST_Transform( ST_Buffer(geom, {{ input.buffer_distance }}), 4326 ) ) AS buffer_geojson FROM boundary; timeout: 15 log: Step 2: 计算 {{ input.buffer_distance }} 米缓冲区 # Step 3: 调用 QGIS CLI 渲染地图需提前配置 QGIS_SERVER_LOG_LEVEL0 - id: render_map tool: qgis_cli_render parameters: geojson_path: /tmp/{{ input.district_name }}_buffer.geojson output_path: /tmp/{{ input.district_name }}_map.png width: 1200 height: 800 timeout: 60 log: Step 3: 渲染地图到 /tmp/{{ input.district_name }}_map.png # Step 4: 用 Jinja2 模板生成 PDF模板文件需提前放入 /opt/workbuddy/templates/ - id: generate_pdf tool: jinja2_pdf parameters: template: gis_report_template.html context: district_name: {{ input.district_name }} buffer_distance: {{ input.buffer_distance }} map_image_path: /tmp/{{ input.district_name }}_map.png geojson_path: /tmp/{{ input.district_name }}_buffer.geojson output_path: /var/www/reports/{{ input.district_name }}_{{ input.buffer_distance }}m_report.pdf timeout: 30 log: Step 4: 生成 PDF 报告 # 错误处理必须覆盖所有已知失败点 error_handling: - on_error: query_district_boundary action: return_error message: 未找到行政区{{ input.district_name }}。请检查名称是否准确或联系 GIS 管理员更新行政区划表。 - on_error: calculate_buffer action: return_error message: 缓冲区计算失败。可能原因输入距离过大导致内存溢出请尝试小于 2000 米。 - on_error: render_map action: retry max_retries: 1 delay: 5 - on_error: generate_pdf action: return_error message: PDF 生成失败。请检查模板文件是否存在或磁盘空间是否充足。 # UI Schema定义前端如何展示结果这才是 WorkBuddy 的灵魂 ui_schema: type: map_preview properties: geojson_path: {{ step.generate_pdf.output.geojson_path }} map_image_path: {{ step.render_map.output.map_image_path }} legend_info: title: 缓冲区分析结果 items: - label: 原始区域 color: #3498db - label: 缓冲区 color: #e74c3c download_buttons: - label: 下载 GeoJSON path: {{ step.generate_pdf.output.geojson_path }} - label: 下载 PDF 报告 path: {{ step.generate_pdf.output.pdf_path }}3.3 MCP 工具注册与权限精控WorkBuddy 的 MCP 工具不是“写好就能用”必须在服务端显式注册。我花了两天才搞懂它的权限模型工具注册文件mcp_tools.yaml这个文件必须放在/opt/workbuddy/config/下内容如下tools: - name: postgres_query description: 执行只读 PostGIS 查询 executable: /usr/bin/psql # 关键所有参数必须白名单禁止 shell 注入 allowed_parameters: - query # 最小权限原则只允许连接特定数据库 environment: PGHOST: gis-db.internal PGPORT: 5432 PGDATABASE: gisdb PGUSER: wb_reader PGPASSWORD: readonly_password_here timeout: 30 - name: qgis_cli_render description: 调用 QGIS Server CLI 渲染地图 executable: /usr/bin/qgis_process allowed_parameters: - geojson_path - output_path - width - height # QGIS 必须以无头模式运行且禁用 GUI environment: DISPLAY: QT_QPA_PLATFORM: offscreen timeout: 120为什么不用 root 权限WorkBuddy 默认以workbuddy用户运行。我最初图省事把qgis_cli_render的executable设为/usr/bin/sudo /usr/bin/qgis_process结果每次渲染都失败。查日志发现sudo在无 TTY 环境下默认拒绝执行。正确做法是给workbuddy用户加一条visudo规则workbuddy ALL(ALL) NOPASSWD: /usr/bin/qgis_process且必须指定完整路径不能用qgis_process别名。PostgreSQL 连接池的坑WorkBuddy 的 MCP 工具默认每次调用都新建数据库连接高频使用时会触发 PostgreSQL 的max_connections限制。我在postgresql.conf里把max_connections从 100 改成 200但还是偶尔报错。最终解决方案是在mcp_tools.yaml里为postgres_query加上连接池配置connection_pool: max_size: 10 min_idle: 2 max_lifetime: 300这个配置项在官方文档里叫pool_config但实际代码里是connection_pool大小写敏感。3.4 工作台Workbench深度定制WorkBuddy 的默认工作台对 GIS 场景支持很弱我做了三项关键改造地图预览组件增强原生地图组件只支持 Leaflet但我们的业务需要 OpenLayers 的矢量图层叠加能力。我 fork 了 WorkBuddy 的workbench-ui仓库在src/components/MapPreview.vue里替换了地图引擎并加了一个vector_layer属性允许 Skill 通过ui_schema.vector_layers传入额外的 GeoJSON URL。这个改动让我能在同一张图上叠加“原始地块”“缓冲区”“人口热力”三个图层。PDF 预览嵌入默认工作台只提供“下载 PDF”按钮用户得开本地阅读器看。我用pdfjs-dist库写了一个轻量级 PDF 预览组件插入到ui_schema的pdf_preview字段里。关键技巧是WorkBuddy 的文件服务默认不支持跨域必须在 Nginx 配置里加location /files/ { add_header Access-Control-Allow-Origin *; alias /var/www/reports/; }一键分发集成报告生成后业务要求自动发邮件给规划科、城建科、分管领导。WorkBuddy 自带邮件工具太简陋不支持附件、无模板。我注册了一个自定义 MCP 工具send_report_email底层调用的是swaksSwiss Army Knife for SMTPswaks --to plandept.gov.cn,citydept.gov.cn,leaderdept.gov.cn \ --from workbuddycompany.com \ --server smtp.company.com:587 \ --auth-user workbuddycompany.com \ --auth-password app_password_here \ --header Subject: 【自动报告】{{ district_name }} 缓冲区分析报告{{ timestamp }} \ --body /tmp/email_body.txt \ --attach /var/www/reports/{{ district_name }}_report.pdf这个命令的--auth-password必须用应用专用密码App Password不能用邮箱主密码否则会被 SMTP 服务器拒绝。4. 从“能跑通”到“敢交付”的五次关键迭代4.1 第一次迭代基础功能可用耗时 3 天目标让 Skill 跑通生成一份能打开的 PDF。成果成功生成朝阳区 500 米缓冲区报告但 PDF 里地图是黑的文字全是乱码。根因QGIS 渲染时字体缺失PDF 模板里用了微软雅黑但容器里没装中文字体。解决在 Dockerfile 里加一行RUN apt-get install -y fonts-wqy-microhei并在 QGIS CLI 启动参数里加--font WenQuanYi Micro Hei。4.2 第二次迭代加入业务校验耗时 2 天目标防止用户输错行政区名称或输入超大缓冲距离。成果增加了input_validation步骤但发现 WorkBuddy 的if语句不支持复杂条件。根因Skill 编码247 的if只支持单个布尔表达式不能写{{ input.buffer_distance 1000 and input.buffer_distance 5000 }}。解决改用switch语句把距离分成三档100, 100-1000, 1000每档走不同分支超范围直接return_error。4.3 第三次迭代性能优化耗时 4 天目标将单次报告生成时间从 120 秒压到 45 秒以内。成果通过并行化query_district_boundary和calculate_buffer但发现 MCP 工具不支持并发。根因WorkBuddy 的 MCP 调用是串行队列强行并发会触发锁竞争。解决把两个查询合并成一个 SQL用 CTE用postgres_query一次执行返回 JSON 对象包含两个结果字段。SQL 改写后耗时降到 38 秒。4.4 第四次迭代容错加固耗时 5 天目标确保任何环节失败都不影响其他用户任务。成果实现了任务隔离但发现 WorkBuddy 的日志全打在同一个workbuddy.log里无法按任务 ID 过滤。根因WorkBuddy 的日志框架没做 MDCMapped Diagnostic Context集成。解决在每个 Skill 的log字段里手动加task_id: {{ uuid() }}然后用 Logstash 做日志路由按task_id分割日志流。4.5 第五次迭代体验升级耗时 3 天目标让非技术人员也能操作无需记住“朝阳区”“500”这种参数。成果在工作台加了一个行政区选择下拉框数据来自/api/districts接口。根因WorkBuddy 的 UI Schema 不支持动态下拉框必须改前端代码。解决在workbench-ui/src/api/districts.js里新增接口前端用v-model绑定参数自动注入 Skill 的input_schema。5. 常见问题与实战排查速查表问题现象可能原因排查命令/步骤解决方案Skill 显示“加载中…”但无日志MCP 工具注册失败或mcp_tools.yaml语法错误journalctl -u workbuddy -n 100 --no-pager | grep mcp检查 YAML 缩进用yamllint mcp_tools.yaml验证确认工具executable路径存在且有执行权限PostgreSQL 连接报password authentication failedPGPASSWORD环境变量未生效或密码含特殊字符echo $PGPASSWORD | hexdump -C查看是否被 shell 截断密码用单引号包裹PGPASSWORDpssw0rd!或改用.pgpass文件QGIS 渲染地图为空白或报错No module named PyQt5容器内缺少 PyQt5 或 Qt 依赖docker exec -it workbuddy bash -c python3 -c import PyQt5在 Dockerfile 里加RUN apt-get install -y python3-pyqt5 libqt5gui5PDF 中文显示为方框容器内无中文字体或模板 CSS 指定字体无效fc-list | grep -i sim检查字体列表安装fonts-wqy-microhei并在 CSS 里写font-family: WenQuanYi Micro Hei, sans-serif;工作台地图不显示控制台报Leaflet is not definedWorkBuddy 前端资源加载失败或 CDN 被拦截curl -I https://unpkg.com/leaflet1.9.4/dist/leaflet.css改用本地资源cp node_modules/leaflet/dist/* /opt/workbuddy/static/leaflet/修改index.html引用路径邮件发送失败日志显示535 Authentication failedSMTP 密码错误或邮箱启用了两步验证swaks --to testexample.com --server smtp.gmail.com:587 --auth手动测试使用 Gmail 的 App Password而非账户密码确认 SMTP 服务器地址和端口正确Skill 执行后UI 不更新仍显示旧结果WorkBuddy 的前端缓存未刷新或ui_schema字段名拼写错误curl http://localhost:8080/api/v1/skills/gis_buffer_analysis | jq .ui_schema检查ui_schema里geojson_path是否与step.xxx.output.xxx路径完全一致清除浏览器缓存或加?v{{ timestamp }}强制刷新实操心得WorkBuddy 的最大学习成本不在 Skill 语法而在“理解它如何与外部世界对话”。每一次失败90% 是环境问题权限、路径、依赖10% 是逻辑问题。我的固定排障三板斧是① 看workbuddy.log里ERROR行② 用curl直接调 MCP 接口绕过前端③ 在服务器上手动执行mcp_tools.yaml里定义的executable命令用相同参数和环境变量。6. 这个任务教会我的三件事我在团队内部做了一次分享标题就叫《一个 GIS 报告背后的 37 个失败日志》没人觉得枯燥因为每一条都对应着一个真实的堵点。现在回头看这个任务的价值远不止于“省了 4 小时人工”它让我彻底看清了 AI 办公的底层逻辑第一WorkBuddy 不是替代人而是放大人的判断力。它不会自己决定“该分析哪个区域”但一旦你输入“朝阳区”它能在 38 秒内完成过去 4 小时的手工操作并把结果结构化呈现。真正的生产力提升来自于把人从重复劳动里解放出来去思考“为什么要做这个分析”“结果意味着什么”“下一步该调整哪些参数”。第二MCP 的价值不在“协议”本身而在“契约精神”。它强迫你把模糊的业务需求翻译成机器可执行的精确指令。写ST_Transform(geom, 4326)这一行代码背后是对坐标系、投影、精度损失的全部理解。这种翻译过程本身就是一次深度的业务梳理。第三Skill 编码247 的“笨”恰恰是它的聪明。它不让你写自由度极高的 Python而是用受限的 YAML 强制你思考输入、输出、错误、超时、重试——这些恰恰是生产环境最需要的健壮性要素。我见过太多用 Python 写的自动化脚本跑一次成功第二次因网络抖动失败第三次因磁盘满崩溃而 Skill 的retry和error_handling是写死在语法里的。最后再分享一个小技巧WorkBuddy 的 Skill 版本管理有个隐藏功能。你在workbuddy.yaml里加一行skill_version_policy: auto_increment每次workbuddy-cli reload-skill时它会自动把version字段从1.3.0升到1.3.1且保留所有历史版本。这样你随时可以回滚到上周稳定的版本而不必手动改 YAML。这个功能没写在文档里是我翻源码core/skill/version_manager.py发现的。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进