flyai-bench 0.1.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.
Files changed (35) hide show
  1. flyai_bench-0.1.0/.github/workflows/validate.yml +53 -0
  2. flyai_bench-0.1.0/.gitignore +6 -0
  3. flyai_bench-0.1.0/LICENSE +21 -0
  4. flyai_bench-0.1.0/PKG-INFO +200 -0
  5. flyai_bench-0.1.0/README.md +185 -0
  6. flyai_bench-0.1.0/agent.py +162 -0
  7. flyai_bench-0.1.0/benchmark.yaml +49 -0
  8. flyai_bench-0.1.0/eval_config.yaml +35 -0
  9. flyai_bench-0.1.0/experiments/README.md +93 -0
  10. flyai_bench-0.1.0/experiments/evaluation/e_commerce/.gitkeep +0 -0
  11. flyai_bench-0.1.0/experiments/evaluation/travel/.gitkeep +0 -0
  12. flyai_bench-0.1.0/experiments/evaluation/travel/20260831_deepseek-v4-flash_mini-swe-agent/metadata.yaml +22 -0
  13. flyai_bench-0.1.0/experiments/evaluation/travel/20260831_deepseek-v4-flash_mini-swe-agent/scores.jsonl +6 -0
  14. flyai_bench-0.1.0/experiments/evaluation/travel/20260831_deepseek-v4-flash_mini-swe-agent/summary.json +23 -0
  15. flyai_bench-0.1.0/experiments/leaderboard.json +1 -0
  16. flyai_bench-0.1.0/leaderboard_space/README.md +14 -0
  17. flyai_bench-0.1.0/leaderboard_space/app.py +118 -0
  18. flyai_bench-0.1.0/leaderboard_space/index.html +319 -0
  19. flyai_bench-0.1.0/leaderboard_space/leaderboard.json +1405 -0
  20. flyai_bench-0.1.0/leaderboard_space/requirements.txt +2 -0
  21. flyai_bench-0.1.0/mini_swe_agent.py +193 -0
  22. flyai_bench-0.1.0/pyproject.toml +28 -0
  23. flyai_bench-0.1.0/run_eval.py +634 -0
  24. flyai_bench-0.1.0/sandbox_setup.sh +113 -0
  25. flyai_bench-0.1.0/src/flyai_bench/__init__.py +2 -0
  26. flyai_bench-0.1.0/src/flyai_bench/agent.py +162 -0
  27. flyai_bench-0.1.0/src/flyai_bench/benchmark.yaml +49 -0
  28. flyai_bench-0.1.0/src/flyai_bench/cli.py +707 -0
  29. flyai_bench-0.1.0/src/flyai_bench/eval_config.yaml +35 -0
  30. flyai_bench-0.1.0/src/flyai_bench/mini_swe_agent.py +193 -0
  31. flyai_bench-0.1.0/src/flyai_bench/sandbox_setup.sh +113 -0
  32. flyai_bench-0.1.0/src/flyai_bench/tool_server.py +86 -0
  33. flyai_bench-0.1.0/src/flyai_bench/validate.py +374 -0
  34. flyai_bench-0.1.0/tool_server.py +86 -0
  35. flyai_bench-0.1.0/validate_submission.py +374 -0
