# 内置提示词的依据与实测 这一份回答两个问题:默认改写提示词**为什么是现在这六步**,以及这个结论**是怎么测出来的**。可复现的入口有两个:`npm run eval:prompt`(三臂盲评)与 `npm run eval:attribution`(归因审计),两个脚本默认都只读已有记录或只打印计划、不发请求。 这里记了两轮测量。第一轮(一~四节)问「六步文本比旧的规则清单好吗」,答案是**好,而且与长度无关**。第二轮(第五节)问的是更硬的一个问题:**改写里有多少东西是用户根本没提的**——答案不好看,但它把一个此前只存在于直觉里的取舍变成了数字;两个方向其实是对立的(第六节)。 ## 一、动它的理由 上一版默认提示词是**并列的规则清单**:22 条规则 + 8 条禁止 + 输出契约,共 2187 字,没有优先级。两个具体问题: 1. **写死了领域。** 第 5 条把任务分成「编码 / 写作 / 调研 / 数据分析 / 配置 / 决策咨询」六类,再按类型选字段。但字段表里有「技术栈与版本」「不得引入的依赖」「性能与成本预算」——对写作、调研、决策类任务全是噪音。 2. **最常见的输入不在枚举里。** 输入框里最常出现的草稿是**提问**(「为什么这个接口报 500」),六类里没有它,于是被硬塞进「决策咨询」,再被第 6 条逼着产出一条「非目标」和一套「验收标准」。 另外,`lib/prompt.js` 的注释声称这份提示词是按 `../prompt-engineering-research.md` §4 校准的,而**那个文件在仓库里、在整个工作区里都不存在**:一个无法被任何人验证的出处声明。 ## 二、依据 - **官方指南的稳定结论**:指令按编号分步、用显式分隔符区分「数据」与「指令」、长输入在前问题在后([Anthropic prompting best practices](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices)、[Google Gemini prompting strategies](https://ai.google.dev/gemini-api/docs/prompting-strategies))。据此**保留**中文编号 Markdown 与 payload 层的 `<<<…>>>` 分隔符,**不引入** XML 标签——那是 Claude 的专有优化,而改写跟随任意会话模型(见下一条)。 - **模型无关是硬约束**:改写用的是会话当前模型,谁的会话就是谁的模型。所以提示词不写任何模型专有语法:不为 Claude 塞 XML,也不为某一系模型牺牲中文编号规则。 - **一条二手说法**:「GPT 系 system prompt 最佳长度 800–1200 字符,超过 2500 字符后指令召回率下降」([segmentfault](https://segmentfault.com/a/1190000047944078)、[简书](https://www.jianshu.com/p/fa09f7f034cd))。没有官方出处,而且与本插件 2187 字的现状直接冲突——所以**不当作结论,当作假设**:评估集里专门留了一个 1148 字的极简臂去检验它。 - **自动提示词优化的文献**(APE/OPRO、[PRewrite](https://aclanthology.org/2024.acl-short.54.pdf)、[System Prompt Optimization with Meta-Learning](https://arxiv.org/abs/2505.09666))走的是「让模型自己搜提示词」这条路。本次**没有走**:在没有独立评分器的前提下,自动循环会过拟合到评审模型本身。手写的三臂盲评是同一思路的低成本版本。 ## 三、方法 - **固定草稿集**:`scripts/eval/drafts.json`,18 条 = 10 条真实草稿(从本仓库工作区的会话日志 `~/.dsh/sessions/--D-workspace-dsh-prompt-tuner--/` 里挖出,脱敏后逐字保留,含口语、错别字与残缺句)+ 8 条合成边界样本(一句话请求 / 中文提问 / 英文提问 / 长规格 / 报错堆栈 / 纯闲聊「早」/ 裸代码片段 / 长杂乱需求堆)。集合用 SHA-256 钉在运行记录里,改一条就会对不上。 - **三臂**:`previous` = 改动前的发货文本;`candidate` = 待测文本(必须与 `lib/prompt.js` 的 `DEFAULT_SYSTEM_PROMPT` 逐字相同);`minimal` = 刻意压到 1148 字的极简版。第一轮另有一个 `baseline` 臂(2187 字的 22 条规则版),第二轮起用 `previous` 取代它。每条臂跑完 18 条草稿。 - **走真实通路**:每条臂通过宿主自己的路由执行——把臂文本写进插件的 `systemPrompt` 设置(`POST /save`),再 `POST /optimize`,跑完在 `finally` 里恢复原设置。这样测到的是用户真会拿到的东西(payload 框架、模型路由、思考强度都一样),而不是一个私有的 HTTP 客户端。语言臂追加的那行指令来自 `scripts/eval/language-*.txt`,并由 `npm run check` 与插件导出**逐字比对**:被测的文本必须就是发货的文本。 - **盲评**:由**另一个模型**在随机顺序、隐去臂标签的条件下给 A/B/C 打分,5 项 1–5 分:保真 / 充分性 / 可执行性 / 体量 / 不编造。盲映射由「运行时间戳 + 草稿 id」决定,可复现。 - **归因审计(第二轮)**:`scripts/eval-attribution.mjs` 只读上一步留下的改写记录,用两种互补的办法问「这段改写加了什么用户没提的东西」。**机械半**不需要模型、完全可复现:把改写里所有反引号片段、路径与带单位的数字挑出来,凡是**在原始输入里根本不存在**的就记一次「静默实体」,除非它出现在改写自己的「待确认」小节里(那是模型主动标出来的,算已标注);同一趟还数模板小节(`目标`/`约束`/`交付物`/`验收` 这类标题)、「请给出/必须包含」这类答案内容清单,以及长度比。**语义半**(`--judge`)由模型逐条列出改写里的每一项要求,判它是 `stated`(原话有)/ `labeled`(用户没提、但标进了待确认)/ `silent`(用户没提、也没标)/ `dropped`(原文有、改写后丢了)。两半都不给质量打分:它们只回答「加了什么」,好坏由人判。 - **审计的对象必须是发货文本**:审计跑在与盲评同一次运行留下的记录上,并重新计算该运行记录的 system sha 与今天的 `lib/prompt.js` 比对——不一致就说明审的不是发货的那一版,脚本会直接说出来。 - **命令**:`node scripts/eval-prompt.mjs --arms candidate,previous,minimal --live --concurrency 3` 跑改写(54 次调用),同参数加 `--judge` 跑盲评(18 次调用),`node scripts/eval-attribution.mjs --judge` 跑归因(54 次调用)。 ## 四、盲评结果(第一轮) 三轮,同一草稿集,候选文本迭代了两次(每轮 3 臂 × 18 条 × 5 项均分): | 轮次 | 候选文本 | baseline | candidate | minimal | | --- | --- | --- | --- | --- | | 1 | 22 条规则改写成六步,2187 字 | 4.08 | 4.03 | **4.18** | | 2 | 加上「不添加用户未要求的要求清单」,2244 字 | 4.01 | 3.86 | **4.34** | | 3 | 压缩到 1379 字(第一轮发货版) | 3.92 | 4.15 | **4.20** | 第 3 轮的分项(发货版 vs 改前): | 臂 | 保真 | 充分性 | 可执行性 | 体量 | 不编造 | 均分 | | --- | --- | --- | --- | --- | --- | --- | | baseline(2187 字) | 3.94 | 4.17 | 3.94 | 3.39 | 4.17 | 3.92 | | **candidate(1379 字,第一轮发货版)** | **4.53** | 3.83 | 3.61 | **4.11** | **4.65** | **4.15** | | minimal(1148 字) | 4.35 | 4.22 | 4.00 | 3.78 | 4.65 | 4.20 | 读法与取舍: - **压缩后的六步文本稳定优于旧的规则清单。** 同一个 baseline 在三轮里分别是 4.08 / 4.01 / 3.92,而发货版在第三轮拿到 4.15;两个方向合起来看,优势主要来自**保真**(3.94 → 4.53)与**体量**(3.39 → 4.11),代价是**充分性**(4.17 → 3.83)——即新提示词更保守,不再硬塞字段。这正是第一轮要的效果。 - **第 2 轮是一次被数据否掉的过度修正。** 我加了「不要添加用户没要求的要求清单」,结果输出中位数从 394 字掉到 225 字,充分性掉到 3.28、均分 3.86(低于 baseline)。这条被改写成「只补缺了就猜的信息,用不到的不写」,并保留字段表,才有第 3 轮。 - **「更短是否更好」没被证实,也没被证伪。** 极简臂三轮都是最高分(4.18 / 4.34 / 4.20),方向一致;但它与发货版的差距(第 3 轮 0.05)小于**同一臂在轮次之间的波动**(baseline 就有 0.16)。所以没有把提示词再砍到 1148 字,而是保留了极简版丢掉的三个小节:示例要求、五类冲突明细、「一次授权不等于永久授权」。 - **那条 800–1200 字的二手主张**:我们的数据**与它方向一致**(1148 字臂三轮第一,2187 字臂两轮垫底),但样本量不足以把它变成结论。可以确定的是:**长度不是这次提分的原因,删掉领域分类与并列清单才是**。 ## 五、归因审计(第二轮):改写里有多少是用户没提的 第一轮量的是「哪版更好」,答不出「这一句到底是用户要的还是改写者加的」。第二轮就是冲着这个问题去的。18 条草稿、三条臂,两条互补的通道: | 臂 | 提示词 | 静默新增/次(语义) | 静默占比 | 机械静默实体 | 模板小节/次 | 改写中位字数 | | --- | --- | --- | --- | --- | --- | --- | | previous | 1379 字(第一轮发货文本,当时的 `previous` 臂) | 3.50 | 30.1% | 2 | 2.44 | 275 | | **candidate** | **2060 字(第二轮发货文本)** | **0.56** | **7.3%** | **0** | **0.83** | **231** | | minimal | 1147 字 | 5.50 | 45.8% | 3 | 2.22 | 555 | 这一节的数字**都是单趟**,而单趟可偏约 2 倍(第七节有三轮重采)。`previous` 这条臂跟着发货文本走:它当时指向 1379 字那版,现在指向 2060 字那版。 同一趟里最说明问题的几个具体样本(都来自本仓库工作区里真实会话草稿的脱敏版): | 原始输入 | 旧版加了什么 | 第二轮发货版 | | --- | --- | --- | | `代码push了吗`(8 字) | 自己编了一整套 git 流程:`git status`、`git log @{u}..HEAD --oneline`、`-f`、`pull`、`rebase`、`merge`、`--force`、`main`、`master`——用户只问了一句「push 了吗」 | 「确认代码是否已经 push 到远端仓库」+两条已标注的假设 | | `最好的通用一些不用写死领域 主要是核心逻辑要清晰`(24 字) | 极简臂**把本插件自己的规则抄进了用户的提示词**:`200 字`、`600 字`、`不超过原文 3 倍`、`验收给 3-6 条` | 不出现本提示词自己的数字、条数与字段名 | | `标准 / 精简 / 结构化 / 扩写 这4个有啥区别?`(24 字) | 替用户规定答案形态(「给出一个对比,逐个说明每个词的含义与适用场景」) | 只澄清指代,「指哪个工具的 4 个选项」进「待确认」 | | 报错堆栈 + 复现描述(`syn-05`) | 凭空补出「验收 3:重新填入数字(如 5)…」「验收 4:直接输入非数字内容(如 abc)」——连测试数据都是造出来的 | 不发明测试用例与示例数据 | | 一条「删除配置项失败」的提问(`real-02`) | 加上「在保留 overlay 的前提下」「若二者互斥则说明…」等用户没说的分支与约束 | 只问原因,分支判断进「待确认」 | 三条结论: 1. **静默新增从 3.50 条/次压到 0.56 条/次**,机械那半是 **0 个**凭空出现的路径/命令/数字,模板小节从 2.44 降到 0.83(剩下的基本都是「待确认」本身,那是设计要的)。 2. **旧版的典型失败不是编造事实,而是替用户写要求**:凭空规定输出形态、补一条「非目标」、替用户设禁止项(「不重构」「不新增依赖」「不改动其他模块」),以及把**本提示词自己的规定**(`600 字`、`不超过原文 3 倍`、`验收 3-6 条`)当成用户的要求写进输出——极简臂在这件事上最严重,它几乎把规则表整段抄了过去。 3. **剩下的 10 条(分布在 6/18 条改写里)仍然是真问题**:`syn-04` 被套上「任务/交付物/面板需求/边界情况」小节,`syn-07` 被要求「以『原文:』加代码块形式重述代码」,`syn-02` 被补出三条澄清问题。这一类是下一轮该动的。 ## 六、取舍:这条要求与「可执行性」是对立的 这是本轮最重要、也最不讨喜的结论:**上面两个方向不能同时最大化。** 评审的 `executability` 定义就是「目标是否写成了结果式、验收标准能否被第三方机械判定、边界与失败路径是否覆盖」——**恰好是被这条要求禁掉的那些字段**。所以把静默新增压下去,可执行性必然掉: | 版本 | 字数 | 静默新增/次 | 均分 | 保真 | 充分性 | 可执行性 | 体量 | 不编造 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 22 条规则(第一轮 baseline) | 2187 | 5.33※ | 3.92 | 3.94 | 4.17 | 3.94 | 3.39 | 4.17 | | previous(第一轮发货文本,当时的 `previous` 臂) | 1379 | 3.50 | 4.24 | 4.35 | 4.33 | 4.11 | 3.83 | 4.56 | | v2「全是禁止、没有义务」(被否) | 1822 | 0.56 | 3.95 | 4.59 | 3.22 | 3.00 | 4.06 | 4.89 | | v3「补清版」(被否) | 1966 | 0.50 | 3.77 | 4.41 | 3.06 | 2.61 | 3.76 | 5.00 | | **v4(第二轮发货文本,2060 字)** | **2060** | **0.56** | **3.99** | **4.61** | 3.56 | 3.17 | **3.94** | 4.67 | | minimal | 1147 | 5.50 | 4.32 | 4.28 | 4.72 | 4.56 | 3.61 | 4.44 | ※ 22 条规则版的归因数字来自第二轮运行里同一草稿集上的 `baseline` 臂。 读法: - **保真与不编造稳定上升,充分性与可执行性稳定下降。** `previous` 的可执行性在三次运行里是 4.11 / 4.06 / 3.83,发货版是 3.17,中间被否的两版是 3.00 / 2.61;这个方向比表里任何别的差异都稳。 - **总分差(3.99 vs 4.24)落在跨轮噪声里,不敢说死。** 同一条臂在不同运行之间就能差 0.23(`previous` 是 4.26 / 4.03 / 4.24)。所以「发货版总分更低」只是提示;「可执行性更低」敢下结论,因为差距约 0.9 且三次运行方向一致。 - **v2 与 v3 是被数据加阅读一起否掉的两次过度修正。** v2 是「全是禁止、没有义务」:`real-04`(24 字的碎片草稿)只被改了标点,等于没干活;v3 补了一句「先补清用户已经想做的事」,却把 `代码push了吗` 变成 9 个字——一样等于没干活。v4 只加一句「消解指代、把「代码」「这个功能」落到具体对象上不允许省略」,两处的充分性与可执行性就回来了(+0.50 / +0.56),而静默新增没有回升(0.50 → 0.56,噪声内)。 - **一个自伤的教训值得记下来。** v2 里为了让问句保持问句,我写了「输出仍然是要执行者**回答**这个问题」,结果模型开始写「请回答…」——那正是紧接着那一条禁止的「答案内容清单」。禁止项里引述坏例子没问题,但**不要在指令句里使用你想要禁止的动词**。v4 把这句话改成了「问句不要改写成动作」。 - **这条要求与评审 rubric 的对立是结构性的,不是调参能绕开的。** 想要「拿到就能执行」的规格,就得反过来放松:允许补验收与边界,但把它们**标进「待确认」**、而不是混进正文——那是唯一既不违反「不加用户没提的要求」、又能把可执行性拿回一部分的位置。本轮没有走这条路,因为用户的要求是明确的。 ## 七、中英两条输出路径 **投诉与复现**。反馈是:打开英文开关后结果「好像就是翻译一下」,中文没问题,应该「按照中文的要求输出英文一样的结果」。本轮改完的发货文本是**正文 2128 字**(第三轮把「输出语言」一节与保真第一条又说清了一次),英文那条口令 939 字、中文 250 字;正文仍在自检的 1000–2200 字带内。18 条草稿跑一遍英文臂(修之前),失败是内容级的: | 原始输入 | 英文臂当时的输出 | | --- | --- | | `代码push了吗`(8 字) | `Did the code get pushed?`——整条改写塌成一句翻译,指代没落实、没有假设小节 | | `早`(1 字) | `Good morning.` 加两条**对用户说话**的 To confirm 项:`if you meant to ask for something, tell me what you need` | | `为什么我无法删除ccx这个模型?…`(152 字) | 换成第一人称叙述:`I ran the deletion for ccx and the prompt showed the message ...` | 三条是同一个病:英文臂不是在用另一种语言执行同一件事,而是**换了人设**——从改写者变成「用英文回答用户的助手」。归因同向:英文臂 **1.00 条静默新增/次**(14.1%,单趟),而当时的中文臂在 0.50–0.56。 **根因与两次修改**。那一行指令原来只说了语言,还用 `never translate them` 把任务框成了翻译。第一次修改声明「本行只决定语言、上文规则照旧适用」,并要求不要对用户说话、不要自己回答请求——人设问题消失(`早` 变成 `Good morning! What would you like to work on today?`,与中文同一个形状)。第二次修改删掉了一句有反作用的表述(`write it as if the user had made the request in English in the first place`,读起来就是「把这句话改成英文」),并写明「这不是翻译、先把改写做完再落成英文、输入越短要做的事越多、把原文换个语言还回去算失败」。 **第二次修改的收益没有测出来。** 第一次修改后的英文单趟是 0.44 / 0.17,第二次修改后三轮均值 0.81(1.22 / 0.78 / 0.44)——区间互相重叠,分不出高低。有证据的是第一次修改(人设与语气)以及「修之前的 1.00 高于修之后」;第二次修改保留下来,是因为它把用户的要求写成了明文,而不是因为它带来了可测的改善。 **顺带发现的测量缺陷(比英文本身更值得记)**。评估脚本当时把「提示词 + 语言行」一起写进 `systemPrompt`,而宿主会**再**按设置里的 `outputLang` 追加一行。用户把开关拨到英文之后,设置里存的是 `en`,于是**每一条臂都在产出英文**——一次标着 zh、一次标着 en 的运行,量的其实是同一个英文配置,看起来却像一次语言对照(本文曾据此得出「英文在短输入上塌陷」的那张表,已作废)。修法:臂只保存裸提示词、把 `outputLang` 钉住,并用插件自己的 `composeSystemPrompt` 校验宿主真正要发的串。教训:**凡是靠宿主设置驱动的测量,都要把依赖的每一项设置钉住并还原**。 **修正后的对照**(同一台宿主、同一批 18 条草稿、两条语言各自钉住): | 指标 | 中文臂 | 英文臂 | | --- | --- | --- | | 假设小节出现(`## 待确认` / `## To confirm`) | 16/18 | 16/18(逐条与中文对齐) | | 中位字数 | 167 | 456 | | 逐条英文/中文字数比 | — | 中位 2.7(范围 0.49–5.93) | | 机械静默实体 | 0 | 0 | | 语义静默新增/次(三轮均值,逐轮) | 0.33(0.44 / 0.28 / 0.28) | 0.81(1.22 / 0.78 / 0.44) | | 三轮归因未解析行 | 4 / 2 / 6 | 1 / 5 / 3 | 字数比是关键读数:同一句内容写成英文本来就要 2–3 倍字符,中位 2.7 正是这个语言系数,而不是「英文加得更多」。 **扣掉评审偏差后,英文仍然偏松。** 语义半会把**「输出英文」这条指令本身**、逐字保留规则、以及「待确认小节」都算成用户没提的要求(中文草稿配中文输出时不会出现这一类)。按这个口径把三轮的静默项分成结构类与内容类:中文 18 项里 5 项结构类、13 项内容类;英文 44 项里 17 项结构类、27 项内容类。也就是**扣掉偏差后,英文仍比中文多出约一倍的内容级新增**,典型的是替用户规定形状(`Present the four items as a bulleted list`、`Treat the four labels as option names`、`用 But I got this message: 把报错标成与预期相冲突的观察`)。结论是「近了很多,还没到等价」,不是「已经一样」。 **还没修好的一条**。两条语言都**几乎不动极短的草稿**:`代码push了吗` 中文回 11 字、英文回 24 字,等于把原文还给你;同一草稿在四次中文运行里是 124 / 296 / 24 / 11 字——**短输入上的输出极不稳定**,而这正是最常出现的一类输入。这不是英文独有的问题。 **语义半的噪声。** 同一批改写跑两次归因,silent/rewrite 一次 0.17、一次 0.94(5.5 倍),差别主要来自那一趟有多少行没返回可解析的 JSONL(7 行 vs 2 行)。为此脚本加了 `--repeat N` 并逐趟打印;三轮重采后中文臂是 0.44 / 0.28 / 0.28。所以:**次序稳**(旧文本 3.50 / 3.67 与发货版 0.33 差一个数量级),**点估计不稳**(单趟可偏约 2 倍)——第五、六节表里的个位数要按这个尺子读。 **要做到完全等价,下一步是给英文一条自己的提示词**(把六步用英文再写一份,而不是在中文提示词后面挂一行):现在的结构是「中文规则 + 英文口令」,规则只能跨语言传递。代价是一次完整重测(3×18 次改写 + 每臂 3×18 次归因)。本轮没有走这条路。 ## 八、已知偏差(必须明说) - 草稿集只有 18 条;评审模型与改写模型同族(自偏好风险);**没有留出集**。 - 更严重一点:v2 / v3 / v4 是在**同一套 18 条草稿**上迭代出来的,这正是本文第二节自己警告过的过拟合形态。可辩护的部分是:这三版的改动由**用户的要求与逐条阅读**驱动(`real-04` 只剩标点、`代码push了吗` 变成 9 个字都是肉眼可见的退化),不是对着分数刷出来的。但无法证明它们对没见过的草稿同样成立。 - 归因的**语义半是模型判定**:同族模型,可能对某类改写有偏好;判定边界(例如「给出判断依据」算不算加要求)有主观性。 - 语义半有**样本缺失且各臂不均**:`previous` 有 2 次、`candidate` 有 2 次、`minimal` 有 1 次返回的不是 JSONL,按 0 项计入——这会**低估**静默数,各臂低估程度不同。 - 机械半是**下界**:它只认反引号片段、路径与带单位的数字。一句凭空加上去的中文要求它看不见(那正是语义半要补的),反过来它也不会把「200 字」与「200」当成两件事(数字只比数字本身)。 - 单轮均值差在 0.2 上下的都可能是噪声。本文所有排序都是**方向性**的,不是精确结论。 - 中英对照只有**各一次**改写运行(每臂 3 趟归因)。所以「英文比中文多出约一倍内容级新增」是提示性的,不是定论——两次归因重采在同一批改写上就出现过 1.6–5.5 倍的波动。 - 语义半会把**语言指令、逐字保留规则与「待确认」小节本身**算成静默新增,而这只在英文输出上出现(中文草稿配中文输出没有这一项)。按结构类/内容类拆分后可以扣掉,但拆分本身是正则做的,边界有主观性。 ## 九、复现 ```sh node scripts/eval-prompt.mjs # 只打印计划:各臂文本、草稿数、candidate 是否等于发货文本 node scripts/eval-prompt.mjs --arms candidate,previous,minimal --live # 54 次改写(会短暂改写本机设置,结束时恢复) node scripts/eval-prompt.mjs --arms candidate,previous,minimal --judge # 18 次盲评,读最新一次改写记录 node scripts/eval-prompt.mjs --live --limit 2 # 冒烟:2 条草稿 × 3 臂 node scripts/eval-prompt.mjs --require-parity # candidate 与 lib/prompt.js 不一致时以退出码 2 失败 node scripts/eval-attribution.mjs # 归因的机械半:读最新一次改写记录,只算,不发请求 node scripts/eval-attribution.mjs --judge --repeat 3 # 归因的语义半:三轮,逐趟打印(点估计必须这样读) node scripts/eval-attribution.mjs --arms candidate --show real-10 # 看某条草稿在各臂下的原文与改写 ``` 两条语言的对照要各自钉住语言跑一次,`--lang` 只是给臂选语言行;宿主按设置追加的那一行由脚本自己钉住并还原: ```sh node scripts/eval-prompt.mjs --arms candidate --lang zh --live # 18 次改写 node scripts/eval-prompt.mjs --arms candidate --lang en --live # 18 次改写 ``` 改写、盲评与归因的原始记录写在 `scripts/eval/runs/*.json`(未纳入版本控制:里面是模型对真实草稿的改写结果),本文的汇总数字就是上面那几张表。