Skip to main content

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}