feat(frontend): add the in-process engine contract and HTTP service - #34
xiaoyu-xyz wants to merge 1 commit into
Conversation
RFC ThinkFlowLab#14 puts the request lifecycle in src/frontend/ and the model behind "a small engine interface", and #3's acceptance criteria depend on that interface existing. Neither half is on main today. This adds the interface and the HTTP service behind it: the Engine trait, its readiness/answer/error types, and engine::app serving /v1/systemone and /health with a request budget measured from the headers, a body limit, and one status code per failure mode. Nothing parses the decision envelope, so a field this crate has never heard of survives and a model's error text cannot produce invalid JSON. submit takes owned bytes and returns a channel, which is the only shape that works for a worker thread with a queue behind it; the reply carries the queue depth to publish, so x-queue-depth is measured by the engine rather than guessed by the transport. No model, no GPU, and no change to main.rs or the forwarding path: the thread, the bounded queue and the drain that plug into this boundary come next.
hsliuustc0106
left a comment
There was a problem hiding this comment.
Reviewed commit e8eec5d0aecdfb3efca50413a0cd4bbabae05a59.
No actionable findings. All 21 workspace tests passed. Reviewed the Engine boundary, readiness/status mapping, body limit, shared upload/inference deadline, response-byte preservation and error escaping.
Levius-Fubuki
left a comment
There was a problem hiding this comment.
Reviewed e8eec5d0aecdfb3efca50413a0cd4bbabae05a59 and the full Engine/HTTP-service diff.
No actionable findings. Reviewed readiness/failure reporting, request-body and shared deadline handling, status mapping, response-byte preservation, and JSON escaping. On Linux with Rust 1.98.1, formatting, strict all-target Clippy, release build, and all 21 workspace tests passed.
The branch still conflicts with current main, so this is a COMMENT rather than merge approval. Please resolve the conflicts and rerun the checks on the rebased head; this review does not certify conflict-resolution changes that do not exist yet. The separate lifecycle implementation in #35 is reviewed separately; this PR does not itself provide a production model engine.
Purpose
RFC #14 puts the request lifecycle in
src/frontend/and the model behind "a small engine interface", and #3's acceptance criteria depend on that interface existing: persistent serving, readiness that follows loading and warmup, and the first inference request after readiness succeeding. Neither half is onmaintoday —src/frontendforwards to an HTTP worker and owns no model state.This PR adds the interface and the HTTP service behind it, and nothing about how a model is loaded or where it runs:
src/frontend/src/engine.rs(287 lines) — theEnginetrait,Answer/EngineError/Readiness/Report, andengine::app:POST /v1/systemoneandGET /healthwith the request budget, the body limit and the status-code mapping.src/frontend/tests/engine_service.rs(317 lines) — eight tests over real sockets, plus a fake engine in the test file.Nothing else in the crate changes:
main.rsand the forwarding path are untouched, so this lands without a model and without a GPU.The contract
Two methods to implement; the last two default to
None.submittakes owned bytes and hands back a channel, because that is the only shape that works for a worker thread with a queue behind it — a borrowed body cannot outlive the handler, and an engine that answers inline cannot report back-pressure. The reply carries the body and the queue depth to publish with it, sox-queue-depthis measured by the engine rather than guessed by the transport.503, and the engine is never askedStarting/Ready/Failed/healthdistinguishes them; a failure carries its reason413503, before the engine is askedBusy(no capacity)503InvalidRequest/InferenceFailed400/500504Nothing parses the decision envelope. Request bytes go in and response bytes come out, so a field this crate has never heard of survives and a model's error text cannot produce invalid JSON — the tests assert exact response text for that reason, including an error message containing a quote and a newline.
Relationship to #16
#16 prototyped an in-process path and was closed. This is written against the RFC text rather than derived from it, and two things differ by design:
/healthis observable. Add native Laya inference with Rust and CUDA #16'sEngine::ready()is abool, so an operator cannot tell a full queue from a dead engine. Here readiness has three states, a failure carries its reason without needing the server log, and the engine can report queuedepthand a cumulativerejected.submitreceives the deadline, so an engine can refuse work that can no longer reach its client instead of starting GPU work nobody will read. Add native Laya inference with Rust and CUDA #16 checks this inside its model-side serve loop, which puts a transport concern in model code.Not in this PR
worker::spawnowns an engine,Running::run_until_drainedjoins it, andmain.rsgains mode selection. This PR is the boundary they plug into.engine::appneeds anEngine, and this PR supplies none outside its tests. Until the worker lands, the binary has no in-process path, which is why nothing inmain.rschanged.Test Plan
cargo fmt --all --check cargo clippy --workspace --locked --all-targets -- -D warnings cargo test --workspace --lockedtests/engine_service.rscovers the three readiness states and that an unready engine never sees the request, byte-for-byte response bodies, the body limit, queue-full503, a silent engine cut off by the deadline, and the400/500mapping. The deadline test asserts that the deadline rather than the client ends the wait.Test Result
System1-Omni Version / Commit:
3062243(main) as base; heade8eec5d.cargo fmt --all --check: clean.cargo clippy --workspace --locked --all-targets -- -D warnings: clean.cargo test --workspace --locked: 21 passed, 0 failed — the 9 existing forwarding tests are unchanged.No new dependency: the tests assert response text, so
serde_jsonwas not added andCargo.lockis untouched.Related: #14 (this is its HTTP integration step), #16 (closed reference implementation), #3, #1.