1414import asyncio
1515import base64
1616import logging
17- from typing import Any , cast
17+ from typing import Any , Literal , cast
1818
1919import numpy as np
2020from openai import AsyncOpenAI , BadRequestError
2828 ToolSpec ,
2929)
3030
31+ RendererTransport = Literal ["vllm" , "dynamo" ]
32+
3133_request_logger = logging .getLogger ("renderers.client" )
3234
3335
@@ -58,12 +60,13 @@ async def generate(
5860 cache_salt : str | None = None ,
5961 priority : int | None = None ,
6062 extra_headers : dict [str , str ] | None = None ,
63+ transport : RendererTransport = "vllm" ,
6164) -> dict [str , Any ]:
62- """Tokenize messages, call vLLM /inference/v1/generate , parse the response.
65+ """Tokenize messages, call the selected token-in backend , parse response.
6366
64- ``sampling_params`` is forwarded to vLLM verbatim. Two fields are always
65- set by us and override caller values: ``stop_token_ids`` (from the
66- renderer) and ``logprobs=1`` (we always emit completion_logprobs). Pass
67+ ``sampling_params`` is forwarded to the selected token-in backend. Two
68+ fields are always set by us and override caller values: stop token IDs
69+ from the renderer and ``logprobs=1`` (we always emit completion_logprobs). Pass
6770 ``prompt_ids`` to skip rendering and use a prebuilt token sequence —
6871 pair it with ``multi_modal_data`` when the prebuilt prompt has image /
6972 video placeholders that need engine-side mm payload.
@@ -101,31 +104,65 @@ def _prepare():
101104 sp ["logprobs" ] = 1
102105 sp .setdefault ("skip_special_tokens" , False )
103106
104- body : dict [str , Any ] = {
105- "model" : model ,
106- "token_ids" : prompt_ids ,
107- "sampling_params" : sp ,
108- }
109- features = (
110- _build_mm_features (renderer , mm_data )
111- if mm_data and not mm_data .is_empty ()
112- else None
113- )
114- if features is not None :
115- body ["features" ] = features
116- if cache_salt is not None :
117- body ["cache_salt" ] = cache_salt
118- if priority is not None :
119- body ["priority" ] = priority
120-
121- # /inference/v1/generate is mounted at the server root, not under /v1
122- # like the OpenAI-compatible endpoints. Build an absolute URL so the
123- # AsyncOpenAI client doesn't prepend its automatic /v1.
124- base = str (client .base_url ).rstrip ("/" ).removesuffix ("/v1" )
125- endpoint = f"{ base } /inference/v1/generate"
107+ if transport == "vllm" :
108+ body : dict [str , Any ] = {
109+ "model" : model ,
110+ "token_ids" : prompt_ids ,
111+ "sampling_params" : sp ,
112+ }
113+ features = (
114+ _build_mm_features (renderer , mm_data )
115+ if mm_data and not mm_data .is_empty ()
116+ else None
117+ )
118+ if features is not None :
119+ body ["features" ] = features
120+ if cache_salt is not None :
121+ body ["cache_salt" ] = cache_salt
122+ if priority is not None :
123+ body ["priority" ] = priority
124+
125+ # /inference/v1/generate is mounted at the server root, not under /v1
126+ # like the OpenAI-compatible endpoints. Build an absolute URL so the
127+ # AsyncOpenAI client doesn't prepend its automatic /v1.
128+ base = str (client .base_url ).rstrip ("/" ).removesuffix ("/v1" )
129+ endpoint = f"{ base } /inference/v1/generate"
130+ elif transport == "dynamo" :
131+ nvext : dict [str , Any ] = {
132+ "token_data" : prompt_ids ,
133+ "extra_fields" : ["completion_token_ids" ],
134+ }
135+ if priority is not None :
136+ nvext ["agent_hints" ] = {"priority" : priority }
137+
138+ body = {
139+ "model" : model ,
140+ "messages" : [{"role" : "user" , "content" : "(token-in mode)" }],
141+ "stream" : False ,
142+ "logprobs" : True ,
143+ "stop" : stop_token_ids ,
144+ "nvext" : nvext ,
145+ }
146+ if cache_salt is not None :
147+ body ["cache_salt" ] = cache_salt
148+
149+ passthrough = dict (sp )
150+ passthrough .pop ("stop_token_ids" , None )
151+ passthrough .pop ("stop" , None )
152+ passthrough .pop ("logprobs" , None )
153+ passthrough .pop ("skip_special_tokens" , None )
154+ max_tokens = passthrough .pop ("max_tokens" , None )
155+ if max_tokens is not None :
156+ body ["max_completion_tokens" ] = max_tokens
157+ body .update ({k : v for k , v in passthrough .items () if v is not None })
158+ endpoint = "/chat/completions"
159+ else :
160+ raise ValueError (f"Unsupported renderer transport: { transport } " )
161+
126162 _request_logger .debug (
127- "POST %s prompt_len=%d max_tokens=%s" ,
163+ "POST %s transport=%s prompt_len=%d max_tokens=%s" ,
128164 endpoint ,
165+ transport ,
129166 len (prompt_ids ),
130167 sp .get ("max_tokens" ),
131168 )
@@ -147,7 +184,23 @@ def _prepare():
147184 raise
148185
149186 choice = (data .get ("choices" ) or [{}])[0 ]
150- completion_ids = choice .get ("token_ids" ) or []
187+ if transport == "dynamo" :
188+ completion_ids = (
189+ choice .get ("token_ids" )
190+ or choice .get ("nvext" , {}).get ("completion_token_ids" )
191+ or data .get ("nvext" , {}).get ("completion_token_ids" )
192+ or []
193+ )
194+ raw_re = (
195+ choice .get ("routed_experts" )
196+ or choice .get ("nvext" , {}).get ("routed_experts" )
197+ or data .get ("nvext" , {}).get ("routed_experts" )
198+ )
199+ request_id = data .get ("id" ) or data .get ("request_id" ) or ""
200+ else :
201+ completion_ids = choice .get ("token_ids" ) or []
202+ raw_re = choice .get ("routed_experts" )
203+ request_id = data .get ("request_id" ) or ""
151204
152205 parsed = await _maybe_offload (
153206 renderer , lambda : renderer .parse_response (completion_ids , tools = tools )
@@ -159,7 +212,6 @@ def _prepare():
159212 completion_logprobs = [float (c .get ("logprob" ) or 0.0 ) for c in content_lp or []]
160213
161214 routed_experts = None
162- raw_re = choice .get ("routed_experts" )
163215 if isinstance (raw_re , dict ) and "data" in raw_re and "shape" in raw_re :
164216 routed_experts = (
165217 np .frombuffer (base64 .b85decode (raw_re ["data" ]), dtype = np .int32 )
@@ -183,7 +235,7 @@ def _prepare():
183235 finish_reason = "tool_calls"
184236
185237 return {
186- "request_id" : data . get ( " request_id" ) or "" ,
238+ "request_id" : request_id ,
187239 "prompt_ids" : list (prompt_ids ),
188240 "completion_ids" : list (completion_ids ),
189241 "completion_logprobs" : completion_logprobs ,
0 commit comments