edgeone 1.6.4 → 1.6.5
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.
|
@@ -13,25 +13,16 @@ from typing import Any, Callable
|
|
|
13
13
|
from urllib.parse import parse_qs
|
|
14
14
|
import httpx
|
|
15
15
|
|
|
16
|
-
# --- Observability bootstrap (
|
|
17
|
-
#
|
|
18
|
-
# or
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return
|
|
27
|
-
_obs_initialized = True
|
|
28
|
-
_obs_entries_raw = os.environ.get("AGENT_OBSERVABILITY_ENTRIES", "")
|
|
29
|
-
if _obs_entries_raw:
|
|
30
|
-
try:
|
|
31
|
-
from _platform._observability import setup as _obs_setup
|
|
32
|
-
_obs_setup(json.loads(_obs_entries_raw))
|
|
33
|
-
except Exception as _obs_err:
|
|
34
|
-
print(f"[observability] setup failed: {_obs_err}", file=sys.stderr, flush=True)
|
|
16
|
+
# --- Observability bootstrap (must run before user code imports) ---
|
|
17
|
+
# Reads entry names from AGENT_OBSERVABILITY_ENTRIES env var (set by main.py
|
|
18
|
+
# or the dev runner). Idempotent — safe to call multiple times.
|
|
19
|
+
_obs_entries_raw = os.environ.get("AGENT_OBSERVABILITY_ENTRIES", "")
|
|
20
|
+
if _obs_entries_raw:
|
|
21
|
+
try:
|
|
22
|
+
from _platform._observability import setup as _obs_setup
|
|
23
|
+
_obs_setup(json.loads(_obs_entries_raw))
|
|
24
|
+
except Exception as _obs_err:
|
|
25
|
+
print(f"[observability] setup failed: {_obs_err}", file=sys.stderr, flush=True)
|
|
35
26
|
|
|
36
27
|
from .store import InMemoryStore, BlobBackedStore
|
|
37
28
|
from .runtime import AgentRuntime, RouteEntry
|
|
@@ -464,58 +455,22 @@ _registry: dict[str, RouteEntry] = {}
|
|
|
464
455
|
# 加载失败的路由:route_path → 失败原因。404 响应时回显,避免静默 404 难以排查。
|
|
465
456
|
_failed_routes: dict[str, str] = {}
|
|
466
457
|
|
|
467
|
-
|
|
468
|
-
# --- Lazy route loading for fast cold start ---
|
|
469
|
-
# Routes are NOT imported at startup. Instead, each route module is loaded
|
|
470
|
-
# on first access (or by the background preload thread). This avoids blocking
|
|
471
|
-
# the health check with heavy imports (langchain, langgraph, pydantic, etc.).
|
|
472
|
-
|
|
473
|
-
class _LazyRouteEntry:
|
|
474
|
-
"""Lazy-loading wrapper for RouteEntry. Defers importlib.import_module() to first access."""
|
|
475
|
-
|
|
476
|
-
__slots__ = ('_module_path', '_is_index', '_handler', '_loaded', '_load_error')
|
|
477
|
-
|
|
478
|
-
def __init__(self, module_path: str, is_index: bool):
|
|
479
|
-
self._module_path = module_path
|
|
480
|
-
self._is_index = is_index
|
|
481
|
-
self._handler = None
|
|
482
|
-
self._loaded = False
|
|
483
|
-
self._load_error: str | None = None
|
|
484
|
-
|
|
485
|
-
@property
|
|
486
|
-
def handler(self):
|
|
487
|
-
if not self._loaded:
|
|
488
|
-
self._do_load()
|
|
489
|
-
if self._load_error:
|
|
490
|
-
raise ImportError(self._load_error)
|
|
491
|
-
return self._handler
|
|
492
|
-
|
|
493
|
-
@property
|
|
494
|
-
def is_index(self):
|
|
495
|
-
return self._is_index
|
|
496
|
-
|
|
497
|
-
@property
|
|
498
|
-
def module_path(self):
|
|
499
|
-
return self._module_path
|
|
500
|
-
|
|
501
|
-
def _do_load(self):
|
|
502
|
-
"""Actually import the module and resolve the handler."""
|
|
503
|
-
try:
|
|
504
|
-
mod = importlib.import_module(self._module_path)
|
|
505
|
-
handler = getattr(mod, "handler", None)
|
|
506
|
-
if handler is None:
|
|
507
|
-
self._load_error = f"module '{self._module_path}' has no 'handler' function"
|
|
508
|
-
else:
|
|
509
|
-
self._handler = handler
|
|
510
|
-
except Exception as e:
|
|
511
|
-
self._load_error = f"failed to import '{self._module_path}': {e}"
|
|
512
|
-
self._loaded = True
|
|
513
|
-
|
|
514
|
-
|
|
515
458
|
for route_path, info in _route_table_raw.items():
|
|
516
459
|
module_path = info["module"]
|
|
517
460
|
is_index = info.get("isIndex", False)
|
|
518
|
-
|
|
461
|
+
try:
|
|
462
|
+
mod = importlib.import_module(module_path)
|
|
463
|
+
handler = getattr(mod, "handler", None)
|
|
464
|
+
if handler is None:
|
|
465
|
+
reason = f"module '{module_path}' has no 'handler' function"
|
|
466
|
+
print(f"[agent-python] WARNING: {reason}, skipping", file=sys.stderr, flush=True)
|
|
467
|
+
_failed_routes[route_path] = reason
|
|
468
|
+
continue
|
|
469
|
+
_registry[route_path] = RouteEntry(handler=handler, is_index=is_index, module_path=module_path)
|
|
470
|
+
except Exception as e:
|
|
471
|
+
reason = f"failed to import '{module_path}': {e}"
|
|
472
|
+
print(f"[agent-python] ERROR loading {module_path}: {e}", file=sys.stderr, flush=True)
|
|
473
|
+
_failed_routes[route_path] = reason
|
|
519
474
|
|
|
520
475
|
_runtime = AgentRuntime(
|
|
521
476
|
registry=_registry,
|
|
@@ -525,46 +480,13 @@ _runtime = AgentRuntime(
|
|
|
525
480
|
failed_routes=_failed_routes,
|
|
526
481
|
)
|
|
527
482
|
|
|
528
|
-
print(f"[agent-python]
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
# import synchronously (no failure, just slower first request).
|
|
536
|
-
|
|
537
|
-
import threading
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
def _background_preload():
|
|
541
|
-
"""Preload routes + observability in background after startup."""
|
|
542
|
-
import time
|
|
543
|
-
time.sleep(0.05) # yield to let uvicorn finish port binding
|
|
544
|
-
|
|
545
|
-
# 1. Initialize observability first (lighter, needed for spans)
|
|
546
|
-
_ensure_observability()
|
|
547
|
-
|
|
548
|
-
# 2. Trigger lazy loading for all routes
|
|
549
|
-
for route_path, entry in _registry.items():
|
|
550
|
-
try:
|
|
551
|
-
_ = entry.handler
|
|
552
|
-
except Exception as e:
|
|
553
|
-
_failed_routes[route_path] = str(e)
|
|
554
|
-
print(f"[agent-python] ERROR loading {entry.module_path}: {e}", file=sys.stderr, flush=True)
|
|
555
|
-
|
|
556
|
-
loaded_count = sum(1 for e in _registry.values() if e._loaded and e._load_error is None)
|
|
557
|
-
if _failed_routes:
|
|
558
|
-
print(
|
|
559
|
-
f"[agent-python] Preload done: {loaded_count}/{len(_registry)} routes loaded, "
|
|
560
|
-
f"{len(_failed_routes)} failed: {', '.join(_failed_routes.keys())}",
|
|
561
|
-
file=sys.stderr, flush=True,
|
|
562
|
-
)
|
|
563
|
-
else:
|
|
564
|
-
print(f"[agent-python] Preload done: {loaded_count}/{len(_registry)} routes loaded", file=sys.stderr, flush=True)
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
threading.Thread(target=_background_preload, daemon=True, name="agent-preload").start()
|
|
483
|
+
print(f"[agent-python] Loaded {len(_registry)} routes, timeout={_timeout}s", file=sys.stderr, flush=True)
|
|
484
|
+
if _failed_routes:
|
|
485
|
+
print(
|
|
486
|
+
f"[agent-python] WARNING: {len(_failed_routes)} route(s) failed to load: "
|
|
487
|
+
f"{', '.join(_failed_routes.keys())}",
|
|
488
|
+
file=sys.stderr, flush=True,
|
|
489
|
+
)
|
|
568
490
|
|
|
569
491
|
|
|
570
492
|
# --- ASGI app ---
|
|
@@ -589,11 +511,6 @@ async def app(scope: dict, receive, send) -> None:
|
|
|
589
511
|
if scope["type"] != "http":
|
|
590
512
|
return
|
|
591
513
|
|
|
592
|
-
# Ensure observability is initialized before processing requests.
|
|
593
|
-
# Usually already done by background preload, but if a request arrives
|
|
594
|
-
# before preload completes, this ensures instrumentation is active.
|
|
595
|
-
_ensure_observability()
|
|
596
|
-
|
|
597
514
|
# Read body
|
|
598
515
|
body_parts = []
|
|
599
516
|
while True:
|