ltcai 8.9.0 → 9.1.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/README.md +81 -58
- package/auto_setup.py +7 -904
- package/desktop/electron/README.md +9 -0
- package/desktop/electron/main.cjs +5 -3
- package/docs/CHANGELOG.md +221 -238
- package/docs/COMMUNITY_AND_PLUGINS.md +3 -2
- package/docs/DEVELOPMENT.md +53 -14
- package/docs/LEGACY_COMPATIBILITY.md +4 -4
- package/docs/ONBOARDING.md +10 -2
- package/docs/OPERATIONS.md +8 -4
- package/docs/PRODUCT_DIRECTION_REVIEW.md +4 -0
- package/docs/ROADMAP_RECOMMENDATIONS.md +61 -36
- package/docs/TRUST_MODEL.md +5 -2
- package/docs/WHY_LATTICE.md +15 -10
- package/docs/WORKFLOW_DESIGNER.md +22 -0
- package/docs/kg-schema.md +13 -2
- package/docs/mcp-tools.md +17 -6
- package/docs/privacy.md +19 -3
- package/docs/public-deploy.md +32 -3
- package/docs/security-model.md +46 -11
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/archive.py +4 -14
- package/lattice_brain/context.py +66 -9
- package/lattice_brain/embeddings.py +38 -2
- package/lattice_brain/graph/_kg_common.py +27 -462
- package/lattice_brain/graph/_kg_constants.py +243 -0
- package/lattice_brain/graph/_kg_fsutil.py +293 -0
- package/lattice_brain/graph/discovery.py +0 -948
- package/lattice_brain/graph/discovery_index.py +1126 -0
- package/lattice_brain/graph/documents.py +44 -9
- package/lattice_brain/graph/ingest.py +47 -20
- package/lattice_brain/graph/provenance.py +13 -6
- package/lattice_brain/graph/retrieval.py +141 -610
- package/lattice_brain/graph/retrieval_docgen.py +243 -0
- package/lattice_brain/graph/retrieval_vector.py +460 -0
- package/lattice_brain/graph/store.py +6 -0
- package/lattice_brain/ingestion.py +69 -4
- package/lattice_brain/portability.py +29 -23
- package/lattice_brain/quality.py +98 -4
- package/lattice_brain/runtime/agent_runtime.py +169 -7
- package/lattice_brain/runtime/hooks.py +30 -13
- package/lattice_brain/runtime/multi_agent.py +27 -8
- package/lattice_brain/runtime/statuses.py +10 -0
- package/lattice_brain/utils.py +46 -0
- package/lattice_brain/workflow.py +1 -5
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/admin.py +1 -1
- package/latticeai/api/agent_registry.py +10 -8
- package/latticeai/api/agents.py +22 -3
- package/latticeai/api/auth.py +78 -16
- package/latticeai/api/browser.py +303 -34
- package/latticeai/api/chat.py +355 -952
- package/latticeai/api/chat_agent_http.py +356 -0
- package/latticeai/api/chat_contracts.py +54 -0
- package/latticeai/api/chat_documents.py +256 -0
- package/latticeai/api/chat_helpers.py +250 -0
- package/latticeai/api/chat_history.py +99 -0
- package/latticeai/api/chat_intents.py +302 -0
- package/latticeai/api/chat_stream.py +115 -0
- package/latticeai/api/computer_use.py +211 -40
- package/latticeai/api/health.py +9 -3
- package/latticeai/api/hooks.py +17 -6
- package/latticeai/api/knowledge_graph.py +78 -14
- package/latticeai/api/local_files.py +7 -2
- package/latticeai/api/marketplace.py +11 -0
- package/latticeai/api/mcp.py +102 -23
- package/latticeai/api/models.py +72 -33
- package/latticeai/api/network.py +5 -5
- package/latticeai/api/permissions.py +70 -29
- package/latticeai/api/plugins.py +21 -7
- package/latticeai/api/portability.py +5 -5
- package/latticeai/api/realtime.py +33 -5
- package/latticeai/api/setup.py +2 -2
- package/latticeai/api/static_routes.py +123 -24
- package/latticeai/api/tools.py +97 -14
- package/latticeai/api/workflow_designer.py +37 -0
- package/latticeai/api/workspace.py +2 -1
- package/latticeai/app_factory.py +185 -405
- package/latticeai/core/agent.py +19 -9
- package/latticeai/core/agent_registry.py +1 -5
- package/latticeai/core/config.py +23 -3
- package/latticeai/core/context_builder.py +21 -3
- package/latticeai/core/invitations.py +1 -4
- package/latticeai/core/io_utils.py +45 -0
- package/latticeai/core/legacy_compatibility.py +24 -3
- package/latticeai/core/local_embeddings.py +2 -4
- package/latticeai/core/marketplace.py +33 -2
- package/latticeai/core/mcp_catalog.py +450 -0
- package/latticeai/core/mcp_registry.py +2 -441
- package/latticeai/core/plugins.py +15 -0
- package/latticeai/core/policy.py +1 -1
- package/latticeai/core/realtime.py +38 -15
- package/latticeai/core/sessions.py +7 -2
- package/latticeai/core/timeutil.py +10 -0
- package/latticeai/core/tool_registry.py +90 -18
- package/latticeai/core/users.py +5 -14
- package/latticeai/core/workspace_graph_trace.py +31 -5
- package/latticeai/core/workspace_memory.py +3 -1
- package/latticeai/core/workspace_os.py +18 -7
- package/latticeai/core/workspace_os_utils.py +2 -21
- package/latticeai/core/workspace_permissions.py +2 -1
- package/latticeai/core/workspace_plugins.py +1 -1
- package/latticeai/core/workspace_runs.py +14 -5
- package/latticeai/core/workspace_skills.py +1 -1
- package/latticeai/core/workspace_snapshots.py +2 -1
- package/latticeai/core/workspace_timeline.py +2 -1
- package/latticeai/integrations/telegram_bot.py +96 -40
- package/latticeai/models/model_providers.py +111 -0
- package/latticeai/models/router.py +189 -173
- package/latticeai/runtime/access_runtime.py +62 -4
- package/latticeai/runtime/audit_runtime.py +27 -16
- package/latticeai/runtime/automation_runtime.py +22 -7
- package/latticeai/runtime/brain_runtime.py +19 -7
- package/latticeai/runtime/chat_wiring.py +2 -0
- package/latticeai/runtime/config_runtime.py +58 -26
- package/latticeai/runtime/context_runtime.py +16 -4
- package/latticeai/runtime/history_runtime.py +163 -0
- package/latticeai/runtime/hooks_runtime.py +1 -1
- package/latticeai/runtime/model_wiring.py +33 -5
- package/latticeai/runtime/namespace_runtime.py +163 -0
- package/latticeai/runtime/network_config_runtime.py +56 -0
- package/latticeai/runtime/platform_runtime_wiring.py +4 -3
- package/latticeai/runtime/review_wiring.py +1 -1
- package/latticeai/runtime/router_registration.py +34 -1
- package/latticeai/runtime/security_runtime.py +110 -13
- package/latticeai/runtime/sso_config_runtime.py +128 -0
- package/latticeai/runtime/stages.py +27 -0
- package/latticeai/runtime/user_key_runtime.py +106 -0
- package/latticeai/server_app.py +5 -1
- package/latticeai/services/architecture_readiness.py +74 -5
- package/latticeai/services/brain_automation.py +3 -26
- package/latticeai/services/chat_service.py +205 -15
- package/latticeai/services/local_knowledge.py +423 -0
- package/latticeai/services/memory_service.py +268 -21
- package/latticeai/services/model_engines.py +48 -33
- package/latticeai/services/model_errors.py +17 -0
- package/latticeai/services/model_loading.py +28 -25
- package/latticeai/services/model_runtime.py +228 -162
- package/latticeai/services/p_reinforce.py +22 -3
- package/latticeai/services/platform_runtime.py +92 -24
- package/latticeai/services/product_readiness.py +19 -17
- package/latticeai/services/review_queue.py +76 -11
- package/latticeai/services/router_context.py +1 -0
- package/latticeai/services/run_executor.py +25 -9
- package/latticeai/services/search_service.py +203 -28
- package/latticeai/services/setup_detection.py +80 -0
- package/latticeai/services/tool_dispatch.py +28 -5
- package/latticeai/services/triggers.py +53 -4
- package/latticeai/services/upload_service.py +13 -2
- package/latticeai/setup/__init__.py +25 -0
- package/latticeai/setup/auto_setup.py +857 -0
- package/latticeai/setup/wizard.py +1264 -0
- package/latticeai/tools/__init__.py +278 -0
- package/{tools → latticeai/tools}/commands.py +67 -7
- package/{tools → latticeai/tools}/computer.py +1 -1
- package/{tools → latticeai/tools}/documents.py +1 -1
- package/{tools → latticeai/tools}/filesystem.py +3 -3
- package/latticeai/tools/knowledge.py +178 -0
- package/{tools → latticeai/tools}/local_files.py +7 -1
- package/{tools → latticeai/tools}/network.py +0 -1
- package/local_knowledge_api.py +4 -342
- package/package.json +22 -2
- package/scripts/brain_quality_eval.py +3 -1
- package/scripts/bump_version.py +3 -0
- package/scripts/capture_release_evidence.mjs +180 -0
- package/scripts/check_current_release_docs.mjs +142 -0
- package/scripts/check_i18n_literals.mjs +107 -31
- package/scripts/check_openapi_drift.mjs +56 -0
- package/scripts/export_openapi.py +67 -7
- package/scripts/i18n_literal_allowlist.json +22 -24
- package/scripts/run_integration_tests.mjs +72 -10
- package/scripts/validate_release_artifacts.py +49 -7
- package/scripts/wheel_smoke.py +5 -0
- package/setup_wizard.py +3 -1304
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +11 -11
- package/static/app/assets/Act-Bzz0bUyW.js +1 -0
- package/static/app/assets/Brain-Dj2J20YA.js +321 -0
- package/static/app/assets/Capture-CqlEl1Ga.js +1 -0
- package/static/app/assets/Library-B03FP1Yx.js +1 -0
- package/static/app/assets/System-80lHW0Ux.js +1 -0
- package/static/app/assets/index-BiMofBTM.js +17 -0
- package/static/app/assets/index-Bmx9rzTc.css +2 -0
- package/static/app/assets/primitives-Q1A96_7v.js +1 -0
- package/static/app/assets/textarea-D13RtnTo.js +1 -0
- package/static/app/index.html +2 -2
- package/static/css/tokens.css +4 -2
- package/static/sw.js +1 -1
- package/tools/__init__.py +21 -269
- package/docs/CODE_REVIEW_2026-07-06.md +0 -764
- package/latticeai/runtime/app_context_runtime.py +0 -13
- package/latticeai/runtime/sso_runtime.py +0 -52
- package/latticeai/runtime/tail_wiring.py +0 -21
- package/scripts/com.pts.claudecode.discord.plist +0 -31
- package/scripts/pts-claudecode-discord-bridge.mjs +0 -207
- package/scripts/start-pts-claudecode-discord.sh +0 -51
- package/static/app/assets/Act-fZokUnC0.js +0 -1
- package/static/app/assets/Brain-DtyuWubr.js +0 -321
- package/static/app/assets/Capture-D5KV3Cu7.js +0 -1
- package/static/app/assets/Library-C9kyFkSt.js +0 -1
- package/static/app/assets/System-VbChmX7r.js +0 -1
- package/static/app/assets/index-DCh5AoXt.css +0 -2
- package/static/app/assets/index-DPdcPoF0.js +0 -17
- package/static/app/assets/primitives-DFeanEV6.js +0 -1
- package/static/app/assets/textarea-CD8UNKIy.js +0 -1
- package/tools/knowledge.py +0 -95
|
@@ -5,11 +5,11 @@ import base64
|
|
|
5
5
|
import os
|
|
6
6
|
import socket
|
|
7
7
|
import tempfile
|
|
8
|
-
import time
|
|
9
8
|
import zipfile
|
|
10
9
|
import json
|
|
11
10
|
from pathlib import Path
|
|
12
11
|
|
|
12
|
+
from latticeai.core.io_utils import atomic_write_json
|
|
13
13
|
from latticeai.core.logging_safety import install_sensitive_log_filter, safe_log_text
|
|
14
14
|
from latticeai.cli.runtime import _load_env_file
|
|
15
15
|
|
|
@@ -26,7 +26,8 @@ def env_value(primary: str, default: str = "") -> str:
|
|
|
26
26
|
|
|
27
27
|
TOKEN = env_value("LATTICEAI_TELEGRAM_BOT_TOKEN")
|
|
28
28
|
API_URL = f"https://api.telegram.org/bot{TOKEN}"
|
|
29
|
-
|
|
29
|
+
SERVER_PORT = int(env_value("LATTICEAI_SERVER_PORT", "4825"))
|
|
30
|
+
BASE_URL = env_value("LATTICEAI_BASE_URL", env_value("LATTICEAI_SERVER_URL", f"http://127.0.0.1:{SERVER_PORT}")).rstrip("/")
|
|
30
31
|
CHAT_URL = f"{BASE_URL}/chat"
|
|
31
32
|
AGENT_URL = f"{BASE_URL}/agent"
|
|
32
33
|
MCP_TOOLS_URL = f"{BASE_URL}/mcp/tools"
|
|
@@ -42,8 +43,7 @@ AGENT_WORKSPACE = Path(env_value("LATTICEAI_AGENT_ROOT", "agent_workspace"
|
|
|
42
43
|
# Pending plan approvals: context_id → (chat_id, executing_model, reviewing_model)
|
|
43
44
|
_bot_pending_plans: dict[str, dict] = {}
|
|
44
45
|
MAX_TELEGRAM_FILE_BYTES = 45 * 1024 * 1024
|
|
45
|
-
|
|
46
|
-
INVITE_CODE = env_value("LATTICEAI_INVITE_CODE", "gemma-lattice-ai")
|
|
46
|
+
INVITE_CODE = env_value("LATTICEAI_INVITE_CODE")
|
|
47
47
|
PUBLIC_WEB_URL = env_value("LATTICEAI_PUBLIC_URL")
|
|
48
48
|
DATA_DIR = Path(env_value("LATTICEAI_DATA_DIR", str(Path.home() / ".ltcai")))
|
|
49
49
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
@@ -55,40 +55,56 @@ logger = logging.getLogger(__name__)
|
|
|
55
55
|
# ── Server session auth ───────────────────────────────────────────────────────
|
|
56
56
|
|
|
57
57
|
def _get_server_session() -> str:
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
admin_emails: set[str] = set()
|
|
66
|
-
if users_file.exists():
|
|
67
|
-
users = json.loads(users_file.read_text())
|
|
68
|
-
admin_emails = {e for e, u in users.items() if u.get("role") == "admin" and not u.get("disabled")}
|
|
69
|
-
now = time.time()
|
|
70
|
-
# Pick the newest non-expired admin session
|
|
71
|
-
best_token, best_ts = "", 0.0
|
|
72
|
-
for token, entry in sessions.items():
|
|
73
|
-
email, created_at = entry[0], float(entry[1])
|
|
74
|
-
if admin_emails and email not in admin_emails:
|
|
75
|
-
continue
|
|
76
|
-
if now - created_at > 7 * 86400:
|
|
77
|
-
continue
|
|
78
|
-
if created_at > best_ts:
|
|
79
|
-
best_token, best_ts = token, created_at
|
|
80
|
-
return best_token
|
|
81
|
-
except Exception:
|
|
82
|
-
return ""
|
|
58
|
+
"""Return only the explicitly provisioned bot-to-server token.
|
|
59
|
+
|
|
60
|
+
Web sessions are hashed at rest and belong to interactive users. Scanning
|
|
61
|
+
``sessions.json`` cannot recover a usable token and would couple the bot to
|
|
62
|
+
whichever administrator happened to be logged in most recently.
|
|
63
|
+
"""
|
|
64
|
+
return env_value("LATTICEAI_SERVER_SESSION_TOKEN").strip()
|
|
83
65
|
|
|
84
66
|
def _server_client(**kwargs) -> httpx.AsyncClient:
|
|
85
|
-
"""
|
|
67
|
+
"""Return a server client authenticated by an explicit bearer capability."""
|
|
86
68
|
token = _get_server_session()
|
|
87
|
-
|
|
88
|
-
|
|
69
|
+
if not token:
|
|
70
|
+
raise RuntimeError(
|
|
71
|
+
"LATTICEAI_SERVER_SESSION_TOKEN is required for Telegram server API calls."
|
|
72
|
+
)
|
|
73
|
+
headers = dict(kwargs.pop("headers", {}) or {})
|
|
74
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
75
|
+
return httpx.AsyncClient(headers=headers, **kwargs)
|
|
89
76
|
|
|
90
77
|
# ── Chat ID registry ─────────────────────────────────────────────────────────
|
|
91
78
|
|
|
79
|
+
def parse_allowed_chat_ids(raw: str) -> frozenset[int]:
|
|
80
|
+
"""Parse a comma-separated Telegram chat-id allowlist."""
|
|
81
|
+
allowed: set[int] = set()
|
|
82
|
+
for value in str(raw or "").split(","):
|
|
83
|
+
value = value.strip()
|
|
84
|
+
if not value:
|
|
85
|
+
continue
|
|
86
|
+
try:
|
|
87
|
+
allowed.add(int(value))
|
|
88
|
+
except ValueError:
|
|
89
|
+
logger.warning(
|
|
90
|
+
"Invalid Telegram chat id ignored in allowlist: %r",
|
|
91
|
+
safe_log_text(value),
|
|
92
|
+
)
|
|
93
|
+
return frozenset(allowed)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def allowed_chat_ids() -> frozenset[int]:
|
|
97
|
+
"""Return the configured allowlist; missing configuration denies all."""
|
|
98
|
+
return parse_allowed_chat_ids(env_value("LATTICEAI_TELEGRAM_ALLOWED_CHAT_IDS"))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def is_chat_allowed(chat_id) -> bool:
|
|
102
|
+
try:
|
|
103
|
+
return int(chat_id) in allowed_chat_ids()
|
|
104
|
+
except (TypeError, ValueError):
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
|
|
92
108
|
def load_chat_ids():
|
|
93
109
|
try:
|
|
94
110
|
if CHAT_IDS_FILE.exists():
|
|
@@ -100,19 +116,24 @@ def load_chat_ids():
|
|
|
100
116
|
|
|
101
117
|
def save_chat_ids(chat_ids):
|
|
102
118
|
try:
|
|
103
|
-
CHAT_IDS_FILE
|
|
104
|
-
json.dumps({"chat_ids": sorted(chat_ids)}, ensure_ascii=False, indent=2),
|
|
105
|
-
encoding="utf-8",
|
|
106
|
-
)
|
|
119
|
+
atomic_write_json(CHAT_IDS_FILE, {"chat_ids": sorted(chat_ids)})
|
|
107
120
|
except Exception as e:
|
|
108
121
|
logger.error("텔레그램 채팅 목록 저장 실패: %s", safe_log_text(e))
|
|
109
122
|
|
|
110
123
|
def register_chat_id(chat_id):
|
|
124
|
+
if not is_chat_allowed(chat_id):
|
|
125
|
+
logger.warning(
|
|
126
|
+
"허용되지 않은 텔레그램 채팅 등록 차단: %s",
|
|
127
|
+
safe_log_text(chat_id),
|
|
128
|
+
)
|
|
129
|
+
return False
|
|
130
|
+
chat_id = int(chat_id)
|
|
111
131
|
chat_ids = load_chat_ids()
|
|
112
132
|
if chat_id not in chat_ids:
|
|
113
133
|
chat_ids.add(chat_id)
|
|
114
134
|
save_chat_ids(chat_ids)
|
|
115
135
|
logger.info("텔레그램 웹 미러링 대상 등록: %s", chat_id)
|
|
136
|
+
return True
|
|
116
137
|
|
|
117
138
|
# ── Telegram API helpers ──────────────────────────────────────────────────────
|
|
118
139
|
|
|
@@ -200,7 +221,8 @@ def get_lan_ip():
|
|
|
200
221
|
def get_web_url():
|
|
201
222
|
if PUBLIC_WEB_URL:
|
|
202
223
|
return PUBLIC_WEB_URL.rstrip("/")
|
|
203
|
-
|
|
224
|
+
base = f"http://{get_lan_ip()}:{SERVER_PORT}/"
|
|
225
|
+
return f"{base}?code={INVITE_CODE}" if INVITE_CODE else base
|
|
204
226
|
|
|
205
227
|
def get_graph_url():
|
|
206
228
|
if PUBLIC_WEB_URL:
|
|
@@ -212,7 +234,10 @@ def get_graph_url():
|
|
|
212
234
|
async def broadcast_web_chat(role, text):
|
|
213
235
|
if not TOKEN:
|
|
214
236
|
return
|
|
215
|
-
|
|
237
|
+
# Old releases registered every sender. Intersect persisted recipients
|
|
238
|
+
# with the current allowlist so stale files cannot keep receiving mirrors.
|
|
239
|
+
allowed = allowed_chat_ids()
|
|
240
|
+
chat_ids = load_chat_ids() & set(allowed)
|
|
216
241
|
if not chat_ids:
|
|
217
242
|
return
|
|
218
243
|
label = "사용자" if role == "user" else "Lattice AI"
|
|
@@ -725,6 +750,14 @@ async def handle_plan_callback(client, chat_id, data: str) -> None:
|
|
|
725
750
|
if len(parts) != 3:
|
|
726
751
|
return
|
|
727
752
|
_, action, context_id = parts
|
|
753
|
+
pending = _bot_pending_plans.get(context_id)
|
|
754
|
+
|
|
755
|
+
# A callback is bound to the chat that received the plan. Even two allowed
|
|
756
|
+
# chats cannot approve or cancel each other's plan by replaying callback
|
|
757
|
+
# data.
|
|
758
|
+
if pending and int(pending.get("chat_id")) != int(chat_id):
|
|
759
|
+
await send_message(client, chat_id, "❌ 다른 채팅의 작업은 처리할 수 없습니다.")
|
|
760
|
+
return
|
|
728
761
|
pending = _bot_pending_plans.pop(context_id, None)
|
|
729
762
|
|
|
730
763
|
if action == "cancel" or not pending:
|
|
@@ -865,10 +898,17 @@ async def handle_command(client, chat_id, command: str, args: str):
|
|
|
865
898
|
# ── Callback query handler ────────────────────────────────────────────────────
|
|
866
899
|
|
|
867
900
|
async def handle_callback_query(client, callback_query):
|
|
868
|
-
cq_id
|
|
869
|
-
chat_id =
|
|
901
|
+
cq_id = callback_query.get("id", "")
|
|
902
|
+
chat_id = (
|
|
903
|
+
(callback_query.get("message") or {}).get("chat") or {}
|
|
904
|
+
).get("id")
|
|
870
905
|
data = callback_query.get("data", "")
|
|
871
906
|
|
|
907
|
+
if not is_chat_allowed(chat_id):
|
|
908
|
+
logger.warning("허용되지 않은 텔레그램 callback 차단: %s", safe_log_text(chat_id))
|
|
909
|
+
await answer_callback(client, cq_id, "허용되지 않은 채팅입니다.")
|
|
910
|
+
return
|
|
911
|
+
|
|
872
912
|
await answer_callback(client, cq_id)
|
|
873
913
|
|
|
874
914
|
if data == "cmd:status":
|
|
@@ -902,6 +942,16 @@ async def run_bot():
|
|
|
902
942
|
if not TOKEN:
|
|
903
943
|
logger.warning("LATTICEAI_TELEGRAM_BOT_TOKEN이 설정되지 않아 텔레그램 봇을 시작하지 않습니다.")
|
|
904
944
|
return
|
|
945
|
+
if not allowed_chat_ids():
|
|
946
|
+
logger.error(
|
|
947
|
+
"LATTICEAI_TELEGRAM_ALLOWED_CHAT_IDS가 없어 텔레그램 봇을 시작하지 않습니다."
|
|
948
|
+
)
|
|
949
|
+
return
|
|
950
|
+
if not _get_server_session():
|
|
951
|
+
logger.error(
|
|
952
|
+
"LATTICEAI_SERVER_SESSION_TOKEN이 없어 텔레그램 봇을 시작하지 않습니다."
|
|
953
|
+
)
|
|
954
|
+
return
|
|
905
955
|
|
|
906
956
|
logger.info("🚀 비동기 텔레그램 봇 모드 시작!")
|
|
907
957
|
last_update_id = None
|
|
@@ -937,6 +987,12 @@ async def run_bot():
|
|
|
937
987
|
|
|
938
988
|
msg = update["message"]
|
|
939
989
|
chat_id = msg["chat"]["id"]
|
|
990
|
+
if not is_chat_allowed(chat_id):
|
|
991
|
+
logger.warning(
|
|
992
|
+
"허용되지 않은 텔레그램 메시지 차단: %s",
|
|
993
|
+
safe_log_text(chat_id),
|
|
994
|
+
)
|
|
995
|
+
continue
|
|
940
996
|
register_chat_id(chat_id)
|
|
941
997
|
text = msg.get("text", "")
|
|
942
998
|
caption = msg.get("caption", "")
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Cloud LLM provider catalog — static data split out of the router.
|
|
2
|
+
|
|
3
|
+
Pure config dicts (OpenAI-compatible providers, per-provider model
|
|
4
|
+
catalog, model→source family map). router.py re-exports these, so
|
|
5
|
+
``from latticeai.models.router import OPENAI_COMPATIBLE_PROVIDERS`` and the
|
|
6
|
+
model_runtime re-export chain are unaffected.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
OPENAI_COMPATIBLE_PROVIDERS = {
|
|
12
|
+
"openai": {
|
|
13
|
+
"env_key": "OPENAI_API_KEY",
|
|
14
|
+
"base_url_env": "OPENAI_BASE_URL",
|
|
15
|
+
"default_model": "gpt-4o-mini",
|
|
16
|
+
},
|
|
17
|
+
"openrouter": {
|
|
18
|
+
"env_key": "OPENROUTER_API_KEY",
|
|
19
|
+
"base_url": "https://openrouter.ai/api/v1",
|
|
20
|
+
"default_model": "openai/gpt-4o-mini",
|
|
21
|
+
},
|
|
22
|
+
"groq": {
|
|
23
|
+
"env_key": "GROQ_API_KEY",
|
|
24
|
+
"base_url": "https://api.groq.com/openai/v1",
|
|
25
|
+
"default_model": "meta-llama/llama-4-scout-17b-16e-instruct",
|
|
26
|
+
},
|
|
27
|
+
"together": {
|
|
28
|
+
"env_key": "TOGETHER_API_KEY",
|
|
29
|
+
"base_url": "https://api.together.xyz/v1",
|
|
30
|
+
"default_model": "Qwen/Qwen3-VL-32B-Instruct",
|
|
31
|
+
},
|
|
32
|
+
"xai": {
|
|
33
|
+
"env_key": "XAI_API_KEY",
|
|
34
|
+
"base_url": "https://api.x.ai/v1",
|
|
35
|
+
"default_model": "grok-beta",
|
|
36
|
+
},
|
|
37
|
+
"ollama": {
|
|
38
|
+
"env_key": "OLLAMA_API_KEY",
|
|
39
|
+
"base_url_env": "OLLAMA_BASE_URL",
|
|
40
|
+
"base_url": "http://localhost:11434/v1",
|
|
41
|
+
"default_model": "hf.co/ggml-org/gemma-4-12B-it-GGUF:Q4_K_M",
|
|
42
|
+
"api_key_fallback": "ollama",
|
|
43
|
+
},
|
|
44
|
+
"vllm": {
|
|
45
|
+
"env_key": "VLLM_API_KEY",
|
|
46
|
+
"base_url_env": "VLLM_BASE_URL",
|
|
47
|
+
"base_url": "http://localhost:8000/v1",
|
|
48
|
+
"default_model": "Qwen/Qwen3-VL-8B-Instruct",
|
|
49
|
+
"api_key_fallback": "vllm",
|
|
50
|
+
},
|
|
51
|
+
"lmstudio": {
|
|
52
|
+
"env_key": "LMSTUDIO_API_KEY",
|
|
53
|
+
"base_url_env": "LMSTUDIO_BASE_URL",
|
|
54
|
+
"base_url": "http://localhost:1234/v1",
|
|
55
|
+
"default_model": "local-model",
|
|
56
|
+
"api_key_fallback": "lmstudio",
|
|
57
|
+
},
|
|
58
|
+
"llamacpp": {
|
|
59
|
+
"env_key": "LLAMACPP_API_KEY",
|
|
60
|
+
"base_url_env": "LLAMACPP_BASE_URL",
|
|
61
|
+
"base_url": "http://localhost:8080/v1",
|
|
62
|
+
"default_model": "llama.cpp-model",
|
|
63
|
+
"api_key_fallback": "llamacpp",
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
PROVIDER_MODEL_CATALOG = {
|
|
68
|
+
"openai": [
|
|
69
|
+
{"id": "gpt-5.5", "name": "GPT-5.5", "family": "GPT"},
|
|
70
|
+
{"id": "gpt-5.4", "name": "GPT-5.4", "family": "GPT"},
|
|
71
|
+
{"id": "gpt-5.4-mini", "name": "GPT-5.4 Mini", "family": "GPT"},
|
|
72
|
+
{"id": "gpt-5.4-nano", "name": "GPT-5.4 Nano", "family": "GPT"},
|
|
73
|
+
{"id": "gpt-4o-mini", "name": "GPT-4o Mini", "family": "GPT"},
|
|
74
|
+
{"id": "gpt-4o", "name": "GPT-4o", "family": "GPT"},
|
|
75
|
+
{"id": "gpt-4.1-mini", "name": "GPT-4.1 Mini", "family": "GPT"},
|
|
76
|
+
{"id": "gpt-4.1", "name": "GPT-4.1", "family": "GPT"},
|
|
77
|
+
],
|
|
78
|
+
"openrouter": [
|
|
79
|
+
{"id": "openai/gpt-5.5", "name": "GPT-5.5 via OpenRouter", "family": "GPT"},
|
|
80
|
+
{"id": "openai/gpt-4o-mini", "name": "GPT-4o Mini via OpenRouter", "family": "GPT"},
|
|
81
|
+
{"id": "anthropic/claude-opus-4.7", "name": "Claude Opus 4.7 via OpenRouter", "family": "Claude"},
|
|
82
|
+
{"id": "anthropic/claude-sonnet-4.6", "name": "Claude Sonnet 4.6 via OpenRouter", "family": "Claude"},
|
|
83
|
+
{"id": "anthropic/claude-haiku-4.5", "name": "Claude Haiku 4.5 via OpenRouter", "family": "Claude"},
|
|
84
|
+
{"id": "qwen/qwen3-vl-235b-a22b-instruct", "name": "Qwen3-VL 235B A22B via OpenRouter", "family": "Qwen"},
|
|
85
|
+
{"id": "google/gemma-4-12b-it", "name": "Gemma 4 12B via OpenRouter", "family": "Gemma"},
|
|
86
|
+
{"id": "x-ai/grok-2", "name": "Grok 2 via OpenRouter", "family": "Grok"},
|
|
87
|
+
{"id": "meta-llama/llama-4-scout-17b-16e-instruct", "name": "Llama 4 Scout via OpenRouter", "family": "Llama"},
|
|
88
|
+
{"id": "google/gemini-2.5-flash", "name": "Gemini 2.5 Flash via OpenRouter", "family": "Gemini"},
|
|
89
|
+
],
|
|
90
|
+
"groq": [
|
|
91
|
+
{"id": "meta-llama/llama-4-scout-17b-16e-instruct", "name": "Llama 4 Scout", "family": "Llama"},
|
|
92
|
+
],
|
|
93
|
+
"together": [
|
|
94
|
+
{"id": "Qwen/Qwen3-VL-32B-Instruct", "name": "Qwen3-VL 32B", "family": "Qwen"},
|
|
95
|
+
{"id": "google/gemma-4-12b-it", "name": "Gemma 4 12B", "family": "Gemma"},
|
|
96
|
+
{"id": "meta-llama/Llama-4-Scout-17B-16E-Instruct", "name": "Llama 4 Scout", "family": "Llama"},
|
|
97
|
+
],
|
|
98
|
+
"xai": [
|
|
99
|
+
{"id": "grok-beta", "name": "Grok Beta", "family": "Grok"},
|
|
100
|
+
{"id": "grok-vision-beta", "name": "Grok Vision Beta", "family": "Grok"},
|
|
101
|
+
],
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
MODEL_SOURCE_BY_FAMILY = {
|
|
105
|
+
"GPT": ("미국", "OpenAI"),
|
|
106
|
+
"Claude": ("미국", "Anthropic"),
|
|
107
|
+
"Qwen": ("중국", "Alibaba"),
|
|
108
|
+
"Llama": ("미국", "Meta"),
|
|
109
|
+
"Gemini": ("미국", "Google"),
|
|
110
|
+
"Grok": ("미국", "xAI"),
|
|
111
|
+
}
|