Skip to content

feat(rollout-gateway): Add ThreadedGatewayServer. Change integration tests. - #93

Merged
Linbo-Liu merged 1 commit into
awslabs:mainfrom
Linbo-Liu:integrate-rollout-gateway
Jul 29, 2026
Merged

feat(rollout-gateway): Add ThreadedGatewayServer. Change integration tests.#93
Linbo-Liu merged 1 commit into
awslabs:mainfrom
Linbo-Liu:integrate-rollout-gateway

Conversation

@Linbo-Liu

Copy link
Copy Markdown
Contributor

feat(rollout-gateway): Add ThreadedGatewayServer.

Issue #, if available:
A sync trainer blocks its thread waiting for episode results, but the gateway must keep answering the agent's LLM calls the whole time. Served on the trainer's thread, the block would freeze the event loop and deadlock the episode. ThreadedGatewayServer runs gateway.app on a daemon thread with its own long-lived event loop, while the session API (create_session / finish_session / drop_session) stays safe to call from the trainer thread. Async trainers don't need it — they mount gateway.app on their own loop.

Description of changes:
Adds ThreadedGatewayServer (rollout_gateway/server.py), the deployment shape for serving a RolloutGateway from synchronous trainers (slime, verl), and switches the integration tests to exercise the gateway through it.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@Linbo-Liu
Linbo-Liu force-pushed the integrate-rollout-gateway branch from b3dfa88 to 156fde4 Compare July 28, 2026 17:04
@Linbo-Liu
Linbo-Liu force-pushed the integrate-rollout-gateway branch from 156fde4 to 0d4c049 Compare July 28, 2026 21:55
@Linbo-Liu
Linbo-Liu merged commit 05c0194 into awslabs:main Jul 29, 2026
3 checks passed
luyuzhe111 added a commit that referenced this pull request Aug 4, 2026
…emas, auto-detected from the chat template (#97)

* feat(rollout-gateway): schema-based derender via tokenizer.parse_response, auto-detected from the chat template

The default derender's XML tool-call regex silently missed Qwen3's JSON
<tool_call> format: zero matches, ill_formed=False hardcoded, the agent saw a
plain-text turn and stopped — the validated GSM8K run was single-turn/no-tool
without any signal. Fix the default instead of adding config:

- response_schemas.py: response-schema dicts vendored from huggingface/trl
  (Apache-2.0, baseline 7073af94; see NOTICE) for qwen3 (JSON <tool_call>:
  Qwen2.5/Qwen3/Instruct-2507/VL), qwen3_5 (XML <function=>: Qwen3.5/3.6,
  Nemotron-3), llama3, glm4moe, gptoss — plus a sha256(chat_template) ->
  schema table for auto-detection. Byte-exact matching: fine-tunes inherit
  the template and keep resolving; a revised template stops matching loudly
  rather than misparsing silently.
- HfTemplateRenderer: when the tokenizer's template hash matches, derender the
  whole output in one tokenizer.parse_response pass (reasoning + text + tool
  calls, ordered by the schema's anchored regex, so a literal <tool_call>
  inside a think block cannot corrupt the split). Parse failures and
  structurally invalid tool calls degrade in place to raw text with
  ill_formed=True — flagged, never a 500, never partial extraction.
- No implicit tool parser anymore: with no matched schema and no injected
  tool_parser, a tools-bearing parse raises instead of guessing with the
  one-format regex (parsing.parse_tool_uses remains available by explicit
  injection). Injected stage parsers disable detection entirely — explicit
  wins (slime's SGLang detectors path is unchanged).
- Drop the two no-op vLLM parser flags from the GSM8K script: they configure
  vLLM's chat layer, which the token-in/token-out path never touches.
- gateway extra: transformers floor 4.44 -> 5.0 (tokenizer.parse_response and
  the schema engine shipped in 5.0.0); declare jmespath (schema transforms).

The verl integration needs zero changes: gateway_host's bare
HfTemplateRenderer(tokenizer) construction now does the right thing for every
covered model family.

* feat(verl): pass the capture session key to the agent via _rollout.api_key

The agent previously derived its gateway session key from the ACR runtime
session id (context.session_id). Make the trainer supply it explicitly instead:
AgentCoreAgentLoop passes api_key=sid through RolloutClient's _rollout
overrides, and the math agent reads payload['_rollout'].get('api_key') — the
key is trainer-supplied configuration like base_url/model_id, not something
the agent infers from its runtime. This also removes rl_app.py's dead
assignment left by the #91/#93 merge (the payload-derived api_key was
immediately overwritten by context.session_id).

Backward compatible both ways: the loop's sid still doubles as the ACR
runtimeSessionId, so an already-deployed image reading context.session_id
produces the same key; and 'EMPTY' still covers local runs and evaluation
endpoints that ignore the api key.

* refactor(rollout-gateway): request bare token ids from apply_chat_template

Pass return_dict=False instead of accepting the BatchEncoding default and
unwrapping input_ids after the fact. The bundled attention mask is a padding
artifact the training backend builds itself when batching rows (verl's own
apply_chat_template wrapper defaults return_dict=False for the same reason).
The stub tokenizer now mirrors the real API's dict-by-default behavior, so
dropping the kwarg fails tests on behavior, not just the kwargs echo.

* refactor(rollout-gateway): drop the llama3 response schema

Remove LLAMA3_SCHEMA, its registry entry, and the Llama 3.1/3.2 template
hashes: Llama 3.x is not a fine-tuning target worth carrying a vendored
schema for. Also drops the only hub-gated repos from template verification,
so hash checks need no HF token.

* test(rollout-gateway): cut tautological renderer tests, test schemas directly

test_render.py: drop test_tool_parser_ill_formed_propagates (asserted a
hard-coded lambda return, already covered by the stage-sequence test) and
test_recognized_chat_template_switches_to_schema_derender (duplicated the
schema-path coverage and leaked a fake hash into the global registry).

test_response_schemas.py: rewrite without the sentinel-template/hash-
registration machinery — tests assign the schema on the renderer directly.
Down to what only this repo can break: one golden-path test per vendored
schema dict (a transcription error in the hand-copied regexes would parse
wrong silently) and the _parse_with_schema degradation contract (parse
failures degrade to raw text flagged ill_formed, never an exception or a
silently dropped call). Adversarial regex cases are TRL's own test surface;
detection is covered live in test_template_hashes_live.py.

* test(rollout-gateway): verify vendored template hashes against the hub

Detection is byte-exact and the offline unit tests bypass it, so a
mistranscribed registry hash — or a vendor revising a chat template
upstream — would silently drop real models into the no-schema rejection.
Fetch each registered hash's actual template from the hub (a few KB per
repo, never weights; one repo per hash, e.g. both Qwen3.5 think/nothink
variants) and assert it resolves to the expected schema. Always-on: runs
in the default suite and CI.

* style(rollout-gateway): hoist test imports to module top
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.

2 participants