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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simile
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: Package for interfacing with Simile AI agents for simulation
5
5
  Author-email: Simile AI <cqz@simile.ai>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simile"
7
- version = "0.2.6"
7
+ version = "0.2.8"
8
8
  authors = [
9
9
  { name="Simile AI", email="cqz@simile.ai" },
10
10
  ]
@@ -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 generate_qual_response(self, question: str, image_url: Optional[str] = None) -> QualGenerationResponse:
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,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simile
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: Package for interfacing with Simile AI agents for simulation
5
5
  Author-email: Simile AI <cqz@simile.ai>
6
6
  License: MIT
@@ -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()
@@ -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