correctover 1.3.1__cp312-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.
- correctover/__init__.py +310 -0
- correctover/_checkup.py +335 -0
- correctover/_checkup.pyc +0 -0
- correctover/_core.py +1557 -0
- correctover/_core.pyc +0 -0
- correctover/_device.py +294 -0
- correctover/_device.pyc +0 -0
- correctover/_engine.py +2779 -0
- correctover/_engine.pyc +0 -0
- correctover/_fixes.py +209 -0
- correctover/_fixes.pyc +0 -0
- correctover/_fixes2.py +178 -0
- correctover/_fixes2.pyc +0 -0
- correctover/_guard.py +169 -0
- correctover/_guard.pyc +0 -0
- correctover/_ping.py +296 -0
- correctover/_ping.pyc +0 -0
- correctover/_security.py +127 -0
- correctover/_security.pyc +0 -0
- correctover/_sentry.py +360 -0
- correctover/_sentry.pyc +0 -0
- correctover/_stats.py +519 -0
- correctover/_stats.pyc +0 -0
- correctover/_tracker.py +191 -0
- correctover/_tracker.pyc +0 -0
- correctover/_types.py +64 -0
- correctover/_types.pyc +0 -0
- correctover/_version.py +5 -0
- correctover/_version.pyc +0 -0
- correctover/benchmark/__init__.py +6 -0
- correctover/benchmark/fault_injector.py +129 -0
- correctover/benchmark/metrics_collector.py +222 -0
- correctover/benchmark/run_benchmark.py +211 -0
- correctover/benchmark/scenarios.py +123 -0
- correctover/carbon.py +451 -0
- correctover/carbon.pyc +0 -0
- correctover/chain.py +951 -0
- correctover/chain.pyc +0 -0
- correctover/checkpoint.py +805 -0
- correctover/checkpoint.pyc +0 -0
- correctover/classifier.py +192 -0
- correctover/classifier.pyc +0 -0
- correctover/cli.py +717 -0
- correctover/cli.pyc +0 -0
- correctover/client.py +400 -0
- correctover/client.pyc +0 -0
- correctover/cloudkill.py +446 -0
- correctover/cloudkill.pyc +0 -0
- correctover/cost_tracker.py +62 -0
- correctover/cost_tracker.pyc +0 -0
- correctover/dashboard.py +1157 -0
- correctover/dashboard.pyc +0 -0
- correctover/demo.py +466 -0
- correctover/demo.pyc +0 -0
- correctover/diagnoser.py +34 -0
- correctover/diagnoser.pyc +0 -0
- correctover/drift.py +262 -0
- correctover/drift.pyc +0 -0
- correctover/free_provider.py +186 -0
- correctover/free_provider.pyc +0 -0
- correctover/gateway.py +732 -0
- correctover/gateway.pyc +0 -0
- correctover/health_scorer.py +54 -0
- correctover/health_scorer.pyc +0 -0
- correctover/integrity.py +56 -0
- correctover/integrity.pyc +0 -0
- correctover/license.py +861 -0
- correctover/license.pyc +0 -0
- correctover/logging_config.py +84 -0
- correctover/logging_config.pyc +0 -0
- correctover/router.py +279 -0
- correctover/router.pyc +0 -0
- correctover/run.py +139 -0
- correctover/run.pyc +0 -0
- correctover/shield.py +366 -0
- correctover/shield.pyc +0 -0
- correctover/smart_router.py +13 -0
- correctover/smart_router.pyc +0 -0
- correctover/state_machine.py +84 -0
- correctover/state_machine.pyc +0 -0
- correctover/status.py +514 -0
- correctover/status.pyc +0 -0
- correctover/telemetry.py +308 -0
- correctover/telemetry.pyc +0 -0
- correctover/tracing.py +89 -0
- correctover/tracing.pyc +0 -0
- correctover-1.3.1.dist-info/LICENSE +5 -0
- correctover-1.3.1.dist-info/METADATA +35 -0
- correctover-1.3.1.dist-info/RECORD +91 -0
- correctover-1.3.1.dist-info/WHEEL +5 -0
- correctover-1.3.1.dist-info/top_level.txt +1 -0
correctover/__init__.py
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Copyright 2024-2026 Correctover Team
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License")
|
|
3
|
+
# Correctover™ — Proprietary MAPE-K Adaptive Loop Architecture
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
"""Correctover SDK — Agent stability SDK. Smart routing, auto-failover, crash recovery."""
|
|
7
|
+
from correctover._version import __version__
|
|
8
|
+
version = __version__
|
|
9
|
+
|
|
10
|
+
# 懒加载字典:所有模块延迟加载
|
|
11
|
+
_lazy = {
|
|
12
|
+
# Core engine
|
|
13
|
+
"SelfHealingEngine": ("correctover._engine", "SelfHealingEngine"),
|
|
14
|
+
"CallResult": ("correctover._engine", "CallResult"),
|
|
15
|
+
"ProviderConfig": ("correctover._engine", "ProviderConfig"),
|
|
16
|
+
"APIError": ("correctover._engine", "APIError"),
|
|
17
|
+
# Diagnoser
|
|
18
|
+
"FaultCategory": ("correctover._engine", "FaultCategory"),
|
|
19
|
+
"Diagnosis": ("correctover._engine", "Diagnosis"),
|
|
20
|
+
"Diagnoser": ("correctover.diagnoser", "Diagnoser"),
|
|
21
|
+
# HA components
|
|
22
|
+
"CircuitBreaker": ("correctover._engine", "CircuitBreaker"),
|
|
23
|
+
"CircuitState": ("correctover._engine", "CircuitState"),
|
|
24
|
+
"RateLimiter": ("correctover._engine", "RateLimiter"),
|
|
25
|
+
"Bulkhead": ("correctover._engine", "Bulkhead"),
|
|
26
|
+
# Semantic topology
|
|
27
|
+
"SemanticTopology": ("correctover._engine", "SemanticTopology"),
|
|
28
|
+
"SemanticDomain": ("correctover._engine", "SemanticDomain"),
|
|
29
|
+
"SemanticClassification": ("correctover._engine", "SemanticClassification"),
|
|
30
|
+
# Flywheel
|
|
31
|
+
"FlywheelLearner": ("correctover._engine", "FlywheelLearner"),
|
|
32
|
+
"LearnedRule": ("correctover._engine", "LearnedRule"),
|
|
33
|
+
"MetricsCollector": ("correctover._engine", "MetricsCollector"),
|
|
34
|
+
# MAPE-K Trace
|
|
35
|
+
"MapeKPhase": ("correctover._engine", "MapeKPhase"),
|
|
36
|
+
"MapeKTrace": ("correctover._engine", "MapeKTrace"),
|
|
37
|
+
# Contract Validator
|
|
38
|
+
"Contract": ("correctover._engine", "Contract"),
|
|
39
|
+
"ContractCheck": ("correctover._engine", "ContractCheck"),
|
|
40
|
+
"ContractResult": ("correctover._engine", "ContractResult"),
|
|
41
|
+
"ContractViolationError": ("correctover._engine", "ContractViolationError"),
|
|
42
|
+
"SemanticBoundaryViolationError": ("correctover._engine", "SemanticBoundaryViolationError"),
|
|
43
|
+
# Compatibility modules
|
|
44
|
+
"HealthScorer": ("correctover.health_scorer", "HealthScorer"),
|
|
45
|
+
"CostTracker": ("correctover.cost_tracker", "CostTracker"),
|
|
46
|
+
"IntegrityChecker": ("correctover.integrity", "IntegrityChecker"),
|
|
47
|
+
"EngineStateMachine": ("correctover.state_machine", "EngineStateMachine"),
|
|
48
|
+
"CorrectoverTelemetry": ("correctover.telemetry", "CorrectoverTelemetry"),
|
|
49
|
+
"TelemetryCollector": ("correctover.telemetry", "TelemetryCollector"),
|
|
50
|
+
"TelemetryConfig": ("correctover.telemetry", "TelemetryConfig"),
|
|
51
|
+
"run": ("correctover.run", "run"),
|
|
52
|
+
"Run": ("correctover.run", "Run"),
|
|
53
|
+
"RecoveryLevel": ("correctover._types", "RecoveryLevel"),
|
|
54
|
+
"DiagnosisResult": ("correctover._types", "DiagnosisResult"),
|
|
55
|
+
"RecoveryAction": ("correctover._types", "RecoveryAction"),
|
|
56
|
+
"RoutingStrategy": ("correctover._types", "RoutingStrategy"),
|
|
57
|
+
# Gateway proxy
|
|
58
|
+
"GatewayConfig": ("correctover.gateway", "GatewayConfig"),
|
|
59
|
+
"serve": ("correctover.gateway", "serve"),
|
|
60
|
+
# License system
|
|
61
|
+
"activate": ("correctover.license", "activate"),
|
|
62
|
+
"verify": ("correctover.license", "verify"),
|
|
63
|
+
"get_plan": ("correctover.license", "get_plan"),
|
|
64
|
+
"is_pro": ("correctover.license", "is_pro"),
|
|
65
|
+
"is_trial": ("correctover.license", "is_trial"),
|
|
66
|
+
"is_enterprise": ("correctover.license", "is_enterprise"),
|
|
67
|
+
"is_expired": ("correctover.license", "is_expired"),
|
|
68
|
+
"is_device_bound": ("correctover.license", "is_device_bound"),
|
|
69
|
+
"require_pro": ("correctover.license", "require_pro"),
|
|
70
|
+
"days_remaining": ("correctover.license", "days_remaining"),
|
|
71
|
+
"max_providers": ("correctover.license", "max_providers"),
|
|
72
|
+
"max_heal_level": ("correctover.license", "max_heal_level"),
|
|
73
|
+
"feature_gate": ("correctover.license", "feature_gate"),
|
|
74
|
+
"watermark_enabled": ("correctover.license", "watermark_enabled"),
|
|
75
|
+
"consume_repair": ("correctover.license", "consume_repair"),
|
|
76
|
+
"can_repair": ("correctover.license", "can_repair"),
|
|
77
|
+
"diagnose_free": ("correctover.license", "diagnose_free"),
|
|
78
|
+
"plan_summary": ("correctover.license", "plan_summary"),
|
|
79
|
+
"LicenseInfo": ("correctover.license", "LicenseInfo"),
|
|
80
|
+
"LicenseError": ("correctover.license", "LicenseError"),
|
|
81
|
+
"RepairLockedError": ("correctover.license", "RepairLockedError"),
|
|
82
|
+
"LicenseExpiredError": ("correctover.license", "LicenseExpiredError"),
|
|
83
|
+
"DeviceMismatchError": ("correctover.license", "DeviceMismatchError"),
|
|
84
|
+
"PLAN_PRICES": ("correctover.license", "PLAN_PRICES"),
|
|
85
|
+
"PLAN_LABELS": ("correctover.license", "PLAN_LABELS"),
|
|
86
|
+
"REPAIR_ACTIONS": ("correctover.license", "REPAIR_ACTIONS"),
|
|
87
|
+
# Device fingerprint
|
|
88
|
+
"device_fingerprint": ("correctover._device", "device_fingerprint"),
|
|
89
|
+
"device_info": ("correctover._device", "device_info"),
|
|
90
|
+
# Stats & ROI
|
|
91
|
+
"savings_report": ("correctover._stats", "savings_report"),
|
|
92
|
+
"console_summary": ("correctover._stats", "console_summary"),
|
|
93
|
+
"dashboard_data": ("correctover._stats", "dashboard_data"),
|
|
94
|
+
"weekly_summary": ("correctover._stats", "weekly_summary"),
|
|
95
|
+
"upgrade_triggers": ("correctover._stats", "upgrade_triggers"),
|
|
96
|
+
# Router + Client (v5.0)
|
|
97
|
+
"Client": ("correctover.client", "Client"),
|
|
98
|
+
"ChatResponse": ("correctover.client", "ChatResponse"),
|
|
99
|
+
"CostReport": ("correctover.client", "CostReport"),
|
|
100
|
+
"Router": ("correctover.router", "Router"),
|
|
101
|
+
"RoutingDecision": ("correctover.router", "RoutingDecision"),
|
|
102
|
+
"Strategy": ("correctover.router", "Strategy"),
|
|
103
|
+
"Complexity": ("correctover.classifier", "Complexity"),
|
|
104
|
+
"classify": ("correctover.classifier", "classify"),
|
|
105
|
+
# Checkpoint — Agent crash recovery (v4.3)
|
|
106
|
+
"Checkpoint": ("correctover.checkpoint", "Checkpoint"),
|
|
107
|
+
"StepResult": ("correctover.checkpoint", "StepResult"),
|
|
108
|
+
"AgentSession": ("correctover.checkpoint", "AgentSession"),
|
|
109
|
+
"RunContext": ("correctover.checkpoint", "RunContext"),
|
|
110
|
+
"CheckpointStore": ("correctover.checkpoint", "CheckpointStore"),
|
|
111
|
+
"FileCheckpointStore": ("correctover.checkpoint", "FileCheckpointStore"),
|
|
112
|
+
"MemoryCheckpointStore": ("correctover.checkpoint", "MemoryCheckpointStore"),
|
|
113
|
+
# Cloud Kill-Switch (v4.3)
|
|
114
|
+
"CloudKillGuardian": ("correctover.cloudkill", "CloudKillGuardian"),
|
|
115
|
+
"detect_tamper": ("correctover.cloudkill", "detect_tamper"),
|
|
116
|
+
"report_tamper": ("correctover.cloudkill", "report_tamper"),
|
|
117
|
+
"check_revoked": ("correctover.cloudkill", "check_revoked"),
|
|
118
|
+
"quick_check": ("correctover.cloudkill", "quick_check"),
|
|
119
|
+
# Telemetry
|
|
120
|
+
"ping": ("correctover._ping", "ping"),
|
|
121
|
+
# Carbon Tracker (v4.4.2)
|
|
122
|
+
"CarbonTracker": ("correctover.carbon", "CarbonTracker"),
|
|
123
|
+
"estimate_wh": ("correctover.carbon", "estimate_wh"),
|
|
124
|
+
"estimate_co2_kg": ("correctover.carbon", "estimate_co2_kg"),
|
|
125
|
+
"MODEL_ENERGY": ("correctover.carbon", "MODEL_ENERGY"),
|
|
126
|
+
"CARBON_INTENSITY_GRID": ("correctover.carbon", "CARBON_INTENSITY_GRID"),
|
|
127
|
+
"DriftMonitor": ("correctover.drift", "DriftMonitor"),
|
|
128
|
+
"DriftAlert": ("correctover.drift", "DriftAlert"),
|
|
129
|
+
# Dashboard (v4.4.2)
|
|
130
|
+
"dashboard": ("correctover.dashboard", "dashboard"),
|
|
131
|
+
"stop_dashboard": ("correctover.dashboard", "stop_dashboard"),
|
|
132
|
+
"dashboard_url": ("correctover.dashboard", "dashboard_url"),
|
|
133
|
+
"dashboard_status": ("correctover.dashboard", "dashboard_status"),
|
|
134
|
+
# Agent Chain Correctover™ (v5.3.0)
|
|
135
|
+
"ChainBuilder": ("correctover.chain", "ChainBuilder"),
|
|
136
|
+
"AgentChain": ("correctover.chain", "AgentChain"),
|
|
137
|
+
"AgentNode": ("correctover.chain", "AgentNode"),
|
|
138
|
+
"NodeResult": ("correctover.chain", "NodeResult"),
|
|
139
|
+
"ChainContext": ("correctover.chain", "ChainContext"),
|
|
140
|
+
"ChainResult": ("correctover.chain", "ChainResult"),
|
|
141
|
+
"CompensationStrategy": ("correctover.chain", "CompensationStrategy"),
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
__all__ = list(_lazy.keys()) + ["checkup"]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def __getattr__(name):
|
|
148
|
+
"""懒加载:只在首次访问时导入模块"""
|
|
149
|
+
if name in _lazy:
|
|
150
|
+
import importlib
|
|
151
|
+
mod_path, cls_name = _lazy[name]
|
|
152
|
+
mod = importlib.import_module(mod_path)
|
|
153
|
+
obj = getattr(mod, cls_name)
|
|
154
|
+
globals()[name] = obj
|
|
155
|
+
return obj
|
|
156
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def __dir__():
|
|
160
|
+
"""返回完整的导出列表"""
|
|
161
|
+
return __all__
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# ── Telemetry (opt-in, default OFF) ──────────────────────────────
|
|
165
|
+
import os as _os
|
|
166
|
+
# Telemetry is OFF by default. Set CORRECTOVER_TELEMETRY=1 to enable.
|
|
167
|
+
# This collects anonymous usage data (version, OS, plan) to improve the SDK.
|
|
168
|
+
# See https://correctover.cn/docs/telemetry for details.
|
|
169
|
+
# Telemetry is OFF by default. Set CORRECTOVER_TELEMETRY=1 to enable.
|
|
170
|
+
if _os.environ.get("CORRECTOVER_TELEMETRY") != "1":
|
|
171
|
+
_os.environ["CORRECTOVER_TELEMETRY"] = "0"
|
|
172
|
+
if not _os.environ.get("CORRECTOVER_TELEMETRY_URL"):
|
|
173
|
+
_os.environ["CORRECTOVER_TELEMETRY_URL"] = "https://license-api-correctover-hk.oss-cn-hongkong.aliyuncs.com/api/v1/telemetry"
|
|
174
|
+
|
|
175
|
+
# ── Auto-ping on import (only if telemetry is opted-in) ─────────
|
|
176
|
+
if _os.environ.get("CORRECTOVER_TELEMETRY") == "1":
|
|
177
|
+
try:
|
|
178
|
+
from correctover._ping import ping as _auto_ping
|
|
179
|
+
_auto_ping()
|
|
180
|
+
except Exception:
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ── First-run checkup ────────────────────────────────────────────
|
|
185
|
+
def checkup():
|
|
186
|
+
"""手动触发体检 (重新检测所有 provider 和连通性)."""
|
|
187
|
+
from correctover._checkup import force_run
|
|
188
|
+
force_run()
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
from correctover._checkup import run as _checkup_run
|
|
192
|
+
_checkup_run()
|
|
193
|
+
except Exception:
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ══════════════════════════════════════════════════════════════════
|
|
198
|
+
# SECURITY LAYER — All logic lives in _security.so (compiled)
|
|
199
|
+
# ══════════════════════════════════════════════════════════════════
|
|
200
|
+
|
|
201
|
+
# ── Step 1: Initialize tracker (silent, non-blocking) ──────────
|
|
202
|
+
try:
|
|
203
|
+
# [FIXED] tracker only loaded when telemetry explicitly opted in
|
|
204
|
+
if _os.environ.get('CORRECTOVER_TELEMETRY') == '1':
|
|
205
|
+
from correctover._tracker import report as _track_report
|
|
206
|
+
from correctover._tracker import flush as _track_flush
|
|
207
|
+
else:
|
|
208
|
+
_track_report = None
|
|
209
|
+
_track_flush = None
|
|
210
|
+
except ImportError as _e:
|
|
211
|
+
_track_report = None
|
|
212
|
+
_track_flush = None
|
|
213
|
+
import sys as _sys
|
|
214
|
+
if "cpython" in str(_e).lower() or "magic" in str(_e).lower():
|
|
215
|
+
print(f"[Correctover] WARNING: _tracker module incompatible with Python {_sys.version_info.major}.{_sys.version_info.minor}. "
|
|
216
|
+
f"Security tracking disabled. Reinstall from source: pip install --no-binary correctover-sdk correctover-sdk", file=_sys.stderr)
|
|
217
|
+
except Exception:
|
|
218
|
+
_track_report = None
|
|
219
|
+
_track_flush = None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# ── Step 2-5: Security init (compiled — logic not readable) ────
|
|
223
|
+
try:
|
|
224
|
+
from correctover._security import init as _security_init
|
|
225
|
+
_security_init()
|
|
226
|
+
except ImportError as _e:
|
|
227
|
+
import sys as _sys
|
|
228
|
+
if "cpython" in str(_e).lower() or "magic" in str(_e).lower():
|
|
229
|
+
print(f"[Correctover] WARNING: _security module incompatible with Python {_sys.version_info.major}.{_sys.version_info.minor}. "
|
|
230
|
+
f"Security init skipped. Reinstall from source: pip install --no-binary correctover-sdk correctover-sdk", file=_sys.stderr)
|
|
231
|
+
except Exception:
|
|
232
|
+
pass
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ── Step 6: Flush pending tracker events ───────────────────────
|
|
236
|
+
try:
|
|
237
|
+
if _track_flush:
|
|
238
|
+
_track_flush()
|
|
239
|
+
except Exception:
|
|
240
|
+
pass
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# ── Step 7: Cloud Kill-Switch guardian ─────────────────────────
|
|
244
|
+
# Background thread: periodically checks for tampering + cloud revocation
|
|
245
|
+
try:
|
|
246
|
+
from correctover.cloudkill import CloudKillGuardian
|
|
247
|
+
if _os.environ.get("CORRECTOVER_CLOUDKILL", "0") == "1": # [FIXED] opt-in
|
|
248
|
+
CloudKillGuardian.start(interval=3600)
|
|
249
|
+
except Exception:
|
|
250
|
+
pass
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# ══════════════════════════════════════════════════════════════════
|
|
254
|
+
# Anthropic 推荐:最简一行调用(Less is More + ACI 设计原则)
|
|
255
|
+
# ══════════════════════════════════════════════════════════════════
|
|
256
|
+
|
|
257
|
+
def run(prompt="", provider="", **kwargs):
|
|
258
|
+
"""一行代码调用 AI,自动配置、自动自愈。
|
|
259
|
+
|
|
260
|
+
Anthropic《构建高效的 Agents》核心原则:
|
|
261
|
+
- Less is More:从最简单开始,一行代码
|
|
262
|
+
- 好的 ACI 设计:用户不需要了解内部细节
|
|
263
|
+
|
|
264
|
+
用法:
|
|
265
|
+
import correctover as nb
|
|
266
|
+
result = nb.run("你好") # 自动读环境变量
|
|
267
|
+
print(result.summary()) # 透明度:看到完整调用链
|
|
268
|
+
|
|
269
|
+
环境变量: DEEPSEEK_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, DASHSCOPE_API_KEY
|
|
270
|
+
"""
|
|
271
|
+
import asyncio, os
|
|
272
|
+
from correctover._engine import SelfHealingEngine, ProviderConfig
|
|
273
|
+
|
|
274
|
+
engine = SelfHealingEngine()
|
|
275
|
+
api_key = kwargs.pop("api_key", "") or os.environ.get("CUSTOM_API_KEY", "")
|
|
276
|
+
candidates = [provider] if provider else ["deepseek", "openai", "anthropic", "dashscope", "agnes"]
|
|
277
|
+
|
|
278
|
+
for p in candidates:
|
|
279
|
+
env_key = api_key or os.environ.get(f"{p.upper()}_API_KEY", "")
|
|
280
|
+
if not env_key:
|
|
281
|
+
continue
|
|
282
|
+
try:
|
|
283
|
+
cfg = ProviderConfig(name=p, base_url=ProviderConfig._get_base_url(p), api_key=env_key)
|
|
284
|
+
engine.add_provider(cfg)
|
|
285
|
+
except Exception:
|
|
286
|
+
continue
|
|
287
|
+
break
|
|
288
|
+
|
|
289
|
+
if prompt:
|
|
290
|
+
return asyncio.run(engine.call(prompt, **kwargs))
|
|
291
|
+
return engine
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# ── Apply runtime patches for known bugs ──────────────────────────
|
|
295
|
+
# Bug #2 (latency), #3 (5xx), and the historical fixes in _fixes.pyc
|
|
296
|
+
# are applied here at import time.
|
|
297
|
+
import os as _patch_os
|
|
298
|
+
try:
|
|
299
|
+
from correctover._fixes import _apply_patches as _apply_fixes
|
|
300
|
+
_apply_fixes()
|
|
301
|
+
except Exception as _fix_err:
|
|
302
|
+
if _patch_os.environ.get("CORRECTOVER_DEBUG"):
|
|
303
|
+
print(f"[Correctover] _fixes load warning: {_fix_err}")
|
|
304
|
+
|
|
305
|
+
try:
|
|
306
|
+
from correctover._fixes2 import _apply_patches as _apply_fixes2
|
|
307
|
+
_apply_fixes2()
|
|
308
|
+
except Exception as _fix2_err:
|
|
309
|
+
if _patch_os.environ.get("CORRECTOVER_DEBUG"):
|
|
310
|
+
print(f"[Correctover] _fixes2 load warning: {_fix2_err}")
|
correctover/_checkup.py
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# Copyright 2024-2026 Correctover Team
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License")
|
|
3
|
+
# Correctover™ — Proprietary MAPE-K Adaptive Loop Architecture
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
"""Correctover First-Run Checkup — 让0调用变成"第一次装就看到价值".
|
|
7
|
+
|
|
8
|
+
核心逻辑:
|
|
9
|
+
325下载 0调用 → 因为装了没动
|
|
10
|
+
体检让用户装完立刻看到: 你有几个key、有没有backup、在不在裸奔
|
|
11
|
+
|
|
12
|
+
设计原则:
|
|
13
|
+
- 只跑一次: ~/.correctover/checkup_done 标记 (版本升级重跑)
|
|
14
|
+
- 极速: 并行检测, <3秒完成
|
|
15
|
+
- 非阻塞: 失败静默, 绝不卡住 import
|
|
16
|
+
- 制造焦虑: "1个provider, 没backup = 裸奔"
|
|
17
|
+
- CI/CD 静默: 检测 CI 环境变量, 不输出体检结果
|
|
18
|
+
|
|
19
|
+
触发时机:
|
|
20
|
+
import correctover → __init__.py 调用 _checkup.run()
|
|
21
|
+
只在首次安装或版本升级时跑一次
|
|
22
|
+
|
|
23
|
+
Output example:
|
|
24
|
+
✅ Correctover v4.4.2 installed
|
|
25
|
+
🔍 Quick health check...
|
|
26
|
+
✅ OpenAI: key valid (234ms)
|
|
27
|
+
🔑 DeepSeek: key detected (skipped connectivity test)
|
|
28
|
+
⚠️ No backup provider — one failure = total outage
|
|
29
|
+
💡 Add a 2nd provider for auto-failover → correctover.cn/docs
|
|
30
|
+
ℹ️ Plan: Free (diagnosis free, repair paid)
|
|
31
|
+
💡 Pro: ¥699/年 | 全功能自愈 | 一key一机
|
|
32
|
+
📧 wangguigui@correctover.cn
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
import json
|
|
36
|
+
import os
|
|
37
|
+
import sys
|
|
38
|
+
import time
|
|
39
|
+
import threading
|
|
40
|
+
|
|
41
|
+
_LOCAL_DIR = os.path.join(os.path.expanduser("~"), ".correctover")
|
|
42
|
+
_CHECKUP_FLAG = os.path.join(_LOCAL_DIR, "checkup_done")
|
|
43
|
+
|
|
44
|
+
# ── Provider key detection map ────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
_PROVIDER_KEYS = {
|
|
47
|
+
"openai": {
|
|
48
|
+
"env": ["OPENAI_API_KEY"],
|
|
49
|
+
"test_url": "https://api.openai.com/v1/models",
|
|
50
|
+
"test_header": "bearer", # Bearer token auth
|
|
51
|
+
"label": "OpenAI",
|
|
52
|
+
},
|
|
53
|
+
"anthropic": {
|
|
54
|
+
"env": ["ANTHROPIC_API_KEY"],
|
|
55
|
+
"test_url": None, # No simple validation endpoint
|
|
56
|
+
"test_header": None,
|
|
57
|
+
"label": "Anthropic",
|
|
58
|
+
},
|
|
59
|
+
"deepseek": {
|
|
60
|
+
"env": ["DEEPSEEK_API_KEY"],
|
|
61
|
+
"test_url": "https://api.deepseek.com/models",
|
|
62
|
+
"test_header": "bearer",
|
|
63
|
+
"label": "DeepSeek",
|
|
64
|
+
},
|
|
65
|
+
"google": {
|
|
66
|
+
"env": ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
|
67
|
+
"test_url": None, # Gemini uses query param auth
|
|
68
|
+
"test_header": None,
|
|
69
|
+
"label": "Google/Gemini",
|
|
70
|
+
},
|
|
71
|
+
"mistral": {
|
|
72
|
+
"env": ["MISTRAL_API_KEY"],
|
|
73
|
+
"test_url": "https://api.mistral.ai/v1/models",
|
|
74
|
+
"test_header": "bearer",
|
|
75
|
+
"label": "Mistral",
|
|
76
|
+
},
|
|
77
|
+
"qwen": {
|
|
78
|
+
"env": ["DASHSCOPE_API_KEY", "ALIBABA_CLOUD_ACCESS_KEY"],
|
|
79
|
+
"test_url": None, # DashScope uses different auth
|
|
80
|
+
"test_header": None,
|
|
81
|
+
"label": "Qwen/通义",
|
|
82
|
+
},
|
|
83
|
+
"zhipu": {
|
|
84
|
+
"env": ["ZHIPU_API_KEY"],
|
|
85
|
+
"test_url": None,
|
|
86
|
+
"test_header": None,
|
|
87
|
+
"label": "GLM/智谱",
|
|
88
|
+
},
|
|
89
|
+
"moonshot": {
|
|
90
|
+
"env": ["MOONSHOT_API_KEY"],
|
|
91
|
+
"test_url": "https://api.moonshot.cn/v1/models",
|
|
92
|
+
"test_header": "bearer",
|
|
93
|
+
"label": "Moonshot/月之暗面",
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# ── CI environment detection ──────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
_CI_ENV_VARS = [
|
|
100
|
+
"CI", "JENKINS_URL", "GITHUB_ACTIONS", "GITLAB_CI",
|
|
101
|
+
"TRAVIS", "CIRCLECI", "BUILDKITE", "TF_BUILD",
|
|
102
|
+
"PYTEST_CURRENT_TEST", "__PYTEST_PLAT", # pytest
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# ── Public API ────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
def run():
|
|
109
|
+
"""首次运行自动体检。只跑一次, CI环境静默。"""
|
|
110
|
+
# CI/CD 环境不输出体检
|
|
111
|
+
if _is_ci():
|
|
112
|
+
_mark_done()
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
# 已跑过且版本没变
|
|
116
|
+
if _already_done():
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
_do_checkup()
|
|
121
|
+
_mark_done()
|
|
122
|
+
except Exception:
|
|
123
|
+
pass # 绝不卡住 import
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def force_run():
|
|
127
|
+
"""强制重新跑体检 (用于 CLI 或测试)."""
|
|
128
|
+
try:
|
|
129
|
+
if os.path.exists(_CHECKUP_FLAG):
|
|
130
|
+
os.remove(_CHECKUP_FLAG)
|
|
131
|
+
except Exception:
|
|
132
|
+
pass
|
|
133
|
+
_do_checkup()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ── Internal ─────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
def _is_ci():
|
|
139
|
+
"""检测是否在 CI/CD 环境运行."""
|
|
140
|
+
for var in _CI_ENV_VARS:
|
|
141
|
+
if os.environ.get(var, "").strip():
|
|
142
|
+
return True
|
|
143
|
+
# 用户主动关闭
|
|
144
|
+
if os.environ.get("CORRECTOVER_NO_CHECKUP", "").strip().lower() in ("1", "true", "yes"):
|
|
145
|
+
return True
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _already_done():
|
|
150
|
+
"""检查是否已体检过, 版本升级则重跑."""
|
|
151
|
+
try:
|
|
152
|
+
if not os.path.exists(_CHECKUP_FLAG):
|
|
153
|
+
return False
|
|
154
|
+
with open(_CHECKUP_FLAG) as f:
|
|
155
|
+
data = json.load(f)
|
|
156
|
+
return data.get("version") == _sdk_version()
|
|
157
|
+
except Exception:
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _mark_done():
|
|
162
|
+
"""标记体检已完成."""
|
|
163
|
+
try:
|
|
164
|
+
os.makedirs(_LOCAL_DIR, exist_ok=True)
|
|
165
|
+
with open(_CHECKUP_FLAG, "w") as f:
|
|
166
|
+
json.dump({"version": _sdk_version(), "ts": int(time.time())}, f)
|
|
167
|
+
except Exception:
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _sdk_version():
|
|
172
|
+
"""安全获取 SDK 版本."""
|
|
173
|
+
try:
|
|
174
|
+
from correctover import __version__
|
|
175
|
+
return __version__
|
|
176
|
+
except Exception:
|
|
177
|
+
return "unknown"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _detect_keys():
|
|
181
|
+
"""扫描环境变量, 检测已配置的 API keys."""
|
|
182
|
+
detected = []
|
|
183
|
+
for name, cfg in _PROVIDER_KEYS.items():
|
|
184
|
+
for env_var in cfg["env"]:
|
|
185
|
+
key = os.environ.get(env_var, "").strip()
|
|
186
|
+
if key:
|
|
187
|
+
detected.append({
|
|
188
|
+
"name": name,
|
|
189
|
+
"env_var": env_var,
|
|
190
|
+
"key_masked": key[:6] + "..." + key[-4:] if len(key) > 12 else key[:4] + "...",
|
|
191
|
+
"label": cfg["label"],
|
|
192
|
+
"test_url": cfg.get("test_url"),
|
|
193
|
+
"test_header": cfg.get("test_header"),
|
|
194
|
+
"full_key": key,
|
|
195
|
+
})
|
|
196
|
+
break
|
|
197
|
+
return detected
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _test_provider(prov, results):
|
|
201
|
+
"""测试单个 provider 连通性 (在线校验 key)."""
|
|
202
|
+
name = prov["name"]
|
|
203
|
+
url = prov.get("test_url")
|
|
204
|
+
header_type = prov.get("test_header")
|
|
205
|
+
key = prov.get("full_key", "")
|
|
206
|
+
|
|
207
|
+
# 没有 test_url 的 provider, 只标记 detected
|
|
208
|
+
if not url or not header_type:
|
|
209
|
+
results[name] = {"status": "detected"}
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
import httpx
|
|
214
|
+
|
|
215
|
+
if header_type == "bearer":
|
|
216
|
+
headers = {"Authorization": f"Bearer {key}"}
|
|
217
|
+
else:
|
|
218
|
+
headers = {}
|
|
219
|
+
|
|
220
|
+
start = time.time()
|
|
221
|
+
resp = httpx.get(url, headers=headers, timeout=3.0)
|
|
222
|
+
latency_ms = (time.time() - start) * 1000
|
|
223
|
+
|
|
224
|
+
if resp.status_code == 200:
|
|
225
|
+
results[name] = {"status": "valid", "latency_ms": latency_ms}
|
|
226
|
+
elif resp.status_code == 401:
|
|
227
|
+
results[name] = {"status": "invalid", "latency_ms": latency_ms}
|
|
228
|
+
elif resp.status_code == 403:
|
|
229
|
+
results[name] = {"status": "invalid", "latency_ms": latency_ms}
|
|
230
|
+
elif resp.status_code == 429:
|
|
231
|
+
# Rate limited but key IS valid
|
|
232
|
+
results[name] = {"status": "valid", "latency_ms": latency_ms}
|
|
233
|
+
else:
|
|
234
|
+
# Other status — might be valid, just unusual
|
|
235
|
+
results[name] = {"status": "valid", "latency_ms": latency_ms}
|
|
236
|
+
except Exception:
|
|
237
|
+
# Timeout, connection error, etc. — key might be valid, just unreachable
|
|
238
|
+
results[name] = {"status": "unreachable"}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _do_checkup():
|
|
242
|
+
"""执行体检并输出结果."""
|
|
243
|
+
version = _sdk_version()
|
|
244
|
+
|
|
245
|
+
print()
|
|
246
|
+
print(f" ✅ Correctover v{version} installed")
|
|
247
|
+
print(f" 🔍 Quick health check...")
|
|
248
|
+
|
|
249
|
+
# 1. 检测 API keys
|
|
250
|
+
detected = _detect_keys()
|
|
251
|
+
|
|
252
|
+
# 2. 并行测试连通性
|
|
253
|
+
results = {}
|
|
254
|
+
if detected:
|
|
255
|
+
threads = []
|
|
256
|
+
for prov in detected:
|
|
257
|
+
t = threading.Thread(
|
|
258
|
+
target=_test_provider, args=(prov, results), daemon=True
|
|
259
|
+
)
|
|
260
|
+
t.start()
|
|
261
|
+
threads.append(t)
|
|
262
|
+
|
|
263
|
+
for t in threads:
|
|
264
|
+
t.join(timeout=4.0)
|
|
265
|
+
|
|
266
|
+
# 3. 输出每个 provider 的状态
|
|
267
|
+
if not detected:
|
|
268
|
+
print(f" ⚠️ No API keys configured")
|
|
269
|
+
print(f" 💡 export OPENAI_API_KEY=sk-... → then import correctover")
|
|
270
|
+
else:
|
|
271
|
+
for prov in detected:
|
|
272
|
+
r = results.get(prov["name"], {})
|
|
273
|
+
status = r.get("status", "unknown")
|
|
274
|
+
latency = r.get("latency_ms")
|
|
275
|
+
|
|
276
|
+
if status == "valid":
|
|
277
|
+
lat_str = f" ({latency:.0f}ms)" if latency else ""
|
|
278
|
+
print(f" ✅ {prov['label']}: key valid{lat_str}")
|
|
279
|
+
elif status == "invalid":
|
|
280
|
+
print(f" ❌ {prov['label']}: key invalid (check {prov['env_var']})")
|
|
281
|
+
elif status == "detected":
|
|
282
|
+
print(f" 🔑 {prov['label']}: key found ({prov['key_masked']})")
|
|
283
|
+
elif status == "unreachable":
|
|
284
|
+
print(f" 🌐 {prov['label']}: key found, but endpoint unreachable")
|
|
285
|
+
else:
|
|
286
|
+
print(f" ❓ {prov['label']}: {status}")
|
|
287
|
+
|
|
288
|
+
# 4. 风险评估 — 裸奔检测
|
|
289
|
+
provider_count = len(detected)
|
|
290
|
+
valid_count = sum(
|
|
291
|
+
1 for r in results.values()
|
|
292
|
+
if r.get("status") in ("valid", "detected", "unreachable")
|
|
293
|
+
)
|
|
294
|
+
invalid_count = sum(
|
|
295
|
+
1 for r in results.values()
|
|
296
|
+
if r.get("status") == "invalid"
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
print()
|
|
300
|
+
if provider_count == 0:
|
|
301
|
+
print(f" ⚠️ No API keys → Correctover needs at least one provider")
|
|
302
|
+
print(f" 💡 export OPENAI_API_KEY=sk-... → https://correctover.cn/docs")
|
|
303
|
+
elif provider_count == 1:
|
|
304
|
+
print(f" ⚠️ Only 1 provider — one failure = total outage (裸奔!)")
|
|
305
|
+
print(f" 💡 Add a 2nd provider → auto-failover protection")
|
|
306
|
+
elif invalid_count > 0:
|
|
307
|
+
print(f" ⚠️ {invalid_count} invalid key(s) → fix or remove them")
|
|
308
|
+
elif valid_count >= 2:
|
|
309
|
+
print(f" ✅ {valid_count} providers ready → multi-provider failover armed")
|
|
310
|
+
|
|
311
|
+
# 5. Plan 状态
|
|
312
|
+
try:
|
|
313
|
+
from correctover.license import get_plan, days_remaining, is_pro
|
|
314
|
+
plan = get_plan()
|
|
315
|
+
if plan == "free":
|
|
316
|
+
print()
|
|
317
|
+
print(f" ℹ️ Plan: Free — 检测免费·修复付费")
|
|
318
|
+
print(f" 💡 Pro: ¥699/年 | 全功能自愈 | 一key一机")
|
|
319
|
+
print(f" 📧 Contact: wangguigui@correctover.cn")
|
|
320
|
+
else:
|
|
321
|
+
days = days_remaining()
|
|
322
|
+
label = "永久" if days >= 99999 else f"{days}天"
|
|
323
|
+
print(f" 🔑 Plan: {plan} | {label} remaining")
|
|
324
|
+
except Exception:
|
|
325
|
+
pass
|
|
326
|
+
|
|
327
|
+
# 6. Next step
|
|
328
|
+
print()
|
|
329
|
+
if provider_count == 0:
|
|
330
|
+
print(f" 📖 Setup guide → correctover.cn/docs")
|
|
331
|
+
elif provider_count == 1:
|
|
332
|
+
print(f" 📖 Multi-provider setup → correctover.cn/docs")
|
|
333
|
+
else:
|
|
334
|
+
print(f" 📖 Full docs → correctover.cn/docs")
|
|
335
|
+
print()
|
correctover/_checkup.pyc
ADDED
|
Binary file
|