function_call-style JSON response from gpt-4, gpt-3_5, codellama, palm-2-chat-bison

This commit is contained in:
Nicholas Albion
2023-09-23 00:45:23 +10:00
parent 376eaaf4e5
commit 48e57edaba
5 changed files with 96 additions and 53 deletions

View File

@@ -21,9 +21,10 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
run: |

View File

@@ -18,25 +18,33 @@ def add_function_calls_to_request(gpt_data, function_calls: FunctionCallSet | No
if function_calls is None:
return
if gpt_data['model'] == 'gpt-4':
gpt_data['functions'] = function_calls['definitions']
if len(function_calls['definitions']) > 1:
gpt_data['function_call'] = 'auto'
else:
gpt_data['function_call'] = {'name': function_calls['definitions'][0]['name']}
return
model: str = gpt_data['model']
is_llama = 'llama' in model
# if model == 'gpt-4':
# gpt_data['functions'] = function_calls['definitions']
# if len(function_calls['definitions']) > 1:
# gpt_data['function_call'] = 'auto'
# else:
# gpt_data['function_call'] = {'name': function_calls['definitions'][0]['name']}
# return
# prompter = CompletionModelPrompter()
# prompter = InstructModelPrompter()
prompter = LlamaInstructPrompter()
prompter = JsonPrompter(is_llama)
if len(function_calls['definitions']) > 1:
function_call = None
else:
function_call = function_calls['definitions'][0]['name']
role = 'user' if '/' in model else 'system'
# role = 'user'
# role = 'system'
# is_llama = True
gpt_data['messages'].append({
'role': 'user',
'role': role,
'content': prompter.prompt('', function_calls['definitions'], function_call)
})
@@ -54,7 +62,7 @@ def parse_agent_response(response, function_calls: FunctionCallSet | None):
"""
if function_calls:
text = re.sub(r'^```json\n', '', response['text'])
text = re.sub(r'^.*```json\s*', '', response['text'], flags=re.DOTALL)
values = list(json.loads(text.strip('` \n')).values())
if len(values) == 1:
return values[0]
@@ -64,11 +72,12 @@ def parse_agent_response(response, function_calls: FunctionCallSet | None):
return response['text']
class LlamaInstructPrompter:
class JsonPrompter:
"""
A prompter for Llama2 instruct models.
Adapted from local_llm_function_calling
"""
def __init__(self, is_llama: bool = False):
self.is_llama = is_llama
def function_descriptions(
self, functions: list[FunctionType], function_to_call: str
@@ -177,7 +186,9 @@ class LlamaInstructPrompter:
"Help choose the appropriate function to call to answer the user's question."
if function_to_call is None
else f"Define the arguments for {function_to_call} to answer the user's question."
) + "In your response you must only use JSON output and provide no notes or commentary."
# ) + "\nYou must return a JSON object without notes or commentary."
) + " \nIn your response you must only use JSON output and provide no explanation or commentary."
data = (
self.function_data(functions, function_to_call)
if function_to_call
@@ -188,6 +199,8 @@ class LlamaInstructPrompter:
if function_to_call
else "Here's the function the user should call: "
)
return f"[INST] <<SYS>>\n{system}\n\n{data}\n<</SYS>>\n\n{prompt} [/INST]"
# {response_start}"
if self.is_llama:
return f"[INST] <<SYS>>\n{system}\n\n{data}\n<</SYS>>\n\n{prompt} [/INST]"
else:
return f"{system}\n\n{data}\n\n{prompt}"

View File

@@ -264,6 +264,8 @@ def stream_gpt_completion(data, req_type):
return return_result({'function_calls': function_calls}, lines_printed)
json_line = choice['delta']
# TODO: token healing? https://github.com/1rgs/jsonformer-claude
# ...Is this what local_llm_function_calling.constrainer is for?
except json.JSONDecodeError:
logger.error(f'Unable to decode line: {line}')

View File

@@ -1,7 +1,7 @@
from local_llm_function_calling.prompter import CompletionModelPrompter, InstructModelPrompter
from const.function_calls import ARCHITECTURE, DEV_STEPS
from .function_calling import parse_agent_response, LlamaInstructPrompter
from .function_calling import parse_agent_response, JsonPrompter
class TestFunctionCalling:
@@ -140,7 +140,7 @@ Function call: '''
def test_llama_instruct_function_prompter_named():
# Given
prompter = LlamaInstructPrompter()
prompter = JsonPrompter()
# When
prompt = prompter.prompt('Create a web-based chat app', ARCHITECTURE['definitions'], 'process_technologies')

View File

@@ -1,14 +1,14 @@
import builtins
import os
import pytest
from dotenv import load_dotenv
from unittest.mock import patch
from const.function_calls import ARCHITECTURE, DEV_STEPS
from helpers.AgentConvo import AgentConvo
from helpers.Project import Project
from helpers.agents.Architect import Architect
from helpers.agents.Developer import Developer
from utils.function_calling import parse_agent_response
from .llm_connection import create_gpt_chat_completion
from main import get_custom_print
@@ -45,44 +45,72 @@ class TestLlmConnection:
# # assert len(convo.messages) == 2
# assert response == ([{'type': 'command', 'description': 'Run the app'}], 'more_tasks')
def test_chat_completion_Architect(self, monkeypatch):
"""Test the chat completion method."""
# @pytest.fixture(params=[
# {"endpoint": "OPENAI", "model": "gpt-4"},
# {"endpoint": "OPENROUTER", "model": "openai/gpt-3.5-turbo"},
# {"endpoint": "OPENROUTER", "model": "meta-llama/codellama-34b-instruct"},
# {"endpoint": "OPENROUTER", "model": "anthropic/claude-2"},
# {"endpoint": "OPENROUTER", "model": "google/palm-2-codechat-bison"},
# {"endpoint": "OPENROUTER", "model": "google/palm-2-chat-bison"},
# ])
# def params(self, request):
# return request.param
@pytest.mark.slow
@pytest.mark.uses_tokens
@pytest.mark.parametrize("endpoint, model", [
("OPENAI", "gpt-4"), # role: system
("OPENROUTER", "openai/gpt-3.5-turbo"), # role: user
("OPENROUTER", "meta-llama/codellama-34b-instruct"), # rule: user, is_llama
("OPENROUTER", "google/palm-2-chat-bison"), # role: user/system
# See https://github.com/1rgs/jsonformer-claude/blob/main/jsonformer_claude/main.py
# ("OPENROUTER", "anthropic/claude-2"), # role: user, prompt 2
# ("OPENROUTER", "google/palm-2-codechat-bison"), # not working
])
def test_chat_completion_Architect(self, endpoint, model, monkeypatch):
# Given
agent = Architect(project)
convo = AgentConvo(agent)
convo.construct_and_add_message_from_prompt('architecture/technologies.prompt',
{
'name': 'Test App',
'prompt': '''
The project involves the development of a web-based chat application named "Test_App".
In this application, users can send direct messages to each other.
However, it does not include a group chat functionality.
Multimedia messaging, such as the exchange of images and videos, is not a requirement for this application.
No clear instructions were given for the inclusion of user profile customization features like profile
picture and status updates, as well as a feature for chat history. The project must be developed strictly
as a monolithic application, regardless of any other suggested methods.
The project's specifications are subject to the project manager's discretion, implying a need for
solution-oriented decision-making in areas where precise instructions were not provided.''',
'app_type': 'web app',
'user_stories': [
'User will be able to send direct messages to another user.',
'User will receive direct messages from other users.',
'User will view the sent and received messages in a conversation view.',
'User will select a user to send a direct message.',
'User will be able to search for users to send direct messages to.',
'Users can view the online status of other users.',
'User will be able to log into the application using their credentials.',
'User will be able to logout from the Test_App.',
'User will be able to register a new account on Test_App.',
]
})
{
'name': 'Test App',
'prompt': '''
The project involves the development of a web-based chat application named "Test_App".
In this application, users can send direct messages to each other.
However, it does not include a group chat functionality.
Multimedia messaging, such as the exchange of images and videos, is not a requirement for this application.
No clear instructions were given for the inclusion of user profile customization features like profile
picture and status updates, as well as a feature for chat history. The project must be developed strictly
as a monolithic application, regardless of any other suggested methods.
The project's specifications are subject to the project manager's discretion, implying a need for
solution-oriented decision-making in areas where precise instructions were not provided.''',
'app_type': 'web app',
'user_stories': [
'User will be able to send direct messages to another user.',
'User will receive direct messages from other users.',
'User will view the sent and received messages in a conversation view.',
'User will select a user to send a direct message.',
'User will be able to search for users to send direct messages to.',
'Users can view the online status of other users.',
'User will be able to log into the application using their credentials.',
'User will be able to logout from the Test_App.',
'User will be able to register a new account on Test_App.',
]
})
# endpoint = 'OPENROUTER'
# monkeypatch.setattr('utils.llm_connection.endpoint', endpoint)
monkeypatch.setenv('ENDPOINT', endpoint)
monkeypatch.setenv('MODEL_NAME', model)
# monkeypatch.setenv('MODEL_NAME', 'meta-llama/codellama-34b-instruct')
# monkeypatch.setenv('MODEL_NAME', 'openai/gpt-3.5-turbo-16k-0613')
# monkeypatch.setenv('MODEL_NAME', 'anthropic/claude-2') # TODO: remove ```json\n ... ```
# monkeypatch.setenv('MODEL_NAME', 'google/palm-2-codechat-bison') # TODO: not JSON
# monkeypatch.setenv('MODEL_NAME', 'google/palm-2-chat-bison') # TODO: not JSON
messages = convo.messages
function_calls = ARCHITECTURE
endpoint = 'OPENROUTER'
# monkeypatch.setattr('utils.llm_connection.endpoint', endpoint)
monkeypatch.setenv('ENDPOINT', endpoint)
monkeypatch.setenv('MODEL_NAME', 'meta-llama/codellama-34b-instruct')
# with patch('.llm_connection.endpoint', endpoint):
# When
@@ -93,11 +121,10 @@ class TestLlmConnection:
assert convo.messages[1]['content'].startswith('You are working in a software development agency')
assert response is not None
response = convo.postprocess_response(response, function_calls)
response = parse_agent_response(response, function_calls)
# response = response['function_calls']['arguments']['technologies']
assert 'Node.js' in response
def _create_convo(self, agent):
convo = AgentConvo(agent)