Skip to content

Commit 4becc88

Browse files
committed
Reduce repeated recall database work and release 0.1.66
1 parent e7d311f commit 4becc88

13 files changed

Lines changed: 278 additions & 72 deletions

File tree

‎app/core/ports/db/concept_repositories.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ def get_concept_bundles(
9999
*,
100100
repo_id: str,
101101
concept_ids: Sequence[str],
102+
include_evidence: bool,
102103
include_lifecycle_events: bool = False,
103104
) -> dict[str, dict[str, Any]]:
104105
"""Return bundles keyed by concept ID; omit missing concepts."""

‎app/core/ports/db/retrieval_repositories.py‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ def list_concept_keyword_corpus(
6060
self,
6161
*,
6262
repo_id: str,
63-
query_terms: Sequence[str] | None = None,
64-
candidate_limit: int | None = None,
6563
) -> Sequence[dict[str, Any]]:
6664
"""This method returns active concept text rows for lexical ranking."""
6765

@@ -75,8 +73,8 @@ def list_structural_memory_relation_rows(
7573
*,
7674
repo_id: str,
7775
include_global: bool,
78-
anchor_memory_id: str,
76+
anchor_memory_ids: Sequence[str],
7977
kinds: Sequence[str] | None,
8078
predicates: Sequence[str],
8179
) -> Sequence[dict[str, Any]]:
82-
"""Return structural relation rows touching an anchor plus visible participants."""
80+
"""Return structural relation rows touching the anchors plus visible participants."""

‎app/core/use_cases/retrieval/concept_seed_retrieval.py‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@
1717
normalize_lexical_text,
1818
)
1919
from app.core.ports.db.retrieval_repositories import (
20-
IConceptKeywordRetrievalRepo,
2120
IConceptSemanticRetrievalRepo,
2221
)
2322

2423

