python-yama 0.1.0__tar.gz → 0.2.0__tar.gz
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.
- {python_yama-0.1.0 → python_yama-0.2.0}/CLAUDE.md +7 -0
- python_yama-0.2.0/PKG-INFO +99 -0
- python_yama-0.2.0/README.md +74 -0
- python_yama-0.1.0/README.md → python_yama-0.2.0/README.zh-CN.md +2 -1
- {python_yama-0.1.0 → python_yama-0.2.0}/docs/agent-skill-test-framework-design.md +38 -7
- {python_yama-0.1.0 → python_yama-0.2.0}/skills/eval-plugin-with-yama/SKILL.md +22 -6
- {python_yama-0.1.0 → python_yama-0.2.0}/skills/eval-plugin-with-yama/references/case-schema.md +2 -1
- {python_yama-0.1.0 → python_yama-0.2.0}/skills/eval-plugin-with-yama/references/debugging.md +13 -6
- {python_yama-0.1.0 → python_yama-0.2.0}/tests/test_cli.py +33 -42
- {python_yama-0.1.0 → python_yama-0.2.0}/tests/test_framework.py +128 -2
- {python_yama-0.1.0 → python_yama-0.2.0}/tests/test_mocks.py +52 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/cli.py +11 -12
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/llm.py +4 -1
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/mocks.py +22 -1
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/report.py +1 -1
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/runner.py +33 -6
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/tools/bash/fs.py +16 -5
- python_yama-0.1.0/PKG-INFO +0 -98
- {python_yama-0.1.0 → python_yama-0.2.0}/.github/workflows/publish.yml +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/.gitignore +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/LICENSE +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/pyproject.toml +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/tests/test_assertions.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/tests/test_cli_shim.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/tests/test_main_entrypoint.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/tests/test_public_api.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/tests/test_report.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/tests/test_skill_tool.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/tests/test_workspace.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/uv.lock +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/__init__.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/__main__.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/assertions.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/loaders/__init__.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/loaders/case.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/loaders/common.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/loaders/tool_loader.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/models.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/tools/__init__.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/tools/base.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/tools/bash/__init__.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/tools/bash/_cli_shim.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/tools/bash/bash.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/tools/skill/__init__.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/tools/skill/skill.py +0 -0
- {python_yama-0.1.0 → python_yama-0.2.0}/yama/workspace.py +0 -0
|
@@ -37,6 +37,11 @@ Reproducible evaluation runner for tool-using Agent skills (see `README.md` and
|
|
|
37
37
|
default `.yama/runs` / `.yama/reports/latest.html` locations) changes.
|
|
38
38
|
Everything beyond that basic subset only needs to stay in
|
|
39
39
|
`docs/agent-skill-test-framework-design.md`.
|
|
40
|
+
- `README.zh-CN.md` is the Chinese mirror of `README.md` (English, also the
|
|
41
|
+
PyPI page). Whenever `README.md` changes, apply the same change to
|
|
42
|
+
`README.zh-CN.md` so the two never drift. Links inside `README.md` use
|
|
43
|
+
absolute GitHub URLs (relative links break on PyPI); `README.zh-CN.md` may
|
|
44
|
+
use relative links.
|
|
40
45
|
- When changing the Case syntax (schema fields, mock/assertion/judge
|
|
41
46
|
configuration, path conventions) or the Case execution logic (tool loop,
|
|
42
47
|
step advancement, failure semantics, artifact layout, CLI behavior), update
|
|
@@ -59,6 +64,8 @@ Reproducible evaluation runner for tool-using Agent skills (see `README.md` and
|
|
|
59
64
|
|
|
60
65
|
## Releasing
|
|
61
66
|
|
|
67
|
+
- The PyPI distribution is named `python-yama` (PyPI rejects `yama` as too
|
|
68
|
+
similar to `yaml`); the import package and CLI command are still `yama`.
|
|
62
69
|
- The package version is derived from git tags by `hatch-vcs`; there is no
|
|
63
70
|
`version` field in `pyproject.toml` to edit. Untagged commits build as
|
|
64
71
|
`+g<hash>` dev versions, which PyPI rejects, so only tagged commits can be
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-yama
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A reproducible evaluation runner for tool-using Agent skills
|
|
5
|
+
Project-URL: Repository, https://github.com/world-sim-dev/yama
|
|
6
|
+
Project-URL: Issues, https://github.com/world-sim-dev/yama/issues
|
|
7
|
+
Author-email: Farmer Sun <podpodiumapp@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agent,evaluation,llm,skill,testing
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Software Development :: Testing
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Requires-Dist: fastapi>=0.139.0
|
|
19
|
+
Requires-Dist: jinja2>=3.1
|
|
20
|
+
Requires-Dist: litellm<2.0,>=1.80
|
|
21
|
+
Requires-Dist: orjson>=3.11.9
|
|
22
|
+
Requires-Dist: pyyaml>=6.0
|
|
23
|
+
Requires-Dist: rich<15.0,>=14.1
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# yama
|
|
27
|
+
|
|
28
|
+
English | [简体中文](https://github.com/world-sim-dev/yama/blob/main/README.zh-CN.md)
|
|
29
|
+
|
|
30
|
+
`yama` is a reproducible evaluation framework for tool-using Agent Skills: a declarative YAML Case describes what the model sees — the system prompt, Skill metadata, tool schemas, and multi-turn user messages — then yama calls the LLM through LiteLLM, drives the tool loop, and scores the transcript with deterministic hard checks or an LLM judge. See the [design doc](https://github.com/world-sim-dev/yama/blob/main/docs/agent-skill-test-framework-design.md) (Chinese) for the full Case schema and execution contract.
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Run from a plugin root; collects __evals__/cases/**/*.yaml by default
|
|
36
|
+
cd dingding-simple
|
|
37
|
+
OPENAI_API_KEY=... uv run --project yama yama
|
|
38
|
+
|
|
39
|
+
# Or, from a workspace root (a directory with yama.toml), pick a plugin by name
|
|
40
|
+
OPENAI_API_KEY=... uv run --project yama yama --plugin dingding-simple
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Writing a Case
|
|
44
|
+
|
|
45
|
+
A Case is a YAML file organized as `context` (what the model sees) → `mocks` (how tool calls get executed) → `steps` (user turns sent in order, each with assertions) → `outcome` (the pass bar for the whole Case):
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
context:
|
|
49
|
+
system_prompt: { default: true } # use SYSTEM.md from the plugin root
|
|
50
|
+
|
|
51
|
+
tools:
|
|
52
|
+
- file: tools/read-dsl.yaml # tool schema, resolved relative to __evals__
|
|
53
|
+
|
|
54
|
+
mocks:
|
|
55
|
+
tools:
|
|
56
|
+
read_dsl:
|
|
57
|
+
respond:
|
|
58
|
+
result:
|
|
59
|
+
visualStyle: { name: Vintage Film }
|
|
60
|
+
|
|
61
|
+
steps:
|
|
62
|
+
- id: request-directions
|
|
63
|
+
user: Give me a few creative directions
|
|
64
|
+
assert:
|
|
65
|
+
hard:
|
|
66
|
+
- tool_called: { name: read_dsl }
|
|
67
|
+
- assistant_contains: creative direction
|
|
68
|
+
|
|
69
|
+
outcome:
|
|
70
|
+
require:
|
|
71
|
+
hard_checks: all_pass
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- `context.tools` is the tool schema sent to the model; `mocks.tools` is how the runner responds when a tool call arrives. The two sets of tool names must match exactly.
|
|
75
|
+
- To use a Skill, declare it by name under `context.skills` and also declare `{builtin: skill}` under `context.tools`; the model reads the Skill body via `skill(name, file?)`.
|
|
76
|
+
- To simulate command-line tools, declare `{builtin: bash}` and configure per-command output under `mocks.cli`.
|
|
77
|
+
- `steps[].assert.hard` are deterministic checks (nine types, including `tool_called`, `tool_arguments`, and `assistant_contains`); add `assert.judge` for LLM scoring.
|
|
78
|
+
|
|
79
|
+
For the full schema (every Skill/tool/mock form, the `bash` sandbox, message injection, judge configuration, and more), see the [design doc](https://github.com/world-sim-dev/yama/blob/main/docs/agent-skill-test-framework-design.md) (Chinese).
|
|
80
|
+
|
|
81
|
+
## CLI usage
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
uv run --project yama yama --plugin dingding-simple --report
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| Flag | Meaning |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| `paths` (positional, repeatable) | Explicit Case YAML paths to run |
|
|
90
|
+
| `--plugin-root PATH` | Use the given path as the single plugin root |
|
|
91
|
+
| `--plugin NAME` (repeatable) | Select plugins by name from `yama.toml` |
|
|
92
|
+
| `--all-plugins` | Run every plugin configured in `yama.toml` |
|
|
93
|
+
| `--result-dir PATH` | Override the artifact root (default `<plugin_root>/.yama/runs`) |
|
|
94
|
+
| `--report [PATH]` | Also generate a single-file HTML report (default `.yama/reports/latest.html`) |
|
|
95
|
+
| `--list` | Only print the collected Cases, without running them |
|
|
96
|
+
| `--no-artifacts` | Write no artifact files at all |
|
|
97
|
+
| `--json` | Emit machine-readable JSON instead of Rich tables |
|
|
98
|
+
|
|
99
|
+
`--plugin`, `--all-plugins`, and `--plugin-root` are mutually exclusive. When none of them is given, yama walks up from the current directory to the nearest directory containing `yama.toml` and uses it as the workspace root (falling back to the cwd), running it as a single plugin root.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# yama
|
|
2
|
+
|
|
3
|
+
English | [简体中文](https://github.com/world-sim-dev/yama/blob/main/README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
`yama` is a reproducible evaluation framework for tool-using Agent Skills: a declarative YAML Case describes what the model sees — the system prompt, Skill metadata, tool schemas, and multi-turn user messages — then yama calls the LLM through LiteLLM, drives the tool loop, and scores the transcript with deterministic hard checks or an LLM judge. See the [design doc](https://github.com/world-sim-dev/yama/blob/main/docs/agent-skill-test-framework-design.md) (Chinese) for the full Case schema and execution contract.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Run from a plugin root; collects __evals__/cases/**/*.yaml by default
|
|
11
|
+
cd dingding-simple
|
|
12
|
+
OPENAI_API_KEY=... uv run --project yama yama
|
|
13
|
+
|
|
14
|
+
# Or, from a workspace root (a directory with yama.toml), pick a plugin by name
|
|
15
|
+
OPENAI_API_KEY=... uv run --project yama yama --plugin dingding-simple
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Writing a Case
|
|
19
|
+
|
|
20
|
+
A Case is a YAML file organized as `context` (what the model sees) → `mocks` (how tool calls get executed) → `steps` (user turns sent in order, each with assertions) → `outcome` (the pass bar for the whole Case):
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
context:
|
|
24
|
+
system_prompt: { default: true } # use SYSTEM.md from the plugin root
|
|
25
|
+
|
|
26
|
+
tools:
|
|
27
|
+
- file: tools/read-dsl.yaml # tool schema, resolved relative to __evals__
|
|
28
|
+
|
|
29
|
+
mocks:
|
|
30
|
+
tools:
|
|
31
|
+
read_dsl:
|
|
32
|
+
respond:
|
|
33
|
+
result:
|
|
34
|
+
visualStyle: { name: Vintage Film }
|
|
35
|
+
|
|
36
|
+
steps:
|
|
37
|
+
- id: request-directions
|
|
38
|
+
user: Give me a few creative directions
|
|
39
|
+
assert:
|
|
40
|
+
hard:
|
|
41
|
+
- tool_called: { name: read_dsl }
|
|
42
|
+
- assistant_contains: creative direction
|
|
43
|
+
|
|
44
|
+
outcome:
|
|
45
|
+
require:
|
|
46
|
+
hard_checks: all_pass
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- `context.tools` is the tool schema sent to the model; `mocks.tools` is how the runner responds when a tool call arrives. The two sets of tool names must match exactly.
|
|
50
|
+
- To use a Skill, declare it by name under `context.skills` and also declare `{builtin: skill}` under `context.tools`; the model reads the Skill body via `skill(name, file?)`.
|
|
51
|
+
- To simulate command-line tools, declare `{builtin: bash}` and configure per-command output under `mocks.cli`.
|
|
52
|
+
- `steps[].assert.hard` are deterministic checks (nine types, including `tool_called`, `tool_arguments`, and `assistant_contains`); add `assert.judge` for LLM scoring.
|
|
53
|
+
|
|
54
|
+
For the full schema (every Skill/tool/mock form, the `bash` sandbox, message injection, judge configuration, and more), see the [design doc](https://github.com/world-sim-dev/yama/blob/main/docs/agent-skill-test-framework-design.md) (Chinese).
|
|
55
|
+
|
|
56
|
+
## CLI usage
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
uv run --project yama yama --plugin dingding-simple --report
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Flag | Meaning |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `paths` (positional, repeatable) | Explicit Case YAML paths to run |
|
|
65
|
+
| `--plugin-root PATH` | Use the given path as the single plugin root |
|
|
66
|
+
| `--plugin NAME` (repeatable) | Select plugins by name from `yama.toml` |
|
|
67
|
+
| `--all-plugins` | Run every plugin configured in `yama.toml` |
|
|
68
|
+
| `--result-dir PATH` | Override the artifact root (default `<plugin_root>/.yama/runs`) |
|
|
69
|
+
| `--report [PATH]` | Also generate a single-file HTML report (default `.yama/reports/latest.html`) |
|
|
70
|
+
| `--list` | Only print the collected Cases, without running them |
|
|
71
|
+
| `--no-artifacts` | Write no artifact files at all |
|
|
72
|
+
| `--json` | Emit machine-readable JSON instead of Rich tables |
|
|
73
|
+
|
|
74
|
+
`--plugin`, `--all-plugins`, and `--plugin-root` are mutually exclusive. When none of them is given, yama walks up from the current directory to the nearest directory containing `yama.toml` and uses it as the workspace root (falling back to the cwd), running it as a single plugin root.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# yama
|
|
2
2
|
|
|
3
|
+
[English](README.md) | 简体中文
|
|
4
|
+
|
|
3
5
|
`yama` 是一个可复现的 Agent Skill 评测框架:用声明式 YAML Case 描述模型看到的 System Prompt、Skill metadata、Tool Schema 与多轮 User Message,通过 LiteLLM 统一调用 LLM 并驱动 Tool Loop,再对 Transcript 做确定性 Hard Check 或 LLM Judge 评分。完整 Case Schema 与执行契约见[设计文档](docs/agent-skill-test-framework-design.md)。
|
|
4
6
|
|
|
5
7
|
## 快速开始
|
|
@@ -63,7 +65,6 @@ uv run --project yama yama --plugin dingding-simple --report
|
|
|
63
65
|
| `--plugin-root PATH` | 以指定路径作为单一 Plugin Root |
|
|
64
66
|
| `--plugin NAME`(可重复) | 按 `yama.toml` 中的 Plugin 名字选择 |
|
|
65
67
|
| `--all-plugins` | 运行 `yama.toml` 中配置的全部 Plugin |
|
|
66
|
-
| `--response-script PATH` | 用脚本化 LLM 响应回放,不调用真实模型 |
|
|
67
68
|
| `--result-dir PATH` | 覆盖产物根目录(默认 `<plugin_root>/.yama/runs`) |
|
|
68
69
|
| `--report [PATH]` | 额外生成单文件 HTML 报告(默认 `.yama/reports/latest.html`) |
|
|
69
70
|
| `--list` | 只打印匹配到的 Case,不运行 |
|
|
@@ -141,6 +141,9 @@ cli.main
|
|
|
141
141
|
`id(context.state)` 做 `ShellSession` 的 key)。
|
|
142
142
|
- **`LLMClient`**:`LiteLLMClient` 走真实 Provider,`ScriptedLLMClient` 用于
|
|
143
143
|
确定性测试/回放,两者共用同一套消息/工具转换逻辑 (`_litellm_call_arguments`)。
|
|
144
|
+
脚本响应可带 `expect`(`tools`/`last_role`/`last_user_contains`)对当次请求做
|
|
145
|
+
断言;请求没有工具时 litellm 参数会整体省略 `tools` 键,`expect: {tools: []}`
|
|
146
|
+
按空列表匹配(Judge 请求正是这种无工具请求)。
|
|
144
147
|
|
|
145
148
|
## 4. Workspace 与 Plugin
|
|
146
149
|
|
|
@@ -214,7 +217,6 @@ yama --plugin-root ./dingding-simple
|
|
|
214
217
|
| `--plugin-root PATH` | 以指定路径作为单一 Plugin Root |
|
|
215
218
|
| `--plugin NAME`(可重复) | 按 `yama.toml` 中的 Plugin 名字选择,一次可传多次 |
|
|
216
219
|
| `--all-plugins` | 运行 `yama.toml` 中配置的全部 Plugin |
|
|
217
|
-
| `--response-script PATH` | 用 `ScriptedLLMClient` 回放一份确定性响应脚本,不调用真实模型 |
|
|
218
220
|
| `--result-dir PATH` | 覆盖产物根目录(默认每个 Case 写到其所属 `plugin_root/.yama/runs`) |
|
|
219
221
|
| `--report [PATH]` | 额外写一份单文件 HTML 报告;省略路径时默认写到 `<第一个 result_root 的上级>/reports/latest.html` |
|
|
220
222
|
| `--list` | 只收集并打印 `case_key` 列表,不运行 |
|
|
@@ -1295,10 +1297,21 @@ mocks:
|
|
|
1295
1297
|
持久 bash 子进程绑定。这个路径是 `plugin_root/case_key/run_index` 的纯函数,Python
|
|
1296
1298
|
Handler 可以在物化发生之前就用包根导出的 `workspace_dir(context)`(定义在
|
|
1297
1299
|
`tools/bash/fs.py`)预先计算它;首次物化时同一路径也会写入共享 state 的
|
|
1298
|
-
`__fs__.workspace_dir`,供没有 `ToolMockContext` 的 Python Checker 读取。
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1300
|
+
`__fs__.workspace_dir`,供没有 `ToolMockContext` 的 Python Checker 读取。
|
|
1301
|
+
|
|
1302
|
+
物化时机以每 run 共享 state 里的 `__fs__` 标记为准,而不是"目录是否已存在"
|
|
1303
|
+
(`fs.py:_get_session`):run 的第一次 `bash` 调用总会执行 `materialize_workspace`
|
|
1304
|
+
(即使 Python Handler 已提前创建了该目录),把当前 Case 配置声明的 `files`、
|
|
1305
|
+
`.mockbin/` 假命令和编译后的 `mocks.cli` 规则文件全部写到最新状态;run 中途因超时/
|
|
1306
|
+
会话死亡而重建 shell 会话时则不再重复物化,模型已写入的文件得以保留。此外
|
|
1307
|
+
`YamaRunner.run_case` 在一个 Case 的第一轮 run 开始前会整体删除
|
|
1308
|
+
`.yama/fs-runs/<sanitized-case-key>/`(`runner.py:_clean_case_dirs`)——run 编号每次
|
|
1309
|
+
执行都从 1 重新计数,不先清掉上一次执行遗留的同名工作目录,本次执行就会复用过期的
|
|
1310
|
+
mock 脚本与旧文件,结果不可复现。run 结束(成功、断言失败或异常)时框架在
|
|
1311
|
+
`runner.py` 的 `finally` 块里保证调用 `cleanup_session` 杀掉持久 shell 进程,
|
|
1312
|
+
工作目录本身保留到下一次执行该 Case 前才被清掉,方便事后排查。因为是同一个真实
|
|
1313
|
+
进程贯穿整个 run,`cd`、`export`、写文件等状态天然延续到下一次 Tool Call,不需要
|
|
1314
|
+
额外的状态字典维护 cwd/环境。
|
|
1302
1315
|
|
|
1303
1316
|
**命令执行:持久 shell + marker 协议。** 每次 Tool Call 把命令包装成如下 payload
|
|
1304
1317
|
写入这个 shell 的 stdin:
|
|
@@ -1919,7 +1932,11 @@ Registry 的执行顺序固定为:
|
|
|
1919
1932
|
`request_index`、`call_index`、`handler_kind`(`"python"` 或 `"declarative"`)、
|
|
1920
1933
|
`callable`(Python Handler 的点分路径,声明式 Mock 为 `None`)、`match_failed`、
|
|
1921
1934
|
`arguments`、`result`、`is_error`、`duration_ms`、`state_diff`(`{op: add|remove|replace,
|
|
1922
|
-
path, value|old}`
|
|
1935
|
+
path, value|old}` 形式的差异列表)。`is_error` 的判定是语义化的
|
|
1936
|
+
(`mocks.py:result_is_error`,Mock Event 与 `ToolCallRecord.is_error` 共用同一函数):
|
|
1937
|
+
结果携带**非 null** 的 `error` 字段,或是 bash 形状的 `{stdout, stderr, exit_code}`
|
|
1938
|
+
信封且 `exit_code != 0` 时才算错误;成功结果里字面的 `error: null` 不算错误,
|
|
1939
|
+
`ToolMockError`/`match_failed` 路径则始终算错误。Run 结束额外保存 `mock-state.json`;Python
|
|
1923
1940
|
Checker 从同一个最终 state 读取业务结果。Handler 源码本身不复制到 Judge 输入,
|
|
1924
1941
|
也不发送给被测模型。
|
|
1925
1942
|
|
|
@@ -1981,7 +1998,7 @@ async def run_step(case, step, messages, registry, run_index, requests):
|
|
|
1981
1998
|
context = ToolMockContext(..., call_index=registry.next_call_index(call.name))
|
|
1982
1999
|
result = await registry.call(call.name, call.arguments, context)
|
|
1983
2000
|
records.append(ToolCallRecord(..., call=call, result=result,
|
|
1984
|
-
is_error=
|
|
2001
|
+
is_error=result_is_error(result))) # 语义化判定,见第 9 章
|
|
1985
2002
|
messages.append({
|
|
1986
2003
|
"role": "tool",
|
|
1987
2004
|
"tool_call_id": call.id,
|
|
@@ -2106,6 +2123,13 @@ class ToolCallRecord:
|
|
|
2106
2123
|
└── result.json # RunResult.to_dict() + generated_at + traceback?
|
|
2107
2124
|
```
|
|
2108
2125
|
|
|
2126
|
+
写产物前,`YamaRunner.run_case` 会先整体删除该 Case 的产物目录
|
|
2127
|
+
`<result_root>/<sanitize(case_key)>/`(`runner.py:_clean_case_dirs`,与 8.4.3 节的
|
|
2128
|
+
`.yama/fs-runs/<case>` 清理同一处):run 编号每次执行都从 1 重新计数,若本次
|
|
2129
|
+
`execution.repeat` 比上次小,不清理就会留下过期的 `run-002` 等目录,人工或脚本
|
|
2130
|
+
扫描 `.yama/runs` 时会把旧执行误当成本次结果。`--no-artifacts` 时不写也不删任何
|
|
2131
|
+
产物目录(用户显式选择不落盘,不应顺带删掉历史产物),但 fs-runs 工作目录照常清理。
|
|
2132
|
+
|
|
2109
2133
|
`--report` 触发的单文件 HTML 报告(`report.py:write_html_report`)是 CLI 层面单独
|
|
2110
2134
|
生成的汇总产物,**不属于**上面这份逐 Case/逐 run 的产物列表,也不是按 Plugin 拆分的
|
|
2111
2135
|
多个文件——一次 CLI 调用只写一个 HTML 文件,汇总本次涉及的所有 Case(见第 13 节)。
|
|
@@ -2383,6 +2407,13 @@ Details
|
|
|
2383
2407
|
`--json` 输出机器可读的 `[CaseResult.to_dict(), ...]` 数组;`--list` 只打印
|
|
2384
2408
|
`case_key` 列表,不运行任何 Case。
|
|
2385
2409
|
|
|
2410
|
+
`cli.py:main` 的错误出口按层归类(退出码都是 2):`ConfigurationError`/
|
|
2411
|
+
`CollectionError`/`ValueError` 打印 `Collection error: ...`(Case 还没跑起来,
|
|
2412
|
+
问题出在工作区配置或 Case 收集/解析);`LLMError` 单独打印 `LLM error: ...`——
|
|
2413
|
+
此时 Case 往往已经收集甚至执行过(例如脚本回放响应未被消费、Provider 配置缺失),
|
|
2414
|
+
把它标成 Collection error 会把排查引到错误的层级。run 内部的 LLM 异常仍由
|
|
2415
|
+
`YamaRunner._run_once` 捕获进 `RunResult.error`,不走这个出口。
|
|
2416
|
+
|
|
2386
2417
|
一个 Plugin 的 Skill、Tool、Case、产物目录不会泄漏到另一个 Plugin——这一点由
|
|
2387
2418
|
"每个 Case 归属唯一 Plugin Root、产物写在该 Plugin 自己的 `.yama/` 下"这一实现
|
|
2388
2419
|
天然保证,不依赖某种显式的隔离机制。
|
|
@@ -105,23 +105,38 @@ outcome:
|
|
|
105
105
|
完整 Schema(多模态消息、`message_inject`、`mocks.cli` 命令树、`mocks.fs` 沙箱、
|
|
106
106
|
Python Handler / Checker、Judge 配置)见 [references/case-schema.md](references/case-schema.md)。
|
|
107
107
|
|
|
108
|
+
## 安装
|
|
109
|
+
|
|
110
|
+
yama 在 PyPI 上的发行名是 `python-yama`(安装后的命令行入口仍叫 `yama`),要求
|
|
111
|
+
Python ≥ 3.12。推荐装成全局命令行工具:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
uv tool install python-yama # 或 pipx install python-yama / pip install python-yama
|
|
115
|
+
yama --help # 验证安装成功
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
已安装过的话,用 `uv tool upgrade python-yama` 升级到最新版。
|
|
119
|
+
|
|
108
120
|
## 运行
|
|
109
121
|
|
|
122
|
+
以下命令假定 `yama` 已按上一节安装、可直接调用:
|
|
123
|
+
|
|
110
124
|
```bash
|
|
111
125
|
# 在 Plugin 根目录直接运行(默认收集 __evals__/cases/**/*.yaml)
|
|
112
126
|
cd <plugin-root>
|
|
113
|
-
OPENAI_API_KEY=...
|
|
127
|
+
OPENAI_API_KEY=... yama
|
|
114
128
|
|
|
115
129
|
# 在 Workspace 根目录(存在 yama.toml)按名字选 Plugin,并生成 HTML 报告
|
|
116
|
-
|
|
130
|
+
yama --plugin <name> --report
|
|
117
131
|
|
|
118
132
|
# 只跑一个 Case;只列出会收集到哪些 Case
|
|
119
|
-
|
|
120
|
-
|
|
133
|
+
yama __evals__/cases/foo.yaml
|
|
134
|
+
yama --list
|
|
121
135
|
```
|
|
122
136
|
|
|
123
|
-
退出码:`0` 全部通过;`1` 有 Case 失败;`2` Collection error
|
|
124
|
-
`
|
|
137
|
+
退出码:`0` 全部通过;`1` 有 Case 失败;`2` Collection error、LLM error
|
|
138
|
+
(stderr 分别打印 `Collection error: ...` / `LLM error: ...`)或没收集到任何
|
|
139
|
+
Case;`130` 手动中断。
|
|
125
140
|
|
|
126
141
|
## 排查不通过的用例
|
|
127
142
|
|
|
@@ -132,6 +147,7 @@ uv run --project yama yama --list
|
|
|
132
147
|
| 现象 | 层级 | 去哪看 |
|
|
133
148
|
| --- | --- | --- |
|
|
134
149
|
| stderr 打印 `Collection error: ...`,退出码 2 | 用例根本没跑:YAML/Schema/路径/配置错误 | 错误消息本身,对照上面「硬规则」 |
|
|
150
|
+
| stderr 打印 `LLM error: ...`,退出码 2 | LLM 层错误(Provider 配置缺失、脚本回放响应未消费),Case 可能已经收集甚至执行过 | 错误消息本身;已写出的产物目录 |
|
|
135
151
|
| 退出码 1,某 Run 标红且带 error | run 级基础设施失败(未 Mock 的 Tool 被调用、sequence 用尽、Step 超时、Handler 抛异常、LLM 请求失败) | `result.json` 的 `runs[].error` / `traceback` |
|
|
136
152
|
| 退出码 1,某 Step 的 hard check 失败 | 断言失败(模型行为不符合预期,或断言写错) | 终端展开的 `expected/actual/reason`;`hard-checks.json` |
|
|
137
153
|
| hard check 全过但 Case 失败 | Judge 不达标 | `judge-result.json` 各维度 `score/reason/evidence` |
|
{python_yama-0.1.0 → python_yama-0.2.0}/skills/eval-plugin-with-yama/references/case-schema.md
RENAMED
|
@@ -235,7 +235,8 @@ mocks:
|
|
|
235
235
|
```
|
|
236
236
|
|
|
237
237
|
- 工作目录:`<plugin_root>/.yama/fs-runs/<case-key>/run-00N/`,run 结束**不清理**,
|
|
238
|
-
|
|
238
|
+
供排查;下一次执行同一 Case 时才整体清掉重建(改 mock 配置后重跑不会复用旧
|
|
239
|
+
`.mockbin` 脚本)。持久 bash 会话贯穿整个 run,`cd`/`export`/写文件状态自然延续。
|
|
239
240
|
- Python Handler 可用 `from yama import workspace_dir` 提前算出该路径
|
|
240
241
|
(`workspace_dir(context)`,纯函数,物化前即可调用);首次 bash 调用后同一路径
|
|
241
242
|
写入共享 state 的 `__fs__.workspace_dir`,供没有 context 的 Python Checker 读取。
|
{python_yama-0.1.0 → python_yama-0.2.0}/skills/eval-plugin-with-yama/references/debugging.md
RENAMED
|
@@ -14,13 +14,15 @@
|
|
|
14
14
|
每次 run 写入 `<plugin_root>/.yama/runs/<目录名>/run-{NNN}/`,目录名 =
|
|
15
15
|
`case_key` 把 `/` 换成 `-` 并去掉 `.yaml` 扩展名(如
|
|
16
16
|
`__evals__/cases/image-led-mv.yaml` → `__evals__-cases-image-led-mv`)。
|
|
17
|
-
`--result-dir` 可覆盖根目录;`--no-artifacts`
|
|
17
|
+
`--result-dir` 可覆盖根目录;`--no-artifacts` 则完全不写(也不删历史产物)。
|
|
18
|
+
每次执行开始时会先整体清掉该 Case 的产物目录再重写,所以目录下看到的
|
|
19
|
+
`run-00N` 一定全部来自最近一次执行,不会混入上一次执行的残留。
|
|
18
20
|
|
|
19
21
|
| 文件 | 内容 | 什么时候看 |
|
|
20
22
|
| --- | --- | --- |
|
|
21
23
|
| `result.json` | run 是否通过、`error`/`traceback`、全量数据汇总 | **永远先看**:error 非空 = run 级错误 |
|
|
22
24
|
| `hard-checks.json` | `{step_id: [每条断言的 type/passed/label/expected/actual/reason/detail]}` | Step 断言失败时 |
|
|
23
|
-
| `mock-events.json` | 每次 Tool 调用:`tool/arguments/result/is_error/match_failed/state_diff/duration_ms` | 怀疑 Mock 匹配 / Tool 行为时 |
|
|
25
|
+
| `mock-events.json` | 每次 Tool 调用:`tool/arguments/result/is_error/match_failed/state_diff/duration_ms`。`is_error` 是语义判定:结果带**非 null** `error`,或 bash 信封 `exit_code != 0`;成功结果里的 `error: null` 不算错误 | 怀疑 Mock 匹配 / Tool 行为时 |
|
|
24
26
|
| `mock-state.json` | run 结束时的最终共享 state | 有状态 Handler / Python Checker 断言失败时 |
|
|
25
27
|
| `requests/request-00N.json` | 每次真实 LLM 调用的完整入参(messages/tools)与响应;请求失败时带 `error` 字段 | 怀疑「模型到底看到了什么」时 |
|
|
26
28
|
| `transcript.json` | 每个 Step 结束后的完整对话快照(含注入消息、Tool Result) | 复盘模型行为时 |
|
|
@@ -34,7 +36,10 @@
|
|
|
34
36
|
|
|
35
37
|
## 第 1 层:Collection error(退出码 2)
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
用例在收集/解析阶段就被拒绝,一条也没运行。若 stderr 打印的是
|
|
40
|
+
`LLM error: ...`(退出码同样是 2),问题不在收集层而在 LLM 层
|
|
41
|
+
(如 Provider 配置缺失、脚本回放响应未消费),不要按下表排查。
|
|
42
|
+
错误消息即答案,常见对照:
|
|
38
43
|
|
|
39
44
|
| 错误消息(关键词) | 原因与修法 |
|
|
40
45
|
| --- | --- |
|
|
@@ -130,6 +135,8 @@ Step 全部失败项。先回答一个问题:**是模型行为错了,还是
|
|
|
130
135
|
|
|
131
136
|
- 沙箱工作目录保留在 `<plugin_root>/.yama/fs-runs/<case-key>/run-00N/`,run 结束
|
|
132
137
|
不清理——直接进去看模型写了什么文件、`.mockbin/` 里编译出了哪些假命令。
|
|
138
|
+
该目录在**下一次执行同一 Case 时**才被整体清掉重建,因此排查时看到的内容
|
|
139
|
+
总是最近一次执行的;改了 `mocks.cli`/`mocks.fs` 配置后重跑不会复用旧脚本。
|
|
133
140
|
- `command not found`(exit 127)三种可能:命令没在 `mocks.cli` 声明;模型用了
|
|
134
141
|
绝对路径/带 `/` 的调用(绕过 PATH shadow,Mock 不生效,要求模型用裸命令名);
|
|
135
142
|
命令名拼错。
|
|
@@ -137,6 +144,8 @@ Step 全部失败项。先回答一个问题:**是模型行为错了,还是
|
|
|
137
144
|
「未声明命令一律 not found」对内建不成立。
|
|
138
145
|
- 模型「没意识到」命令失败:发给模型的 Tool Result 只有 `stdout + stderr` 拼接文本,
|
|
139
146
|
`exit_code` 不可见——mock 的报错信息要写进 `stderr` 文本才对模型有意义。
|
|
147
|
+
(框架侧则看得见:非零 `exit_code` 会让该调用在 `mock-events.json` 里标记
|
|
148
|
+
`is_error: true`,可用来快速过滤失败调用。)
|
|
140
149
|
- 链式命令输出无人工换行(`a && b` 的两段 stdout 直接相连);需要分行就在每段
|
|
141
150
|
`stdout` 末尾自带 `\n`。
|
|
142
151
|
- `BASH_TIMEOUT`:单条命令超过 `timeout_seconds`(默认 30s),会话被重启且 cwd 重置。
|
|
@@ -146,8 +155,6 @@ Step 全部失败项。先回答一个问题:**是模型行为错了,还是
|
|
|
146
155
|
|
|
147
156
|
## 隔离变量的调试手段
|
|
148
157
|
|
|
149
|
-
- 只跑出问题的 Case:`
|
|
150
|
-
- 怀疑框架/断言本身而非模型:`--response-script <path>` 用 `ScriptedLLMClient` 回放
|
|
151
|
-
固定 LLM 响应,让 run 完全确定,再单独验证断言与 Mock 行为。
|
|
158
|
+
- 只跑出问题的 Case:`yama __evals__/cases/foo.yaml`。
|
|
152
159
|
- 结果不稳定:`execution.repeat` 调大看 `pass_rate`,每次 run 的产物目录独立
|
|
153
160
|
(`run-001`、`run-002`…),可横向对比 transcript 找分叉点。
|
|
@@ -7,8 +7,10 @@ import os
|
|
|
7
7
|
import tempfile
|
|
8
8
|
import unittest
|
|
9
9
|
from pathlib import Path
|
|
10
|
+
from unittest.mock import patch
|
|
10
11
|
|
|
11
12
|
from yama.cli import DEFAULT_REPORT, _parser, main
|
|
13
|
+
from yama.llm import LLMError, ScriptedLLMClient
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
def write_plugin(root: Path, *, case_names: list[str] | None = None) -> Path:
|
|
@@ -32,15 +34,14 @@ steps:
|
|
|
32
34
|
return root
|
|
33
35
|
|
|
34
36
|
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
def scripted_llm(*, count: int = 1):
|
|
38
|
+
"""Patch the CLI's LLM client with deterministic scripted responses."""
|
|
39
|
+
return patch(
|
|
40
|
+
"yama.cli.LiteLLMClient",
|
|
41
|
+
lambda: ScriptedLLMClient(
|
|
42
|
+
[{"content": "hi there, friend!"} for _ in range(count)]
|
|
43
|
+
),
|
|
41
44
|
)
|
|
42
|
-
script_path.write_text(f"responses:\n{responses}\n", encoding="utf-8")
|
|
43
|
-
return script_path
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
@contextlib.contextmanager
|
|
@@ -60,7 +61,6 @@ class ParserTests(unittest.TestCase):
|
|
|
60
61
|
self.assertIsNone(args.plugin_root)
|
|
61
62
|
self.assertIsNone(args.plugin)
|
|
62
63
|
self.assertFalse(args.all_plugins)
|
|
63
|
-
self.assertIsNone(args.response_script)
|
|
64
64
|
self.assertIsNone(args.result_dir)
|
|
65
65
|
self.assertIsNone(args.report)
|
|
66
66
|
self.assertFalse(args.list)
|
|
@@ -102,15 +102,12 @@ class MainEndToEndTests(unittest.TestCase):
|
|
|
102
102
|
def test_successful_run_with_scripted_responses_and_json_output(self) -> None:
|
|
103
103
|
with tempfile.TemporaryDirectory() as tmp:
|
|
104
104
|
plugin_root = write_plugin(Path(tmp) / "plugin")
|
|
105
|
-
script = write_response_script(plugin_root)
|
|
106
105
|
stdout = io.StringIO()
|
|
107
|
-
with contextlib.redirect_stdout(stdout):
|
|
106
|
+
with scripted_llm(), contextlib.redirect_stdout(stdout):
|
|
108
107
|
code = main(
|
|
109
108
|
[
|
|
110
109
|
"--plugin-root",
|
|
111
110
|
str(plugin_root),
|
|
112
|
-
"--response-script",
|
|
113
|
-
str(script),
|
|
114
111
|
"--no-artifacts",
|
|
115
112
|
"--json",
|
|
116
113
|
]
|
|
@@ -123,15 +120,12 @@ class MainEndToEndTests(unittest.TestCase):
|
|
|
123
120
|
def test_text_output_renders_results_table(self) -> None:
|
|
124
121
|
with tempfile.TemporaryDirectory() as tmp:
|
|
125
122
|
plugin_root = write_plugin(Path(tmp) / "plugin")
|
|
126
|
-
script = write_response_script(plugin_root)
|
|
127
123
|
stdout = io.StringIO()
|
|
128
|
-
with contextlib.redirect_stdout(stdout):
|
|
124
|
+
with scripted_llm(), contextlib.redirect_stdout(stdout):
|
|
129
125
|
code = main(
|
|
130
126
|
[
|
|
131
127
|
"--plugin-root",
|
|
132
128
|
str(plugin_root),
|
|
133
|
-
"--response-script",
|
|
134
|
-
str(script),
|
|
135
129
|
"--no-artifacts",
|
|
136
130
|
]
|
|
137
131
|
)
|
|
@@ -141,16 +135,13 @@ class MainEndToEndTests(unittest.TestCase):
|
|
|
141
135
|
def test_report_flag_writes_html_report(self) -> None:
|
|
142
136
|
with tempfile.TemporaryDirectory() as tmp:
|
|
143
137
|
plugin_root = write_plugin(Path(tmp) / "plugin")
|
|
144
|
-
script = write_response_script(plugin_root)
|
|
145
138
|
report_path = Path(tmp) / "report.html"
|
|
146
139
|
stdout = io.StringIO()
|
|
147
|
-
with contextlib.redirect_stdout(stdout):
|
|
140
|
+
with scripted_llm(), contextlib.redirect_stdout(stdout):
|
|
148
141
|
code = main(
|
|
149
142
|
[
|
|
150
143
|
"--plugin-root",
|
|
151
144
|
str(plugin_root),
|
|
152
|
-
"--response-script",
|
|
153
|
-
str(script),
|
|
154
145
|
"--no-artifacts",
|
|
155
146
|
"--report",
|
|
156
147
|
str(report_path),
|
|
@@ -162,16 +153,13 @@ class MainEndToEndTests(unittest.TestCase):
|
|
|
162
153
|
def test_default_report_path_is_derived_from_result_root(self) -> None:
|
|
163
154
|
with tempfile.TemporaryDirectory() as tmp:
|
|
164
155
|
plugin_root = write_plugin(Path(tmp) / "plugin")
|
|
165
|
-
script = write_response_script(plugin_root)
|
|
166
156
|
result_dir = Path(tmp) / "results"
|
|
167
157
|
stdout = io.StringIO()
|
|
168
|
-
with contextlib.redirect_stdout(stdout):
|
|
158
|
+
with scripted_llm(), contextlib.redirect_stdout(stdout):
|
|
169
159
|
code = main(
|
|
170
160
|
[
|
|
171
161
|
"--plugin-root",
|
|
172
162
|
str(plugin_root),
|
|
173
|
-
"--response-script",
|
|
174
|
-
str(script),
|
|
175
163
|
"--result-dir",
|
|
176
164
|
str(result_dir),
|
|
177
165
|
"--no-artifacts",
|
|
@@ -190,10 +178,24 @@ class MainEndToEndTests(unittest.TestCase):
|
|
|
190
178
|
self.assertEqual(code, 2)
|
|
191
179
|
self.assertIn("Collection error", stderr.getvalue())
|
|
192
180
|
|
|
181
|
+
def test_llm_error_is_reported_as_llm_error(self) -> None:
|
|
182
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
183
|
+
plugin_root = write_plugin(Path(tmp) / "plugin")
|
|
184
|
+
stderr = io.StringIO()
|
|
185
|
+
|
|
186
|
+
def raise_llm_error():
|
|
187
|
+
raise LLMError("scripted LLM has 1 unconsumed responses")
|
|
188
|
+
|
|
189
|
+
with patch("yama.cli.LiteLLMClient", raise_llm_error), \
|
|
190
|
+
contextlib.redirect_stderr(stderr):
|
|
191
|
+
code = main(["--plugin-root", str(plugin_root), "--no-artifacts"])
|
|
192
|
+
self.assertEqual(code, 2)
|
|
193
|
+
self.assertIn("LLM error", stderr.getvalue())
|
|
194
|
+
self.assertNotIn("Collection error", stderr.getvalue())
|
|
195
|
+
|
|
193
196
|
def test_keyboard_interrupt_returns_130(self) -> None:
|
|
194
197
|
with tempfile.TemporaryDirectory() as tmp:
|
|
195
198
|
plugin_root = write_plugin(Path(tmp) / "plugin")
|
|
196
|
-
from unittest.mock import patch
|
|
197
199
|
|
|
198
200
|
def _raise(coro):
|
|
199
201
|
coro.close()
|
|
@@ -206,14 +208,11 @@ class MainEndToEndTests(unittest.TestCase):
|
|
|
206
208
|
def test_default_target_is_current_workspace_root(self) -> None:
|
|
207
209
|
with tempfile.TemporaryDirectory() as tmp:
|
|
208
210
|
plugin_root = write_plugin(Path(tmp) / "plugin")
|
|
209
|
-
script = write_response_script(plugin_root)
|
|
210
211
|
with chdir(plugin_root):
|
|
211
212
|
stdout = io.StringIO()
|
|
212
|
-
with contextlib.redirect_stdout(stdout):
|
|
213
|
+
with scripted_llm(), contextlib.redirect_stdout(stdout):
|
|
213
214
|
code = main(
|
|
214
215
|
[
|
|
215
|
-
"--response-script",
|
|
216
|
-
str(script),
|
|
217
216
|
"--no-artifacts",
|
|
218
217
|
"--json",
|
|
219
218
|
]
|
|
@@ -225,19 +224,16 @@ class MainEndToEndTests(unittest.TestCase):
|
|
|
225
224
|
def test_all_plugins_and_named_plugin_selection(self) -> None:
|
|
226
225
|
with tempfile.TemporaryDirectory() as tmp:
|
|
227
226
|
workspace_root = Path(tmp)
|
|
228
|
-
|
|
229
|
-
script = write_response_script(plugin_root)
|
|
227
|
+
write_plugin(workspace_root / "demo")
|
|
230
228
|
(workspace_root / "yama.toml").write_text(
|
|
231
229
|
'[plugins.demo]\npath = "demo"\n', encoding="utf-8"
|
|
232
230
|
)
|
|
233
231
|
with chdir(workspace_root):
|
|
234
232
|
stdout = io.StringIO()
|
|
235
|
-
with contextlib.redirect_stdout(stdout):
|
|
233
|
+
with scripted_llm(), contextlib.redirect_stdout(stdout):
|
|
236
234
|
code = main(
|
|
237
235
|
[
|
|
238
236
|
"--all-plugins",
|
|
239
|
-
"--response-script",
|
|
240
|
-
str(script),
|
|
241
237
|
"--no-artifacts",
|
|
242
238
|
"--json",
|
|
243
239
|
]
|
|
@@ -247,13 +243,11 @@ class MainEndToEndTests(unittest.TestCase):
|
|
|
247
243
|
self.assertEqual(len(payload), 1)
|
|
248
244
|
|
|
249
245
|
stdout2 = io.StringIO()
|
|
250
|
-
with contextlib.redirect_stdout(stdout2):
|
|
246
|
+
with scripted_llm(), contextlib.redirect_stdout(stdout2):
|
|
251
247
|
code2 = main(
|
|
252
248
|
[
|
|
253
249
|
"--plugin",
|
|
254
250
|
"demo",
|
|
255
|
-
"--response-script",
|
|
256
|
-
str(script),
|
|
257
251
|
"--no-artifacts",
|
|
258
252
|
"--json",
|
|
259
253
|
]
|
|
@@ -276,17 +270,14 @@ class MainEndToEndTests(unittest.TestCase):
|
|
|
276
270
|
plugin_root = write_plugin(
|
|
277
271
|
Path(tmp) / "plugin", case_names=["one", "two"]
|
|
278
272
|
)
|
|
279
|
-
script = write_response_script(plugin_root)
|
|
280
273
|
only_one = plugin_root / "__evals__" / "cases" / "one.yaml"
|
|
281
274
|
stdout = io.StringIO()
|
|
282
|
-
with contextlib.redirect_stdout(stdout):
|
|
275
|
+
with scripted_llm(), contextlib.redirect_stdout(stdout):
|
|
283
276
|
code = main(
|
|
284
277
|
[
|
|
285
278
|
str(only_one),
|
|
286
279
|
"--plugin-root",
|
|
287
280
|
str(plugin_root),
|
|
288
|
-
"--response-script",
|
|
289
|
-
str(script),
|
|
290
281
|
"--no-artifacts",
|
|
291
282
|
"--json",
|
|
292
283
|
]
|