Skip to content

feat(frontend): add the in-process engine contract and HTTP service - #34

Open
xiaoyu-xyz wants to merge 1 commit into
ThinkFlowLab:mainfrom
xiaoyu-xyz:frontend-engine-service
Open

xiaoyu-xyz wants to merge 1 commit into
ThinkFlowLab:mainfrom
xiaoyu-xyz:frontend-engine-service

Conversation

@xiaoyu-xyz

Copy link
Copy Markdown

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 on main today — src/frontend forwards 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) — the Engine trait, Answer/EngineError/Readiness/Report, and engine::app: POST /v1/systemone and GET /health with 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.rs and the forwarding path are untouched, so this lands without a model and without a GPU.

The contract

pub trait Engine: Send + Sync + 'static {
    fn readiness(&self) -> Readiness;
    fn submit(&self, body: Vec<u8>, deadline: Instant) -> Result<Reply, EngineError>;
    fn report(&self) -> Option<Report> { None }
    fn failure(&self) -> Option<String> { None }
}

Two methods to implement; the last two default to None. submit takes 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, so x-queue-depth is measured by the engine rather than guessed by the transport.

Behaviour Answer
Engine not ready 503, and the engine is never asked
Starting / Ready / Failed /health distinguishes them; a failure carries its reason
Body over the limit 413
Budget expired, including during upload 503, before the engine is asked
Engine Busy (no capacity) 503
InvalidRequest / InferenceFailed 400 / 500
Engine never replies 504

Nothing 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:

  1. /health is observable. Add native Laya inference with Rust and CUDA #16's Engine::ready() is a bool, 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 queue depth and a cumulative rejected.
  2. The budget reaches the engine. submit receives 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

  • The thread, the bounded queue, ready transitions and drain. They are the next change: worker::spawn owns an engine, Running::run_until_drained joins it, and main.rs gains mode selection. This PR is the boundary they plug into.
  • A runnable engine. engine::app needs an Engine, and this PR supplies none outside its tests. Until the worker lands, the binary has no in-process path, which is why nothing in main.rs changed.
  • CUDA Graph capture, RoPE, weights, encoder, scorer. The model half.

Test Plan

cargo fmt --all --check
cargo clippy --workspace --locked --all-targets -- -D warnings
cargo test --workspace --locked

tests/engine_service.rs covers the three readiness states and that an unready engine never sees the request, byte-for-byte response bodies, the body limit, queue-full 503, a silent engine cut off by the deadline, and the 400/500 mapping. The deadline test asserts that the deadline rather than the client ends the wait.

Test Result

System1-Omni Version / Commit: 3062243 (main) as base; head e8eec5d.

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_json was not added and Cargo.lock is untouched.

Related: #14 (this is its HTTP integration step), #16 (closed reference implementation), #3, #1.

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 hsliuustc0106 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@twu3202 twu3202 mentioned this pull request Sep 30, 2026
4 tasks done

@Levius-Fubuki Levius-Fubuki left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants