simile 0.2.6__tar.gz → 0.2.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of simile might be problematic. Click here for more details.
- {simile-0.2.6 → simile-0.2.8}/PKG-INFO +1 -1
- {simile-0.2.6 → simile-0.2.8}/pyproject.toml +1 -1
- simile-0.2.8/simile/client.py +323 -0
- simile-0.2.8/simile/models.py +198 -0
- {simile-0.2.6 → simile-0.2.8}/simile/resources.py +9 -4
- {simile-0.2.6 → simile-0.2.8}/simile.egg-info/PKG-INFO +1 -1
- simile-0.2.6/simile/client.py +0 -202
- simile-0.2.6/simile/models.py +0 -107
- {simile-0.2.6 → simile-0.2.8}/LICENSE +0 -0
- {simile-0.2.6 → simile-0.2.8}/README.md +0 -0
- {simile-0.2.6 → simile-0.2.8}/setup.cfg +0 -0
- {simile-0.2.6 → simile-0.2.8}/setup.py +0 -0
- {simile-0.2.6 → simile-0.2.8}/simile/__init__.py +0 -0
- {simile-0.2.6 → simile-0.2.8}/simile/exceptions.py +0 -0
- {simile-0.2.6 → simile-0.2.8}/simile.egg-info/SOURCES.txt +0 -0
- {simile-0.2.6 → simile-0.2.8}/simile.egg-info/dependency_links.txt +0 -0
- {simile-0.2.6 → simile-0.2.8}/simile.egg-info/requires.txt +0 -0
- {simile-0.2.6 → simile-0.2.8}/simile.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from httpx import AsyncClient
|
|
3
|
+
from typing import List, Dict, Any, Optional, Union, Type
|
|
4
|
+
import uuid
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from .models import (
|
|
8
|
+
Population,
|
|
9
|
+
Agent as AgentModel,
|
|
10
|
+
DataItem,
|
|
11
|
+
DeletionResponse,
|
|
12
|
+
QualGenerationRequest,
|
|
13
|
+
QualGenerationResponse,
|
|
14
|
+
MCGenerationRequest,
|
|
15
|
+
MCGenerationResponse,
|
|
16
|
+
CreatePopulationPayload,
|
|
17
|
+
CreateAgentPayload,
|
|
18
|
+
CreateDataItemPayload,
|
|
19
|
+
UpdateDataItemPayload,
|
|
20
|
+
InitialDataItemPayload,
|
|
21
|
+
SurveySessionCreateResponse,
|
|
22
|
+
SurveySessionDetailResponse,
|
|
23
|
+
)
|
|
24
|
+
from .resources import Agent, SurveySession
|
|
25
|
+
from .exceptions import (
|
|
26
|
+
SimileAPIError,
|
|
27
|
+
SimileAuthenticationError,
|
|
28
|
+
SimileNotFoundError,
|
|
29
|
+
SimileBadRequestError,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
DEFAULT_BASE_URL = "https://simile-api-3a83be7adae0.herokuapp.com/api/v1"
|
|
33
|
+
TIMEOUT_CONFIG = httpx.Timeout(5.0, read=30.0, write=30.0, pool=30.0)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Simile:
|
|
37
|
+
APIError = SimileAPIError
|
|
38
|
+
AuthenticationError = SimileAuthenticationError
|
|
39
|
+
NotFoundError = SimileNotFoundError
|
|
40
|
+
BadRequestError = SimileBadRequestError
|
|
41
|
+
|
|
42
|
+
def __init__(self, api_key: str, base_url: str = DEFAULT_BASE_URL):
|
|
43
|
+
if not api_key:
|
|
44
|
+
raise ValueError("API key is required.")
|
|
45
|
+
self.api_key = api_key
|
|
46
|
+
self.base_url = base_url.rstrip("/")
|
|
47
|
+
self._client = AsyncClient(
|
|
48
|
+
headers={"X-API-Key": self.api_key}, timeout=TIMEOUT_CONFIG
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
async def _request(
|
|
52
|
+
self, method: str, endpoint: str, **kwargs
|
|
53
|
+
) -> Union[httpx.Response, BaseModel]:
|
|
54
|
+
url = f"{self.base_url}/{endpoint.lstrip('/')}"
|
|
55
|
+
response_model_cls: Optional[Type[BaseModel]] = kwargs.pop(
|
|
56
|
+
"response_model", None
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
response = await self._client.request(method, url, **kwargs)
|
|
61
|
+
response.raise_for_status()
|
|
62
|
+
|
|
63
|
+
if response_model_cls:
|
|
64
|
+
return response_model_cls(**response.json())
|
|
65
|
+
else:
|
|
66
|
+
return response
|
|
67
|
+
except httpx.HTTPStatusError as e:
|
|
68
|
+
status_code = e.response.status_code
|
|
69
|
+
try:
|
|
70
|
+
error_data = e.response.json()
|
|
71
|
+
detail = error_data.get("detail", e.response.text)
|
|
72
|
+
except Exception:
|
|
73
|
+
detail = e.response.text
|
|
74
|
+
|
|
75
|
+
if status_code == 401:
|
|
76
|
+
raise SimileAuthenticationError(detail=detail)
|
|
77
|
+
elif status_code == 404:
|
|
78
|
+
raise SimileNotFoundError(detail=detail)
|
|
79
|
+
elif status_code == 400:
|
|
80
|
+
raise SimileBadRequestError(detail=detail)
|
|
81
|
+
else:
|
|
82
|
+
raise SimileAPIError(
|
|
83
|
+
f"API request failed: {e}", status_code=status_code, detail=detail
|
|
84
|
+
)
|
|
85
|
+
except httpx.ConnectTimeout:
|
|
86
|
+
raise SimileAPIError("Connection timed out while trying to connect.")
|
|
87
|
+
except httpx.ReadTimeout:
|
|
88
|
+
raise SimileAPIError("Timed out waiting for data from the server.")
|
|
89
|
+
except httpx.WriteTimeout:
|
|
90
|
+
raise SimileAPIError("Timed out while sending data to the server.")
|
|
91
|
+
except httpx.PoolTimeout:
|
|
92
|
+
raise SimileAPIError("Timed out waiting for a connection from the pool.")
|
|
93
|
+
except httpx.ConnectError:
|
|
94
|
+
raise SimileAPIError("Failed to connect to the server.")
|
|
95
|
+
except httpx.ProtocolError:
|
|
96
|
+
raise SimileAPIError("A protocol error occurred.")
|
|
97
|
+
except httpx.DecodingError:
|
|
98
|
+
raise SimileAPIError("Failed to decode the response.")
|
|
99
|
+
except httpx.RequestError as e:
|
|
100
|
+
raise SimileAPIError(
|
|
101
|
+
f"An unknown request error occurred: {type(e).__name__}: {e}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def agent(self, agent_id: uuid.UUID) -> Agent:
|
|
105
|
+
"""Returns an Agent object to interact with a specific agent."""
|
|
106
|
+
return Agent(agent_id=agent_id, client=self)
|
|
107
|
+
|
|
108
|
+
async def create_survey_session(self, agent_id: uuid.UUID) -> SurveySession:
|
|
109
|
+
"""Creates a new survey session for the given agent and returns a SurveySession object."""
|
|
110
|
+
endpoint = "sessions/"
|
|
111
|
+
response_data = await self._request(
|
|
112
|
+
"POST",
|
|
113
|
+
endpoint,
|
|
114
|
+
json={"agent_id": str(agent_id)},
|
|
115
|
+
response_model=SurveySessionCreateResponse,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Create and return a SurveySession object
|
|
119
|
+
return SurveySession(
|
|
120
|
+
id=response_data.id,
|
|
121
|
+
agent_id=response_data.agent_id,
|
|
122
|
+
status=response_data.status,
|
|
123
|
+
client=self,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
async def get_survey_session_details(
|
|
127
|
+
self, session_id: Union[str, uuid.UUID]
|
|
128
|
+
) -> SurveySessionDetailResponse:
|
|
129
|
+
"""Retrieves detailed information about a survey session including typed conversation history."""
|
|
130
|
+
endpoint = f"sessions/{str(session_id)}"
|
|
131
|
+
response_data = await self._request(
|
|
132
|
+
"GET", endpoint, response_model=SurveySessionDetailResponse
|
|
133
|
+
)
|
|
134
|
+
return response_data
|
|
135
|
+
|
|
136
|
+
async def get_survey_session(
|
|
137
|
+
self, session_id: Union[str, uuid.UUID]
|
|
138
|
+
) -> SurveySession:
|
|
139
|
+
"""Resume an existing survey session by ID and return a SurveySession object."""
|
|
140
|
+
session_details = await self.get_survey_session(session_id)
|
|
141
|
+
|
|
142
|
+
if session_details.status == "closed":
|
|
143
|
+
raise ValueError(f"Session {session_id} is already closed")
|
|
144
|
+
|
|
145
|
+
return SurveySession(
|
|
146
|
+
id=session_details.id,
|
|
147
|
+
agent_id=session_details.agent_id,
|
|
148
|
+
status=session_details.status,
|
|
149
|
+
client=self,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
async def create_population(
|
|
153
|
+
self, name: str, description: Optional[str] = None
|
|
154
|
+
) -> Population:
|
|
155
|
+
"""Creates a new population."""
|
|
156
|
+
payload = CreatePopulationPayload(name=name, description=description)
|
|
157
|
+
response_data = await self._request(
|
|
158
|
+
"POST",
|
|
159
|
+
"populations/create",
|
|
160
|
+
json=payload.model_dump(mode="json", exclude_none=True),
|
|
161
|
+
response_model=Population,
|
|
162
|
+
)
|
|
163
|
+
return response_data
|
|
164
|
+
|
|
165
|
+
async def get_population(self, population_id: Union[str, uuid.UUID]) -> Population:
|
|
166
|
+
response_data = await self._request(
|
|
167
|
+
"GET", f"populations/get/{str(population_id)}", response_model=Population
|
|
168
|
+
)
|
|
169
|
+
return response_data
|
|
170
|
+
|
|
171
|
+
async def delete_population(
|
|
172
|
+
self, population_id: Union[str, uuid.UUID]
|
|
173
|
+
) -> DeletionResponse:
|
|
174
|
+
response_data = await self._request(
|
|
175
|
+
"DELETE",
|
|
176
|
+
f"populations/delete/{str(population_id)}",
|
|
177
|
+
response_model=DeletionResponse,
|
|
178
|
+
)
|
|
179
|
+
return response_data
|
|
180
|
+
|
|
181
|
+
async def get_agents_in_population(
|
|
182
|
+
self, population_id: Union[str, uuid.UUID]
|
|
183
|
+
) -> List[AgentModel]:
|
|
184
|
+
"""Retrieves all agents belonging to a specific population."""
|
|
185
|
+
endpoint = f"populations/{str(population_id)}/agents"
|
|
186
|
+
raw_response = await self._request("GET", endpoint)
|
|
187
|
+
agents_data_list = raw_response.json()
|
|
188
|
+
return [AgentModel(**data) for data in agents_data_list]
|
|
189
|
+
|
|
190
|
+
async def create_agent(
|
|
191
|
+
self,
|
|
192
|
+
name: str,
|
|
193
|
+
population_id: Optional[Union[str, uuid.UUID]] = None,
|
|
194
|
+
agent_data: Optional[List[Dict[str, Any]]] = None,
|
|
195
|
+
) -> AgentModel:
|
|
196
|
+
"""Creates a new agent, optionally within a population and with initial data items."""
|
|
197
|
+
pop_id_uuid: Optional[uuid.UUID] = None
|
|
198
|
+
if population_id:
|
|
199
|
+
pop_id_uuid = (
|
|
200
|
+
uuid.UUID(str(population_id))
|
|
201
|
+
if not isinstance(population_id, uuid.UUID)
|
|
202
|
+
else population_id
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
payload = CreateAgentPayload(
|
|
206
|
+
name=name, population_id=pop_id_uuid, agent_data=agent_data
|
|
207
|
+
)
|
|
208
|
+
response_data = await self._request(
|
|
209
|
+
"POST",
|
|
210
|
+
"agents/create",
|
|
211
|
+
json=payload.model_dump(mode="json", exclude_none=True),
|
|
212
|
+
response_model=AgentModel,
|
|
213
|
+
)
|
|
214
|
+
return response_data
|
|
215
|
+
|
|
216
|
+
async def get_agent(self, agent_id: Union[str, uuid.UUID]) -> AgentModel:
|
|
217
|
+
response_data = await self._request(
|
|
218
|
+
"GET", f"agents/get/{str(agent_id)}", response_model=AgentModel
|
|
219
|
+
)
|
|
220
|
+
return response_data
|
|
221
|
+
|
|
222
|
+
async def delete_agent(self, agent_id: Union[str, uuid.UUID]) -> DeletionResponse:
|
|
223
|
+
response_data = await self._request(
|
|
224
|
+
"DELETE", f"agents/delete/{str(agent_id)}", response_model=DeletionResponse
|
|
225
|
+
)
|
|
226
|
+
return response_data
|
|
227
|
+
|
|
228
|
+
async def create_data_item(
|
|
229
|
+
self, agent_id: Union[str, uuid.UUID], data_type: str, content: Any
|
|
230
|
+
) -> DataItem:
|
|
231
|
+
"""Creates a new data item for a specific agent."""
|
|
232
|
+
payload = CreateDataItemPayload(data_type=data_type, content=content)
|
|
233
|
+
response_data = await self._request(
|
|
234
|
+
"POST",
|
|
235
|
+
f"data_item/create/{str(agent_id)}",
|
|
236
|
+
json=payload.model_dump(mode="json"),
|
|
237
|
+
response_model=DataItem,
|
|
238
|
+
)
|
|
239
|
+
return response_data
|
|
240
|
+
|
|
241
|
+
async def get_data_item(self, data_item_id: Union[str, uuid.UUID]) -> DataItem:
|
|
242
|
+
response_data = await self._request(
|
|
243
|
+
"GET", f"data_item/get/{str(data_item_id)}", response_model=DataItem
|
|
244
|
+
)
|
|
245
|
+
return response_data
|
|
246
|
+
|
|
247
|
+
async def list_data_items(
|
|
248
|
+
self, agent_id: Union[str, uuid.UUID], data_type: Optional[str] = None
|
|
249
|
+
) -> List[DataItem]:
|
|
250
|
+
params = {}
|
|
251
|
+
if data_type:
|
|
252
|
+
params["data_type"] = data_type
|
|
253
|
+
agent_id_str = str(agent_id)
|
|
254
|
+
raw_response = await self._request(
|
|
255
|
+
"GET", f"data_item/list/{agent_id_str}", params=params
|
|
256
|
+
)
|
|
257
|
+
return [DataItem(**item) for item in raw_response.json()]
|
|
258
|
+
|
|
259
|
+
async def update_data_item(
|
|
260
|
+
self, data_item_id: Union[str, uuid.UUID], content: Any
|
|
261
|
+
) -> DataItem:
|
|
262
|
+
"""Updates an existing data item."""
|
|
263
|
+
payload = UpdateDataItemPayload(content=content)
|
|
264
|
+
response_data = await self._request(
|
|
265
|
+
"POST",
|
|
266
|
+
f"data_item/update/{str(data_item_id)}",
|
|
267
|
+
json=payload.model_dump(),
|
|
268
|
+
response_model=DataItem,
|
|
269
|
+
)
|
|
270
|
+
return response_data
|
|
271
|
+
|
|
272
|
+
async def delete_data_item(
|
|
273
|
+
self, data_item_id: Union[str, uuid.UUID]
|
|
274
|
+
) -> DeletionResponse:
|
|
275
|
+
response_data = await self._request(
|
|
276
|
+
"DELETE",
|
|
277
|
+
f"data_item/delete/{str(data_item_id)}",
|
|
278
|
+
response_model=DeletionResponse,
|
|
279
|
+
)
|
|
280
|
+
return response_data
|
|
281
|
+
|
|
282
|
+
async def generate_qual_response(
|
|
283
|
+
self, agent_id: uuid.UUID, question: str, image_url: Optional[str] = None
|
|
284
|
+
) -> QualGenerationResponse:
|
|
285
|
+
"""Generates a qualitative response from an agent based on a question."""
|
|
286
|
+
endpoint = f"/generation/qual/{str(agent_id)}"
|
|
287
|
+
request_payload = QualGenerationRequest(question=question, image_url=image_url)
|
|
288
|
+
response_data = await self._request(
|
|
289
|
+
"POST",
|
|
290
|
+
endpoint,
|
|
291
|
+
json=request_payload.model_dump(),
|
|
292
|
+
response_model=QualGenerationResponse,
|
|
293
|
+
)
|
|
294
|
+
return response_data
|
|
295
|
+
|
|
296
|
+
async def generate_mc_response(
|
|
297
|
+
self,
|
|
298
|
+
agent_id: uuid.UUID,
|
|
299
|
+
question: str,
|
|
300
|
+
options: List[str],
|
|
301
|
+
image_url: Optional[str] = None,
|
|
302
|
+
) -> MCGenerationResponse:
|
|
303
|
+
"""Generates a multiple-choice response from an agent."""
|
|
304
|
+
endpoint = f"generation/mc/{str(agent_id)}"
|
|
305
|
+
request_payload = MCGenerationRequest(
|
|
306
|
+
question=question, options=options, image_url=image_url
|
|
307
|
+
)
|
|
308
|
+
response_data = await self._request(
|
|
309
|
+
"POST",
|
|
310
|
+
endpoint,
|
|
311
|
+
json=request_payload.model_dump(),
|
|
312
|
+
response_model=MCGenerationResponse,
|
|
313
|
+
)
|
|
314
|
+
return response_data
|
|
315
|
+
|
|
316
|
+
async def aclose(self):
|
|
317
|
+
await self._client.aclose()
|
|
318
|
+
|
|
319
|
+
async def __aenter__(self):
|
|
320
|
+
return self
|
|
321
|
+
|
|
322
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
323
|
+
await self.aclose()
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
from typing import List, Dict, Any, Optional, Union, Literal
|
|
2
|
+
from pydantic import BaseModel, Field, validator
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from enum import Enum
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Population(BaseModel):
|
|
9
|
+
population_id: uuid.UUID
|
|
10
|
+
name: str
|
|
11
|
+
description: Optional[str] = None
|
|
12
|
+
created_at: datetime
|
|
13
|
+
updated_at: datetime
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DataItem(BaseModel):
|
|
17
|
+
id: uuid.UUID
|
|
18
|
+
agent_id: uuid.UUID
|
|
19
|
+
data_type: str
|
|
20
|
+
content: Any
|
|
21
|
+
created_at: datetime
|
|
22
|
+
updated_at: datetime
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Agent(BaseModel):
|
|
26
|
+
agent_id: uuid.UUID
|
|
27
|
+
name: str
|
|
28
|
+
population_id: Optional[uuid.UUID] = None
|
|
29
|
+
created_at: datetime
|
|
30
|
+
updated_at: datetime
|
|
31
|
+
data_items: List[DataItem] = Field(default_factory=list)
|
|
32
|
+
|
|
33
|
+
class CreatePopulationPayload(BaseModel):
|
|
34
|
+
name: str
|
|
35
|
+
description: Optional[str] = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class InitialDataItemPayload(BaseModel):
|
|
39
|
+
data_type: str
|
|
40
|
+
content: Any
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class CreateAgentPayload(BaseModel):
|
|
44
|
+
name: str
|
|
45
|
+
population_id: Optional[uuid.UUID] = None
|
|
46
|
+
agent_data: Optional[List[InitialDataItemPayload]] = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class CreateDataItemPayload(BaseModel):
|
|
50
|
+
data_type: str
|
|
51
|
+
content: Any
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class UpdateDataItemPayload(BaseModel):
|
|
55
|
+
content: Any
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DeletionResponse(BaseModel):
|
|
59
|
+
message: str
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# --- Generation Operation Models ---
|
|
63
|
+
class QualGenerationRequest(BaseModel):
|
|
64
|
+
question: str
|
|
65
|
+
image_url: Optional[str] = None # For backward compatibility
|
|
66
|
+
images: Optional[Dict[str, str]] = None # Dict of {description: url} for multiple images
|
|
67
|
+
|
|
68
|
+
class QualGenerationResponse(BaseModel):
|
|
69
|
+
question: str
|
|
70
|
+
answer: str
|
|
71
|
+
|
|
72
|
+
class MCGenerationRequest(BaseModel):
|
|
73
|
+
question: str
|
|
74
|
+
options: List[str]
|
|
75
|
+
image_url: Optional[str] = None
|
|
76
|
+
|
|
77
|
+
class MCGenerationResponse(BaseModel):
|
|
78
|
+
question: str
|
|
79
|
+
options: List[str]
|
|
80
|
+
chosen_option: str
|
|
81
|
+
|
|
82
|
+
class AddContextRequest(BaseModel):
|
|
83
|
+
context: str
|
|
84
|
+
|
|
85
|
+
class AddContextResponse(BaseModel):
|
|
86
|
+
message: str
|
|
87
|
+
session_id: uuid.UUID
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# --- Survey Session Models ---
|
|
91
|
+
class TurnType(str, Enum):
|
|
92
|
+
"""Enum for different types of conversation turns."""
|
|
93
|
+
CONTEXT = "context"
|
|
94
|
+
IMAGE = "image"
|
|
95
|
+
QUALITATIVE_QUESTION = "qualitative_question"
|
|
96
|
+
MULTIPLE_CHOICE_QUESTION = "multiple_choice_question"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class BaseTurn(BaseModel):
|
|
100
|
+
"""Base model for all conversation turns."""
|
|
101
|
+
timestamp: datetime = Field(default_factory=lambda: datetime.now())
|
|
102
|
+
type: TurnType
|
|
103
|
+
|
|
104
|
+
class Config:
|
|
105
|
+
use_enum_values = True
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ContextTurn(BaseTurn):
|
|
109
|
+
"""A context turn that provides background information."""
|
|
110
|
+
type: Literal[TurnType.CONTEXT] = TurnType.CONTEXT
|
|
111
|
+
user_context: str
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class ImageTurn(BaseTurn):
|
|
115
|
+
"""A standalone image turn (e.g., for context or reference)."""
|
|
116
|
+
type: Literal[TurnType.IMAGE] = TurnType.IMAGE
|
|
117
|
+
image_url: str
|
|
118
|
+
caption: Optional[str] = None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class QualitativeQuestionTurn(BaseTurn):
|
|
122
|
+
"""A qualitative question-answer turn."""
|
|
123
|
+
type: Literal[TurnType.QUALITATIVE_QUESTION] = TurnType.QUALITATIVE_QUESTION
|
|
124
|
+
user_question: str
|
|
125
|
+
user_image_url: Optional[str] = None # For backward compatibility
|
|
126
|
+
user_images: Optional[Dict[str, str]] = None # Dict of {description: url} for multiple images
|
|
127
|
+
llm_response: Optional[str] = None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class MultipleChoiceQuestionTurn(BaseTurn):
|
|
131
|
+
"""A multiple choice question-answer turn."""
|
|
132
|
+
type: Literal[TurnType.MULTIPLE_CHOICE_QUESTION] = TurnType.MULTIPLE_CHOICE_QUESTION
|
|
133
|
+
user_question: str
|
|
134
|
+
user_options: List[str]
|
|
135
|
+
user_image_url: Optional[str] = None
|
|
136
|
+
llm_chosen_option: Optional[str] = None
|
|
137
|
+
|
|
138
|
+
@validator('user_options')
|
|
139
|
+
def validate_options(cls, v):
|
|
140
|
+
if not v:
|
|
141
|
+
raise ValueError("Multiple choice questions must have at least one option")
|
|
142
|
+
if len(v) < 2:
|
|
143
|
+
raise ValueError("Multiple choice questions should have at least two options")
|
|
144
|
+
return v
|
|
145
|
+
|
|
146
|
+
@validator('llm_chosen_option')
|
|
147
|
+
def validate_chosen_option(cls, v, values):
|
|
148
|
+
if v is not None and 'user_options' in values and v not in values['user_options']:
|
|
149
|
+
raise ValueError(f"Chosen option '{v}' must be one of the provided options")
|
|
150
|
+
return v
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# Union type for all possible turn types
|
|
154
|
+
SurveySessionTurn = Union[ContextTurn, ImageTurn, QualitativeQuestionTurn, MultipleChoiceQuestionTurn]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class SurveySessionCreateResponse(BaseModel):
|
|
158
|
+
id: uuid.UUID # Session ID
|
|
159
|
+
agent_id: uuid.UUID
|
|
160
|
+
created_at: datetime
|
|
161
|
+
status: str
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class SurveySessionDetailResponse(BaseModel):
|
|
165
|
+
"""Detailed survey session response with typed conversation turns."""
|
|
166
|
+
id: uuid.UUID
|
|
167
|
+
agent_id: uuid.UUID
|
|
168
|
+
created_at: datetime
|
|
169
|
+
updated_at: datetime
|
|
170
|
+
status: str
|
|
171
|
+
conversation_history: List[SurveySessionTurn] = Field(default_factory=list)
|
|
172
|
+
|
|
173
|
+
class Config:
|
|
174
|
+
json_encoders = {
|
|
175
|
+
datetime: lambda v: v.isoformat()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class SurveySessionListItemResponse(BaseModel):
|
|
180
|
+
"""Summary response for listing survey sessions."""
|
|
181
|
+
id: uuid.UUID
|
|
182
|
+
agent_id: uuid.UUID
|
|
183
|
+
created_at: datetime
|
|
184
|
+
updated_at: datetime
|
|
185
|
+
status: str
|
|
186
|
+
turn_count: int = Field(description="Number of turns in conversation history")
|
|
187
|
+
|
|
188
|
+
class Config:
|
|
189
|
+
json_encoders = {
|
|
190
|
+
datetime: lambda v: v.isoformat()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class SurveySessionCloseResponse(BaseModel):
|
|
195
|
+
id: uuid.UUID # Session ID
|
|
196
|
+
status: str
|
|
197
|
+
updated_at: datetime
|
|
198
|
+
message: Optional[str] = None
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import uuid
|
|
2
|
-
from typing import TYPE_CHECKING, List, Optional
|
|
2
|
+
from typing import TYPE_CHECKING, List, Optional, Dict
|
|
3
3
|
|
|
4
4
|
from .models import (
|
|
5
5
|
QualGenerationRequest,
|
|
@@ -8,7 +8,8 @@ from .models import (
|
|
|
8
8
|
MCGenerationResponse,
|
|
9
9
|
SurveySessionCloseResponse,
|
|
10
10
|
AddContextRequest,
|
|
11
|
-
AddContextResponse
|
|
11
|
+
AddContextResponse,
|
|
12
|
+
SurveySessionDetailResponse
|
|
12
13
|
)
|
|
13
14
|
|
|
14
15
|
if TYPE_CHECKING:
|
|
@@ -53,10 +54,14 @@ class SurveySession:
|
|
|
53
54
|
def status(self) -> str:
|
|
54
55
|
return self._status
|
|
55
56
|
|
|
56
|
-
async def
|
|
57
|
+
async def get_details(self) -> SurveySessionDetailResponse:
|
|
58
|
+
"""Retrieves detailed information about this survey session including typed conversation history."""
|
|
59
|
+
return await self._client.get_survey_session(self._id)
|
|
60
|
+
|
|
61
|
+
async def generate_qual_response(self, question: str, image_url: Optional[str] = None, images: Optional[Dict[str, str]] = None) -> QualGenerationResponse:
|
|
57
62
|
"""Generates a qualitative response within this survey session."""
|
|
58
63
|
endpoint = f"sessions/{str(self._id)}/qual"
|
|
59
|
-
payload = QualGenerationRequest(question=question, image_url=image_url)
|
|
64
|
+
payload = QualGenerationRequest(question=question, image_url=image_url, images=images)
|
|
60
65
|
return await self._client._request(
|
|
61
66
|
"POST",
|
|
62
67
|
endpoint,
|
simile-0.2.6/simile/client.py
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import httpx
|
|
2
|
-
from httpx import AsyncClient
|
|
3
|
-
from typing import List, Dict, Any, Optional, Union, Type
|
|
4
|
-
import uuid
|
|
5
|
-
from pydantic import BaseModel
|
|
6
|
-
|
|
7
|
-
from .models import (
|
|
8
|
-
Population, Agent as AgentModel, DataItem, DeletionResponse,
|
|
9
|
-
QualGenerationRequest, QualGenerationResponse,
|
|
10
|
-
MCGenerationRequest, MCGenerationResponse,
|
|
11
|
-
CreatePopulationPayload, CreateAgentPayload, CreateDataItemPayload, UpdateDataItemPayload,
|
|
12
|
-
InitialDataItemPayload,
|
|
13
|
-
SurveySessionCreateResponse
|
|
14
|
-
)
|
|
15
|
-
from .resources import Agent, SurveySession
|
|
16
|
-
from .exceptions import (
|
|
17
|
-
SimileAPIError, SimileAuthenticationError, SimileNotFoundError, SimileBadRequestError
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
DEFAULT_BASE_URL = "https://simile-api-3a83be7adae0.herokuapp.com/api/v1"
|
|
21
|
-
TIMEOUT_CONFIG = httpx.Timeout(5.0, read=30.0, write=30.0, pool=30.0)
|
|
22
|
-
|
|
23
|
-
class Simile:
|
|
24
|
-
APIError = SimileAPIError
|
|
25
|
-
AuthenticationError = SimileAuthenticationError
|
|
26
|
-
NotFoundError = SimileNotFoundError
|
|
27
|
-
BadRequestError = SimileBadRequestError
|
|
28
|
-
|
|
29
|
-
def __init__(self, api_key: str, base_url: str = DEFAULT_BASE_URL):
|
|
30
|
-
if not api_key:
|
|
31
|
-
raise ValueError("API key is required.")
|
|
32
|
-
self.api_key = api_key
|
|
33
|
-
self.base_url = base_url.rstrip('/')
|
|
34
|
-
self._client = AsyncClient(
|
|
35
|
-
headers={"X-API-Key": self.api_key},
|
|
36
|
-
timeout=TIMEOUT_CONFIG
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
async def _request(self, method: str, endpoint: str, **kwargs) -> Union[httpx.Response, BaseModel]:
|
|
40
|
-
url = f"{self.base_url}/{endpoint.lstrip('/')}"
|
|
41
|
-
response_model_cls: Optional[Type[BaseModel]] = kwargs.pop("response_model", None)
|
|
42
|
-
|
|
43
|
-
try:
|
|
44
|
-
response = await self._client.request(method, url, **kwargs)
|
|
45
|
-
response.raise_for_status()
|
|
46
|
-
|
|
47
|
-
if response_model_cls:
|
|
48
|
-
return response_model_cls(**response.json())
|
|
49
|
-
else:
|
|
50
|
-
return response
|
|
51
|
-
except httpx.HTTPStatusError as e:
|
|
52
|
-
status_code = e.response.status_code
|
|
53
|
-
try:
|
|
54
|
-
error_data = e.response.json()
|
|
55
|
-
detail = error_data.get("detail", e.response.text)
|
|
56
|
-
except Exception:
|
|
57
|
-
detail = e.response.text
|
|
58
|
-
|
|
59
|
-
if status_code == 401:
|
|
60
|
-
raise SimileAuthenticationError(detail=detail)
|
|
61
|
-
elif status_code == 404:
|
|
62
|
-
raise SimileNotFoundError(detail=detail)
|
|
63
|
-
elif status_code == 400:
|
|
64
|
-
raise SimileBadRequestError(detail=detail)
|
|
65
|
-
else:
|
|
66
|
-
raise SimileAPIError(f"API request failed: {e}", status_code=status_code, detail=detail)
|
|
67
|
-
except httpx.ConnectTimeout:
|
|
68
|
-
raise SimileAPIError("Connection timed out while trying to connect.")
|
|
69
|
-
except httpx.ReadTimeout:
|
|
70
|
-
raise SimileAPIError("Timed out waiting for data from the server.")
|
|
71
|
-
except httpx.WriteTimeout:
|
|
72
|
-
raise SimileAPIError("Timed out while sending data to the server.")
|
|
73
|
-
except httpx.PoolTimeout:
|
|
74
|
-
raise SimileAPIError("Timed out waiting for a connection from the pool.")
|
|
75
|
-
except httpx.ConnectError:
|
|
76
|
-
raise SimileAPIError("Failed to connect to the server.")
|
|
77
|
-
except httpx.ProtocolError:
|
|
78
|
-
raise SimileAPIError("A protocol error occurred.")
|
|
79
|
-
except httpx.DecodingError:
|
|
80
|
-
raise SimileAPIError("Failed to decode the response.")
|
|
81
|
-
except httpx.RequestError as e:
|
|
82
|
-
raise SimileAPIError(f"An unknown request error occurred: {type(e).__name__}: {e}")
|
|
83
|
-
|
|
84
|
-
def agent(self, agent_id: uuid.UUID) -> Agent:
|
|
85
|
-
"""Returns an Agent object to interact with a specific agent."""
|
|
86
|
-
return Agent(agent_id=agent_id, client=self)
|
|
87
|
-
|
|
88
|
-
async def create_survey_session(self, agent_id: uuid.UUID) -> SurveySession:
|
|
89
|
-
"""Creates a new survey session for the given agent and returns a SurveySession object."""
|
|
90
|
-
endpoint = "sessions/"
|
|
91
|
-
response_data = await self._request(
|
|
92
|
-
"POST",
|
|
93
|
-
endpoint,
|
|
94
|
-
json={"agent_id": str(agent_id)},
|
|
95
|
-
response_model=SurveySessionCreateResponse
|
|
96
|
-
)
|
|
97
|
-
return SurveySession(
|
|
98
|
-
id=response_data.id,
|
|
99
|
-
agent_id=response_data.agent_id,
|
|
100
|
-
status=response_data.status,
|
|
101
|
-
client=self
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
async def create_population(self, name: str, description: Optional[str] = None) -> Population:
|
|
105
|
-
"""Creates a new population."""
|
|
106
|
-
payload = CreatePopulationPayload(name=name, description=description)
|
|
107
|
-
response_data = await self._request("POST", "populations/create", json=payload.model_dump(mode='json', exclude_none=True), response_model=Population)
|
|
108
|
-
return response_data
|
|
109
|
-
|
|
110
|
-
async def get_population(self, population_id: Union[str, uuid.UUID]) -> Population:
|
|
111
|
-
response_data = await self._request("GET", f"populations/get/{str(population_id)}", response_model=Population)
|
|
112
|
-
return response_data
|
|
113
|
-
|
|
114
|
-
async def delete_population(self, population_id: Union[str, uuid.UUID]) -> DeletionResponse:
|
|
115
|
-
response_data = await self._request("DELETE", f"populations/delete/{str(population_id)}", response_model=DeletionResponse)
|
|
116
|
-
return response_data
|
|
117
|
-
|
|
118
|
-
async def get_agents_in_population(self, population_id: Union[str, uuid.UUID]) -> List[AgentModel]:
|
|
119
|
-
"""Retrieves all agents belonging to a specific population."""
|
|
120
|
-
endpoint = f"populations/{str(population_id)}/agents"
|
|
121
|
-
raw_response = await self._request("GET", endpoint)
|
|
122
|
-
agents_data_list = raw_response.json()
|
|
123
|
-
return [AgentModel(**data) for data in agents_data_list]
|
|
124
|
-
|
|
125
|
-
async def create_agent(self, name: str, population_id: Optional[Union[str, uuid.UUID]] = None, agent_data: Optional[List[Dict[str, Any]]] = None) -> AgentModel:
|
|
126
|
-
"""Creates a new agent, optionally within a population and with initial data items."""
|
|
127
|
-
pop_id_uuid: Optional[uuid.UUID] = None
|
|
128
|
-
if population_id:
|
|
129
|
-
pop_id_uuid = uuid.UUID(str(population_id)) if not isinstance(population_id, uuid.UUID) else population_id
|
|
130
|
-
|
|
131
|
-
payload = CreateAgentPayload(name=name, population_id=pop_id_uuid, agent_data=agent_data)
|
|
132
|
-
response_data = await self._request("POST", "agents/create", json=payload.model_dump(mode='json', exclude_none=True), response_model=AgentModel)
|
|
133
|
-
return response_data
|
|
134
|
-
|
|
135
|
-
async def get_agent(self, agent_id: Union[str, uuid.UUID]) -> AgentModel:
|
|
136
|
-
response_data = await self._request("GET", f"agents/get/{str(agent_id)}", response_model=AgentModel)
|
|
137
|
-
return response_data
|
|
138
|
-
|
|
139
|
-
async def delete_agent(self, agent_id: Union[str, uuid.UUID]) -> DeletionResponse:
|
|
140
|
-
response_data = await self._request("DELETE", f"agents/delete/{str(agent_id)}", response_model=DeletionResponse)
|
|
141
|
-
return response_data
|
|
142
|
-
|
|
143
|
-
async def create_data_item(self, agent_id: Union[str, uuid.UUID], data_type: str, content: Any) -> DataItem:
|
|
144
|
-
"""Creates a new data item for a specific agent."""
|
|
145
|
-
payload = CreateDataItemPayload(data_type=data_type, content=content)
|
|
146
|
-
response_data = await self._request("POST", f"data_item/create/{str(agent_id)}", json=payload.model_dump(mode='json'), response_model=DataItem)
|
|
147
|
-
return response_data
|
|
148
|
-
|
|
149
|
-
async def get_data_item(self, data_item_id: Union[str, uuid.UUID]) -> DataItem:
|
|
150
|
-
response_data = await self._request("GET", f"data_item/get/{str(data_item_id)}", response_model=DataItem)
|
|
151
|
-
return response_data
|
|
152
|
-
|
|
153
|
-
async def list_data_items(self, agent_id: Union[str, uuid.UUID], data_type: Optional[str] = None) -> List[DataItem]:
|
|
154
|
-
params = {}
|
|
155
|
-
if data_type:
|
|
156
|
-
params["data_type"] = data_type
|
|
157
|
-
agent_id_str = str(agent_id)
|
|
158
|
-
raw_response = await self._request("GET", f"data_item/list/{agent_id_str}", params=params)
|
|
159
|
-
return [DataItem(**item) for item in raw_response.json()]
|
|
160
|
-
|
|
161
|
-
async def update_data_item(self, data_item_id: Union[str, uuid.UUID], content: Any) -> DataItem:
|
|
162
|
-
"""Updates an existing data item."""
|
|
163
|
-
payload = UpdateDataItemPayload(content=content)
|
|
164
|
-
response_data = await self._request("POST", f"data_item/update/{str(data_item_id)}", json=payload.model_dump(), response_model=DataItem)
|
|
165
|
-
return response_data
|
|
166
|
-
|
|
167
|
-
async def delete_data_item(self, data_item_id: Union[str, uuid.UUID]) -> DeletionResponse:
|
|
168
|
-
response_data = await self._request("DELETE", f"data_item/delete/{str(data_item_id)}", response_model=DeletionResponse)
|
|
169
|
-
return response_data
|
|
170
|
-
|
|
171
|
-
async def generate_qual_response(self, agent_id: uuid.UUID, question: str, image_url: Optional[str] = None) -> QualGenerationResponse:
|
|
172
|
-
"""Generates a qualitative response from an agent based on a question."""
|
|
173
|
-
endpoint = f"/generation/qual/{str(agent_id)}"
|
|
174
|
-
request_payload = QualGenerationRequest(question=question, image_url=image_url)
|
|
175
|
-
response_data = await self._request(
|
|
176
|
-
"POST",
|
|
177
|
-
endpoint,
|
|
178
|
-
json=request_payload.model_dump(),
|
|
179
|
-
response_model=QualGenerationResponse
|
|
180
|
-
)
|
|
181
|
-
return response_data
|
|
182
|
-
|
|
183
|
-
async def generate_mc_response(self, agent_id: uuid.UUID, question: str, options: List[str], image_url: Optional[str] = None) -> MCGenerationResponse:
|
|
184
|
-
"""Generates a multiple-choice response from an agent."""
|
|
185
|
-
endpoint = f"generation/mc/{str(agent_id)}"
|
|
186
|
-
request_payload = MCGenerationRequest(question=question, options=options, image_url=image_url)
|
|
187
|
-
response_data = await self._request(
|
|
188
|
-
"POST",
|
|
189
|
-
endpoint,
|
|
190
|
-
json=request_payload.model_dump(),
|
|
191
|
-
response_model=MCGenerationResponse
|
|
192
|
-
)
|
|
193
|
-
return response_data
|
|
194
|
-
|
|
195
|
-
async def aclose(self):
|
|
196
|
-
await self._client.aclose()
|
|
197
|
-
|
|
198
|
-
async def __aenter__(self):
|
|
199
|
-
return self
|
|
200
|
-
|
|
201
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
202
|
-
await self.aclose()
|
simile-0.2.6/simile/models.py
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
from typing import List, Dict, Any, Optional
|
|
2
|
-
from pydantic import BaseModel, Field
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
import uuid
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class Population(BaseModel):
|
|
8
|
-
population_id: uuid.UUID
|
|
9
|
-
name: str
|
|
10
|
-
description: Optional[str] = None
|
|
11
|
-
created_at: datetime
|
|
12
|
-
updated_at: datetime
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class DataItem(BaseModel):
|
|
16
|
-
id: uuid.UUID
|
|
17
|
-
agent_id: uuid.UUID
|
|
18
|
-
data_type: str
|
|
19
|
-
content: Any
|
|
20
|
-
created_at: datetime
|
|
21
|
-
updated_at: datetime
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class Agent(BaseModel):
|
|
25
|
-
agent_id: uuid.UUID
|
|
26
|
-
name: str
|
|
27
|
-
population_id: Optional[uuid.UUID] = None
|
|
28
|
-
created_at: datetime
|
|
29
|
-
updated_at: datetime
|
|
30
|
-
data_items: List[DataItem] = Field(default_factory=list)
|
|
31
|
-
|
|
32
|
-
class CreatePopulationPayload(BaseModel):
|
|
33
|
-
name: str
|
|
34
|
-
description: Optional[str] = None
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class InitialDataItemPayload(BaseModel):
|
|
38
|
-
data_type: str
|
|
39
|
-
content: Any
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class CreateAgentPayload(BaseModel):
|
|
43
|
-
name: str
|
|
44
|
-
population_id: Optional[uuid.UUID] = None
|
|
45
|
-
agent_data: Optional[List[InitialDataItemPayload]] = None
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class CreateDataItemPayload(BaseModel):
|
|
49
|
-
data_type: str
|
|
50
|
-
content: Any
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class UpdateDataItemPayload(BaseModel):
|
|
54
|
-
content: Any
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
class DeletionResponse(BaseModel):
|
|
58
|
-
message: str
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# --- Generation Operation Models ---
|
|
62
|
-
class QualGenerationRequest(BaseModel):
|
|
63
|
-
question: str
|
|
64
|
-
image_url: Optional[str] = None
|
|
65
|
-
|
|
66
|
-
class QualGenerationResponse(BaseModel):
|
|
67
|
-
question: str
|
|
68
|
-
answer: str
|
|
69
|
-
|
|
70
|
-
class MCGenerationRequest(BaseModel):
|
|
71
|
-
question: str
|
|
72
|
-
options: List[str]
|
|
73
|
-
image_url: Optional[str] = None
|
|
74
|
-
|
|
75
|
-
class MCGenerationResponse(BaseModel):
|
|
76
|
-
question: str
|
|
77
|
-
options: List[str]
|
|
78
|
-
chosen_option: str
|
|
79
|
-
|
|
80
|
-
class AddContextRequest(BaseModel):
|
|
81
|
-
context: str
|
|
82
|
-
|
|
83
|
-
class AddContextResponse(BaseModel):
|
|
84
|
-
message: str
|
|
85
|
-
session_id: uuid.UUID
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
# --- Survey Session Models ---
|
|
89
|
-
class ConversationTurn(BaseModel):
|
|
90
|
-
type: str # "qual" or "mc"
|
|
91
|
-
question: str
|
|
92
|
-
options: Optional[List[str]] = None
|
|
93
|
-
answer: Optional[str] = None
|
|
94
|
-
chosen_option: Optional[str] = None
|
|
95
|
-
timestamp: datetime
|
|
96
|
-
|
|
97
|
-
class SurveySessionCreateResponse(BaseModel):
|
|
98
|
-
id: uuid.UUID # Session ID
|
|
99
|
-
agent_id: uuid.UUID
|
|
100
|
-
created_at: datetime
|
|
101
|
-
status: str
|
|
102
|
-
|
|
103
|
-
class SurveySessionCloseResponse(BaseModel):
|
|
104
|
-
id: uuid.UUID # Session ID
|
|
105
|
-
status: str
|
|
106
|
-
updated_at: datetime
|
|
107
|
-
message: Optional[str] = None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|