package-query-skills.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. """Prepare/finalize the query-rule handoff without copying private databases."""
  2. import argparse
  3. import hashlib
  4. import json
  5. from pathlib import Path
  6. import shutil
  7. import zipfile
  8. ROOT = Path(__file__).resolve().parents[1]
  9. DIST = Path('F:/文档/康师傅skill')
  10. WORK = ROOT / 'work/agent-query-20261106'
  11. OUT = ROOT / 'artifacts/数据库精准检索规则-20261106'
  12. NAME = 'ksf-agent-query-rules-20261106-r1'
  13. RELEASE = OUT / NAME
  14. SKILLS = ('ksf-workshop-runtime', 'ksf-voc-library')
  15. def sha(path):
  16. with path.open('rb') as stream:
  17. return hashlib.file_digest(stream, 'sha256').hexdigest()
  18. def write(path, data):
  19. path.parent.mkdir(parents=True, exist_ok=True)
  20. path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  21. def files(folder):
  22. return {p.relative_to(folder).as_posix(): sha(p) for p in sorted(folder.rglob('*')) if p.is_file() and '__pycache__' not in p.parts}
  23. def manifest(folder):
  24. entries = {name: value for name, value in files(folder).items() if name != 'FILES.sha256'}
  25. (folder / 'FILES.sha256').write_text(''.join(value + ' ' + name + '\n' for name, value in entries.items()), encoding='utf-8', newline='\n')
  26. def prepare():
  27. if RELEASE.exists():
  28. raise ValueError('RELEASE_ALREADY_PREPARED')
  29. for name in ('query_voc.py', 'voc_db.py', 'retrieval.py', 'schema.sql', 'taxonomy.json'):
  30. shutil.copyfile(ROOT / 'server/voc-library' / name, ROOT / 'skills/ksf-voc-library/scripts' / name)
  31. for name in SKILLS:
  32. manifest(ROOT / 'skills' / name)
  33. shutil.copytree(ROOT / 'skills' / name, RELEASE / 'skills' / name)
  34. baselines = {name: [files(WORK / 'baseline-skills' / name)] for name in SKILLS}
  35. with zipfile.ZipFile(DIST / '历史归档/20261104-先查库/ksf-workshop-runtime-20261104.zip') as archive:
  36. baselines['ksf-workshop-runtime'].append({name: hashlib.sha256(archive.read(name)).hexdigest() for name in archive.namelist() if not name.endswith('/')})
  37. write(RELEASE / 'baselines.json', baselines)
  38. shutil.copyfile(ROOT / 'scripts/install-query-skills.py', RELEASE / 'install-query-skills.py')
  39. for source in (ROOT / 'docs/voc-agent-query-20261106').glob('*.md'):
  40. target = RELEASE / 'docs' / source.name
  41. target.parent.mkdir(parents=True, exist_ok=True)
  42. target.write_text(source.read_text(encoding='utf-8').replace('../../skills/', '../skills/'), encoding='utf-8')
  43. scope = dict(platform='jd', brand='ksf', category='banmian')
  44. examples = {
  45. '01-jd-ksf-banmian-products': dict(entities=['products'], filters=scope, limit=20),
  46. '02-jd-ksf-banmian-reviews': dict(entities=['reviews'], filters=scope, quality='analysis', limit=20),
  47. '03-jd-ksf-banmian-review-audit': dict(entities=['reviews'], filters=scope, quality='audit', groupBy=['isDefault', 'identityState', 'categoryState'], limit=100),
  48. '04-brand-products': dict(entities=['products'], filters=dict(brand='ksf'), limit=20),
  49. '05-nutrition-documents': dict(entities=['documents', 'clauses'], text=dict(any=['营养标签', '营养成分表']), limit=20),
  50. '06-jd-ksf-banmian-prices-specs': dict(entities=['prices', 'specs'], filters=scope, limit=20),
  51. '07-jd-ksf-banmian-portion': dict(entities=['reviews'], filters=scope, text=dict(concepts=['portion_size']), limit=20),
  52. '08-jd-ksf-banmian-review-statistics': dict(entities=['reviews'], filters=scope, groupBy=['platform', 'brand', 'category'], limit=100),
  53. '09-jd-ksf-banmian-coverage': dict(entities=['coverage'], filters=scope, limit=20),
  54. }
  55. for name, data in examples.items():
  56. write(RELEASE / 'examples' / (name + '.json'), data)
  57. write(RELEASE / 'release.json', dict(version=NAME, agentRulesVersion='ksf.agent-data-rules.20261106.v1',
  58. schema='ksf.voc-library.v2', retrievalContract='ksf.voc-retrieval.v2', databaseIncluded=False,
  59. databaseMigrationRequired=False, productionInstalled=False, realMessageVerified=False,
  60. workshopRuntimeScriptsChanged=False, researchScriptsChanged=['voc_db.py', 'retrieval.py']))
  61. print(json.dumps(dict(prepared=str(RELEASE), next='Run verify-query-skills.py before finalize'), ensure_ascii=False))
  62. def finalize():
  63. validation = json.loads((WORK / 'validation.json').read_text(encoding='utf-8'))
  64. if not validation['ok'] or (OUT / (NAME + '.zip')).exists():
  65. raise ValueError('VALIDATION_REQUIRED_OR_FROZEN_ZIP_EXISTS')
  66. for name in SKILLS:
  67. if files(ROOT / 'skills' / name) != files(RELEASE / 'skills' / name):
  68. raise ValueError('SKILL_CHANGED_AFTER_PREPARATION')
  69. shutil.copyfile(WORK / 'validation.json', RELEASE / 'local-validation.json')
  70. manifest(RELEASE)
  71. archive = OUT / (NAME + '.zip')
  72. with zipfile.ZipFile(archive, 'x', zipfile.ZIP_DEFLATED) as z:
  73. for path in sorted(RELEASE.rglob('*')):
  74. if path.is_file():
  75. z.write(path, path.relative_to(RELEASE).as_posix())
  76. receipt = dict(release=NAME, filename=archive.name, bytes=archive.stat().st_size, sha256=sha(archive),
  77. files=len(files(RELEASE)), localValidated=True, productionInstalled=False, realMessageVerified=False)
  78. write(OUT / 'delivery-receipt.json', receipt)
  79. for name in SKILLS:
  80. source, target = ROOT / 'skills' / name, DIST / name
  81. for path in source.rglob('*'):
  82. if path.is_file():
  83. out = target / path.relative_to(source)
  84. out.parent.mkdir(parents=True, exist_ok=True)
  85. shutil.copyfile(path, out)
  86. shutil.copyfile(archive, DIST / archive.name)
  87. write(DIST / '版本与校验.json', dict(preparedDate='2026-11-06', release=NAME,
  88. skills={name: dict(files=files(DIST / name), manifestSha256=sha(DIST / name / 'FILES.sha256')) for name in SKILLS},
  89. zip=receipt, previousPreparedVersion='历史归档/20261105-r2-查询规则更新前.zip',
  90. databaseIncluded=False, productionInstalled=False, realMessageValidated=False))
  91. (DIST / 'README.md').write_text('''# 当前Skill分发 · 2026-11-06
  92. 当前安装入口:ksf-agent-query-rules-20261106-r1.zip;ZIP、两份展开Skill与版本与校验.json一致。
  93. - ksf-workshop-runtime:工作坊技能,研究底料先查研究库、报告和项目查业务库;仅说明和清单升级,身份、昵称、上传与运行脚本沿用原版。
  94. - ksf-voc-library:只读研究查询,精确字段AND、商品评价关联、来源回读、统计口径及同一generation校验;支持与看板使用同一registry/current.json。
  95. - 不含数据库,沿用已安装v2研究库。缺库时需先用原完整r2资料包建立授权库;不能只复制Skill就声称能查到资料。
  96. 本机准备和验证完成,未在王刚001生产实例安装/加载/真实消息验收。请将当前完整ZIP与包内docs/发给运维001.md一起转运维;安装后将docs/发给王刚001.md交本人真实消息验证。历史ZIP不再作为当前升级入口,旧r2展开内容已归档,原ZIP未修改。
  97. 新增同结构数据通过新代际更新,Agent新研究读取当前代际;新增字段/实体/分类定义才更新合同与Skill。没有新增采集工具,不调用付费上游。
  98. ''', encoding='utf-8')
  99. handoff = Path('F:/文档/新数字人交接')
  100. (handoff / '10-精准查库规则增量-20261106.md').write_text('''# 精准查库规则增量 · 2026-11-06
  101. 当前增量包:F:/文档/康师傅skill/ksf-agent-query-rules-20261106-r1.zip。
  102. 原00—09、materials与旧ZIP保持历史快照;新增实例在完成原有身份/运行时开通和授权研究库后,使用此增量包对齐两份Skill。不要把旧materials直接当成当前查询规则,或复用其他员工身份/会话/凭据。
  103. 包内含精确基线升级器、两份Skill、数据与来源合同、9个查询示例、运维与王刚001验收单及本地验证回执。不含数据库,无Schema迁移;同一研究任务固定generation,新增数据经同一current.json取新代际。
  104. 目前仅本地准备和测试完成,生产安装、Agent实际加载、真实消息验收仍待运维与本人回报。
  105. ''', encoding='utf-8')
  106. print(json.dumps(receipt, ensure_ascii=False, indent=2))
  107. if __name__ == '__main__':
  108. parser = argparse.ArgumentParser(description=__doc__)
  109. parser.add_argument('phase', choices=['prepare', 'finalize'])
  110. args = parser.parse_args()
  111. prepare() if args.phase == 'prepare' else finalize()