airia 0.1.29__py3-none-any.whl → 0.1.31__py3-none-any.whl

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.
@@ -1,6 +1,9 @@
1
- from typing import Any, Dict, List, Literal, Optional, Union, overload
1
+ from typing import Any, Dict, List, Literal, Optional, Type, Union, overload
2
+
3
+ from pydantic import BaseModel
2
4
 
3
5
  from ...types._api_version import ApiVersion
6
+ from ...types._structured_output import create_schema_system_message, parse_response_to_model
4
7
  from ...types.api.pipeline_execution import (
5
8
  PipelineExecutionAsyncStreamedResponse,
6
9
  PipelineExecutionResponse,
@@ -80,6 +83,7 @@ class AsyncPipelineExecution(BasePipelineExecution):
80
83
  additional_info: Optional[List[Any]] = None,
81
84
  prompt_variables: Optional[Dict[str, Any]] = None,
82
85
  voice_enabled: bool = False,
86
+ output_schema: Optional[Type[BaseModel]] = None,
83
87
  correlation_id: Optional[str] = None,
84
88
  ) -> PipelineExecutionResponse: ...
85
89
 
@@ -103,6 +107,7 @@ class AsyncPipelineExecution(BasePipelineExecution):
103
107
  additional_info: Optional[List[Any]] = None,
104
108
  prompt_variables: Optional[Dict[str, Any]] = None,
105
109
  voice_enabled: bool = False,
110
+ output_schema: Optional[Type[BaseModel]] = None,
106
111
  correlation_id: Optional[str] = None,
107
112
  ) -> PipelineExecutionAsyncStreamedResponse: ...
108
113
 
@@ -125,6 +130,7 @@ class AsyncPipelineExecution(BasePipelineExecution):
125
130
  additional_info: Optional[List[Any]] = None,
126
131
  prompt_variables: Optional[Dict[str, Any]] = None,
127
132
  voice_enabled: bool = False,
133
+ output_schema: Optional[Type[BaseModel]] = None,
128
134
  correlation_id: Optional[str] = None,
