- rewrite Part 2 as real tests: Claude Fable 5 / Codex GPT-5.5 / Qwen3.6-27B - add cross-env comparison table with per-round token caliber caveats - backfill roadmap evidence: lookup.py encoding fix, equity/misc.md 9252-line pile-up Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
目录
🛠 第一部分 · 技术路径
开发者视角 —— playbook 是什么、如何搭建、往哪演进
项目介绍
TSL(天软语言)是天软金融分析平台的专用编程语言,语法接近 Pascal,内置海量金融数据仓库函数(行情、财务、板块、选股、回测等)。它的三个特点,让通用大模型很容易写错:
- 语法冷门 —— 赋值用
:=、类定义要写type Name = class ... end;、.tsl有严格的"语句区在前、声明区在后"规则,与主流语言的直觉直接冲突。 - 函数量巨大 —— 数据仓库函数上万个,签名各异,模型凭记忆一定会编造参数。
- 文件模型敏感 ——
.tsl(可执行脚本)与.tsf(可复用声明)选错就直接编译失败。
TSL Playbook 就是为解决这个问题而生的知识层。它不是解释器,也不是运行时,而是一套喂给 AI 编码代理的结构化文档 + 检索工具 + 硬约束路由,目标只有一个:
让 AI 在写 TSL 时,每一处语法和每一个函数签名,都来自可核对的文档事实,而不是"看起来像别的语言"的幻觉。
核心理念
整个 playbook 建立在四条铁律之上,它们贯穿所有文档:
| 原则 | 含义 |
|---|---|
| 🚫 禁止发明语法 | 无文档结论、文件模型不明或执行事实缺失时,停止并确认,绝不用 Pascal / Python / JS 的相似写法补全。 |
| 🎯 首跳路由 | 任何 TSL 需求,第一跳统一进唯一入口,由入口分流到具体页,不全目录搜索、不凭空猜路径。 |
| 🧱 块级证据 | 页面级元数据只做粗判断;落代码时以块级 代码块身份 为准 —— 只有标注 可直接照写示例 的代码块能当源码外形照抄。 |
| 💾 Token 高效 | 分层索引 + 按需检索,相比通读全部文档可节省 80–90% 上下文开销。 |
架构设计
playbook 是一个金字塔式的知识结构:顶部是极简的硬约束,中部是分层的事实文档,底部是可执行的检索工具。下面四小节自顶向下逐层拆解,最后由「首跳路由」把各层串成一条完整链路。
分层知识库
文档按职责而非按目录平铺组织,每一层只回答自己那一层的问题:
| 层 | 位置 | 负责回答 | 不负责 |
|---|---|---|---|
| 硬约束层 | AGENTS.md |
铁律、首跳、阻断条件 | 完整语法(故意不复写) |
| 语法事实层 | docs/tsl/syntax/ |
语言规则、文件模型、控制流、类、TS-SQL | 具体函数签名 |
| 模块集成层 | docs/tsl/modules/ |
pyTSL、策略回测、微信通知 API | 纯语法、通用取数 |
| 风格偏好层 | code_style.md naming.md |
命名与组织偏好 | 语法事实(明确声明"仅偏好") |
| 函数事实层 | tsl-api-reference skill |
12455 个函数的名字 / 签名 / 参数 / 返回值 | 语言语法 |
每个语法文档页头都带一组机器可读的元数据,让代理在读正文前就能判断能否照抄:
文档类型:语法主线
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
遇到不确定时:<指向下一跳的具体页>
TSL 语法体系
docs/tsl/syntax/ 用 24 个专题覆盖了 TSL 语言的方方面面,从最短骨架到矩阵深潜:
| 主题簇 | 专题 |
|---|---|
| 入门与模型 | 01 快速落代码 · 02 文件模型(.tsl/.tsf)· 03 值与字面量 · 04 变量常量 |
| 函数与表达式 | 05 函数与调用 · 06 表达式运算符 · 07 控制流 |
| 对象与模块 | 08 类与对象 · 09 unit 与作用域 · 24 算符/遍历重载 |
| 运行时 | 10 运行时上下文与 with · 15 调试与性能 · 20 对象自省 · 21 内置运行时对象 |
| 数据结构 | 12 数组与集合 · 22 矩阵深潜 · 23 FMArray · 13 结果集过滤 |
| 数据查询 | 14 TS-SQL |
| 底层与互操作 | 16 词法与编译选项 · 17 类型转换 · 18 external/DLL/线程 · 19 namespace/Libpath |
| 避坑 | 11 高频误写与反例 |
每一页都用四种块级身份给代码块打标签,这是"禁止发明语法"落地的关键:
| 代码块身份 | 能否照抄 | 用途 |
|---|---|---|
✅ 可直接照写示例 |
能 | 唯一可当源码外形的块 |
⚠️ 反例 / 不可照写 |
否 | 展示会编译/运行失败的写法 |
📤 输出片段 |
否 | 只是运行结果 |
🔧 配置片段 / 概念骨架 |
否 | 带 <占位符> 的模板 |
示例: 语法页会明确告诉代理,
.tsl语句区可以调用后置的函数声明,但在声明区后再追加脚本语句是反例 —— 并附上真实报错invalid statement作为负向边界。
tsl-api-reference Skill
上万个数据仓库函数无法塞进上下文,playbook 把它做成一个可调用的 Agent Skill:一个 function_index.tsv(12455 条:2404 个 builtin + 10051 个 dotnet)加一个 lookup.py 检索脚本。
# 已知函数名 → 精确查签名、参数、返回值、示例
python skills/tsl-api-reference/scripts/lookup.py --name argmax
# 只知道行为 / 中文关键词 → 关键词检索(AND 语义)候选
python skills/tsl-api-reference/scripts/lookup.py --kw 数组 排序
检索是两段式的:关键词查询先返回候选行(名字 + 一句话摘要),代理挑中后再用 --name 拉取该函数的完整文档块。函数文档本身分两大来源:
| 来源 | 分类 | 覆盖 |
|---|---|---|
| builtin(语言内置) | 12 类 | math(636) · base(560) · language(441) · external · resource · graphics · document · gui · system … |
| dotnet(数据仓库) | 21 类 | sector(2405) · equity(1967) · fund(1561) · fundamentals · market_data · bond · quant · macro · futures … |
Skill 还内置了防幻觉规则:不从记忆或相似语言推断签名;TSL 名字大小写无关但下划线有意义,查询时不得删下划线或改成驼峰。
首跳路由机制
这是 playbook 最核心的设计。代理面对任何 TSL 需求,都遵循同一条决策链 —— 永远从固定入口开始,逐级收窄,而不是漫无目的地翻文档:
flowchart TD
A[用户提示词] --> B{AGENTS.md<br/>硬约束 + 首跳}
B -->|需要任何 TSL 事实| C[docs/tsl/index.md<br/>总入口]
C -->|写代码/语言规则| D[syntax/index.md<br/>语法路由]
C -->|取金融数据/查函数| E[tsl-api-reference<br/>Skill]
C -->|模块集成| F[modules/index.md]
C -->|命名/风格| G[naming.md /<br/>code_style.md]
D --> H[24 个语法专题]
E -->|已知名 --name<br/>未知名 --kw| I[12455 条索引]
H --> Z[✅ 落 .tsl / .tsf 代码]
I --> Z
F --> Z
style B fill:#2b7489,color:#fff
style C fill:#3178c6,color:#fff
style E fill:#8250df,color:#fff
style Z fill:#2ea043,color:#fff
每个路由表都是**「任务信号 → 入口 → 阻断条件」三列结构,多行命中时自上而下取第一个,且每一跳都带明确的"什么时候不**该走这条"的阻断条件,避免过度泛化。
目录结构
playbook/
├── AGENTS.md # 顶层硬约束:铁律 + 首跳路由(不复写语法)
├── docs/tsl/
│ ├── index.md # TSL 总入口:任务 → 文档区分流
│ ├── naming.md # 命名偏好(非语法事实)
│ ├── code_style.md # 代码风格偏好(非语法事实)
│ ├── toolchain.md # 工具链与验证命令模板
│ ├── syntax/ # 24 个语法专题 + index.md 路由
│ │ ├── 01_quickstart.md # ├─ 快速落代码 / 最小骨架
│ │ ├── 02_core_model.md # ├─ .tsl / .tsf 文件模型
│ │ ├── ... # ├─ 函数/类/控制流/矩阵/TS-SQL...
│ │ └── 24_object_overloads... # └─ 算符与遍历重载
│ └── modules/ # 模块集成:pyTSL / 回测 / 微信
└── skills/
└── tsl-api-reference/ # 函数检索 Skill
├── SKILL.md # ├─ 触发说明
├── scripts/lookup.py # ├─ 检索脚本
├── data/function_index.tsv# ├─ 12455 条函数索引
└── references/codegen/ # └─ 函数完整文档块
后续升级方向
playbook 目前已覆盖语法、函数、模块三大知识面,下一步按优先级从高到低,围绕架构演进、分类治理、覆盖广度、检索质量四个方向推进:
🧩 架构演进 · 语法层 Skill 化
- 方向 — 把
syntax/的 24 篇知识页搬进独立 skill 作为references/,由SKILL.md正文承载「任务信号 → 页 → 阻断条件」的首跳路由表,与tsl-api-reference对齐成统一的 skill 调用模型;AGENTS.md保留为常驻硬约束闸门。 - 目的 — 让语法与函数两类知识统一走 skill 触发,获得可分发、可移植的标准打包单元,并降低对"先读
AGENTS.md、再照链接逐跳"这条隐式流程的依赖。 - 难点
- 确定性 vs 模糊触发 — 纲领是可审计的有序路由 + 阻断条件,而 skill 靠
description被模型模糊匹配触发;syntax 的"写代码"触发面与 api-reference 的"取数"高度重叠,有序消歧逻辑必须完整保留进SKILL.md正文,不能退化成模型猜测。 - 常驻不变量 — skill 是被触发的,替代不了
AGENTS.md「禁止发明语法 + 先定.tsl/.tsf文件模型」这条必须最先命中的硬约束,须防止自动触发把代理直接拽进语法页、跳过文件模型判断。 - 证据随迁不走样 —
可直接照写示例等块级身份标注要在目录搬迁后保持有效、不失链。
- 确定性 vs 模糊触发 — 纲领是可审计的有序路由 + 阻断条件,而 skill 靠
🗂 分类治理 · codegen 归类校准与全量可用性核验
- 方向 — 复核 codegen 母本(builtin 12 类 / dotnet 21 类)的函数归类,纠正错分与
misc兜底堆积(如equity/misc.md单页已累至 9252 行、759 个函数),让类目边界清晰;并对全部 12455 个函数逐一探针实测,给每个函数标注真实可用性状态。 - 目的 — 一方面让
--kw关键词检索落在符合直觉的类目、候选更准;另一方面用实测的「可用 / 真缺失 / 参数不对」三态,取代"索引里有就默认能用"的假设,从源头杜绝把不可用函数写进生成代码。 - 难点
- 重分类无生成器 — codegen 生成脚本只剩
.pyc,母本 md 已是产物;重新归类只能直接改 md 母本与 routes 并同步维护索引一致性,不能再靠重跑生成器。 - 可用性须真解释器 — 「可用」无法静态判定,须接真实 TSL 解释器对上万函数逐一探针(
toolchain.md的执行入口目前仍是模板占位),规模大、成本高。 - 假阴性要甄别 — 探针报错可能是"函数真缺失",也可能只是"参数没给对";两者须隔离区分(
special/pendingvsspecial/unknown),否则会把本可用的函数误标为不可用。
- 重分类无生成器 — codegen 生成脚本只剩
📚 覆盖广度
- 扩充
modules/:补充更多平台模块(数据落库、告警渠道、报表导出)的 API 事实页。 - 沉淀场景配方库(Recipe):把"选股 → 回测 → 通知"这类高频端到端流程做成可照写的组合骨架。
- 为常见报错建立反例 → 正确写法的快速跳转索引,让避坑更即时。
🔍 检索质量
- 为
function_index.tsv增加参数类型实证:当前仍有约 2600 个函数(builtin 246 / dotnet 2382)的参数声明为any/object,需用dataType()逐一收紧。 - 引入语义检索(embedding),让"我想算个动量因子"这类模糊意图也能命中相关函数。
- 补齐关键词索引的中文近义词,降低
--kw空命中率。 - 修复
lookup.py跨环境输出编码(GBK/UTF-8):第三轮实测中,乱码直接导致大量重复检索。
👤 第二部分 · 实际场景
用户视角 —— 同一条提示词,不同环境实测
这一部分没有虚构:同一条提示词,投给不同的 AI 编码环境,记录真实行为与产出。 三轮实测:Claude Code · Fable 5、Codex CLI · GPT-5.5、Qwen 3.6-27B。
统一提示词
💬 阅读AGENTS.md。写一个tsl策略。获取昨天的主板所有股票涨幅排名,取前50股票代码
考察点只有五个:函数敢不敢编、语法照不照抄、文件后缀选没选对、会不会自己验证、拿不准时是硬写还是明说。
第一轮实测:Claude Code · Fable 5
真实过程回放(每一步都能对应第一部分的机制):
| 步骤 | 实际行为 |
|---|---|
| 🧭 首跳照走 | 读 AGENTS.md → 进 docs/tsl/index.md 总入口 → 按路由取用快速落代码 / 控制流 / 数组集合三个语法页 |
| 🔍 函数全部查证 | 调用 tsl-api-reference skill 逐一核实 getAbkbyDate · lastTradeDay · stockZf8 · appendArray · sortTableByField —— 五个函数零编造 |
| 📄 文件模型判对 | 一次性取数任务 → 按规则判为 .tsl,落盘 scripts/main_board_top50_zf.tsl |
| ✅ 主动验证 | 本机解释器实测:键表构造、循环截断、排序等 builtin 逻辑全部跑通,整个文件编译通过 |
| 🗣 诚实报告边界 | 数据仓库函数离线环境没有(function not found),如实说明需在天软客户端端到端确认,而不是谎称全部跑通 |
产出代码(原样):
// 主板股票昨日涨幅排名前 50
// 主板成分:上证A股 + 深证A股(getAbkbyDate 支持分号分隔多板块)
// "昨天"取当前日期的前一个交易日,避开周末/节假日
setSysParam(pn_cycle(), cy_day());
yday := lastTradeDay(date()); // 昨日(最近一个已收盘交易日)
prevday := lastTradeDay(yday); // 昨日的前一个交易日
stocks := getAbkbyDate('上证A股;深证A股', yday);
result := array();
for i, stockid in stocks do
begin
setSysParam(pn_stock(), stockid);
zf := stockZf8(prevday, yday); // prevday收盘 -> yday收盘的涨幅(%)
appendArray(result, array("StockID": stockid, "Zf": zf));
end
sortTableByField(result, "Zf", 0);
top50 := array();
n := length(result);
if n > 50 then begin n := 50; end
for i := 0 to n - 1 do
begin
appendArray(top50, result[i]["StockID"]);
end
return top50;
两处细节见功力:"昨天"没有偷懒写成日历昨天,而是用 lastTradeDay(date()) 取最近一个已收盘交易日——周一运行会正确落在上周五;"主板 = 上证A股 + 深证A股"这个组合也是从函数文档示例里查证的,不是想当然。
成本: 总耗时 16 分 57 秒;token 为模型自估——输入约 15–20 万(多数命中提示缓存),输出约 0.5–1 万。
第二轮实测:Codex CLI · GPT-5.5
环境:OpenAI Codex CLI v0.142.5 · gpt-5.5(reasoning xhigh);提示词在第一轮基础上追加指定了输出路径
scripts/main_board_top50_zf_codex.tsl。
真实过程回放:
| 步骤 | 实际行为 |
|---|---|
| 🧭 首跳照走 | 读 AGENTS.md → 进 docs/tsl/index.md → 按路由取用 TS-SQL / 控制流 / 数组集合语法页,并核对 toolchain.md 确认验证入口仍是占位 |
| 🧗 Skill 未注册,自行接通 | tsl-api-reference 不在其会话技能清单里 —— 没有放弃也没有瞎编,循索引找到仓库内 skills/tsl-api-reference/SKILL.md,直接用 python 调起 lookup.py 完成检索 |
| 🔍 函数全部查证 | --kw 圈定候选后逐一 --name 核实 getAbkbyDate · stockZf3 · yesterday · spec · specDate · sortTableByField · min · length · writeLn;查 MarketTable 无此函数 → 正确判断它是数据表名,转 TS-SQL 语法页核对写法 |
| 🚫 证据驱动的弃用 | 查到 getTopN 存在,但其文档记录示例运行报 not found —— 主动弃用,改用文档已证明的 order by ... desc + 手动循环取前 50 |
| 🗣 诚实报告边界 | PATH 中无 tsl/tslcli,无法真实运行 → 只做静态检查(无裸 = 赋值、无声明区、关键语句在位),并如实说明"没法做真实 TSL 运行验证" |
产出代码(原样):
end_t := yesterday();
stocks := getAbkbyDate('上证A股;深证A股', end_t);
data := select ['StockID'] as "Code",
spec(specDate(stockZf3(), ['date']), ['StockID']) as "Zf"
from markettable datekey end_t to end_t of stocks end;
sorted := select ["Code"] from data order by ["Zf"] desc end;
n := min(50, length(sorted));
result := array();
if n > 0 then
begin
for i := 0 to n - 1 do
begin
result[i] := sorted[i]["Code"];
writeLn(result[i]);
end;
end;
return result;
可圈可点: getTopN 的弃用是「块级证据」规则的教科书式执行——函数在索引里存在不等于能用,文档里的负向记录同样是事实;Skill 未注册时循 AGENTS.md 路由自行找到本地脚本,说明硬约束层的兜底指路生效了。一处对照差异: "昨天"用的是 yesterday() 日历昨天,未处理周末/节假日——第一轮用 lastTradeDay 规避了周一取到周日的问题。
成本: 本轮工作 9 分 21 秒;token 为 /status 实测——总量 18.5 万 = 输入 15.9 万 + 输出 2.57 万(上下文余量 42%)。
第三轮实测:Qwen 3.6-27B
环境:qwen3.6-27b(27B 开源模型,代理框架未注明);提示词同样追加指定了输出路径
scripts/main_board_top50_zf_qwen3.6-27b.tsl;耗时未记录。
真实过程回放:
| 步骤 | 实际行为 |
|---|---|
| 🧭 首跳照走 | 读 AGENTS.md → 进 docs/tsl/index.md → 取用快速落代码 / TS-SQL 语法页,并直接调用 lookup.py 检索(其环境中文输出乱码,带病工作) |
| 🌀 文档缺口处反复打转 | 盯上 StockZf_No(区间涨幅排名),但该函数的 sort_by / return_type 文档确实没给取值 —— 数十轮反复检索、重读同一页,期间明确引用铁律"无文档结论……不发明语法",始终拒绝猜参数,最终绕开该函数 |
| ⚠️ 起念假设,又自行回撤 | 中途多次想按"惯例"假设 MarketTable 的 pre_close 等字段名与 refof/refsof 用法,推演后自行放弃该路线,改用全部经查证的 stockZf 循环方案 |
| 🔍 最终函数全部有据 | 落码只用查证过的 yesterday · getAbkbyDate · setSysParam/PN_Stock · stockZf · drange · dateToStr · echo |
| 🤐 收尾缺验证与边界声明 | 产出后仅回读文件自查"逻辑看起来正确",未做静态检查,也未声明"未经运行验证" —— 三轮中唯一没交代验证边界的 |
产出代码(原样):
#! 获取昨天主板所有股票涨幅排名,取前50股票代码
dt := yesterday();
dt_prev := dt - 1;
stocks := getAbkbyDate('上证A股;深证A股', dt);
n := length(stocks);
gains := array();
for i := 0 to n - 1 do
begin
setSysParam(PN_Stock(), stocks[i]);
zf := stockZf(dt_prev, dt);
gains[i] := array("code": stocks[i], "zf": zf);
end;
top50 := select * from gains
order by ["zf"] desc
end;
top50 := select drange(0 to 49) * from top50 end;
echo "昨天(" $ dateToStr(dt) $ ")主板涨幅前50:";
for i := 0 to length(top50) - 1 do
begin
echo (i + 1) $ ". " $ top50[i]["code"] $ " 涨幅:" $ top50[i]["zf"] $ "%";
end;
这一轮最有意思: 铁律对 27B 小模型同样有约束力——面对文档缺口,它宁可绕路几十轮也没编造参数;但代价是收敛效率的巨大差距,且收尾少了前两轮的验证动作与诚实边界。它还反向暴露了知识库自己的洞:StockZf_No 的参数取值枚举确实没写——正是「后续升级方向 · 检索质量 / 分类治理」要补的文档债。交易日处理同样最弱:yesterday() 与 dt - 1 全按日历日,周末/节假日双双踩空。
成本(模型自估,口径偏小): 约 1.5–2.5 万 token。其事后自查归因:20+ 次无效检索全耗在 StockZf_No 没写的参数枚举上;9252 行的 equity/misc.md 被反复载入约 15 次;lookup.py 输出 GBK/UTF-8 乱码又加剧了关键词重试。注意:该自估按"读到的内容量"计,未计 30+ 次工具调用的上下文重复携带,实际消耗应显著更高。
横向对比
| 观察维度 | Claude Code · Fable 5 | Codex CLI · GPT-5.5 | Qwen 3.6-27B |
|---|---|---|---|
| Skill 接入 | 已注册,直接调用 | 未注册 → 循路由自行找到 lookup.py 手动调用 |
未注册 → 手动调用(检索输出乱码,带病工作) |
| 函数真实性 | 5/5 全部查证,零编造 | 全部查证,并依据文档负向记录弃用 getTopN |
全部查证;面对 StockZf_No 文档缺口拒绝编参数,绕路重来 |
| 交易日处理 | lastTradeDay 取最近交易日,周一跑正确落上周五 |
yesterday() 日历昨天,逢周末可能取到无行情日 |
yesterday() 与 dt-1 全日历日,三轮中最弱 |
| 文件模型 | .tsl 判定正确 |
.tsl(提示词给定文件名) |
.tsl(提示词给定文件名) |
| 自主验证 | 本机解释器实测 builtin 跑通、整文件编译通过 | 环境无解释器,静态检查 | 仅回读文件自查,无静态/运行验证 |
| 边界诚实度 | 明说 dotnet 函数需客户端确认 | 明说无法真实运行验证,列出静态证据 | 收尾未声明未验证状态 |
| 检索效率 | 一次路由到位 | 一次路由到位 + 证据驱动取舍 | 在同一文档缺口反复打转数十轮 |
| 总耗时 | 16m57s | 9m21s | 未记录 |
| token | 自估:输入 15–20 万 / 输出 0.5–1 万 | 实测:18.5 万 = 15.9 万入 + 2.57 万出 | 自估:约 1.5–2.5 万(未计上下文重复,口径偏小) |
对照说明: ① 第二、三轮提示词在第一轮基础上各自追加了输出文件名,文件命名被显式给定;② 第二轮运行时仓库内已有本 README 的同题示例,模型读到过该片段(其后仍逐一核对了函数事实);③ 三轮本地条件不同——第一轮机器装有离线 TSL 解释器,第二轮 PATH 无解释器,第三轮未尝试任何运行验证;④ 第三轮的代理框架与耗时未记录,token 为模型按"内容量"事后自估、未计多轮上下文重复携带——三轮 token 口径各不相同(实测 / 含缓存自估 / 内容量自估),不可直接横比。以上数字与行为仅作真实记录,不构成严格受控对比。
TSL Playbook —— 不是让 AI "会写代码",而是让 AI "只写有据可查的代码"。