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, SelectResult};
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    /// - `hint`: `LearnedDependencyProvider.select()` の結果(LoRA、vote_count 等を含む)
71    ///
72    /// # Returns
73    /// - `Some(DependencyGraph)`: 依存グラフが生成された場合
74    /// - `None`: 依存グラフ生成をスキップする場合(デフォルト)
75    ///
76    /// # Example
77    ///
78    /// ```ignore
79    /// let graph = invoker.plan_dependencies(
80    ///     "Find the auth handler in src/",
81    ///     &actions,  // Vec<ActionDef>
82    ///     Some(&select_result),  // SelectResult from LearnedDependencyProvider
83    /// );
84    /// // => Some(DependencyGraph { edges: [Grep -> Read], start: [Grep], terminal: [Read] })
85    /// ```
86    fn plan_dependencies(
87        &self,
88        _task: &str,
89        _actions: &[ActionDef],
90        _hint: Option<&SelectResult>,
91    ) -> Option<DependencyGraph> {
92        None
93    }
94
95    /// プロセッサ名(デバッグ/ログ用)
96    fn name(&self) -> &str;
97
98    /// ヘルスチェック
99    fn is_healthy(&self) -> bool {
100        true
101    }
102}
103
104/// Batch 推論結果
105pub type BatchInvokeResult = Vec<(WorkerId, Result<DecisionResponse, BatchInvokeError>)>;
106
107/// Batch 推論エラー
108#[derive(Debug, Clone, thiserror::Error)]
109pub enum BatchInvokeError {
110    /// 一時的エラー(リトライ可能)
111    #[error("Batch invoke error (transient): {0}")]
112    Transient(String),
113
114    /// 恒久的エラー(リトライ不可)
115    #[error("Batch invoke error: {0}")]
116    Permanent(String),
117}
118
119impl BatchInvokeError {
120    pub fn transient(message: impl Into<String>) -> Self {
121        Self::Transient(message.into())
122    }
123
124    pub fn permanent(message: impl Into<String>) -> Self {
125        Self::Permanent(message.into())
126    }
127
128    pub fn is_transient(&self) -> bool {
129        matches!(self, Self::Transient(_))
130    }
131
132    pub fn message(&self) -> &str {
133        match self {
134            Self::Transient(msg) => msg,
135            Self::Permanent(msg) => msg,
136        }
137    }
138}
139
140impl From<crate::error::SwarmError> for BatchInvokeError {
141    fn from(err: crate::error::SwarmError) -> Self {
142        if err.is_transient() {
143            Self::Transient(err.message())
144        } else {
145            Self::Permanent(err.message())
146        }
147    }
148}
149
150impl From<BatchInvokeError> for crate::error::SwarmError {
151    fn from(err: BatchInvokeError) -> Self {
152        match err {
153            BatchInvokeError::Transient(message) => {
154                crate::error::SwarmError::LlmTransient { message }
155            }
156            BatchInvokeError::Permanent(message) => {
157                crate::error::SwarmError::LlmPermanent { message }
158            }
159        }
160    }
161}
162
163/// LLM からの判断レスポンス
164#[derive(Debug, Clone)]
165pub struct DecisionResponse {
166    /// 選択されたツール/Action
167    pub tool: String,
168    /// ターゲット
169    pub target: String,
170    /// 引数
171    pub args: HashMap<String, String>,
172    /// 推論理由
173    pub reasoning: Option<String>,
174    /// 信頼度 (0.0 - 1.0)
175    pub confidence: f64,
176    /// 送信したプロンプト(デバッグ/Snapshot用)
177    pub prompt: Option<String>,
178    /// LLM からの生レスポンス(デバッグ/Snapshot用)
179    pub raw_response: Option<String>,
180}
181
182impl Default for DecisionResponse {
183    fn default() -> Self {
184        Self {
185            tool: String::new(),
186            target: String::new(),
187            args: HashMap::new(),
188            reasoning: None,
189            confidence: 0.0,
190            prompt: None,
191            raw_response: None,
192        }
193    }
194}