mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Make CLI pip-installable (#8772)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
5fe7578f45
commit
4aed3944cf
2
.github/workflows/ghcr-build.yml
vendored
2
.github/workflows/ghcr-build.yml
vendored
@ -293,7 +293,7 @@ jobs:
|
|||||||
- name: Install poetry via pipx
|
- name: Install poetry via pipx
|
||||||
run: pipx install poetry
|
run: pipx install poetry
|
||||||
- name: Install Python dependencies using Poetry
|
- name: Install Python dependencies using Poetry
|
||||||
run: make install-python-dependencies POETRY_GROUP=main,test,runtime INSTALL_PLAYWRIGHT=0
|
run: make install-python-dependencies INSTALL_PLAYWRIGHT=0
|
||||||
- name: Run docker runtime tests
|
- name: Run docker runtime tests
|
||||||
run: |
|
run: |
|
||||||
# We install pytest-xdist in order to run tests across CPUs
|
# We install pytest-xdist in order to run tests across CPUs
|
||||||
|
|||||||
2
.github/workflows/integration-runner.yml
vendored
2
.github/workflows/integration-runner.yml
vendored
@ -54,7 +54,7 @@ jobs:
|
|||||||
Hi! I started running the integration tests on your PR. You will receive a comment with the results shortly.
|
Hi! I started running the integration tests on your PR. You will receive a comment with the results shortly.
|
||||||
|
|
||||||
- name: Install Python dependencies using Poetry
|
- name: Install Python dependencies using Poetry
|
||||||
run: poetry install --without evaluation
|
run: poetry install --with dev,test,runtime
|
||||||
|
|
||||||
- name: Configure config.toml for testing with Haiku
|
- name: Configure config.toml for testing with Haiku
|
||||||
env:
|
env:
|
||||||
|
|||||||
4
.github/workflows/py-unit-tests.yml
vendored
4
.github/workflows/py-unit-tests.yml
vendored
@ -44,7 +44,7 @@ jobs:
|
|||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
cache: 'poetry'
|
cache: 'poetry'
|
||||||
- name: Install Python dependencies using Poetry
|
- name: Install Python dependencies using Poetry
|
||||||
run: poetry install --without evaluation
|
run: poetry install --with dev,test,runtime
|
||||||
- name: Build Environment
|
- name: Build Environment
|
||||||
run: make build
|
run: make build
|
||||||
- name: Run Unit Tests
|
- name: Run Unit Tests
|
||||||
@ -71,7 +71,7 @@ jobs:
|
|||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
cache: 'poetry'
|
cache: 'poetry'
|
||||||
- name: Install Python dependencies using Poetry
|
- name: Install Python dependencies using Poetry
|
||||||
run: poetry install --without evaluation
|
run: poetry install --with dev,test,runtime
|
||||||
- name: Run Windows unit tests
|
- name: Run Windows unit tests
|
||||||
run: poetry run pytest -svv tests/unit/test_windows_bash.py
|
run: poetry run pytest -svv tests/unit/test_windows_bash.py
|
||||||
- name: Run Windows runtime tests with LocalRuntime
|
- name: Run Windows runtime tests with LocalRuntime
|
||||||
|
|||||||
2
Makefile
2
Makefile
@ -151,7 +151,7 @@ install-python-dependencies:
|
|||||||
echo "Installing only POETRY_GROUP=${POETRY_GROUP}"; \
|
echo "Installing only POETRY_GROUP=${POETRY_GROUP}"; \
|
||||||
poetry install --only $${POETRY_GROUP}; \
|
poetry install --only $${POETRY_GROUP}; \
|
||||||
else \
|
else \
|
||||||
poetry install; \
|
poetry install --with dev,test,runtime; \
|
||||||
fi
|
fi
|
||||||
@if [ "${INSTALL_PLAYWRIGHT}" != "false" ] && [ "${INSTALL_PLAYWRIGHT}" != "0" ]; then \
|
@if [ "${INSTALL_PLAYWRIGHT}" != "false" ] && [ "${INSTALL_PLAYWRIGHT}" != "0" ]; then \
|
||||||
if [ -f "/etc/manjaro-release" ]; then \
|
if [ -f "/etc/manjaro-release" ]; then \
|
||||||
|
|||||||
@ -26,7 +26,7 @@ RUN apt-get update -y \
|
|||||||
|
|
||||||
COPY ./pyproject.toml ./poetry.lock ./
|
COPY ./pyproject.toml ./poetry.lock ./
|
||||||
RUN touch README.md
|
RUN touch README.md
|
||||||
RUN export POETRY_CACHE_DIR && poetry install --without evaluation --no-root && rm -rf $POETRY_CACHE_DIR
|
RUN export POETRY_CACHE_DIR && poetry install --no-root && rm -rf $POETRY_CACHE_DIR
|
||||||
|
|
||||||
FROM python:3.12.3-slim AS openhands-app
|
FROM python:3.12.3-slim AS openhands-app
|
||||||
|
|
||||||
|
|||||||
@ -325,7 +325,7 @@ async def run_session(
|
|||||||
return new_session_requested
|
return new_session_requested
|
||||||
|
|
||||||
|
|
||||||
async def main(loop: asyncio.AbstractEventLoop) -> None:
|
async def main_with_loop(loop: asyncio.AbstractEventLoop) -> None:
|
||||||
"""Runs the agent in CLI mode."""
|
"""Runs the agent in CLI mode."""
|
||||||
args = parse_arguments()
|
args = parse_arguments()
|
||||||
|
|
||||||
@ -417,11 +417,11 @@ async def main(loop: asyncio.AbstractEventLoop) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
def main():
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
try:
|
try:
|
||||||
loop.run_until_complete(main(loop))
|
loop.run_until_complete(main_with_loop(loop))
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print('Received keyboard interrupt, shutting down...')
|
print('Received keyboard interrupt, shutting down...')
|
||||||
except ConnectionRefusedError as e:
|
except ConnectionRefusedError as e:
|
||||||
@ -443,3 +443,7 @@ if __name__ == '__main__':
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'Error during cleanup: {e}')
|
print(f'Error during cleanup: {e}')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|||||||
1170
poetry.lock
generated
1170
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -20,47 +20,36 @@ packages = [
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.12,<3.14"
|
python = "^3.12,<3.14"
|
||||||
litellm = "^1.60.0, !=1.64.4, !=1.67.*" # avoid 1.64.4 (known bug) & 1.67.* (known bug #10272)
|
litellm = "^1.60.0, !=1.64.4, !=1.67.*" # avoid 1.64.4 (known bug) & 1.67.* (known bug #10272)
|
||||||
aiohttp = ">=3.9.0,!=3.11.13" # Pin to avoid yanked version 3.11.13
|
aiohttp = ">=3.9.0,!=3.11.13" # Pin to avoid yanked version 3.11.13
|
||||||
google-generativeai = "*" # To use litellm with Gemini Pro API
|
google-generativeai = "*" # To use litellm with Gemini Pro API
|
||||||
google-api-python-client = "^2.164.0" # For Google Sheets API
|
google-api-python-client = "^2.164.0" # For Google Sheets API
|
||||||
google-auth-httplib2 = "*" # For Google Sheets authentication
|
google-auth-httplib2 = "*" # For Google Sheets authentication
|
||||||
google-auth-oauthlib = "*" # For Google Sheets OAuth
|
google-auth-oauthlib = "*" # For Google Sheets OAuth
|
||||||
termcolor = "*"
|
termcolor = "*"
|
||||||
docker = "*"
|
docker = "*"
|
||||||
fastapi = "*"
|
fastapi = "*"
|
||||||
toml = "*"
|
toml = "*"
|
||||||
uvicorn = "*"
|
|
||||||
types-toml = "*"
|
types-toml = "*"
|
||||||
|
uvicorn = "*"
|
||||||
numpy = "*"
|
numpy = "*"
|
||||||
json-repair = "*"
|
json-repair = "*"
|
||||||
browsergym-core = "0.13.3" # integrate browsergym-core as the browsing interface
|
browsergym-core = "0.13.3" # integrate browsergym-core as the browsing interface
|
||||||
html2text = "*"
|
html2text = "*"
|
||||||
e2b = ">=1.0.5,<1.4.0"
|
e2b = ">=1.0.5,<1.4.0"
|
||||||
pexpect = "*"
|
pexpect = "*"
|
||||||
jinja2 = "^3.1.3"
|
jinja2 = "^3.1.3"
|
||||||
python-multipart = "*"
|
python-multipart = "*"
|
||||||
boto3 = "*"
|
|
||||||
minio = "^7.2.8"
|
|
||||||
tenacity = ">=8.5,<10.0"
|
tenacity = ">=8.5,<10.0"
|
||||||
zope-interface = "7.2"
|
zope-interface = "7.2"
|
||||||
pathspec = "^0.12.1"
|
pathspec = "^0.12.1"
|
||||||
google-cloud-aiplatform = "*"
|
|
||||||
anthropic = { extras = [ "vertex" ], version = "*" }
|
|
||||||
tree-sitter = "^0.24.0"
|
|
||||||
bashlex = "^0.18"
|
|
||||||
pyjwt = "^2.9.0"
|
pyjwt = "^2.9.0"
|
||||||
dirhash = "*"
|
dirhash = "*"
|
||||||
python-frontmatter = "^1.1.0"
|
|
||||||
python-docx = "*"
|
|
||||||
PyPDF2 = "*"
|
|
||||||
python-pptx = "*"
|
|
||||||
pylatexenc = "*"
|
|
||||||
tornado = "*"
|
tornado = "*"
|
||||||
python-dotenv = "*"
|
python-dotenv = "*"
|
||||||
rapidfuzz = "^3.9.0"
|
rapidfuzz = "^3.9.0"
|
||||||
whatthepatch = "^1.0.6"
|
whatthepatch = "^1.0.6"
|
||||||
protobuf = "^4.21.6,<5.0.0" # chromadb currently fails on 5.0+
|
protobuf = "^4.21.6,<5.0.0" # chromadb currently fails on 5.0+
|
||||||
opentelemetry-api = "1.25.0"
|
opentelemetry-api = "1.25.0"
|
||||||
opentelemetry-exporter-otlp-proto-grpc = "1.25.0"
|
opentelemetry-exporter-otlp-proto-grpc = "1.25.0"
|
||||||
modal = ">=0.66.26,<0.78.0"
|
modal = ">=0.66.26,<0.78.0"
|
||||||
@ -70,14 +59,8 @@ pygithub = "^2.5.0"
|
|||||||
joblib = "*"
|
joblib = "*"
|
||||||
openhands-aci = "0.3.0"
|
openhands-aci = "0.3.0"
|
||||||
python-socketio = "^5.11.4"
|
python-socketio = "^5.11.4"
|
||||||
redis = ">=5.2,<7.0"
|
|
||||||
sse-starlette = "^2.1.3"
|
sse-starlette = "^2.1.3"
|
||||||
psutil = "*"
|
psutil = "*"
|
||||||
stripe = ">=11.5,<13.0"
|
|
||||||
ipywidgets = "^8.1.5"
|
|
||||||
qtconsole = "^5.6.1"
|
|
||||||
memory-profiler = "^0.61.0"
|
|
||||||
daytona-sdk = "0.18.1"
|
|
||||||
python-json-logger = "^3.2.1"
|
python-json-logger = "^3.2.1"
|
||||||
prompt-toolkit = "^3.0.50"
|
prompt-toolkit = "^3.0.50"
|
||||||
poetry = "^2.1.2"
|
poetry = "^2.1.2"
|
||||||
@ -85,6 +68,27 @@ anyio = "4.9.0"
|
|||||||
pythonnet = "*"
|
pythonnet = "*"
|
||||||
fastmcp = "^2.5.2"
|
fastmcp = "^2.5.2"
|
||||||
mcpm = "1.12.0"
|
mcpm = "1.12.0"
|
||||||
|
python-frontmatter = "^1.1.0"
|
||||||
|
# TODO: Should these go into the runtime group?
|
||||||
|
ipywidgets = "^8.1.5"
|
||||||
|
qtconsole = "^5.6.1"
|
||||||
|
PyPDF2 = "*"
|
||||||
|
python-pptx = "*"
|
||||||
|
pylatexenc = "*"
|
||||||
|
python-docx = "*"
|
||||||
|
bashlex = "^0.18"
|
||||||
|
|
||||||
|
# TODO: These are integrations that should probably be optional
|
||||||
|
redis = ">=5.2,<7.0"
|
||||||
|
minio = "^7.2.8"
|
||||||
|
daytona-sdk = "0.18.1"
|
||||||
|
stripe = ">=11.5,<13.0"
|
||||||
|
google-cloud-aiplatform = "*"
|
||||||
|
anthropic = { extras = [ "vertex" ], version = "*" }
|
||||||
|
boto3 = "*"
|
||||||
|
|
||||||
|
[tool.poetry.group.dev]
|
||||||
|
optional = true
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
ruff = "0.11.11"
|
ruff = "0.11.11"
|
||||||
@ -93,6 +97,9 @@ pre-commit = "4.2.0"
|
|||||||
build = "*"
|
build = "*"
|
||||||
types-setuptools = "*"
|
types-setuptools = "*"
|
||||||
|
|
||||||
|
[tool.poetry.group.test]
|
||||||
|
optional = true
|
||||||
|
|
||||||
[tool.poetry.group.test.dependencies]
|
[tool.poetry.group.test.dependencies]
|
||||||
pytest = "*"
|
pytest = "*"
|
||||||
pytest-cov = "*"
|
pytest-cov = "*"
|
||||||
@ -104,11 +111,18 @@ pandas = "*"
|
|||||||
reportlab = "*"
|
reportlab = "*"
|
||||||
gevent = ">=24.2.1,<26.0.0"
|
gevent = ">=24.2.1,<26.0.0"
|
||||||
|
|
||||||
|
[tool.poetry.group.runtime]
|
||||||
|
optional = true
|
||||||
|
|
||||||
[tool.poetry.group.runtime.dependencies]
|
[tool.poetry.group.runtime.dependencies]
|
||||||
jupyterlab = "*"
|
jupyterlab = "*"
|
||||||
notebook = "*"
|
notebook = "*"
|
||||||
jupyter_kernel_gateway = "*"
|
jupyter_kernel_gateway = "*"
|
||||||
flake8 = "*"
|
flake8 = "*"
|
||||||
|
memory-profiler = "^0.61.0"
|
||||||
|
|
||||||
|
[tool.poetry.group.evaluation]
|
||||||
|
optional = true
|
||||||
|
|
||||||
[tool.poetry.group.evaluation.dependencies]
|
[tool.poetry.group.evaluation.dependencies]
|
||||||
streamlit = "*"
|
streamlit = "*"
|
||||||
@ -132,6 +146,7 @@ browsergym-visualwebarena = "0.13.3"
|
|||||||
boto3-stubs = { extras = [ "s3" ], version = "^1.37.19" }
|
boto3-stubs = { extras = [ "s3" ], version = "^1.37.19" }
|
||||||
pyarrow = "20.0.0" # transitive dependency, pinned here to avoid conflicts
|
pyarrow = "20.0.0" # transitive dependency, pinned here to avoid conflicts
|
||||||
datasets = "*"
|
datasets = "*"
|
||||||
|
joblib = "*"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
openhands = "openhands.cli.main:main"
|
openhands = "openhands.cli.main:main"
|
||||||
|
|||||||
@ -1381,6 +1381,7 @@ async def test_first_user_message_with_identical_content(test_event_stream, mock
|
|||||||
await controller.close()
|
await controller.close()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
async def test_agent_controller_processes_null_observation_with_cause():
|
async def test_agent_controller_processes_null_observation_with_cause():
|
||||||
"""Test that AgentController processes NullObservation events with a cause value.
|
"""Test that AgentController processes NullObservation events with a cause value.
|
||||||
|
|
||||||
@ -1395,6 +1396,9 @@ async def test_agent_controller_processes_null_observation_with_cause():
|
|||||||
|
|
||||||
# Create a mock agent with necessary attributes
|
# Create a mock agent with necessary attributes
|
||||||
mock_agent = MagicMock(spec=Agent)
|
mock_agent = MagicMock(spec=Agent)
|
||||||
|
mock_agent.get_system_message = MagicMock(
|
||||||
|
return_value=None,
|
||||||
|
)
|
||||||
mock_agent.llm = MagicMock(spec=LLM)
|
mock_agent.llm = MagicMock(spec=LLM)
|
||||||
mock_agent.llm.metrics = Metrics()
|
mock_agent.llm.metrics = Metrics()
|
||||||
mock_agent.llm.config = OpenHandsConfig().get_llm_config()
|
mock_agent.llm.config = OpenHandsConfig().get_llm_config()
|
||||||
@ -1408,14 +1412,14 @@ async def test_agent_controller_processes_null_observation_with_cause():
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Patch the controller's step method to track calls
|
# Patch the controller's step method to track calls
|
||||||
with patch.object(controller, 'step') as mock_step:
|
with patch.object(controller, '_step') as mock_step:
|
||||||
# Create and add the first user message (will have ID 0)
|
# Create and add the first user message (will have ID 0)
|
||||||
user_message = MessageAction(content='First user message')
|
user_message = MessageAction(content='First user message')
|
||||||
user_message._source = EventSource.USER # type: ignore[attr-defined]
|
user_message._source = EventSource.USER # type: ignore[attr-defined]
|
||||||
event_stream.add_event(user_message, EventSource.USER)
|
event_stream.add_event(user_message, EventSource.USER)
|
||||||
|
|
||||||
# Give it a little time to process
|
# Give it a little time to process
|
||||||
await asyncio.sleep(0.3)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
# Get all events from the stream
|
# Get all events from the stream
|
||||||
events = list(event_stream.get_events())
|
events = list(event_stream.get_events())
|
||||||
|
|||||||
@ -381,7 +381,7 @@ async def test_main_without_task(
|
|||||||
mock_run_session.return_value = False
|
mock_run_session.return_value = False
|
||||||
|
|
||||||
# Run the function
|
# Run the function
|
||||||
await cli.main(loop)
|
await cli.main_with_loop(loop)
|
||||||
|
|
||||||
# Assertions
|
# Assertions
|
||||||
mock_parse_args.assert_called_once()
|
mock_parse_args.assert_called_once()
|
||||||
@ -458,7 +458,7 @@ async def test_main_with_task(
|
|||||||
mock_run_session.side_effect = [True, False]
|
mock_run_session.side_effect = [True, False]
|
||||||
|
|
||||||
# Run the function
|
# Run the function
|
||||||
await cli.main(loop)
|
await cli.main_with_loop(loop)
|
||||||
|
|
||||||
# Assertions
|
# Assertions
|
||||||
mock_parse_args.assert_called_once()
|
mock_parse_args.assert_called_once()
|
||||||
@ -553,7 +553,7 @@ async def test_main_with_session_name_passes_name_to_run_session(
|
|||||||
mock_run_session.return_value = False
|
mock_run_session.return_value = False
|
||||||
|
|
||||||
# Run the function
|
# Run the function
|
||||||
await cli.main(loop)
|
await cli.main_with_loop(loop)
|
||||||
|
|
||||||
# Assertions
|
# Assertions
|
||||||
mock_parse_args.assert_called_once()
|
mock_parse_args.assert_called_once()
|
||||||
@ -713,7 +713,7 @@ async def test_main_security_check_fails(
|
|||||||
mock_check_security.return_value = False
|
mock_check_security.return_value = False
|
||||||
|
|
||||||
# Run the function
|
# Run the function
|
||||||
await cli.main(loop)
|
await cli.main_with_loop(loop)
|
||||||
|
|
||||||
# Assertions
|
# Assertions
|
||||||
mock_parse_args.assert_called_once()
|
mock_parse_args.assert_called_once()
|
||||||
@ -796,7 +796,7 @@ async def test_config_loading_order(
|
|||||||
mock_run_session.return_value = False # No new session requested
|
mock_run_session.return_value = False # No new session requested
|
||||||
|
|
||||||
# Run the function
|
# Run the function
|
||||||
await cli.main(loop)
|
await cli.main_with_loop(loop)
|
||||||
|
|
||||||
# Assertions for argument parsing and config setup
|
# Assertions for argument parsing and config setup
|
||||||
mock_parse_args.assert_called_once()
|
mock_parse_args.assert_called_once()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user