1616
1717package org .springframework .ai .azure .openai ;
1818
19- import com .azure .ai .openai .OpenAIAsyncClient ;
20- import com .azure .ai .openai .OpenAIClient ;
21- import com .azure .ai .openai .OpenAIClientBuilder ;
22- import com .azure .ai .openai .models .*;
23- import com .azure .core .util .BinaryData ;
19+ import java .util .ArrayList ;
20+ import java .util .Base64 ;
21+ import java .util .Collections ;
22+ import java .util .HashSet ;
23+ import java .util .List ;
24+ import java .util .Map ;
25+ import java .util .Optional ;
26+ import java .util .Set ;
27+ import java .util .concurrent .atomic .AtomicBoolean ;
28+
2429import org .springframework .ai .azure .openai .metadata .AzureOpenAiUsage ;
2530import org .springframework .ai .chat .messages .AssistantMessage ;
2631import org .springframework .ai .chat .messages .Message ;
3641import org .springframework .ai .chat .model .ChatModel ;
3742import org .springframework .ai .chat .model .ChatResponse ;
3843import org .springframework .ai .chat .model .Generation ;
44+ import org .springframework .ai .chat .observation .ChatModelObservationContext ;
45+ import org .springframework .ai .chat .observation .ChatModelObservationConvention ;
46+ import org .springframework .ai .chat .observation .ChatModelObservationDocumentation ;
47+ import org .springframework .ai .chat .observation .DefaultChatModelObservationConvention ;
3948import org .springframework .ai .chat .prompt .ChatOptions ;
4049import org .springframework .ai .chat .prompt .Prompt ;
4150import org .springframework .ai .model .Media ;
4251import org .springframework .ai .model .ModelOptionsUtils ;
4352import org .springframework .ai .model .function .FunctionCallback ;
4453import org .springframework .ai .model .function .FunctionCallbackContext ;
54+ import org .springframework .ai .observation .conventions .AiProvider ;
4555import org .springframework .util .Assert ;
4656import org .springframework .util .CollectionUtils ;
57+
58+ import com .azure .ai .openai .OpenAIAsyncClient ;
59+ import com .azure .ai .openai .OpenAIClient ;
60+ import com .azure .ai .openai .OpenAIClientBuilder ;
61+ import com .azure .ai .openai .models .ChatChoice ;
62+ import com .azure .ai .openai .models .ChatCompletions ;
63+ import com .azure .ai .openai .models .ChatCompletionsFunctionToolCall ;
64+ import com .azure .ai .openai .models .ChatCompletionsFunctionToolDefinition ;
65+ import com .azure .ai .openai .models .ChatCompletionsJsonResponseFormat ;
66+ import com .azure .ai .openai .models .ChatCompletionsOptions ;
67+ import com .azure .ai .openai .models .ChatCompletionsResponseFormat ;
68+ import com .azure .ai .openai .models .ChatCompletionsTextResponseFormat ;
69+ import com .azure .ai .openai .models .ChatCompletionsToolCall ;
70+ import com .azure .ai .openai .models .ChatCompletionsToolDefinition ;
71+ import com .azure .ai .openai .models .ChatMessageContentItem ;
72+ import com .azure .ai .openai .models .ChatMessageImageContentItem ;
73+ import com .azure .ai .openai .models .ChatMessageImageUrl ;
74+ import com .azure .ai .openai .models .ChatMessageTextContentItem ;
75+ import com .azure .ai .openai .models .ChatRequestAssistantMessage ;
76+ import com .azure .ai .openai .models .ChatRequestMessage ;
77+ import com .azure .ai .openai .models .ChatRequestSystemMessage ;
78+ import com .azure .ai .openai .models .ChatRequestToolMessage ;
79+ import com .azure .ai .openai .models .ChatRequestUserMessage ;
80+ import com .azure .ai .openai .models .CompletionsFinishReason ;
81+ import com .azure .ai .openai .models .ContentFilterResultsForPrompt ;
82+ import com .azure .ai .openai .models .FunctionCall ;
83+ import com .azure .ai .openai .models .FunctionDefinition ;
84+ import com .azure .core .util .BinaryData ;
85+ import io .micrometer .observation .ObservationRegistry ;
4786import reactor .core .publisher .Flux ;
4887import reactor .core .publisher .Mono ;
4988
50- import java .util .ArrayList ;
51- import java .util .Base64 ;
52- import java .util .Collections ;
53- import java .util .HashSet ;
54- import java .util .List ;
55- import java .util .Map ;
56- import java .util .Optional ;
57- import java .util .Set ;
58- import java .util .concurrent .atomic .AtomicBoolean ;
59-
6089/**
6190 * {@link ChatModel} implementation for {@literal Microsoft Azure AI} backed by
6291 * {@link OpenAIClient}.
@@ -81,6 +110,8 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
81110
82111 private static final Double DEFAULT_TEMPERATURE = 0.7 ;
83112
113+ private static final ChatModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultChatModelObservationConvention ();
114+
84115 /**
85116 * The {@link OpenAIClient} used to interact with the Azure OpenAI service.
86117 */
@@ -96,8 +127,18 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
96127 */
97128 private final AzureOpenAiChatOptions defaultOptions ;
98129
99- public AzureOpenAiChatModel (OpenAIClientBuilder microsoftOpenAiClient ) {
100- this (microsoftOpenAiClient ,
130+ /**
131+ * Observation registry used for instrumentation.
132+ */
133+ private final ObservationRegistry observationRegistry ;
134+
135+ /**
136+ * Conventions to use for generating observations.
137+ */
138+ private ChatModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION ;
139+
140+ public AzureOpenAiChatModel (OpenAIClientBuilder openAIClientBuilder ) {
141+ this (openAIClientBuilder ,
101142 AzureOpenAiChatOptions .builder ()
102143 .withDeploymentName (DEFAULT_DEPLOYMENT_NAME )
103144 .withTemperature (DEFAULT_TEMPERATURE )
@@ -115,12 +156,19 @@ public AzureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder, AzureOpenAi
115156
116157 public AzureOpenAiChatModel (OpenAIClientBuilder openAIClientBuilder , AzureOpenAiChatOptions options ,
117158 FunctionCallbackContext functionCallbackContext , List <FunctionCallback > toolFunctionCallbacks ) {
159+ this (openAIClientBuilder , options , functionCallbackContext , List .of (), ObservationRegistry .NOOP );
160+ }
161+
162+ public AzureOpenAiChatModel (OpenAIClientBuilder openAIClientBuilder , AzureOpenAiChatOptions options ,
163+ FunctionCallbackContext functionCallbackContext , List <FunctionCallback > toolFunctionCallbacks ,
164+ ObservationRegistry observationRegistry ) {
118165 super (functionCallbackContext , options , toolFunctionCallbacks );
119166 Assert .notNull (openAIClientBuilder , "com.azure.ai.openai.OpenAIClient must not be null" );
120167 Assert .notNull (options , "AzureOpenAiChatOptions must not be null" );
121168 this .openAIClient = openAIClientBuilder .buildClient ();
122169 this .openAIAsyncClient = openAIClientBuilder .buildAsyncClient ();
123170 this .defaultOptions = options ;
171+ this .observationRegistry = observationRegistry ;
124172 }
125173
126174 public AzureOpenAiChatOptions getDefaultOptions () {
@@ -130,22 +178,34 @@ public AzureOpenAiChatOptions getDefaultOptions() {
130178 @ Override
131179 public ChatResponse call (Prompt prompt ) {
132180
133- ChatCompletionsOptions options = toAzureChatCompletionsOptions (prompt );
134- options .setStream (false );
181+ ChatModelObservationContext observationContext = ChatModelObservationContext .builder ()
182+ .prompt (prompt )
183+ .provider (AiProvider .AZURE_OPENAI .value ())
184+ .requestOptions (prompt .getOptions () != null ? prompt .getOptions () : this .defaultOptions )
185+ .build ();
135186
136- ChatCompletions chatCompletions = this .openAIClient .getChatCompletions (options .getModel (), options );
187+ ChatResponse response = ChatModelObservationDocumentation .CHAT_MODEL_OPERATION
188+ .observation (this .observationConvention , DEFAULT_OBSERVATION_CONVENTION , () -> observationContext ,
189+ this .observationRegistry )
190+ .observe (() -> {
191+ ChatCompletionsOptions options = toAzureChatCompletionsOptions (prompt );
192+ options .setStream (false );
137193
138- ChatResponse chatResponse = toChatResponse (chatCompletions );
194+ ChatCompletions chatCompletions = this .openAIClient .getChatCompletions (options .getModel (), options );
195+ ChatResponse chatResponse = toChatResponse (chatCompletions );
196+ observationContext .setResponse (chatResponse );
197+ return chatResponse ;
198+ });
139199
140200 if (!isProxyToolCalls (prompt , this .defaultOptions )
141- && isToolCall (chatResponse , Set .of (String .valueOf (CompletionsFinishReason .TOOL_CALLS ).toLowerCase ()))) {
142- var toolCallConversation = handleToolCalls (prompt , chatResponse );
201+ && isToolCall (response , Set .of (String .valueOf (CompletionsFinishReason .TOOL_CALLS ).toLowerCase ()))) {
202+ var toolCallConversation = handleToolCalls (prompt , response );
143203 // Recursively call the call method with the tool call message
144204 // conversation that contains the call responses.
145205 return this .call (new Prompt (toolCallConversation , prompt .getOptions ()));
146206 }
147207
148- return chatResponse ;
208+ return response ;
149209 }
150210
151211 @ Override
0 commit comments