129
135
  ) -> Union[
130
136
  PipelineExecutionResponse,
@@ -151,6 +157,7 @@ class AsyncPipelineExecution(BasePipelineExecution):
151
157
  additional_info: Optional additional information.
152
158
  prompt_variables: Optional variables to be used in the prompt.
153
159
  voice_enabled: Whether the request came through the airia-voice-proxy. Default is False.
160
+ output_schema: Optional Pydantic model class for structured output.
154
161
  correlation_id: Optional correlation ID for request tracing. If not provided,
155
162
  one will be generated automatically.
156
163
 
@@ -161,7 +168,8 @@ class AsyncPipelineExecution(BasePipelineExecution):
161
168
  AiriaAPIError: If the API request fails with details about the error.
162
169
  aiohttp.ClientError: For other request-related errors.
163
170
 
164
- Example:
171
+ Examples:
172
+ Basic usage:
165
173
  ```python
166
174
  client = AiriaAsyncClient(api_key="your_api_key")
167
175
  response = await client.pipeline_execution.execute_pipeline(
@@ -170,6 +178,21 @@ class AsyncPipelineExecution(BasePipelineExecution):
170
178
  )
171
179
  print(response.result)
172
180
  ```
181
+
182
+ With structured output:
183
+ ```python
184
+ from pydantic import BaseModel
185
+
186
+ class PersonInfo(BaseModel):
187
+ name: str
188
+ age: int
189
+
190
+ response = await client.pipeline_execution.execute_pipeline(
191
+ pipeline_id="pipeline_123",
192
+ user_input="Extract person info",
193
+ output_schema=PersonInfo
194
+ )
195
+ ```
173
196
  """
174
197
  # Validate user_input parameter
175
198
  if not user_input:
@@ -182,6 +205,15 @@ class AsyncPipelineExecution(BasePipelineExecution):
182
205
  if images or files:
183
206
  file_urls, image_urls = await self._upload_files(files or [], images or [])
184
207
 
208
+ # Handle structured output by injecting schema as system message
209
+ modified_in_memory_messages = in_memory_messages
210
+ if output_schema is not None:
211
+ # Create a copy of in_memory_messages if it exists, otherwise create new list
212
+ modified_in_memory_messages = list(in_memory_messages) if in_memory_messages else []
213
+ # Insert schema instruction as first system message
214
+ schema_message = create_schema_system_message(output_schema)
215
+ modified_in_memory_messages.insert(0, schema_message)
216
+
185
217
  request_data = self._pre_execute_pipeline(
186
218
  pipeline_id=pipeline_id,
187
219
  user_input=user_input,
@@ -194,12 +226,13 @@ class AsyncPipelineExecution(BasePipelineExecution):
194
226
  files=file_urls,
195
227
  data_source_folders=data_source_folders,
196
228
  data_source_files=data_source_files,
197
- in_memory_messages=in_memory_messages,
229
+ in_memory_messages=modified_in_memory_messages,
198
230
  current_date_time=current_date_time,
199
231
  save_history=save_history,
200
232
  additional_info=additional_info,
201
233
  prompt_variables=prompt_variables,
202
234
  voice_enabled=voice_enabled,
235
+ output_configuration=None, # Not using output_configuration anymore
203
236
  correlation_id=correlation_id,
204
237
  api_version=ApiVersion.V2.value,
205
238
  )
@@ -210,7 +243,11 @@ class AsyncPipelineExecution(BasePipelineExecution):
210
243
  )
211
244
 
212
245
  if not async_output:
213
- return PipelineExecutionResponse(**resp)
246
+ response = PipelineExecutionResponse(**resp)
247
+ # Parse response to Pydantic model if output_schema was provided
248
+ if output_schema is not None and response.result:
249
+ response.result = parse_response_to_model(response.result, output_schema)
250
+ return response
214
251
 
215
252
  return PipelineExecutionAsyncStreamedResponse(stream=resp)
216
253
 
@@ -237,6 +274,7 @@ class AsyncPipelineExecution(BasePipelineExecution):
237
274
  images: Optional[List[str]] = None,
238
275
  in_memory_messages: Optional[List[Dict[str, Any]]] = None,
239
276
  output_configuration: Optional[Dict[str, Any]] = None,
277
+ output_schema: Optional[Type[BaseModel]] = None,
240
278
  prompt_variables: Optional[Dict[str, Any]] = None,
241
279
  user_id: Optional[str] = None,
242
280
  user_input_id: Optional[str] = None,
@@ -267,6 +305,7 @@ class AsyncPipelineExecution(BasePipelineExecution):
267
305
  images: Optional[List[str]] = None,
268
306
  in_memory_messages: Optional[List[Dict[str, Any]]] = None,
269
307
  output_configuration: Optional[Dict[str, Any]] = None,
308
+ output_schema: Optional[Type[BaseModel]] = None,
270
309
  prompt_variables: Optional[Dict[str, Any]] = None,
271
310
  user_id: Optional[str] = None,
272
311
  user_input_id: Optional[str] = None,
@@ -296,6 +335,7 @@ class AsyncPipelineExecution(BasePipelineExecution):
296
335
  images: Optional[List[str]] = None,
297
336
  in_memory_messages: Optional[List[Dict[str, Any]]] = None,
298
337
  output_configuration: Optional[Dict[str, Any]] = None,
338
+ output_schema: Optional[Type[BaseModel]] = None,
299
339
  prompt_variables: Optional[Dict[str, Any]] = None,
300
340
  user_id: Optional[str] = None,
301
341
  user_input_id: Optional[str] = None,
@@ -332,7 +372,9 @@ class AsyncPipelineExecution(BasePipelineExecution):
332
372
  files: Optional list of file identifiers
333
373
  images: Optional list of image identifiers
334
374
  in_memory_messages: Optional list of in-memory messages
335
- output_configuration: Optional output configuration
375
+ output_configuration: Optional output configuration (raw dict format)
376
+ output_schema: Optional Pydantic model class for structured output.
377
+ If provided, takes precedence over output_configuration.
336
378
  prompt_variables: Optional prompt variables dictionary
337
379
  user_id: Optional user identifier
338
380
  user_input_id: Optional unique identifier for user input
@@ -349,7 +391,8 @@ class AsyncPipelineExecution(BasePipelineExecution):
349
391
  aiohttp.ClientError: For other request-related errors.
350
392
  ValueError: If required parameters are missing or invalid.
351
393
 
352
- Example:
394
+ Examples:
395
+ Basic usage:
353
396
  ```python
354
397
  client = AiriaAsyncClient(api_key="your_api_key")
355
398
  response = await client.pipeline_execution.execute_temporary_assistant(
@@ -363,6 +406,21 @@ class AsyncPipelineExecution(BasePipelineExecution):
363
406
  )
364
407
  print(response.result)
365
408
  ```
409
+
410
+ With structured output:
411
+ ```python
412
+ from pydantic import BaseModel
413
+
414
+ class WeatherInfo(BaseModel):
415
+ temperature: float
416
+ conditions: str
417
+
418
+ response = await client.pipeline_execution.execute_temporary_assistant(
419
+ model_parameters={...},
420
+ user_input="What's the weather?",
421
+ output_schema=WeatherInfo
422
+ )
423
+ ```
366
424
  """
367
425
  # Validate required parameters
368
426
  if not user_input:
@@ -378,6 +436,17 @@ class AsyncPipelineExecution(BasePipelineExecution):
378
436
  if images or files:
379
437
  file_urls, image_urls = await self._upload_files(files or [], images or [])
380
438
 
439
+ # Handle structured output by injecting schema as system message
440
+ modified_in_memory_messages = in_memory_messages
441
+ if output_schema is not None:
442
+ # Create a copy of in_memory_messages if it exists, otherwise create new list
443
+ modified_in_memory_messages = list(in_memory_messages) if in_memory_messages else []
444
+ # Insert schema instruction as first system message
445
+ schema_message = create_schema_system_message(output_schema)
446
+ modified_in_memory_messages.insert(0, schema_message)
447
+ # Don't use output_configuration when using output_schema
448
+ output_configuration = None
449
+
381
450
  request_data = self._pre_execute_temporary_assistant(
382
451
  model_parameters=model_parameters,
383
452
  user_input=user_input,
@@ -397,7 +466,7 @@ class AsyncPipelineExecution(BasePipelineExecution):
397
466
  external_user_id=external_user_id,
398
467
  files=file_urls,
399
468
  images=image_urls,
400
- in_memory_messages=in_memory_messages,
469
+ in_memory_messages=modified_in_memory_messages,
401
470
  output_configuration=output_configuration,
402
471
  prompt_variables=prompt_variables,
403
472
  user_id=user_id,
@@ -415,4 +484,8 @@ class AsyncPipelineExecution(BasePipelineExecution):
415
484
  if async_output:
416
485
  return TemporaryAssistantAsyncStreamedResponse(stream=resp)
417
486
 
418
- return TemporaryAssistantResponse(**resp)
487
+ response = TemporaryAssistantResponse(**resp)
488
+ # Parse response to Pydantic model if output_schema was provided
489
+ if output_schema is not None and response.result:
490
+ response.result = parse_response_to_model(str(response.result), output_schema)
491
+ return response
@@ -42,6 +42,7 @@ class BasePipelineExecution:
42
42
  additional_info: Optional[List[Any]] = None,
43
43
  prompt_variables: Optional[Dict[str, Any]] = None,
44
44
  voice_enabled: bool = False,
45
+ output_configuration: Optional[Dict[str, Any]] = None,
45
46
  correlation_id: Optional[str] = None,
46
47
  api_version: str = ApiVersion.V2.value,
47
48
  ):
@@ -69,6 +70,7 @@ class BasePipelineExecution:
69
70
  additional_info: Optional additional information
70
71
  prompt_variables: Optional prompt variables
71
72
  voice_enabled: Whether the request came through the airia-voice-proxy
73
+ output_configuration: Optional output configuration for structured output
72
74
  correlation_id: Optional correlation ID for tracing
73
75
  api_version: API version to use for the request
74
76
 
@@ -105,6 +107,7 @@ class BasePipelineExecution:
105
107
  "additionalInfo": additional_info,
106
108
  "promptVariables": prompt_variables,
107
109
  "voiceEnabled": voice_enabled,
110
+ "outputConfiguration": output_configuration,
108
111
  }
