Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ Environment variables:
## Optional Dependencies

Install based on needed functionality:
- `pip install vlmrun[cli]` - CLI with Typer/Rich
- `pip install vlmrun[video]` - Video processing (numpy)
- `pip install vlmrun[video]` - Video processing (numpy, opencv-python)
- `pip install vlmrun[doc]` - PDF processing (pypdfium2)
- `pip install vlmrun[openai]` - OpenAI SDK for chat completions API
- `pip install vlmrun[all]` - All optional dependencies
- `pip install vlmrun[all]` - All optional dependencies (video, doc, pandas, IPython)

The CLI, OpenAI SDK, and gateway chat commands are included in the base `pip install vlmrun` install.

## Testing

Expand Down
13 changes: 4 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ pip install vlmrun

The package provides optional features that can be installed based on your needs:

- Chat with Orion via the CLI (see `vlmrun chat`)
```bash
pip install "vlmrun[cli]"
```

- Video processing features (numpy, opencv-python):
```bash
pip install "vlmrun[video]"
Expand All @@ -45,16 +40,18 @@ The package provides optional features that can be installed based on your needs
pip install "vlmrun[doc]"
```

- OpenAI SDK integration (for chat completions API):
- Visualization and notebook helpers (pandas, IPython):
```bash
pip install "vlmrun[openai]"
pip install "vlmrun[all]"
```

- All optional features:
```bash
pip install "vlmrun[all]"
```

The CLI and OpenAI-compatible gateway (`vlmrun gw chat`, `vlmrun chat`) work out of the box with `pip install vlmrun`.

### Basic Usage

```python
Expand Down Expand Up @@ -120,8 +117,6 @@ async def main():
asyncio.run(main())
```

**Installation**: Install with OpenAI support using `pip install vlmrun[openai]`

### CLI Chat with Skills

The `vlmrun chat` command supports **skills** — local directories containing a `SKILL.md` and optional assets that give the agent domain-specific expertise. Skills are sent inline with each request (no server-side upload required).
Expand Down
17 changes: 6 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,21 @@ license = {text = "Apache-2.0"}
dynamic = ["version", "dependencies"]

[project.optional-dependencies]
test = ["pytest", "openai", "pre-commit"]
test = ["pytest", "pre-commit"]
build = ["twine", "build"]
openai = ["openai>=1.0.0"]
video = [
"numpy>=1.24.0",
"opencv-python>=4.8.0",
]
doc = [
"pypdfium2>=4.30.0"
]
cli = [
"typer>=0.9.0",
"rich>=13.0.0",
"openai>=1.0.0",
"pypdfium2>=4.30.0",
]
all = [
"numpy>=1.24.0",
"opencv-python>=4.8.0",
"pypdfium2>=4.30.0",
"openai>=1.0.0",
"typer>=0.9.0",
"rich>=13.0.0",
"pandas",
"ipython",
]

[tool.setuptools.dynamic]
Expand Down
6 changes: 1 addition & 5 deletions requirements/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
cachetools
IPython
loguru
opencv-python>=4.8.0
pandas
openai>=1.0.0
Pillow>=10.2.0
pydantic>=2.5,<3
pydantic_core>=2.23.4
requests
rich
tabulate
tenacity
tqdm
typer>=0.9.0
vlmrun-hub>=0.1.28
95 changes: 63 additions & 32 deletions tests/common/test_dependencies.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,77 @@
"""Tests for verifying correct installation of optional dependencies."""
"""Tests for verifying optional dependency handling."""

from __future__ import annotations

import builtins
import sys

import pytest

from vlmrun.client.exceptions import DependencyError
from vlmrun.common import dependencies


def _block_import(monkeypatch, module_name: str) -> None:
for key in list(sys.modules):
if key == module_name or key.startswith(f"{module_name}."):
monkeypatch.delitem(sys.modules, key, raising=False)

@pytest.mark.skip(reason="Temporarily skipped as requested")
def test_base_dependencies():
"""Verify base installation has no optional dependencies."""
with pytest.raises(ImportError):
import cv2 # noqa: F401
real_import = builtins.__import__

with pytest.raises(ImportError):
import pypdfium2 # noqa: F401
def mock_import(name, globals=None, locals=None, fromlist=(), level=0):
blocked = (
name == module_name
or name.startswith(f"{module_name}.")
or (fromlist and module_name in fromlist)
)
if blocked:
raise ImportError(f"No module named '{module_name}'")
return real_import(name, globals, locals, fromlist, level)

