claude-smart 0.2.43 → 0.2.44
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 +1 -1
- package/bin/claude-smart.js +2 -2
- 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 +21 -1
- package/plugin/pyproject.toml +2 -2
- package/plugin/scripts/backend-service.sh +50 -4
- package/plugin/scripts/cli.sh +2 -1
- package/plugin/scripts/ensure-plugin-root.sh +1 -0
- package/plugin/scripts/hook_entry.sh +5 -3
- package/plugin/scripts/smart-install.sh +2 -2
- package/plugin/src/README.md +57 -0
- package/plugin/uv.lock +126 -5
- package/plugin/vendor/reflexio/.env.example +9 -0
- package/plugin/vendor/reflexio/pyproject.toml +5 -2
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +0 -1
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +7 -4
- package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +4 -4
- package/plugin/vendor/reflexio/reflexio/lib/_storage_labels.py +2 -2
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +13 -4
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +3 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/validators.py +59 -6
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/README.md +7 -1
- package/plugin/vendor/reflexio/reflexio/server/api.py +188 -34
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +34 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +33 -11
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +278 -29
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +457 -181
- package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +22 -12
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +137 -9
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +36 -3
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +13 -3
- package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.1.prompt.md +69 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +71 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.0.prompt.md +27 -23
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.2.prompt.md +234 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +244 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +19 -9
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +58 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/group_evaluation_runner.py +1 -5
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +44 -2
- package/plugin/vendor/reflexio/reflexio/server/services/embedding_text.py +62 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +8 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +91 -24
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +3 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extractor_config_utils.py +6 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extractor_interaction_utils.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +18 -5
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +2 -2
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_consolidator.py +88 -3
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_deduplicator.py +36 -5
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service.py +6 -3
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service.py +6 -3
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +32 -22
- package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +89 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +46 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +12 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +8 -4
|
@@ -5,11 +5,14 @@ Utils for service layer
|
|
|
5
5
|
import ast
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
|
+
import os
|
|
8
9
|
import re
|
|
9
10
|
from dataclasses import dataclass
|
|
10
11
|
from datetime import UTC, datetime
|
|
11
12
|
from typing import Any
|
|
12
13
|
|
|
14
|
+
import tiktoken
|
|
15
|
+
|
|
13
16
|
from reflexio.cli.log_format import LLM_IO_LOG_FILE, next_llm_entry_id
|
|
14
17
|
from reflexio.models.api_schema.internal_schema import RequestInteractionDataModel
|
|
15
18
|
from reflexio.models.api_schema.service_schemas import (
|
|
@@ -25,6 +28,88 @@ logger = logging.getLogger(__name__)
|
|
|
25
28
|
# Already registered in server/__init__.py; import the numeric constant only.
|
|
26
29
|
MODEL_RESPONSE_LEVEL = 25
|
|
27
30
|
|
|
31
|
+
# Per-interaction content token budget when formatting interactions into an
|
|
32
|
+
# extraction/evaluation prompt. Read from REFLEXIO_MAX_INTERACTION_CONTENT_TOKENS;
|
|
33
|
+
# a value <= 0 disables slicing. See _resolve_max_interaction_content_tokens.
|
|
34
|
+
DEFAULT_MAX_INTERACTION_CONTENT_TOKENS = 512
|
|
35
|
+
_MAX_INTERACTION_CONTENT_TOKENS_ENV = "REFLEXIO_MAX_INTERACTION_CONTENT_TOKENS"
|
|
36
|
+
# Inserted between the head and tail of a sliced interaction so the LLM can see
|
|
37
|
+
# that the middle of the content was elided.
|
|
38
|
+
_CONTENT_TRUNCATION_MARKER = " …[truncated] "
|
|
39
|
+
# Encoder used purely as a length heuristic for slicing. tiktoken does not know
|
|
40
|
+
# the real tokenizer for non-OpenAI providers, but cl100k_base is a consistent,
|
|
41
|
+
# good-enough proxy for budgeting prompt content (mirrors the embedding path).
|
|
42
|
+
_CONTENT_ENCODING_NAME = "cl100k_base"
|
|
43
|
+
_content_token_encoding: tiktoken.Encoding | None = None
|
|
44
|
+
# Guards against logging the same malformed env value on every call.
|
|
45
|
+
_INVALID_MAX_TOKENS_WARNED = False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _get_content_token_encoding() -> tiktoken.Encoding:
|
|
49
|
+
"""Return a process-cached tiktoken encoder for content slicing."""
|
|
50
|
+
global _content_token_encoding
|
|
51
|
+
if _content_token_encoding is None:
|
|
52
|
+
_content_token_encoding = tiktoken.get_encoding(_CONTENT_ENCODING_NAME)
|
|
53
|
+
return _content_token_encoding
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _resolve_max_interaction_content_tokens() -> int | None:
|
|
57
|
+
"""Resolve the per-interaction content token budget from the environment.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
int | None: The positive token budget, or None when slicing is
|
|
61
|
+
disabled (env value <= 0). Unset or malformed values fall back to
|
|
62
|
+
``DEFAULT_MAX_INTERACTION_CONTENT_TOKENS``.
|
|
63
|
+
"""
|
|
64
|
+
global _INVALID_MAX_TOKENS_WARNED
|
|
65
|
+
raw = os.getenv(_MAX_INTERACTION_CONTENT_TOKENS_ENV, "").strip()
|
|
66
|
+
if not raw:
|
|
67
|
+
return DEFAULT_MAX_INTERACTION_CONTENT_TOKENS
|
|
68
|
+
try:
|
|
69
|
+
value = int(raw)
|
|
70
|
+
except ValueError:
|
|
71
|
+
if not _INVALID_MAX_TOKENS_WARNED:
|
|
72
|
+
logger.warning(
|
|
73
|
+
"Invalid %s=%r; falling back to default %d",
|
|
74
|
+
_MAX_INTERACTION_CONTENT_TOKENS_ENV,
|
|
75
|
+
raw,
|
|
76
|
+
DEFAULT_MAX_INTERACTION_CONTENT_TOKENS,
|
|
77
|
+
)
|
|
78
|
+
_INVALID_MAX_TOKENS_WARNED = True
|
|
79
|
+
return DEFAULT_MAX_INTERACTION_CONTENT_TOKENS
|
|
80
|
+
return value if value > 0 else None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def slice_content_by_tokens(content: str, max_tokens: int | None) -> str:
|
|
84
|
+
"""Slice interaction content to a token budget, keeping head and tail.
|
|
85
|
+
|
|
86
|
+
When the content exceeds ``max_tokens`` tokens, keep the first ``max_tokens
|
|
87
|
+
// 2`` tokens and the last ``max_tokens - max_tokens // 2`` tokens, joined by
|
|
88
|
+
a truncation marker so the elision is visible to the LLM. Content within
|
|
89
|
+
budget (or when ``max_tokens`` is None / content is empty) is returned
|
|
90
|
+
unchanged.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
content (str): The interaction content to slice.
|
|
94
|
+
max_tokens (int | None): Token budget, or None to disable slicing.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
str: The original or head+marker+tail-sliced content.
|
|
98
|
+
"""
|
|
99
|
+
if max_tokens is None or not content:
|
|
100
|
+
return content
|
|
101
|
+
encoding = _get_content_token_encoding()
|
|
102
|
+
tokens = encoding.encode(content)
|
|
103
|
+
if len(tokens) <= max_tokens:
|
|
104
|
+
return content
|
|
105
|
+
head = max_tokens // 2
|
|
106
|
+
tail = max_tokens - head
|
|
107
|
+
return (
|
|
108
|
+
f"{encoding.decode(tokens[:head])}"
|
|
109
|
+
f"{_CONTENT_TRUNCATION_MARKER}"
|
|
110
|
+
f"{encoding.decode(tokens[-tail:])}"
|
|
111
|
+
)
|
|
112
|
+
|
|
28
113
|
|
|
29
114
|
def _format_response_for_logging(response: Any) -> Any:
|
|
30
115
|
"""Render ``ToolCallingChatResponse`` with pretty tool_calls; pass others through.
|
|
@@ -188,22 +273,22 @@ def format_interactions_to_history_string(interactions: list[Interaction]) -> st
|
|
|
188
273
|
user: I love sushi
|
|
189
274
|
user: click menu item
|
|
190
275
|
"""
|
|
276
|
+
max_content_tokens = _resolve_max_interaction_content_tokens()
|
|
191
277
|
formatted_interactions = []
|
|
192
278
|
for interaction in interactions:
|
|
193
279
|
# Add text content with tools_used prefix if present
|
|
194
280
|
if interaction.content:
|
|
281
|
+
content = slice_content_by_tokens(interaction.content, max_content_tokens)
|
|
195
282
|
if interaction.tools_used:
|
|
196
283
|
tool_prefix = " ".join(
|
|
197
284
|
f"[used tool: {t.tool_name}({json.dumps(t.tool_data)})]"
|
|
198
285
|
for t in interaction.tools_used
|
|
199
286
|
)
|
|
200
287
|
formatted_interactions.append(
|
|
201
|
-
f"{interaction.role}: ```{tool_prefix} {
|
|
288
|
+
f"{interaction.role}: ```{tool_prefix} {content}```"
|
|
202
289
|
)
|
|
203
290
|
else:
|
|
204
|
-
formatted_interactions.append(
|
|
205
|
-
f"{interaction.role}: ```{interaction.content}```"
|
|
206
|
-
)
|
|
291
|
+
formatted_interactions.append(f"{interaction.role}: ```{content}```")
|
|
207
292
|
|
|
208
293
|
# Add user action
|
|
209
294
|
if interaction.user_action != UserActionType.NONE:
|
package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py
CHANGED
|
@@ -333,6 +333,7 @@ class SQLiteAgentRunMixin:
|
|
|
333
333
|
next_resume_at: datetime | None = None,
|
|
334
334
|
last_error: str | None = None,
|
|
335
335
|
increment_finalization_attempts: bool = False,
|
|
336
|
+
expected_statuses: tuple[AgentRunStatus, ...] | None = None,
|
|
336
337
|
) -> AgentRunRecord | None:
|
|
337
338
|
current_timestamp = self._current_timestamp()
|
|
338
339
|
assignments = ["status = ?", "updated_at = ?"]
|
|
@@ -361,14 +362,58 @@ class SQLiteAgentRunMixin:
|
|
|
361
362
|
assignments.append("finalized_at = ?")
|
|
362
363
|
params.append(current_timestamp)
|
|
363
364
|
params.append(run_id)
|
|
365
|
+
status_filter = ""
|
|
366
|
+
if expected_statuses:
|
|
367
|
+
placeholders = ",".join("?" for _ in expected_statuses)
|
|
368
|
+
status_filter = f" AND status IN ({placeholders})"
|
|
369
|
+
params.extend(expected.value for expected in expected_statuses)
|
|
364
370
|
with self._lock:
|
|
365
371
|
self.conn.execute(
|
|
366
|
-
f"UPDATE _agent_runs SET {', '.join(assignments)} WHERE id = ?",
|
|
372
|
+
f"UPDATE _agent_runs SET {', '.join(assignments)} WHERE id = ?{status_filter}",
|
|
367
373
|
params,
|
|
368
374
|
)
|
|
369
375
|
self.conn.commit()
|
|
370
376
|
return self.get_agent_run(run_id)
|
|
371
377
|
|
|
378
|
+
@SQLiteStorageBase.handle_exceptions
|
|
379
|
+
def fail_running_agent_runs_for_request(
|
|
380
|
+
self,
|
|
381
|
+
*,
|
|
382
|
+
org_id: str,
|
|
383
|
+
extractor_kind: str,
|
|
384
|
+
user_id: str | None,
|
|
385
|
+
request_id: str,
|
|
386
|
+
last_error: str,
|
|
387
|
+
) -> int:
|
|
388
|
+
current_timestamp = self._current_timestamp()
|
|
389
|
+
with self._lock:
|
|
390
|
+
cursor = self.conn.execute(
|
|
391
|
+
"""
|
|
392
|
+
UPDATE _agent_runs
|
|
393
|
+
SET status = ?,
|
|
394
|
+
updated_at = ?,
|
|
395
|
+
last_error = ?
|
|
396
|
+
WHERE org_id = ?
|
|
397
|
+
AND extractor_kind = ?
|
|
398
|
+
AND user_id IS ?
|
|
399
|
+
AND request_id = ?
|
|
400
|
+
AND status IN (?, ?)
|
|
401
|
+
""",
|
|
402
|
+
(
|
|
403
|
+
AgentRunStatus.FAILED.value,
|
|
404
|
+
current_timestamp,
|
|
405
|
+
last_error,
|
|
406
|
+
org_id,
|
|
407
|
+
extractor_kind,
|
|
408
|
+
user_id,
|
|
409
|
+
request_id,
|
|
410
|
+
AgentRunStatus.RUNNING.value,
|
|
411
|
+
AgentRunStatus.RESUMING.value,
|
|
412
|
+
),
|
|
413
|
+
)
|
|
414
|
+
self.conn.commit()
|
|
415
|
+
return cursor.rowcount
|
|
416
|
+
|
|
372
417
|
@SQLiteStorageBase.handle_exceptions
|
|
373
418
|
def create_pending_tool_call(
|
|
374
419
|
self, record: PendingToolCallRecord
|
|
@@ -220,9 +220,21 @@ class AgentRunMixin:
|
|
|
220
220
|
next_resume_at: datetime | None = None,
|
|
221
221
|
last_error: str | None = None,
|
|
222
222
|
increment_finalization_attempts: bool = False,
|
|
223
|
+
expected_statuses: tuple[AgentRunStatus, ...] | None = None,
|
|
223
224
|
) -> AgentRunRecord | None:
|
|
224
225
|
raise NotImplementedError(f"{type(self).__name__} does not support agent runs")
|
|
225
226
|
|
|
227
|
+
def fail_running_agent_runs_for_request(
|
|
228
|
+
self,
|
|
229
|
+
*,
|
|
230
|
+
org_id: str,
|
|
231
|
+
extractor_kind: str,
|
|
232
|
+
user_id: str | None,
|
|
233
|
+
request_id: str,
|
|
234
|
+
last_error: str,
|
|
235
|
+
) -> int:
|
|
236
|
+
raise NotImplementedError(f"{type(self).__name__} does not support agent runs")
|
|
237
|
+
|
|
226
238
|
def create_pending_tool_call(
|
|
227
239
|
self, record: PendingToolCallRecord
|
|
228
240
|
) -> PendingToolCallRecord:
|
|
@@ -160,7 +160,7 @@ class OperationMixin:
|
|
|
160
160
|
payload (dict | None): Optional serialized request payload preserved
|
|
161
161
|
for the rerun loop. Required so the rerun runs against the SAME
|
|
162
162
|
interactions the blocked publish enqueued, not whatever the
|
|
163
|
-
bookmark currently points at (R2
|
|
163
|
+
bookmark currently points at (R2).
|
|
164
164
|
|
|
165
165
|
Returns:
|
|
166
166
|
dict: Result with keys:
|
|
@@ -357,7 +357,8 @@ def _apply_floors(
|
|
|
357
357
|
) -> tuple[list[UserProfile], list[AgentPlaybook], list[UserPlaybook]]:
|
|
358
358
|
"""Apply the per-arm relevance floor to each entity arm in parallel."""
|
|
359
359
|
with ThreadPoolExecutor(max_workers=3) as ex:
|
|
360
|
-
f_profiles =
|
|
360
|
+
f_profiles = _submit_with_current_context(
|
|
361
|
+
ex,
|
|
361
362
|
apply_relevance_floor,
|
|
362
363
|
query,
|
|
363
364
|
profiles,
|
|
@@ -365,7 +366,8 @@ def _apply_floors(
|
|
|
365
366
|
top_k,
|
|
366
367
|
arm="profiles",
|
|
367
368
|
)
|
|
368
|
-
f_agent =
|
|
369
|
+
f_agent = _submit_with_current_context(
|
|
370
|
+
ex,
|
|
369
371
|
apply_relevance_floor,
|
|
370
372
|
query,
|
|
371
373
|
agent_playbooks,
|
|
@@ -373,7 +375,8 @@ def _apply_floors(
|
|
|
373
375
|
top_k,
|
|
374
376
|
arm="agent_playbooks",
|
|
375
377
|
)
|
|
376
|
-
f_user =
|
|
378
|
+
f_user = _submit_with_current_context(
|
|
379
|
+
ex,
|
|
377
380
|
apply_relevance_floor,
|
|
378
381
|
query,
|
|
379
382
|
user_playbooks,
|
|
@@ -533,9 +536,10 @@ def _submit_with_current_context(
|
|
|
533
536
|
executor: ThreadPoolExecutor,
|
|
534
537
|
fn: Callable[..., object],
|
|
535
538
|
*args: object,
|
|
539
|
+
**kwargs: object,
|
|
536
540
|
) -> Future[Any]:
|
|
537
541
|
context = contextvars.copy_context()
|
|
538
|
-
return executor.submit(context.run, fn, *args)
|
|
542
|
+
return executor.submit(context.run, fn, *args, **kwargs)
|
|
539
543
|
|
|
540
544
|
|
|
541
545
|
def _storage_backend_name(storage: BaseStorage) -> str:
|