""" Test script for function call in chat completion. To run this script, use the following command: MLC_SERVE_MODEL_LIB=dist/gorilla-openfunctions-v1-q4f16_1_MLC/gorilla-openfunctions-v1-q4f16_1-cuda.so MLC_SERVE_MODEL_LIB=${MLC_SERVE_MODEL_LIB} python -m pytest -x tests/python/serve/server/test_server_function_call.py """ # noqa: E501 import json import os from typing import Dict, List, Optional, Tuple # noqa: UP035 import pytest import requests OPENAI_V1_CHAT_COMPLETION_URL = "http://127.0.0.1:8000/v1/chat/completions" def check_openai_nonstream_response( response: Dict, # noqa: UP006 *, model: str, object_str: str, num_choices: int, finish_reason: List[str], # noqa: UP006 completion_tokens: Optional[int] = None, ): print(response) assert response["model"] == model assert response["object"] == object_str choices = response["choices"] assert isinstance(choices, list) assert len(choices) == num_choices for idx, choice in enumerate(choices): assert choice["index"] == idx assert choice["finish_reason"] in finish_reason # text: str message = choice["message"] assert message["role"] == "assistant" if choice["finish_reason"] == "tool_calls": assert message["content"] is None assert isinstance(message["tool_calls"], list) else: assert message["tool_calls"] is None assert message["content"] is not None usage = response["usage"] assert isinstance(usage, dict) assert usage["total_tokens"] == usage["prompt_tokens"] + usage["completion_tokens"] assert usage["prompt_tokens"] > 0 if completion_tokens is not None: assert usage["completion_tokens"] == completion_tokens def check_openai_stream_response( responses: List[Dict], # noqa: UP006 *, model: str, object_str: str, num_choices: int, finish_reason: str, echo_prompt: Optional[str] = None, suffix: Optional[str] = None, stop: Optional[List[str]] = None, # noqa: UP006 require_substr: Optional[List[str]] = None, # noqa: UP006 ): assert len(responses) > 0 finished = [False for _ in range(num_choices)] outputs = ["" for _ in range(num_choices)] for response in responses: assert response["model"] == model assert response["object"] == object_str choices = response["choices"] assert isinstance(choices, list) assert len(choices) == num_choices for idx, choice in enumerate(choices): assert choice["index"] == idx delta = choice["delta"] assert delta["role"] == "assistant" assert isinstance(delta["content"], str) outputs[idx] += delta["content"] if finished[idx]: assert choice["finish_reason"] == finish_reason elif choice["finish_reason"] is not None: assert choice["finish_reason"] == finish_reason finished[idx] = True for output in outputs: if echo_prompt is not None: assert output.startswith(echo_prompt) if suffix is not None: assert output.endswith(suffix) if stop is not None: for stop_str in stop: assert stop_str not in output if require_substr is not None: for substr in require_substr: assert substr in output tools = [ { "type": "function", "function": { "name": "get_current_weather", "description": "Get the current weather in a given location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state, e.g. San Francisco, CA", }, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, }, "required": ["location"], }, }, } ] CHAT_COMPLETION_MESSAGES = [ # messages #0 [ { "role": "user", "content": "What is the current weather in Pittsburgh, PA?", } ], # messages #1 [ { "role": "user", "content": "What is the current weather in Pittsburgh, PA and Tokyo, JP?", } ], # messages #2 [ { "role": "user", "content": "What is the current weather in Pittsburgh, PA in fahrenheit?", } ], ] @pytest.mark.parametrize("stream", [False, True]) @pytest.mark.parametrize("messages", CHAT_COMPLETION_MESSAGES) def test_openai_v1_chat_completion_function_call( served_model: Tuple[str, str], # noqa: UP006 launch_server, stream: bool, messages: List[Dict[str, str]], # noqa: UP006 ): # `served_model` and `launch_server` are pytest fixtures # defined in conftest.py. payload = { "model": served_model[0], "messages": messages, "stream": stream, "tools": tools, } response = requests.post(OPENAI_V1_CHAT_COMPLETION_URL, json=payload, timeout=60) if not stream: check_openai_nonstream_response( response.json(), model=served_model[0], object_str="chat.completion", num_choices=1, finish_reason=["tool_calls", "error"], ) else: responses = [] for chunk in response.iter_lines(chunk_size=512): if not chunk or chunk == b"data: [DONE]": continue responses.append(json.loads(chunk.decode("utf-8")[6:])) check_openai_stream_response( responses, model=served_model[0], object_str="chat.completion.chunk", num_choices=1, finish_reason="tool_calls", ) if __name__ == "__main__": model_lib = os.environ.get("MLC_SERVE_MODEL_LIB") if model_lib is None: raise ValueError( 'Environment variable "MLC_SERVE_MODEL_LIB" not found. ' "Please set it to model lib compiled by MLC LLM " "(e.g., `./dist/gorilla-openfunctions-v1-q4f16_1_MLC/gorilla-openfunctions-v1-q4f16_1-cuda.so`) " # noqa: E501 "which supports function calls." ) MODEL = (os.path.dirname(model_lib), model_lib) for msg in CHAT_COMPLETION_MESSAGES: test_openai_v1_chat_completion_function_call(MODEL, None, stream=False, messages=msg) test_openai_v1_chat_completion_function_call(MODEL, None, stream=True, messages=msg)