# hefa-core
Rust 製 LLM 基盤ライブラリ。設計や詳細仕様は `docs/` を参照してください。
## 開発手順
1. `.env.example` をコピーして `.env` を作成し、必要な API キーを設定します。
2. `cargo fmt` / `cargo clippy` / `cargo test` で品質を維持します。
3. docs:
- `docs/hefa-core-spec.md` 要件定義
- `docs/AGENTS.md` Agent 層仕様
- `docs/PLAN.md` 実装チェックリスト
## 実行環境に関する注記
- `OPENAI_API_KEY` は環境変数として既に投入済みなので `.env` には記載せず、そのまま利用してください。
- LM Studio / Ollama は 192.168.11.16 上で稼働しています。
- LM Studio: `openai/gpt-oss-20b` モデルを OpenAI 互換エンドポイント経由で利用可能。
- Ollama: `gpt-oss:20b` モデルを利用可能。
## トレース切替
Agent では `hefa_core::Tracer` を差し替えることでトレースの保存先を切り替えられます。
```rust
use hefa_core::{StdoutTracer, SqliteTracer};
let tracer = StdoutTracer::default(); // 開発時は標準出力
// もしくは永続化
let tracer = SqliteTracer::new("trace.db")?;
let agent = agent.with_tracer(std::sync::Arc::new(tracer));
```
`SqliteTracer` には `list_recent_spans` / `list_events` があり、SQLite 上に保存された span / event の検索が可能です。
## LLM クライアント
`hefa_core::LLMClient` はプロバイダ種別に応じて自動で OpenAI Responses API / OpenAI 互換 ChatCompletion API を呼び分けます。以下は「エンジニアの日報からリリースノートのドラフトを作る」例です。
```rust
use hefa_core::{
llm::StructuredOutput, Agent, AgentConfig, ProviderKind, Tool, ToolError, ToolResult,
};
use async_trait::async_trait;
use serde_json::{json, Value};
/// Tool used when LLM asks for metadata to enrich the release note.
/// LLM が「リリースノートの下書きを作って」と要求した際に呼ばれる Hook。
struct ReleaseNoteTool;
#[async_trait]
impl Tool for ReleaseNoteTool {
fn name(&self) -> &'static str {
"release_note"
}
fn json_schema(&self) -> Value {
json!({
"type": "object",
"properties": {
"feature": { "type": "string" },
"impact": { "type": "string" }
},
"required": ["feature", "impact"]
})
}
async fn call(&self, args: Value) -> Result<ToolResult, ToolError> {
let feature = args
.get("feature")
.and_then(Value::as_str)
.ok_or_else(|| ToolError::InvalidInput("missing feature".into()))?;
let impact = args
.get("impact")
.and_then(Value::as_str)
.ok_or_else(|| ToolError::InvalidInput("missing impact".into()))?;
Ok(ToolResult {
content: json!({ "draft": format!("Feature: {feature} — Impact: {impact}") }),
})
}
}
let structured_output = StructuredOutput::new(json!({
"type": "object",
"properties": {
"summary": { "type": "string" }
}
}));
let mut agent = Agent::new(AgentConfig {
instruction: "You are a release-note assistant. Respond in 1 sentence.".into(),
provider: ProviderKind::OpenAi,
model: "gpt-4o-mini".into(),
structured_output: Some(structured_output),
tools: vec![Box::new(ReleaseNoteTool)],
})?;
let prompt = "Update: Added offline search with 50% faster indexing.";
let result = agent.invoke(prompt).await?;
println!("Release note: {}", result.response.content);
```
出力例:
```
Release note: OK — Feature: Added offline search with 50% faster indexing.
```
## チュートリアル / Examples
1. **ステップ1 – LLM 選択 & Hello**
まずは Provider とモデルを決め、最小構成の Agent で “Hello” を返す例 (`examples/basic_agent.rs`) を実行します。
```
cargo run --example basic_agent
```
2. **ステップ2 – Tool 追加**
`examples/tool_agent.rs` では LLM の `tool_calls` を処理する Hook を登録し、ReleaseNoteTool で外部ロジックを呼び出す流れを体験します。
```
cargo run --example tool_agent
```
3. **ステップ3 – 構造化出力**
JSON Schema を指定して LLM の最終回答を構造化する例は `examples/structured_agent.rs` で確認できます。README で紹介した `StructuredOutput::from_type::<T>()` も併用してください。
```
cargo run --example structured_agent
```
4. **ステップ4 – トレース**
`examples/trace_agent.rs` は `SqliteTracer` を使って span / event を保存し、`list_recent_spans` で属性フィルタ(例: `instruction` に “release” が含まれる)をかけて検索する例です。`Tracer` には `start_trace` / `start_child_span` / `record_event` が用意されており、Agent はこれらを内部で呼び出します。
```
cargo run --example trace_agent
```
5. **ステップ5 – Release Note Agent (統合)**
`examples/release_note.rs` で、Tool + 構造化出力 + トレースの統合例を確認できます。
```
cargo run --example release_note
```
## ライブ接続テスト
OpenAI / LM Studio / Ollama に接続する実機テストは `tests/live_llm.rs` にあります。
以下の環境変数をセットして個別に実行してください。
```
HEFA_LIVE_OPENAI=1 cargo test live_openai_responses
HEFA_LIVE_LMSTUDIO=1 LMSTUDIO_API_BASE=http://192.168.11.16:1234/v1 cargo test live_lmstudio_chat
HEFA_LIVE_OLLAMA=1 OLLAMA_API_BASE=http://192.168.11.16:11434 cargo test live_ollama_chat
```
## 構造化出力と `schemars` 連携
Feature `schema` を有効にすると、`schemars::JsonSchema` を derive した型から
`StructuredOutput::from_type::<T>()` で JSON Schema を生成できます。LLM へ構造化出力を要求する際に役立ちます。
```
cargo add schemars --features derive
cargo test --features schema
```
```rust
use hefa_core::llm::StructuredOutput;
use schemars::JsonSchema;
use serde::Serialize;
#[derive(Serialize, JsonSchema)]
struct MyAnswer {
summary: String,
tags: Vec<String>,
}
let output = StructuredOutput::from_type::<MyAnswer>();
```
## ライセンス
TBD