k-skill seoul-weather-risk 行政洞解析设计:如何把「행정동 이름 + 자연어」确定性转换为 ASK Seoul 的 place_id
2026/9/17 14:57:25 网站建设 项目流程

k-skill seoul-weather-risk 行政洞解析设计:如何把「행정동 이름 + 자연어」确定性转换为 ASK Seoul 的 place_id

【免费下载链接】k-skill한국인을 위한 스킬 모음집 - 에이전트를 한국인으로项目地址: https://gitcode.com/GitHub_Trending/ks/k-skill

本文围绕仓库 docs/superpowers/specs/2026-08-09-seoul-weather-risk-admin-dong-resolution-design.md 展开,并结合 实施计划、helper 源码、单元测试 与 功能指南 进行纵深讲解。

一、设计目标:让普通用户不再需要知道 place_id

seoul-weather-risk是 k-skill 仓库中面向「서울(首尔)气象风险」的只读技能:它以weather_place_risk_window这一单产品为唯一数据出口,返回指定地点在特定时间窗内的 폭염(酷暑)、한파(寒潮)、호우(暴雨)、대설(大雪)、강풍(大风)等风险候选时段。

该产品在数据契约层面以place_id作为地点主键,格式为seoul_admd_<10자리 행정동 코드>(10 位行政洞代码)。但普通用户并不知道这一内部 ID。因此本次设计的核心目标是:

让用户即使不知道place_id,也能通过「행정동 이름(行政洞名称)+ 자연어 질문(自然语言提问)」使用该技能。Skill helper 在本地把行政洞名称确定性地解析为 ASK Seoul 的正规place_id,再调用既有 hosted proxy 的只读 data 路径。

换句话说,这是一次「输入层人文化、数据契约零改动」的增强:用户层只接触人类可读的地名,而下游 proxy 与 ASK Seoul 服务仍然只收到place_id

二、不改变的安全边界(设计红线)

设计文档首先明确划定了「变更边界」,这是整套方案安全性的前提,原文逐条保留如下:

  • 目标产品维持weather_place_risk_window单一产品不变;
  • 不拓宽 hosted proxy 的认证、scope、单产品 allowlist 边界;
  • 不修改 D1、Worker、Publisher、dbt 模型及 Marketplace 数据契约;
  • 不新增任意 SQL、fuzzy search、坐标 geocoding、实时位置推断;
  • 既有--filter place_id=...高级用户路径保持向下兼容

从源码看,这些边界都有对应实现佐证。例如 seoul_weather_risk.py 中EXACT_PRODUCT_IDS仅包含weather_place_risk_window一个产品;bundle 校验函数 会校验 bundle 返回的产品集合与该集合完全一致,出现漂移(drift)时直接以response_contract_invalid契约错误中止(对应测试test_bundle_single_product_drift_fails_closed)。这正是「单产品 allowlist 不拓宽」的实现证据。

三、基准数据与版本:427 个行政洞的冻结快照

解析所依赖的基准数据来自外部数据管线,设计文档给出的关键事实如下:

基准源ASAC-DBTweather_place_grid_mapping.csv
基准版本mapping_method=kma_admin_dong_grid_20260325
快照规模서울 행정동 427 个,唯一place_id427 个
place_id格式seoul_admd_<10자리 행정동 코드>
名称重复신사동1 组同名(강남구 / 관악구)

关键约束:技能必须携带一份版本冻结的 JSON reference,运行时不依赖任何外部仓库或 API。reference 只保存最小字段:mapping_versionsourcegenerated_at,以及每条记录admin_dongguplace_id

仓库中的实际快照位于 seoul-weather-risk/references/admin-dong-place-map.json,头部即包含"mapping_version":"kma_admin_dong_grid_20260325""source":"ASAC-DBT/domains/traffic_weather/seeds/weather/weather_place_grid_mapping.csv""generated_at":"2026-08-09"。数据按place_id排序,例如잠실본동(송파구)→seoul_admd_1171065000신사동分别出现在 강남구(seoul_admd_1168051000)与 관악구(seoul_admd_1162068500)两条记录中。

