This commit is contained in:
2025-08-06 08:50:32 +02:00
parent 5cb52ba6b1
commit 04403b62a4
83 changed files with 892 additions and 807 deletions

View File

@@ -0,0 +1,19 @@
# Quick Example: Transcribing Audio
```v
import freeflowuniverse.herolib.clients.openai
mut client:= openai.get()! //will be the default client, key is in `AIKEY` on environment variable or `OPENROUTER_API_KEY`
// Assuming you have an audio file named 'audio.mp3' in the same directory
// For a real application, handle file paths dynamically
audio_file_path := 'audio.mp3'
resp := client.audio.create_transcription(
file: audio_file_path,
model: 'whisper-1'
)!
```

View File

@@ -1,9 +1,88 @@
module openai
module audio
import json
import freeflowuniverse.herolib.core.httpconnection
import os
import net.http
import freeflowuniverse.herolib.clients.openai { OpenAI }
type OpenAIAlias = OpenAI
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'
}
}
}
pub enum AudioRespType {
json
@@ -44,6 +123,7 @@ fn audio_resp_type_str(i AudioRespType) string {
}
}
@[params]
pub struct AudioArgs {
pub mut:
filepath string
@@ -60,17 +140,17 @@ pub mut:
// create transcription from an audio file
// supported audio formats are mp3, mp4, mpeg, mpga, m4a, wav, or webm
pub fn (mut f OpenAI) create_transcription(args AudioArgs) !AudioResponse {
pub fn (mut f OpenAIAlias) create_transcription(args AudioArgs) !AudioResponse {
return f.create_audio_request(args, 'audio/transcriptions')
}
// create translation to english from an audio file
// supported audio formats are mp3, mp4, mpeg, mpga, m4a, wav, or webm
pub fn (mut f OpenAI) create_tranlation(args AudioArgs) !AudioResponse {
pub fn (mut f OpenAIAlias) create_tranlation(args AudioArgs) !AudioResponse {
return f.create_audio_request(args, 'audio/translations')
}
fn (mut f OpenAI) create_audio_request(args AudioArgs, endpoint string) !AudioResponse {
fn (mut f OpenAIAlias) create_audio_request(args AudioArgs, endpoint string) !AudioResponse {
file_content := os.read_file(args.filepath)!
ext := os.file_ext(args.filepath)
mut file_mime_type := ''
@@ -131,7 +211,7 @@ pub:
speed f32
}
pub fn (mut f OpenAI) create_speech(args CreateSpeechArgs) ! {
pub fn (mut f OpenAIAlias) create_speech(args CreateSpeechArgs) ! {
mut output_file := os.open_file(args.output_path, 'w+')!
req := CreateSpeechRequest{

View File

@@ -0,0 +1,26 @@
module audio_test
import os
import clients.openai
import clients.openai.audio
import clients.openai.openai_factory_ { get }
import freeflowuniverse.crystallib.osal { play }
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'
voice: audio.Voice.alloy
response_format: audio.AudioFormat.mp3
)!
assert os.exists('/tmp/output.mp3')
}

View File

@@ -1,6 +1,10 @@
module openai
import os
import openai.audio
import openai.files
import openai.finetune
import openai.images
fn test_chat_completion() {
mut client := get()!
@@ -24,83 +28,3 @@ fn test_chat_completion() {
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')
// }

View File

@@ -2,63 +2,61 @@ module openai
import json
pub struct ChatCompletion {
pub mut:
id string
object string
created u32
choices []Choice
usage Usage
}
pub struct Choice {
pub mut:
index int
message MessageRaw
finish_reason string
}
pub struct Message {
pub mut:
role RoleType
content string
}
pub struct Usage {
pub mut:
prompt_tokens int
completion_tokens int
total_tokens int
}
pub struct Messages {
pub mut:
messages []Message
}
pub struct MessageRaw {
pub mut:
role string
content string
}
struct ChatMessagesRaw {
mut:
model string
messages []MessageRaw
temperature f64 = 0.5
max_completion_tokens int = 32000
}
@[params]
pub struct CompletionArgs {
pub mut:
model string
msgs Messages
temperature f64 = 0.5
messages []Message // optional because we can use message, which means we just pass a string
message string
temperature f64 = 0.2
max_completion_tokens int = 32000
}
struct Message {
mut:
role RoleType
content string
}
pub enum RoleType {
system
user
assistant
function
}
fn roletype_str(x RoleType) string {
return match x {
.system {
'system'
}
.user {
'user'
}
.assistant {
'assistant'
}
.function {
'function'
}
}
}
struct Usage {
mut:
prompt_tokens int
completion_tokens int
total_tokens int
}
struct ChatCompletion {
mut:
id string
created u32
result string
usage Usage
}
// creates a new chat completion given a list of messages
// each message consists of message content and the role of the author
pub fn (mut f OpenAI) chat_completion(args_ CompletionArgs) !ChatCompletion {
@@ -71,19 +69,38 @@ pub fn (mut f OpenAI) chat_completion(args_ CompletionArgs) !ChatCompletion {
temperature: args.temperature
max_completion_tokens: args.max_completion_tokens
}
for msg in args.msgs.messages {
for msg in args.messages {
mr := MessageRaw{
role: roletype_str(msg.role)
content: msg.content
}
m.messages << mr
}
if args.message != '' {
mr := MessageRaw{
role: 'user'
content: args.message
}
m.messages << mr
}
data := json.encode(m)
// println('data: ${data}')
mut conn := f.connection()!
r := conn.post_json_str(prefix: 'chat/completions', data: data)!
// println('res: ${r}')
res := json.decode(ChatCompletion, r)!
return res
res := json.decode(ChatCompletionRaw, r)!
mut result := ''
for choice in res.choices {
result += choice.message.content
}
mut chat_completion_result := ChatCompletion{
id: res.id
created: res.created
result: result
usage: res.usage
}
return chat_completion_result
}

View File

@@ -0,0 +1,17 @@
# Quick Example: Creating Embeddings
```v
import freeflowuniverse.herolib.clients.openai
mut client:= openai.get()! //will be the default client, key is in `AIKEY` on environment variable or `OPENROUTER_API_KEY`
text_to_embed := 'The quick brown fox jumps over the lazy dog.'
resp := client.embeddings.create_embedding(
input: text_to_embed,
model: 'text-embedding-ada-002'
)!
```

View File

@@ -1,6 +1,9 @@
module openai
module embeddings
import json
import freeflowuniverse.herolib.clients.openai { OpenAI, Usage }
type OpenAIAlias = OpenAI
pub enum EmbeddingModel {
text_embedding_ada
@@ -42,7 +45,7 @@ pub mut:
usage Usage
}
pub fn (mut f OpenAI) create_embeddings(args EmbeddingCreateArgs) !EmbeddingResponse {
pub fn (mut f OpenAIAlias) create_embeddings(args EmbeddingCreateArgs) !EmbeddingResponse {
req := EmbeddingCreateRequest{
input: args.input
model: embedding_model_str(args.model)

View File

@@ -0,0 +1,24 @@
module embeddings_test
import os
import clients.openai
import clients.openai.embeddings
import clients.openai.openai_factory_ { get }
import freeflowuniverse.crystallib.osal { play }
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: embeddings.EmbeddingModel.text_embedding_ada
)!
assert res.data.len == 1
assert res.data[0].embedding.len == 1536
}

View File

@@ -0,0 +1,25 @@
# Example: Uploading a File
```v
import freeflowuniverse.herolib.clients.openai
mut client:= openai.get()! //will be the default client, key is in `AIKEY` on environment variable or `OPENROUTER_API_KEY`
// Assuming you have a file named 'mydata.jsonl' in the same directory
// For a real application, handle file paths dynamically
file_path := 'mydata.jsonl'
resp := client.files.upload_file(
file: file_path,
purpose: 'fine-tune' // or 'assistants', 'batch', 'vision'
)
if resp.id.len > 0 {
println('File uploaded successfully with ID: ${resp.id}')
} else {
eprintln('Failed to upload file.')
}
```

View File

@@ -1,9 +1,12 @@
module openai
module files
import json
import freeflowuniverse.herolib.core.httpconnection
import os
import net.http
import freeflowuniverse.herolib.clients.openai { OpenAI }
type OpenAIAlias = OpenAI
const jsonl_mime_type = 'text/jsonl'
@@ -44,7 +47,7 @@ pub mut:
}
// upload file to client org, usually used for fine tuning
pub fn (mut f OpenAI) upload_file(args FileUploadArgs) !File {
pub fn (mut f OpenAIAlias) upload_file(args FileUploadArgs) !File {
file_content := os.read_file(args.filepath)!
file_data := http.FileData{
@@ -74,28 +77,28 @@ pub fn (mut f OpenAI) upload_file(args FileUploadArgs) !File {
}
// list all files in client org
pub fn (mut f OpenAI) list_files() !Files {
pub fn (mut f OpenAIAlias) list_files() !Files {
mut conn := f.connection()!
r := conn.get(prefix: 'files')!
return json.decode(Files, r)!
}
// deletes a file
pub fn (mut f OpenAI) delete_file(file_id string) !DeleteResp {
pub fn (mut f OpenAIAlias) delete_file(file_id string) !DeleteResp {
mut conn := f.connection()!
r := conn.delete(prefix: 'files/' + file_id)!
return json.decode(DeleteResp, r)!
}
// returns a single file metadata
pub fn (mut f OpenAI) get_file(file_id string) !File {
pub fn (mut f OpenAIAlias) get_file(file_id string) !File {
mut conn := f.connection()!
r := conn.get(prefix: 'files/' + file_id)!
return json.decode(File, r)!
}
// returns the content of a specific file
pub fn (mut f OpenAI) get_file_content(file_id string) !string {
pub fn (mut f OpenAIAlias) get_file_content(file_id string) !string {
mut conn := f.connection()!
r := conn.get(prefix: 'files/' + file_id + '/content')!
return r

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,22 @@
# OpenAI Fine-tuning Client
```v
import freeflowuniverse.herolib.clients.openai
mut client:= openai.get()! //will be the default client, key is in `AIKEY` on environment variable or `OPENROUTER_API_KEY`
// Assuming you have a training file ID from the Files API
training_file_id := 'file-xxxxxxxxxxxxxxxxxxxxxxxxx'
resp := client.finetune.create_fine_tune(
training_file: training_file_id,
model: 'gpt-3.5-turbo'
)
if resp.id.len > 0 {
println('Fine-tuning job created with ID: ${resp.id}')
} else {
eprintln('Failed to create fine-tuning job.')
}
```

View File

@@ -1,7 +1,11 @@
module openai
module finetune
import freeflowuniverse.herolib.clients.openai { OpenAI }
import freeflowuniverse.herolib.clients.openai.files { File }
import json
type OpenAIAlias = OpenAI
pub struct FineTune {
pub:
id string
@@ -61,7 +65,7 @@ pub mut:
}
// creates a new fine-tune based on an already uploaded file
pub fn (mut f OpenAI) create_fine_tune(args FineTuneCreateArgs) !FineTune {
pub fn (mut f OpenAIAlias) create_fine_tune(args FineTuneCreateArgs) !FineTune {
data := json.encode(args)
mut conn := f.connection()!
r := conn.post_json_str(prefix: 'fine-tunes', data: data)!
@@ -70,28 +74,28 @@ pub fn (mut f OpenAI) create_fine_tune(args FineTuneCreateArgs) !FineTune {
}
// returns all fine-tunes in this account
pub fn (mut f OpenAI) list_fine_tunes() !FineTuneList {
pub fn (mut f OpenAIAlias) list_fine_tunes() !FineTuneList {
mut conn := f.connection()!
r := conn.get(prefix: 'fine-tunes')!
return json.decode(FineTuneList, r)!
}
// get a single fine-tune information
pub fn (mut f OpenAI) get_fine_tune(fine_tune string) !FineTune {
pub fn (mut f OpenAIAlias) get_fine_tune(fine_tune string) !FineTune {
mut conn := f.connection()!
r := conn.get(prefix: 'fine-tunes/' + fine_tune)!
return json.decode(FineTune, r)!
}
// cancel a fine-tune that didn't finish yet
pub fn (mut f OpenAI) cancel_fine_tune(fine_tune string) !FineTune {
pub fn (mut f OpenAIAlias) cancel_fine_tune(fine_tune string) !FineTune {
mut conn := f.connection()!
r := conn.post_json_str(prefix: 'fine-tunes/' + fine_tune + '/cancel')!
return json.decode(FineTune, r)!
}
// returns all events for a fine tune in this account
pub fn (mut f OpenAI) list_fine_tune_events(fine_tune string) !FineTuneEventList {
pub fn (mut f OpenAIAlias) list_fine_tune_events(fine_tune string) !FineTuneEventList {
mut conn := f.connection()!
r := conn.get(prefix: 'fine-tunes/' + fine_tune + '/events')!
return json.decode(FineTuneEventList, r)!

View File

@@ -0,0 +1,21 @@
# Example: Creating an Image
```v
import freeflowuniverse.herolib.clients.openai
mut client:= openai.get()! //will be the default client, key is in `AIKEY` on environment variable or `OPENROUTER_API_KEY`
resp := client.images.create_image(
prompt: 'A futuristic city at sunset',
n: 1,
size: '1024x1024'
)
if resp.data.len > 0 {
println('Image created. URL: ${resp.data[0].url}')
} else {
eprintln('Failed to create image.')
}
```

View File

@@ -4,6 +4,9 @@ import json
import net.http
import os
import freeflowuniverse.herolib.core.httpconnection
import freeflowuniverse.herolib.clients.openai { OpenAI }
type OpenAIAlias = OpenAI
const image_mine_type = 'image/png'
@@ -43,6 +46,7 @@ fn image_resp_type_str(i ImageRespType) string {
}
}
@[params]
pub struct ImageCreateArgs {
pub mut:
prompt string
@@ -95,7 +99,7 @@ pub mut:
// Create new images generation given a prompt
// the amount of images returned is specified by `num_images`
pub fn (mut f OpenAI) create_image(args ImageCreateArgs) !Images {
pub fn (mut f OpenAIAlias) create_image(args ImageCreateArgs) !Images {
image_size := image_size_str(args.size)
response_format := image_resp_type_str(args.format)
request := ImageRequest{
@@ -115,7 +119,7 @@ pub fn (mut f OpenAI) create_image(args ImageCreateArgs) !Images {
// image needs to be in PNG format and transparent or else a mask of the same size needs
// to be specified to indicate where the image should be in the generated image
// the amount of images returned is specified by `num_images`
pub fn (mut f OpenAI) create_edit_image(args ImageEditArgs) !Images {
pub fn (mut f OpenAIAlias) create_edit_image(args ImageEditArgs) !Images {
image_content := os.read_file(args.image_path)!
image_file := http.FileData{
filename: os.base(args.image_path)
@@ -160,7 +164,7 @@ pub fn (mut f OpenAI) create_edit_image(args ImageEditArgs) !Images {
// create variations of the given image
// image needs to be in PNG format
// the amount of images returned is specified by `num_images`
pub fn (mut f OpenAI) create_variation_image(args ImageVariationArgs) !Images {
pub fn (mut f OpenAIAlias) create_variation_image(args ImageVariationArgs) !Images {
image_content := os.read_file(args.image_path)!
image_file := http.FileData{
filename: os.base(args.image_path)

View File

@@ -1,101 +0,0 @@
module openai
pub enum RoleType {
system
user
assistant
function
}
fn roletype_str(x RoleType) string {
return match x {
.system {
'system'
}
.user {
'user'
}
.assistant {
'assistant'
}
.function {
'function'
}
}
}
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'
}
}
}

View File

@@ -0,0 +1,31 @@
module openai
struct ChatCompletionRaw {
mut:
id string
object string
created u32
choices []ChoiceRaw
usage Usage
}
struct ChoiceRaw {
mut:
index int
message MessageRaw
finish_reason string
}
struct MessageRaw {
mut:
role string
content string
}
struct ChatMessagesRaw {
mut:
model string
messages []MessageRaw
temperature f64 = 0.5
max_completion_tokens int = 32000
}

View File

@@ -0,0 +1,30 @@
# OpenAI Moderation Client
This directory contains the V client for OpenAI's Moderation API.
```v
import freeflowuniverse.herolib.clients.openai
mut client:= openai.get()! //will be the default client, key is in `AIKEY` on environment variable or `OPENROUTER_API_KEY`
text_to_moderate := 'I want to kill them all.'
resp := client.moderation.create_moderation(
input: text_to_moderate
)
if resp.results.len > 0 {
if resp.results[0].flagged {
println('Text was flagged for moderation.')
println('Categories: ${resp.results[0].categories}')
} else {
println('Text passed moderation.')
}
} else {
eprintln('Failed to get moderation result.')
}
```

View File

@@ -1,6 +1,9 @@
module openai
module moderation
import json
import freeflowuniverse.herolib.clients.openai { OpenAI }
type OpenAIAlias = OpenAI
pub enum ModerationModel {
text_moderation_latest
@@ -69,7 +72,7 @@ pub mut:
results []ModerationResult
}
pub fn (mut f OpenAI) create_moderation(input string, model ModerationModel) !ModerationResponse {
pub fn (mut f OpenAIAlias) create_moderation(input string, model ModerationModel) !ModerationResponse {
req := ModerationRequest{
input: input
model: moderation_model_str(model)

View File

@@ -8,57 +8,42 @@ pub const version = '0.0.0'
const singleton = false
const default = true
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
// @[heap]
// pub struct OpenAIBase {
// pub mut:
// name string = 'default'
// api_key string
// url string
// model_default string
// }
@[heap]
pub struct OpenAI {
pub mut:
name string = 'default'
api_key string
url string
model_default string
url string = 'https://openrouter.ai/api/v1'
model_default string = 'gpt-oss-120b'
conn ?&httpconnection.HTTPConnection @[skip; str: skip]
}
// your checking & initialization code if needed
fn obj_init(mycfg_ OpenAI) !OpenAI {
mut mycfg := mycfg_
if mycfg.model_default == '' {
k := os.getenv('AIMODEL')
if k != '' {
mycfg.model_default = k
}
}
if mycfg.url == '' {
k := os.getenv('AIURL')
if k != '' {
mycfg.url = k
}
}
if mycfg.api_key == '' {
mut k := os.getenv('AIKEY')
k := os.getenv('AIKEY')
if k != '' {
mycfg.api_key = k
k = os.getenv('AIURL')
if k != '' {
mycfg.url = k
} else {
return error('found AIKEY in env, but not AIURL')
}
k = os.getenv('AIMODEL')
if k != '' {
mycfg.model_default = k
}
return mycfg
}
mycfg.url = 'https://api.openai.com/v1/models'
k = os.getenv('OPENAI_API_KEY')
if k != '' {
mycfg.api_key = k
return mycfg
}
k = os.getenv('OPENROUTER_API_KEY')
if k != '' {
mycfg.api_key = k
mycfg.url = 'https://openrouter.ai/api/v1'
return mycfg
if k == '' {
k2 := os.getenv('OPENROUTER_API_KEY')
if k2 != '' {
mycfg.api_key = k2
}
}
}
return mycfg

View File

@@ -4,21 +4,27 @@ To get started
```vlang
import freeflowuniverse.herolib.clients.openai
import freeflowuniverse.herolib.core.playcmds
playcmds.run(
heroscript:'
!!openai.configure name: "default" key: "" url: "https://openrouter.ai/api/v1" model_default: "gpt-oss-120b"
'
heroscript_path:''
reset: false
)!
//name:'default' is the default, you can change it to whatever you want
mut client:= openai.get()!
client...
mut r:=client.chat_completion(
model: "gpt-3.5-turbo",
message: 'Hello, world!'
temperature: 0.5
max_completion_tokens: 1024
)!
```
## example heroscript
```hero
!!openai.configure key
```
if key empty then will try to get it from environment variable `AIKEY` or `OPENROUTER_API_KEY`

View File

@@ -1 +0,0 @@
hello world

View File

@@ -1 +0,0 @@
testfile2 content