mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-13 04:13:35 +08:00
fix(sandbox): update Tenki SDKs and drop removed project_id (#18117)
### Summary Brings both halves of the Tenki sandbox provider onto current SDKs and removes `project_id`, which Tenki deleted from its API. **Go:** `github.com/LuxorLabs/tenki-sdk-go/sandbox` `v0.5.2` → `v0.7.0` (current latest). **Python:** the provider's SDK was renamed on PyPI — `tenki-sandbox` is frozen at 0.4.0 and everything from 0.5 ships as [`tenki`](https://pypi.org/project/tenki/). The docs told operators to `pip install tenki-sandbox`, which installs a stale SDK that no longer matches this provider's expectations. **`project_id` is gone.** Tenki removed project scoping from the sandbox API in 0.5.x: `Client.create()` no longer accepts `project_id`, so the current code path would raise `TypeError` against a current SDK. It was also marked `required: True` in the config schema, so the Admin > Sandbox Settings form asked for a value that no longer exists.
This commit is contained in:
@@ -71,7 +71,6 @@ class TenkiProvider(SandboxProvider):
|
||||
|
||||
def __init__(self):
|
||||
self.api_key = ""
|
||||
self.project_id = ""
|
||||
self.base_url = ""
|
||||
self.image = ""
|
||||
self.allow_outbound = False
|
||||
@@ -89,7 +88,6 @@ class TenkiProvider(SandboxProvider):
|
||||
|
||||
def initialize(self, config: Dict[str, Any]) -> bool:
|
||||
self.api_key = str(config.get("api_key", "") or "").strip()
|
||||
self.project_id = str(config.get("project_id", "") or "").strip()
|
||||
self.base_url = str(config.get("base_url", "") or "").strip()
|
||||
self.image = str(config.get("image", "") or "").strip()
|
||||
self.allow_outbound = bool(config.get("allow_outbound", False))
|
||||
@@ -105,7 +103,6 @@ class TenkiProvider(SandboxProvider):
|
||||
is_valid, error_message = self.validate_config(
|
||||
{
|
||||
"api_key": self.api_key,
|
||||
"project_id": self.project_id,
|
||||
"timeout": self.timeout,
|
||||
"max_lifetime": self.max_lifetime,
|
||||
"max_output_bytes": self.max_output_bytes,
|
||||
@@ -131,7 +128,6 @@ class TenkiProvider(SandboxProvider):
|
||||
errors = self._tenki_errors()
|
||||
|
||||
create_kwargs: dict[str, Any] = {
|
||||
"project_id": self.project_id,
|
||||
"allow_outbound": self.allow_outbound,
|
||||
"max_duration": self.max_lifetime,
|
||||
"metadata": {"source": "ragflow"},
|
||||
@@ -285,13 +281,6 @@ class TenkiProvider(SandboxProvider):
|
||||
"placeholder": "tk_...",
|
||||
"description": "Tenki API key. Create one at https://app.tenki.cloud under API Keys.",
|
||||
},
|
||||
"project_id": {
|
||||
"type": "string",
|
||||
"required": True,
|
||||
"label": "Project ID",
|
||||
"placeholder": "Tenki project UUID",
|
||||
"description": "Tenki project that sandboxes are created under.",
|
||||
},
|
||||
"base_url": {
|
||||
"type": "string",
|
||||
"required": False,
|
||||
@@ -388,12 +377,9 @@ class TenkiProvider(SandboxProvider):
|
||||
|
||||
def validate_config(self, config: Dict[str, Any]) -> tuple[bool, Optional[str]]:
|
||||
api_key = str(config.get("api_key", "") or "").strip()
|
||||
project_id = str(config.get("project_id", "") or "").strip()
|
||||
|
||||
if not api_key:
|
||||
return False, "Tenki API key is required"
|
||||
if not project_id:
|
||||
return False, "Tenki project_id is required"
|
||||
|
||||
for key in ("timeout", "max_lifetime", "max_output_bytes", "max_artifacts", "max_artifact_bytes"):
|
||||
try:
|
||||
@@ -528,8 +514,8 @@ def _get_tenki_module():
|
||||
try:
|
||||
import tenki_sandbox
|
||||
except ImportError as exc:
|
||||
# tenki-sandbox is an optional dependency: it requires protobuf>=6.31,
|
||||
# tenki is an optional dependency: it requires protobuf>=6.31,
|
||||
# which conflicts with RAGFlow's pinned gRPC stack, so it is not a core
|
||||
# dependency. Install it into the runtime to enable this provider.
|
||||
raise SandboxProviderConfigError("tenki-sandbox is required for the Tenki sandbox provider. Install it with `pip install tenki-sandbox` (or `uv pip install tenki-sandbox`).") from exc
|
||||
raise SandboxProviderConfigError("tenki is required for the Tenki sandbox provider. Install it with `pip install tenki` (or `uv pip install tenki`).") from exc
|
||||
return tenki_sandbox
|
||||
|
||||
@@ -45,16 +45,15 @@ Admin > Sandbox Settings after the services are up.
|
||||
|
||||
`tenki` runs each code execution in a fresh Tenki microVM and destroys it afterwards. It is cloud-hosted, so it needs no local sandbox services, gVisor, or Docker base images — only outbound network access and an API key.
|
||||
|
||||
The `tenki-sandbox` SDK is an optional dependency (it requires `protobuf>=6.31`, which differs from RAGFlow's default gRPC stack), so it is not installed by default. Install it into the RAGFlow runtime before selecting this provider:
|
||||
The `tenki` SDK is an optional dependency (it requires `protobuf>=6.31`, which differs from RAGFlow's default gRPC stack), so it is not installed by default. Install it into the RAGFlow runtime before selecting this provider:
|
||||
|
||||
```bash
|
||||
pip install tenki-sandbox
|
||||
pip install tenki
|
||||
```
|
||||
|
||||
Configure it in **Admin > Sandbox Settings**:
|
||||
|
||||
- `api_key` (required): Tenki API key. Create one at [app.tenki.cloud](https://app.tenki.cloud) under **API Keys**.
|
||||
- `project_id` (required): the Tenki project that sandboxes are created under.
|
||||
- `base_url` (optional): override the Tenki API endpoint.
|
||||
- `image` (optional): sandbox base image. Leave empty to use the Tenki default image, which includes `python3` and `node`.
|
||||
- `allow_outbound` (optional, security-relevant): whether the sandbox may make outbound network connections. Defaults to `false` so sandboxed code has no network access; set it to `true` when code needs the network (for example, to install packages).
|
||||
|
||||
2
go.mod
2
go.mod
@@ -5,7 +5,7 @@ go 1.26.4
|
||||
require (
|
||||
cloud.google.com/go/storage v1.63.0
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
github.com/LuxorLabs/tenki-sdk-go/sandbox v0.5.2
|
||||
github.com/LuxorLabs/tenki-sdk-go/sandbox v0.7.0
|
||||
github.com/alibabacloud-go/agentrun-20250910/v5 v5.8.4
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.2.1
|
||||
github.com/alicebob/miniredis/v2 v2.38.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -39,8 +39,8 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.57.0/go.mod h1:dzcEjy1WJ0Q4u9twNR3LcLhNoYMRCrMCMafpxa0TjPQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 h1:RoO5+d7uCmDqovLrHCr2/BuViUXvdcrNxyNM1pN9dDQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0/go.mod h1:YqwkQPrWSC7+byyc1VlKbWLBF5JsW5IoL6xUkemYSXk=
|
||||
github.com/LuxorLabs/tenki-sdk-go/sandbox v0.5.2 h1:HN2UwfNhHYCT7qmJJ5KKf+erRoH5RTfGb7N8dEbE9FQ=
|
||||
github.com/LuxorLabs/tenki-sdk-go/sandbox v0.5.2/go.mod h1:rBypesed6hSrj+OgpJwyMVBF76PaQ1iOqc2RGUL4RmI=
|
||||
github.com/LuxorLabs/tenki-sdk-go/sandbox v0.7.0 h1:PBQ+ad8fZiLH/1ij1hoynR9aAPVmI3EzT7DiLNmUdBM=
|
||||
github.com/LuxorLabs/tenki-sdk-go/sandbox v0.7.0/go.mod h1:rBypesed6hSrj+OgpJwyMVBF76PaQ1iOqc2RGUL4RmI=
|
||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||
github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o=
|
||||
github.com/alibabacloud-go/agentrun-20250910/v5 v5.8.4 h1:hiAsm9pz6aICOPLI1FC54vga10xwd/XxfNj06ow5jVM=
|
||||
|
||||
@@ -125,7 +125,6 @@ class _FakeClient:
|
||||
def _build_provider(sandbox: _FakeSandbox, monkeypatch) -> tuple[TenkiProvider, _FakeClient]:
|
||||
provider = TenkiProvider()
|
||||
provider.api_key = "tk_test"
|
||||
provider.project_id = "proj-1"
|
||||
provider.timeout = 30
|
||||
provider.max_output_bytes = 1024 * 1024
|
||||
provider.max_artifacts = 20
|
||||
@@ -227,7 +226,7 @@ def test_tenki_provider_create_passes_project_and_outbound(monkeypatch):
|
||||
|
||||
provider.create_instance("python")
|
||||
|
||||
assert client.create_kwargs["project_id"] == "proj-1"
|
||||
assert "project_id" not in client.create_kwargs
|
||||
assert client.create_kwargs["allow_outbound"] is True
|
||||
assert client.create_kwargs["image"] == "my-image"
|
||||
assert client.create_kwargs["cpu_cores"] == 4
|
||||
@@ -237,12 +236,12 @@ def test_tenki_provider_config_schema_and_validation():
|
||||
schema = TenkiProvider.get_config_schema()
|
||||
assert schema["api_key"]["required"] is True
|
||||
assert schema["api_key"]["secret"] is True
|
||||
assert schema["project_id"]["required"] is True
|
||||
assert "project_id" not in schema
|
||||
|
||||
provider = TenkiProvider()
|
||||
ok, _ = provider.validate_config({"api_key": "tk", "project_id": "p", "timeout": 30, "max_lifetime": 3600, "max_output_bytes": 1024, "max_artifacts": 5, "max_artifact_bytes": 1024})
|
||||
ok, _ = provider.validate_config({"api_key": "tk", "timeout": 30, "max_lifetime": 3600, "max_output_bytes": 1024, "max_artifacts": 5, "max_artifact_bytes": 1024})
|
||||
assert ok is True
|
||||
bad, msg = provider.validate_config({"api_key": "", "project_id": "p"})
|
||||
bad, msg = provider.validate_config({"api_key": ""})
|
||||
assert bad is False
|
||||
assert "API key" in msg
|
||||
|
||||
@@ -262,7 +261,7 @@ def test_tenki_provider_initialize_maps_auth_error(monkeypatch):
|
||||
monkeypatch.setattr(provider, "_create_client", lambda: _UnauthorizedClient())
|
||||
|
||||
with pytest.raises(SandboxProviderConfigError, match="authentication failed"):
|
||||
provider.initialize({"api_key": "tk_bad", "project_id": "proj-1"})
|
||||
provider.initialize({"api_key": "tk_bad"})
|
||||
|
||||
|
||||
def test_tenki_provider_instances_are_independent(monkeypatch):
|
||||
@@ -277,7 +276,6 @@ def test_tenki_provider_instances_are_independent(monkeypatch):
|
||||
|
||||
provider = TenkiProvider()
|
||||
provider.api_key = "tk_test"
|
||||
provider.project_id = "proj-1"
|
||||
provider.timeout = 30
|
||||
provider._initialized = True
|
||||
provider._client = _MultiClient(None)
|
||||
|
||||
Reference in New Issue
Block a user