nucleus-mcp 1.0.7 → 1.2.0
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/package.json +2 -4
- package/.github/workflows/ci.yml +0 -72
- package/.smithery/shttp/module.js +0 -3
- package/.smithery/shttp/module.js.map +0 -7
- package/.smithery/stdio/index.cjs +0 -74
- package/.smithery/stdio/index.cjs.map +0 -7
- package/CHANGELOG.md +0 -65
- package/CODE_OF_CONDUCT.md +0 -34
- package/COMPARISON.md +0 -20
- package/CONTRIBUTING.md +0 -137
- package/DEVELOPMENT.md +0 -109
- package/LICENSE +0 -21
- package/README.md +0 -319
- package/ROADMAP.md +0 -24
- package/SECURITY.md +0 -88
- package/docs/COMMUNITY_FAQ.md +0 -48
- package/docs/COMPETITIVE_ANALYSIS.md +0 -90
- package/docs/DEMO_SCRIPT.md +0 -86
- package/docs/ENTERPRISE.md +0 -169
- package/docs/FAQ.md +0 -228
- package/docs/QUICK_START.md +0 -155
- package/docs/README.md +0 -67
- package/docs/RECURSIVE_SNAP_DEMO.gif +0 -0
- package/docs/social-preview-hq.png +0 -0
- package/docs/social-preview.png +0 -0
- package/examples/basic_usage.py +0 -84
- package/package.json.real +0 -24
- package/pyproject.toml +0 -50
- package/registry-server.json +0 -20
- package/scripts/demo_governance.py +0 -109
- package/scripts/sse_bridge.py +0 -87
- package/scripts/verify_windows_paths.py +0 -48
- package/server.json +0 -20
- package/src/mcp_server_nucleus/__init__.py +0 -8583
- package/src/mcp_server_nucleus/__main__.py +0 -9
- package/src/mcp_server_nucleus/cli.py +0 -1225
- package/src/mcp_server_nucleus/commitment_core.py +0 -146
- package/src/mcp_server_nucleus/commitment_ledger.py +0 -841
- package/src/mcp_server_nucleus/core/__init__.py +0 -6
- package/src/mcp_server_nucleus/core/orchestrator.py +0 -9
- package/src/mcp_server_nucleus/core/tool_registration_impl.py +0 -99
- package/src/mcp_server_nucleus/daemon/safety/lock.py +0 -121
- package/src/mcp_server_nucleus/deployment/render_integration.py +0 -4
- package/src/mcp_server_nucleus/hypervisor/__init__.py +0 -0
- package/src/mcp_server_nucleus/hypervisor/injector.py +0 -83
- package/src/mcp_server_nucleus/hypervisor/locker.py +0 -130
- package/src/mcp_server_nucleus/hypervisor/watchdog.py +0 -71
- package/src/mcp_server_nucleus/runtime/__init__.py +0 -58
- package/src/mcp_server_nucleus/runtime/agent.py +0 -517
- package/src/mcp_server_nucleus/runtime/agent_pool.py +0 -797
- package/src/mcp_server_nucleus/runtime/agents/base.py +0 -34
- package/src/mcp_server_nucleus/runtime/agents/oracle/manifest.json +0 -28
- package/src/mcp_server_nucleus/runtime/auth.py +0 -98
- package/src/mcp_server_nucleus/runtime/autopilot.py +0 -1057
- package/src/mcp_server_nucleus/runtime/broker.py +0 -126
- package/src/mcp_server_nucleus/runtime/budget.py +0 -153
- package/src/mcp_server_nucleus/runtime/capabilities/base.py +0 -20
- package/src/mcp_server_nucleus/runtime/capabilities/brain_ops.py +0 -253
- package/src/mcp_server_nucleus/runtime/capabilities/code_ops.py +0 -192
- package/src/mcp_server_nucleus/runtime/capabilities/depth_tracker.py +0 -81
- package/src/mcp_server_nucleus/runtime/capabilities/feature_map.py +0 -274
- package/src/mcp_server_nucleus/runtime/capabilities/marketing_engine.py +0 -205
- package/src/mcp_server_nucleus/runtime/capabilities/memory_ops.py +0 -79
- package/src/mcp_server_nucleus/runtime/capabilities/proof_system.py +0 -127
- package/src/mcp_server_nucleus/runtime/capabilities/render_ops.py +0 -80
- package/src/mcp_server_nucleus/runtime/capabilities/render_poller_cap.py +0 -266
- package/src/mcp_server_nucleus/runtime/capabilities/self_healing.py +0 -110
- package/src/mcp_server_nucleus/runtime/capabilities/strategy.py +0 -138
- package/src/mcp_server_nucleus/runtime/capabilities/synthesizer.py +0 -134
- package/src/mcp_server_nucleus/runtime/capabilities/web_ops.py +0 -94
- package/src/mcp_server_nucleus/runtime/common.py +0 -134
- package/src/mcp_server_nucleus/runtime/context_manager.py +0 -342
- package/src/mcp_server_nucleus/runtime/context_rules.json +0 -86
- package/src/mcp_server_nucleus/runtime/crdt_task_store.py +0 -349
- package/src/mcp_server_nucleus/runtime/critic.py +0 -150
- package/src/mcp_server_nucleus/runtime/daemon.py +0 -142
- package/src/mcp_server_nucleus/runtime/dashboard.py +0 -1087
- package/src/mcp_server_nucleus/runtime/depth_ops.py +0 -339
- package/src/mcp_server_nucleus/runtime/event_ops.py +0 -103
- package/src/mcp_server_nucleus/runtime/event_stream.py +0 -213
- package/src/mcp_server_nucleus/runtime/export.py +0 -64
- package/src/mcp_server_nucleus/runtime/factory.py +0 -594
- package/src/mcp_server_nucleus/runtime/federation.py +0 -1040
- package/src/mcp_server_nucleus/runtime/fetcher.py +0 -92
- package/src/mcp_server_nucleus/runtime/file_monitor.py +0 -208
- package/src/mcp_server_nucleus/runtime/firestore_bridge.py +0 -91
- package/src/mcp_server_nucleus/runtime/gcloud_ops.py +0 -186
- package/src/mcp_server_nucleus/runtime/hooks.py +0 -246
- package/src/mcp_server_nucleus/runtime/identity/gatekeeper.py +0 -106
- package/src/mcp_server_nucleus/runtime/identity/keygen.py +0 -147
- package/src/mcp_server_nucleus/runtime/identity/manifest.py +0 -62
- package/src/mcp_server_nucleus/runtime/identity/trust.py +0 -27
- package/src/mcp_server_nucleus/runtime/inspector.py +0 -74
- package/src/mcp_server_nucleus/runtime/installer.py +0 -81
- package/src/mcp_server_nucleus/runtime/ipc_auth.py +0 -377
- package/src/mcp_server_nucleus/runtime/lifecycle.py +0 -129
- package/src/mcp_server_nucleus/runtime/llm_client.py +0 -408
- package/src/mcp_server_nucleus/runtime/llm_tiers.json +0 -88
- package/src/mcp_server_nucleus/runtime/locking.py +0 -228
- package/src/mcp_server_nucleus/runtime/loops/fixer.py +0 -110
- package/src/mcp_server_nucleus/runtime/memory.py +0 -158
- package/src/mcp_server_nucleus/runtime/mounter.py +0 -210
- package/src/mcp_server_nucleus/runtime/mounter_ops.py +0 -250
- package/src/mcp_server_nucleus/runtime/nuke_protocol.py +0 -147
- package/src/mcp_server_nucleus/runtime/orchestrator.py +0 -370
- package/src/mcp_server_nucleus/runtime/orchestrator_unified.py +0 -263
- package/src/mcp_server_nucleus/runtime/orchestrator_v3.py +0 -617
- package/src/mcp_server_nucleus/runtime/plugin_loader.py +0 -84
- package/src/mcp_server_nucleus/runtime/policy.py +0 -117
- package/src/mcp_server_nucleus/runtime/profiling.py +0 -219
- package/src/mcp_server_nucleus/runtime/prometheus.py +0 -286
- package/src/mcp_server_nucleus/runtime/proposals.py +0 -101
- package/src/mcp_server_nucleus/runtime/publisher.py +0 -109
- package/src/mcp_server_nucleus/runtime/pulse.py +0 -516
- package/src/mcp_server_nucleus/runtime/registry.py +0 -82
- package/src/mcp_server_nucleus/runtime/render_ops.py +0 -83
- package/src/mcp_server_nucleus/runtime/schema_gen.py +0 -140
- package/src/mcp_server_nucleus/runtime/session_ops.py +0 -224
- package/src/mcp_server_nucleus/runtime/stdio_server.py +0 -633
- package/src/mcp_server_nucleus/runtime/storage.py +0 -83
- package/src/mcp_server_nucleus/runtime/strategy.py +0 -91
- package/src/mcp_server_nucleus/runtime/swarm.py +0 -170
- package/src/mcp_server_nucleus/runtime/sync_ops.py +0 -878
- package/src/mcp_server_nucleus/runtime/task_ingestion.py +0 -1020
- package/src/mcp_server_nucleus/runtime/task_ops.py +0 -449
- package/src/mcp_server_nucleus/runtime/task_scheduler.py +0 -488
- package/src/mcp_server_nucleus/runtime/team.py +0 -59
- package/src/mcp_server_nucleus/runtime/triggers.py +0 -237
- package/src/mcp_server_nucleus/runtime/vector_store.py +0 -125
- package/src/mcp_server_nucleus/runtime/watcher.py +0 -112
- package/src/mcp_server_nucleus/tool_tiers.py +0 -243
- package/tests/__init__.py +0 -1
- package/tests/conftest.py +0 -42
- package/tests/test_core.py +0 -235
- package/tests/test_search.py +0 -69
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Nucleus LLM Client
|
|
3
|
-
==================
|
|
4
|
-
Multi-Tier LLM Client with intelligent routing.
|
|
5
|
-
Primary: google-genai (v1.0+) with Vertex AI
|
|
6
|
-
Fallback: google-generativeai (Legacy) or API Key
|
|
7
|
-
|
|
8
|
-
MDR_010 Compliant: Ensures high availability and reliability.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import os
|
|
12
|
-
import logging
|
|
13
|
-
import json
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
from datetime import datetime
|
|
16
|
-
from typing import Optional, Dict, Any
|
|
17
|
-
from enum import Enum
|
|
18
|
-
|
|
19
|
-
# Configure logger
|
|
20
|
-
logger = logging.getLogger("nucleus.llm")
|
|
21
|
-
|
|
22
|
-
# ============================================================
|
|
23
|
-
# SDK AVAILABILITY FLAGS & ENVIRONMENT CONTROL
|
|
24
|
-
# ============================================================
|
|
25
|
-
# Environment variable to force SDK selection:
|
|
26
|
-
# USE_NEW_GENAI=true → Prefer google-genai (new SDK)
|
|
27
|
-
# USE_NEW_GENAI=false → Prefer google.generativeai (legacy)
|
|
28
|
-
# Default: Try new SDK first, fall back to legacy
|
|
29
|
-
# ============================================================
|
|
30
|
-
|
|
31
|
-
# Primary SDK: google-genai (v1.0+)
|
|
32
|
-
try:
|
|
33
|
-
from google import genai
|
|
34
|
-
from google.genai import types
|
|
35
|
-
HAS_GENAI = True
|
|
36
|
-
logger.info("✅ SDK: google-genai (new) available")
|
|
37
|
-
except ImportError:
|
|
38
|
-
HAS_GENAI = False
|
|
39
|
-
logger.error("❌ SDK: google-genai NOT installed! Please run 'pip install google-genai'")
|
|
40
|
-
|
|
41
|
-
# Fallback SDK: google.generativeai (Legacy)
|
|
42
|
-
try:
|
|
43
|
-
import warnings
|
|
44
|
-
with warnings.catch_warnings():
|
|
45
|
-
warnings.filterwarnings("ignore", category=FutureWarning)
|
|
46
|
-
import google.generativeai as genai_legacy
|
|
47
|
-
HAS_LEGACY = True
|
|
48
|
-
except ImportError:
|
|
49
|
-
HAS_LEGACY = False
|
|
50
|
-
|
|
51
|
-
def get_active_sdk() -> str:
|
|
52
|
-
if HAS_GENAI:
|
|
53
|
-
return "NEW"
|
|
54
|
-
if HAS_LEGACY:
|
|
55
|
-
return "LEGACY"
|
|
56
|
-
return "NONE"
|
|
57
|
-
|
|
58
|
-
_active = get_active_sdk()
|
|
59
|
-
logger.info(f"🎯 SDK Selection: Using {_active}")
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# ============================================================
|
|
63
|
-
# TIER ROUTER: Intelligent Multi-Tier LLM Selection
|
|
64
|
-
# ============================================================
|
|
65
|
-
|
|
66
|
-
class LLMTier(Enum):
|
|
67
|
-
"""Available LLM pricing/capability tiers."""
|
|
68
|
-
PREMIUM = "premium" # gemini-3-pro, highest capability
|
|
69
|
-
STANDARD = "standard" # gemini-2.5-flash, default production
|
|
70
|
-
ECONOMY = "economy" # gemini-2.5-flash-lite, background tasks
|
|
71
|
-
LOCAL_PAID = "local_paid" # API Key with billing
|
|
72
|
-
LOCAL_FREE = "local_free" # API Key free tier (100 req/day)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class TierRouter:
|
|
76
|
-
"""
|
|
77
|
-
Intelligent router that selects the optimal LLM tier based on:
|
|
78
|
-
- Job type (CRITICAL, RESEARCH, ORCHESTRATION, BACKGROUND, TESTING)
|
|
79
|
-
- Budget mode (spartan, balanced, premium)
|
|
80
|
-
- Quota availability (fallback on errors)
|
|
81
|
-
"""
|
|
82
|
-
|
|
83
|
-
TIER_CONFIGS = {
|
|
84
|
-
LLMTier.PREMIUM: {
|
|
85
|
-
"model": "gemini-2.5-pro", # Note: gemini-3-pro not yet in Vertex AI
|
|
86
|
-
"platform": "vertex",
|
|
87
|
-
"cost_level": "high",
|
|
88
|
-
"description": "Advanced reasoning, complex architecture"
|
|
89
|
-
},
|
|
90
|
-
LLMTier.STANDARD: {
|
|
91
|
-
"model": "gemini-2.5-flash",
|
|
92
|
-
"platform": "vertex",
|
|
93
|
-
"cost_level": "medium",
|
|
94
|
-
"description": "Default production, 95% of work"
|
|
95
|
-
},
|
|
96
|
-
LLMTier.ECONOMY: {
|
|
97
|
-
"model": "gemini-2.5-flash-lite",
|
|
98
|
-
"platform": "vertex",
|
|
99
|
-
"cost_level": "low",
|
|
100
|
-
"description": "Background tasks, batch jobs"
|
|
101
|
-
},
|
|
102
|
-
LLMTier.LOCAL_PAID: {
|
|
103
|
-
"model": "gemini-2.5-flash",
|
|
104
|
-
"platform": "api_key",
|
|
105
|
-
"cost_level": "medium",
|
|
106
|
-
"description": "Fallback when Vertex fails"
|
|
107
|
-
},
|
|
108
|
-
LLMTier.LOCAL_FREE: {
|
|
109
|
-
"model": "gemini-2.0-flash",
|
|
110
|
-
"platform": "api_key",
|
|
111
|
-
"cost_level": "free",
|
|
112
|
-
"description": "Testing only, 100 req/day"
|
|
113
|
-
},
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
JOB_ROUTING = {
|
|
117
|
-
"CRITICAL": LLMTier.PREMIUM,
|
|
118
|
-
"RESEARCH": LLMTier.STANDARD,
|
|
119
|
-
"ORCHESTRATION": LLMTier.STANDARD,
|
|
120
|
-
"BACKGROUND": LLMTier.ECONOMY,
|
|
121
|
-
"TESTING": LLMTier.LOCAL_FREE,
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
BUDGET_MODES = {
|
|
125
|
-
"spartan": [LLMTier.LOCAL_FREE, LLMTier.ECONOMY, LLMTier.STANDARD],
|
|
126
|
-
"balanced": [LLMTier.STANDARD, LLMTier.ECONOMY, LLMTier.LOCAL_PAID],
|
|
127
|
-
"premium": [LLMTier.PREMIUM, LLMTier.STANDARD, LLMTier.ECONOMY],
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
FALLBACK_CHAIN = [
|
|
131
|
-
LLMTier.PREMIUM,
|
|
132
|
-
LLMTier.STANDARD,
|
|
133
|
-
LLMTier.ECONOMY,
|
|
134
|
-
LLMTier.LOCAL_PAID,
|
|
135
|
-
LLMTier.LOCAL_FREE
|
|
136
|
-
]
|
|
137
|
-
|
|
138
|
-
@classmethod
|
|
139
|
-
def route(cls, job_type: str = "ORCHESTRATION", budget_mode: str = "balanced") -> LLMTier:
|
|
140
|
-
"""
|
|
141
|
-
Route to optimal tier based on job type and budget.
|
|
142
|
-
|
|
143
|
-
Args:
|
|
144
|
-
job_type: CRITICAL, RESEARCH, ORCHESTRATION, BACKGROUND, TESTING
|
|
145
|
-
budget_mode: spartan, balanced, premium
|
|
146
|
-
|
|
147
|
-
Returns:
|
|
148
|
-
The recommended LLMTier
|
|
149
|
-
"""
|
|
150
|
-
# Check for forced tier (env var override)
|
|
151
|
-
forced_tier = os.environ.get("NUCLEUS_LLM_TIER")
|
|
152
|
-
if forced_tier:
|
|
153
|
-
try:
|
|
154
|
-
return LLMTier(forced_tier.lower())
|
|
155
|
-
except ValueError:
|
|
156
|
-
logger.warning(f"Invalid NUCLEUS_LLM_TIER: {forced_tier}")
|
|
157
|
-
|
|
158
|
-
# Route based on job type
|
|
159
|
-
job_upper = job_type.upper() if job_type else "ORCHESTRATION"
|
|
160
|
-
base_tier = cls.JOB_ROUTING.get(job_upper, LLMTier.STANDARD)
|
|
161
|
-
|
|
162
|
-
# CRITICAL jobs ALWAYS get their designated tier (premium)
|
|
163
|
-
# Quality for important decisions overrides budget constraints
|
|
164
|
-
if job_upper == "CRITICAL":
|
|
165
|
-
logger.info("🎯 TierRouter: CRITICAL job - using premium tier regardless of budget")
|
|
166
|
-
return LLMTier.PREMIUM
|
|
167
|
-
|
|
168
|
-
# TESTING jobs ALWAYS use free tier to save costs
|
|
169
|
-
if job_upper == "TESTING":
|
|
170
|
-
return LLMTier.LOCAL_FREE
|
|
171
|
-
|
|
172
|
-
# For other jobs, apply budget constraints
|
|
173
|
-
budget_tiers = cls.BUDGET_MODES.get(budget_mode.lower(), cls.BUDGET_MODES["balanced"])
|
|
174
|
-
|
|
175
|
-
# If base tier is in budget, use it; otherwise use first budget tier
|
|
176
|
-
if base_tier in budget_tiers:
|
|
177
|
-
return base_tier
|
|
178
|
-
return budget_tiers[0]
|
|
179
|
-
|
|
180
|
-
@classmethod
|
|
181
|
-
def get_config(cls, tier: LLMTier) -> Dict[str, Any]:
|
|
182
|
-
"""Get configuration for a specific tier."""
|
|
183
|
-
return cls.TIER_CONFIGS.get(tier, cls.TIER_CONFIGS[LLMTier.STANDARD])
|
|
184
|
-
|
|
185
|
-
@classmethod
|
|
186
|
-
def get_fallback(cls, current_tier: LLMTier) -> Optional[LLMTier]:
|
|
187
|
-
"""Get next tier in fallback chain."""
|
|
188
|
-
try:
|
|
189
|
-
idx = cls.FALLBACK_CHAIN.index(current_tier)
|
|
190
|
-
if idx < len(cls.FALLBACK_CHAIN) - 1:
|
|
191
|
-
return cls.FALLBACK_CHAIN[idx + 1]
|
|
192
|
-
except ValueError:
|
|
193
|
-
pass
|
|
194
|
-
return None
|
|
195
|
-
|
|
196
|
-
class DualEngineLLM:
|
|
197
|
-
"""
|
|
198
|
-
Unified LLM Client wrapper with intelligent tier routing.
|
|
199
|
-
Transparently falls back to legacy SDK if V1 is not available.
|
|
200
|
-
|
|
201
|
-
Supports:
|
|
202
|
-
- Explicit tier selection: DualEngineLLM(tier=LLMTier.PREMIUM)
|
|
203
|
-
- Job-based routing: DualEngineLLM(job_type="CRITICAL")
|
|
204
|
-
- Budget modes: "spartan", "balanced", "premium"
|
|
205
|
-
"""
|
|
206
|
-
|
|
207
|
-
def __init__(
|
|
208
|
-
self,
|
|
209
|
-
model_name: Optional[str] = None,
|
|
210
|
-
tier: Optional[LLMTier] = None,
|
|
211
|
-
job_type: Optional[str] = None,
|
|
212
|
-
budget_mode: str = "balanced",
|
|
213
|
-
system_instruction: Optional[str] = None,
|
|
214
|
-
api_key: Optional[str] = None
|
|
215
|
-
):
|
|
216
|
-
# TIER ROUTING LOGIC
|
|
217
|
-
self.tier = None
|
|
218
|
-
self.tier_config = None
|
|
219
|
-
|
|
220
|
-
if tier:
|
|
221
|
-
# Explicit tier selection
|
|
222
|
-
self.tier = tier
|
|
223
|
-
self.tier_config = TierRouter.get_config(tier)
|
|
224
|
-
model_name = self.tier_config["model"]
|
|
225
|
-
logger.info(f"🎯 LLM: Using explicit tier '{tier.value}' → {model_name}")
|
|
226
|
-
elif job_type:
|
|
227
|
-
# Job-based routing
|
|
228
|
-
self.tier = TierRouter.route(job_type, budget_mode)
|
|
229
|
-
self.tier_config = TierRouter.get_config(self.tier)
|
|
230
|
-
model_name = self.tier_config["model"]
|
|
231
|
-
logger.info(f"🎯 LLM: Routed job '{job_type}' (budget={budget_mode}) → tier '{self.tier.value}' → {model_name}")
|
|
232
|
-
else:
|
|
233
|
-
# Default to standard tier
|
|
234
|
-
self.tier = LLMTier.STANDARD
|
|
235
|
-
self.tier_config = TierRouter.get_config(self.tier)
|
|
236
|
-
model_name = model_name or self.tier_config["model"]
|
|
237
|
-
|
|
238
|
-
self.model_name = model_name
|
|
239
|
-
self.system_instruction = system_instruction
|
|
240
|
-
self.api_key = api_key or os.environ.get("GEMINI_API_KEY")
|
|
241
|
-
self.client = None
|
|
242
|
-
self.engine = "NONE"
|
|
243
|
-
self.budget_mode = budget_mode
|
|
244
|
-
|
|
245
|
-
# Determine platform from tier config
|
|
246
|
-
use_vertex = self.tier_config.get("platform", "vertex") == "vertex" if self.tier_config else True
|
|
247
|
-
force_vertex_env = os.environ.get("FORCE_VERTEX", "1") == "1"
|
|
248
|
-
|
|
249
|
-
# For local tiers, don't use vertex
|
|
250
|
-
if self.tier in [LLMTier.LOCAL_PAID, LLMTier.LOCAL_FREE]:
|
|
251
|
-
use_vertex = False
|
|
252
|
-
else:
|
|
253
|
-
use_vertex = use_vertex or force_vertex_env
|
|
254
|
-
|
|
255
|
-
if not self.api_key and not use_vertex:
|
|
256
|
-
raise ValueError("GEMINI_API_KEY is required for local tiers (or set FORCE_VERTEX=1).")
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
# 1. Initialize google-genai (Primary)
|
|
260
|
-
if HAS_GENAI:
|
|
261
|
-
try:
|
|
262
|
-
if use_vertex:
|
|
263
|
-
project_id = os.environ.get("GCP_PROJECT_ID", os.environ.get("GOOGLE_CLOUD_PROJECT", "gen-lang-client-0894185576"))
|
|
264
|
-
location = os.environ.get("GCP_LOCATION", "us-central1")
|
|
265
|
-
|
|
266
|
-
logger.info(f"🏢 LLM Client: Vertex AI Mode ({project_id})")
|
|
267
|
-
self.client = genai.Client(vertexai=True, project=project_id, location=location)
|
|
268
|
-
else:
|
|
269
|
-
logger.info("🔑 LLM Client: API Key Mode")
|
|
270
|
-
# Use v1alpha for experimental features if needed, or stable v1
|
|
271
|
-
self.client = genai.Client(api_key=self.api_key)
|
|
272
|
-
|
|
273
|
-
self.engine = "NEW"
|
|
274
|
-
return
|
|
275
|
-
except Exception as e:
|
|
276
|
-
logger.warning(f"⚠️ V1 Init failed: {e}")
|
|
277
|
-
|
|
278
|
-
# 2. Try Legacy (Fallback)
|
|
279
|
-
if HAS_LEGACY:
|
|
280
|
-
try:
|
|
281
|
-
genai_legacy.configure(api_key=self.api_key)
|
|
282
|
-
# Map newer model names to legacy compatible ones if needed
|
|
283
|
-
if "2.0" in model_name:
|
|
284
|
-
logger.warning(f"⚠️ Legacy SDK may not support {model_name}. Using gemini-1.5-flash.")
|
|
285
|
-
self.model_name = "gemini-1.5-flash"
|
|
286
|
-
|
|
287
|
-
self.model = genai_legacy.GenerativeModel(
|
|
288
|
-
model_name=self.model_name,
|
|
289
|
-
system_instruction=system_instruction
|
|
290
|
-
)
|
|
291
|
-
self.engine = "LEGACY"
|
|
292
|
-
logger.info(f"✅ LLM Client: Initialized google-generativeai (Legacy) for {self.model_name}")
|
|
293
|
-
return
|
|
294
|
-
except Exception as e:
|
|
295
|
-
logger.error(f"❌ LLM Client: Legacy Init failed: {e}")
|
|
296
|
-
|
|
297
|
-
if self.engine == "NONE":
|
|
298
|
-
raise ImportError("Could not initialize any Gemini SDK. Install google-genai or google-generativeai.")
|
|
299
|
-
|
|
300
|
-
def _log_interaction(self, prompt: str, response: Any):
|
|
301
|
-
"""
|
|
302
|
-
Automatic Capture (Brain Consolidation - Phase 1).
|
|
303
|
-
Saves the raw interaction to disk for later mining/consolidation.
|
|
304
|
-
"""
|
|
305
|
-
try:
|
|
306
|
-
brain_path = Path(os.environ.get("NUCLEAR_BRAIN_PATH", "/Users/lokeshgarg/ai-mvp-backend/.brain"))
|
|
307
|
-
raw_path = brain_path / "raw"
|
|
308
|
-
raw_path.mkdir(parents=True, exist_ok=True)
|
|
309
|
-
|
|
310
|
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
|
|
311
|
-
filename = raw_path / f"llm_interaction_{timestamp}.json"
|
|
312
|
-
|
|
313
|
-
# Extract text from response (Best effort)
|
|
314
|
-
response_text = "Unknown"
|
|
315
|
-
if hasattr(response, 'text'):
|
|
316
|
-
response_text = response.text
|
|
317
|
-
|
|
318
|
-
data = {
|
|
319
|
-
"timestamp": datetime.now().isoformat(),
|
|
320
|
-
"engine": self.engine,
|
|
321
|
-
"model": self.model_name,
|
|
322
|
-
"prompt": str(prompt)[:5000], # Truncate massive prompts
|
|
323
|
-
"response_text": response_text
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
with open(filename, 'w') as f:
|
|
327
|
-
json.dump(data, f, indent=2)
|
|
328
|
-
|
|
329
|
-
except Exception as e:
|
|
330
|
-
logger.warning(f"Failed to log interaction: {e}")
|
|
331
|
-
|
|
332
|
-
def generate_content(self, prompt: str, **kwargs) -> Any:
|
|
333
|
-
try:
|
|
334
|
-
if self.engine == "NEW":
|
|
335
|
-
config_args = {}
|
|
336
|
-
if self.system_instruction:
|
|
337
|
-
config_args['system_instruction'] = self.system_instruction
|
|
338
|
-
|
|
339
|
-
if 'tools' in kwargs:
|
|
340
|
-
tools_raw = kwargs['tools']
|
|
341
|
-
if isinstance(tools_raw, dict) and "function_declarations" in tools_raw:
|
|
342
|
-
config_args['tools'] = [tools_raw]
|
|
343
|
-
else:
|
|
344
|
-
config_args['tools'] = tools_raw
|
|
345
|
-
|
|
346
|
-
if 'tool_config' in kwargs:
|
|
347
|
-
config_args['tool_config'] = kwargs['tool_config']
|
|
348
|
-
|
|
349
|
-
config = types.GenerateContentConfig(**config_args)
|
|
350
|
-
|
|
351
|
-
response = self.client.models.generate_content(
|
|
352
|
-
model=self.model_name,
|
|
353
|
-
contents=prompt,
|
|
354
|
-
config=config
|
|
355
|
-
)
|
|
356
|
-
self._log_interaction(prompt, response)
|
|
357
|
-
return response
|
|
358
|
-
|
|
359
|
-
elif self.engine == "LEGACY":
|
|
360
|
-
# Legacy SDK logic
|
|
361
|
-
generation_config = {}
|
|
362
|
-
# Legacy doesn't support tools same way here, basic text only for now or map tools manually
|
|
363
|
-
# For Marketing Autopilot, we mostly use text.
|
|
364
|
-
|
|
365
|
-
response = self.model.generate_content(prompt, generation_config=generation_config)
|
|
366
|
-
self._log_interaction(prompt, response)
|
|
367
|
-
return response
|
|
368
|
-
|
|
369
|
-
except Exception as e:
|
|
370
|
-
logger.error(f"❌ LLM Generate Content Failed ({self.engine}): {e}")
|
|
371
|
-
raise
|
|
372
|
-
|
|
373
|
-
def embed_content(self, text: str, task_type: str = "retrieval_document", title: Optional[str] = None) -> Dict[str, Any]:
|
|
374
|
-
try:
|
|
375
|
-
if self.engine == "NEW":
|
|
376
|
-
normalized_task_type = task_type.replace("retrieval_", "RETRIEVAL_").upper()
|
|
377
|
-
config = {'task_type': normalized_task_type}
|
|
378
|
-
if title:
|
|
379
|
-
config['title'] = title
|
|
380
|
-
|
|
381
|
-
response = self.client.models.embed_content(
|
|
382
|
-
model=self.model_name,
|
|
383
|
-
contents=text,
|
|
384
|
-
config=config
|
|
385
|
-
)
|
|
386
|
-
if hasattr(response, 'embeddings') and response.embeddings:
|
|
387
|
-
return {'embedding': response.embeddings[0].values}
|
|
388
|
-
return {'embedding': []}
|
|
389
|
-
|
|
390
|
-
elif self.engine == "LEGACY":
|
|
391
|
-
# Legacy SDK
|
|
392
|
-
# task_type mapping
|
|
393
|
-
# content
|
|
394
|
-
result = genai_legacy.embed_content(
|
|
395
|
-
model="models/text-embedding-004", # Hardcoded or passed in
|
|
396
|
-
content=text,
|
|
397
|
-
task_type=task_type,
|
|
398
|
-
title=title
|
|
399
|
-
)
|
|
400
|
-
return result
|
|
401
|
-
|
|
402
|
-
except Exception as e:
|
|
403
|
-
logger.error(f"❌ LLM Embed Content Failed ({self.engine}): {e}")
|
|
404
|
-
raise
|
|
405
|
-
|
|
406
|
-
@property
|
|
407
|
-
def active_engine(self):
|
|
408
|
-
return self.engine
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"tiers": {
|
|
3
|
-
"premium": {
|
|
4
|
-
"model": "gemini-2.5-pro",
|
|
5
|
-
"platform": "vertex",
|
|
6
|
-
"description": "Advanced reasoning, complex architecture, critical decisions",
|
|
7
|
-
"cost_per_1m_tokens": {
|
|
8
|
-
"input": 2.00,
|
|
9
|
-
"output": 8.00
|
|
10
|
-
},
|
|
11
|
-
"max_context_tokens": 1000000
|
|
12
|
-
},
|
|
13
|
-
"standard": {
|
|
14
|
-
"model": "gemini-2.5-flash",
|
|
15
|
-
"platform": "vertex",
|
|
16
|
-
"description": "Default production tier. 95% of agent work.",
|
|
17
|
-
"cost_per_1m_tokens": {
|
|
18
|
-
"input": 0.10,
|
|
19
|
-
"output": 0.40
|
|
20
|
-
},
|
|
21
|
-
"max_context_tokens": 1000000
|
|
22
|
-
},
|
|
23
|
-
"economy": {
|
|
24
|
-
"model": "gemini-2.5-flash-lite",
|
|
25
|
-
"platform": "vertex",
|
|
26
|
-
"description": "Background tasks, batch processing, digests",
|
|
27
|
-
"cost_per_1m_tokens": {
|
|
28
|
-
"input": 0.05,
|
|
29
|
-
"output": 0.20
|
|
30
|
-
},
|
|
31
|
-
"max_context_tokens": 100000
|
|
32
|
-
},
|
|
33
|
-
"local_paid": {
|
|
34
|
-
"model": "gemini-2.5-flash",
|
|
35
|
-
"platform": "api_key",
|
|
36
|
-
"description": "Fallback when Vertex/ADC fails. Requires GEMINI_API_KEY with billing.",
|
|
37
|
-
"cost_per_1m_tokens": {
|
|
38
|
-
"input": 0.10,
|
|
39
|
-
"output": 0.40
|
|
40
|
-
},
|
|
41
|
-
"max_context_tokens": 32000
|
|
42
|
-
},
|
|
43
|
-
"local_free": {
|
|
44
|
-
"model": "gemini-2.0-flash",
|
|
45
|
-
"platform": "api_key",
|
|
46
|
-
"description": "Testing only. 100 req/day limit. Do NOT use for production.",
|
|
47
|
-
"cost_per_1m_tokens": {
|
|
48
|
-
"input": 0,
|
|
49
|
-
"output": 0
|
|
50
|
-
},
|
|
51
|
-
"max_context_tokens": 32000,
|
|
52
|
-
"daily_limit": 100
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
"job_routing": {
|
|
56
|
-
"CRITICAL": "premium",
|
|
57
|
-
"RESEARCH": "standard",
|
|
58
|
-
"ORCHESTRATION": "standard",
|
|
59
|
-
"BACKGROUND": "economy",
|
|
60
|
-
"TESTING": "local_free"
|
|
61
|
-
},
|
|
62
|
-
"budget_modes": {
|
|
63
|
-
"spartan": [
|
|
64
|
-
"local_free",
|
|
65
|
-
"economy",
|
|
66
|
-
"standard"
|
|
67
|
-
],
|
|
68
|
-
"balanced": [
|
|
69
|
-
"standard",
|
|
70
|
-
"economy",
|
|
71
|
-
"local_paid"
|
|
72
|
-
],
|
|
73
|
-
"premium": [
|
|
74
|
-
"premium",
|
|
75
|
-
"standard",
|
|
76
|
-
"economy"
|
|
77
|
-
]
|
|
78
|
-
},
|
|
79
|
-
"fallback_chain": [
|
|
80
|
-
"premium",
|
|
81
|
-
"standard",
|
|
82
|
-
"economy",
|
|
83
|
-
"local_paid",
|
|
84
|
-
"local_free"
|
|
85
|
-
],
|
|
86
|
-
"default_tier": "standard",
|
|
87
|
-
"default_budget_mode": "balanced"
|
|
88
|
-
}
|