Skip to content

Commit

Permalink
Merge branch 'main' into feature/strict-mypy-checks
Browse files Browse the repository at this point in the history
  • Loading branch information
neubig authored Feb 11, 2025
2 parents 66a7920 + 6a6dc93 commit d309455
Show file tree
Hide file tree
Showing 15 changed files with 347 additions and 175 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ghcr-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.3.0
uses: docker/setup-qemu-action@v3.4.0
with:
image: tonistiigi/binfmt:latest
- name: Login to GHCR
Expand Down Expand Up @@ -91,7 +91,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.3.0
uses: docker/setup-qemu-action@v3.4.0
with:
image: tonistiigi/binfmt:latest
- name: Login to GHCR
Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"scripts": {
"dev": "npm run make-i18n && cross-env VITE_MOCK_API=false react-router dev",
"dev:mock": "npm run make-i18n && cross-env VITE_MOCK_API=true react-router dev",
"build": "npm run make-i18n && tsc && react-router build",
"build": "npm run make-i18n && npm run typecheck && react-router build",
"start": "npx sirv-cli build/ --single",
"test": "vitest run",
"test:e2e": "playwright test",
Expand Down
1 change: 0 additions & 1 deletion openhands/agenthub/codeact_agent/codeact_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,6 @@ def get_observation_message(
and len(obs.set_of_marks) > 0
and self.config.enable_som_visual_browsing
and self.llm.vision_is_active()
and self.llm.is_visual_browser_tool_supported()
):
text += 'Image: Current webpage screenshot (Note that only visible portion of webpage is present in the screenshot. You may need to scroll to view the remaining portion of the web-page.)\n'
message = Message(
Expand Down
4 changes: 4 additions & 0 deletions openhands/core/config/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pathlib
import platform
import sys
from ast import literal_eval
from types import UnionType
from typing import Any, MutableMapping, get_args, get_origin
from uuid import uuid4
Expand Down Expand Up @@ -72,6 +73,9 @@ def set_attr_from_env(sub_config: BaseModel, prefix=''):
# Attempt to cast the env var to type hinted in the dataclass
if field_type is bool:
cast_value = str(value).lower() in ['true', '1']
# parse dicts like SANDBOX_RUNTIME_STARTUP_ENV_VARS
elif get_origin(field_type) is dict:
cast_value = literal_eval(value)
else:
cast_value = field_type(value)
setattr(sub_config, field_name, cast_value)
Expand Down
19 changes: 0 additions & 19 deletions openhands/llm/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,6 @@
'o3-mini',
]

# visual browsing tool supported models
# This flag is needed since gpt-4o and gpt-4o-mini do not allow passing image_urls with role='tool'
VISUAL_BROWSING_TOOL_SUPPORTED_MODELS = [
'claude-3-5-sonnet',
'claude-3-5-sonnet-20240620',
'claude-3-5-sonnet-20241022',
'o1-2024-12-17',
]


REASONING_EFFORT_SUPPORTED_MODELS = [
'o1-2024-12-17',
'o1',
Expand Down Expand Up @@ -495,15 +485,6 @@ def is_function_calling_active(self) -> bool:
"""
return self._function_calling_active

def is_visual_browser_tool_supported(self) -> bool:
return (
self.config.model in VISUAL_BROWSING_TOOL_SUPPORTED_MODELS
or self.config.model.split('/')[-1] in VISUAL_BROWSING_TOOL_SUPPORTED_MODELS
or any(
m in self.config.model for m in VISUAL_BROWSING_TOOL_SUPPORTED_MODELS
)
)

def _post_completion(self, response: ModelResponse) -> float:
"""Post-process the completion response.
Expand Down
17 changes: 11 additions & 6 deletions openhands/resolver/patching/apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,17 @@ def apply_diff(diff, text, reverse=False, use_patch=False):
hunk=hunk,
)
if lines[old - 1] != line:
raise HunkApplyException(
'context line {n}, "{line}" does not match "{sl}"'.format(
n=old, line=line, sl=lines[old - 1]
),
hunk=hunk,
)
# Try to normalize whitespace by replacing multiple spaces with a single space
# This helps with patches that have different indentation levels
normalized_line = ' '.join(line.split())
normalized_source = ' '.join(lines[old - 1].split())
if normalized_line != normalized_source:
raise HunkApplyException(
'context line {n}, "{line}" does not match "{sl}"'.format(
n=old, line=line, sl=lines[old - 1]
),
hunk=hunk,
)

# for calculating the old line
r = 0
Expand Down
19 changes: 19 additions & 0 deletions openhands/runtime/action_execution_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from pathlib import Path
from zipfile import ZipFile

import psutil
from fastapi import Depends, FastAPI, HTTPException, Request, UploadFile
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse, StreamingResponse
Expand Down Expand Up @@ -108,6 +109,22 @@ def __init__(
self.last_execution_time = self.start_time
self._initialized = False

if _override_max_memory_gb := os.environ.get('RUNTIME_MAX_MEMORY_GB', None):
self.max_memory_gb = int(_override_max_memory_gb)
logger.info(
f'Setting max memory to {self.max_memory_gb}GB (according to the RUNTIME_MAX_MEMORY_GB environment variable)'
)
else:
# Get available system memory
total_memory_gb = psutil.virtual_memory().total / (
1024 * 1024 * 1024
) # Convert to GB
self.max_memory_gb = int(max(0.5, total_memory_gb - 1.0))
# Reserve 1GB as head room, minimum of 0.5GB
logger.info(
f'Total memory: {total_memory_gb}GB, setting limit to {self.max_memory_gb}GB (reserved 1GB for action execution server, minimum 0.5GB)'
)

@property
def initial_cwd(self):
return self._initial_cwd
Expand All @@ -120,8 +137,10 @@ async def ainit(self):
no_change_timeout_seconds=int(
os.environ.get('NO_CHANGE_TIMEOUT_SECONDS', 30)
),
max_memory_mb=self.max_memory_gb * 1024,
)
self.bash_session.initialize()

await wait_all(
(self._init_plugin(plugin) for plugin in self.plugins_to_load),
timeout=30,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,13 @@ def copy_from(self, path: str) -> Path:
stream=True,
timeout=30,
) as response:
temp_file = tempfile.NamedTemporaryFile(delete=False)
for chunk in response.iter_content(chunk_size=8192):
if chunk: # filter out keep-alive new chunks
temp_file.write(chunk)
return Path(temp_file.name)
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
total_length = 0
for chunk in response.iter_content(chunk_size=8192):
if chunk: # filter out keep-alive new chunks
total_length += len(chunk)
temp_file.write(chunk)
return Path(temp_file.name)
except requests.Timeout:
raise TimeoutError('Copy operation timed out')

Expand Down
10 changes: 7 additions & 3 deletions openhands/runtime/impl/remote/remote_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,13 +212,17 @@ def _start_runtime(self):
plugins=self.plugins,
app_config=self.config,
)
environment = {
'DEBUG': 'true'
if self.config.debug or os.environ.get('DEBUG', 'false').lower() == 'true'
else {},
}
environment.update(self.config.sandbox.runtime_startup_env_vars)
start_request = {
'image': self.container_image,
'command': command,
'working_dir': '/openhands/code/',
'environment': {'DEBUG': 'true'}
if self.config.debug or os.environ.get('DEBUG', 'false').lower() == 'true'
else {},
'environment': environment,
'session_id': self.sid,
'resource_factor': self.config.sandbox.remote_runtime_resource_factor,
}
Expand Down
16 changes: 12 additions & 4 deletions openhands/runtime/utils/bash.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,25 +175,32 @@ def __init__(
work_dir: str,
username: str | None = None,
no_change_timeout_seconds: int = 30,
max_memory_mb: int | None = None,
):
self.NO_CHANGE_TIMEOUT_SECONDS = no_change_timeout_seconds
self.work_dir = work_dir
self.username = username
self._initialized = False
self.max_memory_mb = max_memory_mb

def initialize(self):
self.server = libtmux.Server()
window_command = '/bin/bash'
_shell_command = '/bin/bash'
if self.username in ['root', 'openhands']:
# This starts a non-login (new) shell for the given user
window_command = f'su {self.username} -'
_shell_command = f'su {self.username} -'
# otherwise, we are running as the CURRENT USER (e.g., when running LocalRuntime)
if self.max_memory_mb is not None:
window_command = (
f'prlimit --as={self.max_memory_mb * 1024 * 1024} {_shell_command}'
)
else:
window_command = _shell_command

logger.debug(f'Initializing bash session with command: {window_command}')
session_name = f'openhands-{self.username}-{uuid.uuid4()}'
self.session = self.server.new_session(
session_name=session_name,
window_name='bash',
window_command=window_command,
start_directory=self.work_dir,
kill_session=True,
x=1000,
Expand All @@ -207,6 +214,7 @@ def initialize(self):
# We need to create a new pane because the initial pane's history limit is (default) 2000
_initial_window = self.session.attached_window
self.window = self.session.new_window(
window_name='bash',
window_shell=window_command,
start_directory=self.work_dir,
)
Expand Down
Loading

0 comments on commit d309455

Please sign in to comment.