Skip to content

Commit b2430e1

Browse files
committed
fix: update litellm and stabilize review persistence
1 parent af2668f commit b2430e1

11 files changed

Lines changed: 125 additions & 31 deletions

core/health.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def _check_litellm() -> List[str]:
9595
except metadata.PackageNotFoundError:
9696
return []
9797

98-
expected = "1.61.15"
98+
expected = "1.83.0"
9999
if installed != expected:
100100
return [
101101
f"litellm version {installed} detected; expected {expected}."

core/memory_manager.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -647,9 +647,10 @@ def get_semantic(self, node_id: str) -> Optional[Dict[str, object]]:
647647
return None
648648

649649
def list_layer(self, layer: str) -> List[Dict[str, object]]:
650-
"""Return layer payloads from the available store."""
650+
"""Return layer payloads from the available store, ordered oldest to newest."""
651651
if self._collection is None:
652-
return list(self._fallback[layer].values())
652+
items = list(self._fallback[layer].values())
653+
return self._sort_layer_payloads(layer, items)
653654

654655
try: # pragma: no cover - depends on chromadb
655656
query = self._collection.get(
@@ -661,10 +662,22 @@ def list_layer(self, layer: str) -> List[Dict[str, object]]:
661662
for doc in documents:
662663
if isinstance(doc, str):
663664
parsed.append(cast(Dict[str, object], json.loads(doc)))
664-
return parsed
665+
return self._sort_layer_payloads(layer, parsed)
665666
except Exception as exc:
666667
raise MemoryError(f"Failed to query {layer} memory: {exc}") from exc
667668

669+
@staticmethod
670+
def _sort_layer_payloads(
671+
layer: str,
672+
items: Sequence[Dict[str, object]],
673+
) -> List[Dict[str, object]]:
674+
"""Return payloads in deterministic chronological order for each layer."""
675+
timestamp_field = "timestamp" if layer in {"episodic", "semantic"} else "created_at"
676+
return sorted(
677+
items,
678+
key=lambda item: RedisMemoryStore._coerce_timestamp(item.get(timestamp_field)),
679+
)
680+
668681
def query_layer(self, layer: str, query: str, limit: int) -> List[Dict[str, object]]:
669682
"""Return layer entries relevant to *query*, preferring semantic search."""
670683
limit = max(1, limit)

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
chromadb==0.5.3
2-
litellm==1.61.15
2+
litellm==1.83.0
33
pydantic==2.9.2
44
python-dotenv==1.0.1
55
PySide6==6.8.2

tests/test_chroma_integration.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import types
66
from pathlib import Path
77

8+
import pytest
9+
810
from config.settings import load_app_config
911
from core.memory_manager import MemoryManager
1012
from models.memory import SemanticNode
@@ -14,12 +16,24 @@ class _StubCollection:
1416
def __init__(self) -> None:
1517
self._store: dict[str, str] = {}
1618

17-
def upsert(self, ids=None, documents=None, metadatas=None, **_: object): # pragma: no cover - stub
19+
def upsert(
20+
self,
21+
ids: list[str] | None = None,
22+
documents: list[str] | None = None,
23+
metadatas: list[dict[str, object]] | None = None,
24+
**_: object,
25+
) -> None: # pragma: no cover - stub
1826
if not ids or not documents:
1927
return
2028
self._store[ids[0]] = documents[0]
2129

22-
def get(self, *, where=None, include=None, ids=None): # pragma: no cover - stub
30+
def get(
31+
self,
32+
*,
33+
where: object = None,
34+
include: object = None,
35+
ids: list[str] | None = None,
36+
) -> dict[str, list[str]]: # pragma: no cover - stub
2337
if ids:
2438
document = self._store.get(ids[0])
2539
return {"documents": [document] if document else []}
@@ -31,14 +45,21 @@ def __init__(self, path: str) -> None: # pragma: no cover - simple stub
3145
self._path = path
3246
self._collection = _StubCollection()
3347

34-
def get_or_create_collection(self, name: str, embedding_function=None):
48+
def get_or_create_collection(
49+
self,
50+
name: str,
51+
embedding_function: object = None,
52+
) -> _StubCollection:
3553
return self._collection
3654

37-
def list_collections(self): # pragma: no cover - stub
55+
def list_collections(self) -> list[object]: # pragma: no cover - stub
3856
return []
3957

4058

41-
def test_semantic_roundtrip_with_chroma_stub(monkeypatch, tmp_path: Path) -> None:
59+
def test_semantic_roundtrip_with_chroma_stub(
60+
monkeypatch: pytest.MonkeyPatch,
61+
tmp_path: Path,
62+
) -> None:
4263
chroma_stub = types.SimpleNamespace(PersistentClient=_StubClient)
4364

4465
monkeypatch.setenv("DRM_MEMORY_LOG_PATH", str(tmp_path / "revisions.jsonl"))

tests/test_cli_smoke.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
from __future__ import annotations
44

55
import logging
6+
from pathlib import Path
7+
8+
import pytest
69

710
from config.settings import load_app_config
811
from core.user_settings import UserSettingsManager
@@ -14,11 +17,21 @@
1417
class _StubLoop:
1518
last_instance: "_StubLoop | None" = None
1619

17-
def __init__(self, _config, user_settings=None) -> None: # pragma: no cover - simple stub
18-
self.last_override = None
20+
def __init__(
21+
self,
22+
_config: object,
23+
user_settings: object = None,
24+
) -> None: # pragma: no cover - simple stub
25+
self.last_override: str | None = None
1926
type(self).last_instance = self
2027

21-
def run_task(self, *, task: str, workflow_override=None, human_feedback=None):
28+
def run_task(
29+
self,
30+
*,
31+
task: str,
32+
workflow_override: str | None = None,
33+
human_feedback: str | None = None,
34+
) -> TaskRunOutcome:
2235
assert task == "demo"
2336
assert human_feedback == "note"
2437
self.last_override = workflow_override
@@ -44,7 +57,10 @@ def run_task(self, *, task: str, workflow_override=None, human_feedback=None):
4457
)
4558

4659

47-
def test_run_cli_emits_feedback(monkeypatch, caplog) -> None:
60+
def test_run_cli_emits_feedback(
61+
monkeypatch: pytest.MonkeyPatch,
62+
caplog: pytest.LogCaptureFixture,
63+
) -> None:
4864
monkeypatch.setattr("main.LiveTaskLoop", _StubLoop)
4965
config = load_app_config()
5066

@@ -56,7 +72,10 @@ def test_run_cli_emits_feedback(monkeypatch, caplog) -> None:
5672
assert "Mitigation actions" in caplog.text
5773

5874

59-
def test_run_cli_prefers_saved_workflow(monkeypatch, tmp_path) -> None:
75+
def test_run_cli_prefers_saved_workflow(
76+
monkeypatch: pytest.MonkeyPatch,
77+
tmp_path: Path,
78+
) -> None:
6079
monkeypatch.setattr("main.LiveTaskLoop", _StubLoop)
6180
config = load_app_config()
6281

tests/test_controller.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
from typing import cast
56
from uuid import uuid4
67

78
from config.settings import load_app_config
@@ -68,5 +69,7 @@ def test_controller_records_slo_breaches() -> None:
6869

6970
plan = controller.last_plan
7071
assert plan
71-
assert set(plan.get("slo_breaches", [])) == {"latency", "quality"}
72-
assert "expand_context_retrieval" in plan.get("recommended_actions", [])
72+
slo_breaches = cast(list[str], plan.get("slo_breaches", []))
73+
recommended_actions = cast(list[str], plan.get("recommended_actions", []))
74+
assert set(slo_breaches) == {"latency", "quality"}
75+
assert "expand_context_retrieval" in recommended_actions

tests/test_health.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@ def ping(self) -> bool:
3535

3636
redis_module = types.SimpleNamespace(Redis=_RedisClient)
3737
chroma_module = _build_chroma_stub(tmp_path)
38-
litellm_module = types.SimpleNamespace(__version__="1.61.15")
38+
litellm_module = types.SimpleNamespace(__version__="1.83.0")
3939

4040
monkeypatch.setitem(sys.modules, "redis", redis_module)
4141
monkeypatch.setitem(sys.modules, "chromadb", chroma_module)
4242
monkeypatch.setitem(sys.modules, "litellm", litellm_module)
4343

44-
monkeypatch.setattr("core.health.metadata.version", lambda _: "1.61.15")
44+
monkeypatch.setattr("core.health.metadata.version", lambda _: "1.83.0")
4545

4646

4747
def _build_chroma_stub(tmp_path: Path) -> types.SimpleNamespace:
@@ -83,6 +83,7 @@ def test_health_checks_warn_on_redis_failure(monkeypatch: pytest.MonkeyPatch, tm
8383
def test_health_checks_warn_when_standard_embedding_missing(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
8484
config = _load_config(tmp_path)
8585
config.memory.chromadb.persist_directory = str(tmp_path / "chroma")
86+
assert config.embedding is not None
8687
config.embedding.model = "text-embedding-3-large"
8788

8889
_install_stub_modules(monkeypatch, tmp_path)

tests/test_live_task_loop.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
from __future__ import annotations
44

5-
from typing import Optional
5+
from pathlib import Path
6+
from typing import Any, Optional, cast
7+
8+
import pytest
69

710
from config.settings import load_app_config
811
from core.controller import SelfAdjustingController
@@ -50,7 +53,10 @@ def perform_review(
5053
)
5154

5255

53-
def test_live_task_loop_persists_memory(monkeypatch, tmp_path) -> None:
56+
def test_live_task_loop_persists_memory(
57+
monkeypatch: pytest.MonkeyPatch,
58+
tmp_path: Path,
59+
) -> None:
5460
"""Running the live loop should persist artefacts and log revisions."""
5561

5662
monkeypatch.setenv("DRM_MEMORY_LOG_PATH", str(tmp_path / "revisions.jsonl"))
@@ -65,8 +71,8 @@ def test_live_task_loop_persists_memory(monkeypatch, tmp_path) -> None:
6571
loop = LiveTaskLoop(
6672
config,
6773
memory_manager=memory_manager,
68-
executor=_StubExecutor(),
69-
review_engine=_StubReviewEngine(),
74+
executor=cast(Any, _StubExecutor()),
75+
review_engine=cast(Any, _StubReviewEngine()),
7076
controller=controller,
7177
)
7278

tests/test_memory_revisions.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
from __future__ import annotations
44

55
import json
6+
from pathlib import Path
7+
8+
import pytest
69

710
from config.settings import load_app_config
811
from core.memory_manager import MemoryManager
@@ -14,7 +17,10 @@
1417
)
1518

1619

17-
def test_memory_revision_log_records_changes(monkeypatch, tmp_path) -> None:
20+
def test_memory_revision_log_records_changes(
21+
monkeypatch: pytest.MonkeyPatch,
22+
tmp_path: Path,
23+
) -> None:
1824
"""Ensure all memory layers append to the revision log."""
1925

2026
monkeypatch.setenv("DRM_MEMORY_LOG_PATH", str(tmp_path / "revisions.jsonl"))
@@ -54,7 +60,10 @@ def test_memory_revision_log_records_changes(monkeypatch, tmp_path) -> None:
5460
assert (tmp_path / "revisions.jsonl").exists()
5561

5662

57-
def test_revision_log_verification_and_replay(monkeypatch, tmp_path) -> None:
63+
def test_revision_log_verification_and_replay(
64+
monkeypatch: pytest.MonkeyPatch,
65+
tmp_path: Path,
66+
) -> None:
5867
"""Revision log should expose verification and replay helpers."""
5968

6069
log_path = tmp_path / "revisions.jsonl"
@@ -102,7 +111,10 @@ def test_revision_log_verification_and_replay(monkeypatch, tmp_path) -> None:
102111
assert not manager.verify_revision_log()
103112

104113

105-
def test_query_layer_prefers_relevant_results(monkeypatch, tmp_path) -> None:
114+
def test_query_layer_prefers_relevant_results(
115+
monkeypatch: pytest.MonkeyPatch,
116+
tmp_path: Path,
117+
) -> None:
106118
"""Querying episodic memory should surface the most relevant entries."""
107119

108120
monkeypatch.setenv("DRM_MEMORY_LOG_PATH", str(tmp_path / "revisions.jsonl"))

tests/test_semantic_graph.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22

33
from __future__ import annotations
44

5+
from pathlib import Path
6+
57
import pytest
68

79
from config.settings import load_app_config
810
from core.memory_manager import MemoryManager
911
from models.memory import SemanticNode, WorkingMemoryItem
1012

1113

12-
def _bootstrap_manager(monkeypatch, tmp_path) -> MemoryManager:
14+
def _bootstrap_manager(
15+
monkeypatch: pytest.MonkeyPatch,
16+
tmp_path: Path,
17+
) -> MemoryManager:
1318
monkeypatch.setenv("DRM_MEMORY_LOG_PATH", str(tmp_path / "revisions.jsonl"))
1419
monkeypatch.setattr("core.memory_manager.redis_module", None)
1520
monkeypatch.setattr("core.memory_manager.chromadb_module", None)
@@ -18,7 +23,10 @@ def _bootstrap_manager(monkeypatch, tmp_path) -> MemoryManager:
1823
return MemoryManager(config)
1924

2025

21-
def test_link_semantic_nodes_updates_relations(monkeypatch, tmp_path) -> None:
26+
def test_link_semantic_nodes_updates_relations(
27+
monkeypatch: pytest.MonkeyPatch,
28+
tmp_path: Path,
29+
) -> None:
2230
manager = _bootstrap_manager(monkeypatch, tmp_path)
2331

2432
node_a = SemanticNode(id="concept:a", label="Alpha", definition="Alpha concept")
@@ -37,7 +45,10 @@ def test_link_semantic_nodes_updates_relations(monkeypatch, tmp_path) -> None:
3745
assert forward[0][1] == pytest.approx(0.75, abs=1e-6)
3846

3947

40-
def test_list_semantic_nodes_returns_ordered(monkeypatch, tmp_path) -> None:
48+
def test_list_semantic_nodes_returns_ordered(
49+
monkeypatch: pytest.MonkeyPatch,
50+
tmp_path: Path,
51+
) -> None:
4152
manager = _bootstrap_manager(monkeypatch, tmp_path)
4253

4354
first = SemanticNode(id="concept:old", label="Old", definition="Old concept")
@@ -53,7 +64,10 @@ def test_list_semantic_nodes_returns_ordered(monkeypatch, tmp_path) -> None:
5364
assert [node.id for node in limited] == [second.id]
5465

5566

56-
def test_apply_drift_mitigation_prunes_working(monkeypatch, tmp_path) -> None:
67+
def test_apply_drift_mitigation_prunes_working(
68+
monkeypatch: pytest.MonkeyPatch,
69+
tmp_path: Path,
70+
) -> None:
5771
manager = _bootstrap_manager(monkeypatch, tmp_path)
5872

5973
for index in range(3):

0 commit comments

Comments
 (0)