reference 的加载与校验实现在 源码_load_location_mapping:逐项检查版本号、sourcegenerated_at非空、行数必须等于 427、每行字段集合必须精确为{admin_dong, gu, place_id}且均为非空字符串、place_id必须匹配seoul_admd_+ 10 位数字、不得存在重复place_id或重复(行政洞, 自治区)组合;任何违反都以location_mapping_invalid失败。常量LOCATION_MAPPING_VERSIONLOCATION_MAPPING_SIZE定义于 源码第 26-27 行。

四、输入契约:位置输入「三选一」

query命令新增以下可选参数(实现在 parser 定义处):

  • --admin-dong <행정동명>:普通用户默认位置输入;
  • --gu <자치구명>:仅用于解决同名歧义,必须与--admin-dong同时使用
  • 既有--filter place_id=<정규 ID>:高级用户与既有调用的兼容路径。

互斥规则:位置输入只允许「恰好一种方式」。

输入组合结果
--admin-dongplace_idfilter 同时给出conflicting_location_input失败
--gu,无--admin-donginvalid_location_input失败
--admin-dong(无歧义)解析为唯一place_id
--admin-dong(同名歧义)ambiguous_admin_dong+ 候选自治区列表
--admin-dong+--gu(可确认唯一)解析为对应place_id

源码 run 函数中的校验段 完整实现了这一契约:先检查--gu是否伴随--admin-dong,再检查两者是否为空串(invalid_location_input),随后若place_id已存在于 filters 中则抛conflicting_location_input,最后调用_resolve_admin_dong并把解析出的正规 ID 写入filters["place_id"]。对应的 测试用例 验证了--gu--admin-dong与冲突输入的失败路径,且确保失败时不会把admin_donggu字符串泄漏进上游请求参数。

五、规范化与解析规则:精确匹配优先、安全失败

设计文档明确规定了 4 步字符串规范化流程:

  1. Unicode NFC 规范化;
  2. 去除首尾空白;
  3. 连续空白折叠为单空格;
  4. 仅接受规范化字符串的精确匹配

同时明确不做的事:不做后缀去除、不做 초성(初声)搜索、不做相似字符串匹配、不做自动错别字修正。设计哲学是「宁可安全失败,也不查错地点」——错误地返回另一个洞的数据比报错危害更大。

源码中对应实现为_normalize_location_name

def _normalize_location_name(value: str) -> str: return " ".join(unicodedata.normalize("NFC", value).strip().split())

解析结果矩阵

候选情况结果
候选 1 个转换为该place_id
候选多个、未给--gu返回ambiguous_admin_dong+ 候选自治区列表
候选多个、--gu确认 1 个转换为该place_id
候选 0 个unknown_admin_dong
--gu未知unknown_gu

确定性的书写别名(来自 instruction.md 的补充约定)

虽然「精确匹配」是主路径,但韩国行政洞名的书写变体是现实问题。instruction.md 与实现补充了一套可确定性生成的别名规则(设计文档未展开、但在最终产物中落地),值得完整继承:

  • 数字前紧邻的可省略:如성수2가제3동성수2가3동
  • 数字分隔点允许「마침표./ 가운데점·/ 省略」三种形式:如종로1.2.3.4가동종로1·2·3·4가동종로1234가동均可解析到同一 ID;
  • 除此之外不生成任何别名:오타、유사 이름、생활권·통칭、部分名称(如성수동)一律unknown_admin_dong,不做 fuzzy 猜测。

实现位于_alias_keys(用正则제(?=\d)只删数字前的,并枚举./·/省略的组合)与_location_indexes(建立正名索引与别名索引,正名命中优先于别名)。测试文件 对每个含、含.的 reference 行逐一验证所有可生成别名均能带--gu解析成功,且제기동这类「非数字前 제」不会被误删(기동应报unknown_admin_dong)。

