OpenAI class using hbCurl

OpenAI class using hbCurl

Postby reinaldocrespo » Tue May 09, 2023 6:42 pm

Hello Fivewinners;

For what's worth and in case anyone is interested, I share the class I wrote to query OpenAI for ICD10 diagnosis codes. It works really nice.

I think the only external function I use here is LogData() and I will share it if anyone wants it.

How you parse the response for the output you seek may be different.

Happy coding.

Reinaldo.

Code: Select all  Expand view  RUN


#INCLUDE "hbClass.ch"
#INCLUDE "hbcurl.ch"

#DEFINE CRLF CHR(13)+CHR(10)

CLASS TOpenAI

   DATA hCurl
   DATA httpcode
   DATA nError             INIT 0

   DATA Response
   DATA Prompt

   DATA model              INIT "text-davinci-003"
   DATA cUrl               INIT "https://api.openai.com/v1/completions"
   DATA max_tokens         INIT 2048
   DATA temperature        INIT 0
   DATA top_p              INIT 1
   DATA frequency_penalty  INIT 0.00
   DATA presence_penalty   INIT 0.00

   DATA cLogFile        INIT "TOpenAI.log"
   DATA nMaxLogSize     INIT 32768
   DATA lDebug          INIT .F.

   METHOD New()
   METHOD End()
   METHOD Send()
   METHOD Reset()

   //parses response fetching ICD10 codes coded by OpenAI.
   METHOD GetICD10sFromResponse()

ENDCLASS


//------------------------------------------------------------------------------------------------
METHOD New() CLASS TOpenAI

   ::hCurl := curl_easy_init()

RETURN Self


//------------------------------------------------------------------------------------------------
METHOD Send() CLASS TOpenAI
   LOCAL aheaders
   Local httpcode
   Local cJson
   Local h := { => }

   curl_easy_setopt( ::hCurl, HB_CURLOPT_POST, .T. )
   curl_easy_setopt( ::hCurl, HB_CURLOPT_URL, ::cUrl )

   //curl_easy_setopt( ::hCurl, HB_CURLOPT_PASSWORD, OPENAI_API_KEY )
   //http header could also be an array of headers
   aheaders := { "Content-Type: application/json", ;
                   "Authorization: Bearer " + OPENAI_API_KEY }
   
   curl_easy_setopt( ::hCurl, HB_CURLOPT_HTTPHEADER, aheaders )

   curl_easy_setopt( ::hCurl, HB_CURLOPT_USERNAME, '' )
   curl_easy_setopt( ::hCurl, HB_CURLOPT_DL_BUFF_SETUP )
   curl_easy_setopt( ::hCurl, HB_CURLOPT_SSL_VERIFYPEER, .F. )

   hb_HSet( h, "model", ::model )
   hb_HSet( h, "prompt", ::Prompt )
   hb_HSet( h, "max_tokens", ::max_tokens )
   hb_HSet( h, "temperature", ::temperature )
   hb_HSet( h, "frequency_penalty", ::frequency_penalty )
   hb_HSet( h, "presence_penalty", ::presence_penalty )

   cJson := hb_jsonEncode( h )
   curl_easy_setopt( ::hcurl, HB_CURLOPT_POSTFIELDS, cJson ) //cPostFields )

   ::nError := curl_easy_perform( ::hCurl )
   curl_easy_getinfo( ::hCurl, HB_CURLINFO_RESPONSE_CODE, @httpcode )

   ::httpcode := httpcode

   IF ::nError == HB_CURLE_OK

      ::Response = curl_easy_dl_buff_get( ::hCurl )

      IF ::lDebug
         LogData( ::cLogFile, { "prompt", ::Prompt, "model", ::model }, ::nMaxLogSize )
         LogData( ::cLogFile, ::Response, ::nMaxLogSize )
      ENDIF
   Else

      LogData( ::cLogFile, { "TTOpenAI prompt was:", ::Prompt }, ::nMaxLogSize )
      LogData( ::cLogFile, { "Error Num:", ::nError, "Httpcode:", ::httpcode }, ::nMaxLogSize )
      LogData( ::cLogFile, { "Response:", ::Response }, ::nMaxLogSize )
      LogData( ::cLogFile, curl_easy_strerror( ::nError ), ::nMaxLogSize )

   ENDIF

