update: worker -> llm-worker

This commit is contained in:
Keisuke Hirata 2026-01-09 00:42:33 +09:00
parent 1d890395cc
commit 3b5c7e2d46
67 changed files with 125 additions and 94 deletions

60
Cargo.lock generated
View File

@ -631,6 +631,36 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
name = "llm-worker"
version = "0.1.0"
dependencies = [
"async-trait",
"clap",
"dotenv",
"eventsource-stream",
"futures",
"llm-worker-macros",
"reqwest",
"schemars",
"serde",
"serde_json",
"tempfile",
"thiserror",
"tokio",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "llm-worker-macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "log"
version = "0.4.29"
@ -1567,36 +1597,6 @@ version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "worker"
version = "0.1.0"
dependencies = [
"async-trait",
"clap",
"dotenv",
"eventsource-stream",
"futures",
"reqwest",
"schemars",
"serde",
"serde_json",
"tempfile",
"thiserror",
"tokio",
"tracing",
"tracing-subscriber",
"worker-macros",
]
[[package]]
name = "worker-macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "writeable"
version = "0.6.2"

View File

@ -1,8 +1,8 @@
[workspace]
resolver = "2"
members = [
"worker",
"worker-macros",
"llm-worker",
"llm-worker-macros",
]
[workspace.package]

View File

@ -1,5 +1,36 @@
# llm-worker-rs
# llm-worker
Rusty, Efficient, and Agentic LLM Client Library
`llm-worker-rs` is a Rust library designed for building robust and efficient LLM applications. It unifies interactions with multiple LLM providers (Anthropic, Gemini, OpenAI, Ollama) under a single abstraction, with type-safe state management, efficient context caching, and a powerful event-driven architecture.
`llm-worker` is a Rust library for building autonomous LLM-powered systems. Define tools, register hooks, and let the Worker handle the agentic loop — tool calls are executed automatically until the task completes.
## Features
- Autonomous Execution: The `Worker` manages the full request-response-tool cycle. You provide tools and input; it loops until done.
- Multi-Provider Support: Unified interface for Anthropic, Gemini, OpenAI, and Ollama.
- Tool System: Define tools as async functions. The Worker automatically parses LLM tool calls, executes them in parallel, and feeds results back.
- Hook System: Intercept execution flow with `before_tool_call`, `after_tool_call`, and `on_turn_end` hooks for validation, logging, or self-correction.
- Event-Driven Streaming: Subscribe to real-time events (text deltas, tool calls, usage) for responsive UIs.
- Cache-Aware State Management: Type-state pattern (`Mutable` → `Locked`) ensures KV cache efficiency by protecting the conversation prefix.
## Quick Start
```rust
use llm_worker::{Worker, Message};
// Create a Worker with your LLM client
let mut worker = Worker::new(client)
.system_prompt("You are a helpful assistant.");
// Register tools (optional)
worker.register_tool(SearchTool::new());
worker.register_tool(CalculatorTool::new());
// Run — the Worker handles tool calls automatically
let history = worker.run("What is 2+2?").await?;
```
## License
MIT

View File

@ -1,5 +1,5 @@
[package]
name = "worker-macros"
name = "llm-worker-macros"
version = "0.1.0"
publish.workspace = true
edition.workspace = true

View File

@ -1,4 +1,4 @@
//! worker-macros - Tool生成用手続きマクロ
//! llm-worker-macros - Tool生成用手続きマクロ
//!
//! `#[tool_registry]` と `#[tool]` マクロを提供し、
//! ユーザー定義のメソッドから `Tool` トレイト実装を自動生成する。
@ -193,7 +193,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
quote! {
match result {
Ok(val) => Ok(format!("{:?}", val)),
Err(e) => Err(::worker::tool::ToolError::ExecutionFailed(format!("{}", e))),
Err(e) => Err(::llm_worker::tool::ToolError::ExecutionFailed(format!("{}", e))),
}
}
} else {
@ -230,7 +230,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
} else {
quote! {
let args: #args_struct_name = serde_json::from_str(input_json)
.map_err(|e| ::worker::tool::ToolError::InvalidArgument(e.to_string()))?;
.map_err(|e| ::llm_worker::tool::ToolError::InvalidArgument(e.to_string()))?;
let result = self.ctx.#method_name(#(#arg_names),*)#awaiter;
#result_handling
@ -246,7 +246,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
}
#[async_trait::async_trait]
impl ::worker::tool::Tool for #tool_struct_name {
impl ::llm_worker::tool::Tool for #tool_struct_name {
fn name(&self) -> &str {
#tool_name
}
@ -260,7 +260,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
serde_json::to_value(schema).unwrap_or(serde_json::json!({}))
}
async fn execute(&self, input_json: &str) -> Result<String, ::worker::tool::ToolError> {
async fn execute(&self, input_json: &str) -> Result<String, ::llm_worker::tool::ToolError> {
#execute_body
}
}

