Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,31 @@ jobs:

- name: Verify PostgreSQL migrations and restart persistence
run: cargo test -p agentic-server-core --test postgres_storage_integration -- --ignored --test-threads=1

dynamo-upstream:
runs-on: ubuntu-latest
timeout-minutes: 20
env:
CARGO_INCREMENTAL: "0"
CARGO_PROFILE_TEST_DEBUG: "0"
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: 1.98.0

- name: Cache Rust dependencies
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
cache-bin: false

- name: Validate recorded cassettes
run: |
python3 -m pip install 'PyYAML==6.0.3'
python3 scripts/validate-cassettes.py

- name: Replay Dynamo upstream cassettes
run: cargo test -p agentic-server-core --test dynamo_cassette_test
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

All notable changes to Agentic API are documented here.

## [Unreleased]

### Added

- Documented running Agentic API in front of NVIDIA Dynamo and recorded Dynamo cassettes for stateful and
function-call flows.

### Testing

- Added Dynamo upstream replay tests, a generic cassette validator (`scripts/validate-cassettes.py`), and a dedicated
CI job for them.

## [0.5.0] - 2026-08-25

### Changed
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ vllm serve Qwen/Qwen3-30B-A3B-FP8 \
--reasoning-parser qwen3 --port 5050
```

Serving through [NVIDIA Dynamo](https://github.com/ai-dynamo/dynamo) instead of a standalone `vllm serve`? Point the
gateway at the Dynamo frontend the same way; see
[Running Agentic API in front of NVIDIA Dynamo](docs/guides/dynamo-upstream.md).

**2. Start Agentic API**, pointing it at the vLLM server (set the `YOU_*` variables to enable built-in web search):

```bash
Expand Down
12 changes: 12 additions & 0 deletions crates/agentic-server-core/tests/cassettes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ turns:
| `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-executed function tool call cassettes (streaming + non-streaming) | NVIDIA Dynamo frontend |

### Text-only (OpenAI)

Expand All @@ -219,6 +220,17 @@ vllm serve Qwen/Qwen3-30B-A3B-FP8 --tool-call-parser hermes --enable-auto-tool-c
VLLM_URL=http://0.0.0.0:5050 MODEL=Qwen/Qwen3-30B-A3B-FP8 bash tests/cassettes/record_tool_call_cassettes.sh
```

### NVIDIA Dynamo (vLLM worker behind the Dynamo frontend)

Dynamo's `/v1/responses` rejects `previous_response_id` with `501`, so the recorder's own turn chaining cannot be
used. The script records turn 1 from a prompt, builds turn 2's input from turn 1's recorded assistant message (the
hydrated item history the gateway sends upstream), records it, and merges both into one cassette. See
[docs/guides/dynamo-upstream.md](../../../../docs/guides/dynamo-upstream.md) for the Dynamo launch commands.

```bash
DYNAMO_URL=http://127.0.0.1:8000 MODEL=openai/gpt-oss-20b bash tests/cassettes/record_dynamo_cassettes.sh
```

### Web search (gateway and OpenAI)

The default records both providers. Use `WEB_SEARCH_RECORD_SET=gateway` or
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
turns:
- filename: t1
request:
body:
input: 'Remember the word APPLE. Just say: OK'
max_output_tokens: 2048
model: openai/gpt-oss-20b
store: true
stream: false
headers:
accept: '*/*'
content-type: application/json
user-agent: python-httpx/0.28.1
method: POST
path: /v1/responses
query_params: {}
response:
body:
background: false
billing: null
completed_at: 1787829072
conversation: null
created_at: 1787829072
error: null
frequency_penalty: 0.0
id: resp_e823dacfde14451baad50ab4915bd39b
incomplete_details: null
instructions: null
max_output_tokens: 2048
max_tool_calls: null
metadata: {}
model: openai/gpt-oss-20b
object: response
output:
- content:
- annotations: []
logprobs: []
text: OK
type: output_text
id: msg_aeb098ffaa8c4c0092ed38e3586e8e24
role: assistant
status: completed
type: message
parallel_tool_calls: true
presence_penalty: 0.0
previous_response_id: null
prompt: null
prompt_cache_key: null
prompt_cache_retention: null
reasoning: null
safety_identifier: null
service_tier: auto
status: completed
store: true
temperature: 1.0
text:
format:
type: text
tool_choice: auto
tools: []
top_logprobs: 0
top_p: 1.0
truncation: disabled
usage:
input_tokens: 77
input_tokens_details:
cached_tokens: 0
output_tokens: 35
output_tokens_details:
reasoning_tokens: 0
total_tokens: 112
headers:
content-type: application/json
status_code: 200
- filename: t2
request:
body:
input:
- content: 'Remember the word APPLE. Just say: OK'
role: user
type: message
- content:
- text: OK
type: output_text
id: msg_aeb098ffaa8c4c0092ed38e3586e8e24
role: assistant
status: completed
type: message
- content: What word did I ask you to remember? Reply with just the word.
role: user
type: message
max_output_tokens: 2048
model: openai/gpt-oss-20b
store: true
stream: false
headers:
accept: '*/*'
content-type: application/json
user-agent: python-httpx/0.28.1
method: POST
path: /v1/responses
query_params: {}
response:
body:
background: false
billing: null
completed_at: 1787829074
conversation: null
created_at: 1787829074
error: null
frequency_penalty: 0.0
id: resp_f24a1d1897c74063bfcbbd6fc91b5667
incomplete_details: null
instructions: null
max_output_tokens: 2048
max_tool_calls: null
metadata: {}
model: openai/gpt-oss-20b
object: response
output:
- content:
- annotations: []
logprobs: []
text: APPLE
type: output_text
id: msg_a52548907c8844198586ba7ce6eef89e
role: assistant
status: completed
type: message
parallel_tool_calls: true
presence_penalty: 0.0
previous_response_id: null
prompt: null
prompt_cache_key: null
prompt_cache_retention: null
reasoning: null
safety_identifier: null
service_tier: auto
status: completed
store: true
temperature: 1.0
text:
format:
type: text
tool_choice: auto
tools: []
top_logprobs: 0
top_p: 1.0
truncation: disabled
usage:
input_tokens: 103
input_tokens_details:
cached_tokens: 64
output_tokens: 35
output_tokens_details:
reasoning_tokens: 0
total_tokens: 138
headers:
content-type: application/json
status_code: 200
Loading