merge: llm engine rename
This commit is contained in:
commit
20cfcfaca3
|
|
@ -0,0 +1 @@
|
||||||
|
{"id":"orch-plan-20260625-132518-1","ticket_id":"00001KVZD10ED","kind":"accepted_plan","accepted_plan":{"summary":"Ticket `00001KVZD10ED` は implementation_ready。専用 worktree `/home/hare/Projects/yoi/.worktree/00001KVZD10ED-llm-engine-rename` と branch `work/00001KVZD10ED-llm-engine-rename` で、`llm-worker` / `llm-worker-macros` を `llm-engine` / `llm-engine-macros` に rename し、主要 public turn-engine 型を `Worker` から `Engine` 系へ rename する。責務移動や worker-runtime 実装、互換 alias は non-goal。","branch":"work/00001KVZD10ED-llm-engine-rename","worktree":"/home/hare/Projects/yoi/.worktree/00001KVZD10ED-llm-engine-rename","role_plan":"Orchestrator: accept/routing, worktree creation, final integration/validation/cleanup. Coder: repository-wide crate/type rename in dedicated child worktree. Reviewer: read-only review focusing on mechanical rename completeness, no compatibility alias, no behavior/authority movement, and validation evidence."},"author":"yoi-orchestrator","at":"2026-06-25T13:25:18Z"}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
title: 'llm-worker crateをllm-engineへ改名する'
|
title: 'llm-worker crateをllm-engineへ改名する'
|
||||||
state: 'queued'
|
state: 'done'
|
||||||
created_at: '2026-06-25T12:45:38Z'
|
created_at: '2026-06-25T12:45:38Z'
|
||||||
updated_at: '2026-06-25T13:24:26Z'
|
updated_at: '2026-06-25T13:57:29Z'
|
||||||
assignee: null
|
assignee: null
|
||||||
queued_by: 'workspace-panel'
|
queued_by: 'workspace-panel'
|
||||||
queued_at: '2026-06-25T13:24:26Z'
|
queued_at: '2026-06-25T13:24:26Z'
|
||||||
|
|
|
||||||
|
|
@ -30,4 +30,288 @@ Marked ready by `yoi ticket state`.
|
||||||
Ticket を `workspace-panel` が queued にしました。
|
Ticket を `workspace-panel` が queued にしました。
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: decision author: yoi-orchestrator at: 2026-06-25T13:26:00Z -->
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Routing decision: implementation_ready
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
- Dashboard Queue により人間が Orchestrator routing を許可した queued Ticket として確認した。
|
||||||
|
- Ticket body は `llm-worker` / `llm-worker-macros` crate rename、Rust import path rename、major public type rename (`Worker` -> `Engine`, config/result/error names)、repo-wide references、validation を具体的に列挙している。
|
||||||
|
- `TicketRelationQuery` は blocking relation 0 件、`TicketOrchestrationPlanQuery` は routing 前 plan 0 件だった。
|
||||||
|
- bounded context check で repository-wide `llm-worker` / `llm_worker` references を確認し、主な変更 surface は `crates/llm-worker`, `crates/llm-worker-macros`, workspace/dependency metadata, `pod`/`manifest` imports, docs/tests/examples/Nix/Cargo.lock である。これは大きめだが mechanical rename task として明確で、責務移動や WorkerRuntime 実装は non-goal として分離されている。
|
||||||
|
- risk は API/naming churn だが、受け入れ条件と validation が明確で、Coder の bounded implementation latitude に収まる。planning return が必要な未決定事項は見つからない。
|
||||||
|
|
||||||
|
Evidence checked:
|
||||||
|
- Ticket body / thread: `item.md`, `thread.md`。thread は create、planning->ready、ready->queued のみで未解決 blocker は記録されていない。
|
||||||
|
- Relations / orchestration plan: relation 0 件、routing 前 plan 0 件。accepted plan `orch-plan-20260625-132518-1` を記録済み。
|
||||||
|
- Code context: `git grep` で `crates/llm-worker`, `crates/llm-worker-macros`, `llm_worker`, `llm_worker_macros`, `Worker` imports/examples/tests/docs references を確認。
|
||||||
|
- Workspace state: `/home/hare/Projects/yoi/.worktree/orchestration` は clean。queued Ticket はこの 1 件、inprogress Ticket は 0 件。
|
||||||
|
|
||||||
|
IntentPacket:
|
||||||
|
|
||||||
|
Intent:
|
||||||
|
- LLM turn-processing crate を `llm-worker` から `llm-engine` へ rename し、public turn-engine主体型を `Worker` から `Engine` 系へ rename することで、今後の Runtime-scoped Worker concept と衝突しない package/API naming に整理する。
|
||||||
|
|
||||||
|
Binding decisions / invariants:
|
||||||
|
- `llm-engine` は LLM turn engine。Runtime / Worker identity / process lifecycle / socket protocol / session file authority は持たない。
|
||||||
|
- 責務移動は最小限。provider request/stream handling、tool-call loop、reasoning/usage/retry/continuation/history/callback semantics は変えない。
|
||||||
|
- `crates/llm-worker` / `crates/llm-worker-macros` は残さない。
|
||||||
|
- `llm-worker` / `llm_worker` / `llm_worker_macros` compatibility alias は作らない。
|
||||||
|
- `pod` crate and dependents should compile against `llm_engine::Engine` and renamed config/result/error types.
|
||||||
|
- New `worker-runtime` crate or Pod->Worker migration is non-goal.
|
||||||
|
|
||||||
|
Requirements / acceptance criteria:
|
||||||
|
- `crates/llm-engine` and `crates/llm-engine-macros` exist; old directories gone.
|
||||||
|
- Cargo package/dependency names and Rust import paths use `llm-engine` / `llm_engine` and `llm-engine-macros` / `llm_engine_macros`.
|
||||||
|
- Public turn-engine type is `llm_engine::Engine`, not `Worker`; config/result/error names no longer use `Worker` for the turn engine concept.
|
||||||
|
- Repository-wide old references are gone except intentional historical/migration notes if any.
|
||||||
|
- `pod`, `manifest`, `yoi`, examples/tests/docs/Nix/Cargo.lock update consistently.
|
||||||
|
- Existing runtime behavior is unchanged except names.
|
||||||
|
- Validation target includes `cargo test -p llm-engine`, `cargo test -p pod`, `cargo check -p yoi`, `git diff --check`, `nix build .#yoi --no-link`.
|
||||||
|
|
||||||
|
Implementation latitude:
|
||||||
|
- Result/output type exact names may follow Ticket guidance (`EngineRunResult`, `EngineRunOutput`, etc.) as long as public API no longer presents LLM turn engine as Worker.
|
||||||
|
- Internal file names may be renamed for clarity (`worker.rs` -> `engine.rs`) if practical; otherwise public module/API must be clean.
|
||||||
|
- Historical ticket ids or changelog-like references may remain only if clearly intentional and not active API/docs guidance.
|
||||||
|
|
||||||
|
Escalate if:
|
||||||
|
- Rename requires behavior changes to provider streaming/tool-loop/history semantics.
|
||||||
|
- A compatibility alias appears necessary to make internal crates compile.
|
||||||
|
- Existing macro/test generated names cannot be renamed without broader procedural macro redesign.
|
||||||
|
- `cargo test -p pod` failure is not the known prompt guidance snapshot caveat but a rename regression.
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- `cargo test -p llm-engine`
|
||||||
|
- `cargo test -p pod`
|
||||||
|
- `cargo check -p yoi`
|
||||||
|
- `git diff --check`
|
||||||
|
- `nix build .#yoi --no-link`
|
||||||
|
- Add focused grep evidence for old names.
|
||||||
|
|
||||||
|
Current code map:
|
||||||
|
- Primary: `crates/llm-worker`, `crates/llm-worker-macros`, workspace `Cargo.toml`, `Cargo.lock`, `package.nix`。
|
||||||
|
- Secondary: dependent imports in `crates/pod`, `crates/manifest`, examples/tests/docs/resources as found by grep。
|
||||||
|
- Avoid: worker-runtime implementation, Pod runtime migration, session/socket authority changes, provider/tool-loop behavior changes。
|
||||||
|
|
||||||
|
Critical risks / reviewer focus:
|
||||||
|
- stale old crate/import/type references。
|
||||||
|
- hidden compatibility alias left behind。
|
||||||
|
- behavior changes mixed into mechanical rename。
|
||||||
|
- `Worker` remaining as public turn-engine主体名 where it conflicts with Runtime Worker concept。
|
||||||
|
- Nix/Cargo lock/hash updates missed。
|
||||||
|
|
||||||
|
Next action:
|
||||||
|
- `queued -> inprogress` を記録してから worktree-workflow で `/home/hare/Projects/yoi/.worktree/00001KVZD10ED-llm-engine-rename` を作成し、multi-agent-workflow で Coder/Reviewer sibling loop に進める。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: state_changed author: yoi-orchestrator at: 2026-06-25T13:26:35Z from: queued to: inprogress reason: orchestrator_acceptance_unblocked field: state -->
|
||||||
|
|
||||||
|
## State changed
|
||||||
|
|
||||||
|
Orchestrator acceptance: queued -> inprogress
|
||||||
|
|
||||||
|
- 直前確認で `TicketShow` は state `queued`、blocking relation は 0 件、accepted plan `orch-plan-20260625-132518-1` を確認した。
|
||||||
|
- routing decision と IntentPacket は Ticket thread に記録済み。
|
||||||
|
- orchestration worktree は clean。この Ticket 用 worktree/branch はまだ作成していない。
|
||||||
|
- これ以降に worktree-workflow で `/home/hare/Projects/yoi/.worktree/00001KVZD10ED-llm-engine-rename` を作成し、multi-agent-workflow に接続する。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-25T13:28:20Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
Implementation orchestration update:
|
||||||
|
|
||||||
|
- worktree-workflow に従い、implementation worktree `/home/hare/Projects/yoi/.worktree/00001KVZD10ED-llm-engine-rename` と branch `work/00001KVZD10ED-llm-engine-rename` を作成した。
|
||||||
|
- child worktree では tracked `.yoi` project records を見える状態にし、`.yoi/memory`、local/runtime/log/lock/secret-like `.yoi` paths を sparse-checkout で除外する方針にした。
|
||||||
|
- Coder Pod `yoi-coder-00001KVZD10ED-llm-engine` を sibling として起動した。write scope は child worktree に限定し、runtime workspace root `/home/hare/Projects/yoi` は startup 要件を満たすため non-recursive read のみにした。
|
||||||
|
- Coder には root/original workspace と orchestration worktree で read/write/validation/git operation を行わないよう明示した。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: yoi-coder-00001KVZD10ED-llm-engine at: 2026-06-25T13:47:26Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
Implementation report:
|
||||||
|
|
||||||
|
対象 Ticket:
|
||||||
|
- `00001KVZD10ED` — `llm-worker crateをllm-engineへ改名する`
|
||||||
|
|
||||||
|
実装概要:
|
||||||
|
- `crates/llm-worker` → `crates/llm-engine`
|
||||||
|
- `crates/llm-worker-macros` → `crates/llm-engine-macros`
|
||||||
|
- Cargo package / workspace dependency 名を `llm-engine` / `llm-engine-macros` へ更新。
|
||||||
|
- Rust import path を `llm_engine` / `llm_engine_macros` へ更新。
|
||||||
|
- turn-engine API を rename。
|
||||||
|
- `Worker` → `Engine`
|
||||||
|
- `WorkerConfig` → `EngineConfig`
|
||||||
|
- `WorkerError` → `EngineError`
|
||||||
|
- `WorkerResult` → `EngineResult`
|
||||||
|
- `WorkerState` → `EngineState`
|
||||||
|
- `RunOutput` → `EngineRunOutput`
|
||||||
|
- `pod` 側の engine 保持 API / error / alert source 等も rename fallout として更新。
|
||||||
|
- docs / tests / examples / Nix / `Cargo.lock` / `package.nix` hash を更新。
|
||||||
|
- 旧 crate directory は削除済みで、互換 alias は追加していない。
|
||||||
|
|
||||||
|
Commit:
|
||||||
|
- `292fc4ea refactor: rename llm worker crate to engine`
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- `cargo test -p llm-engine`: success
|
||||||
|
- `cargo test -p pod`: success
|
||||||
|
- 既知 caveat の prompt guidance snapshot failure は今回発生しなかった。
|
||||||
|
- `cargo check -p yoi`: success
|
||||||
|
- `git diff --check`: success
|
||||||
|
- `nix build .#yoi --no-link`: success
|
||||||
|
|
||||||
|
Grep evidence:
|
||||||
|
- `.yoi/tickets/**` と `docs/report/**` を除外した active tree で以下の旧 active references は 0 件。
|
||||||
|
- `llm-worker`
|
||||||
|
- `llm_worker`
|
||||||
|
- `llm-worker-macros`
|
||||||
|
- `llm_worker_macros`
|
||||||
|
- `crates/llm-engine` / `crates/llm-engine-macros` 内の旧 public Worker API 名も 0 件。
|
||||||
|
- `\bWorker\b`
|
||||||
|
- `WorkerConfig`
|
||||||
|
- `WorkerError`
|
||||||
|
- `WorkerResult`
|
||||||
|
- `WorkerState`
|
||||||
|
- historical `.yoi/tickets` / `docs/report` 内には旧語が残る。count: 786。これは Ticket 本文・過去 report 類の履歴文脈として意図的に残している。
|
||||||
|
|
||||||
|
Residual risks / notes:
|
||||||
|
- 互換 alias は入れていないため、外部未追跡コードが `llm-worker` / `llm_worker::Worker` を直接参照している場合は破壊的変更になる。ただし Ticket の binding invariant 通り。
|
||||||
|
- merge / Ticket close / worktree cleanup は未実施。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: review author: yoi-reviewer-00001KVZD10ED-llm-engine at: 2026-06-25T13:54:15Z status: approve -->
|
||||||
|
|
||||||
|
## Review: approve
|
||||||
|
|
||||||
|
Review result: approve
|
||||||
|
|
||||||
|
確認結果、Ticket `00001KVZD10ED` の IntentPacket / acceptance criteria に対してブロッカーは見つかりませんでした。
|
||||||
|
|
||||||
|
Read-only 確認:
|
||||||
|
- 対象 worktree / branch / commit:
|
||||||
|
- `work/00001KVZD10ED-llm-engine-rename`
|
||||||
|
- HEAD `292fc4ea`
|
||||||
|
- base `22598710` は ancestor。
|
||||||
|
- Ticket 記録:
|
||||||
|
- `thread.md` の IntentPacket / acceptance criteria / reviewer focus を確認。
|
||||||
|
- 要件は `llm-worker` → `llm-engine`、public turn-engine API を `Worker` 系から `Engine` 系へ rename、互換 alias なし、責務移動なし。
|
||||||
|
- crate / directory:
|
||||||
|
- `crates/llm-engine`, `crates/llm-engine-macros` が存在。
|
||||||
|
- `crates/llm-worker`, `crates/llm-worker-macros` は存在しない。
|
||||||
|
- Cargo / Nix:
|
||||||
|
- root `Cargo.toml` の members/default-members/workspace deps が `llm-engine` / `llm-engine-macros`。
|
||||||
|
- `crates/pod/Cargo.toml` 等の依存も `llm-engine`。
|
||||||
|
- `Cargo.lock` は旧 package 名から新 package 名へ置換済み。
|
||||||
|
- `package.nix` の `cargoHash` 更新を確認。
|
||||||
|
- public API:
|
||||||
|
- `crates/llm-engine/src/lib.rs` は `Engine`, `EngineConfig`, `EngineError`, `EngineResult`, `EngineRunOutput` を export。
|
||||||
|
- `WorkerConfig` / `WorkerError` / `WorkerResult` / `WorkerState` / bare `RunOutput` は active tree で検出されず。
|
||||||
|
- `llm-engine` / `llm-engine-macros` 内に `pub type Worker` 等の互換 alias は見つからず。
|
||||||
|
- stale reference:
|
||||||
|
- `.yoi/tickets/**` と `docs/report/**` を除いた tracked active tree で `llm-worker`, `llm_worker`, `llm-worker-macros`, `llm_worker_macros` は検出されず。
|
||||||
|
- behavior / responsibility risk:
|
||||||
|
- `crates/llm-worker/src/worker.rs` → `crates/llm-engine/src/engine.rs` は正規化比較で import order / formatting 程度の差分に収まっていることを確認。
|
||||||
|
- `crates/llm-engine/README.md` に Pod names / sockets / process lifecycle / scope delegation / durable Pod state を持たない境界が明記されており、Runtime/Worker identity や socket/session authority の責務移動は見当たらない。
|
||||||
|
- whitespace:
|
||||||
|
- `git diff --check 22598710..HEAD`: success。
|
||||||
|
|
||||||
|
未再実行:
|
||||||
|
- `cargo test -p llm-engine`
|
||||||
|
- `cargo test -p pod`
|
||||||
|
- `cargo check -p yoi`
|
||||||
|
- `nix build .#yoi --no-link`
|
||||||
|
|
||||||
|
上記は reviewer の read-only/no-write 条件を優先して再実行していない。coder 報告では成功済み。
|
||||||
|
|
||||||
|
Non-blocking note:
|
||||||
|
- `pod` 側には local variable や既存 runtime/manifest 文脈としての `worker` 語が残っているが、今回の acceptance 対象である old crate/import/API 名や `llm_engine::Worker` 互換 alias ではないため問題なし。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-25T13:57:16Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
Integration outcome:
|
||||||
|
|
||||||
|
- Coder `yoi-coder-00001KVZD10ED-llm-engine` が implementation branch `work/00001KVZD10ED-llm-engine-rename` に実装を commit した。
|
||||||
|
- implementation commit: `292fc4ea refactor: rename llm worker crate to engine`
|
||||||
|
- Reviewer `yoi-reviewer-00001KVZD10ED-llm-engine` は read-only review で `approve`。old crate directories removal、Cargo/Nix consistency、public Engine API、old active references なし、compatibility alias なし、behavior/responsibility movement なしを確認した。
|
||||||
|
- Orchestrator worktree `/home/hare/Projects/yoi/.worktree/orchestration` で branch `work/00001KVZD10ED-llm-engine-rename` を merge 済み。
|
||||||
|
- merge commit: `254ecccb merge: 00001KVZD10ED llm engine rename`
|
||||||
|
|
||||||
|
Implemented behavior:
|
||||||
|
- `crates/llm-worker` -> `crates/llm-engine`
|
||||||
|
- `crates/llm-worker-macros` -> `crates/llm-engine-macros`
|
||||||
|
- Cargo package/dependency/import path を `llm-engine` / `llm_engine`, `llm-engine-macros` / `llm_engine_macros` に更新。
|
||||||
|
- Public turn-engine API を Engine 系へ rename。
|
||||||
|
- `Worker` -> `Engine`
|
||||||
|
- `WorkerConfig` -> `EngineConfig`
|
||||||
|
- `WorkerError` -> `EngineError`
|
||||||
|
- `WorkerResult` -> `EngineResult`
|
||||||
|
- `WorkerState` -> `EngineState`
|
||||||
|
- `RunOutput` -> `EngineRunOutput`
|
||||||
|
- `pod`, `manifest`, docs, examples, tests, Nix, `Cargo.lock`, `package.nix` hash を更新。
|
||||||
|
- old crate directories は削除済み。compatibility alias は追加していない。
|
||||||
|
|
||||||
|
Validation in Orchestrator worktree:
|
||||||
|
- `cargo test -p llm-engine`: success
|
||||||
|
- `cargo test -p pod`: success
|
||||||
|
- `cargo check -p yoi`: success
|
||||||
|
- active old reference grep excluding `.yoi/tickets/**` and `docs/report/**`: success, none found for:
|
||||||
|
- `llm-worker`
|
||||||
|
- `llm_worker`
|
||||||
|
- `llm-worker-macros`
|
||||||
|
- `llm_worker_macros`
|
||||||
|
- `crates/llm-engine` / `crates/llm-engine-macros` old public Worker API grep: success, none found for:
|
||||||
|
- `\bWorker\b`
|
||||||
|
- `WorkerConfig`
|
||||||
|
- `WorkerError`
|
||||||
|
- `WorkerResult`
|
||||||
|
- `WorkerState`
|
||||||
|
- `git diff --check`: success
|
||||||
|
- `nix build .#yoi --no-link`: success
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- Historical `.yoi/tickets/**` and `docs/report/**` references to old names remain as durable history/report context and are not active API/docs guidance.
|
||||||
|
- Unlike recent Plugin full `cargo test -p pod` runs, this validation passed `cargo test -p pod` fully.
|
||||||
|
|
||||||
|
Next action:
|
||||||
|
- Mark Ticket done after this integration/validation evidence.
|
||||||
|
- Then stop related child Pods and remove only the child implementation worktree/branch.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: state_changed author: yoi-orchestrator at: 2026-06-25T13:57:29Z from: inprogress to: done reason: merged_validated field: state -->
|
||||||
|
|
||||||
|
## State changed
|
||||||
|
|
||||||
|
Implementation is merged into the orchestration branch and validated.
|
||||||
|
|
||||||
|
Evidence:
|
||||||
|
- merge commit: `254ecccb merge: 00001KVZD10ED llm engine rename`
|
||||||
|
- reviewer result: approve
|
||||||
|
- validation in `/home/hare/Projects/yoi/.worktree/orchestration` succeeded:
|
||||||
|
- `cargo test -p llm-engine`
|
||||||
|
- `cargo test -p pod`
|
||||||
|
- `cargo check -p yoi`
|
||||||
|
- active old reference grep excluding `.yoi/tickets/**` and `docs/report/**`
|
||||||
|
- old public Worker API grep in `crates/llm-engine` / `crates/llm-engine-macros`
|
||||||
|
- `git diff --check`
|
||||||
|
- `nix build .#yoi --no-link`
|
||||||
|
|
||||||
|
Closure is not performed here; this state records implementation completion after merge/validation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -2102,7 +2102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
|
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "llm-worker"
|
name = "llm-engine"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
|
@ -2110,7 +2110,7 @@ dependencies = [
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"eventsource-stream",
|
"eventsource-stream",
|
||||||
"futures",
|
"futures",
|
||||||
"llm-worker-macros",
|
"llm-engine-macros",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -2127,7 +2127,7 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "llm-worker-macros"
|
name = "llm-engine-macros"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -2242,7 +2242,7 @@ name = "manifest"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"llm-worker",
|
"llm-engine",
|
||||||
"mlua",
|
"mlua",
|
||||||
"protocol",
|
"protocol",
|
||||||
"secrets",
|
"secrets",
|
||||||
|
|
@ -2373,7 +2373,7 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"libc",
|
"libc",
|
||||||
"lint-common",
|
"lint-common",
|
||||||
"llm-worker",
|
"llm-engine",
|
||||||
"manifest",
|
"manifest",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -2888,7 +2888,7 @@ dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"include_dir",
|
"include_dir",
|
||||||
"libc",
|
"libc",
|
||||||
"llm-worker",
|
"llm-engine",
|
||||||
"manifest",
|
"manifest",
|
||||||
"mcp",
|
"mcp",
|
||||||
"memory",
|
"memory",
|
||||||
|
|
@ -3045,7 +3045,7 @@ dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64",
|
"base64",
|
||||||
"chrono",
|
"chrono",
|
||||||
"llm-worker",
|
"llm-engine",
|
||||||
"manifest",
|
"manifest",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"secrets",
|
"secrets",
|
||||||
|
|
@ -3901,7 +3901,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"futures",
|
"futures",
|
||||||
"llm-worker",
|
"llm-engine",
|
||||||
"protocol",
|
"protocol",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
@ -4352,7 +4352,7 @@ dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"chrono",
|
"chrono",
|
||||||
"fs4",
|
"fs4",
|
||||||
"llm-worker",
|
"llm-engine",
|
||||||
"project-record",
|
"project-record",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -4535,7 +4535,7 @@ dependencies = [
|
||||||
"grep-searcher",
|
"grep-searcher",
|
||||||
"html5ever",
|
"html5ever",
|
||||||
"ignore",
|
"ignore",
|
||||||
"llm-worker",
|
"llm-engine",
|
||||||
"manifest",
|
"manifest",
|
||||||
"markup5ever_rcdom",
|
"markup5ever_rcdom",
|
||||||
"pdf-extract",
|
"pdf-extract",
|
||||||
|
|
@ -4715,7 +4715,7 @@ dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"client",
|
"client",
|
||||||
"crossterm 0.28.1",
|
"crossterm 0.28.1",
|
||||||
"llm-worker",
|
"llm-engine",
|
||||||
"manifest",
|
"manifest",
|
||||||
"minijinja",
|
"minijinja",
|
||||||
"pod-registry",
|
"pod-registry",
|
||||||
|
|
|
||||||
12
Cargo.toml
12
Cargo.toml
|
|
@ -2,8 +2,8 @@
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"crates/client",
|
"crates/client",
|
||||||
"crates/llm-worker",
|
"crates/llm-engine",
|
||||||
"crates/llm-worker-macros",
|
"crates/llm-engine-macros",
|
||||||
"crates/session-store",
|
"crates/session-store",
|
||||||
"crates/secrets",
|
"crates/secrets",
|
||||||
"crates/manifest",
|
"crates/manifest",
|
||||||
|
|
@ -29,8 +29,8 @@ members = [
|
||||||
]
|
]
|
||||||
default-members = [
|
default-members = [
|
||||||
"crates/client",
|
"crates/client",
|
||||||
"crates/llm-worker",
|
"crates/llm-engine",
|
||||||
"crates/llm-worker-macros",
|
"crates/llm-engine-macros",
|
||||||
"crates/session-store",
|
"crates/session-store",
|
||||||
"crates/secrets",
|
"crates/secrets",
|
||||||
"crates/manifest",
|
"crates/manifest",
|
||||||
|
|
@ -61,8 +61,8 @@ license = "MIT"
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
# Internal crates
|
# Internal crates
|
||||||
client = { path = "crates/client" }
|
client = { path = "crates/client" }
|
||||||
llm-worker = { path = "crates/llm-worker", version = "0.2" }
|
llm-engine = { path = "crates/llm-engine", version = "0.2" }
|
||||||
llm-worker-macros = { path = "crates/llm-worker-macros", version = "0.2" }
|
llm-engine-macros = { path = "crates/llm-engine-macros", version = "0.2" }
|
||||||
manifest = { path = "crates/manifest" }
|
manifest = { path = "crates/manifest" }
|
||||||
mcp = { path = "crates/mcp" }
|
mcp = { path = "crates/mcp" }
|
||||||
lint-common = { path = "crates/lint-common" }
|
lint-common = { path = "crates/lint-common" }
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ Does not own:
|
||||||
- product command names (`yoi`)
|
- product command names (`yoi`)
|
||||||
- Pod state authority (`pod`, `pod-store`, `session-store`)
|
- Pod state authority (`pod`, `pod-store`, `session-store`)
|
||||||
- UI rendering (`tui`)
|
- UI rendering (`tui`)
|
||||||
- Worker turn semantics (`llm-worker`)
|
- Engine turn semantics (`llm-engine`)
|
||||||
|
|
||||||
## Design notes
|
## Design notes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "llm-worker-macros"
|
name = "llm-engine-macros"
|
||||||
description = "llm-worker's proc macros"
|
description = "llm-engine's proc macros"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# llm-worker-macros
|
# llm-engine-macros
|
||||||
|
|
||||||
## Role
|
## Role
|
||||||
|
|
||||||
`llm-worker-macros` provides procedural macros for declaring Rust methods as LLM-callable tools.
|
`llm-engine-macros` provides procedural macros for declaring Rust methods as LLM-callable tools.
|
||||||
|
|
||||||
## Boundaries
|
## Boundaries
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//! llm-worker-macros - Procedural macros for Tool generation
|
//! llm-engine-macros - Procedural macros for Tool generation
|
||||||
//!
|
//!
|
||||||
//! Provides `#[tool_registry]` and `#[tool]` macros to
|
//! Provides `#[tool_registry]` and `#[tool]` macros to
|
||||||
//! automatically generate `Tool` trait implementations from user-defined methods.
|
//! automatically generate `Tool` trait implementations from user-defined methods.
|
||||||
|
|
@ -215,7 +215,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
|
||||||
quote! {
|
quote! {
|
||||||
match result {
|
match result {
|
||||||
Ok(val) => Ok(format!("{:?}", val).into()),
|
Ok(val) => Ok(format!("{:?}", val).into()),
|
||||||
Err(e) => Err(::llm_worker::tool::ToolError::ExecutionFailed(format!("{}", e))),
|
Err(e) => Err(::llm_engine::tool::ToolError::ExecutionFailed(format!("{}", e))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -252,7 +252,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
let args: #args_struct_name = serde_json::from_str(input_json)
|
let args: #args_struct_name = serde_json::from_str(input_json)
|
||||||
.map_err(|e| ::llm_worker::tool::ToolError::InvalidArgument(e.to_string()))?;
|
.map_err(|e| ::llm_engine::tool::ToolError::InvalidArgument(e.to_string()))?;
|
||||||
|
|
||||||
let result = #method_call #awaiter;
|
let result = #method_call #awaiter;
|
||||||
#result_handling
|
#result_handling
|
||||||
|
|
@ -268,23 +268,23 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl ::llm_worker::tool::Tool for #tool_struct_name {
|
impl ::llm_engine::tool::Tool for #tool_struct_name {
|
||||||
async fn execute(&self, input_json: &str, ctx: ::llm_worker::tool::ToolExecutionContext) -> Result<::llm_worker::tool::ToolOutput, ::llm_worker::tool::ToolError> {
|
async fn execute(&self, input_json: &str, ctx: ::llm_engine::tool::ToolExecutionContext) -> Result<::llm_engine::tool::ToolOutput, ::llm_engine::tool::ToolError> {
|
||||||
let _ = &ctx;
|
let _ = &ctx;
|
||||||
#execute_body
|
#execute_body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl #self_ty {
|
impl #self_ty {
|
||||||
/// Get ToolDefinition (for registering with Worker)
|
/// Get ToolDefinition (for registering with Engine)
|
||||||
pub fn #definition_name(&self) -> ::llm_worker::tool::ToolDefinition {
|
pub fn #definition_name(&self) -> ::llm_engine::tool::ToolDefinition {
|
||||||
let ctx = self.clone();
|
let ctx = self.clone();
|
||||||
::std::sync::Arc::new(move || {
|
::std::sync::Arc::new(move || {
|
||||||
let schema = schemars::schema_for!(#args_struct_name);
|
let schema = schemars::schema_for!(#args_struct_name);
|
||||||
let meta = ::llm_worker::tool::ToolMeta::new(#tool_name)
|
let meta = ::llm_engine::tool::ToolMeta::new(#tool_name)
|
||||||
.description(#description)
|
.description(#description)
|
||||||
.input_schema(serde_json::to_value(schema).unwrap_or(serde_json::json!({})));
|
.input_schema(serde_json::to_value(schema).unwrap_or(serde_json::json!({})));
|
||||||
let tool: ::std::sync::Arc<dyn ::llm_worker::tool::Tool> =
|
let tool: ::std::sync::Arc<dyn ::llm_engine::tool::Tool> =
|
||||||
::std::sync::Arc::new(#tool_struct_name { ctx: ctx.clone() });
|
::std::sync::Arc::new(#tool_struct_name { ctx: ctx.clone() });
|
||||||
(meta, tool)
|
(meta, tool)
|
||||||
})
|
})
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "llm-worker"
|
name = "llm-engine"
|
||||||
description = "A library for building autonomous LLM-powered systems"
|
description = "A library for building autonomous LLM-powered systems"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
|
@ -17,7 +17,7 @@ tokio-util = "0.7"
|
||||||
reqwest = { version = "0.13", default-features = false, features = ["stream", "json", "native-tls", "http2"] }
|
reqwest = { version = "0.13", default-features = false, features = ["stream", "json", "native-tls", "http2"] }
|
||||||
eventsource-stream = "0.2"
|
eventsource-stream = "0.2"
|
||||||
zstd = "0.13"
|
zstd = "0.13"
|
||||||
llm-worker-macros = { workspace = true }
|
llm-engine-macros = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
clap = { version = "4.5", features = ["derive", "env"] }
|
clap = { version = "4.5", features = ["derive", "env"] }
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
# llm-worker
|
# llm-engine
|
||||||
|
|
||||||
## Role
|
## Role
|
||||||
|
|
||||||
`llm-worker` owns provider-independent model turn orchestration over committed history, tools, callbacks, retries, continuation, pruning, and compaction boundaries.
|
`llm-engine` owns provider-independent model turn orchestration over committed history, tools, callbacks, retries, continuation, pruning, and compaction boundaries.
|
||||||
|
|
||||||
## Boundaries
|
## Boundaries
|
||||||
|
|
||||||
Owns:
|
Owns:
|
||||||
|
|
||||||
- Worker history mutation and append contracts
|
- Engine history mutation and append contracts
|
||||||
- tool-call loop semantics
|
- tool-call loop semantics
|
||||||
- pre-stream retry and stream-started continuation policy
|
- pre-stream retry and stream-started continuation policy
|
||||||
- pruning/compaction coordination from the Worker perspective
|
- pruning/compaction coordination from the Engine perspective
|
||||||
- provider-neutral events/callbacks/interceptors
|
- provider-neutral events/callbacks/interceptors
|
||||||
|
|
||||||
Does not own:
|
Does not own:
|
||||||
|
|
@ -23,7 +23,7 @@ Does not own:
|
||||||
|
|
||||||
## Design notes
|
## Design notes
|
||||||
|
|
||||||
The Worker is where turn lifecycle belongs because it sees history, in-flight usage, partial output, and tool-call state. It should not receive context-only volatile facts; model-affecting inputs must first be appended to history.
|
The Engine is where turn lifecycle belongs because it sees history, in-flight usage, partial output, and tool-call state. It should not receive context-only volatile facts; model-affecting inputs must first be appended to history.
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
# llm-worker アーキテクチャ
|
# llm-engine アーキテクチャ
|
||||||
|
|
||||||
## 概要
|
## 概要
|
||||||
|
|
||||||
llm-workerは3層構成でLLMとのインタラクションを管理する。
|
llm-engineは3層構成でLLMとのインタラクションを管理する。
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────┐
|
┌─────────────────────────────────────────┐
|
||||||
│ Worker (オーケストレーション) │
|
│ Engine (オーケストレーション) │
|
||||||
│ ターンループ / フック / ツール実行 │
|
│ ターンループ / フック / ツール実行 │
|
||||||
│ Type-state: Mutable ↔ CacheLocked │
|
│ Type-state: Mutable ↔ CacheLocked │
|
||||||
└───────────┬─────────────────────────────┘
|
└───────────┬─────────────────────────────┘
|
||||||
|
|
@ -27,7 +27,7 @@ llm-workerは3層構成でLLMとのインタラクションを管理する。
|
||||||
|
|
||||||
| モジュール | 責務 | 要件 |
|
| モジュール | 責務 | 要件 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `worker` | ターンループ、フック統合、ツール実行、Pause/Resume | R1, R4 |
|
| `engine` | ターンループ、フック統合、ツール実行、Pause/Resume | R1, R4 |
|
||||||
| `state` | Type-state (Mutable/CacheLocked) | R2 |
|
| `state` | Type-state (Mutable/CacheLocked) | R2 |
|
||||||
| `hook` | Hook trait、10フックポイント | R3, R4 |
|
| `hook` | Hook trait、10フックポイント | R3, R4 |
|
||||||
| `tool` / `tool_server` | ツール定義・登録・実行 | R3 |
|
| `tool` / `tool_server` | ツール定義・登録・実行 | R3 |
|
||||||
|
|
@ -42,7 +42,7 @@ llm-workerは3層構成でLLMとのインタラクションを管理する。
|
||||||
|
|
||||||
### リクエスト(送信)
|
### リクエスト(送信)
|
||||||
```
|
```
|
||||||
Worker.history (Vec<Item>)
|
Engine.history (Vec<Item>)
|
||||||
→ build_request() → Request { items, tools, config }
|
→ build_request() → Request { items, tools, config }
|
||||||
→ Scheme.build_request() → プロバイダ固有JSON
|
→ Scheme.build_request() → プロバイダ固有JSON
|
||||||
→ Provider.stream() → HTTP POST
|
→ Provider.stream() → HTTP POST
|
||||||
|
|
@ -55,7 +55,7 @@ HTTP SSE bytes
|
||||||
→ Scheme.parse_event() → Event (統一型)
|
→ Scheme.parse_event() → Event (統一型)
|
||||||
→ Timeline.dispatch() → Handler.on_event()
|
→ Timeline.dispatch() → Handler.on_event()
|
||||||
→ TextBlockCollector / ToolCallCollector
|
→ TextBlockCollector / ToolCallCollector
|
||||||
→ Worker: 履歴に追加、ツール実行判定
|
→ Engine: 履歴に追加、ツール実行判定
|
||||||
```
|
```
|
||||||
|
|
||||||
## 内部型
|
## 内部型
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# llm-worker 要件
|
# llm-engine 要件
|
||||||
|
|
||||||
## 前提
|
## 前提
|
||||||
|
|
||||||
|
|
@ -12,23 +12,23 @@ c. ツール・フックの基本的なスキーマ自動化を提供する
|
||||||
|
|
||||||
メッセージの送信と生成のResume、一時停止/再開。
|
メッセージの送信と生成のResume、一時停止/再開。
|
||||||
|
|
||||||
- `Worker::run()` でターンを開始
|
- `Engine::run()` でターンを開始
|
||||||
- フックから `Pause` を返してターンを一時停止
|
- フックから `Pause` を返してターンを一時停止
|
||||||
- `Worker::resume()` でユーザーメッセージを追加せず継続
|
- `Engine::resume()` でユーザーメッセージを追加せず継続
|
||||||
- AIは中断を認識せず、継続として処理する
|
- AIは中断を認識せず、継続として処理する
|
||||||
|
|
||||||
**実装**: `worker.rs` — `resume()`, `get_pending_tool_calls()`, `WorkerResult::Paused`
|
**実装**: `engine.rs` — `resume()`, `get_pending_tool_calls()`, `EngineResult::Paused`
|
||||||
|
|
||||||
### R2: 暗黙的KVキャッシュ保証
|
### R2: 暗黙的KVキャッシュ保証
|
||||||
|
|
||||||
キャッシュを破壊しうる操作を明示的にブロックせずとも、いつの間にかキャッシュ破壊してた状態にはしたくない。
|
キャッシュを破壊しうる操作を明示的にブロックせずとも、いつの間にかキャッシュ破壊してた状態にはしたくない。
|
||||||
|
|
||||||
- Type-stateパターン(`Mutable` / `CacheLocked`)でコンパイル時に保証
|
- Type-stateパターン(`Mutable` / `CacheLocked`)でコンパイル時に保証
|
||||||
- `Worker::lock()` でCacheLocked状態に遷移
|
- `Engine::lock()` でCacheLocked状態に遷移
|
||||||
- CacheLocked状態ではシステムプロンプトや履歴の変更APIが型レベルで利用不可
|
- CacheLocked状態ではシステムプロンプトや履歴の変更APIが型レベルで利用不可
|
||||||
- `locked_prefix_len` でプレフィックスの不変性を追跡
|
- `locked_prefix_len` でプレフィックスの不変性を追跡
|
||||||
|
|
||||||
**実装**: `state.rs` (sealed trait), `worker.rs` (state-specific impl blocks)
|
**実装**: `state.rs` (sealed trait), `engine.rs` (state-specific impl blocks)
|
||||||
|
|
||||||
### R3: ツール・フックスキーマ自動化
|
### R3: ツール・フックスキーマ自動化
|
||||||
|
|
||||||
|
|
@ -36,13 +36,13 @@ c. ツール・フックの基本的なスキーマ自動化を提供する
|
||||||
- `#[tool_registry]` マクロでツールサーバーを自動構成
|
- `#[tool_registry]` マクロでツールサーバーを自動構成
|
||||||
- `Hook` traitで10種のフックポイント
|
- `Hook` traitで10種のフックポイント
|
||||||
|
|
||||||
**実装**: `llm-worker-macros/`, `tool.rs`, `tool_server.rs`, `hook.rs`
|
**実装**: `llm-engine-macros/`, `tool.rs`, `tool_server.rs`, `hook.rs`
|
||||||
|
|
||||||
### R4: フックは上層の関心事
|
### R4: フックは上層の関心事
|
||||||
|
|
||||||
フックはLLMクライアント層ではなく、Worker(オーケストレーション)層に配置する。
|
フックはLLMクライアント層ではなく、Engine(オーケストレーション)層に配置する。
|
||||||
|
|
||||||
- LLMクライアント (`llm_client/`) はストリーミングとプロトコルのみ
|
- LLMクライアント (`llm_client/`) はストリーミングとプロトコルのみ
|
||||||
- Worker層でフック実行、ツール統合、Pause/Resume制御
|
- Engine層でフック実行、ツール統合、Pause/Resume制御
|
||||||
|
|
||||||
**実装**: `worker.rs` (hook integration), `hook.rs` (trait definitions)
|
**実装**: `engine.rs` (hook integration), `hook.rs` (trait definitions)
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
//! Worker cancellation demo
|
//! Engine cancellation demo
|
||||||
//!
|
//!
|
||||||
//! Example of cancelling from another thread during streaming
|
//! Example of cancelling from another thread during streaming
|
||||||
|
|
||||||
use llm_worker::llm_client::scheme::{Scheme, anthropic::AnthropicScheme};
|
use llm_engine::llm_client::scheme::{Scheme, anthropic::AnthropicScheme};
|
||||||
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth};
|
use llm_engine::llm_client::transport::{HttpTransport, ResolvedAuth};
|
||||||
use llm_worker::{Worker, WorkerResult};
|
use llm_engine::{Engine, EngineResult};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
@ -28,29 +28,29 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let cap = scheme.default_capability();
|
let cap = scheme.default_capability();
|
||||||
let base_url = scheme.default_base_url().to_string();
|
let base_url = scheme.default_base_url().to_string();
|
||||||
let client = HttpTransport::new(scheme, model, base_url, ResolvedAuth::ApiKey(api_key), cap);
|
let client = HttpTransport::new(scheme, model, base_url, ResolvedAuth::ApiKey(api_key), cap);
|
||||||
let worker = Worker::new(client);
|
let engine = Engine::new(client);
|
||||||
|
|
||||||
println!("🚀 Starting Worker...");
|
println!("🚀 Starting Engine...");
|
||||||
println!("💡 Will cancel after 2 seconds\n");
|
println!("💡 Will cancel after 2 seconds\n");
|
||||||
|
|
||||||
// Get cancel sender before run (Mutable state)
|
// Get cancel sender before run (Mutable state)
|
||||||
let cancel_tx = worker.cancel_sender();
|
let cancel_tx = engine.cancel_sender();
|
||||||
|
|
||||||
// Task: Cancel after 2 seconds
|
// Task: Cancel after 2 seconds
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||||
println!("\n🛑 Cancelling worker...");
|
println!("\n🛑 Cancelling engine...");
|
||||||
let _ = cancel_tx.send(()).await;
|
let _ = cancel_tx.send(()).await;
|
||||||
});
|
});
|
||||||
|
|
||||||
println!("📡 Sending request to LLM...");
|
println!("📡 Sending request to LLM...");
|
||||||
|
|
||||||
match worker.run("Tell me a very long story about a brave knight. Make it as detailed as possible with many paragraphs.").await {
|
match engine.run("Tell me a very long story about a brave knight. Make it as detailed as possible with many paragraphs.").await {
|
||||||
Ok(out) => match out.result {
|
Ok(out) => match out.result {
|
||||||
WorkerResult::Finished => println!("✅ Task completed normally"),
|
EngineResult::Finished => println!("✅ Task completed normally"),
|
||||||
WorkerResult::Paused => println!("⏸️ Task paused"),
|
EngineResult::Paused => println!("⏸️ Task paused"),
|
||||||
WorkerResult::LimitReached => println!("🔒 Turn limit reached"),
|
EngineResult::LimitReached => println!("🔒 Turn limit reached"),
|
||||||
WorkerResult::Yielded => println!("↩️ Task yielded"),
|
EngineResult::Yielded => println!("↩️ Task yielded"),
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("❌ Task error: {}", e);
|
println!("❌ Task error: {}", e);
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//! Interactive CLI client using Worker
|
//! Interactive CLI client using Engine
|
||||||
//!
|
//!
|
||||||
//! A CLI application for interacting with multiple LLM providers (Anthropic, Gemini, OpenAI, Ollama).
|
//! A CLI application for interacting with multiple LLM providers (Anthropic, Gemini, OpenAI, Ollama).
|
||||||
//! Demonstrates tool registration and execution, and streaming response display.
|
//! Demonstrates tool registration and execution, and streaming response display.
|
||||||
|
|
@ -12,22 +12,22 @@
|
||||||
//! echo "OPENAI_API_KEY=your-api-key" >> .env
|
//! echo "OPENAI_API_KEY=your-api-key" >> .env
|
||||||
//!
|
//!
|
||||||
//! # Anthropic (default)
|
//! # Anthropic (default)
|
||||||
//! cargo run --example worker_cli
|
//! cargo run --example engine_cli
|
||||||
//!
|
//!
|
||||||
//! # Gemini
|
//! # Gemini
|
||||||
//! cargo run --example worker_cli -- --provider gemini
|
//! cargo run --example engine_cli -- --provider gemini
|
||||||
//!
|
//!
|
||||||
//! # OpenAI
|
//! # OpenAI
|
||||||
//! cargo run --example worker_cli -- --provider openai --model gpt-4o
|
//! cargo run --example engine_cli -- --provider openai --model gpt-4o
|
||||||
//!
|
//!
|
||||||
//! # Ollama (local)
|
//! # Ollama (local)
|
||||||
//! cargo run --example worker_cli -- --provider ollama --model llama3.2
|
//! cargo run --example engine_cli -- --provider ollama --model llama3.2
|
||||||
//!
|
//!
|
||||||
//! # With options
|
//! # With options
|
||||||
//! cargo run --example worker_cli -- --provider anthropic --model claude-3-haiku-20240307 --system "You are a helpful assistant."
|
//! cargo run --example engine_cli -- --provider anthropic --model claude-3-haiku-20240307 --system "You are a helpful assistant."
|
||||||
//!
|
//!
|
||||||
//! # Show help
|
//! # Show help
|
||||||
//! cargo run --example worker_cli -- --help
|
//! cargo run --example engine_cli -- --help
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
@ -39,8 +39,8 @@ use tracing::info;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use llm_worker::{
|
use llm_engine::{
|
||||||
Worker,
|
Engine,
|
||||||
interceptor::{Interceptor, PostToolAction, ToolResultInfo},
|
interceptor::{Interceptor, PostToolAction, ToolResultInfo},
|
||||||
llm_client::{
|
llm_client::{
|
||||||
LlmClient,
|
LlmClient,
|
||||||
|
|
@ -52,7 +52,7 @@ use llm_worker::{
|
||||||
},
|
},
|
||||||
timeline::{Handler, TextBlockEvent, TextBlockKind, ToolUseBlockEvent, ToolUseBlockKind},
|
timeline::{Handler, TextBlockEvent, TextBlockKind, ToolUseBlockEvent, ToolUseBlockKind},
|
||||||
};
|
};
|
||||||
use llm_worker_macros::tool_registry;
|
use llm_engine_macros::tool_registry;
|
||||||
|
|
||||||
// Required imports for macro expansion
|
// Required imports for macro expansion
|
||||||
use schemars;
|
use schemars;
|
||||||
|
|
@ -114,8 +114,8 @@ impl Provider {
|
||||||
|
|
||||||
/// Interactive CLI client supporting multiple LLM providers
|
/// Interactive CLI client supporting multiple LLM providers
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(name = "worker-cli")]
|
#[command(name = "engine-cli")]
|
||||||
#[command(about = "Interactive CLI client for multiple LLM providers using Worker")]
|
#[command(about = "Interactive CLI client for multiple LLM providers using Engine")]
|
||||||
#[command(version)]
|
#[command(version)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Provider to use
|
/// Provider to use
|
||||||
|
|
@ -393,7 +393,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
|
|
||||||
// Initialize logging
|
// Initialize logging
|
||||||
// Use RUST_LOG=debug cargo run --example worker_cli ... for detailed logs
|
// Use RUST_LOG=debug cargo run --example engine_cli ... for detailed logs
|
||||||
// Default is warn level, can be overridden with RUST_LOG environment variable
|
// Default is warn level, can be overridden with RUST_LOG environment variable
|
||||||
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn"));
|
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn"));
|
||||||
|
|
||||||
|
|
@ -408,7 +408,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
info!(
|
info!(
|
||||||
provider = ?args.provider,
|
provider = ?args.provider,
|
||||||
model = ?args.model,
|
model = ?args.model,
|
||||||
"Starting worker CLI"
|
"Starting engine CLI"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Interactive mode or one-shot mode
|
// Interactive mode or one-shot mode
|
||||||
|
|
@ -421,7 +421,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
.unwrap_or_else(|| args.provider.default_model().to_string());
|
.unwrap_or_else(|| args.provider.default_model().to_string());
|
||||||
|
|
||||||
if is_interactive {
|
if is_interactive {
|
||||||
let title = format!("Worker CLI - {}", args.provider.display_name());
|
let title = format!("Engine CLI - {}", args.provider.display_name());
|
||||||
let border_len = title.len() + 6;
|
let border_len = title.len() + 6;
|
||||||
println!("╔{}╗", "═".repeat(border_len));
|
println!("╔{}╗", "═".repeat(border_len));
|
||||||
println!("║ {} ║", title);
|
println!("║ {} ║", title);
|
||||||
|
|
@ -453,34 +453,34 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create Worker
|
// Create Engine
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
|
|
||||||
let tool_call_names = Arc::new(Mutex::new(HashMap::new()));
|
let tool_call_names = Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
// Set system prompt
|
// Set system prompt
|
||||||
if let Some(ref system_prompt) = args.system {
|
if let Some(ref system_prompt) = args.system {
|
||||||
worker.set_system_prompt(system_prompt);
|
engine.set_system_prompt(system_prompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register tools (unless --no-tools)
|
// Register tools (unless --no-tools)
|
||||||
if !args.no_tools {
|
if !args.no_tools {
|
||||||
let app = AppContext;
|
let app = AppContext;
|
||||||
worker.register_tool(app.get_current_time_definition());
|
engine.register_tool(app.get_current_time_definition());
|
||||||
worker.register_tool(app.calculate_definition());
|
engine.register_tool(app.calculate_definition());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register streaming display handlers
|
// Register streaming display handlers
|
||||||
worker
|
engine
|
||||||
.timeline_mut()
|
.timeline_mut()
|
||||||
.on_text_block(StreamingPrinter::new())
|
.on_text_block(StreamingPrinter::new())
|
||||||
.on_tool_use_block(ToolCallPrinter::new(tool_call_names.clone()));
|
.on_tool_use_block(ToolCallPrinter::new(tool_call_names.clone()));
|
||||||
|
|
||||||
worker.set_interceptor(ToolResultPrinterPolicy::new(tool_call_names));
|
engine.set_interceptor(ToolResultPrinterPolicy::new(tool_call_names));
|
||||||
|
|
||||||
// One-shot mode
|
// One-shot mode
|
||||||
if let Some(prompt) = args.prompt {
|
if let Some(prompt) = args.prompt {
|
||||||
match worker.run(&prompt).await {
|
match engine.run(&prompt).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("\n❌ Error: {}", e);
|
eprintln!("\n❌ Error: {}", e);
|
||||||
|
|
@ -504,8 +504,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut locked = match worker.run(first_input).await {
|
let mut locked = match engine.run(first_input).await {
|
||||||
Ok(out) => out.worker,
|
Ok(out) => out.engine,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("\n❌ Error: {}", e);
|
eprintln!("\n❌ Error: {}", e);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -20,10 +20,10 @@ mod recorder;
|
||||||
mod scenarios;
|
mod scenarios;
|
||||||
|
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use llm_worker::llm_client::scheme::{
|
use llm_engine::llm_client::scheme::{
|
||||||
Scheme, anthropic::AnthropicScheme, gemini::GeminiScheme, openai_chat::OpenAIScheme,
|
Scheme, anthropic::AnthropicScheme, gemini::GeminiScheme, openai_chat::OpenAIScheme,
|
||||||
};
|
};
|
||||||
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth};
|
use llm_engine::llm_client::transport::{HttpTransport, ResolvedAuth};
|
||||||
|
|
||||||
fn make_transport<S: Scheme>(scheme: S, model: &str, auth: ResolvedAuth) -> HttpTransport<S> {
|
fn make_transport<S: Scheme>(scheme: S, model: &str, auth: ResolvedAuth) -> HttpTransport<S> {
|
||||||
let cap = scheme.default_capability();
|
let cap = scheme.default_capability();
|
||||||
|
|
@ -225,7 +225,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\n✅ Done!");
|
println!("\n✅ Done!");
|
||||||
println!("Run tests with: cargo test -p worker");
|
println!("Run tests with: cargo test -p engine");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ use std::path::Path;
|
||||||
use std::time::{Instant, SystemTime, UNIX_EPOCH};
|
use std::time::{Instant, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use llm_worker::llm_client::{LlmClient, Request};
|
use llm_engine::llm_client::{LlmClient, Request};
|
||||||
|
|
||||||
/// Recorded event
|
/// Recorded event
|
||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
|
@ -79,7 +79,7 @@ pub async fn record_request<C: LlmClient>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save
|
// Save
|
||||||
let fixtures_dir = Path::new("worker/tests/fixtures").join(subdir);
|
let fixtures_dir = Path::new("engine/tests/fixtures").join(subdir);
|
||||||
fs::create_dir_all(&fixtures_dir)?;
|
fs::create_dir_all(&fixtures_dir)?;
|
||||||
|
|
||||||
let filepath = fixtures_dir.join(format!("{}.jsonl", output_name));
|
let filepath = fixtures_dir.join(format!("{}.jsonl", output_name));
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
//!
|
//!
|
||||||
//! Defines requests and output file names for each scenario
|
//! Defines requests and output file names for each scenario
|
||||||
|
|
||||||
use llm_worker::llm_client::{Request, ToolDefinition};
|
use llm_engine::llm_client::{Request, ToolDefinition};
|
||||||
|
|
||||||
/// Test scenario
|
/// Test scenario
|
||||||
pub struct TestScenario {
|
pub struct TestScenario {
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! Closure-based event callback API
|
//! Closure-based event callback API
|
||||||
//!
|
//!
|
||||||
//! Provides a closure-based alternative to implementing `Handler<K>` directly.
|
//! Provides a closure-based alternative to implementing `Handler<K>` directly.
|
||||||
//! Register callbacks on `Worker` via `on_text_block()`, `on_tool_use_block()`,
|
//! Register callbacks on `Engine` via `on_text_block()`, `on_tool_use_block()`,
|
||||||
//! `on_usage()`, etc.
|
//! `on_usage()`, etc.
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
@ -18,13 +18,13 @@ use crate::tool::ToolCall;
|
||||||
|
|
||||||
/// Callback scope for a text block.
|
/// Callback scope for a text block.
|
||||||
///
|
///
|
||||||
/// Passed to the setup closure registered with `Worker::on_text_block()`.
|
/// Passed to the setup closure registered with `Engine::on_text_block()`.
|
||||||
/// Register per-block callbacks via `on_delta()` and `on_stop()`.
|
/// Register per-block callbacks via `on_delta()` and `on_stop()`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// worker.on_text_block(|block| {
|
/// engine.on_text_block(|block| {
|
||||||
/// block.on_delta(|text| print!("{}", text));
|
/// block.on_delta(|text| print!("{}", text));
|
||||||
/// block.on_stop(|full_text| println!("\n--- {} chars ---", full_text.len()));
|
/// block.on_stop(|full_text| println!("\n--- {} chars ---", full_text.len()));
|
||||||
/// });
|
/// });
|
||||||
|
|
@ -176,13 +176,13 @@ impl Handler<ThinkingBlockKind> for ClosureThinkingBlockHandler {
|
||||||
|
|
||||||
/// Callback scope for a tool use block.
|
/// Callback scope for a tool use block.
|
||||||
///
|
///
|
||||||
/// Passed to the setup closure registered with `Worker::on_tool_use_block()`.
|
/// Passed to the setup closure registered with `Engine::on_tool_use_block()`.
|
||||||
/// The setup closure also receives `&ToolUseBlockStart` with `id` and `name`.
|
/// The setup closure also receives `&ToolUseBlockStart` with `id` and `name`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// worker.on_tool_use_block(|start, block| {
|
/// engine.on_tool_use_block(|start, block| {
|
||||||
/// println!("Tool: {} ({})", start.name, start.id);
|
/// println!("Tool: {} ({})", start.name, start.id);
|
||||||
/// block.on_delta(|json| { /* streaming JSON fragment */ });
|
/// block.on_delta(|json| { /* streaming JSON fragment */ });
|
||||||
/// block.on_stop(|call| println!("Done: {}", call.name));
|
/// block.on_stop(|call| println!("Done: {}", call.name));
|
||||||
|
|
@ -22,19 +22,19 @@ use crate::{
|
||||||
ToolDefinition, error::is_retryable, event::Event, retry::RetryPolicy,
|
ToolDefinition, error::is_retryable, event::Event, retry::RetryPolicy,
|
||||||
transport::DEFAULT_FIRST_STREAM_EVENT_TIMEOUT, types::parse_tool_arguments,
|
transport::DEFAULT_FIRST_STREAM_EVENT_TIMEOUT, types::parse_tool_arguments,
|
||||||
},
|
},
|
||||||
state::{Locked, Mutable, WorkerState},
|
state::{EngineState, Locked, Mutable},
|
||||||
timeline::event::{ErrorEvent, StatusEvent, UsageEvent},
|
timeline::event::{ErrorEvent, StatusEvent, UsageEvent},
|
||||||
timeline::{TextBlockCollector, ThinkingBlockCollector, Timeline, ToolCallCollector},
|
timeline::{TextBlockCollector, ThinkingBlockCollector, Timeline, ToolCallCollector},
|
||||||
tool::{
|
tool::{
|
||||||
ToolCall, ToolDefinition as WorkerToolDefinition, ToolError, ToolExecutionContext,
|
ToolCall, ToolDefinition as EngineToolDefinition, ToolError, ToolExecutionContext,
|
||||||
ToolOutputLimits, ToolResult, truncate_content,
|
ToolOutputLimits, ToolResult, truncate_content,
|
||||||
},
|
},
|
||||||
tool_server::{ToolServer, ToolServerHandle},
|
tool_server::{ToolServer, ToolServerHandle},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Worker errors
|
/// Engine errors
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum WorkerError {
|
pub enum EngineError {
|
||||||
/// Client error
|
/// Client error
|
||||||
#[error("Client error: {0}")]
|
#[error("Client error: {0}")]
|
||||||
Client(#[from] ClientError),
|
Client(#[from] ClientError),
|
||||||
|
|
@ -60,17 +60,17 @@ pub enum ToolRegistryError {
|
||||||
DuplicateName(String),
|
DuplicateName(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Worker configuration
|
/// Engine configuration
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct WorkerConfig {
|
pub struct EngineConfig {
|
||||||
// Reserved for future extensions (currently empty)
|
// Reserved for future extensions (currently empty)
|
||||||
_private: (),
|
_private: (),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Worker execution result (status)
|
/// Engine execution result (status)
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum WorkerResult {
|
pub enum EngineResult {
|
||||||
/// Completed (waiting for user input)
|
/// Completed (waiting for user input)
|
||||||
Finished,
|
Finished,
|
||||||
/// Paused (can be resumed)
|
/// Paused (can be resumed)
|
||||||
|
|
@ -85,14 +85,14 @@ pub enum WorkerResult {
|
||||||
Yielded,
|
Yielded,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result of [`Worker<C, Mutable>::run()`] / [`Worker<C, Mutable>::resume()`].
|
/// Result of [`Engine<C, Mutable>::run()`] / [`Engine<C, Mutable>::resume()`].
|
||||||
///
|
///
|
||||||
/// Contains the `Locked` Worker (ready for subsequent runs) and the outcome.
|
/// Contains the `Locked` Engine (ready for subsequent runs) and the outcome.
|
||||||
pub struct RunOutput<C: LlmClient> {
|
pub struct EngineRunOutput<C: LlmClient> {
|
||||||
/// The Worker, now in Locked state.
|
/// The Engine, now in Locked state.
|
||||||
pub worker: Worker<C, Locked>,
|
pub engine: Engine<C, Locked>,
|
||||||
/// Outcome of the turn.
|
/// Outcome of the turn.
|
||||||
pub result: WorkerResult,
|
pub result: EngineResult,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal: tool execution result
|
/// Internal: tool execution result
|
||||||
|
|
@ -113,27 +113,27 @@ const MAX_STREAM_CONTINUATIONS: u32 = 3;
|
||||||
/// - [`Mutable`]: Initial state. System prompt, history, and tools can be freely edited.
|
/// - [`Mutable`]: Initial state. System prompt, history, and tools can be freely edited.
|
||||||
/// - [`Locked`]: Cache-protected state. Prefix context is immutable; only `run()` / `resume()` are available.
|
/// - [`Locked`]: Cache-protected state. Prefix context is immutable; only `run()` / `resume()` are available.
|
||||||
///
|
///
|
||||||
/// Calling `run()` on a `Mutable` Worker consumes it and returns a
|
/// Calling `run()` on a `Mutable` Engine consumes it and returns a
|
||||||
/// `Locked` Worker together with the result. This ensures the
|
/// `Locked` Engine together with the result. This ensures the
|
||||||
/// cache prefix is fixed for optimal KV cache hit rate.
|
/// cache prefix is fixed for optimal KV cache hit rate.
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// let mut worker = Worker::new(client)
|
/// let mut engine = Engine::new(client)
|
||||||
/// .system_prompt("You are a helpful assistant.");
|
/// .system_prompt("You are a helpful assistant.");
|
||||||
/// worker.register_tool(my_tool);
|
/// engine.register_tool(my_tool);
|
||||||
///
|
///
|
||||||
/// // Mutable::run() consumes self → RunOutput { worker: Locked, result }
|
/// // Mutable::run() consumes self → EngineRunOutput { engine: Locked, result }
|
||||||
/// let out = worker.run("Hello").await?;
|
/// let out = engine.run("Hello").await?;
|
||||||
/// let mut worker = out.worker;
|
/// let mut engine = out.engine;
|
||||||
///
|
///
|
||||||
/// // Locked::run() borrows &mut self
|
/// // Locked::run() borrows &mut self
|
||||||
/// worker.run("Follow-up").await?;
|
/// engine.run("Follow-up").await?;
|
||||||
///
|
///
|
||||||
/// // To edit between turns, unlock back to Mutable
|
/// // To edit between turns, unlock back to Mutable
|
||||||
/// let mut worker = worker.unlock();
|
/// let mut engine = engine.unlock();
|
||||||
/// worker.truncate_history(5);
|
/// engine.truncate_history(5);
|
||||||
/// let out = worker.run("Continue").await?;
|
/// let out = engine.run("Continue").await?;
|
||||||
/// let mut worker = out.worker;
|
/// let mut engine = out.engine;
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct LlmRetryNotice {
|
pub struct LlmRetryNotice {
|
||||||
|
|
@ -152,7 +152,7 @@ enum StreamCompletion {
|
||||||
Interrupted { reason: String },
|
Interrupted { reason: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Worker<C: LlmClient, S: WorkerState = Mutable> {
|
pub struct Engine<C: LlmClient, S: EngineState = Mutable> {
|
||||||
/// LLM client
|
/// LLM client
|
||||||
client: C,
|
client: C,
|
||||||
/// Retry policy for opening an LLM response stream.
|
/// Retry policy for opening an LLM response stream.
|
||||||
|
|
@ -172,22 +172,22 @@ pub struct Worker<C: LlmClient, S: WorkerState = Mutable> {
|
||||||
interceptor: Box<dyn Interceptor>,
|
interceptor: Box<dyn Interceptor>,
|
||||||
/// System prompt
|
/// System prompt
|
||||||
system_prompt: Option<String>,
|
system_prompt: Option<String>,
|
||||||
/// Item history (owned by Worker)
|
/// Item history (owned by Engine)
|
||||||
history: Vec<Item>,
|
history: Vec<Item>,
|
||||||
/// History length at lock time (only meaningful in Locked state)
|
/// History length at lock time (only meaningful in Locked state)
|
||||||
locked_prefix_len: usize,
|
locked_prefix_len: usize,
|
||||||
/// AgentTurn count.
|
/// AgentTurn count.
|
||||||
///
|
///
|
||||||
/// Once retry (`llm-worker-stream-continuation`) is implemented, an
|
/// Once retry (`llm-engine-stream-continuation`) is implemented, an
|
||||||
/// AgentTurn collapses N retried `LlmCall`s with identical input;
|
/// AgentTurn collapses N retried `LlmCall`s with identical input;
|
||||||
/// today retry is not implemented so AgentTurn and LlmCall fire 1:1
|
/// today retry is not implemented so AgentTurn and LlmCall fire 1:1
|
||||||
/// and the increment site (the LLM-call loop) is shared.
|
/// and the increment site (the LLM-call loop) is shared.
|
||||||
/// `max_turns` is interpreted as a per-`run()` AgentTurn cap.
|
/// `max_turns` is interpreted as a per-`run()` AgentTurn cap.
|
||||||
turn_count: usize,
|
turn_count: usize,
|
||||||
/// LlmCall count (per-Worker running counter, monotonic). Unlike
|
/// LlmCall count (per-Engine running counter, monotonic). Unlike
|
||||||
/// `turn_count` this never collapses retries.
|
/// `turn_count` this never collapses retries.
|
||||||
llm_call_count: usize,
|
llm_call_count: usize,
|
||||||
/// Tool execution batch count (per-Worker running counter, monotonic).
|
/// Tool execution batch count (per-Engine running counter, monotonic).
|
||||||
/// Each batch corresponds to one collected assistant tool-call set or one
|
/// Each batch corresponds to one collected assistant tool-call set or one
|
||||||
/// resumed pending tool-call set.
|
/// resumed pending tool-call set.
|
||||||
tool_execution_batch_count: usize,
|
tool_execution_batch_count: usize,
|
||||||
|
|
@ -212,7 +212,7 @@ pub struct Worker<C: LlmClient, S: WorkerState = Mutable> {
|
||||||
/// Pre-stream lifecycle callbacks for debugging stalls before provider
|
/// Pre-stream lifecycle callbacks for debugging stalls before provider
|
||||||
/// stream events become visible.
|
/// stream events become visible.
|
||||||
lifecycle_trace_cbs: Vec<Arc<dyn Fn(usize, usize, &str, &Value) + Send + Sync>>,
|
lifecycle_trace_cbs: Vec<Arc<dyn Fn(usize, usize, &str, &Value) + Send + Sync>>,
|
||||||
/// Non-fatal warning callbacks. Invoked when the Worker wants to
|
/// Non-fatal warning callbacks. Invoked when the Engine wants to
|
||||||
/// surface an advisory message to the upper layer (e.g. Pod) so it
|
/// surface an advisory message to the upper layer (e.g. Pod) so it
|
||||||
/// can be forwarded to the user — distinct from `tracing::warn!`,
|
/// can be forwarded to the user — distinct from `tracing::warn!`,
|
||||||
/// which is for developer-facing logs.
|
/// which is for developer-facing logs.
|
||||||
|
|
@ -223,7 +223,7 @@ pub struct Worker<C: LlmClient, S: WorkerState = Mutable> {
|
||||||
/// enters history.
|
/// enters history.
|
||||||
tool_result_cbs: Vec<Box<dyn Fn(&ToolResult) + Send + Sync>>,
|
tool_result_cbs: Vec<Box<dyn Fn(&ToolResult) + Send + Sync>>,
|
||||||
/// History-append callbacks. Invoked for non-streamed items when they
|
/// History-append callbacks. Invoked for non-streamed items when they
|
||||||
/// are appended to persistent worker history, so upper layers can
|
/// are appended to persistent engine history, so upper layers can
|
||||||
/// broadcast those items using history itself as the source of truth.
|
/// broadcast those items using history itself as the source of truth.
|
||||||
history_append_cbs: Vec<Box<dyn Fn(&Item) + Send + Sync>>,
|
history_append_cbs: Vec<Box<dyn Fn(&Item) + Send + Sync>>,
|
||||||
/// Request configuration (max_tokens, temperature, etc.)
|
/// Request configuration (max_tokens, temperature, etc.)
|
||||||
|
|
@ -260,7 +260,7 @@ pub struct Worker<C: LlmClient, S: WorkerState = Mutable> {
|
||||||
_state: PhantomData<S>,
|
_state: PhantomData<S>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
impl<C: LlmClient, S: EngineState> Engine<C, S> {
|
||||||
fn reset_interruption_state(&mut self) {
|
fn reset_interruption_state(&mut self) {
|
||||||
self.last_run_interrupted = false;
|
self.last_run_interrupted = false;
|
||||||
}
|
}
|
||||||
|
|
@ -269,7 +269,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
while self.cancel_rx.try_recv().is_ok() {}
|
while self.cancel_rx.try_recv().is_ok() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Discard pending cancellation notifications while the worker is idle.
|
/// Discard pending cancellation notifications while the engine is idle.
|
||||||
///
|
///
|
||||||
/// Cancellation is a running-turn control signal. Callers that own a higher
|
/// Cancellation is a running-turn control signal. Callers that own a higher
|
||||||
/// level run state can use this before starting a new turn so an old idle
|
/// level run state can use this before starting a new turn so an old idle
|
||||||
|
|
@ -296,7 +296,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// worker.on_text_block(|block| {
|
/// engine.on_text_block(|block| {
|
||||||
/// block.on_delta(|text| print!("{}", text));
|
/// block.on_delta(|text| print!("{}", text));
|
||||||
/// block.on_stop(|full_text| println!("\n--- {} chars ---", full_text.len()));
|
/// block.on_stop(|full_text| println!("\n--- {} chars ---", full_text.len()));
|
||||||
/// });
|
/// });
|
||||||
|
|
@ -335,7 +335,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// worker.on_tool_use_block(|start, block| {
|
/// engine.on_tool_use_block(|start, block| {
|
||||||
/// println!("Tool: {} ({})", start.name, start.id);
|
/// println!("Tool: {} ({})", start.name, start.id);
|
||||||
/// block.on_delta(|json| { /* streaming JSON fragment */ });
|
/// block.on_delta(|json| { /* streaming JSON fragment */ });
|
||||||
/// block.on_stop(|call| println!("Done: {}", call.name));
|
/// block.on_stop(|call| println!("Done: {}", call.name));
|
||||||
|
|
@ -468,7 +468,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
/// Register a non-fatal warning callback.
|
/// Register a non-fatal warning callback.
|
||||||
///
|
///
|
||||||
/// The callback is invoked with a short human-readable message
|
/// The callback is invoked with a short human-readable message
|
||||||
/// whenever the Worker encounters a condition that should be
|
/// whenever the Engine encounters a condition that should be
|
||||||
/// surfaced to a human (e.g. tool output byte-cap truncation).
|
/// surfaced to a human (e.g. tool output byte-cap truncation).
|
||||||
/// This channel is separate from `tracing::warn!`, which remains
|
/// This channel is separate from `tracing::warn!`, which remains
|
||||||
/// in place for developer logs.
|
/// in place for developer logs.
|
||||||
|
|
@ -498,7 +498,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a callback invoked for items appended directly to worker
|
/// Register a callback invoked for items appended directly to engine
|
||||||
/// history outside streaming timeline callbacks.
|
/// history outside streaming timeline callbacks.
|
||||||
pub fn on_history_append(&mut self, callback: impl Fn(&Item) + Send + Sync + 'static) {
|
pub fn on_history_append(&mut self, callback: impl Fn(&Item) + Send + Sync + 'static) {
|
||||||
self.history_append_cbs.push(Box::new(callback));
|
self.history_append_cbs.push(Box::new(callback));
|
||||||
|
|
@ -639,7 +639,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
self.turn_count
|
self.turn_count
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current LlmCall count (per-Worker running counter, never
|
/// Get the current LlmCall count (per-Engine running counter, never
|
||||||
/// collapsed by retry).
|
/// collapsed by retry).
|
||||||
pub fn llm_call_count(&self) -> usize {
|
pub fn llm_call_count(&self) -> usize {
|
||||||
self.llm_call_count
|
self.llm_call_count
|
||||||
|
|
@ -657,7 +657,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// worker.set_max_tokens(4096);
|
/// engine.set_max_tokens(4096);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_max_tokens(&mut self, max_tokens: u32) {
|
pub fn set_max_tokens(&mut self, max_tokens: u32) {
|
||||||
self.request_config.max_tokens = Some(max_tokens);
|
self.request_config.max_tokens = Some(max_tokens);
|
||||||
|
|
@ -671,7 +671,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// worker.set_temperature(0.7);
|
/// engine.set_temperature(0.7);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_temperature(&mut self, temperature: f32) {
|
pub fn set_temperature(&mut self, temperature: f32) {
|
||||||
self.request_config.temperature = Some(temperature);
|
self.request_config.temperature = Some(temperature);
|
||||||
|
|
@ -682,7 +682,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// worker.set_top_p(0.9);
|
/// engine.set_top_p(0.9);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_top_p(&mut self, top_p: f32) {
|
pub fn set_top_p(&mut self, top_p: f32) {
|
||||||
self.request_config.top_p = Some(top_p);
|
self.request_config.top_p = Some(top_p);
|
||||||
|
|
@ -695,7 +695,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// worker.set_top_k(40);
|
/// engine.set_top_k(40);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_top_k(&mut self, top_k: u32) {
|
pub fn set_top_k(&mut self, top_k: u32) {
|
||||||
self.request_config.top_k = Some(top_k);
|
self.request_config.top_k = Some(top_k);
|
||||||
|
|
@ -706,7 +706,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// worker.add_stop_sequence("\n\n");
|
/// engine.add_stop_sequence("\n\n");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn add_stop_sequence(&mut self, sequence: impl Into<String>) {
|
pub fn add_stop_sequence(&mut self, sequence: impl Into<String>) {
|
||||||
self.request_config.stop_sequences.push(sequence.into());
|
self.request_config.stop_sequences.push(sequence.into());
|
||||||
|
|
@ -730,23 +730,23 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
/// Cancel execution
|
/// Cancel execution
|
||||||
///
|
///
|
||||||
/// Interrupts currently running streaming or tool execution.
|
/// Interrupts currently running streaming or tool execution.
|
||||||
/// WorkerError::Cancelled is returned at the next event loop checkpoint.
|
/// EngineError::Cancelled is returned at the next event loop checkpoint.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// use std::sync::Arc;
|
/// use std::sync::Arc;
|
||||||
/// let worker = Arc::new(Mutex::new(Worker::new(client)));
|
/// let engine = Arc::new(Mutex::new(Engine::new(client)));
|
||||||
///
|
///
|
||||||
/// // Run in another thread
|
/// // Run in another thread
|
||||||
/// let worker_clone = worker.clone();
|
/// let worker_clone = engine.clone();
|
||||||
/// tokio::spawn(async move {
|
/// tokio::spawn(async move {
|
||||||
/// let mut w = worker_clone.lock().unwrap();
|
/// let mut w = worker_clone.lock().unwrap();
|
||||||
/// w.run("Long task...").await
|
/// w.run("Long task...").await
|
||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// // Cancel
|
/// // Cancel
|
||||||
/// worker.lock().unwrap().cancel();
|
/// engine.lock().unwrap().cancel();
|
||||||
/// ```
|
/// ```
|
||||||
pub fn cancel(&self) {
|
pub fn cancel(&self) {
|
||||||
let _ = self.cancel_tx.try_send(());
|
let _ = self.cancel_tx.try_send(());
|
||||||
|
|
@ -849,15 +849,15 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
///
|
///
|
||||||
async fn finalize_interruption<T>(
|
async fn finalize_interruption<T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
result: Result<T, WorkerError>,
|
result: Result<T, EngineError>,
|
||||||
) -> Result<T, WorkerError> {
|
) -> Result<T, EngineError> {
|
||||||
match result {
|
match result {
|
||||||
Ok(value) => Ok(value),
|
Ok(value) => Ok(value),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
let reason = match &err {
|
let reason = match &err {
|
||||||
WorkerError::Aborted(reason) => reason.clone(),
|
EngineError::Aborted(reason) => reason.clone(),
|
||||||
WorkerError::Cancelled => "Cancelled".to_string(),
|
EngineError::Cancelled => "Cancelled".to_string(),
|
||||||
_ => err.to_string(),
|
_ => err.to_string(),
|
||||||
};
|
};
|
||||||
self.interceptor.on_abort(&reason).await;
|
self.interceptor.on_abort(&reason).await;
|
||||||
|
|
@ -913,7 +913,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
async fn execute_tools(
|
async fn execute_tools(
|
||||||
&mut self,
|
&mut self,
|
||||||
tool_calls: Vec<ToolCall>,
|
tool_calls: Vec<ToolCall>,
|
||||||
) -> Result<ToolExecutionResult, WorkerError> {
|
) -> Result<ToolExecutionResult, EngineError> {
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
|
|
||||||
// Map from tool call ID to (ToolCall, Meta, Tool, Context)
|
// Map from tool call ID to (ToolCall, Meta, Tool, Context)
|
||||||
|
|
@ -953,7 +953,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
}
|
}
|
||||||
PreToolAction::Abort(reason) => {
|
PreToolAction::Abort(reason) => {
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Err(WorkerError::Aborted(reason));
|
return Err(EngineError::Aborted(reason));
|
||||||
}
|
}
|
||||||
PreToolAction::Pause => {
|
PreToolAction::Pause => {
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
|
|
@ -1010,7 +1010,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
}
|
}
|
||||||
self.timeline.abort_current_block();
|
self.timeline.abort_current_block();
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Err(WorkerError::Cancelled);
|
return Err(EngineError::Cancelled);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
results.extend(synthetic_results);
|
results.extend(synthetic_results);
|
||||||
|
|
@ -1032,7 +1032,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
PostToolAction::Continue => {}
|
PostToolAction::Continue => {}
|
||||||
PostToolAction::Abort(reason) => {
|
PostToolAction::Abort(reason) => {
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Err(WorkerError::Aborted(reason));
|
return Err(EngineError::Aborted(reason));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Reflect interceptor-modified results
|
// Reflect interceptor-modified results
|
||||||
|
|
@ -1084,14 +1084,14 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal turn execution logic
|
/// Internal turn execution logic
|
||||||
async fn run_turn_loop(&mut self) -> Result<WorkerResult, WorkerError> {
|
async fn run_turn_loop(&mut self) -> Result<EngineResult, EngineError> {
|
||||||
self.reset_interruption_state();
|
self.reset_interruption_state();
|
||||||
let tool_definitions = self.build_tool_definitions();
|
let tool_definitions = self.build_tool_definitions();
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
item_count = self.history.len(),
|
item_count = self.history.len(),
|
||||||
tool_count = tool_definitions.len(),
|
tool_count = tool_definitions.len(),
|
||||||
"Starting worker run"
|
"Starting engine run"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Resume pending tool calls from a previous Pause
|
// Resume pending tool calls from a previous Pause
|
||||||
|
|
@ -1109,7 +1109,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
info!("Execution cancelled");
|
info!("Execution cancelled");
|
||||||
self.timeline.abort_current_block();
|
self.timeline.abort_current_block();
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Err(WorkerError::Cancelled);
|
return Err(EngineError::Cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_turn = self.turn_count;
|
let current_turn = self.turn_count;
|
||||||
|
|
@ -1138,7 +1138,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
// Prune projection: if both the config and the savings
|
// Prune projection: if both the config and the savings
|
||||||
// estimator are configured, drop ToolResult.content from
|
// estimator are configured, drop ToolResult.content from
|
||||||
// prunable candidates whose estimated savings meet the
|
// prunable candidates whose estimated savings meet the
|
||||||
// threshold. Worker does not own usage history itself; the
|
// threshold. Engine does not own usage history itself; the
|
||||||
// estimator is injected by the layer that does.
|
// estimator is injected by the layer that does.
|
||||||
if let (Some(config), Some(token_estimator), Some(savings_estimator)) = (
|
if let (Some(config), Some(token_estimator), Some(savings_estimator)) = (
|
||||||
&self.prune_config,
|
&self.prune_config,
|
||||||
|
|
@ -1199,7 +1199,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
cb(current_turn);
|
cb(current_turn);
|
||||||
}
|
}
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Err(WorkerError::Aborted(reason));
|
return Err(EngineError::Aborted(reason));
|
||||||
}
|
}
|
||||||
PreRequestAction::YieldWith(items) => {
|
PreRequestAction::YieldWith(items) => {
|
||||||
self.append_history_items(items.clone());
|
self.append_history_items(items.clone());
|
||||||
|
|
@ -1209,7 +1209,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
cb(current_turn);
|
cb(current_turn);
|
||||||
}
|
}
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Ok(WorkerResult::Yielded);
|
return Ok(EngineResult::Yielded);
|
||||||
}
|
}
|
||||||
PreRequestAction::Yield => {
|
PreRequestAction::Yield => {
|
||||||
info!("Yielded by interceptor");
|
info!("Yielded by interceptor");
|
||||||
|
|
@ -1217,7 +1217,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
cb(current_turn);
|
cb(current_turn);
|
||||||
}
|
}
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Ok(WorkerResult::Yielded);
|
return Ok(EngineResult::Yielded);
|
||||||
}
|
}
|
||||||
PreRequestAction::ContinueWith(items) => {
|
PreRequestAction::ContinueWith(items) => {
|
||||||
self.append_history_items(items.clone());
|
self.append_history_items(items.clone());
|
||||||
|
|
@ -1227,7 +1227,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LlmCall boundary fires per LLM generation request — today
|
// LlmCall boundary fires per LLM generation request — today
|
||||||
// 1:1 with AgentTurn, but retry (`llm-worker-stream-continuation`)
|
// 1:1 with AgentTurn, but retry (`llm-engine-stream-continuation`)
|
||||||
// will multiply this within a single AgentTurn.
|
// will multiply this within a single AgentTurn.
|
||||||
let current_llm_call = self.llm_call_count;
|
let current_llm_call = self.llm_call_count;
|
||||||
for cb in &self.llm_call_start_cbs {
|
for cb in &self.llm_call_start_cbs {
|
||||||
|
|
@ -1262,7 +1262,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
stream_continuations += 1;
|
stream_continuations += 1;
|
||||||
if stream_continuations > MAX_STREAM_CONTINUATIONS {
|
if stream_continuations > MAX_STREAM_CONTINUATIONS {
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Err(WorkerError::Client(ClientError::Api {
|
return Err(EngineError::Client(ClientError::Api {
|
||||||
status: None,
|
status: None,
|
||||||
code: None,
|
code: None,
|
||||||
message: format!("LLM stream interrupted too many times: {reason}"),
|
message: format!("LLM stream interrupted too many times: {reason}"),
|
||||||
|
|
@ -1315,7 +1315,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
match self.interceptor.on_turn_end(&self.history).await {
|
match self.interceptor.on_turn_end(&self.history).await {
|
||||||
TurnEndAction::Finish => {
|
TurnEndAction::Finish => {
|
||||||
self.last_run_interrupted = false;
|
self.last_run_interrupted = false;
|
||||||
return Ok(WorkerResult::Finished);
|
return Ok(EngineResult::Finished);
|
||||||
}
|
}
|
||||||
TurnEndAction::ContinueWithMessages(additional) => {
|
TurnEndAction::ContinueWithMessages(additional) => {
|
||||||
self.append_history_items(additional);
|
self.append_history_items(additional);
|
||||||
|
|
@ -1323,7 +1323,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
}
|
}
|
||||||
TurnEndAction::Pause => {
|
TurnEndAction::Pause => {
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Ok(WorkerResult::Paused);
|
return Ok(EngineResult::Paused);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1340,7 +1340,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
"Turn limit reached"
|
"Turn limit reached"
|
||||||
);
|
);
|
||||||
self.last_run_interrupted = false;
|
self.last_run_interrupted = false;
|
||||||
return Ok(WorkerResult::LimitReached);
|
return Ok(EngineResult::LimitReached);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1351,7 +1351,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
request: Request,
|
request: Request,
|
||||||
turn: usize,
|
turn: usize,
|
||||||
llm_call: usize,
|
llm_call: usize,
|
||||||
) -> Result<ResponseStream, WorkerError> {
|
) -> Result<ResponseStream, EngineError> {
|
||||||
let policy = self.retry_policy.clone();
|
let policy = self.retry_policy.clone();
|
||||||
let started = Instant::now();
|
let started = Instant::now();
|
||||||
let mut failed_attempt: u32 = 0;
|
let mut failed_attempt: u32 = 0;
|
||||||
|
|
@ -1385,7 +1385,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
);
|
);
|
||||||
self.timeline.abort_current_block();
|
self.timeline.abort_current_block();
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Err(WorkerError::Cancelled);
|
return Err(EngineError::Cancelled);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1417,7 +1417,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
);
|
);
|
||||||
self.timeline.abort_current_block();
|
self.timeline.abort_current_block();
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Err(WorkerError::Cancelled);
|
return Err(EngineError::Cancelled);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match first_event_result {
|
match first_event_result {
|
||||||
|
|
@ -1459,7 +1459,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
let next_failed_attempt = failed_attempt + 1;
|
let next_failed_attempt = failed_attempt + 1;
|
||||||
if next_failed_attempt >= policy.max_attempts || !is_retryable(&err) {
|
if next_failed_attempt >= policy.max_attempts || !is_retryable(&err) {
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Err(WorkerError::Client(err));
|
return Err(EngineError::Client(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
let wait = err
|
let wait = err
|
||||||
|
|
@ -1468,7 +1468,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
let elapsed = started.elapsed();
|
let elapsed = started.elapsed();
|
||||||
if elapsed + wait > policy.total_timeout {
|
if elapsed + wait > policy.total_timeout {
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Err(WorkerError::Client(err));
|
return Err(EngineError::Client(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
warn!(
|
warn!(
|
||||||
|
|
@ -1497,7 +1497,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
}
|
}
|
||||||
self.timeline.abort_current_block();
|
self.timeline.abort_current_block();
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Err(WorkerError::Cancelled);
|
return Err(EngineError::Cancelled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1511,7 +1511,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
request: Request,
|
request: Request,
|
||||||
turn: usize,
|
turn: usize,
|
||||||
llm_call: usize,
|
llm_call: usize,
|
||||||
) -> Result<StreamCompletion, WorkerError> {
|
) -> Result<StreamCompletion, EngineError> {
|
||||||
debug!(
|
debug!(
|
||||||
item_count = request.items.len(),
|
item_count = request.items.len(),
|
||||||
tool_count = request.tools.len(),
|
tool_count = request.tools.len(),
|
||||||
|
|
@ -1561,7 +1561,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
self.timeline.abort_current_block();
|
self.timeline.abort_current_block();
|
||||||
self.timeline.flush_usage();
|
self.timeline.flush_usage();
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Err(WorkerError::Client(ClientError::Api {
|
return Err(EngineError::Client(ClientError::Api {
|
||||||
status: None,
|
status: None,
|
||||||
code: err.code.clone(),
|
code: err.code.clone(),
|
||||||
message: err.message.clone(),
|
message: err.message.clone(),
|
||||||
|
|
@ -1579,7 +1579,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
self.timeline.abort_current_block();
|
self.timeline.abort_current_block();
|
||||||
self.timeline.flush_usage();
|
self.timeline.flush_usage();
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return Err(WorkerError::Cancelled);
|
return Err(EngineError::Cancelled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1595,11 +1595,11 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
async fn execute_and_commit_tools(
|
async fn execute_and_commit_tools(
|
||||||
&mut self,
|
&mut self,
|
||||||
tool_calls: Vec<ToolCall>,
|
tool_calls: Vec<ToolCall>,
|
||||||
) -> Result<Option<WorkerResult>, WorkerError> {
|
) -> Result<Option<EngineResult>, EngineError> {
|
||||||
match self.execute_tools(tool_calls).await {
|
match self.execute_tools(tool_calls).await {
|
||||||
Ok(ToolExecutionResult::Paused) => {
|
Ok(ToolExecutionResult::Paused) => {
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
Ok(Some(WorkerResult::Paused))
|
Ok(Some(EngineResult::Paused))
|
||||||
}
|
}
|
||||||
Ok(ToolExecutionResult::Completed(results)) => {
|
Ok(ToolExecutionResult::Completed(results)) => {
|
||||||
// Route per-result pushes through the callback path so
|
// Route per-result pushes through the callback path so
|
||||||
|
|
@ -1624,8 +1624,8 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: LlmClient> Worker<C, Mutable> {
|
impl<C: LlmClient> Engine<C, Mutable> {
|
||||||
/// Create a new Worker (in Mutable state)
|
/// Create a new Engine (in Mutable state)
|
||||||
pub fn new(client: C) -> Self {
|
pub fn new(client: C) -> Self {
|
||||||
let text_block_collector = TextBlockCollector::new();
|
let text_block_collector = TextBlockCollector::new();
|
||||||
let tool_call_collector = ToolCallCollector::new();
|
let tool_call_collector = ToolCallCollector::new();
|
||||||
|
|
@ -1684,13 +1684,13 @@ impl<C: LlmClient> Worker<C, Mutable> {
|
||||||
///
|
///
|
||||||
/// The factory is queued and executed at the next `run()` or `resume()` call.
|
/// The factory is queued and executed at the next `run()` or `resume()` call.
|
||||||
/// Duplicate name detection occurs at that point and surfaces as
|
/// Duplicate name detection occurs at that point and surfaces as
|
||||||
/// [`WorkerError::ToolRegistry`].
|
/// [`EngineError::ToolRegistry`].
|
||||||
pub fn register_tool(&mut self, factory: WorkerToolDefinition) {
|
pub fn register_tool(&mut self, factory: EngineToolDefinition) {
|
||||||
self.tool_server.register_tool(factory);
|
self.tool_server.register_tool(factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register multiple tool factories for deferred initialization.
|
/// Register multiple tool factories for deferred initialization.
|
||||||
pub fn register_tools(&mut self, factories: impl IntoIterator<Item = WorkerToolDefinition>) {
|
pub fn register_tools(&mut self, factories: impl IntoIterator<Item = EngineToolDefinition>) {
|
||||||
self.tool_server.register_tools(factories);
|
self.tool_server.register_tools(factories);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1719,7 +1719,7 @@ impl<C: LlmClient> Worker<C, Mutable> {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// let worker = Worker::new(client)
|
/// let engine = Engine::new(client)
|
||||||
/// .system_prompt("You are a helpful assistant.")
|
/// .system_prompt("You are a helpful assistant.")
|
||||||
/// .max_tokens(4096);
|
/// .max_tokens(4096);
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -1733,7 +1733,7 @@ impl<C: LlmClient> Worker<C, Mutable> {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// let worker = Worker::new(client)
|
/// let engine = Engine::new(client)
|
||||||
/// .temperature(0.7);
|
/// .temperature(0.7);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn temperature(mut self, temperature: f32) -> Self {
|
pub fn temperature(mut self, temperature: f32) -> Self {
|
||||||
|
|
@ -1768,7 +1768,7 @@ impl<C: LlmClient> Worker<C, Mutable> {
|
||||||
/// .with_max_tokens(4096)
|
/// .with_max_tokens(4096)
|
||||||
/// .with_temperature(0.7);
|
/// .with_temperature(0.7);
|
||||||
///
|
///
|
||||||
/// let worker = Worker::new(client)
|
/// let engine = Engine::new(client)
|
||||||
/// .system_prompt("...")
|
/// .system_prompt("...")
|
||||||
/// .with_config(config);
|
/// .with_config(config);
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -1791,7 +1791,7 @@ impl<C: LlmClient> Worker<C, Mutable> {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// let worker = Worker::new(client)
|
/// let engine = Engine::new(client)
|
||||||
/// .temperature(0.7)
|
/// .temperature(0.7)
|
||||||
/// .top_k(40)
|
/// .top_k(40)
|
||||||
/// .validate()?; // Error if using OpenAI since top_k is not supported
|
/// .validate()?; // Error if using OpenAI since top_k is not supported
|
||||||
|
|
@ -1799,13 +1799,13 @@ impl<C: LlmClient> Worker<C, Mutable> {
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// * `Ok(Self)` - Validation successful
|
/// * `Ok(Self)` - Validation successful
|
||||||
/// * `Err(WorkerError::ConfigWarnings)` - Has unsupported settings
|
/// * `Err(EngineError::ConfigWarnings)` - Has unsupported settings
|
||||||
pub fn validate(self) -> Result<Self, WorkerError> {
|
pub fn validate(self) -> Result<Self, EngineError> {
|
||||||
let warnings = self.client.validate_config(&self.request_config);
|
let warnings = self.client.validate_config(&self.request_config);
|
||||||
if warnings.is_empty() {
|
if warnings.is_empty() {
|
||||||
Ok(self)
|
Ok(self)
|
||||||
} else {
|
} else {
|
||||||
Err(WorkerError::ConfigWarnings(warnings))
|
Err(EngineError::ConfigWarnings(warnings))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1820,7 +1820,7 @@ impl<C: LlmClient> Worker<C, Mutable> {
|
||||||
|
|
||||||
/// Append items to history and notify history-append observers for each
|
/// Append items to history and notify history-append observers for each
|
||||||
/// item before it lands. This is the only public Mutable-state API for
|
/// item before it lands. This is the only public Mutable-state API for
|
||||||
/// growing worker history; callers that need session-log persistence must
|
/// growing engine history; callers that need session-log persistence must
|
||||||
/// install [`on_history_append`](Self::on_history_append) before calling it.
|
/// install [`on_history_append`](Self::on_history_append) before calling it.
|
||||||
pub fn append_history(&mut self, items: impl IntoIterator<Item = Item>) {
|
pub fn append_history(&mut self, items: impl IntoIterator<Item = Item>) {
|
||||||
self.append_history_items(items);
|
self.append_history_items(items);
|
||||||
|
|
@ -1855,7 +1855,7 @@ impl<C: LlmClient> Worker<C, Mutable> {
|
||||||
|
|
||||||
/// Apply configuration (reserved for future extensions)
|
/// Apply configuration (reserved for future extensions)
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn config(self, _config: WorkerConfig) -> Self {
|
pub fn config(self, _config: EngineConfig) -> Self {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1864,13 +1864,16 @@ impl<C: LlmClient> Worker<C, Mutable> {
|
||||||
/// This is the primary entry point for first use. Equivalent to
|
/// This is the primary entry point for first use. Equivalent to
|
||||||
/// `self.lock()` followed by `locked.run(user_input)`.
|
/// `self.lock()` followed by `locked.run(user_input)`.
|
||||||
///
|
///
|
||||||
/// Subsequent runs can use [`Worker<C, Locked>::run()`] directly.
|
/// Subsequent runs can use [`Engine<C, Locked>::run()`] directly.
|
||||||
/// To edit state between turns, call [`unlock()`](Worker::unlock) first.
|
/// To edit state between turns, call [`unlock()`](Engine::unlock) first.
|
||||||
pub async fn run(self, user_input: impl Into<String>) -> Result<RunOutput<C>, WorkerError> {
|
pub async fn run(
|
||||||
|
self,
|
||||||
|
user_input: impl Into<String>,
|
||||||
|
) -> Result<EngineRunOutput<C>, EngineError> {
|
||||||
let mut locked = self.lock();
|
let mut locked = self.lock();
|
||||||
let result = locked.run(user_input).await?;
|
let result = locked.run(user_input).await?;
|
||||||
Ok(RunOutput {
|
Ok(EngineRunOutput {
|
||||||
worker: locked,
|
engine: locked,
|
||||||
result,
|
result,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -1878,11 +1881,11 @@ impl<C: LlmClient> Worker<C, Mutable> {
|
||||||
/// Resume from Paused, consuming self and transitioning to Locked.
|
/// Resume from Paused, consuming self and transitioning to Locked.
|
||||||
///
|
///
|
||||||
/// Used after `unlock()` → edit → resume.
|
/// Used after `unlock()` → edit → resume.
|
||||||
pub async fn resume(self) -> Result<RunOutput<C>, WorkerError> {
|
pub async fn resume(self) -> Result<EngineRunOutput<C>, EngineError> {
|
||||||
let mut locked = self.lock();
|
let mut locked = self.lock();
|
||||||
let result = locked.resume().await?;
|
let result = locked.resume().await?;
|
||||||
Ok(RunOutput {
|
Ok(EngineRunOutput {
|
||||||
worker: locked,
|
engine: locked,
|
||||||
result,
|
result,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -1895,15 +1898,15 @@ impl<C: LlmClient> Worker<C, Mutable> {
|
||||||
///
|
///
|
||||||
/// Most callers should use [`run()`](Self::run) instead, which calls
|
/// Most callers should use [`run()`](Self::run) instead, which calls
|
||||||
/// this internally. Use `lock()` directly only when you need the
|
/// this internally. Use `lock()` directly only when you need the
|
||||||
/// `Locked` worker back on error (e.g. in a persistence layer).
|
/// `Locked` engine back on error (e.g. in a persistence layer).
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if a pending tool factory produces a duplicate name.
|
/// Panics if a pending tool factory produces a duplicate name.
|
||||||
pub fn lock(self) -> Worker<C, Locked> {
|
pub fn lock(self) -> Engine<C, Locked> {
|
||||||
self.tool_server.flush_pending();
|
self.tool_server.flush_pending();
|
||||||
let locked_prefix_len = self.history.len();
|
let locked_prefix_len = self.history.len();
|
||||||
Worker {
|
Engine {
|
||||||
client: self.client,
|
client: self.client,
|
||||||
retry_policy: self.retry_policy,
|
retry_policy: self.retry_policy,
|
||||||
timeline: self.timeline,
|
timeline: self.timeline,
|
||||||
|
|
@ -1947,7 +1950,7 @@ impl<C: LlmClient> Worker<C, Mutable> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: LlmClient> Worker<C, Locked> {
|
impl<C: LlmClient> Engine<C, Locked> {
|
||||||
/// Execute a turn
|
/// Execute a turn
|
||||||
///
|
///
|
||||||
/// Adds a new user message to history and sends a request to the LLM.
|
/// Adds a new user message to history and sends a request to the LLM.
|
||||||
|
|
@ -1955,7 +1958,7 @@ impl<C: LlmClient> Worker<C, Locked> {
|
||||||
pub async fn run(
|
pub async fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
user_input: impl Into<String>,
|
user_input: impl Into<String>,
|
||||||
) -> Result<WorkerResult, WorkerError> {
|
) -> Result<EngineResult, EngineError> {
|
||||||
self.reset_interruption_state();
|
self.reset_interruption_state();
|
||||||
// Interceptor: on_prompt_submit
|
// Interceptor: on_prompt_submit
|
||||||
let mut user_item = Item::user_message(user_input);
|
let mut user_item = Item::user_message(user_input);
|
||||||
|
|
@ -1963,7 +1966,7 @@ impl<C: LlmClient> Worker<C, Locked> {
|
||||||
PromptAction::Cancel(reason) => {
|
PromptAction::Cancel(reason) => {
|
||||||
self.last_run_interrupted = true;
|
self.last_run_interrupted = true;
|
||||||
return self
|
return self
|
||||||
.finalize_interruption(Err(WorkerError::Aborted(reason)))
|
.finalize_interruption(Err(EngineError::Aborted(reason)))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
PromptAction::Continue => Vec::new(),
|
PromptAction::Continue => Vec::new(),
|
||||||
|
|
@ -1980,7 +1983,7 @@ impl<C: LlmClient> Worker<C, Locked> {
|
||||||
/// Resume execution (from Paused state)
|
/// Resume execution (from Paused state)
|
||||||
///
|
///
|
||||||
/// Resumes turn processing from current state without adding a new user message.
|
/// Resumes turn processing from current state without adding a new user message.
|
||||||
pub async fn resume(&mut self) -> Result<WorkerResult, WorkerError> {
|
pub async fn resume(&mut self) -> Result<EngineResult, EngineError> {
|
||||||
self.reset_interruption_state();
|
self.reset_interruption_state();
|
||||||
let result = self.run_turn_loop().await;
|
let result = self.run_turn_loop().await;
|
||||||
self.finalize_interruption(result).await
|
self.finalize_interruption(result).await
|
||||||
|
|
@ -1995,8 +1998,8 @@ impl<C: LlmClient> Worker<C, Locked> {
|
||||||
///
|
///
|
||||||
/// Note: After this operation, subsequent requests may not hit the cache.
|
/// Note: After this operation, subsequent requests may not hit the cache.
|
||||||
/// Use only when you need to edit history.
|
/// Use only when you need to edit history.
|
||||||
pub fn unlock(self) -> Worker<C, Mutable> {
|
pub fn unlock(self) -> Engine<C, Mutable> {
|
||||||
Worker {
|
Engine {
|
||||||
client: self.client,
|
client: self.client,
|
||||||
retry_policy: self.retry_policy,
|
retry_policy: self.retry_policy,
|
||||||
timeline: self.timeline,
|
timeline: self.timeline,
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//! Public event types for Worker layer
|
//! Public event types for Engine layer
|
||||||
//!
|
//!
|
||||||
//! Re-exports from the canonical event definitions in llm_client.
|
//! Re-exports from the canonical event definitions in llm_client.
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ pub trait Kind {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// use llm_worker::timeline::{Handler, TextBlockEvent, TextBlockKind};
|
/// use llm_engine::timeline::{Handler, TextBlockEvent, TextBlockKind};
|
||||||
///
|
///
|
||||||
/// struct TextCollector {
|
/// struct TextCollector {
|
||||||
/// texts: Vec<String>,
|
/// texts: Vec<String>,
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
//! Interceptor - control flow delegation for the Worker execution loop
|
//! Interceptor - control flow delegation for the Engine execution loop
|
||||||
//!
|
//!
|
||||||
//! Defines the [`Interceptor`] trait that upper layers (e.g. Pod) implement
|
//! Defines the [`Interceptor`] trait that upper layers (e.g. Pod) implement
|
||||||
//! to inject orchestration decisions (approval, skip, pause, abort)
|
//! to inject orchestration decisions (approval, skip, pause, abort)
|
||||||
//! into the Worker's turn loop without the Worker knowing about
|
//! into the Engine's turn loop without the Engine knowing about
|
||||||
//! higher-level concepts.
|
//! higher-level concepts.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
@ -36,13 +36,13 @@ pub enum PromptAction {
|
||||||
pub enum PreRequestAction {
|
pub enum PreRequestAction {
|
||||||
/// Proceed normally.
|
/// Proceed normally.
|
||||||
Continue,
|
Continue,
|
||||||
/// Proceed after appending these items to durable worker history.
|
/// Proceed after appending these items to durable engine history.
|
||||||
///
|
///
|
||||||
/// This is for upper-layer budget/status nudges that the model may react
|
/// This is for upper-layer budget/status nudges that the model may react
|
||||||
/// to: the items are committed before the request so later turns can see
|
/// to: the items are committed before the request so later turns can see
|
||||||
/// why the worker changed course.
|
/// why the engine changed course.
|
||||||
ContinueWith(Vec<Item>),
|
ContinueWith(Vec<Item>),
|
||||||
/// Yield after appending these items to durable worker history.
|
/// Yield after appending these items to durable engine history.
|
||||||
///
|
///
|
||||||
/// This is for host-mediated pre-request appends that must be visible to
|
/// This is for host-mediated pre-request appends that must be visible to
|
||||||
/// usage accounting and compaction checks before the current LLM request is
|
/// usage accounting and compaction checks before the current LLM request is
|
||||||
|
|
@ -52,7 +52,7 @@ pub enum PreRequestAction {
|
||||||
Cancel(String),
|
Cancel(String),
|
||||||
/// Yield control to the caller for external processing.
|
/// Yield control to the caller for external processing.
|
||||||
///
|
///
|
||||||
/// The Worker exits the turn loop cleanly with `WorkerResult::Yielded`.
|
/// The Engine exits the turn loop cleanly with `EngineResult::Yielded`.
|
||||||
/// The caller is expected to resume execution later.
|
/// The caller is expected to resume execution later.
|
||||||
Yield,
|
Yield,
|
||||||
}
|
}
|
||||||
|
|
@ -129,9 +129,9 @@ pub struct ToolResultInfo {
|
||||||
// Interceptor Trait
|
// Interceptor Trait
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
/// Intercepts the Worker execution loop at key decision points.
|
/// Intercepts the Engine execution loop at key decision points.
|
||||||
///
|
///
|
||||||
/// All methods have default implementations that let the Worker
|
/// All methods have default implementations that let the Engine
|
||||||
/// proceed without intervention. Upper layers (e.g. Pod) provide
|
/// proceed without intervention. Upper layers (e.g. Pod) provide
|
||||||
/// richer implementations for approval flows, permission checks, etc.
|
/// richer implementations for approval flows, permission checks, etc.
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|
@ -141,7 +141,7 @@ pub trait Interceptor: Send + Sync {
|
||||||
PromptAction::Continue
|
PromptAction::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Items that should be **committed to `worker.history`** just
|
/// Items that should be **committed to `engine.history`** just
|
||||||
/// before the next LLM request. Returned items are `extend`ed into
|
/// before the next LLM request. Returned items are `extend`ed into
|
||||||
/// the persistent history (and therefore picked up by the per-turn
|
/// the persistent history (and therefore picked up by the per-turn
|
||||||
/// clone that backs the LLM request, plus the usual
|
/// clone that backs the LLM request, plus the usual
|
||||||
|
|
@ -164,12 +164,12 @@ pub trait Interceptor: Send + Sync {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called before each LLM request. The context starts as a clone
|
/// Called before each LLM request. The context starts as a clone
|
||||||
/// of `worker.history` (after `pending_history_appends` and the
|
/// of `engine.history` (after `pending_history_appends` and the
|
||||||
/// Worker's own prune projection have been applied).
|
/// Engine's own prune projection have been applied).
|
||||||
///
|
///
|
||||||
/// Direct mutations to `context` remain request-local and are not persisted.
|
/// Direct mutations to `context` remain request-local and are not persisted.
|
||||||
/// If an interceptor derives a human/model-visible nudge from the current
|
/// If an interceptor derives a human/model-visible nudge from the current
|
||||||
/// request context, return [`PreRequestAction::ContinueWith`] so the Worker
|
/// request context, return [`PreRequestAction::ContinueWith`] so the Engine
|
||||||
/// commits it to history before the request is sent.
|
/// commits it to history before the request is sent.
|
||||||
async fn pre_llm_request(&self, _context: &mut Vec<Item>) -> PreRequestAction {
|
async fn pre_llm_request(&self, _context: &mut Vec<Item>) -> PreRequestAction {
|
||||||
PreRequestAction::Continue
|
PreRequestAction::Continue
|
||||||
|
|
@ -194,7 +194,7 @@ pub trait Interceptor: Send + Sync {
|
||||||
async fn on_abort(&self, _reason: &str) {}
|
async fn on_abort(&self, _reason: &str) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default interceptor: no intervention. Worker proceeds through the loop
|
/// Default interceptor: no intervention. Engine proceeds through the loop
|
||||||
/// without any external control flow decisions.
|
/// without any external control flow decisions.
|
||||||
pub(crate) struct DefaultInterceptor;
|
pub(crate) struct DefaultInterceptor;
|
||||||
|
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
//! llm-worker - LLM Worker Library
|
//! llm-engine - LLM Engine Library
|
||||||
//!
|
//!
|
||||||
//! Provides components for managing interactions with LLMs.
|
//! Provides components for managing interactions with LLMs.
|
||||||
//!
|
//!
|
||||||
//! # Main Components
|
//! # Main Components
|
||||||
//!
|
//!
|
||||||
//! - [`Worker`] - Central component for managing LLM interactions
|
//! - [`Engine`] - Central component for managing LLM interactions
|
||||||
//! - [`tool::Tool`] - Tools that can be invoked by the LLM
|
//! - [`tool::Tool`] - Tools that can be invoked by the LLM
|
||||||
//! - [`interceptor::Interceptor`] - Control-flow delegation for the execution loop
|
//! - [`interceptor::Interceptor`] - Control-flow delegation for the execution loop
|
||||||
//! - Closure-based event callbacks via `Worker::on_text_block()`, `on_tool_use_block()`, etc.
|
//! - Closure-based event callbacks via `Engine::on_text_block()`, `on_tool_use_block()`, etc.
|
||||||
//!
|
//!
|
||||||
//! # Quick Start
|
//! # Quick Start
|
||||||
//!
|
//!
|
||||||
//! ```ignore
|
//! ```ignore
|
||||||
//! use llm_worker::{Worker, Item};
|
//! use llm_engine::{Engine, Item};
|
||||||
//!
|
//!
|
||||||
//! // Create a Worker
|
//! // Create a Engine
|
||||||
//! let mut worker = Worker::new(client)
|
//! let mut engine = Engine::new(client)
|
||||||
//! .system_prompt("You are a helpful assistant.");
|
//! .system_prompt("You are a helpful assistant.");
|
||||||
//!
|
//!
|
||||||
//! // Register tools (optional)
|
//! // Register tools (optional)
|
||||||
//! // worker.register_tool(my_tool_definition)?;
|
//! // engine.register_tool(my_tool_definition)?;
|
||||||
//!
|
//!
|
||||||
//! // Run the interaction
|
//! // Run the interaction
|
||||||
//! let history = worker.run("Hello!").await?;
|
//! let history = engine.run("Hello!").await?;
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! # Cache Protection
|
//! # Cache Protection
|
||||||
|
|
@ -31,15 +31,15 @@
|
||||||
//! call `unlock_cache()` first; the next `run()` re-locks automatically.
|
//! call `unlock_cache()` first; the next `run()` re-locks automatically.
|
||||||
//!
|
//!
|
||||||
//! ```ignore
|
//! ```ignore
|
||||||
//! worker.run("user input").await?;
|
//! engine.run("user input").await?;
|
||||||
//! worker.unlock_cache();
|
//! engine.unlock_cache();
|
||||||
//! worker.set_system_prompt("new prompt");
|
//! engine.set_system_prompt("new prompt");
|
||||||
//! worker.run("next input").await?;
|
//! engine.run("next input").await?;
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
mod engine;
|
||||||
mod handler;
|
mod handler;
|
||||||
mod message;
|
mod message;
|
||||||
mod worker;
|
|
||||||
|
|
||||||
pub(crate) mod callback;
|
pub(crate) mod callback;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
|
|
@ -54,11 +54,12 @@ pub mod tool_server;
|
||||||
pub mod usage_record;
|
pub mod usage_record;
|
||||||
|
|
||||||
pub use callback::{TextBlockScope, ThinkingBlockScope, ToolUseBlockScope};
|
pub use callback::{TextBlockScope, ThinkingBlockScope, ToolUseBlockScope};
|
||||||
|
pub use engine::{
|
||||||
|
Engine, EngineConfig, EngineError, EngineResult, EngineRunOutput, LlmRetryNotice,
|
||||||
|
ToolRegistryError,
|
||||||
|
};
|
||||||
pub use handler::ToolUseBlockStart;
|
pub use handler::ToolUseBlockStart;
|
||||||
pub use interceptor::Interceptor;
|
pub use interceptor::Interceptor;
|
||||||
pub use message::{ContentPart, Item, Message, Role};
|
pub use message::{ContentPart, Item, Message, Role};
|
||||||
pub use tool::{ToolCall, ToolExecutionContext, ToolOutputLimits, ToolResult};
|
pub use tool::{ToolCall, ToolExecutionContext, ToolOutputLimits, ToolResult};
|
||||||
pub use usage_record::UsageRecord;
|
pub use usage_record::UsageRecord;
|
||||||
pub use worker::{
|
|
||||||
LlmRetryNotice, RunOutput, ToolRegistryError, Worker, WorkerConfig, WorkerError, WorkerResult,
|
|
||||||
};
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! `Scheme` 実装と通信層が要求する認証要件、および動的認証プロバイダ。
|
//! `Scheme` 実装と通信層が要求する認証要件、および動的認証プロバイダ。
|
||||||
//!
|
//!
|
||||||
//! マニフェスト側の型(`ModelConfig` / `SchemeKind` / `AuthRef`)は
|
//! マニフェスト側の型(`ModelConfig` / `SchemeKind` / `AuthRef`)は
|
||||||
//! `crates/manifest` に置き、llm-worker はそれを知らずに済む。
|
//! `crates/manifest` に置き、llm-engine はそれを知らずに済む。
|
||||||
//! `AuthRequirement` は scheme が宣言する「この scheme はどんな認証を
|
//! `AuthRequirement` は scheme が宣言する「この scheme はどんな認証を
|
||||||
//! 期待するか」のランタイム記述で、manifest 側の `AuthRef` との
|
//! 期待するか」のランタイム記述で、manifest 側の `AuthRef` との
|
||||||
//! 照合(`AuthRef → ResolvedAuth` 変換の適否)は `crates/provider`
|
//! 照合(`AuthRef → ResolvedAuth` 変換の適否)は `crates/provider`
|
||||||
|
|
@ -36,7 +36,7 @@ pub enum AuthRequirement {
|
||||||
/// Codex OAuth のように access_token が refresh で更新されたり、
|
/// Codex OAuth のように access_token が refresh で更新されたり、
|
||||||
/// `ChatGPT-Account-Id` / `X-OpenAI-Fedramp` のような複数ヘッダを
|
/// `ChatGPT-Account-Id` / `X-OpenAI-Fedramp` のような複数ヘッダを
|
||||||
/// 同時に注入する必要があるケースで使う。実体は `crates/provider`
|
/// 同時に注入する必要があるケースで使う。実体は `crates/provider`
|
||||||
/// 側に置き、llm-worker は trait を知るだけ。
|
/// 側に置き、llm-engine は trait を知るだけ。
|
||||||
///
|
///
|
||||||
/// 返したヘッダはそのまま `HeaderMap` に挿入される。`Authorization`
|
/// 返したヘッダはそのまま `HeaderMap` に挿入される。`Authorization`
|
||||||
/// 含む scheme 既定の認証ヘッダは送出されないので、必要なら
|
/// 含む scheme 既定の認証ヘッダは送出されないので、必要なら
|
||||||
|
|
@ -81,7 +81,7 @@ impl Clone for Box<dyn LlmClient> {
|
||||||
|
|
||||||
/// `Box<dyn LlmClient>` に対する `LlmClient` の実装
|
/// `Box<dyn LlmClient>` に対する `LlmClient` の実装
|
||||||
///
|
///
|
||||||
/// これにより、動的ディスパッチを使用するクライアントも `Worker` で利用可能になる。
|
/// これにより、動的ディスパッチを使用するクライアントも `Engine` で利用可能になる。
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl LlmClient for Box<dyn LlmClient> {
|
impl LlmClient for Box<dyn LlmClient> {
|
||||||
async fn stream(&self, request: Request) -> Result<ResponseStream, ClientError> {
|
async fn stream(&self, request: Request) -> Result<ResponseStream, ClientError> {
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! LLM response stream を開く前の transient error 向けリトライポリシー。
|
//! LLM response stream を開く前の transient error 向けリトライポリシー。
|
||||||
//!
|
//!
|
||||||
//! Worker が `LlmClient::stream` の open error に対して `is_retryable` を見て
|
//! Engine が `LlmClient::stream` の open error に対して `is_retryable` を見て
|
||||||
//! retry / backoff / TUI event / cancellation をまとめて管理する。
|
//! retry / backoff / TUI event / cancellation をまとめて管理する。
|
||||||
//! SSE 読み出し開始後の失敗は対象外。
|
//! SSE 読み出し開始後の失敗は対象外。
|
||||||
|
|
||||||
|
|
@ -8,8 +8,8 @@ use std::time::Duration;
|
||||||
|
|
||||||
/// 指数バックオフ + ジッター + 累積タイムアウトを表すポリシー。
|
/// 指数バックオフ + ジッター + 累積タイムアウトを表すポリシー。
|
||||||
///
|
///
|
||||||
/// `Default` は llm-worker 全体の固定値を返す。manifest 経由の上書きが
|
/// `Default` は llm-engine 全体の固定値を返す。manifest 経由の上書きが
|
||||||
/// 必要になったら拡張する(現状は不要 → `tickets/llm-worker-transient-retry.md`)。
|
/// 必要になったら拡張する(現状は不要 → `tickets/llm-engine-transient-retry.md`)。
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RetryPolicy {
|
pub struct RetryPolicy {
|
||||||
/// 指数の基準値。`base * 2^attempt` を `cap` で頭打ちにした上限から
|
/// 指数の基準値。`base * 2^attempt` を `cap` で頭打ちにした上限から
|
||||||
|
|
@ -62,7 +62,7 @@ impl fmt::Debug for RequestTrace {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// use llm_worker::Item;
|
/// use llm_engine::Item;
|
||||||
///
|
///
|
||||||
/// let user = Item::user_message("Hello!");
|
/// let user = Item::user_message("Hello!");
|
||||||
/// let assistant = Item::assistant_message("Hi there!");
|
/// let assistant = Item::assistant_message("Hi there!");
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
//! Prune は **コンテキスト射影** であり、history の変換ではない。
|
//! Prune は **コンテキスト射影** であり、history の変換ではない。
|
||||||
//! この crate が提供するのは pure な候補抽出 [`prunable_indices`] のみで、
|
//! この crate が提供するのは pure な候補抽出 [`prunable_indices`] のみで、
|
||||||
//! 射影の適用は上位層(`pod::prune_hook` 等)が LLM に送る一時コンテキスト
|
//! 射影の適用は上位層(`pod::prune_hook` 等)が LLM に送る一時コンテキスト
|
||||||
//! に対してだけ行う。Worker の永続履歴は決して変更されない。
|
//! に対してだけ行う。Engine の永続履歴は決して変更されない。
|
||||||
//!
|
//!
|
||||||
//! 保護境界は末尾 token budget で決めるが、この crate は usage 履歴を
|
//! 保護境界は末尾 token budget で決めるが、この crate は usage 履歴を
|
||||||
//! 所有しない。prefix ごとの token 推定値と savings 推定は上位層から
|
//! 所有しない。prefix ごとの token 推定値と savings 推定は上位層から
|
||||||
|
|
@ -32,8 +32,8 @@ pub type TokenEstimator = Box<dyn Fn(&[Item]) -> Vec<TokenEstimate> + Send + Syn
|
||||||
/// Callback that estimates the token savings for projecting the
|
/// Callback that estimates the token savings for projecting the
|
||||||
/// `ToolResult.content` out of `history[i]` for each `i` in `indices`.
|
/// `ToolResult.content` out of `history[i]` for each `i` in `indices`.
|
||||||
///
|
///
|
||||||
/// Injected into [`crate::Worker`] via `set_savings_estimator` so the
|
/// Injected into [`crate::Engine`] via `set_savings_estimator` so the
|
||||||
/// Worker can make `min_savings` decisions without knowing about usage
|
/// Engine can make `min_savings` decisions without knowing about usage
|
||||||
/// measurement sources. Return `0` to signal "no data / refuse to prune".
|
/// measurement sources. Return `0` to signal "no data / refuse to prune".
|
||||||
///
|
///
|
||||||
/// 推定対象は「drop する範囲全体」ではなく「content を None にする差分」
|
/// 推定対象は「drop する範囲全体」ではなく「content を None にする差分」
|
||||||
|
|
@ -44,7 +44,7 @@ pub type SavingsEstimator = Box<dyn Fn(&[Item], &[usize]) -> u64 + Send + Sync>;
|
||||||
/// Result of one prune evaluation pass, surfaced to the optional
|
/// Result of one prune evaluation pass, surfaced to the optional
|
||||||
/// [`PruneObserver`] for instrumentation.
|
/// [`PruneObserver`] for instrumentation.
|
||||||
///
|
///
|
||||||
/// Worker は LLM リクエストごとに 1 回 prune の評価をし、その結果を
|
/// Engine は LLM リクエストごとに 1 回 prune の評価をし、その結果を
|
||||||
/// (observer が登録されていれば)この値で通知する。fire/skip の判定
|
/// (observer が登録されていれば)この値で通知する。fire/skip の判定
|
||||||
/// 結果と、判定材料になった候補数 / 推定 savings / 保護領域の先頭 index を持つ。
|
/// 結果と、判定材料になった候補数 / 推定 savings / 保護領域の先頭 index を持つ。
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -61,7 +61,7 @@ pub struct PruneEvaluation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Outcome of one prune evaluation. Each variant is one branch of the
|
/// Outcome of one prune evaluation. Each variant is one branch of the
|
||||||
/// "fire vs skip" decision tree the Worker walks before each LLM request.
|
/// "fire vs skip" decision tree the Engine walks before each LLM request.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum PruneDecision {
|
pub enum PruneDecision {
|
||||||
/// `prunable_indices` が空 → 何もしない。
|
/// `prunable_indices` が空 → 何もしない。
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
//! Worker State
|
//! Engine State
|
||||||
//!
|
//!
|
||||||
//! State marker types for cache protection using the Type-state pattern.
|
//! State marker types for cache protection using the Type-state pattern.
|
||||||
//! Worker has state transitions from `Mutable` → `Locked`.
|
//! Engine has state transitions from `Mutable` → `Locked`.
|
||||||
|
|
||||||
/// Marker trait representing Worker state
|
/// Marker trait representing Engine state
|
||||||
///
|
///
|
||||||
/// This trait is sealed and cannot be implemented externally.
|
/// This trait is sealed and cannot be implemented externally.
|
||||||
pub trait WorkerState: private::Sealed + Send + Sync + 'static {}
|
pub trait EngineState: private::Sealed + Send + Sync + 'static {}
|
||||||
|
|
||||||
mod private {
|
mod private {
|
||||||
pub trait Sealed {}
|
pub trait Sealed {}
|
||||||
|
|
@ -19,28 +19,28 @@ mod private {
|
||||||
/// - Editing message history (add, delete, clear)
|
/// - Editing message history (add, delete, clear)
|
||||||
/// - Registering tools and hooks
|
/// - Registering tools and hooks
|
||||||
///
|
///
|
||||||
/// Can transition to [`Locked`] state via `Worker::lock()`.
|
/// Can transition to [`Locked`] state via `Engine::lock()`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// use llm_worker::Worker;
|
/// use llm_engine::Engine;
|
||||||
///
|
///
|
||||||
/// let mut worker = Worker::new(client)
|
/// let mut engine = Engine::new(client)
|
||||||
/// .system_prompt("You are helpful.");
|
/// .system_prompt("You are helpful.");
|
||||||
///
|
///
|
||||||
/// // History can be edited
|
/// // History can be edited
|
||||||
/// worker.push_message(Message::user("Hello"));
|
/// engine.push_message(Message::user("Hello"));
|
||||||
/// worker.clear_history();
|
/// engine.clear_history();
|
||||||
///
|
///
|
||||||
/// // Lock to protected state
|
/// // Lock to protected state
|
||||||
/// let locked = worker.lock();
|
/// let locked = engine.lock();
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct Mutable;
|
pub struct Mutable;
|
||||||
|
|
||||||
impl private::Sealed for Mutable {}
|
impl private::Sealed for Mutable {}
|
||||||
impl WorkerState for Mutable {}
|
impl EngineState for Mutable {}
|
||||||
|
|
||||||
/// Cache locked state (cache protected)
|
/// Cache locked state (cache protected)
|
||||||
///
|
///
|
||||||
|
|
@ -51,10 +51,10 @@ impl WorkerState for Mutable {}
|
||||||
/// To ensure LLM API KV cache hits,
|
/// To ensure LLM API KV cache hits,
|
||||||
/// using this state during execution is recommended.
|
/// using this state during execution is recommended.
|
||||||
///
|
///
|
||||||
/// Can return to [`Mutable`] state via `Worker::unlock()`,
|
/// Can return to [`Mutable`] state via `Engine::unlock()`,
|
||||||
/// but note that cache protection will be released.
|
/// but note that cache protection will be released.
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct Locked;
|
pub struct Locked;
|
||||||
|
|
||||||
impl private::Sealed for Locked {}
|
impl private::Sealed for Locked {}
|
||||||
impl WorkerState for Locked {}
|
impl EngineState for Locked {}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! Timeline層
|
//! Timeline層
|
||||||
//!
|
//!
|
||||||
//! LLMからのイベントストリームを受信し、登録されたHandlerにディスパッチします。
|
//! LLMからのイベントストリームを受信し、登録されたHandlerにディスパッチします。
|
||||||
//! 通常はWorker経由で使用しますが、直接使用することも可能です。
|
//! 通常はEngine経由で使用しますが、直接使用することも可能です。
|
||||||
|
|
||||||
use std::{collections::HashMap, marker::PhantomData};
|
use std::{collections::HashMap, marker::PhantomData};
|
||||||
|
|
||||||
|
|
@ -348,7 +348,7 @@ where
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// use llm_worker::{Timeline, Handler, TextBlockKind, TextBlockEvent};
|
/// use llm_engine::{Timeline, Handler, TextBlockKind, TextBlockEvent};
|
||||||
///
|
///
|
||||||
/// struct MyHandler;
|
/// struct MyHandler;
|
||||||
/// impl Handler<TextBlockKind> for MyHandler {
|
/// impl Handler<TextBlockKind> for MyHandler {
|
||||||
|
|
@ -33,7 +33,7 @@ pub enum ToolError {
|
||||||
/// Outputs this small don't benefit from pruning.
|
/// Outputs this small don't benefit from pruning.
|
||||||
pub const SUMMARY_THRESHOLD: usize = 200;
|
pub const SUMMARY_THRESHOLD: usize = 200;
|
||||||
|
|
||||||
/// Byte-size caps applied to tool execution `content` at the Worker's
|
/// Byte-size caps applied to tool execution `content` at the Engine's
|
||||||
/// tool-execution boundary, before results enter conversation history.
|
/// tool-execution boundary, before results enter conversation history.
|
||||||
///
|
///
|
||||||
/// Exists so a single oversized tool result (e.g. a wide `Glob` scan)
|
/// Exists so a single oversized tool result (e.g. a wide `Glob` scan)
|
||||||
|
|
@ -131,7 +131,7 @@ impl From<String> for ToolOutput {
|
||||||
///
|
///
|
||||||
/// This metadata is intentionally not part of the provider-facing tool schema.
|
/// This metadata is intentionally not part of the provider-facing tool schema.
|
||||||
/// It lets host layers audit where a model-visible tool definition came from
|
/// It lets host layers audit where a model-visible tool definition came from
|
||||||
/// while keeping execution and permission semantics in the normal Worker path.
|
/// while keeping execution and permission semantics in the normal Engine path.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct ToolOrigin {
|
pub struct ToolOrigin {
|
||||||
/// Origin kind, for example `plugin` or `builtin`.
|
/// Origin kind, for example `plugin` or `builtin`.
|
||||||
|
|
@ -154,7 +154,7 @@ pub struct ToolOrigin {
|
||||||
|
|
||||||
/// Tool meta information (fixed at registration, immutable)
|
/// Tool meta information (fixed at registration, immutable)
|
||||||
///
|
///
|
||||||
/// Generated from `ToolDefinition` factory and does not change after registration with Worker.
|
/// Generated from `ToolDefinition` factory and does not change after registration with Engine.
|
||||||
/// Used for sending tool definitions to LLM.
|
/// Used for sending tool definitions to LLM.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct ToolMeta {
|
pub struct ToolMeta {
|
||||||
|
|
@ -205,7 +205,7 @@ impl ToolMeta {
|
||||||
/// Tool definition factory
|
/// Tool definition factory
|
||||||
///
|
///
|
||||||
/// When called, returns `(ToolMeta, Arc<dyn Tool>)`.
|
/// When called, returns `(ToolMeta, Arc<dyn Tool>)`.
|
||||||
/// Called once during Worker registration, and the meta information and instance
|
/// Called once during Engine registration, and the meta information and instance
|
||||||
/// are cached at session scope.
|
/// are cached at session scope.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
|
@ -219,22 +219,22 @@ impl ToolMeta {
|
||||||
/// Arc::new(MyToolImpl { state: 0 }) as Arc<dyn Tool>,
|
/// Arc::new(MyToolImpl { state: 0 }) as Arc<dyn Tool>,
|
||||||
/// )
|
/// )
|
||||||
/// });
|
/// });
|
||||||
/// worker.register_tool(def)?;
|
/// engine.register_tool(def)?;
|
||||||
/// ```
|
/// ```
|
||||||
pub type ToolDefinition = Arc<dyn Fn() -> (ToolMeta, Arc<dyn Tool>) + Send + Sync>;
|
pub type ToolDefinition = Arc<dyn Fn() -> (ToolMeta, Arc<dyn Tool>) + Send + Sync>;
|
||||||
|
|
||||||
/// Per-call context supplied by the worker when executing a tool call.
|
/// Per-call context supplied by the engine when executing a tool call.
|
||||||
///
|
///
|
||||||
/// The context identifies a tool call within one assistant response's tool-call
|
/// The context identifies a tool call within one assistant response's tool-call
|
||||||
/// batch without imposing any scheduling policy on the worker. Tool
|
/// batch without imposing any scheduling policy on the engine. Tool
|
||||||
/// implementations may use it for response-local ordering, diagnostics, or
|
/// implementations may use it for response-local ordering, diagnostics, or
|
||||||
/// correlation, but it is intentionally not a handle to worker state, history,
|
/// correlation, but it is intentionally not a handle to engine state, history,
|
||||||
/// or session mutation.
|
/// or session mutation.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct ToolExecutionContext {
|
pub struct ToolExecutionContext {
|
||||||
/// Provider/tool-call id for the call being executed.
|
/// Provider/tool-call id for the call being executed.
|
||||||
pub call_id: String,
|
pub call_id: String,
|
||||||
/// Worker-local identity shared by all tool calls from one execution batch.
|
/// Engine-local identity shared by all tool calls from one execution batch.
|
||||||
pub batch_id: String,
|
pub batch_id: String,
|
||||||
/// Zero-based order of this call in the model-returned tool-call list.
|
/// Zero-based order of this call in the model-returned tool-call list.
|
||||||
pub call_index: usize,
|
pub call_index: usize,
|
||||||
|
|
@ -249,7 +249,7 @@ impl ToolExecutionContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Context for direct, non-worker calls in unit tests and low-level callers.
|
/// Context for direct, non-engine calls in unit tests and low-level callers.
|
||||||
pub fn direct() -> Self {
|
pub fn direct() -> Self {
|
||||||
Self::new("direct", "direct", 0)
|
Self::new("direct", "direct", 0)
|
||||||
}
|
}
|
||||||
|
|
@ -285,13 +285,13 @@ impl Default for ToolExecutionContext {
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// // Register
|
/// // Register
|
||||||
/// worker.register_tool(app.search_definition())?;
|
/// engine.register_tool(app.search_definition())?;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Manual Implementation
|
/// # Manual Implementation
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// use llm_worker::tool::{Tool, ToolError, ToolExecutionContext, ToolMeta, ToolDefinition, ToolOutput};
|
/// use llm_engine::tool::{Tool, ToolError, ToolExecutionContext, ToolMeta, ToolDefinition, ToolOutput};
|
||||||
/// use std::sync::Arc;
|
/// use std::sync::Arc;
|
||||||
///
|
///
|
||||||
/// struct MyTool { counter: std::sync::atomic::AtomicUsize }
|
/// struct MyTool { counter: std::sync::atomic::AtomicUsize }
|
||||||
|
|
@ -5,7 +5,7 @@ use thiserror::Error;
|
||||||
|
|
||||||
use crate::llm_client::ToolDefinition as LlmToolDefinition;
|
use crate::llm_client::ToolDefinition as LlmToolDefinition;
|
||||||
use crate::tool::{
|
use crate::tool::{
|
||||||
Tool, ToolDefinition as WorkerToolDefinition, ToolExecutionContext, ToolMeta, ToolOutput,
|
Tool, ToolDefinition as EngineToolDefinition, ToolExecutionContext, ToolMeta, ToolOutput,
|
||||||
};
|
};
|
||||||
|
|
||||||
type ToolMap = HashMap<String, (ToolMeta, Arc<dyn Tool>)>;
|
type ToolMap = HashMap<String, (ToolMeta, Arc<dyn Tool>)>;
|
||||||
|
|
@ -28,7 +28,7 @@ pub enum ToolServerError {
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct ToolServer {
|
pub struct ToolServer {
|
||||||
tools: Arc<Mutex<ToolMap>>,
|
tools: Arc<Mutex<ToolMap>>,
|
||||||
pending: Arc<Mutex<Vec<WorkerToolDefinition>>>,
|
pending: Arc<Mutex<Vec<EngineToolDefinition>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToolServer {
|
impl ToolServer {
|
||||||
|
|
@ -50,7 +50,7 @@ impl ToolServer {
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct ToolServerHandle {
|
pub struct ToolServerHandle {
|
||||||
tools: Arc<Mutex<ToolMap>>,
|
tools: Arc<Mutex<ToolMap>>,
|
||||||
pending: Arc<Mutex<Vec<WorkerToolDefinition>>>,
|
pending: Arc<Mutex<Vec<EngineToolDefinition>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToolServerHandle {
|
impl ToolServerHandle {
|
||||||
|
|
@ -58,8 +58,8 @@ impl ToolServerHandle {
|
||||||
///
|
///
|
||||||
/// The factory is **not** called here; it is stored and executed
|
/// The factory is **not** called here; it is stored and executed
|
||||||
/// when [`flush_pending`](Self::flush_pending) is called (typically
|
/// when [`flush_pending`](Self::flush_pending) is called (typically
|
||||||
/// at the start of `Worker::run()`).
|
/// at the start of `Engine::run()`).
|
||||||
pub(crate) fn register_tool(&self, factory: WorkerToolDefinition) {
|
pub(crate) fn register_tool(&self, factory: EngineToolDefinition) {
|
||||||
self.pending
|
self.pending
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap_or_else(|e| e.into_inner())
|
.unwrap_or_else(|e| e.into_inner())
|
||||||
|
|
@ -67,14 +67,14 @@ impl ToolServerHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queue many tool factories for deferred initialization.
|
/// Queue many tool factories for deferred initialization.
|
||||||
pub(crate) fn register_tools(&self, factories: impl IntoIterator<Item = WorkerToolDefinition>) {
|
pub(crate) fn register_tools(&self, factories: impl IntoIterator<Item = EngineToolDefinition>) {
|
||||||
let mut guard = self.pending.lock().unwrap_or_else(|e| e.into_inner());
|
let mut guard = self.pending.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
guard.extend(factories);
|
guard.extend(factories);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute all pending factories and register the resulting tools.
|
/// Execute all pending factories and register the resulting tools.
|
||||||
///
|
///
|
||||||
/// Called implicitly by `Worker::lock()` before the first turn.
|
/// Called implicitly by `Engine::lock()` before the first turn.
|
||||||
/// Exposed as `pub` so higher layers (e.g. Pod) can force-materialise
|
/// Exposed as `pub` so higher layers (e.g. Pod) can force-materialise
|
||||||
/// tools earlier — for example when building a system-prompt template
|
/// tools earlier — for example when building a system-prompt template
|
||||||
/// context that needs the list of registered tool names. Redundant
|
/// context that needs the list of registered tool names. Redundant
|
||||||
|
|
@ -150,7 +150,7 @@ impl ToolServerHandle {
|
||||||
/// The factory is called immediately and the resulting tool overwrites
|
/// The factory is called immediately and the resulting tool overwrites
|
||||||
/// the entry with the same name. Returns `ToolNotFound` if the name
|
/// the entry with the same name. Returns `ToolNotFound` if the name
|
||||||
/// produced by the factory does not match any registered tool.
|
/// produced by the factory does not match any registered tool.
|
||||||
pub fn replace(&self, factory: WorkerToolDefinition) -> Result<(), ToolServerError> {
|
pub fn replace(&self, factory: EngineToolDefinition) -> Result<(), ToolServerError> {
|
||||||
let (meta, instance) = factory();
|
let (meta, instance) = factory();
|
||||||
let mut guard = self.tools.lock().unwrap_or_else(|e| e.into_inner());
|
let mut guard = self.tools.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
if !guard.contains_key(&meta.name) {
|
if !guard.contains_key(&meta.name) {
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Closure callback API tests
|
//! Closure callback API tests
|
||||||
//!
|
//!
|
||||||
//! Tests for the closure-based event subscription API on Worker.
|
//! Tests for the closure-based event subscription API on Engine.
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
|
@ -10,11 +10,11 @@ use std::time::Duration;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use common::MockLlmClient;
|
use common::MockLlmClient;
|
||||||
use llm_worker::Worker;
|
use llm_engine::Engine;
|
||||||
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent as ClientStatusEvent};
|
use llm_engine::llm_client::event::{Event, ResponseStatus, StatusEvent as ClientStatusEvent};
|
||||||
use llm_worker::llm_client::retry::RetryPolicy;
|
use llm_engine::llm_client::retry::RetryPolicy;
|
||||||
use llm_worker::llm_client::{ClientError, LlmClient, Request, ResponseStream};
|
use llm_engine::llm_client::{ClientError, LlmClient, Request, ResponseStream};
|
||||||
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
|
use llm_engine::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct FailOnceClient {
|
struct FailOnceClient {
|
||||||
|
|
@ -52,7 +52,7 @@ async fn test_callback_llm_retry_event() {
|
||||||
calls: Arc::new(AtomicUsize::new(0)),
|
calls: Arc::new(AtomicUsize::new(0)),
|
||||||
events,
|
events,
|
||||||
};
|
};
|
||||||
let mut worker = Worker::new(client).with_retry_policy(RetryPolicy {
|
let mut engine = Engine::new(client).with_retry_policy(RetryPolicy {
|
||||||
base: Duration::from_millis(1),
|
base: Duration::from_millis(1),
|
||||||
cap: Duration::from_millis(1),
|
cap: Duration::from_millis(1),
|
||||||
max_attempts: 2,
|
max_attempts: 2,
|
||||||
|
|
@ -61,12 +61,12 @@ async fn test_callback_llm_retry_event() {
|
||||||
|
|
||||||
let notices = Arc::new(Mutex::new(Vec::new()));
|
let notices = Arc::new(Mutex::new(Vec::new()));
|
||||||
let sink = notices.clone();
|
let sink = notices.clone();
|
||||||
worker.on_llm_retry(move |llm_call, notice| {
|
engine.on_llm_retry(move |llm_call, notice| {
|
||||||
sink.lock().unwrap().push((llm_call, notice.clone()));
|
sink.lock().unwrap().push((llm_call, notice.clone()));
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = worker.run("retry once").await;
|
let result = engine.run("retry once").await;
|
||||||
assert!(result.is_ok(), "worker should succeed after one retry");
|
assert!(result.is_ok(), "engine should succeed after one retry");
|
||||||
|
|
||||||
let notices = notices.lock().unwrap();
|
let notices = notices.lock().unwrap();
|
||||||
assert_eq!(notices.len(), 1);
|
assert_eq!(notices.len(), 1);
|
||||||
|
|
@ -90,14 +90,14 @@ async fn test_callback_text_block_events() {
|
||||||
];
|
];
|
||||||
|
|
||||||
let client = MockLlmClient::new(events);
|
let client = MockLlmClient::new(events);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
|
|
||||||
let text_deltas = Arc::new(Mutex::new(Vec::new()));
|
let text_deltas = Arc::new(Mutex::new(Vec::new()));
|
||||||
let text_completes = Arc::new(Mutex::new(Vec::new()));
|
let text_completes = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
|
||||||
let deltas = text_deltas.clone();
|
let deltas = text_deltas.clone();
|
||||||
let completes = text_completes.clone();
|
let completes = text_completes.clone();
|
||||||
worker.on_text_block(move |block| {
|
engine.on_text_block(move |block| {
|
||||||
let d = deltas.clone();
|
let d = deltas.clone();
|
||||||
block.on_delta(move |text| {
|
block.on_delta(move |text| {
|
||||||
d.lock().unwrap().push(text.to_owned());
|
d.lock().unwrap().push(text.to_owned());
|
||||||
|
|
@ -108,9 +108,9 @@ async fn test_callback_text_block_events() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mutable::run consumes self, returns (Locked, WorkerResult)
|
// Mutable::run consumes self, returns (Locked, EngineResult)
|
||||||
let result = worker.run("Greet me").await;
|
let result = engine.run("Greet me").await;
|
||||||
assert!(result.is_ok(), "Worker should complete");
|
assert!(result.is_ok(), "Engine should complete");
|
||||||
|
|
||||||
let deltas = text_deltas.lock().unwrap();
|
let deltas = text_deltas.lock().unwrap();
|
||||||
assert_eq!(deltas.len(), 2);
|
assert_eq!(deltas.len(), 2);
|
||||||
|
|
@ -136,14 +136,14 @@ async fn test_callback_tool_call_complete() {
|
||||||
];
|
];
|
||||||
|
|
||||||
let client = MockLlmClient::new(events);
|
let client = MockLlmClient::new(events);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
|
|
||||||
let tool_starts = Arc::new(Mutex::new(Vec::<(String, String)>::new()));
|
let tool_starts = Arc::new(Mutex::new(Vec::<(String, String)>::new()));
|
||||||
let tool_completes = Arc::new(Mutex::new(Vec::new()));
|
let tool_completes = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
|
||||||
let starts = tool_starts.clone();
|
let starts = tool_starts.clone();
|
||||||
let completes = tool_completes.clone();
|
let completes = tool_completes.clone();
|
||||||
worker.on_tool_use_block(move |start, block| {
|
engine.on_tool_use_block(move |start, block| {
|
||||||
starts
|
starts
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -154,8 +154,8 @@ async fn test_callback_tool_call_complete() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mutable::run consumes self, returns (Locked, WorkerResult)
|
// Mutable::run consumes self, returns (Locked, EngineResult)
|
||||||
let _ = worker.run("Weather please").await;
|
let _ = engine.run("Weather please").await;
|
||||||
|
|
||||||
let starts = tool_starts.lock().unwrap();
|
let starts = tool_starts.lock().unwrap();
|
||||||
assert_eq!(starts.len(), 1);
|
assert_eq!(starts.len(), 1);
|
||||||
|
|
@ -182,23 +182,23 @@ async fn test_callback_turn_events() {
|
||||||
];
|
];
|
||||||
|
|
||||||
let client = MockLlmClient::new(events);
|
let client = MockLlmClient::new(events);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
|
|
||||||
let turn_starts = Arc::new(Mutex::new(Vec::new()));
|
let turn_starts = Arc::new(Mutex::new(Vec::new()));
|
||||||
let turn_ends = Arc::new(Mutex::new(Vec::new()));
|
let turn_ends = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
|
||||||
let starts = turn_starts.clone();
|
let starts = turn_starts.clone();
|
||||||
worker.on_turn_start(move |turn| {
|
engine.on_turn_start(move |turn| {
|
||||||
starts.lock().unwrap().push(turn);
|
starts.lock().unwrap().push(turn);
|
||||||
});
|
});
|
||||||
|
|
||||||
let ends = turn_ends.clone();
|
let ends = turn_ends.clone();
|
||||||
worker.on_turn_end(move |turn| {
|
engine.on_turn_end(move |turn| {
|
||||||
ends.lock().unwrap().push(turn);
|
ends.lock().unwrap().push(turn);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mutable::run consumes self, returns (Locked, WorkerResult)
|
// Mutable::run consumes self, returns (Locked, EngineResult)
|
||||||
let result = worker.run("Do something").await;
|
let result = engine.run("Do something").await;
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
let starts = turn_starts.lock().unwrap();
|
let starts = turn_starts.lock().unwrap();
|
||||||
|
|
@ -221,7 +221,7 @@ impl Tool for FixedOutputTool {
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
_input_json: &str,
|
_input_json: &str,
|
||||||
_ctx: llm_worker::tool::ToolExecutionContext,
|
_ctx: llm_engine::tool::ToolExecutionContext,
|
||||||
) -> Result<ToolOutput, ToolError> {
|
) -> Result<ToolOutput, ToolError> {
|
||||||
Ok(self.output.clone())
|
Ok(self.output.clone())
|
||||||
}
|
}
|
||||||
|
|
@ -253,9 +253,9 @@ async fn test_callback_tool_result_events() {
|
||||||
];
|
];
|
||||||
|
|
||||||
let client = MockLlmClient::new(events);
|
let client = MockLlmClient::new(events);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
|
|
||||||
worker.register_tool(fixed_tool(
|
engine.register_tool(fixed_tool(
|
||||||
"fixed",
|
"fixed",
|
||||||
ToolOutput {
|
ToolOutput {
|
||||||
summary: "did the thing".into(),
|
summary: "did the thing".into(),
|
||||||
|
|
@ -266,7 +266,7 @@ async fn test_callback_tool_result_events() {
|
||||||
let captured: Arc<Mutex<Vec<(String, String, Option<String>, bool)>>> =
|
let captured: Arc<Mutex<Vec<(String, String, Option<String>, bool)>>> =
|
||||||
Arc::new(Mutex::new(Vec::new()));
|
Arc::new(Mutex::new(Vec::new()));
|
||||||
let sink = captured.clone();
|
let sink = captured.clone();
|
||||||
worker.on_tool_result(move |result| {
|
engine.on_tool_result(move |result| {
|
||||||
sink.lock().unwrap().push((
|
sink.lock().unwrap().push((
|
||||||
result.tool_use_id.clone(),
|
result.tool_use_id.clone(),
|
||||||
result.summary.clone(),
|
result.summary.clone(),
|
||||||
|
|
@ -275,7 +275,7 @@ async fn test_callback_tool_result_events() {
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
let _ = worker.run("call it").await;
|
let _ = engine.run("call it").await;
|
||||||
|
|
||||||
let observed = captured.lock().unwrap();
|
let observed = captured.lock().unwrap();
|
||||||
assert_eq!(observed.len(), 1);
|
assert_eq!(observed.len(), 1);
|
||||||
|
|
@ -296,7 +296,7 @@ impl Tool for ErroringTool {
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
_input_json: &str,
|
_input_json: &str,
|
||||||
_ctx: llm_worker::tool::ToolExecutionContext,
|
_ctx: llm_engine::tool::ToolExecutionContext,
|
||||||
) -> Result<ToolOutput, ToolError> {
|
) -> Result<ToolOutput, ToolError> {
|
||||||
Err(ToolError::ExecutionFailed(self.message.clone()))
|
Err(ToolError::ExecutionFailed(self.message.clone()))
|
||||||
}
|
}
|
||||||
|
|
@ -328,14 +328,14 @@ async fn test_callback_tool_result_error_path() {
|
||||||
];
|
];
|
||||||
|
|
||||||
let client = MockLlmClient::new(events);
|
let client = MockLlmClient::new(events);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
|
|
||||||
worker.register_tool(erroring_tool("erroring", "boom"));
|
engine.register_tool(erroring_tool("erroring", "boom"));
|
||||||
|
|
||||||
let captured: Arc<Mutex<Vec<(String, String, Option<String>, bool)>>> =
|
let captured: Arc<Mutex<Vec<(String, String, Option<String>, bool)>>> =
|
||||||
Arc::new(Mutex::new(Vec::new()));
|
Arc::new(Mutex::new(Vec::new()));
|
||||||
let sink = captured.clone();
|
let sink = captured.clone();
|
||||||
worker.on_tool_result(move |result| {
|
engine.on_tool_result(move |result| {
|
||||||
sink.lock().unwrap().push((
|
sink.lock().unwrap().push((
|
||||||
result.tool_use_id.clone(),
|
result.tool_use_id.clone(),
|
||||||
result.summary.clone(),
|
result.summary.clone(),
|
||||||
|
|
@ -344,7 +344,7 @@ async fn test_callback_tool_result_error_path() {
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
let _ = worker.run("fail it").await;
|
let _ = engine.run("fail it").await;
|
||||||
|
|
||||||
let observed = captured.lock().unwrap();
|
let observed = captured.lock().unwrap();
|
||||||
assert_eq!(observed.len(), 1);
|
assert_eq!(observed.len(), 1);
|
||||||
|
|
@ -372,17 +372,17 @@ async fn test_callback_usage_events() {
|
||||||
];
|
];
|
||||||
|
|
||||||
let client = MockLlmClient::new(events);
|
let client = MockLlmClient::new(events);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
|
|
||||||
let usage_events = Arc::new(Mutex::new(Vec::new()));
|
let usage_events = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
|
||||||
let usages = usage_events.clone();
|
let usages = usage_events.clone();
|
||||||
worker.on_usage(move |event| {
|
engine.on_usage(move |event| {
|
||||||
usages.lock().unwrap().push(event.clone());
|
usages.lock().unwrap().push(event.clone());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mutable::run consumes self, returns (Locked, WorkerResult)
|
// Mutable::run consumes self, returns (Locked, EngineResult)
|
||||||
let _ = worker.run("Hello").await;
|
let _ = engine.run("Hello").await;
|
||||||
|
|
||||||
let usages = usage_events.lock().unwrap();
|
let usages = usage_events.lock().unwrap();
|
||||||
assert_eq!(usages.len(), 1);
|
assert_eq!(usages.len(), 1);
|
||||||
|
|
@ -8,9 +8,9 @@ use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use llm_worker::llm_client::event::{BlockType, DeltaContent, Event};
|
use llm_engine::llm_client::event::{BlockType, DeltaContent, Event};
|
||||||
use llm_worker::llm_client::{ClientError, LlmClient, Request};
|
use llm_engine::llm_client::{ClientError, LlmClient, Request};
|
||||||
use llm_worker::timeline::{Handler, TextBlockEvent, TextBlockKind, Timeline};
|
use llm_engine::timeline::{Handler, TextBlockEvent, TextBlockKind, Timeline};
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
|
@ -272,7 +272,7 @@ pub fn assert_timeline_integration(subdir: &str) {
|
||||||
});
|
});
|
||||||
|
|
||||||
for event in &events {
|
for event in &events {
|
||||||
let timeline_event: llm_worker::timeline::event::Event = event.clone().into();
|
let timeline_event: llm_engine::timeline::event::Event = event.clone().into();
|
||||||
timeline.dispatch(&timeline_event);
|
timeline.dispatch(&timeline_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Worker fixture-based integration tests
|
//! Engine fixture-based integration tests
|
||||||
//!
|
//!
|
||||||
//! Tests Worker behavior using recorded API responses.
|
//! Tests Engine behavior using recorded API responses.
|
||||||
//! Can run locally without API keys.
|
//! Can run locally without API keys.
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
@ -11,8 +11,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use common::MockLlmClient;
|
use common::MockLlmClient;
|
||||||
use llm_worker::Worker;
|
use llm_engine::Engine;
|
||||||
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
|
use llm_engine::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
|
||||||
|
|
||||||
/// Fixture directory path
|
/// Fixture directory path
|
||||||
fn fixtures_dir() -> std::path::PathBuf {
|
fn fixtures_dir() -> std::path::PathBuf {
|
||||||
|
|
@ -61,7 +61,7 @@ impl Tool for MockWeatherTool {
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
input_json: &str,
|
input_json: &str,
|
||||||
_ctx: llm_worker::tool::ToolExecutionContext,
|
_ctx: llm_engine::tool::ToolExecutionContext,
|
||||||
) -> Result<ToolOutput, ToolError> {
|
) -> Result<ToolOutput, ToolError> {
|
||||||
self.call_count.fetch_add(1, Ordering::SeqCst);
|
self.call_count.fetch_add(1, Ordering::SeqCst);
|
||||||
|
|
||||||
|
|
@ -102,7 +102,7 @@ fn test_mock_client_from_fixture() {
|
||||||
/// Creates a client with programmatically constructed events instead of using fixture files.
|
/// Creates a client with programmatically constructed events instead of using fixture files.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mock_client_from_events() {
|
fn test_mock_client_from_events() {
|
||||||
use llm_worker::llm_client::event::Event;
|
use llm_engine::llm_client::event::Event;
|
||||||
|
|
||||||
// Specify events directly
|
// Specify events directly
|
||||||
let events = vec![
|
let events = vec![
|
||||||
|
|
@ -116,54 +116,54 @@ fn test_mock_client_from_events() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Worker Tests with Fixtures
|
// Engine Tests with Fixtures
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
/// Verify that Worker can correctly process simple text responses
|
/// Verify that Engine can correctly process simple text responses
|
||||||
///
|
///
|
||||||
/// Uses simple_text.jsonl fixture to test scenarios without tool calls.
|
/// Uses simple_text.jsonl fixture to test scenarios without tool calls.
|
||||||
/// Skipped if fixture is not present.
|
/// Skipped if fixture is not present.
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_worker_simple_text_response() {
|
async fn test_engine_simple_text_response() {
|
||||||
let fixture_path = fixtures_dir().join("simple_text.jsonl");
|
let fixture_path = fixtures_dir().join("simple_text.jsonl");
|
||||||
if !fixture_path.exists() {
|
if !fixture_path.exists() {
|
||||||
println!("Fixture not found: {:?}, skipping test", fixture_path);
|
println!("Fixture not found: {:?}, skipping test", fixture_path);
|
||||||
println!("Run: cargo run --example record_worker_test");
|
println!("Run: cargo run --example record_engine_test");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = MockLlmClient::from_fixture(&fixture_path).unwrap();
|
let client = MockLlmClient::from_fixture(&fixture_path).unwrap();
|
||||||
let worker = Worker::new(client);
|
let engine = Engine::new(client);
|
||||||
|
|
||||||
// Send a simple message (Mutable::run consumes self, returns tuple)
|
// Send a simple message (Mutable::run consumes self, returns tuple)
|
||||||
let result = worker.run("Hello").await;
|
let result = engine.run("Hello").await;
|
||||||
|
|
||||||
assert!(result.is_ok(), "Worker should complete successfully");
|
assert!(result.is_ok(), "Engine should complete successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify that Worker can correctly process responses containing tool calls
|
/// Verify that Engine can correctly process responses containing tool calls
|
||||||
///
|
///
|
||||||
/// Uses tool_call.jsonl fixture to test that MockWeatherTool is called.
|
/// Uses tool_call.jsonl fixture to test that MockWeatherTool is called.
|
||||||
/// Sets max_turns=1 to prevent loop after tool execution.
|
/// Sets max_turns=1 to prevent loop after tool execution.
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_worker_tool_call() {
|
async fn test_engine_tool_call() {
|
||||||
let fixture_path = fixtures_dir().join("tool_call.jsonl");
|
let fixture_path = fixtures_dir().join("tool_call.jsonl");
|
||||||
if !fixture_path.exists() {
|
if !fixture_path.exists() {
|
||||||
println!("Fixture not found: {:?}, skipping test", fixture_path);
|
println!("Fixture not found: {:?}, skipping test", fixture_path);
|
||||||
println!("Run: cargo run --example record_worker_test");
|
println!("Run: cargo run --example record_engine_test");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = MockLlmClient::from_fixture(&fixture_path).unwrap();
|
let client = MockLlmClient::from_fixture(&fixture_path).unwrap();
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
|
|
||||||
// Register tool
|
// Register tool
|
||||||
let weather_tool = MockWeatherTool::new();
|
let weather_tool = MockWeatherTool::new();
|
||||||
let tool_for_check = weather_tool.clone();
|
let tool_for_check = weather_tool.clone();
|
||||||
worker.register_tool(weather_tool.definition());
|
engine.register_tool(weather_tool.definition());
|
||||||
|
|
||||||
// Send message (Mutable::run consumes self, returns tuple)
|
// Send message (Mutable::run consumes self, returns tuple)
|
||||||
let _result = worker.run("What's the weather in Tokyo?").await;
|
let _result = engine.run("What's the weather in Tokyo?").await;
|
||||||
|
|
||||||
// Verify tool was called
|
// Verify tool was called
|
||||||
// Note: max_turns=1 so no request is sent after tool result
|
// Note: max_turns=1 so no request is sent after tool result
|
||||||
|
|
@ -174,13 +174,13 @@ async fn test_worker_tool_call() {
|
||||||
// But ends after 1 turn due to max_turns=1
|
// But ends after 1 turn due to max_turns=1
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify that Worker works without fixture files
|
/// Verify that Engine works without fixture files
|
||||||
///
|
///
|
||||||
/// Constructs event sequence programmatically and passes to MockLlmClient.
|
/// Constructs event sequence programmatically and passes to MockLlmClient.
|
||||||
/// Useful when test independence is needed and external file dependency should be eliminated.
|
/// Useful when test independence is needed and external file dependency should be eliminated.
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_worker_with_programmatic_events() {
|
async fn test_engine_with_programmatic_events() {
|
||||||
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
|
use llm_engine::llm_client::event::{Event, ResponseStatus, StatusEvent};
|
||||||
|
|
||||||
// Construct event sequence programmatically
|
// Construct event sequence programmatically
|
||||||
let events = vec![
|
let events = vec![
|
||||||
|
|
@ -194,12 +194,12 @@ async fn test_worker_with_programmatic_events() {
|
||||||
];
|
];
|
||||||
|
|
||||||
let client = MockLlmClient::new(events);
|
let client = MockLlmClient::new(events);
|
||||||
let worker = Worker::new(client);
|
let engine = Engine::new(client);
|
||||||
|
|
||||||
// Mutable::run consumes self, returns tuple
|
// Mutable::run consumes self, returns tuple
|
||||||
let result = worker.run("Greet me").await;
|
let result = engine.run("Greet me").await;
|
||||||
|
|
||||||
assert!(result.is_ok(), "Worker should complete successfully");
|
assert!(result.is_ok(), "Engine should complete successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify that ToolCallCollector correctly collects ToolCall from ToolUse block events
|
/// Verify that ToolCallCollector correctly collects ToolCall from ToolUse block events
|
||||||
|
|
@ -208,8 +208,8 @@ async fn test_worker_with_programmatic_events() {
|
||||||
/// correctly extracts id, name, and input (JSON).
|
/// correctly extracts id, name, and input (JSON).
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_tool_call_collector_integration() {
|
async fn test_tool_call_collector_integration() {
|
||||||
use llm_worker::llm_client::event::Event;
|
use llm_engine::llm_client::event::Event;
|
||||||
use llm_worker::timeline::{Timeline, ToolCallCollector};
|
use llm_engine::timeline::{Timeline, ToolCallCollector};
|
||||||
|
|
||||||
// Event sequence containing ToolUse block
|
// Event sequence containing ToolUse block
|
||||||
let events = vec![
|
let events = vec![
|
||||||
|
|
@ -225,7 +225,7 @@ async fn test_tool_call_collector_integration() {
|
||||||
|
|
||||||
// Dispatch events
|
// Dispatch events
|
||||||
for event in &events {
|
for event in &events {
|
||||||
let timeline_event: llm_worker::timeline::event::Event = event.clone().into();
|
let timeline_event: llm_engine::timeline::event::Event = event.clone().into();
|
||||||
timeline.dispatch(&timeline_event);
|
timeline.dispatch(&timeline_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//! Worker state management tests
|
//! Engine state management tests
|
||||||
//!
|
//!
|
||||||
//! Tests for state transitions using the Type-state pattern (Mutable/Locked)
|
//! Tests for state transitions using the Type-state pattern (Mutable/Locked)
|
||||||
//! and state preservation between turns.
|
//! and state preservation between turns.
|
||||||
|
|
@ -10,10 +10,10 @@ use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use common::MockLlmClient;
|
use common::MockLlmClient;
|
||||||
use llm_worker::Item;
|
use llm_engine::Item;
|
||||||
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
|
use llm_engine::llm_client::event::{Event, ResponseStatus, StatusEvent};
|
||||||
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
|
use llm_engine::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
|
||||||
use llm_worker::{Worker, WorkerError};
|
use llm_engine::{Engine, EngineError};
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Mutable State Tests
|
// Mutable State Tests
|
||||||
|
|
@ -23,13 +23,13 @@ use llm_worker::{Worker, WorkerError};
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mutable_set_system_prompt() {
|
fn test_mutable_set_system_prompt() {
|
||||||
let client = MockLlmClient::new(vec![]);
|
let client = MockLlmClient::new(vec![]);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
|
|
||||||
assert!(worker.get_system_prompt().is_none());
|
assert!(engine.get_system_prompt().is_none());
|
||||||
|
|
||||||
worker.set_system_prompt("You are a helpful assistant.");
|
engine.set_system_prompt("You are a helpful assistant.");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
worker.get_system_prompt(),
|
engine.get_system_prompt(),
|
||||||
Some("You are a helpful assistant.")
|
Some("You are a helpful assistant.")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -38,41 +38,41 @@ fn test_mutable_set_system_prompt() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mutable_history_manipulation() {
|
fn test_mutable_history_manipulation() {
|
||||||
let client = MockLlmClient::new(vec![]);
|
let client = MockLlmClient::new(vec![]);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
|
|
||||||
// Initial state is empty
|
// Initial state is empty
|
||||||
assert!(worker.history().is_empty());
|
assert!(engine.history().is_empty());
|
||||||
|
|
||||||
// Add to history
|
// Add to history
|
||||||
worker.append_history(vec![Item::user_message("Hello")]);
|
engine.append_history(vec![Item::user_message("Hello")]);
|
||||||
worker.append_history(vec![Item::assistant_message("Hi there!")]);
|
engine.append_history(vec![Item::assistant_message("Hi there!")]);
|
||||||
assert_eq!(worker.history().len(), 2);
|
assert_eq!(engine.history().len(), 2);
|
||||||
|
|
||||||
// Append to history via the callback-aware API.
|
// Append to history via the callback-aware API.
|
||||||
worker.append_history(vec![Item::user_message("How are you?")]);
|
engine.append_history(vec![Item::user_message("How are you?")]);
|
||||||
assert_eq!(worker.history().len(), 3);
|
assert_eq!(engine.history().len(), 3);
|
||||||
|
|
||||||
// Clear history
|
// Clear history
|
||||||
worker.clear_history();
|
engine.clear_history();
|
||||||
assert!(worker.history().is_empty());
|
assert!(engine.history().is_empty());
|
||||||
|
|
||||||
// Set history
|
// Set history
|
||||||
let items = vec![
|
let items = vec![
|
||||||
Item::user_message("Test"),
|
Item::user_message("Test"),
|
||||||
Item::assistant_message("Response"),
|
Item::assistant_message("Response"),
|
||||||
];
|
];
|
||||||
worker.set_history(items);
|
engine.set_history(items);
|
||||||
assert_eq!(worker.history().len(), 2);
|
assert_eq!(engine.history().len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify that Worker can be constructed using builder pattern
|
/// Verify that Engine can be constructed using builder pattern
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mutable_builder_pattern() {
|
fn test_mutable_builder_pattern() {
|
||||||
let client = MockLlmClient::new(vec![]);
|
let client = MockLlmClient::new(vec![]);
|
||||||
let worker = Worker::new(client).system_prompt("System prompt");
|
let engine = Engine::new(client).system_prompt("System prompt");
|
||||||
|
|
||||||
assert_eq!(worker.get_system_prompt(), Some("System prompt"));
|
assert_eq!(engine.get_system_prompt(), Some("System prompt"));
|
||||||
assert!(worker.history().is_empty());
|
assert!(engine.history().is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify that multiple items can be added with append_history and callbacks fire.
|
/// Verify that multiple items can be added with append_history and callbacks fire.
|
||||||
|
|
@ -81,22 +81,22 @@ fn test_mutable_append_history() {
|
||||||
let client = MockLlmClient::new(vec![]);
|
let client = MockLlmClient::new(vec![]);
|
||||||
let observed = Arc::new(Mutex::new(Vec::new()));
|
let observed = Arc::new(Mutex::new(Vec::new()));
|
||||||
let observed_for_callback = Arc::clone(&observed);
|
let observed_for_callback = Arc::clone(&observed);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
worker.on_history_append(move |item| {
|
engine.on_history_append(move |item| {
|
||||||
if let Some(text) = item.as_text() {
|
if let Some(text) = item.as_text() {
|
||||||
observed_for_callback.lock().unwrap().push(text.to_string());
|
observed_for_callback.lock().unwrap().push(text.to_string());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
worker.append_history(vec![Item::user_message("First")]);
|
engine.append_history(vec![Item::user_message("First")]);
|
||||||
|
|
||||||
worker.append_history(vec![
|
engine.append_history(vec![
|
||||||
Item::assistant_message("Response 1"),
|
Item::assistant_message("Response 1"),
|
||||||
Item::user_message("Second"),
|
Item::user_message("Second"),
|
||||||
Item::assistant_message("Response 2"),
|
Item::assistant_message("Response 2"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert_eq!(worker.history().len(), 4);
|
assert_eq!(engine.history().len(), 4);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
observed.lock().unwrap().as_slice(),
|
observed.lock().unwrap().as_slice(),
|
||||||
["First", "Response 1", "Second", "Response 2"]
|
["First", "Response 1", "Second", "Response 2"]
|
||||||
|
|
@ -139,7 +139,7 @@ impl Tool for CountingTool {
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
_input_json: &str,
|
_input_json: &str,
|
||||||
_ctx: llm_worker::tool::ToolExecutionContext,
|
_ctx: llm_engine::tool::ToolExecutionContext,
|
||||||
) -> Result<ToolOutput, ToolError> {
|
) -> Result<ToolOutput, ToolError> {
|
||||||
self.calls.fetch_add(1, Ordering::SeqCst);
|
self.calls.fetch_add(1, Ordering::SeqCst);
|
||||||
Ok(format!("{}-ok", self.name).into())
|
Ok(format!("{}-ok", self.name).into())
|
||||||
|
|
@ -150,11 +150,11 @@ impl Tool for CountingTool {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mutable_can_register_tool() {
|
fn test_mutable_can_register_tool() {
|
||||||
let client = MockLlmClient::new(vec![]);
|
let client = MockLlmClient::new(vec![]);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
let tool = CountingTool::new("count_tool");
|
let tool = CountingTool::new("count_tool");
|
||||||
|
|
||||||
// register_tool is infallible (factory deferred to run-time flush)
|
// register_tool is infallible (factory deferred to run-time flush)
|
||||||
worker.register_tool(tool.definition());
|
engine.register_tool(tool.definition());
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
@ -165,37 +165,37 @@ fn test_mutable_can_register_tool() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lock_transition() {
|
fn test_lock_transition() {
|
||||||
let client = MockLlmClient::new(vec![]);
|
let client = MockLlmClient::new(vec![]);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
|
|
||||||
worker.set_system_prompt("System");
|
engine.set_system_prompt("System");
|
||||||
worker.append_history(vec![Item::user_message("Hello")]);
|
engine.append_history(vec![Item::user_message("Hello")]);
|
||||||
worker.append_history(vec![Item::assistant_message("Hi")]);
|
engine.append_history(vec![Item::assistant_message("Hi")]);
|
||||||
|
|
||||||
// Lock
|
// Lock
|
||||||
let locked_worker = worker.lock();
|
let locked_engine = engine.lock();
|
||||||
|
|
||||||
// History and system prompt are still accessible in Locked state
|
// History and system prompt are still accessible in Locked state
|
||||||
assert_eq!(locked_worker.get_system_prompt(), Some("System"));
|
assert_eq!(locked_engine.get_system_prompt(), Some("System"));
|
||||||
assert_eq!(locked_worker.history().len(), 2);
|
assert_eq!(locked_engine.history().len(), 2);
|
||||||
assert_eq!(locked_worker.locked_prefix_len(), 2);
|
assert_eq!(locked_engine.locked_prefix_len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify that unlock() transitions from Locked -> Mutable state
|
/// Verify that unlock() transitions from Locked -> Mutable state
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unlock_transition() {
|
fn test_unlock_transition() {
|
||||||
let client = MockLlmClient::new(vec![]);
|
let client = MockLlmClient::new(vec![]);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
|
|
||||||
worker.append_history(vec![Item::user_message("Hello")]);
|
engine.append_history(vec![Item::user_message("Hello")]);
|
||||||
let locked_worker = worker.lock();
|
let locked_engine = engine.lock();
|
||||||
|
|
||||||
// Unlock
|
// Unlock
|
||||||
let mut worker = locked_worker.unlock();
|
let mut engine = locked_engine.unlock();
|
||||||
|
|
||||||
// History operations are available again in Mutable state
|
// History operations are available again in Mutable state
|
||||||
worker.append_history(vec![Item::assistant_message("Hi")]);
|
engine.append_history(vec![Item::assistant_message("Hi")]);
|
||||||
worker.clear_history();
|
engine.clear_history();
|
||||||
assert!(worker.history().is_empty());
|
assert!(engine.history().is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
@ -204,7 +204,7 @@ fn test_unlock_transition() {
|
||||||
|
|
||||||
/// Verify that history is correctly updated after running a turn in Mutable state
|
/// Verify that history is correctly updated after running a turn in Mutable state
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_mutable_run_updates_history() -> Result<(), WorkerError> {
|
async fn test_mutable_run_updates_history() -> Result<(), EngineError> {
|
||||||
let events = vec![
|
let events = vec![
|
||||||
Event::text_block_start(0),
|
Event::text_block_start(0),
|
||||||
Event::text_delta(0, "Hello, I'm an assistant!"),
|
Event::text_delta(0, "Hello, I'm an assistant!"),
|
||||||
|
|
@ -215,14 +215,14 @@ async fn test_mutable_run_updates_history() -> Result<(), WorkerError> {
|
||||||
];
|
];
|
||||||
|
|
||||||
let client = MockLlmClient::new(events);
|
let client = MockLlmClient::new(events);
|
||||||
let worker = Worker::new(client);
|
let engine = Engine::new(client);
|
||||||
|
|
||||||
// Execute (Mutable::run consumes self, returns RunOutput)
|
// Execute (Mutable::run consumes self, returns EngineRunOutput)
|
||||||
let out = worker.run("Hi there").await?;
|
let out = engine.run("Hi there").await?;
|
||||||
let worker = out.worker;
|
let engine = out.engine;
|
||||||
|
|
||||||
// History is updated
|
// History is updated
|
||||||
let history = worker.history();
|
let history = engine.history();
|
||||||
assert_eq!(history.len(), 2); // user + assistant
|
assert_eq!(history.len(), 2); // user + assistant
|
||||||
|
|
||||||
// User message
|
// User message
|
||||||
|
|
@ -259,24 +259,24 @@ async fn test_locked_multi_turn_history_accumulation() {
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let worker = Worker::new(client).system_prompt("You are helpful.");
|
let engine = Engine::new(client).system_prompt("You are helpful.");
|
||||||
|
|
||||||
// Lock (after setting system prompt)
|
// Lock (after setting system prompt)
|
||||||
let mut locked_worker = worker.lock();
|
let mut locked_engine = engine.lock();
|
||||||
assert_eq!(locked_worker.locked_prefix_len(), 0); // No items yet
|
assert_eq!(locked_engine.locked_prefix_len(), 0); // No items yet
|
||||||
|
|
||||||
// Turn 1
|
// Turn 1
|
||||||
let result1 = locked_worker.run("Hello!").await;
|
let result1 = locked_engine.run("Hello!").await;
|
||||||
assert!(result1.is_ok());
|
assert!(result1.is_ok());
|
||||||
assert_eq!(locked_worker.history().len(), 2); // user + assistant
|
assert_eq!(locked_engine.history().len(), 2); // user + assistant
|
||||||
|
|
||||||
// Turn 2
|
// Turn 2
|
||||||
let result2 = locked_worker.run("Can you help me?").await;
|
let result2 = locked_engine.run("Can you help me?").await;
|
||||||
assert!(result2.is_ok());
|
assert!(result2.is_ok());
|
||||||
assert_eq!(locked_worker.history().len(), 4); // 2 * (user + assistant)
|
assert_eq!(locked_engine.history().len(), 4); // 2 * (user + assistant)
|
||||||
|
|
||||||
// Verify history contents
|
// Verify history contents
|
||||||
let history = locked_worker.history();
|
let history = locked_engine.history();
|
||||||
|
|
||||||
// Turn 1 user message
|
// Turn 1 user message
|
||||||
assert_eq!(history[0].as_text(), Some("Hello!"));
|
assert_eq!(history[0].as_text(), Some("Hello!"));
|
||||||
|
|
@ -313,29 +313,29 @@ async fn test_locked_prefix_len_tracking() {
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
|
|
||||||
// Add items beforehand
|
// Add items beforehand
|
||||||
worker.append_history(vec![Item::user_message("Pre-existing message 1")]);
|
engine.append_history(vec![Item::user_message("Pre-existing message 1")]);
|
||||||
worker.append_history(vec![Item::assistant_message("Pre-existing response 1")]);
|
engine.append_history(vec![Item::assistant_message("Pre-existing response 1")]);
|
||||||
|
|
||||||
assert_eq!(worker.history().len(), 2);
|
assert_eq!(engine.history().len(), 2);
|
||||||
|
|
||||||
// Lock
|
// Lock
|
||||||
let mut locked_worker = worker.lock();
|
let mut locked_engine = engine.lock();
|
||||||
assert_eq!(locked_worker.locked_prefix_len(), 2); // 2 items at lock time
|
assert_eq!(locked_engine.locked_prefix_len(), 2); // 2 items at lock time
|
||||||
|
|
||||||
// Execute turn
|
// Execute turn
|
||||||
locked_worker.run("New message").await.unwrap();
|
locked_engine.run("New message").await.unwrap();
|
||||||
|
|
||||||
// History grows but locked_prefix_len remains unchanged
|
// History grows but locked_prefix_len remains unchanged
|
||||||
assert_eq!(locked_worker.history().len(), 4); // 2 + 2
|
assert_eq!(locked_engine.history().len(), 4); // 2 + 2
|
||||||
assert_eq!(locked_worker.locked_prefix_len(), 2); // Unchanged
|
assert_eq!(locked_engine.locked_prefix_len(), 2); // Unchanged
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify that turn count is correctly incremented
|
/// Verify that turn count is correctly incremented
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_turn_count_increment() -> Result<(), WorkerError> {
|
async fn test_turn_count_increment() -> Result<(), EngineError> {
|
||||||
let client = MockLlmClient::with_responses(vec![
|
let client = MockLlmClient::with_responses(vec![
|
||||||
vec![
|
vec![
|
||||||
Event::text_block_start(0),
|
Event::text_block_start(0),
|
||||||
|
|
@ -355,21 +355,21 @@ async fn test_turn_count_increment() -> Result<(), WorkerError> {
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let worker = Worker::new(client);
|
let engine = Engine::new(client);
|
||||||
|
|
||||||
assert_eq!(worker.turn_count(), 0);
|
assert_eq!(engine.turn_count(), 0);
|
||||||
assert_eq!(worker.llm_call_count(), 0);
|
assert_eq!(engine.llm_call_count(), 0);
|
||||||
|
|
||||||
// First run consumes Mutable, returns RunOutput
|
// First run consumes Mutable, returns EngineRunOutput
|
||||||
let mut worker = worker.run("First").await?.worker;
|
let mut engine = engine.run("First").await?.engine;
|
||||||
assert_eq!(worker.turn_count(), 1);
|
assert_eq!(engine.turn_count(), 1);
|
||||||
// Retry not yet implemented → AgentTurn:LlmCall is 1:1.
|
// Retry not yet implemented → AgentTurn:LlmCall is 1:1.
|
||||||
assert_eq!(worker.llm_call_count(), 1);
|
assert_eq!(engine.llm_call_count(), 1);
|
||||||
|
|
||||||
// Subsequent runs on Locked take &mut self
|
// Subsequent runs on Locked take &mut self
|
||||||
worker.run("Second").await?;
|
engine.run("Second").await?;
|
||||||
assert_eq!(worker.turn_count(), 2);
|
assert_eq!(engine.turn_count(), 2);
|
||||||
assert_eq!(worker.llm_call_count(), 2);
|
assert_eq!(engine.llm_call_count(), 2);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -386,14 +386,14 @@ async fn test_unlock_edit_relock() {
|
||||||
}),
|
}),
|
||||||
]]);
|
]]);
|
||||||
|
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
worker.append_history(vec![
|
engine.append_history(vec![
|
||||||
Item::user_message("Hello"),
|
Item::user_message("Hello"),
|
||||||
Item::assistant_message("Hi"),
|
Item::assistant_message("Hi"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Lock -> Unlock
|
// Lock -> Unlock
|
||||||
let locked = worker.lock();
|
let locked = engine.lock();
|
||||||
assert_eq!(locked.locked_prefix_len(), 2);
|
assert_eq!(locked.locked_prefix_len(), 2);
|
||||||
|
|
||||||
let mut unlocked = locked.unlock();
|
let mut unlocked = locked.unlock();
|
||||||
|
|
@ -446,11 +446,11 @@ async fn test_lock_unlock_relock_tools_remain_effective() {
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
let tool_a = CountingTool::new("tool_a");
|
let tool_a = CountingTool::new("tool_a");
|
||||||
worker.register_tool(tool_a.definition());
|
engine.register_tool(tool_a.definition());
|
||||||
|
|
||||||
let mut locked = worker.lock();
|
let mut locked = engine.lock();
|
||||||
locked.run("first").await.expect("first run");
|
locked.run("first").await.expect("first run");
|
||||||
assert_eq!(tool_a.call_count(), 1, "tool_a should be called once");
|
assert_eq!(tool_a.call_count(), 1, "tool_a should be called once");
|
||||||
|
|
||||||
|
|
@ -473,9 +473,9 @@ async fn test_lock_unlock_relock_tools_remain_effective() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_system_prompt_preserved_in_locked_state() {
|
fn test_system_prompt_preserved_in_locked_state() {
|
||||||
let client = MockLlmClient::new(vec![]);
|
let client = MockLlmClient::new(vec![]);
|
||||||
let worker = Worker::new(client).system_prompt("Important system prompt");
|
let engine = Engine::new(client).system_prompt("Important system prompt");
|
||||||
|
|
||||||
let locked = worker.lock();
|
let locked = engine.lock();
|
||||||
assert_eq!(locked.get_system_prompt(), Some("Important system prompt"));
|
assert_eq!(locked.get_system_prompt(), Some("Important system prompt"));
|
||||||
|
|
||||||
let unlocked = locked.unlock();
|
let unlocked = locked.unlock();
|
||||||
|
|
@ -489,9 +489,9 @@ fn test_system_prompt_preserved_in_locked_state() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_system_prompt_change_after_unlock() {
|
fn test_system_prompt_change_after_unlock() {
|
||||||
let client = MockLlmClient::new(vec![]);
|
let client = MockLlmClient::new(vec![]);
|
||||||
let worker = Worker::new(client).system_prompt("Original prompt");
|
let engine = Engine::new(client).system_prompt("Original prompt");
|
||||||
|
|
||||||
let locked = worker.lock();
|
let locked = engine.lock();
|
||||||
let mut unlocked = locked.unlock();
|
let mut unlocked = locked.unlock();
|
||||||
|
|
||||||
unlocked.set_system_prompt("New prompt");
|
unlocked.set_system_prompt("New prompt");
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
//! Parallel tool execution tests
|
//! Parallel tool execution tests
|
||||||
//!
|
//!
|
||||||
//! Verify that Worker executes multiple tools in parallel.
|
//! Verify that Engine executes multiple tools in parallel.
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use llm_worker::Worker;
|
use llm_engine::Engine;
|
||||||
use llm_worker::interceptor::{
|
use llm_engine::interceptor::{
|
||||||
Interceptor, PostToolAction, PreToolAction, ToolCallInfo, ToolResultInfo,
|
Interceptor, PostToolAction, PreToolAction, ToolCallInfo, ToolResultInfo,
|
||||||
};
|
};
|
||||||
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
|
use llm_engine::llm_client::event::{Event, ResponseStatus, StatusEvent};
|
||||||
use llm_worker::tool::{
|
use llm_engine::tool::{
|
||||||
Tool, ToolDefinition, ToolError, ToolExecutionContext, ToolMeta, ToolOutput, ToolResult,
|
Tool, ToolDefinition, ToolError, ToolExecutionContext, ToolMeta, ToolOutput, ToolResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -64,7 +64,7 @@ impl Tool for SlowTool {
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
_input_json: &str,
|
_input_json: &str,
|
||||||
_ctx: llm_worker::tool::ToolExecutionContext,
|
_ctx: llm_engine::tool::ToolExecutionContext,
|
||||||
) -> Result<ToolOutput, ToolError> {
|
) -> Result<ToolOutput, ToolError> {
|
||||||
self.call_count.fetch_add(1, Ordering::SeqCst);
|
self.call_count.fetch_add(1, Ordering::SeqCst);
|
||||||
tokio::time::sleep(Duration::from_millis(self.delay_ms)).await;
|
tokio::time::sleep(Duration::from_millis(self.delay_ms)).await;
|
||||||
|
|
@ -146,7 +146,7 @@ async fn test_parallel_tool_execution() {
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
let tool1 = SlowTool::new("slow_tool_1", 100);
|
let tool1 = SlowTool::new("slow_tool_1", 100);
|
||||||
let tool2 = SlowTool::new("slow_tool_2", 100);
|
let tool2 = SlowTool::new("slow_tool_2", 100);
|
||||||
let tool3 = SlowTool::new("slow_tool_3", 100);
|
let tool3 = SlowTool::new("slow_tool_3", 100);
|
||||||
|
|
@ -155,13 +155,13 @@ async fn test_parallel_tool_execution() {
|
||||||
let tool2_clone = tool2.clone();
|
let tool2_clone = tool2.clone();
|
||||||
let tool3_clone = tool3.clone();
|
let tool3_clone = tool3.clone();
|
||||||
|
|
||||||
worker.register_tool(tool1.definition());
|
engine.register_tool(tool1.definition());
|
||||||
worker.register_tool(tool2.definition());
|
engine.register_tool(tool2.definition());
|
||||||
worker.register_tool(tool3.definition());
|
engine.register_tool(tool3.definition());
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
// Mutable::run consumes self, returns (Locked, WorkerResult)
|
// Mutable::run consumes self, returns (Locked, EngineResult)
|
||||||
let _result = worker.run("Run all tools").await;
|
let _result = engine.run("Run all tools").await;
|
||||||
let elapsed = start.elapsed();
|
let elapsed = start.elapsed();
|
||||||
|
|
||||||
// Verify all tools were called
|
// Verify all tools were called
|
||||||
|
|
@ -206,14 +206,14 @@ async fn test_tool_execution_context_order_and_batch_id() {
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
let contexts = Arc::new(Mutex::new(Vec::new()));
|
let contexts = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
|
||||||
worker.register_tool(ContextRecordingTool::new("record_a", contexts.clone()).definition());
|
engine.register_tool(ContextRecordingTool::new("record_a", contexts.clone()).definition());
|
||||||
worker.register_tool(ContextRecordingTool::new("record_b", contexts.clone()).definition());
|
engine.register_tool(ContextRecordingTool::new("record_b", contexts.clone()).definition());
|
||||||
worker.register_tool(ContextRecordingTool::new("record_c", contexts.clone()).definition());
|
engine.register_tool(ContextRecordingTool::new("record_c", contexts.clone()).definition());
|
||||||
|
|
||||||
let _ = worker.run("record contexts").await;
|
let _ = engine.run("record contexts").await;
|
||||||
|
|
||||||
let mut contexts = contexts.lock().unwrap().clone();
|
let mut contexts = contexts.lock().unwrap().clone();
|
||||||
contexts.sort_by_key(|ctx| ctx.call_index);
|
contexts.sort_by_key(|ctx| ctx.call_index);
|
||||||
|
|
@ -257,12 +257,12 @@ async fn test_tool_execution_context_batch_id_changes_between_batches() {
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
let contexts = Arc::new(Mutex::new(Vec::new()));
|
let contexts = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
|
||||||
worker.register_tool(ContextRecordingTool::new("record", contexts.clone()).definition());
|
engine.register_tool(ContextRecordingTool::new("record", contexts.clone()).definition());
|
||||||
|
|
||||||
let _ = worker.run("record batches").await;
|
let _ = engine.run("record batches").await;
|
||||||
|
|
||||||
let contexts = contexts.lock().unwrap().clone();
|
let contexts = contexts.lock().unwrap().clone();
|
||||||
assert_eq!(contexts.len(), 2);
|
assert_eq!(contexts.len(), 2);
|
||||||
|
|
@ -299,17 +299,17 @@ async fn test_tool_execution_context_for_skipped_and_synthetic_paths() {
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
let executed_contexts = Arc::new(Mutex::new(Vec::new()));
|
let executed_contexts = Arc::new(Mutex::new(Vec::new()));
|
||||||
let pre_contexts = Arc::new(Mutex::new(Vec::new()));
|
let pre_contexts = Arc::new(Mutex::new(Vec::new()));
|
||||||
let post_contexts = Arc::new(Mutex::new(Vec::new()));
|
let post_contexts = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
|
||||||
worker
|
engine
|
||||||
.register_tool(ContextRecordingTool::new("record", executed_contexts.clone()).definition());
|
.register_tool(ContextRecordingTool::new("record", executed_contexts.clone()).definition());
|
||||||
worker.register_tool(
|
engine.register_tool(
|
||||||
ContextRecordingTool::new("skip_tool", executed_contexts.clone()).definition(),
|
ContextRecordingTool::new("skip_tool", executed_contexts.clone()).definition(),
|
||||||
);
|
);
|
||||||
worker.register_tool(
|
engine.register_tool(
|
||||||
ContextRecordingTool::new("synthetic_tool", executed_contexts.clone()).definition(),
|
ContextRecordingTool::new("synthetic_tool", executed_contexts.clone()).definition(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -341,12 +341,12 @@ async fn test_tool_execution_context_for_skipped_and_synthetic_paths() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.set_interceptor(ContextPolicy {
|
engine.set_interceptor(ContextPolicy {
|
||||||
pre_contexts: pre_contexts.clone(),
|
pre_contexts: pre_contexts.clone(),
|
||||||
post_contexts: post_contexts.clone(),
|
post_contexts: post_contexts.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let _ = worker.run("record skipped and synthetic contexts").await;
|
let _ = engine.run("record skipped and synthetic contexts").await;
|
||||||
|
|
||||||
let mut pre_contexts = pre_contexts.lock().unwrap().clone();
|
let mut pre_contexts = pre_contexts.lock().unwrap().clone();
|
||||||
pre_contexts.sort_by_key(|ctx| ctx.call_index);
|
pre_contexts.sort_by_key(|ctx| ctx.call_index);
|
||||||
|
|
@ -390,7 +390,7 @@ async fn test_before_tool_call_skip() {
|
||||||
];
|
];
|
||||||
|
|
||||||
let client = MockLlmClient::new(events);
|
let client = MockLlmClient::new(events);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
|
|
||||||
let allowed_tool = SlowTool::new("allowed_tool", 10);
|
let allowed_tool = SlowTool::new("allowed_tool", 10);
|
||||||
let blocked_tool = SlowTool::new("blocked_tool", 10);
|
let blocked_tool = SlowTool::new("blocked_tool", 10);
|
||||||
|
|
@ -398,8 +398,8 @@ async fn test_before_tool_call_skip() {
|
||||||
let allowed_clone = allowed_tool.clone();
|
let allowed_clone = allowed_tool.clone();
|
||||||
let blocked_clone = blocked_tool.clone();
|
let blocked_clone = blocked_tool.clone();
|
||||||
|
|
||||||
worker.register_tool(allowed_tool.definition());
|
engine.register_tool(allowed_tool.definition());
|
||||||
worker.register_tool(blocked_tool.definition());
|
engine.register_tool(blocked_tool.definition());
|
||||||
|
|
||||||
// Policy to skip "blocked_tool"
|
// Policy to skip "blocked_tool"
|
||||||
struct BlockingPolicy;
|
struct BlockingPolicy;
|
||||||
|
|
@ -415,10 +415,10 @@ async fn test_before_tool_call_skip() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.set_interceptor(BlockingPolicy);
|
engine.set_interceptor(BlockingPolicy);
|
||||||
|
|
||||||
// Mutable::run consumes self, returns (Locked, WorkerResult)
|
// Mutable::run consumes self, returns (Locked, EngineResult)
|
||||||
let _result = worker.run("Test hook").await;
|
let _result = engine.run("Test hook").await;
|
||||||
|
|
||||||
// allowed_tool is called, but blocked_tool is not
|
// allowed_tool is called, but blocked_tool is not
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -458,7 +458,7 @@ async fn test_post_tool_call_modification() {
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct SimpleTool;
|
struct SimpleTool;
|
||||||
|
|
@ -468,7 +468,7 @@ async fn test_post_tool_call_modification() {
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
_: &str,
|
_: &str,
|
||||||
_ctx: llm_worker::tool::ToolExecutionContext,
|
_ctx: llm_engine::tool::ToolExecutionContext,
|
||||||
) -> Result<ToolOutput, ToolError> {
|
) -> Result<ToolOutput, ToolError> {
|
||||||
Ok("Original Result".to_string().into())
|
Ok("Original Result".to_string().into())
|
||||||
}
|
}
|
||||||
|
|
@ -483,7 +483,7 @@ async fn test_post_tool_call_modification() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.register_tool(simple_tool_definition());
|
engine.register_tool(simple_tool_definition());
|
||||||
|
|
||||||
// Policy to modify results
|
// Policy to modify results
|
||||||
struct ModifyingPolicy {
|
struct ModifyingPolicy {
|
||||||
|
|
@ -500,14 +500,14 @@ async fn test_post_tool_call_modification() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let modified_content = Arc::new(std::sync::Mutex::new(None));
|
let modified_content = Arc::new(std::sync::Mutex::new(None));
|
||||||
worker.set_interceptor(ModifyingPolicy {
|
engine.set_interceptor(ModifyingPolicy {
|
||||||
modified_content: modified_content.clone(),
|
modified_content: modified_content.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mutable::run consumes self, returns (Locked, WorkerResult)
|
// Mutable::run consumes self, returns (Locked, EngineResult)
|
||||||
let result = worker.run("Test modification").await;
|
let result = engine.run("Test modification").await;
|
||||||
|
|
||||||
assert!(result.is_ok(), "Worker should complete");
|
assert!(result.is_ok(), "Engine should complete");
|
||||||
|
|
||||||
// Verify hook was called and content was modified
|
// Verify hook was called and content was modified
|
||||||
let content = modified_content.lock().unwrap().clone();
|
let content = modified_content.lock().unwrap().clone();
|
||||||
|
|
@ -541,10 +541,10 @@ async fn test_before_tool_call_synthetic_result_committed() {
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
let blocked_tool = SlowTool::new("blocked_tool", 10);
|
let blocked_tool = SlowTool::new("blocked_tool", 10);
|
||||||
let blocked_clone = blocked_tool.clone();
|
let blocked_clone = blocked_tool.clone();
|
||||||
worker.register_tool(blocked_tool.definition());
|
engine.register_tool(blocked_tool.definition());
|
||||||
|
|
||||||
struct SyntheticPolicy;
|
struct SyntheticPolicy;
|
||||||
|
|
||||||
|
|
@ -558,14 +558,14 @@ async fn test_before_tool_call_synthetic_result_committed() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.set_interceptor(SyntheticPolicy);
|
engine.set_interceptor(SyntheticPolicy);
|
||||||
|
|
||||||
let result = worker.run("Test synthetic result").await.unwrap();
|
let result = engine.run("Test synthetic result").await.unwrap();
|
||||||
|
|
||||||
assert_eq!(blocked_clone.call_count(), 0, "Blocked tool should not run");
|
assert_eq!(blocked_clone.call_count(), 0, "Blocked tool should not run");
|
||||||
assert!(result.worker.history().iter().any(|item| matches!(
|
assert!(result.engine.history().iter().any(|item| matches!(
|
||||||
item,
|
item,
|
||||||
llm_worker::Item::ToolResult {
|
llm_engine::Item::ToolResult {
|
||||||
call_id,
|
call_id,
|
||||||
summary,
|
summary,
|
||||||
is_error: true,
|
is_error: true,
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Reasoning history round-trip 統合テスト
|
//! Reasoning history round-trip 統合テスト
|
||||||
//!
|
//!
|
||||||
//! Worker のストリーム → history append → 次リクエスト送出までの
|
//! Engine のストリーム → history append → 次リクエスト送出までの
|
||||||
//! ライフサイクルで `Item::Reasoning` が脱落せず保持されることを確認する。
|
//! ライフサイクルで `Item::Reasoning` が脱落せず保持されることを確認する。
|
||||||
//!
|
//!
|
||||||
//! 検証点:
|
//! 検証点:
|
||||||
|
|
@ -14,9 +14,9 @@
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
use common::MockLlmClient;
|
use common::MockLlmClient;
|
||||||
use llm_worker::Item;
|
use llm_engine::Engine;
|
||||||
use llm_worker::Worker;
|
use llm_engine::Item;
|
||||||
use llm_worker::llm_client::event::{
|
use llm_engine::llm_client::event::{
|
||||||
BlockMetadata, BlockStart, BlockStop, BlockType, Event, ReasoningBlockData, ResponseStatus,
|
BlockMetadata, BlockStart, BlockStop, BlockType, Event, ReasoningBlockData, ResponseStatus,
|
||||||
StatusEvent,
|
StatusEvent,
|
||||||
};
|
};
|
||||||
|
|
@ -28,9 +28,9 @@ fn reasoning_block(text: impl Into<String>, data: ReasoningBlockData) -> Vec<Eve
|
||||||
block_type: BlockType::Thinking,
|
block_type: BlockType::Thinking,
|
||||||
metadata: BlockMetadata::Thinking,
|
metadata: BlockMetadata::Thinking,
|
||||||
}),
|
}),
|
||||||
Event::BlockDelta(llm_worker::llm_client::event::BlockDelta {
|
Event::BlockDelta(llm_engine::llm_client::event::BlockDelta {
|
||||||
index: 100,
|
index: 100,
|
||||||
delta: llm_worker::llm_client::event::DeltaContent::Thinking(text.into()),
|
delta: llm_engine::llm_client::event::DeltaContent::Thinking(text.into()),
|
||||||
}),
|
}),
|
||||||
Event::BlockStop(BlockStop {
|
Event::BlockStop(BlockStop {
|
||||||
index: 100,
|
index: 100,
|
||||||
|
|
@ -42,7 +42,7 @@ fn reasoning_block(text: impl Into<String>, data: ReasoningBlockData) -> Vec<Eve
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Anthropic 風: thinking ブロック → text → 終了 のシーケンス。
|
/// Anthropic 風: thinking ブロック → text → 終了 のシーケンス。
|
||||||
/// Worker history に Reasoning(signature 付き) → assistant_message が並ぶ。
|
/// Engine history に Reasoning(signature 付き) → assistant_message が並ぶ。
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn anthropic_thinking_round_trips_signature_into_history() {
|
async fn anthropic_thinking_round_trips_signature_into_history() {
|
||||||
let mut events = reasoning_block(
|
let mut events = reasoning_block(
|
||||||
|
|
@ -64,11 +64,11 @@ async fn anthropic_thinking_round_trips_signature_into_history() {
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
let client = MockLlmClient::new(events);
|
let client = MockLlmClient::new(events);
|
||||||
let worker = Worker::new(client);
|
let engine = Engine::new(client);
|
||||||
let out = worker.run("question?").await.expect("run ok");
|
let out = engine.run("question?").await.expect("run ok");
|
||||||
let worker = out.worker;
|
let engine = out.engine;
|
||||||
|
|
||||||
let history = worker.history();
|
let history = engine.history();
|
||||||
// user / reasoning / assistant_message
|
// user / reasoning / assistant_message
|
||||||
assert_eq!(history.len(), 3, "history: {history:?}");
|
assert_eq!(history.len(), 3, "history: {history:?}");
|
||||||
|
|
||||||
|
|
@ -108,11 +108,11 @@ async fn openai_reasoning_round_trips_encrypted_and_summary() {
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
let client = MockLlmClient::new(events);
|
let client = MockLlmClient::new(events);
|
||||||
let worker = Worker::new(client);
|
let engine = Engine::new(client);
|
||||||
let out = worker.run("q").await.expect("run ok");
|
let out = engine.run("q").await.expect("run ok");
|
||||||
let worker = out.worker;
|
let engine = out.engine;
|
||||||
|
|
||||||
let history = worker.history();
|
let history = engine.history();
|
||||||
match &history[1] {
|
match &history[1] {
|
||||||
Item::Reasoning {
|
Item::Reasoning {
|
||||||
text,
|
text,
|
||||||
|
|
@ -154,19 +154,19 @@ async fn reasoning_precedes_text_in_assistant_burst() {
|
||||||
status: ResponseStatus::Completed,
|
status: ResponseStatus::Completed,
|
||||||
}));
|
}));
|
||||||
let client = MockLlmClient::new(events);
|
let client = MockLlmClient::new(events);
|
||||||
let worker = Worker::new(client);
|
let engine = Engine::new(client);
|
||||||
let out = worker.run("q").await.expect("run ok");
|
let out = engine.run("q").await.expect("run ok");
|
||||||
let worker = out.worker;
|
let engine = out.engine;
|
||||||
|
|
||||||
let history = worker.history();
|
let history = engine.history();
|
||||||
// user / reasoning(先頭) / assistant_message
|
// user / reasoning(先頭) / assistant_message
|
||||||
assert!(matches!(history[1], Item::Reasoning { .. }));
|
assert!(matches!(history[1], Item::Reasoning { .. }));
|
||||||
assert_eq!(history[2].as_text(), Some("intermediate"));
|
assert_eq!(history[2].as_text(), Some("intermediate"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// resume シナリオ: history.json 由来の Item::Reasoning(signature) を Worker に
|
/// resume シナリオ: history.json 由来の Item::Reasoning(signature) を Engine に
|
||||||
/// 注入して run しても、次の outgoing request の `Request::items` にそのまま
|
/// 注入して run しても、次の outgoing request の `Request::items` にそのまま
|
||||||
/// 載って LLM へ渡る(worker は items を改変しない契約)。
|
/// 載って LLM へ渡る(engine は items を改変しない契約)。
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn injected_reasoning_survives_into_outgoing_request() {
|
async fn injected_reasoning_survives_into_outgoing_request() {
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
@ -174,7 +174,7 @@ async fn injected_reasoning_survives_into_outgoing_request() {
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use llm_worker::llm_client::{ClientError, LlmClient, Request};
|
use llm_engine::llm_client::{ClientError, LlmClient, Request};
|
||||||
|
|
||||||
/// Request を 1 度だけキャプチャして空ストリームを返す client。
|
/// Request を 1 度だけキャプチャして空ストリームを返す client。
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
@ -206,15 +206,15 @@ async fn injected_reasoning_survives_into_outgoing_request() {
|
||||||
captured: captured.clone(),
|
captured: captured.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut worker = Worker::new(client);
|
let mut engine = Engine::new(client);
|
||||||
// resume: 既存 history を流し込む
|
// resume: 既存 history を流し込む
|
||||||
worker.set_history(vec![
|
engine.set_history(vec![
|
||||||
Item::user_message("prior question"),
|
Item::user_message("prior question"),
|
||||||
Item::reasoning("prior thinking").with_signature("SIG-PRIOR"),
|
Item::reasoning("prior thinking").with_signature("SIG-PRIOR"),
|
||||||
Item::assistant_message("prior answer"),
|
Item::assistant_message("prior answer"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let _ = worker.run("follow up").await.expect("run ok");
|
let _ = engine.run("follow up").await.expect("run ok");
|
||||||
|
|
||||||
let req = captured
|
let req = captured
|
||||||
.lock()
|
.lock()
|
||||||
|
|
@ -9,8 +9,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use schemars;
|
use schemars;
|
||||||
use serde;
|
use serde;
|
||||||
|
|
||||||
use llm_worker::ToolExecutionContext;
|
use llm_engine::ToolExecutionContext;
|
||||||
use llm_worker_macros::tool_registry;
|
use llm_engine_macros::tool_registry;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Test: Basic Tool Generation
|
// Test: Basic Tool Generation
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
//! HTTP transport の単発 request / error classification テスト。
|
//! HTTP transport の単発 request / error classification テスト。
|
||||||
//!
|
//!
|
||||||
//! Retry/backoff は Worker の lifecycle 管理に属するため、transport は 1 回だけ
|
//! Retry/backoff は Engine の lifecycle 管理に属するため、transport は 1 回だけ
|
||||||
//! request を送り、HTTP status / Retry-After を `ClientError` に載せて返す。
|
//! request を送り、HTTP status / Retry-After を `ClientError` に載せて返す。
|
||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use llm_worker::llm_client::LlmClient;
|
use llm_engine::llm_client::LlmClient;
|
||||||
use llm_worker::llm_client::auth::AuthRequirement;
|
use llm_engine::llm_client::auth::AuthRequirement;
|
||||||
use llm_worker::llm_client::capability::ModelCapability;
|
use llm_engine::llm_client::capability::ModelCapability;
|
||||||
use llm_worker::llm_client::error::ClientError;
|
use llm_engine::llm_client::error::ClientError;
|
||||||
use llm_worker::llm_client::event::Event;
|
use llm_engine::llm_client::event::Event;
|
||||||
use llm_worker::llm_client::scheme::Scheme;
|
use llm_engine::llm_client::scheme::Scheme;
|
||||||
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth};
|
use llm_engine::llm_client::transport::{HttpTransport, ResolvedAuth};
|
||||||
use llm_worker::llm_client::types::Request;
|
use llm_engine::llm_client::types::Request;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use wiremock::matchers::{method, path};
|
use wiremock::matchers::{method, path};
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use llm_worker::Worker;
|
use llm_engine::Engine;
|
||||||
use llm_worker::llm_client::capability::{
|
use llm_engine::llm_client::capability::{
|
||||||
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,
|
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,
|
||||||
};
|
};
|
||||||
use llm_worker::llm_client::scheme::anthropic::AnthropicScheme;
|
use llm_engine::llm_client::scheme::anthropic::AnthropicScheme;
|
||||||
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth};
|
use llm_engine::llm_client::transport::{HttpTransport, ResolvedAuth};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
@ -21,8 +21,8 @@ fn main() {
|
||||||
ResolvedAuth::None,
|
ResolvedAuth::None,
|
||||||
cap,
|
cap,
|
||||||
);
|
);
|
||||||
let worker = Worker::new(client);
|
let engine = Engine::new(client);
|
||||||
let mut locked = worker.lock();
|
let mut locked = engine.lock();
|
||||||
let def: llm_worker::tool::ToolDefinition = Arc::new(|| panic!("unused"));
|
let def: llm_engine::tool::ToolDefinition = Arc::new(|| panic!("unused"));
|
||||||
let _ = locked.register_tool(def);
|
let _ = locked.register_tool(def);
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
error[E0599]: no method named `register_tool` found for struct `Worker<HttpTransport<AnthropicScheme>, Locked>` in the current scope
|
error[E0599]: no method named `register_tool` found for struct `Engine<HttpTransport<AnthropicScheme>, Locked>` in the current scope
|
||||||
--> tests/ui/locked_register_tool.rs:27:20
|
--> tests/ui/locked_register_tool.rs:27:20
|
||||||
|
|
|
|
||||||
27 | let _ = locked.register_tool(def);
|
27 | let _ = locked.register_tool(def);
|
||||||
| ^^^^^^^^^^^^^ method not found in `Worker<HttpTransport<AnthropicScheme>, Locked>`
|
| ^^^^^^^^^^^^^ method not found in `Engine<HttpTransport<AnthropicScheme>, Locked>`
|
||||||
|
|
|
|
||||||
= note: the method was found for
|
= note: the method was found for
|
||||||
- `Worker<C>`
|
- `Engine<C>`
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use llm_worker::Worker;
|
use llm_engine::Engine;
|
||||||
use llm_worker::llm_client::capability::{
|
use llm_engine::llm_client::capability::{
|
||||||
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,
|
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,
|
||||||
};
|
};
|
||||||
use llm_worker::llm_client::scheme::anthropic::AnthropicScheme;
|
use llm_engine::llm_client::scheme::anthropic::AnthropicScheme;
|
||||||
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth};
|
use llm_engine::llm_client::transport::{HttpTransport, ResolvedAuth};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
@ -21,8 +21,8 @@ fn main() {
|
||||||
ResolvedAuth::None,
|
ResolvedAuth::None,
|
||||||
cap,
|
cap,
|
||||||
);
|
);
|
||||||
let worker = Worker::new(client);
|
let engine = Engine::new(client);
|
||||||
let handle = worker.tool_server_handle();
|
let handle = engine.tool_server_handle();
|
||||||
let def: llm_worker::tool::ToolDefinition = Arc::new(|| panic!("unused"));
|
let def: llm_engine::tool::ToolDefinition = Arc::new(|| panic!("unused"));
|
||||||
let _ = handle.register_tool(def);
|
let _ = handle.register_tool(def);
|
||||||
}
|
}
|
||||||
|
|
@ -6,5 +6,5 @@ error[E0624]: method `register_tool` is private
|
||||||
|
|
|
|
||||||
::: src/tool_server.rs
|
::: src/tool_server.rs
|
||||||
|
|
|
|
||||||
| pub(crate) fn register_tool(&self, factory: WorkerToolDefinition) {
|
| pub(crate) fn register_tool(&self, factory: EngineToolDefinition) {
|
||||||
| ----------------------------------------------------------------- private method defined here
|
| ----------------------------------------------------------------- private method defined here
|
||||||
|
|
@ -6,7 +6,7 @@ license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arc-swap = "1"
|
arc-swap = "1"
|
||||||
llm-worker = { workspace = true }
|
llm-engine = { workspace = true }
|
||||||
mlua = { version = "0.11.4", features = ["lua54", "vendored", "serialize"] }
|
mlua = { version = "0.11.4", features = ["lua54", "vendored", "serialize"] }
|
||||||
protocol = { workspace = true }
|
protocol = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,9 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
// `ModelCapability` は `llm-worker` 側に定義される runtime 構造だが、
|
// `ModelCapability` は `llm-engine` 側に定義される runtime 構造だが、
|
||||||
// マニフェストで任意に override できるよう型だけ再エクスポートする。
|
// マニフェストで任意に override できるよう型だけ再エクスポートする。
|
||||||
pub use llm_worker::llm_client::capability::{ModelCapability, ReasoningControl, ReasoningEffort};
|
pub use llm_engine::llm_client::capability::{ModelCapability, ReasoningControl, ReasoningEffort};
|
||||||
|
|
||||||
/// Pod マニフェストの `[model]` セクション。
|
/// Pod マニフェストの `[model]` セクション。
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,7 @@ async fn permission_denial_style_shutdown_sends_no_tools_call() {
|
||||||
let mut client = McpStdioClient::connect(mock_server("tools-call-forbidden"), tight_limits())
|
let mut client = McpStdioClient::connect(mock_server("tools-call-forbidden"), tight_limits())
|
||||||
.await
|
.await
|
||||||
.expect("connect");
|
.expect("connect");
|
||||||
// This mirrors Worker pre-tool-call denial: the ordinary Tool execution body
|
// This mirrors Engine pre-tool-call denial: the ordinary Tool execution body
|
||||||
// is never entered, so the MCP server sees lifecycle shutdown but no call.
|
// is never entered, so the MCP server sees lifecycle shutdown but no call.
|
||||||
client.shutdown().await.expect("shutdown");
|
client.shutdown().await.expect("shutdown");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user