add: docs

This commit is contained in:
Keisuke Hirata 2025-08-30 04:22:42 +09:00
parent 9b608f1a54
commit d03172610d
6 changed files with 1194 additions and 0 deletions

55
CLAUDE.md Normal file
View File

@ -0,0 +1,55 @@
# CLAUDE.md
This file provides guidance to Claude Code when working with code in this repository.
## Development Commands
- **Build**: `cargo build --workspace` or `cargo check --workspace` for quick validation
- **Tests**: `cargo test --workspace` (note: tests currently fail due to unsafe environment variable operations)
- **Fix warnings**: `cargo fix --lib -p worker`
- **Dev environment**: Uses Nix flake - run `nix develop` to enter dev shell with Rust toolchain
## Architecture Overview
This is a Rust workspace implementing a multi-LLM worker system with tool calling capabilities. The architecture follows the Core Crate Pattern to avoid circular dependencies:
### Workspace Structure
- **`worker-types`**: Core type definitions (Tool trait, Message, StreamEvent, hook types) - foundational types used by all other crates
- **`worker-macros`**: Procedural macros (`#[tool]`, `#[hook]`) for automatic Tool and WorkerHook trait implementations
- **`worker`**: Main library containing Worker struct, LLM clients, prompt composer, and session management
### Key Components
**Worker**: Central orchestrator that manages LLM interactions, tool execution, and session state. Supports streaming responses via async streams.
**LLM Providers**: Modular clients in `worker/src/llm/` for:
- Anthropic (Claude)
- Google (Gemini)
- OpenAI (GPT)
- xAI (Grok)
- Ollama (local models)
**Tool System**: Uses `#[tool]` macro to convert async functions into LLM-callable tools. Tools must return `ToolResult<T>` and take a single struct argument implementing `Deserialize + Serialize + JsonSchema`.
**Hook System**: Uses `#[hook]` macro to create lifecycle event handlers. Hook functions take `HookContext` and return `(HookContext, HookResult)` tuple, supporting events like OnMessageSend, PreToolUse, PostToolUse, and OnTurnCompleted with regex matcher support.
**Prompt Management**: Handlebars-based templating system for dynamic prompt generation based on roles and context.
**MCP Integration**: Model Context Protocol support for external tool server communication.
## Development Notes
- Edition 2024 Rust with async/await throughout
- Uses `tokio` for async runtime and `reqwest` for HTTP clients
- Environment-based configuration for API keys and base URLs
- Session persistence using JSON serialization
- Streaming responses via `futures-util` streams
- Current test suite has unsafe environment variable operations that need `unsafe` blocks to compile
## Important Patterns
- **Error Handling**: `ToolResult<T>` for tools, `WorkerError` for library operations with automatic conversions
- **Streaming**: All LLM interactions return `StreamEvent` streams for real-time UI updates
- **Tool Registration**: Dynamic tool registration at runtime using boxed trait objects
- **Hook Registration**: Dynamic hook registration with lifecycle event filtering and regex matching
- **Core Crate Pattern**: `worker-macros` references `worker-types` directly via complete paths to prevent circular dependencies

50
docs/architecture.md Normal file
View File

