vis-agent 0.2.41.dev7619__tar.gz → 0.2.42__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.
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/.gitignore +0 -3
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/PKG-INFO +3 -1
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/evals.json +9 -3
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/pyproject.toml +3 -1
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/evals/__init__.py +14 -9
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/evals/_gateway.py +9 -2
- vis_agent-0.2.42/src/blockether/vis/evals/agent.py +341 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/evals/bench.py +7 -6
- vis_agent-0.2.42/src/blockether/vis/evals/check.py +211 -0
- vis_agent-0.2.42/src/blockether/vis/evals/environment.py +253 -0
- vis_agent-0.2.42/src/blockether/vis/evals/grade.py +1551 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/evals/harbor.py +68 -0
- vis_agent-0.2.42/src/blockether/vis/evals/judge-rubric.toml +22 -0
- vis_agent-0.2.42/src/blockether/vis/evals/judge.py +216 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/evals/leaderboard.py +5 -2
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/evals/rounds.py +47 -24
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/conftest.py +10 -0
- vis_agent-0.2.42/tests/evals_atif.py +157 -0
- vis_agent-0.2.42/tests/test_evals_agent.py +448 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_evals_bench.py +7 -2
- vis_agent-0.2.42/tests/test_evals_check.py +113 -0
- vis_agent-0.2.42/tests/test_evals_environment.py +225 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_evals_gateway.py +4 -1
- vis_agent-0.2.42/tests/test_evals_grade.py +368 -0
- vis_agent-0.2.42/tests/test_evals_guards.py +1611 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_evals_harbor.py +27 -0
- vis_agent-0.2.42/tests/test_evals_judge.py +145 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_evals_rounds.py +61 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_sdk_typing.py +14 -5
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/LICENSE +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/README.md +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/fixtures/activity-arguments.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/fixtures/activity-cases.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/fixtures/activity-copy.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/fixtures/activity-execution.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/fixtures/activity-groups.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/fixtures/activity-read-session.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/fixtures/activity-reads.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/fixtures/activity-results.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/fixtures/activity-tables.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/fixtures/activity.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/fixtures/diff.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/fixtures/live-primitives.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/fixtures/session-health.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/fixtures/view.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/activity.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/agents.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/automations.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/common.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/config.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/content.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/council.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/diff.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/gateway.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/improve.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/provider.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/rooms.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/symbol.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/toggle.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/contracts/schema/view.json +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/examples/greeter/tests/test_greeter.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/hatch_build.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/__init__.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/_contracts.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/_outside.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/_wire.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/activity.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/engine/__init__.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/engine/_agent.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/engine/_agents.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/engine/_client.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/engine/_council.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/engine/_extensions.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/engine/_local.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/evals/compare.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/evals/labels.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/evals/repeats.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/evals/stages.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/evals/stats.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/evals/trajectory.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/extension.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/extension_package.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/py.typed +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/rooms.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/src/blockether/vis/views.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_activity.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_agent.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_agent_extension_lifecycle.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_agent_extensions.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_agents.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_api_guides.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_attachments.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_authoring.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_catalog.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_catalog_recipe.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_ci_report_recipe.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_client.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_client_extensions_engine.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_code_quality_recipe.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_contracts.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_council.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_declarations.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_engine.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_engine_docstrings.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_evals_compare.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_evals_labels.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_evals_repeats.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_evals_stages.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_evals_stats.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_evals_trajectory.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_extension_package.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_extension_reference.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_gateway_performance.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_goals.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_live_primitives.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_live_wait.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_local.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_method_typing.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_monitor_recipe.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_namespace.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_outside.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_providers.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_read_session.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_record_arguments.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_registration.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_repair_hooks.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_result_sequence.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_rooms.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_scoped_settings.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_sdk_bootstrap.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_sdk_guide.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_structured_live.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_symbol_contract.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_testing.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_text_list_arguments.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_tools.py +0 -0
- {vis_agent-0.2.41.dev7619 → vis_agent-0.2.42}/tests/test_views.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vis-agent
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.42
|
|
4
4
|
Summary: The Vis Python SDK — author extensions and control local or remote engines.
|
|
5
5
|
Project-URL: Homepage, https://vis.blockether.com
|
|
6
6
|
Project-URL: Documentation, https://vis.blockether.com
|
|
@@ -22,6 +22,8 @@ Requires-Dist: pydantic<3,>=2.13.5
|
|
|
22
22
|
Requires-Dist: referencing>=0.28.4
|
|
23
23
|
Provides-Extra: docs
|
|
24
24
|
Requires-Dist: pdoc==16.0.0; extra == 'docs'
|
|
25
|
+
Provides-Extra: harbor
|
|
26
|
+
Requires-Dist: harbor==0.24.0; (python_version >= '3.12') and extra == 'harbor'
|
|
25
27
|
Description-Content-Type: text/markdown
|
|
26
28
|
|
|
27
29
|
# vis-agent
|
|
@@ -211,10 +211,12 @@
|
|
|
211
211
|
"type": "object",
|
|
212
212
|
"properties": {
|
|
213
213
|
"package": {
|
|
214
|
-
"type": "string"
|
|
214
|
+
"type": "string",
|
|
215
|
+
"description": "The Harbor Hub package of the board, for example terminal-bench/terminal-bench."
|
|
215
216
|
},
|
|
216
217
|
"name": {
|
|
217
|
-
"type": "string"
|
|
218
|
+
"type": "string",
|
|
219
|
+
"description": "The board name in the package, for example 4-0-0."
|
|
218
220
|
},
|
|
219
221
|
"summary": {
|
|
220
222
|
"type": "object",
|
|
@@ -227,10 +229,14 @@
|
|
|
227
229
|
]
|
|
228
230
|
}
|
|
229
231
|
},
|
|
232
|
+
"required": [
|
|
233
|
+
"package",
|
|
234
|
+
"name"
|
|
235
|
+
],
|
|
230
236
|
"additionalProperties": false
|
|
231
237
|
},
|
|
232
238
|
"bench_report": {
|
|
233
|
-
"description": "The score of one
|
|
239
|
+
"description": "The score of one benchmark round. Rates are in percent. Intervals are 95% intervals.",
|
|
234
240
|
"type": "object",
|
|
235
241
|
"properties": {
|
|
236
242
|
"dataset": {
|
|
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
|
|
|
8
8
|
|
|
9
9
|
[project]
|
|
10
10
|
name = "vis-agent"
|
|
11
|
-
version = "0.2.
|
|
11
|
+
version = "0.2.42"
|
|
12
12
|
description = "The Vis Python SDK — author extensions and control local or remote engines."
|
|
13
13
|
readme = "README.md"
|
|
14
14
|
requires-python = ">=3.11"
|
|
@@ -27,6 +27,8 @@ classifiers = [
|
|
|
27
27
|
|
|
28
28
|
[project.optional-dependencies]
|
|
29
29
|
docs = ["pdoc==16.0.0"]
|
|
30
|
+
# The Harbor agent, host environment, check and judge of `blockether.vis.evals`.
|
|
31
|
+
harbor = ["harbor==0.24.0; python_version >= '3.12'"]
|
|
30
32
|
|
|
31
33
|
[project.urls]
|
|
32
34
|
Homepage = "https://vis.blockether.com"
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
"""Evaluation tools: read Vis runs and Harbor jobs, and compare their results.
|
|
2
2
|
|
|
3
|
-
Read one Vis run with `load_trajectory` or `parse_trajectory`. Read the
|
|
4
|
-
Harbor
|
|
5
|
-
`summarize`. Seal a finished round with
|
|
6
|
-
`compare_cases`, or test paired changes with
|
|
7
|
-
Score repeated attempts with `task_attempts` and
|
|
8
|
-
|
|
9
|
-
diagnose it with `bench_report` and `diagnose
|
|
10
|
-
|
|
11
|
-
causes against human labels with `agreement`, and judge stages with
|
|
3
|
+
Read one Vis run with `load_trajectory` or `parse_trajectory`. Read the cases of a
|
|
4
|
+
Harbor dataset with `read_tasks`, and the trials of a job or round with `read_trials`.
|
|
5
|
+
Count the trials with `case_results` or `summarize`. Seal a finished round with
|
|
6
|
+
`seal_round`. Compare two results with `compare_cases`, or test paired changes with
|
|
7
|
+
`paired_change` and `mcnemar_exact`. Score repeated attempts with `task_attempts` and
|
|
8
|
+
`repeat_stats`. Summarize a round from its Harbor job folders with `round_summary`,
|
|
9
|
+
then score and diagnose it with `bench_report` and `diagnose`. Keep human cause labels
|
|
10
|
+
with `save_label`. Place a round on a Harbor Hub board with `read_leaderboard`. Check
|
|
11
|
+
automatic causes against human labels with `agreement`, and judge stages with
|
|
12
|
+
`stage_agreement`. For installation and sessions, use the
|
|
12
13
|
[Python SDK guide](https://vis.blockether.com/python-sdk.html).
|
|
13
14
|
"""
|
|
14
15
|
|
|
@@ -38,9 +39,11 @@ from .bench import (
|
|
|
38
39
|
)
|
|
39
40
|
from .compare import CaseChange, CaseComparison, RouteTest, compare_cases
|
|
40
41
|
from .harbor import (
|
|
42
|
+
HarborTask,
|
|
41
43
|
HarborTrial,
|
|
42
44
|
RunSummary,
|
|
43
45
|
case_results,
|
|
46
|
+
read_tasks,
|
|
44
47
|
read_trial,
|
|
45
48
|
read_trials,
|
|
46
49
|
seal_digest,
|
|
@@ -117,6 +120,7 @@ __all__ = [
|
|
|
117
120
|
"Diagnosis",
|
|
118
121
|
"Finding",
|
|
119
122
|
"FunctionCall",
|
|
123
|
+
"HarborTask",
|
|
120
124
|
"HarborTrial",
|
|
121
125
|
"Leaderboard",
|
|
122
126
|
"LeaderboardRow",
|
|
@@ -162,6 +166,7 @@ __all__ = [
|
|
|
162
166
|
"paired_change",
|
|
163
167
|
"parse_trajectory",
|
|
164
168
|
"read_leaderboard",
|
|
169
|
+
"read_tasks",
|
|
165
170
|
"read_trial",
|
|
166
171
|
"read_trials",
|
|
167
172
|
"repeat_stats",
|
|
@@ -61,6 +61,13 @@ def _word(body: Mapping, key: str, default: str) -> str:
|
|
|
61
61
|
return default if value is None else value
|
|
62
62
|
|
|
63
63
|
|
|
64
|
+
def _required_text(body: Mapping, key: str) -> str:
|
|
65
|
+
value = _text(body, key)
|
|
66
|
+
if not value:
|
|
67
|
+
raise ValueError(f"`{key}` is required.")
|
|
68
|
+
return value
|
|
69
|
+
|
|
70
|
+
|
|
64
71
|
def _cases(body: Mapping, key: str) -> dict[tuple[str, ...], tuple[int, int]]:
|
|
65
72
|
rows = body.get(key)
|
|
66
73
|
if not isinstance(rows, list):
|
|
@@ -133,8 +140,8 @@ def _agreement(body: Mapping):
|
|
|
133
140
|
def _leaderboard(body: Mapping):
|
|
134
141
|
summary = body.get("summary")
|
|
135
142
|
return read_leaderboard(
|
|
136
|
-
|
|
137
|
-
|
|
143
|
+
_required_text(body, "package"),
|
|
144
|
+
_required_text(body, "name"),
|
|
138
145
|
None if summary is None else _mapping(body, "summary"),
|
|
139
146
|
_text(body, "round_name"),
|
|
140
147
|
)
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"""Run the Vis CLI as a Harbor agent on the host.
|
|
2
|
+
|
|
3
|
+
Select it with the Harbor agent import path ``blockether.vis.evals.agent:VisAgent``.
|
|
4
|
+
It runs only in ``blockether.vis.evals.environment:HostEnvironment``. Install the
|
|
5
|
+
``harbor`` extra of the SDK first. Each trial starts one CLI process with its own
|
|
6
|
+
database. With ``--persist`` the
|
|
7
|
+
CLI runs the gateway in the same process, so no shared gateway and no operator
|
|
8
|
+
extension changes the measurement. The agent writes these files to its logs
|
|
9
|
+
directory:
|
|
10
|
+
|
|
11
|
+
- ``trace.jsonl``: the ``--full-trace-json-stream`` output.
|
|
12
|
+
- ``stderr.txt``: the error output of the CLI.
|
|
13
|
+
- ``trajectory.json``: the ATIF trajectory of the session.
|
|
14
|
+
- ``run.json``: the provider, the model, the exit code and the wall time.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import functools
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import shlex
|
|
24
|
+
import shutil
|
|
25
|
+
import subprocess
|
|
26
|
+
import tempfile
|
|
27
|
+
import time
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
from harbor.agents.base import BaseAgent
|
|
31
|
+
from harbor.agents.capabilities import AgentCapabilities
|
|
32
|
+
from harbor.agents.options import AgentOptions
|
|
33
|
+
from harbor.environments.base import BaseEnvironment
|
|
34
|
+
from harbor.models.agent.context import AgentContext
|
|
35
|
+
from pydantic import Field
|
|
36
|
+
|
|
37
|
+
from .environment import HostEnvironment
|
|
38
|
+
from .grade import RUN_FILE, STDERR_FILE, TRACE_FILE, TRAJECTORY_FILE
|
|
39
|
+
from .trajectory import load_trajectory as load_vis_trajectory
|
|
40
|
+
|
|
41
|
+
DEFAULT_COMMAND = "vis-agent"
|
|
42
|
+
CLASSPATH_TIMEOUT_SEC = 120
|
|
43
|
+
_NATIVE_MAGIC = (
|
|
44
|
+
b"\x7fELF",
|
|
45
|
+
b"\xcf\xfa\xed\xfe",
|
|
46
|
+
b"\xfe\xed\xfa\xcf",
|
|
47
|
+
b"\xca\xfe\xba\xbe",
|
|
48
|
+
)
|
|
49
|
+
_SOURCE_ENGINE = (
|
|
50
|
+
'cd "$VIS_HARBOR_SOURCE" && "$VIS_HARBOR_ENGINE" -Scp "$VIS_HARBOR_CLASSPATH" '
|
|
51
|
+
'-J-Duser.dir="$VIS_HARBOR_WORKSPACE" -M:vis'
|
|
52
|
+
)
|
|
53
|
+
_NATIVE_ENGINE = '"$VIS_HARBOR_ENGINE" -Duser.dir="$VIS_HARBOR_WORKSPACE"'
|
|
54
|
+
_INSTALLED_ENGINE = '"$VIS_HARBOR_ENGINE"'
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class VisOptions(AgentOptions):
|
|
58
|
+
"""The options that ``--ak key=value`` sets."""
|
|
59
|
+
|
|
60
|
+
provider: str | None = Field(
|
|
61
|
+
default=None, description="Vis provider. Wins over the provider in --model."
|
|
62
|
+
)
|
|
63
|
+
model: str | None = Field(
|
|
64
|
+
default=None, description="Vis model. Wins over the model in --model."
|
|
65
|
+
)
|
|
66
|
+
reasoning_effort: str | None = Field(
|
|
67
|
+
default=None, description="Value of the CLI option --reasoning-effort."
|
|
68
|
+
)
|
|
69
|
+
source: str | None = Field(
|
|
70
|
+
default=None, description="Vis checkout to run from source with Clojure."
|
|
71
|
+
)
|
|
72
|
+
clojure: str | None = Field(default=None, description="Clojure CLI executable.")
|
|
73
|
+
native_bin: str | None = Field(
|
|
74
|
+
default=None, description="Raw native Vis executable to run."
|
|
75
|
+
)
|
|
76
|
+
command: str | None = Field(
|
|
77
|
+
default=None, description="Shell prefix that replaces the engine, for tests."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@functools.cache
|
|
82
|
+
def source_classpath(clojure: str, source: Path) -> str:
|
|
83
|
+
"""Resolve the :vis classpath once, with absolute entries for any user.dir."""
|
|
84
|
+
classpath = subprocess.check_output(
|
|
85
|
+
[clojure, "-Spath", "-A:vis"],
|
|
86
|
+
cwd=source,
|
|
87
|
+
text=True,
|
|
88
|
+
timeout=CLASSPATH_TIMEOUT_SEC,
|
|
89
|
+
).strip()
|
|
90
|
+
return os.pathsep.join(
|
|
91
|
+
entry if os.path.isabs(entry) else str(source / entry)
|
|
92
|
+
for entry in classpath.split(os.pathsep)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@functools.cache
|
|
97
|
+
def source_commit(source: Path) -> str | None:
|
|
98
|
+
"""Return the commit of the checkout ``source``, or None when git cannot tell."""
|
|
99
|
+
try:
|
|
100
|
+
completed = subprocess.run(
|
|
101
|
+
["git", "rev-parse", "--short=12", "HEAD"],
|
|
102
|
+
cwd=source,
|
|
103
|
+
capture_output=True,
|
|
104
|
+
text=True,
|
|
105
|
+
timeout=30,
|
|
106
|
+
check=True,
|
|
107
|
+
)
|
|
108
|
+
except (OSError, subprocess.SubprocessError):
|
|
109
|
+
return None
|
|
110
|
+
return completed.stdout.strip() or None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class ProviderUnavailableError(RuntimeError):
|
|
114
|
+
"""The provider refused the turn, so the trial measures nothing about Vis."""
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def provider_failure(trace: Path) -> str | None:
|
|
118
|
+
"""Return the provider error that ended the turn in ``trace``, or None.
|
|
119
|
+
|
|
120
|
+
A provider quota or outage ends the turn with an error block whose code
|
|
121
|
+
starts with ``provider_``. Such a trial is an infrastructure error, not a
|
|
122
|
+
wrong answer.
|
|
123
|
+
"""
|
|
124
|
+
try:
|
|
125
|
+
lines = trace.read_text(encoding="utf-8").splitlines()
|
|
126
|
+
except OSError:
|
|
127
|
+
return None
|
|
128
|
+
for line in reversed(lines):
|
|
129
|
+
try:
|
|
130
|
+
frame = json.loads(line)
|
|
131
|
+
except ValueError:
|
|
132
|
+
continue
|
|
133
|
+
if not isinstance(frame, dict) or frame.get("event") != "result":
|
|
134
|
+
continue
|
|
135
|
+
payload = frame.get("payload")
|
|
136
|
+
content = payload.get("content") if isinstance(payload, dict) else None
|
|
137
|
+
for block in content if isinstance(content, list) else ():
|
|
138
|
+
if not isinstance(block, dict) or block.get("type") != "error":
|
|
139
|
+
continue
|
|
140
|
+
code = str(block.get("code"))
|
|
141
|
+
if code.startswith("provider_"):
|
|
142
|
+
return f"{code}: {block.get('title') or 'provider error'}"
|
|
143
|
+
return None
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def check_native_binary(path: str) -> None:
|
|
148
|
+
"""Refuse a launcher script: the measurement must use the native image."""
|
|
149
|
+
with open(path, "rb") as executable:
|
|
150
|
+
magic = executable.read(4)
|
|
151
|
+
if magic not in _NATIVE_MAGIC:
|
|
152
|
+
raise ValueError("native_bin must be a raw native executable, not a launcher")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class VisAgent(BaseAgent):
|
|
156
|
+
"""Harbor agent that runs one Vis CLI session in the host workspace.
|
|
157
|
+
|
|
158
|
+
The provider and the model come from the first source that sets them:
|
|
159
|
+
|
|
160
|
+
1. The ``provider`` and ``model`` options (``--ak``).
|
|
161
|
+
2. ``--model provider/model``.
|
|
162
|
+
3. ``VIS_PROVIDER`` and ``VIS_MODEL`` in ``--ae`` or in the host environment.
|
|
163
|
+
|
|
164
|
+
The engine comes from the first option that is set: ``command``, ``native_bin``
|
|
165
|
+
(``VIS_HARBOR_NATIVE_BIN``), ``source`` (``VIS_HARBOR_SOURCE``). Without them, the
|
|
166
|
+
agent runs the installed ``vis-agent`` CLI.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
capabilities = AgentCapabilities(atif=True)
|
|
170
|
+
options_model = VisOptions
|
|
171
|
+
|
|
172
|
+
def __init__(self, logs_dir: Path, model_name: str | None = None, **kwargs) -> None:
|
|
173
|
+
super().__init__(logs_dir=logs_dir, model_name=model_name, **kwargs)
|
|
174
|
+
options = self.options
|
|
175
|
+
if not isinstance(options, VisOptions):
|
|
176
|
+
raise TypeError("VisAgent needs VisOptions")
|
|
177
|
+
provider = (
|
|
178
|
+
options.provider
|
|
179
|
+
or self._parsed_model_provider
|
|
180
|
+
or self._setting("VIS_PROVIDER")
|
|
181
|
+
)
|
|
182
|
+
model = options.model or self._parsed_model_name or self._setting("VIS_MODEL")
|
|
183
|
+
if not provider or not model:
|
|
184
|
+
raise ValueError(
|
|
185
|
+
"VisAgent needs a provider and a model: use --model provider/model"
|
|
186
|
+
)
|
|
187
|
+
self.provider, self.model = provider, model
|
|
188
|
+
self.reasoning_effort = options.reasoning_effort or self._setting(
|
|
189
|
+
"VIS_REASONING_EFFORT"
|
|
190
|
+
)
|
|
191
|
+
self.clojure = (
|
|
192
|
+
options.clojure or self._setting("VIS_HARBOR_CLOJURE") or "clojure"
|
|
193
|
+
)
|
|
194
|
+
native_bin = options.native_bin or self._setting("VIS_HARBOR_NATIVE_BIN")
|
|
195
|
+
self.native_bin = os.path.abspath(native_bin) if native_bin else None
|
|
196
|
+
source = options.source or self._setting("VIS_HARBOR_SOURCE")
|
|
197
|
+
self.source = Path(source).resolve() if source else None
|
|
198
|
+
self.engine_override = options.command
|
|
199
|
+
self.classpath: str | None = None
|
|
200
|
+
self.model_name = f"{self.provider}/{self.model}"
|
|
201
|
+
self._init_model_info()
|
|
202
|
+
|
|
203
|
+
@staticmethod
|
|
204
|
+
def name() -> str:
|
|
205
|
+
return "vis"
|
|
206
|
+
|
|
207
|
+
def version(self) -> str | None:
|
|
208
|
+
if self.engine_override or self.native_bin or self.source is None:
|
|
209
|
+
return None
|
|
210
|
+
return source_commit(self.source)
|
|
211
|
+
|
|
212
|
+
def _setting(self, name: str) -> str | None:
|
|
213
|
+
value = self._extra_env.get(name, os.environ.get(name, ""))
|
|
214
|
+
return value.strip() or None
|
|
215
|
+
|
|
216
|
+
def vis_arguments(self) -> list[str]:
|
|
217
|
+
"""Return the CLI options before ``--trajectory`` and the prompt."""
|
|
218
|
+
arguments = [
|
|
219
|
+
"--full-trace-json-stream",
|
|
220
|
+
"--provider",
|
|
221
|
+
self.provider,
|
|
222
|
+
"--model",
|
|
223
|
+
self.model,
|
|
224
|
+
]
|
|
225
|
+
if self.reasoning_effort:
|
|
226
|
+
arguments += ["--reasoning-effort", self.reasoning_effort]
|
|
227
|
+
return [*arguments, "--persist"]
|
|
228
|
+
|
|
229
|
+
def shell_command(self) -> str:
|
|
230
|
+
"""Return the bash command of one trial.
|
|
231
|
+
|
|
232
|
+
Paths and the prompt stay in environment variables. Then the path
|
|
233
|
+
translation of the host environment cannot change them.
|
|
234
|
+
"""
|
|
235
|
+
if self.engine_override:
|
|
236
|
+
engine = self.engine_override
|
|
237
|
+
elif self.native_bin:
|
|
238
|
+
engine = _NATIVE_ENGINE
|
|
239
|
+
elif self.source is not None:
|
|
240
|
+
engine = _SOURCE_ENGINE
|
|
241
|
+
else:
|
|
242
|
+
engine = _INSTALLED_ENGINE
|
|
243
|
+
arguments = " ".join(shlex.quote(item) for item in self.vis_arguments())
|
|
244
|
+
return (
|
|
245
|
+
"unset VIS_GATEWAY_URL; "
|
|
246
|
+
f"{engine} {arguments} "
|
|
247
|
+
f'--trajectory "$VIS_HARBOR_AGENT_LOGS/{TRAJECTORY_FILE}" '
|
|
248
|
+
'"$VIS_HARBOR_PROMPT" '
|
|
249
|
+
f'> "$VIS_HARBOR_AGENT_LOGS/{TRACE_FILE}" '
|
|
250
|
+
f'2> "$VIS_HARBOR_AGENT_LOGS/{STDERR_FILE}"'
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def process_env(self, instruction: str, runtime: Path) -> dict[str, str]:
|
|
254
|
+
"""Return the variables of the CLI process, with a database for this trial."""
|
|
255
|
+
env = dict(self._extra_env)
|
|
256
|
+
env["VIS_DB_PATH"] = str(runtime / "vis.mdb")
|
|
257
|
+
env["VIS_EXTENSIONS_DIR"] = self._setting("VIS_EXTENSIONS_DIR") or str(
|
|
258
|
+
runtime / "extensions"
|
|
259
|
+
)
|
|
260
|
+
env["VIS_HARBOR_PROMPT"] = instruction
|
|
261
|
+
if self.native_bin:
|
|
262
|
+
env["VIS_HARBOR_ENGINE"] = self.native_bin
|
|
263
|
+
elif self.source is not None:
|
|
264
|
+
env["VIS_HARBOR_ENGINE"] = self.clojure
|
|
265
|
+
env["VIS_HARBOR_SOURCE"] = str(self.source)
|
|
266
|
+
else:
|
|
267
|
+
env["VIS_HARBOR_ENGINE"] = DEFAULT_COMMAND
|
|
268
|
+
if self.classpath is not None:
|
|
269
|
+
env["VIS_HARBOR_CLASSPATH"] = self.classpath
|
|
270
|
+
return env
|
|
271
|
+
|
|
272
|
+
async def setup(self, environment: BaseEnvironment) -> None:
|
|
273
|
+
if not isinstance(environment, HostEnvironment):
|
|
274
|
+
raise TypeError(
|
|
275
|
+
"VisAgent runs only in blockether.vis.evals.environment:HostEnvironment"
|
|
276
|
+
)
|
|
277
|
+
if self.engine_override:
|
|
278
|
+
return
|
|
279
|
+
if self.native_bin:
|
|
280
|
+
check_native_binary(self.native_bin)
|
|
281
|
+
return
|
|
282
|
+
if self.source is not None:
|
|
283
|
+
self.classpath = await asyncio.to_thread(
|
|
284
|
+
source_classpath, self.clojure, self.source
|
|
285
|
+
)
|
|
286
|
+
return
|
|
287
|
+
if shutil.which(DEFAULT_COMMAND) is None:
|
|
288
|
+
raise FileNotFoundError(
|
|
289
|
+
f"{DEFAULT_COMMAND} is not on PATH: install Vis, or set source or native_bin"
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
async def run(
|
|
293
|
+
self, instruction: str, environment: BaseEnvironment, context: AgentContext
|
|
294
|
+
) -> None:
|
|
295
|
+
runtime = Path(tempfile.mkdtemp(prefix="vis_harbor_runtime_"))
|
|
296
|
+
record = {
|
|
297
|
+
"provider": self.provider,
|
|
298
|
+
"model": self.model,
|
|
299
|
+
"reasoning_effort": self.reasoning_effort,
|
|
300
|
+
"exit_code": None,
|
|
301
|
+
"wall_s": None,
|
|
302
|
+
"is_cancelled": False,
|
|
303
|
+
}
|
|
304
|
+
started = time.monotonic()
|
|
305
|
+
try:
|
|
306
|
+
result = await environment.exec(
|
|
307
|
+
self.shell_command(), env=self.process_env(instruction, runtime)
|
|
308
|
+
)
|
|
309
|
+
record["exit_code"] = result.return_code
|
|
310
|
+
except asyncio.CancelledError:
|
|
311
|
+
record["is_cancelled"] = True
|
|
312
|
+
raise
|
|
313
|
+
finally:
|
|
314
|
+
record["wall_s"] = round(time.monotonic() - started, 3)
|
|
315
|
+
self.logs_dir.mkdir(parents=True, exist_ok=True)
|
|
316
|
+
(self.logs_dir / RUN_FILE).write_text(json.dumps(record, indent=2) + "\n")
|
|
317
|
+
shutil.rmtree(runtime, ignore_errors=True)
|
|
318
|
+
self.populate_context_post_run(context)
|
|
319
|
+
if failure := provider_failure(self.logs_dir / TRACE_FILE):
|
|
320
|
+
raise ProviderUnavailableError(failure)
|
|
321
|
+
|
|
322
|
+
def populate_context_post_run(self, context: AgentContext) -> None:
|
|
323
|
+
"""Copy the token and cost totals of the trajectory into ``context``."""
|
|
324
|
+
path = self.logs_dir / TRAJECTORY_FILE
|
|
325
|
+
if not path.is_file():
|
|
326
|
+
return
|
|
327
|
+
# Harbor calls this hook outside the error handling of a trial. An exception
|
|
328
|
+
# here stops the whole job, so a trajectory that cannot be read only loses
|
|
329
|
+
# its totals.
|
|
330
|
+
try:
|
|
331
|
+
trajectory = load_vis_trajectory(path, strict=False)
|
|
332
|
+
except Exception as error:
|
|
333
|
+
self.logger.warning("Cannot read %s: %s", path, error)
|
|
334
|
+
return
|
|
335
|
+
metrics = trajectory.final_metrics
|
|
336
|
+
if metrics is None:
|
|
337
|
+
return
|
|
338
|
+
context.n_input_tokens = metrics.total_prompt_tokens
|
|
339
|
+
context.n_cache_tokens = metrics.total_cached_tokens
|
|
340
|
+
context.n_output_tokens = metrics.total_completion_tokens
|
|
341
|
+
context.cost_usd = metrics.total_cost_usd
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Score a
|
|
1
|
+
"""Score a Harbor benchmark round and find where it loses tasks.
|
|
2
2
|
|
|
3
3
|
`round_summary` reads a round summary from Harbor job folders: the trials, the task
|
|
4
4
|
outcomes and the usage of the round. Give that summary to `bench_report` for
|
|
@@ -78,7 +78,7 @@ class OutcomeCount:
|
|
|
78
78
|
|
|
79
79
|
@dataclass(frozen=True)
|
|
80
80
|
class BenchReport:
|
|
81
|
-
"""The
|
|
81
|
+
"""The benchmark score of one round, with its usage.
|
|
82
82
|
|
|
83
83
|
`pass_rate_percent` divides solved tasks by scored tasks, by the latest scored
|
|
84
84
|
attempt of each task. `strict_pass_rate_percent` divides them by all dataset tasks,
|
|
@@ -359,7 +359,7 @@ def attempt_cause(trial: Mapping, summary: Mapping, limits: Mapping[str, float])
|
|
|
359
359
|
"""Give the `diagnose` cause of one attempt, or `solved`.
|
|
360
360
|
|
|
361
361
|
The causes are `dead_tools`, `agent_errors`, `agent_timeouts`, `unscored`,
|
|
362
|
-
`early_stops` and `other`.
|
|
362
|
+
`early_stops` and `other`. Empty `limits` use the `agent_limits` of the summary.
|
|
363
363
|
"""
|
|
364
364
|
outcome = attempt_outcome(trial)
|
|
365
365
|
if outcome == "solved":
|
|
@@ -373,7 +373,7 @@ def attempt_cause(trial: Mapping, summary: Mapping, limits: Mapping[str, float])
|
|
|
373
373
|
if outcome == "unscored":
|
|
374
374
|
return "unscored"
|
|
375
375
|
seconds = trial.get("agent_seconds")
|
|
376
|
-
limit = limits.get(_short(trial.get("task")))
|
|
376
|
+
limit = (limits or summary.get("agent_limits") or {}).get(_short(trial.get("task")))
|
|
377
377
|
if seconds and limit and seconds / limit <= EARLY_STOP_SHARE:
|
|
378
378
|
return "early_stops"
|
|
379
379
|
return "other"
|
|
@@ -430,7 +430,8 @@ def diagnose(
|
|
|
430
430
|
"""Find where a round loses tasks, from its `round_summary` data.
|
|
431
431
|
|
|
432
432
|
`limits` gives the agent time limit of each task, from `agent_limits`. Without
|
|
433
|
-
limits,
|
|
433
|
+
limits, it uses the `agent_limits` of the summary, and without those no attempt
|
|
434
|
+
counts as an early stop. `labels` maps a trial name to a human
|
|
434
435
|
label with a `cause` value.
|
|
435
436
|
"""
|
|
436
437
|
outcomes = {
|
|
@@ -440,7 +441,7 @@ def diagnose(
|
|
|
440
441
|
scored = int(summary.get("scored_tasks") or 0)
|
|
441
442
|
solved = len(outcomes.get("solved", ()))
|
|
442
443
|
latest = latest_trials(summary)
|
|
443
|
-
limits = limits or {}
|
|
444
|
+
limits = limits or summary.get("agent_limits") or {}
|
|
444
445
|
|
|
445
446
|
def notes(outcome: str) -> list[TaskNote]:
|
|
446
447
|
return [
|