109
112
 
110
113
  request_data = self._request_handler.prepare_request(
@@ -1,6 +1,9 @@
1
- from typing import Any, Dict, List, Literal, Optional, Union, overload
1
+ from typing import Any, Dict, List, Literal, Optional, Type, Union, overload
2
+
3
+ from pydantic import BaseModel
2
4
 
3
5
  from ...types._api_version import ApiVersion
6
+ from ...types._structured_output import create_schema_system_message, parse_response_to_model
4
7
  from ...types.api.pipeline_execution import (
5
8
  PipelineExecutionResponse,
6
9
  PipelineExecutionStreamedResponse,
@@ -80,6 +83,7 @@ class PipelineExecution(BasePipelineExecution):
80
83
  additional_info: Optional[List[Any]] = None,
81
84
  prompt_variables: Optional[Dict[str, Any]] = None,
82
85
  voice_enabled: bool = False,
86
+ output_schema: Optional[Type[BaseModel]] = None,
83
87
  correlation_id: Optional[str] = None,
84
88
  ) -> PipelineExecutionResponse: ...
85
89
 
@@ -103,6 +107,7 @@ class PipelineExecution(BasePipelineExecution):
103
107
  additional_info: Optional[List[Any]] = None,
104
108
  prompt_variables: Optional[Dict[str, Any]] = None,
105
109
  voice_enabled: bool = False,
110
+ output_schema: Optional[Type[BaseModel]] = None,
106
111
  correlation_id: Optional[str] = None,
107
112
  ) -> PipelineExecutionStreamedResponse: ...
108
113
 
@@ -125,6 +130,7 @@ class PipelineExecution(BasePipelineExecution):
125
130
  additional_info: Optional[List[Any]] = None,
126
131
  prompt_variables: Optional[Dict[str, Any]] = None,
127
132
  voice_enabled: bool = False,
133
+ output_schema: Optional[Type[BaseModel]] = None,
128
134
  correlation_id: Optional[str] = None,
129
135
  ) -> Union[PipelineExecutionResponse, PipelineExecutionStreamedResponse]:
