Skip to content
Open
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
10 changes: 10 additions & 0 deletions list_issues.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import urllib.request, json
url = "https://api.github.com/repos/repowise-dev/repowise/issues?state=open&per_page=15"
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req) as response:
data = json.loads(response.read().decode())

for item in data:
if "pull_request" not in item:
labels = [l["name"] for l in item["labels"]]
print(f"#{item['number']}: {item['title']} (Labels: {', '.join(labels)})")
16 changes: 12 additions & 4 deletions packages/cli/src/repowise/cli/providers/embedders.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import os
from typing import Any

from repowise.core.providers.embedding.base import EmbedderConfigError, parse_numeric_env


def _embedder_kwargs(embedder_name: str) -> dict[str, Any]:
kwargs: dict[str, Any] = {}
Expand All @@ -21,13 +23,15 @@ def _embedder_kwargs(embedder_name: str) -> dict[str, Any]:
if base_url:
kwargs["base_url"] = base_url
if dimensions:
kwargs["dimensions"] = int(dimensions)
env_name = "OLLAMA_EMBEDDING_DIMS" if "OLLAMA_EMBEDDING_DIMS" in os.environ else "REPOWISE_EMBEDDING_DIMS"
kwargs["dimensions"] = parse_numeric_env(dimensions, env_name, is_int=True)
if timeout:
kwargs["timeout"] = float(timeout)
env_name = "OLLAMA_EMBEDDING_TIMEOUT" if "OLLAMA_EMBEDDING_TIMEOUT" in os.environ else "REPOWISE_EMBEDDING_TIMEOUT"
kwargs["timeout"] = parse_numeric_env(timeout, env_name)
elif embedder_name == "gemini":
dimensions = os.environ.get("REPOWISE_EMBEDDING_DIMS")
if dimensions:
kwargs["output_dimensionality"] = int(dimensions)
kwargs["output_dimensionality"] = parse_numeric_env(dimensions, "REPOWISE_EMBEDDING_DIMS", is_int=True)
if model:
kwargs["model"] = model
return kwargs
Expand Down Expand Up @@ -79,6 +83,10 @@ def build_embedder(embedder_name_resolved: str) -> Any:
if embedder_name_resolved == "mock":
return MockEmbedder()
try:
return get_embedder(embedder_name_resolved, **_embedder_kwargs(embedder_name_resolved))
kwargs = _embedder_kwargs(embedder_name_resolved)
except EmbedderConfigError:
raise
try:
return get_embedder(embedder_name_resolved, **kwargs)
except Exception:
return MockEmbedder()
22 changes: 21 additions & 1 deletion packages/core/src/repowise/core/providers/embedding/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,27 @@
import hashlib
import math
import struct
from typing import Protocol, runtime_checkable
from typing import Any, Protocol, runtime_checkable


class EmbedderConfigError(ValueError):
"""Raised when an embedder configuration value is malformed."""
pass


def parse_numeric_env(value: Any, name: str, is_int: bool = False) -> float | int:
"""Parse a numeric environment variable, requiring it to be finite and > 0."""
try:
parsed = int(value) if is_int else float(value)
except (ValueError, TypeError):
raise EmbedderConfigError(
f"Invalid {name}: {value!r} (must be a positive {'integer' if is_int else 'number'})"
)
if math.isnan(parsed) or math.isinf(parsed) or parsed <= 0:
raise EmbedderConfigError(
f"Invalid {name}: {value!r} (must be a positive {'integer' if is_int else 'number'})"
)
return parsed


