Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/together/lib/cli/api/evals/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
ParametersEvaluationCompareParametersModelAEvaluationModelRequest,
ParametersEvaluationCompareParametersModelBEvaluationModelRequest,
)
from together.lib.cli.components.upload_progress import upload_file_with_progress


async def create(
Expand Down Expand Up @@ -140,7 +141,15 @@ async def create(
# If the user passes a path to a file, try to upload it to the files API first
# Uploads are idempotent so we can depend on this API always giving us a file ID
if _check_path_exists(input_data_file_path):
file_upload = await config.client.files.upload(Path(input_data_file_path), purpose="eval", check=False)
input_data_path = Path(input_data_file_path)
file_upload = await upload_file_with_progress(
config.client.files.upload,
input_data_path,
enabled=not config.json,
description=f"Uploading eval input file {input_data_path.name}",
purpose="eval",
check=False,
)
training_file = file_upload.id
else:
training_file = input_data_file_path
Expand Down
107 changes: 106 additions & 1 deletion tests/cli/test_evals.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

import os
import json
from typing import cast
from typing import Any, cast
from pathlib import Path
from unittest.mock import AsyncMock, patch

import httpx
import pytest
from respx import MockRouter
from respx.models import Call

from tests.cli.utils import CliRunner
from together.types.file_response import FileResponse

base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
API_KEY = "0000000000000000000000000000000000000000"
Expand All @@ -27,6 +30,23 @@
_EVAL_STATUS = {"status": "completed", "results": None}


def _file_response(**kwargs: Any) -> FileResponse:
defaults: dict[str, Any] = {
"id": "file-up",
"bytes": 10,
"created_at": 1,
"filename": "eval-input.jsonl",
"FileType": "jsonl",
"object": "file",
"Processed": True,
"purpose": "eval",
}
defaults.update(kwargs)
if hasattr(FileResponse, "model_validate"):
return FileResponse.model_validate(defaults)
return FileResponse.parse_obj(defaults) # pyright: ignore[reportDeprecated]


class TestEvalsList:
@pytest.mark.respx(base_url=base_url)
def test_list_passes_status_and_limit(self, respx_mock: MockRouter, cli_runner: CliRunner) -> None:
Expand Down Expand Up @@ -97,3 +117,88 @@ def test_compare_passes_disable_position_bias_correction(
payload = json.loads(req.content)
assert payload["type"] == "compare"
assert payload["parameters"]["disable_position_bias_correction"] is True

@pytest.mark.respx(base_url=base_url)
def test_local_input_upload_uses_progress_callback(
self, tmp_path: Path, respx_mock: MockRouter, cli_runner: CliRunner
) -> None:
input_file = tmp_path / "eval-input.jsonl"
input_file.write_text('{"prompt": "hello", "response_a": "a", "response_b": "b"}\n')
uploaded = _file_response(id="file-uploaded")
route = respx_mock.post("/evaluation").mock(
return_value=httpx.Response(200, json={"workflow_id": "eval-wf-1", "status": "pending"})
)

with patch("together.resources.files.AsyncFilesResource.upload", new_callable=AsyncMock) as upload_mock:
upload_mock.return_value = uploaded
result = cli_runner.invoke(
[
"evals",
"create",
"--type",
"compare",
"--judge-model",
"Qwen/Qwen3.5-9B",
"--judge-model-source",
"serverless",
"--judge-system-template",
"Choose the better response.",
"--input-data-file-path",
str(input_file),
"--model-a-field",
"response_a",
"--model-b-field",
"response_b",
]
)

assert result.exit_code == 0
upload_mock.assert_called_once()
upload_kwargs = upload_mock.call_args.kwargs
assert upload_kwargs["file"] == input_file
assert upload_kwargs["purpose"] == "eval"
assert upload_kwargs["check"] is False
assert upload_kwargs["progress_callback"] is not None
req = cast(Call, route.calls[0]).request
payload = json.loads(req.content)
assert payload["parameters"]["input_data_file_path"] == "file-uploaded"

@pytest.mark.respx(base_url=base_url)
def test_local_input_upload_json_mode_disables_progress_callback(
self, tmp_path: Path, respx_mock: MockRouter, cli_runner: CliRunner
) -> None:
input_file = tmp_path / "eval-input.jsonl"
input_file.write_text('{"prompt": "hello", "response_a": "a", "response_b": "b"}\n')
uploaded = _file_response(id="file-uploaded")
respx_mock.post("/evaluation").mock(
return_value=httpx.Response(200, json={"workflow_id": "eval-wf-1", "status": "pending"})
)

with patch("together.resources.files.AsyncFilesResource.upload", new_callable=AsyncMock) as upload_mock:
upload_mock.return_value = uploaded
result = cli_runner.invoke(
[
"evals",
"create",
"--type",
"compare",
"--judge-model",
"Qwen/Qwen3.5-9B",
"--judge-model-source",
"serverless",
"--judge-system-template",
"Choose the better response.",
"--input-data-file-path",
str(input_file),
"--model-a-field",
"response_a",
"--model-b-field",
"response_b",
"--json",
]
)

assert result.exit_code == 0
upload_mock.assert_called_once()
assert upload_mock.call_args.kwargs["progress_callback"] is None
assert json.loads(result.out_out.lstrip("\n"))["workflow_id"] == "eval-wf-1"
Loading