130
136
  """
@@ -148,6 +154,7 @@ class PipelineExecution(BasePipelineExecution):
148
154
  additional_info: Optional additional information.
149
155
  prompt_variables: Optional variables to be used in the prompt.
150
156
  voice_enabled: Whether the request came through the airia-voice-proxy. Default is False.
157
+ output_schema: Optional Pydantic model class for structured output.
151
158
  correlation_id: Optional correlation ID for request tracing. If not provided,
152
159
  one will be generated automatically.
153
160
 
@@ -158,7 +165,8 @@ class PipelineExecution(BasePipelineExecution):
158
165
  AiriaAPIError: If the API request fails with details about the error.
159
166
  requests.RequestException: For other request-related errors.
160
167
 
161
- Example:
168
+ Examples:
169
+ Basic usage:
162
170
  ```python
163
171
  client = AiriaClient(api_key="your_api_key")
164
172
  response = client.pipeline_execution.execute_pipeline(
@@ -167,6 +175,23 @@ class PipelineExecution(BasePipelineExecution):
167
175
  )
168
176
  print(response.result)
169
177
  ```
178
+
179
+ With structured output using a Pydantic model:
180
+ ```python
181
+ from pydantic import BaseModel
182
+
183
+ class PersonInfo(BaseModel):
184
+ name: str
185
+ age: int
186
+ occupation: str
187
+
188
+ response = client.pipeline_execution.execute_pipeline(
189
+ pipeline_id="pipeline_123",
190
+ user_input="Extract the person's information",
191
+ output_schema=PersonInfo
192
+ )
193
+ # Response will conform to PersonInfo schema
194
+ ```
170
195
  """
171
196
  # Validate user_input parameter
172
197
  if not user_input:
@@ -179,6 +204,15 @@ class PipelineExecution(BasePipelineExecution):
179
204
  if images or files:
180
205
  file_urls, image_urls = self._upload_files(files or [], images or [])
181
206
 
207
+ # Handle structured output by injecting schema as system message
208
+ modified_in_memory_messages = in_memory_messages
209
+ if output_schema is not None:
210
+ # Create a copy of in_memory_messages if it exists, otherwise create new list
211
+ modified_in_memory_messages = list(in_memory_messages) if in_memory_messages else []
212
+ # Insert schema instruction as first system message
213
+ schema_message = create_schema_system_message(output_schema)
214
+ modified_in_memory_messages.insert(0, schema_message)
215
+
182
216
  request_data = self._pre_execute_pipeline(
183
217
  pipeline_id=pipeline_id,
184
218
  user_input=user_input,
@@ -191,12 +225,13 @@ class PipelineExecution(BasePipelineExecution):
191
225
  files=file_urls,
192
226
  data_source_folders=data_source_folders,
193
227
  data_source_files=data_source_files,
194
- in_memory_messages=in_memory_messages,
228
+ in_memory_messages=modified_in_memory_messages,
195
229
  current_date_time=current_date_time,
196
230
  save_history=save_history,
197
231
  additional_info=additional_info,
198
232
  prompt_variables=prompt_variables,
199
233
  voice_enabled=voice_enabled,
234
+ output_configuration=None, # Not using output_configuration anymore
200
235
  correlation_id=correlation_id,
201
236
  api_version=ApiVersion.V2.value,
202
237
  )
@@ -207,7 +242,11 @@ class PipelineExecution(BasePipelineExecution):
207
242
  )
208
243
 
209
244
  if not async_output:
210
- return PipelineExecutionResponse(**resp)
245
+ response = PipelineExecutionResponse(**resp)
246
+ # Parse response to Pydantic model if output_schema was provided
247
+ if output_schema is not None and response.result:
248
+ response.result = parse_response_to_model(response.result, output_schema)
249
+ return response
211
250
 
212
251
  return PipelineExecutionStreamedResponse(stream=resp)
213
252
 
@@ -234,6 +273,7 @@ class PipelineExecution(BasePipelineExecution):
234
273
  images: Optional[List[str]] = None,
235
274
  in_memory_messages: Optional[List[Dict[str, Any]]] = None,
236
275
  output_configuration: Optional[Dict[str, Any]] = None,
276
+ output_schema: Optional[Type[BaseModel]] = None,
237
277
  prompt_variables: Optional[Dict[str, Any]] = None,
238
278
  user_id: Optional[str] = None,
239
279
  user_input_id: Optional[str] = None,
@@ -264,6 +304,7 @@ class PipelineExecution(BasePipelineExecution):
264
304
  images: Optional[List[str]] = None,
265
305
  in_memory_messages: Optional[List[Dict[str, Any]]] = None,
266
306
  output_configuration: Optional[Dict[str, Any]] = None,
307
+ output_schema: Optional[Type[BaseModel]] = None,
267
308
  prompt_variables: Optional[Dict[str, Any]] = None,
268
309
  user_id: Optional[str] = None,
269
310
  user_input_id: Optional[str] = None,
@@ -293,6 +334,7 @@ class PipelineExecution(BasePipelineExecution):
293
334
  images: Optional[List[str]] = None,
294
335
  in_memory_messages: Optional[List[Dict[str, Any]]] = None,
295
336
  output_configuration: Optional[Dict[str, Any]] = None,
337
+ output_schema: Optional[Type[BaseModel]] = None,
296
338
  prompt_variables: Optional[Dict[str, Any]] = None,
