diff --git a/backend/.gitignore b/backend/.gitignore
index b4d02cc..d21cba4 100644
--- a/backend/.gitignore
+++ b/backend/.gitignore
@@ -7,6 +7,7 @@ wheels/
*.egg-info
.coverage
.coverage.*
+.ruff_cache
agent_history.gif
static/browser_history/*.gif
diff --git a/backend/.vscode/extensions.json b/backend/.vscode/extensions.json
new file mode 100644
index 0000000..37b9f37
--- /dev/null
+++ b/backend/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["charliermarsh.ruff"]
+}
diff --git a/backend/.vscode/settings.json b/backend/.vscode/settings.json
new file mode 100644
index 0000000..077c411
--- /dev/null
+++ b/backend/.vscode/settings.json
@@ -0,0 +1,10 @@
+{
+ "[python]": {
+ "editor.formatOnSave": true,
+ "editor.codeActionsOnSave": {
+ "source.fixAll": "explicit",
+ "source.organizeImports": "explicit"
+ },
+ "editor.defaultFormatter": "charliermarsh.ruff"
+ }
+}
diff --git a/backend/pyproject.toml b/backend/pyproject.toml
index 97807d1..8df486c 100644
--- a/backend/pyproject.toml
+++ b/backend/pyproject.toml
@@ -15,3 +15,8 @@ dependencies = [
"readabilipy>=0.3.0",
"tavily-python>=0.7.17",
]
+
+[dependency-groups]
+dev = [
+ "ruff>=0.14.11",
+]
diff --git a/backend/ruff.toml b/backend/ruff.toml
new file mode 100644
index 0000000..6dbc56c
--- /dev/null
+++ b/backend/ruff.toml
@@ -0,0 +1,10 @@
+line-length = 240
+target-version = "py312"
+
+[lint]
+select = ["E", "F", "I", "UP"]
+ignore = []
+
+[format]
+quote-style = "double"
+indent-style = "space"
diff --git a/backend/src/agents/lead_agent/agent.py b/backend/src/agents/lead_agent/agent.py
index 45a8877..7ea415b 100644
--- a/backend/src/agents/lead_agent/agent.py
+++ b/backend/src/agents/lead_agent/agent.py
@@ -1,4 +1,5 @@
from langchain.agents import create_agent
+
from src.agents.lead_agent.prompt import apply_prompt_template
from src.models import create_chat_model
from src.tools import get_available_tools
diff --git a/backend/src/agents/lead_agent/prompt.py b/backend/src/agents/lead_agent/prompt.py
index 81967dc..93d6e59 100644
--- a/backend/src/agents/lead_agent/prompt.py
+++ b/backend/src/agents/lead_agent/prompt.py
@@ -71,7 +71,4 @@ All temporary work happens in `{MOUNT_POINT}/user-data/workspace`. Final deliver
def apply_prompt_template() -> str:
- return (
- SYSTEM_PROMPT
- + f"\n{datetime.now().strftime("%Y-%m-%d, %A")}"
- )
+ return SYSTEM_PROMPT + f"\n{datetime.now().strftime('%Y-%m-%d, %A')}"
diff --git a/backend/src/config/app_config.py b/backend/src/config/app_config.py
index 2168652..a23d9fa 100644
--- a/backend/src/config/app_config.py
+++ b/backend/src/config/app_config.py
@@ -13,14 +13,10 @@ from src.config.tool_config import ToolConfig, ToolGroupConfig
class AppConfig(BaseModel):
"""Config for the DeerFlow application"""
- models: list[ModelConfig] = Field(
- default_factory=list, description="Available models"
- )
+ models: list[ModelConfig] = Field(default_factory=list, description="Available models")
sandbox: SandboxConfig = Field(description="Sandbox configuration")
tools: list[ToolConfig] = Field(default_factory=list, description="Available tools")
- tool_groups: list[ToolGroupConfig] = Field(
- default_factory=list, description="Available tool groups"
- )
+ tool_groups: list[ToolGroupConfig] = Field(default_factory=list, description="Available tool groups")
model_config = ConfigDict(extra="allow", frozen=False)
@classmethod
@@ -35,16 +31,12 @@ class AppConfig(BaseModel):
if config_path:
path = Path(config_path)
if not Path.exists(path):
- raise FileNotFoundError(
- f"Config file specified by param `config_path` not found at {path}"
- )
+ raise FileNotFoundError(f"Config file specified by param `config_path` not found at {path}")
return path
elif os.getenv("DEER_FLOW_CONFIG_PATH"):
path = Path(os.getenv("DEER_FLOW_CONFIG_PATH"))
if not Path.exists(path):
- raise FileNotFoundError(
- f"Config file specified by environment variable `DEER_FLOW_CONFIG_PATH` not found at {path}"
- )
+ raise FileNotFoundError(f"Config file specified by environment variable `DEER_FLOW_CONFIG_PATH` not found at {path}")
return path
else:
# Check if the config.yaml is in the parent directory of CWD
@@ -66,7 +58,7 @@ class AppConfig(BaseModel):
AppConfig: The loaded config.
"""
resolved_path = cls.resolve_config_path(config_path)
- with open(resolved_path, "r") as f:
+ with open(resolved_path) as f:
config_data = yaml.safe_load(f)
cls.resolve_env_variables(config_data)
result = cls.model_validate(config_data)
diff --git a/backend/src/reflection/resolvers.py b/backend/src/reflection/resolvers.py
index 3b73a10..9f17a7d 100644
--- a/backend/src/reflection/resolvers.py
+++ b/backend/src/reflection/resolvers.py
@@ -1,12 +1,12 @@
from importlib import import_module
-from typing import Type, TypeVar
+from typing import TypeVar
T = TypeVar("T")
def resolve_variable[T](
variable_path: str,
- expected_type: Type[T] | tuple[Type, ...] | None = None,
+ expected_type: type[T] | tuple[type, ...] | None = None,
) -> T:
"""Resolve a variable from a path.
@@ -25,9 +25,7 @@ def resolve_variable[T](
try:
module_path, variable_name = variable_path.rsplit(":", 1)
except ValueError as err:
- raise ImportError(
- f"{variable_path} doesn't look like a variable path. Example: parent_package_name.sub_package_name.module_name:variable_name"
- ) from err
+ raise ImportError(f"{variable_path} doesn't look like a variable path. Example: parent_package_name.sub_package_name.module_name:variable_name") from err
try:
module = import_module(module_path)
@@ -37,26 +35,18 @@ def resolve_variable[T](
try:
variable = getattr(module, variable_name)
except AttributeError as err:
- raise ImportError(
- f"Module {module_path} does not define a {variable_name} attribute/class"
- ) from err
+ raise ImportError(f"Module {module_path} does not define a {variable_name} attribute/class") from err
# Type validation
if expected_type is not None:
if not isinstance(variable, expected_type):
- type_name = (
- expected_type.__name__
- if isinstance(expected_type, type)
- else " or ".join(t.__name__ for t in expected_type)
- )
- raise ValueError(
- f"{variable_path} is not an instance of {type_name}, got {type(variable).__name__}"
- )
+ type_name = expected_type.__name__ if isinstance(expected_type, type) else " or ".join(t.__name__ for t in expected_type)
+ raise ValueError(f"{variable_path} is not an instance of {type_name}, got {type(variable).__name__}")
return variable
-def resolve_class(class_path: str, base_class: Type[T] | None = None) -> Type[T]:
+def resolve_class[T](class_path: str, base_class: type[T] | None = None) -> type[T]:
"""Resolve a class from a module path and class name.
Args:
diff --git a/backend/src/sandbox/local/local_sandbox.py b/backend/src/sandbox/local/local_sandbox.py
index f09c516..dc3fd0b 100644
--- a/backend/src/sandbox/local/local_sandbox.py
+++ b/backend/src/sandbox/local/local_sandbox.py
@@ -29,7 +29,7 @@ class LocalSandbox(Sandbox):
return list_dir(path, max_depth)
def read_file(self, path: str) -> str:
- with open(path, "r") as f:
+ with open(path) as f:
return f.read()
def write_file(self, path: str, content: str, append: bool = False) -> None:
diff --git a/backend/uv.lock b/backend/uv.lock
index dc083cb..46a950d 100644
--- a/backend/uv.lock
+++ b/backend/uv.lock
@@ -128,6 +128,11 @@ dependencies = [
{ name = "tavily-python" },
]
+[package.dev-dependencies]
+dev = [
+ { name = "ruff" },
+]
+
[package.metadata]
requires-dist = [
{ name = "langchain", specifier = ">=1.2.3" },
@@ -141,6 +146,9 @@ requires-dist = [
{ name = "tavily-python", specifier = ">=0.7.17" },
]
+[package.metadata.requires-dev]
+dev = [{ name = "ruff", specifier = ">=0.14.11" }]
+
[[package]]
name = "distro"
version = "1.9.0"
@@ -897,6 +905,32 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" },
]
+[[package]]
+name = "ruff"
+version = "0.14.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d4/77/9a7fe084d268f8855d493e5031ea03fa0af8cc05887f638bf1c4e3363eb8/ruff-0.14.11.tar.gz", hash = "sha256:f6dc463bfa5c07a59b1ff2c3b9767373e541346ea105503b4c0369c520a66958", size = 5993417, upload-time = "2026-01-08T19:11:58.322Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f0/a6/a4c40a5aaa7e331f245d2dc1ac8ece306681f52b636b40ef87c88b9f7afd/ruff-0.14.11-py3-none-linux_armv6l.whl", hash = "sha256:f6ff2d95cbd335841a7217bdfd9c1d2e44eac2c584197ab1385579d55ff8830e", size = 12951208, upload-time = "2026-01-08T19:12:09.218Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/5c/360a35cb7204b328b685d3129c08aca24765ff92b5a7efedbdd6c150d555/ruff-0.14.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f6eb5c1c8033680f4172ea9c8d3706c156223010b8b97b05e82c59bdc774ee6", size = 13330075, upload-time = "2026-01-08T19:12:02.549Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/9e/0cc2f1be7a7d33cae541824cf3f95b4ff40d03557b575912b5b70273c9ec/ruff-0.14.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f2fc34cc896f90080fca01259f96c566f74069a04b25b6205d55379d12a6855e", size = 12257809, upload-time = "2026-01-08T19:12:00.366Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/e5/5faab97c15bb75228d9f74637e775d26ac703cc2b4898564c01ab3637c02/ruff-0.14.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53386375001773ae812b43205d6064dae49ff0968774e6befe16a994fc233caa", size = 12678447, upload-time = "2026-01-08T19:12:13.899Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/33/e9767f60a2bef779fb5855cab0af76c488e0ce90f7bb7b8a45c8a2ba4178/ruff-0.14.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a697737dce1ca97a0a55b5ff0434ee7205943d4874d638fe3ae66166ff46edbe", size = 12758560, upload-time = "2026-01-08T19:11:42.55Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/84/4c6cf627a21462bb5102f7be2a320b084228ff26e105510cd2255ea868e5/ruff-0.14.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6845ca1da8ab81ab1dce755a32ad13f1db72e7fba27c486d5d90d65e04d17b8f", size = 13599296, upload-time = "2026-01-08T19:11:30.371Z" },
+ { url = "https://files.pythonhosted.org/packages/88/e1/92b5ed7ea66d849f6157e695dc23d5d6d982bd6aa8d077895652c38a7cae/ruff-0.14.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e36ce2fd31b54065ec6f76cb08d60159e1b32bdf08507862e32f47e6dde8bcbf", size = 15048981, upload-time = "2026-01-08T19:12:04.742Z" },
+ { url = "https://files.pythonhosted.org/packages/61/df/c1bd30992615ac17c2fb64b8a7376ca22c04a70555b5d05b8f717163cf9f/ruff-0.14.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:590bcc0e2097ecf74e62a5c10a6b71f008ad82eb97b0a0079e85defe19fe74d9", size = 14633183, upload-time = "2026-01-08T19:11:40.069Z" },
+ { url = "https://files.pythonhosted.org/packages/04/e9/fe552902f25013dd28a5428a42347d9ad20c4b534834a325a28305747d64/ruff-0.14.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53fe71125fc158210d57fe4da26e622c9c294022988d08d9347ec1cf782adafe", size = 14050453, upload-time = "2026-01-08T19:11:37.555Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/93/f36d89fa021543187f98991609ce6e47e24f35f008dfe1af01379d248a41/ruff-0.14.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a35c9da08562f1598ded8470fcfef2afb5cf881996e6c0a502ceb61f4bc9c8a3", size = 13757889, upload-time = "2026-01-08T19:12:07.094Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/9f/c7fb6ecf554f28709a6a1f2a7f74750d400979e8cd47ed29feeaa1bd4db8/ruff-0.14.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0f3727189a52179393ecf92ec7057c2210203e6af2676f08d92140d3e1ee72c1", size = 13955832, upload-time = "2026-01-08T19:11:55.064Z" },
+ { url = "https://files.pythonhosted.org/packages/db/a0/153315310f250f76900a98278cf878c64dfb6d044e184491dd3289796734/ruff-0.14.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:eb09f849bd37147a789b85995ff734a6c4a095bed5fd1608c4f56afc3634cde2", size = 12586522, upload-time = "2026-01-08T19:11:35.356Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/2b/a73a2b6e6d2df1d74bf2b78098be1572191e54bec0e59e29382d13c3adc5/ruff-0.14.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c61782543c1231bf71041461c1f28c64b961d457d0f238ac388e2ab173d7ecb7", size = 12724637, upload-time = "2026-01-08T19:11:47.796Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/41/09100590320394401cd3c48fc718a8ba71c7ddb1ffd07e0ad6576b3a3df2/ruff-0.14.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:82ff352ea68fb6766140381748e1f67f83c39860b6446966cff48a315c3e2491", size = 13145837, upload-time = "2026-01-08T19:11:32.87Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/d8/e035db859d1d3edf909381eb8ff3e89a672d6572e9454093538fe6f164b0/ruff-0.14.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:728e56879df4ca5b62a9dde2dd0eb0edda2a55160c0ea28c4025f18c03f86984", size = 13850469, upload-time = "2026-01-08T19:12:11.694Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/02/bb3ff8b6e6d02ce9e3740f4c17dfbbfb55f34c789c139e9cd91985f356c7/ruff-0.14.11-py3-none-win32.whl", hash = "sha256:337c5dd11f16ee52ae217757d9b82a26400be7efac883e9e852646f1557ed841", size = 12851094, upload-time = "2026-01-08T19:11:45.163Z" },
+ { url = "https://files.pythonhosted.org/packages/58/f1/90ddc533918d3a2ad628bc3044cdfc094949e6d4b929220c3f0eb8a1c998/ruff-0.14.11-py3-none-win_amd64.whl", hash = "sha256:f981cea63d08456b2c070e64b79cb62f951aa1305282974d4d5216e6e0178ae6", size = 14001379, upload-time = "2026-01-08T19:11:52.591Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/1c/1dbe51782c0e1e9cfce1d1004752672d2d4629ea46945d19d731ad772b3b/ruff-0.14.11-py3-none-win_arm64.whl", hash = "sha256:649fb6c9edd7f751db276ef42df1f3df41c38d67d199570ae2a7bd6cbc3590f0", size = 12938644, upload-time = "2026-01-08T19:11:50.027Z" },
+]
+
[[package]]
name = "six"
version = "1.17.0"
diff --git a/deer-flow.code-workspace b/deer-flow.code-workspace
new file mode 100644
index 0000000..c4ed7ce
--- /dev/null
+++ b/deer-flow.code-workspace
@@ -0,0 +1,7 @@
+{
+ "folders": [
+ {
+ "path": "backend"
+ }
+ ]
+}