@agentunion/kite 1.3.1 → 1.4.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/CHANGELOG.md +287 -1
- package/cli.js +76 -0
- package/extensions/agents/assistant/entry.py +111 -1
- package/extensions/agents/assistant/server.py +263 -197
- package/extensions/channels/acp_channel/entry.py +111 -1
- package/extensions/channels/acp_channel/module.md +23 -22
- package/extensions/channels/acp_channel/server.py +263 -197
- package/extensions/event_hub_bench/entry.py +107 -1
- package/extensions/services/backup/entry.py +408 -72
- package/extensions/services/backup/module.md +24 -22
- package/extensions/services/model_service/entry.py +255 -71
- package/extensions/services/model_service/module.md +21 -22
- package/extensions/services/watchdog/entry.py +344 -90
- package/extensions/services/watchdog/monitor.py +237 -21
- package/extensions/services/web/WEBSOCKET_STATUS.md +143 -0
- package/extensions/services/web/config_example.py +35 -0
- package/extensions/services/web/config_loader.py +110 -0
- package/extensions/services/web/entry.py +114 -26
- package/extensions/services/web/module.md +35 -24
- package/extensions/services/web/pairing.py +250 -0
- package/extensions/services/web/pairing_codes.jsonl +16 -0
- package/extensions/services/web/relay.py +643 -0
- package/extensions/services/web/relay_config.json5 +67 -0
- package/extensions/services/web/routes/routes_management_ws.py +127 -0
- package/extensions/services/web/routes/routes_rpc.py +89 -0
- package/extensions/services/web/routes/routes_test.py +61 -0
- package/extensions/services/web/server.py +445 -99
- package/extensions/services/web/static/css/style.css +138 -2
- package/extensions/services/web/static/index.html +295 -2
- package/extensions/services/web/static/js/app.js +1579 -5
- package/extensions/services/web/static/js/kernel-client-example.js +161 -0
- package/extensions/services/web/static/js/kernel-client.js +383 -0
- package/extensions/services/web/static/js/registry-tests.js +558 -0
- package/extensions/services/web/static/js/token-manager.js +175 -0
- package/extensions/services/web/static/pairing.html +248 -0
- package/extensions/services/web/static/test_registry.html +262 -0
- package/extensions/services/web/web_config.json5 +29 -0
- package/kernel/entry.py +120 -32
- package/kernel/event_hub.py +159 -16
- package/kernel/module.md +36 -33
- package/kernel/registry_store.py +70 -20
- package/kernel/rpc_router.py +134 -57
- package/kernel/server.py +292 -15
- package/kite_cli/__init__.py +3 -0
- package/kite_cli/__main__.py +5 -0
- package/kite_cli/commands/__init__.py +1 -0
- package/kite_cli/commands/clean.py +101 -0
- package/kite_cli/commands/doctor.py +35 -0
- package/kite_cli/commands/history.py +111 -0
- package/kite_cli/commands/info.py +96 -0
- package/kite_cli/commands/install.py +313 -0
- package/kite_cli/commands/list.py +143 -0
- package/kite_cli/commands/log.py +81 -0
- package/kite_cli/commands/rollback.py +88 -0
- package/kite_cli/commands/search.py +73 -0
- package/kite_cli/commands/uninstall.py +85 -0
- package/kite_cli/commands/update.py +118 -0
- package/kite_cli/core/__init__.py +1 -0
- package/kite_cli/core/checker.py +142 -0
- package/kite_cli/core/dependency.py +229 -0
- package/kite_cli/core/downloader.py +209 -0
- package/kite_cli/core/install_info.py +40 -0
- package/kite_cli/core/tool_installer.py +397 -0
- package/kite_cli/core/validator.py +78 -0
- package/kite_cli/main.py +289 -0
- package/kite_cli/utils/__init__.py +1 -0
- package/kite_cli/utils/i18n.py +252 -0
- package/kite_cli/utils/interactive.py +63 -0
- package/kite_cli/utils/operation_log.py +77 -0
- package/kite_cli/utils/paths.py +34 -0
- package/kite_cli/utils/version.py +308 -0
- package/launcher/count_lines.py +34 -0
- package/launcher/entry.py +905 -166
- package/launcher/logging_setup.py +104 -0
- package/launcher/module.md +37 -37
- package/launcher/process_manager.py +12 -1
- package/package.json +2 -1
- package/scripts/plan_manager.py +315 -0
|
@@ -23,7 +23,113 @@ import websockets
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
# ── Module configuration ──
|
|
26
|
-
|
|
26
|
+
|
|
27
|
+
def _load_module_config() -> dict:
|
|
28
|
+
"""Load module configuration from module.md frontmatter.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dict with keys: name, preferred_port, advertise_ip
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
SystemExit: If module.md is invalid or name is non-compliant
|
|
35
|
+
"""
|
|
36
|
+
_this_dir = os.path.dirname(os.path.abspath(__file__))
|
|
37
|
+
module_md = os.path.join(_this_dir, "module.md")
|
|
38
|
+
|
|
39
|
+
# Calculate relative path for error messages
|
|
40
|
+
project_root = os.environ.get("KITE_PROJECT", "")
|
|
41
|
+
if project_root and _this_dir.startswith(project_root):
|
|
42
|
+
rel_path = os.path.relpath(_this_dir, project_root)
|
|
43
|
+
else:
|
|
44
|
+
rel_path = _this_dir
|
|
45
|
+
|
|
46
|
+
# Default values (will be overridden if valid config exists)
|
|
47
|
+
result = {
|
|
48
|
+
"name": "",
|
|
49
|
+
"preferred_port": 0,
|
|
50
|
+
"advertise_ip": "0.0.0.0"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Check if module.md exists
|
|
54
|
+
if not os.path.exists(module_md):
|
|
55
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
56
|
+
print(f" Path: {rel_path}/module.md")
|
|
57
|
+
print(f" Reason: File not found")
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
with open(module_md, encoding="utf-8") as f:
|
|
62
|
+
text = f.read()
|
|
63
|
+
|
|
64
|
+
# Extract YAML frontmatter (between --- markers)
|
|
65
|
+
import re
|
|
66
|
+
m = re.match(r'^---\s*\n(.*?)\n---', text, re.DOTALL)
|
|
67
|
+
if not m:
|
|
68
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
69
|
+
print(f" Path: {rel_path}/module.md")
|
|
70
|
+
print(f" Reason: Missing YAML frontmatter")
|
|
71
|
+
sys.exit(1)
|
|
72
|
+
|
|
73
|
+
# Parse YAML frontmatter
|
|
74
|
+
try:
|
|
75
|
+
import yaml
|
|
76
|
+
fm = yaml.safe_load(m.group(1)) or {}
|
|
77
|
+
except ImportError:
|
|
78
|
+
print(f"[{rel_path}] ERROR: PyYAML not installed, cannot parse module.md")
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
82
|
+
print(f" Path: {rel_path}/module.md")
|
|
83
|
+
print(f" Reason: YAML parse error: {e}")
|
|
84
|
+
sys.exit(1)
|
|
85
|
+
|
|
86
|
+
# Validate 'name' field (required)
|
|
87
|
+
if "name" not in fm:
|
|
88
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
89
|
+
print(f" Path: {rel_path}/module.md")
|
|
90
|
+
print(f" Reason: Missing 'name' field")
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
|
|
93
|
+
raw_name = str(fm["name"]).strip()
|
|
94
|
+
|
|
95
|
+
if not raw_name:
|
|
96
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
97
|
+
print(f" Path: {rel_path}/module.md")
|
|
98
|
+
print(f" Reason: Empty module name")
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
|
|
101
|
+
# Validate name characters
|
|
102
|
+
sanitized = re.sub(r'[^a-zA-Z0-9_\-]', '', raw_name)
|
|
103
|
+
|
|
104
|
+
if sanitized != raw_name:
|
|
105
|
+
invalid_chars = ''.join(sorted(set(c for c in raw_name if c not in sanitized)))
|
|
106
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
107
|
+
print(f" Path: {rel_path}/module.md")
|
|
108
|
+
print(f" Reason: Invalid characters in name '{raw_name}': {repr(invalid_chars)}")
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
|
|
111
|
+
result["name"] = sanitized
|
|
112
|
+
|
|
113
|
+
# Extract optional fields
|
|
114
|
+
if "preferred_port" in fm:
|
|
115
|
+
try:
|
|
116
|
+
result["preferred_port"] = int(fm["preferred_port"])
|
|
117
|
+
except (ValueError, TypeError):
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
if "advertise_ip" in fm:
|
|
121
|
+
result["advertise_ip"] = str(fm["advertise_ip"])
|
|
122
|
+
|
|
123
|
+
except SystemExit:
|
|
124
|
+
raise # Re-raise exit to prevent catching by outer except
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print(f"[{rel_path}] ERROR: Failed to read module.md: {e}")
|
|
127
|
+
sys.exit(1)
|
|
128
|
+
|
|
129
|
+
return result
|
|
130
|
+
|
|
131
|
+
_module_config = _load_module_config()
|
|
132
|
+
MODULE_NAME = _module_config["name"]
|
|
27
133
|
|
|
28
134
|
|
|
29
135
|
class _SafeWriter:
|
|
@@ -263,10 +369,20 @@ def _read_stdin_kite_message(expected_type: str, timeout: float = 10) -> dict |
|
|
|
263
369
|
|
|
264
370
|
# Global WS reference for publish_event callback
|
|
265
371
|
_ws_global = None
|
|
372
|
+
_shutting_down = False
|
|
373
|
+
_exit_code = 0 # Exit code for main() to use
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _is_auth_failure(e: Exception) -> bool:
|
|
377
|
+
"""Check if a WebSocket exception indicates authentication failure."""
|
|
378
|
+
if hasattr(e, 'rcvd') and e.rcvd is not None:
|
|
379
|
+
code = e.rcvd.code if hasattr(e.rcvd, 'code') else 0
|
|
380
|
+
return code in (4001, 4003)
|
|
381
|
+
return False
|
|
266
382
|
|
|
267
383
|
|
|
268
384
|
async def main():
|
|
269
|
-
global _ws_global
|
|
385
|
+
global _ws_global, _shutting_down
|
|
270
386
|
# Initialize log file paths
|
|
271
387
|
global _log_dir, _log_latest_path, _crash_log_path
|
|
272
388
|
module_data = os.environ.get("KITE_MODULE_DATA")
|
|
@@ -318,41 +434,90 @@ async def main():
|
|
|
318
434
|
|
|
319
435
|
print(f"[model_service] Token received ({len(token)} chars), kernel port: {kernel_port} ({_fmt_elapsed(_t0)})")
|
|
320
436
|
|
|
321
|
-
#
|
|
437
|
+
# Start reconnect loop
|
|
438
|
+
await _ws_loop(token, kernel_port, _t0)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
async def _ws_loop(token: str, kernel_port: int, _t0: float):
|
|
442
|
+
"""Connect to Kernel with exponential backoff reconnection."""
|
|
443
|
+
global _shutting_down, _exit_code
|
|
444
|
+
retry_delay = 0.3
|
|
445
|
+
max_delay = 5.0
|
|
446
|
+
max_retries = 10
|
|
447
|
+
attempt = 0
|
|
448
|
+
while not _shutting_down:
|
|
449
|
+
try:
|
|
450
|
+
await _ws_connect(token, kernel_port, _t0)
|
|
451
|
+
retry_delay = 0.3
|
|
452
|
+
attempt = 0
|
|
453
|
+
except asyncio.CancelledError:
|
|
454
|
+
return
|
|
455
|
+
except Exception as e:
|
|
456
|
+
attempt += 1
|
|
457
|
+
if _is_auth_failure(e):
|
|
458
|
+
print(f"[model_service] Kernel 认证失败,退出")
|
|
459
|
+
_exit_code = 1
|
|
460
|
+
_shutting_down = True
|
|
461
|
+
return
|
|
462
|
+
if attempt >= max_retries:
|
|
463
|
+
print(f"[model_service] 重连失败 {max_retries} 次,退出")
|
|
464
|
+
_exit_code = 1
|
|
465
|
+
_shutting_down = True
|
|
466
|
+
return
|
|
467
|
+
_write_crash(type(e), e, e.__traceback__, severity="error", handled=True)
|
|
468
|
+
print(f"[model_service] 连接错误: {e}, {retry_delay:.1f}s 后重试 ({attempt}/{max_retries})")
|
|
469
|
+
_ws_global_clear()
|
|
470
|
+
if _shutting_down:
|
|
471
|
+
return
|
|
472
|
+
await asyncio.sleep(retry_delay)
|
|
473
|
+
retry_delay = min(retry_delay * 2, max_delay)
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def _ws_global_clear():
|
|
477
|
+
global _ws_global
|
|
478
|
+
_ws_global = None
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
async def _ws_connect(token: str, kernel_port: int, _t0: float):
|
|
482
|
+
"""Single WebSocket session: connect → subscribe → register → ready → receive loop."""
|
|
483
|
+
global _ws_global
|
|
484
|
+
|
|
322
485
|
ws_url = f"ws://127.0.0.1:{kernel_port}/ws?token={token}&id=model_service"
|
|
323
486
|
print(f"[model_service] Connecting to Kernel: {ws_url}")
|
|
324
487
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
"
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
"
|
|
345
|
-
"
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
488
|
+
async with websockets.connect(ws_url, open_timeout=5, ping_interval=20, ping_timeout=20, close_timeout=10) as ws:
|
|
489
|
+
_ws_global = ws
|
|
490
|
+
print(f"[model_service] Connected to Kernel ({_fmt_elapsed(_t0)})")
|
|
491
|
+
|
|
492
|
+
# Subscribe to events
|
|
493
|
+
await _rpc_call(ws, "event.subscribe", {
|
|
494
|
+
"events": [
|
|
495
|
+
"module.started",
|
|
496
|
+
"module.stopped",
|
|
497
|
+
"module.shutdown",
|
|
498
|
+
],
|
|
499
|
+
})
|
|
500
|
+
print(f"[model_service] Subscribed to events ({_fmt_elapsed(_t0)})")
|
|
501
|
+
|
|
502
|
+
# Register to Kernel Registry via RPC
|
|
503
|
+
await _rpc_call(ws, "registry.register", {
|
|
504
|
+
"module_id": "model_service",
|
|
505
|
+
"module_type": "service",
|
|
506
|
+
"events_publish": {
|
|
507
|
+
"model_service": {
|
|
508
|
+
"test": {"description": "Test event from model_service module"},
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
"events_subscribe": [
|
|
512
|
+
"module.started",
|
|
513
|
+
"module.stopped",
|
|
514
|
+
"module.shutdown",
|
|
515
|
+
],
|
|
516
|
+
})
|
|
517
|
+
print(f"[model_service] Registered to Kernel ({_fmt_elapsed(_t0)})")
|
|
354
518
|
|
|
355
|
-
|
|
519
|
+
# Publish module.ready (every reconnect)
|
|
520
|
+
if not _shutting_down:
|
|
356
521
|
await _rpc_call(ws, "event.publish", {
|
|
357
522
|
"event_id": str(uuid.uuid4()),
|
|
358
523
|
"event": "module.ready",
|
|
@@ -363,34 +528,34 @@ async def main():
|
|
|
363
528
|
})
|
|
364
529
|
print(f"[model_service] module.ready published ({_fmt_elapsed(_t0)})")
|
|
365
530
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
# Message loop: handle incoming RPC + events
|
|
370
|
-
async for raw in ws:
|
|
371
|
-
try:
|
|
372
|
-
msg = json.loads(raw)
|
|
373
|
-
except (json.JSONDecodeError, TypeError):
|
|
374
|
-
continue
|
|
375
|
-
|
|
376
|
-
try:
|
|
377
|
-
has_method = "method" in msg
|
|
378
|
-
has_id = "id" in msg
|
|
379
|
-
|
|
380
|
-
if has_method and not has_id:
|
|
381
|
-
# Event Notification
|
|
382
|
-
await _handle_event_notification(msg)
|
|
383
|
-
elif has_method and has_id:
|
|
384
|
-
# Incoming RPC request
|
|
385
|
-
await _handle_rpc_request(ws, msg)
|
|
386
|
-
# Ignore RPC responses (we don't await them in this simple impl)
|
|
387
|
-
except Exception as e:
|
|
388
|
-
print(f"[model_service] 消息处理异常(已忽略): {e}")
|
|
531
|
+
# Start test event loop in background
|
|
532
|
+
test_task = asyncio.create_task(_test_event_loop(ws))
|
|
389
533
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
534
|
+
# Message loop: handle incoming RPC + events
|
|
535
|
+
# CRITICAL: RPC 死锁防范
|
|
536
|
+
# - 入站 RPC 请求必须用 create_task() 异步执行,不可 await
|
|
537
|
+
# - 原因:如果 handler 内部调用 rpc_call() 发出站请求,出站响应需要本接收循环来分发
|
|
538
|
+
# - 如果接收循环被 await handler 阻塞,出站响应永远收不到 → 超时死锁
|
|
539
|
+
# - 事件通知和 RPC 响应可以同步处理(它们不会反向调用 rpc_call)
|
|
540
|
+
async for raw in ws:
|
|
541
|
+
try:
|
|
542
|
+
msg = json.loads(raw)
|
|
543
|
+
except (json.JSONDecodeError, TypeError):
|
|
544
|
+
continue
|
|
545
|
+
|
|
546
|
+
try:
|
|
547
|
+
has_method = "method" in msg
|
|
548
|
+
has_id = "id" in msg
|
|
549
|
+
|
|
550
|
+
if has_method and not has_id:
|
|
551
|
+
# Event Notification
|
|
552
|
+
await _handle_event_notification(msg)
|
|
553
|
+
elif has_method and has_id:
|
|
554
|
+
# Incoming RPC request — run in background to prevent deadlock
|
|
555
|
+
asyncio.create_task(_handle_rpc_request(ws, msg))
|
|
556
|
+
# Ignore RPC responses (we don't await them in this simple impl)
|
|
557
|
+
except Exception as e:
|
|
558
|
+
print(f"[model_service] 消息处理异常(已忽略): {e}")
|
|
394
559
|
|
|
395
560
|
|
|
396
561
|
async def _rpc_call(ws, method: str, params: dict = None):
|
|
@@ -416,10 +581,14 @@ async def _handle_event_notification(msg: dict):
|
|
|
416
581
|
event_type = params.get("event", "")
|
|
417
582
|
data = params.get("data", {})
|
|
418
583
|
|
|
419
|
-
# Special handling for module.shutdown
|
|
420
|
-
if event_type == "module.shutdown"
|
|
421
|
-
|
|
422
|
-
|
|
584
|
+
# Special handling for module.shutdown
|
|
585
|
+
if event_type == "module.shutdown":
|
|
586
|
+
target = data.get("module_id", "")
|
|
587
|
+
reason = data.get("reason", "")
|
|
588
|
+
# Handle both targeted shutdown (module_id == "model_service") and broadcast shutdown (no module_id or launcher_lost)
|
|
589
|
+
if target == "model_service" or not target or reason == "launcher_lost":
|
|
590
|
+
await _handle_shutdown()
|
|
591
|
+
return
|
|
423
592
|
|
|
424
593
|
# Log other events
|
|
425
594
|
print(f"[model_service] Event received: {event_type}")
|
|
@@ -472,22 +641,37 @@ async def _rpc_status() -> dict:
|
|
|
472
641
|
|
|
473
642
|
|
|
474
643
|
async def _handle_shutdown():
|
|
475
|
-
"""Handle module.shutdown event — ack
|
|
644
|
+
"""Handle module.shutdown event — ack → exiting → cleanup → ready → exit."""
|
|
645
|
+
global _shutting_down
|
|
476
646
|
print("[model_service] Received shutdown request")
|
|
477
|
-
|
|
647
|
+
_shutting_down = True
|
|
648
|
+
# Step 1: Send ack (立即确认收到)
|
|
478
649
|
await _publish_event(_ws_global, {
|
|
479
650
|
"event": "module.shutdown.ack",
|
|
480
|
-
"data": {"module_id": "model_service"
|
|
651
|
+
"data": {"module_id": "model_service"},
|
|
652
|
+
})
|
|
653
|
+
# Step 2: Send module.exiting (开始清理)
|
|
654
|
+
await _publish_event(_ws_global, {
|
|
655
|
+
"event": "module.exiting",
|
|
656
|
+
"data": {
|
|
657
|
+
"module_id": "model_service",
|
|
658
|
+
"type": "passive",
|
|
659
|
+
"reason": "shutdown_requested",
|
|
660
|
+
"restart": "auto",
|
|
661
|
+
"action": "none",
|
|
662
|
+
"timeout": 2.0,
|
|
663
|
+
"restart_delay": 0.0,
|
|
664
|
+
},
|
|
481
665
|
})
|
|
482
|
-
# Step
|
|
483
|
-
# Step
|
|
666
|
+
# Step 3: Cleanup (nothing to clean up for model_service)
|
|
667
|
+
# Step 4: Send ready (清理完成)
|
|
484
668
|
await _publish_event(_ws_global, {
|
|
485
669
|
"event": "module.shutdown.ready",
|
|
486
670
|
"data": {"module_id": "model_service"},
|
|
487
671
|
})
|
|
488
672
|
print("[model_service] Shutdown ready, exiting")
|
|
489
|
-
# Step
|
|
490
|
-
sys.exit(
|
|
673
|
+
# Step 5: Exit
|
|
674
|
+
sys.exit(_exit_code)
|
|
491
675
|
|
|
492
676
|
|
|
493
677
|
async def _test_event_loop(ws):
|
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: model_service
|
|
3
|
-
display_name: Model Service
|
|
4
|
-
version:
|
|
5
|
-
type: service
|
|
6
|
-
state:
|
|
7
|
-
runtime: python
|
|
8
|
-
entry: entry.py
|
|
9
|
-
events:
|
|
10
|
-
|
|
11
|
-
subscriptions:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
- 事件通知 — 通过 Event Hub 发布模型服务状态事件
|
|
1
|
+
---
|
|
2
|
+
name: model_service
|
|
3
|
+
display_name: Model Service
|
|
4
|
+
version: '1.0'
|
|
5
|
+
type: service
|
|
6
|
+
state: manual
|
|
7
|
+
runtime: python
|
|
8
|
+
entry: entry.py
|
|
9
|
+
events:
|
|
10
|
+
- model_service.test
|
|
11
|
+
subscriptions:
|
|
12
|
+
- module.started
|
|
13
|
+
- module.stopped
|
|
14
|
+
- module.shutdown
|
|
15
|
+
---
|
|
16
|
+
# Model Service(大模型服务)
|
|
17
|
+
|
|
18
|
+
大模型服务模块,提供统一的 LLM 调用接口。
|
|
19
|
+
|
|
20
|
+
- 模型调用 — 封装多种大模型 API 的统一调用接口
|
|
21
|
+
- 事件通知 — 通过 Event Hub 发布模型服务状态事件
|