oh-my-knowledge 0.51.1 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -6
- package/README.zh.md +27 -6
- package/dist/assets/agent-skills/omk/SKILL.md +10 -8
- package/dist/assets/agent-skills/omk/references/commands.md +2 -2
- package/dist/authoring/generator.d.ts +15 -5
- package/dist/authoring/generator.js +181 -18
- package/dist/authoring/sample-fixer.d.ts +2 -0
- package/dist/authoring/sample-fixer.js +19 -5
- package/dist/cli/commands/sample.js +11 -4
- package/dist/eval-core/evaluation-execution.js +11 -2
- package/dist/eval-core/fact-checker.d.ts +15 -2
- package/dist/eval-core/fact-checker.js +75 -20
- package/dist/eval-core/mock-hook.cjs +40 -1
- package/dist/eval-core/mocks-runtime.js +39 -2
- package/dist/eval-core/task-planner.d.ts +2 -2
- package/dist/eval-core/task-planner.js +7 -7
- package/dist/eval-workflows/evaluation-pipeline.js +2 -0
- package/dist/eval-workflows/run-evaluation.js +2 -1
- package/dist/executors/capabilities.d.ts +15 -0
- package/dist/executors/capabilities.js +64 -0
- package/dist/executors/index.d.ts +1 -0
- package/dist/executors/index.js +4 -1
- package/dist/observability/codex-conversation-index.d.ts +88 -0
- package/dist/observability/codex-conversation-index.js +566 -0
- package/dist/observability/codex-protocol.d.ts +12 -0
- package/dist/observability/codex-protocol.js +78 -0
- package/dist/observability/codex-tool-status.d.ts +16 -0
- package/dist/observability/codex-tool-status.js +114 -0
- package/dist/observability/codex-trace-adapter.js +342 -119
- package/dist/observability/conversation-catalog.d.ts +38 -0
- package/dist/observability/conversation-catalog.js +573 -0
- package/dist/observability/conversation-index-process.d.ts +1 -0
- package/dist/observability/conversation-index-process.js +31 -0
- package/dist/observability/conversation-view-model.d.ts +5 -0
- package/dist/observability/conversation-view-model.js +100 -0
- package/dist/observability/experience.d.ts +3 -1
- package/dist/observability/experience.js +248 -7
- package/dist/observability/inbox.js +40 -2
- package/dist/observability/knowledge-debugger.d.ts +4 -0
- package/dist/observability/knowledge-debugger.js +364 -0
- package/dist/observability/polling-subscription-hub.d.ts +27 -0
- package/dist/observability/polling-subscription-hub.js +149 -0
- package/dist/observability/source-record-archive.d.ts +13 -0
- package/dist/observability/source-record-archive.js +324 -0
- package/dist/observability/task-window.d.ts +8 -0
- package/dist/observability/task-window.js +36 -0
- package/dist/observability/trace-ir.d.ts +66 -1
- package/dist/observability/trace-source.d.ts +1 -0
- package/dist/observability/trace-source.js +12 -5
- package/dist/observability/turn-index.d.ts +3 -0
- package/dist/observability/turn-index.js +251 -0
- package/dist/renderer/conversation-renderer.d.ts +9 -0
- package/dist/renderer/conversation-renderer.js +410 -0
- package/dist/renderer/icons.js +1 -0
- package/dist/renderer/inline-markdown.d.ts +6 -0
- package/dist/renderer/inline-markdown.js +158 -0
- package/dist/renderer/knowledge-debugger-renderer.d.ts +9 -0
- package/dist/renderer/knowledge-debugger-renderer.js +2280 -0
- package/dist/renderer/observation-inbox-renderer.js +9 -0
- package/dist/renderer/skill-list-renderer.js +2 -1
- package/dist/renderer/trajectory-live.d.ts +42 -0
- package/dist/renderer/trajectory-live.js +258 -0
- package/dist/renderer/trajectory-routing.d.ts +60 -0
- package/dist/renderer/trajectory-routing.js +351 -0
- package/dist/server/report-server.d.ts +4 -1
- package/dist/server/report-server.js +274 -6
- package/dist/server/skill-insights.js +1 -1
- package/dist/shared/sample-contract.d.ts +1 -0
- package/dist/shared/sample-contract.js +35 -0
- package/dist/shared/tool-identity.d.ts +8 -0
- package/dist/shared/tool-identity.js +13 -0
- package/dist/types/eval.d.ts +7 -6
- package/dist/types/executor.d.ts +3 -2
- package/dist/types/observability.d.ts +225 -1
- package/package.json +10 -7
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OMK
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/oh-my-knowledge)
|
|
4
4
|
[](https://www.npmjs.com/package/oh-my-knowledge)
|
|
@@ -8,14 +8,19 @@
|
|
|
8
8
|
|
|
9
9
|
**English** | [简体中文](./README.zh.md)
|
|
10
10
|
|
|
11
|
-
**
|
|
12
|
-
|
|
11
|
+
**Observe. Measure. Know.**
|
|
12
|
+
|
|
13
|
+
**OMK makes every knowledge change in your AI application evidence-backed.**
|
|
14
|
+
|
|
15
|
+
Observe real-world performance, measure version differences, and determine whether the change is effective and the version is ready to ship.
|
|
16
|
+
|
|
17
|
+
**Same model. Same evaluation samples. Only the knowledge artifact changes.**
|
|
13
18
|
|
|
14
19
|

|
|
15
20
|
|
|
16
21
|
📖 **Full documentation: [oh-my-knowledge.pages.dev](https://oh-my-knowledge.pages.dev)** (searchable, English / 简体中文)
|
|
17
22
|
|
|
18
|
-
## What
|
|
23
|
+
## What OMK helps you know
|
|
19
24
|
|
|
20
25
|
| Decision | Command | Evidence you get |
|
|
21
26
|
|---|---|---|
|
|
@@ -23,6 +28,7 @@
|
|
|
23
28
|
| Is v2 actually better than v1? | `omk eval` | one-line verdict, confidence interval, failed samples, cost |
|
|
24
29
|
| Why did it pass or fail? | `omk studio` | report view with scores, diagnostics, and examples |
|
|
25
30
|
| Should this version become the accepted one? | `omk promote` / `omk evolve` | evidence-gated accept or generate a better candidate |
|
|
31
|
+
| What happened during one real AI task? | `omk observe` / Studio Task Trajectory | a trace-backed view of the request, visible Knowledge, tool calls, results, response, and user correction |
|
|
26
32
|
| What did real usage expose? | `omk observe` / `omk sample --from-traces` | production gaps drafted for review; reviewed drafts can become eval samples |
|
|
27
33
|
|
|
28
34
|

|
|
@@ -57,9 +63,23 @@ Walkthrough: [5-minute quickstart guide](docs/quickstart-skill-eval.md) (recomme
|
|
|
57
63
|
|
|
58
64
|
Deeper: [who omk is for](docs/explanation/who-omk-is-for.md) · [CLI reference](docs/reference/cli.md) · [how it works](docs/explanation/architecture.md) · [eval sample format](docs/reference/eval-sample-format.md) · [executors](docs/reference/executors.md) · [artifact layout](docs/reference/artifact-layout.md)
|
|
59
65
|
|
|
60
|
-
##
|
|
66
|
+
## Inspect one Codex task
|
|
67
|
+
|
|
68
|
+
If you only want to see what happened behind one Codex conversation, you do not need to run `observe ingest` first:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
omk studio
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Studio opens the local Codex conversation overview at `http://127.0.0.1:7799` by default. Select a conversation, then a task, to open **Task Trajectory**. Its four lanes — **Conversation, Actions, Results, and Knowledge** — show the request, AI responses, tool calls, tool returns, and observable context, with drill-downs into normalized events and raw logs.
|
|
75
|
+
|
|
76
|
+
Running tasks are prioritized and update live. While **Following**, the trajectory advances smoothly as events arrive; after you inspect an earlier point, Studio keeps your position and offers **View updates**. Old logs without a terminal event are marked **End status not recorded** instead of remaining live forever.
|
|
77
|
+
|
|
78
|
+
Task Trajectory only reconstructs facts observable in the log. It does not reveal or infer hidden reasoning. See [Observe production traces](docs/guides/observe-production.md#inspect-one-task) for the full model.
|
|
79
|
+
|
|
80
|
+
## The OMK loop
|
|
61
81
|
|
|
62
|
-
|
|
82
|
+
OMK is for authors and maintainers of LLM knowledge artifacts who need a release decision, not for passive end-users of a skill. The main loop is deliberately controlled:
|
|
63
83
|
|
|
64
84
|
```text
|
|
65
85
|
change a prompt / RAG / skill / agent artifact
|
|
@@ -166,6 +186,7 @@ The full docs are published at **[oh-my-knowledge.pages.dev](https://oh-my-knowl
|
|
|
166
186
|
- **[CLI reference](docs/reference/cli.md)** — all top-level commands with bash examples and flag tables
|
|
167
187
|
- **[Executors](docs/reference/executors.md)** & **[artifact layout](docs/reference/artifact-layout.md)** — built-in / custom executors; how `variant` resolves to an artifact + runtime context
|
|
168
188
|
- **[How-to guides](docs/guides/agent-eval.md)** — [evaluate an agent](docs/guides/agent-eval.md) (project runtime context) and [use non-Claude models](docs/guides/non-claude-models.md) (GLM / Qwen / DeepSeek / Moonshot / Ollama)
|
|
189
|
+
- **[Observe & inspect task trajectories](docs/guides/observe-production.md)** — browse local Codex conversations, drill into one task, and follow its observable execution live
|
|
169
190
|
- **[Quickstart](docs/quickstart-skill-eval.md)** — first-time five-minute walkthrough
|
|
170
191
|
- **[Example gallery](https://github.com/lizhiyao/oh-my-knowledge/tree/main/examples)** — a set of runnable examples in the repo, arranged simplest-to-richest
|
|
171
192
|
- **[Sample design spec](docs/specs/sample-design-spec.md)** — capability / construct / provenance metadata; industry-gap mapping
|
package/README.zh.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OMK
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/oh-my-knowledge)
|
|
4
4
|
[](https://www.npmjs.com/package/oh-my-knowledge)
|
|
@@ -8,14 +8,19 @@
|
|
|
8
8
|
|
|
9
9
|
[English](./README.md) | **简体中文**
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
**Observe. Measure. Know.**
|
|
12
|
+
|
|
13
|
+
**OMK,让 AI 应用的知识改动有据可依。**
|
|
14
|
+
|
|
15
|
+
观测真实表现,量出版本差异,判断改动是否有效、版本能否发布。
|
|
16
|
+
|
|
17
|
+
**相同模型,相同评测用例,只改变知识载体。**
|
|
13
18
|
|
|
14
19
|

|
|
15
20
|
|
|
16
21
|
📖 **完整文档:[oh-my-knowledge.pages.dev/zh](https://oh-my-knowledge.pages.dev/zh/)**(可搜索,可切换英文)
|
|
17
22
|
|
|
18
|
-
##
|
|
23
|
+
## OMK 让你知道什么
|
|
19
24
|
|
|
20
25
|
| 决策问题 | 命令 | 你会得到的证据 |
|
|
21
26
|
|------|------|------|
|
|
@@ -23,6 +28,7 @@
|
|
|
23
28
|
| v2 是否真的优于 v1? | `omk eval` | 一行 verdict、置信区间、失败样本、成本 |
|
|
24
29
|
| 它为什么通过或失败? | `omk studio` | 分数、诊断、样本证据的报告视图 |
|
|
25
30
|
| 这个版本是否应成为接受版本? | `omk promote` / `omk evolve` | 基于证据接受,或生成更好的候选版 |
|
|
31
|
+
| 一次真实 AI 任务中发生了什么? | `omk observe` / Studio 任务轨迹 | 请求、可见知识、工具调用与结果、回答和用户纠正的可核验轨迹 |
|
|
26
32
|
| 真实使用暴露了哪些知识缺口? | `omk observe` / `omk sample --from-traces` | 将线上缺口生成待复核草稿,复核后再沉淀为评测样本 |
|
|
27
33
|
|
|
28
34
|

|
|
@@ -57,9 +63,23 @@ export OMK_EXECUTOR=codex
|
|
|
57
63
|
|
|
58
64
|
深入:[为谁、解决什么](docs/zh/explanation/who-omk-is-for.md) · [CLI 参考](docs/zh/reference/cli.md) · [工作原理](docs/zh/explanation/architecture.md) · [评测用例格式](docs/zh/reference/eval-sample-format.md) · [执行器](docs/zh/reference/executors.md) · [知识载体布局](docs/zh/reference/artifact-layout.md)
|
|
59
65
|
|
|
60
|
-
##
|
|
66
|
+
## 先看清一次 Codex 任务
|
|
67
|
+
|
|
68
|
+
只想知道一次 Codex 对话背后发生了什么,不需要先运行 `observe ingest`:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
omk studio
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Studio 默认在 `http://127.0.0.1:7799` 打开本机 Codex 对话总览。先选择一段对话,再选择其中一次任务,即可进入「任务轨迹」:按**对话、执行、结果、知识**四条泳道查看请求、AI 回答、工具调用、工具返回和可见上下文,并可下钻到规范化事件与原始日志。
|
|
75
|
+
|
|
76
|
+
进行中的任务会优先显示并实时更新。保持「跟随中」时,轨迹会随新事件平滑前进;手动查看历史位置后,页面保留当前位置并提示「查看更新」。旧日志如果没有记录结束事件,会标记为「未记录结束状态」,不会一直冒充进行中。
|
|
77
|
+
|
|
78
|
+
任务轨迹只还原日志中可观测的事实,不展示或推断隐藏思维。完整说明见[观测与任务轨迹](docs/zh/guides/observe-production.md#查看一次任务)。
|
|
79
|
+
|
|
80
|
+
## OMK 的闭环
|
|
61
81
|
|
|
62
|
-
|
|
82
|
+
OMK 主要给 LLM 知识载体的作者 / 维护者用,帮他们做发布判断;它不是给被动安装 skill 的普通使用者用的。主流程刻意保持受控:
|
|
63
83
|
|
|
64
84
|
```text
|
|
65
85
|
改了一份 prompt / RAG / skill / agent 知识载体
|
|
@@ -166,6 +186,7 @@ RAG 专项评测请看 RAGAS(独立 niche,跟 omk 互补)。完整对比
|
|
|
166
186
|
- **[CLI 参考](docs/zh/reference/cli.md)** —— 顶层命令的 bash 示例和 flag 表
|
|
167
187
|
- **[执行器](docs/zh/reference/executors.md)** & **[知识载体布局](docs/zh/reference/artifact-layout.md)** —— 内置 / 自定义执行器;variant 如何解析为 artifact + runtime context
|
|
168
188
|
- **[操作指南](docs/zh/guides/agent-eval.md)** —— [评测 agent](docs/zh/guides/agent-eval.md)(项目 runtime context)与[使用非 Claude 模型](docs/zh/guides/non-claude-models.md)(GLM / 通义 / DeepSeek / Moonshot / Ollama)
|
|
189
|
+
- **[观测与任务轨迹](docs/zh/guides/observe-production.md)** —— 浏览本机 Codex 对话,下钻一次任务,并实时跟随可观测执行过程
|
|
169
190
|
- **[快速上手](docs/zh/quickstart-skill-eval.md)** —— 第一次跑评测的 5 分钟教程
|
|
170
191
|
- **[示例画廊](https://github.com/lizhiyao/oh-my-knowledge/tree/main/examples)** —— 仓库里一组可直接跑的示例,按由简到全排成上手路径
|
|
171
192
|
- **[用例设计规范](docs/zh/specs/sample-design-spec.md)** —— capability / construct / provenance 元数据;行业 gap 映射
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: omk
|
|
3
3
|
description: |
|
|
4
|
-
|
|
4
|
+
OMK(Observe. Measure. Know.)让 AI 应用的知识改动有据可依。观测真实表现,受控测量 prompt / RAG / skill / agent / workflow 的版本差异,判断改动是否有效、版本能否发布,并支持自动迭代改进。
|
|
5
5
|
Use when: 用户提到"评测"、"测评"、"eval"、"benchmark"、"对比 skill"、"改进 skill"、"evolve"、"生成测试用例"、"gen-samples"、"omk"。
|
|
6
6
|
user-invocable: true
|
|
7
7
|
argument-hint: "<doctor|eval|evolve|init|install|list|observe|promote|rollback|sample|studio> [options]"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
# OMK —
|
|
10
|
+
# OMK — Observe. Measure. Know.
|
|
11
11
|
|
|
12
|
-
你是
|
|
12
|
+
你是 OMK 的智能代理。帮助用户观测真实表现、受控测量和改进 AI 应用的知识(prompt / RAG / skill / agent / workflow),判断改动是否有效、版本能否发布。
|
|
13
13
|
|
|
14
14
|
## 第一步:检查环境
|
|
15
15
|
|
|
@@ -37,7 +37,7 @@ Codex 是 omk 的一等 runtime。运行在 Codex 任务中时,`omk eval` / `d
|
|
|
37
37
|
| 改进 / 优化 skill | → `omk evolve`(自动多轮迭代) |
|
|
38
38
|
| 生成测试用例 | → `omk sample` |
|
|
39
39
|
| 体检 skill 写法 | → `omk doctor` |
|
|
40
|
-
|
|
|
40
|
+
| 浏览对话、任务轨迹与报告 | → `omk studio`(启动本地知识工作台) |
|
|
41
41
|
| 看真实使用 trace | → `omk observe` |
|
|
42
42
|
| 查看受管 skill 状态 | → `omk list` |
|
|
43
43
|
| 按证据接受 / 回退某版本 | → `omk promote` / `omk rollback` |
|
|
@@ -123,6 +123,8 @@ omk sample skills/my-skill/SKILL.md --focus "重点覆盖搜索失败 / 权限
|
|
|
123
123
|
omk sample --batch
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
+
目标执行器不支持工具拦截时,`omk sample` 会自动生成无 mock 用例。当前 `codex` / `codex-sdk` 属于这种情况;不要手工补 `mocks` 或 `mock_hit`。已有 mocks 用例会被 `omk eval` 在模型调用前拒绝,避免把执行器能力缺口误判成模型失败。`environment.files_available` 只提供题设上下文,不会在 `cwd` 物化文件。
|
|
127
|
+
|
|
126
128
|
输出位置:目录 skill(`<skill>/SKILL.md`)→ `<skill>/.omk/samples.json`(标准);扁平 `.md` 单次生成 → 当前目录 `eval-samples.json`(项目级兜底);扁平 `.md` 的 `--batch` 兼容生成 `<skill-dir>/<name>.eval-samples.json`。
|
|
127
129
|
|
|
128
130
|
### 观测真实使用
|
|
@@ -136,7 +138,7 @@ omk observe ingest ~/.codex/sessions
|
|
|
136
138
|
omk observe ~/.claude/projects/<project> --last 7d
|
|
137
139
|
```
|
|
138
140
|
|
|
139
|
-
Codex rollout 会保留 `sourceKind=codex`、模型、父子任务、tool call 和 token 证据,并从实际读取的 `skills/<name>/SKILL.md` 归因 skill。`observe ingest`
|
|
141
|
+
Codex rollout 会保留 `sourceKind=codex`、模型、父子任务、tool call 和 token 证据,并从实际读取的 `skills/<name>/SKILL.md` 归因 skill。`omk studio` 无需先 ingest,即可从本机 Codex 对话总览进入某次任务的实时轨迹;`observe ingest` 只在需要生成待复核 observation 时运行。任务轨迹按对话、执行、结果和知识呈现结构化事实,并联动检查配对后的工具调用与结果、AI 回答和用户纠正。该页面只呈现 trace 中可观测的执行过程,不推断隐藏思维或失败根因。确认真实知识缺口后,再用 `omk sample --from-traces` 草拟评测用例。
|
|
140
142
|
|
|
141
143
|
### 体检 skill 写法
|
|
142
144
|
|
|
@@ -153,16 +155,16 @@ omk doctor skills/my-skill.md
|
|
|
153
155
|
|
|
154
156
|
`omk eval` 默认会先跑一次 doctor 当 preflight 门禁,所以一般不用单独跑;想在 eval 之前先把结构问题先扫一遍再跑评测,就单独跑 `omk doctor`。
|
|
155
157
|
|
|
156
|
-
###
|
|
158
|
+
### 浏览对话、任务轨迹与报告
|
|
157
159
|
|
|
158
160
|
```bash
|
|
159
|
-
omk studio #
|
|
161
|
+
omk studio # 启动本地知识工作台(默认端口 7799)
|
|
160
162
|
omk studio --port 8080 # 改端口
|
|
161
163
|
omk studio --host 0.0.0.0 # 局域网访问(默认 127.0.0.1)
|
|
162
164
|
omk studio --no-open # 不自动开浏览器
|
|
163
165
|
```
|
|
164
166
|
|
|
165
|
-
Studio
|
|
167
|
+
Studio 首页直接索引本机 Codex 对话。先选择对话,再选择任务查看四泳道任务轨迹;进行中的任务支持实时跟随。顶部「知识载体」入口用于浏览 doctor / eval / observe 报告,`/observe-inbox` 用于复核 observation。无需为了浏览本机 Codex 对话而先运行 `omk observe ingest`。
|
|
166
168
|
|
|
167
169
|
## 第五步:解读结果
|
|
168
170
|
|
|
@@ -602,7 +602,7 @@ omk sample [skillPath] [flags]
|
|
|
602
602
|
- `--from-traces` `boolean`:from-traces 模式:从 observe inbox 的失败信号回流生成评测用例草稿(provenance: production-trace),落草稿待人工 review。
|
|
603
603
|
- `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
|
|
604
604
|
- `--model` `option`:生成 LLM model 名。Codex 自动读取本机配置;也可用 OMK_MODEL 设置环境偏好。
|
|
605
|
-
- `--no-mock` `boolean`:不生成 mocks
|
|
605
|
+
- `--no-mock` `boolean`:不生成 mocks。执行器不支持工具拦截时会自动启用,避免产生必然失败的 mock_hit。
|
|
606
606
|
- `--observations-dir` `option`:observe inbox 目录(from-traces 模式用),默认项目 .omk/observe-inbox。
|
|
607
607
|
- `--reports-dir` `option`:报告目录(fix 模式用),默认 ~/.oh-my-knowledge/reports。
|
|
608
608
|
- `--skill` `option`:仅从指定 skill 的 observe inbox 信号生成草稿(仅 from-traces 模式用)。
|
|
@@ -689,7 +689,7 @@ omk studio --port 8080 --no-open
|
|
|
689
689
|
| `construct` | 否 | 测的是什么构念 |
|
|
690
690
|
| `provenance` | 否 | 用例来源(`omk sample` 自动打) |
|
|
691
691
|
| `mocks` | 否 | 工具调用 mock 返回(sandbox 评测) |
|
|
692
|
-
| `environment` | 否 |
|
|
692
|
+
| `environment` | 否 | 题设环境声明(仅注入 prompt,不物化) |
|
|
693
693
|
| `tripwire` | 否 | 标记为「故意诱错」样本,failed 时 diagnostic 不建议改 skill |
|
|
694
694
|
|
|
695
695
|
完整 schema 见 [docs/specs/sample-design-spec.md](https://github.com/lizhiyao/oh-my-knowledge/blob/main/docs/specs/sample-design-spec.md)。
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ExecutorFn, Sample } from '../types/index.js';
|
|
2
2
|
import type { ObservationInboxItem } from '../types/observability.js';
|
|
3
|
-
|
|
3
|
+
export declare function sampleGenerationUsesMocks(executorName: string | undefined, noMock?: boolean): boolean;
|
|
4
|
+
export interface GenerateSamplesOptions {
|
|
4
5
|
skillContent: string;
|
|
5
6
|
count?: number;
|
|
6
7
|
model: string;
|
|
@@ -12,6 +13,8 @@ interface GenerateSamplesOptions {
|
|
|
12
13
|
focus?: string;
|
|
13
14
|
/** 不生成 mocks/mocksStrict,eval 时真实执行所有工具调用。 */
|
|
14
15
|
noMock?: boolean;
|
|
16
|
+
/** Injectable executor for tests. Defaults to createExecutor(executorName). */
|
|
17
|
+
executor?: ExecutorFn;
|
|
15
18
|
}
|
|
16
19
|
/**
|
|
17
20
|
* 拼出送给 LLM 的 user prompt。抽出来便于单测验证 focus 是否真的注入了。
|
|
@@ -26,7 +29,7 @@ export declare function buildSamplesPrompt({ skillContent, count, focus, noMock
|
|
|
26
29
|
focus?: string;
|
|
27
30
|
noMock?: boolean;
|
|
28
31
|
}): string;
|
|
29
|
-
export declare function generateSamples({ skillContent, count, model, executorName, focus, noMock }: GenerateSamplesOptions): Promise<{
|
|
32
|
+
export declare function generateSamples({ skillContent, count, model, executorName, focus, noMock, executor: injectedExecutor, }: GenerateSamplesOptions): Promise<{
|
|
30
33
|
samples: Sample[];
|
|
31
34
|
costUSD: number;
|
|
32
35
|
}>;
|
|
@@ -62,12 +65,16 @@ export declare function stratifyTraceSignals(items: TraceSignalItem[]): Stratifi
|
|
|
62
65
|
* production failures. The trace text feeds the *generator* only — never the
|
|
63
66
|
* judge prompt — so judge-prompt isolation is unaffected.
|
|
64
67
|
*/
|
|
65
|
-
export declare function buildSamplesFromTracesPrompt(items: TraceSignalItem[], count?: number
|
|
68
|
+
export declare function buildSamplesFromTracesPrompt(items: TraceSignalItem[], count?: number, options?: {
|
|
69
|
+
noMock?: boolean;
|
|
70
|
+
}): string;
|
|
66
71
|
export interface GenerateSamplesFromTracesOptions {
|
|
67
72
|
items: TraceSignalItem[];
|
|
68
73
|
count?: number;
|
|
69
74
|
model: string;
|
|
70
75
|
executorName?: string;
|
|
76
|
+
/** 不生成 mocks。目标 executor 不支持时会自动启用。 */
|
|
77
|
+
noMock?: boolean;
|
|
71
78
|
/** Injectable executor (tests). Defaults to createExecutor(executorName). */
|
|
72
79
|
executor?: ExecutorFn;
|
|
73
80
|
}
|
|
@@ -77,12 +84,15 @@ export interface GenerateSamplesFromTracesOptions {
|
|
|
77
84
|
* stamps `provenance: 'production-trace'`. Output is meant to land in a review draft,
|
|
78
85
|
* not the live dataset (the CLI enforces that).
|
|
79
86
|
*/
|
|
80
|
-
export declare function generateSamplesFromTraces({ items, count, model, executorName, executor: injectedExecutor, }: GenerateSamplesFromTracesOptions): Promise<{
|
|
87
|
+
export declare function generateSamplesFromTraces({ items, count, model, executorName, noMock, executor: injectedExecutor, }: GenerateSamplesFromTracesOptions): Promise<{
|
|
81
88
|
samples: Sample[];
|
|
82
89
|
costUSD: number;
|
|
83
90
|
}>;
|
|
84
91
|
export declare function sanitizeGeneratedSamples(samples: Sample[], opts?: {
|
|
85
92
|
skillContent?: string;
|
|
93
|
+
mockless?: boolean;
|
|
94
|
+
migrateMocklessEnvironment?: boolean;
|
|
95
|
+
preserveMocklessEnvironment?: boolean;
|
|
86
96
|
}): {
|
|
87
97
|
stripped: string[];
|
|
88
98
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createExecutor } from '../executors/index.js';
|
|
2
|
+
import { executorSupportsSampleMocks } from '../executors/capabilities.js';
|
|
2
3
|
import { DEFAULT_GATE_THRESHOLD } from '../eval-core/verdict.js';
|
|
4
|
+
import { sampleMockReferenceKeys } from '../shared/sample-contract.js';
|
|
3
5
|
const SYSTEM_PROMPT = `你是一个评测用例生成器。你的任务是根据用户提供的 skill(系统提示词)内容,生成高质量的评测用例。
|
|
4
6
|
|
|
5
7
|
样本结构决策(必须先做):先扫一遍 skill 内容判断它属于哪一类,按对应配比和数量生成。
|
|
@@ -195,14 +197,14 @@ const SYSTEM_PROMPT = `你是一个评测用例生成器。你的任务是根据
|
|
|
195
197
|
tool_input_not_contains 或 tools_not_called。
|
|
196
198
|
- { "type": "regex", "pattern": "...", "weight": 1 }
|
|
197
199
|
↑ 同 contains 限制:只用在固定格式字面量(如 SHA / UUID / 路径模板)。
|
|
198
|
-
- environment:
|
|
200
|
+
- environment: 可选,对象。**仅作 prompt 上下文的题设环境声明**,不会修改 PATH、创建文件或物化 fixture。
|
|
199
201
|
字段:
|
|
200
202
|
- cli_available: string[],已在 PATH 上的 CLI(如 ["node", "git", "code-host"])
|
|
201
203
|
- files_available: string[],已存在的文件/脚本(如 ["~/.req-tool-api.json", "$SKILL_DIR/scripts/x.js"])
|
|
202
204
|
- notes: string,自由文本兜底(如"DevAPI 凭证有效,工号 testuser001")
|
|
203
205
|
原则:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
+
只有用例明确把某项环境能力作为题设前提时才写。需要读取真实内容的文件不能放在
|
|
207
|
+
files_available 里冒充 fixture:应放进 sample.cwd 下的真实 fixture,或由 mock 返回内容。
|
|
206
208
|
- mocksStrict: **必填且必须设为 true**(只要 sample 配了 mocks)。
|
|
207
209
|
原因:mocksStrict=false 时,LLM 调到没匹配 mock 的命令会**透传到真 shell**,
|
|
208
210
|
既可能真调外部接口产生副作用,也可能因二进制不存在(如 mcporter)报噪声错误污染评测信号。
|
|
@@ -223,8 +225,8 @@ const SYSTEM_PROMPT = `你是一个评测用例生成器。你的任务是根据
|
|
|
223
225
|
- 对(✅):写一条宽 mock:\`{tool:"Bash", match:{command_glob:"ls *"},
|
|
224
226
|
return:{stdout:"<模拟目录列表>", exit:0}}\` — \`command_glob\` 用 \`*\` 兜底各种
|
|
225
227
|
ls 参数变体(\`ls\` / \`ls -la\` / \`ls -d\` / \`ls /xx\` 全命中)
|
|
226
|
-
(c)
|
|
227
|
-
|
|
228
|
+
(c) 题设明确声明可用的凭证 / CLI 可写进 \`environment\`,让 LLM 不做可用性探测。
|
|
229
|
+
该字段只进 prompt,不会真的安装 CLI、创建凭证文件或改变 runtime。
|
|
228
230
|
(d) **intent-level mock(文件搜索/读取类操作)** — LLM 搜代码时会自由选择 Bash grep、
|
|
229
231
|
Grep 工具、Glob+Read 组合、甚至 Agent 子代理,逐个枚举工具写 mock 不可持续。
|
|
230
232
|
正确做法:用 \`tool: "*"\` + \`input_contains: "关键词"\` 按意图匹配:
|
|
@@ -367,6 +369,37 @@ const SYSTEM_PROMPT = `你是一个评测用例生成器。你的任务是根据
|
|
|
367
369
|
- 字符串字段(prompt / rubric / capability 等)内部如需引号,**必须用全角「」**而不是半角 \`""\`,避免漏转义破坏 JSON 解析
|
|
368
370
|
- 例:错 → \`"prompt": "查询"Daily"标签..."\`(内部 \`"\` 未转义,JSON 解析失败)
|
|
369
371
|
对 → \`"prompt": "查询「Daily」标签..."\`(全角引号,无转义压力)`;
|
|
372
|
+
const MOCKLESS_SYSTEM_OVERRIDE = `
|
|
373
|
+
|
|
374
|
+
## 目标执行器能力约束(最高优先级)
|
|
375
|
+
|
|
376
|
+
目标执行器不支持可靠的工具调用拦截。本节覆盖上文所有要求 mocks、mock_hit、
|
|
377
|
+
files_available 或正向工具调用断言的规则:
|
|
378
|
+
|
|
379
|
+
- 不要输出 mocks、mocksStrict、environment 字段。
|
|
380
|
+
- 不要输出 mock_hit、tools_called、tools_count_min、tool_input_contains、tool_output_contains。
|
|
381
|
+
- 可以保留 tools_not_called、tools_count_max、tool_input_not_contains 这类负向安全约束。
|
|
382
|
+
- 把必要的输入事实直接写进 context,把可判分结果写进 rubric 或输出内容断言。
|
|
383
|
+
- 不要假装某个文件、CLI 或工具调用已经由 fixture 物化。`;
|
|
384
|
+
function generationSystemPrompt(mockless) {
|
|
385
|
+
return mockless ? `${SYSTEM_PROMPT}${MOCKLESS_SYSTEM_OVERRIDE}` : SYSTEM_PROMPT;
|
|
386
|
+
}
|
|
387
|
+
const warnedAutoMocklessExecutors = new Set();
|
|
388
|
+
function warnAutoMockless(executorName, noMock) {
|
|
389
|
+
if (noMock
|
|
390
|
+
|| !executorName
|
|
391
|
+
|| executorSupportsSampleMocks(executorName)
|
|
392
|
+
|| warnedAutoMocklessExecutors.has(executorName))
|
|
393
|
+
return;
|
|
394
|
+
warnedAutoMocklessExecutors.add(executorName);
|
|
395
|
+
process.stderr.write(`[omk sample] 执行器「${executorName}」不支持工具调用拦截,`
|
|
396
|
+
+ '已自动切换为无 mocks 用例。\n');
|
|
397
|
+
}
|
|
398
|
+
export function sampleGenerationUsesMocks(executorName, noMock = false) {
|
|
399
|
+
if (noMock)
|
|
400
|
+
return false;
|
|
401
|
+
return executorName ? executorSupportsSampleMocks(executorName) : true;
|
|
402
|
+
}
|
|
370
403
|
/**
|
|
371
404
|
* 拼出送给 LLM 的 user prompt。抽出来便于单测验证 focus 是否真的注入了。
|
|
372
405
|
*
|
|
@@ -390,9 +423,12 @@ ${skillContent}
|
|
|
390
423
|
|
|
391
424
|
${countLine}直接输出 JSON 数组。${focusBlock}${noMockBlock}`;
|
|
392
425
|
}
|
|
393
|
-
export async function generateSamples({ skillContent, count, model, executorName, focus, noMock }) {
|
|
394
|
-
const executor = createExecutor(executorName);
|
|
395
|
-
const
|
|
426
|
+
export async function generateSamples({ skillContent, count, model, executorName, focus, noMock, executor: injectedExecutor, }) {
|
|
427
|
+
const executor = injectedExecutor ?? createExecutor(executorName);
|
|
428
|
+
const mockless = !sampleGenerationUsesMocks(executorName, noMock);
|
|
429
|
+
warnAutoMockless(executorName, noMock);
|
|
430
|
+
const prompt = buildSamplesPrompt({ skillContent, count, focus, noMock: mockless });
|
|
431
|
+
const system = generationSystemPrompt(mockless);
|
|
396
432
|
// 生成场景比单次 eval 调用更重(LLM 要思考结构 + 输出大段 JSON),
|
|
397
433
|
// 默认 120s 对长 skill + count >= 8 经常不够,这里用 5 分钟兜底。
|
|
398
434
|
// lean=true 关掉 agent 工具循环 / skill 发现 — 生成只需要纯文本,不需要 Bash / Read 等工具。
|
|
@@ -405,7 +441,7 @@ export async function generateSamples({ skillContent, count, model, executorName
|
|
|
405
441
|
const attemptPrompt = attempt === 1
|
|
406
442
|
? prompt
|
|
407
443
|
: `${prompt}\n\n上一次输出解析失败:${lastErr}\n请严格按 JSON 规范输出(字符串内部用「」全角引号),只输出数组,不要包含其他文字。`;
|
|
408
|
-
const result = await executor({ model, system
|
|
444
|
+
const result = await executor({ model, system, prompt: attemptPrompt, timeoutMs: 300_000, lean: true });
|
|
409
445
|
totalCost += result.costUSD || 0;
|
|
410
446
|
if (!result.ok) {
|
|
411
447
|
lastErr = result.error || 'unknown error';
|
|
@@ -435,12 +471,15 @@ export async function generateSamples({ skillContent, count, model, executorName
|
|
|
435
471
|
continue;
|
|
436
472
|
}
|
|
437
473
|
// 通过校验,跳出循环继续后续 sanitize
|
|
438
|
-
return await finalizeSamples(samples, totalCost,
|
|
474
|
+
return await finalizeSamples(samples, totalCost, {
|
|
475
|
+
skillContent,
|
|
476
|
+
mockless,
|
|
477
|
+
});
|
|
439
478
|
}
|
|
440
479
|
// 不可达 (循环里所有出口都 throw 或 return),保留是为了 TS 类型推断
|
|
441
480
|
throw new Error('unreachable');
|
|
442
481
|
}
|
|
443
|
-
async function finalizeSamples(samples, costUSD,
|
|
482
|
+
async function finalizeSamples(samples, costUSD, options) {
|
|
444
483
|
// Validate required fields + sanitize metadata enums *at generator boundary*
|
|
445
484
|
// (see sanitizeGeneratedSamples). skillContent is passed so the function can
|
|
446
485
|
// strip "脑补"-style fact assertions whose tool name has no literal mention
|
|
@@ -448,7 +487,10 @@ async function finalizeSamples(samples, costUSD, skillContent) {
|
|
|
448
487
|
// the data-security-review v1-v5 regen series (generator kept producing
|
|
449
488
|
// tool_input_contains "WebFetch:语雀URL" even after 7 prompt iterations,
|
|
450
489
|
// because LLM's "URL → fetch" training prior overrides instructional text).
|
|
451
|
-
const { stripped } = sanitizeGeneratedSamples(samples, {
|
|
490
|
+
const { stripped } = sanitizeGeneratedSamples(samples, {
|
|
491
|
+
...options,
|
|
492
|
+
migrateMocklessEnvironment: true,
|
|
493
|
+
});
|
|
452
494
|
if (stripped.length > 0) {
|
|
453
495
|
process.stderr.write(`[omk sample] LLM-output 含 ${stripped.length} 个非法元数据/断言字段,已剥离避免污染:\n - ${stripped.join('\n - ')}\n`);
|
|
454
496
|
}
|
|
@@ -492,7 +534,7 @@ export function stratifyTraceSignals(items) {
|
|
|
492
534
|
* production failures. The trace text feeds the *generator* only — never the
|
|
493
535
|
* judge prompt — so judge-prompt isolation is unaffected.
|
|
494
536
|
*/
|
|
495
|
-
export function buildSamplesFromTracesPrompt(items, count) {
|
|
537
|
+
export function buildSamplesFromTracesPrompt(items, count, options = {}) {
|
|
496
538
|
// 先按频次分层(合并重复 + 算占比 + 降序),让模型按「占比」分配配额,而非每信号一刀切。
|
|
497
539
|
const stratified = stratifyTraceSignals(items);
|
|
498
540
|
const sections = stratified.map((it, i) => {
|
|
@@ -515,13 +557,16 @@ export function buildSamplesFromTracesPrompt(items, count) {
|
|
|
515
557
|
const countLine = typeof count === 'number'
|
|
516
558
|
? `共生成约 ${count} 条评测用例,按各信号的「占比」分配配额(高频多、低频少),覆盖整体失败分布。`
|
|
517
559
|
: '按各信号「占比」分配:高频信号多生成、低频少生成,覆盖整体失败分布。';
|
|
560
|
+
const mocklessBlock = options.noMock
|
|
561
|
+
? '\n\n目标执行器不支持工具调用拦截:不要生成 mocks、mocksStrict、environment 或正向工具调用断言;把 trace 证据写入 context 和 rubric。'
|
|
562
|
+
: '';
|
|
518
563
|
return `${TRACE_GEN_INSTRUCTIONS}
|
|
519
564
|
|
|
520
565
|
## 观测到的失败信号(共 ${stratified.length} 个,已按出现频次降序)
|
|
521
566
|
|
|
522
567
|
${sections}
|
|
523
568
|
|
|
524
|
-
${countLine}直接输出 JSON
|
|
569
|
+
${countLine}直接输出 JSON 数组。${mocklessBlock}`;
|
|
525
570
|
}
|
|
526
571
|
/** Build a sanitize context string from trace evidence so finalizeSamples keeps
|
|
527
572
|
* tool-name assertions that reference tools actually seen in the traces (an empty
|
|
@@ -539,14 +584,17 @@ function traceSanitizeContext(items) {
|
|
|
539
584
|
* stamps `provenance: 'production-trace'`. Output is meant to land in a review draft,
|
|
540
585
|
* not the live dataset (the CLI enforces that).
|
|
541
586
|
*/
|
|
542
|
-
export async function generateSamplesFromTraces({ items, count, model, executorName, executor: injectedExecutor, }) {
|
|
587
|
+
export async function generateSamplesFromTraces({ items, count, model, executorName, noMock, executor: injectedExecutor, }) {
|
|
543
588
|
if (items.length === 0)
|
|
544
589
|
return { samples: [], costUSD: 0 };
|
|
545
590
|
if (!injectedExecutor && !executorName) {
|
|
546
591
|
throw new Error('executorName is required when no executor is injected');
|
|
547
592
|
}
|
|
548
593
|
const executor = injectedExecutor ?? createExecutor(executorName);
|
|
549
|
-
const
|
|
594
|
+
const mockless = !sampleGenerationUsesMocks(executorName, noMock);
|
|
595
|
+
warnAutoMockless(executorName, noMock);
|
|
596
|
+
const prompt = buildSamplesFromTracesPrompt(items, count, { noMock: mockless });
|
|
597
|
+
const system = generationSystemPrompt(mockless);
|
|
550
598
|
const sanitizeContext = traceSanitizeContext(items);
|
|
551
599
|
const PROVENANCE = 'production-trace';
|
|
552
600
|
const MAX_ATTEMPTS = 3;
|
|
@@ -556,7 +604,7 @@ export async function generateSamplesFromTraces({ items, count, model, executorN
|
|
|
556
604
|
const attemptPrompt = attempt === 1
|
|
557
605
|
? prompt
|
|
558
606
|
: `${prompt}\n\n上一次输出解析失败:${lastErr}\n请严格按 JSON 规范输出(字符串内部用「」全角引号),只输出数组,不要包含其他文字。`;
|
|
559
|
-
const result = await executor({ model, system
|
|
607
|
+
const result = await executor({ model, system, prompt: attemptPrompt, timeoutMs: 300_000, lean: true });
|
|
560
608
|
totalCost += result.costUSD || 0;
|
|
561
609
|
if (!result.ok) {
|
|
562
610
|
lastErr = result.error || 'unknown error';
|
|
@@ -593,7 +641,10 @@ export async function generateSamplesFromTraces({ items, count, model, executorN
|
|
|
593
641
|
// Stamp provenance before sanitize so it survives (it's a valid enum value).
|
|
594
642
|
for (const s of samples)
|
|
595
643
|
s.provenance = PROVENANCE;
|
|
596
|
-
return await finalizeSamples(samples, totalCost,
|
|
644
|
+
return await finalizeSamples(samples, totalCost, {
|
|
645
|
+
skillContent: sanitizeContext,
|
|
646
|
+
mockless,
|
|
647
|
+
});
|
|
597
648
|
}
|
|
598
649
|
throw new Error('unreachable');
|
|
599
650
|
}
|
|
@@ -656,6 +707,78 @@ const TEXT_VALUE_TYPES = new Set([
|
|
|
656
707
|
const TOOL_POSITIVE_TYPES = new Set([
|
|
657
708
|
'tool_input_contains', 'tool_output_contains', 'mock_hit',
|
|
658
709
|
]);
|
|
710
|
+
const MOCKLESS_POSITIVE_TOOL_TYPES = new Set([
|
|
711
|
+
'mock_hit',
|
|
712
|
+
'tools_called',
|
|
713
|
+
'tools_count_min',
|
|
714
|
+
'tool_input_contains',
|
|
715
|
+
'tool_output_contains',
|
|
716
|
+
]);
|
|
717
|
+
function stripMocklessPositiveToolAssertions(assertions, label, stripped) {
|
|
718
|
+
const kept = [];
|
|
719
|
+
for (const [index, assertion] of assertions.entries()) {
|
|
720
|
+
const assertionLabel = `${label}[${index}]`;
|
|
721
|
+
if (assertion?.type === 'assert-set' && Array.isArray(assertion.children)) {
|
|
722
|
+
const children = stripMocklessPositiveToolAssertions(assertion.children, `${assertionLabel}.children`, stripped);
|
|
723
|
+
if (children.length === 0) {
|
|
724
|
+
stripped.push(`${assertionLabel}.assert-set(无 mock 模式下没有可执行子断言)`);
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
assertion.children = children;
|
|
728
|
+
kept.push(assertion);
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
if (MOCKLESS_POSITIVE_TOOL_TYPES.has(assertion?.type)) {
|
|
732
|
+
stripped.push(`${assertionLabel}.${assertion.type}(目标执行器不支持 mocks,正向工具证据不可复现)`);
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
kept.push(assertion);
|
|
736
|
+
}
|
|
737
|
+
return kept;
|
|
738
|
+
}
|
|
739
|
+
function stripInvalidMockHitAssertions(assertions, mockKeys, label, stripped) {
|
|
740
|
+
const kept = [];
|
|
741
|
+
for (const [index, assertion] of assertions.entries()) {
|
|
742
|
+
const assertionLabel = `${label}[${index}]`;
|
|
743
|
+
if (assertion?.type === 'assert-set' && Array.isArray(assertion.children)) {
|
|
744
|
+
const children = stripInvalidMockHitAssertions(assertion.children, mockKeys, `${assertionLabel}.children`, stripped);
|
|
745
|
+
if (children.length === 0) {
|
|
746
|
+
stripped.push(`${assertionLabel}.assert-set(没有可执行子断言)`);
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
assertion.children = children;
|
|
750
|
+
kept.push(assertion);
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
if (assertion?.type === 'mock_hit'
|
|
754
|
+
&& typeof assertion.value === 'string'
|
|
755
|
+
&& !mockKeys.has(assertion.value)) {
|
|
756
|
+
stripped.push(`${assertionLabel}.mock_hit(未引用实际 mock:${assertion.value})`);
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
kept.push(assertion);
|
|
760
|
+
}
|
|
761
|
+
return kept;
|
|
762
|
+
}
|
|
763
|
+
function environmentAsPromptContext(environment) {
|
|
764
|
+
if (!environment || typeof environment !== 'object' || Array.isArray(environment)) {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
const env = environment;
|
|
768
|
+
const lines = ['题设环境声明(仅作上下文,不会在 cwd 物化):'];
|
|
769
|
+
if (Array.isArray(env.cli_available)
|
|
770
|
+
&& env.cli_available.every((item) => typeof item === 'string' && item.length > 0)) {
|
|
771
|
+
lines.push(`- 可用 CLI:${env.cli_available.join('、')}`);
|
|
772
|
+
}
|
|
773
|
+
if (Array.isArray(env.files_available)
|
|
774
|
+
&& env.files_available.every((item) => typeof item === 'string' && item.length > 0)) {
|
|
775
|
+
lines.push(`- 题设引用路径(未物化):${env.files_available.join('、')}`);
|
|
776
|
+
}
|
|
777
|
+
if (typeof env.notes === 'string' && env.notes.trim()) {
|
|
778
|
+
lines.push(`- 说明:${env.notes.trim()}`);
|
|
779
|
+
}
|
|
780
|
+
return lines.length > 1 ? lines.join('\n') : null;
|
|
781
|
+
}
|
|
659
782
|
function isAsciiTokenLike(v) {
|
|
660
783
|
if (typeof v !== 'string')
|
|
661
784
|
return false;
|
|
@@ -719,6 +842,40 @@ export function sanitizeGeneratedSamples(samples, opts = {}) {
|
|
|
719
842
|
stripped.push(`samples[${i}].tripwire (${typeof s.tripwire})`);
|
|
720
843
|
delete s.tripwire;
|
|
721
844
|
}
|
|
845
|
+
if (opts.mockless) {
|
|
846
|
+
if (Array.isArray(s.mocks) && s.mocks.length > 0) {
|
|
847
|
+
stripped.push(`samples[${i}].mocks(目标执行器不支持工具调用拦截)`);
|
|
848
|
+
}
|
|
849
|
+
delete s.mocks;
|
|
850
|
+
if (s.mocksStrict !== undefined) {
|
|
851
|
+
stripped.push(`samples[${i}].mocksStrict(无 mocks)`);
|
|
852
|
+
}
|
|
853
|
+
delete s.mocksStrict;
|
|
854
|
+
if (s.environment !== undefined && !opts.preserveMocklessEnvironment) {
|
|
855
|
+
const environmentContext = opts.migrateMocklessEnvironment
|
|
856
|
+
? environmentAsPromptContext(s.environment)
|
|
857
|
+
: null;
|
|
858
|
+
if (environmentContext) {
|
|
859
|
+
const existingContext = typeof s.context === 'string' ? s.context.trim() : '';
|
|
860
|
+
s.context = existingContext
|
|
861
|
+
? `${existingContext}\n\n${environmentContext}`
|
|
862
|
+
: environmentContext;
|
|
863
|
+
stripped.push(`samples[${i}].environment(已迁移到题设 context,未物化)`);
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
stripped.push(`samples[${i}].environment(未物化的环境声明不能充当 fixture)`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
if (!opts.preserveMocklessEnvironment) {
|
|
870
|
+
delete s.environment;
|
|
871
|
+
}
|
|
872
|
+
if (Array.isArray(s.assertions)) {
|
|
873
|
+
s.assertions = stripMocklessPositiveToolAssertions(s.assertions, `samples[${i}].assertions`, stripped);
|
|
874
|
+
if (s.assertions.length === 0) {
|
|
875
|
+
delete s.assertions;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
722
879
|
// assertions 校验:loader 会拒掉两类无效断言 — 在 generator boundary 提前 strip,
|
|
723
880
|
// 避免落盘的 sample 跑不动:
|
|
724
881
|
// 1. tools_called / tools_not_called 的 values 必须非空
|
|
@@ -843,6 +1000,12 @@ export function sanitizeGeneratedSamples(samples, opts = {}) {
|
|
|
843
1000
|
delete s.mocks;
|
|
844
1001
|
}
|
|
845
1002
|
}
|
|
1003
|
+
if (Array.isArray(s.assertions)) {
|
|
1004
|
+
s.assertions = stripInvalidMockHitAssertions(s.assertions, sampleMockReferenceKeys(s.mocks), `samples[${i}].assertions`, stripped);
|
|
1005
|
+
if (s.assertions.length === 0) {
|
|
1006
|
+
delete s.assertions;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
846
1009
|
// mocksStrict 兜底:有 mocks 时强制 true。
|
|
847
1010
|
// SYSTEM_PROMPT 已要求 LLM 必填,但偶尔 LLM 漏填 — 在 generator boundary 修掉,
|
|
848
1011
|
// 避免运行时 mock 未命中透传到真 shell(报 mcporter not found 等噪声错误)。
|
|
@@ -34,6 +34,8 @@ export interface FixSamplesOptions {
|
|
|
34
34
|
}>;
|
|
35
35
|
model: string;
|
|
36
36
|
maxAttemptsPerSample?: number;
|
|
37
|
+
/** Target executor cannot intercept tool calls; strip mocks and dependent positive evidence. */
|
|
38
|
+
mockless?: boolean;
|
|
37
39
|
}
|
|
38
40
|
export interface FixSamplesResult {
|
|
39
41
|
samples: Record<string, unknown>[];
|