Add NVIDIA Dynamo upstream guide, cassettes, and CI replay tests - #212
Merged
Conversation
franciscojavierarceo
requested review from
bbrowning,
jiahuei,
leseb,
maralbahari,
noobHappylife,
qandrew and
tjtanaa
as code owners
August 27, 2026 10:40
There was a problem hiding this comment.
Pull request overview
Adds documented and replay-tested NVIDIA Dynamo support as a stateless inference upstream.
Changes:
- Adds Dynamo setup, verification, and troubleshooting guidance.
- Records streaming and non-streaming stateful/tool-call traffic.
- Adds four replay tests and dedicated CI validation.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
README.md |
Links the Dynamo guide. |
mkdocs.yaml |
Adds guide navigation. |
docs/guides/dynamo-upstream.md |
Documents Dynamo deployment and usage. |
crates/agentic-server-core/tests/dynamo_cassette_test.rs |
Tests rehydration and function calls. |
crates/agentic-server-core/tests/cassettes/record_dynamo_cassettes.sh |
Records Dynamo scenarios. |
crates/agentic-server-core/tests/cassettes/README.md |
Documents cassette recording. |
crates/agentic-server-core/tests/cassettes/dynamo/stateful_turn2_input.json |
Defines hydrated turn-two input. |
crates/agentic-server-core/tests/cassettes/dynamo/dynamo-stateful-openai-gpt-oss-20b-streaming.yaml |
Records streaming stateful traffic. |
crates/agentic-server-core/tests/cassettes/dynamo/dynamo-stateful-openai-gpt-oss-20b-nonstreaming.yaml |
Records non-streaming stateful traffic. |
crates/agentic-server-core/tests/cassettes/dynamo/dynamo-tool-call-auto-openai-gpt-oss-20b-streaming.yaml |
Records streaming function-call traffic. |
crates/agentic-server-core/tests/cassettes/dynamo/dynamo-tool-call-auto-openai-gpt-oss-20b-nonstreaming.yaml |
Records non-streaming function-call traffic. |
CHANGELOG.md |
Notes Dynamo support and tests. |
.github/workflows/rust.yml |
Adds Dynamo cassette CI. |
Suppressed comments (2)
crates/agentic-server-core/tests/dynamo_cassette_test.rs:153
- Use the normative “client-executed function tools” wording here;
TERMINOLOGY.md:16explicitly lists “client-owned tool” as terminology to avoid.
assert_eq!(requests.len(), 1, "client-owned tools take one model call");
docs/guides/dynamo-upstream.md:78
- Update the expected field name to
reasoning_content. Also,TERMINOLOGY.md:317-320calls this exposed plaintext “reasoning text,” distinguishing it from raw chain of thought.
`content` should hold the answer and `reasoning` the chain of thought. If the answer starts with `analysis`, the
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| assert_eq!(requests.len(), 2, "one upstream call per turn"); | ||
| for request in requests { | ||
| assert!( | ||
| request.get("previous_response_id").is_none_or(Value::is_null), |
Comment on lines
+66
to
+68
| assert_eq!( | ||
| request_input_texts(&requests[1]), | ||
| vec![TURN1_PROMPT, "OK", TURN2_PROMPT], |
| run_stateful_two_turn(true).await; | ||
| } | ||
|
|
||
| async fn run_client_function_call(streaming: bool) { |
| | `record_custom_tool_cassettes.sh` | Matching two-turn custom-tool flows (streaming + non-streaming) | gateway and OpenAI reference | | ||
| | `record_mcp_cassettes.sh` | Native MCP counter tool discovery and calls (streaming + non-streaming) | gateway and OpenAI reference | | ||
| | `record_web_search_cassettes.sh` | Matching web-search calls (streaming + non-streaming) | gateway and OpenAI reference | | ||
| | `record_dynamo_cassettes.sh` | Stateful two-turn and client function-call cassettes (streaming + non-streaming) | NVIDIA Dynamo frontend | |
Dynamo's frontend speaks the same /v1/responses surface as vLLM but is stateless: it rejects previous_response_id with 501. The gateway already rehydrates the item history and sends it upstream, so it works in front of Dynamo unchanged. This records that behavior against a live Dynamo 1.4.1 + gpt-oss-20b worker and pins it in CI. - docs/guides/dynamo-upstream.md: install, worker flags (--dyn-* parsers, file discovery, memory sizing), verification, troubleshooting - cassettes/dynamo: stateful two-turn and client-executed function tool recordings (streaming + non-streaming) plus record_dynamo_cassettes.sh, which builds turn 2 from turn 1's recorded assistant message - tests/dynamo_cassette_test.rs: replays the cassettes and asserts the upstream requests carry the exact hydrated item history, never a previous_response_id key - rust.yml: dedicated dynamo-upstream job validating the cassettes and running the replay tests Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BGqhGxWPxyip8XF6DmZ7rc Signed-off-by: Francisco Javier Arceo <arceofrancisco@gmail.com>
Collaborator
Author
|
Addressed the Copilot review:
Commit is squashed and signed off for DCO. |
franciscojavierarceo
force-pushed
the
dynamo-upstream-guide
branch
from
August 27, 2026 10:55
e5bb120 to
a907480
Compare
Replace the Dynamo-specific inline validation in CI with scripts/validate-cassettes.py, which checks every cassette under tests/cassettes: request path/method/body, response status, exactly one of body or sse, JSON-decodable SSE data lines, and a terminal event or [DONE] marker for 2xx streams. Dynamo-specific behavior stays in the Rust replay test. Messages cassettes for Dynamo are tracked in #213. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BGqhGxWPxyip8XF6DmZ7rc Signed-off-by: Francisco Javier Arceo <arceofrancisco@gmail.com>
The guide implied a separate vllm serve was required next to Dynamo. It is not: ai-dynamo[vllm] installs the vLLM engine and dynamo.vllm runs it. Drop the coexistence framing and use port 8000 for the frontend. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BGqhGxWPxyip8XF6DmZ7rc Signed-off-by: Francisco Javier Arceo <arceofrancisco@gmail.com>
- Install with "ai-dynamo[vllm]==1.4.1" instead of --prerelease=allow,
which resolved an unpinned ai-dynamo to a 1.5.0.dev build. Cassettes
re-recorded from a real 1.4.1 install.
- Dynamo /health returns 200 healthy with an empty instances list before
any worker registers, so the gateway's readiness probe does not imply a
loaded model; document /v1/models/{model}/ready (404 until registered).
- Drop the host-specific --gpu-memory-utilization 0.15 from the launch
command and explain sizing; only claim the verified gpt_oss/harmony
parser pair and point at --help for other model families.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGqhGxWPxyip8XF6DmZ7rc
Signed-off-by: Francisco Javier Arceo <arceofrancisco@gmail.com>
Collaborator
Author
|
Follow-up from an external review pass (Codex) — all verified against a live install:
|
maralbahari
approved these changes
Aug 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Agentic API works in front of NVIDIA Dynamo with no code changes. This PR documents how, records real Dynamo traffic as cassettes, and adds CI coverage so the Dynamo upstream stays working without a GPU in CI.
Verified locally against Dynamo 1.4.1 (
dynamo.frontend+dynamo.vllmworker, vLLM 0.26.0) servingopenai/gpt-oss-20bon a GB10.What Dynamo changes vs.
vllm serve/v1/responsesis stateless:previous_response_idreturns501 Validation: previous_response_id is not supported.The gateway never forwards it (it rehydrates the full item history), which is what makes stateful conversations work anyway. The new tests pin that upstream request shape.--dyn-reasoning-parser/--dyn-tool-call-parser; vLLM's--tool-call-parseris rejected and--reasoning-parseris ignored.Changes
docs/guides/dynamo-upstream.md— install, launch flags, verification curls, troubleshooting table; linked from README and mkdocs nav.tests/cassettes/dynamo/— stateful two-turn and function-call cassettes (streaming + non-streaming) recorded from the live Dynamo, plusrecord_dynamo_cassettes.sh(turn 2 is recorded from the hydrated item array since the recorder's own chaining can't be used against a stateless upstream).tests/dynamo_cassette_test.rs— 4 replay tests: outputs match the recordings (OK→APPLE,get_stock_pricewithNVDA), and the upstream requests carry the full history with noprevious_response_id..github/workflows/rust.yml— newdynamo-upstreamjob that validates the cassette files and runs the replay tests (they also run under the existingcargo testjob).CHANGELOG.md— Unreleased entry.Test plan
cargo test -p agentic-server-core --test dynamo_cassette_test— 4 passedcargo fmt --check,cargo clippy --all-targets -- -D warnings, pre-commit hooksprevious_response_idand function tool call succeed; same second request straight to Dynamo returns 501dynamo-upstreamjob green🤖 Generated with Claude Code
https://claude.ai/code/session_01BGqhGxWPxyip8XF6DmZ7rc