297
339
  user_id: Optional[str] = None,
298
340
  user_input_id: Optional[str] = None,
@@ -329,7 +371,9 @@ class PipelineExecution(BasePipelineExecution):
329
371
  files: Optional list of file identifiers
330
372
  images: Optional list of image identifiers
331
373
  in_memory_messages: Optional list of in-memory messages
332
- output_configuration: Optional output configuration
374
+ output_configuration: Optional output configuration (raw dict format)
375
+ output_schema: Optional Pydantic model class for structured output.
376
+ If provided, takes precedence over output_configuration.
333
377
  prompt_variables: Optional prompt variables dictionary
334
378
  user_id: Optional user identifier (GUID string or UUID)
335
379
  user_input_id: Optional unique identifier for user input (GUID string or UUID)
@@ -345,7 +389,8 @@ class PipelineExecution(BasePipelineExecution):
345
389
  requests.RequestException: For other request-related errors.
346
390
  ValueError: If required parameters are missing or invalid.
347
391
 
348
- Example:
392
+ Examples:
393
+ Basic usage:
349
394
  ```python
350
395
  client = AiriaClient(api_key="your_api_key")
351
396
  response = client.pipeline_execution.execute_temporary_assistant(
@@ -359,6 +404,23 @@ class PipelineExecution(BasePipelineExecution):
359
404
  )
360
405
  print(response.result)
361
406
  ```
