wip: add tests for openai client
This commit is contained in:
@@ -110,3 +110,41 @@ fn (mut f OpenAI) create_audio_request(args AudioArgs, endpoint string) !AudioRe
|
|||||||
}
|
}
|
||||||
return json.decode(AudioResponse, r.body)!
|
return json.decode(AudioResponse, r.body)!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct CreateSpeechArgs {
|
||||||
|
pub:
|
||||||
|
model ModelType = .tts_1
|
||||||
|
input string @[required]
|
||||||
|
voice Voice = .alloy
|
||||||
|
response_format AudioFormat = .mp3
|
||||||
|
speed f32 = 1.0
|
||||||
|
output_path string @[required]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CreateSpeechRequest {
|
||||||
|
pub:
|
||||||
|
model string
|
||||||
|
input string
|
||||||
|
voice string
|
||||||
|
response_format string
|
||||||
|
speed f32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut f OpenAI) create_speech(args CreateSpeechArgs) ! {
|
||||||
|
mut output_file := os.open_file(args.output_path, 'w+')!
|
||||||
|
|
||||||
|
req := CreateSpeechRequest{
|
||||||
|
model: modelname_str(args.model)
|
||||||
|
input: args.input
|
||||||
|
voice: voice_str(args.voice)
|
||||||
|
response_format: audio_format_str(args.response_format)
|
||||||
|
speed: args.speed
|
||||||
|
}
|
||||||
|
data := json.encode(req)
|
||||||
|
|
||||||
|
mut conn := f.connection()!
|
||||||
|
r := conn.post_json_str(prefix: 'audio/speech', data: data)!
|
||||||
|
|
||||||
|
output_file.write(r.bytes())!
|
||||||
|
}
|
||||||
|
|||||||
105
lib/clients/openai/client_test.v
Normal file
105
lib/clients/openai/client_test.v
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
module openai
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
fn test_chat_completion() {
|
||||||
|
key := os.getenv('OPENAI_API_KEY')
|
||||||
|
heroscript := '!!openai.configure api_key: "${key}"'
|
||||||
|
|
||||||
|
play(heroscript: heroscript)!
|
||||||
|
|
||||||
|
mut client := get()!
|
||||||
|
|
||||||
|
res := client.chat_completion(.gpt_4o_2024_08_06, Messages{
|
||||||
|
messages: [
|
||||||
|
Message{
|
||||||
|
role: .user
|
||||||
|
content: 'Say these words exactly as i write them with no punctuation: AI is getting out of hand'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})!
|
||||||
|
|
||||||
|
assert res.choices.len == 1
|
||||||
|
|
||||||
|
assert res.choices[0].message.content == 'AI is getting out of hand'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_embeddings() {
|
||||||
|
key := os.getenv('OPENAI_API_KEY')
|
||||||
|
heroscript := '!!openai.configure api_key: "${key}"'
|
||||||
|
|
||||||
|
play(heroscript: heroscript)!
|
||||||
|
|
||||||
|
mut client := get()!
|
||||||
|
|
||||||
|
res := client.create_embeddings(
|
||||||
|
input: ['The food was delicious and the waiter..']
|
||||||
|
model: .text_embedding_ada
|
||||||
|
)!
|
||||||
|
|
||||||
|
assert res.data.len == 1
|
||||||
|
assert res.data[0].embedding.len == 1536
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_files() {
|
||||||
|
key := os.getenv('OPENAI_API_KEY')
|
||||||
|
heroscript := '!!openai.configure api_key: "${key}"'
|
||||||
|
|
||||||
|
play(heroscript: heroscript)!
|
||||||
|
|
||||||
|
mut client := get()!
|
||||||
|
uploaded_file := client.upload_file(
|
||||||
|
filepath: '${os.dir(@FILE) + '/testdata/testfile.txt'}'
|
||||||
|
purpose: .assistants
|
||||||
|
)!
|
||||||
|
|
||||||
|
assert uploaded_file.filename == 'testfile.txt'
|
||||||
|
assert uploaded_file.purpose == 'assistants'
|
||||||
|
|
||||||
|
got_file := client.get_file(uploaded_file.id)!
|
||||||
|
assert got_file == uploaded_file
|
||||||
|
|
||||||
|
uploaded_file2 := client.upload_file(
|
||||||
|
filepath: '${os.dir(@FILE) + '/testdata/testfile2.txt'}'
|
||||||
|
purpose: .assistants
|
||||||
|
)!
|
||||||
|
|
||||||
|
assert uploaded_file2.filename == 'testfile2.txt'
|
||||||
|
assert uploaded_file2.purpose == 'assistants'
|
||||||
|
|
||||||
|
mut got_list := client.list_files()!
|
||||||
|
|
||||||
|
assert got_list.data.len >= 2 // there could be other older files
|
||||||
|
|
||||||
|
mut ids := []string{}
|
||||||
|
for file in got_list.data {
|
||||||
|
ids << file.id
|
||||||
|
}
|
||||||
|
|
||||||
|
assert uploaded_file.id in ids
|
||||||
|
assert uploaded_file2.id in ids
|
||||||
|
|
||||||
|
for file in got_list.data {
|
||||||
|
client.delete_file(file.id)!
|
||||||
|
}
|
||||||
|
|
||||||
|
got_list = client.list_files()!
|
||||||
|
assert got_list.data.len == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_audio() {
|
||||||
|
key := os.getenv('OPENAI_API_KEY')
|
||||||
|
heroscript := '!!openai.configure api_key: "${key}"'
|
||||||
|
|
||||||
|
play(heroscript: heroscript)!
|
||||||
|
|
||||||
|
mut client := get()!
|
||||||
|
|
||||||
|
// create speech
|
||||||
|
client.create_speech(
|
||||||
|
input: 'the quick brown fox jumps over the lazy dog'
|
||||||
|
output_path: '/tmp/output.mp3'
|
||||||
|
)!
|
||||||
|
|
||||||
|
assert os.exists('/tmp/output.mp3')
|
||||||
|
}
|
||||||
@@ -63,8 +63,10 @@ pub fn (mut f OpenAI) chat_completion(model_type ModelType, msgs Messages) !Chat
|
|||||||
m.messages << mr
|
m.messages << mr
|
||||||
}
|
}
|
||||||
data := json.encode(m)
|
data := json.encode(m)
|
||||||
|
println('data: ${data}')
|
||||||
mut conn := f.connection()!
|
mut conn := f.connection()!
|
||||||
r := conn.post_json_str(prefix: 'chat/completions', data: data)!
|
r := conn.post_json_str(prefix: 'chat/completions', data: data)!
|
||||||
|
println('res: ${r}')
|
||||||
|
|
||||||
res := json.decode(ChatCompletion, r)!
|
res := json.decode(ChatCompletion, r)!
|
||||||
return res
|
return res
|
||||||
|
|||||||
@@ -11,7 +11,14 @@ const jsonl_mime_type = 'text/jsonl'
|
|||||||
pub struct FileUploadArgs {
|
pub struct FileUploadArgs {
|
||||||
pub:
|
pub:
|
||||||
filepath string
|
filepath string
|
||||||
purpose string
|
purpose FilePurpose
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum FilePurpose {
|
||||||
|
assistants
|
||||||
|
vision
|
||||||
|
batch
|
||||||
|
fine_tuning
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct File {
|
pub struct File {
|
||||||
@@ -51,7 +58,7 @@ pub fn (mut f OpenAI) upload_file(args FileUploadArgs) !File {
|
|||||||
'file': [file_data]
|
'file': [file_data]
|
||||||
}
|
}
|
||||||
form: {
|
form: {
|
||||||
'purpose': args.purpose
|
'purpose': file_purpose_str(args.purpose)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,3 +100,21 @@ pub fn (mut f OpenAI) get_file_content(file_id string) !string {
|
|||||||
r := conn.get(prefix: 'files/' + file_id + '/content')!
|
r := conn.get(prefix: 'files/' + file_id + '/content')!
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns the purpose of the file in string format
|
||||||
|
fn file_purpose_str(purpose FilePurpose) string {
|
||||||
|
return match purpose {
|
||||||
|
.assistants {
|
||||||
|
'assistants'
|
||||||
|
}
|
||||||
|
.vision {
|
||||||
|
'vision'
|
||||||
|
}
|
||||||
|
.batch {
|
||||||
|
'batch'
|
||||||
|
}
|
||||||
|
.fine_tuning {
|
||||||
|
'fine_tuning'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
module openai
|
module openai
|
||||||
|
|
||||||
pub enum ModelType {
|
pub enum ModelType {
|
||||||
|
gpt_4o_2024_08_06
|
||||||
gpt_3_5_turbo
|
gpt_3_5_turbo
|
||||||
gpt_4
|
gpt_4
|
||||||
gpt_4_0613
|
gpt_4_0613
|
||||||
@@ -10,16 +11,17 @@ pub enum ModelType {
|
|||||||
gpt_3_5_turbo_16k
|
gpt_3_5_turbo_16k
|
||||||
gpt_3_5_turbo_16k_0613
|
gpt_3_5_turbo_16k_0613
|
||||||
whisper_1
|
whisper_1
|
||||||
|
tts_1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn modelname_str(e ModelType) string {
|
fn modelname_str(e ModelType) string {
|
||||||
if e == .gpt_4 {
|
|
||||||
return 'gpt-4'
|
|
||||||
}
|
|
||||||
if e == .gpt_3_5_turbo {
|
|
||||||
return 'gpt-3.5-turbo'
|
|
||||||
}
|
|
||||||
return match e {
|
return match e {
|
||||||
|
.tts_1 {
|
||||||
|
'tts-1'
|
||||||
|
}
|
||||||
|
.gpt_4o_2024_08_06 {
|
||||||
|
'gpt-4o-2024-08-06'
|
||||||
|
}
|
||||||
.gpt_4 {
|
.gpt_4 {
|
||||||
'gpt-4'
|
'gpt-4'
|
||||||
}
|
}
|
||||||
@@ -73,3 +75,79 @@ fn roletype_str(x RoleType) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum Voice {
|
||||||
|
alloy
|
||||||
|
ash
|
||||||
|
coral
|
||||||
|
echo
|
||||||
|
fable
|
||||||
|
onyx
|
||||||
|
nova
|
||||||
|
sage
|
||||||
|
shimmer
|
||||||
|
}
|
||||||
|
|
||||||
|
fn voice_str(x Voice) string {
|
||||||
|
return match x {
|
||||||
|
.alloy {
|
||||||
|
'alloy'
|
||||||
|
}
|
||||||
|
.ash {
|
||||||
|
'ash'
|
||||||
|
}
|
||||||
|
.coral {
|
||||||
|
'coral'
|
||||||
|
}
|
||||||
|
.echo {
|
||||||
|
'echo'
|
||||||
|
}
|
||||||
|
.fable {
|
||||||
|
'fable'
|
||||||
|
}
|
||||||
|
.onyx {
|
||||||
|
'onyx'
|
||||||
|
}
|
||||||
|
.nova {
|
||||||
|
'nova'
|
||||||
|
}
|
||||||
|
.sage {
|
||||||
|
'sage'
|
||||||
|
}
|
||||||
|
.shimmer {
|
||||||
|
'shimmer'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum AudioFormat {
|
||||||
|
mp3
|
||||||
|
opus
|
||||||
|
aac
|
||||||
|
flac
|
||||||
|
wav
|
||||||
|
pcm
|
||||||
|
}
|
||||||
|
|
||||||
|
fn audio_format_str(x AudioFormat) string {
|
||||||
|
return match x {
|
||||||
|
.mp3 {
|
||||||
|
'mp3'
|
||||||
|
}
|
||||||
|
.opus {
|
||||||
|
'opus'
|
||||||
|
}
|
||||||
|
.aac {
|
||||||
|
'aac'
|
||||||
|
}
|
||||||
|
.flac {
|
||||||
|
'flac'
|
||||||
|
}
|
||||||
|
.wav {
|
||||||
|
'wav'
|
||||||
|
}
|
||||||
|
.pcm {
|
||||||
|
'pcm'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
module openai
|
module openai
|
||||||
|
|
||||||
import freeflowuniverse.crystallib.core.base
|
import freeflowuniverse.herolib.core.base
|
||||||
import freeflowuniverse.crystallib.core.playbook
|
import freeflowuniverse.herolib.core.playbook
|
||||||
|
|
||||||
__global (
|
__global (
|
||||||
openai_global map[string]&OpenAI
|
openai_global map[string]&OpenAI
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ pub fn heroscript_default() !string {
|
|||||||
|
|
||||||
pub struct OpenAI {
|
pub struct OpenAI {
|
||||||
pub mut:
|
pub mut:
|
||||||
name string = 'default'
|
name string = 'default'
|
||||||
key string @[secret]
|
api_key string @[secret]
|
||||||
|
|
||||||
conn ?&httpconnection.HTTPConnection
|
conn ?&httpconnection.HTTPConnection
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ fn cfg_play(p paramsparser.Params) ! {
|
|||||||
// THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE WITH struct above
|
// THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE WITH struct above
|
||||||
mut mycfg := OpenAI{
|
mut mycfg := OpenAI{
|
||||||
name: p.get_default('name', 'default')!
|
name: p.get_default('name', 'default')!
|
||||||
key: p.get('key')!
|
api_key: p.get('api_key')!
|
||||||
}
|
}
|
||||||
set(mycfg)!
|
set(mycfg)!
|
||||||
}
|
}
|
||||||
@@ -47,15 +47,13 @@ pub fn (mut client OpenAI) connection() !&httpconnection.HTTPConnection {
|
|||||||
mut c := client.conn or {
|
mut c := client.conn or {
|
||||||
mut c2 := httpconnection.new(
|
mut c2 := httpconnection.new(
|
||||||
name: 'openaiconnection_${client.name}'
|
name: 'openaiconnection_${client.name}'
|
||||||
url: 'https://openrouter.ai/api/v1'
|
url: 'https://api.openai.com/v1'
|
||||||
cache: false
|
cache: false
|
||||||
retry: 0
|
|
||||||
)!
|
)!
|
||||||
c2
|
c2
|
||||||
}
|
}
|
||||||
|
|
||||||
c.default_header.set(.authorization, 'Bearer ${client.key}')
|
c.default_header.set(.authorization, 'Bearer ${client.api_key}')
|
||||||
c.default_header.set(.content_type, 'application/json')
|
|
||||||
|
|
||||||
client.conn = c
|
client.conn = c
|
||||||
return c
|
return c
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
# openai
|
# openai
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
To get started
|
To get started
|
||||||
|
|
||||||
```vlang
|
```vlang
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import freeflowuniverse.crystallib.clients. openai
|
import freeflowuniverse.herolib.clients. openai
|
||||||
|
|
||||||
mut client:= openai.get()!
|
mut client:= openai.get()!
|
||||||
|
|
||||||
@@ -21,12 +19,9 @@ client...
|
|||||||
|
|
||||||
## example heroscript
|
## example heroscript
|
||||||
|
|
||||||
|
|
||||||
```hero
|
```hero
|
||||||
!!openai.configure
|
!!openai.configure
|
||||||
secret: '...'
|
secret: '...'
|
||||||
host: 'localhost'
|
host: 'localhost'
|
||||||
port: 8888
|
port: 8888
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
module ourdb
|
|
||||||
|
|
||||||
fn test_basic_operations() {
|
|
||||||
// mut client := get()
|
|
||||||
}
|
|
||||||
1
lib/clients/openai/testdata/testfile.txt
vendored
Normal file
1
lib/clients/openai/testdata/testfile.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
hello world
|
||||||
1
lib/clients/openai/testdata/testfile2.txt
vendored
Normal file
1
lib/clients/openai/testdata/testfile2.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
testfile2 content
|
||||||
Reference in New Issue
Block a user