return ::Response            

//------------------------------------------------------------------------------------------------
METHOD Reset() CLASS TOpenAI
     
   curl_easy_reset( ::hCurl )
   ::nError := HB_CURLE_OK
   ::Response := Nil

return NIL



//------------------------------------------------------------------------------------------------
METHOD End() CLASS TOpenAI

   curl_easy_cleanup( ::hCurl )
   ::hCurl := Nil  

return NIL


/*------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------*/

METHOD GetICD10sFromResponse() CLASS TOpenAI
   LOCAL aICD10s := {}
   LOCAL aParsed, iter, acodelines, code
   LOCAL a := {}
   LOCAL aKeys
   LOCAL hResponse := hb_jsonDecode( ::Response )

   if ::lDebug

      LogData( ::cLogFile, { "hb_isHash( hResponse )", hb_isHash( hResponse ), ;
                           "hb_HHasKey( hResponse, 'choices' )", hb_HHasKey( hResponse, "choices" ) } )
      aKeys := HGetKeys( hResponse )
      LogData( ::cLogFile, aKeys, ::nMaxLogSize )

   ENDIF


   IF hb_isHash( hResponse ) .AND. ;
      hb_HHasKey( hResponse, "choices" )

      if HB_ISARRAY( hResponse[ "choices" ] )
         a := hResponse[ "choices" ]
      endif

      FOR iter := 1 TO LEN( a )

         IF hb_HHasKey( a[ iter ], "text" )
         
            acodelines := hb_ATokens( a[ iter ][ "text" ], CRLF )

            FOR EACH Code IN acodelines

               aParsed := hb_ATokens( Code, "-" )
               AADD( aIcd10s, aParsed )

            NEXT

         ENDIF

      NEXT iter

   ENDIF

RETURN aICD10s
 
User avatar
reinaldocrespo
 
Posts: 979
Joined: Thu Nov 17, 2005 5:49 pm
Location: Fort Lauderdale, FL

Re: OpenAI class using hbCurl

Postby Antonio Linares » Tue May 09, 2023 6:45 pm

Dear Reinaldo,

Congratulations for porting it to use CURL and many thanks for sharing it.

Just curiosity, would you mind to show an example about how you query it ?
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Antonio Linares
Site Admin
 
Posts: 42122
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain

Re: OpenAI class using hbCurl

Postby reinaldocrespo » Tue May 09, 2023 7:40 pm

Yes, it makes sense.

Here is sample code on how I use it. I'm pasting text that the program reads from dictations so you see how to feed OpenAI:

Code: Select all  Expand view  RUN

      TEXT INTO cText
         ENDOVAGINAL SONOGRAM:
         Compared to previous study dated 08/24/2022 reporting tiny fibroid and endometrial polyp.
         Normal size, retroflexed uterus measuring 7 x 4.3 x 5 cm. Focal area of endometrial thickening measuring 9 mm suggesting an endometrial polyp, stable since previous study. Intramural fibroid in posterior fundal wall measuring 9 mm, also stable. Multiple cervical Nabothian cysts. Normal endocervical stripe.
         Right ovary: 3.3 x 2.1 x 2.3 cm, with dominant follicle measuring 1.1 cm.
         Left ovary: 3.7 x 2.3 x 2.2 cm, with small corpus luteum cyst measuring 8 mm.
         No adnexal masses, or fluid collections.
         
         IMPRESSION:        
         SMALL ENDOMETRIAL POLYP AND INTRAMURAL FIBROID, WITHOUT SIGNIFICANT CHANGE SINCE PREVIOUS STUDY.
      ENDTEXT

      oAI := TOpenAI():New()
      oAI:lDebug := .T.
      //Open AI models allowed for completions are:
      //text-davinci-003, text-davinci-002, text-curie-001, text-babbage-001, text-ada-001
      //text-ada is the fastest and least expensive.
      oAI:model  := "text-ada-001" //"text-davinci-003"
      oAI:Prompt := "Code with ICD10 diagnosis codes the following radiological imaging transcription: " + CRLF + cText
      oAI:Send()

      IF !Empty( oAI:Response )
          MsgInfo( oAI:Response )
      ENDIF

 


