uncloseai.com/public/languages/fortran/uncloseai.f90

296 lines
10 KiB
Fortran

! UncloseAI Fortran Library
! OpenAI-compatible API client with streaming support
! Compatible with vLLM, Ollama, and OpenAI-compatible endpoints
!
! Note: Fortran lacks native HTTP support, so this uses curl via shell commands
module uncloseai_lib
implicit none
private
public :: uncloseai_client, uncloseai_init, uncloseai_chat, uncloseai_chat_stream, uncloseai_tts
type :: model_info
character(len=512) :: id
character(len=512) :: endpoint
integer :: max_tokens
end type model_info
type :: uncloseai_client
type(model_info), dimension(:), allocatable :: models
character(len=512), dimension(:), allocatable :: tts_endpoints
integer :: model_count
integer :: tts_count
integer :: timeout
end type uncloseai_client
contains
! Initialize client with auto-discovery
subroutine uncloseai_init(client)
type(uncloseai_client), intent(out) :: client
integer :: i, ios
character(len=512) :: var_name, endpoint
character(len=1024) :: cmd
logical :: file_exists
write(*,*) 'Initializing UncloseAI client...'
client%model_count = 0
client%tts_count = 0
client%timeout = 30
allocate(client%models(100))
allocate(client%tts_endpoints(10))
! Discover chat/code models
do i = 1, 9999
write(var_name, '(A,I0)') 'MODEL_ENDPOINT_', i
call get_environment_variable(trim(var_name), endpoint)
if (len_trim(endpoint) == 0) exit
write(*,'(A,I0,A,A)') 'Endpoint ', i, ': ', trim(endpoint)
! Use curl to get models, filter modelperm-*, save model ID
write(cmd, '(A,A,A)') &
'curl -s "', trim(endpoint), '/models" | ' // &
'jq -r ''.data[]|select(.id|test("^modelperm-")|not)|.id'' | ' // &
'head -1 > /tmp/fortran_model.txt 2>/dev/null || echo "" > /tmp/fortran_model.txt'
call execute_command_line(trim(cmd), exitstat=ios)
! Read discovered model
inquire(file='/tmp/fortran_model.txt', exist=file_exists)
if (file_exists) then
open(unit=10, file='/tmp/fortran_model.txt', status='old', action='read', iostat=ios)
if (ios == 0) then
read(10, '(A)', iostat=ios) client%models(client%model_count + 1)%id
close(10)
if (ios == 0 .and. len_trim(client%models(client%model_count + 1)%id) > 0) then
client%models(client%model_count + 1)%endpoint = trim(endpoint)
client%models(client%model_count + 1)%max_tokens = 8192
client%model_count = client%model_count + 1
end if
else
close(10)
end if
end if
end do
! Discover TTS endpoints
do i = 1, 9999
write(var_name, '(A,I0)') 'TTS_ENDPOINT_', i
call get_environment_variable(trim(var_name), endpoint)
if (len_trim(endpoint) == 0) exit
client%tts_endpoints(client%tts_count + 1) = trim(endpoint)
client%tts_count = client%tts_count + 1
end do
write(*,'(A,I0,A,I0,A)') 'Discovered ', client%model_count, &
' models, ', client%tts_count, ' TTS endpoints'
write(*,*)
end subroutine uncloseai_init
! Non-streaming chat completion
subroutine uncloseai_chat(client, messages, model_idx, response, status)
type(uncloseai_client), intent(in) :: client
character(len=*), intent(in) :: messages
integer, intent(in), optional :: model_idx
character(len=4096), intent(out) :: response
integer, intent(out) :: status
integer :: idx, ios
character(len=4096) :: cmd
logical :: file_exists
idx = 0
if (present(model_idx)) idx = model_idx
if (idx >= client%model_count) then
response = 'ERROR: Invalid model index'
status = -1
return
end if
! Build curl command for non-streaming
write(cmd, '(A,A,A,A,A)') &
'curl -s -X POST "', trim(client%models(idx + 1)%endpoint), '/chat/completions" ', &
'-H "Content-Type: application/json" ', &
'-d ''{"model":"', trim(client%models(idx + 1)%id), &
'","messages":', trim(messages), &
',"stream":false,"max_tokens":100}'' | ' // &
'jq -r ''.choices[0].message.content'' > /tmp/fortran_response.txt 2>/dev/null'
call execute_command_line(trim(cmd), exitstat=ios)
if (ios == 0) then
inquire(file='/tmp/fortran_response.txt', exist=file_exists)
if (file_exists) then
open(unit=10, file='/tmp/fortran_response.txt', status='old', action='read', iostat=ios)
if (ios == 0) then
read(10, '(A)', iostat=ios) response
close(10)
status = 0
else
close(10)
response = 'ERROR: Failed to read response'
status = -1
end if
else
response = 'ERROR: No response file'
status = -1
end if
else
response = 'ERROR: HTTP request failed'
status = -1
end if
call execute_command_line('rm -f /tmp/fortran_response.txt', exitstat=ios)
end subroutine uncloseai_chat
! Streaming chat completion
subroutine uncloseai_chat_stream(client, messages, model_idx, status)
type(uncloseai_client), intent(in) :: client
character(len=*), intent(in) :: messages
integer, intent(in), optional :: model_idx
integer, intent(out) :: status
integer :: idx, ios
character(len=4096) :: cmd
idx = 0
if (present(model_idx)) idx = model_idx
if (idx >= client%model_count) then
write(*,*) 'ERROR: Invalid model index'
status = -1
return
end if
! Build curl command for streaming with SSE parsing
write(cmd, '(A,A,A,A,A)') &
'curl -s --no-buffer -X POST "', trim(client%models(idx + 1)%endpoint), &
'/chat/completions" ', &
'-H "Content-Type: application/json" ', &
'-d ''{"model":"', trim(client%models(idx + 1)%id), &
'","messages":', trim(messages), &
',"stream":true,"max_tokens":500}'' | ' // &
'while IFS= read -r line; do ' // &
'[[ "$line" =~ ^data:\ (.+)$ ]] || continue; ' // &
'data="${BASH_REMATCH[1]}"; ' // &
'[ "$data" = "[DONE]" ] && break; ' // &
'printf "%s" "$(echo "$data"|jq -r ''.choices[0].delta.content//empty'')"; ' // &
'done'
call execute_command_line(trim(cmd), exitstat=ios)
status = ios
end subroutine uncloseai_chat_stream
! Text-to-speech generation
subroutine uncloseai_tts(client, text, voice, output_file, status)
type(uncloseai_client), intent(in) :: client
character(len=*), intent(in) :: text
character(len=*), intent(in), optional :: voice
character(len=*), intent(in), optional :: output_file
integer, intent(out) :: status
character(len=256) :: voice_str, file_str
character(len=2048) :: cmd
integer :: ios
voice_str = 'alloy'
if (present(voice)) voice_str = trim(voice)
file_str = '/tmp/speech.mp3'
if (present(output_file)) file_str = trim(output_file)
if (client%tts_count == 0) then
status = -1
return
end if
! Build curl command for TTS
write(cmd, '(A,A,A,A,A,A,A)') &
'curl -s -X POST "', trim(client%tts_endpoints(1)), '/audio/speech" ', &
'-H "Content-Type: application/json" ', &
'-d ''{"model":"tts-1","voice":"', trim(voice_str), &
'","input":"', trim(text), '"}'' ', &
'-o ', trim(file_str)
call execute_command_line(trim(cmd), exitstat=ios)
status = ios
end subroutine uncloseai_tts
end module uncloseai_lib
! Demo program showing library usage
program main
use uncloseai_lib
implicit none
type(uncloseai_client) :: client
character(len=4096) :: response
integer :: status, model_idx
logical :: file_exists
write(*,*) '=== UncloseAI Fortran Client (with Streaming) ==='
write(*,*)
! Initialize client
call uncloseai_init(client)
if (client%model_count == 0) then
write(*,*) 'ERROR: No models discovered'
stop 1
end if
! Non-streaming chat example
write(*,*) '=== Non-Streaming Chat ==='
write(*,'(A,A)') 'Model: ', trim(client%models(1)%id)
call uncloseai_chat(client, &
'[{"role":"user","content":"Explain quantum computing in one sentence"}]', &
0, response, status)
if (status == 0) then
write(*,'(A,A)') 'Response: ', trim(response)
else
write(*,*) trim(response)
end if
write(*,*)
! Streaming chat example
model_idx = 0
if (client%model_count >= 2) model_idx = 1
write(*,*) '=== Streaming Chat ==='
write(*,'(A,A)') 'Model: ', trim(client%models(model_idx + 1)%id)
write(*,'(A)', advance='no') 'Response: '
call uncloseai_chat_stream(client, &
'[{"role":"user","content":"Write a hello world program in Fortran"}]', &
model_idx, status)
write(*,*)
write(*,*)
! TTS example
if (client%tts_count > 0) then
write(*,*) '=== TTS Speech Generation ==='
write(*,*) 'Model: tts-1'
call uncloseai_tts(client, &
'Hello from UncloseAI Fortran client!', &
'alloy', '/tmp/speech.mp3', status)
if (status == 0) then
write(*,*) 'Audio saved to /tmp/speech.mp3'
else
write(*,*) 'TTS failed'
end if
end if
write(*,*)
write(*,*) '=== Examples Complete ==='
end program main