Skip to content

Instantly share code, notes, and snippets.

@FradSer
Last active June 30, 2026 15:51
Show Gist options
  • Select an option

  • Save FradSer/4b5973f98fc27d789b3c5d82503d2b1d to your computer and use it in GitHub Desktop.

Select an option

Save FradSer/4b5973f98fc27d789b3c5d82503d2b1d to your computer and use it in GitHub Desktop.
lark-* skill 组织策略 E2E 测试(decentralized vs routed router-skill),对比 larksuite/cli issue #1465

lark-* skill 组织策略 E2E 测试

对比两种 skill 发现面:decentralized(每个 skill description 自驱动)vs routed(子 skill 保留原版描述 + 上层 lark-router 统筹分发)。用真实 @skills/ lark-* 27 个 skill。模型:GLM-5.2

文件

  • cases.py — 12 个重叠测试用例
  • select_test.py — 主测试:把 skill 目录喂给 headless claude -p,让其输出 PICK: <skill>。用独立 HOME 隔离全局 office:lark 插件。
  • SKILL.md + dispatch.md — 自编 lark-router 的两个文件(gist 扁平化,原本在 lark-router/ 与 lark-router/references/ 下)
  • REPORT.md — 完整结果与结论

运行

# SKILLS_DIR 指向 larksuite/cli 的 skills/ 目录
SKILLS_DIR=/path/to/cli/skills python3 select_test.py > results.json
# 需先准备一个不带 plugins 的独立 HOME 用于隔离:
mkdir -p /tmp/fakehome/.claude && cp ~/.claude/.credentials.json /tmp/fakehome/.claude/
# select_test.py 内已把 HOME 指向 /tmp/lark-e2e/fakehome,按需改路径

需要 claude CLI(Claude Code)已登录。

结果摘要

  • decentralized: 12/12 (100%)
  • routed(原版描述 + router): 12/12 (100%)
  • /wiki/ 6 次重复:decentralized lark-doc 6/6;routed lark-doc 5/6, lark-wiki 1/6

结论:GLM-5.2 上 router 不削掉子层 description 时,加不加统筹路由 skill 无区别(纯开销)。

# Each case: (id, prompt, expected_terminal_skill)
# expected_terminal_skill = the skill that SHOULD ultimately handle it
# For routed strategy, success = the chain ends at this skill (router -> this).
CASES = [
# Overlapping URL/token routing — the regime that matters
("wiki_url", "帮我读一下这个知识库文档 https://example.feishu.cn/wiki/AbCdEf123456 的内容", "lark-doc"),
("docx_url", "这份云文档 https://example.feishu.cn/docx/ZeNtR9087 帮我看看正文写了什么", "lark-doc"),
("sheets_url", "https://example.feishu.cn/sheets/qrsTUV12 这个表格的第二个工作表有哪些字段", "lark-sheets"),
("base_url", "https://example.feishu.cn/base/bXyZ55 这个多维表格里有哪些记录", "lark-base"),
("slides_url", "https://example.feishu.cn/slides/PpQ998 这份幻灯片第一页写了什么", "lark-slides"),
("drive_upload","我有一个本地 Word 文件要上传到飞书云盘", "lark-drive"),
("markdown_edit","创建一个飞书 Markdown 文件并写入一段表格内容", "lark-markdown"),
("contact_lookup","帮我用张三的姓名查到他的 open_id,方便等会儿给他发消息", "lark-contact"),
("vc_history","查一下上周五那个已结束的视频会议有哪些参会人", "lark-vc"),
("task_create","给我创建一个今天到期的待办任务:完成季度复盘", "lark-task"),
("okr_view","看看我这个季度的 OKR 目标和关键结果", "lark-okr"),
("approval_pending","我有哪些待审批的审批单", "lark-approval"),
]