2524
def retrieve_concept_seeds(
2625
request_data: dict[str, Any],
2726
*,
28-
concept_keyword_retrieval: IConceptKeywordRetrievalRepo | None,
27+
concept_corpus: Sequence[dict[str, Any]],
2928
concept_semantic_retrieval: IConceptSemanticRetrievalRepo | None,
3029
query_vector: Sequence[float],
3130
query_model: str | None,
@@ -46,7 +45,7 @@ def retrieve_concept_seeds(
4645
)
4746
keyword = _keyword_concept_candidates(
4847
request_data,
49-
concept_keyword_retrieval=concept_keyword_retrieval,
48+
concept_corpus=concept_corpus,
5049
lexical_query=lexical_query,
5150
thresholds=thresholds,
5251
limit=limit,
@@ -81,19 +80,19 @@ def _semantic_concept_candidates(
8180
def _keyword_concept_candidates(
8281
request_data: dict[str, Any],
8382
*,
84-
concept_keyword_retrieval: IConceptKeywordRetrievalRepo | None,
83+
concept_corpus: Sequence[dict[str, Any]],
8584
lexical_query: LexicalQuery,
8685
thresholds: ThresholdSettings,
8786
limit: int,
8887
) -> list[dict[str, Any]]:
89-
if concept_keyword_retrieval is None:
90-
return []
88+
terms = lexical_query.terms
89+
matching_rows = [
90+
row
91+
for row in concept_corpus
92+
if any(term in str(row["text"]).lower() for term in terms)
93+
]
9194
candidates = _rank_concept_keyword_candidates(
92-
concept_keyword_retrieval.list_concept_keyword_corpus(
93-
repo_id=str(request_data["repo_id"]),
94-
query_terms=lexical_query.terms,
95-
candidate_limit=max(limit * 10, 100),
96-
),
95+
(matching_rows or concept_corpus)[: max(limit * 10, 100)],
9796
lexical_query=lexical_query,
9897
mode=str(request_data["mode"]),
9998
limit=limit,

‎app/core/use_cases/retrieval/deterministic_graph_recall.py‎

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ def build_deterministic_graph_pack(
143143
concept_bundles = uow.concepts.get_concept_bundles(
144144
repo_id=request.repo_id,
145145
concept_ids=list(concept_candidates),
146+
include_evidence=False,
146147
)
147148
selected_concepts, concept_trace = _select_concepts(
148149
request=request,
@@ -473,6 +474,22 @@ def _expand_structural_memory_relations(
473474
) -> dict[str, Any]:
474475
"""Add canonical structural memory-relation neighbors to recall candidates."""
475476

477+
rows_by_anchor = defaultdict(list)
478+
rows = uow.read_policy.list_structural_memory_relation_rows(
479+
repo_id=request.repo_id,
480+
include_global=request.include_global,
481+
anchor_memory_ids=tuple(memory_candidates),
482+
kinds=request.kinds or list(MATURE_MEMORY_KIND_VALUES),
483+
predicates=(
484+
*STRUCTURAL_PROBLEM_RELATION_PREDICATES,
485+
*STRUCTURAL_FACT_UPDATE_RELATION_PREDICATES,
486+
),
487+
)
488+
for row in rows:
489+
for anchor_id in {row["subject_memory_id"], row["object_memory_id"]}:
490+
if anchor_id in memory_candidates:
491+
rows_by_anchor[anchor_id].append(row)
492+
476493
discovered: list[dict[str, Any]] = []
477494
for anchor_memory_id in tuple(memory_candidates):
478495
anchor_entry = memory_candidates[anchor_memory_id]
@@ -481,13 +498,11 @@ def _expand_structural_memory_relations(
481498
STRUCTURAL_PROBLEM_RELATION_PREDICATES,
482499
STRUCTURAL_FACT_UPDATE_RELATION_PREDICATES,
483500
):
484-
rows = uow.read_policy.list_structural_memory_relation_rows(
485-
repo_id=request.repo_id,
486-
include_global=request.include_global,
487-
anchor_memory_id=anchor_memory_id,
488-
kinds=request.kinds or list(MATURE_MEMORY_KIND_VALUES),
489-
predicates=predicates,
490-
)
501+
rows = [
502+
row
503+
for row in rows_by_anchor[anchor_memory_id]
504+
if row["predicate"] in predicates
505+
]
491506
for row in rows:
492507
key = (
493508
row["subject_memory_id"],
@@ -578,11 +593,14 @@ def _discover_concepts(
578593
memory_entry["concept_refs"].add(concept_id)
579594
memory_entry["link_roles"].add(role)
580595

596+
concept_corpus = uow.concept_keyword_retrieval.list_concept_keyword_corpus(
597+
repo_id=request.repo_id
598+
)
581599
for lane in lanes:
582600
query_vector, query_model = embeddings[lane.name]
583601
seeds = retrieve_concept_seeds(
584602
_lane_request_data(request=request, query=lane.query),
585-
concept_keyword_retrieval=uow.concept_keyword_retrieval,
603+
concept_corpus=concept_corpus,
586604
concept_semantic_retrieval=uow.concept_semantic_retrieval,
587605
query_vector=query_vector,
588606
query_model=query_model,
@@ -733,6 +751,7 @@ def _traverse_selected_concepts(
733751
uow.concepts.get_concept_bundles(
734752
repo_id=request.repo_id,
735753
concept_ids=missing_ids,
754+
include_evidence=False,
736755
)
737756
)
738757
for neighbor_id in unique_neighbor_ids:

‎app/infrastructure/db/runtime/repos/relational/concepts_repo.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ def get_concept_bundle(
445445
return self.get_concept_bundles(
446446
repo_id=repo_id,
447447
concept_ids=[concept.id],
448+
include_evidence=True,
448449
include_lifecycle_events=include_lifecycle_events,
449450
).get(concept.id)
450451

@@ -453,6 +454,7 @@ def get_concept_bundles(
453454
*,
454455
repo_id: str,
455456
concept_ids: Sequence[str],
457+
include_evidence: bool,
456458
include_lifecycle_events: bool = False,
457459
) -> dict[str, dict[str, Any]]:
458460
"""Fetch candidate bundles in a fixed number of repo-scoped queries."""
@@ -535,7 +537,7 @@ def rows(table, condition):
535537
for concept_id in concept_ids_for_event:
536538
bundles[concept_id]["lifecycle_events"].append(event)
537539
owners[("lifecycle_event", event.id)].update(concept_ids_for_event)
538-
if owners:
540+
if include_evidence and owners:
539541
for row in self._unified_evidence_rows(
540542
repo_id=repo_id,
541543
targets=[(f"concept_{kind}", target_id) for kind, target_id in owners],

‎app/infrastructure/db/runtime/repos/relational/read_policy_repo.py‎

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@ def list_structural_memory_relation_rows(
2626
*,
2727
repo_id: str,
2828
include_global: bool,
29-
anchor_memory_id: str,
29+
anchor_memory_ids: Sequence[str],
3030
kinds: Sequence[str] | None,
3131
predicates: Sequence[str],
3232
) -> Sequence[dict[str, Any]]:
33-
"""Return active structural relation rows touching an anchor."""
33+
"""Return active structural relation rows touching the anchors in stable order."""
34+
35+
if not anchor_memory_ids:
36+
return []
3437

3538
rows = (
3639
self._session.execute(
@@ -41,23 +44,41 @@ def list_structural_memory_relation_rows(
4144
structural_memory_relations.c.status,
4245
structural_memory_relations.c.confidence,
4346
structural_memory_relations.c.validated_at,
44-
).where(
47+
)
48+
.where(
4549
structural_memory_relations.c.repo_id == repo_id,
4650
structural_memory_relations.c.predicate.in_(list(predicates)),
4751
structural_memory_relations.c.status.in_(
4852
list(POSITIVE_LIFECYCLE_STATUSES)
4953
),
5054
or_(
51-
structural_memory_relations.c.subject_memory_id
52-
== anchor_memory_id,
53-
structural_memory_relations.c.object_memory_id
54-
== anchor_memory_id,
55+
structural_memory_relations.c.subject_memory_id.in_(
56+
anchor_memory_ids
57+
),
58+
structural_memory_relations.c.object_memory_id.in_(
59+
anchor_memory_ids
60+
),
5561
),
5662
)
63+
.order_by(
64+
structural_memory_relations.c.subject_memory_id,
65+
structural_memory_relations.c.predicate,
66+
structural_memory_relations.c.object_memory_id,
67+
)
5768
)
5869
.mappings()
5970
.all()
6071
)
72+
visible_ids = self._visible_memory_ids(
73+
repo_id=repo_id,
74+
include_global=include_global,
75+
kinds=kinds,
76+
memory_ids=[
77+
str(row[field])
78+
for row in rows
79+
for field in ("subject_memory_id", "object_memory_id")
80+
],
81+
)
6182
return [
6283
{
6384
"subject_memory_id": str(row["subject_memory_id"]),
@@ -68,15 +89,8 @@ def list_structural_memory_relation_rows(
6889
"validated_at": row["validated_at"],
6990
"visible_memory_ids": tuple(
7091
sorted(
71-
self._visible_memory_ids(
72-
repo_id=repo_id,
73-
include_global=include_global,
74-
kinds=kinds,
75-
memory_ids=(
76-
str(row["subject_memory_id"]),
77-
str(row["object_memory_id"]),
78-
),
79-
)
92+
{str(row["subject_memory_id"]), str(row["object_memory_id"])}
93+
& visible_ids
8094
)
8195
),
8296
}

‎app/infrastructure/db/runtime/repos/semantic/concept_retrieval_repo.py‎

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ def list_concept_keyword_corpus(
3333
self,
3434
*,
3535
repo_id: str,
36-
query_terms: Sequence[str] | None = None,
37-
candidate_limit: int | None = None,
3836
) -> Sequence[dict[str, Any]]:
3937
"""Return one aggregate lexical document per active concept."""
4038

@@ -70,15 +68,11 @@ def list_concept_keyword_corpus(
7068
self._append_claim_parts(repo_id=repo_id, parts_by_id=parts_by_id)
7169
self._append_grounding_parts(repo_id=repo_id, parts_by_id=parts_by_id)
7270

73-
rows = [
71+
return [
7472
{"concept_id": concept_id, "text": " ".join(parts_by_id[concept_id])}
7573
for concept_id in ordered_ids
7674
if parts_by_id[concept_id]
7775
]
78-
filtered_rows = _filter_candidate_rows(rows, query_terms=query_terms)
79-
if candidate_limit is not None and candidate_limit > 0:
80-
return filtered_rows[: int(candidate_limit)]
81-
return filtered_rows
8276

8377
def _append_alias_parts(
8478
self, *, repo_id: str, parts_by_id: dict[str, list[str]]
@@ -96,7 +90,9 @@ def _append_alias_parts(
9690
concepts.c.repo_id == repo_id,
9791
concepts.c.status == "active",
9892
)
99-
.order_by(concept_aliases.c.concept_id.asc(), concept_aliases.c.alias.asc())
93+
.order_by(
94+
concept_aliases.c.concept_id.asc(), concept_aliases.c.alias.asc()
95+
)
10096
)
10197
.mappings()
10298
.all()
@@ -243,20 +239,6 @@ def _extend_parts(parts: list[str], *values: object) -> None:
243239
parts.append(text)
244240

245241

246-
def _filter_candidate_rows(
247-
rows: list[dict[str, Any]], *, query_terms: Sequence[str] | None
248-
) -> list[dict[str, Any]]:
249-
if not query_terms:
250-
return rows
251-
terms = [str(term).strip().lower() for term in query_terms if str(term).strip()]
252-
if not terms:
253-
return rows
254-
matched = [
255-
row for row in rows if any(term in str(row["text"]).lower() for term in terms)
256-
]
257-
return matched or rows
258-
259-
260242
def _locator_scalars(value: object) -> tuple[str, ...]:
261243
scalars: list[str] = []
262244

‎app/infrastructure/db/runtime/repos/semantic/keyword_retrieval_repo.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ def _ranked_fts_rows(
6666

6767
ts_vector = func.to_tsvector(_ENGLISH_REGCONFIG, memories.c.text)
6868
ts_query = func.websearch_to_tsquery(_ENGLISH_REGCONFIG, query_string)
69+
# Keep the broad visibility query from choosing a full text-parsing scan.
70+
keyword_hits = (
71+
select(memories.c.id)
72+
.where(ts_vector.op("@@")(ts_query))
73+
.cte("keyword_hits")
74+
.prefix_with("MATERIALIZED")
75+
)
6976
rank = func.ts_rank_cd(ts_vector, ts_query)
7077
stmt = (
7178
select(
@@ -79,7 +86,7 @@ def _ranked_fts_rows(
7986
include_global=include_global,
8087
kinds=kinds,
8188
),
82-
ts_vector.op("@@")(ts_query),
89+
memories.c.id.in_(select(keyword_hits.c.id)),
8390
)
8491
.order_by(desc(rank), memories.c.id.asc())
8592
)

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "shellbrain"
7-
version = "0.1.65"
7+
version = "0.1.66"
88
description = "Repo-scoped Shellbrain CLI with explicit evidence-backed writes."
99
readme = "README.md"
1010
requires-python = ">=3.11"

‎tests/operations/concepts/execution/test_concept_show_use_case.py‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,17 +370,36 @@ def count(*args):
370370
event.listen(connection, "before_cursor_execute", count)
371371
try:
372372
assert (
373-
uow.concepts.get_concept_bundles(repo_id="repo-a", concept_ids=[]) == {}
373+
uow.concepts.get_concept_bundles(
374+
include_evidence=True, repo_id="repo-a", concept_ids=[]
375+
)
376+
== {}
374377
)
375378
assert statements == []
376379
bundles = uow.concepts.get_concept_bundles(
380+
include_evidence=True,
377381
repo_id="repo-a",
378382
concept_ids=["third", second_id, "missing", "foreign", first_id],
379383
include_lifecycle_events=True,
380384
)
381385
finally:
382386
event.remove(connection, "before_cursor_execute", count)
383387
assert len(statements) == 9
388+
statements.clear()
389+
event.listen(connection, "before_cursor_execute", count)
390+
try:
391+
lean_bundles = uow.concepts.get_concept_bundles(
392+
repo_id="repo-a",
393+
concept_ids=list(bundles),
394+
include_evidence=False,
395+
include_lifecycle_events=True,
396+
)
397+
finally:
398+
event.remove(connection, "before_cursor_execute", count)
399+
assert len(statements) == 8
400+
assert lean_bundles == {
401+
key: {**bundle, "evidence": []} for key, bundle in bundles.items()
402+
}
384403
assert set(bundles) == {first_id, second_id, "third"}
385404
assert bundles[first_id]["relations"] == bundles[second_id]["relations"]
386405
assert bundles[first_id]["lifecycle_events"] == []
@@ -401,6 +420,7 @@ def count(*args):
401420
repo_id="repo-a", concept_ref=concept_id, include_lifecycle_events=True
402421
)
403422
assert bundles == uow.concepts.get_concept_bundles(
423+
include_evidence=True,
404424
repo_id="repo-a",
405425
concept_ids=list(reversed(bundles)),
406426
include_lifecycle_events=True,

0 commit comments

Comments
 (0)