Response in this case will be aJSon structure with ICD10 codes and descriptions. I parse the result and move to an xbrowse where coders work with it. After formatting the response it looks like this:
ICD10 codes:

Tiny fibroid: D25.0
Endometrial polyp: N84.0
Retroflexed uterus: N89.3
Endometrial thickening: N85.00
Intramural fibroid: D25.1
Nabothian cysts: N88.0
Dominant follicle: N83.0
Corpus luteum cyst: N83.1
No adnexal masses or fluid collections: R93.8


They no longer need to lookup codes on books or websites.
User avatar
reinaldocrespo
 
Posts: 979
Joined: Thu Nov 17, 2005 5:49 pm
Location: Fort Lauderdale, FL

Re: OpenAI class using hbCurl

Postby reinaldocrespo » Tue May 09, 2023 7:47 pm

Here is another example:
Code: Select all  Expand view  RUN
     TEXT INTO cText
         CHEST FOR RIBS:
         There is a non displaced fracture of the anterolateral aspect of the right 7th rib.
         There are wire sutures in the sternum. There is cardiomegaly and there is elongation and tortuosity of the aorta.
      ENDTEXT

      oAI := TOpenAI():New()
      oAI:lDebug := .T.
      //Open AI models allowed for completions are:
      //text-davinci-003, text-davinci-002, text-curie-001, text-babbage-001, text-ada-001
      //text-ada is the fastest and least expensive.
      oAI:model  := "text-ada-001" //"text-davinci-003"
      oAI:Prompt := "Code with ICD10 diagnosis codes the following radiological imaging transcription: " + CRLF + cText
      oAI:Send()

      IF !Empty( oAI:Response )
         ...do whatever with oAI:Response
     ENDIF
 


After parsing the Json data received on Response, this is what I get:
ICD10 codes:
- Non-displaced fracture of the rib(s): S22.4-
- Wire sutures in the sternum: Z98.1
- Cardiomegaly: I51.7
- Elongation and tortuosity of the aorta: Q27.8


Hope it helps.
User avatar
reinaldocrespo
 
Posts: 979
Joined: Thu Nov 17, 2005 5:49 pm
Location: Fort Lauderdale, FL

Re: OpenAI class using hbCurl

Postby Antonio Linares » Wed May 10, 2023 5:44 am

Amazing... :-)
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Antonio Linares
Site Admin
 
Posts: 42122
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain

Re: OpenAI class using hbCurl

Postby Otto » Wed May 10, 2023 6:18 am

Dear Antonio, dear Reinaldo,

Since I'm unsure about the costs, I'm currently using an offline version. For example, in a reception with multiple users making requests all day, hundreds of requests could be generated, and using the API could become expensive.
At the moment, users seem to expect information to be free, but that won't remain the case.

@ Reinado, we just had a great event. Here's a link:

https://pustertal-online.com/index.prg?recnr=781
:-) Powered by mod harbour

If there's interest in a new edition of a Fivewin/Harbour meeting, we'd be happy to organize it.


Image

Image

Image
********************************************************************
mod harbour - Vamos a la conquista de la Web
modharbour.org
https://www.facebook.com/groups/modharbour.club
********************************************************************
User avatar
Otto
 
Posts: 6346
Joined: Fri Oct 07, 2005 7:07 pm

Re: OpenAI class using hbCurl

Postby reinaldocrespo » Wed May 10, 2023 12:27 pm

Dear Otto;

If you organize that meeting again, count me in. I will be the first the attend. Perhaps you can start a thread to count who else is interested. I bear witness that your last meeting was an event to be remembered forever.

@fivewinners--
On the subject of OpenAI, I have made some changes to the class. I have learned that there are differences on the models and depending on your needs, you would have to change the models and URL.

Here is the new class now:
Code: Select all  Expand view  RUN


#INCLUDE "hbClass.ch"
#INCLUDE "hbcurl.ch"

#DEFINE CRLF CHR(13)+CHR(10)

