@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 735284ed86
Fork 0

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):