chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
# ruff: noqa
|
||||
import json
|
||||
import random
|
||||
import string
|
||||
|
||||
from vllm import LLM
|
||||
from vllm.sampling_params import SamplingParams
|
||||
|
||||
# This script is an offline demo for function calling
|
||||
#
|
||||
# If you want to run a server/client setup, please follow this code:
|
||||
#
|
||||
# - Server:
|
||||
#
|
||||
# ```bash
|
||||
# vllm serve mistralai/Mistral-7B-Instruct-v0.3 --tokenizer-mode mistral --load-format mistral --config-format mistral
|
||||
# ```
|
||||
#
|
||||
# - Client:
|
||||
#
|
||||
# ```bash
|
||||
# curl --location 'http://<your-node-url>:8000/v1/chat/completions' \
|
||||
# --header 'Content-Type: application/json' \
|
||||
# --header 'Authorization: Bearer token' \
|
||||
# --data '{
|
||||
# "model": "mistralai/Mistral-7B-Instruct-v0.3"
|
||||
# "messages": [
|
||||
# {
|
||||
# "role": "user",
|
||||
# "content": [
|
||||
# {"type" : "text", "text": "Describe this image in detail please."},
|
||||
# {"type": "image_url", "image_url": {"url": "https://s3.amazonaws.com/cms.ipressroom.com/338/files/201808/5b894ee1a138352221103195_A680%7Ejogging-edit/A680%7Ejogging-edit_hero.jpg"}},
|
||||
# {"type" : "text", "text": "and this one as well. Answer in French."},
|
||||
# {"type": "image_url", "image_url": {"url": "https://www.wolframcloud.com/obj/resourcesystem/images/a0e/a0ee3983-46c6-4c92-b85d-059044639928/6af8cfb971db031b.png"}}
|
||||
# ]
|
||||
# }
|
||||
# ]
|
||||
# }'
|
||||
# ```
|
||||
#
|
||||
# Usage:
|
||||
# python demo.py simple
|
||||
# python demo.py advanced
|
||||
|
||||
model_name = "mistralai/Mistral-7B-Instruct-v0.3"
|
||||
# or switch to "mistralai/Mistral-Nemo-Instruct-2407"
|
||||
# or "mistralai/Mistral-Large-Instruct-2407"
|
||||
# or any other mistral model with function calling ability
|
||||
|
||||
sampling_params = SamplingParams(max_tokens=8192, temperature=0.0)
|
||||
llm = LLM(
|
||||
model=model_name,
|
||||
tokenizer_mode="mistral",
|
||||
config_format="mistral",
|
||||
load_format="mistral",
|
||||
)
|
||||
|
||||
|
||||
def generate_random_id(length=9):
|
||||
characters = string.ascii_letters + string.digits
|
||||
random_id = "".join(random.choice(characters) for _ in range(length))
|
||||
return random_id
|
||||
|
||||
|
||||
# simulate an API that can be called
|
||||
def get_current_weather(city: str, state: str, unit: "str"):
|
||||
return (
|
||||
f"The weather in {city}, {state} is 85 degrees {unit}. It is "
|
||||
"partly cloudly, with highs in the 90's."
|
||||
)
|
||||
|
||||
|
||||
tool_functions = {"get_current_weather": get_current_weather}
|
||||
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_current_weather",
|
||||
"description": "Get the current weather in a given location",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": {
|
||||
"type": "string",
|
||||
"description": "The city to find the weather for, e.g. 'San Francisco'",
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": "the two-letter abbreviation for the state that the city is"
|
||||
" in, e.g. 'CA' which would mean 'California'",
|
||||
},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"description": "The unit to fetch the temperature in",
|
||||
"enum": ["celsius", "fahrenheit"],
|
||||
},
|
||||
},
|
||||
"required": ["city", "state", "unit"],
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Can you tell me what the temperate will be in Dallas, in fahrenheit?",
|
||||
}
|
||||
]
|
||||
|
||||
outputs = llm.chat(messages, sampling_params=sampling_params, tools=tools)
|
||||
output = outputs[0].outputs[0].text.strip()
|
||||
|
||||
# append the assistant message
|
||||
messages.append(
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": output,
|
||||
}
|
||||
)
|
||||
|
||||
# let's now actually parse and execute the model's output simulating an API call by using the
|
||||
# above defined function
|
||||
tool_calls = json.loads(output)
|
||||
tool_answers = [
|
||||
tool_functions[call["name"]](**call["arguments"]) for call in tool_calls
|
||||
]
|
||||
|
||||
# append the answer as a tool message and let the LLM give you an answer
|
||||
messages.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"content": "\n\n".join(tool_answers),
|
||||
"tool_call_id": generate_random_id(),
|
||||
}
|
||||
)
|
||||
|
||||
outputs = llm.chat(messages, sampling_params, tools=tools)
|
||||
|
||||
print(outputs[0].outputs[0].text.strip())
|
||||
# yields
|
||||
# 'The weather in Dallas, TX is 85 degrees Fahrenheit. '
|
||||
# 'It is partly cloudly, with highs in the 90's.'
|
||||
@@ -0,0 +1,195 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""
|
||||
Set up this example by starting a vLLM OpenAI-compatible server with tool call
|
||||
options enabled. For example:
|
||||
|
||||
IMPORTANT: for mistral, you must use one of the provided mistral tool call
|
||||
templates, or your own - the model default doesn't work for tool calls with vLLM
|
||||
See the vLLM docs on OpenAI server & tool calling for more details.
|
||||
|
||||
vllm serve mistralai/Mistral-7B-Instruct-v0.3 \
|
||||
--chat-template examples/tool_chat_template_mistral.jinja \
|
||||
--enable-auto-tool-choice --tool-call-parser mistral
|
||||
|
||||
OR
|
||||
vllm serve NousResearch/Hermes-2-Pro-Llama-3-8B \
|
||||
--chat-template examples/tool_chat_template_hermes.jinja \
|
||||
--enable-auto-tool-choice --tool-call-parser hermes
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from openai import OpenAI
|
||||
|
||||
# Modify OpenAI's API key and API base to use vLLM's API server.
|
||||
openai_api_key = "EMPTY"
|
||||
openai_api_base = "http://localhost:8000/v1"
|
||||
|
||||
properties = {
|
||||
"city": {
|
||||
"type": "string",
|
||||
"description": "The city to find the weather for, e.g. 'San Francisco'",
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": "the two-letter abbreviation for the state that the city is"
|
||||
" in, e.g. 'CA' which would mean 'California'",
|
||||
},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"description": "The unit to fetch the temperature in",
|
||||
"enum": ["celsius", "fahrenheit"],
|
||||
},
|
||||
}
|
||||
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_current_weather",
|
||||
"description": "Get the current weather in a given location",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": properties,
|
||||
"required": ["city", "state", "unit"],
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "Hi! How are you doing today?"},
|
||||
{"role": "assistant", "content": "I'm doing well! How can I help you?"},
|
||||
{
|
||||
"role": "user",
|
||||
"content": (
|
||||
"Can you tell me what the temperate will be in Dallas, in fahrenheit?"
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def get_current_weather(city: str, state: str, unit: "str"):
|
||||
return (
|
||||
"The weather in Dallas, Texas is 85 degrees fahrenheit. It is "
|
||||
"partly cloudly, with highs in the 90's."
|
||||
)
|
||||
|
||||
|
||||
def handle_tool_calls_stream(
|
||||
client: OpenAI,
|
||||
messages: list[dict[str, str]],
|
||||
model: str,
|
||||
tools: list[dict[str, Any]],
|
||||
) -> list[Any]:
|
||||
tool_calls_stream = client.chat.completions.create(
|
||||
messages=messages, model=model, tools=tools, stream=True
|
||||
)
|
||||
chunks = []
|
||||
print("chunks: ")
|
||||
for chunk in tool_calls_stream:
|
||||
chunks.append(chunk)
|
||||
if chunk.choices[0].delta.tool_calls:
|
||||
print(chunk.choices[0].delta.tool_calls[0])
|
||||
else:
|
||||
print(chunk.choices[0].delta)
|
||||
return chunks
|
||||
|
||||
|
||||
def handle_tool_calls_arguments(chunks: list[Any]) -> list[str]:
|
||||
arguments = []
|
||||
tool_call_idx = -1
|
||||
print("arguments: ")
|
||||
for chunk in chunks:
|
||||
if chunk.choices[0].delta.tool_calls:
|
||||
tool_call = chunk.choices[0].delta.tool_calls[0]
|
||||
if tool_call.index != tool_call_idx:
|
||||
if tool_call_idx >= 0:
|
||||
print(f"streamed tool call arguments: {arguments[tool_call_idx]}")
|
||||
tool_call_idx = chunk.choices[0].delta.tool_calls[0].index
|
||||
arguments.append("")
|
||||
if tool_call.id:
|
||||
print(f"streamed tool call id: {tool_call.id} ")
|
||||
|
||||
if tool_call.function:
|
||||
if tool_call.function.name:
|
||||
print(f"streamed tool call name: {tool_call.function.name}")
|
||||
|
||||
if tool_call.function.arguments:
|
||||
arguments[tool_call_idx] += tool_call.function.arguments
|
||||
|
||||
return arguments
|
||||
|
||||
|
||||
def main():
|
||||
# Initialize OpenAI client
|
||||
client = OpenAI(
|
||||
# defaults to os.environ.get("OPENAI_API_KEY")
|
||||
api_key=openai_api_key,
|
||||
base_url=openai_api_base,
|
||||
)
|
||||
|
||||
# Get available models and select one
|
||||
models = client.models.list()
|
||||
model = models.data[0].id
|
||||
|
||||
chat_completion = client.chat.completions.create(
|
||||
messages=messages, model=model, tools=tools
|
||||
)
|
||||
|
||||
print("-" * 70)
|
||||
print("Chat completion results:")
|
||||
print(chat_completion)
|
||||
print("-" * 70)
|
||||
|
||||
# Stream tool calls
|
||||
chunks = handle_tool_calls_stream(client, messages, model, tools)
|
||||
print("-" * 70)
|
||||
|
||||
# Handle arguments from streamed tool calls
|
||||
arguments = handle_tool_calls_arguments(chunks)
|
||||
|
||||
if len(arguments):
|
||||
print(f"streamed tool call arguments: {arguments[-1]}\n")
|
||||
|
||||
print("-" * 70)
|
||||
|
||||
# Add tool call results to the conversation
|
||||
messages.append(
|
||||
{
|
||||
"role": "assistant",
|
||||
"tool_calls": chat_completion.choices[0].message.tool_calls,
|
||||
"reasoning": chat_completion.choices[0].message.reasoning,
|
||||
}
|
||||
)
|
||||
|
||||
# Now, simulate a tool call
|
||||
available_tools = {"get_current_weather": get_current_weather}
|
||||
|
||||
completion_tool_calls = chat_completion.choices[0].message.tool_calls
|
||||
for call in completion_tool_calls:
|
||||
tool_to_call = available_tools[call.function.name]
|
||||
args = json.loads(call.function.arguments)
|
||||
result = tool_to_call(**args)
|
||||
print("tool_to_call result: ", result)
|
||||
messages.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"content": result,
|
||||
"tool_call_id": call.id,
|
||||
"name": call.function.name,
|
||||
}
|
||||
)
|
||||
|
||||
chat_completion_2 = client.chat.completions.create(
|
||||
messages=messages, model=model, tools=tools, stream=False
|
||||
)
|
||||
print("Chat completion2 results:")
|
||||
print(chat_completion_2)
|
||||
print("-" * 70)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,130 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""
|
||||
To run this example, you can start the vLLM server
|
||||
without any specific flags:
|
||||
|
||||
```bash
|
||||
vllm serve unsloth/Llama-3.2-1B-Instruct \
|
||||
--structured-outputs-config.backend outlines
|
||||
```
|
||||
|
||||
This example demonstrates how to generate chat completions
|
||||
using the OpenAI Python client library.
|
||||
"""
|
||||
|
||||
from openai import OpenAI
|
||||
|
||||
# Modify OpenAI's API key and API base to use vLLM's API server.
|
||||
openai_api_key = "EMPTY"
|
||||
openai_api_base = "http://localhost:8000/v1"
|
||||
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_current_weather",
|
||||
"description": "Get the current weather in a given location",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": {
|
||||
"type": "string",
|
||||
"description": "The city to find the weather for"
|
||||
", e.g. 'San Francisco'",
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"the two-letter abbreviation for the state that the "
|
||||
"city is in, e.g. 'CA' which would mean 'California'"
|
||||
),
|
||||
},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"description": "The unit to fetch the temperature in",
|
||||
"enum": ["celsius", "fahrenheit"],
|
||||
},
|
||||
},
|
||||
"required": ["city", "state", "unit"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_forecast",
|
||||
"description": "Get the weather forecast for a given location",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"The city to get the forecast for, e.g. 'New York'"
|
||||
),
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"The two-letter abbreviation for the state, e.g. 'NY'"
|
||||
),
|
||||
},
|
||||
"days": {
|
||||
"type": "integer",
|
||||
"description": "Number of days to get the forecast for (1-7)",
|
||||
},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"description": "The unit to fetch the temperature in",
|
||||
"enum": ["celsius", "fahrenheit"],
|
||||
},
|
||||
},
|
||||
"required": ["city", "state", "days", "unit"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "Hi! How are you doing today?"},
|
||||
{"role": "assistant", "content": "I'm doing well! How can I help you?"},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Can you tell me what the current weather is in Dallas \
|
||||
and the forecast for the next 5 days, in fahrenheit?",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
client = OpenAI(
|
||||
# defaults to os.environ.get("OPENAI_API_KEY")
|
||||
api_key=openai_api_key,
|
||||
base_url=openai_api_base,
|
||||
)
|
||||
|
||||
models = client.models.list()
|
||||
model = models.data[0].id
|
||||
|
||||
chat_completion = client.chat.completions.create(
|
||||
messages=messages,
|
||||
model=model,
|
||||
tools=tools,
|
||||
tool_choice="required",
|
||||
stream=True, # Enable streaming response
|
||||
)
|
||||
|
||||
for chunk in chat_completion:
|
||||
if chunk.choices and chunk.choices[0].delta.tool_calls:
|
||||
print(chunk.choices[0].delta.tool_calls)
|
||||
|
||||
chat_completion = client.chat.completions.create(
|
||||
messages=messages, model=model, tools=tools, tool_choice="required"
|
||||
)
|
||||
|
||||
print(chat_completion.choices[0].message.tool_calls)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,245 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# ruff: noqa: E501
|
||||
"""
|
||||
Set up this example by starting a vLLM OpenAI-compatible server with tool call
|
||||
options enabled for xLAM-2 models:
|
||||
|
||||
vllm serve --model Salesforce/Llama-xLAM-2-8b-fc-r --enable-auto-tool-choice --tool-call-parser xlam
|
||||
|
||||
OR
|
||||
|
||||
vllm serve --model Salesforce/xLAM-2-3b-fc-r --enable-auto-tool-choice --tool-call-parser xlam
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
|
||||
from openai import OpenAI
|
||||
|
||||
# Modify OpenAI's API key and API base to use vLLM's API server.
|
||||
openai_api_key = "empty"
|
||||
openai_api_base = "http://localhost:8000/v1"
|
||||
|
||||
|
||||
# Define tool functions
|
||||
def get_weather(location: str, unit: str):
|
||||
return f"Weather in {location} is 22 degrees {unit}."
|
||||
|
||||
|
||||
def calculate_expression(expression: str):
|
||||
try:
|
||||
result = eval(expression)
|
||||
return f"The result of {expression} is {result}"
|
||||
except Exception as e:
|
||||
return f"Could not calculate {expression}: {e}"
|
||||
|
||||
|
||||
def translate_text(text: str, target_language: str):
|
||||
return f"Translation of '{text}' to {target_language}: [translated content]"
|
||||
|
||||
|
||||
# Define tools
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": "Get the current weather in a given location",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "City and state, e.g., 'San Francisco, CA'",
|
||||
},
|
||||
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
|
||||
},
|
||||
"required": ["location", "unit"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "calculate_expression",
|
||||
"description": "Calculate a mathematical expression",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expression": {
|
||||
"type": "string",
|
||||
"description": "Mathematical expression to evaluate, needs to be a valid python expression",
|
||||
}
|
||||
},
|
||||
"required": ["expression"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "translate_text",
|
||||
"description": "Translate text to another language",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {"type": "string", "description": "Text to translate"},
|
||||
"target_language": {
|
||||
"type": "string",
|
||||
"description": "Target language for translation",
|
||||
},
|
||||
},
|
||||
"required": ["text", "target_language"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
# Map of function names to implementations
|
||||
tool_functions = {
|
||||
"get_weather": get_weather,
|
||||
"calculate_expression": calculate_expression,
|
||||
"translate_text": translate_text,
|
||||
}
|
||||
|
||||
|
||||
def process_response(response, tool_functions, original_query):
|
||||
"""Process a non-streaming response with possible tool calls"""
|
||||
|
||||
print("\n--- Response Output ---")
|
||||
|
||||
# Check if the response has content
|
||||
if response.choices[0].message.content:
|
||||
print(f"Content: {response.choices[0].message.content}")
|
||||
|
||||
# Check if the response has tool calls
|
||||
if response.choices[0].message.tool_calls:
|
||||
print("--------------------------------")
|
||||
print(f"Tool calls: {response.choices[0].message.tool_calls}")
|
||||
print("--------------------------------")
|
||||
|
||||
# Collect all tool calls and results before making follow-up request
|
||||
tool_results = []
|
||||
assistant_message = {"role": "assistant"}
|
||||
|
||||
if response.choices[0].message.content:
|
||||
assistant_message["content"] = response.choices[0].message.content
|
||||
|
||||
assistant_tool_calls = []
|
||||
|
||||
# Process each tool call
|
||||
for tool_call in response.choices[0].message.tool_calls:
|
||||
function_name = tool_call.function.name
|
||||
function_args = tool_call.function.arguments
|
||||
function_id = tool_call.id
|
||||
|
||||
print(f"Function called: {function_name}")
|
||||
print(f"Arguments: {function_args}")
|
||||
print(f"Function ID: {function_id}")
|
||||
|
||||
# Execute the function
|
||||
try:
|
||||
# Parse the JSON arguments
|
||||
args = json.loads(function_args)
|
||||
|
||||
# Call the function with the arguments
|
||||
function_result = tool_functions[function_name](**args)
|
||||
print(f"\n--- Function Result ---\n{function_result}\n")
|
||||
|
||||
# Add tool call to assistant message
|
||||
assistant_tool_calls.append(
|
||||
{
|
||||
"id": function_id,
|
||||
"type": "function",
|
||||
"function": {"name": function_name, "arguments": function_args},
|
||||
}
|
||||
)
|
||||
|
||||
# Add tool result to tool_results
|
||||
tool_results.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": function_id,
|
||||
"content": function_result,
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error executing function: {e}")
|
||||
|
||||
# Add tool_calls to assistant message
|
||||
assistant_message["tool_calls"] = assistant_tool_calls
|
||||
|
||||
# Create a follow-up message with all function results
|
||||
follow_up_messages = [
|
||||
{"role": "user", "content": original_query},
|
||||
assistant_message,
|
||||
]
|
||||
|
||||
# Add all tool results to the messages
|
||||
follow_up_messages.extend(tool_results)
|
||||
|
||||
# Get completion with all tool results in a single follow-up
|
||||
follow_up_response = client.chat.completions.create(
|
||||
model=client.models.list().data[0].id,
|
||||
messages=follow_up_messages,
|
||||
stream=False,
|
||||
)
|
||||
|
||||
print("\n--- Follow-up Response ---")
|
||||
print(follow_up_response.choices[0].message.content)
|
||||
print("--- End Follow-up ---\n")
|
||||
|
||||
print("--- End Response ---\n")
|
||||
|
||||
|
||||
def run_test_case(query, test_name):
|
||||
"""Run a single test case with the given query"""
|
||||
print(f"\n{'=' * 50}\nTEST CASE: {test_name}\n{'=' * 50}")
|
||||
print(f"Query: '{query}'")
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# Create non-streaming chat completion request
|
||||
response = client.chat.completions.create(
|
||||
model=client.models.list().data[0].id,
|
||||
messages=[{"role": "user", "content": query}],
|
||||
tools=tools,
|
||||
tool_choice="auto",
|
||||
stream=False,
|
||||
)
|
||||
|
||||
# Process the non-streaming response, passing the original query
|
||||
process_response(response, tool_functions, query)
|
||||
|
||||
end_time = time.time()
|
||||
print(f"Test completed in {end_time - start_time:.2f} seconds")
|
||||
|
||||
|
||||
def main():
|
||||
# Initialize OpenAI client
|
||||
global client
|
||||
client = OpenAI(
|
||||
api_key=openai_api_key,
|
||||
base_url=openai_api_base,
|
||||
)
|
||||
|
||||
# Run test cases
|
||||
test_cases = [
|
||||
("I want to know the weather in San Francisco", "Weather Information"),
|
||||
("Calculate 25 * 17 + 31", "Math Calculation"),
|
||||
("Translate 'Hello world' to Spanish", "Text Translation"),
|
||||
("What is the weather in Tokyo and New York in celsius", "Multiple Tool Usage"),
|
||||
]
|
||||
|
||||
# Execute all test cases
|
||||
for query, test_name in test_cases:
|
||||
run_test_case(query, test_name)
|
||||
time.sleep(1) # Small delay between tests
|
||||
|
||||
print("\nAll tests completed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,273 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# ruff: noqa: E501
|
||||
"""
|
||||
Set up this example by starting a vLLM OpenAI-compatible server with tool call
|
||||
options enabled for xLAM-2 models:
|
||||
|
||||
vllm serve --model Salesforce/Llama-xLAM-2-8b-fc-r --enable-auto-tool-choice --tool-call-parser xlam
|
||||
|
||||
OR
|
||||
|
||||
vllm serve --model Salesforce/xLAM-2-3b-fc-r --enable-auto-tool-choice --tool-call-parser xlam
|
||||
|
||||
This example demonstrates streaming tool calls with xLAM models.
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
|
||||
from openai import OpenAI
|
||||
|
||||
# Modify OpenAI's API key and API base to use vLLM's API server.
|
||||
openai_api_key = "empty"
|
||||
openai_api_base = "http://localhost:8000/v1"
|
||||
|
||||
|
||||
# Define tool functions
|
||||
def get_weather(location: str, unit: str):
|
||||
return f"Weather in {location} is 22 degrees {unit}."
|
||||
|
||||
|
||||
def calculate_expression(expression: str):
|
||||
try:
|
||||
result = eval(expression)
|
||||
return f"The result of {expression} is {result}"
|
||||
except Exception as e:
|
||||
return f"Could not calculate {expression}: {e}"
|
||||
|
||||
|
||||
def translate_text(text: str, target_language: str):
|
||||
return f"Translation of '{text}' to {target_language}: [translated content]"
|
||||
|
||||
|
||||
# Define tools
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": "Get the current weather in a given location",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "City and state, e.g., 'San Francisco, CA'",
|
||||
},
|
||||
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
|
||||
},
|
||||
"required": ["location", "unit"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "calculate_expression",
|
||||
"description": "Calculate a mathematical expression",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expression": {
|
||||
"type": "string",
|
||||
"description": "Mathematical expression to evaluate, needs to be a valid Python expression",
|
||||
}
|
||||
},
|
||||
"required": ["expression"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "translate_text",
|
||||
"description": "Translate text to another language",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {"type": "string", "description": "Text to translate"},
|
||||
"target_language": {
|
||||
"type": "string",
|
||||
"description": "Target language for translation",
|
||||
},
|
||||
},
|
||||
"required": ["text", "target_language"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
# Map of function names to implementations
|
||||
tool_functions = {
|
||||
"get_weather": get_weather,
|
||||
"calculate_expression": calculate_expression,
|
||||
"translate_text": translate_text,
|
||||
}
|
||||
|
||||
|
||||
def process_stream(response, tool_functions, original_query):
|
||||
"""Process a streaming response with possible tool calls"""
|
||||
# Track multiple tool calls
|
||||
tool_calls = {} # Dictionary to store tool calls by ID
|
||||
|
||||
current_id = None
|
||||
|
||||
print("\n--- Stream Output ---")
|
||||
for chunk in response:
|
||||
# Handle tool calls in the stream
|
||||
if chunk.choices[0].delta.tool_calls:
|
||||
for tool_call_chunk in chunk.choices[0].delta.tool_calls:
|
||||
# Get the tool call ID
|
||||
if hasattr(tool_call_chunk, "id") and tool_call_chunk.id:
|
||||
current_id = tool_call_chunk.id
|
||||
if current_id not in tool_calls:
|
||||
tool_calls[current_id] = {
|
||||
"function_name": None,
|
||||
"function_args": "",
|
||||
"function_id": current_id,
|
||||
}
|
||||
|
||||
# Extract function information as it comes in chunks
|
||||
if (
|
||||
hasattr(tool_call_chunk, "function")
|
||||
and current_id
|
||||
and current_id in tool_calls
|
||||
):
|
||||
if (
|
||||
hasattr(tool_call_chunk.function, "name")
|
||||
and tool_call_chunk.function.name
|
||||
):
|
||||
tool_calls[current_id]["function_name"] = (
|
||||
tool_call_chunk.function.name
|
||||
)
|
||||
print(f"Function called: {tool_call_chunk.function.name}")
|
||||
|
||||
if (
|
||||
hasattr(tool_call_chunk.function, "arguments")
|
||||
and tool_call_chunk.function.arguments
|
||||
):
|
||||
tool_calls[current_id]["function_args"] += (
|
||||
tool_call_chunk.function.arguments
|
||||
)
|
||||
print(f"Arguments chunk: {tool_call_chunk.function.arguments}")
|
||||
|
||||
# Handle regular content in the stream
|
||||
elif chunk.choices[0].delta.content:
|
||||
print(chunk.choices[0].delta.content, end="")
|
||||
|
||||
print("\n--- End Stream ---\n")
|
||||
|
||||
# Execute each function call and build messages for follow-up
|
||||
follow_up_messages = [{"role": "user", "content": original_query}]
|
||||
|
||||
for tool_id, tool_data in tool_calls.items():
|
||||
function_name = tool_data["function_name"]
|
||||
function_args = tool_data["function_args"]
|
||||
function_id = tool_data["function_id"]
|
||||
|
||||
if function_name and function_args:
|
||||
try:
|
||||
# Parse the JSON arguments
|
||||
args = json.loads(function_args)
|
||||
|
||||
# Call the function with the arguments
|
||||
function_result = tool_functions[function_name](**args)
|
||||
print(
|
||||
f"\n--- Function Result ({function_name}) ---\n{function_result}\n"
|
||||
)
|
||||
|
||||
# Add the assistant message with tool call
|
||||
follow_up_messages.append(
|
||||
{
|
||||
"role": "assistant",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": function_id,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": function_name,
|
||||
"arguments": function_args,
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
# Add the tool message with function result
|
||||
follow_up_messages.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": function_id,
|
||||
"content": function_result,
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error executing function: {e}")
|
||||
|
||||
# Only send follow-up if we have results to process
|
||||
if len(follow_up_messages) > 1:
|
||||
# Create a follow-up message with all the function results
|
||||
follow_up_response = client.chat.completions.create(
|
||||
model=client.models.list().data[0].id,
|
||||
messages=follow_up_messages,
|
||||
stream=True,
|
||||
)
|
||||
|
||||
print("\n--- Follow-up Response ---")
|
||||
for chunk in follow_up_response:
|
||||
if chunk.choices[0].delta.content:
|
||||
print(chunk.choices[0].delta.content, end="")
|
||||
print("\n--- End Follow-up ---\n")
|
||||
|
||||
|
||||
def run_test_case(query, test_name):
|
||||
"""Run a single test case with the given query"""
|
||||
print(f"\n{'=' * 50}\nTEST CASE: {test_name}\n{'=' * 50}")
|
||||
print(f"Query: '{query}'")
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# Create streaming chat completion request
|
||||
response = client.chat.completions.create(
|
||||
model=client.models.list().data[0].id,
|
||||
messages=[{"role": "user", "content": query}],
|
||||
tools=tools,
|
||||
tool_choice="auto",
|
||||
stream=True,
|
||||
)
|
||||
|
||||
# Process the streaming response
|
||||
process_stream(response, tool_functions, query)
|
||||
|
||||
end_time = time.time()
|
||||
print(f"Test completed in {end_time - start_time:.2f} seconds")
|
||||
|
||||
|
||||
def main():
|
||||
# Initialize OpenAI client
|
||||
global client
|
||||
client = OpenAI(
|
||||
api_key=openai_api_key,
|
||||
base_url=openai_api_base,
|
||||
)
|
||||
|
||||
# Run test cases
|
||||
test_cases = [
|
||||
("I want to know the weather in San Francisco", "Weather Information"),
|
||||
("Calculate 25 * 17 + 31", "Math Calculation"),
|
||||
("Translate 'Hello world' to Spanish", "Text Translation"),
|
||||
("What is the weather in Tokyo and New York in celsius", "Multiple Tool Usage"),
|
||||
]
|
||||
|
||||
# Execute all test cases
|
||||
for query, test_name in test_cases:
|
||||
run_test_case(query, test_name)
|
||||
time.sleep(1) # Small delay between tests
|
||||
|
||||
print("\nAll tests completed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,183 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""
|
||||
Example demonstrating MCP (Model Context Protocol) tools with the Responses API.
|
||||
|
||||
This example shows how to use MCP tools with different allowed_tools configurations:
|
||||
1. No filter (allows all tools from the MCP server)
|
||||
2. Wildcard "*" (explicitly allows all tools)
|
||||
3. Specific tool names (filters to only those tools)
|
||||
|
||||
Set up this example by starting a vLLM OpenAI-compatible server with MCP tools enabled.
|
||||
For example:
|
||||
vllm serve openai/gpt-oss-20b --enforce-eager --tool-server demo
|
||||
|
||||
Environment variables:
|
||||
- VLLM_ENABLE_RESPONSES_API_STORE=1
|
||||
- VLLM_GPT_OSS_SYSTEM_TOOL_MCP_LABELS=code_interpreter,container
|
||||
- VLLM_GPT_OSS_HARMONY_SYSTEM_INSTRUCTIONS=1
|
||||
"""
|
||||
|
||||
from openai import OpenAI
|
||||
|
||||
|
||||
def example_no_filter():
|
||||
"""Example with no allowed_tools filter - allows all tools."""
|
||||
print("=" * 60)
|
||||
print("Example 1: No allowed_tools filter (allows all tools)")
|
||||
print("=" * 60)
|
||||
|
||||
base_url = "http://0.0.0.0:8000/v1"
|
||||
client = OpenAI(base_url=base_url, api_key="empty")
|
||||
model = client.models.list().data[0].id
|
||||
|
||||
response = client.responses.create(
|
||||
model=model,
|
||||
input="Execute this code: print('Hello from Python!')",
|
||||
instructions="Use the Python tool to execute code.",
|
||||
tools=[
|
||||
{
|
||||
"type": "mcp",
|
||||
"server_label": "code_interpreter",
|
||||
"server_url": "http://localhost:8888",
|
||||
# No allowed_tools specified - all tools are available
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
print(f"Status: {response.status}")
|
||||
print(f"Output: {response.output_text}")
|
||||
print()
|
||||
|
||||
|
||||
def example_wildcard():
|
||||
"""Example with allowed_tools=['*'] - explicitly allows all tools."""
|
||||
print("=" * 60)
|
||||
print("Example 2: allowed_tools=['*'] (select all tools)")
|
||||
print("=" * 60)
|
||||
|
||||
base_url = "http://0.0.0.0:8000/v1"
|
||||
client = OpenAI(base_url=base_url, api_key="empty")
|
||||
model = client.models.list().data[0].id
|
||||
|
||||
response = client.responses.create(
|
||||
model=model,
|
||||
input="Execute this code: print('Hello from Python with wildcard!')",
|
||||
instructions="Use the Python tool to execute code.",
|
||||
tools=[
|
||||
{
|
||||
"type": "mcp",
|
||||
"server_label": "code_interpreter",
|
||||
"server_url": "http://localhost:8888",
|
||||
# Using "*" to explicitly allow all tools from this MCP server
|
||||
# This is equivalent to not specifying allowed_tools
|
||||
"allowed_tools": ["*"],
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
print(f"Status: {response.status}")
|
||||
print(f"Output: {response.output_text}")
|
||||
print()
|
||||
|
||||
|
||||
def example_specific_tools():
|
||||
"""Example with specific allowed_tools list - filters available tools.
|
||||
|
||||
Note: This example uses 'web_search_preview' (browser) which has multiple
|
||||
sub-tools: 'search', 'open', 'find'. The code_interpreter (python) doesn't
|
||||
have sub-tools, so filtering doesn't apply there.
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Example 3: allowed_tools=['search'] (filter browser to specific tools)")
|
||||
print("=" * 60)
|
||||
|
||||
base_url = "http://0.0.0.0:8000/v1"
|
||||
client = OpenAI(base_url=base_url, api_key="empty")
|
||||
model = client.models.list().data[0].id
|
||||
|
||||
response = client.responses.create(
|
||||
model=model,
|
||||
input="Search for 'Python programming tutorials'",
|
||||
instructions="Use the browser tool to search.",
|
||||
tools=[
|
||||
{
|
||||
"type": "mcp",
|
||||
"server_label": "web_search_preview",
|
||||
"server_url": "http://localhost:8888",
|
||||
# Browser has tools: 'search', 'open', 'find'
|
||||
# Only allow 'search' - blocks 'open' and 'find'
|
||||
"allowed_tools": ["search"],
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
print(f"Status: {response.status}")
|
||||
print(f"Output: {response.output_text}")
|
||||
print()
|
||||
|
||||
|
||||
def example_object_format():
|
||||
"""Example using object format for allowed_tools with browser tools."""
|
||||
print("=" * 60)
|
||||
print("Example 4: allowed_tools with object format")
|
||||
print("=" * 60)
|
||||
|
||||
base_url = "http://0.0.0.0:8000/v1"
|
||||
client = OpenAI(base_url=base_url, api_key="empty")
|
||||
model = client.models.list().data[0].id
|
||||
|
||||
response = client.responses.create(
|
||||
model=model,
|
||||
input="Search for 'machine learning' and open the first result",
|
||||
instructions="Use the browser tool.",
|
||||
tools=[
|
||||
{
|
||||
"type": "mcp",
|
||||
"server_label": "web_search_preview",
|
||||
"server_url": "http://localhost:8888",
|
||||
# Object format with tool_names field
|
||||
# Can also include read_only and other fields
|
||||
# Browser has tools: 'search', 'open', 'find'
|
||||
"allowed_tools": {
|
||||
"tool_names": [
|
||||
"search",
|
||||
"open",
|
||||
], # Allow search and open, block find
|
||||
"read_only": False,
|
||||
},
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
print(f"Status: {response.status}")
|
||||
print(f"Output: {response.output_text}")
|
||||
print()
|
||||
|
||||
|
||||
def main():
|
||||
"""Run all examples."""
|
||||
print("\n" + "=" * 60)
|
||||
print("MCP Tools with allowed_tools Examples")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
# Run all examples
|
||||
example_no_filter()
|
||||
example_wildcard()
|
||||
example_specific_tools()
|
||||
example_object_format()
|
||||
|
||||
print("=" * 60)
|
||||
print("Summary:")
|
||||
print(" - No filter or '*' → All tools available from server")
|
||||
print(" - Specific list → Only those sub-tools available")
|
||||
print(" - Object format → More control with tool_names field")
|
||||
print("")
|
||||
print("Note: allowed_tools filters SUB-TOOLS within an MCP server:")
|
||||
print(" - code_interpreter (python): No sub-tools to filter")
|
||||
print(" - web_search_preview (browser): Has 'search', 'open', 'find'")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,82 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""
|
||||
Set up this example by starting a vLLM OpenAI-compatible server with tool call
|
||||
options enabled.
|
||||
Reasoning models can be used through the Responses API as seen here
|
||||
https://platform.openai.com/docs/api-reference/responses
|
||||
For example:
|
||||
vllm serve Qwen/Qwen3-1.7B --reasoning-parser qwen3 \
|
||||
--structured-outputs-config.backend xgrammar \
|
||||
--enable-auto-tool-choice --tool-call-parser hermes
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from openai import OpenAI
|
||||
|
||||
|
||||
def get_weather(latitude: float, longitude: float) -> str:
|
||||
"""
|
||||
Mock function to simulate getting weather data.
|
||||
In a real application, this would call an external weather API.
|
||||
"""
|
||||
return f"Current temperature at ({latitude}, {longitude}) is 20°C."
|
||||
|
||||
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"name": "get_weather",
|
||||
"description": "Get current temperature for provided coordinates in celsius.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"latitude": {"type": "number"},
|
||||
"longitude": {"type": "number"},
|
||||
},
|
||||
"required": ["latitude", "longitude"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
"strict": True,
|
||||
}
|
||||
]
|
||||
|
||||
input_messages = [
|
||||
{"role": "user", "content": "What's the weather like in Paris today?"}
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
base_url = "http://0.0.0.0:8000/v1"
|
||||
client = OpenAI(base_url=base_url, api_key="empty")
|
||||
model = client.models.list().data[0].id
|
||||
response = client.responses.create(
|
||||
model=model, input=input_messages, tools=tools, tool_choice="required"
|
||||
)
|
||||
|
||||
for out in response.output:
|
||||
if out.type == "function_call":
|
||||
print("Function call:", out.name, out.arguments)
|
||||
tool_call = out
|
||||
args = json.loads(tool_call.arguments)
|
||||
result = get_weather(args["latitude"], args["longitude"])
|
||||
|
||||
input_messages.append(tool_call) # append model's function call message
|
||||
input_messages.append(
|
||||
{ # append result message
|
||||
"type": "function_call_output",
|
||||
"call_id": tool_call.call_id,
|
||||
"output": str(result),
|
||||
}
|
||||
)
|
||||
response_2 = client.responses.create(
|
||||
model=model,
|
||||
input=input_messages,
|
||||
tools=tools,
|
||||
)
|
||||
print(response_2.output_text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user