CLASS TOpenAI

   DATA hCurl
   DATA httpcode
   DATA nError             INIT 0

   DATA Response
   DATA Prompt

   DATA model              INIT "text-davinci-003"
   DATA cUrl               INIT "https://api.openai.com/v1/completions"
   DATA max_tokens         INIT 2048
   DATA temperature        INIT 0
   DATA top_p              INIT 1
   DATA frequency_penalty  INIT 0.00
   DATA presence_penalty   INIT 0.00

   DATA cLogFile        INIT "TOpenAI.log"
   DATA nMaxLogSize     INIT 32768
   DATA lDebug          INIT .F.

   METHOD New()
   METHOD End()
   METHOD Send()
   METHOD Reset()

ENDCLASS


//------------------------------------------------------------------------------------------------
METHOD New() CLASS TOpenAI

   ::hCurl := curl_easy_init()

RETURN Self


//------------------------------------------------------------------------------------------------
METHOD Send() CLASS TOpenAI
   LOCAL aheaders
   Local httpcode
   Local cJson
   Local h := { => }

   curl_easy_setopt( ::hCurl, HB_CURLOPT_POST, .T. )
   curl_easy_setopt( ::hCurl, HB_CURLOPT_URL, ::cUrl )

   //curl_easy_setopt( ::hCurl, HB_CURLOPT_PASSWORD, OPENAI_API_KEY )
   //http header could also be an array of headers
   aheaders := { "Content-Type: application/json", ;
                   "Authorization: Bearer " + OPENAI_API_KEY }
   
   curl_easy_setopt( ::hCurl, HB_CURLOPT_HTTPHEADER, aheaders )

   curl_easy_setopt( ::hCurl, HB_CURLOPT_USERNAME, '' )
   curl_easy_setopt( ::hCurl, HB_CURLOPT_DL_BUFF_SETUP )
   curl_easy_setopt( ::hCurl, HB_CURLOPT_SSL_VERIFYPEER, .F. )

   hb_HSet( h, "model", ::model )
   hb_HSet( h, "prompt", ::Prompt )
   hb_HSet( h, "max_tokens", ::max_tokens )
   hb_HSet( h, "temperature", ::temperature )
   hb_HSet( h, "frequency_penalty", ::frequency_penalty )
   hb_HSet( h, "presence_penalty", ::presence_penalty )

   cJson := hb_jsonEncode( h )
   curl_easy_setopt( ::hcurl, HB_CURLOPT_POSTFIELDS, cJson ) //cPostFields )

   ::nError := curl_easy_perform( ::hCurl )
   curl_easy_getinfo( ::hCurl, HB_CURLINFO_RESPONSE_CODE, @httpcode )

   ::httpcode := httpcode

   IF ::nError == HB_CURLE_OK

      ::Response = curl_easy_dl_buff_get( ::hCurl )

      IF ::lDebug
         LogData( ::cLogFile, { "prompt", ::Prompt, "model", ::model }, ::nMaxLogSize )
         LogData( ::cLogFile, ::Response, ::nMaxLogSize )
      ENDIF
   Else

      LogData( ::cLogFile, { "TTOpenAI prompt was:", ::Prompt }, ::nMaxLogSize )
      LogData( ::cLogFile, { "Error Num:", ::nError, "Httpcode:", ::httpcode }, ::nMaxLogSize )
      LogData( ::cLogFile, { "Response:", ::Response }, ::nMaxLogSize )
      LogData( ::cLogFile, curl_easy_strerror( ::nError ), ::nMaxLogSize )

   ENDIF

return ::Response            

//------------------------------------------------------------------------------------------------
METHOD Reset() CLASS TOpenAI
     
   curl_easy_reset( ::hCurl )
   ::nError := HB_CURLE_OK
   ::Response := Nil

return NIL



//------------------------------------------------------------------------------------------------
METHOD End() CLASS TOpenAI

   curl_easy_cleanup( ::hCurl )
   ::hCurl := Nil  

return NIL


