Skip to content

Commit 7241381

Browse files
Vapi Taskerclaude
andcommitted
feat: Add OpenAI spec compliance with developer role and type definitions
- Add MessageRole enum with all valid roles including 'developer' role (required for GPT-5.x and o-series models) - Add OpenAIModel enum with latest model identifiers (GPT-5.2 series, o-series) - Add Message class for structured message creation with validation - Add supports_developer_role() utility for model capability detection - Add deprecation warning for 'function' role (recommend 'tool' instead) - Enhance add_message() with role validation, name, and tool_call_id params - Add say() and end_call() convenience methods - Add comprehensive type hints and docstrings throughout - Add full test coverage for all new functionality - Update Python version support to 3.8+ and add 3.12 support Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ba16f2b commit 7241381

File tree

9 files changed

+1195
-34
lines changed

9 files changed

+1195
-34
lines changed

pytest.ini

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[pytest]
2+
testpaths = tests
3+
python_files = test_*.py
4+
python_classes = Test*
5+
python_functions = test_*
6+
addopts = -v --tb=short
7+
filterwarnings =
8+
ignore::DeprecationWarning:vapi_python.*

requirements_dev.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ coverage==4.5.4
88
Sphinx==1.8.5
99
twine==1.14.0
1010
Click==7.1.2
11-
12-
11+
pytest>=7.0.0
12+
pytest-cov>=4.0.0

setup.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,23 @@ def read_requirements(file):
1717

1818

1919
requirements = read_requirements('requirements.txt')
20-
test_requirements = read_requirements('requirements.txt')
20+
test_requirements = ['pytest>=7.0.0', 'pytest-cov>=4.0.0']
2121