@@ -0,0 +1,53 @@
1
+ name: Validate Submission
2
+
3
+ on:
4
+ pull_request:
5
+ paths:
6
+ - 'experiments/evaluation/**'
7
+
8
+ jobs:
9
+ validate:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ with:
14
+ fetch-depth: 0
15
+
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: '3.12'
19
+
20
+ - name: Install dependencies
21
+ run: pip install pyyaml
22
+
23
+ - name: Validate new submissions
24
+ run: python validate_submission.py check-pr
25
+
26
+ update-leaderboard:
27
+ runs-on: ubuntu-latest
28
+ if: github.event.pull_request.merged == true
29
+ needs: validate
30
+ permissions:
31
+ contents: write
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ with:
35
+ ref: main
36
+
37
+ - uses: actions/setup-python@v5
38
+ with:
39
+ python-version: '3.12'
40
+
41
+ - name: Install dependencies
42
+ run: pip install pyyaml
43
+
44
+ - name: Rebuild leaderboard
45
+ run: python validate_submission.py rebuild-leaderboard
46
+
47
+ - name: Commit leaderboard update
48
+ run: |
49
+ git config user.name "github-actions[bot]"
50
+ git config user.email "github-actions[bot]@users.noreply.github.com"
51
+ git add experiments/leaderboard.json experiments/README.md
52
+ git diff --cached --quiet || git commit -m "Update leaderboard"
53
+ git push
@@ -0,0 +1,6 @@
1
+ *.local.yaml
2
+ dist/
3
+ __pycache__/
4
+ .venv/
5
+ e2e_test_results/
6
+ *.egg-info/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FlyaiLab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,200 @@
1
+ Metadata-Version: 2.5
2
+ Name: flyai-bench
3
+ Version: 0.1.0
4
+ Summary: E-Commerce Last Exam: Agent benchmark for travel and e-commerce tool-use tasks
5
+ Author-email: FlyaiLab <flyaimainsite@gmail.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: huggingface-hub>=0.20
10
+ Requires-Dist: openai>=1.0
11
+ Requires-Dist: pyyaml>=6.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest; extra == 'dev'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # flyai-bench
17
+
18
+ ![Python](https://img.shields.io/badge/python-%3E%3D3.10-blue)
19
+ ![License](https://img.shields.io/badge/license-MIT-green)
20
+
21
+ An evaluation framework for LLM agents. It loads evaluation tasks from a
22
+ HuggingFace dataset, runs an agent plus verifier inside isolated Docker
23
+ containers, and produces standardized scores (reward in the range `0.00 – 1.00`).
24
+
25
+ ## Prerequisites
26
+
27
+ - Python >= 3.10
28
+ - Docker (with access to the image registry that hosts the benchmark images;
29
+ the target platform is `linux/amd64`)
30
+ - An OpenAI-compatible LLM endpoint for the agent, and one for the judge/verifier
31
+ (they can be the same endpoint)
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ # From a clone of this repository
37
+ pip install -e .
38
+
39
+ # Or install the runtime dependencies directly
40
+ pip install pyyaml huggingface_hub openai
41
+ ```
42
+
43
+ Installing the package exposes a `flyai-bench` console command. The examples
44
+ below also work by invoking `run_eval.py` directly.
45
+
46
+ ## Quick start
47
+
48
+ ```bash
49
+ # 1. Copy the default config and fill in your values
50
+ cp eval_config.yaml eval_config.local.yaml
51
+ # edit eval_config.local.yaml: set the image registry, the LLM/judge
52
+ # base URLs, API keys, and the model name
53
+
54
+ # 2. Run the evaluation
55
+ python run_eval.py run --config eval_config.local.yaml
56
+
57
+ # 3. Inspect results
58
+ python run_eval.py report --config eval_config.local.yaml
59
+ ```
60
+
61
+ ## Supported benchmarks
62
+
63
+ | Benchmark | Dataset | Tasks | Description |
64
+ |-----------|---------|-------|-------------|
65
+ | ecommerce_last_exam | [FlyaiLab/ecommerce_last_exam](https://huggingface.co/datasets/FlyaiLab/ecommerce_last_exam) | 1000 | Travel + e-commerce agent tool-use evaluation (500 travel, 500 e-commerce) |
66
+
67
+ ## Configuration
68
+
69
+ Key fields in `eval_config.yaml` (copy to `eval_config.local.yaml` before editing):
70
+
71
+ | Field | Meaning |
72
+ |-------|---------|
73
+ | `dataset.repo_id` / `dataset.config` / `dataset.split` | HuggingFace dataset, config (`travel` / `e_commerce`), and split |
74
+ | `docker.registry` | Image registry that hosts the benchmark images. Set to your own registry, or override at runtime with `--registry`. Leave empty to use image names as-is. |
75
+ | `docker.platform` | Container platform (default `linux/amd64`) |
76
+ | `agent.cmd` | Command that runs the agent inside the container |
77
+ | `agent.llm_base_url` / `agent.llm_api_key` / `agent.llm_model` | OpenAI-compatible endpoint, key, and model for the agent |
78
+ | `verifier.judge_base_url` / `judge_api_key` / `judge_model` | Endpoint, key, and model for the judge/verifier |
79
+ | `runner.concurrency` / `runner.limit` / `runner.skip_done` | Parallelism, cap on number of instances (`null` = all), and whether to skip instances that already have a reward |
80
+
81
+ > API keys are read from your local config and injected into the containers as
82
+ > environment variables. Never commit `eval_config.local.yaml` — it is already
83
+ > in `.gitignore`.
84
+
85
+ ## Evaluation flow
86
+
87
+ ```
88
+ HuggingFace Dataset
89
+
90
+
91
+ ┌─────────────────────────────────┐
92
+ │ For each instance: │
93
+ │ 1. docker pull <image> │
94
+ │ 2. docker run (start env) │
95
+ │ 3. agent calls tools -> answer │
96
+ │ 4. verifier (test.sh) scores │
97
+ │ 5. emit reward.txt (0 – 1) │
98
+ └─────────────────────────────────┘
99
+
100
+
101
+ scores.jsonl + summary.json
102
+ ```
103
+
104
+ ## CLI commands
105
+
106
+ ```bash
107
+ # Run the evaluation
108
+ python run_eval.py run [--dataset-config travel|e_commerce] [--limit N] [--dry-run]
109
+
110
+ # Check progress
111
+ python run_eval.py status
112
+
113
+ # Generate a report
114
+ python run_eval.py report
115
+
116
+ # Package results for leaderboard submission
117
+ python run_eval.py submit --model deepseek-v4-flash --provider deepseek
118
+ ```
119
+
120
+ ## Output
121
+
122
+ | File | Contents |
123
+ |------|----------|
124
+ | `scores.jsonl` | One line per instance: `{instance_id, domain, reward, duration_sec}` |
125
+ | `summary.json` | Aggregate statistics (`avg_reward`, breakdown `by_domain`) |
126
+
127
+ `reward` is a float in `0.00 – 1.00` produced by the verifier for each instance.
128
+
129
+ ## Submitting results to the leaderboard
130
+
131
+ 1. After evaluating, package the results with `submit`:
132
+
133
+ ```bash
134
+ python run_eval.py submit \
135
+ --dataset-config travel \
136
+ --model deepseek-v4-flash \
137
+ --provider deepseek \
138
+ --agent-type mini-swe-agent
139
+ ```
140
+
141
+ 2. Review `experiments/evaluation/travel/<slug>/metadata.yaml` and complete the
142
+ model and agent details.
143
+
144
+ 3. Open a pull request to the `main` branch of the flyai-bench repository.
145
+
146
+ 4. CI validates the format automatically; once merged, the leaderboard updates.
147
+
148
+ ### Submission package layout
149
+
150
+ ```
151
+ evaluation/travel/20260830_deepseek-v4-flash_mini-swe-agent/
152
+ ├── metadata.yaml # model/agent info + evaluation statistics
153
+ ├── scores.jsonl # per instance: {instance_id, domain, reward, duration_sec}
154
+ └── summary.json # aggregate statistics (avg_reward, by_domain)
155
+ ```
156
+
157
+ ## Writing a custom agent
158
+
159
+ Implement a script that, inside the container:
160
+
161
+ 1. Reads `/app/tool_defs.json` for the tool definitions
162
+ 2. Reads `/app/system.md` + `/app/instruction.md` for the task
163
+ 3. Calls `/app/tools/<name> --arg val` to execute a tool
164
+ 4. Writes the result to `/app/answer.json`
165
+
166
+ Point the runner at your agent with `--agent-cmd`:
167
+
168
+ ```bash
169
+ python run_eval.py run --agent-cmd "python /app/my_agent.py"
170
+ ```
171
+
172
+ `agent.py` (a minimal LLM agent) and `mini_swe_agent.py` (a terminal/bash agent)
173
+ are provided as reference implementations.
174
+
175
+ ## Project structure
176
+
177
+ ```
178
+ flyai-bench/
179
+ ├── README.md
180
+ ├── pyproject.toml # packaging (installs the `flyai-bench` CLI)
181
+ ├── run_eval.py # evaluation CLI (run / status / report / submit)
182
+ ├── eval_config.yaml # default config (copy to eval_config.local.yaml)
183
+ ├── benchmark.yaml # benchmark metadata
184
+ ├── agent.py # reference LLM agent
185
+ ├── mini_swe_agent.py # reference terminal agent
186
+ ├── tool_server.py # in-sandbox tool proxy (permission isolation)
187
+ ├── sandbox_setup.sh # container permission setup
188
+ ├── validate_submission.py # submission validation + leaderboard rebuild
189
+ ├── experiments/ # submitted results + leaderboard.json
190
+ ├── leaderboard_space/ # HuggingFace Space (leaderboard UI)
191
+ └── src/flyai_bench/ # installable package mirror of the above
192
+ ```
193
+
194
+ > Note: the top-level scripts and the `src/flyai_bench/` package currently hold
195
+ > parallel copies of the same code. Prefer editing one and keeping them in sync
196
+ > (or consolidating on the package) to avoid drift.
197
+
198
+ ## License
199
+
200
+ Released under the MIT license. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,185 @@
1
+ # flyai-bench
2
+
3
+ ![Python](https://img.shields.io/badge/python-%3E%3D3.10-blue)
4
+ ![License](https://img.shields.io/badge/license-MIT-green)
5
+
6
+ An evaluation framework for LLM agents. It loads evaluation tasks from a
7
+ HuggingFace dataset, runs an agent plus verifier inside isolated Docker
8
+ containers, and produces standardized scores (reward in the range `0.00 – 1.00`).
9
+
10
+ ## Prerequisites
11
+
12
+ - Python >= 3.10
13
+ - Docker (with access to the image registry that hosts the benchmark images;
14
+ the target platform is `linux/amd64`)
15
+ - An OpenAI-compatible LLM endpoint for the agent, and one for the judge/verifier
16
+ (they can be the same endpoint)
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ # From a clone of this repository
22
+ pip install -e .
23
+
24
+ # Or install the runtime dependencies directly
25
+ pip install pyyaml huggingface_hub openai
26
+ ```
27
+
28
+ Installing the package exposes a `flyai-bench` console command. The examples
29
+ below also work by invoking `run_eval.py` directly.
30
+
31
+ ## Quick start
32
+
33
+ ```bash
34
+ # 1. Copy the default config and fill in your values
35
+ cp eval_config.yaml eval_config.local.yaml
36
+ # edit eval_config.local.yaml: set the image registry, the LLM/judge
37
+ # base URLs, API keys, and the model name
38
+
39
+ # 2. Run the evaluation
40
+ python run_eval.py run --config eval_config.local.yaml
41
+
42
+ # 3. Inspect results
43
+ python run_eval.py report --config eval_config.local.yaml
44
+ ```
45
+
46
+ ## Supported benchmarks
47
+
48
+ | Benchmark | Dataset | Tasks | Description |
49
+ |-----------|---------|-------|-------------|
50
+ | ecommerce_last_exam | [FlyaiLab/ecommerce_last_exam](https://huggingface.co/datasets/FlyaiLab/ecommerce_last_exam) | 1000 | Travel + e-commerce agent tool-use evaluation (500 travel, 500 e-commerce) |
51
+
52
+ ## Configuration
53
+
54
+ Key fields in `eval_config.yaml` (copy to `eval_config.local.yaml` before editing):
55
+
56
+ | Field | Meaning |
57
+ |-------|---------|
58
+ | `dataset.repo_id` / `dataset.config` / `dataset.split` | HuggingFace dataset, config (`travel` / `e_commerce`), and split |
59
+ | `docker.registry` | Image registry that hosts the benchmark images. Set to your own registry, or override at runtime with `--registry`. Leave empty to use image names as-is. |
60
+ | `docker.platform` | Container platform (default `linux/amd64`) |
61
+ | `agent.cmd` | Command that runs the agent inside the container |
62
+ | `agent.llm_base_url` / `agent.llm_api_key` / `agent.llm_model` | OpenAI-compatible endpoint, key, and model for the agent |
63
+ | `verifier.judge_base_url` / `judge_api_key` / `judge_model` | Endpoint, key, and model for the judge/verifier |
64
+ | `runner.concurrency` / `runner.limit` / `runner.skip_done` | Parallelism, cap on number of instances (`null` = all), and whether to skip instances that already have a reward |
65
+
66
+ > API keys are read from your local config and injected into the containers as
67
+ > environment variables. Never commit `eval_config.local.yaml` — it is already
68
+ > in `.gitignore`.
69
+
70
+ ## Evaluation flow
71
+
72
+ ```
73
+ HuggingFace Dataset
74
+
75
+
76
+ ┌─────────────────────────────────┐
77
+ │ For each instance: │
78
+ │ 1. docker pull <image> │
79
+ │ 2. docker run (start env) │
80
+ │ 3. agent calls tools -> answer │
81
+ │ 4. verifier (test.sh) scores │
82
+ │ 5. emit reward.txt (0 – 1) │
83
+ └─────────────────────────────────┘
84
+
85
+
86
+ scores.jsonl + summary.json
87
+ ```
88
+
89
+ ## CLI commands
90
+
91
+ ```bash
92
+ # Run the evaluation
93
+ python run_eval.py run [--dataset-config travel|e_commerce] [--limit N] [--dry-run]
94
+
95
+ # Check progress
96
+ python run_eval.py status
97
+
98
+ # Generate a report
99
+ python run_eval.py report
100
+
101
+ # Package results for leaderboard submission
102
+ python run_eval.py submit --model deepseek-v4-flash --provider deepseek
103
+ ```
104
+
105
+ ## Output
106
+
107
+ | File | Contents |
108
+ |------|----------|
109
+ | `scores.jsonl` | One line per instance: `{instance_id, domain, reward, duration_sec}` |
110
+ | `summary.json` | Aggregate statistics (`avg_reward`, breakdown `by_domain`) |
111
+
112
+ `reward` is a float in `0.00 – 1.00` produced by the verifier for each instance.
113
+
114
+ ## Submitting results to the leaderboard
115
+
116
+ 1. After evaluating, package the results with `submit`:
117
+
118
+ ```bash
119
+ python run_eval.py submit \
120
+ --dataset-config travel \
121
+ --model deepseek-v4-flash \
122
+ --provider deepseek \
123
+ --agent-type mini-swe-agent
124
+ ```
125
+
126
+ 2. Review `experiments/evaluation/travel/<slug>/metadata.yaml` and complete the
127
+ model and agent details.
128
+
129
+ 3. Open a pull request to the `main` branch of the flyai-bench repository.
130
+
131
+ 4. CI validates the format automatically; once merged, the leaderboard updates.
132
+
133
+ ### Submission package layout
134
+
135
+ ```
136
+ evaluation/travel/20260830_deepseek-v4-flash_mini-swe-agent/
137
+ ├── metadata.yaml # model/agent info + evaluation statistics
138
+ ├── scores.jsonl # per instance: {instance_id, domain, reward, duration_sec}
139
+ └── summary.json # aggregate statistics (avg_reward, by_domain)
140
+ ```
141
+
142
+ ## Writing a custom agent
143
+
144
+ Implement a script that, inside the container:
145
+
146
+ 1. Reads `/app/tool_defs.json` for the tool definitions
147
+ 2. Reads `/app/system.md` + `/app/instruction.md` for the task
148
+ 3. Calls `/app/tools/<name> --arg val` to execute a tool
149
+ 4. Writes the result to `/app/answer.json`
150
+
151
+ Point the runner at your agent with `--agent-cmd`:
152
+
153
+ ```bash
154
+ python run_eval.py run --agent-cmd "python /app/my_agent.py"
155
+ ```
156
+
157
+ `agent.py` (a minimal LLM agent) and `mini_swe_agent.py` (a terminal/bash agent)
158
+ are provided as reference implementations.
159
+
160
+ ## Project structure
161
+
162
+ ```
163
+ flyai-bench/
164
+ ├── README.md
165
+ ├── pyproject.toml # packaging (installs the `flyai-bench` CLI)
166
+ ├── run_eval.py # evaluation CLI (run / status / report / submit)
167
+ ├── eval_config.yaml # default config (copy to eval_config.local.yaml)
168
+ ├── benchmark.yaml # benchmark metadata
169
+ ├── agent.py # reference LLM agent
170
+ ├── mini_swe_agent.py # reference terminal agent
171
+ ├── tool_server.py # in-sandbox tool proxy (permission isolation)
172
+ ├── sandbox_setup.sh # container permission setup
173
+ ├── validate_submission.py # submission validation + leaderboard rebuild
174
+ ├── experiments/ # submitted results + leaderboard.json
175
+ ├── leaderboard_space/ # HuggingFace Space (leaderboard UI)
176
+ └── src/flyai_bench/ # installable package mirror of the above
177
+ ```
178
+
179
+ > Note: the top-level scripts and the `src/flyai_bench/` package currently hold
180
+ > parallel copies of the same code. Prefer editing one and keeping them in sync
181
+ > (or consolidating on the package) to avoid drift.
182
+
183
+ ## License
184
+
185
+ Released under the MIT license. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env python3
2
+ """Minimal reference agent for E-Commerce Last Exam benchmark.
3
+
4
+ This file demonstrates the agent/environment interface. In real evaluations it can be replaced by:
5
+ - mini-swe-agent (general-purpose CLI agent)
6
+ - a custom LLM agent (calling a model via an OpenAI-compatible API)
7
+
8
+ Agent responsibilities:
9
+ 1. Read /app/tool_defs.json for the available tool definitions (OpenAI function-calling schema)
10
+ 2. Read system.md + instruction.md for the task prompt
11
+ 3. Call the LLM to decide -> call tools -> loop until the task is done
12
+ 4. Write the final result to /app/answer.json
13
+
14
+ Tool call convention:
15
+ /app/tools/<tool_name> --param1 value1 --param2 value2
16
+ Output is JSON (stdout)
17
+ """
18
+ import json
19
+ import os
20
+ import subprocess
21
+ import sys
22
+
23
+ # Environment file paths (inside the container)
24
+ TOOL_DEFS_PATH = "/app/tool_defs.json"
25
+ SYSTEM_MD_PATH = "/app/system.md"
26
+ INSTRUCTION_MD_PATH = "/app/instruction.md"
27
+ ANSWER_PATH = "/app/answer.json"
28
+ TOOL_CALL_LOG = os.environ.get("TOOL_CALL_LOG", "/app/.tool_calls.jsonl")
29
+
30
+ # Agent LLM config (injected via environment variables)
31
+ LLM_BASE_URL = os.environ.get("LLM_BASE_URL", "http://localhost:4000")
32
+ LLM_API_KEY = os.environ.get("LLM_API_KEY", "")
33
+ LLM_MODEL = os.environ.get("LLM_MODEL", "qwen3.7-plus")
34
+
35
+
36
+ def load_tool_defs():
37
+ with open(TOOL_DEFS_PATH, encoding="utf-8") as f:
38
+ return json.load(f)
39
+
40
+
41
+ def load_prompts():
42
+ system = ""
43
+ instruction = ""
44
+ if os.path.exists(SYSTEM_MD_PATH):
45
+ with open(SYSTEM_MD_PATH, encoding="utf-8") as f:
46
+ system = f.read()
47
+ if os.path.exists(INSTRUCTION_MD_PATH):
48
+ with open(INSTRUCTION_MD_PATH, encoding="utf-8") as f:
49
+ instruction = f.read()
50
+ return system, instruction
51
+
52
+
53
+ def call_tool(tool_name, arguments):
54
+ """Execute a single tool call and return the output JSON."""
55
+ tool_path = f"/app/tools/{tool_name}"
56
+ if not os.path.exists(tool_path):
57
+ return {"error": f"tool not found: {tool_name}"}
58
+
59
+ cmd = [tool_path]
60
+ for k, v in arguments.items():
61
+ cmd += [f"--{k}", str(v)]
62
+
63
+ try:
64
+ r = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
65
+ if r.returncode != 0:
66
+ return {"error": r.stderr[:500]}
67
+ try:
68
+ return json.loads(r.stdout)
69
+ except json.JSONDecodeError:
70
+ return {"raw_output": r.stdout[:2000]}
71
+ except subprocess.TimeoutExpired:
72
+ return {"error": "tool call timeout"}
73
+
74
+
75
+ def log_tool_call(tool_name, arguments, result):
76
+ """Log a tool call."""
77
+ entry = {"tool": tool_name, "arguments": arguments, "result": result}
78
+ with open(TOOL_CALL_LOG, "a", encoding="utf-8") as f:
79
+ f.write(json.dumps(entry, ensure_ascii=False) + "\n")
80
+
81
+
82
+ def call_llm(messages, tools):
83
+ """Call the LLM API (OpenAI-compatible)."""
84
+ try:
85
+ import openai
86
+ except ImportError:
87
+ print("ERROR: openai package not installed", file=sys.stderr)
88
+ sys.exit(1)
89
+
90
+ client = openai.OpenAI(base_url=LLM_BASE_URL, api_key=LLM_API_KEY)
91
+ response = client.chat.completions.create(
92
+ model=LLM_MODEL,
93
+ messages=messages,
94
+ tools=tools,
95
+ tool_choice="auto",
96
+ temperature=0.1,
97
+ )
98
+ return response.choices[0].message
99
+
100
+
101
+ def run():
102
+ """Main agent loop."""
103
+ tool_defs = load_tool_defs()
104
+ system_prompt, instruction = load_prompts()
105
+
106
+ # Build the OpenAI tools format
107
+ tools = []
108
+ for td in tool_defs:
109
+ tools.append({
110
+ "type": "function",
111
+ "function": {
112
+ "name": td["name"],
113
+ "description": td.get("description", ""),
114
+ "parameters": td.get("parameters", {}),
115
+ }
116
+ })
117
+
118
+ messages = [
119
+ {"role": "system", "content": system_prompt},
120
+ {"role": "user", "content": instruction},
121
+ ]
122
+
123
+ max_turns = 20
124
+ for turn in range(max_turns):
125
+ response = call_llm(messages, tools)
126
+
127
+ if response.tool_calls:
128
+ messages.append(response.model_dump())
129
+ for tc in response.tool_calls:
130
+ fn_name = tc.function.name
131
+ try:
132
+ fn_args = json.loads(tc.function.arguments)
133
+ except json.JSONDecodeError:
134
+ fn_args = {}
135
+
136
+ result = call_tool(fn_name, fn_args)
137
+ log_tool_call(fn_name, fn_args, result)
138
+
139
+ messages.append({
140
+ "role": "tool",
141
+ "tool_call_id": tc.id,
142
+ "content": json.dumps(result, ensure_ascii=False),
143
+ })
144
+ else:
145
+ # Agent done; parse the final reply into answer.json
146
+ content = response.content or ""
147
+ try:
148
+ answer = json.loads(content)
149
+ except json.JSONDecodeError:
150
+ answer = {"raw_response": content}
151
+
152
+ with open(ANSWER_PATH, "w", encoding="utf-8") as f:
153
+ json.dump(answer, f, ensure_ascii=False, indent=2)
154
+ print(f"Agent completed in {turn + 1} turns. Answer written to {ANSWER_PATH}")
155
+ return
156
+
157
+ print(f"WARNING: Agent reached max turns ({max_turns}) without final answer",
158
+ file=sys.stderr)
159
+
160
+
161
+ if __name__ == "__main__":
162
+ run()
@@ -0,0 +1,49 @@
1
+ name: ecommerce_last_exam
2
+ version: "1.0"
3
+ description: "E-Commerce Last Exam: Agent benchmark for travel and e-commerce tool-use tasks"
4
+
5
+ dataset:
6
+ repo: FlyaiLab/ecommerce_last_exam
7
+ configs:
8
+ - travel # 500 Chinese trip_guide tasks (hotel/traffic/poi)
9
+ - e_commerce # 500 English shopping tasks (fashion/health/electronics/outdoor/home)
10
+ split: test
11
+
12
+ agent:
13
+ type: mini-swe-agent
14
+ timeout_sec: 1800
15
+ interface: tool-calling
16
+ output_file: /app/answer.json
17
+
18
+ verifier:
19
+ timeout_sec: 1200
20
+ test_script: /app/tests/test.sh
21
+ reward_file: /logs/verifier/reward.txt
22
+ env:
23
+ JUDGE_BASE_URL: "${JUDGE_BASE_URL}"
24
+ JUDGE_API_KEY: "${JUDGE_API_KEY}"
25
+ JUDGE_MODEL: "${JUDGE_MODEL}"
26
+
27
+ docker:
28
+ registry: ""
29
+ platform: linux/amd64
30
+
31
+ output:
32
+ scores_file: scores.jsonl
33
+ summary_file: summary.json
34
+ reward_format: float # 0.00 - 1.00
35
+
36
+ submission:
37
+ experiments_dir: experiments/evaluation/
38
+ format:
39
+ required:
40
+ - metadata.yaml # model/agent info + evaluation stats
41
+ - scores.jsonl # per-instance: {instance_id, domain, reward, duration_sec}
42
+ optional:
43
+ - summary.json # aggregate stats (auto-generated if missing)
44
+ - trajs/ # per-instance agent traces
45
+ workflow:
46
+ - "python run_eval.py run --dataset-config travel"
47
+ - "python run_eval.py report --dataset-config travel"
48
+ - "python run_eval.py submit --dataset-config travel --model your-model"
49
+ - "# then PR to experiments repo"