Skip to content

Commit 421608a

Browse files
committed
feat(convo): add async support for conversation model operations
- add AsyncConversationModel class for async individual model operations - add AsyncConversationsModels class for async models collection operations - add async tests for conversation model and models functionality - add async fixtures for testing async conversation model operations
1 parent fb20e89 commit 421608a

5 files changed

Lines changed: 466 additions & 0 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""
2+
This module provides async functionality for managing individual conversation models in Typesense.
3+
4+
It contains the AsyncConversationModel class, which allows for retrieving, updating, and deleting
5+
conversation models asynchronously.
6+
7+
Classes:
8+
AsyncConversationModel: Manages async operations on a single conversation model in the Typesense API.
9+
10+
Dependencies:
11+
- typesense.async_api_call: Provides the AsyncApiCall class for making async API requests.
12+
- typesense.types.conversations_model: Provides ConversationModelCreateSchema, ConversationModelDeleteSchema, and ConversationModelSchema types.
13+
14+
Note: This module uses conditional imports to support both Python 3.11+ and earlier versions.
15+
"""
16+
17+
from typesense.async_api_call import AsyncApiCall
18+
from typesense.types.conversations_model import (
19+
ConversationModelCreateSchema,
20+
ConversationModelDeleteSchema,
21+
ConversationModelSchema,
22+
)
23+
24+
25+
class AsyncConversationModel:
26+
"""
27+
Manages async operations on a single conversation model in the Typesense API.
28+
29+
This class provides async methods to retrieve, update, and delete a conversation model.
30+
31+
Attributes:
32+
model_id (str): The ID of the conversation model.
33+
api_call (AsyncApiCall): The AsyncApiCall instance for making async API requests.
34+
"""
35+
36+
def __init__(self, api_call: AsyncApiCall, model_id: str) -> None:
37+
"""
38+
Initialize the AsyncConversationModel instance.
39+
40+
Args:
41+
api_call (AsyncApiCall): The AsyncApiCall instance for making async API requests.
42+
model_id (str): The ID of the conversation model.
43+
"""
44+
self.model_id = model_id
45+
self.api_call = api_call
46+
47+
async def retrieve(self) -> ConversationModelSchema:
48+
"""
49+
Retrieve this specific conversation model.
50+
51+
Returns:
52+
ConversationModelSchema: The schema containing the conversation model details.
53+
"""
54+
response: ConversationModelSchema = await self.api_call.get(
55+
self._endpoint_path,
56+
as_json=True,
57+
entity_type=ConversationModelSchema,
58+
)
59+
return response
60+
61+
async def update(
62+
self, model: ConversationModelCreateSchema
63+
) -> ConversationModelSchema:
64+
"""
65+
Update this specific conversation model.
66+
67+
Args:
68+
model (ConversationModelCreateSchema):
69+
The schema containing the updated model details.
70+
71+
Returns:
72+
ConversationModelSchema: The schema containing the updated conversation model.
73+
"""
74+
response: ConversationModelSchema = await self.api_call.put(
75+
self._endpoint_path,
76+
body=model,
77+
entity_type=ConversationModelSchema,
78+
)
79+
return response
80+
81+
async def delete(self) -> ConversationModelDeleteSchema:
82+
"""
83+
Delete this specific conversation model.
84+
85+
Returns:
86+
ConversationModelDeleteSchema: The schema containing the deletion response.
87+
"""
88+
response: ConversationModelDeleteSchema = await self.api_call.delete(
89+
self._endpoint_path,
90+
entity_type=ConversationModelDeleteSchema,
91+
)
92+
return response
93+
94+
@property
95+
def _endpoint_path(self) -> str:
96+
"""
97+
Construct the API endpoint path for this specific conversation model.
98+
99+
Returns:
100+
str: The constructed endpoint path.
101+
"""
102+
from typesense.async_conversations_models import AsyncConversationsModels
103+
104+
return "/".join([AsyncConversationsModels.resource_path, self.model_id])
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
"""
2+
This module provides async functionality for managing conversation models in Typesense.
3+
4+
It contains the AsyncConversationsModels class, which allows for creating, retrieving, and
5+
accessing individual conversation models asynchronously.
6+
7+
Classes:
8+
AsyncConversationsModels: Manages conversation models in the Typesense API (async).
9+
10+
Dependencies:
11+
- typesense.async_api_call: Provides the AsyncApiCall class for making async API requests.
12+
- typesense.async_conversation_model: Provides the AsyncConversationModel class for individual conversation model operations.
13+
- typesense.types.conversations_model: Provides ConversationModelCreateSchema and ConversationModelSchema types.
14+
15+
Note: This module uses conditional imports to support both Python 3.11+ and earlier versions.
16+
"""
17+
18+
import sys
19+
20+
from typesense.async_api_call import AsyncApiCall
21+
from typesense.async_conversation_model import AsyncConversationModel
22+
from typesense.types.conversations_model import (
23+
ConversationModelCreateSchema,
24+
ConversationModelSchema,
25+
)
26+
27+
if sys.version_info >= (3, 11):
28+
import typing
29+
else:
30+
import typing_extensions as typing
31+
32+
33+
class AsyncConversationsModels:
34+
"""
35+
Manages conversation models in the Typesense API (async).
36+
37+
This class provides async methods to create, retrieve, and access individual conversation models.
38+
39+
Attributes:
40+
resource_path (str): The API endpoint path for conversation models operations.
41+
api_call (AsyncApiCall): The AsyncApiCall instance for making async API requests.
42+
conversations_models (Dict[str, AsyncConversationModel]):
43+
A dictionary of AsyncConversationModel instances, keyed by model ID.
44+
"""
45+
46+
resource_path: typing.Final[str] = "/conversations/models"
47+
48+
def __init__(self, api_call: AsyncApiCall) -> None:
49+
"""
50+
Initialize the AsyncConversationsModels instance.
51+
52+
Args:
53+
api_call (AsyncApiCall): The AsyncApiCall instance for making async API requests.
54+
"""
55+
self.api_call = api_call
56+
self.conversations_models: typing.Dict[str, AsyncConversationModel] = {}
57+
58+
def __getitem__(self, model_id: str) -> AsyncConversationModel:
59+
"""
60+
Get or create an AsyncConversationModel instance for a given model ID.
61+
62+
This method allows accessing conversation models using dictionary-like syntax.
63+
If the AsyncConversationModel instance doesn't exist, it creates a new one.
64+
65+
Args:
66+
model_id (str): The ID of the conversation model.
67+
68+
Returns:
69+
AsyncConversationModel: The AsyncConversationModel instance for the specified model ID.
70+
71+
Example:
72+
>>> conversations_models = AsyncConversationsModels(async_api_call)
73+
>>> model = conversations_models["model_id"]
74+
"""
75+
if model_id not in self.conversations_models:
76+
self.conversations_models[model_id] = AsyncConversationModel(
77+
self.api_call,
78+
model_id,
79+
)
80+
return self.conversations_models[model_id]
81+
82+
async def create(
83+
self, model: ConversationModelCreateSchema
84+
) -> ConversationModelSchema:
85+
"""
86+
Create a new conversation model.
87+
88+
Args:
89+
model (ConversationModelCreateSchema):
90+
The schema for creating the conversation model.
91+
92+
Returns:
93+
ConversationModelSchema: The created conversation model.
94+
95+
Example:
96+
>>> conversations_models = AsyncConversationsModels(async_api_call)
97+
>>> model = await conversations_models.create(
98+
... {
99+
... "api_key": "key",
100+
... "model_name": "openai/gpt-3.5-turbo",
101+
... "history_collection": "conversation_store",
102+
... }
103+
... )
104+
"""
105+
response: ConversationModelSchema = await self.api_call.post(
106+
endpoint=AsyncConversationsModels.resource_path,
107+
entity_type=ConversationModelSchema,
108+
as_json=True,
109+
body=model,
110+
)
111+
return response
112+
113+
async def retrieve(self) -> typing.List[ConversationModelSchema]:
114+
"""
115+
Retrieve all conversation models.
116+
117+
Returns:
118+
List[ConversationModelSchema]: A list of all conversation models.
119+
120+
Example:
121+
>>> conversations_models = AsyncConversationsModels(async_api_call)
122+
>>> all_models = await conversations_models.retrieve()
123+
>>> for model in all_models:
124+
... print(model["id"])
125+
"""
126+
response: typing.List[ConversationModelSchema] = await self.api_call.get(
127+
endpoint=AsyncConversationsModels.resource_path,
128+
entity_type=typing.List[ConversationModelSchema],
129+
as_json=True,
130+
)
131+
return response

tests/conversation_model_test.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
assert_to_contain_keys,
1212
)
1313
from typesense.api_call import ApiCall
14+
from typesense.async_api_call import AsyncApiCall
15+
from typesense.async_conversation_model import AsyncConversationModel
16+
from typesense.async_conversations_models import AsyncConversationsModels
1417
from typesense.conversation_model import ConversationModel
1518
from typesense.conversations_models import ConversationsModels
1619
from typesense.types.conversations_model import (
@@ -44,6 +47,29 @@ def test_init(fake_api_call: ApiCall) -> None:
4447
)
4548

4649

50+
def test_init_async(fake_async_api_call: AsyncApiCall) -> None:
51+
"""Test that the AsyncConversationModel object is initialized correctly."""
52+
conversation_model = AsyncConversationModel(
53+
fake_async_api_call,
54+
"conversation_model_id",
55+
)
56+
57+
assert conversation_model.model_id == "conversation_model_id"
58+
assert_match_object(conversation_model.api_call, fake_async_api_call)
59+
assert_object_lists_match(
60+
conversation_model.api_call.node_manager.nodes,
61+
fake_async_api_call.node_manager.nodes,
62+
)
63+
assert_match_object(
64+
conversation_model.api_call.config.nearest_node,
65+
fake_async_api_call.config.nearest_node,
66+
)
67+
assert (
68+
conversation_model._endpoint_path # noqa: WPS437
69+
== "/conversations/models/conversation_model_id"
70+
)
71+
72+
4773
@pytest.mark.open_ai
4874
def test_actual_retrieve(
4975
actual_conversations_models: ConversationsModels,
@@ -113,3 +139,80 @@ def test_actual_delete(
113139
assert response.get("system_prompt") == "This is a system prompt"
114140
assert response.get("id") == create_conversations_model
115141
assert response.get("id") == create_conversations_model
142+
143+
144+
@pytest.mark.open_ai
145+
async def test_actual_retrieve_async(
146+
actual_async_conversations_models: AsyncConversationsModels,
147+
delete_all_conversations_models: None,
148+
create_conversations_model: str,
149+
) -> None:
150+
"""Test it can retrieve a conversation_model from Typesense Server."""
151+
response = await actual_async_conversations_models[
152+
create_conversations_model
153+
].retrieve()
154+
155+
assert_to_contain_keys(
156+
response,
157+
["id", "model_name", "system_prompt", "max_bytes", "api_key"],
158+
)
159+
assert response.get("id") == create_conversations_model
160+
161+
162+
@pytest.mark.open_ai
163+
async def test_actual_update_async(
164+
actual_async_conversations_models: AsyncConversationsModels,
165+
delete_all_conversations_models: None,
166+
create_conversations_model: str,
167+
) -> None:
168+
"""Test that it can update a conversation_model from Typesense Server."""
169+
response = await actual_async_conversations_models[
170+
create_conversations_model
171+
].update(
172+
{"system_prompt": "This is a new system prompt"},
173+
)
174+
175+
assert_to_contain_keys(
176+
response,
177+
[
178+
"id",
179+
"model_name",
180+
"system_prompt",
181+
"max_bytes",
182+
"api_key",
183+
"ttl",
184+
"history_collection",
185+
],
186+
)
187+
188+
assert response.get("system_prompt") == "This is a new system prompt"
189+
assert response.get("id") == create_conversations_model
190+
191+
192+
@pytest.mark.open_ai
193+
async def test_actual_delete_async(
194+
actual_async_conversations_models: AsyncConversationsModels,
195+
delete_all_conversations_models: None,
196+
create_conversations_model: str,
197+
) -> None:
198+
"""Test that it can delete an conversation_model from Typesense Server."""
199+
response = await actual_async_conversations_models[
200+
create_conversations_model
201+
].delete()
202+
203+
assert_to_contain_keys(
204+
response,
205+
[
206+
"id",
207+
"model_name",
208+
"system_prompt",
209+
"max_bytes",
210+
"api_key",
211+
"ttl",
212+
"history_collection",
213+
],
214+
)
215+
216+
assert response.get("system_prompt") == "This is a system prompt"
217+
assert response.get("id") == create_conversations_model
218+
assert response.get("id") == create_conversations_model

0 commit comments

Comments
 (0)