Skip to content

Commit 0c9f9e1

Browse files
M-HietalaMarko Hietaladargilco
authored
removing exception when instrumenting multiple times, adding content … (Azure#38336)
* removing exception when instrumenting multiple times, adding content recording parameter * fixing a typo * Update version number and changelog * Update model used in AOAI chat completions tests * Update test recordings * check tool related fix and readme code snippet update * sphinx tool warning fix --------- Co-authored-by: Marko Hietala <[email protected]> Co-authored-by: Darren Cohen <[email protected]>
1 parent 5c28e02 commit 0c9f9e1

File tree

7 files changed

+176
-43
lines changed

7 files changed

+176
-43
lines changed

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

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

3+
## 1.0.0b6 (2024-11-15)
4+
5+
### Features Added
6+
7+
* OpenTelemetry tracing:
8+
* Method `AIInferenceInstrumentor().instrument()` updated with an input argument `enable_content_recording`.
9+
* Calling `AIInferenceInstrumentor().instrument()` twice no longer results in an exception.
10+
* Added method `AIInferenceInstrumentor().is_content_recording_enabled()`
11+
312
## 1.0.0b5 (2024-10-16)
413

514
### Features Added

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -301,10 +301,7 @@ response = client.complete(
301301
"role": "assistant",
302302
"content": "The main construction of the International Space Station (ISS) was completed between 1998 and 2011. During this period, more than 30 flights by US space shuttles and 40 by Russian rockets were conducted to transport components and modules to the station.",
303303
},
304-
{
305-
"role": "user",
306-
"content": "And what was the estimated cost to build it?"
307-
},
304+
{"role": "user", "content": "And what was the estimated cost to build it?"},
308305
]
309306
}
310307
)
@@ -578,6 +575,7 @@ Or configure it in the code with the following snippet:
578575

579576
```python
580577
from azure.core.settings import settings
578+
581579
settings.tracing_implementation = "opentelemetry"
582580
```
583581

@@ -591,6 +589,7 @@ The final step is to enable Azure AI Inference instrumentation with the followin
591589

592590
```python
593591
from azure.ai.inference.tracing import AIInferenceInstrumentor
592+
594593
# Instrument AI Inference API
595594
AIInferenceInstrumentor().instrument()
596595
```
@@ -616,11 +615,13 @@ The `@tracer.start_as_current_span` decorator can be used to trace your own func
616615

