Skip to content

Commit fddb8bb

Browse files
authored
Updates for beta 9 release of azure-ai-inference SDK (Azure#39579)
1 parent 20078c1 commit fddb8bb

27 files changed

+775
-220
lines changed

.vscode/cspell.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1369,7 +1369,9 @@
13691369
"wday",
13701370
"Hola",
13711371
"cómo",
1372-
"estás"
1372+
"estás",
1373+
"logprobs",
1374+
"conver",
13731375
]
13741376
},
13751377
{

sdk/ai/azure-ai-inference/CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Release History
22

3+
## 1.0.0b9 (2025-02-14)
4+
5+
### Features Added
6+
7+
* Added support for chat completion messages with `developer` role.
8+
* Updated package document with an example of how to set custom HTTP request headers,
9+
and an example of providing chat completion "messages" as an array of Python `dict` objects.
10+
* Add a descriptive Exception error message when `load_client` function or
11+
`get_model_info` method fails to run on an endpoint that does not support the `/info` route.
12+
13+
### Bugs Fixed
14+
15+
* Fix for Exception raised while parsing Chat Completions streaming response, in some rare cases, for
16+
multibyte UTF-8 languages like Chinese ([GitHub Issue 39565](https://github.com/Azure/azure-sdk-for-python/issues/39565)).
17+
318
## 1.0.0b8 (2025-01-29)
419

520
### Features Added

sdk/ai/azure-ai-inference/README.md

Lines changed: 49 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,8 @@ In the following sections you will find simple examples of:
239239

240240
* [Chat completions](#chat-completions-example)
241241
* [Streaming chat completions](#streaming-chat-completions-example)
242-
* [Chat completions with additional model-specific parameters](#chat-completions-with-additional-model-specific-parameters)
242+
* [Adding model-specific parameters](#adding-model-specific-parameters)
243+
* [Adding HTTP request headers](#adding-http-request-headers)
243244
* [Text Embeddings](#text-embeddings-example)
244245
* [Image Embeddings](#image-embeddings-example)
245246

@@ -274,7 +275,7 @@ print(response.choices[0].message.content)
274275

275276
<!-- END SNIPPET -->
276277

277-
The following types of messages are supported: `SystemMessage`,`UserMessage`, `AssistantMessage`, `ToolMessage`. See also samples:
278+
The following types of messages are supported: `SystemMessage`,`UserMessage`, `AssistantMessage`, `ToolMessage`, `DeveloperMessage`. See also samples:
278279

279280
* [sample_chat_completions_with_tools.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-inference/samples/sample_chat_completions_with_tools.py) for usage of `ToolMessage`.
280281
* [sample_chat_completions_with_image_url.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-inference/samples/sample_chat_completions_with_image_url.py) for usage of `UserMessage` that
@@ -284,10 +285,9 @@ includes sending image data read from a local file.
284285
* [sample_chat_completions_with_audio_data.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-inference/samples/sample_chat_completions_with_image_data.py) for usage of `UserMessage` that includes sending audio data read from a local file.
285286
* [sample_chat_completions_with_structured_output.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-inference/samples/sample_chat_completions_with_structured_output.py) and [sample_chat_completions_with_structured_output_pydantic.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-inference/samples/sample_chat_completions_with_structured_output_pydantic.py) for configuring the service to respond with a JSON-formatted string, adhering to your schema.
286287

288+
Alternatively, you can provide the full request body as a Python dictionary (`dict` object) instead of using the strongly typed classes like `SystemMessage` and `UserMessage`:
287289

288-
Alternatively, you can provide the messages as dictionary instead of using the strongly typed classes like `SystemMessage` and `UserMessage`:
289-
290-
<!-- SNIPPET:sample_chat_completions_from_input_dict.chat_completions -->
290+
<!-- SNIPPET:sample_chat_completions_from_input_dict.chat_completions_full_request_as_dict -->
291291

292292
```python
293293
response = client.complete(
@@ -313,6 +313,27 @@ response = client.complete(
313313

314314
<!-- END SNIPPET -->
315315

316+
Or you can provide just the `messages` input argument as a list of Python `dict`:
317+
318+
<!-- SNIPPET:sample_chat_completions_from_input_dict.chat_completions_messages_as_dict -->
319+
320+
```python
321+
response = client.complete(
322+
messages=[
323+
{
324+
"role": "system",
325+
"content": "You are an AI assistant that helps people find information.",
326+
},
327+
{
328+
"role": "user",
329+
"content": "How many feet are in a mile?",
330+
},
331+
]
332+
)
333+
```
334+
335+
<!-- END SNIPPET -->
336+
316337
To generate completions for additional messages, simply call `client.complete` multiple times using the same `client`.
317338

318339
### Streaming chat completions example
@@ -339,7 +360,10 @@ response = client.complete(
339360
)
340361

341362
for update in response:
342-
print(update.choices[0].delta.content or "", end="", flush=True)
363+
if update.choices and update.choices[0].delta:
364+
print(update.choices[0].delta.content or "", end="", flush=True)
365+
if update.usage:
366+
print(f"\n\nUsage: {update.usage}")
343367

344368
client.close()
345369
```
@@ -350,9 +374,9 @@ In the above `for` loop that prints the results you should see the answer progre
350374

351375
To generate completions for additional messages, simply call `client.complete` multiple times using the same `client`.
352376

353-
### Chat completions with additional model-specific parameters
377+
### Adding model-specific parameters
354378

355-
In this example, extra JSON elements are inserted at the root of the request body by setting `model_extras` when calling the `complete` method. These are intended for AI models that require additional model-specific parameters beyond what is defined in the REST API [Request Body table](https://learn.microsoft.com/azure/ai-studio/reference/reference-model-inference-chat-completions#request-body).
379+
In this example, extra JSON elements are inserted at the root of the request body by setting `model_extras` when calling the `complete` method of the `ChatCompletionsClient`. These are intended for AI models that require additional model-specific parameters beyond what is defined in the REST API [Request Body table](https://learn.microsoft.com/azure/ai-studio/reference/reference-model-inference-chat-completions#request-body).
356380

357381
<!-- SNIPPET:sample_chat_completions_with_model_extras.model_extras -->
358382

@@ -383,6 +407,23 @@ In the above example, this will be the JSON payload in the HTTP request:
383407

384408
Note that by default, the service will reject any request payload that includes extra parameters. In order to change the default service behaviour, when the `complete` method includes `model_extras`, the client library will automatically add the HTTP request header `"extra-parameters": "pass-through"`.
385409

410+
Use the same method to add additional paramaters in the request of other clients in this package.
411+
412+
### Adding HTTP request headers
413+
414+
To add your own HTTP request headers, include a `headers` keyword in the client constructor, and specify a `dict` with your
415+
header names and values. For example:
416+
417+
```python
418+
client = ChatCompletionsClient(
419+
endpoint=endpoint,
420+
credential=AzureKeyCredential(key),
421+
headers={"header1", "value1", "header2", "value2"}
422+
)
423+
```
424+
425+
And similarly for the other clients in this package.
426+
386427
### Text Embeddings example
387428

388429
This example demonstrates how to get text embeddings, for a Serverless API or Managed Compute endpoint, with key authentication, assuming `endpoint` and `key` are already defined. For Entra ID authentication, GitHub models endpoint or Azure OpenAI endpoint, modify the code to create the client as specified in the above sections.
@@ -450,46 +491,6 @@ data[0]: length=1024, [0.0103302, -0.04425049, ..., -0.011543274, -0.0009088516]
450491

451492
To generate image embeddings for additional images, simply call `client.embed` multiple times using the same `client`.
452493

453-
<!--
454-
### Image Embeddings example
455-
456-
This example demonstrates how to get image embeddings.
457-
458-
<! -- SNIPPET:sample_image_embeddings.image_embeddings -- >
459-
460-
```python
461-
from azure.ai.inference import ImageEmbeddingsClient
462-
from azure.ai.inference.models import ImageEmbeddingInput
463-
from azure.core.credentials import AzureKeyCredential
464-
465-
with open("sample1.png", "rb") as f:
466-
image1: str = base64.b64encode(f.read()).decode("utf-8")
467-
with open("sample2.png", "rb") as f:
468-
image2: str = base64.b64encode(f.read()).decode("utf-8")
469-
470-
client = ImageEmbeddingsClient(endpoint=endpoint, credential=AzureKeyCredential(key))
471-
472-
response = client.embed(input=[ImageEmbeddingInput(image=image1), ImageEmbeddingInput(image=image2)])
473-
474-
for item in response.data:
475-
length = len(item.embedding)
476-
print(
477-
f"data[{item.index}]: length={length}, [{item.embedding[0]}, {item.embedding[1]}, "
478-
f"..., {item.embedding[length-2]}, {item.embedding[length-1]}]"
479-
)
480-
```
481-
482-
-- END SNIPPET --
483-
484-
The printed result of course depends on the model, but you should see something like this:
485-
486-
```txt
487-
TBD
488-
```
489-
490-
To generate embeddings for additional phrases, simply call `client.embed` multiple times using the same `client`.
491-
-->
492-
493494
## Troubleshooting
494495

495496
### Exceptions

sdk/ai/azure-ai-inference/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "python",
44
"TagPrefix": "python/ai/azure-ai-inference",
5-
"Tag": "python/ai/azure-ai-inference_bc7c5bd581"
5+
"Tag": "python/ai/azure-ai-inference_3f06cee8a7"
66
}

sdk/ai/azure-ai-inference/azure/ai/inference/_model_base.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,15 +373,34 @@ def __ne__(self, other: typing.Any) -> bool:
373373
return not self.__eq__(other)
374374

375375
def keys(self) -> typing.KeysView[str]:
376+
"""
377+
:returns: a set-like object providing a view on D's keys
378+
:rtype: ~typing.KeysView
379+
"""
376380
return self._data.keys()
377381

378382
def values(self) -> typing.ValuesView[typing.Any]:
383+
"""
384+
:returns: an object providing a view on D's values
385+
:rtype: ~typing.ValuesView
386+
"""
379387
return self._data.values()
380388

381389
def items(self) -> typing.ItemsView[str, typing.Any]:
390+
"""
391+
:returns: set-like object providing a view on D's items
392+
:rtype: ~typing.ItemsView
393+
"""
382394
return self._data.items()
383395

384396
def get(self, key: str, default: typing.Any = None) -> typing.Any:
397+
"""
398+
Get the value for key if key is in the dictionary, else default.
399+
:param str key: The key to look up.
400+
:param any default: The value to return if key is not in the dictionary. Defaults to None
401+
:returns: D[k] if k in D, else d.
402+
:rtype: any
403+
"""
385404
try:
386405
return self[key]
387406
except KeyError:
@@ -397,17 +416,38 @@ def pop(self, key: str, default: _T) -> _T: ...
397416
def pop(self, key: str, default: typing.Any) -> typing.Any: ...
398417

399418
def pop(self, key: str, default: typing.Any = _UNSET) -> typing.Any:
419+
"""
420+
Removes specified key and return the corresponding value.
421+
:param str key: The key to pop.
422+
:param any default: The value to return if key is not in the dictionary
423+
:returns: The value corresponding to the key.
424+
:rtype: any
425+
:raises KeyError: If key is not found and default is not given.
426+
"""
400427
if default is _UNSET:
401428
return self._data.pop(key)
402429
return self._data.pop(key, default)
403430

404431
def popitem(self) -> typing.Tuple[str, typing.Any]:
432+
"""
433+
Removes and returns some (key, value) pair
434+
:returns: The (key, value) pair.
435+
:rtype: tuple
436+
:raises KeyError: if D is empty.
437+
"""
405438
return self._data.popitem()
406439

407440
def clear(self) -> None:
441+
"""
442+
Remove all items from D.
443+
"""
408444
self._data.clear()
409445

410446
def update(self, *args: typing.Any, **kwargs: typing.Any) -> None:
447+
"""
448+
Updates D from mapping/iterable E and F.
449+
:param any args: Either a mapping object or an iterable of key-value pairs.
450+
"""
411451
self._data.update(*args, **kwargs)
412452

413453
@typing.overload
@@ -417,6 +457,13 @@ def setdefault(self, key: str, default: None = None) -> None: ...
417457
def setdefault(self, key: str, default: typing.Any) -> typing.Any: ...
418458

419459
def setdefault(self, key: str, default: typing.Any = _UNSET) -> typing.Any:
460+
"""
461+
Same as calling D.get(k, d), and setting D[k]=d if k not found
462+
:param str key: The key to look up.
463+
:param any default: The value to set if key is not in the dictionary
464+
:returns: D[k] if k in D, else d.
465+
:rtype: any
466+
"""
420467
if default is _UNSET:
421468
return self._data.setdefault(key)
422469
return self._data.setdefault(key, default)
@@ -910,6 +957,19 @@ def _failsafe_deserialize(
910957
return None
911958

912959

960+
def _failsafe_deserialize_xml(
961+
deserializer: typing.Any,
962+
value: typing.Any,
963+
) -> typing.Any:
964+
try:
965+
return _deserialize_xml(deserializer, value)
966+
except DeserializationError:
967+
_LOGGER.warning(
968+
"Ran into a deserialization error. Ignoring since this is failsafe deserialization", exc_info=True
969+
)
970+
return None
971+
972+
913973
class _RestField:
914974
def __init__(
915975
self,

sdk/ai/azure-ai-inference/azure/ai/inference/_operations/_operations.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -181,15 +181,6 @@ def build_image_embeddings_get_model_info_request(**kwargs: Any) -> HttpRequest:
181181

182182
class ChatCompletionsClientOperationsMixin(ChatCompletionsClientMixinABC):
183183

184-
@overload
185-
def _complete(
186-
self,
187-
body: JSON,
188-
*,
189-
extra_params: Optional[Union[str, _models._enums.ExtraParameters]] = None,
190-
content_type: str = "application/json",
191-
**kwargs: Any
192-
) -> _models.ChatCompletions: ...
193184
@overload
194185
def _complete(
195186
self,
@@ -214,6 +205,15 @@ def _complete(
214205
**kwargs: Any
215206
) -> _models.ChatCompletions: ...
216207
@overload
208+
def _complete(
209+
self,
210+
body: JSON,
211+
*,
212+
extra_params: Optional[Union[str, _models._enums.ExtraParameters]] = None,
213+
content_type: str = "application/json",
214+
**kwargs: Any
215+
) -> _models.ChatCompletions: ...
216+
@overload
217217
def _complete(
218218
self,
219219
body: IO[bytes],
@@ -488,23 +488,23 @@ class EmbeddingsClientOperationsMixin(EmbeddingsClientMixinABC):
488488
@overload
489489
def _embed(
490490
self,
491-
body: JSON,
492491
*,
492+
input: List[str],
493493
extra_params: Optional[Union[str, _models._enums.ExtraParameters]] = None,
494494
content_type: str = "application/json",
495+
dimensions: Optional[int] = None,
496+
encoding_format: Optional[Union[str, _models.EmbeddingEncodingFormat]] = None,
497+
input_type: Optional[Union[str, _models.EmbeddingInputType]] = None,
498+
model: Optional[str] = None,
495499
**kwargs: Any
496500
) -> _models.EmbeddingsResult: ...
497501
@overload
498502
def _embed(
499503
self,
504+
body: JSON,
500505
*,
501-
input: List[str],
502506
extra_params: Optional[Union[str, _models._enums.ExtraParameters]] = None,
503507
content_type: str = "application/json",
504-
dimensions: Optional[int] = None,
505-
encoding_format: Optional[Union[str, _models.EmbeddingEncodingFormat]] = None,
506-
input_type: Optional[Union[str, _models.EmbeddingInputType]] = None,
507-
model: Optional[str] = None,
508508
**kwargs: Any
509509
) -> _models.EmbeddingsResult: ...
510510
@overload
@@ -701,23 +701,23 @@ class ImageEmbeddingsClientOperationsMixin(ImageEmbeddingsClientMixinABC):
701701
@overload
702702
def _embed(
703703
self,
704-
body: JSON,
705704
*,
705+
input: List[_models.ImageEmbeddingInput],
706706
extra_params: Optional[Union[str, _models._enums.ExtraParameters]] = None,
707707
content_type: str = "application/json",
708+
dimensions: Optional[int] = None,
709+
encoding_format: Optional[Union[str, _models.EmbeddingEncodingFormat]] = None,
710+
input_type: Optional[Union[str, _models.EmbeddingInputType]] = None,
711+
model: Optional[str] = None,
708712
**kwargs: Any
709713
) -> _models.EmbeddingsResult: ...
710714
@overload
711715
def _embed(
712716
self,
717+
body: JSON,
713718
*,
714-
input: List[_models.ImageEmbeddingInput],
715719
extra_params: Optional[Union[str, _models._enums.ExtraParameters]] = None,
716720
content_type: str = "application/json",
717-
dimensions: Optional[int] = None,
718-
encoding_format: Optional[Union[str, _models.EmbeddingEncodingFormat]] = None,
719-
input_type: Optional[Union[str, _models.EmbeddingInputType]] = None,
720-
model: Optional[str] = None,
721721
**kwargs: Any
722722
) -> _models.EmbeddingsResult: ...
723723
@overload

0 commit comments

Comments
 (0)