路由表(资源类型 → 具体 skill):

  • /docx/ URL/token、云文档正文读写 → lark-doc
  • /wiki/ URL/token、知识空间/节点/成员 → lark-wiki
  • /sheets/ URL/token、电子表格 → lark-sheets
  • /base/ URL/token、多维表格 → lark-base
  • /slides/ URL/token、幻灯片 → lark-slides
  • 画板 → lark-whiteboard
  • 云盘文件/文件夹、导入文件 → lark-drive
  • 消息/群聊 → lark-im
  • 邮件 → lark-mail
  • 日历/会议/会议室 → lark-calendar
  • 待办任务 → lark-task
  • OKR → lark-okr
  • 审批 → lark-approval
  • 考勤 → lark-attendance
  • 已结束会议/纪要/妙记产物 → lark-vc
  • 进行中会议/会中事件 → lark-vc-agent
  • minute_token/妙记 → lark-minutes
  • note_id/纪要直查 → lark-note
  • 姓名↔open_id 解析 → lark-contact
  • Markdown 文件 → lark-markdown
  • 实时事件监听 → lark-event
  • 妙搭应用 → lark-apps
  • 认证/授权 → lark-shared
  • 原生 OpenAPI 探索 → lark-openapi-explorer
  • 自定义 skill 制作 → lark-skill-maker
  • 会议纪要工作流 → lark-workflow-meeting-summary
  • 日程待办摘要工作流 → lark-workflow-standup-report

lark-* skill 组织策略 E2E 测试 — 更正版