617616
```python
618617
from opentelemetry.trace import get_tracer
618+
619619
tracer = get_tracer(__name__)
620620

621+
621622
# The tracer.start_as_current_span decorator will trace the function call and enable adding additional attributes
622623
# to the span in the function implementation. Note that this will trace the function parameters and their values.
623-
@tracer.start_as_current_span("get_temperature") # type: ignore
624+
@tracer.start_as_current_span("get_temperature") # type: ignore
624625
def get_temperature(city: str) -> str:
625626

626627
# Adding attributes to the current span

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_3934744053"
5+
"Tag": "python/ai/azure-ai-inference_f4719b1cae"
66
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
77
# --------------------------------------------------------------------------
88

9-
VERSION = "1.0.0b5"
9+
VERSION = "1.0.0b6"

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

Lines changed: 80 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,26 @@ def __init__(self):
6363
# and have a parameter that specifies the version to use.
6464
self._impl = _AIInferenceInstrumentorPreview()
6565

66-
def instrument(self) -> None:
66+
def instrument(self, enable_content_recording: Optional[bool] = None) -> None:
6767
"""
6868
Enable trace instrumentation for AI Inference.
6969
70-
Raises:
71-
RuntimeError: If instrumentation is already enabled.
72-
70+
:param enable_content_recording: Whether content recording is enabled as part
71+
of the traces or not. Content in this context refers to chat message content
72+
and function call tool related function names, function parameter names and
73+
values. True will enable content recording, False will disable it. If no value
74+
s provided, then the value read from environment variable
75+
AZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED is used. If the environment variable
76+
is not found, then the value will default to False. Please note that successive calls
77+
to instrument will always apply the content recording value provided with the most
78+
recent call to instrument (including applying the environment variable if no value is
79+
provided and defaulting to false if the environment variable is not found), even if
80+
instrument was already previously called without uninstrument being called in between
81+
the instrument calls.
82+
83+
:type enable_content_recording: bool, optional
7384
"""
74-
self._impl.instrument()
85+
self._impl.instrument(enable_content_recording=enable_content_recording)
7586

7687
def uninstrument(self) -> None:
7788
"""
@@ -94,6 +105,15 @@ def is_instrumented(self) -> bool:
94105
"""
95106
return self._impl.is_instrumented()
96107

108+
def is_content_recording_enabled(self) -> bool:
109+
"""
110+
This function gets the content recording value.
111+
112+
:return: A bool value indicating whether content recording is enabled.
113+
:rtype: bool
114+
"""
115+
return self._impl.is_content_recording_enabled()
116+
97117

98118
class _AIInferenceInstrumentorPreview:
99119
"""
@@ -108,37 +128,37 @@ def _str_to_bool(self, s):
108128
return False
109129
return str(s).lower() == "true"
110130

111-
def instrument(self):
131+
def instrument(self, enable_content_recording: Optional[bool] = None):
112132
"""
113133
Enable trace instrumentation for AI Inference.
114134
115-
Raises:
116-
RuntimeError: If instrumentation is already enabled.
135+
:param enable_content_recording: Whether content recording is enabled as part
136+
of the traces or not. Content in this context refers to chat message content
137+
and function call tool related function names, function parameter names and
138+
values. True will enable content recording, False will disable it. If no value
139+
is provided, then the value read from environment variable
140+
AZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED is used. If the environment variable
141+
is not found, then the value will default to False.
117142
118-
This method checks the environment variable
119-
'AZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED' to determine
120-
whether to enable content tracing.
143+
:type enable_content_recording: bool, optional
121144
"""
122-
if self.is_instrumented():
123-
raise RuntimeError("Already instrumented")
124-
125-
var_value = os.environ.get("AZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED")
126-
enable_content_tracing = self._str_to_bool(var_value)
127-
self._instrument_inference(enable_content_tracing)
145+
if enable_content_recording is None:
146+
var_value = os.environ.get("AZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED")
147+
enable_content_recording = self._str_to_bool(var_value)
148+
if not self.is_instrumented():
149+
self._instrument_inference(enable_content_recording)
150+
else:
151+
self._set_content_recording_enabled(enable_content_recording=enable_content_recording)
128152

129153
def uninstrument(self):
130154
"""
131155
Disable trace instrumentation for AI Inference.
132156
133-
Raises:
134-
RuntimeError: If instrumentation is not currently enabled.
135-
136157
This method removes any active instrumentation, stopping the tracing
137158
of AI Inference.
138159
"""
139-
if not self.is_instrumented():
140-
raise RuntimeError("Not instrumented")
141-
self._uninstrument_inference()
160+
if self.is_instrumented():
161+
self._uninstrument_inference()
142162

143163
def is_instrumented(self):
144164
"""
@@ -149,6 +169,24 @@ def is_instrumented(self):
149169
"""
150170
return self._is_instrumented()
151171

172+
def set_content_recording_enabled(self, enable_content_recording: bool = False) -> None:
173+
"""This function sets the content recording value.
174+
175+
:param enable_content_recording: Indicates whether tracing of message content should be enabled.
176+
This also controls whether function call tool function names,
177+
parameter names and parameter values are traced.
178+
:type enable_content_recording: bool
179+
"""
180+
self._set_content_recording_enabled(enable_content_recording=enable_content_recording)
181+
182+
def is_content_recording_enabled(self) -> bool:
183+
"""This function gets the content recording value.
184+
185+
:return: A bool value indicating whether content tracing is enabled.
186+
:rtype bool
187+
"""
188+
return self._is_content_recording_enabled()
189+
152190
def _set_attributes(self, span: "AbstractSpan", *attrs: Tuple[str, Any]) -> None:
153191
for attr in attrs:
154192
key, value = attr
@@ -764,3 +802,22 @@ def _is_instrumented(self):
764802
:rtype: bool
765803
"""
766804
return _inference_traces_enabled
805+
806+
def _set_content_recording_enabled(self, enable_content_recording: bool = False) -> None:
807+
"""This function sets the content recording value.
808+
809+
:param enable_content_recording: Indicates whether tracing of message content should be enabled.
810+
This also controls whether function call tool function names,
811+
parameter names and parameter values are traced.
812+
:type enable_content_recording: bool
813+
"""
814+
global _trace_inference_content # pylint: disable=W0603
815+
_trace_inference_content = enable_content_recording
816+
817+
def _is_content_recording_enabled(self) -> bool:
818+
"""This function gets the content recording value.
819+
820+
:return: A bool value indicating whether content tracing is enabled.
821+
:rtype bool
822+
"""
823+
return _trace_inference_content

sdk/ai/azure-ai-inference/tests/model_inference_test_base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@
4949
# hosted on Azure OpenAI (AOAI) endpoint.
5050
# TODO: When we have a MaaS model that supports chat completions with image input,
5151
# use that instead.
52-
# AZURE_OPENAI_CHAT_ENDPOINT=https://<endpont-name>.openai.azure.com/openai/deployments/gpt-4o-0806
52+
# AZURE_OPENAI_CHAT_ENDPOINT=https://<endpont-name>.openai.azure.com/openai/deployments/gpt-4o
5353
# AZURE_OPENAI_CHAT_KEY=<32-char-api-key>
5454
#
5555
ServicePreparerAOAIChatCompletions = functools.partial(
5656
EnvironmentVariableLoader,
5757
"azure_openai_chat",
58-
azure_openai_chat_endpoint="https://your-deployment-name.openai.azure.com/openai/deployments/gpt-4o-0806",
58+
azure_openai_chat_endpoint="https://your-deployment-name.openai.azure.com/openai/deployments/gpt-4o",
5959
azure_openai_chat_key="00000000000000000000000000000000",
6060
)
6161

sdk/ai/azure-ai-inference/tests/test_model_inference_client.py

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -856,67 +856,133 @@ def test_instrumentation(self, **kwargs):
856856

857857
@ServicePreparerChatCompletions()
858858
@recorded_by_proxy
859-
def test_instrumenting_twice_causes_exception(self, **kwargs):
859+
def test_instrumenting_twice_does_not_cause_exception(self, **kwargs):
860860
# Make sure code is not instrumented due to a previous test exception
861861
try:
862862
AIInferenceInstrumentor().uninstrument()
863863
except RuntimeError as e:
864864
pass
865865
client = self._create_chat_client(**kwargs)
866866
exception_caught = False
867-
instrumented_once = False
868867
try:
869868
AIInferenceInstrumentor().instrument()
870-
instrumented_once = True
871869
AIInferenceInstrumentor().instrument()
872870
except RuntimeError as e:
873871
exception_caught = True
874872
print(e)
875873
AIInferenceInstrumentor().uninstrument()
876874
client.close()
877-
assert instrumented_once == True
878-
assert exception_caught == True
875+
assert exception_caught == False
876+
877+
@ServicePreparerChatCompletions()
878+
@recorded_by_proxy
879+
def test_uninstrumenting_uninstrumented_does_not_cause_exception(self, **kwargs):
880+
# Make sure code is not instrumented due to a previous test exception
881+
try:
882+
AIInferenceInstrumentor().uninstrument()
883+
except RuntimeError as e:
884+
pass
885+
client = self._create_chat_client(**kwargs)
886+
exception_caught = False
887+
try:
888+
AIInferenceInstrumentor().uninstrument()
889+
except RuntimeError as e:
890+
exception_caught = True
891+
print(e)
892+
client.close()
893+
assert exception_caught == False
879894

880895
@ServicePreparerChatCompletions()
881896
@recorded_by_proxy
882-
def test_uninstrumenting_uninstrumented_causes_exception(self, **kwargs):
897+
def test_uninstrumenting_twice_does_not_cause_exception(self, **kwargs):
883898
# Make sure code is not instrumented due to a previous test exception
884899
try:
885900
AIInferenceInstrumentor().uninstrument()
886901
except RuntimeError as e:
887902
pass
888903
client = self._create_chat_client(**kwargs)
889904
exception_caught = False
905+
uninstrumented_once = False
890906
try:
907+
AIInferenceInstrumentor().instrument()
908+
AIInferenceInstrumentor().uninstrument()
891909
AIInferenceInstrumentor().uninstrument()
892910
except RuntimeError as e:
893911
exception_caught = True
894912
print(e)
895913
client.close()
896-
assert exception_caught == True
914+
assert exception_caught == False
897915

898916
@ServicePreparerChatCompletions()
899917
@recorded_by_proxy
900-
def test_uninstrumenting_twice_causes_exception(self, **kwargs):
918+
def test_is_content_recording_enabled(self, **kwargs):
901919
# Make sure code is not instrumented due to a previous test exception
902920
try:
903921
AIInferenceInstrumentor().uninstrument()
904922
except RuntimeError as e:
905923
pass
924+
self.modify_env_var(CONTENT_TRACING_ENV_VARIABLE, "False")
906925
client = self._create_chat_client(**kwargs)
907926
exception_caught = False
908927
uninstrumented_once = False
909928
try:
929+
# From environment variable instrumenting from uninstrumented
930+
AIInferenceInstrumentor().instrument()
931+
self.modify_env_var(CONTENT_TRACING_ENV_VARIABLE, "False")
910932
AIInferenceInstrumentor().instrument()
933+
content_recording_enabled = AIInferenceInstrumentor().is_content_recording_enabled()
934+
assert content_recording_enabled == False
911935
AIInferenceInstrumentor().uninstrument()
912-
uninstrumented_once = True
936+
self.modify_env_var(CONTENT_TRACING_ENV_VARIABLE, "True")
937+
AIInferenceInstrumentor().instrument()
938+
content_recording_enabled = AIInferenceInstrumentor().is_content_recording_enabled()
939+
assert content_recording_enabled == True
913940
AIInferenceInstrumentor().uninstrument()
941+
self.modify_env_var(CONTENT_TRACING_ENV_VARIABLE, "invalid")
942+
AIInferenceInstrumentor().instrument()
943+
content_recording_enabled = AIInferenceInstrumentor().is_content_recording_enabled()
944+
assert content_recording_enabled == False
945+
946+
# From environment variable instrumenting from instrumented
947+
self.modify_env_var(CONTENT_TRACING_ENV_VARIABLE, "True")
948+
AIInferenceInstrumentor().instrument()
949+
content_recording_enabled = AIInferenceInstrumentor().is_content_recording_enabled()
950+
assert content_recording_enabled == True
951+
self.modify_env_var(CONTENT_TRACING_ENV_VARIABLE, "True")
952+
AIInferenceInstrumentor().instrument()
953+
content_recording_enabled = AIInferenceInstrumentor().is_content_recording_enabled()
954+
assert content_recording_enabled == True
955+
self.modify_env_var(CONTENT_TRACING_ENV_VARIABLE, "invalid")
956+
AIInferenceInstrumentor().instrument()
957+
content_recording_enabled = AIInferenceInstrumentor().is_content_recording_enabled()
958+
assert content_recording_enabled == False
959+
960+
# From parameter instrumenting from uninstrumented
961+
AIInferenceInstrumentor().uninstrument()
962+
self.modify_env_var(CONTENT_TRACING_ENV_VARIABLE, "True")
963+
AIInferenceInstrumentor().instrument(enable_content_recording=False)
964+
content_recording_enabled = AIInferenceInstrumentor().is_content_recording_enabled()
965+
assert content_recording_enabled == False
966+
AIInferenceInstrumentor().uninstrument()
967+
self.modify_env_var(CONTENT_TRACING_ENV_VARIABLE, "False")
968+
AIInferenceInstrumentor().instrument(enable_content_recording=True)
969+
content_recording_enabled = AIInferenceInstrumentor().is_content_recording_enabled()
970+
assert content_recording_enabled == True
971+
972+
# From parameter instrumenting from instrumented
973+
self.modify_env_var(CONTENT_TRACING_ENV_VARIABLE, "True")
974+
AIInferenceInstrumentor().instrument(enable_content_recording=False)
975+
content_recording_enabled = AIInferenceInstrumentor().is_content_recording_enabled()
976+
assert content_recording_enabled == False
977+
self.modify_env_var(CONTENT_TRACING_ENV_VARIABLE, "False")
978+
AIInferenceInstrumentor().instrument(enable_content_recording=True)
979+
content_recording_enabled = AIInferenceInstrumentor().is_content_recording_enabled()
980+
assert content_recording_enabled == True
914981
except RuntimeError as e:
915982
exception_caught = True
916983
print(e)
917984
client.close()
918-
assert uninstrumented_once == True
919-
assert exception_caught == True
985+
assert exception_caught == False
920986

921987
@ServicePreparerChatCompletions()
922988
@recorded_by_proxy

0 commit comments

Comments
 (0)