claude-smart 0.2.46 → 0.2.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +19 -11
- package/bin/claude-smart.js +290 -68
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +11 -10
- package/plugin/dashboard/app/layout.tsx +20 -0
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +36 -2
- package/plugin/dashboard/package-lock.json +61 -390
- package/plugin/dashboard/package.json +2 -2
- package/plugin/opencode/dist/server.mjs +76 -2
- package/plugin/opencode/server.mts +79 -2
- package/plugin/pyproject.toml +6 -2
- package/plugin/scripts/smart-install.sh +7 -1
- package/plugin/src/claude_smart/cli.py +210 -22
- package/plugin/src/claude_smart/context_format.py +9 -9
- package/plugin/src/claude_smart/cs_cite.py +66 -19
- package/plugin/uv.lock +5 -5
- package/plugin/vendor/reflexio/.env.example +7 -0
- package/plugin/vendor/reflexio/README.md +3 -3
- package/plugin/vendor/reflexio/pyproject.toml +2 -1
- package/plugin/vendor/reflexio/reflexio/README.md +11 -6
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +1 -1
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +2 -2
- package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
- package/plugin/vendor/reflexio/reflexio/client/client.py +97 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +8 -0
- package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +23 -18
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +9 -8
- package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +16 -1
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +27 -16
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +27 -5
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +9 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +45 -3
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +37 -0
- package/plugin/vendor/reflexio/reflexio/server/README.md +38 -9
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +21 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +274 -3267
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +4 -3
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +16 -1
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/{_auth.py → auth.py} +2 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +62 -36
- package/plugin/vendor/reflexio/reflexio/server/deployment_profile.py +69 -0
- package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +424 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_json_extraction.py +249 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_structured_output.py +195 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +152 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +980 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_types.py +110 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +73 -1819
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +57 -5
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/middleware.py +244 -0
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +9 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +14 -2
- package/plugin/vendor/reflexio/reflexio/server/rate_limit.py +79 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/_common.py +25 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/_metering.py +98 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/braintrust.py +129 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/config.py +210 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +549 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +259 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/playbooks.py +578 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/profiles.py +423 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/provenance.py +345 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/search.py +349 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/system.py +261 -0
- package/plugin/vendor/reflexio/reflexio/server/scheduling.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +5 -2
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/__init__.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_batch_progress.py +298 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_config_filter.py +152 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +243 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +299 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_status_change.py +273 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +258 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +17 -1183
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +8 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +26 -41
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +232 -123
- package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +385 -78
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +9 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +121 -525
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_clustering.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_postprocessing.py +130 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_prompt_formatting.py +212 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +258 -69
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +9 -9
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/publish_learning_worker.py +288 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +29 -13
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +43 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +10 -1167
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +58 -351
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +49 -19
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +452 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +11 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1 -2133
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +7 -1126
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +73 -33
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_agent_run_store.py +506 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_pending_tool_call_store.py +704 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_run_tool_dependency_store.py +123 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_audit.py +122 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +465 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_purge.py +387 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_rebuild_hide.py +332 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +511 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +955 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +844 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +896 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_search.py +270 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +50 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +64 -370
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +0 -909
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +20 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_agent_run_store.py +86 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +195 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_pending_tool_call_store.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_run_tool_dependency_store.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_audit.py +29 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_erase_execution.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_purge.py +74 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_rebuild_hide.py +32 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_subject_barrier.py +49 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +73 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/{_profiles.py → profiles/_profile_store.py} +57 -86
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_search.py +32 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +153 -12
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +0 -84
package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_search.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""Interaction + profile search methods for SQLite storage.
|
|
2
|
+
|
|
3
|
+
Extracted verbatim from ``_profiles.py`` (the Search bucket). Profile CRUD lives
|
|
4
|
+
in ``profiles._profile_store``; interaction CRUD in ``profiles._interaction_store``.
|
|
5
|
+
The shared ``_build_tags_sql`` helper stays module-level in ``_profiles`` (it is
|
|
6
|
+
used by both this bucket and ProfileStore) and is imported here.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from reflexio.models.api_schema.retriever_schema import (
|
|
13
|
+
SearchInteractionRequest,
|
|
14
|
+
SearchUserProfileRequest,
|
|
15
|
+
)
|
|
16
|
+
from reflexio.models.api_schema.service_schemas import (
|
|
17
|
+
Interaction,
|
|
18
|
+
Status,
|
|
19
|
+
UserProfile,
|
|
20
|
+
)
|
|
21
|
+
from reflexio.models.config_schema import SearchMode
|
|
22
|
+
|
|
23
|
+
from .._base import (
|
|
24
|
+
SQLiteStorageBase,
|
|
25
|
+
_build_status_sql,
|
|
26
|
+
_effective_search_mode,
|
|
27
|
+
_epoch_now,
|
|
28
|
+
_row_to_interaction,
|
|
29
|
+
_row_to_profile,
|
|
30
|
+
_sanitize_fts_query,
|
|
31
|
+
_true_rrf_merge,
|
|
32
|
+
_vector_rank_rows,
|
|
33
|
+
)
|
|
34
|
+
from .._profiles import _build_tags_sql
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ProfileSearchMixin:
|
|
40
|
+
"""Mixin providing profile + interaction search."""
|
|
41
|
+
|
|
42
|
+
# Type hints for instance attributes/methods provided by SQLiteStorageBase via MRO
|
|
43
|
+
_fetchall: Any
|
|
44
|
+
|
|
45
|
+
# ------------------------------------------------------------------
|
|
46
|
+
# Search — Interactions & Profiles
|
|
47
|
+
# ------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
@SQLiteStorageBase.handle_exceptions
|
|
50
|
+
def search_interaction(
|
|
51
|
+
self,
|
|
52
|
+
search_interaction_request: SearchInteractionRequest,
|
|
53
|
+
query_embedding: list[float] | None = None,
|
|
54
|
+
) -> list[Interaction]:
|
|
55
|
+
req = search_interaction_request
|
|
56
|
+
has_query = bool(req.query)
|
|
57
|
+
match_count = req.most_recent_k or 10
|
|
58
|
+
mode = _effective_search_mode(req.search_mode, query_embedding, req.query)
|
|
59
|
+
|
|
60
|
+
conditions: list[str] = ["i.user_id = ?"]
|
|
61
|
+
params: list[str | int | float] = [req.user_id]
|
|
62
|
+
|
|
63
|
+
if req.request_id:
|
|
64
|
+
conditions.append("i.request_id = ?")
|
|
65
|
+
params.append(req.request_id)
|
|
66
|
+
if req.start_time:
|
|
67
|
+
conditions.append("i.created_at >= ?")
|
|
68
|
+
params.append(req.start_time.timestamp())
|
|
69
|
+
if req.end_time:
|
|
70
|
+
conditions.append("i.created_at <= ?")
|
|
71
|
+
params.append(req.end_time.timestamp())
|
|
72
|
+
|
|
73
|
+
where_clause = " AND ".join(conditions)
|
|
74
|
+
overfetch = match_count * 5 if mode != SearchMode.FTS else match_count
|
|
75
|
+
|
|
76
|
+
# Vector-only: rank by embedding similarity
|
|
77
|
+
if (
|
|
78
|
+
mode in (SearchMode.VECTOR, SearchMode.HYBRID)
|
|
79
|
+
and query_embedding
|
|
80
|
+
and not has_query
|
|
81
|
+
):
|
|
82
|
+
vector_limit = match_count * 10
|
|
83
|
+
sql = f"""SELECT i.* FROM interactions i
|
|
84
|
+
WHERE {where_clause}
|
|
85
|
+
ORDER BY i.created_at DESC
|
|
86
|
+
LIMIT ?"""
|
|
87
|
+
rows = self._fetchall(sql, (*params, vector_limit))
|
|
88
|
+
rows = _vector_rank_rows(rows, query_embedding, match_count)
|
|
89
|
+
elif has_query:
|
|
90
|
+
# FTS search (with optional HYBRID re-ranking)
|
|
91
|
+
fts_query = _sanitize_fts_query(req.query) # type: ignore[arg-type]
|
|
92
|
+
fts_conditions = ["interactions_fts MATCH ?", *conditions]
|
|
93
|
+
fts_where = " AND ".join(fts_conditions)
|
|
94
|
+
fts_params: list[str | int | float] = [fts_query, *params, overfetch]
|
|
95
|
+
sql = f"""SELECT i.* FROM interactions i
|
|
96
|
+
JOIN interactions_fts f ON i.interaction_id = f.rowid
|
|
97
|
+
WHERE {fts_where}
|
|
98
|
+
ORDER BY bm25(interactions_fts, 1.0, 2.0)
|
|
99
|
+
LIMIT ?"""
|
|
100
|
+
fts_rows = self._fetchall(sql, tuple(fts_params))
|
|
101
|
+
|
|
102
|
+
if mode == SearchMode.HYBRID and query_embedding:
|
|
103
|
+
vec_limit = match_count * 10
|
|
104
|
+
vec_sql = f"""SELECT i.* FROM interactions i
|
|
105
|
+
WHERE {where_clause}
|
|
106
|
+
ORDER BY i.created_at DESC
|
|
107
|
+
LIMIT ?"""
|
|
108
|
+
vec_candidates = self._fetchall(vec_sql, (*params, vec_limit))
|
|
109
|
+
vec_rows = _vector_rank_rows(vec_candidates, query_embedding, overfetch)
|
|
110
|
+
rows = _true_rrf_merge(
|
|
111
|
+
fts_rows,
|
|
112
|
+
vec_rows,
|
|
113
|
+
"interaction_id",
|
|
114
|
+
match_count,
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
rows = fts_rows[:match_count]
|
|
118
|
+
else:
|
|
119
|
+
if req.most_recent_k:
|
|
120
|
+
# No query — just fetch most recent interactions by time
|
|
121
|
+
sql = f"""SELECT i.* FROM interactions i
|
|
122
|
+
WHERE {where_clause}
|
|
123
|
+
ORDER BY i.created_at DESC
|
|
124
|
+
LIMIT ?"""
|
|
125
|
+
rows = self._fetchall(sql, (*params, req.most_recent_k))
|
|
126
|
+
return [_row_to_interaction(r) for r in reversed(rows)]
|
|
127
|
+
return []
|
|
128
|
+
|
|
129
|
+
interactions = [_row_to_interaction(r) for r in rows]
|
|
130
|
+
if req.most_recent_k:
|
|
131
|
+
sorted_ints = sorted(interactions, key=lambda x: x.created_at, reverse=True)
|
|
132
|
+
return list(reversed(sorted_ints[: req.most_recent_k]))
|
|
133
|
+
return interactions
|
|
134
|
+
|
|
135
|
+
@SQLiteStorageBase.handle_exceptions
|
|
136
|
+
def search_user_profile( # noqa: C901
|
|
137
|
+
self,
|
|
138
|
+
search_user_profile_request: SearchUserProfileRequest,
|
|
139
|
+
status_filter: list[Status | None] | None = None,
|
|
140
|
+
query_embedding: list[float] | None = None,
|
|
141
|
+
) -> list[UserProfile]:
|
|
142
|
+
if status_filter is None:
|
|
143
|
+
status_filter = [None]
|
|
144
|
+
|
|
145
|
+
req = search_user_profile_request
|
|
146
|
+
match_count = req.top_k or 10
|
|
147
|
+
current_ts = _epoch_now()
|
|
148
|
+
has_query = bool(req.query)
|
|
149
|
+
mode = _effective_search_mode(req.search_mode, query_embedding, req.query)
|
|
150
|
+
has_embedding = query_embedding is not None
|
|
151
|
+
logger.info(
|
|
152
|
+
"Profile search: requested_mode=%s, effective_mode=%s, has_query=%s, has_embedding=%s, user_id=%s",
|
|
153
|
+
req.search_mode,
|
|
154
|
+
mode,
|
|
155
|
+
has_query,
|
|
156
|
+
has_embedding,
|
|
157
|
+
req.user_id,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
conditions: list[str] = ["p.expiration_timestamp >= ?"]
|
|
161
|
+
params: list[object] = [current_ts]
|
|
162
|
+
|
|
163
|
+
if req.user_id:
|
|
164
|
+
conditions.append("p.user_id = ?")
|
|
165
|
+
params.append(req.user_id)
|
|
166
|
+
if req.start_time:
|
|
167
|
+
conditions.append("p.last_modified_timestamp >= ?")
|
|
168
|
+
params.append(int(req.start_time.timestamp()))
|
|
169
|
+
if req.end_time:
|
|
170
|
+
conditions.append("p.last_modified_timestamp <= ?")
|
|
171
|
+
params.append(int(req.end_time.timestamp()))
|
|
172
|
+
if req.source:
|
|
173
|
+
conditions.append("LOWER(p.source) = LOWER(?)")
|
|
174
|
+
params.append(req.source)
|
|
175
|
+
if status_filter is not None:
|
|
176
|
+
frag, sparams = _build_status_sql(status_filter)
|
|
177
|
+
conditions.append(frag)
|
|
178
|
+
params.extend(sparams)
|
|
179
|
+
tag_frag, tag_params = _build_tags_sql("p", req.tags)
|
|
180
|
+
if tag_frag:
|
|
181
|
+
conditions.append(tag_frag)
|
|
182
|
+
params.extend(tag_params)
|
|
183
|
+
|
|
184
|
+
where_clause = " AND ".join(conditions)
|
|
185
|
+
overfetch = match_count * 5 if mode != SearchMode.FTS else match_count
|
|
186
|
+
|
|
187
|
+
# Pure vector search: fetch all candidates, rank by cosine similarity
|
|
188
|
+
if mode == SearchMode.VECTOR and query_embedding:
|
|
189
|
+
if req.generated_from_request_id:
|
|
190
|
+
conditions.append("p.generated_from_request_id = ?")
|
|
191
|
+
params.append(req.generated_from_request_id)
|
|
192
|
+
where_clause = " AND ".join(conditions)
|
|
193
|
+
sql = f"""SELECT p.* FROM profiles p
|
|
194
|
+
WHERE {where_clause}
|
|
195
|
+
ORDER BY p.last_modified_timestamp DESC"""
|
|
196
|
+
rows = self._fetchall(sql, tuple(params))
|
|
197
|
+
logger.info(
|
|
198
|
+
"VECTOR search: %d candidates fetched, ranking by embedding", len(rows)
|
|
199
|
+
)
|
|
200
|
+
rows = _vector_rank_rows(rows, query_embedding, match_count)
|
|
201
|
+
elif has_query:
|
|
202
|
+
fts_query = _sanitize_fts_query(req.query) # type: ignore[arg-type]
|
|
203
|
+
sql = f"""SELECT p.* FROM profiles p
|
|
204
|
+
JOIN profiles_fts f ON p.profile_id = f.profile_id
|
|
205
|
+
WHERE profiles_fts MATCH ?
|
|
206
|
+
AND {where_clause}
|
|
207
|
+
ORDER BY bm25(profiles_fts, 0.0, 1.0)
|
|
208
|
+
LIMIT ?"""
|
|
209
|
+
params_list: list[object] = [fts_query, *params, overfetch]
|
|
210
|
+
fts_rows = self._fetchall(sql, tuple(params_list))
|
|
211
|
+
logger.info("FTS search: %d results from BM25", len(fts_rows))
|
|
212
|
+
|
|
213
|
+
if mode == SearchMode.HYBRID and query_embedding:
|
|
214
|
+
logger.info("HYBRID merging FTS + vector results via RRF")
|
|
215
|
+
vec_limit = match_count * 10
|
|
216
|
+
vec_sql = f"""SELECT p.* FROM profiles p
|
|
217
|
+
WHERE {where_clause}
|
|
218
|
+
ORDER BY p.last_modified_timestamp DESC
|
|
219
|
+
LIMIT ?"""
|
|
220
|
+
vec_candidates = self._fetchall(vec_sql, (*params, vec_limit))
|
|
221
|
+
vec_rows = _vector_rank_rows(vec_candidates, query_embedding, overfetch)
|
|
222
|
+
rows = _true_rrf_merge(
|
|
223
|
+
fts_rows,
|
|
224
|
+
vec_rows,
|
|
225
|
+
"profile_id",
|
|
226
|
+
match_count,
|
|
227
|
+
)
|
|
228
|
+
else:
|
|
229
|
+
rows = fts_rows
|
|
230
|
+
elif query_embedding:
|
|
231
|
+
# HYBRID without query text: rank by embedding only
|
|
232
|
+
if req.generated_from_request_id:
|
|
233
|
+
conditions.append("p.generated_from_request_id = ?")
|
|
234
|
+
params.append(req.generated_from_request_id)
|
|
235
|
+
where_clause = " AND ".join(conditions)
|
|
236
|
+
sql = f"""SELECT p.* FROM profiles p
|
|
237
|
+
WHERE {where_clause}
|
|
238
|
+
ORDER BY p.last_modified_timestamp DESC"""
|
|
239
|
+
rows = self._fetchall(sql, tuple(params))
|
|
240
|
+
logger.info(
|
|
241
|
+
"HYBRID (no query text) search: %d candidates, ranking by embedding",
|
|
242
|
+
len(rows),
|
|
243
|
+
)
|
|
244
|
+
rows = _vector_rank_rows(rows, query_embedding, match_count)
|
|
245
|
+
else:
|
|
246
|
+
if req.generated_from_request_id:
|
|
247
|
+
conditions.append("p.generated_from_request_id = ?")
|
|
248
|
+
params.append(req.generated_from_request_id)
|
|
249
|
+
where_clause = " AND ".join(conditions)
|
|
250
|
+
sql = f"""SELECT p.* FROM profiles p
|
|
251
|
+
WHERE {where_clause}
|
|
252
|
+
ORDER BY p.last_modified_timestamp DESC
|
|
253
|
+
LIMIT ?"""
|
|
254
|
+
params_list = [*params, overfetch]
|
|
255
|
+
rows = self._fetchall(sql, tuple(params_list))
|
|
256
|
+
|
|
257
|
+
profiles = [_row_to_profile(r) for r in rows]
|
|
258
|
+
logger.info("Profile search: %d profiles before post-filtering", len(profiles))
|
|
259
|
+
|
|
260
|
+
# Apply filters that can't easily go into SQL
|
|
261
|
+
filtered: list[UserProfile] = []
|
|
262
|
+
for profile in profiles:
|
|
263
|
+
if req.custom_feature and (
|
|
264
|
+
req.custom_feature.lower() not in str(profile.custom_features).lower()
|
|
265
|
+
):
|
|
266
|
+
continue
|
|
267
|
+
filtered.append(profile)
|
|
268
|
+
if len(filtered) >= match_count:
|
|
269
|
+
break
|
|
270
|
+
return filtered
|
|
@@ -25,21 +25,45 @@ from ._base import BaseStorageCore, matches_status_filter
|
|
|
25
25
|
from ._extras import ExtrasMixin
|
|
26
26
|
from ._lineage import EntityType, LineageEventMixin
|
|
27
27
|
from ._operations import OperationMixin
|
|
28
|
-
from ._playbook import PlaybookMixin
|
|
29
|
-
from ._profiles import ProfileMixin
|
|
30
28
|
from ._requests import RequestMixin
|
|
31
29
|
from ._retrieval_log import RetrievalLogMixin
|
|
32
30
|
from ._shadow_verdicts import ShadowVerdictsMixin
|
|
33
31
|
from ._share_links import ShareLinkMixin
|
|
34
32
|
from ._stall_state import StallStateMixin
|
|
33
|
+
from .governance import (
|
|
34
|
+
AuditEventStoreMixin,
|
|
35
|
+
GovernanceEraseExecutionMixin,
|
|
36
|
+
PurgeOperationStoreMixin,
|
|
37
|
+
RebuildHideMixin,
|
|
38
|
+
SubjectBarrierMixin,
|
|
39
|
+
)
|
|
40
|
+
from .playbook import (
|
|
41
|
+
AgentEvaluationResultStoreMixin,
|
|
42
|
+
AgentPlaybookStoreMixin,
|
|
43
|
+
OptimizationJobStoreMixin,
|
|
44
|
+
PlaybookSourceLinkageMixin,
|
|
45
|
+
UserPlaybookStoreMixin,
|
|
46
|
+
)
|
|
47
|
+
from .profiles import InteractionStoreMixin, ProfileSearchMixin, ProfileStoreMixin
|
|
35
48
|
|
|
36
49
|
|
|
37
50
|
class BaseStorage(
|
|
38
51
|
AgentRunMixin,
|
|
39
|
-
|
|
52
|
+
ProfileStoreMixin,
|
|
53
|
+
InteractionStoreMixin,
|
|
54
|
+
ProfileSearchMixin,
|
|
40
55
|
RequestMixin,
|
|
41
|
-
|
|
56
|
+
AgentPlaybookStoreMixin,
|
|
57
|
+
UserPlaybookStoreMixin,
|
|
58
|
+
PlaybookSourceLinkageMixin,
|
|
59
|
+
OptimizationJobStoreMixin,
|
|
60
|
+
AgentEvaluationResultStoreMixin,
|
|
42
61
|
RetrievalLogMixin,
|
|
62
|
+
AuditEventStoreMixin,
|
|
63
|
+
PurgeOperationStoreMixin,
|
|
64
|
+
SubjectBarrierMixin,
|
|
65
|
+
GovernanceEraseExecutionMixin,
|
|
66
|
+
RebuildHideMixin,
|
|
43
67
|
LineageEventMixin,
|
|
44
68
|
OperationMixin,
|
|
45
69
|
ExtrasMixin,
|
|
@@ -150,8 +174,8 @@ class BaseStorage(
|
|
|
150
174
|
interaction_count = len(self.get_user_interaction(user_id))
|
|
151
175
|
|
|
152
176
|
# All statuses a user's row can have — including tombstones (SUPERSEDED,
|
|
153
|
-
# MERGED). Erasure MUST reach every row the user owns regardless
|
|
154
|
-
# status; the old filter excluded tombstones, leaving them in the DB
|
|
177
|
+
# MERGED, EXPIRED). Erasure MUST reach every row the user owns regardless
|
|
178
|
+
# of status; the old filter excluded tombstones, leaving them in the DB
|
|
155
179
|
# after clear_user_data (GDPR regression).
|
|
156
180
|
_all_statuses: list[Status | None] = [
|
|
157
181
|
None, # CURRENT
|
|
@@ -160,6 +184,7 @@ class BaseStorage(
|
|
|
160
184
|
Status.ARCHIVE_IN_PROGRESS,
|
|
161
185
|
Status.SUPERSEDED,
|
|
162
186
|
Status.MERGED,
|
|
187
|
+
Status.EXPIRED,
|
|
163
188
|
]
|
|
164
189
|
|
|
165
190
|
# Snapshot user_playbook ids for the user and partition into
|
|
@@ -178,10 +203,14 @@ class BaseStorage(
|
|
|
178
203
|
)
|
|
179
204
|
|
|
180
205
|
# Snapshot profile ids for the user and partition into
|
|
181
|
-
# purge vs delete sets.
|
|
206
|
+
# purge vs delete sets. include_expired=True drops the
|
|
207
|
+
# expiration_timestamp >= now guard so EXPIRED tombstones (expiry in
|
|
208
|
+
# the past) are enumerated and reached by the erasure path.
|
|
182
209
|
raw_profile_ids = [
|
|
183
210
|
p.profile_id
|
|
184
|
-
for p in self.get_user_profile(
|
|
211
|
+
for p in self.get_user_profile(
|
|
212
|
+
user_id, status_filter=_all_statuses, include_expired=True
|
|
213
|
+
)
|
|
185
214
|
if p.profile_id is not None
|
|
186
215
|
]
|
|
187
216
|
purge_profile_ids, delete_profile_ids = self._partition_purge_vs_delete(
|
|
@@ -244,7 +273,19 @@ __all__ = [
|
|
|
244
273
|
"PendingToolCallRecord",
|
|
245
274
|
"PendingToolCallStatus",
|
|
246
275
|
"PendingToolCallUpsertResult",
|
|
247
|
-
"
|
|
276
|
+
"AgentEvaluationResultStoreMixin",
|
|
277
|
+
"AuditEventStoreMixin",
|
|
278
|
+
"PurgeOperationStoreMixin",
|
|
279
|
+
"SubjectBarrierMixin",
|
|
280
|
+
"GovernanceEraseExecutionMixin",
|
|
281
|
+
"RebuildHideMixin",
|
|
282
|
+
"AgentPlaybookStoreMixin",
|
|
283
|
+
"OptimizationJobStoreMixin",
|
|
284
|
+
"PlaybookSourceLinkageMixin",
|
|
285
|
+
"UserPlaybookStoreMixin",
|
|
286
|
+
"InteractionStoreMixin",
|
|
287
|
+
"ProfileSearchMixin",
|
|
288
|
+
"ProfileStoreMixin",
|
|
248
289
|
"RetrievalLogMixin",
|
|
249
290
|
"PriorAnswerMatch",
|
|
250
291
|
"RunToolDependencyKind",
|