View File

@ -1,5 +1,5 @@
[package]
name = "worker"
name = "llm-worker"
description = ""
version = "0.1.0"
publish.workspace = true
@ -17,7 +17,7 @@ futures = "0.3"
tokio = { version = "1.49", features = ["macros", "rt-multi-thread"] }
reqwest = { version = "0.13.1", default-features = false, features = ["stream", "json", "native-tls"] }
eventsource-stream = "0.2"
worker-macros = { path = "../worker-macros", version = "0.1" }
llm-worker-macros = { path = "../llm-worker-macros", version = "0.1" }
[dev-dependencies]
clap = { version = "4.5", features = ["derive", "env"] }

View File

@ -20,9 +20,9 @@ mod recorder;
mod scenarios;
use clap::{Parser, ValueEnum};
use worker::llm_client::providers::anthropic::AnthropicClient;
use worker::llm_client::providers::gemini::GeminiClient;
use worker::llm_client::providers::openai::OpenAIClient;
use llm_worker::llm_client::providers::anthropic::AnthropicClient;
use llm_worker::llm_client::providers::gemini::GeminiClient;
use llm_worker::llm_client::providers::openai::OpenAIClient;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
@ -101,7 +101,7 @@ async fn run_scenario_with_ollama(
subdir: &str,
model: Option<String>,
) -> Result<(), Box<dyn std::error::Error>> {
use worker::llm_client::providers::ollama::OllamaClient;
use llm_worker::llm_client::providers::ollama::OllamaClient;
// Ollama typically runs local, no key needed or placeholder
let model = model.as_deref().unwrap_or("llama3"); // default example
let client = OllamaClient::new(model); // base_url placeholder, handled by client default

View File

@ -8,7 +8,7 @@ use std::path::Path;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use futures::StreamExt;
use worker::llm_client::{LlmClient, Request};
use llm_worker::llm_client::{LlmClient, Request};
/// 記録されたイベント
#[derive(Debug, serde::Serialize, serde::Deserialize)]

View File

@ -2,7 +2,7 @@
//!
//! 各シナリオのリクエストと出力ファイル名を定義
use worker::llm_client::{Request, ToolDefinition};
use llm_worker::llm_client::{Request, ToolDefinition};
/// テストシナリオ
pub struct TestScenario {

View File

@ -39,7 +39,7 @@ use tracing::info;
use tracing_subscriber::EnvFilter;
use clap::{Parser, ValueEnum};
use worker::{
use llm_worker::{
Worker,
hook::{ControlFlow, HookError, ToolResult, WorkerHook},
llm_client::{
@ -51,7 +51,7 @@ use worker::{
},
timeline::{Handler, TextBlockEvent, TextBlockKind, ToolUseBlockEvent, ToolUseBlockKind},
};
use worker_macros::tool_registry;
use llm_worker_macros::tool_registry;
// 必要なマクロ展開用インポート
use schemars;

View File

@ -32,7 +32,7 @@ pub trait Kind {
/// # Examples
///
/// ```ignore
/// use worker::timeline::{Handler, TextBlockEvent, TextBlockKind};
/// use llm_worker::timeline::{Handler, TextBlockEvent, TextBlockKind};
///
/// struct TextCollector {
/// texts: Vec<String>,

View File

@ -109,8 +109,8 @@ pub enum HookError {
/// # Examples
///
/// ```ignore
/// use worker::hook::{ControlFlow, HookError, ToolCall, TurnResult, WorkerHook};
/// use worker::Message;
/// use llm_worker::hook::{ControlFlow, HookError, ToolCall, TurnResult, WorkerHook};
/// use llm_worker::Message;
///
/// struct ValidationHook;
///

View File

@ -1,4 +1,4 @@
//! worker - LLMワーカーライブラリ
//! llm-worker - LLMワーカーライブラリ
//!
//! LLMとの対話を管理するコンポーネントを提供します。
//!
@ -12,14 +12,14 @@
//! # Quick Start
//!
//! ```ignore
//! use worker::{Worker, Message};
//! use llm_worker::{Worker, Message};
//!
//! // Workerを作成
//! let mut worker = Worker::new(client)
//! .system_prompt("You are a helpful assistant.");
//!
//! // ツールを登録(オプション)
//! use worker::tool::Tool;
//! use llm_worker::tool::Tool;
//! worker.register_tool(my_tool);
//!
//! // 対話を実行

View File

@ -20,7 +20,7 @@ pub enum Role {
/// # Examples
///
/// ```ignore
/// use worker::Message;
/// use llm_worker::Message;
///
/// // ユーザーメッセージ
/// let user_msg = Message::user("Hello!");
@ -79,7 +79,7 @@ impl Message {
/// # Examples
///
/// ```ignore
/// use worker::Message;
/// use llm_worker::Message;
/// let msg = Message::user("こんにちは");
/// ```
pub fn user(content: impl Into<String>) -> Self {

View File

@ -24,7 +24,7 @@ mod private {
/// # Examples
///
/// ```ignore
/// use worker::Worker;
/// use llm_worker::Worker;
///
/// let mut worker = Worker::new(client)
/// .system_prompt("You are helpful.");

View File

@ -33,8 +33,8 @@ use crate::{
/// # Examples
///
/// ```ignore
/// use worker::subscriber::WorkerSubscriber;
/// use worker::timeline::TextBlockEvent;
/// use llm_worker::subscriber::WorkerSubscriber;
/// use llm_worker::timeline::TextBlockEvent;
///
/// struct StreamPrinter;
///

View File

@ -328,7 +328,7 @@ where
/// # Examples
///
/// ```ignore
/// use worker::{Timeline, Handler, TextBlockKind, TextBlockEvent};
/// use llm_worker::{Timeline, Handler, TextBlockKind, TextBlockEvent};
///
/// struct MyHandler;
/// impl Handler<TextBlockKind> for MyHandler {

View File

@ -31,7 +31,7 @@ pub enum ToolError {
/// 通常は`#[tool]`マクロを使用して自動実装します:
///
/// ```ignore
/// use worker::tool;
/// use llm_worker::tool;
///
/// #[tool(description = "Search the web for information")]
/// async fn search(query: String) -> String {
@ -43,7 +43,7 @@ pub enum ToolError {
/// # 手動実装
///
/// ```ignore
/// use worker::tool::{Tool, ToolError};
/// use llm_worker::tool::{Tool, ToolError};
/// use serde_json::{json, Value};
///
/// struct MyTool;

View File

@ -95,7 +95,7 @@ impl<S: WorkerSubscriber + 'static> TurnNotifier for SubscriberTurnNotifier<S> {
/// # Examples
///
/// ```ignore
/// use worker::{Worker, Message};
/// use llm_worker::{Worker, Message};
///
/// // Workerを作成してツールを登録
/// let mut worker = Worker::new(client)
@ -163,7 +163,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// # Examples
///
/// ```ignore
/// use worker::{Worker, WorkerSubscriber, TextBlockEvent};
/// use llm_worker::{Worker, WorkerSubscriber, TextBlockEvent};
///
/// struct MyPrinter;
/// impl WorkerSubscriber for MyPrinter {
@ -211,7 +211,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// # Examples
///
/// ```ignore
/// use worker::Worker;
/// use llm_worker::Worker;
/// use my_tools::SearchTool;
///
/// worker.register_tool(SearchTool::new());
@ -236,7 +236,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// # Examples
///
/// ```ignore
/// use worker::{Worker, WorkerHook, ControlFlow, ToolCall};
/// use llm_worker::{Worker, WorkerHook, ControlFlow, ToolCall};
///
/// struct LoggingHook;
///

View File

@ -8,9 +8,9 @@ use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use futures::Stream;
use worker::llm_client::event::{BlockType, DeltaContent, Event};
use worker::llm_client::{ClientError, LlmClient, Request};
use worker::timeline::{Handler, TextBlockEvent, TextBlockKind, Timeline};
use llm_worker::llm_client::event::{BlockType, DeltaContent, Event};
use llm_worker::llm_client::{ClientError, LlmClient, Request};
use llm_worker::timeline::{Handler, TextBlockEvent, TextBlockKind, Timeline};
use std::sync::atomic::{AtomicUsize, Ordering};
@ -267,7 +267,7 @@ pub fn assert_timeline_integration(subdir: &str) {
});
for event in &events {
let timeline_event: worker::timeline::event::Event = event.clone().into();
let timeline_event: llm_worker::timeline::event::Event = event.clone().into();
timeline.dispatch(&timeline_event);
}

View File

@ -7,10 +7,10 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{Duration, Instant};
use async_trait::async_trait;
use worker::Worker;
use worker::hook::{ControlFlow, HookError, ToolCall, ToolResult, WorkerHook};
use worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
use worker::tool::{Tool, ToolError};
use llm_worker::Worker;
use llm_worker::hook::{ControlFlow, HookError, ToolCall, ToolResult, WorkerHook};
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
use llm_worker::tool::{Tool, ToolError};
mod common;
use common::MockLlmClient;

View File

@ -7,12 +7,12 @@ mod common;
use std::sync::{Arc, Mutex};
use common::MockLlmClient;
use worker::Worker;
use worker::hook::ToolCall;
use worker::llm_client::event::{Event, ResponseStatus, StatusEvent as ClientStatusEvent};
use worker::subscriber::WorkerSubscriber;
use worker::timeline::event::{ErrorEvent, StatusEvent, UsageEvent};
use worker::timeline::{TextBlockEvent, ToolUseBlockEvent};
use llm_worker::Worker;
use llm_worker::hook::ToolCall;
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent as ClientStatusEvent};
use llm_worker::subscriber::WorkerSubscriber;
use llm_worker::timeline::event::{ErrorEvent, StatusEvent, UsageEvent};
use llm_worker::timeline::{TextBlockEvent, ToolUseBlockEvent};
// =============================================================================
// Test Subscriber

View File

@ -9,8 +9,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use schemars;
use serde;
use worker::tool::Tool;
use worker_macros::tool_registry;
use llm_worker::tool::Tool;
use llm_worker_macros::tool_registry;
// =============================================================================
// Test: Basic Tool Generation

View File

@ -11,8 +11,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use async_trait::async_trait;
use common::MockLlmClient;
use worker::Worker;
use worker::tool::{Tool, ToolError};
use llm_worker::Worker;
use llm_worker::tool::{Tool, ToolError};
/// フィクスチャディレクトリのパス
fn fixtures_dir() -> std::path::PathBuf {
@ -100,7 +100,7 @@ fn test_mock_client_from_fixture() {
/// fixtureファイルを使わず、プログラムでイベントを構築してクライアントを作成する。
#[test]
fn test_mock_client_from_events() {
use worker::llm_client::event::Event;
use llm_worker::llm_client::event::Event;
// 直接イベントを指定
let events = vec![
@ -178,7 +178,7 @@ async fn test_worker_tool_call() {
/// テストの独立性を高め、外部ファイルへの依存を排除したい場合に有用。
#[tokio::test]
async fn test_worker_with_programmatic_events() {
use worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
// プログラムでイベントシーケンスを構築
let events = vec![
@ -205,8 +205,8 @@ async fn test_worker_with_programmatic_events() {
/// id, name, inputJSONを正しく抽出できることを検証する。
#[tokio::test]
async fn test_tool_call_collector_integration() {
use worker::llm_client::event::Event;
use worker::timeline::{Timeline, ToolCallCollector};
use llm_worker::llm_client::event::Event;
use llm_worker::timeline::{Timeline, ToolCallCollector};
// ToolUseブロックを含むイベントシーケンス
let events = vec![
@ -222,7 +222,7 @@ async fn test_tool_call_collector_integration() {
// イベントをディスパッチ
for event in &events {
let timeline_event: worker::timeline::event::Event = event.clone().into();
let timeline_event: llm_worker::timeline::event::Event = event.clone().into();
timeline.dispatch(&timeline_event);
}

View File

@ -6,9 +6,9 @@
mod common;
use common::MockLlmClient;
use worker::Worker;
use worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
use worker::{Message, MessageContent};
use llm_worker::Worker;
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
use llm_worker::{Message, MessageContent};
// =============================================================================
// Mutable状態のテスト