mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
* [437e398a] Wave 1A — Remove piragi/torch dependencies entirely (#161) * [437e398a] chore(deps): remove piragi and torch from pyproject.toml and uv.lock - Remove piragi[postgres] from [project.dependencies] - Remove torch entry and its CPU-only comment from [project.dependencies] - Remove [[tool.uv.index]] pytorch-cpu block and [tool.uv.sources] torch override - Remove torch from [tool.deptry.per_rule_ignores] DEP002 - Keep piragi.* in [[tool.mypy.overrides]] ignore_missing_imports so the remaining optimal_brain/ piragi references don't break the mypy gate (Wave 1B will complete that migration) - Regenerate uv.lock: neither piragi nor torch appear in the resolved set * [437e398a] feat(kb): add piragi-free roboco/kb module with Chunk, OllamaEmbedder, shared embedder singleton - roboco/kb/__init__.py: new package entry point; 'import roboco.kb' works without piragi - roboco/kb/ollama_embedder.py: local Chunk dataclass (text/embedding/metadata), full OllamaEmbedder with parallel batch, LRU cache, retry/rate-limit logic - roboco/kb/shared_embedder.py: async singleton factory (OllamaEmbedder only, piragi EmbeddingGenerator branch removed) - All files pass ruff format+check and mypy with zero errors --------- Co-authored-by: Backend Developer 2 <be-dev-2@agents.roboco.dev> * [df01fa23] Replace piragi/torch with in-house RAG engine (#160) * [df01fa23] feat(rag): replace piragi/torch with in-house RAG engine - Remove piragi[postgres] and torch from pyproject.toml dependencies - Delete piragi_patches.py; all piragi.types imports replaced with local types - Add text_chunker.py: character-based sliding-window chunker with local Chunk/Document/Citation dataclasses (no tiktoken, no HuggingFace AutoTokenizer) - Add vector_store.py: VectorStore using asyncpg + pgvector, CREATE TABLE IF NOT EXISTS, ivfflat index, before/after startup timing note in docstring - Rewrite base.py: HyDE in _compute_query_embedding() via Ollama LLM with raw-query fallback; zero references to _sync/_conn/_init_schema/AsyncRagi - Update shared_embedder.py, ollama_embedder.py, code.py, docs.py to import Chunk from text_chunker instead of piragi.types - Refresh uv.lock removing piragi/torch entries - ruff check exits 0; mypy exits 0 on 253 source files; 2301 unit tests pass * [df01fa23] fix(tests): remove piragi stub block from conftest.py and clean up remaining piragi references in tests/ - Replace tests/unit/services/optimal_brain/conftest.py content with a minimal one-line docstring (removes _StubChunker, _ensure_piragi_stubbed, and its module-level call) — satisfies AC#7 explicitly - Remove piragi stub injection block from test_rate_limit_retry.py (_PIRAGI_STUB_NAMES, _stub_piragi(), and the call); also drop unused sys/types imports and now-redundant # noqa: E402 directives - Update _make_journal_plugin() helper to use the new _store/_chunker/_embedder attributes instead of the removed _ragi attribute - Remove dead piragi comment from test_indexes_base.py - All 28 rate-limit tests + 15 optimal_brain tests pass; ruff=0, mypy=0 * [df01fa23] fix(rag): delete piragi_patches.py to satisfy AC2 - file staged for removal --------- Co-authored-by: Backend Developer 1 <be-dev-1@agents.roboco.dev> * fix(rag): VectorStore.close tolerates a closed event loop The in-house engine's asyncpg pool is bound to the loop that created it. The optimal-service singleton can outlive that loop (cross-loop teardown between tests), so pool.close() raised 'RuntimeError: Event loop is closed' — failing test_optimal_grounding in the full suite (the work's first end-to-end gate). Swallow that specific RuntimeError (connections died with the loop); other RuntimeErrors still propagate. +3 unit tests. * fix(rag): validate table identifier + bandit-clean SQL construction bandit flagged B608 (SQL injection) on the in-house VectorStore's f-string queries interpolating the table name. The name is enum-derived (never user input), but the gate runs bandit -ll with skips=[] so it failed. Fix at the root, no nosec: validate the table identifier against a strict allowlist in __init__ (raises on anything unsafe), and inject it via _q()/str.replace (not %/format/f-string/+) so the controlled substitution isn't a B608 vector. Values remain $N bind params. +tests for the identifier guard. --------- Co-authored-by: Backend Developer 2 <be-dev-2@agents.roboco.dev> Co-authored-by: Backend Developer 1 <be-dev-1@agents.roboco.dev> Co-authored-by: Renn F <rennf93@users.noreply.github.com>
77 lines
3.2 KiB
Python
77 lines
3.2 KiB
Python
"""Tests for indexes.base — None-safe doc_source builder."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from roboco.services.optimal_brain.indexes.base import build_doc_source
|
|
from roboco.services.optimal_brain.indexes.conversations import ConversationsIndexPlugin
|
|
from roboco.services.optimal_brain.indexes.journals import JournalsIndexPlugin
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests for build_doc_source (module-level helper)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_doc_source_returns_none_when_id_missing() -> None:
|
|
assert build_doc_source(kind="journals", id_=None) is None
|
|
|
|
|
|
def test_doc_source_with_id() -> None:
|
|
result = build_doc_source(kind="journals", id_="abc-123")
|
|
assert result == "roboco://journals/abc-123"
|
|
|
|
|
|
def test_doc_source_conversations_with_id() -> None:
|
|
result = build_doc_source(kind="conversations", id_="sess-001-agent-007")
|
|
assert result == "roboco://conversations/sess-001-agent-007"
|
|
|
|
|
|
def test_doc_source_conversations_returns_none_when_id_missing() -> None:
|
|
assert build_doc_source(kind="conversations", id_=None) is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests for JournalsIndexPlugin.build_source_uri
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_journals_plugin_build_source_uri_returns_none_when_entry_id_none() -> None:
|
|
"""build_source_uri returns None when entry_id kwarg is None (the spam scenario)."""
|
|
plugin = JournalsIndexPlugin.__new__(JournalsIndexPlugin)
|
|
result = plugin.build_source_uri(doc_id=None, entry_id=None)
|
|
assert result is None
|
|
|
|
|
|
def test_journals_plugin_build_source_uri_with_entry_id() -> None:
|
|
"""build_source_uri returns correct URI when entry_id is set."""
|
|
plugin = JournalsIndexPlugin.__new__(JournalsIndexPlugin)
|
|
result = plugin.build_source_uri(doc_id=None, entry_id="entry-abc-123")
|
|
assert result == "roboco://journals/entry-abc-123"
|
|
|
|
|
|
def test_journals_plugin_build_source_uri_falls_back_to_doc_id() -> None:
|
|
"""build_source_uri falls back to doc_id when entry_id kwarg is absent."""
|
|
plugin = JournalsIndexPlugin.__new__(JournalsIndexPlugin)
|
|
result = plugin.build_source_uri(doc_id="fallback-id")
|
|
assert result == "roboco://journals/fallback-id"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests for ConversationsIndexPlugin.build_source_uri
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_conversations_plugin_returns_none_when_session_id_none() -> None:
|
|
"""build_source_uri returns None when session_id kwarg is None."""
|
|
plugin = ConversationsIndexPlugin.__new__(ConversationsIndexPlugin)
|
|
result = plugin.build_source_uri(doc_id=None, session_id=None, agent_id="agent-1")
|
|
assert result is None
|
|
|
|
|
|
def test_conversations_plugin_build_source_uri_with_session_id() -> None:
|
|
"""build_source_uri returns correct URI when session_id is set."""
|
|
plugin = ConversationsIndexPlugin.__new__(ConversationsIndexPlugin)
|
|
result = plugin.build_source_uri(
|
|
doc_id=None, session_id="sess-999", agent_id="agent-007"
|
|
)
|
|
assert result == "roboco://conversations/sess-999-agent-007"
|