claude-smart 0.2.49 → 0.2.50
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 +2 -2
- package/README.md +10 -43
- package/bin/claude-smart.js +105 -0
- package/package.json +3 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.coverage +0 -0
- package/plugin/README.md +4 -3
- package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +4 -2
- package/plugin/dashboard/app/dashboard/page.tsx +6 -1
- package/plugin/dashboard/app/preferences/[id]/page.tsx +18 -6
- package/plugin/dashboard/app/preferences/page.tsx +32 -35
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +16 -1
- package/plugin/dashboard/app/sessions/page.tsx +2 -0
- package/plugin/dashboard/app/skills/page.tsx +65 -50
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +17 -8
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +1 -6
- package/plugin/dashboard/components/common/host-badge.tsx +118 -0
- package/plugin/dashboard/components/common/learning-application-badge.tsx +34 -0
- package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
- package/plugin/dashboard/components/common/page-header.tsx +3 -3
- package/plugin/dashboard/lib/config-file.ts +5 -1
- package/plugin/dashboard/lib/host-attribution.ts +62 -0
- package/plugin/dashboard/lib/session-reader.ts +40 -2
- package/plugin/dashboard/lib/types.ts +7 -1
- package/plugin/pyproject.toml +1 -1
- package/plugin/scripts/_lib.sh +197 -1
- package/plugin/scripts/backend-python-runner.py +46 -0
- package/plugin/scripts/backend-service.sh +757 -119
- package/plugin/scripts/codex-hook.js +63 -225
- package/plugin/scripts/dashboard-open.sh +6 -4
- package/plugin/scripts/dashboard-service.sh +117 -136
- package/plugin/scripts/hook_entry.sh +3 -0
- package/plugin/scripts/smart-install.sh +15 -1
- package/plugin/src/claude_smart/cli.py +14 -0
- package/plugin/src/claude_smart/context_inject.py +3 -0
- package/plugin/src/claude_smart/env_config.py +4 -1
- package/plugin/src/claude_smart/events/post_tool.py +2 -1
- package/plugin/src/claude_smart/events/session_end.py +2 -1
- package/plugin/src/claude_smart/events/stop.py +3 -0
- package/plugin/src/claude_smart/events/user_prompt.py +2 -1
- package/plugin/src/claude_smart/internal_call.py +5 -2
- package/plugin/src/claude_smart/optimizer_assistant.py +59 -13
- package/plugin/src/claude_smart/publish.py +59 -7
- package/plugin/src/claude_smart/reflexio_adapter.py +137 -14
- package/plugin/src/claude_smart/runtime.py +15 -6
- package/plugin/src/claude_smart/state.py +211 -52
- package/plugin/uv.lock +1 -1
- package/plugin/vendor/reflexio/.env.example +13 -0
- package/plugin/vendor/reflexio/reflexio/README.md +7 -3
- package/plugin/vendor/reflexio/reflexio/__init__.py +12 -0
- package/plugin/vendor/reflexio/reflexio/client/client.py +126 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +10 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +28 -0
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +41 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +177 -25
- package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +7 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +2 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +63 -4
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +8 -1
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/README.md +22 -4
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +18 -8
- package/plugin/vendor/reflexio/reflexio/server/__main__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/api.py +66 -3
- package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +263 -3
- package/plugin/vendor/reflexio/reflexio/server/callback_executor.py +164 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +81 -81
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +62 -5
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -52
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +9 -1
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedder_warmup.py +329 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +85 -10
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +20 -5
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +77 -9
- package/plugin/vendor/reflexio/reflexio/server/org_fanout.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.1.0.prompt.md +32 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.4.0.prompt.md +63 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v2.0.0.prompt.md +30 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_impact/v1.0.0.prompt.md +51 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_relevance/v1.0.0.prompt.md +39 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.1.0.prompt.md +43 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/config.py +3 -3
- package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +122 -28
- package/plugin/vendor/reflexio/reflexio/server/routes/system.py +22 -3
- package/plugin/vendor/reflexio/reflexio/server/scheduling.py +64 -3
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +6 -4
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +41 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/retrieved_learning_evaluator.py +554 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +35 -1
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/runner.py +253 -101
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/scheduler.py +5 -7
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/service.py +27 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +11 -5
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +6 -2
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +255 -74
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +72 -0
- package/plugin/vendor/reflexio/reflexio/server/services/deferred_learning_plan.py +270 -0
- package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/worker.py +85 -16
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/hero_state.py +2 -11
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/rule_attribution.py +6 -2
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +17 -13
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +18 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +2 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +66 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +510 -63
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +179 -99
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/vector_backfill_sweep.py +139 -0
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +66 -27
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_trigger.py +177 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +68 -2
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +360 -49
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/extractor.py +27 -30
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +8 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +137 -69
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +20 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +13 -6
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +40 -25
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/consolidator.py +12 -39
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +30 -35
- package/plugin/vendor/reflexio/reflexio/server/services/profile/service.py +122 -44
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/service.py +457 -107
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/session_dedup.py +127 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/temporal.py +104 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/dispatcher.py +139 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +16 -6
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/worker.py +137 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +12 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/lifecycle_filters.py +54 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +41 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +40 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +50 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +11 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +28 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_learning_jobs.py +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +16 -5
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +104 -42
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +7 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +43 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +430 -5
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +45 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +146 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +42 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +9 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py +50 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +3 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/evaluation_state_keys.py +78 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +171 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +52 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +59 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_profile_store.py +52 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/retrieved_learning_state.py +226 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +5 -6
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +209 -29
- package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +3 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +33 -0
- package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
|
@@ -2,14 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Fetches all requests and interactions for a session,
|
|
4
4
|
checks completion status, runs evaluation, and marks the group as evaluated.
|
|
5
|
+
|
|
6
|
+
Also runs the retrieved-learning relevance/impact evaluation for the session
|
|
7
|
+
after agent-success work completes. The two completions are independent: the
|
|
8
|
+
existing ``agent_success_group_eval`` marker stays agent-success-only, and the
|
|
9
|
+
retrieved evaluation keeps its own generation/fingerprint-fenced state (see
|
|
10
|
+
``storage_base/retrieved_learning_state.py``).
|
|
5
11
|
"""
|
|
6
12
|
|
|
7
13
|
import logging
|
|
8
|
-
import random
|
|
9
14
|
from collections import defaultdict
|
|
15
|
+
from dataclasses import dataclass
|
|
10
16
|
from datetime import UTC, datetime
|
|
17
|
+
from typing import Literal
|
|
11
18
|
|
|
12
|
-
from reflexio.models.api_schema.domain.entities import Interaction
|
|
13
19
|
from reflexio.models.api_schema.internal_schema import RequestInteractionDataModel
|
|
14
20
|
from reflexio.server.api_endpoints.request_context import RequestContext
|
|
15
21
|
from reflexio.server.llm.litellm_client import LiteLLMClient
|
|
@@ -18,6 +24,9 @@ from reflexio.server.services.agent_success_evaluation._eval_health import SkipR
|
|
|
18
24
|
from reflexio.server.services.agent_success_evaluation.agent_success_evaluation_utils import (
|
|
19
25
|
AgentSuccessEvaluationRequest,
|
|
20
26
|
)
|
|
27
|
+
from reflexio.server.services.agent_success_evaluation.components.retrieved_learning_evaluator import (
|
|
28
|
+
RetrievedLearningEvaluator,
|
|
29
|
+
)
|
|
21
30
|
from reflexio.server.services.agent_success_evaluation.scheduler import (
|
|
22
31
|
_EFFECTIVE_DELAY_SECONDS,
|
|
23
32
|
)
|
|
@@ -25,17 +34,53 @@ from reflexio.server.services.agent_success_evaluation.service import (
|
|
|
25
34
|
AgentSuccessEvaluationService,
|
|
26
35
|
)
|
|
27
36
|
from reflexio.server.services.extractor_config_utils import get_extractor_name
|
|
28
|
-
from reflexio.server.services.
|
|
37
|
+
from reflexio.server.services.storage.storage_base.evaluation_state_keys import (
|
|
38
|
+
build_agent_success_marker_key,
|
|
39
|
+
)
|
|
40
|
+
from reflexio.server.services.storage.storage_base.retrieved_learning_state import (
|
|
41
|
+
session_fingerprint,
|
|
42
|
+
)
|
|
29
43
|
|
|
30
44
|
logger = logging.getLogger(__name__)
|
|
31
45
|
|
|
32
46
|
# Key prefix for operation state tracking
|
|
33
47
|
OPERATION_STATE_KEY_PREFIX = "agent_success_group_eval"
|
|
34
48
|
|
|
49
|
+
type AgentSuccessInvocationStatus = Literal[
|
|
50
|
+
"complete", "failed", "not_applicable", "skipped"
|
|
51
|
+
]
|
|
52
|
+
type RetrievedLearningInvocationStatus = Literal[
|
|
53
|
+
"pending",
|
|
54
|
+
"complete",
|
|
55
|
+
"degraded",
|
|
56
|
+
"failed",
|
|
57
|
+
"not_applicable",
|
|
58
|
+
"stale",
|
|
59
|
+
"superseded",
|
|
60
|
+
"skipped",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class GroupEvaluationOutcome:
|
|
66
|
+
"""What one runner invocation did, per evaluation family.
|
|
67
|
+
|
|
68
|
+
``retrieved_learning_fingerprint`` carries the session fingerprint at
|
|
69
|
+
which a terminal/applied retrieved outcome linearized; ``None`` for
|
|
70
|
+
nonterminal or skipped-without-state outcomes.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
agent_success_status: AgentSuccessInvocationStatus
|
|
74
|
+
retrieved_learning_status: RetrievedLearningInvocationStatus
|
|
75
|
+
retrieved_learning_fingerprint: str | None = None
|
|
76
|
+
|
|
35
77
|
|
|
36
78
|
def _build_state_key(org_id: str, user_id: str, session_id: str) -> str:
|
|
37
79
|
"""Build the operation state key for a session.
|
|
38
80
|
|
|
81
|
+
Delegates to the shared key builder so governance erasure scrubs the
|
|
82
|
+
exact same keys this runner writes.
|
|
83
|
+
|
|
39
84
|
Args:
|
|
40
85
|
org_id: Organization ID
|
|
41
86
|
user_id: User ID
|
|
@@ -44,7 +89,7 @@ def _build_state_key(org_id: str, user_id: str, session_id: str) -> str:
|
|
|
44
89
|
Returns:
|
|
45
90
|
str: The operation state key
|
|
46
91
|
"""
|
|
47
|
-
return
|
|
92
|
+
return build_agent_success_marker_key(org_id, user_id, session_id)
|
|
48
93
|
|
|
49
94
|
|
|
50
95
|
def run_group_evaluation(
|
|
@@ -57,7 +102,7 @@ def run_group_evaluation(
|
|
|
57
102
|
llm_client: LiteLLMClient,
|
|
58
103
|
*,
|
|
59
104
|
force_regenerate: bool = False,
|
|
60
|
-
) ->
|
|
105
|
+
) -> GroupEvaluationOutcome:
|
|
61
106
|
"""Run agent success evaluation for an entire session.
|
|
62
107
|
|
|
63
108
|
Steps:
|
|
@@ -84,30 +129,23 @@ def run_group_evaluation(
|
|
|
84
129
|
force_regenerate: When True, bypass the already-evaluated short-circuit
|
|
85
130
|
and the completeness delay gate so the regenerate worker can
|
|
86
131
|
re-evaluate sessions of any age regardless of prior state.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
GroupEvaluationOutcome: Per-family statuses for this invocation.
|
|
87
135
|
"""
|
|
88
136
|
storage = request_context.storage
|
|
89
137
|
state_key = _build_state_key(org_id, user_id, session_id)
|
|
90
138
|
|
|
91
|
-
# 1.
|
|
92
|
-
# regenerate worker can re-evaluate a session that's already been marked.
|
|
93
|
-
if not force_regenerate:
|
|
94
|
-
existing_state = storage.get_operation_state(state_key) # type: ignore[reportOptionalMemberAccess]
|
|
95
|
-
if existing_state and isinstance(existing_state.get("operation_state"), dict):
|
|
96
|
-
op_state = existing_state["operation_state"]
|
|
97
|
-
if op_state.get("evaluated"):
|
|
98
|
-
_eval_health.record_skip(SkipReason.ALREADY_EVALUATED)
|
|
99
|
-
logger.info("Session %s already evaluated, skipping", session_id)
|
|
100
|
-
return
|
|
101
|
-
|
|
102
|
-
# 2. Fetch all requests for the session
|
|
139
|
+
# 1. Fetch all requests for the session
|
|
103
140
|
requests = storage.get_requests_by_session(user_id, session_id) # type: ignore[reportOptionalMemberAccess]
|
|
104
141
|
if not requests:
|
|
105
142
|
_eval_health.record_skip(SkipReason.NO_REQUESTS)
|
|
106
143
|
logger.info("No requests found for session %s, skipping", session_id)
|
|
107
|
-
return
|
|
144
|
+
return GroupEvaluationOutcome("not_applicable", "skipped")
|
|
108
145
|
|
|
109
|
-
#
|
|
110
|
-
# force_regenerate mode so the operator can re-evaluate any session.
|
|
146
|
+
# 2. Verify completion: latest request must be >= delay ago — skipped in
|
|
147
|
+
# force_regenerate mode so the operator can re-evaluate any session. The
|
|
148
|
+
# liveness gate applies to both evaluation families.
|
|
111
149
|
if not force_regenerate:
|
|
112
150
|
latest_created_at = max(r.created_at for r in requests)
|
|
113
151
|
now = int(datetime.now(UTC).timestamp())
|
|
@@ -120,7 +158,36 @@ def run_group_evaluation(
|
|
|
120
158
|
elapsed,
|
|
121
159
|
_EFFECTIVE_DELAY_SECONDS,
|
|
122
160
|
)
|
|
123
|
-
return
|
|
161
|
+
return GroupEvaluationOutcome("skipped", "skipped")
|
|
162
|
+
|
|
163
|
+
# 3. Check if agent success is already evaluated — skipped in
|
|
164
|
+
# force_regenerate mode so the regenerate worker can re-evaluate a session
|
|
165
|
+
# that's already been marked. Retrieved-learning evaluation still runs
|
|
166
|
+
# below: its completion is independent of the agent-success marker.
|
|
167
|
+
agent_success_already_evaluated = False
|
|
168
|
+
if not force_regenerate:
|
|
169
|
+
existing_state = storage.get_operation_state(state_key) # type: ignore[reportOptionalMemberAccess]
|
|
170
|
+
if existing_state and isinstance(existing_state.get("operation_state"), dict):
|
|
171
|
+
op_state = existing_state["operation_state"]
|
|
172
|
+
if op_state.get("evaluated"):
|
|
173
|
+
_eval_health.record_skip(SkipReason.ALREADY_EVALUATED)
|
|
174
|
+
logger.info(
|
|
175
|
+
"Session %s already evaluated (agent success), skipping to"
|
|
176
|
+
" retrieved-learning evaluation",
|
|
177
|
+
session_id,
|
|
178
|
+
)
|
|
179
|
+
agent_success_already_evaluated = True
|
|
180
|
+
|
|
181
|
+
if agent_success_already_evaluated:
|
|
182
|
+
return _finish_with_retrieved_evaluation(
|
|
183
|
+
"skipped",
|
|
184
|
+
user_id=user_id,
|
|
185
|
+
session_id=session_id,
|
|
186
|
+
agent_version=agent_version,
|
|
187
|
+
request_context=request_context,
|
|
188
|
+
llm_client=llm_client,
|
|
189
|
+
force_regenerate=force_regenerate,
|
|
190
|
+
)
|
|
124
191
|
|
|
125
192
|
# 4. Fetch interactions for all requests
|
|
126
193
|
request_ids = [r.request_id for r in requests]
|
|
@@ -128,7 +195,7 @@ def run_group_evaluation(
|
|
|
128
195
|
if not all_interactions:
|
|
129
196
|
_eval_health.record_skip(SkipReason.NO_INTERACTIONS)
|
|
130
197
|
logger.info("No interactions found for session %s, skipping", session_id)
|
|
131
|
-
return
|
|
198
|
+
return GroupEvaluationOutcome("not_applicable", "skipped")
|
|
132
199
|
|
|
133
200
|
# Group interactions by request_id
|
|
134
201
|
interactions_by_request: dict[str, list] = defaultdict(list)
|
|
@@ -157,7 +224,7 @@ def run_group_evaluation(
|
|
|
157
224
|
"No request interaction data models built for session %s, skipping",
|
|
158
225
|
session_id,
|
|
159
226
|
)
|
|
160
|
-
return
|
|
227
|
+
return GroupEvaluationOutcome("not_applicable", "skipped")
|
|
161
228
|
|
|
162
229
|
# 5. When regenerating, capture the prior result_ids
|
|
163
230
|
# so we can delete ONLY them AFTER the new rows have been saved. Doing
|
|
@@ -206,7 +273,7 @@ def run_group_evaluation(
|
|
|
206
273
|
evaluation_service.last_run_save_failed,
|
|
207
274
|
len(old_result_ids),
|
|
208
275
|
)
|
|
209
|
-
return
|
|
276
|
+
return GroupEvaluationOutcome("failed", "skipped")
|
|
210
277
|
|
|
211
278
|
if evaluation_service.last_run_saved_result_count == 0:
|
|
212
279
|
logger.warning(
|
|
@@ -215,21 +282,7 @@ def run_group_evaluation(
|
|
|
215
282
|
session_id,
|
|
216
283
|
len(old_result_ids),
|
|
217
284
|
)
|
|
218
|
-
return
|
|
219
|
-
|
|
220
|
-
# F1: per-turn shadow comparison. Dispatched only AFTER the regular
|
|
221
|
-
# success eval succeeds — a session whose success grade is unreliable
|
|
222
|
-
# would yield noisy verdicts that mislead the headline metric. The
|
|
223
|
-
# dispatch loop swallows per-interaction failures so one judge call
|
|
224
|
-
# cannot abort an entire batch.
|
|
225
|
-
_dispatch_shadow_comparison_judge(
|
|
226
|
-
storage=storage,
|
|
227
|
-
interactions=all_interactions,
|
|
228
|
-
session_id=session_id,
|
|
229
|
-
agent_version=agent_version,
|
|
230
|
-
request_context=request_context,
|
|
231
|
-
llm_client=llm_client,
|
|
232
|
-
)
|
|
285
|
+
return GroupEvaluationOutcome("failed", "skipped")
|
|
233
286
|
|
|
234
287
|
# 6. New rows saved successfully — now safe to remove the captured prior
|
|
235
288
|
# rows. New rows have fresh auto-increment result_ids that do not overlap
|
|
@@ -254,85 +307,184 @@ def run_group_evaluation(
|
|
|
254
307
|
)
|
|
255
308
|
logger.info("Marked session %s as evaluated at %d", session_id, evaluated_at)
|
|
256
309
|
|
|
310
|
+
# 8. Retrieved-learning evaluation — independent completion; a failure
|
|
311
|
+
# here can never be reported as complete nor force agent-success rows to
|
|
312
|
+
# regenerate.
|
|
313
|
+
return _finish_with_retrieved_evaluation(
|
|
314
|
+
"complete",
|
|
315
|
+
user_id=user_id,
|
|
316
|
+
session_id=session_id,
|
|
317
|
+
agent_version=agent_version,
|
|
318
|
+
request_context=request_context,
|
|
319
|
+
llm_client=llm_client,
|
|
320
|
+
force_regenerate=force_regenerate,
|
|
321
|
+
)
|
|
322
|
+
|
|
257
323
|
|
|
258
|
-
def
|
|
324
|
+
def _finish_with_retrieved_evaluation(
|
|
325
|
+
agent_success_status: AgentSuccessInvocationStatus,
|
|
259
326
|
*,
|
|
260
|
-
|
|
261
|
-
interactions: list[Interaction],
|
|
327
|
+
user_id: str,
|
|
262
328
|
session_id: str,
|
|
263
329
|
agent_version: str,
|
|
264
330
|
request_context: RequestContext,
|
|
265
331
|
llm_client: LiteLLMClient,
|
|
266
|
-
|
|
267
|
-
|
|
332
|
+
force_regenerate: bool,
|
|
333
|
+
) -> GroupEvaluationOutcome:
|
|
334
|
+
"""Run the retrieved-learning phase best-effort and build the outcome."""
|
|
335
|
+
try:
|
|
336
|
+
retrieved_status, fingerprint = _run_retrieved_learning_evaluation(
|
|
337
|
+
user_id=user_id,
|
|
338
|
+
session_id=session_id,
|
|
339
|
+
agent_version=agent_version,
|
|
340
|
+
request_context=request_context,
|
|
341
|
+
llm_client=llm_client,
|
|
342
|
+
force_regenerate=force_regenerate,
|
|
343
|
+
)
|
|
344
|
+
except Exception:
|
|
345
|
+
# Best-effort: never let the retrieved phase break the runner. The
|
|
346
|
+
# generation-guarded state was not advanced to a terminal status, so
|
|
347
|
+
# the next scheduled or forced run retries.
|
|
348
|
+
logger.exception(
|
|
349
|
+
"event=retrieved_learning_eval_failed session_id=%s reason=unexpected_error",
|
|
350
|
+
session_id,
|
|
351
|
+
)
|
|
352
|
+
_eval_health.record_retrieved_outcome("failed")
|
|
353
|
+
_eval_health.record_producer_failure()
|
|
354
|
+
return GroupEvaluationOutcome(agent_success_status, "failed")
|
|
355
|
+
return GroupEvaluationOutcome(agent_success_status, retrieved_status, fingerprint)
|
|
268
356
|
|
|
269
|
-
Iterates the session's interactions, skips any without ``shadow_content``,
|
|
270
|
-
invokes :class:`ShadowComparisonJudge.judge_turn`, and persists each
|
|
271
|
-
returned verdict via ``storage.save_shadow_comparison_verdict``. Per-
|
|
272
|
-
interaction exceptions are logged and the loop continues — partial
|
|
273
|
-
verdict sets are strictly better than nothing for the headline metric.
|
|
274
357
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
358
|
+
def _run_retrieved_learning_evaluation(
|
|
359
|
+
*,
|
|
360
|
+
user_id: str,
|
|
361
|
+
session_id: str,
|
|
362
|
+
agent_version: str,
|
|
363
|
+
request_context: RequestContext,
|
|
364
|
+
llm_client: LiteLLMClient,
|
|
365
|
+
force_regenerate: bool,
|
|
366
|
+
) -> tuple[RetrievedLearningInvocationStatus, str | None]:
|
|
367
|
+
"""Evaluate the session's retrieved learnings with fencing.
|
|
368
|
+
|
|
369
|
+
Implements the generation + session-fingerprint protocol: allocate a
|
|
370
|
+
generation, judge the post-allocation snapshot, and atomically replace
|
|
371
|
+
the session's result set only while the fingerprint recomputed under the
|
|
372
|
+
replacement lock still matches. One immediate retry on a stale snapshot,
|
|
373
|
+
then ``pending`` for the next trigger.
|
|
290
374
|
|
|
291
375
|
Returns:
|
|
292
|
-
|
|
293
|
-
|
|
376
|
+
tuple: (invocation status, session fingerprint for terminal/applied
|
|
377
|
+
outcomes else None).
|
|
294
378
|
"""
|
|
295
|
-
|
|
296
|
-
|
|
379
|
+
storage = request_context.storage
|
|
380
|
+
if storage is None:
|
|
381
|
+
return "skipped", None
|
|
382
|
+
|
|
383
|
+
snapshot = storage.load_bounded_retrieved_learning_snapshot(user_id, session_id)
|
|
384
|
+
fingerprint = session_fingerprint(snapshot)
|
|
385
|
+
|
|
386
|
+
# Fast path: terminal state at this exact fingerprint — nothing changed.
|
|
387
|
+
if not force_regenerate:
|
|
388
|
+
terminal = storage.get_matching_retrieved_learning_terminal_state(
|
|
389
|
+
user_id, session_id, fingerprint
|
|
390
|
+
)
|
|
391
|
+
if terminal:
|
|
392
|
+
status = terminal.get("status")
|
|
393
|
+
if status in ("complete", "not_applicable"):
|
|
394
|
+
return status, fingerprint
|
|
395
|
+
|
|
396
|
+
config = request_context.configurator.get_config()
|
|
397
|
+
agent_success = config.agent_success_config if config else None
|
|
398
|
+
success_definition = (
|
|
399
|
+
agent_success.success_definition_prompt.strip()
|
|
400
|
+
if agent_success and agent_success.success_definition_prompt
|
|
401
|
+
else ""
|
|
402
|
+
)
|
|
403
|
+
evaluator = RetrievedLearningEvaluator(
|
|
404
|
+
request_context=request_context,
|
|
297
405
|
llm_client=llm_client,
|
|
298
|
-
|
|
299
|
-
|
|
406
|
+
agent_context=(config.agent_context_prompt or "") if config else "",
|
|
407
|
+
success_definition=success_definition,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
logger.info(
|
|
411
|
+
"event=retrieved_learning_eval_started session_id=%s raw_attachments=%d",
|
|
412
|
+
session_id,
|
|
413
|
+
snapshot.raw_attachment_count,
|
|
300
414
|
)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
415
|
+
|
|
416
|
+
generation = 0
|
|
417
|
+
for _stale_attempt in range(2):
|
|
418
|
+
generation = storage.begin_retrieved_learning_evaluation_run(
|
|
419
|
+
user_id, session_id
|
|
420
|
+
)
|
|
421
|
+
# Judge the post-allocation snapshot, never the freshness-check one:
|
|
422
|
+
# every mutation is either visible here or changes the fingerprint
|
|
423
|
+
# that replacement recomputes under lock at commit time.
|
|
424
|
+
snapshot = storage.load_bounded_retrieved_learning_snapshot(user_id, session_id)
|
|
425
|
+
fingerprint = session_fingerprint(snapshot)
|
|
426
|
+
run = evaluator.evaluate(user_id, session_id, agent_version, snapshot)
|
|
427
|
+
if run.outcome == "failed":
|
|
428
|
+
storage.finish_retrieved_learning_evaluation_run(
|
|
429
|
+
user_id, session_id, generation, "failed", run.diagnostics
|
|
313
430
|
)
|
|
314
|
-
except Exception as exc: # noqa: BLE001 — judge failure must not abort batch
|
|
315
431
|
logger.warning(
|
|
316
|
-
"
|
|
317
|
-
|
|
318
|
-
|
|
432
|
+
"event=retrieved_learning_eval_failed session_id=%s reason=%s",
|
|
433
|
+
session_id,
|
|
434
|
+
run.diagnostics.get("error_type", "unknown"),
|
|
319
435
|
)
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
436
|
+
_eval_health.record_retrieved_outcome("failed", diagnostics=run.diagnostics)
|
|
437
|
+
_eval_health.record_producer_failure()
|
|
438
|
+
return "failed", None
|
|
439
|
+
commit = storage.replace_retrieved_learning_evaluation_results(
|
|
440
|
+
user_id,
|
|
441
|
+
session_id,
|
|
442
|
+
generation,
|
|
443
|
+
fingerprint,
|
|
444
|
+
run.proposed_status,
|
|
445
|
+
run.diagnostics,
|
|
446
|
+
run.rows,
|
|
447
|
+
)
|
|
448
|
+
if commit.disposition == "applied":
|
|
449
|
+
# An applied commit's authoritative status is always one of
|
|
450
|
+
# complete/degraded/not_applicable (the storage layer validates
|
|
451
|
+
# proposed_status and may only downgrade to not_applicable).
|
|
452
|
+
final_status: RetrievedLearningInvocationStatus = (
|
|
453
|
+
commit.status
|
|
454
|
+
if commit.status in ("complete", "degraded", "not_applicable")
|
|
455
|
+
else run.proposed_status
|
|
331
456
|
)
|
|
332
|
-
|
|
333
|
-
|
|
457
|
+
logger.info(
|
|
458
|
+
"event=retrieved_learning_eval_%s session_id=%s candidates=%d"
|
|
459
|
+
" committed=%d failed_relevance_chunks=%s failed_impact_chunks=%s",
|
|
460
|
+
"completed"
|
|
461
|
+
if final_status in ("complete", "not_applicable")
|
|
462
|
+
else final_status,
|
|
463
|
+
session_id,
|
|
464
|
+
len(run.rows),
|
|
465
|
+
commit.committed_count,
|
|
466
|
+
run.diagnostics.get("failed_relevance_chunks", 0),
|
|
467
|
+
run.diagnostics.get("failed_impact_chunks", 0),
|
|
468
|
+
)
|
|
469
|
+
_eval_health.record_retrieved_outcome(
|
|
470
|
+
final_status, diagnostics=run.diagnostics
|
|
471
|
+
)
|
|
472
|
+
return final_status, fingerprint
|
|
473
|
+
if commit.disposition == "superseded":
|
|
474
|
+
logger.info(
|
|
475
|
+
"event=retrieved_learning_eval_superseded session_id=%s generation=%d",
|
|
476
|
+
session_id,
|
|
477
|
+
generation,
|
|
478
|
+
)
|
|
479
|
+
return "superseded", None
|
|
334
480
|
logger.info(
|
|
335
|
-
"
|
|
336
|
-
saved_count,
|
|
481
|
+
"event=retrieved_learning_eval_stale session_id=%s generation=%d",
|
|
337
482
|
session_id,
|
|
483
|
+
generation,
|
|
338
484
|
)
|
|
485
|
+
|
|
486
|
+
# Two stale snapshots in a row: leave pending for the next trigger.
|
|
487
|
+
storage.finish_retrieved_learning_evaluation_run(
|
|
488
|
+
user_id, session_id, generation, "pending", {"error_type": "stale_snapshot"}
|
|
489
|
+
)
|
|
490
|
+
return "pending", None
|
package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/scheduler.py
CHANGED
|
@@ -13,7 +13,9 @@ import os
|
|
|
13
13
|
import threading
|
|
14
14
|
import time
|
|
15
15
|
from collections.abc import Callable
|
|
16
|
+
from functools import partial
|
|
16
17
|
|
|
18
|
+
from reflexio.server.callback_executor import submit_callback
|
|
17
19
|
from reflexio.server.services.agent_success_evaluation import _eval_health
|
|
18
20
|
|
|
19
21
|
logger = logging.getLogger(__name__)
|
|
@@ -135,14 +137,10 @@ class GroupEvaluationScheduler:
|
|
|
135
137
|
# Remove from scheduled map and fire
|
|
136
138
|
del self._scheduled[key]
|
|
137
139
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
args=(key, callback),
|
|
142
|
-
daemon=True,
|
|
143
|
-
name=f"group-eval-{key[2][:20]}",
|
|
140
|
+
submit_callback(
|
|
141
|
+
f"group-eval-{key[2][:20]}",
|
|
142
|
+
partial(self._run_callback, key, callback),
|
|
144
143
|
)
|
|
145
|
-
t.start()
|
|
146
144
|
|
|
147
145
|
except Exception:
|
|
148
146
|
logger.exception("Error in group evaluation scheduler loop")
|
|
@@ -213,6 +213,33 @@ class AgentSuccessEvaluationService(
|
|
|
213
213
|
attempt,
|
|
214
214
|
)
|
|
215
215
|
|
|
216
|
+
def _resolve_write_plan(self, results: list) -> None:
|
|
217
|
+
"""Compute-half — write the evaluation results HERE (never durable-split).
|
|
218
|
+
|
|
219
|
+
Agent-success evaluation was never split into a durable compute/persist
|
|
220
|
+
pair: its results never flow through the durable worker's fence, so the
|
|
221
|
+
save stays inline in compute exactly as before, routed through the
|
|
222
|
+
permanent ``_finalize_extracted_items`` -> ``_process_results`` path
|
|
223
|
+
(which has its own retry/backoff + EvalHealth accounting). Returns
|
|
224
|
+
``None`` so ``persist_generation`` invokes no further write. This is the
|
|
225
|
+
concrete default that keeps ``AgentSuccessEvaluationService`` instantiable
|
|
226
|
+
after ``BaseGenerationService`` flipped ``_resolve_write_plan`` /
|
|
227
|
+
``_persist_write_plan`` to ``@abstractmethod`` (gate b, Task 8).
|
|
228
|
+
"""
|
|
229
|
+
for result in results:
|
|
230
|
+
if result:
|
|
231
|
+
self._finalize_extracted_items(result)
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
def _persist_write_plan(self, plan: object) -> None:
|
|
235
|
+
"""Persist-half — NO-OP: the write already ran in ``_resolve_write_plan``.
|
|
236
|
+
|
|
237
|
+
``_resolve_write_plan`` returns ``None`` so ``persist_generation`` never
|
|
238
|
+
reaches this; it exists only to satisfy the abstract contract.
|
|
239
|
+
"""
|
|
240
|
+
del plan
|
|
241
|
+
return
|
|
242
|
+
|
|
216
243
|
def has_run_failures(self) -> bool:
|
|
217
244
|
"""Return True if extractor execution or result persistence failed."""
|
|
218
245
|
extractor_failed_count = self._last_extractor_run_stats.get("failed", 0)
|
package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py
CHANGED
|
@@ -31,6 +31,7 @@ from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
|
31
31
|
|
|
32
32
|
from reflexio.server.api_endpoints.request_context import RequestContext
|
|
33
33
|
from reflexio.server.llm.token_accounting import RunTokenTotals
|
|
34
|
+
from reflexio.server.services.deferred_learning_plan import ExtractorBookmarkAdvance
|
|
34
35
|
from reflexio.server.services.extraction.outcome import ExtractionOutcome
|
|
35
36
|
from reflexio.server.services.extractor_config_utils import get_extractor_name
|
|
36
37
|
from reflexio.server.services.storage.storage_base import AgentRunStatus, BaseStorage
|
|
@@ -61,6 +62,7 @@ class ExtractionRunLifecycleMixin(Generic[TExtractorConfig, TGenerationServiceCo
|
|
|
61
62
|
_last_extractor_run_stats: dict[str, int]
|
|
62
63
|
_last_extraction_run_ids: list[str]
|
|
63
64
|
_last_token_totals: RunTokenTotals | None
|
|
65
|
+
_last_bookmark_advance: ExtractorBookmarkAdvance | None
|
|
64
66
|
|
|
65
67
|
if TYPE_CHECKING:
|
|
66
68
|
# Abstract on the base ABC (stays there per SINK-2); declared here type-only so
|
|
@@ -117,6 +119,10 @@ class ExtractionRunLifecycleMixin(Generic[TExtractorConfig, TGenerationServiceCo
|
|
|
117
119
|
if result.run_id:
|
|
118
120
|
self._last_extraction_run_ids.append(result.run_id)
|
|
119
121
|
self._last_token_totals = result.token_totals
|
|
122
|
+
# Capture the deferred stride-bookmark advance (F1); applied
|
|
123
|
+
# later in ``persist_generation`` (durable fence) or in
|
|
124
|
+
# ``_run_generation``'s persist half for the synchronous path.
|
|
125
|
+
self._last_bookmark_advance = result.bookmark_advance
|
|
120
126
|
if result.status == "completed" and result.items:
|
|
121
127
|
return result.items
|
|
122
128
|
logger.info(
|
|
@@ -166,8 +172,8 @@ class ExtractionRunLifecycleMixin(Generic[TExtractorConfig, TGenerationServiceCo
|
|
|
166
172
|
"""Mark active agent-run rows failed when the service timeout fires."""
|
|
167
173
|
if self.storage is None or self.service_config is None:
|
|
168
174
|
return
|
|
169
|
-
|
|
170
|
-
if not
|
|
175
|
+
generation_request_id = getattr(self.service_config, "request_id", None)
|
|
176
|
+
if not generation_request_id:
|
|
171
177
|
return
|
|
172
178
|
user_id = getattr(self.service_config, "user_id", None)
|
|
173
179
|
try:
|
|
@@ -175,7 +181,7 @@ class ExtractionRunLifecycleMixin(Generic[TExtractorConfig, TGenerationServiceCo
|
|
|
175
181
|
org_id=self.request_context.org_id,
|
|
176
182
|
extractor_kind=extractor_kind,
|
|
177
183
|
user_id=user_id,
|
|
178
|
-
request_id=
|
|
184
|
+
request_id=generation_request_id,
|
|
179
185
|
last_error=last_error,
|
|
180
186
|
)
|
|
181
187
|
except NotImplementedError:
|
|
@@ -189,10 +195,10 @@ class ExtractionRunLifecycleMixin(Generic[TExtractorConfig, TGenerationServiceCo
|
|
|
189
195
|
return
|
|
190
196
|
if failed_count:
|
|
191
197
|
logger.warning(
|
|
192
|
-
"Marked %d timed-out %s agent run(s) failed for
|
|
198
|
+
"Marked %d timed-out %s agent run(s) failed for generation_request_id=%s",
|
|
193
199
|
failed_count,
|
|
194
200
|
extractor_kind,
|
|
195
|
-
|
|
201
|
+
generation_request_id,
|
|
196
202
|
)
|
|
197
203
|
|
|
198
204
|
def _finalize_extraction_runs(self) -> None:
|
|
@@ -123,10 +123,10 @@ class ShouldRunPrecheckMixin(Generic[TExtractorConfig, TGenerationServiceConfig]
|
|
|
123
123
|
)
|
|
124
124
|
return False
|
|
125
125
|
|
|
126
|
-
# Cheap pre-filter:
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
#
|
|
126
|
+
# Cheap pre-filter: apply user-text-only heuristics (slash-commands,
|
|
127
|
+
# short turns, extractor-prompt echoes) without burning a 5–7s LLM
|
|
128
|
+
# call. Batches with no non-empty user text bypass these heuristics;
|
|
129
|
+
# stride/window eligibility was already evaluated independently.
|
|
130
130
|
from reflexio.server.services.base_generation_service import (
|
|
131
131
|
_cheap_should_run_reject,
|
|
132
132
|
)
|
|
@@ -189,8 +189,9 @@ class UsageBillingMixin(Generic[TExtractorConfig, TGenerationServiceConfig]): #
|
|
|
189
189
|
``None`` here and resolved enterprise-side at rollup (Phase 1).
|
|
190
190
|
|
|
191
191
|
Gated by ``EMITS_LEARNING_BILLING`` — only profile/playbook generation
|
|
192
|
-
services opt in.
|
|
193
|
-
|
|
192
|
+
services opt in here. Non-extraction learning mutation paths emit their
|
|
193
|
+
own ``learnings_generated`` value-facet events when they durably apply
|
|
194
|
+
revisions/successors.
|
|
194
195
|
|
|
195
196
|
Args:
|
|
196
197
|
prepared: The prepared generation run (used for input-text computation).
|
|
@@ -227,7 +228,10 @@ class UsageBillingMixin(Generic[TExtractorConfig, TGenerationServiceConfig]): #
|
|
|
227
228
|
platform_llm=platform_llm,
|
|
228
229
|
platform_storage=None,
|
|
229
230
|
pipeline=ctx.get("pipeline"),
|
|
231
|
+
user_id=ctx.get("user_id"),
|
|
230
232
|
request_id=ctx.get("request_id"),
|
|
233
|
+
source=ctx.get("source"),
|
|
234
|
+
agent_version=ctx.get("agent_version"),
|
|
231
235
|
)
|
|
232
236
|
|
|
233
237
|
# ② Learning — cost: input-anchored extraction tokens + real provider tokens.
|