1515// under the License. 
1616
1717import  ballerina /ai ;
18+ import  ballerina /ai .observe ;
1819import  ballerina /constraint ;
1920import  ballerina /lang .array ;
2021import  ballerinax /azure .openai .chat ;
@@ -105,17 +106,22 @@ isolated function getGetResultsToolChoice() returns chat:ChatCompletionNamedTool
105106    }
106107};
107108
108- isolated  function  getGetResultsTool(map < json >  parameters ) returns  chat : ChatCompletionTool []| error  =>
109-     [
110-     {
111-         'type :  FUNCTION ,
112-         'function :  {
113-             name :  GET_RESULTS_TOOL ,
114-             parameters :  check  parameters .cloneWithType (),
115-             description :  " Tool to call with the response from a large language model (LLM) for a user prompt." 
116-         }
109+ isolated  function  getGetResultsTool(map < json >  parameters ) returns  chat : ChatCompletionTool []| ai :Error  {
110+     chat : ChatCompletionFunctionParameters | error  toolParam  =  parameters .ensureType ();
111+     if  toolParam  is  error  {
112+         return  error (" Error in generated schema: "   +  toolParam .message ());
117113    }
118- ];
114+     return  [
115+         {
116+             'type :  FUNCTION ,
117+             'function :  {
118+                 name :  GET_RESULTS_TOOL ,
119+                 parameters :  toolParam ,
120+                 description :  " Tool to call with the response from a large language model (LLM) for a user prompt." 
121+             }
122+         }
123+     ];
124+ }
119125
120126isolated  function  generateChatCreationContent(ai : Prompt  prompt ) returns  DocumentContentPart []| ai : Error  {
121127    string [] &  readonly  strings =  prompt .strings ;
@@ -234,11 +240,19 @@ isolated function handleParseResponseError(error chatResponseError) returns erro
234240isolated  function  generateLlmResponse(chat : Client  llmClient , string  deploymentId ,
235241        string  apiVersion , decimal  temperature , int  maxTokens , ai : Prompt  prompt ,
236242        typedesc < json >  expectedResponseTypedesc ) returns  anydata | ai : Error  {
237-     DocumentContentPart [] content  =  check  generateChatCreationContent (prompt );
238-     ResponseSchema  ResponseSchema  =  check  getExpectedResponseSchema (expectedResponseTypedesc );
239-     chat : ChatCompletionTool []| error  tools  =  getGetResultsTool (ResponseSchema .schema );
240-     if  tools  is  error  {
241-         return  error (" Error in generated schema: "   +  tools .message ());
243+     observe : GenerateContentSpan  span  =  observe : createGenerateContentSpan (deploymentId );
244+     span .addTemperature (temperature );
245+ 
246+     DocumentContentPart [] content ;
247+     ResponseSchema  responseSchema ;
248+     chat : ChatCompletionTool [] tools ;
249+     do  {
250+         content  =  check  generateChatCreationContent (prompt );
251+         responseSchema  =  check  getExpectedResponseSchema (expectedResponseTypedesc );
252+         tools  =  check  getGetResultsTool (responseSchema .schema );
253+     } on  fail  ai : Error  err  {
254+         span .close (err );
255+         return  err ;
242256    }
243257
244258    chat : CreateChatCompletionRequest  request  =  {
@@ -253,13 +267,43 @@ isolated function generateLlmResponse(chat:Client llmClient, string deploymentId
253267        max_tokens :  maxTokens ,
254268        tool_choice :  getGetResultsToolChoice ()
255269    };
270+     span .addInputMessages (request .messages .toJson ());
256271
257272    chat : CreateChatCompletionResponse | error  response  = 
258273        llmClient -> / deployments / [deploymentId ]/ chat / completions .post (apiVersion , request );
259274    if  response  is  error  {
260-         return  error (" LLM call failed: "   +  response .message (), cause  =  response .cause (), detail  =  response .detail ());
275+         ai : Error  err  =  error (" LLM call failed: "   +  response .message (), cause  =  response .cause (), detail  =  response .detail ());
276+         span .close (err );
277+         return  err ;
261278    }
262279
280+     string ? responseId =  response .id ;
281+     if  responseId  is  string  {
282+         span .addResponseId (responseId );
283+     }
284+     int ? inputTokens =  response .usage ?.prompt_tokens ;
285+     if  inputTokens  is  int  {
286+         span .addInputTokenCount (inputTokens );
287+     }
288+     int ? outputTokens =  response .usage ?.completion_tokens ;
289+     if  outputTokens  is  int  {
290+         span .addOutputTokenCount (outputTokens );
291+     }
292+ 
293+     anydata | ai : Error  result  =  ensureAnydataResult (response , expectedResponseTypedesc ,
294+             responseSchema .isOriginallyJsonObject , span );
295+     if  result  is  ai : Error  {
296+         span .close (result );
297+         return  result ;
298+     }
299+     span .addOutputMessages (result .toJson ());
300+     span .close ();
301+     return  result ;
302+ }
303+ 
304+ isolated  function  ensureAnydataResult(chat : CreateChatCompletionResponse  response ,
305+         typedesc < json >  expectedResponseTypedesc , boolean  isOriginallyJsonObject ,
306+         observe : GenerateContentSpan  span ) returns  anydata | ai : Error  {
263307    record  {
264308        chat : ChatCompletionResponseMessage  message ?;
265309        chat : ContentFilterChoiceResults  content_filter_results ?;
@@ -276,15 +320,18 @@ isolated function generateLlmResponse(chat:Client llmClient, string deploymentId
276320    if  toolCalls  is  () ||  toolCalls .length () ==  0  {
277321        return  error (NO_RELEVANT_RESPONSE_FROM_THE_LLM );
278322    }
323+     string ? finishReason =  choices [0 ].finish_reason ;
324+     if  finishReason  is  string  {
325+         span .addFinishReason (finishReason );
326+     }
279327
280328    chat : ChatCompletionMessageToolCall  tool  =  toolCalls [0 ];
281329    map < json >| error  arguments  =  tool .'function .arguments .fromJsonStringWithType ();
282330    if  arguments  is  error  {
283331        return  error (NO_RELEVANT_RESPONSE_FROM_THE_LLM );
284332    }
285333
286-     anydata | error  res =  parseResponseAsType (arguments .toJsonString (), expectedResponseTypedesc ,
287-             ResponseSchema .isOriginallyJsonObject );
334+     anydata | error  res =  parseResponseAsType (arguments .toJsonString (), expectedResponseTypedesc , isOriginallyJsonObject );
288335    if  res  is  error  {
289336        return  error  ai : LlmInvalidGenerationError (string  ` Invalid value returned from the LLM Client, expected: '${
290337            expectedResponseTypedesc .toBalString ()}', found '${res .toBalString ()}'`  );
0 commit comments