2222
setup(
2323
author="Vapi AI",
2424
author_email='team@vapi.ai',
25-
python_requires='>=3.6',
25+
python_requires='>=3.8',
2626
classifiers=[
27-
'Development Status :: 2 - Pre-Alpha',
27+
'Development Status :: 3 - Alpha',
2828
'Intended Audience :: Developers',
2929
'License :: OSI Approved :: MIT License',
3030
'Natural Language :: English',
3131
'Programming Language :: Python :: 3',
32-
'Programming Language :: Python :: 3.6',
33-
'Programming Language :: Python :: 3.7',
3432
'Programming Language :: Python :: 3.8',
33+
'Programming Language :: Python :: 3.9',
34+
'Programming Language :: Python :: 3.10',
35+
'Programming Language :: Python :: 3.11',
36+
'Programming Language :: Python :: 3.12',
3537
],
3638
description="This package lets you start Vapi calls directly from Python.",
3739
entry_points={
@@ -43,12 +45,12 @@ def read_requirements(file):
4345
license="MIT license",
4446
long_description=readme + '\n\n' + history,
4547
include_package_data=True,
46-
keywords='vapi_python',
48+
keywords='vapi_python vapi voice ai assistant',
4749
name='vapi_python',
4850
packages=find_packages(include=['vapi_python', 'vapi_python.*']),
4951
test_suite='tests',
5052
tests_require=test_requirements,
51-
url='https://github.com/jordan.cde/vapi_python',
52-
version='0.1.9',
53+
url='https://github.com/VapiAI/client-sdk-python',
54+
version='0.2.0',
5355
zip_safe=False,
5456
)

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Tests for Vapi Python SDK."""

tests/test_types.py

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
"""
2+
Tests for Vapi Python SDK type definitions.
3+
4+
This module tests the MessageRole, Message, and related type utilities
5+
for OpenAI API specification compliance.
6+
"""
7+
8+
import pytest
9+
import warnings
10+
from vapi_python.types import (
11+
MessageRole,
12+
Message,
13+
MessageType,
14+
OpenAIModel,
15+
VALID_MESSAGE_ROLES,
16+
DEVELOPER_ROLE_MODELS,
17+
validate_role,
18+
supports_developer_role,
19+
)
20+
21+
22+
class TestMessageRole:
23+
"""Tests for the MessageRole enum."""
24+
25+
def test_all_roles_defined(self):
26+
"""Verify all expected roles are defined."""
27+
expected_roles = {'system', 'user', 'assistant', 'developer', 'tool', 'function'}
28+
actual_roles = {role.value for role in MessageRole}
29+
assert actual_roles == expected_roles
30+
31+
def test_role_values(self):
32+
"""Verify role string values."""
33+
assert MessageRole.SYSTEM.value == "system"
34+
assert MessageRole.USER.value == "user"
35+
assert MessageRole.ASSISTANT.value == "assistant"
36+
assert MessageRole.DEVELOPER.value == "developer"
37+
assert MessageRole.TOOL.value == "tool"
38+
assert MessageRole.FUNCTION.value == "function"
39+
40+
def test_developer_role_exists(self):
41+
"""Test that developer role is available (OpenAI spec compliance)."""
42+
assert hasattr(MessageRole, 'DEVELOPER')
43+
assert MessageRole.DEVELOPER.value == "developer"
44+
45+
46+
class TestValidateRole:
47+
"""Tests for the validate_role function."""
48+
49+
def test_valid_roles(self):
50+
"""Test validation of all valid roles."""
51+
for role in ['system', 'user', 'assistant', 'developer', 'tool', 'function']:
52+
result = validate_role(role)
53+
assert result == role
54+
55+
def test_case_insensitive(self):
56+
"""Test that role validation is case-insensitive."""
57+
assert validate_role('USER') == 'user'
58+
assert validate_role('Developer') == 'developer'
59+
assert validate_role('SYSTEM') == 'system'
60+
61+
def test_invalid_role_raises(self):
62+
"""Test that invalid roles raise ValueError."""
63+
with pytest.raises(ValueError) as exc_info:
64+
validate_role('invalid_role')
65+
assert 'Invalid role' in str(exc_info.value)
66+
67+
def test_function_role_deprecation_warning(self):
68+
"""Test that 'function' role emits deprecation warning."""
69+
with warnings.catch_warnings(record=True) as w:
70+
warnings.simplefilter("always")
71+
result = validate_role('function')
72+
assert result == 'function'
73+
assert len(w) == 1
74+
assert issubclass(w[0].category, DeprecationWarning)
75+
assert 'deprecated' in str(w[0].message).lower()
76+
assert 'tool' in str(w[0].message).lower()
77+
78+
def test_developer_role_no_warning(self):
79+
"""Test that 'developer' role does not emit warnings."""
80+
with warnings.catch_warnings(record=True) as w:
81+
warnings.simplefilter("always")
82+
result = validate_role('developer')
83+
assert result == 'developer'
84+
# Filter out any unrelated warnings
85+
deprecation_warnings = [
86+
warning for warning in w
87+
if issubclass(warning.category, DeprecationWarning)
88+
]
89+
assert len(deprecation_warnings) == 0
90+
91+
92+
class TestMessage:
93+
"""Tests for the Message class."""
94+
95+
def test_basic_message(self):
96+
"""Test creating a basic message."""
97+
msg = Message(role='user', content='Hello!')
98+
assert msg.role == 'user'
99+
assert msg.content == 'Hello!'
100+
assert msg.name is None
101+
assert msg.tool_call_id is None
102+
103+
def test_message_with_enum_role(self):
104+
"""Test creating a message with MessageRole enum."""
105+
msg = Message(role=MessageRole.DEVELOPER, content='Be concise.')
106+
assert msg.role == 'developer'
107+
assert msg.content == 'Be concise.'
108+
109+
def test_developer_role_message(self):
110+
"""Test creating a message with developer role."""
111+
msg = Message(role='developer', content='Follow these instructions.')
112+
assert msg.role == 'developer'
113+
assert msg.content == 'Follow these instructions.'
114+
115+
def test_tool_message_requires_tool_call_id(self):
116+
"""Test that tool messages require tool_call_id."""
117+
with pytest.raises(ValueError) as exc_info:
118+
Message(role='tool', content='result')
119+
assert 'tool_call_id' in str(exc_info.value)
120+
121+
def test_tool_message_with_tool_call_id(self):
122+
"""Test creating a valid tool message."""
123+
msg = Message(
124+
role='tool',
125+
content='{"result": "success"}',
126+
tool_call_id='call_123'
127+
)
128+
assert msg.role == 'tool'
129+
assert msg.tool_call_id == 'call_123'
130+
131+
def test_to_dict_basic(self):
132+
"""Test to_dict for a basic message."""
133+
msg = Message(role='user', content='Test')
134+
result = msg.to_dict()
135+
assert result == {'role': 'user', 'content': 'Test'}
136+
137+
def test_to_dict_with_all_fields(self):
138+
"""Test to_dict with all optional fields."""
139+
msg = Message(
140+
role='tool',
141+
content='result',
142+
name='get_weather',
143+
tool_call_id='call_abc'
144+
)
145+
result = msg.to_dict()
146+
assert result == {
147+
'role': 'tool',
148+
'content': 'result',
149+
'name': 'get_weather',
150+
'tool_call_id': 'call_abc'
151+
}
152+
153+
def test_invalid_role_raises(self):
154+
"""Test that invalid roles raise ValueError."""
155+
with pytest.raises(ValueError):
156+
Message(role='invalid', content='test')
157+
158+
159+
class TestMessageType:
160+
"""Tests for the MessageType enum."""
161+
162+
def test_message_types_defined(self):
163+
"""Verify all expected message types are defined."""
164+
assert MessageType.ADD_MESSAGE.value == "add-message"
165+
assert MessageType.SAY.value == "say"
166+
assert MessageType.END_CALL.value == "end-call"
167+
assert MessageType.TRANSFER_CALL.value == "transfer-call"
168+
169+
170+
class TestOpenAIModel:
171+
"""Tests for the OpenAIModel enum."""
172+
173+
def test_gpt5_models_defined(self):
174+
"""Test that GPT-5 series models are defined."""
175+
assert OpenAIModel.GPT_5_2.value == "gpt-5.2"
176+
assert OpenAIModel.GPT_5_2_CHAT.value == "gpt-5.2-chat"
177+
assert OpenAIModel.GPT_5_2_CHAT_LATEST.value == "gpt-5.2-chat-latest"
178+
assert OpenAIModel.GPT_5_2_CODEX.value == "gpt-5.2-codex"
179+
180+
def test_o_series_models_defined(self):
181+
"""Test that o-series models are defined."""
182+
assert OpenAIModel.O1.value == "o1"
183+
assert OpenAIModel.O1_MINI.value == "o1-mini"
184+
assert OpenAIModel.O3.value == "o3"
185+
assert OpenAIModel.O3_MINI.value == "o3-mini"
186+
assert OpenAIModel.O4_MINI.value == "o4-mini"
187+
188+
def test_legacy_models_defined(self):
189+
"""Test that legacy models are still available."""
190+
assert OpenAIModel.GPT_4.value == "gpt-4"
191+
assert OpenAIModel.GPT_4O.value == "gpt-4o"
192+
assert OpenAIModel.GPT_3_5_TURBO.value == "gpt-3.5-turbo"
193+
194+
195+
class TestSupportsDeveloperRole:
196+
"""Tests for the supports_developer_role function."""
197+
198+
def test_gpt5_models_support_developer(self):
199+
"""Test that GPT-5 models support developer role."""
200+
assert supports_developer_role("gpt-5.2") is True
201+
assert supports_developer_role("gpt-5.2-chat") is True
202+
assert supports_developer_role("gpt-5.2-chat-latest") is True
203+
assert supports_developer_role("gpt-5.2-codex") is True
204+
205+
def test_o_series_support_developer(self):
206+
"""Test that o-series models support developer role."""
207+
assert supports_developer_role("o1") is True
208+
assert supports_developer_role("o1-mini") is True
209+
assert supports_developer_role("o3") is True
210+
assert supports_developer_role("o3-mini") is True
211+
assert supports_developer_role("o4-mini") is True
212+
213+
def test_older_models_no_developer(self):
214+
"""Test that older models do not support developer role."""
215+
assert supports_developer_role("gpt-4") is False
216+
assert supports_developer_role("gpt-4o") is False
217+
assert supports_developer_role("gpt-3.5-turbo") is False
218+
219+
def test_unknown_model(self):
220+
"""Test behavior with unknown model."""
221+
assert supports_developer_role("unknown-model") is False
222+
223+
224+
class TestValidMessageRolesConstant:
225+
"""Tests for the VALID_MESSAGE_ROLES constant."""
226+
227+
def test_contains_all_roles(self):
228+
"""Test that constant contains all valid roles."""
229+
expected = {'system', 'user', 'assistant', 'developer', 'tool', 'function'}
230+
assert VALID_MESSAGE_ROLES == expected
231+
232+
def test_developer_in_valid_roles(self):
233+
"""Test that developer role is in valid roles."""
234+
assert 'developer' in VALID_MESSAGE_ROLES
235+
236+
237+
class TestDeveloperRoleModelsConstant:
238+
"""Tests for the DEVELOPER_ROLE_MODELS constant."""
239+
240+
def test_contains_gpt5_models(self):
241+
"""Test that GPT-5 models are in the set."""
242+
assert "gpt-5.2" in DEVELOPER_ROLE_MODELS
243+
assert "gpt-5.2-chat" in DEVELOPER_ROLE_MODELS
244+
245+
def test_contains_o_series(self):
246+
"""Test that o-series models are in the set."""
247+
assert "o1" in DEVELOPER_ROLE_MODELS
248+
assert "o3" in DEVELOPER_ROLE_MODELS
249+
250+
def test_does_not_contain_older_models(self):
251+
"""Test that older models are not in the set."""
252+
assert "gpt-4" not in DEVELOPER_ROLE_MODELS
253+
assert "gpt-3.5-turbo" not in DEVELOPER_ROLE_MODELS

0 commit comments

Comments
 (0)