codeer-cli 0.1.13__tar.gz → 0.1.14__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.
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/PKG-INFO +29 -1
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/README.md +28 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/pyproject.toml +1 -1
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/commands/eval_cmd.py +52 -5
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/eval_.py +13 -0
- codeer_cli-0.1.14/tests/test_eval_evaluators.py +230 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/uv.lock +1 -1
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/.gitignore +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/API_REFERENCE.md +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/__init__.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/_validate.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/agents.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/chats.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/cli.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/client.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/commands/__init__.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/commands/_util.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/commands/agent.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/commands/check.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/commands/history.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/commands/kb.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/commands/model.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/commands/profile.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/constants.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/histories.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/kb.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/models.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/src/codeer_cli/parse.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/tests/test_agent_handoff.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/tests/test_agent_model_settings.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/tests/test_chats_v2.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/tests/test_client_transport.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/tests/test_eval_labels.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/tests/test_eval_pairs.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/tests/test_history_read.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/tests/test_history_send.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/tests/test_kb_nodes.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/tests/test_kb_ranges.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/tests/test_models.py +0 -0
- {codeer_cli-0.1.13 → codeer_cli-0.1.14}/tests/test_util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: codeer-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14
|
|
4
4
|
Summary: Command line tools for managing Codeer agents over the Codeer API.
|
|
5
5
|
Project-URL: Homepage, https://www.codeer.ai
|
|
6
6
|
Author: Codeer.AI
|
|
@@ -123,6 +123,34 @@ List the active cloud models without opening the Codeer web app:
|
|
|
123
123
|
codeer model list --type text
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
+
## Custom evaluator judge models
|
|
127
|
+
|
|
128
|
+
Custom evaluator create/update commands can select a judge LLM model by ID:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
codeer eval evaluator-create \
|
|
132
|
+
--name "Correctness" \
|
|
133
|
+
--system-prompt-template-file evaluator-prompt.txt \
|
|
134
|
+
--judge-model <model-id> \
|
|
135
|
+
--dry-run
|
|
136
|
+
|
|
137
|
+
codeer eval evaluator-update \
|
|
138
|
+
--evaluator <evaluator-id> \
|
|
139
|
+
--judge-model <model-id> \
|
|
140
|
+
--dry-run
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Omit the judge-model flags on update to leave the current setting unchanged.
|
|
144
|
+
Use `--clear-judge-model` to explicitly clear the override and return to the
|
|
145
|
+
system default:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
codeer eval evaluator-update \
|
|
149
|
+
--evaluator <evaluator-id> \
|
|
150
|
+
--clear-judge-model \
|
|
151
|
+
--dry-run
|
|
152
|
+
```
|
|
153
|
+
|
|
126
154
|
## Agent human handoff
|
|
127
155
|
|
|
128
156
|
`codeer agent apply` accepts the same `human_handoff` object as the Agent API.
|
|
@@ -105,6 +105,34 @@ List the active cloud models without opening the Codeer web app:
|
|
|
105
105
|
codeer model list --type text
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
## Custom evaluator judge models
|
|
109
|
+
|
|
110
|
+
Custom evaluator create/update commands can select a judge LLM model by ID:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
codeer eval evaluator-create \
|
|
114
|
+
--name "Correctness" \
|
|
115
|
+
--system-prompt-template-file evaluator-prompt.txt \
|
|
116
|
+
--judge-model <model-id> \
|
|
117
|
+
--dry-run
|
|
118
|
+
|
|
119
|
+
codeer eval evaluator-update \
|
|
120
|
+
--evaluator <evaluator-id> \
|
|
121
|
+
--judge-model <model-id> \
|
|
122
|
+
--dry-run
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Omit the judge-model flags on update to leave the current setting unchanged.
|
|
126
|
+
Use `--clear-judge-model` to explicitly clear the override and return to the
|
|
127
|
+
system default:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
codeer eval evaluator-update \
|
|
131
|
+
--evaluator <evaluator-id> \
|
|
132
|
+
--clear-judge-model \
|
|
133
|
+
--dry-run
|
|
134
|
+
```
|
|
135
|
+
|
|
108
136
|
## Agent human handoff
|
|
109
137
|
|
|
110
138
|
`codeer agent apply` accepts the same `human_handoff` object as the Agent API.
|
|
@@ -128,6 +128,12 @@ def register(subparsers):
|
|
|
128
128
|
g.add_argument("--system-prompt-template", help="Evaluator system prompt template text")
|
|
129
129
|
g.add_argument("--system-prompt-template-file", help="Path to evaluator system prompt template")
|
|
130
130
|
p.add_argument("--description", default=None)
|
|
131
|
+
p.add_argument(
|
|
132
|
+
"--judge-model",
|
|
133
|
+
default=None,
|
|
134
|
+
metavar="MODEL_ID",
|
|
135
|
+
help="Judge LLM model ID (default: system default)",
|
|
136
|
+
)
|
|
131
137
|
p.add_argument("--dry-run", action="store_true",
|
|
132
138
|
help="Validate inputs and print intended mutation without writing server state.")
|
|
133
139
|
p.set_defaults(func=run_evaluator_create)
|
|
@@ -140,6 +146,18 @@ def register(subparsers):
|
|
|
140
146
|
g.add_argument("--system-prompt-template", help="Evaluator system prompt template text")
|
|
141
147
|
g.add_argument("--system-prompt-template-file", help="Path to evaluator system prompt template")
|
|
142
148
|
p.add_argument("--description", default=None)
|
|
149
|
+
g = p.add_mutually_exclusive_group()
|
|
150
|
+
g.add_argument(
|
|
151
|
+
"--judge-model",
|
|
152
|
+
default=None,
|
|
153
|
+
metavar="MODEL_ID",
|
|
154
|
+
help="Set the judge LLM model ID",
|
|
155
|
+
)
|
|
156
|
+
g.add_argument(
|
|
157
|
+
"--clear-judge-model",
|
|
158
|
+
action="store_true",
|
|
159
|
+
help="Clear the evaluator override and use the system default judge model",
|
|
160
|
+
)
|
|
143
161
|
p.add_argument("--dry-run", action="store_true",
|
|
144
162
|
help="Validate inputs and print intended mutation without writing server state.")
|
|
145
163
|
p.set_defaults(func=run_evaluator_update)
|
|
@@ -249,6 +267,7 @@ def _evaluator_summary(evaluator: dict, *, full: bool = False) -> dict:
|
|
|
249
267
|
"id": evaluator.get("id"),
|
|
250
268
|
"name": evaluator.get("name"),
|
|
251
269
|
"description": evaluator.get("description"),
|
|
270
|
+
"judge_llm_model_id": evaluator.get("judge_llm_model_id"),
|
|
252
271
|
"system_prompt_template_chars": len(template),
|
|
253
272
|
"has_tool_steps_placeholder": "{tool_steps}" in template,
|
|
254
273
|
"has_output_placeholder": "{output}" in template,
|
|
@@ -534,6 +553,10 @@ def run_evaluator_create(args, client) -> int:
|
|
|
534
553
|
"workspace_id": workspace_id,
|
|
535
554
|
"name": args.name,
|
|
536
555
|
"description": args.description,
|
|
556
|
+
"judge_model": {
|
|
557
|
+
"action": "set" if args.judge_model is not None else "use_system_default",
|
|
558
|
+
"model_id": args.judge_model,
|
|
559
|
+
},
|
|
537
560
|
"system_prompt_template_chars": len(system_prompt_template or ""),
|
|
538
561
|
"would_write_server_state": True,
|
|
539
562
|
"next_step": "Review this summary, then rerun without --dry-run after approval.",
|
|
@@ -545,6 +568,7 @@ def run_evaluator_create(args, client) -> int:
|
|
|
545
568
|
name=args.name,
|
|
546
569
|
system_prompt_template=system_prompt_template,
|
|
547
570
|
description=args.description,
|
|
571
|
+
judge_llm_model_id=args.judge_model,
|
|
548
572
|
)
|
|
549
573
|
print_json(_evaluator_summary(evaluator, full=True))
|
|
550
574
|
return 0
|
|
@@ -560,13 +584,27 @@ def run_evaluator_update(args, client) -> int:
|
|
|
560
584
|
else:
|
|
561
585
|
system_prompt_template = args.system_prompt_template
|
|
562
586
|
|
|
563
|
-
if
|
|
587
|
+
if (
|
|
588
|
+
args.name is None
|
|
589
|
+
and args.description is None
|
|
590
|
+
and system_prompt_template is None
|
|
591
|
+
and args.judge_model is None
|
|
592
|
+
and not args.clear_judge_model
|
|
593
|
+
):
|
|
564
594
|
log(
|
|
565
595
|
"error: provide at least one of --name, --description, "
|
|
566
|
-
"--system-prompt-template, --system-prompt-template-file"
|
|
596
|
+
"--system-prompt-template, --system-prompt-template-file, "
|
|
597
|
+
"--judge-model, --clear-judge-model"
|
|
567
598
|
)
|
|
568
599
|
return 2
|
|
569
600
|
|
|
601
|
+
if args.clear_judge_model:
|
|
602
|
+
judge_model = {"action": "clear_to_system_default", "model_id": None}
|
|
603
|
+
elif args.judge_model is not None:
|
|
604
|
+
judge_model = {"action": "set", "model_id": args.judge_model}
|
|
605
|
+
else:
|
|
606
|
+
judge_model = {"action": "unchanged"}
|
|
607
|
+
|
|
570
608
|
if args.dry_run:
|
|
571
609
|
print_json({
|
|
572
610
|
"dry_run": True,
|
|
@@ -574,6 +612,7 @@ def run_evaluator_update(args, client) -> int:
|
|
|
574
612
|
"evaluator_id": args.evaluator,
|
|
575
613
|
"name": args.name,
|
|
576
614
|
"description": args.description,
|
|
615
|
+
"judge_model": judge_model,
|
|
577
616
|
"system_prompt_template_chars": (
|
|
578
617
|
len(system_prompt_template) if system_prompt_template is not None else None
|
|
579
618
|
),
|
|
@@ -582,12 +621,20 @@ def run_evaluator_update(args, client) -> int:
|
|
|
582
621
|
})
|
|
583
622
|
return 0
|
|
584
623
|
|
|
624
|
+
evaluator_kwargs: dict[str, Any] = {
|
|
625
|
+
"name": args.name,
|
|
626
|
+
"system_prompt_template": system_prompt_template,
|
|
627
|
+
"description": args.description,
|
|
628
|
+
}
|
|
629
|
+
if args.clear_judge_model:
|
|
630
|
+
evaluator_kwargs["judge_llm_model_id"] = None
|
|
631
|
+
elif args.judge_model is not None:
|
|
632
|
+
evaluator_kwargs["judge_llm_model_id"] = args.judge_model
|
|
633
|
+
|
|
585
634
|
evaluator = eval_mod.update_evaluator(
|
|
586
635
|
client,
|
|
587
636
|
evaluator_id=args.evaluator,
|
|
588
|
-
|
|
589
|
-
system_prompt_template=system_prompt_template,
|
|
590
|
-
description=args.description,
|
|
637
|
+
**evaluator_kwargs,
|
|
591
638
|
)
|
|
592
639
|
print_json(_evaluator_summary(evaluator, full=True))
|
|
593
640
|
return 0
|
|
@@ -12,6 +12,13 @@ from typing import Any, List, Optional
|
|
|
12
12
|
from .client import CodeerClient
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
class _UnsetType:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_UNSET = _UnsetType()
|
|
20
|
+
|
|
21
|
+
|
|
15
22
|
# --- cases ----------------------------------------------------------------
|
|
16
23
|
|
|
17
24
|
def create_case(
|
|
@@ -186,6 +193,7 @@ def create_evaluator(
|
|
|
186
193
|
name: str,
|
|
187
194
|
system_prompt_template: str,
|
|
188
195
|
description: Optional[str] = None,
|
|
196
|
+
judge_llm_model_id: Optional[str] = None,
|
|
189
197
|
) -> dict:
|
|
190
198
|
body: dict[str, Any] = {
|
|
191
199
|
"name": name,
|
|
@@ -193,6 +201,8 @@ def create_evaluator(
|
|
|
193
201
|
}
|
|
194
202
|
if description is not None:
|
|
195
203
|
body["description"] = description
|
|
204
|
+
if judge_llm_model_id is not None:
|
|
205
|
+
body["judge_llm_model_id"] = judge_llm_model_id
|
|
196
206
|
return client.post("/external/eval/evaluators", json=body)
|
|
197
207
|
|
|
198
208
|
|
|
@@ -211,6 +221,7 @@ def update_evaluator(
|
|
|
211
221
|
name: Optional[str] = None,
|
|
212
222
|
system_prompt_template: Optional[str] = None,
|
|
213
223
|
description: Optional[str] = None,
|
|
224
|
+
judge_llm_model_id: str | None | _UnsetType = _UNSET,
|
|
214
225
|
) -> dict:
|
|
215
226
|
body: dict[str, Any] = {}
|
|
216
227
|
if name is not None:
|
|
@@ -219,6 +230,8 @@ def update_evaluator(
|
|
|
219
230
|
body["system_prompt_template"] = system_prompt_template
|
|
220
231
|
if description is not None:
|
|
221
232
|
body["description"] = description
|
|
233
|
+
if judge_llm_model_id is not _UNSET:
|
|
234
|
+
body["judge_llm_model_id"] = judge_llm_model_id
|
|
222
235
|
return client.put(f"/external/eval/evaluators/{evaluator_id}", json=body)
|
|
223
236
|
|
|
224
237
|
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import io
|
|
5
|
+
import json
|
|
6
|
+
import unittest
|
|
7
|
+
from contextlib import redirect_stderr, redirect_stdout
|
|
8
|
+
from types import SimpleNamespace
|
|
9
|
+
|
|
10
|
+
from codeer_cli import eval_ as eval_mod
|
|
11
|
+
from codeer_cli.commands.eval_cmd import (
|
|
12
|
+
register,
|
|
13
|
+
run_evaluator_create,
|
|
14
|
+
run_evaluator_update,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FakeClient:
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
self.calls: list[tuple[str, str, dict]] = []
|
|
21
|
+
|
|
22
|
+
def post(self, path: str, **kwargs):
|
|
23
|
+
self.calls.append(("POST", path, kwargs))
|
|
24
|
+
return {"id": "created", **kwargs["json"]}
|
|
25
|
+
|
|
26
|
+
def put(self, path: str, **kwargs):
|
|
27
|
+
self.calls.append(("PUT", path, kwargs))
|
|
28
|
+
return {"id": "updated", **kwargs["json"]}
|
|
29
|
+
|
|
30
|
+
def resolve_scope(self):
|
|
31
|
+
return "ws-1", "org-1"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class EvaluatorRequestTests(unittest.TestCase):
|
|
35
|
+
def test_create_evaluator_sends_judge_model(self) -> None:
|
|
36
|
+
client = FakeClient()
|
|
37
|
+
|
|
38
|
+
eval_mod.create_evaluator(
|
|
39
|
+
client, # type: ignore[arg-type]
|
|
40
|
+
workspace_id="ws-1",
|
|
41
|
+
name="Correctness",
|
|
42
|
+
system_prompt_template="Judge {output}",
|
|
43
|
+
judge_llm_model_id="model-1",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
self.assertEqual(client.calls[0][0:2], ("POST", "/external/eval/evaluators"))
|
|
47
|
+
self.assertEqual(client.calls[0][2]["json"]["judge_llm_model_id"], "model-1")
|
|
48
|
+
|
|
49
|
+
def test_create_evaluator_omits_unspecified_judge_model(self) -> None:
|
|
50
|
+
client = FakeClient()
|
|
51
|
+
|
|
52
|
+
eval_mod.create_evaluator(
|
|
53
|
+
client, # type: ignore[arg-type]
|
|
54
|
+
workspace_id="ws-1",
|
|
55
|
+
name="Correctness",
|
|
56
|
+
system_prompt_template="Judge {output}",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
self.assertNotIn("judge_llm_model_id", client.calls[0][2]["json"])
|
|
60
|
+
|
|
61
|
+
def test_update_evaluator_sends_judge_model(self) -> None:
|
|
62
|
+
client = FakeClient()
|
|
63
|
+
|
|
64
|
+
eval_mod.update_evaluator(
|
|
65
|
+
client, # type: ignore[arg-type]
|
|
66
|
+
"eval-1",
|
|
67
|
+
judge_llm_model_id="model-2",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
self.assertEqual(client.calls[0][2]["json"], {"judge_llm_model_id": "model-2"})
|
|
71
|
+
|
|
72
|
+
def test_update_evaluator_omits_unspecified_judge_model(self) -> None:
|
|
73
|
+
client = FakeClient()
|
|
74
|
+
|
|
75
|
+
eval_mod.update_evaluator(
|
|
76
|
+
client, # type: ignore[arg-type]
|
|
77
|
+
"eval-1",
|
|
78
|
+
name="Updated",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
self.assertEqual(client.calls[0][2]["json"], {"name": "Updated"})
|
|
82
|
+
|
|
83
|
+
def test_update_evaluator_sends_null_to_clear_judge_model(self) -> None:
|
|
84
|
+
client = FakeClient()
|
|
85
|
+
|
|
86
|
+
eval_mod.update_evaluator(
|
|
87
|
+
client, # type: ignore[arg-type]
|
|
88
|
+
"eval-1",
|
|
89
|
+
judge_llm_model_id=None,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
self.assertEqual(client.calls[0][2]["json"], {"judge_llm_model_id": None})
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class EvaluatorCliTests(unittest.TestCase):
|
|
96
|
+
def test_create_dry_run_reports_set_judge_model(self) -> None:
|
|
97
|
+
client = FakeClient()
|
|
98
|
+
args = SimpleNamespace(
|
|
99
|
+
name="Correctness",
|
|
100
|
+
system_prompt_template="Judge {output}",
|
|
101
|
+
system_prompt_template_file=None,
|
|
102
|
+
description=None,
|
|
103
|
+
judge_model="model-1",
|
|
104
|
+
dry_run=True,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
report, result = self._capture(run_evaluator_create, args, client)
|
|
108
|
+
|
|
109
|
+
self.assertEqual(result, 0)
|
|
110
|
+
self.assertEqual(
|
|
111
|
+
report["judge_model"], {"action": "set", "model_id": "model-1"}
|
|
112
|
+
)
|
|
113
|
+
self.assertEqual(client.calls, [])
|
|
114
|
+
|
|
115
|
+
def test_create_dry_run_reports_system_default_when_model_is_omitted(self) -> None:
|
|
116
|
+
client = FakeClient()
|
|
117
|
+
args = SimpleNamespace(
|
|
118
|
+
name="Correctness",
|
|
119
|
+
system_prompt_template="Judge {output}",
|
|
120
|
+
system_prompt_template_file=None,
|
|
121
|
+
description=None,
|
|
122
|
+
judge_model=None,
|
|
123
|
+
dry_run=True,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
report, result = self._capture(run_evaluator_create, args, client)
|
|
127
|
+
|
|
128
|
+
self.assertEqual(result, 0)
|
|
129
|
+
self.assertEqual(
|
|
130
|
+
report["judge_model"],
|
|
131
|
+
{"action": "use_system_default", "model_id": None},
|
|
132
|
+
)
|
|
133
|
+
self.assertEqual(client.calls, [])
|
|
134
|
+
|
|
135
|
+
def test_update_dry_run_reports_set_judge_model(self) -> None:
|
|
136
|
+
client = FakeClient()
|
|
137
|
+
args = self._update_args(judge_model="model-2")
|
|
138
|
+
|
|
139
|
+
report, result = self._capture(run_evaluator_update, args, client)
|
|
140
|
+
|
|
141
|
+
self.assertEqual(result, 0)
|
|
142
|
+
self.assertEqual(
|
|
143
|
+
report["judge_model"], {"action": "set", "model_id": "model-2"}
|
|
144
|
+
)
|
|
145
|
+
self.assertEqual(client.calls, [])
|
|
146
|
+
|
|
147
|
+
def test_update_dry_run_reports_unspecified_judge_model_as_unchanged(self) -> None:
|
|
148
|
+
client = FakeClient()
|
|
149
|
+
args = self._update_args(name="Updated")
|
|
150
|
+
|
|
151
|
+
report, result = self._capture(run_evaluator_update, args, client)
|
|
152
|
+
|
|
153
|
+
self.assertEqual(result, 0)
|
|
154
|
+
self.assertEqual(report["judge_model"], {"action": "unchanged"})
|
|
155
|
+
self.assertEqual(client.calls, [])
|
|
156
|
+
|
|
157
|
+
def test_update_dry_run_reports_explicit_clear(self) -> None:
|
|
158
|
+
client = FakeClient()
|
|
159
|
+
args = self._update_args(clear_judge_model=True)
|
|
160
|
+
|
|
161
|
+
report, result = self._capture(run_evaluator_update, args, client)
|
|
162
|
+
|
|
163
|
+
self.assertEqual(result, 0)
|
|
164
|
+
self.assertEqual(
|
|
165
|
+
report["judge_model"],
|
|
166
|
+
{"action": "clear_to_system_default", "model_id": None},
|
|
167
|
+
)
|
|
168
|
+
self.assertEqual(client.calls, [])
|
|
169
|
+
|
|
170
|
+
def test_update_clear_flag_sends_explicit_null(self) -> None:
|
|
171
|
+
client = FakeClient()
|
|
172
|
+
args = self._update_args(clear_judge_model=True, dry_run=False)
|
|
173
|
+
|
|
174
|
+
_, result = self._capture(run_evaluator_update, args, client)
|
|
175
|
+
|
|
176
|
+
self.assertEqual(result, 0)
|
|
177
|
+
self.assertEqual(client.calls[0][2]["json"], {"judge_llm_model_id": None})
|
|
178
|
+
|
|
179
|
+
def test_update_other_field_does_not_send_judge_model(self) -> None:
|
|
180
|
+
client = FakeClient()
|
|
181
|
+
args = self._update_args(name="Updated", dry_run=False)
|
|
182
|
+
|
|
183
|
+
_, result = self._capture(run_evaluator_update, args, client)
|
|
184
|
+
|
|
185
|
+
self.assertEqual(result, 0)
|
|
186
|
+
self.assertEqual(client.calls[0][2]["json"], {"name": "Updated"})
|
|
187
|
+
|
|
188
|
+
def test_parser_rejects_set_and_clear_together(self) -> None:
|
|
189
|
+
parser = argparse.ArgumentParser()
|
|
190
|
+
subparsers = parser.add_subparsers(dest="group")
|
|
191
|
+
register(subparsers)
|
|
192
|
+
|
|
193
|
+
with redirect_stderr(io.StringIO()), self.assertRaises(SystemExit):
|
|
194
|
+
parser.parse_args(
|
|
195
|
+
[
|
|
196
|
+
"eval",
|
|
197
|
+
"evaluator-update",
|
|
198
|
+
"--evaluator",
|
|
199
|
+
"eval-1",
|
|
200
|
+
"--judge-model",
|
|
201
|
+
"model-1",
|
|
202
|
+
"--clear-judge-model",
|
|
203
|
+
]
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
def _capture(func, args, client) -> tuple[dict, int]:
|
|
208
|
+
stdout = io.StringIO()
|
|
209
|
+
with redirect_stdout(stdout):
|
|
210
|
+
result = func(args, client)
|
|
211
|
+
return json.loads(stdout.getvalue()), result
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
def _update_args(**overrides) -> SimpleNamespace:
|
|
215
|
+
values = {
|
|
216
|
+
"evaluator": "eval-1",
|
|
217
|
+
"name": None,
|
|
218
|
+
"system_prompt_template": None,
|
|
219
|
+
"system_prompt_template_file": None,
|
|
220
|
+
"description": None,
|
|
221
|
+
"judge_model": None,
|
|
222
|
+
"clear_judge_model": False,
|
|
223
|
+
"dry_run": True,
|
|
224
|
+
}
|
|
225
|
+
values.update(overrides)
|
|
226
|
+
return SimpleNamespace(**values)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
if __name__ == "__main__":
|
|
230
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|