模型:GLM-5.2(headless claude -p

测试

真实 @skills/ 下 27 个 lark-* skill,对比两种发现面(skill 正文完全一致,只改 description):

  • decentralized:每个 skill 原版 description 自驱动
  • routed:子 skill 保留原版完整 description + 上层加 lark-router 统筹 skill + references/dispatch.md 路由表(不削掉子层描述)

测 description 驱动的触发准确率。为隔离全局 office:lark 插件截胡,用独立 HOME(不带 plugins)跑。12 个最重叠用例(/docx/ /wiki/ /sheets/ /base/ /slides/ URL 等)。

结果

策略 全量准确率 /wiki/ 6 次重复
decentralized 12/12 (100%) lark-doc 6/6
routed(原版描述 + router) 12/12 (100%) lark-doc 5/6, lark-wiki 1/6

结论

在 GLM-5.2 上,router 层不削掉子 skill description 时,加不加统筹路由 skill 在触发准确率上没有区别——两策略全量 100% 持平,/wiki/ 也基本一致。

  • router 本身无准确率收益、也无损失,是纯开销(多一跳 + 多读 dispatch 表)。
  • 只有当 router 把子层 description 自隐掉(典型"统筹入口"设计)时准确率才下降——印证 issue #1465 评论的顾虑。
  • 若要做 router,务必保留子 skill 完整 description。

局限

测的是 description 驱动的选择,非真实 Skill 工具执行链(full-skill E2E 时模型倾向直接 Bash 调 lark-cli、且全局插件截胡),但隔离后干净测到了争论的量。不同模型可能有别。

import json, subprocess, os, re, glob, concurrent.futures, time, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from cases import CASES
# Point this at a checkout of larksuite/cli's skills/ dir (or any lark-* skill dir).
SKILLS_DIR = os.environ.get("SKILLS_DIR", os.path.join(os.path.dirname(os.path.abspath(__file__)), "skills"))
def load_descriptions():
descs = {}
for d in sorted(glob.glob(os.path.join(SKILLS_DIR, "lark-*"))):
p = os.path.join(d, "SKILL.md")
if not os.path.isfile(p):
continue
txt = open(p, encoding="utf-8").read()
fm = txt.split("---", 2)[1] if txt.startswith("---") else ""
m = re.search(r'^description:\s*(.+?)(?=\n[a-z_-]+:|\n---|\Z)', fm, re.S | re.M)
desc = (m.group(1).strip() if m else "").strip().strip('"')
descs[os.path.basename(d)] = desc
return descs
DESCS = load_descriptions()
ROUTER_DESC = '飞书/Lark 资源统筹路由:当用户给出任何飞书资源 URL/token(/docx/ /wiki/ /sheets/ /base/ /slides/ 路径)或描述飞书操作意图时使用本 skill 做统筹分发,先识别资源类型再分发到对应具体 skill。'
def build_prompt(strat, user_prompt):
valid_names = sorted(DESCS.keys())
name_list = ", ".join(valid_names)
name_constraint = (
f"VALID SKILL NAMES (you MUST pick one of these EXACT strings, nothing else — "
f"do not invent or use any name outside this list): {name_list}\n"
)
if strat == "decentralized":
catalog = "\n".join(f"- {n}: {d}" for n, d in sorted(DESCS.items()))
instr = (
f"Below are the available skills (name: description). Read the user request and output EXACTLY one line: PICK: <skill-name>\n"
f"{name_constraint}"
f"Pick the single skill whose description best matches the request. Output only the PICK line, no explanation.\n\n{catalog}\n\nUSER REQUEST: {user_prompt}"
)
else:
# Routed strategy: router skill ON TOP of the sub-skills' ORIGINAL full
# descriptions (same surface as decentralized, plus a router entry).
# This tests whether adding a router layer helps/hurts when sub-skill
# descriptions are fully present.
catalog = "\n".join(f"- {n}: {d}" for n, d in sorted(DESCS.items()))
instr = (
f"Below are the available skills (name: description), headed by a routing skill. Read the user request and output EXACTLY one line: PICK: <sub-skill-name>\n"
f"{name_constraint}"
f"Route via lark-router conceptually first, then pick the terminal sub-skill it would dispatch to (do not pick lark-router itself). Output only the PICK line, no explanation.\n\n"
f"ROUTER: lark-router: {ROUTER_DESC}\nSUB-SKILLS:\n{catalog}\n\nUSER REQUEST: {user_prompt}"
)
return instr
def run(strat, case):
cid, prompt, expected = case
instr = build_prompt(strat, prompt)
start = time.time()
env = dict(os.environ, HOME="/tmp/lark-e2e/fakehome")
# cwd = the strategy's skill dir so ./.claude/skills (the clone) is discovered
cwd = f"/tmp/lark-e2e/{strat}"
proc = subprocess.run(
["claude", "-p", "--output-format", "json", instr],
cwd=cwd, capture_output=True, text=True, timeout=180, stdin=subprocess.DEVNULL, env=env)
pick = ""
try:
ev = json.loads(proc.stdout)
txt = ev.get("result", "")
raw_pick = ""
for ln in txt.splitlines():
if "PICK:" in ln:
raw_pick = ln.split("PICK:", 1)[1].strip().strip("`,").split()[0]
break
# If the model picked a name outside the catalog (e.g. its own
# installed office:lark router), that counts as no clone selected.
if raw_pick in DESCS:
pick = raw_pick
else:
# fall back: any catalog name mentioned in the full text
mentions = [n for n in DESCS if n in txt]
pick = mentions[0] if mentions else ""
except Exception:
pass
return dict(strat=strat, case=cid, expected=expected, pick=pick,
correct=(pick == expected), dur=round(time.time() - start, 1))
cases = [(s, c) for s in ["decentralized", "routed"] for c in CASES]
results = []
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as ex:
futs = {ex.submit(run, s, c): (s, c) for s, c in cases}
for fut in concurrent.futures.as_completed(futs):
try:
results.append(fut.result())
except Exception as e:
s, c = futs[fut]
results.append(dict(strat=s, case=c[0], error=str(e)))
results.sort(key=lambda r: (r["strat"], r["case"]))
print(json.dumps(results, ensure_ascii=False, indent=2))
name lark-router
description 飞书/Lark 资源统筹路由:当用户给出任何飞书资源 URL/token(doubao.com 的 /docx/ /wiki/ /sheets/ /base/ /slides/ 路径)、或描述一个飞书操作意图(文档、表格、多维表格、幻灯片、画板、云盘、知识库、消息、邮件、日历、任务、OKR、审批、考勤、视频会议、妙记、纪要)时使用本 skill 做统筹分发。先识别资源类型,再加载路由表分发到对应具体 skill。不要直接处理业务,只做识别与分发。

Lark Router (统筹路由)

你是所有飞书/Lark 资源操作的统一入口。识别资源类型后,分发到对应具体 skill,不要自己处理。

步骤

  1. 从用户输入识别资源类型(URL 路径模式 / token / 操作意图)。
  2. 加载 @references/dispatch.md 查路由表。
  3. 用 Skill 工具调用对应具体 skill(如 lark-doc / lark-wiki / lark-sheets / lark-base / lark-slides / lark-whiteboard / lark-drive / lark-im / lark-mail / lark-calendar / lark-task / lark-okr / lark-approval / lark-attendance / lark-vc / lark-vc-agent / lark-minutes / lark-note / lark-contact / lark-markdown / lark-event / lark-apps / lark-shared / lark-openapi-explorer / lark-skill-maker / lark-workflow-meeting-summary / lark-workflow-standup-report)。
  4. 转交用户请求并返回该 skill 的输出。

完成后,最终消息输出一行:RESULT: routed to

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment