forked from HKUDS/OpenSpace
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathorchestrator.py
More file actions
148 lines (121 loc) · 4.7 KB
/
Copy pathorchestrator.py
File metadata and controls
148 lines (121 loc) · 4.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""Evolution orchestration — dispatch, throttle, and background scheduling.
Functions extracted from ``SkillEvolver`` (Epic 5.2):
- ``dispatch_evolution`` — route a single context to the right engine
- ``execute_contexts`` — run a batch of contexts in parallel (throttled)
- ``schedule_background`` — fire-and-forget background evolution task
- ``log_background_result`` — callback that logs task outcome
"""
from __future__ import annotations
import asyncio
import logging
from typing import TYPE_CHECKING, List, Optional
from openspace.utils.logging import Logger
from .models import EvolutionContext
from openspace.skill_engine.types import EvolutionType
if TYPE_CHECKING:
from openspace.skill_engine.types import SkillRecord
logger = Logger.get_logger(__name__)
# ---------------------------------------------------------------------------
# Single-context dispatch
# ---------------------------------------------------------------------------
async def dispatch_evolution(
evolver,
ctx: EvolutionContext,
) -> Optional["SkillRecord"]:
"""Execute one evolution action. Returns new ``SkillRecord`` or *None*.
Delegates to the appropriate engine method on *evolver*:
``_evolve_fix``, ``_evolve_derived``, or ``_evolve_captured``.
The global semaphore is **not** acquired here — it is managed by
``execute_contexts`` so the concurrency limit covers whole batches.
"""
evo_type: EvolutionType = ctx.suggestion.evolution_type
try:
if evo_type == EvolutionType.FIX:
return await evolver._evolve_fix(ctx)
elif evo_type == EvolutionType.DERIVED:
return await evolver._evolve_derived(ctx)
elif evo_type == EvolutionType.CAPTURED:
return await evolver._evolve_captured(ctx)
else:
logger.warning("Unknown evolution type: %s", evo_type)
return None
except Exception as e:
targets = "+".join(ctx.suggestion.target_skill_ids) or "(new)"
logger.error(
"Evolution failed [%s] target=%s: %s",
evo_type.value,
targets,
e,
)
return None
# ---------------------------------------------------------------------------
# Batch execution with throttling
# ---------------------------------------------------------------------------
async def execute_contexts(
evolver,
contexts: List[EvolutionContext],
trigger_label: str,
) -> List["SkillRecord"]:
"""Execute a list of evolution contexts in parallel (throttled).
Uses *evolver._semaphore* for concurrency control and routes each
context through ``dispatch_evolution``.
"""
async def _throttled(c: EvolutionContext) -> Optional["SkillRecord"]:
async with evolver._semaphore:
return await evolver.evolve(c)
raw = await asyncio.gather(
*[_throttled(c) for c in contexts],
return_exceptions=True,
)
results: List["SkillRecord"] = []
for r in raw:
if isinstance(r, BaseException):
logger.error("[Trigger:%s] Evolution task raised: %s", trigger_label, r)
elif r is not None:
results.append(r)
if results:
names = [r.name for r in results]
logger.info(
"[Trigger:%s] Evolved %d skill(s): %s",
trigger_label,
len(results),
names,
)
return results
# ---------------------------------------------------------------------------
# Background task scheduling
# ---------------------------------------------------------------------------
def schedule_background(
background_tasks: set,
coro,
*,
label: str = "background_evolution",
) -> Optional[asyncio.Task]:
"""Launch *coro* as a background ``asyncio.Task``.
*background_tasks* is the ``set`` that tracks outstanding tasks
(typically ``SkillEvolver._background_tasks``). The task is removed
from the set on completion via ``discard`` callback.
"""
try:
loop = asyncio.get_running_loop()
except RuntimeError:
logger.warning("No running event loop — cannot schedule %s", label)
return None
task = loop.create_task(coro, name=label)
background_tasks.add(task)
task.add_done_callback(background_tasks.discard)
task.add_done_callback(log_background_result)
return task
def log_background_result(task: asyncio.Task) -> None:
"""Log the outcome of a background evolution task."""
if task.cancelled():
logger.debug("Background task '%s' was cancelled", task.get_name())
return
exc = task.exception()
if exc:
logger.error(
"Background task '%s' failed: %s",
task.get_name(),
exc,
exc_info=exc,
)