mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-19 04:14:46 +08:00
fix: fix local path for local sandbox (#3)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,3 +23,4 @@ config.yaml
|
|||||||
# Coverage report
|
# Coverage report
|
||||||
coverage.xml
|
coverage.xml
|
||||||
coverage/
|
coverage/
|
||||||
|
.deer-flow/
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
MOUNT_POINT = os.path.expanduser("~/mnt")
|
|
||||||
|
|
||||||
SYSTEM_PROMPT = f"""
|
SYSTEM_PROMPT = f"""
|
||||||
<role>
|
<role>
|
||||||
You are DeerFlow 2.0, an open-source super agent.
|
You are DeerFlow 2.0, an open-source super agent.
|
||||||
@@ -17,7 +15,7 @@ You are DeerFlow 2.0, an open-source super agent.
|
|||||||
You have access to skills that provide optimized workflows for specific tasks. Each skill contains best practices, frameworks, and references to additional resources.
|
You have access to skills that provide optimized workflows for specific tasks. Each skill contains best practices, frameworks, and references to additional resources.
|
||||||
|
|
||||||
**Progressive Loading Pattern:**
|
**Progressive Loading Pattern:**
|
||||||
1. When a user query matches a skill's use case, immediately call `view` on the skill's main file located at `{MOUNT_POINT}/skills/{"{skill_name}"}/SKILL.md`
|
1. When a user query matches a skill's use case, immediately call `view` on the skill's main file located at `/mnt/skills/{"{skill_name}"}/SKILL.md`
|
||||||
2. Read and understand the skill's workflow and instructions
|
2. Read and understand the skill's workflow and instructions
|
||||||
3. The skill file contains references to external resources under the same folder
|
3. The skill file contains references to external resources under the same folder
|
||||||
4. Load referenced resources only when needed during execution
|
4. Load referenced resources only when needed during execution
|
||||||
@@ -35,12 +33,12 @@ Extract text, fill forms, merge PDFs (pypdf, pdfplumber)
|
|||||||
</skill_system>
|
</skill_system>
|
||||||
|
|
||||||
<working_directory existed="true">
|
<working_directory existed="true">
|
||||||
- User uploads: `{MOUNT_POINT}/user-data/uploads`
|
- User uploads: `/mnt/user-data/uploads`
|
||||||
- User workspace: `{MOUNT_POINT}/user-data/workspace`
|
- User workspace: `/mnt/user-data/workspace`
|
||||||
- subagents: `{MOUNT_POINT}/user-data/workspace/subagents`
|
- subagents: `/mnt/user-data/workspace/subagents`
|
||||||
- Output files: `{MOUNT_POINT}/user-data/outputs`
|
- Output files: `/mnt/user-data/outputs`
|
||||||
|
|
||||||
All temporary work happens in `{MOUNT_POINT}/user-data/workspace`. Final deliverables must be copied to `{MOUNT_POINT}/user-data/outputs`.
|
All temporary work happens in `/mnt/user-data/workspace`. Final deliverables must be copied to `/mnt/user-data/outputs`.
|
||||||
</working_directory>
|
</working_directory>
|
||||||
|
|
||||||
<response_style>
|
<response_style>
|
||||||
@@ -58,7 +56,7 @@ All temporary work happens in `{MOUNT_POINT}/user-data/workspace`. Final deliver
|
|||||||
<critical_reminders>
|
<critical_reminders>
|
||||||
- Skill First: Always load the relevant skill before starting **complex** tasks.
|
- Skill First: Always load the relevant skill before starting **complex** tasks.
|
||||||
- Progressive Loading: Load resources incrementally as referenced in skills
|
- Progressive Loading: Load resources incrementally as referenced in skills
|
||||||
- Output Files: Final deliverables must be in `{MOUNT_POINT}/user-data/outputs`
|
- Output Files: Final deliverables must be in `/mnt/user-data/outputs`
|
||||||
- Clarity: Be direct and helpful, avoid unnecessary meta-commentary
|
- Clarity: Be direct and helpful, avoid unnecessary meta-commentary
|
||||||
- Multi-task: Better utilize parallel tool calling to call multiple tools at one time for better performance
|
- Multi-task: Better utilize parallel tool calling to call multiple tools at one time for better performance
|
||||||
- Language Consistency: Keep using the same language as user's
|
- Language Consistency: Keep using the same language as user's
|
||||||
|
|||||||
@@ -1,10 +1,109 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
from langchain.tools import ToolRuntime, tool
|
from langchain.tools import ToolRuntime, tool
|
||||||
from langgraph.typing import ContextT
|
from langgraph.typing import ContextT
|
||||||
|
|
||||||
from src.agents.thread_state import ThreadState
|
from src.agents.thread_state import ThreadDataState, ThreadState
|
||||||
from src.sandbox.sandbox import Sandbox
|
from src.sandbox.sandbox import Sandbox
|
||||||
from src.sandbox.sandbox_provider import get_sandbox_provider
|
from src.sandbox.sandbox_provider import get_sandbox_provider
|
||||||
|
|
||||||
|
# Virtual path prefix used in sandbox environments
|
||||||
|
VIRTUAL_PATH_PREFIX = "/mnt/user-data"
|
||||||
|
|
||||||
|
|
||||||
|
def replace_virtual_path(path: str, thread_data: ThreadDataState | None) -> str:
|
||||||
|
"""Replace virtual /mnt/user-data paths with actual thread data paths.
|
||||||
|
|
||||||
|
Mapping:
|
||||||
|
/mnt/user-data/workspace/* -> thread_data['workspace_path']/*
|
||||||
|
/mnt/user-data/uploads/* -> thread_data['uploads_path']/*
|
||||||
|
/mnt/user-data/outputs/* -> thread_data['outputs_path']/*
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: The path that may contain virtual path prefix.
|
||||||
|
thread_data: The thread data containing actual paths.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The path with virtual prefix replaced by actual path.
|
||||||
|
"""
|
||||||
|
if not path.startswith(VIRTUAL_PATH_PREFIX):
|
||||||
|
return path
|
||||||
|
|
||||||
|
if thread_data is None:
|
||||||
|
return path
|
||||||
|
|
||||||
|
# Map virtual subdirectories to thread_data keys
|
||||||
|
path_mapping = {
|
||||||
|
"workspace": thread_data.get("workspace_path"),
|
||||||
|
"uploads": thread_data.get("uploads_path"),
|
||||||
|
"outputs": thread_data.get("outputs_path"),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract the subdirectory after /mnt/user-data/
|
||||||
|
relative_path = path[len(VIRTUAL_PATH_PREFIX) :].lstrip("/")
|
||||||
|
if not relative_path:
|
||||||
|
return path
|
||||||
|
|
||||||
|
# Find which subdirectory this path belongs to
|
||||||
|
parts = relative_path.split("/", 1)
|
||||||
|
subdir = parts[0]
|
||||||
|
rest = parts[1] if len(parts) > 1 else ""
|
||||||
|
|
||||||
|
actual_base = path_mapping.get(subdir)
|
||||||
|
if actual_base is None:
|
||||||
|
return path
|
||||||
|
|
||||||
|
if rest:
|
||||||
|
return f"{actual_base}/{rest}"
|
||||||
|
return actual_base
|
||||||
|
|
||||||
|
|
||||||
|
def replace_virtual_paths_in_command(command: str, thread_data: ThreadDataState | None) -> str:
|
||||||
|
"""Replace all virtual /mnt/user-data paths in a command string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: The command string that may contain virtual paths.
|
||||||
|
thread_data: The thread data containing actual paths.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The command with all virtual paths replaced.
|
||||||
|
"""
|
||||||
|
if VIRTUAL_PATH_PREFIX not in command:
|
||||||
|
return command
|
||||||
|
|
||||||
|
if thread_data is None:
|
||||||
|
return command
|
||||||
|
|
||||||
|
# Pattern to match /mnt/user-data followed by path characters
|
||||||
|
pattern = re.compile(rf"{re.escape(VIRTUAL_PATH_PREFIX)}(/[^\s\"';&|<>()]*)?")
|
||||||
|
|
||||||
|
def replace_match(match: re.Match) -> str:
|
||||||
|
full_path = match.group(0)
|
||||||
|
return replace_virtual_path(full_path, thread_data)
|
||||||
|
|
||||||
|
return pattern.sub(replace_match, command)
|
||||||
|
|
||||||
|
|
||||||
|
def get_thread_data(runtime: ToolRuntime[ContextT, ThreadState] | None) -> ThreadDataState | None:
|
||||||
|
"""Extract thread_data from runtime state."""
|
||||||
|
if runtime is None:
|
||||||
|
return None
|
||||||
|
return runtime.state.get("thread_data")
|
||||||
|
|
||||||
|
|
||||||
|
def is_local_sandbox(runtime: ToolRuntime[ContextT, ThreadState] | None) -> bool:
|
||||||
|
"""Check if the current sandbox is a local sandbox.
|
||||||
|
|
||||||
|
Path replacement is only needed for local sandbox since aio sandbox
|
||||||
|
already has /mnt/user-data mounted in the container.
|
||||||
|
"""
|
||||||
|
if runtime is None:
|
||||||
|
return False
|
||||||
|
sandbox_state = runtime.state.get("sandbox")
|
||||||
|
if sandbox_state is None:
|
||||||
|
return False
|
||||||
|
return sandbox_state.get("sandbox_id") == "local"
|
||||||
|
|
||||||
|
|
||||||
def sandbox_from_runtime(runtime: ToolRuntime[ContextT, ThreadState] | None = None) -> Sandbox:
|
def sandbox_from_runtime(runtime: ToolRuntime[ContextT, ThreadState] | None = None) -> Sandbox:
|
||||||
if runtime is None:
|
if runtime is None:
|
||||||
@@ -35,6 +134,9 @@ def bash_tool(runtime: ToolRuntime[ContextT, ThreadState], description: str, com
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sandbox = sandbox_from_runtime(runtime)
|
sandbox = sandbox_from_runtime(runtime)
|
||||||
|
if is_local_sandbox(runtime):
|
||||||
|
thread_data = get_thread_data(runtime)
|
||||||
|
command = replace_virtual_paths_in_command(command, thread_data)
|
||||||
return sandbox.execute_command(command)
|
return sandbox.execute_command(command)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error: {e}"
|
return f"Error: {e}"
|
||||||
@@ -50,6 +152,9 @@ def ls_tool(runtime: ToolRuntime[ContextT, ThreadState], description: str, path:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sandbox = sandbox_from_runtime(runtime)
|
sandbox = sandbox_from_runtime(runtime)
|
||||||
|
if is_local_sandbox(runtime):
|
||||||
|
thread_data = get_thread_data(runtime)
|
||||||
|
path = replace_virtual_path(path, thread_data)
|
||||||
children = sandbox.list_dir(path)
|
children = sandbox.list_dir(path)
|
||||||
if not children:
|
if not children:
|
||||||
return "(empty)"
|
return "(empty)"
|
||||||
@@ -74,6 +179,9 @@ def read_file_tool(
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sandbox = sandbox_from_runtime(runtime)
|
sandbox = sandbox_from_runtime(runtime)
|
||||||
|
if is_local_sandbox(runtime):
|
||||||
|
thread_data = get_thread_data(runtime)
|
||||||
|
path = replace_virtual_path(path, thread_data)
|
||||||
content = sandbox.read_file(path)
|
content = sandbox.read_file(path)
|
||||||
if not content:
|
if not content:
|
||||||
return "(empty)"
|
return "(empty)"
|
||||||
@@ -102,6 +210,9 @@ def write_file_tool(
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sandbox = sandbox_from_runtime(runtime)
|
sandbox = sandbox_from_runtime(runtime)
|
||||||
|
if is_local_sandbox(runtime):
|
||||||
|
thread_data = get_thread_data(runtime)
|
||||||
|
path = replace_virtual_path(path, thread_data)
|
||||||
sandbox.write_file(path, content, append)
|
sandbox.write_file(path, content, append)
|
||||||
return "OK"
|
return "OK"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -129,6 +240,9 @@ def str_replace_tool(
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sandbox = sandbox_from_runtime(runtime)
|
sandbox = sandbox_from_runtime(runtime)
|
||||||
|
if is_local_sandbox(runtime):
|
||||||
|
thread_data = get_thread_data(runtime)
|
||||||
|
path = replace_virtual_path(path, thread_data)
|
||||||
content = sandbox.read_file(path)
|
content = sandbox.read_file(path)
|
||||||
if not content:
|
if not content:
|
||||||
return "OK"
|
return "OK"
|
||||||
|
|||||||
Reference in New Issue
Block a user