swarm_engine_core/agent/batch.rs
1//! BatchInvoker - Batch LLM 推論の抽象化
2//!
3//! # 型の定義場所
4//!
5//! Batch関連の型は以下のモジュールで定義されています:
6//!
7//! - **正規定義 (`manager.rs`)**: `BatchDecisionRequest`, `WorkerDecisionRequest`, `ManagerId`
8//! - **本モジュール (`batch.rs`)**: `BatchInvoker` trait, `BatchInvokeResult`,
9//! `BatchInvokeError`, `DecisionResponse`
10//!
11//! 本モジュールは `manager.rs` の型を re-export しており、
12//! 利用者はどちらからも import 可能です。
13
14use std::collections::HashMap;
15
16use crate::actions::ActionDef;
17use crate::exploration::DependencyGraph;
18use crate::extensions::Extensions;
19use crate::types::WorkerId;
20
21// Re-export from manager.rs (正規定義はそちら)
22pub use super::manager::{BatchDecisionRequest, ManagerId, WorkerDecisionRequest};
23
24/// Batch LLM 推論の抽象化
25///
26/// Manager の BatchDecisionRequest を受け取り、各 Worker への DecisionResponse を返す。
27/// vLLM / Ollama / OpenAI 等のバックエンドに応じた実装を LLM 層で提供する。
28///
29/// # 設計
30///
31/// ```text
32/// Orchestrator
33/// │
34/// ├─ Manager.prepare_batch() → BatchDecisionRequest
35/// │
36/// ├─ BatchInvoker.invoke(request, extensions) → BatchInvokeResult ← ここを抽象化
37/// │
38/// └─ Manager.finalize_batch(responses) → ManagementDecision
39/// ```
40///
41/// # Extensions によるランタイム設定
42///
43/// `extensions` には以下の設定が含まれる可能性があります:
44/// - `LlmConfig`: LLM プロバイダ、モデル、temperature 等
45/// - `ActionsConfig`: 利用可能な Action 一覧
46/// - `BatchProcessorConfig`: 並列処理設定等
47///
48/// 実装は必要に応じてこれらを `extensions.get::<T>()` で取得します。
49///
50/// # 同期 vs 非同期
51///
52/// 同期インターフェース(`invoke`)を提供。
53/// 非同期が必要な場合は実装側で `block_on` 等を使用する。
54pub trait BatchInvoker: Send + Sync {
55 /// Batch リクエストを処理
56 ///
57 /// # Arguments
58 /// - `request`: Manager が生成した Batch リクエスト
59 /// - `extensions`: ランタイム設定(LlmConfig, ActionsConfig 等)
60 fn invoke(&self, request: BatchDecisionRequest, extensions: &Extensions) -> BatchInvokeResult;
61
62 /// タスクとアクション一覧からアクション依存グラフを生成
63 ///
64 /// Swarm の Ticks 開始前に呼び出され、アクション間の依存関係を計画する。
65 /// LLM を使用して動的に依存グラフを生成する。
66 ///
67 /// # Arguments
68 /// - `task`: タスク説明(SwarmTask.description)
69 /// - `actions`: 利用可能なアクション定義の一覧
70 ///
71 /// # Returns
72 /// - `Some(DependencyGraph)`: 依存グラフが生成された場合
73 /// - `None`: 依存グラフ生成をスキップする場合(デフォルト)
74 ///
75 /// # Example
76 ///
77 /// ```ignore
78 /// let graph = invoker.plan_dependencies(
79 /// "Find the auth handler in src/",
80 /// &actions, // Vec<ActionDef>
81 /// );
82 /// // => Some(DependencyGraph { edges: [Grep -> Read], start: [Grep], terminal: [Read] })
83 /// ```
84 fn plan_dependencies(&self, _task: &str, _actions: &[ActionDef]) -> Option<DependencyGraph> {
85 None
86 }
87
88 /// プロセッサ名(デバッグ/ログ用)
89 fn name(&self) -> &str;
90
91 /// ヘルスチェック
92 fn is_healthy(&self) -> bool {
93 true
94 }
95}
96
97/// Batch 推論結果
98pub type BatchInvokeResult = Vec<(WorkerId, Result<DecisionResponse, BatchInvokeError>)>;
99
100/// Batch 推論エラー
101#[derive(Debug, Clone, thiserror::Error)]
102pub enum BatchInvokeError {
103 /// 一時的エラー(リトライ可能)
104 #[error("Batch invoke error (transient): {0}")]
105 Transient(String),
106
107 /// 恒久的エラー(リトライ不可)
108 #[error("Batch invoke error: {0}")]
109 Permanent(String),
110}
111
112impl BatchInvokeError {
113 pub fn transient(message: impl Into<String>) -> Self {
114 Self::Transient(message.into())
115 }
116
117 pub fn permanent(message: impl Into<String>) -> Self {
118 Self::Permanent(message.into())
119 }
120
121 pub fn is_transient(&self) -> bool {
122 matches!(self, Self::Transient(_))
123 }
124
125 pub fn message(&self) -> &str {
126 match self {
127 Self::Transient(msg) => msg,
128 Self::Permanent(msg) => msg,
129 }
130 }
131}
132
133impl From<crate::error::SwarmError> for BatchInvokeError {
134 fn from(err: crate::error::SwarmError) -> Self {
135 if err.is_transient() {
136 Self::Transient(err.message())
137 } else {
138 Self::Permanent(err.message())
139 }
140 }
141}
142
143impl From<BatchInvokeError> for crate::error::SwarmError {
144 fn from(err: BatchInvokeError) -> Self {
145 match err {
146 BatchInvokeError::Transient(message) => {
147 crate::error::SwarmError::LlmTransient { message }
148 }
149 BatchInvokeError::Permanent(message) => {
150 crate::error::SwarmError::LlmPermanent { message }
151 }
152 }
153 }
154}
155
156/// LLM からの判断レスポンス
157#[derive(Debug, Clone)]
158pub struct DecisionResponse {
159 /// 選択されたツール/Action
160 pub tool: String,
161 /// ターゲット
162 pub target: String,
163 /// 引数
164 pub args: HashMap<String, String>,
165 /// 推論理由
166 pub reasoning: Option<String>,
167 /// 信頼度 (0.0 - 1.0)
168 pub confidence: f64,
169 /// 送信したプロンプト(デバッグ/Snapshot用)
170 pub prompt: Option<String>,
171 /// LLM からの生レスポンス(デバッグ/Snapshot用)
172 pub raw_response: Option<String>,
173}
174
175impl Default for DecisionResponse {
176 fn default() -> Self {
177 Self {
178 tool: String::new(),
179 target: String::new(),
180 args: HashMap::new(),
181 reasoning: None,
182 confidence: 0.0,
183 prompt: None,
184 raw_response: None,
185 }
186 }
187}