Report the elapsed time when run-llm finishes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 22:38:28 +08:00
co-authored by Claude Fable 5
parent db9fbd71d4
commit 4313d58471
2 changed files with 22 additions and 7 deletions
@@ -33,6 +33,7 @@ from typing import Any, Self
import anthropic import anthropic
from ..config import get_settings from ..config import get_settings
from ..utils import format_duration
MODEL: str = "claude-sonnet-4-6" MODEL: str = "claude-sonnet-4-6"
TEMPERATURE: float = 0.0 TEMPERATURE: float = 0.0
@@ -450,6 +451,7 @@ def main(argv: list[str] | None = None) -> int:
:param argv: The command-line arguments, or None for ``sys.argv``. :param argv: The command-line arguments, or None for ``sys.argv``.
:return: The exit status: 0 on success, non-zero on failure. :return: The exit status: 0 on success, non-zero on failure.
""" """
started: float = time.monotonic()
args: argparse.Namespace = parse_args(argv) args: argparse.Namespace = parse_args(argv)
try: try:
items: list[InputItem] = load_items(args.input) items: list[InputItem] = load_items(args.input)
@@ -504,6 +506,8 @@ def main(argv: list[str] | None = None) -> int:
print(f"error: failed items: {', '.join(failed)}", print(f"error: failed items: {', '.join(failed)}",
file=sys.stderr) file=sys.stderr)
return 1 return 1
elapsed: str = format_duration(time.monotonic() - started)
print(f"done: {len(items)} items;" print(f"done: {len(items)} items;"
f" archived to {archive_dir}", file=sys.stderr) f" archived to {archive_dir} {elapsed} elapsed.",
file=sys.stderr)
return 0 return 0
+17 -6
View File
@@ -326,13 +326,15 @@ class TestMainFlow(RunLLMTestCase):
return client return client
@staticmethod @staticmethod
def __run_main(argv: list[str], def __run_main(
client: mock.Mock | None = None) -> tuple[int, str]: argv: list[str],
client: mock.Mock | None = None) -> tuple[int, str, str]:
"""Run main() with a mocked client and captured output. """Run main() with a mocked client and captured output.
:param argv: The command-line arguments. :param argv: The command-line arguments.
:param client: The mock client, or None for dry runs. :param client: The mock client, or None for dry runs.
:return: A tuple of the exit status and the standard output. :return: A tuple of the exit status, the standard output,
and the standard error.
""" """
stdout: io.StringIO = io.StringIO() stdout: io.StringIO = io.StringIO()
stderr: io.StringIO = io.StringIO() stderr: io.StringIO = io.StringIO()
@@ -340,13 +342,14 @@ class TestMainFlow(RunLLMTestCase):
return_value=client), \ return_value=client), \
redirect_stdout(stdout), redirect_stderr(stderr): redirect_stdout(stdout), redirect_stderr(stderr):
status: int = run_llm.main(argv) status: int = run_llm.main(argv)
return status, stdout.getvalue() return status, stdout.getvalue(), stderr.getvalue()
def test_dry_run(self) -> None: def test_dry_run(self) -> None:
"""Test the dry-run behavior.""" """Test the dry-run behavior."""
status: int status: int
stdout: str stdout: str
status, stdout = self.__run_main( stderr: str
status, stdout, stderr = self.__run_main(
self.__argv + ["--dry-run"]) self.__argv + ["--dry-run"])
self.assertEqual(status, 0) self.assertEqual(status, 0)
run_dir: Path = self.__archive_dir run_dir: Path = self.__archive_dir
@@ -363,13 +366,18 @@ class TestMainFlow(RunLLMTestCase):
self.assertEqual(request["custom_id"], "a") self.assertEqual(request["custom_id"], "a")
self.assertEqual(request["params"]["system"], self.assertEqual(request["params"]["system"],
"The task prompt.\n") "The task prompt.\n")
self.assertNotRegex(stderr, r"\d{2}:\d{2} elapsed\.")
def test_run_produces_output_file(self) -> None: def test_run_produces_output_file(self) -> None:
"""Test that a run submits one batch and writes output.""" """Test that a run submits one batch and writes output."""
client: mock.Mock = self.__make_client( client: mock.Mock = self.__make_client(
[self._make_success_entry("a", "answer a"), [self._make_success_entry("a", "answer a"),
self._make_success_entry("b", "answer b")]) self._make_success_entry("b", "answer b")])
status: int = self.__run_main(self.__argv, client)[0] status: int
stderr: str
with mock.patch(
"time.monotonic", side_effect=[1000.0, 1125.0]):
status, _, stderr = self.__run_main(self.__argv, client)
self.assertEqual(status, 0) self.assertEqual(status, 0)
self.assertEqual( self.assertEqual(
client.messages.batches.create.call_count, 1) client.messages.batches.create.call_count, 1)
@@ -385,6 +393,9 @@ class TestMainFlow(RunLLMTestCase):
self.assertEqual(meta["batch"]["batch_id"], "batch_run1") self.assertEqual(meta["batch"]["batch_id"], "batch_run1")
self.assertEqual(meta["usage"], self.assertEqual(meta["usage"],
{"input_tokens": 20, "output_tokens": 10}) {"input_tokens": 20, "output_tokens": 10})
self.assertTrue(stderr.rstrip("\n").endswith(
"done: 2 items; archived to"
f" {run_dir} 02:05 elapsed."))
def test_existing_archive_rejected_without_replace(self) -> None: def test_existing_archive_rejected_without_replace(self) -> None:
"""Test that an existing archive without --replace fails.""" """Test that an existing archive without --replace fails."""