llm_worker_rs/llm-worker/src/event.rs

447 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Worker層の公開イベント型
//!
//! 外部利用者に公開するためのイベント表現。
use serde::{Deserialize, Serialize};
// =============================================================================
// Core Event Types (from llm_client layer)
// =============================================================================
/// LLMからのストリーミングイベント
///
/// 各LLMプロバイダからのレスポンスは、この`Event`のストリームとして
/// 統一的に処理されます。
///
/// # イベントの種類
///
/// - **メタイベント**: `Ping`, `Usage`, `Status`, `Error`
/// - **ブロックイベント**: `BlockStart`, `BlockDelta`, `BlockStop`, `BlockAbort`
///
/// # ブロックのライフサイクル
///
/// テキストやツール呼び出しは、`BlockStart` → `BlockDelta`(複数) → `BlockStop`
/// の順序でイベントが発生します。
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Event {
/// ハートビート
Ping(PingEvent),
/// トークン使用量
Usage(UsageEvent),
/// ストリームのステータス変化
Status(StatusEvent),
/// エラー発生
Error(ErrorEvent),
/// ブロック開始(テキスト、ツール使用等)
BlockStart(BlockStart),
/// ブロックの差分データ
BlockDelta(BlockDelta),
/// ブロック正常終了
BlockStop(BlockStop),
/// ブロック中断
BlockAbort(BlockAbort),
}
// =============================================================================
// Meta Events
// =============================================================================
/// Pingイベントハートビート
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct PingEvent {
pub timestamp: Option<u64>,
}
/// 使用量イベント
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct UsageEvent {
/// 入力トークン数
pub input_tokens: Option<u64>,
/// 出力トークン数
pub output_tokens: Option<u64>,
/// 合計トークン数
pub total_tokens: Option<u64>,
/// キャッシュ読み込みトークン数
pub cache_read_input_tokens: Option<u64>,
/// キャッシュ作成トークン数
pub cache_creation_input_tokens: Option<u64>,
}
/// ステータスイベント
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StatusEvent {
pub status: ResponseStatus,
}
/// レスポンスステータス
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ResponseStatus {
/// ストリーム開始
Started,
/// 正常完了
Completed,
/// キャンセルされた
Cancelled,
/// エラー発生
Failed,
}
/// エラーイベント
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ErrorEvent {
pub code: Option<String>,
pub message: String,
}
// =============================================================================
// Block Types
// =============================================================================
/// ブロックの種別
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BlockType {
/// テキスト生成
Text,
/// 思考 (Claude Extended Thinking等)
Thinking,
/// ツール呼び出し
ToolUse,
/// ツール結果
ToolResult,
}
/// ブロック開始イベント
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlockStart {
/// ブロックのインデックス
pub index: usize,
/// ブロックの種別
pub block_type: BlockType,
/// ブロック固有のメタデータ
pub metadata: BlockMetadata,
}
impl BlockStart {
pub fn block_type(&self) -> BlockType {
self.block_type
}
}
/// ブロックのメタデータ
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BlockMetadata {
Text,
Thinking,
ToolUse { id: String, name: String },
ToolResult { tool_use_id: String },
}
/// ブロックデルタイベント
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlockDelta {
/// ブロックのインデックス
pub index: usize,
/// デルタの内容
pub delta: DeltaContent,
}
/// デルタの内容
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum DeltaContent {
/// テキストデルタ
Text(String),
/// 思考デルタ
Thinking(String),
/// ツール引数のJSON部分文字列
InputJson(String),
}
impl DeltaContent {
/// デルタのブロック種別を取得
pub fn block_type(&self) -> BlockType {
match self {
DeltaContent::Text(_) => BlockType::Text,
DeltaContent::Thinking(_) => BlockType::Thinking,
DeltaContent::InputJson(_) => BlockType::ToolUse,
}
}
}
/// ブロック停止イベント
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlockStop {
/// ブロックのインデックス
pub index: usize,
/// ブロックの種別
pub block_type: BlockType,
/// 停止理由
pub stop_reason: Option<StopReason>,
}
impl BlockStop {
pub fn block_type(&self) -> BlockType {
self.block_type
}
}
/// ブロック中断イベント
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlockAbort {
/// ブロックのインデックス
pub index: usize,
/// ブロックの種別
pub block_type: BlockType,
/// 中断理由
pub reason: String,
}
impl BlockAbort {
pub fn block_type(&self) -> BlockType {
self.block_type
}
}
/// 停止理由
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum StopReason {
/// 自然終了
EndTurn,
/// 最大トークン数到達
MaxTokens,
/// ストップシーケンス到達
StopSequence,
/// ツール使用
ToolUse,
}
// =============================================================================
// Builder / Factory helpers
// =============================================================================
impl Event {
/// テキストブロック開始イベントを作成
pub fn text_block_start(index: usize) -> Self {
Event::BlockStart(BlockStart {
index,
block_type: BlockType::Text,
metadata: BlockMetadata::Text,
})
}
/// テキストデルタイベントを作成
pub fn text_delta(index: usize, text: impl Into<String>) -> Self {
Event::BlockDelta(BlockDelta {
index,
delta: DeltaContent::Text(text.into()),
})
}
/// テキストブロック停止イベントを作成
pub fn text_block_stop(index: usize, stop_reason: Option<StopReason>) -> Self {
Event::BlockStop(BlockStop {
index,
block_type: BlockType::Text,
stop_reason,
})
}
/// ツール使用ブロック開始イベントを作成
pub fn tool_use_start(index: usize, id: impl Into<String>, name: impl Into<String>) -> Self {
Event::BlockStart(BlockStart {
index,
block_type: BlockType::ToolUse,
metadata: BlockMetadata::ToolUse {
id: id.into(),
name: name.into(),
},
})
}
/// ツール引数デルタイベントを作成
pub fn tool_input_delta(index: usize, json: impl Into<String>) -> Self {
Event::BlockDelta(BlockDelta {
index,
delta: DeltaContent::InputJson(json.into()),
})
}
/// ツール使用ブロック停止イベントを作成
pub fn tool_use_stop(index: usize) -> Self {
Event::BlockStop(BlockStop {
index,
block_type: BlockType::ToolUse,
stop_reason: Some(StopReason::ToolUse),
})
}
/// 使用量イベントを作成
pub fn usage(input_tokens: u64, output_tokens: u64) -> Self {
Event::Usage(UsageEvent {
input_tokens: Some(input_tokens),
output_tokens: Some(output_tokens),
total_tokens: Some(input_tokens + output_tokens),
cache_read_input_tokens: None,
cache_creation_input_tokens: None,
})
}
/// Pingイベントを作成
pub fn ping() -> Self {
Event::Ping(PingEvent { timestamp: None })
}
}
// =============================================================================
// Conversions: timeline::event -> worker::event
// =============================================================================
impl From<crate::timeline::event::ResponseStatus> for ResponseStatus {
fn from(value: crate::timeline::event::ResponseStatus) -> Self {
match value {
crate::timeline::event::ResponseStatus::Started => ResponseStatus::Started,
crate::timeline::event::ResponseStatus::Completed => ResponseStatus::Completed,
crate::timeline::event::ResponseStatus::Cancelled => ResponseStatus::Cancelled,
crate::timeline::event::ResponseStatus::Failed => ResponseStatus::Failed,
}
}
}
impl From<crate::timeline::event::BlockType> for BlockType {
fn from(value: crate::timeline::event::BlockType) -> Self {
match value {
crate::timeline::event::BlockType::Text => BlockType::Text,
crate::timeline::event::BlockType::Thinking => BlockType::Thinking,
crate::timeline::event::BlockType::ToolUse => BlockType::ToolUse,
crate::timeline::event::BlockType::ToolResult => BlockType::ToolResult,
}
}
}
impl From<crate::timeline::event::BlockMetadata> for BlockMetadata {
fn from(value: crate::timeline::event::BlockMetadata) -> Self {
match value {
crate::timeline::event::BlockMetadata::Text => BlockMetadata::Text,
crate::timeline::event::BlockMetadata::Thinking => BlockMetadata::Thinking,
crate::timeline::event::BlockMetadata::ToolUse { id, name } => {
BlockMetadata::ToolUse { id, name }
}
crate::timeline::event::BlockMetadata::ToolResult { tool_use_id } => {
BlockMetadata::ToolResult { tool_use_id }
}
}
}
}
impl From<crate::timeline::event::DeltaContent> for DeltaContent {
fn from(value: crate::timeline::event::DeltaContent) -> Self {
match value {
crate::timeline::event::DeltaContent::Text(text) => DeltaContent::Text(text),
crate::timeline::event::DeltaContent::Thinking(text) => DeltaContent::Thinking(text),
crate::timeline::event::DeltaContent::InputJson(json) => DeltaContent::InputJson(json),
}
}
}
impl From<crate::timeline::event::StopReason> for StopReason {
fn from(value: crate::timeline::event::StopReason) -> Self {
match value {
crate::timeline::event::StopReason::EndTurn => StopReason::EndTurn,
crate::timeline::event::StopReason::MaxTokens => StopReason::MaxTokens,
crate::timeline::event::StopReason::StopSequence => StopReason::StopSequence,
crate::timeline::event::StopReason::ToolUse => StopReason::ToolUse,
}
}
}
impl From<crate::timeline::event::PingEvent> for PingEvent {
fn from(value: crate::timeline::event::PingEvent) -> Self {
PingEvent {
timestamp: value.timestamp,
}
}
}
impl From<crate::timeline::event::UsageEvent> for UsageEvent {
fn from(value: crate::timeline::event::UsageEvent) -> Self {
UsageEvent {
input_tokens: value.input_tokens,
output_tokens: value.output_tokens,
total_tokens: value.total_tokens,
cache_read_input_tokens: value.cache_read_input_tokens,
cache_creation_input_tokens: value.cache_creation_input_tokens,
}
}
}
impl From<crate::timeline::event::StatusEvent> for StatusEvent {
fn from(value: crate::timeline::event::StatusEvent) -> Self {
StatusEvent {
status: value.status.into(),
}
}
}
impl From<crate::timeline::event::ErrorEvent> for ErrorEvent {
fn from(value: crate::timeline::event::ErrorEvent) -> Self {
ErrorEvent {
code: value.code,
message: value.message,
}
}
}
impl From<crate::timeline::event::BlockStart> for BlockStart {
fn from(value: crate::timeline::event::BlockStart) -> Self {
BlockStart {
index: value.index,
block_type: value.block_type.into(),
metadata: value.metadata.into(),
}
}
}
impl From<crate::timeline::event::BlockDelta> for BlockDelta {
fn from(value: crate::timeline::event::BlockDelta) -> Self {
BlockDelta {
index: value.index,
delta: value.delta.into(),
}
}
}
impl From<crate::timeline::event::BlockStop> for BlockStop {
fn from(value: crate::timeline::event::BlockStop) -> Self {
BlockStop {
index: value.index,
block_type: value.block_type.into(),
stop_reason: value.stop_reason.map(Into::into),
}
}
}
impl From<crate::timeline::event::BlockAbort> for BlockAbort {
fn from(value: crate::timeline::event::BlockAbort) -> Self {
BlockAbort {
index: value.index,
block_type: value.block_type.into(),
reason: value.reason,
}
}
}
impl From<crate::timeline::event::Event> for Event {
fn from(value: crate::timeline::event::Event) -> Self {
match value {
crate::timeline::event::Event::Ping(p) => Event::Ping(p.into()),
crate::timeline::event::Event::Usage(u) => Event::Usage(u.into()),
crate::timeline::event::Event::Status(s) => Event::Status(s.into()),
crate::timeline::event::Event::Error(e) => Event::Error(e.into()),
crate::timeline::event::Event::BlockStart(s) => Event::BlockStart(s.into()),
crate::timeline::event::Event::BlockDelta(d) => Event::BlockDelta(d.into()),
crate::timeline::event::Event::BlockStop(s) => Event::BlockStop(s.into()),
crate::timeline::event::Event::BlockAbort(a) => Event::BlockAbort(a.into()),
}
}
}