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}