diff --git a/docs/_specs/response/interface_response_body_spec.md b/docs/_specs/response/interface_response_body_spec.md new file mode 100644 index 000000000..e4c7bfbf4 --- /dev/null +++ b/docs/_specs/response/interface_response_body_spec.md @@ -0,0 +1,80 @@ +# 接口响应体说明 + +## 基础结构 + +| 字段 | 类型 | 描述 | +| --- | --- | --- | +| status | String | 枚举值:Sucess/Failed。
代表请求是否正常由沙箱执行完成。 | +| message | String | 响应信息。 | +| error | String | 表示当status=Failed时的错误描述信息。 | +| result | Object | 请求执行结果。根据具体功能,结构有所差异。 | +| └code | Integer | 响应状态码。参考下方状态码定义。 | +| └failure\_reason | String | 当code=5xxx时,包含一些额外的错误信息。 | +| └... | | 根据不同接口,扩展信息有所不同。 | + +### 响应状态码定义 + +| **值** | **描述** | +| --- | --- | +| 2xxx | 命令执行成功 | +| 5xxx | 服务端异常。该场景建议用户进行重试。 | +| 6xxx | 命令执行错误 | + +## result扩展信息定义 + +### execute/run\_in\_session + +| 字段 | 类型 | 描述 | +| --- | --- | --- | +| exit\_code | Integer | 命令执行退出码。
特殊退出码:-1(超时/网络/会话失效) | +| stdout | String | 命令执行标准输出 | +| stderr | String | 命令执行标准错误 | + +### create\_session + +| 字段 | 类型 | 描述 | +| --- | --- | --- | +| output | String | session创建结果。 | +| session\_type | String | session类型。
枚举值:
* bash | + +### close\_session + +| 字段 | 类型 | 描述 | +| --- | --- | --- | +| session\_type | String | session类型。
枚举值:
* bash | + +### upload + +| **字段** | **类型** | **描述** | +| --- | --- | --- | +| success | Boolean | 文件上传是否成功。 | +| message | String | 上传结果信息。 | +| file\_name | String | 文件名 | + +现有响应结构固定返回默认值,需要调整。 + +```shell +{ + "status": "Success", + "message": null, + "error": null, + "result": { + "success": false, + "message": "", + "file_name": "" + } +} +``` + +### write\_file + +| **字段** | **类型** | **描述** | +| --- | --- | --- | +| success | Boolean | 文件写入是否成功。 | +| message | String | 写入结果信息。 | + +### read\_file + +| **字段** | **类型** | **描述** | +| --- | --- | --- | +| content | String | 文件内容。 | \ No newline at end of file diff --git a/rock/actions/__init__.py b/rock/actions/__init__.py index d900c2f36..11f67809d 100644 --- a/rock/actions/__init__.py +++ b/rock/actions/__init__.py @@ -31,7 +31,6 @@ OssSetupResponse, ReadFileResponse, SandboxResponse, - SandboxStatusResponse, UploadResponse, WriteFileResponse, ) @@ -65,7 +64,6 @@ "ReadFileRequest", "UploadRequest", "IsAliveResponse", - "SandboxStatusResponse", "CommandResponse", "WriteFileResponse", "OssSetupResponse", diff --git a/rock/actions/response.py b/rock/actions/response.py index 3fef5afd2..65577ecf7 100644 --- a/rock/actions/response.py +++ b/rock/actions/response.py @@ -20,3 +20,13 @@ class BaseResponse(BaseModel): class RockResponse(BaseResponse, Generic[T]): result: T | None = None + + +class ChownResponse(BaseModel): + success: bool = False + message: str = "" + + +class ChmodResponse(BaseModel): + success: bool = False + message: str = "" diff --git a/rock/actions/sandbox/response.py b/rock/actions/sandbox/response.py index e218b566a..9f59a6752 100644 --- a/rock/actions/sandbox/response.py +++ b/rock/actions/sandbox/response.py @@ -8,7 +8,6 @@ class SandboxResponse(BaseModel): code: codes | None = None - exit_code: int | None = None failure_reason: str | None = None @@ -32,30 +31,13 @@ def __bool__(self) -> bool: return self.is_alive -class SandboxStatusResponse(BaseModel): - sandbox_id: str = None - status: dict = None - port_mapping: dict = None - host_name: str | None = None - host_ip: str | None = None - is_alive: bool = True - image: str | None = None - gateway_version: str | None = None - swe_rex_version: str | None = None - user_id: str | None = None - experiment_id: str | None = None - namespace: str | None = None - cpus: float | None = None - memory: str | None = None - - -class CommandResponse(BaseModel): +class CommandResponse(SandboxResponse): + exit_code: int | None = None stdout: str = "" stderr: str = "" - exit_code: int | None = None -class WriteFileResponse(BaseModel): +class WriteFileResponse(SandboxResponse): success: bool = False message: str = "" @@ -70,7 +52,7 @@ class ExecuteBashSessionResponse(BaseModel): message: str = "" -class CreateBashSessionResponse(BaseModel): +class CreateBashSessionResponse(SandboxResponse): output: str = "" session_type: Literal["bash"] = "bash" @@ -80,18 +62,17 @@ class CreateBashSessionResponse(BaseModel): """Union type for all create session responses. Do not use this directly.""" -class BashObservation(BaseModel): +class BashObservation(SandboxResponse): + exit_code: int | None = None session_type: Literal["bash"] = "bash" output: str = "" - exit_code: int | None = None - failure_reason: str = "" expect_string: str = "" Observation = BashObservation -class CloseBashSessionResponse(BaseModel): +class CloseBashSessionResponse(SandboxResponse): session_type: Literal["bash"] = "bash" @@ -99,12 +80,12 @@ class CloseBashSessionResponse(BaseModel): """Union type for all close session responses. Do not use this directly.""" -class ReadFileResponse(BaseModel): +class ReadFileResponse(SandboxResponse): content: str = "" """Content of the file as a string.""" -class UploadResponse(BaseModel): +class UploadResponse(SandboxResponse): success: bool = False message: str = "" file_name: str = "" @@ -113,17 +94,7 @@ class UploadResponse(BaseModel): FileUploadResponse = UploadResponse -class CloseResponse(BaseModel): +class CloseResponse(SandboxResponse): """Response for close operations.""" pass - - -class ChownResponse(BaseModel): - success: bool = False - message: str = "" - - -class ChmodResponse(BaseModel): - success: bool = False - message: str = "" diff --git a/rock/sdk/sandbox/client.py b/rock/sdk/sandbox/client.py index ed85f7b52..b3c1760e0 100644 --- a/rock/sdk/sandbox/client.py +++ b/rock/sdk/sandbox/client.py @@ -32,12 +32,12 @@ ReadFileRequest, ReadFileResponse, SandboxResponse, - SandboxStatusResponse, UploadRequest, UploadResponse, WriteFileRequest, WriteFileResponse, ) +from rock.admin.proto.response import SandboxStatusResponse from rock.common.constants import PID_PREFIX, PID_SUFFIX from rock.sdk.common.constants import RunModeType from rock.sdk.common.exceptions import ( diff --git a/rock/sdk/sandbox/file_system.py b/rock/sdk/sandbox/file_system.py index 4f219eb9b..65454cedc 100644 --- a/rock/sdk/sandbox/file_system.py +++ b/rock/sdk/sandbox/file_system.py @@ -7,9 +7,10 @@ from pathlib import Path from rock.actions import CreateBashSessionRequest, Observation +from rock.actions.response import ChmodResponse, ChownResponse from rock.actions.sandbox.base import AbstractSandbox from rock.actions.sandbox.request import ChmodRequest, ChownRequest, Command -from rock.actions.sandbox.response import ChmodResponse, ChownResponse, CommandResponse +from rock.actions.sandbox.response import CommandResponse logger = logging.getLogger(__name__) diff --git a/tests/integration/sdk/sandbox/test_file_system.py b/tests/integration/sdk/sandbox/test_file_system.py index 271389b79..ccd75c23a 100644 --- a/tests/integration/sdk/sandbox/test_file_system.py +++ b/tests/integration/sdk/sandbox/test_file_system.py @@ -1,7 +1,8 @@ import logging +from rock.actions.response import ChmodResponse, ChownResponse from rock.actions.sandbox.request import ChmodRequest, ChownRequest, Command, CreateBashSessionRequest -from rock.actions.sandbox.response import ChmodResponse, ChownResponse, CommandResponse, Observation +from rock.actions.sandbox.response import CommandResponse, Observation from rock.sdk.sandbox.client import Sandbox logger = logging.getLogger(__name__) diff --git a/tests/integration/sdk/sandbox/test_sdk_client.py b/tests/integration/sdk/sandbox/test_sdk_client.py index 70bb34e33..f8009601c 100644 --- a/tests/integration/sdk/sandbox/test_sdk_client.py +++ b/tests/integration/sdk/sandbox/test_sdk_client.py @@ -4,7 +4,7 @@ import pytest from rock.actions.sandbox.request import ReadFileRequest -from rock.actions.sandbox.response import SandboxStatusResponse +from rock.admin.proto.response import SandboxStatusResponse from rock.sdk.sandbox.client import Sandbox from rock.sdk.sandbox.config import SandboxConfig from rock.utils.docker import DockerUtil @@ -126,8 +126,6 @@ async def test_execute(sandbox_instance: Sandbox): @SKIP_IF_NO_DOCKER @pytest.mark.asyncio async def test_start_sandbox_upper_limit(sandbox_instance: Sandbox): - from rock.actions import SandboxStatusResponse - status: SandboxStatusResponse = await sandbox_instance.get_status() assert status.cpus == 4 diff --git a/tests/unit/sandbox/test_sandbox_manager.py b/tests/unit/sandbox/test_sandbox_manager.py index fb69a486f..9ca271388 100644 --- a/tests/unit/sandbox/test_sandbox_manager.py +++ b/tests/unit/sandbox/test_sandbox_manager.py @@ -6,8 +6,8 @@ import pytest import ray -from rock.actions import SandboxStatusResponse from rock.actions.sandbox.response import State +from rock.admin.proto.response import SandboxStatusResponse from rock.deployments.config import DockerDeploymentConfig, RayDeploymentConfig from rock.deployments.constants import Port from rock.deployments.status import ServiceStatus diff --git a/tests/unit/sdk/test_arun_nohup.py b/tests/unit/sdk/test_arun_nohup.py index 6c301e475..f519d6c2e 100644 --- a/tests/unit/sdk/test_arun_nohup.py +++ b/tests/unit/sdk/test_arun_nohup.py @@ -4,8 +4,7 @@ from httpx import ReadTimeout from rock.actions.sandbox.response import Observation -from rock.common.constants import PID_PREFIX -from rock.common.constants import PID_SUFFIX +from rock.common.constants import PID_PREFIX, PID_SUFFIX from rock.sdk.sandbox.client import Sandbox from rock.sdk.sandbox.config import SandboxConfig @@ -42,7 +41,7 @@ async def fake_wait(self, pid, session, wait_timeout, wait_interval): ) assert result.exit_code == 0 - assert result.failure_reason == "" + assert result.failure_reason is None assert "/tmp/tmp_1701.out" in result.output assert "without streaming the log content" in result.output assert "File size: 2.00 KB" in result.output diff --git a/tests/unit/test_sandbox_response.py b/tests/unit/test_sandbox_response.py new file mode 100644 index 000000000..ef5cd4cc3 --- /dev/null +++ b/tests/unit/test_sandbox_response.py @@ -0,0 +1,33 @@ +from rock.actions.sandbox.response import ( + BashObservation, + CloseBashSessionResponse, + CommandResponse, + CreateBashSessionResponse, + ReadFileResponse, + UploadResponse, + WriteFileResponse, +) + + +class TestResponseContainsCodeAndFailureReason: + """各 result Response 序列化后应包含 code 和 failure_reason 字段""" + + RESPONSE_CLASSES = [ + CommandResponse, + BashObservation, + CreateBashSessionResponse, + CloseBashSessionResponse, + ReadFileResponse, + WriteFileResponse, + UploadResponse, + ] + + def test_all_responses_have_code_field(self): + for cls in self.RESPONSE_CLASSES: + data = cls().model_dump() + assert "code" in data, f"{cls.__name__} missing 'code'" + + def test_all_responses_have_failure_reason_field(self): + for cls in self.RESPONSE_CLASSES: + data = cls().model_dump() + assert "failure_reason" in data, f"{cls.__name__} missing 'failure_reason'"