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/storage_base/profiles/_search.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
|
|
3
|
+
from reflexio.models.api_schema.domain import (
|
|
4
|
+
Interaction,
|
|
5
|
+
Status,
|
|
6
|
+
UserProfile,
|
|
7
|
+
)
|
|
8
|
+
from reflexio.models.api_schema.retriever_schema import (
|
|
9
|
+
SearchInteractionRequest,
|
|
10
|
+
SearchUserProfileRequest,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ProfileSearchMixin:
|
|
15
|
+
"""Mixin for interaction + profile search methods."""
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def search_interaction(
|
|
19
|
+
self,
|
|
20
|
+
search_interaction_request: SearchInteractionRequest,
|
|
21
|
+
query_embedding: list[float] | None = None,
|
|
22
|
+
) -> list[Interaction]:
|
|
23
|
+
raise NotImplementedError
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def search_user_profile(
|
|
27
|
+
self,
|
|
28
|
+
search_user_profile_request: SearchUserProfileRequest,
|
|
29
|
+
status_filter: list[Status | None] | None = None,
|
|
30
|
+
query_embedding: list[float] | None = None,
|
|
31
|
+
) -> list[UserProfile]:
|
|
32
|
+
raise NotImplementedError
|
|
@@ -17,6 +17,7 @@ from collections import OrderedDict
|
|
|
17
17
|
from collections.abc import Callable
|
|
18
18
|
from concurrent.futures import Future, ThreadPoolExecutor
|
|
19
19
|
from concurrent.futures import TimeoutError as FuturesTimeoutError
|
|
20
|
+
from datetime import UTC, datetime
|
|
20
21
|
from typing import TYPE_CHECKING, Any, cast
|
|
21
22
|
|
|
22
23
|
from reflexio.models.api_schema.retriever_schema import (
|
|
@@ -41,6 +42,13 @@ from reflexio.models.config_schema import (
|
|
|
41
42
|
from reflexio.server.llm.litellm_client import LiteLLMClient
|
|
42
43
|
from reflexio.server.prompt.prompt_manager import PromptManager
|
|
43
44
|
from reflexio.server.services.pre_retrieval import QueryReformulator
|
|
45
|
+
from reflexio.server.services.retrieval.recency import (
|
|
46
|
+
RecencyConfig,
|
|
47
|
+
ScoredItem,
|
|
48
|
+
additive_penalty,
|
|
49
|
+
decay_for_item,
|
|
50
|
+
multiplicative_factor,
|
|
51
|
+
)
|
|
44
52
|
from reflexio.server.services.retrieval.relevance_floor import apply_relevance_floors
|
|
45
53
|
from reflexio.server.services.storage.storage_base import BaseStorage
|
|
46
54
|
from reflexio.server.tracing import profile_step, set_span_data
|
|
@@ -102,6 +110,7 @@ def run_unified_search(
|
|
|
102
110
|
prompt_manager: PromptManager,
|
|
103
111
|
pre_retrieval_model_name: str | None = None,
|
|
104
112
|
retrieval_floor: RetrievalFloorConfig | None = None,
|
|
113
|
+
recency: RecencyConfig | None = None,
|
|
105
114
|
) -> UnifiedSearchResponse:
|
|
106
115
|
"""
|
|
107
116
|
Search across all entity types (profiles, agent playbooks, user playbooks) in parallel.
|
|
@@ -129,7 +138,12 @@ def run_unified_search(
|
|
|
129
138
|
|
|
130
139
|
floor_cfg = retrieval_floor or RetrievalFloorConfig()
|
|
131
140
|
floor_on = floor_cfg.enabled
|
|
132
|
-
|
|
141
|
+
recency_on = bool(recency and recency.enabled)
|
|
142
|
+
fetch_k = max(
|
|
143
|
+
top_k,
|
|
144
|
+
floor_cfg.pool_size if floor_on else 0,
|
|
145
|
+
recency.pool_size if recency_on and recency is not None else 0,
|
|
146
|
+
)
|
|
133
147
|
|
|
134
148
|
# --- Phase A: query reformulation + embedding generation ---
|
|
135
149
|
reformulated_query, embedding = _run_phase_a(
|
|
@@ -153,6 +167,7 @@ def run_unified_search(
|
|
|
153
167
|
query=reformulated_query,
|
|
154
168
|
top_k=fetch_k,
|
|
155
169
|
threshold=threshold,
|
|
170
|
+
recency_on=recency_on,
|
|
156
171
|
)
|
|
157
172
|
|
|
158
173
|
if profiles is None:
|
|
@@ -166,7 +181,28 @@ def run_unified_search(
|
|
|
166
181
|
user_playbooks=user_playbooks, # type: ignore[arg-type]
|
|
167
182
|
top_k=top_k,
|
|
168
183
|
cfg=floor_cfg,
|
|
184
|
+
recency=recency if recency_on else None,
|
|
185
|
+
)
|
|
186
|
+
elif recency_on and recency is not None:
|
|
187
|
+
profiles = _apply_combined_score_recency(
|
|
188
|
+
profiles or [], entity_type="profiles", top_k=top_k, cfg=recency
|
|
189
|
+
)
|
|
190
|
+
agent_playbooks = _apply_combined_score_recency(
|
|
191
|
+
agent_playbooks or [],
|
|
192
|
+
entity_type="agent_playbooks",
|
|
193
|
+
top_k=top_k,
|
|
194
|
+
cfg=recency,
|
|
195
|
+
)
|
|
196
|
+
user_playbooks = _apply_combined_score_recency(
|
|
197
|
+
user_playbooks or [],
|
|
198
|
+
entity_type="user_playbooks",
|
|
199
|
+
top_k=top_k,
|
|
200
|
+
cfg=recency,
|
|
169
201
|
)
|
|
202
|
+
else:
|
|
203
|
+
profiles = _unwrap_items(profiles or [])[:top_k]
|
|
204
|
+
agent_playbooks = _unwrap_items(agent_playbooks or [])[:top_k]
|
|
205
|
+
user_playbooks = _unwrap_items(user_playbooks or [])[:top_k]
|
|
170
206
|
|
|
171
207
|
user_playbooks = _suppress_source_user_playbooks(
|
|
172
208
|
storage=storage,
|
|
@@ -284,10 +320,11 @@ def _run_phase_b(
|
|
|
284
320
|
query: str,
|
|
285
321
|
top_k: int,
|
|
286
322
|
threshold: float,
|
|
323
|
+
recency_on: bool = False,
|
|
287
324
|
) -> tuple[
|
|
288
|
-
list[
|
|
289
|
-
list[
|
|
290
|
-
list[
|
|
325
|
+
list[Any] | None,
|
|
326
|
+
list[Any] | None,
|
|
327
|
+
list[Any] | None,
|
|
291
328
|
]:
|
|
292
329
|
"""Run parallel searches across all entity types by delegating to storage methods.
|
|
293
330
|
|
|
@@ -314,9 +351,18 @@ def _run_phase_b(
|
|
|
314
351
|
entity_types=sorted(entity_types),
|
|
315
352
|
top_k=top_k,
|
|
316
353
|
) as span:
|
|
317
|
-
|
|
354
|
+
# Recency needs the per-row ``combined_score``, which only the scored
|
|
355
|
+
# single-RPC method threads back. Backends that don't advertise
|
|
356
|
+
# ``supports_unified_hybrid_search`` (e.g. native Postgres, which still
|
|
357
|
+
# inherits ``unified_hybrid_search_scored`` and runs it via the same
|
|
358
|
+
# ``_rpc`` it already uses for ``hybrid_match_*``) opt into the scored
|
|
359
|
+
# path only when recency is on, so non-recency routing is unchanged.
|
|
360
|
+
wants_scored_single_rpc = recency_on and callable(
|
|
361
|
+
getattr(storage, "unified_hybrid_search_scored", None)
|
|
362
|
+
)
|
|
363
|
+
if _unified_single_rpc_enabled() and (
|
|
318
364
|
getattr(storage, "supports_unified_hybrid_search", False)
|
|
319
|
-
|
|
365
|
+
or wants_scored_single_rpc
|
|
320
366
|
):
|
|
321
367
|
combined = _run_phase_b_single_rpc(
|
|
322
368
|
request=request,
|
|
@@ -327,6 +373,7 @@ def _run_phase_b(
|
|
|
327
373
|
threshold=threshold,
|
|
328
374
|
entity_types=entity_types,
|
|
329
375
|
allowed_agent_statuses=allowed_agent_statuses,
|
|
376
|
+
recency_on=recency_on,
|
|
330
377
|
)
|
|
331
378
|
if combined is not None:
|
|
332
379
|
profiles, agent_playbooks, user_playbooks = combined
|
|
@@ -437,7 +484,8 @@ def _run_phase_b_single_rpc(
|
|
|
437
484
|
threshold: float,
|
|
438
485
|
entity_types: set[str],
|
|
439
486
|
allowed_agent_statuses: list[PlaybookStatus] | None,
|
|
440
|
-
|
|
487
|
+
recency_on: bool = False,
|
|
488
|
+
) -> tuple[list[Any], list[Any], list[Any]] | None:
|
|
441
489
|
"""Run all Phase B arms through one combined storage round trip.
|
|
442
490
|
|
|
443
491
|
Trades the per-arm round-trip overhead for serialized execution of the
|
|
@@ -458,8 +506,16 @@ def _run_phase_b_single_rpc(
|
|
|
458
506
|
)
|
|
459
507
|
# Resolve storage.unified_hybrid_search before submit so missing or stale
|
|
460
508
|
# capability flags can fall back to the fan-out path.
|
|
461
|
-
|
|
509
|
+
method_name = (
|
|
510
|
+
"unified_hybrid_search_scored" if recency_on else "unified_hybrid_search"
|
|
511
|
+
)
|
|
512
|
+
unified_hybrid_search = getattr(storage, method_name, None)
|
|
462
513
|
if not callable(unified_hybrid_search):
|
|
514
|
+
if recency_on:
|
|
515
|
+
logger.warning(
|
|
516
|
+
"event=search_recency_missing_scores source=single_rpc method=%s",
|
|
517
|
+
method_name,
|
|
518
|
+
)
|
|
463
519
|
return None
|
|
464
520
|
|
|
465
521
|
future = _submit_with_current_context(
|
|
@@ -490,13 +546,14 @@ def _run_phase_b_single_rpc(
|
|
|
490
546
|
return None
|
|
491
547
|
|
|
492
548
|
# Mirror _search_agent_playbooks_via_storage: dedupe by id, cap at top_k.
|
|
493
|
-
deduped: list[
|
|
549
|
+
deduped: list[Any] = []
|
|
494
550
|
seen_ids: set[str] = set()
|
|
495
|
-
for
|
|
551
|
+
for candidate in agent_playbooks:
|
|
552
|
+
playbook = _unwrap_item(candidate)
|
|
496
553
|
playbook_id = str(getattr(playbook, "agent_playbook_id", ""))
|
|
497
554
|
if playbook_id and playbook_id not in seen_ids:
|
|
498
555
|
seen_ids.add(playbook_id)
|
|
499
|
-
deduped.append(
|
|
556
|
+
deduped.append(candidate)
|
|
500
557
|
if len(deduped) >= top_k:
|
|
501
558
|
break
|
|
502
559
|
return profiles, deduped, user_playbooks
|
|
@@ -509,6 +566,7 @@ def _apply_floors(
|
|
|
509
566
|
user_playbooks: list[UserPlaybook],
|
|
510
567
|
top_k: int,
|
|
511
568
|
cfg: RetrievalFloorConfig,
|
|
569
|
+
recency: RecencyConfig | None = None,
|
|
512
570
|
) -> tuple[list[UserProfile], list[AgentPlaybook], list[UserPlaybook]]:
|
|
513
571
|
"""Apply the per-arm relevance floor with one batched cross-encoder call."""
|
|
514
572
|
floored_profiles, floored_agent, floored_user = apply_relevance_floors(
|
|
@@ -519,8 +577,91 @@ def _apply_floors(
|
|
|
519
577
|
("user_playbooks", user_playbooks, cfg.user_playbook_floor),
|
|
520
578
|
],
|
|
521
579
|
top_k,
|
|
580
|
+
content_of=lambda item: _unwrap_item(item).content,
|
|
522
581
|
)
|
|
523
|
-
return
|
|
582
|
+
return (
|
|
583
|
+
_finalize_floor_arm(
|
|
584
|
+
floored_profiles, entity_type="profiles", top_k=top_k, recency=recency
|
|
585
|
+
),
|
|
586
|
+
_finalize_floor_arm(
|
|
587
|
+
floored_agent,
|
|
588
|
+
entity_type="agent_playbooks",
|
|
589
|
+
top_k=top_k,
|
|
590
|
+
recency=recency,
|
|
591
|
+
),
|
|
592
|
+
_finalize_floor_arm(
|
|
593
|
+
floored_user,
|
|
594
|
+
entity_type="user_playbooks",
|
|
595
|
+
top_k=top_k,
|
|
596
|
+
recency=recency,
|
|
597
|
+
),
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _finalize_floor_arm(
|
|
602
|
+
result: Any,
|
|
603
|
+
*,
|
|
604
|
+
entity_type: str,
|
|
605
|
+
top_k: int,
|
|
606
|
+
recency: RecencyConfig | None,
|
|
607
|
+
) -> list[Any]:
|
|
608
|
+
if not recency or not recency.enabled:
|
|
609
|
+
return _unwrap_items(result.items)[:top_k]
|
|
610
|
+
if result.scores is None:
|
|
611
|
+
return _apply_combined_score_recency(
|
|
612
|
+
result.items, entity_type=entity_type, top_k=top_k, cfg=recency
|
|
613
|
+
)
|
|
614
|
+
now = int(datetime.now(UTC).timestamp())
|
|
615
|
+
rescored = []
|
|
616
|
+
for item, score in zip(result.items, result.scores, strict=True):
|
|
617
|
+
unwrapped = _unwrap_item(item)
|
|
618
|
+
freshness = decay_for_item(unwrapped, entity_type=entity_type, now=now)
|
|
619
|
+
rescored.append(
|
|
620
|
+
(unwrapped, score - additive_penalty(freshness, recency.max_penalty_logit))
|
|
621
|
+
)
|
|
622
|
+
rescored.sort(key=lambda pair: pair[1], reverse=True)
|
|
623
|
+
return [item for item, _score in rescored[:top_k]]
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def _apply_combined_score_recency(
|
|
627
|
+
items: list[Any],
|
|
628
|
+
*,
|
|
629
|
+
entity_type: str,
|
|
630
|
+
top_k: int,
|
|
631
|
+
cfg: RecencyConfig,
|
|
632
|
+
) -> list[Any]:
|
|
633
|
+
if not items:
|
|
634
|
+
return []
|
|
635
|
+
scored_items: list[tuple[Any, float]] = []
|
|
636
|
+
for item in items:
|
|
637
|
+
if not isinstance(item, ScoredItem) or item.score is None:
|
|
638
|
+
logger.warning(
|
|
639
|
+
"event=search_recency_missing_scores entity_type=%s items=%d",
|
|
640
|
+
entity_type,
|
|
641
|
+
len(items),
|
|
642
|
+
)
|
|
643
|
+
return _unwrap_items(items)[:top_k]
|
|
644
|
+
scored_items.append((item.item, item.score))
|
|
645
|
+
now = int(datetime.now(UTC).timestamp())
|
|
646
|
+
rescored = []
|
|
647
|
+
for item, score in scored_items:
|
|
648
|
+
freshness = decay_for_item(item, entity_type=entity_type, now=now)
|
|
649
|
+
rescored.append(
|
|
650
|
+
(
|
|
651
|
+
item,
|
|
652
|
+
score * multiplicative_factor(freshness, cfg.max_penalty_frac),
|
|
653
|
+
)
|
|
654
|
+
)
|
|
655
|
+
rescored.sort(key=lambda pair: pair[1], reverse=True)
|
|
656
|
+
return [item for item, _score in rescored[:top_k]]
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def _unwrap_item(item: Any) -> Any:
|
|
660
|
+
return item.item if isinstance(item, ScoredItem) else item
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def _unwrap_items(items: list[Any]) -> list[Any]:
|
|
664
|
+
return [_unwrap_item(item) for item in items]
|
|
524
665
|
|
|
525
666
|
|
|
526
667
|
def _suppress_source_user_playbooks(
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from collections.abc import Callable
|
|
4
|
-
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Protocol
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@dataclass(frozen=True)
|
|
9
|
-
class DetectedEntity:
|
|
10
|
-
start: int
|
|
11
|
-
end: int
|
|
12
|
-
entity_type: str
|
|
13
|
-
replacement: str
|
|
14
|
-
confidence: float
|
|
15
|
-
source: str
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@dataclass(frozen=True)
|
|
19
|
-
class StrippingResult:
|
|
20
|
-
text: str
|
|
21
|
-
detections: list[DetectedEntity] = field(default_factory=list)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class UserDetailDetector(Protocol):
|
|
25
|
-
def detect(self, text: str) -> list[DetectedEntity]: ...
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class UserDetailStripper(Protocol):
|
|
29
|
-
prompt_extra_instructions: str | None
|
|
30
|
-
|
|
31
|
-
def strip_user_details(
|
|
32
|
-
self,
|
|
33
|
-
text: str,
|
|
34
|
-
shared_mapping: dict[str, int] | None = None,
|
|
35
|
-
) -> StrippingResult: ...
|
|
36
|
-
|
|
37
|
-
def sanitize_aggregation_output_text(
|
|
38
|
-
self,
|
|
39
|
-
text: str | None,
|
|
40
|
-
) -> tuple[str | None, int]: ...
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class PassthroughStripper:
|
|
44
|
-
prompt_extra_instructions: str | None = None
|
|
45
|
-
|
|
46
|
-
def strip_user_details(
|
|
47
|
-
self,
|
|
48
|
-
text: str,
|
|
49
|
-
shared_mapping: dict[str, int] | None = None, # noqa: ARG002
|
|
50
|
-
) -> StrippingResult:
|
|
51
|
-
return StrippingResult(text=text, detections=[])
|
|
52
|
-
|
|
53
|
-
def sanitize_aggregation_output_text(
|
|
54
|
-
self,
|
|
55
|
-
text: str | None,
|
|
56
|
-
) -> tuple[str | None, int]:
|
|
57
|
-
return text, 0
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
UserDetailStripperFactory = Callable[[object], UserDetailStripper | None]
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def _default_user_detail_stripper_factory(
|
|
64
|
-
_configurator: object,
|
|
65
|
-
) -> UserDetailStripper | None:
|
|
66
|
-
return None
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
_user_detail_stripper_factory: UserDetailStripperFactory = (
|
|
70
|
-
_default_user_detail_stripper_factory
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def set_user_detail_stripper_factory(factory: UserDetailStripperFactory) -> None:
|
|
75
|
-
"""Register the deployment-specific aggregation stripper factory."""
|
|
76
|
-
global _user_detail_stripper_factory # noqa: PLW0603
|
|
77
|
-
_user_detail_stripper_factory = factory
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def create_aggregation_user_detail_stripper(
|
|
81
|
-
configurator: object,
|
|
82
|
-
) -> UserDetailStripper | None:
|
|
83
|
-
"""Create the deployment-specific stripper for aggregation, if any."""
|
|
84
|
-
return _user_detail_stripper_factory(configurator)
|