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
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Usage-metric + ② Learning billing helpers for ``BaseGenerationService`` (Tier-1b decomposition).
|
|
2
|
+
|
|
3
|
+
``UsageBillingMixin`` holds the money-critical Bucket A: the usage-event helpers
|
|
4
|
+
(``_usage_pipeline``, ``_usage_context``, ``_record_generation_event``) and the
|
|
5
|
+
② Learning billing path (``_extraction_input_text``, ``_record_billing_learning_events``,
|
|
6
|
+
plus the pure ``_count_generated_results`` staticmethod).
|
|
7
|
+
|
|
8
|
+
These helpers only READ the per-run accumulators (``_last_token_totals``,
|
|
9
|
+
``_last_precheck_sessions``, ``service_config``, ``EMITS_LEARNING_BILLING``) — they
|
|
10
|
+
never write them, so moving them introduces no ordering/race change. The billing
|
|
11
|
+
emit-ordering (``record_learnings_generated`` BEFORE ``record_extraction_tokens``,
|
|
12
|
+
success-path only) lives in ``_run_generation`` on the base and is unchanged.
|
|
13
|
+
|
|
14
|
+
SINK-3 (money-critical): the ``billing_meter`` / ``billing_signals`` imports inside
|
|
15
|
+
``_record_billing_learning_events`` stay FUNCTION-LOCAL — the Phase-A drain test
|
|
16
|
+
patches the terminal emitters at their source module (``reflexio.server.billing_meter``),
|
|
17
|
+
and hoisting the imports to module top would both break that patch seam and risk a
|
|
18
|
+
circular import that the ``:392`` exception-swallow would hide silently. Both
|
|
19
|
+
exception swallows (``_record_billing_learning_events`` and ``_extraction_input_text``)
|
|
20
|
+
are moved VERBATIM. Method bodies are otherwise moved unchanged from the former
|
|
21
|
+
monolithic ``base_generation_service.py``.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
26
|
+
|
|
27
|
+
from reflexio.server.api_endpoints.request_context import RequestContext
|
|
28
|
+
from reflexio.server.llm.token_accounting import RunTokenTotals
|
|
29
|
+
from reflexio.server.services.extractor_interaction_utils import (
|
|
30
|
+
get_effective_source_filter,
|
|
31
|
+
get_extractor_window_params,
|
|
32
|
+
)
|
|
33
|
+
from reflexio.server.services.storage.storage_base import BaseStorage
|
|
34
|
+
from reflexio.server.usage_metrics import record_usage_event
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from reflexio.server.services.base_generation_service import PreparedGenerationRun
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
TExtractorConfig = TypeVar("TExtractorConfig")
|
|
42
|
+
TGenerationServiceConfig = TypeVar("TGenerationServiceConfig")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class UsageBillingMixin(Generic[TExtractorConfig, TGenerationServiceConfig]): # noqa: UP046
|
|
46
|
+
"""Usage-event emission + the ② Learning billing path.
|
|
47
|
+
|
|
48
|
+
Mixed into ``BaseGenerationService`` ahead of the other mixins and ``ABC``; the
|
|
49
|
+
per-run ``self`` attributes these methods read (``service_config``, ``storage``,
|
|
50
|
+
``org_id``, ``request_context`` and the billing accumulators
|
|
51
|
+
``_last_token_totals`` / ``_last_precheck_sessions``) are initialised on the base
|
|
52
|
+
``__init__`` — the annotation-only stubs below give pyright the types without
|
|
53
|
+
introducing shared class-level mutable state.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# Annotation-only stubs for base-owned attributes these helpers read (init'd in
|
|
57
|
+
# the base ``__init__``). NEVER assign here — a class-level default would be a
|
|
58
|
+
# shared-state footgun on a mixin.
|
|
59
|
+
service_config: TGenerationServiceConfig | None
|
|
60
|
+
storage: BaseStorage | None
|
|
61
|
+
org_id: str
|
|
62
|
+
request_context: RequestContext
|
|
63
|
+
# Read-only in this bucket: written by the extraction lifecycle / precheck mixins.
|
|
64
|
+
_last_token_totals: RunTokenTotals | None
|
|
65
|
+
_last_precheck_sessions: list[Any] | None
|
|
66
|
+
# Class-level opt-in flag defined on the base; annotation-only here so pyright can
|
|
67
|
+
# resolve ``self.EMITS_LEARNING_BILLING`` without shadowing the base default.
|
|
68
|
+
EMITS_LEARNING_BILLING: bool
|
|
69
|
+
|
|
70
|
+
if TYPE_CHECKING:
|
|
71
|
+
# Abstract on the base ABC (stays there per SINK-2); declared here type-only so
|
|
72
|
+
# pyright can resolve the ``self._get_service_name()`` call. No runtime
|
|
73
|
+
# attribute is added, so ``__abstractmethods__`` is unaffected.
|
|
74
|
+
def _get_service_name(self) -> str: ...
|
|
75
|
+
# Concrete on ``ShouldRunPrecheckMixin`` (resolved via MRO); declared type-only
|
|
76
|
+
# so pyright can resolve the ``self._get_precheck_interaction_query_kwargs()`` call.
|
|
77
|
+
def _get_precheck_interaction_query_kwargs(self) -> dict: ...
|
|
78
|
+
|
|
79
|
+
def _usage_pipeline(self) -> str | None:
|
|
80
|
+
service_name = self._get_service_name()
|
|
81
|
+
if "profile" in service_name:
|
|
82
|
+
return "profile"
|
|
83
|
+
if "playbook" in service_name:
|
|
84
|
+
return "playbook"
|
|
85
|
+
if "evaluation" in service_name:
|
|
86
|
+
return "evaluation"
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
def _usage_context(self) -> dict[str, Any]:
|
|
90
|
+
service_config = self.service_config
|
|
91
|
+
return {
|
|
92
|
+
"org_id": self.org_id,
|
|
93
|
+
"user_id": getattr(service_config, "user_id", None),
|
|
94
|
+
"request_id": getattr(service_config, "request_id", None),
|
|
95
|
+
"source": getattr(service_config, "source", None),
|
|
96
|
+
"agent_version": getattr(service_config, "agent_version", None),
|
|
97
|
+
"pipeline": self._usage_pipeline(),
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
def _record_generation_event(
|
|
101
|
+
self,
|
|
102
|
+
*,
|
|
103
|
+
event_name: str,
|
|
104
|
+
outcome: str,
|
|
105
|
+
count_value: int = 1,
|
|
106
|
+
duration_ms: int | None = None,
|
|
107
|
+
error_kind: str | None = None,
|
|
108
|
+
metadata: dict[str, Any] | None = None,
|
|
109
|
+
) -> None:
|
|
110
|
+
record_usage_event(
|
|
111
|
+
**self._usage_context(),
|
|
112
|
+
event_name=event_name,
|
|
113
|
+
event_category="generation",
|
|
114
|
+
outcome=outcome,
|
|
115
|
+
count_value=count_value,
|
|
116
|
+
duration_ms=duration_ms,
|
|
117
|
+
error_kind=error_kind,
|
|
118
|
+
metadata=metadata,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def _extraction_input_text(self, prepared: "PreparedGenerationRun[Any]") -> str:
|
|
122
|
+
"""Return the interaction content fed to the extractor as a single string.
|
|
123
|
+
|
|
124
|
+
Replays the same storage query the extractor uses (same user_id, source,
|
|
125
|
+
window params) so ``count_input_tokens`` sees the same text the LLM saw.
|
|
126
|
+
Returns an empty string when storage is unavailable or no interactions exist.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
prepared: The prepared generation run, used to access extractor_config
|
|
130
|
+
for window/source parameters.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
str: Formatted interaction history string, or "" if nothing is found.
|
|
134
|
+
"""
|
|
135
|
+
from reflexio.server.services.service_utils import (
|
|
136
|
+
format_sessions_to_history_string,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if self.storage is None or self.service_config is None:
|
|
140
|
+
return ""
|
|
141
|
+
# Reuse the window the should-run gate already fetched
|
|
142
|
+
# (_collect_scoped_interactions_for_precheck stashed it). Same query params,
|
|
143
|
+
# so the token count is byte-identical — and we avoid a second storage read.
|
|
144
|
+
if self._last_precheck_sessions is not None:
|
|
145
|
+
return format_sessions_to_history_string(self._last_precheck_sessions)
|
|
146
|
+
# Fallback storage read: used ONLY when the gate did not pre-fetch
|
|
147
|
+
# (bypass paths: auto_run=False, force_extraction, skip_should_run_check,
|
|
148
|
+
# mock mode). Re-fetches the extractor's input window so billing token
|
|
149
|
+
# counting sees exactly the text the LLM saw.
|
|
150
|
+
try:
|
|
151
|
+
root_config = self.request_context.configurator.get_config()
|
|
152
|
+
global_window_size = (
|
|
153
|
+
getattr(root_config, "window_size", None) if root_config else None
|
|
154
|
+
)
|
|
155
|
+
global_stride_size = (
|
|
156
|
+
getattr(root_config, "stride_size", None) if root_config else None
|
|
157
|
+
)
|
|
158
|
+
window_size, _ = get_extractor_window_params(
|
|
159
|
+
prepared.extractor_config, global_window_size, global_stride_size
|
|
160
|
+
)
|
|
161
|
+
should_skip, effective_source = get_effective_source_filter(
|
|
162
|
+
prepared.extractor_config, getattr(self.service_config, "source", None)
|
|
163
|
+
)
|
|
164
|
+
if should_skip:
|
|
165
|
+
return ""
|
|
166
|
+
session_data_models, _ = self.storage.get_last_k_interactions_grouped(
|
|
167
|
+
user_id=getattr(self.service_config, "user_id", None),
|
|
168
|
+
k=window_size,
|
|
169
|
+
sources=effective_source,
|
|
170
|
+
start_time=getattr(self.service_config, "rerun_start_time", None),
|
|
171
|
+
end_time=getattr(self.service_config, "rerun_end_time", None),
|
|
172
|
+
**self._get_precheck_interaction_query_kwargs(),
|
|
173
|
+
)
|
|
174
|
+
return format_sessions_to_history_string(session_data_models)
|
|
175
|
+
except Exception:
|
|
176
|
+
logger.warning(
|
|
177
|
+
"_extraction_input_text failed; billing_input_tokens will be 0",
|
|
178
|
+
exc_info=True,
|
|
179
|
+
)
|
|
180
|
+
return ""
|
|
181
|
+
|
|
182
|
+
def _record_billing_learning_events(
|
|
183
|
+
self, *, prepared: "PreparedGenerationRun[Any]", generated_count: int
|
|
184
|
+
) -> None:
|
|
185
|
+
"""Emit the ② Learning billing events. Called ONLY when extraction fired.
|
|
186
|
+
|
|
187
|
+
Emits ``learnings_generated`` (value facet) and ``extraction_tokens``
|
|
188
|
+
(cost facet) via the OSS emission helpers. ``platform_storage`` is left
|
|
189
|
+
``None`` here and resolved enterprise-side at rollup (Phase 1).
|
|
190
|
+
|
|
191
|
+
Gated by ``EMITS_LEARNING_BILLING`` — only profile/playbook generation
|
|
192
|
+
services opt in. Evaluation, reflection, consolidation and aggregation
|
|
193
|
+
are bundled and must NOT separately meter the ② Learning line.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
prepared: The prepared generation run (used for input-text computation).
|
|
197
|
+
generated_count: Number of learnings produced by this extraction run.
|
|
198
|
+
"""
|
|
199
|
+
if not self.EMITS_LEARNING_BILLING:
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
from reflexio.server.billing_meter import (
|
|
204
|
+
record_extraction_tokens,
|
|
205
|
+
record_learnings_generated,
|
|
206
|
+
)
|
|
207
|
+
from reflexio.server.billing_signals import (
|
|
208
|
+
count_input_tokens,
|
|
209
|
+
platform_llm_from_config,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
config = self.request_context.configurator.get_config()
|
|
213
|
+
platform_llm = platform_llm_from_config(config)
|
|
214
|
+
ctx = self._usage_context()
|
|
215
|
+
|
|
216
|
+
# session_id is intentionally not passed: the generation path has no
|
|
217
|
+
# session_id source. _usage_context() never includes it, and neither
|
|
218
|
+
# the Profile/Playbook service configs nor their requests carry a
|
|
219
|
+
# session_id (unlike the Application-line path in server/api.py, which
|
|
220
|
+
# reads request_id/session_id off the search request payload). Learning
|
|
221
|
+
# events therefore meter without session attribution by design.
|
|
222
|
+
|
|
223
|
+
# ② Learning — value: learnings generated (helper no-ops on count <= 0).
|
|
224
|
+
record_learnings_generated(
|
|
225
|
+
org_id=ctx["org_id"],
|
|
226
|
+
count=generated_count,
|
|
227
|
+
platform_llm=platform_llm,
|
|
228
|
+
platform_storage=None,
|
|
229
|
+
pipeline=ctx.get("pipeline"),
|
|
230
|
+
request_id=ctx.get("request_id"),
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# ② Learning — cost: input-anchored extraction tokens + real provider tokens.
|
|
234
|
+
totals = self._last_token_totals or RunTokenTotals()
|
|
235
|
+
billing_input_tokens = count_input_tokens(
|
|
236
|
+
self._extraction_input_text(prepared)
|
|
237
|
+
)
|
|
238
|
+
record_extraction_tokens(
|
|
239
|
+
org_id=ctx["org_id"],
|
|
240
|
+
billing_input_tokens=billing_input_tokens,
|
|
241
|
+
prompt_tokens=totals.prompt_tokens,
|
|
242
|
+
completion_tokens=totals.completion_tokens,
|
|
243
|
+
platform_llm=platform_llm,
|
|
244
|
+
platform_storage=None,
|
|
245
|
+
pipeline=ctx.get("pipeline"),
|
|
246
|
+
request_id=ctx.get("request_id"),
|
|
247
|
+
)
|
|
248
|
+
except Exception:
|
|
249
|
+
logger.warning(
|
|
250
|
+
"_record_billing_learning_events failed; billing learning events not emitted",
|
|
251
|
+
exc_info=True,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
@staticmethod
|
|
255
|
+
def _count_generated_results(result: Any) -> int:
|
|
256
|
+
if isinstance(result, list):
|
|
257
|
+
return len(result)
|
|
258
|
+
return 1 if result else 0
|