解析主函数_resolve_admin_dong

核心实现位于 源码第 395-424 行,逻辑为:规范化输入 → 加载并校验 reference → 先查正名索引、未命中再查别名索引 → 候选为空抛unknown_admin_dong→ 若给了--gu则先校验自治区是否已知(否则unknown_gu),再用自治区过滤候选(过滤后为空抛unknown_admin_dong)→ 按(自治区, place_id)排序后若仍多于 1 个则抛ambiguous_admin_dongdetails.candidates带完整候选),否则返回唯一记录。测试 test_resolve_admin_dong_requires_gu_for_duplicate_name 验证신사동不带--gu时精确返回 강남구/관악구两个候选。

六、错误契约:统一的 typed JSON envelope

所有新错误码统一走既有 JSON error envelope,exit code 为 2

错误码含义
invalid_location_input位置输入组合非法(如仅--gu
conflicting_location_input--admin-dongplace_idfilter 冲突
unknown_admin_dongreference 中不存在该行政洞
ambiguous_admin_dong同名/别名候选冲突,details.candidates含自治区与place_id
unknown_gu未知自治区
location_mapping_invalidreference 缺失、ID 重复或 schema 错误

实现上,SkillError携带code/message/details,run 函数 在捕获后向 stderr 输出{"error": {"code", "message", "details"}}并返回 exit code 2。这与该技能既有invalid_limitquery_window_unavailableproduct_not_ready等错误处于同一契约体系。

七、执行流程:解析留在本地,proxy 边界不变

设计文档给出的执行流共 5 步,逐条保留:

  1. helper 照常校验 bundle 与 product metadata 契约;
  2. 本地 reference中解析--admin-dong
  3. 用解析结果校验既有公开 projection filter;
  4. 仅向 proxy 发送place_id、时间范围、limitcursor
  5. 响应行按既有 data 契约校验后原样输出。

由此得到两个关键推论(源码均有印证):

  • proxy 与 ASK Seoul 服务不承担行政洞字符串解析职责:上游请求中只有place_id,绝无admin_dong/gu字符串。测试 test_query_maps_admin_dong_to_place_id_before_proxy_request 断言最终 query 精确等于{"place_id": ["seoul_admd_1171065000"], "limit": ["1"]},且不含admin_dong/gu键。
  • 无需用户 API Key 的安全模型保持不变:helper 不发送Authorization头,ASK Seoul 专用服务密钥只存在于 proxy 运行环境。测试 test_query_uses_narrow_proxy_paths_without_user_bearer_auth 断言三次请求的Authorization均为None。进一步地,test_disabled_proxy_never_echoes_legacy_user_credentials 验证即便环境变量里存在旧式用户密钥,输出中也不会出现它。

八、文档与 Agent 行为约定

设计文档为 skill helper 的使用方式定下了行为准则,最终在 instruction.md 中落地:

  • 默认示例使用人类可读名称,如--admin-dong 잠실본동
  • 从自然语言问题中提取行政洞名后,原样传给--admin-dong
  • 遇到신사동这类歧义时,先向用户询问一次自治区,收到答复后用--gu重新调用;
  • 在解释响应时,优先使用用户输入的行政洞名与产品的预报时刻、风险依据,而非内部place_id

日常快速路径是query --fast(见 fast path 设计):该路径跳过 bundle/product metadata 往返,只调用 data 路由一次,同时保留本地行政洞映射、日期与 limit 校验。典型命令如下(来自 instruction.md):

npx -y @nomadamas/k-skill@0 exec seoul-weather-risk scripts/seoul_weather_risk.py -- query --fast \ --product-id weather_place_risk_window \ --admin-dong 잠실본동 \ --from 2026-08-12 \ --to 2026-08-12 \ --limit 100

同名歧义场景则追加--gu

npx -y @nomadamas/k-skill@0 exec seoul-weather-risk scripts/seoul_weather_risk.py -- query --fast \ --product-id weather_place_risk_window \ --admin-dong 신사동 \ --gu 강남구 \ --limit 100

需要--filter或检查发布契约时才退回 full-contract query(先preflightcatalogdescribe)。日期输入约定:--from YYYY-MM-DD扩展为当日00:00:00--to扩展为23:59:59;若 ASK Seoul serving window 未覆盖自午夜起全天导致422 query_window_unavailable,helper 会按available_from_at/available_to_at与请求区间求交仅重试一次,无交集则返回该错误的 available window 信息并中止(实现见 源码_retry_query_after_unavailable_window与测试 test_query_clips_calendar_day_to_available_window)。

九、验证计划:从单元到全量 CI

设计文档列出 8 项验证,最终在 tests/test_seoul_weather_risk.py 中全部落地:

  1. 잠실본동正确转换为seoul_admd_1171065000并传入 proxy data query(test_query_maps_admin_dong_to_place_id_before_proxy_request);
  2. 首尾/连续空白与 Unicode NFC 规范化(test_resolve_admin_dong_normalizes_unicode_nfctest_resolve_admin_dong_normalizes_internal_whitespace:NFD 输入与" 잠실 본동 "均解析到同一 ID);
  3. 신사동单独输入以含候选自治区的ambiguous_admin_dong失败(test_resolve_admin_dong_requires_gu_for_duplicate_name);
  4. 신사동 + 강남구收敛为单一 ID(test_resolve_admin_dong_uses_gu_to_disambiguate);
  5. 未登记洞、错误自治区、冲突位置输入均为 typed error(test_resolve_admin_dong_rejects_unknown_or_broad_dong_and_gutest_query_rejects_*系列);
  6. 既有place_idfilter 调用行为不变(回归:test_query_uses_narrow_proxy_paths_without_user_bearer_auth);
  7. reference 的 427 行、唯一 ID、版本与必填字段不变量(test_admin_dong_reference_has_expected_version_and_unique_place_ids,以及损坏 reference 的location_mapping_invalid用例test_load_location_mapping_rejects_invalid_reference);
  8. 源码与 CLI bundled copy 完全同步,且整体npm run ci通过(由实施计划 Task 5 规定,通过npm run generate:skill-stubs -- --checknpm run sync:cli-skills -- --checkgit diff --check校验后执行全量 CI)。

此外测试还覆盖了别名生成的不变量:test_resolve_admin_dong_resolves_every_generated_map_alias_with_gu遍历 reference 中每一行生成的每一个别名并断言可解析回原记录,test_resolve_admin_dong_does_not_omit_non_numeric_je则防止对非数字前的误删。

十、完成条件与设计价值

设计文档定义的完成条件为:

  • 用户仅凭行政洞名称即可查询非歧义地点的气象风险数据;
  • 同名地不因缺少自治区而被任意选取;
  • proxy 不新增任何 query field 或用户凭证;
  • 生成桩、CLI bundle、单元测试与全量 CI 全部通过。

从仓库现状看,该设计已完整落地:reference 快照、helper 解析实现、typed 错误契约、单元测试、instruction.md、skill.json 与生成的 SKILL.md 均已就位,且packages/k-skill-cli/skills/seoul-weather-risk同步资产由scripts/generate-skill-stubs.jsscripts/sync-cli-skills.js负责生成与校验。

这一设计的核心价值在于在零数据契约改动、零安全模型放宽的前提下,把「人类可读的行政洞名称」与「机器可查的 place_id」之间的鸿沟,用一份 427 行的冻结 JSON 和一段纯标准库 Python 确定性解析逻辑安全地弥合起来——既照顾了普通用户的自然语言输入体验,也保住了高级用户的place_id兼容路径,同时把「查错地点」的风险降到最低。

【免费下载链接】k-skill한국인을 위한 스킬 모음집 - 에이전트를 한국인으로项目地址: https://gitcode.com/GitHub_Trending/ks/k-skill

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询