Skip to content
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8f05050
fix private dataset accessible issue
May 7, 2026
533a04a
Fix lint
wangq8 May 8, 2026
835e024
fix: prepend bucket prefix to Azure SPN and SAS storage paths (#14185)
voidborne-d May 7, 2026
879d7a6
Feat: support local provider for code exec component & remove some ou…
Magicbook1108 May 7, 2026
eba562d
Fix: Change route name (#14639)
dcc123456 May 7, 2026
a101192
Perf: push metadata filters down to Elasticsearch (#14576)
sxxtony May 7, 2026
e19b0fc
feat: update Turkish localization strings (#14650)
bakiburakogun May 8, 2026
c9d20a7
fix(go): wire CheckConnection to ListModels in ollama, lm-studio, and…
pandadev66 May 8, 2026
a8bd743
Go: implement Balance in SiliconFlow driver (#14643)
pandadev66 May 8, 2026
c814445
Go: implement provider: OpenRouter (#14652)
Haruko386 May 8, 2026
fb369dd
Go: implement Balance in DeepSeek driver (#14632)
pandadev66 May 8, 2026
d773886
fix(go): implement ListModels and CheckConnection in NVIDIA driver (#…
pandadev66 May 8, 2026
d3b3d4d
fix: add bucket prefix to Azure Blob SPN and SAS storage operations (…
D2758695161 May 8, 2026
e21c2f9
Go: implement Rerank in Gitee AI driver (#14656)
pandadev66 May 8, 2026
225afc2
Fix: display error (#14654)
Lynn-Inf May 8, 2026
6ec2d16
Refa: migrate document preview/download to RESTful API (#14633)
buua436 May 8, 2026
40e73cb
Fix: missing authorization checks in `/files/link-to-datasets` (#14649)
jony376 May 8, 2026
2c165a2
Fix cli login (#14658)
JinHai-CN May 8, 2026
29ddf09
Go: implement remaining interface for OpenRouter (#14657)
Haruko386 May 8, 2026
5e7dc71
Go: implement Encode (embeddings) in Aliyun driver (#14647)
pandadev66 May 8, 2026
011295f
Fix: enforce tenant authorization on document download endpoint (#146…
May 8, 2026
f1c80c7
Fix: collapsible thinking display and separate deep research retrieva…
wanghualoong May 8, 2026
d9dcbc5
Fix: add compatibility route for document download under /v1 (#14663)
buua436 May 8, 2026
2358f9a
Fix: path-aware reset in canvas.run() to preserve cross-run outputs (…
wanghualoong May 8, 2026
4410f9d
Fix: type of tenant_rerank_id (#14667)
Lynn-Inf May 8, 2026
b3ebffb
Go CLI: fix register user (#14665)
JinHai-CN May 8, 2026
0940df9
Fix(Go): prevent global state pollution in local model connection che…
Haruko386 May 8, 2026
eac58f0
Go: fix CLI logout command (#14672)
JinHai-CN May 8, 2026
fe8216c
Fix: handle null document_metadata in kb_prompt to prevent citation c…
May 8, 2026
c31ac43
Do not bypass threshold for rerank when metadata filter is enabled (#…
qinling0210 May 8, 2026
0069515
Fix: move file check (#14681)
Lynn-Inf May 8, 2026
f9d4a35
fix: enforce tenant-scoped authorization for chatbot SDK endpoints (#…
dale053 May 8, 2026
0b16cf2
Refactor : Allow search multiple datasets (#14685)
wangq8 May 8, 2026
b1f15d5
fix test failure issue
May 8, 2026
aaa0f30
Merge branch 'main' into fix/private-datasets-remain-accessible-to-te…
May 8, 2026
1ed5cf9
Merge branch 'main' into fix/private-datasets-remain-accessible-to-te…
May 8, 2026
509c6de
Merge branch 'main' into fix/private-datasets-remain-accessible-to-te…
May 8, 2026
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
4 changes: 3 additions & 1 deletion agent/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,10 @@ async def run(self, **kwargs):
self.message_id = get_uuid()
created_at = int(time.time())
self.add_user_input(kwargs.get("query"))
path_set = set(self.path)
for k, cpn in self.components.items():
self.components[k]["obj"].reset(True)
if k in path_set:
self.components[k]["obj"].reset(True)

if kwargs.get("webhook_payload"):
for k, cpn in self.components.items():
Expand Down
113 changes: 89 additions & 24 deletions agent/sandbox/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@

import json
import logging
import os
from typing import Dict, Any, Optional

from api.db.services.system_settings_service import SystemSettingsService
from agent.sandbox.providers import ProviderManager
from agent.sandbox.providers.base import ExecutionResult
from agent.sandbox.providers.base import ExecutionResult, SandboxProviderConfigError

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -59,50 +60,36 @@ def _load_provider_from_settings() -> None:
"""
Load sandbox provider from system settings and configure the provider manager.

This function reads the system settings to determine which provider is active
and initializes it with the appropriate configuration.
This function resolves the active provider type, then loads configuration
from system settings with environment overrides for that provider.
"""
global _provider_manager

if _provider_manager is None:
return

try:
# Get active provider type
provider_type_settings = SystemSettingsService.get_by_name("sandbox.provider_type")
if not provider_type_settings:
raise RuntimeError(
"Sandbox provider type not configured. Please set 'sandbox.provider_type' in system settings."
)
provider_type = provider_type_settings[0].value

# Get provider configuration
provider_config_settings = SystemSettingsService.get_by_name(f"sandbox.{provider_type}")

if not provider_config_settings:
logger.warning(f"No configuration found for provider: {provider_type}")
config = {}
else:
try:
config = json.loads(provider_config_settings[0].value)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse sandbox config for {provider_type}: {e}")
config = {}
provider_type, provider_type_from_env = _resolve_provider_type()
config = _load_provider_config(provider_type)

# Import and instantiate the provider
from agent.sandbox.providers import (
SelfManagedProvider,
AliyunCodeInterpreterProvider,
E2BProvider,
LocalProvider,
)

provider_classes = {
"self_managed": SelfManagedProvider,
"aliyun_codeinterpreter": AliyunCodeInterpreterProvider,
"e2b": E2BProvider,
"local": LocalProvider,
}

if provider_type not in provider_classes:
if provider_type_from_env:
raise SandboxProviderConfigError(f"Unknown sandbox provider type: {provider_type}")
logger.error(f"Unknown provider type: {provider_type}")
return

Expand All @@ -111,19 +98,97 @@ def _load_provider_from_settings() -> None:

# Initialize the provider
if not provider.initialize(config):
logger.error(f"Failed to initialize sandbox provider: {provider_type}. Config keys: {list(config.keys())}")
message = f"Failed to initialize sandbox provider: {provider_type}. Config keys: {list(config.keys())}"
if provider_type == "local" or provider_type_from_env:
raise SandboxProviderConfigError(message)
logger.error(message)
return

# Set the active provider
_provider_manager.set_provider(provider_type, provider)
logger.info(f"Sandbox provider '{provider_type}' initialized successfully")

except SandboxProviderConfigError:
raise
except Exception as e:
logger.error(f"Failed to load sandbox provider from settings: {e}")
import traceback
traceback.print_exc()


def _load_provider_config_from_settings(provider_type: str) -> Dict[str, Any]:
provider_config_settings = SystemSettingsService.get_by_name(f"sandbox.{provider_type}")
if not provider_config_settings:
logger.warning(f"No configuration found for provider: {provider_type}")
return {}

try:
return json.loads(provider_config_settings[0].value)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse sandbox config for {provider_type}: {e}")
return {}


def _resolve_provider_type() -> tuple[str, bool]:
provider_type = os.environ.get("SANDBOX_PROVIDER_TYPE", "").strip()
if provider_type:
return provider_type, True

provider_type_settings = SystemSettingsService.get_by_name("sandbox.provider_type")
if not provider_type_settings:
raise RuntimeError(
"Sandbox provider type not configured. Please set 'sandbox.provider_type' in system settings."
)
return provider_type_settings[0].value, False


def _load_provider_config(provider_type: str) -> Dict[str, Any]:
config = _load_provider_config_from_settings(provider_type)
env_config = _load_provider_config_from_env(provider_type)
if env_config:
config.update(env_config)
return config


def _load_provider_config_from_env(provider_type: str) -> Dict[str, Any]:
if provider_type == "local":
return _load_local_provider_config_from_env()
if provider_type == "self_managed":
return _load_self_managed_provider_config_from_env()
return {}


def _load_local_provider_config_from_env() -> Dict[str, Any]:
env_to_config = {
"SANDBOX_LOCAL_PYTHON_BIN": "python_bin",
"SANDBOX_LOCAL_NODE_BIN": "node_bin",
"SANDBOX_LOCAL_WORK_DIR": "work_dir",
"SANDBOX_LOCAL_TIMEOUT": "timeout",
"SANDBOX_LOCAL_MAX_MEMORY_MB": "max_memory_mb",
"SANDBOX_LOCAL_MAX_OUTPUT_BYTES": "max_output_bytes",
"SANDBOX_LOCAL_MAX_ARTIFACTS": "max_artifacts",
"SANDBOX_LOCAL_MAX_ARTIFACT_BYTES": "max_artifact_bytes",
}
config = {}
for env_name, config_name in env_to_config.items():
if env_name in os.environ:
config[config_name] = os.environ[env_name]
return config


def _load_self_managed_provider_config_from_env() -> Dict[str, Any]:
host = os.environ.get("SANDBOX_HOST", "").strip()
port = os.environ.get("SANDBOX_EXECUTOR_MANAGER_PORT", "").strip()
pool_size = os.environ.get("SANDBOX_EXECUTOR_MANAGER_POOL_SIZE", "").strip()

config = {}
if host:
config["endpoint"] = f"http://{host}:{port or '9385'}"
if pool_size:
config["pool_size"] = pool_size
return config


def reload_provider() -> None:
"""
Reload the sandbox provider from system settings.
Expand Down
6 changes: 5 additions & 1 deletion agent/sandbox/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,24 @@
- aliyun_codeinterpreter.py: Aliyun Code Interpreter provider implementation
Official Documentation: https://help.aliyun.com/zh/functioncompute/fc/sandbox-sandbox-code-interepreter
- e2b.py: E2B provider implementation
- local.py: Local process provider implementation
"""

from .base import SandboxProvider, SandboxInstance, ExecutionResult
from .base import SandboxProvider, SandboxInstance, ExecutionResult, SandboxProviderConfigError
from .manager import ProviderManager
from .self_managed import SelfManagedProvider
from .aliyun_codeinterpreter import AliyunCodeInterpreterProvider
from .e2b import E2BProvider
from .local import LocalProvider

__all__ = [
"SandboxProvider",
"SandboxInstance",
"ExecutionResult",
"SandboxProviderConfigError",
"ProviderManager",
"SelfManagedProvider",
"AliyunCodeInterpreterProvider",
"E2BProvider",
"LocalProvider",
]
74 changes: 4 additions & 70 deletions agent/sandbox/providers/aliyun_codeinterpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import logging
import os
import time
import base64
import json
from typing import Dict, Any, List, Optional
from datetime import datetime, timezone
Expand All @@ -39,10 +38,10 @@
from agentrun.utils.config import Config
from agentrun.utils.exception import ServerError

from agent.sandbox.result_protocol import build_javascript_wrapper, build_python_wrapper, extract_structured_result
from .base import SandboxProvider, SandboxInstance, ExecutionResult

logger = logging.getLogger(__name__)
RESULT_MARKER_PREFIX = "__RAGFLOW_RESULT__:"


class AliyunCodeInterpreterProvider(SandboxProvider):
Expand Down Expand Up @@ -234,9 +233,9 @@ def execute_code(self, instance_id: str, code: str, language: str, timeout: int
# Matches self_managed provider behavior: call main(**arguments)
args_json = json.dumps(arguments or {})
wrapped_code = (
self._build_python_wrapper(code, args_json)
build_python_wrapper(code, args_json)
if normalized_lang == "python"
else self._build_javascript_wrapper(code, args_json)
else build_javascript_wrapper(code, args_json)
)
logger.debug(f"Aliyun Code Interpreter: Wrapped code (first 200 chars): {wrapped_code[:200]}")

Expand Down Expand Up @@ -284,7 +283,7 @@ def execute_code(self, instance_id: str, code: str, language: str, timeout: int

stdout = "\n".join(stdout_parts)
stderr = "\n".join(stderr_parts)
stdout, structured_result = self._extract_structured_result(stdout)
stdout, structured_result = extract_structured_result(stdout)

logger.info(f"Aliyun Code Interpreter: stdout length={len(stdout)}, stderr length={len(stderr)}, exit_code={exit_code}")
if stdout:
Expand Down Expand Up @@ -364,71 +363,6 @@ def health_check(self) -> bool:
# If we get any response (even an error), the service is reachable
return "connection" not in str(e).lower()

@staticmethod
def _build_python_wrapper(code: str, args_json: str) -> str:
marker = RESULT_MARKER_PREFIX
return f'''{code}

if __name__ == "__main__":
import base64
import json

result = main(**{args_json})
payload = json.dumps({{"present": True, "value": result, "type": "json"}}, ensure_ascii=False, separators=(",", ":"))
print("{marker}" + base64.b64encode(payload.encode("utf-8")).decode("ascii"))
'''

@staticmethod
def _build_javascript_wrapper(code: str, args_json: str) -> str:
marker = RESULT_MARKER_PREFIX
return f'''{code}

const __ragflowArgs = {args_json};

(async () => {{
try {{
const output = await Promise.resolve(main(__ragflowArgs));
if (typeof output === 'undefined') {{
throw new Error('main() must return a value. Use null for an empty result.');
}}
const payload = JSON.stringify({{ present: true, value: output, type: 'json' }});
if (typeof payload === 'undefined') {{
throw new Error('main() returned a non-JSON-serializable value.');
}}
console.log('{marker}' + Buffer.from(payload, 'utf8').toString('base64'));
}} catch (err) {{
console.error(err instanceof Error ? err.stack || err.message : String(err));
}}
}})();
'''

@staticmethod
def _extract_structured_result(stdout: str) -> tuple[str, Dict[str, Any]]:
if not stdout:
return "", {}

cleaned_lines: list[str] = []
structured_result: Dict[str, Any] = {}

for line in str(stdout).splitlines():
if line.startswith(RESULT_MARKER_PREFIX):
payload_b64 = line[len(RESULT_MARKER_PREFIX) :].strip()
if not payload_b64:
continue
try:
payload = base64.b64decode(payload_b64).decode("utf-8")
structured_result = json.loads(payload)
except Exception as exc:
logger.warning(f"Aliyun Code Interpreter: failed to decode structured result marker: {exc}")
cleaned_lines.append(line)
continue
cleaned_lines.append(line)

cleaned_stdout = "\n".join(cleaned_lines)
if stdout.endswith("\n") and cleaned_stdout and not cleaned_stdout.endswith("\n"):
cleaned_stdout += "\n"
return cleaned_stdout, structured_result

def get_supported_languages(self) -> List[str]:
"""
Get list of supported programming languages.
Expand Down
6 changes: 5 additions & 1 deletion agent/sandbox/providers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
from typing import Dict, Any, Optional, List


class SandboxProviderConfigError(Exception):
"""Raised when the selected provider is explicitly configured but unusable."""


@dataclass
class SandboxInstance:
"""Represents a sandbox execution instance"""
Expand Down Expand Up @@ -209,4 +213,4 @@ def validate_config(self, config: Dict[str, Any]) -> tuple[bool, Optional[str]]:
>>> return True, None
"""
# Default implementation: no custom validation
return True, None
return True, None
Loading
Loading