在实践的第二步,我们把「建索引」这种重复动作固化成命令行。好的 CLI 不该只是懒人福音,而是让流程可被脚本编排、可被复现。
下面用 argparse 搭一个最小可用的 LEANN CLI 骨架,覆盖 build 与 query 两个子命令。注意我们强制要求 --dim 与嵌入一致,否则索引会静默错乱。
import argparse, json def build(args): # 读文档、切 chunk、嵌入、建索引,落盘到 args.out print(f'从 {args.src} 建索引 -> {args.out} dim={args.dim}') def query(args): print(f'在 {args.index} 上查: {args.text} 取前 {args.topk}') parser = argparse.ArgumentParser(prog='leann') sub = parser.add_subparsers(dest='cmd') pb = sub.add_parser('build'); pb.add_argument('--src'); pb.add_argument('--out'); pb.add_argument('--dim', type=int, required=True) pq = sub.add_parser('query'); pq.add_argument('--index'); pq.add_argument('--text'); pq.add_argument('--topk', type=int, default=5) # 演示:解析一条命令 ns = parser.parse_args('build --src docs/ --out idx/ --dim 64'.split()) getattr(__import__('__main__'), 'build')(ns)
CLI 的价值在于可编排。下面给出一个 shell 风格的执行序列(用 Python 描述),把「建索引→自检→查询」串成一条流水线,失败即停。
STEPS = [ 'leann build --src docs/ --out idx/ --dim 64', 'leann check --index idx/', 'leann query --index idx/ --text "退货政策" --topk 5', ] def run_pipeline(steps): for s in steps: print('执行:', s) # 真实环境用 subprocess.run,这里只演示编排与短路 if s.startswith('leann check') and False: return '自检失败,流水线终止' return '流水线完成' print(run_pipeline(STEPS))
案例:手动建索引漏了 dim 参数
--dim 64,索引用默认 128 建成。required=True,从机制上杜绝漏参。CLI 不是一次写完就不动的。随场景变多,你会想加子命令。下面给出可扩展的子命令注册模式,新命令只需挂进一个字典,不必改主解析逻辑。
COMMANDS = {} def cmd(name): def deco(fn): COMMANDS[name] = fn return fn return deco @cmd('stats') def stats(args): print('索引统计:', args.index) @cmd('export') def export(args): print('导出:', args.index, '->', args.out) def dispatch(name, args): if name not in COMMANDS: raise SystemExit(f'未知命令: {name}') return COMMANDS[name](args) print('已注册:', list(COMMANDS))
这种注册式写法让 CLI 像插件一样生长。我们建议每条命令都返回明确退出码,脚本编排时才能据此判断是否短路,和 3.1 的自检、5.2 的监控串成一条「失败可见」的链路。
CLI 还有一个隐性价值:它把「操作步骤」变成了「可审计的记录」。手动操作靠人记,错了无从复盘;CLI 命令写进脚本,哪天出问题,翻脚本就能还原当时做了什么。在受监管的行业里,这种可复现性本身就是合规要求,不止是工程便利。
我们也提醒一个误区:CLI 不是越多命令越好。命令膨胀会带来隐藏的维护成本和学习成本。我们主张每个命令对应一个明确的高频动作,低频或一次性的操作留给 Python API 或脚本,不让 CLI 变成瑞士军刀式的大杂烩。判断标准很简单:这个动作会不会被脚本反复调用?会,就进 CLI;不会,就别加。
| 该进 CLI | 留给 API/脚本 |
|---|---|
| 建索引、查索引 | 一次性迁移 |
| 健康检查 | 调试探针 |
CLI 的设计还影响团队协作的隐性成本。当所有环境操作都收敛到几条命令,新人不用读源码就能完成日常任务,资深的人也不用被反复打断去教同样的事。我们把这种「把知识固化进工具」的做法,视为团队规模化的前提。很多项目死在三人以上就乱,根因就是操作依赖口口相传,而 CLI 是把口口相传变成可执行文件的最直接手段。
当然,CLI 也要写帮助文本。每条命令的 help 就是它的最小文档,省略 help 等于把使用门槛又悄悄加回去。我们主张 help 里写清「做什么、要什么参数、产出什么」,而不是只列参数名。命令名也要克制,别用缩写让人猜,build 就是 build,可读性在每天被打字的地方最值钱。
把日常操作收敛成命令,本质上是在给团队买「不被打断」的自由,这笔账长期看远比省下的几行脚本值钱。
本节可考核点:能解释 CLI 相对手动操作的工程价值,并说明「必填参数」如何防止维度不匹配这类静默错误。