@runtime_checkable
Expand Down
24 changes: 18 additions & 6 deletions packages/core/src/repowise/core/providers/embedding/ollama.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,31 @@ def __init__(
self._base_url = _normalize_base_url(
base_url or os.environ.get("OLLAMA_BASE_URL") or _DEFAULT_BASE_URL
)
from repowise.core.providers.embedding.base import parse_numeric_env

env_dimensions = os.environ.get("OLLAMA_EMBEDDING_DIMS") or os.environ.get(
"REPOWISE_EMBEDDING_DIMS"
)
self._requested_dimensions = dimensions or (int(env_dimensions) if env_dimensions else None)
if dimensions is not None:
self._requested_dimensions = dimensions
elif env_dimensions:
env_name = "OLLAMA_EMBEDDING_DIMS" if "OLLAMA_EMBEDDING_DIMS" in os.environ else "REPOWISE_EMBEDDING_DIMS"
self._requested_dimensions = parse_numeric_env(env_dimensions, env_name, is_int=True)
else:
self._requested_dimensions = None

self._dimensions = self._requested_dimensions or _infer_dimensions(self._model)

env_timeout = os.environ.get("OLLAMA_EMBEDDING_TIMEOUT") or os.environ.get(
"REPOWISE_EMBEDDING_TIMEOUT"
)
self._timeout = (
timeout
if timeout is not None
else (float(env_timeout) if env_timeout else _DEFAULT_TIMEOUT)
)
if timeout is not None:
self._timeout = timeout
elif env_timeout:
env_name = "OLLAMA_EMBEDDING_TIMEOUT" if "OLLAMA_EMBEDDING_TIMEOUT" in os.environ else "REPOWISE_EMBEDDING_TIMEOUT"
self._timeout = parse_numeric_env(env_timeout, env_name)
else:
self._timeout = _DEFAULT_TIMEOUT

@property
def dimensions(self) -> int:
Expand Down
18 changes: 18 additions & 0 deletions tests/unit/cli/test_shared_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,24 @@ def test_build_embedder_ollama_timeout_from_env(monkeypatch: pytest.MonkeyPatch)
assert embedder._timeout == 300.0


def test_build_embedder_ollama_timeout_invalid(monkeypatch: pytest.MonkeyPatch) -> None:
from repowise.core.providers.embedding.base import EmbedderConfigError

monkeypatch.setenv("OLLAMA_EMBEDDING_MODEL", "qwen3-embedding:0.6b")
monkeypatch.setenv("OLLAMA_EMBEDDING_TIMEOUT", "invalid")
with pytest.raises(EmbedderConfigError, match="Invalid OLLAMA_EMBEDDING_TIMEOUT: 'invalid'"):
providers.build_embedder("ollama")


def test_build_embedder_ollama_dimensions_invalid(monkeypatch: pytest.MonkeyPatch) -> None:
from repowise.core.providers.embedding.base import EmbedderConfigError

monkeypatch.setenv("OLLAMA_EMBEDDING_MODEL", "qwen3-embedding:0.6b")
monkeypatch.setenv("OLLAMA_EMBEDDING_DIMS", "-5")
with pytest.raises(EmbedderConfigError, match="Invalid OLLAMA_EMBEDDING_DIMS: '-5'"):
providers.build_embedder("ollama")


def test_build_vector_store_returns_a_store(tmp_path) -> None:
from repowise.core.providers.embedding.base import MockEmbedder

Expand Down
16 changes: 16 additions & 0 deletions tests/unit/test_persistence/test_ollama_embedder.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,19 @@ async def test_env_timeout_is_applied_to_the_request(monkeypatch: pytest.MonkeyP
await embedder.embed(["one"])

assert _FakeAsyncClient.calls[0]["timeout"] == 300.0


def test_timeout_invalid_raises_error(monkeypatch: pytest.MonkeyPatch) -> None:
from repowise.core.providers.embedding.base import EmbedderConfigError

monkeypatch.setenv("OLLAMA_EMBEDDING_TIMEOUT", "invalid")
with pytest.raises(EmbedderConfigError, match="Invalid OLLAMA_EMBEDDING_TIMEOUT: 'invalid'"):
OllamaEmbedder(model="embeddinggemma")


def test_dimensions_invalid_raises_error(monkeypatch: pytest.MonkeyPatch) -> None:
from repowise.core.providers.embedding.base import EmbedderConfigError

monkeypatch.setenv("OLLAMA_EMBEDDING_DIMS", "-5")
with pytest.raises(EmbedderConfigError, match="Invalid OLLAMA_EMBEDDING_DIMS: '-5'"):
OllamaEmbedder(model="embeddinggemma")
Loading