algo-cli-runtime 0.14.0__py3-none-any.whl
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.
- algo_cli/__init__.py +3 -0
- algo_cli/__main__.py +7 -0
- algo_cli/_internal/__init__.py +12 -0
- algo_cli/_internal/policy_chain.py +259 -0
- algo_cli/action_registry.py +1047 -0
- algo_cli/agent_blocks.py +550 -0
- algo_cli/agent_pipeline.py +1457 -0
- algo_cli/agent_threads.py +308 -0
- algo_cli/animations.py +316 -0
- algo_cli/cache_admission.py +209 -0
- algo_cli/capability_mask.py +66 -0
- algo_cli/chat_protocol.py +116 -0
- algo_cli/chatgpt_auth.py +510 -0
- algo_cli/chatgpt_client.py +657 -0
- algo_cli/code_rag.py +479 -0
- algo_cli/config.py +651 -0
- algo_cli/context_budget.py +679 -0
- algo_cli/credential_helpers.py +315 -0
- algo_cli/deliberation.py +29 -0
- algo_cli/display.py +1470 -0
- algo_cli/evals/__init__.py +21 -0
- algo_cli/evals/algorithm_effectiveness.py +560 -0
- algo_cli/evals/competitive_harness_rating.py +702 -0
- algo_cli/evals/cot_quality.py +220 -0
- algo_cli/evals/harness_retrieval_benchmark.py +401 -0
- algo_cli/evals/performance_regression.py +136 -0
- algo_cli/evals/scorecard_grading.py +308 -0
- algo_cli/evals/session_distribution.py +84 -0
- algo_cli/execution_guardrails.py +806 -0
- algo_cli/extensions_manifest.py +84 -0
- algo_cli/git_evidence.py +227 -0
- algo_cli/google_workspace.py +407 -0
- algo_cli/google_workspace_auth.py +523 -0
- algo_cli/harness.py +2587 -0
- algo_cli/identity.py +557 -0
- algo_cli/index_compute_lab.py +228 -0
- algo_cli/inference_harness.py +70 -0
- algo_cli/intelligence/__init__.py +1103 -0
- algo_cli/intelligence/acrobat_config.py +307 -0
- algo_cli/intelligence/acrobat_manifests.py +338 -0
- algo_cli/intelligence/acrobat_models.py +195 -0
- algo_cli/intelligence/acrobat_pipeline.py +295 -0
- algo_cli/intelligence/acrobat_runtime.py +302 -0
- algo_cli/intelligence/acrobat_security.py +261 -0
- algo_cli/intelligence/acrobat_workflows.py +226 -0
- algo_cli/intelligence/actionability.py +165 -0
- algo_cli/intelligence/adversarial_audit.py +136 -0
- algo_cli/intelligence/agent_arena.py +92 -0
- algo_cli/intelligence/agent_benchmark.py +236 -0
- algo_cli/intelligence/agent_runtime.py +171 -0
- algo_cli/intelligence/agents_as_tools.py +70 -0
- algo_cli/intelligence/artifact_binding.py +80 -0
- algo_cli/intelligence/autonomous_engineer.py +1976 -0
- algo_cli/intelligence/backpressure.py +99 -0
- algo_cli/intelligence/bloom_filter.py +186 -0
- algo_cli/intelligence/bonferroni.py +66 -0
- algo_cli/intelligence/boundary_compaction.py +98 -0
- algo_cli/intelligence/catalog_verifier.py +172 -0
- algo_cli/intelligence/cavecrew.py +118 -0
- algo_cli/intelligence/changelog.py +176 -0
- algo_cli/intelligence/checkpoint_resume.py +92 -0
- algo_cli/intelligence/circuit_breaker.py +88 -0
- algo_cli/intelligence/clarification_gate.py +101 -0
- algo_cli/intelligence/code_graph.py +180 -0
- algo_cli/intelligence/coderank.py +97 -0
- algo_cli/intelligence/consistent_hash.py +150 -0
- algo_cli/intelligence/consortium_synthesis.py +139 -0
- algo_cli/intelligence/construction/__init__.py +241 -0
- algo_cli/intelligence/construction/common.py +273 -0
- algo_cli/intelligence/construction/documents.py +496 -0
- algo_cli/intelligence/construction/labor_units.py +1395 -0
- algo_cli/intelligence/construction/payments.py +470 -0
- algo_cli/intelligence/construction/risk.py +784 -0
- algo_cli/intelligence/content_extractor.py +132 -0
- algo_cli/intelligence/context_adaptive.py +102 -0
- algo_cli/intelligence/context_ops.py +95 -0
- algo_cli/intelligence/count_min.py +145 -0
- algo_cli/intelligence/cow_state.py +103 -0
- algo_cli/intelligence/critic_loop.py +119 -0
- algo_cli/intelligence/cross_source.py +113 -0
- algo_cli/intelligence/daemon_mode.py +99 -0
- algo_cli/intelligence/dag_orchestration.py +151 -0
- algo_cli/intelligence/deep_research.py +155 -0
- algo_cli/intelligence/degenerate_detector.py +78 -0
- algo_cli/intelligence/delta_report.py +92 -0
- algo_cli/intelligence/discovery_event_log.py +92 -0
- algo_cli/intelligence/document_ingest.py +298 -0
- algo_cli/intelligence/dual_layer_validate.py +151 -0
- algo_cli/intelligence/echo_fidelity.py +73 -0
- algo_cli/intelligence/ema_tuning.py +104 -0
- algo_cli/intelligence/event_log.py +92 -0
- algo_cli/intelligence/evidence_graph.py +114 -0
- algo_cli/intelligence/extension_host.py +162 -0
- algo_cli/intelligence/extension_manifest.py +115 -0
- algo_cli/intelligence/falsification_suite.py +178 -0
- algo_cli/intelligence/finance/__init__.py +169 -0
- algo_cli/intelligence/finance/anomalies.py +135 -0
- algo_cli/intelligence/finance/ap_ar.py +351 -0
- algo_cli/intelligence/finance/cash.py +162 -0
- algo_cli/intelligence/finance/close.py +332 -0
- algo_cli/intelligence/finance/common.py +244 -0
- algo_cli/intelligence/finance/construction.py +135 -0
- algo_cli/intelligence/finance/controls.py +172 -0
- algo_cli/intelligence/finance/evidence.py +119 -0
- algo_cli/intelligence/finance/exceptions.py +157 -0
- algo_cli/intelligence/finance/reconciliations.py +254 -0
- algo_cli/intelligence/finance/revenue.py +109 -0
- algo_cli/intelligence/finance/tax.py +74 -0
- algo_cli/intelligence/finance/workpapers.py +111 -0
- algo_cli/intelligence/finding_record.py +120 -0
- algo_cli/intelligence/flow_dag.py +267 -0
- algo_cli/intelligence/gatherer.py +223 -0
- algo_cli/intelligence/golden_master.py +98 -0
- algo_cli/intelligence/graph_rag.py +195 -0
- algo_cli/intelligence/group_chat.py +143 -0
- algo_cli/intelligence/hash_dedup.py +145 -0
- algo_cli/intelligence/hyperloglog.py +128 -0
- algo_cli/intelligence/incremental_index.py +316 -0
- algo_cli/intelligence/index_store.py +16 -0
- algo_cli/intelligence/iteration_plan.py +133 -0
- algo_cli/intelligence/kernel_plugins.py +167 -0
- algo_cli/intelligence/lesson_catalog.py +135 -0
- algo_cli/intelligence/llm_fallback.py +169 -0
- algo_cli/intelligence/log2_histogram.py +267 -0
- algo_cli/intelligence/lsp_integration.py +147 -0
- algo_cli/intelligence/memory_evolution.py +117 -0
- algo_cli/intelligence/minhash_lsh.py +182 -0
- algo_cli/intelligence/multi_model_score.py +174 -0
- algo_cli/intelligence/multi_tier_grade.py +211 -0
- algo_cli/intelligence/negative_controls.py +113 -0
- algo_cli/intelligence/numeric_clamp.py +63 -0
- algo_cli/intelligence/occ_editor.py +66 -0
- algo_cli/intelligence/output_normalize.py +112 -0
- algo_cli/intelligence/parallel_delegation.py +98 -0
- algo_cli/intelligence/parallel_fanout.py +104 -0
- algo_cli/intelligence/permission_modes.py +105 -0
- algo_cli/intelligence/pre_push_gate.py +68 -0
- algo_cli/intelligence/prefetch.py +171 -0
- algo_cli/intelligence/process_framework.py +217 -0
- algo_cli/intelligence/project_graph.py +387 -0
- algo_cli/intelligence/query_expansion.py +146 -0
- algo_cli/intelligence/ralph_loop.py +117 -0
- algo_cli/intelligence/rate_limiter.py +153 -0
- algo_cli/intelligence/refactor_transaction.py +94 -0
- algo_cli/intelligence/research_workspace.py +108 -0
- algo_cli/intelligence/retraction_ledger.py +72 -0
- algo_cli/intelligence/saga_pattern.py +88 -0
- algo_cli/intelligence/session_fork.py +100 -0
- algo_cli/intelligence/shadow_editor.py +67 -0
- algo_cli/intelligence/shell_session.py +213 -0
- algo_cli/intelligence/source_registry.py +143 -0
- algo_cli/intelligence/spawn_scales.py +99 -0
- algo_cli/intelligence/stat_stability.py +104 -0
- algo_cli/intelligence/structural_validator.py +148 -0
- algo_cli/intelligence/subagent_spawner.py +111 -0
- algo_cli/intelligence/symmetric_verify.py +70 -0
- algo_cli/intelligence/task_classifier.py +129 -0
- algo_cli/intelligence/team_execution.py +122 -0
- algo_cli/intelligence/tiered_access.py +121 -0
- algo_cli/intelligence/utility_registry.py +159 -0
- algo_cli/intuition_engine.py +560 -0
- algo_cli/intuition_injector.py +82 -0
- algo_cli/kernels/__init__.py +5 -0
- algo_cli/kernels/manifest.py +763 -0
- algo_cli/main.py +3903 -0
- algo_cli/memory_candidates.py +541 -0
- algo_cli/memory_echo_veil.py +394 -0
- algo_cli/memory_runtime.py +112 -0
- algo_cli/model_info.py +548 -0
- algo_cli/model_profile.py +160 -0
- algo_cli/model_routing.py +74 -0
- algo_cli/oneshot.py +331 -0
- algo_cli/perf_telemetry.py +389 -0
- algo_cli/plugins.py +245 -0
- algo_cli/private_event_store.py +654 -0
- algo_cli/quantization/__init__.py +24 -0
- algo_cli/quantization/lloyd_max.py +98 -0
- algo_cli/quantization/turbo_quant.py +308 -0
- algo_cli/reasoning/__init__.py +46 -0
- algo_cli/reasoning/combinatorial.py +356 -0
- algo_cli/reasoning/graph_of_thought.py +297 -0
- algo_cli/reasoning/mcts.py +220 -0
- algo_cli/reasoning/neuro_symbolic.py +250 -0
- algo_cli/reasoning/react.py +246 -0
- algo_cli/reasoning/reflexion.py +225 -0
- algo_cli/reasoning/tree_of_thought.py +241 -0
- algo_cli/reasoning_bridge.py +150 -0
- algo_cli/reconciliation.py +284 -0
- algo_cli/reflex.py +385 -0
- algo_cli/resources/docs/ALGO.md +13958 -0
- algo_cli/resources/docs/algo-cli-algorithm-evidence-contract.md +60 -0
- algo_cli/resources/docs/algo-cli-execution-verification-contract.md +59 -0
- algo_cli/resources/docs/algo-cli-memory-lifecycle-contract.md +72 -0
- algo_cli/resources/docs/harness-extension-cleanup-recommendation.md +41 -0
- algo_cli/resources/docs/index-compute-lab-integration.md +32 -0
- algo_cli/resources/docs/inference-harness-loop-blueprint-2026-06.md +55 -0
- algo_cli/resources/docs/main-split-map.md +35 -0
- algo_cli/resources/docs/privacy-and-context.md +48 -0
- algo_cli/resources/docs/reflex-loop-v0.2.md +354 -0
- algo_cli/resources/skills/README.md +26 -0
- algo_cli/resources/skills/algo-cli.md +59 -0
- algo_cli/resources/skills/edit-file-precision.md +49 -0
- algo_cli/resources/skills/harness-search-first.md +47 -0
- algo_cli/resources/skills/memory-recall-ritual.md +51 -0
- algo_cli/resources/skills/qol-algorithms.md +224 -0
- algo_cli/resources/skills/smart-error-recovery.md +56 -0
- algo_cli/resources/skills/tool-selection-cheatsheet.md +65 -0
- algo_cli/retrieval_algorithms.py +127 -0
- algo_cli/runtime_qos.py +236 -0
- algo_cli/runtime_services.py +320 -0
- algo_cli/session_commands.py +95 -0
- algo_cli/session_mode.py +113 -0
- algo_cli/skills.py +430 -0
- algo_cli/slash_dispatch.py +1265 -0
- algo_cli/small_context.py +206 -0
- algo_cli/spawn_budget.py +89 -0
- algo_cli/task_ledger.py +84 -0
- algo_cli/task_router.py +197 -0
- algo_cli/tool_context.py +94 -0
- algo_cli/tool_contract.py +99 -0
- algo_cli/tool_policy.py +357 -0
- algo_cli/tool_runtime.py +647 -0
- algo_cli/tools.py +3056 -0
- algo_cli/url_scheme.py +174 -0
- algo_cli/verify.py +154 -0
- algo_cli/version_manifest.py +178 -0
- algo_cli/vision_screenshot_verify.py +76 -0
- algo_cli/workspace_resolver.py +68 -0
- algo_cli/x_account.py +209 -0
- algo_cli/xai_auth.py +374 -0
- algo_cli/xai_client.py +600 -0
- algo_cli_runtime-0.14.0.dist-info/METADATA +369 -0
- algo_cli_runtime-0.14.0.dist-info/RECORD +237 -0
- algo_cli_runtime-0.14.0.dist-info/WHEEL +4 -0
- algo_cli_runtime-0.14.0.dist-info/entry_points.txt +3 -0
- algo_cli_runtime-0.14.0.dist-info/licenses/LICENSE +21 -0
- ollama_cli/__init__.py +67 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""H5 — Lesson-to-Catalog Proposal Pipeline.
|
|
2
|
+
|
|
3
|
+
Scans lesson text for algorithmic patterns and proposes catalog entries.
|
|
4
|
+
Mined from T3MP3ST self-improvement loop (🧪 Research → 📋 Catalog).
|
|
5
|
+
|
|
6
|
+
LLM integration: optionally uses an LLM to scan lesson text and propose
|
|
7
|
+
catalog entries. Falls back to keyword-based extraction when no model is
|
|
8
|
+
available.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class CatalogProposal:
|
|
18
|
+
"""A proposed catalog entry derived from a lesson."""
|
|
19
|
+
|
|
20
|
+
title: str
|
|
21
|
+
use_for: str
|
|
22
|
+
pseudocode: str
|
|
23
|
+
source_lesson: str
|
|
24
|
+
confidence: float = 0.0
|
|
25
|
+
keywords: list[str] = field(default_factory=list)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Keyword patterns that indicate algorithmic lessons
|
|
29
|
+
_PATTERN_KEYWORDS = {
|
|
30
|
+
"algorithm": ["algorithm", "pattern", "approach", "method", "strategy"],
|
|
31
|
+
"verification": ["verify", "check", "validate", "test", "assert"],
|
|
32
|
+
"guard": ["guard", "clamp", "prevent", "block", "gate"],
|
|
33
|
+
"pipeline": ["pipeline", "flow", "stage", "phase", "step"],
|
|
34
|
+
"metric": ["metric", "score", "measure", "telemetry", "signal"],
|
|
35
|
+
"fallback": ["fallback", "retry", "recover", "degrade"],
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _extract_keywords(text: str) -> list[str]:
|
|
40
|
+
"""Extract algorithmic keywords from lesson text."""
|
|
41
|
+
text_lower = text.lower()
|
|
42
|
+
found = []
|
|
43
|
+
for category, keywords in _PATTERN_KEYWORDS.items():
|
|
44
|
+
for kw in keywords:
|
|
45
|
+
if kw in text_lower:
|
|
46
|
+
found.append(kw)
|
|
47
|
+
return found
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _extract_title(text: str) -> str:
|
|
51
|
+
"""Derive a title from lesson text."""
|
|
52
|
+
# Try to find a heading or first sentence
|
|
53
|
+
lines = text.strip().split("\n")
|
|
54
|
+
for line in lines:
|
|
55
|
+
line = line.strip()
|
|
56
|
+
if line.startswith("#"):
|
|
57
|
+
return line.lstrip("#").strip()
|
|
58
|
+
if line and len(line) > 10:
|
|
59
|
+
# Use first sentence, truncated
|
|
60
|
+
first_sentence = line.split(".")[0]
|
|
61
|
+
if len(first_sentence) > 60:
|
|
62
|
+
return first_sentence[:57] + "..."
|
|
63
|
+
return first_sentence
|
|
64
|
+
return "Untitled Pattern"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _compute_confidence(keywords: list[str], text: str) -> float:
|
|
68
|
+
"""Compute confidence based on keyword density."""
|
|
69
|
+
if not keywords:
|
|
70
|
+
return 0.0
|
|
71
|
+
text_lower = text.lower()
|
|
72
|
+
total_hits = sum(text_lower.count(kw) for kw in keywords)
|
|
73
|
+
# Normalize by text length (per 1000 chars)
|
|
74
|
+
density = total_hits / max(len(text), 1) * 1000
|
|
75
|
+
# Sigmoid-like: 0.5 at density=2, approaching 1.0 at density=5+
|
|
76
|
+
return min(1.0, density / 5.0)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def propose_from_lesson(
|
|
80
|
+
lesson_text: str,
|
|
81
|
+
model_client: Any | None = None,
|
|
82
|
+
) -> CatalogProposal:
|
|
83
|
+
"""Propose a catalog entry from a lesson.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
lesson_text: The lesson text to scan.
|
|
87
|
+
model_client: Optional LLM client for richer extraction.
|
|
88
|
+
Falls back to keyword-based extraction when None.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
A CatalogProposal with extracted patterns.
|
|
92
|
+
"""
|
|
93
|
+
keywords = _extract_keywords(lesson_text)
|
|
94
|
+
title = _extract_title(lesson_text)
|
|
95
|
+
confidence = _compute_confidence(keywords, lesson_text)
|
|
96
|
+
|
|
97
|
+
# Build pseudocode from keywords
|
|
98
|
+
if keywords:
|
|
99
|
+
pseudo_lines = [
|
|
100
|
+
f"# Detected patterns: {', '.join(keywords[:5])}",
|
|
101
|
+
f"def {title.lower().replace(' ', '_')}(input):",
|
|
102
|
+
f" # Keywords: {', '.join(keywords)}",
|
|
103
|
+
" result = process(input)",
|
|
104
|
+
" return result",
|
|
105
|
+
]
|
|
106
|
+
pseudocode = "\n".join(pseudo_lines)
|
|
107
|
+
else:
|
|
108
|
+
pseudocode = "# No clear algorithmic pattern detected"
|
|
109
|
+
|
|
110
|
+
use_for = f"Lessons containing: {', '.join(keywords[:3])}" if keywords else "General lessons"
|
|
111
|
+
|
|
112
|
+
return CatalogProposal(
|
|
113
|
+
title=title,
|
|
114
|
+
use_for=use_for,
|
|
115
|
+
pseudocode=pseudocode,
|
|
116
|
+
source_lesson=lesson_text[:200],
|
|
117
|
+
confidence=confidence,
|
|
118
|
+
keywords=keywords,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def propose_batch(
|
|
123
|
+
lessons: list[str],
|
|
124
|
+
model_client: Any | None = None,
|
|
125
|
+
) -> list[CatalogProposal]:
|
|
126
|
+
"""Propose catalog entries from multiple lessons."""
|
|
127
|
+
return [propose_from_lesson(lesson, model_client) for lesson in lessons]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def filter_high_confidence(
|
|
131
|
+
proposals: list[CatalogProposal],
|
|
132
|
+
threshold: float = 0.3,
|
|
133
|
+
) -> list[CatalogProposal]:
|
|
134
|
+
"""Filter proposals below confidence threshold."""
|
|
135
|
+
return [p for p in proposals if p.confidence >= threshold]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""H15 — LLM Fallback Chain.
|
|
2
|
+
|
|
3
|
+
Multi-tier fallback for LLM calls: primary model → fallback model →
|
|
4
|
+
3-tier JSON parsing. Prevents total failure when primary model is unavailable.
|
|
5
|
+
Mined from T3MP3ST WHITEPAPER §5.2 safeLLMCall().
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class FallbackResult:
|
|
17
|
+
"""Result of a fallback chain call."""
|
|
18
|
+
|
|
19
|
+
success: bool
|
|
20
|
+
response: str = ""
|
|
21
|
+
parsed_json: dict | None = None
|
|
22
|
+
model_used: str = ""
|
|
23
|
+
parse_tier: int = 0 # 0=direct, 1=regex, 2=repair, 3=failed
|
|
24
|
+
error: str = ""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _parse_json_tier_1(text: str) -> dict | None:
|
|
28
|
+
"""Tier 1: Direct json.loads."""
|
|
29
|
+
try:
|
|
30
|
+
return json.loads(text)
|
|
31
|
+
except (json.JSONDecodeError, TypeError):
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _parse_json_tier_2(text: str) -> dict | None:
|
|
36
|
+
"""Tier 2: Extract JSON from markdown code blocks or surrounding text."""
|
|
37
|
+
# Try to find ```json ... ``` blocks
|
|
38
|
+
match = re.search(r"```(?:json)?\s*\n?(.*?)\n?```", text, re.DOTALL)
|
|
39
|
+
if match:
|
|
40
|
+
result = _parse_json_tier_1(match.group(1).strip())
|
|
41
|
+
if result is not None:
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
# Try to find the first { ... } block
|
|
45
|
+
match = re.search(r"\{.*\}", text, re.DOTALL)
|
|
46
|
+
if match:
|
|
47
|
+
result = _parse_json_tier_1(match.group(0))
|
|
48
|
+
if result is not None:
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _parse_json_tier_3(text: str) -> dict | None:
|
|
55
|
+
"""Tier 3: Repair common JSON errors (trailing commas, unquoted keys)."""
|
|
56
|
+
repaired = text
|
|
57
|
+
|
|
58
|
+
# Remove trailing commas before } or ]
|
|
59
|
+
repaired = re.sub(r",\s*([}\]])", r"\1", repaired)
|
|
60
|
+
|
|
61
|
+
# Quote unquoted keys (key:)
|
|
62
|
+
repaired = re.sub(r"(\w+)\s*:", r'"\1":', repaired)
|
|
63
|
+
# But don't double-quote already-quoted keys
|
|
64
|
+
repaired = re.sub(r'""(\w+)""\s*:', r'"\1":', repaired)
|
|
65
|
+
|
|
66
|
+
# Remove control characters
|
|
67
|
+
repaired = re.sub(r"[\x00-\x1f]", "", repaired)
|
|
68
|
+
|
|
69
|
+
return _parse_json_tier_1(repaired)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def parse_json_with_fallback(text: str) -> tuple[dict | None, int]:
|
|
73
|
+
"""Parse JSON with 3-tier fallback.
|
|
74
|
+
|
|
75
|
+
Returns (parsed_dict_or_None, tier_used).
|
|
76
|
+
Tier 0 = direct parse, 1 = regex extraction, 2 = repair, 3 = failed.
|
|
77
|
+
"""
|
|
78
|
+
result = _parse_json_tier_1(text)
|
|
79
|
+
if result is not None:
|
|
80
|
+
return result, 0
|
|
81
|
+
|
|
82
|
+
result = _parse_json_tier_2(text)
|
|
83
|
+
if result is not None:
|
|
84
|
+
return result, 1
|
|
85
|
+
|
|
86
|
+
result = _parse_json_tier_3(text)
|
|
87
|
+
if result is not None:
|
|
88
|
+
return result, 2
|
|
89
|
+
|
|
90
|
+
return None, 3
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def llm_call_with_fallback(
|
|
94
|
+
prompt: str,
|
|
95
|
+
primary_client: Any | None = None,
|
|
96
|
+
fallback_client: Any | None = None,
|
|
97
|
+
expect_json: bool = False,
|
|
98
|
+
) -> FallbackResult:
|
|
99
|
+
"""Call LLM with fallback chain.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
prompt: The prompt to send.
|
|
103
|
+
primary_client: Primary model client (must have .generate(prompt) -> str).
|
|
104
|
+
fallback_client: Fallback model client.
|
|
105
|
+
expect_json: If True, attempt JSON parsing with 3-tier fallback.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
FallbackResult with success status and parsed data.
|
|
109
|
+
"""
|
|
110
|
+
# Try primary model
|
|
111
|
+
if primary_client is not None:
|
|
112
|
+
try:
|
|
113
|
+
response = primary_client.generate(prompt)
|
|
114
|
+
if response:
|
|
115
|
+
if expect_json:
|
|
116
|
+
parsed, tier = parse_json_with_fallback(response)
|
|
117
|
+
if parsed is not None:
|
|
118
|
+
return FallbackResult(
|
|
119
|
+
success=True,
|
|
120
|
+
response=response,
|
|
121
|
+
parsed_json=parsed,
|
|
122
|
+
model_used=getattr(primary_client, "model_name", "primary"),
|
|
123
|
+
parse_tier=tier,
|
|
124
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
return FallbackResult(
|
|
127
|
+
success=True,
|
|
128
|
+
response=response,
|
|
129
|
+
model_used=getattr(primary_client, "model_name", "primary"),
|
|
130
|
+
)
|
|
131
|
+
except Exception as e:
|
|
132
|
+
primary_error = str(e)
|
|
133
|
+
else:
|
|
134
|
+
primary_error = "empty response"
|
|
135
|
+
else:
|
|
136
|
+
primary_error = "no primary client"
|
|
137
|
+
|
|
138
|
+
# Try fallback model
|
|
139
|
+
if fallback_client is not None:
|
|
140
|
+
try:
|
|
141
|
+
response = fallback_client.generate(prompt)
|
|
142
|
+
if response:
|
|
143
|
+
if expect_json:
|
|
144
|
+
parsed, tier = parse_json_with_fallback(response)
|
|
145
|
+
if parsed is not None:
|
|
146
|
+
return FallbackResult(
|
|
147
|
+
success=True,
|
|
148
|
+
response=response,
|
|
149
|
+
parsed_json=parsed,
|
|
150
|
+
model_used=getattr(fallback_client, "model_name", "fallback"),
|
|
151
|
+
parse_tier=tier,
|
|
152
|
+
)
|
|
153
|
+
else:
|
|
154
|
+
return FallbackResult(
|
|
155
|
+
success=True,
|
|
156
|
+
response=response,
|
|
157
|
+
model_used=getattr(fallback_client, "model_name", "fallback"),
|
|
158
|
+
)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
fallback_error = str(e)
|
|
161
|
+
else:
|
|
162
|
+
fallback_error = "empty response"
|
|
163
|
+
else:
|
|
164
|
+
fallback_error = "no fallback client"
|
|
165
|
+
|
|
166
|
+
return FallbackResult(
|
|
167
|
+
success=False,
|
|
168
|
+
error=f"Primary: {primary_error}; Fallback: {fallback_error}",
|
|
169
|
+
)
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""Log2 Histogram — memory-efficient latency telemetry.
|
|
2
|
+
|
|
3
|
+
Borrowed from Windows Performance Counters registry
|
|
4
|
+
(HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib\\009):
|
|
5
|
+
Windows uses 16 log2-sized buckets covering 128µs to >30s — 6 orders of
|
|
6
|
+
magnitude in 72 bytes. Binary-search insertion is O(log 16) = O(4).
|
|
7
|
+
Histograms are mergeable across sessions for aggregate statistics.
|
|
8
|
+
|
|
9
|
+
Pattern: B29 in ALGO.md.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import time
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
# Bucket upper bounds in microseconds.
|
|
20
|
+
# Source: Windows Performance Counter registry bucket definitions.
|
|
21
|
+
BOUNDARIES_US: tuple[float, ...] = (
|
|
22
|
+
128, # bucket 01: <= 128 µs
|
|
23
|
+
256, # bucket 02: <= 256 µs
|
|
24
|
+
512, # bucket 03: <= 512 µs
|
|
25
|
+
1_024, # bucket 04: <= 1 ms
|
|
26
|
+
4_096, # bucket 05: <= 4 ms
|
|
27
|
+
16_384, # bucket 06: <= 16 ms
|
|
28
|
+
65_536, # bucket 07: <= 64 ms
|
|
29
|
+
131_072, # bucket 08: <= 128 ms
|
|
30
|
+
262_144, # bucket 09: <= 256 ms
|
|
31
|
+
524_288, # bucket 10: <= 512 ms
|
|
32
|
+
1_048_576, # bucket 11: <= 1 s
|
|
33
|
+
2_097_152, # bucket 12: <= 2 s
|
|
34
|
+
10_485_760, # bucket 13: <= 10 s
|
|
35
|
+
20_971_520, # bucket 14: <= 20 s
|
|
36
|
+
31_457_280, # bucket 15: <= 30 s
|
|
37
|
+
float("inf"), # bucket 16: > 30 s
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Human-readable labels for each bucket.
|
|
41
|
+
BUCKET_LABELS: tuple[str, ...] = (
|
|
42
|
+
"<=128µs", "<=256µs", "<=512µs", "<=1ms", "<=4ms", "<=16ms",
|
|
43
|
+
"<=64ms", "<=128ms", "<=256ms", "<=512ms", "<=1s", "<=2s",
|
|
44
|
+
"<=10s", "<=20s", "<=30s", ">30s",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class Log2Histogram:
|
|
50
|
+
"""Log2-bucketed histogram — O(log b) insert, O(b) quantile.
|
|
51
|
+
|
|
52
|
+
16 buckets cover 128µs to >30s in ~72 bytes.
|
|
53
|
+
Mergeable: two histograms combine by bucket-wise addition.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
buckets: list[int] = field(default_factory=lambda: [0] * len(BOUNDARIES_US))
|
|
57
|
+
count: int = 0
|
|
58
|
+
sum_us: float = 0.0
|
|
59
|
+
min_us: float = float("inf")
|
|
60
|
+
max_us: float = 0.0
|
|
61
|
+
|
|
62
|
+
# --- core operations --------------------------------------------------
|
|
63
|
+
|
|
64
|
+
def observe(self, value_us: float) -> None:
|
|
65
|
+
"""Record a latency observation in microseconds."""
|
|
66
|
+
idx = self._bucket_index(value_us)
|
|
67
|
+
self.buckets[idx] += 1
|
|
68
|
+
self.count += 1
|
|
69
|
+
self.sum_us += value_us
|
|
70
|
+
if value_us < self.min_us:
|
|
71
|
+
self.min_us = value_us
|
|
72
|
+
if value_us > self.max_us:
|
|
73
|
+
self.max_us = value_us
|
|
74
|
+
|
|
75
|
+
def observe_seconds(self, value_s: float) -> None:
|
|
76
|
+
"""Record a latency observation in seconds."""
|
|
77
|
+
self.observe(value_s * 1_000_000)
|
|
78
|
+
|
|
79
|
+
def _bucket_index(self, value: float) -> int:
|
|
80
|
+
"""Binary search for bucket — O(log 16) = O(4)."""
|
|
81
|
+
lo, hi = 0, len(BOUNDARIES_US) - 1
|
|
82
|
+
while lo < hi:
|
|
83
|
+
mid = (lo + hi) // 2
|
|
84
|
+
if value <= BOUNDARIES_US[mid]:
|
|
85
|
+
hi = mid
|
|
86
|
+
else:
|
|
87
|
+
lo = mid + 1
|
|
88
|
+
return lo
|
|
89
|
+
|
|
90
|
+
# --- queries ----------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
def percentile(self, p: float) -> float:
|
|
93
|
+
"""Estimate p-th percentile in microseconds — O(buckets).
|
|
94
|
+
|
|
95
|
+
Uses linear interpolation within the bucket.
|
|
96
|
+
"""
|
|
97
|
+
if self.count == 0:
|
|
98
|
+
return 0.0
|
|
99
|
+
target = self.count * p / 100.0
|
|
100
|
+
cumulative = 0
|
|
101
|
+
for i, bucket_count in enumerate(self.buckets):
|
|
102
|
+
cumulative += bucket_count
|
|
103
|
+
if cumulative >= target:
|
|
104
|
+
lower = 0.0 if i == 0 else BOUNDARIES_US[i - 1]
|
|
105
|
+
upper = BOUNDARIES_US[i]
|
|
106
|
+
if bucket_count == 0:
|
|
107
|
+
return lower
|
|
108
|
+
frac = (target - (cumulative - bucket_count)) / bucket_count
|
|
109
|
+
return lower + frac * (upper - lower)
|
|
110
|
+
return BOUNDARIES_US[-2] # second-to-last (last is inf)
|
|
111
|
+
|
|
112
|
+
def percentile_seconds(self, p: float) -> float:
|
|
113
|
+
"""Estimate p-th percentile in seconds."""
|
|
114
|
+
return self.percentile(p) / 1_000_000
|
|
115
|
+
|
|
116
|
+
def mean_us(self) -> float:
|
|
117
|
+
"""Arithmetic mean in microseconds."""
|
|
118
|
+
return self.sum_us / self.count if self.count else 0.0
|
|
119
|
+
|
|
120
|
+
def mean_seconds(self) -> float:
|
|
121
|
+
return self.mean_us() / 1_000_000
|
|
122
|
+
|
|
123
|
+
# --- merge ------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
def merge(self, other: Log2Histogram) -> Log2Histogram:
|
|
126
|
+
"""Merge two histograms — O(buckets)."""
|
|
127
|
+
result = Log2Histogram()
|
|
128
|
+
result.buckets = [a + b for a, b in zip(self.buckets, other.buckets)]
|
|
129
|
+
result.count = self.count + other.count
|
|
130
|
+
result.sum_us = self.sum_us + other.sum_us
|
|
131
|
+
result.min_us = min(self.min_us, other.min_us)
|
|
132
|
+
result.max_us = max(self.max_us, other.max_us)
|
|
133
|
+
return result
|
|
134
|
+
|
|
135
|
+
# --- serialization ----------------------------------------------------
|
|
136
|
+
|
|
137
|
+
def to_dict(self) -> dict[str, Any]:
|
|
138
|
+
return {
|
|
139
|
+
"buckets": list(self.buckets),
|
|
140
|
+
"count": self.count,
|
|
141
|
+
"sum_us": self.sum_us,
|
|
142
|
+
"min_us": self.min_us if self.min_us != float("inf") else 0.0,
|
|
143
|
+
"max_us": self.max_us,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@classmethod
|
|
147
|
+
def from_dict(cls, data: dict[str, Any]) -> Log2Histogram:
|
|
148
|
+
h = cls()
|
|
149
|
+
h.buckets = list(data.get("buckets", [0] * len(BOUNDARIES_US)))
|
|
150
|
+
h.count = data.get("count", 0)
|
|
151
|
+
h.sum_us = data.get("sum_us", 0.0)
|
|
152
|
+
h.min_us = data.get("min_us", float("inf"))
|
|
153
|
+
h.max_us = data.get("max_us", 0.0)
|
|
154
|
+
return h
|
|
155
|
+
|
|
156
|
+
def to_json(self) -> str:
|
|
157
|
+
return json.dumps(self.to_dict())
|
|
158
|
+
|
|
159
|
+
@classmethod
|
|
160
|
+
def from_json(cls, s: str) -> Log2Histogram:
|
|
161
|
+
return cls.from_dict(json.loads(s))
|
|
162
|
+
|
|
163
|
+
# --- display ----------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
def summary(self) -> dict[str, float]:
|
|
166
|
+
"""Compact summary for dashboards."""
|
|
167
|
+
return {
|
|
168
|
+
"count": self.count,
|
|
169
|
+
"mean_us": round(self.mean_us(), 1),
|
|
170
|
+
"p50_us": round(self.percentile(50), 1),
|
|
171
|
+
"p90_us": round(self.percentile(90), 1),
|
|
172
|
+
"p99_us": round(self.percentile(99), 1),
|
|
173
|
+
"min_us": round(self.min_us, 1) if self.min_us != float("inf") else 0,
|
|
174
|
+
"max_us": round(self.max_us, 1),
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
def histogram_text(self) -> str:
|
|
178
|
+
"""ASCII histogram for terminal display."""
|
|
179
|
+
if self.count == 0:
|
|
180
|
+
return "(empty)"
|
|
181
|
+
max_count = max(self.buckets) or 1
|
|
182
|
+
lines = []
|
|
183
|
+
for i, count in enumerate(self.buckets):
|
|
184
|
+
if count == 0:
|
|
185
|
+
continue
|
|
186
|
+
bar_len = int(count / max_count * 40)
|
|
187
|
+
bar = "█" * bar_len
|
|
188
|
+
lines.append(f" {BUCKET_LABELS[i]:>8s} │{bar:<40s} │ {count}")
|
|
189
|
+
return "\n".join(lines)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# ---------------------------------------------------------------------------
|
|
193
|
+
# Registry — per-tool latency histograms
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
_REGISTRY: dict[str, Log2Histogram] = {}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def get_histogram(name: str) -> Log2Histogram:
|
|
200
|
+
"""Get or create a named histogram (e.g., 'read_file', 'embed', 'model')."""
|
|
201
|
+
if name not in _REGISTRY:
|
|
202
|
+
_REGISTRY[name] = Log2Histogram()
|
|
203
|
+
return _REGISTRY[name]
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def record_latency(name: str, duration_s: float) -> None:
|
|
207
|
+
"""Record a latency observation for a named operation."""
|
|
208
|
+
get_histogram(name).observe_seconds(duration_s)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def all_summaries() -> dict[str, dict[str, float]]:
|
|
212
|
+
"""Get summaries for all registered histograms."""
|
|
213
|
+
return {name: h.summary() for name, h in _REGISTRY.items()}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def save_to_file(path: Path) -> None:
|
|
217
|
+
"""Persist all histograms to a JSON file."""
|
|
218
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
219
|
+
data = {name: h.to_dict() for name, h in _REGISTRY.items()}
|
|
220
|
+
path.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def load_from_file(path: Path) -> None:
|
|
224
|
+
"""Load histograms from a JSON file (merges into registry)."""
|
|
225
|
+
if not path.exists():
|
|
226
|
+
return
|
|
227
|
+
try:
|
|
228
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
229
|
+
except (json.JSONDecodeError, OSError):
|
|
230
|
+
return
|
|
231
|
+
for name, hist_data in data.items():
|
|
232
|
+
existing = _REGISTRY.get(name)
|
|
233
|
+
loaded = Log2Histogram.from_dict(hist_data)
|
|
234
|
+
if existing:
|
|
235
|
+
_REGISTRY[name] = existing.merge(loaded)
|
|
236
|
+
else:
|
|
237
|
+
_REGISTRY[name] = loaded
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def clear_registry() -> None:
|
|
241
|
+
"""Clear all registered histograms (for testing)."""
|
|
242
|
+
_REGISTRY.clear()
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# ---------------------------------------------------------------------------
|
|
246
|
+
# Context manager for easy timing
|
|
247
|
+
# ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
class LatencyTimer:
|
|
250
|
+
"""Context manager that records latency into a named histogram.
|
|
251
|
+
|
|
252
|
+
Usage:
|
|
253
|
+
with LatencyTimer("read_file"):
|
|
254
|
+
content = read_file(path)
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
def __init__(self, name: str):
|
|
258
|
+
self.name = name
|
|
259
|
+
self._start = 0.0
|
|
260
|
+
|
|
261
|
+
def __enter__(self) -> LatencyTimer:
|
|
262
|
+
self._start = time.perf_counter()
|
|
263
|
+
return self
|
|
264
|
+
|
|
265
|
+
def __exit__(self, *exc: Any) -> None:
|
|
266
|
+
duration = time.perf_counter() - self._start
|
|
267
|
+
record_latency(self.name, duration)
|