407
+
408
+ With structured output using a Pydantic model:
409
+ ```python
410
+ from pydantic import BaseModel
411
+
412
+ class WeatherInfo(BaseModel):
413
+ temperature: float
414
+ conditions: str
415
+ humidity: int
416
+
417
+ response = client.pipeline_execution.execute_temporary_assistant(
418
+ model_parameters={...},
419
+ user_input="What's the weather like?",
420
+ output_schema=WeatherInfo
421
+ )
422
+ # Response will conform to WeatherInfo schema
423
+ ```
362
424
  """
363
425
  # Validate required parameters
364
426
  if not user_input:
@@ -374,6 +436,17 @@ class PipelineExecution(BasePipelineExecution):
374
436
  if images or files:
375
437
  file_urls, image_urls = self._upload_files(files or [], images or [])
376
438
 
439
+ # Handle structured output by injecting schema as system message
440
+ modified_in_memory_messages = in_memory_messages
441
+ if output_schema is not None:
442
+ # Create a copy of in_memory_messages if it exists, otherwise create new list
443
+ modified_in_memory_messages = list(in_memory_messages) if in_memory_messages else []
444
+ # Insert schema instruction as first system message
445
+ schema_message = create_schema_system_message(output_schema)
446
+ modified_in_memory_messages.insert(0, schema_message)
447
+ # Don't use output_configuration when using output_schema
448
+ output_configuration = None
449
+
377
450
  # Convert UUID objects to strings for API compatibility
378
451
  conversation_id_str = str(conversation_id) if conversation_id else conversation_id
379
452
  user_id_str = str(user_id) if user_id else user_id
@@ -398,7 +471,7 @@ class PipelineExecution(BasePipelineExecution):
398
471
  external_user_id=external_user_id,
399
472
  files=file_urls,
400
473
  images=image_urls,
401
- in_memory_messages=in_memory_messages,
474
+ in_memory_messages=modified_in_memory_messages,
402
475
  output_configuration=output_configuration,
403
476
  prompt_variables=prompt_variables,
404
477
  user_id=user_id_str,
@@ -416,4 +489,8 @@ class PipelineExecution(BasePipelineExecution):
416
489
  if async_output:
417
490
  return TemporaryAssistantStreamedResponse(stream=resp)
418
491
 
419
- return TemporaryAssistantResponse(**resp)
492
+ response = TemporaryAssistantResponse(**resp)
493
+ # Parse response to Pydantic model if output_schema was provided
494
+ if output_schema is not None and response.result:
495
+ response.result = parse_response_to_model(str(response.result), output_schema)
496
+ return response
airia/types/__init__.py CHANGED
@@ -0,0 +1,11 @@
1
+ """Type definitions for the Airia SDK."""
2
+
3
+ from airia.types._structured_output import (
4
+ create_schema_system_message,
5
+ parse_response_to_model,
6
+ )
7
+
8
+ __all__ = [
9
+ "create_schema_system_message",
10
+ "parse_response_to_model",
11
+ ]
@@ -0,0 +1,182 @@
1
+ """Helper utilities for structured output with Pydantic models."""
2
+
3
+ import json
4
+ import re
5
+ import uuid
6
+ from typing import Any, Dict, Type
7
+ from textwrap import dedent
8
+
9
+ from pydantic import BaseModel, ValidationError
10
+
11
+
12
+ def create_schema_system_message(model: Type[BaseModel]) -> Dict[str, Any]:
13
+ """
14
+ Create a system message that instructs the LLM to return structured output.
15
+
16
+ Args:
17
+ model: The Pydantic model class to generate schema from
18
+
19
+ Returns:
20
+ Dictionary representing a system message for in_memory_messages
21
+
22
+ Example:
23
+ ```python
24
+ from pydantic import BaseModel
25
+
26
+ class UserInfo(BaseModel):
27
+ name: str
28
+ age: int
29
+
30
+ message = create_schema_system_message(UserInfo)
31
+ # Use in in_memory_messages parameter
32
+ ```
33
+ """
34
+ schema = model.model_json_schema()
35
+
36
+ message_content = dedent(f"""As a genius expert, your task is to understand the content and provide the parsed objects in json that match the following json_schema:
37
+
38
+ {json.dumps(schema, indent=2, ensure_ascii=False)}
39
+
40
+ Make sure to return an instance of the JSON, not the schema itself""")
41
+
42
+ return {
43
+ "id": str(uuid.uuid4()),
44
+ "message": message_content,
45
+ "role": "system",
46
+ "toolRequests": [],
47
+ "toolResponses": [],
48
+ }
49
+
50
+
51
+ def remove_start_md_json(response_msg: str) -> str:
52
+ """
53
+ Checks the message for the listed start patterns and removes them if present.
54
+
55
+ Args:
56
+ response_msg: The response message to check.
57
+
58
+ Returns:
59
+ The response message without the start marker (if one was present).
60
+ """
61
+ start_pattern = re.compile(
62
+ r"^(```json\n|`json\n|```\n|`\n|```json|`json|```|`|json|json\n)"
63
+ )
64
+ match = start_pattern.match(response_msg)
65
+ if match:
66
+ response_msg = response_msg[match.end() :]
67
+
68
+ return response_msg
69
+
70
+
71
+ def remove_end_md_json(response_msg: str) -> str:
72
+ """
73
+ Checks the message for the listed end patterns and removes them if present.
74
+
75
+ Args:
76
+ response_msg: The response message to check.
77
+
78
+ Returns:
79
+ The response message without the end marker (if one was present).
80
+ """
81
+ end_pattern = re.compile(r"(\n```|\n`|```|`)$")
82
+ match = end_pattern.search(response_msg)
83
+ if match:
84
+ response_msg = response_msg[: match.start()]
85
+
86
+ return response_msg
87
+
88
+
89
+ def extract_json_from_string(response_msg: str) -> str:
90
+ """
91
+ Attempts to extract JSON (object or array) from within a larger string, not specific to markdown.
92
+
93
+ Args:
94
+ response_msg: The response message to check.
95
+
96
+ Returns:
97
+ The extracted JSON string if found, otherwise the original string.
98
+ """
99
+ json_pattern = re.compile(r"\{.*\}|\[.*\]", re.DOTALL)
100
+ match = json_pattern.search(response_msg)
101
+ if match:
102
+ return match.group(0)
103
+
104
+ return response_msg
105
+
106
+
107
+ def remove_markdown_json(response_msg: str) -> str:
108
+ """
109
+ Checks if the response message is in JSON format and removes Markdown formatting if present.
110
+
111
+ Args:
112
+ response_msg: The response message to check.
113
+
114
+ Returns:
115
+ The response message without Markdown formatting if present, or an error message.
116
+ """
117
+ response_msg = remove_start_md_json(response_msg)
118
+ response_msg = remove_end_md_json(response_msg)
119
+
120
+ # Validate if the remaining response message is valid JSON. If it's still not valid
121
+ # after removing the markdown notation, try to extract JSON from within the string.
122
+ try:
123
+ json.loads(response_msg)
124
+ return response_msg
125
+ except json.JSONDecodeError:
126
+ response_msg = extract_json_from_string(response_msg)
127
+ try:
128
+ json.loads(response_msg)
129
+ return response_msg
130
+ except json.JSONDecodeError:
131
+ return f"Invalid JSON response: {response_msg}"
132
+
133
+
134
+ def parse_response_to_model(response_text: str, model: Type[BaseModel]) -> BaseModel:
135
+ """
136
+ Parse a response string to a Pydantic model instance.
137
+
138
+ This function cleans markdown formatting from JSON responses and validates
139
+ the parsed data against the provided Pydantic model.
140
+
141
+ Args:
142
+ response_text: The raw response text from the LLM
143
+ model: The Pydantic model class to parse into
144
+
145
+ Returns:
146
+ An instance of the Pydantic model
147
+
148
+ Raises:
149
+ ValidationError: If the response doesn't match the model schema
150
+ ValueError: If the response is not valid JSON
151
+
152
+ Example:
153
+ ```python
154
+ from pydantic import BaseModel
155
+
156
+ class UserInfo(BaseModel):
157
+ name: str
158
+ age: int
159
+
160
+ response = '```json\\n{"name": "John", "age": 30}\\n```'
161
+ user = parse_response_to_model(response, UserInfo)
162
+ print(user.name) # "John"
163
+ ```
164
+ """
165
+ # Clean markdown formatting
166
+ cleaned_json = remove_markdown_json(response_text)
167
+
168
+ # Check for error message
169
+ if cleaned_json.startswith("Invalid JSON response:"):
170
+ raise ValueError(cleaned_json)
171
+
172
+ # Parse JSON
173
+ try:
174
+ data = json.loads(cleaned_json)
175
+ except json.JSONDecodeError as e:
176
+ raise ValueError(f"Failed to parse JSON: {e}") from e
177
+
178
+ # Validate against Pydantic model
179
+ try:
180
+ return model.model_validate(data)
181
+ except ValidationError as e:
182
+ raise ValidationError(f"Response does not match schema: {e}") from e
@@ -111,7 +111,7 @@ class ModelItem(BaseModel):
111
111
  source_type: str = Field(alias="sourceType")
112
112
  type: str
113
113
  provider: str
114
- tenant_id: str = Field(alias="tenantId")
114
+ tenant_id: Optional[str] = Field(None, alias="tenantId")
115
115
  project_id: Optional[str] = Field(None, alias="projectId")
116
116
  project_name: Optional[str] = Field(None, alias="projectName")
117
117
  project: Optional[ModelProject] = None
@@ -121,7 +121,7 @@ class ModelItem(BaseModel):
121
121
  has_tool_support: bool = Field(alias="hasToolSupport")
122
122
  has_stream_support: bool = Field(alias="hasStreamSupport")
123
123
  library_model_id: Optional[str] = Field(None, alias="libraryModelId")
124
- user_provided_details: ModelUserProvidedDetails = Field(alias="userProvidedDetails")
124
+ user_provided_details: Optional[ModelUserProvidedDetails] = Field(None, alias="userProvidedDetails")
125
125
  allow_airia_credentials: bool = Field(alias="allowAiriaCredentials")
126
126
  allow_byok_credentials: bool = Field(alias="allowBYOKCredentials")
127
127
  price_type: str = Field(alias="priceType")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: airia
3
- Version: 0.1.29
3
+ Version: 0.1.31
4
4
  Summary: Python SDK for Airia API
5
5
  Author-email: Airia LLC <support@airia.com>
6
6
  License: MIT
@@ -35,9 +35,9 @@ airia/client/models/async_models.py,sha256=gwW3tWwzSZ9_3JmGlwryY48w9KrFXoLET6Jps
35
35
  airia/client/models/base_models.py,sha256=FchsA7P-Fc7fxzlN88jJ3BEVXPWDcIHSzci6wtSArqg,3439
36
36
  airia/client/models/sync_models.py,sha256=FQLM4xCoxcBk1NuLf6z7jVVejlDKx5KpMRBz2tQm71o,5748
37
37
  airia/client/pipeline_execution/__init__.py,sha256=7qEZsPRTLySC71zlwYioBuJs6B4BwRCgFL3TQyFWXmc,175
38
- airia/client/pipeline_execution/async_pipeline_execution.py,sha256=alglTaHtWEipJx56WOUdV8MNhpOmo4GuhCXI9wkv7Mw,18022
39
- airia/client/pipeline_execution/base_pipeline_execution.py,sha256=fVGG__zHPpKLZdcwOL3GYmEyjCC7V4GZVuZ4CagGRP4,9608
40
- airia/client/pipeline_execution/sync_pipeline_execution.py,sha256=tvzo05lKgcUT-_LgFbyU7HdyV-EFWQMPAgC6znRKLzI,18106
38
+ airia/client/pipeline_execution/async_pipeline_execution.py,sha256=hk_bHFKcDfRTzpdWC-3noflO5WGqtfitxzPulfckElU,21539
39
+ airia/client/pipeline_execution/base_pipeline_execution.py,sha256=9nmJSVAL6zfAlfBpGQcJOFwIRoZBJNouCH6o3JSLYX0,9814
40
+ airia/client/pipeline_execution/sync_pipeline_execution.py,sha256=VRgdAvR9-Ims46YYMso90deimxcRtff77oEqU4GxBxY,21852
41
41
  airia/client/pipeline_import/__init__.py,sha256=ELSVZbekuhTnGDWFZsqE3-ILWsyHUwj9J_-Z75zGz_0,157
42
42
  airia/client/pipeline_import/async_pipeline_import.py,sha256=BC6HkkMNiU7_7H8vAhXwehV_Q5781xuNLTik6ehTgiU,7251
43
43
  airia/client/pipeline_import/base_pipeline_import.py,sha256=_6AHf_bL3RABDaIQN3-ivL3Z8NR1l0J7A4S0ilJCluY,3844
@@ -58,9 +58,10 @@ airia/client/tools/__init__.py,sha256=9bRyvbHD_AthBZA8YXMG79ZYoQjoGCl6mVnu5kVH1J
58
58
  airia/client/tools/async_tools.py,sha256=Qnh0aDT-QM0P_Au3G-ehBL4iSxbMMf58TsbBBqzF1ow,11231
59
59
  airia/client/tools/base_tools.py,sha256=YFTLESJBwvm5Z4YiRiadOQis9PH8Cwz6QC4scf1hazw,5913
60
60
  airia/client/tools/sync_tools.py,sha256=mFWkHwbcHPf95UB58TDtAgbBvDbpXylEC1S5fRXFhAE,10618
61
- airia/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
+ airia/types/__init__.py,sha256=fbNw1eto8l-qgz-W2QG32i-zQY7WiWZY3JR6fL2HsKM,235
62
62
  airia/types/_api_version.py,sha256=Uzom6O2ZG92HN_Z2h-lTydmO2XYX9RVs4Yi4DJmXytE,255
63
63
  airia/types/_request_data.py,sha256=q8t7KfO2WvgbPVYPvPWiwYb8LyP0kovlOgHFhZIU6ns,1278
64
+ airia/types/_structured_output.py,sha256=j_uqBMl5SmDNv0v_FrAli0ugQoLzhZKT_YGLHvWc2A0,5357
64
65
  airia/types/api/__init__.py,sha256=hAkcgB07FFdYUqGAP9-7CUFTBTYUOn6DFRH4wMo2PoM,150
65
66
  airia/types/api/attachments/__init__.py,sha256=zFSCwsmPr05I7NJRG6MCWME3AKhBpL0MhgOBOaF7rok,78
66
67
  airia/types/api/attachments/upload_file.py,sha256=XBCm1lZJAloFxmyp_3fbtuJ9Y28O-mbAfwy6D0EvTgQ,457
@@ -74,7 +75,7 @@ airia/types/api/deployments/get_deployments.py,sha256=5Dm7pTkEFmIZ4p4Scle9x9p3Nq
74
75
  airia/types/api/library/__init__.py,sha256=b2TLWT90EhQ41f8LcGMtvzEftd3ol_eKc41ian6zXX8,201
75
76
  airia/types/api/library/_library_models.py,sha256=d0YbmKNuUDXrieSRZh1IkhW_2aKrnaU08yuxqij98Dg,10486
76
77
  airia/types/api/models/__init__.py,sha256=bm4Rn86Tuk7q3988bJ_gvS5zY7t-sqCFt1BYb0nS7Xo,224
77
- airia/types/api/models/list_models.py,sha256=6tZRi_MBiMaQZb6whU2Ky8VRnUw_JDMkDZPY0PS3vQA,5451
78
+ airia/types/api/models/list_models.py,sha256=r4xpRhoawG8VLmNwVSZRsS__K6bCv-HTVVw2c_ClJow,5483
78
79
  airia/types/api/pipeline_execution/__init__.py,sha256=jlBLNN3yzd9TBQEfi7JJWlp3ur2NfXmON3YDnMkYdEY,674
79
80
  airia/types/api/pipeline_execution/_pipeline_execution.py,sha256=4E8rmr3Ok9EHkTzHjeZHQFlQVIXQIXIpbEeDtKAh3sQ,6425
80
81
  airia/types/api/pipeline_import/__init__.py,sha256=5m8e4faOYGCEFLQWJgj-v5H4NXPNtnlAKAxA4tGJUbw,267
@@ -93,8 +94,8 @@ airia/types/api/tools/_tools.py,sha256=PSJYFok7yQdE4it55iQmbryFzKN54nT6N161X1Rkp
93
94
  airia/types/sse/__init__.py,sha256=KWnNTfsQnthfrU128pUX6ounvSS7DvjC-Y21FE-OdMk,1863
94
95
  airia/types/sse/sse_messages.py,sha256=asq9KG5plT2XSgQMz-Nqo0WcKlXvE8UT3E-WLhCegPk,30244
95
96
  airia/utils/sse_parser.py,sha256=XCTkuaroYWaVQOgBq8VpbseQYSAVruF69AvKUwZQKTA,4251
96
- airia-0.1.29.dist-info/licenses/LICENSE,sha256=R3ClUMMKPRItIcZ0svzyj2taZZnFYw568YDNzN9KQ1Q,1066
97
- airia-0.1.29.dist-info/METADATA,sha256=SjH5WPody0kAfZ8TreZ_Vr3A1PLkxXNb-CzSLJ2BQtQ,4506
98
- airia-0.1.29.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
99
- airia-0.1.29.dist-info/top_level.txt,sha256=qUQEKfs_hdOYTwjKj1JZbRhS5YeXDNaKQaVTrzabS6w,6
100
- airia-0.1.29.dist-info/RECORD,,
97
+ airia-0.1.31.dist-info/licenses/LICENSE,sha256=R3ClUMMKPRItIcZ0svzyj2taZZnFYw568YDNzN9KQ1Q,1066
98
+ airia-0.1.31.dist-info/METADATA,sha256=Kozozw2Ucrhze9XJ60rBuIB63VHVTUN7tEgWrFfdySQ,4506
99
+ airia-0.1.31.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
100
+ airia-0.1.31.dist-info/top_level.txt,sha256=qUQEKfs_hdOYTwjKj1JZbRhS5YeXDNaKQaVTrzabS6w,6
101
+ airia-0.1.31.dist-info/RECORD,,
File without changes