monkeypatch.setattr(builtins, "__import__", mock_import)

@pytest.mark.skip(reason="Temporarily skipped as requested")
def test_video_dependencies():
"""Verify video dependencies are available."""
import cv2 # noqa: F401
import numpy as np # noqa: F401

# Verify we can import and get versions
assert cv2.__version__, "cv2 version should be available"
assert np.__version__, "numpy version should be available"
def test_require_openai_suggestion(monkeypatch):
"""OpenAI is a core dependency; errors should point at base install."""
_block_import(monkeypatch, "openai")
with pytest.raises(DependencyError) as exc_info:
dependencies.require_openai()
assert "pip install vlmrun" in exc_info.value.suggestion
assert "[openai]" not in exc_info.value.suggestion


@pytest.mark.skip(reason="Temporarily skipped as requested")
def test_doc_dependencies():
"""Verify doc dependencies are available."""
import pypdfium2 # noqa: F401
@pytest.mark.parametrize(
("require_fn", "module_name", "extra"),
[
(dependencies.require_pandas, "pandas", "all"),
(dependencies.require_numpy, "numpy", "video"),
(dependencies.require_cv2, "cv2", "video"),
(dependencies.require_ipython_html, "IPython", "all"),
(dependencies.require_pypdfium2, "pypdfium2", "doc"),
],
)
def test_optional_dependency_errors(require_fn, module_name, extra, monkeypatch):
"""Missing optional deps should raise DependencyError with install hints."""
_block_import(monkeypatch, module_name)
with pytest.raises(DependencyError) as exc_info:
require_fn()
assert f"vlmrun[{extra}]" in exc_info.value.suggestion

# Verify we can import and get version
assert pypdfium2.__version__, "pypdfium2 version should be available"

def test_markdown_table_to_dataframe_requires_pandas(monkeypatch):
"""MarkdownTable.to_dataframe should lazy-load pandas."""
def _raise_pandas():
raise DependencyError(
message="pandas is not installed",
suggestion="Install it with `pip install vlmrun[all]`",
)

@pytest.mark.skip(reason="Temporarily skipped as requested")
def test_all_dependencies():
"""Verify all dependencies are available."""
import cv2 # noqa: F401
import numpy as np # noqa: F401
import pypdfium2 # noqa: F401
monkeypatch.setattr("vlmrun.client.types.require_pandas", _raise_pandas)
from vlmrun.client.types import MarkdownTable, TableHeader

# Verify we can import and get versions
assert cv2.__version__, "cv2 version should be available"
assert np.__version__, "numpy version should be available"
assert pypdfium2.__version__, "pypdfium2 version should be available"
table = MarkdownTable(
headers=[TableHeader(id="col1", column=0, name="Column 1")],
data=[{"col1": "value"}],
)
with pytest.raises(DependencyError):
table.to_dataframe()
6 changes: 3 additions & 3 deletions tests/test_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ class _Resp:
status_code = 200
is_success = True

monkeypatch.setattr("httpx.get", lambda *a, **k: _Resp())
monkeypatch.setattr("requests.get", lambda *a, **k: _Resp())
assert g.health() is True

def test_health_falls_back_to_models_on_404(self, monkeypatch):
Expand All @@ -318,7 +318,7 @@ class _Resp:
status_code = 404
is_success = False

monkeypatch.setattr("httpx.get", lambda *a, **k: _Resp())
monkeypatch.setattr("requests.get", lambda *a, **k: _Resp())

class _Models:
def list(self):
Expand All @@ -336,7 +336,7 @@ def test_health_false_on_connection_error(self, monkeypatch):
def _boom(*a, **k):
raise RuntimeError("no network")

monkeypatch.setattr("httpx.get", _boom)
monkeypatch.setattr("requests.get", _boom)

class _Models:
def list(self):
Expand Down
10 changes: 3 additions & 7 deletions vlmrun/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,12 @@ Visual AI from your terminal. Chat with VLM Run's Orion visual AI agent to proce

## Installation

The CLI is included as an extra in the vlmrun package:

```bash
# Install vlmrun with CLI support
pip install "vlmrun[cli]"

# Or with uv
uv pip install "vlmrun[cli]"
pip install vlmrun
```

The CLI and OpenAI-compatible gateway work out of the box with the base install.

## Quick Start