/*------------------------------------------------------------------------------------------------
typeical response:
{
    "id": "cmpl-7EOhp7sTGKvQv0hsntGbg41BdadX9",
    "object": "text_completion",
    "created": 1683665917,
    "model": "text-davinci-003",
    "choices": [
        {
            "text": "\r\nICD-10 Diagnosis Codes: \r\n1. S52.521A - Fracture of lower end of right radius, initial encounter for closed fracture \r\n2. S52.522A - Fracture of lower end of right ulna, initial encounter for closed fracture \r\n3. S52.531A - Fracture of shaft of right radius, initial encounter for closed fracture \r\n4. S52.532A - Fracture of shaft of right ulna, initial encounter for closed fracture",
            "index": 0,
            "logprobs": null,
            "finish_reason": "stop"
        }
    ],
    "usage": {
        "prompt_tokens": 417,
        "completion_tokens": 113,
        "total_tokens": 530
    }
}
-------------------------------------------------------------------------------------------------*/

 


If you wish to emulate chatting, which is the most powerful, you'd change the URL and model like this:

:cURL := https://api.openai.com/v1/chat/completions
:Model := "gpt-3.5-turbo"

and then make the call using the :send() method after you have loaded the prompt.

I suspect I will be making many more changes to the class so that it works with any URL and Model combination and be able to manage any response. Keep in mind that the Json response will be different depending on what model you are using.

For my current use, which is to parse response for ICD10 codes, here is what I'm doing:
Code: Select all  Expand view  RUN
CLASS TCodeICD10sWOpenAI FROM TOpenAI
   METHOD GetICD10sFromResponse()
END CLASS

//#DEFINE _ONLY_ICD10S "\b[A-TV-Z]\d{2}\.\d{1,4}[A-Z]?\b\s*-\s*.+"
#DEFINE _ONLY_ICD10S "\s*[A-TV-Z]\d{2}\.\d{1,4}[A-Z]*\b\s*-\s.+"
METHOD GetICD10sFromResponse() CLASS TCodeICD10sWOpenAI
   LOCAL aICD10s := {}
   LOCAL aParsed, iter, acodelines, CodeLine
   LOCAL a := {}
   LOCAL aKeys, aCleanLn
   LOCAL hResponse := hb_jsonDecode( ::Response )

   if ::lDebug

      LogData( ::cLogFile, { "hb_isHash( hResponse )", hb_isHash( hResponse ), ;
                           "hb_HHasKey( hResponse, 'choices' )", hb_HHasKey( hResponse, "choices" ) } )
      aKeys := HGetKeys( hResponse )
      LogData( ::cLogFile, aKeys, ::nMaxLogSize )

   ENDIF


   IF hb_isHash( hResponse ) .AND. ;
      hb_HHasKey( hResponse, "choices" )

      if HB_ISARRAY( hResponse[ "choices" ] )
         a := hResponse[ "choices" ]
      endif

      FOR iter := 1 TO LEN( a )

         IF hb_HHasKey( a[ iter ], "text" )

            acodelines := hb_ATokens( a[ iter ]["text"], CRLF )

            FOR EACH CodeLine IN acodelines
               aCleanLn := hb_RegEx( _ONLY_ICD10S, CodeLine )

               IF Len( aCleanLn ) > 0

                  aParsed := hb_ATokens( aCleanLn[ 1 ], "-" )
                  AADD( aIcd10s, aParsed )

               ENDIF

            NEXT

         ENDIF

      NEXT iter

   ENDIF

RETURN aICD10s
 


Exactly how you can benefit from OpenAI, takes imagination. For me, being able to have the system automatically code medical procedures using CPT codes and diagnosis conditions using International coding diagnosis version 10 codes places my software ahead of everyone else. I know that won't last long. Others will see it and will easily imitate it but I know my users will be very very happy with this new feature.

Reinaldo.
User avatar
reinaldocrespo
 
Posts: 979
Joined: Thu Nov 17, 2005 5:49 pm
Location: Fort Lauderdale, FL

Re: OpenAI class using hbCurl

Postby Antonio Linares » Wed May 10, 2023 1:46 pm

Dear Reinaldo,

many thanks!

very good :-)
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Antonio Linares
Site Admin
 
Posts: 42122
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain

Re: OpenAI class using hbCurl

Postby Antonio Linares » Thu May 11, 2023 12:38 pm

Dear Reinaldo,

Are you aware that chatGPT many thanks fails (it invents) on its answers ?

This may be specially important when dealing with medical issues...
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Antonio Linares
Site Admin
 
Posts: 42122
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain


Return to FiveWin for Harbour/xHarbour

Who is online

Users browsing this forum: No registered users and 31 guests