simile 0.2.11__tar.gz → 0.2.13__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.11 → simile-0.2.13}/PKG-INFO +1 -1
- {simile-0.2.11 → simile-0.2.13}/pyproject.toml +1 -1
- {simile-0.2.11 → simile-0.2.13}/simile/__init__.py +3 -1
- simile-0.2.13/simile/auth_client.py +270 -0
- {simile-0.2.11 → simile-0.2.13}/simile/client.py +9 -2
- {simile-0.2.11 → simile-0.2.13}/simile/models.py +2 -0
- {simile-0.2.11 → simile-0.2.13}/simile/resources.py +47 -1
- {simile-0.2.11 → simile-0.2.13}/simile.egg-info/PKG-INFO +1 -1
- {simile-0.2.11 → simile-0.2.13}/simile.egg-info/SOURCES.txt +1 -0
- {simile-0.2.11 → simile-0.2.13}/LICENSE +0 -0
- {simile-0.2.11 → simile-0.2.13}/README.md +0 -0
- {simile-0.2.11 → simile-0.2.13}/setup.cfg +0 -0
- {simile-0.2.11 → simile-0.2.13}/setup.py +0 -0
- {simile-0.2.11 → simile-0.2.13}/simile/exceptions.py +0 -0
- {simile-0.2.11 → simile-0.2.13}/simile.egg-info/dependency_links.txt +0 -0
- {simile-0.2.11 → simile-0.2.13}/simile.egg-info/requires.txt +0 -0
- {simile-0.2.11 → simile-0.2.13}/simile.egg-info/top_level.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from .client import Simile
|
|
2
|
+
from .auth_client import SimileAuth
|
|
2
3
|
from .models import (
|
|
3
4
|
Population, Agent, DataItem,
|
|
4
5
|
CreatePopulationPayload, CreateAgentPayload, CreateDataItemPayload, UpdateDataItemPayload,
|
|
@@ -17,6 +18,7 @@ from .exceptions import (
|
|
|
17
18
|
|
|
18
19
|
__all__ = [
|
|
19
20
|
"Simile",
|
|
21
|
+
"SimileAuth",
|
|
20
22
|
"Population", "Agent", "DataItem",
|
|
21
23
|
"CreatePopulationPayload", "CreateAgentPayload", "CreateDataItemPayload", "UpdateDataItemPayload",
|
|
22
24
|
"DeletionResponse",
|
|
@@ -25,4 +27,4 @@ __all__ = [
|
|
|
25
27
|
"SimileAPIError", "SimileAuthenticationError", "SimileNotFoundError", "SimileBadRequestError"
|
|
26
28
|
]
|
|
27
29
|
|
|
28
|
-
__version__ = "0.
|
|
30
|
+
__version__ = "0.2.13"
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Authentication client for Simile API using bearer token authentication.
|
|
3
|
+
|
|
4
|
+
This client handles authentication-related operations using Google OAuth tokens
|
|
5
|
+
rather than API keys.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from httpx import AsyncClient
|
|
10
|
+
from typing import List, Optional, Union
|
|
11
|
+
import uuid
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
|
|
14
|
+
from .models import BaseModel
|
|
15
|
+
from .exceptions import (
|
|
16
|
+
SimileAPIError,
|
|
17
|
+
SimileAuthenticationError,
|
|
18
|
+
SimileNotFoundError,
|
|
19
|
+
SimileBadRequestError,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Import the models from auth_api_endpoints
|
|
24
|
+
# In production, these would be in a shared models file
|
|
25
|
+
class UserInfo(BaseModel):
|
|
26
|
+
"""User information from Google authentication"""
|
|
27
|
+
|
|
28
|
+
user_id: str
|
|
29
|
+
email: str
|
|
30
|
+
name: str
|
|
31
|
+
picture: Optional[str] = None
|
|
32
|
+
created_at: datetime
|
|
33
|
+
last_login: datetime
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class APIKey(BaseModel):
|
|
37
|
+
"""API Key model"""
|
|
38
|
+
|
|
39
|
+
key_id: uuid.UUID
|
|
40
|
+
name: str
|
|
41
|
+
key_prefix: str
|
|
42
|
+
created_at: datetime
|
|
43
|
+
last_used: Optional[datetime] = None
|
|
44
|
+
expires_at: Optional[datetime] = None
|
|
45
|
+
is_active: bool = True
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class APIKeyCreateResponse(BaseModel):
|
|
49
|
+
"""Response when creating a new API key"""
|
|
50
|
+
|
|
51
|
+
key_id: uuid.UUID
|
|
52
|
+
name: str
|
|
53
|
+
key: str
|
|
54
|
+
key_prefix: str
|
|
55
|
+
created_at: datetime
|
|
56
|
+
expires_at: Optional[datetime] = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class PopulationAccess(BaseModel):
|
|
60
|
+
"""Population access information"""
|
|
61
|
+
|
|
62
|
+
population_id: uuid.UUID
|
|
63
|
+
name: str
|
|
64
|
+
description: Optional[str] = None
|
|
65
|
+
role: str
|
|
66
|
+
created_at: datetime
|
|
67
|
+
member_count: int
|
|
68
|
+
agent_count: int
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class PopulationShareResponse(BaseModel):
|
|
72
|
+
"""Response when creating a share code"""
|
|
73
|
+
|
|
74
|
+
share_code: str
|
|
75
|
+
population_id: uuid.UUID
|
|
76
|
+
role: str
|
|
77
|
+
expires_at: datetime
|
|
78
|
+
max_uses: Optional[int] = None
|
|
79
|
+
created_at: datetime
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class PopulationJoinResponse(BaseModel):
|
|
83
|
+
"""Response when joining a population"""
|
|
84
|
+
|
|
85
|
+
population_id: uuid.UUID
|
|
86
|
+
name: str
|
|
87
|
+
description: Optional[str] = None
|
|
88
|
+
role: str
|
|
89
|
+
message: str = "Successfully joined population"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
DEFAULT_BASE_URL = "https://api.simile.ai/api/v1"
|
|
93
|
+
TIMEOUT_CONFIG = httpx.Timeout(5.0, read=30.0, write=30.0, pool=30.0)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class SimileAuth:
|
|
97
|
+
"""
|
|
98
|
+
Authentication client for Simile API.
|
|
99
|
+
|
|
100
|
+
This client uses bearer token authentication from Google OAuth
|
|
101
|
+
instead of API keys.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(self, bearer_token: str, base_url: str = DEFAULT_BASE_URL):
|
|
105
|
+
"""
|
|
106
|
+
Initialize the authentication client.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
bearer_token: The Google OAuth bearer token
|
|
110
|
+
base_url: The base URL of the API
|
|
111
|
+
"""
|
|
112
|
+
if not bearer_token:
|
|
113
|
+
raise ValueError("Bearer token is required.")
|
|
114
|
+
self.bearer_token = bearer_token
|
|
115
|
+
self.base_url = base_url.rstrip("/")
|
|
116
|
+
self._client = AsyncClient(
|
|
117
|
+
headers={"Authorization": f"Bearer {self.bearer_token}"},
|
|
118
|
+
timeout=TIMEOUT_CONFIG,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
async def _request(
|
|
122
|
+
self, method: str, endpoint: str, **kwargs
|
|
123
|
+
) -> Union[httpx.Response, BaseModel]:
|
|
124
|
+
"""Make an HTTP request to the API."""
|
|
125
|
+
url = f"{self.base_url}/{endpoint.lstrip('/')}"
|
|
126
|
+
response_model_cls = kwargs.pop("response_model", None)
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
response = await self._client.request(method, url, **kwargs)
|
|
130
|
+
response.raise_for_status()
|
|
131
|
+
|
|
132
|
+
if response_model_cls:
|
|
133
|
+
return response_model_cls(**response.json())
|
|
134
|
+
else:
|
|
135
|
+
return response
|
|
136
|
+
except httpx.HTTPStatusError as e:
|
|
137
|
+
status_code = e.response.status_code
|
|
138
|
+
try:
|
|
139
|
+
error_data = e.response.json()
|
|
140
|
+
detail = error_data.get("detail", e.response.text)
|
|
141
|
+
except Exception:
|
|
142
|
+
detail = e.response.text
|
|
143
|
+
|
|
144
|
+
if status_code == 401:
|
|
145
|
+
raise SimileAuthenticationError(detail=detail)
|
|
146
|
+
elif status_code == 404:
|
|
147
|
+
raise SimileNotFoundError(detail=detail)
|
|
148
|
+
elif status_code == 400:
|
|
149
|
+
raise SimileBadRequestError(detail=detail)
|
|
150
|
+
else:
|
|
151
|
+
raise SimileAPIError(
|
|
152
|
+
f"API request failed: {e}", status_code=status_code, detail=detail
|
|
153
|
+
)
|
|
154
|
+
except httpx.RequestError as e:
|
|
155
|
+
raise SimileAPIError(f"Request error: {type(e).__name__}: {e}")
|
|
156
|
+
|
|
157
|
+
async def get_current_user(self) -> UserInfo:
|
|
158
|
+
"""Get current user information."""
|
|
159
|
+
response = await self._request("GET", "auth/me", response_model=UserInfo)
|
|
160
|
+
return response
|
|
161
|
+
|
|
162
|
+
async def list_api_keys(self) -> List[APIKey]:
|
|
163
|
+
"""List all API keys for the current user."""
|
|
164
|
+
response = await self._request("GET", "auth/api-keys")
|
|
165
|
+
return [APIKey(**key_data) for key_data in response.json()]
|
|
166
|
+
|
|
167
|
+
async def create_api_key(
|
|
168
|
+
self, name: str, expires_in_days: Optional[int] = None
|
|
169
|
+
) -> APIKeyCreateResponse:
|
|
170
|
+
"""
|
|
171
|
+
Create a new API key.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
name: Name for the API key
|
|
175
|
+
expires_in_days: Optional expiration time in days
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
APIKeyCreateResponse with the full key (only shown once)
|
|
179
|
+
"""
|
|
180
|
+
payload = {"name": name}
|
|
181
|
+
if expires_in_days is not None:
|
|
182
|
+
payload["expires_in_days"] = expires_in_days
|
|
183
|
+
|
|
184
|
+
response = await self._request(
|
|
185
|
+
"POST", "auth/api-keys", json=payload, response_model=APIKeyCreateResponse
|
|
186
|
+
)
|
|
187
|
+
return response
|
|
188
|
+
|
|
189
|
+
async def delete_api_key(self, key_id: Union[str, uuid.UUID]) -> dict:
|
|
190
|
+
"""
|
|
191
|
+
Delete an API key.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
key_id: The ID of the API key to delete
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Success message
|
|
198
|
+
"""
|
|
199
|
+
response = await self._request("DELETE", f"auth/api-keys/{str(key_id)}")
|
|
200
|
+
return response.json()
|
|
201
|
+
|
|
202
|
+
async def list_accessible_populations(self) -> List[PopulationAccess]:
|
|
203
|
+
"""List all populations the current user has access to."""
|
|
204
|
+
response = await self._request("GET", "auth/populations")
|
|
205
|
+
return [PopulationAccess(**pop_data) for pop_data in response.json()]
|
|
206
|
+
|
|
207
|
+
async def create_population_share_code(
|
|
208
|
+
self,
|
|
209
|
+
population_id: Union[str, uuid.UUID],
|
|
210
|
+
role: str = "viewer",
|
|
211
|
+
expires_in_hours: Optional[int] = 24,
|
|
212
|
+
max_uses: Optional[int] = None,
|
|
213
|
+
) -> PopulationShareResponse:
|
|
214
|
+
"""
|
|
215
|
+
Create a share code for a population.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
population_id: The ID of the population to share
|
|
219
|
+
role: The role to grant (viewer or editor)
|
|
220
|
+
expires_in_hours: Hours until the code expires (default 24)
|
|
221
|
+
max_uses: Maximum number of times the code can be used
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
PopulationShareResponse with the share code
|
|
225
|
+
"""
|
|
226
|
+
payload = {
|
|
227
|
+
"population_id": str(population_id),
|
|
228
|
+
"role": role,
|
|
229
|
+
"expires_in_hours": expires_in_hours,
|
|
230
|
+
}
|
|
231
|
+
if max_uses is not None:
|
|
232
|
+
payload["max_uses"] = max_uses
|
|
233
|
+
|
|
234
|
+
response = await self._request(
|
|
235
|
+
"POST",
|
|
236
|
+
"auth/populations/share",
|
|
237
|
+
json=payload,
|
|
238
|
+
response_model=PopulationShareResponse,
|
|
239
|
+
)
|
|
240
|
+
return response
|
|
241
|
+
|
|
242
|
+
async def join_population_with_share_code(
|
|
243
|
+
self, share_code: str
|
|
244
|
+
) -> PopulationJoinResponse:
|
|
245
|
+
"""
|
|
246
|
+
Join a population using a share code.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
share_code: The share code to use
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
PopulationJoinResponse with population details
|
|
253
|
+
"""
|
|
254
|
+
response = await self._request(
|
|
255
|
+
"POST",
|
|
256
|
+
"auth/populations/join",
|
|
257
|
+
json={"share_code": share_code},
|
|
258
|
+
response_model=PopulationJoinResponse,
|
|
259
|
+
)
|
|
260
|
+
return response
|
|
261
|
+
|
|
262
|
+
async def aclose(self):
|
|
263
|
+
"""Close the HTTP client."""
|
|
264
|
+
await self._client.aclose()
|
|
265
|
+
|
|
266
|
+
async def __aenter__(self):
|
|
267
|
+
return self
|
|
268
|
+
|
|
269
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
270
|
+
await self.aclose()
|
|
@@ -29,7 +29,7 @@ from .exceptions import (
|
|
|
29
29
|
SimileBadRequestError,
|
|
30
30
|
)
|
|
31
31
|
|
|
32
|
-
DEFAULT_BASE_URL = "https://
|
|
32
|
+
DEFAULT_BASE_URL = "https://api.simile.ai/api/v1"
|
|
33
33
|
TIMEOUT_CONFIG = httpx.Timeout(5.0, read=30.0, write=30.0, pool=30.0)
|
|
34
34
|
|
|
35
35
|
|
|
@@ -283,11 +283,18 @@ class Simile:
|
|
|
283
283
|
self,
|
|
284
284
|
agent_id: uuid.UUID,
|
|
285
285
|
question: str,
|
|
286
|
+
data_types: Optional[List[str]] = None,
|
|
287
|
+
exclude_data_types: Optional[List[str]] = None,
|
|
286
288
|
images: Optional[Dict[str, str]] = None,
|
|
287
289
|
) -> QualGenerationResponse:
|
|
288
290
|
"""Generates a qualitative response from an agent based on a question."""
|
|
289
291
|
endpoint = f"/generation/qual/{str(agent_id)}"
|
|
290
|
-
request_payload = QualGenerationRequest(
|
|
292
|
+
request_payload = QualGenerationRequest(
|
|
293
|
+
question=question,
|
|
294
|
+
data_types=data_types,
|
|
295
|
+
exclude_data_types=exclude_data_types,
|
|
296
|
+
images=images,
|
|
297
|
+
)
|
|
291
298
|
response_data = await self._request(
|
|
292
299
|
"POST",
|
|
293
300
|
endpoint,
|
|
@@ -63,6 +63,8 @@ class DeletionResponse(BaseModel):
|
|
|
63
63
|
# --- Generation Operation Models ---
|
|
64
64
|
class QualGenerationRequest(BaseModel):
|
|
65
65
|
question: str
|
|
66
|
+
data_types: Optional[List[str]] = None
|
|
67
|
+
exclude_data_types: Optional[List[str]] = None
|
|
66
68
|
images: Optional[Dict[str, str]] = (
|
|
67
69
|
None # Dict of {description: url} for multiple images
|
|
68
70
|
)
|
|
@@ -81,7 +81,12 @@ class SurveySession:
|
|
|
81
81
|
) -> QualGenerationResponse:
|
|
82
82
|
"""Generates a qualitative response within this survey session."""
|
|
83
83
|
endpoint = f"sessions/{str(self._id)}/qual"
|
|
84
|
-
payload = QualGenerationRequest(
|
|
84
|
+
payload = QualGenerationRequest(
|
|
85
|
+
question=question,
|
|
86
|
+
data_types=None,
|
|
87
|
+
exclude_data_types=None,
|
|
88
|
+
images=images,
|
|
89
|
+
)
|
|
85
90
|
return await self._client._request(
|
|
86
91
|
"POST",
|
|
87
92
|
endpoint,
|
|
@@ -119,3 +124,44 @@ class SurveySession:
|
|
|
119
124
|
return await self._client._request(
|
|
120
125
|
"POST", endpoint, response_model=SurveySessionCloseResponse
|
|
121
126
|
)
|
|
127
|
+
|
|
128
|
+
async def add_historical_mc_turn(
|
|
129
|
+
self,
|
|
130
|
+
question: str,
|
|
131
|
+
options: List[str],
|
|
132
|
+
chosen_option: str,
|
|
133
|
+
timestamp: Optional[str] = None,
|
|
134
|
+
) -> Dict:
|
|
135
|
+
"""Adds a historical multiple choice turn to this session with a pre-specified answer.
|
|
136
|
+
|
|
137
|
+
This method allows you to add a multiple choice question-answer pair to the session's
|
|
138
|
+
conversation history without generating a new response. This is useful for recreating
|
|
139
|
+
conversation history or adding context from previous interactions.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
question: The multiple choice question text
|
|
143
|
+
options: List of answer options
|
|
144
|
+
chosen_option: The option that was selected
|
|
145
|
+
timestamp: Optional ISO timestamp of when this interaction occurred
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Dictionary with success status and the added turn details
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
Simile.APIError: If the API request fails
|
|
152
|
+
"""
|
|
153
|
+
endpoint = f"sessions/{str(self._id)}/historical-mc"
|
|
154
|
+
payload = {
|
|
155
|
+
"question": question,
|
|
156
|
+
"options": options,
|
|
157
|
+
"chosen_option": chosen_option,
|
|
158
|
+
}
|
|
159
|
+
if timestamp:
|
|
160
|
+
payload["timestamp"] = timestamp
|
|
161
|
+
|
|
162
|
+
return await self._client._request(
|
|
163
|
+
"POST",
|
|
164
|
+
endpoint,
|
|
165
|
+
json=payload,
|
|
166
|
+
response_model=None, # Return raw dict since we don't have a specific model
|
|
167
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|