1. **Get your API key** at [app.vlm.run](https://app.vlm.run)
Expand Down
20 changes: 1 addition & 19 deletions vlmrun/client/agent.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Documentation still claims chat completions raise a missing-dependency error

The completions helpers still document a missing-dependency error (Raises: DependencyError at vlmrun/client/agent.py:305 and vlmrun/client/agent.py:350) even though the OpenAI SDK is now imported unconditionally at module level, so the docs no longer match behavior.
Impact: Readers are told about an error condition that can never occur, which is misleading.

Docstrings not updated with the import change

agent.py now does from openai import AsyncOpenAI, OpenAI at the top (vlmrun/client/agent.py:22) and the try/except that raised DependencyError was removed from both completions and async_completions. AGENTS.md requires docstrings/docs be updated when the implementation deviates; the Raises: sections should be dropped (or replaced with an ImportError note).

(Refers to lines 304-306)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
AgentCreationResponse,
AgentToolset,
)
from vlmrun.client.exceptions import DependencyError
from openai import AsyncOpenAI, OpenAI

# VLM Run-specific kwargs accepted by the agent API that are not part of the
# standard OpenAI chat completions signature. They are forwarded to the server
Expand Down Expand Up @@ -307,15 +307,6 @@ def completions(self):
Returns:
OpenAI Completions object configured for VLMRun agent endpoint
"""
try:
from openai import OpenAI
except ImportError:
raise DependencyError(
message="OpenAI SDK is not installed",
suggestion="Install it with `pip install vlmrun[openai]` or `pip install openai`",
error_type="missing_dependency",
)

base_url = f"{self._client.base_url}/openai"
openai_client = OpenAI(
api_key=self._client.api_key,
Expand Down Expand Up @@ -361,15 +352,6 @@ async def main():
Returns:
OpenAI AsyncCompletions object configured for VLMRun agent endpoint
"""
try:
from openai import AsyncOpenAI
except ImportError:
raise DependencyError(
message="OpenAI SDK is not installed",
suggestion="Install it with `pip install vlmrun[openai]` or `pip install openai`",
error_type="missing_dependency",
)

base_url = f"{self._client.base_url}/openai"
async_openai_client = AsyncOpenAI(
api_key=self._client.api_key,
Expand Down
24 changes: 7 additions & 17 deletions vlmrun/client/gateway.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Gateway availability check crashes instead of reporting status

The gateway liveness check reads a success flag that does not exist on the new HTTP library's response object (resp.is_success at vlmrun/client/gateway.py:231), so any reachable gateway that answers the health request makes the check blow up instead of returning true/false.
Impact: Users running gateway commands see an unexpected crash rather than a healthy/unhealthy result.

httpx→requests migration missed the response API difference

health() was switched from httpx.get to requests.get (vlmrun/client/gateway.py:210-216). httpx.Response exposes is_success, but requests.Response does not (it exposes ok), so line 231 raises AttributeError for every non-404 response. The exception is outside the try blocks (which only cover the request and the models fallback), so it propagates to callers such as the vlmrun gw CLI health checks. The tests still pass only because the fakes in tests/test_gateway.py:307-321 define an is_success attribute.

(Refers to line 231)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,11 @@
from typing import Any, List, Optional

from vlmrun.constants import DEFAULT_GATEWAY_URL
from vlmrun.client.exceptions import DependencyError
from vlmrun.common.dependencies import require_openai
from vlmrun.types.abstract import VLMRunProtocol


def _require_openai():
"""Import the OpenAI SDK or raise a helpful :class:`DependencyError`."""
try:
import openai # noqa: F401
except ImportError as e:
raise DependencyError(
message="OpenAI SDK is not installed",
suggestion="Install it with `pip install vlmrun[openai]` or `pip install openai`",
error_type="missing_dependency",
) from e
return openai
# Re-export for CLI/tests that patch gateway._require_openai.
_require_openai = require_openai


class Gateway:
Expand Down Expand Up @@ -217,13 +207,13 @@ def health(self) -> bool:
Returns:
True if the gateway is reachable and authenticated, else False.
"""
# httpx is a hard dependency of the openai SDK, so it is always
# available whenever the gateway is usable.
import httpx
import requests

headers = {"Authorization": f"Bearer {self._client.api_key}"}
try:
resp = httpx.get(f"{self.base_url}/health", headers=headers, timeout=30.0)
resp = requests.get(
f"{self.base_url}/health", headers=headers, timeout=30.0
)
except Exception:
# No dedicated health route reachable — fall back to a real call.
try:
Expand Down
Loading
Loading