A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 11, 21:15 UTC
[responsesAPI][7] Browser, Container MCP tools for non harmony models (#29989)
Signed-off-by: Andrew Xia <axia@meta.com> Signed-off-by: Andrew Xia <axia@fb.com> Co-authored-by: Andrew Xia <axia@fb.com> Co-authored-by: Cyrus Leung <tlleungac@connect.ust.hk>
Andrew Xia committed
Dec 7, 2025 at 18:04 UTC
735284ed865e0c863cbd084527f0ae77a560fac1
1 file changed
+83
-6
vllm/entrypoints/context.py
+83
-6
@@ -278,12 +278,14 @@ class ParsableContext(ConversationContext):
278
def need_builtin_tool_call(self) -> bool:
279
"""Return true if the last message is a MCP tool call"""
280
last_message = self.parser.response_messages[-1]
281
- # TODO: figure out which tools are MCP tools
282
- if ( # noqa: SIM103
283
- last_message.type == "function_call"
284
- and last_message.name in ("code_interpreter", "python")
285
- ):
286
- return True
281
+ # TODO(qandrew): figure out which tools are MCP tools
282
+ if last_message.type == "function_call": # noqa: SIM102
283
+ if last_message.name in (
284
+ "code_interpreter",
285
+ "python",
286
+ "web_search_preview",
287
+ ) or last_message.name.startswith("container"):
288
+ return True
289
290
return False
291
@@ -310,12 +312,87 @@ class ParsableContext(ConversationContext):
312
313
return [message]
314
315
+ async def call_search_tool(
316
+ self, tool_session: Union["ClientSession", Tool], last_msg: FunctionCall
317
+ ) -> list[ResponseInputOutputItem]:
318
+ self.called_tools.add("browser")
319
+ if isinstance(tool_session, Tool):
320
+ return await tool_session.get_result_parsable_context(self)
321
+ if envs.VLLM_TOOL_JSON_ERROR_AUTOMATIC_RETRY:
322
+ try:
323
+ args = json.loads(last_msg.arguments)
324
+ except json.JSONDecodeError as e:
325
+ return _create_json_parse_error_messages(last_msg, e)
326
+ else:
327
+ args = json.loads(last_msg.arguments)
328
+ result = await tool_session.call_tool("search", args)
329
+ result_str = result.content[0].text
330
+
331
+ message = ResponseFunctionToolCallOutputItem(
332
+ id=f"fco_{random_uuid()}",
333
+ type="function_call_output",
334
+ call_id=f"call_{random_uuid()}",
335
+ output=result_str,
336
+ status="completed",
337
+ )
338
+
339
+ return [message]
340
+
341
+ async def call_container_tool(
342
+ self, tool_session: Union["ClientSession", Tool], last_msg: Message
343
+ ) -> list[Message]:
344
+ """
345
+ Call container tool. Expect this to be run in a stateful docker
346
+ with command line terminal.
347
+ The official container tool would at least
348
+ expect the following format:
349
+ - for tool name: exec
350
+ - args:
351
+ {
352
+ "cmd":List[str] "command to execute",
353
+ "workdir":optional[str] "current working directory",
354
+ "env":optional[object/dict] "environment variables",
355
+ "session_name":optional[str] "session name",
356
+ "timeout":optional[int] "timeout in seconds",
357
+ "user":optional[str] "user name",
358
+ }
359
+ """
360
+ self.called_tools.add("container")
361
+ if isinstance(tool_session, Tool):
362
+ return await tool_session.get_result_parsable_context(self)
363
+ # tool_name = last_msg.recipient.split(".")[1].split(" ")[0]
364
+ if envs.VLLM_TOOL_JSON_ERROR_AUTOMATIC_RETRY:
365
+ try:
366
+ args = json.loads(last_msg.arguments)
367
+ except json.JSONDecodeError as e:
368
+ return _create_json_parse_error_messages(last_msg, e)
369
+ else:
370
+ args = json.loads(last_msg.arguments)
371
+ result = await tool_session.call_tool("exec", args)
372
+ result_str = result.content[0].text
373
+
374
+ message = ResponseFunctionToolCallOutputItem(
375
+ id=f"fco_{random_uuid()}",
376
+ type="function_call_output",
377
+ call_id=f"call_{random_uuid()}",
378
+ output=result_str,
379
+ status="completed",
380
+ )
381
+
382
+ return [message]
383
+
384
async def call_tool(self) -> list[ResponseInputOutputItem]:
385
if not self.parser.response_messages:
386
return []
387
last_msg = self.parser.response_messages[-1]
388
if last_msg.name == "code_interpreter":
389
return await self.call_python_tool(self._tool_sessions["python"], last_msg)
390
+ elif last_msg.name == "web_search_preview":
391
+ return await self.call_search_tool(self._tool_sessions["browser"], last_msg)
392
+ elif last_msg.name.startswith("container"):
393
+ return await self.call_container_tool(
394
+ self._tool_sessions["container"], last_msg
395
+ )
396
return []
397
398
def render_for_completion(self):