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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
//! ライフサイクル管理
//!
//! - validate(): 設定検証
//! - ensure_exploration_space(): ExplorationSpace の自動生成
//! - should_terminate(): 終了判定
use std::time::Instant;
use tracing::{debug, info, warn};
use crate::actions::ActionDef;
use crate::error::SwarmError;
use crate::events::{ActionContext, ActionEventBuilder, ActionEventResult};
use crate::exploration::{ExplorationSpaceV2, NodeRules};
use crate::types::WorkerId;
use super::Orchestrator;
impl Orchestrator {
/// Orchestrator の設定を検証
///
/// run_task() を呼ぶ前にこのメソッドで設定を検証できます。
/// run_task() 内でも同様のチェックが行われますが、事前に検証したい場合に使用します。
///
/// # Errors
///
/// - `SwarmError::NoWorkers`: Worker が設定されていない
/// - `SwarmError::MissingDependencyGraph`: DependencyGraph のソースが設定されていない
///
/// # Example
///
/// ```ignore
/// let orchestrator = OrchestratorBuilder::new()
/// .add_worker(worker)
/// .batch_invoker(invoker)
/// .build(runtime);
///
/// // 事前検証
/// orchestrator.validate()?;
///
/// // タスク実行
/// let result = orchestrator.run_task(task)?;
/// ```
pub fn validate(&self) -> Result<(), SwarmError> {
// Worker チェック
if self.workers.is_empty() {
return Err(SwarmError::NoWorkers);
}
// ============================================================
// DependencyGraph ソースチェック(自動判別対応)
// ============================================================
// DependencyGraph は必須だが、以下のいずれかがあれば**自動生成**される:
//
// 1. dependency_provider - 明示的プロバイダー(最優先)
// 2. batch_invoker - LLM で plan_dependencies() を呼び出して生成
// 3. Extensions に DependencyGraph が登録済み
// 4. space_v2 が既に設定されている
//
// シナリオファイルで dependency_graph を明示的に設定する必要は通常ない。
// batch_invoker があれば LLM が自動生成を試みる。
// ============================================================
let has_dependency_graph = self
.state
.shared
.extensions
.get::<crate::exploration::DependencyGraph>()
.is_some();
if !has_dependency_graph
&& self.space_v2.is_none()
&& self.dependency_provider.is_none()
&& self.batch_invoker.is_none()
{
return Err(SwarmError::MissingDependencyGraph {
hint: "No DependencyGraph source configured. \
Use batch_invoker() with LLM that supports plan_dependencies(), \
or dependency_provider() with custom provider, \
or extension(DependencyGraph) for static graph."
.to_string(),
});
}
Ok(())
}
/// ExplorationSpaceV2 がなければ DependencyGraph を自動生成
///
/// ## 自動判別の優先順位
///
/// 以下の順序で DependencyGraph を取得を試みる:
///
/// 1. **DependencyGraphProvider** - 明示的に設定されたプロバイダー
/// 2. **BatchInvoker.plan_dependencies()** - LLM による自動生成
/// 3. エラー(DependencyGraph は必須)
///
/// ## シナリオでの明示的設定は不要
///
/// シナリオファイル(TOML)で `dependency_graph` を設定しなくても、
/// `batch_invoker` が設定されていれば LLM が自動生成を試みる。
///
/// ## 失敗するケース
///
/// - LLM がタスクに適した依存関係を推論できない場合
/// - LLM のレスポンスパースに失敗した場合
/// - 生成されたグラフのバリデーションに失敗した場合
///
/// これらの場合はシナリオファイルで明示的に設定する必要がある。
///
/// # Errors
///
/// - `SwarmError::MissingDependencyGraph`: DependencyGraph を生成できなかった
pub(super) fn ensure_exploration_space(
&mut self,
task_goal: &str,
actions: &[ActionDef],
) -> Result<(), SwarmError> {
// 既に space_v2 がある場合は OK
if self.space_v2.is_some() {
return Ok(());
}
// アクションがない場合もスキップ(後続で別のエラーになる)
if actions.is_empty() {
debug!("No available actions, skipping ExplorationSpaceV2 generation");
return Ok(());
}
// DependencyGraphProvider 用にアクション名リストを作成
let action_names: Vec<String> = actions.iter().map(|a| a.name.clone()).collect();
// 1. DependencyGraphProvider があれば使用
if let Some(provider) = &self.dependency_provider {
info!(
task = %task_goal,
actions = ?action_names,
"Auto-generating DependencyGraph via Provider"
);
let start_time = Instant::now();
let graph_opt = provider.provide_graph(task_goal, &action_names);
let elapsed = start_time.elapsed();
// 学習記録: dependency_provider イベント
let (result, success) = if graph_opt.is_some() {
(ActionEventResult::success(), true)
} else {
(ActionEventResult::failure("provider_returned_none"), false)
};
let event = ActionEventBuilder::new(0, WorkerId::MANAGER, "dependency_provider")
.duration(elapsed)
.result(result)
.context(
ActionContext::new()
.with_metadata("task", task_goal.to_string())
.with_metadata("action_count", action_names.len().to_string()),
)
.build();
self.state.shared.stats.record(&event);
if let Some(ref collector) = self.action_collector {
collector.record(event);
}
if let Some(graph) = graph_opt {
info!(
start = ?graph.start_actions(),
terminal = ?graph.terminal_actions(),
edges = graph.edges().len(),
"DependencyGraph auto-generated via Provider"
);
// V2: NodeRules に変換して OperatorProvider 経由で Operator を作成
let rules: NodeRules = (&graph).into();
let operator = self.operator_provider.provide(rules, None);
self.space_v2 =
Some(ExplorationSpaceV2::new(operator).with_dependency_graph(graph));
return Ok(());
} else if !success {
warn!("DependencyGraphProvider returned None");
}
}
// 2. BatchInvoker があればフォールバックとして使用
if let Some(invoker) = &self.batch_invoker {
// DependencyGraphProvider から SelectResult を取得(ヒントとして使用)
let select_hint = self
.dependency_provider
.as_ref()
.and_then(|p| p.select(task_goal, &action_names));
info!(
task = %task_goal,
actions = ?action_names,
has_hint = select_hint.is_some(),
"Auto-generating DependencyGraph via BatchInvoker fallback"
);
let start_time = Instant::now();
let graph_opt = invoker.plan_dependencies(task_goal, actions, select_hint.as_ref());
let elapsed = start_time.elapsed();
// 学習記録: llm_plan_deps イベント
let (result, success) = if graph_opt.is_some() {
(ActionEventResult::success(), true)
} else {
(ActionEventResult::failure("llm_returned_none"), false)
};
let event = ActionEventBuilder::new(0, WorkerId::MANAGER, "llm_plan_deps")
.duration(elapsed)
.result(result)
.context(
ActionContext::new()
.with_metadata("task", task_goal.to_string())
.with_metadata("action_count", action_names.len().to_string())
.with_metadata("invoker", invoker.name().to_string()),
)
.build();
self.state.shared.stats.record(&event);
if let Some(ref collector) = self.action_collector {
collector.record(event);
}
if let Some(graph) = graph_opt {
info!(
start = ?graph.start_actions(),
terminal = ?graph.terminal_actions(),
edges = graph.edges().len(),
"DependencyGraph auto-generated via BatchInvoker"
);
// V2: NodeRules に変換して OperatorProvider 経由で Operator を作成
let rules: NodeRules = (&graph).into();
let operator = self.operator_provider.provide(rules, None);
self.space_v2 =
Some(ExplorationSpaceV2::new(operator).with_dependency_graph(graph));
return Ok(());
} else if !success {
warn!("BatchInvoker.plan_dependencies() returned None");
}
}
// どちらも失敗した場合はエラー
Err(SwarmError::MissingDependencyGraph {
hint: format!(
"Failed to generate DependencyGraph for task '{}' with actions {:?}. \
Ensure your LLM backend supports plan_dependencies(), \
or provide a custom DependencyGraphProvider, \
or register a static DependencyGraph via extension().",
task_goal, action_names
),
})
}
/// 終了判定
///
/// TerminationJudge に委譲。ExplorationSpace の完了も judge 経由で判定。
pub(super) fn should_terminate(&mut self) -> bool {
// Check ExplorationSpaceV2 and notify judge if exhausted
if let Some(ref space_v2) = self.space_v2 {
if space_v2.is_complete()
&& !self
.termination_judge
.completion_state()
.is_exploration_done()
{
let exhausted = space_v2.is_exhausted();
if space_v2.has_completed() {
info!("ExplorationSpaceV2: marked as completed, notifying judge");
} else {
info!("ExplorationSpaceV2: exhausted (Strategy判定), notifying judge");
}
self.termination_judge
.notify_exploration_complete(exhausted);
}
}
// Delegate to TerminationJudge
self.termination_judge.should_terminate()
}
}