-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Function calling vllm #8
Changes from 10 commits
8b60241
b1932fc
ec522e0
3efd44c
ad8cb80
dde9521
400d612
4139701
80f8260
22ec060
ed9f7ef
7ac3801
4da8fc5
595dfc8
96f5e5e
241024f
60dbdb3
1a41188
b22fdda
31a492a
c4ae437
f58668b
bd24e5f
0992305
8572716
a300ca7
919478a
68d19ce
585ae1e
68b35b8
bb575c3
dbb0c2e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
import logging | ||
from copy import copy | ||
from typing import Union | ||
from argparse import Namespace | ||
|
||
|
||
class ToolFunctions: | ||
""" | ||
Represents a tool function with specific attributes. | ||
|
||
Attributes: | ||
description (str): Description of the tool function. | ||
parameters (dict): Parameters required for the tool function. | ||
name (str): Name of the tool function. | ||
tool_type (str): Type of the tool function. | ||
|
||
Methods: | ||
__init__(description: Union[str, None], parameters: Union[dict, None], name: Union[str, None], tool_type: Union[str, None]): | ||
Initializes a ToolFunctions instance with the provided attributes. Raises NotImplementedError if any attribute is None. | ||
|
||
_check_attributes(): | ||
Checks if the required attributes (description, parameters, name, tool_type) are not None. | ||
Raises NotImplementedError if any attribute is None. | ||
|
||
generate_dict() -> dict: | ||
Generates and returns a dictionary representation of the tool function, including its type, name, description, and parameters. | ||
""" | ||
def __init__(self, description:Union[str, None], parameters:Union[dict, None], name:Union[str, None], tool_type:Union[str, None]): | ||
self.description:str = description | ||
self.parameters:dict = parameters | ||
self.name:str = name | ||
self.tool_type:str = tool_type | ||
self._check_attributes() | ||
|
||
def _check_attributes(self): | ||
if not self.description: | ||
raise NotImplementedError("This attributes must be different to None") | ||
if not self.parameters: | ||
raise NotImplementedError("This attributes must be different to None") | ||
if not self.name: | ||
raise NotImplementedError("This attributes must be different to None") | ||
if not self.tool_type: | ||
raise NotImplementedError("This attributes must be different to None") | ||
|
||
def generate_dict(self): | ||
return { | ||
"type": self.tool_type, | ||
"function": { | ||
"name": self.name, | ||
"description": self.description, | ||
"parameters": self.parameters, | ||
|
||
} | ||
} | ||
|
||
|
||
class Weather(ToolFunctions): | ||
""" | ||
Represents a example tool function about the weather. | ||
""" | ||
def __init__(self): | ||
tool_type = "function" | ||
name = "get_current_weather" | ||
description = "Get current weather" | ||
parameters = { | ||
"type": "object", | ||
"properties": { | ||
"location": { | ||
"type": "string", | ||
"description": "The city and state, e.g. San Francisco, CA", | ||
}, | ||
"format": { | ||
"type": "string", | ||
"enum": ["celsius", "fahrenheit"], | ||
"description": "The temperature unit to use. Infer this from the users location.", | ||
}, | ||
}, | ||
"required": ["location", "format"] | ||
} | ||
super().__init__(description=description, parameters=parameters, name=name, tool_type=tool_type) | ||
|
||
|
||
class Music(ToolFunctions): | ||
""" | ||
Represents a example tool function about the music. | ||
""" | ||
def __init__(self): | ||
tool_type = "function" | ||
name = "ask_database" | ||
description = "Use this function to answer user questions about music. Input should be a fully formed SQL query." | ||
parameters = { | ||
"type": "object", | ||
"properties": { | ||
"query": { | ||
"type": "string", | ||
"description": f""" | ||
SQL query extracting info to answer the user's question. | ||
SQL should be written using this database schema: | ||
<schema> | ||
The query should be returned in plain text, not in JSON. | ||
""", | ||
} | ||
}, | ||
"required": ["query"] | ||
} | ||
super().__init__(description=description, parameters=parameters, name=name, tool_type=tool_type) | ||
|
||
|
||
TOOLS_DICT = { | ||
'weather': Weather, | ||
'music': Music | ||
} | ||
TOOLS = [] | ||
|
||
def get_tools(): | ||
return TOOLS_DICT, TOOLS | ||
|
||
def reset_tools_dict_and_tools(): | ||
""" | ||
Resets the global variables TOOLS_DICT and TOOLS with new default values. | ||
|
||
Returns: | ||
tuple: A tuple containing the updated values of TOOLS_DICT and TOOLS. | ||
TOOLS_DICT is a dictionary with keys for different tools and corresponding values, | ||
and TOOLS is an empty list. | ||
""" | ||
global TOOLS_DICT | ||
global TOOLS | ||
TOOLS_DICT = { | ||
'weather': Weather, | ||
'music': Music | ||
} | ||
TOOLS = [] | ||
|
||
|
||
def update_tools(args: Namespace): | ||
""" | ||
Updates the global variables TOOLS_DICT and TOOLS based on the provided arguments. | ||
|
||
Args: | ||
args (Namespace): A Namespace object containing parsed command-line arguments. | ||
|
||
Returns: | ||
tuple: A tuple containing the updated values of TOOLS_DICT and TOOLS. | ||
TOOLS_DICT is updated with instances of selected tools or set to None if no tools are selected. | ||
TOOLS is updated with names of selected tools or set to None if no tools are selected. | ||
""" | ||
global TOOLS_DICT | ||
global TOOLS | ||
if args.tools and args.tool_choice and 'none' not in args.tool_choice: | ||
tools = {} | ||
for t in args.tool_choice: | ||
if TOOLS_DICT.get(t.lower(), None): | ||
tools[t.lower()] = TOOLS_DICT[t.lower()]() | ||
else: | ||
raise KeyError(f"The tool '{t.lower()}' is not available in TOOLS_DICT") | ||
TOOLS_DICT = tools | ||
TOOLS = [t.lower() for t in args.tool_choice] | ||
else: | ||
if args.tools and args.tool_choice is None: | ||
raise ValueError("The argument '--tool-choice' is required when '--tools' is specified") | ||
elif args.tools is None and args.tool_choice: | ||
raise ValueError("The argument '--tools' is required when '--tool-choice' is specified") | ||
TOOLS_DICT = None | ||
TOOLS = None | ||
|
||
|
||
def clean_tools(): | ||
""" | ||
Clears the global variable TOOLS_DICT, removing all entries. | ||
|
||
Returns: | ||
dict: An empty dictionary representing the cleaned TOOLS_DICT after removal of all entries. | ||
""" | ||
global TOOLS_DICT | ||
if TOOLS_DICT: | ||
TOOLS_DICT.clear() | ||
|
||
|
||
def get_tools_prompt() -> dict: | ||
""" | ||
Returns a dictionary containing information about selected tools. | ||
|
||
Returns: | ||
dict or None: A dictionary containing information about selected tools, structured as follows: | ||
- "tools": A list of dictionaries, each representing a tool's generated dictionary. | ||
- "tool_choice": A dictionary containing type and function details of the first tool in the list, | ||
or None if TOOLS is empty. | ||
Returns None if TOOLS is empty. | ||
""" | ||
tools_dict = copy(TOOLS_DICT) | ||
tools = copy(TOOLS) | ||
if tools: | ||
return { | ||
"tools": [tools_dict[t].generate_dict() for t in tools], | ||
"tool_choice": [ | ||
{ | ||
"type": tools_dict[t].tool_type, | ||
"function": {"name":tools_dict[t].name} | ||
} | ||
for t in tools | ||
][0] | ||
} | ||
else: | ||
return None |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,12 +19,16 @@ | |
|
||
import os | ||
import json | ||
from fastapi import Depends | ||
from starlette.requests import Request | ||
from starlette.responses import JSONResponse | ||
from pydantic import BaseModel, Field, conint | ||
from typing import Any, List, Union, Optional | ||
from vllm.entrypoints.openai.protocol import ResponseFormat, CompletionResponse, ChatCompletionResponse | ||
from vllm.entrypoints.openai.protocol import ResponseFormat, CompletionResponse, ChatCompletionResponse, ChatCompletionRequest | ||
|
||
from .utils import NumpyArrayEncoder | ||
from ...function_tools.functions import get_tools_prompt | ||
|
||
|
||
# Load the response examples | ||
directory = os.path.dirname(os.path.abspath(__file__)) | ||
|
@@ -136,4 +140,30 @@ class HappyvllmCompletionResponse(CompletionResponse): | |
|
||
|
||
class HappyvllmChatCompletionResponse(ChatCompletionResponse): | ||
model_config = {"json_schema_extra": {"examples": [response_examples["chat_completion_response"]]}} | ||
model_config = {"json_schema_extra": {"examples": [response_examples["chat_completion_response"]]}} | ||
|
||
|
||
async def update_chat_completion_request(request: Request, data: ChatCompletionRequest): | ||
""" | ||
Updates a ChatCompletionRequest object with additional tools and settings if available. | ||
|
||
Args: | ||
request (Request): The incoming request object. | ||
data (ChatCompletionRequest): The original ChatCompletionRequest object to be updated. | ||
|
||
Returns: | ||
ChatCompletionRequest: The updated ChatCompletionRequest object if tools are present, | ||
otherwise returns the original ChatCompletionRequest object. | ||
""" | ||
tools : Union[dict, None] = get_tools_prompt() | ||
if tools: | ||
data_dict = data.dict() | ||
if data_dict['tools']: | ||
data_dict['tools'].extend(tools["tools"]) | ||
else: | ||
data_dict['tools'] = tools["tools"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is it necessary to re assign a None value to "top_logprobs" variable? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ChatCompletionRequest have top_logprobs(int) and logprobs(bool) optional and a method(pydantic) to check if top_logprobs then logprobs should be True. |
||
if data_dict['top_logprobs'] == 0: | ||
data_dict['top_logprobs'] = None | ||
data_dict['tool_choice'] = tools["tool_choice"] | ||
return ChatCompletionRequest(**data_dict) | ||
return data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, the implementation doesn't match with happy_vllm ideas, so :