@ -0,0 +1,50 @@
## 各クレートの役割
- **`worker-types`**: 全ての基本型定義Tool, Message, StreamEvent, フック関連型等)
- **`worker-macros`**: プロシージャルマクロ(#[tool])による自動実装
- **`worker`**: メインライブラリWorker, LLMクライアント, プロンプトコンポーザー等)
- **`tools`**: 具体的なツール実装read, write, bash, mcp等
`worker`クレートは`worker-types`の全型を再エクスポートし、外部からは`worker::types::`でアクセス可能です。
## エラーハンドリング戦略
- **`worker-types`**: `ToolResult<T>` - 汎用的なツールエラー(`Result<T, Box<dyn std::error::Error + Send + Sync>>`
- **`worker`**: `WorkerError` - ライブラリ固有エラー設定、ネットワーク、ツール実行、JSON解析等
自動的な型変換(`From` trait実装により、一貫したエラーハンドリングを実現しています。認証エラーの自動検出機能も含まれています。
## マクロ実装
`worker-macros`は完全修飾パスで型を参照し、Core Crate Patternに準拠
```rust
// #[tool]マクロが生成するコード例
impl ::worker_types::Tool for ReadFileTool {
fn name(&self) -> &str { "read_file" }
fn description(&self) -> &str { "ファイルを読み込みます" }
fn parameters_schema(&self) -> ::worker_types::serde_json::Value { /* スキーマ */ }
#[::worker_types::async_trait::async_trait]
async fn execute(&self, args: ::worker_types::serde_json::Value)
-> ::worker_types::ToolResult<::worker_types::serde_json::Value> {
// 元の関数呼び出し
}
}
```
## 利用ガイドライン
- **新しいツール**: `#[tool]`マクロでasync関数を装飾し、`worker::types::ToolResult`を返り値型に使用
- **新しいLLMプロバイダ**: `worker/src/llm/`にクライアント実装を追加し、`LlmClient` enumに追加
- **新しいフック**: `WorkerHook` traitを実装し、`HookManager`で管理
- **型の変更**: `worker-types`で基本型を変更し、コンパイルエラーで影響範囲を確認
- **MCP統合**: `mcp_tool`モジュールを使用してModel Context Protocolサーバーとの連携を実装
## 主要な設計パターン
- **委譲パターン**: `LlmClient` enumでの各プロバイダクライアントへの委譲
- **ストリーミング**: async streamを使用したリアルタイムイベント処理
- **プロンプトテンプレート**: Handlebarsベースの動的プロンプト生成
- **セッション管理**: メッセージ履歴の永続化と復元
- **並列処理**: MCPサーバーの並列初期化による高速化

718
docs/hooks.md Normal file
View File

@ -0,0 +1,718 @@
# Worker Hook システム
Workerのフックシステムは、LLMとの対話プロセスの各段階でカスタムロジックを実行し、システムの動作を柔軟に制御できる強力な機能です。
## 概要
Hooksシステムは、WorkerがLLMとの対話で実行する各フェーズメッセージ処理、ツール実行、ターン完了でユーザー定義の処理を挿入できる機能です。これにより、以下のような高度なカスタマイズが可能になります
- **メッセージの前処理**: ユーザーメッセージにコンテキスト情報を追加
- **ツール実行後の処理**: ファイル操作後の自動コミットやフォーマット
- **ターン完了時の処理**: 統計記録や追加情報の提供
- **ストリーミング制御**: リアルタイム応答の変更・拡張
## アーキテクチャ
### コア型
```rust
// worker-types/src/lib.rs
pub trait WorkerHook: Send + Sync {
fn name(&self) -> &str;
fn hook_type(&self) -> &str;
fn matcher(&self) -> &str;
async fn execute(&self, context: HookContext) -> (HookContext, HookResult);
}
pub struct HookManager {
hooks: HashMap<HookEvent, Vec<Box<dyn WorkerHook>>>,
}
pub enum HookEvent {
OnMessageSend,
PreToolUse,
PostToolUse,
OnTurnCompleted,
}
```
### Hook実行フロー
```
1. ユーザーメッセージ受信
2. OnMessageSend hooks 実行
3. プロンプト構築・LLMへ送信
4. ストリーミング応答開始
5. ツール呼び出し検出
├── PreToolUse hooks 実行
├── ツール実行
└── PostToolUse hooks 実行
6. 応答完了
7. OnTurnCompleted hooks 実行
8. 結果返却
```
## Hook の種類
### フェーズ別Hook
| フェーズ | タイプ名 | 実行タイミング | ストリーミング |
|---------|----------|---------------|---------------|
| メッセージ送信時 | `OnMessageSend` | ユーザーメッセージをLLMに送信する直前 | × |
| ツール実行前 | `PreToolUse` | 特定のツール実行直前 | ○ |
| ツール実行後 | `PostToolUse` | 特定のツール実行完了後 | ○ |
| ターン完了時 | `OnTurnCompleted` | AIの応答が完了した時点 | ○ |
### マッチャーパターン
`PreToolUse`、`PostToolUse`および`OnTurnCompleted`では、`matcher`パラメータで特定のツールに対してのみHookを実行できます
```rust
// Edit または Create ツールの実行後のみ実行
#[hook(hook_type = "PostToolUse", matcher = "Edit|Create")]
// すべてのツールで実行matcher省略時
#[hook(hook_type = "OnTurnCompleted")]
// 正規表現マッチング
#[hook(hook_type = "PostToolUse", matcher = r"^(Read|Write)File.*")]
```
## Hook の作成方法
### 基本構文
```rust
use worker::types::{HookContext, HookResult, Role};
use worker_macros::hook;
#[hook(hook_type = "フェーズ名", matcher = "パターン")]
pub async fn your_hook_name(mut context: HookContext) -> HookResult {
// カスタムロジックをここに実装
HookResult::Continue
}
```
### マクロを使わない実装
```rust
use worker::types::{WorkerHook, HookContext, HookResult};
use async_trait::async_trait;
pub struct CustomHook {
name: String,
}
impl CustomHook {
pub fn new(name: String) -> Self {
Self { name }
}
}
#[async_trait]
impl WorkerHook for CustomHook {
fn name(&self) -> &str {
&self.name
}
fn hook_type(&self) -> &str {
"OnMessageSend"
}
fn matcher(&self) -> &str {
""
}
async fn execute(&self, mut context: HookContext) -> (HookContext, HookResult) {
// Hook処理をここに実装
(context, HookResult::Continue)
}
}
```
## HookContext API
`HookContext`は、Hook内で使用できる情報とメソッドを提供します
### 利用可能なデータ
```rust
pub struct HookContext {
pub content: String, // 処理対象のコンテンツ
pub workspace_path: String, // 現在のワークスペースパス
pub message_history: Vec<Message>, // これまでの会話履歴
pub tools: Vec<DynamicToolDefinition>, // 利用可能なツール一覧
pub variables: HashMap<String, String>, // Hook間で共有可能な変数
pub tool_name: Option<String>, // 実行中のツール名(ツール関連フックのみ)
pub tool_args: Option<serde_json::Value>, // ツール実行引数(ツール関連フックのみ)
pub tool_result: Option<String>, // ツール実行結果PostToolUseのみ
}
```
### 操作メソッド
```rust
impl HookContext {
// ワークスペースでコマンドを実行
pub async fn run_command(&self, command: &str) -> Result<String, Box<dyn std::error::Error>>;
// ツールを実行(将来実装予定)
pub async fn run_tool(&self, tool_name: &str, args: serde_json::Value) -> Result<String, Box<dyn std::error::Error>>;
// メッセージを履歴に追加
pub fn add_message(&mut self, content: String, role: Role);
// コンテンツを書き換え
pub fn set_content(&mut self, content: String);
// 変数の設定・取得
pub fn set_variable(&mut self, key: String, value: String);
pub fn get_variable(&self, key: &str) -> Option<&String>;
// ツール情報の取得
pub fn get_tool(&self, tool_name: &str) -> Option<&DynamicToolDefinition>;
pub fn list_tool_names(&self) -> Vec<&String>;
}
```
### ストリーミング用メソッド
```rust
impl HookContext {
// ストリーミング中にメッセージを送信
pub fn stream_message(&self, content: String, role: Role);
// ストリーミング中にシステム通知を送信
pub fn stream_system_message(&self, content: String);
// ストリーミング中にデバッグ情報を送信
pub fn stream_debug(&self, title: String, data: serde_json::Value);
}
```
## HookResult の種類
Hook関数は以下のいずれかの結果を返す必要があります
```rust
pub enum HookResult {
// 処理を続行
Continue,
// コンテンツを変更して続行
ModifyContent(String),
// システムメッセージを追加して続行
AddMessage(String, Role),
// 複数のメッセージを追加して続行
AddMessages(Vec<Message>),
// ターンを強制完了
Complete,
// エラーでターンを終了
Error(String),
// Hook処理をスキップデバッグ用
Skip,
}
```
## 実用例
### 1. タイムスタンプ付きメッセージ
```rust
/// メッセージ送信時に現在時刻を追加するHook
#[hook(hook_type = "OnMessageSend")]
pub async fn add_timestamp_hook(mut context: HookContext) -> HookResult {
let timestamp = chrono::Local::now().format("%H:%M:%S").to_string();
let enhanced_content = format!("[{}] {}", timestamp, context.content);
HookResult::ModifyContent(enhanced_content)
}
```
### 2. ファイル操作後の自動Git追跡
```rust
/// ファイル編集後にGitステータスを確認するHook
#[hook(hook_type = "PostToolUse", matcher = "Edit|Create|Write")]
pub async fn file_change_notification_hook(mut context: HookContext) -> HookResult {
match context.run_command("git status --porcelain").await {
Ok(output) => {
if !output.trim().is_empty() {
context.add_message(
format!("📝 ファイル変更を検出:\n{}", output),
Role::System,
);
HookResult::Continue
} else {
HookResult::Continue
}
}
Err(_) => HookResult::Continue,
}
}
```
### 3. 長い応答への注意喚起
```rust
/// 長い応答が生成された際に注意を促すHook
#[hook(hook_type = "OnTurnCompleted")]
pub async fn long_response_warning_hook(context: HookContext) -> HookResult {
if context.content.len() > 2000 {
HookResult::AddMessage(
"⚠️ 長い応答が生成されました。スクロールして全体を確認してください。".to_string(),
Role::System,
)
} else {
HookResult::Continue
}
}
```
### 4. ツール実行前のバリデーション
```rust
/// 危険なコマンドの実行前に確認を行うHook
#[hook(hook_type = "PreToolUse", matcher = "Execute|Shell")]
pub async fn dangerous_command_hook(context: HookContext) -> HookResult {
if let Some(args) = &context.tool_args {
if let Some(command) = args.get("command").and_then(|v| v.as_str()) {
let dangerous_commands = ["rm -rf", "format", "dd if="];
for dangerous in &dangerous_commands {
if command.contains(dangerous) {
return HookResult::Error(format!(
"危険なコマンド「{}」の実行が阻止されました: {}",
dangerous, command
));
}
}
}
}
HookResult::Continue
}
```
### 5. 自動読み取りHook (TUI用)
```rust
/// ファイル作成・編集後に自動的にファイル内容を読み取るHook
#[hook(hook_type = "PostToolUse", matcher = "Edit|Create|Write")]
pub async fn auto_read_hook(mut context: HookContext) -> HookResult {
// ツール実行結果からファイルパスを抽出
if let Some(tool_result) = &context.tool_result {
if let Ok(result_data) = serde_json::from_str::<serde_json::Value>(tool_result) {
if let Some(file_path) = result_data.get("file_path").and_then(|v| v.as_str()) {
// ファイル内容を読み取って表示
match tokio::fs::read_to_string(file_path).await {
Ok(content) => {
context.stream_system_message(format!(
"📄 **{}** の内容:\n```\n{}\n```",
file_path, content
));
}
Err(e) => {
context.stream_system_message(format!(
"❌ ファイル読み取りエラー ({}): {}",
file_path, e
));
}
}
}
}
}
HookResult::Continue
}
```
## Hook の登録と管理
### Workerへの登録
```rust
use worker::Worker;
// 単一のHookを登録
worker.register_hook(Box::new(YourHook::new()));
// 複数のHookを一括登録
let hooks = vec![
Box::new(TimestampHook::new()),
Box::new(FileNotificationHook::new()),
Box::new(DebugHook::new()),
];
worker.register_hooks(hooks);
// TUI用デフォルトHookの登録
let tui_hooks = crate::tui::hooks::get_default_hooks();
worker.register_hooks(tui_hooks);
```
### Hook の実行順序
同じフェーズで複数のHookが登録されている場合、登録順に実行されます。先に実行されたHookの結果コンテキストの変更などは、後続のHookに引き継がれます。
```rust
// 実行順序の例
worker.register_hook(Box::new(TimestampHook)); // 1番目
worker.register_hook(Box::new(ValidationHook)); // 2番目
worker.register_hook(Box::new(LoggingHook)); // 3番目
```
### Hook の中断
`HookResult::Complete`または`HookResult::Error`を返すHookがあると、それ以降のHookは実行されず、処理が中断されます。
### Hook の動的管理
```rust
impl Worker {
// Hook一覧を取得
pub fn list_hooks(&self) -> Vec<(&str, &str)>; // (name, hook_type)
// 特定のHookを削除
pub fn remove_hook(&mut self, hook_name: &str) -> bool;
// フェーズ別Hookを削除
pub fn remove_hooks_by_phase(&mut self, hook_type: &str);
// すべてのHookをクリア
pub fn clear_hooks(&mut self);
}
```
## ストリーミング処理
### ストリーミング中のHook実行
```rust
// worker/src/lib.rs の process_with_shared_state より
stream! {
// ... LLM応答処理中 ...
// ツール呼び出し検出時
if let Some(tool_calls) = &response.tool_calls {
for tool_call in tool_calls {
// PreToolUse hooks 実行
let (context, hook_result) = execute_hooks(
HookEvent::PreToolUse,
tool_call.name.clone()
).await;
match hook_result {
HookResult::Error(msg) => {
yield Ok(StreamEvent::Error(msg));
continue;
}
HookResult::Complete => break,
_ => {}
}
// ツール実行
let result = execute_tool(tool_call).await;
// PostToolUse hooks 実行(ストリーミング中)
let (context, hook_result) = execute_hooks(
HookEvent::PostToolUse,
tool_call.name.clone()
).await;
// Hook結果を即座にストリーミング
if let HookResult::AddMessage(msg, role) = hook_result {
yield Ok(StreamEvent::HookMessage {
hook_name: "PostToolUse".to_string(),
content: msg,
role,
});
}
}
}
}
```
## ベストプラクティス
### 1. エラーハンドリング
```rust
#[hook(hook_type = "OnTurnCompleted")]
pub async fn robust_hook(mut context: HookContext) -> HookResult {
match context.run_command("potentially_failing_command").await {
Ok(output) => {
// 成功時の処理
context.add_message(format!("実行完了: {}", output), Role::System);
HookResult::Continue
}
Err(error) => {
// エラーログを記録するが、処理は継続
tracing::warn!("Hook実行時にエラー: {}", error);
HookResult::Continue
}
}
}
```
### 2. パフォーマンス配慮
```rust
#[hook(hook_type = "OnTurnCompleted")]
pub async fn performance_aware_hook(context: HookContext) -> HookResult {
// 重い処理は条件分岐で制限
if context.content.len() > 10000 {
// 大きなコンテンツの場合はスキップ
return HookResult::Skip;
}
// 非同期処理は適切にawaitする
let result = tokio::time::timeout(
Duration::from_secs(5),
expensive_operation(&context)
).await;
match result {
Ok(output) => HookResult::AddMessage(output, Role::System),
Err(_) => {
tracing::warn!("Hook処理がタイムアウトしました");
HookResult::Continue
}
}
}
```
### 3. 設定可能なHook
```rust
#[hook(hook_type = "OnMessageSend")]
pub async fn configurable_hook(mut context: HookContext) -> HookResult {
// 環境変数で動作を制御
let enabled = std::env::var("HOOK_ENABLED")
.unwrap_or_default()
.parse::<bool>()
.unwrap_or(false);
if !enabled {
return HookResult::Skip;
}
// 設定ファイルからオプション読み込み
let config_path = format!("{}/.nia/hook_config.json", context.workspace_path);
if let Ok(config_content) = tokio::fs::read_to_string(&config_path).await {
if let Ok(config) = serde_json::from_str::<HookConfig>(&config_content) {
// 設定に基づく処理
return process_with_config(&mut context, &config).await;
}
}
HookResult::Continue
}
```
### 4. 条件付きHook
```rust
#[hook(hook_type = "PostToolUse", matcher = ".*")]
pub async fn conditional_hook(context: HookContext) -> HookResult {
// ワークスペースの種類に応じて処理を変更
let is_git_repo = context.run_command("git status").await.is_ok();
let is_rust_project = tokio::fs::metadata(
format!("{}/Cargo.toml", context.workspace_path)
).await.is_ok();
match (is_git_repo, is_rust_project) {
(true, true) => {
// Rustプロジェクト + Git
context.run_command("cargo fmt").await.ok();
HookResult::AddMessage("Rustコードをフォーマットしました".to_string(), Role::System)
}
(true, false) => {
// その他のGitプロジェクト
HookResult::AddMessage("Gitプロジェクトで作業中です".to_string(), Role::System)
}
_ => HookResult::Continue
}
}
```
## デバッグとテスト
### Hook のテスト
```rust
#[cfg(test)]
mod tests {
use super::*;
use worker::types::*;
#[tokio::test]
async fn test_timestamp_hook() {
let mut context = HookContext {
content: "Hello, world!".to_string(),
workspace_path: "/tmp".to_string(),
message_history: vec![],
tools: vec![],
variables: HashMap::new(),
tool_name: None,
tool_args: None,
tool_result: None,
};
let result = add_timestamp_hook(context).await;
match result {
HookResult::ModifyContent(content) => {
assert!(content.contains("Hello, world!"));
assert!(content.contains("["));
assert!(content.contains("]"));
}
_ => panic!("Expected ModifyContent"),
}
}
}
```
### Hook のデバッグ
```rust
#[hook(hook_type = "OnMessageSend")]
pub async fn debug_hook(context: HookContext) -> HookResult {
tracing::debug!(
"Hook実行 - コンテンツ長: {}, ツール数: {}, 履歴数: {}",
context.content.len(),
context.tools.len(),
context.message_history.len()
);
// デバッグ情報をストリーミング
context.stream_debug(
"Hook Debug Info".to_string(),
serde_json::json!({
"content_length": context.content.len(),
"tool_count": context.tools.len(),
"history_count": context.message_history.len(),
"workspace": context.workspace_path
})
);
HookResult::Continue
}
```
## 高度なHookパターン
### 状態を持つHook
```rust
pub struct StatefulHook {
counter: Arc<Mutex<u32>>,
}
impl StatefulHook {
pub fn new() -> Self {
Self {
counter: Arc::new(Mutex::new(0)),
}
}
}
#[async_trait]
impl WorkerHook for StatefulHook {
fn name(&self) -> &str { "stateful_hook" }
fn hook_type(&self) -> &str { "OnTurnCompleted" }
fn matcher(&self) -> &str { "" }
async fn execute(&self, mut context: HookContext) -> (HookContext, HookResult) {
let mut count = self.counter.lock().unwrap();
*count += 1;
context.set_variable("turn_count".to_string(), count.to_string());
if *count % 10 == 0 {
(
context,
HookResult::AddMessage(
format!("🎉 {}回目のターンです!", count),
Role::System
)
)
} else {
(context, HookResult::Continue)
}
}
}
```
### チェーン可能なHook
```rust
pub struct HookChain {
hooks: Vec<Box<dyn WorkerHook>>,
}
impl HookChain {
pub fn new() -> Self {
Self { hooks: Vec::new() }
}
pub fn add_hook(mut self, hook: Box<dyn WorkerHook>) -> Self {
self.hooks.push(hook);
self
}
}
#[async_trait]
impl WorkerHook for HookChain {
fn name(&self) -> &str { "hook_chain" }
fn hook_type(&self) -> &str { "OnMessageSend" }
fn matcher(&self) -> &str { "" }
async fn execute(&self, mut context: HookContext) -> (HookContext, HookResult) {
for hook in &self.hooks {
let (new_context, result) = hook.execute(context).await;
context = new_context;
match result {
HookResult::Continue | HookResult::Skip => continue,
other => return (context, other),
}
}
(context, HookResult::Continue)
}
}
```
## よくある質問
### Q: Hookはどのように読み込まれますか
A: 現在、HookはRustコードとしてコンパイル時に組み込まれます。`#[hook]`マクロを使用するか、`WorkerHook`トレイトを実装して`worker.register_hook()`で登録します。
### Q: Hook内でファイルシステムにアクセスできますか
A: はい。`run_command`メソッドを使用してシェルコマンドを実行できるほか、Rustの標準ライブラリやtokioを使用した直接的なファイル操作も可能です。
### Q: Hook間でデータを共有できますか
A: `HookContext`の`variables`を使用して、同一ターン内のHook間でデータを共有できます。永続的なデータ共有には、ファイルやデータベースを使用してください。
### Q: ストリーミング中にHookの結果を表示できますか
A: はい。`context.stream_message()`や`context.stream_system_message()`を使用することで、ストリーミング中にリアルタイムでメッセージを送信できます。
### Q: Hookでエラーが発生した場合はどうなりますか
A: `HookResult::Error`を返すと、そのターンは中断されます。継続したい場合は、エラーをログに記録して`HookResult::Continue`を返してください。
## 関連ドキュメント
- [worker.md](worker.md) - Worker全体の文書
- [worker-macro.md](worker-macro.md) - マクロシステム
- `worker/src/lib.rs` - Hook実装コード
- `worker-types/src/lib.rs` - Hook型定義
- `nia-cli/src/tui/hooks/` - TUI用Hook実装例

71
docs/prompt-composer.md Normal file
View File

@ -0,0 +1,71 @@
# PromptComposer
テンプレートベースのプロンプト構築システム。Handlebarsテンプレートエンジンによる動的プロンプト生成。
## 基本使用方法
```rust
// 初期化
let composer = PromptComposer::from_config_file("role.yaml", context)?;
composer.initialize_session(&messages)?;
// プロンプト構築
let messages = composer.compose(&user_messages)?;
```
## テンプレート構文
### 変数展開
```handlebars
{{workspace.project_name}} # プロジェクト名
{{workspace.project_type}} # プロジェクト種別
{{model.provider}}/{{model.model_name}} # モデル情報
{{tools_schema}} # ツールスキーマ
```
### 条件分岐
```handlebars
{{#if workspace.has_nia_md}}
Project info: {{workspace_content}}
{{/if}}
{{#eq workspace.project_type "Rust"}}
Focus on memory safety and performance.
{{/eq}}
```
### 繰り返し処理
```handlebars
{{#each tools}}
- **{{name}}**: {{description}}
{{/each}}
```
### パーシャルテンプレート
```handlebars
{{> header}}
{{> coding_guidelines}}
{{> footer}}
```
## カスタムヘルパー
### include_file
外部ファイルを読み込み:
```handlebars
{{include_file "~/.config/nia/templates/guidelines.md"}}
```
### workspace_content
ワークスペースのnia.md内容を取得
```handlebars
{{workspace_content}}
```
## 利用可能なコンテキスト変数
- `workspace`: プロジェクト情報root_path、project_type、git_info等
- `model`: LLMモデル情報provider、model_name、capabilities
- `session`: セッション情報conversation_id、message_count
- `user_input`: ユーザー入力内容
- `tools_schema`: ツール定義JSON

115
docs/worker-macro.md Normal file
View File

@ -0,0 +1,115 @@
# Worker Macro Documentation
## `#[tool]` マクロ
関数をLLMツールとして登録するためのプロシージャルマクロです。関数のドキュメントコメントを自動抽出してツール説明として利用し、JSONスキーマ生成、動的ツール登録に必要な`Tool` trait実装を自動生成します。
**重要**: このマクロはCore Crate Patternに基づき`worker-macros`クレートで実装され、`worker-types`クレートの型を完全修飾パスで直接参照しています。これにより循環依存を防ぎ、保守性の高いアーキテクチャを実現しています。
### 基本的な使用方法
```rust
use worker_macros::tool;
use worker::types::ToolResult; // worker-typesからの再エクスポート
use serde::{Deserialize, Serialize};
use schemars::JsonSchema;
#[derive(Deserialize, Serialize, JsonSchema)]
struct CalculateArgs {
/// 第一の数値
a: i32,
/// 第二の数値
b: i32,
}
/// 二つの数値を加算し、結果を返します。
/// このコメントはLLMに送られるツール説明になります。
#[tool]
async fn calculate(args: CalculateArgs) -> ToolResult<i32> {
Ok(args.a + args.b)
}
```
### 動的ツール登録での使用方法
```rust
// Worker初期化
let mut worker = Worker::new(
LlmProvider::Gemini,
"gemini-1.5-flash",
&api_keys,
None
)?;
// マクロで生成されたツールを登録
worker.register_tool(Box::new(CalculateTool::new()))?;
// ツール実行
let args = serde_json::json!({"a": 10, "b": 20});
let result = worker.execute_tool("calculate", args).await?;
println!("Result: {}", result); // Result: 30
```
### 機能
1. **自動ドキュメント抽出**: 関数のドキュメントコメント(`///`)を自動抽出してツール説明として使用
2. **JSONスキーマ生成**: `schemars`を使用して引数型からJSONスキーマを自動生成
3. **フォールバック説明**: ドキュメントコメントがない場合、関数名からデフォルト説明を生成
4. **Tool trait実装**: 動的ツール登録に必要な全メソッドを自動実装
5. **エラーハンドリング**: `ToolResult<T>`型を使用した一貫したエラー処理
### 要件
- **関数シグニチャ**: `async fn`である必要があります
- **引数型**: 単一の構造体で`Deserialize + Serialize + JsonSchema`トレイトを実装必要
- **返り値型**: `ToolResult<T>`で`T`は`Serialize + JsonSchema`トレイトを実装必要
- **エラー型**: `ToolResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>``worker-types`で定義)
**注意**: プリミティブ型(`i32`, `String`等)を直接引数に使用することはできません。必ず構造体でラップしてください。
### 生成されるコード
マクロは以下の要素を自動生成します:
1. **元の関数**: 元のasync関数をそのまま保持
2. **Tool構造体**: 関数名からPascalCaseで構造体名を生成`read_file` → `ReadFileTool`
3. **Tool trait実装**: 以下のメソッドを自動実装:
- `name()`: 関数名を返す
- `description()`: ドキュメントコメントから抽出
- `parameters_schema()`: 引数型からJSONスキーマを生成
- `execute()`: 元の関数を呼び出して結果をシリアライズ
**実装詳細**: マクロは`::worker_types::`名前空間で型を参照し、`async_trait`を使用してtrait実装を行います。
### 自動生成される構造体名
関数名からsnake_caseをPascalCaseに変換し、`Tool`サフィックスを付加:
- `read_file``ReadFileTool`
- `calculate_sum``CalculateSumTool`
- `fetch_data``FetchDataTool`
- `send_email``SendEmailTool`
- `process_image``ProcessImageTool`
**コンストラクタ**: 生成された構造体には`new()`メソッドが自動的に提供されます。
## Core Crate Patternとの統合
`#[tool]`マクロはCore Crate Patternの利点を最大限に活用
- **循環依存の完全回避**: `worker-macros``worker-types`の一方向依存のみ
- **コンパイル時型安全性**: 型チェックで実行時エラーを事前検出
- **自動更新保守性**: `worker-types`の型変更がマクロ生成コードに自動反映
- **拡張容易性**: 新しいtraitや機能を簡単に追加可能
- **パフォーマンス**: コンパイル時コード生成で実行時オーバーヘッドなし
## メリットと使用事例
このアーキテクチャにより、以下のような利点が得られます:
- **簡单なツール作成**: 関数にマクロを付けるだけでLLMツール完成
- **ドキュメント連動**: コメントがそのままLLMのツール説明に
- **型安全なインターフェース**: JSONシリアライゼーションを自動処理
- **動的登録サポート**: 実行時ツールの追加・削除が可能
- **MCP統合**: Model Context Protocolとのシームレスな連携
これにより、柔軟性と使いやすさを兼ね備えたツールシステムが実現されています。

185
docs/worker.md Normal file
View File

@ -0,0 +1,185 @@
# `worker` ライブラリ
`worker`は、LLM大規模言語モデルとの対話を抽象化し、複数のLLMプロバイダを統一的に扱うための共通ライブラリです。動的ツール登録システム、フックシステム、MCPプロトコル統合を提供します。
## アーキテクチャ
**Core Crate Pattern**を採用:`worker-types`(基本型)→ `worker-macros`(マクロ)→ `worker`(メインライブラリ)
循環依存を解消し、型安全性と保守性を向上させています。
## 概要
LLMプロバイダGemini, Claude, OpenAI, Ollama, XAIを統一インターフェースで利用できます。`Worker`構造体が中心となり、プロンプト管理、ツール登録、フックシステム、MCPサーバー統合を提供します。
## 主要なコンポーネント
### `Worker`
LLMとの対話における中心的な構造体です。
- **LLMクライアントの保持**: `LlmClient` enumを通じて複数プロバイダの統一インターフェースを提供
- **プロンプト管理**: `PromptComposer`によるテンプレートベースのプロンプト組み立て
- **ストリーミング処理**: リアルタイムイベントストリーム(`process_task_stream`)とメッセージ履歴管理(`process_task_with_history`
- **動的ツール管理**: 実行時ツール登録・実行、MCPサーバー統合
- **フックシステム**: メッセージ送信、ツール利用、ターン完了時の拡張ポイント
- **セッション管理**: メッセージ履歴の保存・復元機能
### `Tool` トレイト
動的ツール登録の基盤。`worker-types`で定義され、`#[tool]`マクロで自動実装可能です。
### `StreamEvent`
LLMストリーミング応答のイベント型。テキストチャンク、ツール呼び出し、ツール結果、完了通知、デバッグ情報、フックメッセージをサポートします。
### `LlmClient` Enum
各LLMプロバイダクライアントの統合enum。`LlmClientTrait`を実装し、統一されたストリーミング対話と接続確認機能を提供します。
### フックシステム
- `WorkerHook` トレイト: カスタムフック実装の基盤
- `HookManager`: フック登録・実行管理
- `HookEvent`: OnMessageSend、PreToolUse、PostToolUse、OnTurnCompleted
- `HookContext`: フック実行時のコンテキスト情報
### その他の主要型
- `Message`: LLM対話のメッセージrole + content + tool_calls
- `LlmDebug`: デバッグログ制御と詳細出力
- `ModelInfo`: モデル情報とサポート機能
- `SessionData`: セッション永続化データ
## 基本的な使用方法
```rust
use worker::{Worker, LlmProvider, types::{Message, Role, StreamEvent}};
use futures_util::StreamExt;
// Worker初期化
let mut worker = Worker::new(
LlmProvider::Gemini,
"gemini-1.5-flash",
&api_keys,
None
)?;
// ツール登録(オプション)
worker.register_tool(Box::new(some_tool))?;
// メッセージ送信とストリーム処理(履歴あり)
let mut stream = worker.process_task_with_history(
"Hello, how are you?".to_string(),
None
).await;
while let Some(Ok(event)) = stream.next().await {
match event {
StreamEvent::Chunk(text) => print!("{}", text),
StreamEvent::ToolCall(call) => println!("Tool: {}", call.name),
StreamEvent::ToolResult { tool_name, result } => {
println!("Result from {}: {:?}", tool_name, result);
},
StreamEvent::Completion(_) => break,
_ => {}
}
}
```
### ツール登録と実行
```rust
// 個別ツール登録
worker.register_tool(Box::new(ReadFileTool::new()))?;
// MCPサーバーからのツール登録
let mcp_config = McpServerConfig {
name: "filesystem".to_string(),
command: "npx".to_string(),
args: vec!["-y".to_string(), "@modelcontextprotocol/server-filesystem".to_string()],
env: HashMap::new(),
};
worker.register_mcp_tools(mcp_config).await?;
// 複数MCPサーバーの並列初期化
worker.queue_mcp_server(config1);
worker.queue_mcp_server(config2);
worker.init_mcp_servers().await?;
// ツール実行
let result = worker.execute_tool("read_file", json!({"path": "./file.txt"})).await?;
```
### フックシステム
```rust
// カスタムフック実装例
struct MyHook;
#[async_trait::async_trait]
impl WorkerHook for MyHook {
fn name(&self) -> &str { "my_hook" }
fn hook_type(&self) -> &str { "OnMessageSend" }
fn matcher(&self) -> &str { "" }
async fn execute(&self, mut context: HookContext) -> (HookContext, HookResult) {
// メッセージ前処理
let new_content = format!("[処理済み] {}", context.content);
context.set_content(new_content);
(context, HookResult::Continue)
}
}
// フック登録
worker.register_hook(Box::new(MyHook));
```
### 主要API
- `register_tool()`: ツール登録
- `register_mcp_tools()`: MCPサーバーからのツール登録
- `register_hook()`: フック登録
- `get_tools()`: 登録済みツール一覧
- `execute_tool()`: ツール実行
- `process_task_stream()`: LLMストリーミング対話履歴なし
- `process_task_with_history()`: メッセージ履歴付きストリーミング対話
- `get_session_data()`: セッションデータ取得
- `load_session()`: セッション復元
## 型システム
**Core Crate Pattern**により型を分離:
- `worker-types`: 基本型Tool, Message, StreamEvent等
- `worker`: 実装とエラー型WorkerError等
後方互換性のため`worker::types::`経由で全型にアクセス可能。
## 主要型
- `ToolResult<T>`: ツール実行結果
- `DynamicToolDefinition`: ツール定義情報
- `WorkerError`: ライブラリ固有エラー型
## LLMプロバイダサポート
全プロバイダでストリーミング対話、ツール呼び出し、モデル一覧取得、接続確認を完全実装:
- Gemini (Google)
- Claude (Anthropic)
- OpenAI
- Ollama
- XAI (Grok)
## 主要機能
- **Core Crate Pattern**: 循環依存解消による保守性向上
- **動的ツール登録**: 実行時ツール追加・実行
- **#[tool]マクロ**: 関数からツール自動生成
- **フックシステム**: メッセージ送信・ツール利用・ターン完了時の拡張ポイント
- **ストリーミング対話**: リアルタイムLLM応答とイベント処理
- **複数プロバイダ**: 統一インターフェースでの5つのLLMプロバイダサポート
- **MCP統合**: Model Context Protocolサーバーとの動的連携
- **セッション管理**: メッセージ履歴の永続化・復元
- **ワークスペース検出**: Git情報を含む作業環境の自動認識
- **型安全性**: 強い型チェックとエラーハンドリング
- **並列処理**: MCPサーバーの並列初期化による高速化