From 705fbfdebcc1757908cda765e49bb84ae1add6c8 Mon Sep 17 00:00:00 2001 From: mariobassem Date: Thu, 26 Dec 2024 18:30:46 +0200 Subject: [PATCH 1/3] fixes in openai client --- lib/clients/openai/{ => _archive}/actions.v | 2 +- lib/clients/openai/{ => _archive}/factory.v | 0 lib/clients/openai/_archive/openai_factory_.v | 27 ++++++ lib/clients/openai/_archive/openai_model.v | 49 +++++++++++ lib/clients/openai/audio.v | 22 ++--- lib/clients/openai/completions.v | 7 +- lib/clients/openai/embeddings.v | 8 +- lib/clients/openai/files.v | 33 ++++--- lib/clients/openai/fine_tunes.v | 25 +++--- lib/clients/openai/images.v | 45 +++++----- lib/clients/openai/models.v | 10 ++- lib/clients/openai/moderation.v | 5 +- lib/clients/openai/openai_factory_.v | 88 +++++++++++++++++-- lib/clients/openai/openai_model.v | 54 +++++++++--- lib/clients/openai/readme.md | 54 ++++-------- lib/clients/openai/test_client.v | 5 ++ 16 files changed, 315 insertions(+), 119 deletions(-) rename lib/clients/openai/{ => _archive}/actions.v (94%) rename lib/clients/openai/{ => _archive}/factory.v (100%) create mode 100644 lib/clients/openai/_archive/openai_factory_.v create mode 100644 lib/clients/openai/_archive/openai_model.v create mode 100644 lib/clients/openai/test_client.v diff --git a/lib/clients/openai/actions.v b/lib/clients/openai/_archive/actions.v similarity index 94% rename from lib/clients/openai/actions.v rename to lib/clients/openai/_archive/actions.v index 58fccd39..3ded7b85 100644 --- a/lib/clients/openai/actions.v +++ b/lib/clients/openai/_archive/actions.v @@ -15,7 +15,7 @@ pub fn heroplay(mut plbook playbook.PlayBook) ! { instance := p.get_default('instance', 'default')! // cfg.keyname = p.get('keyname')! mut cl := get(instance, - openaikey: p.get('openaikey')! + openaikey: p.get('openaikey')! description: p.get_default('description', '')! )! cl.config_save()! diff --git a/lib/clients/openai/factory.v b/lib/clients/openai/_archive/factory.v similarity index 100% rename from lib/clients/openai/factory.v rename to lib/clients/openai/_archive/factory.v diff --git a/lib/clients/openai/_archive/openai_factory_.v b/lib/clients/openai/_archive/openai_factory_.v new file mode 100644 index 00000000..3a3cf11a --- /dev/null +++ b/lib/clients/openai/_archive/openai_factory_.v @@ -0,0 +1,27 @@ +module openai + +import freeflowuniverse.herolib.core.base +import freeflowuniverse.herolib.core.playbook +import freeflowuniverse.herolib.ui.console + +__global ( + openai_global map[string]&OpenAI + openai_default string +) + +/////////FACTORY + +@[params] +pub struct ArgsGet { +pub mut: + name string +} + +pub fn get(args_ ArgsGet) !&OpenAI { + return &OpenAI{} +} + +// switch instance to be used for openai +pub fn switch(name string) { + openai_default = name +} diff --git a/lib/clients/openai/_archive/openai_model.v b/lib/clients/openai/_archive/openai_model.v new file mode 100644 index 00000000..bc61a1c0 --- /dev/null +++ b/lib/clients/openai/_archive/openai_model.v @@ -0,0 +1,49 @@ +module openai + +import freeflowuniverse.herolib.data.paramsparser +import os +import freeflowuniverse.herolib.core.httpconnection + +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 OpenAI { +pub mut: + name string = 'default' + mail_from string + mail_password string @[secret] + mail_port int + mail_server string + mail_username string +} + +fn obj_init(obj_ OpenAI) !OpenAI { + // never call get here, only thing we can do here is work on object itself + mut obj := obj_ + panic('implement') + return obj +} + +pub fn (mut client OpenAI) connection() !&httpconnection.HTTPConnection { + mut c := client.conn or { + mut c2 := httpconnection.new( + name: 'openrouterclient_${client.name}' + url: 'https://openrouter.ai/api/v1/chat/completions' + cache: false + retry: 0 + )! + c2 + } + + // see https://modules.vlang.io/net.http.html#CommonHeader + // -H "Authorization: Bearer $OPENROUTER_API_KEY" \ + c.default_header.set(.authorization, 'Bearer ${client.openaikey}') + c.default_header.add_custom('HTTP-Referer', client.your_site_url)! + c.default_header.add_custom('X-Title', client.your_site_name)! + client.conn = c + return c +} diff --git a/lib/clients/openai/audio.v b/lib/clients/openai/audio.v index 70f1b154..14a1be6c 100644 --- a/lib/clients/openai/audio.v +++ b/lib/clients/openai/audio.v @@ -60,38 +60,38 @@ pub mut: // create transcription from an audio file // supported audio formats are mp3, mp4, mpeg, mpga, m4a, wav, or webm -pub fn (mut f OpenAIClient[Config]) create_transcription(args AudioArgs) !AudioResponse { +pub fn (mut f OpenAI) 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 OpenAIClient[Config]) create_tranlation(args AudioArgs) !AudioResponse { +pub fn (mut f OpenAI) create_tranlation(args AudioArgs) !AudioResponse { return f.create_audio_request(args, 'audio/translations') } -fn (mut f OpenAIClient[Config]) create_audio_request(args AudioArgs, endpoint string) !AudioResponse { +fn (mut f OpenAI) 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 := '' - if ext in audio_mime_types { - file_mime_type = audio_mime_types[ext] + if ext in openai.audio_mime_types { + file_mime_type = openai.audio_mime_types[ext] } else { return error('file extenion not supported') } file_data := http.FileData{ - filename: os.base(args.filepath) + filename: os.base(args.filepath) content_type: file_mime_type - data: file_content + data: file_content } form := http.PostMultipartFormConfig{ files: { 'file': [file_data] } - form: { - 'model': audio_model + form: { + 'model': openai.audio_model 'prompt': args.prompt 'response_format': audio_resp_type_str(args.response_format) 'temperature': args.temperature.str() @@ -102,7 +102,9 @@ fn (mut f OpenAIClient[Config]) create_audio_request(args AudioArgs, endpoint st req := httpconnection.Request{ prefix: endpoint } - r := f.connection.post_multi_part(req, form)! + + mut conn := f.connection()! + r := conn.post_multi_part(req, form)! if r.status_code != 200 { return error('got error from server: ${r.body}') } diff --git a/lib/clients/openai/completions.v b/lib/clients/openai/completions.v index 8ed34b22..c91c85c1 100644 --- a/lib/clients/openai/completions.v +++ b/lib/clients/openai/completions.v @@ -50,20 +50,21 @@ mut: // 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 OpenAIClient[Config]) chat_completion(model_type ModelType, msgs Messages) !ChatCompletion { +pub fn (mut f OpenAI) chat_completion(model_type ModelType, msgs Messages) !ChatCompletion { model_type0 := modelname_str(model_type) mut m := ChatMessagesRaw{ model: model_type0 } for msg in msgs.messages { mr := MessageRaw{ - role: roletype_str(msg.role) + role: roletype_str(msg.role) content: msg.content } m.messages << mr } data := json.encode(m) - r := f.connection.post_json_str(prefix: 'chat/completions', data: data)! + mut conn := f.connection()! + r := conn.post_json_str(prefix: 'chat/completions', data: data)! res := json.decode(ChatCompletion, r)! return res diff --git a/lib/clients/openai/embeddings.v b/lib/clients/openai/embeddings.v index f179e291..a9941374 100644 --- a/lib/clients/openai/embeddings.v +++ b/lib/clients/openai/embeddings.v @@ -42,13 +42,15 @@ pub mut: usage Usage } -pub fn (mut f OpenAIClient[Config]) create_embeddings(args EmbeddingCreateArgs) !EmbeddingResponse { +pub fn (mut f OpenAI) create_embeddings(args EmbeddingCreateArgs) !EmbeddingResponse { req := EmbeddingCreateRequest{ input: args.input model: embedding_model_str(args.model) - user: args.user + user: args.user } data := json.encode(req) - r := f.connection.post_json_str(prefix: 'embeddings', data: data)! + + mut conn := f.connection()! + r := conn.post_json_str(prefix: 'embeddings', data: data)! return json.decode(EmbeddingResponse, r)! } diff --git a/lib/clients/openai/files.v b/lib/clients/openai/files.v index 899d15df..ee421b10 100644 --- a/lib/clients/openai/files.v +++ b/lib/clients/openai/files.v @@ -37,20 +37,20 @@ pub mut: } // upload file to client org, usually used for fine tuning -pub fn (mut f OpenAIClient[Config]) upload_file(args FileUploadArgs) !File { +pub fn (mut f OpenAI) upload_file(args FileUploadArgs) !File { file_content := os.read_file(args.filepath)! file_data := http.FileData{ - filename: os.base(args.filepath) - data: file_content - content_type: jsonl_mime_type + filename: os.base(args.filepath) + data: file_content + content_type: openai.jsonl_mime_type } form := http.PostMultipartFormConfig{ files: { 'file': [file_data] } - form: { + form: { 'purpose': args.purpose } } @@ -58,7 +58,8 @@ pub fn (mut f OpenAIClient[Config]) upload_file(args FileUploadArgs) !File { req := httpconnection.Request{ prefix: 'files' } - r := f.connection.post_multi_part(req, form)! + mut conn := f.connection()! + r := conn.post_multi_part(req, form)! if r.status_code != 200 { return error('got error from server: ${r.body}') } @@ -66,25 +67,29 @@ pub fn (mut f OpenAIClient[Config]) upload_file(args FileUploadArgs) !File { } // list all files in client org -pub fn (mut f OpenAIClient[Config]) list_files() !Files { - r := f.connection.get(prefix: 'files')! +pub fn (mut f OpenAI) list_files() !Files { + mut conn := f.connection()! + r := conn.get(prefix: 'files')! return json.decode(Files, r)! } // deletes a file -pub fn (mut f OpenAIClient[Config]) delete_file(file_id string) !DeleteResp { - r := f.connection.delete(prefix: 'files/' + file_id)! +pub fn (mut f OpenAI) 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 OpenAIClient[Config]) get_file(file_id string) !File { - r := f.connection.get(prefix: 'files/' + file_id)! +pub fn (mut f OpenAI) 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 OpenAIClient[Config]) get_file_content(file_id string) !string { - r := f.connection.get(prefix: 'files/' + file_id + '/content')! +pub fn (mut f OpenAI) get_file_content(file_id string) !string { + mut conn := f.connection()! + r := conn.get(prefix: 'files/' + file_id + '/content')! return r } diff --git a/lib/clients/openai/fine_tunes.v b/lib/clients/openai/fine_tunes.v index 99dce686..78a867f2 100644 --- a/lib/clients/openai/fine_tunes.v +++ b/lib/clients/openai/fine_tunes.v @@ -61,33 +61,38 @@ pub mut: } // creates a new fine-tune based on an already uploaded file -pub fn (mut f OpenAIClient[Config]) create_fine_tune(args FineTuneCreateArgs) !FineTune { +pub fn (mut f OpenAI) create_fine_tune(args FineTuneCreateArgs) !FineTune { data := json.encode(args) - r := f.connection.post_json_str(prefix: 'fine-tunes', data: data)! + mut conn := f.connection()! + r := conn.post_json_str(prefix: 'fine-tunes', data: data)! return json.decode(FineTune, r)! } // returns all fine-tunes in this account -pub fn (mut f OpenAIClient[Config]) list_fine_tunes() !FineTuneList { - r := f.connection.get(prefix: 'fine-tunes')! +pub fn (mut f OpenAI) 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 OpenAIClient[Config]) get_fine_tune(fine_tune string) !FineTune { - r := f.connection.get(prefix: 'fine-tunes/' + fine_tune)! +pub fn (mut f OpenAI) 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 OpenAIClient[Config]) cancel_fine_tune(fine_tune string) !FineTune { - r := f.connection.post_json_str(prefix: 'fine-tunes/' + fine_tune + '/cancel')! +pub fn (mut f OpenAI) 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 OpenAIClient[Config]) list_fine_tune_events(fine_tune string) !FineTuneEventList { - r := f.connection.get(prefix: 'fine-tunes/' + fine_tune + '/events')! +pub fn (mut f OpenAI) 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)! } diff --git a/lib/clients/openai/images.v b/lib/clients/openai/images.v index 1061df17..096e225b 100644 --- a/lib/clients/openai/images.v +++ b/lib/clients/openai/images.v @@ -95,18 +95,19 @@ pub mut: // Create new images generation given a prompt // the amount of images returned is specified by `num_images` -pub fn (mut f OpenAIClient[Config]) create_image(args ImageCreateArgs) !Images { +pub fn (mut f OpenAI) create_image(args ImageCreateArgs) !Images { image_size := image_size_str(args.size) response_format := image_resp_type_str(args.format) request := ImageRequest{ - prompt: args.prompt - n: args.num_images - size: image_size + prompt: args.prompt + n: args.num_images + size: image_size response_format: response_format - user: args.user + user: args.user } data := json.encode(request) - r := f.connection.post_json_str(prefix: 'images/generations', data: data)! + mut conn := f.connection()! + r := conn.post_json_str(prefix: 'images/generations', data: data)! return json.decode(Images, r)! } @@ -114,20 +115,20 @@ pub fn (mut f OpenAIClient[Config]) 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 OpenAIClient[Config]) create_edit_image(args ImageEditArgs) !Images { +pub fn (mut f OpenAI) create_edit_image(args ImageEditArgs) !Images { image_content := os.read_file(args.image_path)! image_file := http.FileData{ - filename: os.base(args.image_path) - content_type: image_mine_type - data: image_content + filename: os.base(args.image_path) + content_type: openai.image_mine_type + data: image_content } mut mask_file := []http.FileData{} if args.mask_path != '' { mask_content := os.read_file(args.mask_path)! mask_file << http.FileData{ - filename: os.base(args.mask_path) - content_type: image_mine_type - data: mask_content + filename: os.base(args.mask_path) + content_type: openai.image_mine_type + data: mask_content } } @@ -136,7 +137,7 @@ pub fn (mut f OpenAIClient[Config]) create_edit_image(args ImageEditArgs) !Image 'image': [image_file] 'mask': mask_file } - form: { + form: { 'prompt': args.prompt 'n': args.num_images.str() 'response_format': image_resp_type_str(args.format) @@ -148,7 +149,8 @@ pub fn (mut f OpenAIClient[Config]) create_edit_image(args ImageEditArgs) !Image req := httpconnection.Request{ prefix: 'images/edits' } - r := f.connection.post_multi_part(req, form)! + mut conn := f.connection()! + r := conn.post_multi_part(req, form)! if r.status_code != 200 { return error('got error from server: ${r.body}') } @@ -158,19 +160,19 @@ pub fn (mut f OpenAIClient[Config]) create_edit_image(args ImageEditArgs) !Image // 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 OpenAIClient[Config]) create_variation_image(args ImageVariationArgs) !Images { +pub fn (mut f OpenAI) create_variation_image(args ImageVariationArgs) !Images { image_content := os.read_file(args.image_path)! image_file := http.FileData{ - filename: os.base(args.image_path) - content_type: image_mine_type - data: image_content + filename: os.base(args.image_path) + content_type: openai.image_mine_type + data: image_content } form := http.PostMultipartFormConfig{ files: { 'image': [image_file] } - form: { + form: { 'n': args.num_images.str() 'response_format': image_resp_type_str(args.format) 'size': image_size_str(args.size) @@ -181,7 +183,8 @@ pub fn (mut f OpenAIClient[Config]) create_variation_image(args ImageVariationAr req := httpconnection.Request{ prefix: 'images/variations' } - r := f.connection.post_multi_part(req, form)! + mut conn := f.connection()! + r := conn.post_multi_part(req, form)! if r.status_code != 200 { return error('got error from server: ${r.body}') } diff --git a/lib/clients/openai/models.v b/lib/clients/openai/models.v index 70ce1b77..09d5ff59 100644 --- a/lib/clients/openai/models.v +++ b/lib/clients/openai/models.v @@ -34,13 +34,15 @@ pub mut: } // list current models available in Open AI -pub fn (mut f OpenAIClient[Config]) list_models() !Models { - r := f.connection.get(prefix: 'models')! +pub fn (mut f OpenAI) list_models() !Models { + mut conn := f.connection()! + r := conn.get(prefix: 'models')! return json.decode(Models, r)! } // returns details of a model using the model id -pub fn (mut f OpenAIClient[Config]) get_model(model string) !Model { - r := f.connection.get(prefix: 'models/' + model)! +pub fn (mut f OpenAI) get_model(model string) !Model { + mut conn := f.connection()! + r := conn.get(prefix: 'models/' + model)! return json.decode(Model, r)! } diff --git a/lib/clients/openai/moderation.v b/lib/clients/openai/moderation.v index bf63b39a..19015b72 100644 --- a/lib/clients/openai/moderation.v +++ b/lib/clients/openai/moderation.v @@ -69,12 +69,13 @@ pub mut: results []ModerationResult } -pub fn (mut f OpenAIClient[Config]) create_moderation(input string, model ModerationModel) !ModerationResponse { +pub fn (mut f OpenAI) create_moderation(input string, model ModerationModel) !ModerationResponse { req := ModerationRequest{ input: input model: moderation_model_str(model) } data := json.encode(req) - r := f.connection.post_json_str(prefix: 'moderations', data: data)! + mut conn := f.connection()! + r := conn.post_json_str(prefix: 'moderations', data: data)! return json.decode(ModerationResponse, r)! } diff --git a/lib/clients/openai/openai_factory_.v b/lib/clients/openai/openai_factory_.v index 3a3cf11a..e203ca48 100644 --- a/lib/clients/openai/openai_factory_.v +++ b/lib/clients/openai/openai_factory_.v @@ -1,8 +1,7 @@ module openai -import freeflowuniverse.herolib.core.base -import freeflowuniverse.herolib.core.playbook -import freeflowuniverse.herolib.ui.console +import freeflowuniverse.crystallib.core.base +import freeflowuniverse.crystallib.core.playbook __global ( openai_global map[string]&OpenAI @@ -14,11 +13,90 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' +} + +fn args_get(args_ ArgsGet) ArgsGet { + mut args := args_ + if args.name == '' { + args.name = openai_default + } + if args.name == '' { + args.name = 'default' + } + return args } pub fn get(args_ ArgsGet) !&OpenAI { - return &OpenAI{} + mut args := args_get(args_) + if args.name !in openai_global { + if !config_exists() { + if default { + config_save()! + } + } + config_load()! + } + return openai_global[args.name] or { + println(openai_global) + panic('bug in get from factory: ') + } +} + +fn config_exists(args_ ArgsGet) bool { + mut args := args_get(args_) + mut context := base.context() or { panic('bug') } + return context.hero_config_exists('openai', args.name) +} + +fn config_load(args_ ArgsGet) ! { + mut args := args_get(args_) + mut context := base.context()! + mut heroscript := context.hero_config_get('openai', args.name)! + play(heroscript: heroscript)! +} + +fn config_save(args_ ArgsGet) ! { + mut args := args_get(args_) + mut context := base.context()! + context.hero_config_set('openai', args.name, heroscript_default()!)! +} + +fn set(o OpenAI) ! { + mut o2 := obj_init(o)! + openai_global['default'] = &o2 +} + +@[params] +pub struct PlayArgs { +pub mut: + name string = 'default' + heroscript string // if filled in then plbook will be made out of it + plbook ?playbook.PlayBook + reset bool + + start bool + stop bool + restart bool + delete bool + configure bool // make sure there is at least one installed +} + +pub fn play(args_ PlayArgs) ! { + mut args := args_ + + if args.heroscript == '' { + args.heroscript = heroscript_default()! + } + mut plbook := args.plbook or { playbook.new(text: args.heroscript)! } + + mut install_actions := plbook.find(filter: 'openai.configure')! + if install_actions.len > 0 { + for install_action in install_actions { + mut p := install_action.params + cfg_play(p)! + } + } } // switch instance to be used for openai diff --git a/lib/clients/openai/openai_model.v b/lib/clients/openai/openai_model.v index d65408bc..96272e4e 100644 --- a/lib/clients/openai/openai_model.v +++ b/lib/clients/openai/openai_model.v @@ -1,28 +1,62 @@ module openai import freeflowuniverse.herolib.data.paramsparser -import os +import freeflowuniverse.herolib.core.httpconnection -pub const version = '0.0.0' +pub const version = '1.14.3' const singleton = false const default = true +// TODO: THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE TO STRUCT BELOW, IS STRUCTURED AS HEROSCRIPT +pub fn heroscript_default() !string { + heroscript := " + !!openai.configure + name:'openai' + key: 'YOUR_API_KEY' + " + + return heroscript +} + // THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED -@[heap] pub struct OpenAI { pub mut: - name string = 'default' - mail_from string - mail_password string @[secret] - mail_port int - mail_server string - mail_username string + name string = 'default' + key string @[secret] + + conn ?&httpconnection.HTTPConnection +} + +fn cfg_play(p paramsparser.Params) ! { + // THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE WITH struct above + mut mycfg := OpenAI{ + name: p.get_default('name', 'default')! + key: p.get('key')! + } + set(mycfg)! } fn obj_init(obj_ OpenAI) !OpenAI { // never call get here, only thing we can do here is work on object itself mut obj := obj_ - panic('implement') return obj } + +pub fn (mut client OpenAI) connection() !&httpconnection.HTTPConnection { + mut c := client.conn or { + mut c2 := httpconnection.new( + name: 'openaiconnection_${client.name}' + url: 'https://openrouter.ai/api/v1' + cache: false + retry: 0 + )! + c2 + } + + c.default_header.set(.authorization, 'Bearer ${client.key}') + c.default_header.set(.content_type, 'application/json') + + client.conn = c + return c +} diff --git a/lib/clients/openai/readme.md b/lib/clients/openai/readme.md index 08827c57..ecf115a6 100644 --- a/lib/clients/openai/readme.md +++ b/lib/clients/openai/readme.md @@ -1,50 +1,32 @@ -# OpenAI +# openai -An implementation of an OpenAI client using Vlang. -## Supported methods -- List available models -- Chat Completion -- Translate Audio -- Transcribe Audio -- Create image based on prompt -- Edit an existing image -- Create variation of an image +To get started + +```vlang + + + +import freeflowuniverse.crystallib.clients. openai + +mut client:= openai.get()! + +client... -## Usage -To use the client you need a OpenAi key which can be generated from [here](https://platform.openai.com/account/api-keys). -The key should be exposed in an environment variable as following: -```bash -export OPENAI_API_KEY= ``` -To get a new instance of the client: +## example heroscript -```v -import freeflowuniverse.herolib.clients.openai -ai_cli := openai.new()! +```hero +!!openai.configure + secret: '...' + host: 'localhost' + port: 8888 ``` -Then it is possible to perform all the listed operations: -```v -// listing models -models := ai_cli.list_models()! - -// creating a new chat completion - -mut msg := []op.Message{} -msg << op.Message{ - role: op.RoleType.user - content: 'Say this is a test!' -} -mut msgs := op.Messages{ - messages: msg -} -res := ai_cli.chat_completion(op.ModelType.gpt_3_5_turbo, msgs)! -``` diff --git a/lib/clients/openai/test_client.v b/lib/clients/openai/test_client.v new file mode 100644 index 00000000..1435baf9 --- /dev/null +++ b/lib/clients/openai/test_client.v @@ -0,0 +1,5 @@ +module ourdb + +fn test_basic_operations() { + // mut client := get() +} From df63231db56f5eceee5d409cd897fb82e7b4b486 Mon Sep 17 00:00:00 2001 From: mariobassem Date: Sun, 29 Dec 2024 18:00:58 +0200 Subject: [PATCH 2/3] fix crystallib imports --- aiprompts/ai_core/core_params.md | 10 +- aiprompts/ai_core/osal_os_system_tools.md | 385 +++++++++--------- aiprompts/ui console chalk.md | 6 +- aiprompts/ui console.md | 27 +- .../vshell example script instructions.md | 4 +- examples/hero/alpine_example.vsh | 7 +- .../generation/blank_generation/example_1.vsh | 4 +- .../generation/blank_generation/example_2.vsh | 4 +- .../openapi_generation/example_actor/actor.v | 32 +- examples/hero/openapi/actor.vsh | 297 +++++++------- examples/hero/openapi/server.vsh | 219 +++++----- 11 files changed, 504 insertions(+), 491 deletions(-) diff --git a/aiprompts/ai_core/core_params.md b/aiprompts/ai_core/core_params.md index f201874a..8cc0d8fc 100644 --- a/aiprompts/ai_core/core_params.md +++ b/aiprompts/ai_core/core_params.md @@ -5,7 +5,7 @@ works very well in combination with heroscript ## How to get the paramsparser ```v -import freeflowuniverse.crystallib.data.paramsparser +import freeflowuniverse.herolib.data.paramsparser // Create new params from text params := paramsparser.new("color:red size:'large' priority:1 enable:true")! @@ -25,6 +25,7 @@ The parser supports several formats: 4. Comments: `// this is a comment` Example: + ```v text := "name:'John Doe' age:30 active:true // user details" params := paramsparser.new(text)! @@ -59,6 +60,7 @@ progress := params.get_percentage("progress")! The module supports various type conversions: ### Basic Types + - `get_int()`: Convert to int32 - `get_u32()`: Convert to unsigned 32-bit integer - `get_u64()`: Convert to unsigned 64-bit integer @@ -67,10 +69,12 @@ The module supports various type conversions: - `get_percentage()`: Convert percentage string to float (e.g., "80%" → 0.8) ### Boolean Values + - `get_default_true()`: Returns true if value is empty, "1", "true", "y", or "yes" - `get_default_false()`: Returns false if value is empty, "0", "false", "n", or "no" ### Lists + The module provides robust support for parsing and converting lists: ```v @@ -89,6 +93,7 @@ clean_names := params.get_list_namefix("categories")! ``` Supported list types: + - `get_list()`: String list - `get_list_u8()`, `get_list_u16()`, `get_list_u32()`, `get_list_u64()`: Unsigned integers - `get_list_i8()`, `get_list_i16()`, `get_list_int()`, `get_list_i64()`: Signed integers @@ -97,6 +102,7 @@ Supported list types: Each list method has a corresponding `_default` version that accepts a default value. Valid list formats: + ```v users: "john, jane,bob" ids: "1,2,3,4,5" @@ -133,4 +139,4 @@ get_timestamp(key string) !Duration get_timestamp_default(key string, defval Duration) !Duration -``` \ No newline at end of file +``` diff --git a/aiprompts/ai_core/osal_os_system_tools.md b/aiprompts/ai_core/osal_os_system_tools.md index 355e3b16..5f456785 100644 --- a/aiprompts/ai_core/osal_os_system_tools.md +++ b/aiprompts/ai_core/osal_os_system_tools.md @@ -1,10 +1,9 @@ # module osal - -import as +import as ```vlang -import freeflowuniverse.crystallib.osal +import freeflowuniverse.herolib.osal osal.ping... @@ -46,7 +45,6 @@ pub enum CPUType { ## process - ### execute jobs ```v @@ -56,7 +54,7 @@ println(job2) //wont die, the result can be found in /tmp/execscripts mut job:=osal.exec(cmd:"ls dsds",ignore_error:true)? //this one has an error -println(job) +println(job) ``` All scripts are executed from a file from /tmp/execscripts @@ -91,24 +89,24 @@ info returns like: ## other commands -fn bin_path() !string -fn cmd_add(args_ CmdAddArgs) ! - copy a binary to the right location on the local computer . e.g. is /usr/local/bin on linux . e.g. is ~/hero/bin on osx . will also add the bin location to the path of .zprofile and .zshrc (different per platform) -fn cmd_exists(cmd string) bool +fn bin*path() !string +fn cmd_add(args* CmdAddArgs) ! +copy a binary to the right location on the local computer . e.g. is /usr/local/bin on linux . e.g. is ~/hero/bin on osx . will also add the bin location to the path of .zprofile and .zshrc (different per platform) +fn cmd*exists(cmd string) bool fn cmd_exists_profile(cmd string) bool fn cmd_path(cmd string) !string - is same as executing which in OS returns path or error +is same as executing which in OS returns path or error fn cmd_to_script_path(cmd Command) !string - will return temporary path which then can be executed, is a helper function for making script out of command +will return temporary path which then can be executed, is a helper function for making script out of command fn cputype() CPUType fn cputype_enum_from_string(cpytype string) CPUType - Returns the enum value that matches the provided string for CPUType +Returns the enum value that matches the provided string for CPUType fn dir_delete(path string) ! - remove all if it exists +remove all if it exists fn dir_ensure(path string) ! - remove all if it exists +remove all if it exists fn dir_reset(path string) ! - remove all if it exists and then (re-)create +remove all if it exists and then (re-)create fn done_delete(key string) ! fn done_exists(key string) bool fn done_get(key string) ?string @@ -117,45 +115,45 @@ fn done_get_str(key string) string fn done_print() ! fn done_reset() ! fn done_set(key string, val string) ! -fn download(args_ DownloadArgs) !pathlib.Path - if name is not specified, then will be the filename part if the last ends in an extension like .md .txt .log .text ... the file will be downloaded +fn download(args* DownloadArgs) !pathlib.Path +if name is not specified, then will be the filename part if the last ends in an extension like .md .txt .log .text ... the file will be downloaded fn env_get(key string) !string - Returns the requested environment variable if it exists or throws an error if it does not +Returns the requested environment variable if it exists or throws an error if it does not fn env_get_all() map[string]string - Returns all existing environment variables +Returns all existing environment variables fn env_get_default(key string, def string) string - Returns the requested environment variable if it exists or returns the provided default value if it does not +Returns the requested environment variable if it exists or returns the provided default value if it does not fn env_set(args EnvSet) - Sets an environment if it was not set before, it overwrites the enviroment variable if it exists and if overwrite was set to true (default) +Sets an environment if it was not set before, it overwrites the enviroment variable if it exists and if overwrite was set to true (default) fn env_set_all(args EnvSetAll) - Allows to set multiple enviroment variables in one go, if clear_before_set is true all existing environment variables will be unset before the operation, if overwrite_if_exists is set to true it will overwrite all existing enviromnent variables +Allows to set multiple enviroment variables in one go, if clear_before_set is true all existing environment variables will be unset before the operation, if overwrite_if_exists is set to true it will overwrite all existing enviromnent variables fn env_unset(key string) - Unsets an environment variable +Unsets an environment variable fn env_unset_all() - Unsets all environment variables +Unsets all environment variables fn exec(cmd Command) !Job - cmd is the cmd to execute can use ' ' and spaces . if \n in cmd it will write it to ext and then execute with bash . if die==false then will just return returncode,out but not return error . if stdout will show stderr and stdout . . if cmd starts with find or ls, will give to bash -c so it can execute . if cmd has no path, path will be found . . Command argument: . - ``` - name string // to give a name to your command, good to see logs... - cmd string - description string - timeout int = 3600 // timeout in sec - stdout bool = true - stdout_log bool = true - raise_error bool = true // if false, will not raise an error but still error report - ignore_error bool // means if error will just exit and not raise, there will be no error reporting - work_folder string // location where cmd will be executed - environment map[string]string // env variables - ignore_error_codes []int - scriptpath string // is the path where the script will be put which is executed - scriptkeep bool // means we don't remove the script - debug bool // if debug will put +ex in the script which is being executed and will make sure script stays - shell bool // means we will execute it in a shell interactive - retry int - interactive bool = true // make sure we run on non interactive way - async bool - runtime RunTime (.bash, .python) - +cmd is the cmd to execute can use ' ' and spaces . if \n in cmd it will write it to ext and then execute with bash . if die==false then will just return returncode,out but not return error . if stdout will show stderr and stdout . . if cmd starts with find or ls, will give to bash -c so it can execute . if cmd has no path, path will be found . . Command argument: . +``` +name string // to give a name to your command, good to see logs... +cmd string +description string +timeout int = 3600 // timeout in sec +stdout bool = true +stdout_log bool = true +raise_error bool = true // if false, will not raise an error but still error report +ignore_error bool // means if error will just exit and not raise, there will be no error reporting +work_folder string // location where cmd will be executed +environment map[string]string // env variables +ignore_error_codes []int +scriptpath string // is the path where the script will be put which is executed +scriptkeep bool // means we don't remove the script +debug bool // if debug will put +ex in the script which is being executed and will make sure script stays +shell bool // means we will execute it in a shell interactive +retry int +interactive bool = true // make sure we run on non interactive way +async bool +runtime RunTime (.bash, .python) + returns Job: start time.Time end time.Time @@ -167,33 +165,35 @@ fn exec(cmd Command) !Job process os.Process ``` return Job . + fn exec_string(cmd Command) !string - cmd is the cmd to execute can use ' ' and spaces if \n in cmd it will write it to ext and then execute with bash if die==false then will just return returncode,out but not return error if stdout will show stderr and stdout - +cmd is the cmd to execute can use ' ' and spaces if \n in cmd it will write it to ext and then execute with bash if die==false then will just return returncode,out but not return error if stdout will show stderr and stdout + if cmd starts with find or ls, will give to bash -c so it can execute if cmd has no path, path will be found $... are remplaced by environment arguments TODO:implement - + Command argument: cmd string timeout int = 600 stdout bool = true die bool = true debug bool - + return what needs to be executed can give it to bash -c ... -fn execute_debug(cmd string) !string + +fn execute*debug(cmd string) !string fn execute_interactive(cmd string) ! - shortcut to execute a job interactive means in shell +shortcut to execute a job interactive means in shell fn execute_ok(cmd string) bool - executes a cmd, if not error return true +executes a cmd, if not error return true fn execute_silent(cmd string) !string - shortcut to execute a job silent +shortcut to execute a job silent fn execute_stdout(cmd string) !string - shortcut to execute a job to stdout +shortcut to execute a job to stdout fn file_read(path string) !string fn file_write(path string, text string) ! fn get_logger() log.Logger - Returns a logger object and allows you to specify via environment argument OSAL_LOG_LEVEL the debug level +Returns a logger object and allows you to specify via environment argument OSAL_LOG_LEVEL the debug level fn hero_path() !string fn hostname() !string fn initname() !string - e.g. systemd, bash, zinit +e.g. systemd, bash, zinit fn ipaddr_pub_get() !string - Returns the ipaddress as known on the public side is using resolver4.opendns.com +Returns the ipaddress as known on the public side is using resolver4.opendns.com fn is_linux() bool fn is_linux_arm() bool fn is_linux_intel() bool @@ -205,24 +205,23 @@ fn load_env_file(file_path string) ! fn memdb_exists(key string) bool fn memdb_get(key string) string fn memdb_set(key string, val string) -fn package_install(name_ string) ! - install a package will use right commands per platform +fn package_install(name* string) ! +install a package will use right commands per platform fn package_refresh() ! - update the package list +update the package list fn ping(args PingArgs) PingResult - if reached in timout result will be True address is e.g. 8.8.8.8 ping means we check if the destination responds +if reached in timout result will be True address is e.g. 8.8.8.8 ping means we check if the destination responds fn platform() PlatformType fn platform_enum_from_string(platform string) PlatformType fn process_exists(pid int) bool fn process_exists_byname(name string) !bool fn process_kill_recursive(args ProcessKillArgs) ! - kill process and all the ones underneith +kill process and all the ones underneith fn processinfo_children(pid int) !ProcessMap - get all children of 1 process +get all children of 1 process fn processinfo_get(pid int) !ProcessInfo - get process info from 1 specific process returns - ``` - pub struct ProcessInfo { +get process info from 1 specific process returns +` pub struct ProcessInfo { pub mut: cpu_perc f32 mem_perc f32 @@ -232,209 +231,209 @@ fn processinfo_get(pid int) !ProcessInfo //resident memory rss int } - ``` + ` fn processinfo_get_byname(name string) ![]ProcessInfo fn processinfo_with_children(pid int) !ProcessMap - return the process and its children +return the process and its children fn processmap_get() !ProcessMap - make sure to use new first, so that the connection has been initted then you can get it everywhere +make sure to use new first, so that the connection has been initted then you can get it everywhere fn profile_path() string fn profile_path_add(args ProfilePathAddArgs) ! - add the following path to a profile +add the following path to a profile fn profile_path_add_hero() !string fn profile_path_source() string - return the source statement if the profile exists +return the source statement if the profile exists fn profile_path_source_and() string - return source $path && . or empty if it doesn't exist +return source $path && . or empty if it doesn't exist fn sleep(duration int) - sleep in seconds +sleep in seconds fn tcp_port_test(args TcpPortTestArgs) bool - test if a tcp port answers - ``` - address string //192.168.8.8 +test if a tcp port answers +` address string //192.168.8.8 port int = 22 timeout u16 = 2000 // total time in milliseconds to keep on trying - ``` + ` fn user_add(args UserArgs) !int - add's a user if the user does not exist yet +add's a user if the user does not exist yet fn user_exists(username string) bool fn user_id_get(username string) !int fn usr_local_path() !string - /usr/local on linux, ${os.home_dir()}/hero on osx +/usr/local on linux, ${os.home_dir()}/hero on osx fn whoami() !string fn write_flags[T](options T) string enum CPUType { - unknown - intel - arm - intel32 - arm32 +unknown +intel +arm +intel32 +arm32 } enum ErrorType { - exec - timeout - args +exec +timeout +args } enum JobStatus { - init - running - error_exec - error_timeout - error_args - done +init +running +error_exec +error_timeout +error_args +done } enum PMState { - init - ok - old +init +ok +old } enum PingResult { - ok - timeout // timeout from ping - unknownhost // means we don't know the hostname its a dns issue +ok +timeout // timeout from ping +unknownhost // means we don't know the hostname its a dns issue } enum PlatformType { - unknown - osx - ubuntu - alpine - arch - suse +unknown +osx +ubuntu +alpine +arch +suse } enum RunTime { - bash - python - heroscript - herocmd - v +bash +python +heroscript +herocmd +v } struct CmdAddArgs { pub mut: - cmdname string - source string @[required] // path where the binary is - symlink bool // if rather than copy do a symlink - reset bool // if existing cmd will delete - // bin_repo_url string = 'https://github.com/freeflowuniverse/freeflow_binary' // binary where we put the results +cmdname string +source string @[required] // path where the binary is +symlink bool // if rather than copy do a symlink +reset bool // if existing cmd will delete +// bin_repo_url string = 'https://github.com/freeflowuniverse/freeflow_binary' // binary where we put the results } struct Command { pub mut: - name string // to give a name to your command, good to see logs... - cmd string - description string - timeout int = 3600 // timeout in sec - stdout bool = true - stdout_log bool = true - raise_error bool = true // if false, will not raise an error but still error report - ignore_error bool // means if error will just exit and not raise, there will be no error reporting - work_folder string // location where cmd will be executed - environment map[string]string // env variables - ignore_error_codes []int - scriptpath string // is the path where the script will be put which is executed - scriptkeep bool // means we don't remove the script - debug bool // if debug will put +ex in the script which is being executed and will make sure script stays - shell bool // means we will execute it in a shell interactive - retry int - interactive bool = true - async bool - runtime RunTime +name string // to give a name to your command, good to see logs... +cmd string +description string +timeout int = 3600 // timeout in sec +stdout bool = true +stdout_log bool = true +raise_error bool = true // if false, will not raise an error but still error report +ignore_error bool // means if error will just exit and not raise, there will be no error reporting +work_folder string // location where cmd will be executed +environment map[string]string // env variables +ignore_error_codes []int +scriptpath string // is the path where the script will be put which is executed +scriptkeep bool // means we don't remove the script +debug bool // if debug will put +ex in the script which is being executed and will make sure script stays +shell bool // means we will execute it in a shell interactive +retry int +interactive bool = true +async bool +runtime RunTime } struct DownloadArgs { pub mut: - name string // optional (otherwise derived out of filename) - url string - reset bool // will remove - hash string // if hash is known, will verify what hash is - dest string // if specified will copy to that destination - timeout int = 180 - retry int = 3 - minsize_kb u32 = 10 // is always in kb - maxsize_kb u32 - expand_dir string - expand_file string +name string // optional (otherwise derived out of filename) +url string +reset bool // will remove +hash string // if hash is known, will verify what hash is +dest string // if specified will copy to that destination +timeout int = 180 +retry int = 3 +minsize_kb u32 = 10 // is always in kb +maxsize_kb u32 +expand_dir string +expand_file string } struct EnvSet { pub mut: - key string @[required] - value string @[required] - overwrite bool = true +key string @[required] +value string @[required] +overwrite bool = true } struct EnvSetAll { pub mut: - env map[string]string - clear_before_set bool - overwrite_if_exists bool = true +env map[string]string +clear_before_set bool +overwrite_if_exists bool = true } struct Job { pub mut: - start time.Time - end time.Time - cmd Command - output string - error string - exit_code int - status JobStatus - process ?&os.Process @[skip; str: skip] - runnr int // nr of time it runs, is for retry +start time.Time +end time.Time +cmd Command +output string +error string +exit_code int +status JobStatus +process ?&os.Process @[skip; str: skip] +runnr int // nr of time it runs, is for retry } fn (mut job Job) execute_retry() ! - execute the job and wait on result will retry as specified +execute the job and wait on result will retry as specified fn (mut job Job) execute() ! - execute the job, start process, process will not be closed . important you need to close the process later by job.close()! otherwise we get zombie processes +execute the job, start process, process will not be closed . important you need to close the process later by job.close()! otherwise we get zombie processes fn (mut job Job) wait() ! - wait till the job finishes or goes in error +wait till the job finishes or goes in error fn (mut job Job) process() ! - process (read std.err and std.out of process) +process (read std.err and std.out of process) fn (mut job Job) close() ! - will wait & close +will wait & close struct JobError { - Error +Error pub mut: - job Job - error_type ErrorType +job Job +error_type ErrorType } struct PingArgs { pub mut: - address string @[required] - count u8 = 1 // the ping is successful if it got count amount of replies from the other side - timeout u16 = 1 // the time in which the other side should respond in seconds - retry u8 +address string @[required] +count u8 = 1 // the ping is successful if it got count amount of replies from the other side +timeout u16 = 1 // the time in which the other side should respond in seconds +retry u8 } struct ProcessInfo { pub mut: - cpu_perc f32 - mem_perc f32 - cmd string - pid int - ppid int // parentpid - // resident memory - rss int +cpu_perc f32 +mem_perc f32 +cmd string +pid int +ppid int // parentpid +// resident memory +rss int } fn (mut p ProcessInfo) str() string struct ProcessKillArgs { pub mut: - name string - pid int +name string +pid int } struct ProcessMap { pub mut: - processes []ProcessInfo - lastscan time.Time - state PMState - pids []int +processes []ProcessInfo +lastscan time.Time +state PMState +pids []int } struct ProfilePathAddArgs { pub mut: - path string @[required] - todelete string // see which one to remove +path string @[required] +todelete string // see which one to remove } struct TcpPortTestArgs { pub mut: - address string @[required] // 192.168.8.8 - port int = 22 - timeout u16 = 2000 // total time in milliseconds to keep on trying +address string @[required] // 192.168.8.8 +port int = 22 +timeout u16 = 2000 // total time in milliseconds to keep on trying } struct UserArgs { pub mut: - name string @[required] +name string @[required] } -* \ No newline at end of file + +- diff --git a/aiprompts/ui console chalk.md b/aiprompts/ui console chalk.md index f2ec8e2b..5ebbbf88 100644 --- a/aiprompts/ui console chalk.md +++ b/aiprompts/ui console chalk.md @@ -1,14 +1,14 @@ # module ui.console.chalk - Chalk offers functions:- `console.color_fg(text string, color string)` - To change the foreground color. + - `console.color_bg(text string, color string)` - To change the background color. - `console.style(text string, style string)` - To change the text style. Example: ```vlang -import freeflowuniverse.crystallib.ui.console +import freeflowuniverse.herolib.ui.console # basic usage println('I am really ' + console.color_fg('happy', 'green')) @@ -18,6 +18,7 @@ println('I am really ' + console.color_fg(console.style('ANGRY', 'bold'), 'red') ``` Available colors:- black + - red - green - yellow @@ -36,6 +37,7 @@ Available colors:- black - white Available styles:- bold + - dim - underline - blink diff --git a/aiprompts/ui console.md b/aiprompts/ui console.md index 5cdba86c..8203c25b 100644 --- a/aiprompts/ui console.md +++ b/aiprompts/ui console.md @@ -2,17 +2,16 @@ has mechanisms to print better to console, see the methods below -import as +import as ```vlang -import freeflowuniverse.crystallib.ui.console +import freeflowuniverse.herolib.ui.console ``` - ## Methods -```v +````v fn clear() //reset the console screen @@ -86,13 +85,12 @@ fn style(c Style) string fn trim(c_ string) string -``` +```` ## Console Object Is used to ask feedback to users - ```v struct UIConsole { @@ -105,14 +103,14 @@ pub mut: } //DropDownArgs: -// - description string -// - items []string -// - warning string +// - description string +// - items []string +// - warning string // - clear bool = true fn (mut c UIConsole) ask_dropdown_int(args_ DropDownArgs) !int - // return the dropdown as an int + // return the dropdown as an int fn (mut c UIConsole) ask_dropdown_multiple(args_ DropDownArgs) ![]string // result can be multiple, aloso can select all description string items []string warning string clear bool = true @@ -135,7 +133,7 @@ fn (mut c UIConsole) ask_time(args QuestionArgs) !string fn (mut c UIConsole) ask_date(args QuestionArgs) !string fn (mut c UIConsole) ask_yesno(args YesNoArgs) !bool - // yes is true, no is false + // yes is true, no is false // args: // - description string // - question string @@ -148,14 +146,11 @@ fn (mut c UIConsole) status() string ``` - - ## enums - ```v enum BackgroundColor { - default_color = 49 // 'default' is a reserved keyword in V + default_color = 49 // 'default' is a reserved keyword in V black = 40 red = 41 green = 42 @@ -174,7 +169,7 @@ enum BackgroundColor { white = 107 } enum ForegroundColor { - default_color = 39 // 'default' is a reserved keyword in V + default_color = 39 // 'default' is a reserved keyword in V white = 97 black = 30 red = 31 diff --git a/aiprompts/vshell example script instructions.md b/aiprompts/vshell example script instructions.md index 5da015d0..bf693cd8 100644 --- a/aiprompts/vshell example script instructions.md +++ b/aiprompts/vshell example script instructions.md @@ -1,4 +1,3 @@ - # how to run the vshell example scripts this is how we want example scripts to be, see the first line @@ -6,7 +5,7 @@ this is how we want example scripts to be, see the first line ```vlang #!/usr/bin/env -S v -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run -import freeflowuniverse.crystallib.installers.sysadmintools.daguserver +import freeflowuniverse.herolib.installers.sysadmintools.daguserver mut ds := daguserver.get()! @@ -18,4 +17,3 @@ the files are in ~/code/github/freeflowuniverse/crystallib/examples for crystall ## important instructions - never use fn main() in a .vsh script - diff --git a/examples/hero/alpine_example.vsh b/examples/hero/alpine_example.vsh index 5db4c894..cdc61a37 100755 --- a/examples/hero/alpine_example.vsh +++ b/examples/hero/alpine_example.vsh @@ -1,8 +1,7 @@ #!/usr/bin/env -S v -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run +import freeflowuniverse.herolib.hero.bootstrap -import freeflowuniverse.crystallib.hero.bootstrap +mut al := bootstrap.new_alpine_loader() -mut al:=bootstrap.new_alpine_loader() - -al.start()! \ No newline at end of file +al.start()! diff --git a/examples/hero/generation/blank_generation/example_1.vsh b/examples/hero/generation/blank_generation/example_1.vsh index 73133089..0843291b 100644 --- a/examples/hero/generation/blank_generation/example_1.vsh +++ b/examples/hero/generation/blank_generation/example_1.vsh @@ -1,7 +1,7 @@ #!/usr/bin/env -S v -w -n -enable-globals run -import freeflowuniverse.crystallib.hero.generation +import freeflowuniverse.herolib.hero.generation generation.generate_actor( name: 'Example' -) \ No newline at end of file +) diff --git a/examples/hero/generation/blank_generation/example_2.vsh b/examples/hero/generation/blank_generation/example_2.vsh index 536e195b..de0b0646 100644 --- a/examples/hero/generation/blank_generation/example_2.vsh +++ b/examples/hero/generation/blank_generation/example_2.vsh @@ -1,8 +1,8 @@ #!/usr/bin/env -S v -w -n -enable-globals run -import freeflowuniverse.crystallib.hero.generation +import freeflowuniverse.herolib.hero.generation generation.generate_actor( name: 'Example' interfaces: [] -) \ No newline at end of file +) diff --git a/examples/hero/generation/openapi_generation/example_actor/actor.v b/examples/hero/generation/openapi_generation/example_actor/actor.v index ad76d387..4403523e 100644 --- a/examples/hero/generation/openapi_generation/example_actor/actor.v +++ b/examples/hero/generation/openapi_generation/example_actor/actor.v @@ -1,8 +1,8 @@ module example_actor import os -import freeflowuniverse.crystallib.hero.baobab.actor {IActor, RunParams} -import freeflowuniverse.crystallib.web.openapi +import freeflowuniverse.herolib.hero.baobab.actor { IActor, RunParams } +import freeflowuniverse.herolib.web.openapi import time const openapi_spec_path = '${os.dir(@FILE)}/specs/openapi.json' @@ -10,27 +10,25 @@ const openapi_spec_json = os.read_file(openapi_spec_path) or { panic(err) } const openapi_specification = openapi.json_decode(openapi_spec_json)! struct ExampleActor { - actor.Actor + actor.Actor } fn new() !ExampleActor { - return ExampleActor{ - actor.new('example') - } + return ExampleActor{actor.new('example')} } pub fn run() ! { - mut a_ := new()! - mut a := IActor(a_) - a.run()! + mut a_ := new()! + mut a := IActor(a_) + a.run()! } pub fn run_server(params RunParams) ! { - mut a := new()! - mut server := actor.new_server( - redis_url: 'localhost:6379' - redis_queue: a.name - openapi_spec: openapi_specification - )! - server.run(params) -} \ No newline at end of file + mut a := new()! + mut server := actor.new_server( + redis_url: 'localhost:6379' + redis_queue: a.name + openapi_spec: example_actor.openapi_specification + )! + server.run(params) +} diff --git a/examples/hero/openapi/actor.vsh b/examples/hero/openapi/actor.vsh index 776c5f2f..ec134c82 100755 --- a/examples/hero/openapi/actor.vsh +++ b/examples/hero/openapi/actor.vsh @@ -6,210 +6,229 @@ import veb import json import x.json2 import net.http -import freeflowuniverse.crystallib.web.openapi {Server, Context, Request, Response} -import freeflowuniverse.crystallib.hero.processor {Processor, ProcedureCall, ProcedureResponse, ProcessParams} -import freeflowuniverse.crystallib.clients.redisclient +import freeflowuniverse.herolib.web.openapi +import freeflowuniverse.herolib.hero.processor +import freeflowuniverse.herolib.clients.redisclient @[heap] struct Actor { mut: - rpc redisclient.RedisRpc - data_store DataStore + rpc redisclient.RedisRpc + data_store DataStore } pub struct DataStore { mut: - pets map[int]Pet - orders map[int]Order - users map[int]User + pets map[int]Pet + orders map[int]Order + users map[int]User } struct Pet { - id int - name string - tag string + id int + name string + tag string } struct Order { - id int - pet_id int - quantity int - ship_date string - status string - complete bool + id int + pet_id int + quantity int + ship_date string + status string + complete bool } struct User { - id int - username string - email string - phone string + id int + username string + email string + phone string } // Entry point for the actor fn main() { - mut redis := redisclient.new('localhost:6379') or {panic(err)} - mut rpc := redis.rpc_get('procedure_queue') + mut redis := redisclient.new('localhost:6379') or { panic(err) } + mut rpc := redis.rpc_get('procedure_queue') - mut actor := Actor{ - rpc: rpc - data_store: DataStore{} - } + mut actor := Actor{ + rpc: rpc + data_store: DataStore{} + } - actor.listen() or {panic(err)} + actor.listen() or { panic(err) } } // Actor listens to the Redis queue for method invocations fn (mut actor Actor) listen() ! { - println('Actor started and listening for tasks...') - for { - actor.rpc.process(actor.handle_method)! - time.sleep(time.millisecond * 100) // Prevent CPU spinning - } + println('Actor started and listening for tasks...') + for { + actor.rpc.process(actor.handle_method)! + time.sleep(time.millisecond * 100) // Prevent CPU spinning + } } // Handle method invocations fn (mut actor Actor) handle_method(cmd string, data string) !string { - println('debugzo received rpc ${cmd}:${data}') - param_anys := json2.raw_decode(data)!.arr() - match cmd { - 'listPets' { - pets := if param_anys.len == 0 { - actor.data_store.list_pets() - } else { - params := json.decode(ListPetParams, param_anys[0].str())! - actor.data_store.list_pets(params) - } - return json.encode(pets) - } - 'createPet' { - response := if param_anys.len == 0 { - return error('at least data expected') - } else if param_anys.len == 1 { - payload := json.decode(NewPet, param_anys[0].str())! - actor.data_store.create_pet(payload) - } else { - return error('expected 1 param, found too many') - } - // data := json.decode(NewPet, data) or { return error('Invalid pet data: $err') } - // created_pet := actor.data_store.create_pet(pet) - return json.encode(response) - } - 'getPet' { - response := if param_anys.len == 0 { - return error('at least data expected') - } else if param_anys.len == 1 { - payload := param_anys[0].int() - actor.data_store.get_pet(payload)! - } else { - return error('expected 1 param, found too many') - } - - return json.encode(response) - } - 'deletePet' { - params := json.decode(map[string]int, data) or { return error('Invalid params: $err') } - actor.data_store.delete_pet(params['petId']) or { return error('Pet not found: $err') } - return json.encode({'message': 'Pet deleted'}) - } - 'listOrders' { - orders := actor.data_store.list_orders() - return json.encode(orders) - } - 'getOrder' { - params := json.decode(map[string]int, data) or { return error('Invalid params: $err') } - order := actor.data_store.get_order(params['orderId']) or { - return error('Order not found: $err') - } - return json.encode(order) - } - 'deleteOrder' { - params := json.decode(map[string]int, data) or { return error('Invalid params: $err') } - actor.data_store.delete_order(params['orderId']) or { - return error('Order not found: $err') - } - return json.encode({'message': 'Order deleted'}) - } - 'createUser' { - user := json.decode(NewUser, data) or { return error('Invalid user data: $err') } - created_user := actor.data_store.create_user(user) - return json.encode(created_user) - } - else { - return error('Unknown method: $cmd') - } - } + println('debugzo received rpc ${cmd}:${data}') + param_anys := json2.raw_decode(data)!.arr() + match cmd { + 'listPets' { + pets := if param_anys.len == 0 { + actor.data_store.list_pets() + } else { + params := json.decode(ListPetParams, param_anys[0].str())! + actor.data_store.list_pets(params) + } + return json.encode(pets) + } + 'createPet' { + response := if param_anys.len == 0 { + return error('at least data expected') + } else if param_anys.len == 1 { + payload := json.decode(NewPet, param_anys[0].str())! + actor.data_store.create_pet(payload) + } else { + return error('expected 1 param, found too many') + } + // data := json.decode(NewPet, data) or { return error('Invalid pet data: $err') } + // created_pet := actor.data_store.create_pet(pet) + return json.encode(response) + } + 'getPet' { + response := if param_anys.len == 0 { + return error('at least data expected') + } else if param_anys.len == 1 { + payload := param_anys[0].int() + actor.data_store.get_pet(payload)! + } else { + return error('expected 1 param, found too many') + } + + return json.encode(response) + } + 'deletePet' { + params := json.decode(map[string]int, data) or { + return error('Invalid params: ${err}') + } + actor.data_store.delete_pet(params['petId']) or { + return error('Pet not found: ${err}') + } + return json.encode({ + 'message': 'Pet deleted' + }) + } + 'listOrders' { + orders := actor.data_store.list_orders() + return json.encode(orders) + } + 'getOrder' { + params := json.decode(map[string]int, data) or { + return error('Invalid params: ${err}') + } + order := actor.data_store.get_order(params['orderId']) or { + return error('Order not found: ${err}') + } + return json.encode(order) + } + 'deleteOrder' { + params := json.decode(map[string]int, data) or { + return error('Invalid params: ${err}') + } + actor.data_store.delete_order(params['orderId']) or { + return error('Order not found: ${err}') + } + return json.encode({ + 'message': 'Order deleted' + }) + } + 'createUser' { + user := json.decode(NewUser, data) or { return error('Invalid user data: ${err}') } + created_user := actor.data_store.create_user(user) + return json.encode(created_user) + } + else { + return error('Unknown method: ${cmd}') + } + } } @[params] pub struct ListPetParams { - limit u32 + limit u32 } // DataStore methods for managing data fn (mut store DataStore) list_pets(params ListPetParams) []Pet { - if params.limit > 0 { - if params.limit >= store.pets.values().len { - return store.pets.values() - } - return store.pets.values()[..params.limit] - } - return store.pets.values() + if params.limit > 0 { + if params.limit >= store.pets.values().len { + return store.pets.values() + } + return store.pets.values()[..params.limit] + } + return store.pets.values() } fn (mut store DataStore) create_pet(new_pet NewPet) Pet { - id := store.pets.keys().len + 1 - pet := Pet{id: id, name: new_pet.name, tag: new_pet.tag} - store.pets[id] = pet - return pet + id := store.pets.keys().len + 1 + pet := Pet{ + id: id + name: new_pet.name + tag: new_pet.tag + } + store.pets[id] = pet + return pet } fn (mut store DataStore) get_pet(id int) !Pet { - return store.pets[id] or { - return error('Pet with id ${id} not found.') - } + return store.pets[id] or { return error('Pet with id ${id} not found.') } } fn (mut store DataStore) delete_pet(id int) ! { - if id in store.pets { - store.pets.delete(id) - return - } - return error('Pet not found') + if id in store.pets { + store.pets.delete(id) + return + } + return error('Pet not found') } fn (mut store DataStore) list_orders() []Order { - return store.orders.values() + return store.orders.values() } fn (mut store DataStore) get_order(id int) !Order { - return store.orders[id] or { none } + return store.orders[id] or { none } } fn (mut store DataStore) delete_order(id int) ! { - if id in store.orders { - store.orders.delete(id) - return - } - return error('Order not found') + if id in store.orders { + store.orders.delete(id) + return + } + return error('Order not found') } fn (mut store DataStore) create_user(new_user NewUser) User { - id := store.users.keys().len + 1 - user := User{id: id, username: new_user.username, email: new_user.email, phone: new_user.phone} - store.users[id] = user - return user + id := store.users.keys().len + 1 + user := User{ + id: id + username: new_user.username + email: new_user.email + phone: new_user.phone + } + store.users[id] = user + return user } // NewPet struct for creating a pet struct NewPet { - name string - tag string + name string + tag string } // NewUser struct for creating a user struct NewUser { - username string - email string - phone string -} \ No newline at end of file + username string + email string + phone string +} diff --git a/examples/hero/openapi/server.vsh b/examples/hero/openapi/server.vsh index 57262925..b8190d1a 100755 --- a/examples/hero/openapi/server.vsh +++ b/examples/hero/openapi/server.vsh @@ -4,138 +4,135 @@ import os import time import veb import json -import x.json2 {Any} +import x.json2 { Any } import net.http -import freeflowuniverse.crystallib.data.jsonschema {Schema} -import freeflowuniverse.crystallib.web.openapi {Server, Context, Request, Response} -import freeflowuniverse.crystallib.hero.processor {Processor, ProcedureCall, ProcedureResponse, ProcessParams} -import freeflowuniverse.crystallib.clients.redisclient +import freeflowuniverse.herolib.data.jsonschema { Schema } +import freeflowuniverse.herolib.web.openapi { Context, Request, Response, Server } +import freeflowuniverse.herolib.hero.processor { ProcedureCall, ProcessParams, Processor } +import freeflowuniverse.herolib.clients.redisclient const spec_path = '${os.dir(@FILE)}/data/openapi.json' const spec_json = os.read_file(spec_path) or { panic(err) } // Main function to start the server fn main() { - // Initialize the Redis client and RPC mechanism - mut redis := redisclient.new('localhost:6379')! - mut rpc := redis.rpc_get('procedure_queue') + // Initialize the Redis client and RPC mechanism + mut redis := redisclient.new('localhost:6379')! + mut rpc := redis.rpc_get('procedure_queue') - // Initialize the server - mut server := &Server{ - specification: openapi.json_decode(spec_json)! - handler: Handler{ - processor: Processor{ - rpc: rpc - } - } - } + // Initialize the server + mut server := &Server{ + specification: openapi.json_decode(spec_json)! + handler: Handler{ + processor: Processor{ + rpc: rpc + } + } + } - // Start the server - veb.run[Server, Context](mut server, 8080) + // Start the server + veb.run[Server, Context](mut server, 8080) } pub struct Handler { - mut: - processor Processor +mut: + processor Processor } fn (mut handler Handler) handle(request Request) !Response { - // Convert incoming OpenAPI request to a procedure call - mut params := []string{} + // Convert incoming OpenAPI request to a procedure call + mut params := []string{} - if request.arguments.len > 0 { - params = request.arguments.values().map(it.str()).clone() - } + if request.arguments.len > 0 { + params = request.arguments.values().map(it.str()).clone() + } - if request.body != '' { - params << request.body - } + if request.body != '' { + params << request.body + } - if request.parameters.len != 0 { - mut param_map := map[string]Any{} // Store parameters with correct types + if request.parameters.len != 0 { + mut param_map := map[string]Any{} // Store parameters with correct types - for param_name, param_value in request.parameters { - operation_param := request.operation.parameters.filter(it.name == param_name) - if operation_param.len > 0 { - param_schema := operation_param[0].schema as Schema - param_type := param_schema.typ - param_format := param_schema.format + for param_name, param_value in request.parameters { + operation_param := request.operation.parameters.filter(it.name == param_name) + if operation_param.len > 0 { + param_schema := operation_param[0].schema as Schema + param_type := param_schema.typ + param_format := param_schema.format - // Convert parameter value to corresponding type - match param_type { - 'integer' { - match param_format { - 'int32' { - param_map[param_name] = param_value.int() // Convert to int - } - 'int64' { - param_map[param_name] = param_value.i64() // Convert to i64 - } - else { - param_map[param_name] = param_value.int() // Default to int - } - } - } - 'string' { - param_map[param_name] = param_value // Already a string - } - 'boolean' { - param_map[param_name] = param_value.bool() // Convert to bool - } - 'number' { - match param_format { - 'float' { - param_map[param_name] = param_value.f32() // Convert to float - } - 'double' { - param_map[param_name] = param_value.f64() // Convert to double - } - else { - param_map[param_name] = param_value.f64() // Default to double - } - } - } - else { - param_map[param_name] = param_value // Leave as string for unknown types - } - } - } else { - // If the parameter is not defined in the OpenAPI operation, skip or log it - println('Unknown parameter: $param_name') - } - } + // Convert parameter value to corresponding type + match param_type { + 'integer' { + match param_format { + 'int32' { + param_map[param_name] = param_value.int() // Convert to int + } + 'int64' { + param_map[param_name] = param_value.i64() // Convert to i64 + } + else { + param_map[param_name] = param_value.int() // Default to int + } + } + } + 'string' { + param_map[param_name] = param_value // Already a string + } + 'boolean' { + param_map[param_name] = param_value.bool() // Convert to bool + } + 'number' { + match param_format { + 'float' { + param_map[param_name] = param_value.f32() // Convert to float + } + 'double' { + param_map[param_name] = param_value.f64() // Convert to double + } + else { + param_map[param_name] = param_value.f64() // Default to double + } + } + } + else { + param_map[param_name] = param_value // Leave as string for unknown types + } + } + } else { + // If the parameter is not defined in the OpenAPI operation, skip or log it + println('Unknown parameter: ${param_name}') + } + } - // Encode the parameter map to JSON if needed - params << json.encode(param_map.str()) - } + // Encode the parameter map to JSON if needed + params << json.encode(param_map.str()) + } - call := ProcedureCall{ - method: request.operation.operation_id - params: "[${params.join(',')}]" // Keep as a string since ProcedureCall expects a string - } + call := ProcedureCall{ + method: request.operation.operation_id + params: '[${params.join(',')}]' // Keep as a string since ProcedureCall expects a string + } - // Process the procedure call - procedure_response := handler.processor.process( - call, - ProcessParams{ - timeout: 30 // Set timeout in seconds - } - ) or { - // Handle ProcedureError - if err is processor.ProcedureError { - return Response{ - status: http.status_from_int(err.code()) // Map ProcedureError reason to HTTP status code - body: json.encode({ - 'error': err.msg() - }) - } - } - return error('Unexpected error: $err') - } + // Process the procedure call + procedure_response := handler.processor.process(call, ProcessParams{ + timeout: 30 // Set timeout in seconds + }) or { + // Handle ProcedureError + if err is processor.ProcedureError { + return Response{ + status: http.status_from_int(err.code()) // Map ProcedureError reason to HTTP status code + body: json.encode({ + 'error': err.msg() + }) + } + } + return error('Unexpected error: ${err}') + } - // Convert returned procedure response to OpenAPI response - return Response{ - status: http.Status.ok // Assuming success if no error - body: procedure_response.result - } -} \ No newline at end of file + // Convert returned procedure response to OpenAPI response + return Response{ + status: http.Status.ok // Assuming success if no error + body: procedure_response.result + } +} From 3c9f86613d824486ebdc248cd1b5d2f4c49e6d28 Mon Sep 17 00:00:00 2001 From: mariobassem Date: Sun, 29 Dec 2024 18:01:27 +0200 Subject: [PATCH 3/3] wip: add tests for openai client --- lib/clients/openai/audio.v | 38 ++++++++ lib/clients/openai/client_test.v | 105 ++++++++++++++++++++++ lib/clients/openai/completions.v | 2 + lib/clients/openai/files.v | 29 +++++- lib/clients/openai/model_enums.v | 90 +++++++++++++++++-- lib/clients/openai/openai_factory_.v | 4 +- lib/clients/openai/openai_model.v | 12 ++- lib/clients/openai/readme.md | 7 +- lib/clients/openai/test_client.v | 5 -- lib/clients/openai/testdata/testfile.txt | 1 + lib/clients/openai/testdata/testfile2.txt | 1 + 11 files changed, 266 insertions(+), 28 deletions(-) create mode 100644 lib/clients/openai/client_test.v delete mode 100644 lib/clients/openai/test_client.v create mode 100644 lib/clients/openai/testdata/testfile.txt create mode 100644 lib/clients/openai/testdata/testfile2.txt diff --git a/lib/clients/openai/audio.v b/lib/clients/openai/audio.v index 14a1be6c..b9ab5a3a 100644 --- a/lib/clients/openai/audio.v +++ b/lib/clients/openai/audio.v @@ -110,3 +110,41 @@ fn (mut f OpenAI) create_audio_request(args AudioArgs, endpoint string) !AudioRe } 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())! +} diff --git a/lib/clients/openai/client_test.v b/lib/clients/openai/client_test.v new file mode 100644 index 00000000..60aeef72 --- /dev/null +++ b/lib/clients/openai/client_test.v @@ -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') +} diff --git a/lib/clients/openai/completions.v b/lib/clients/openai/completions.v index c91c85c1..5448f2da 100644 --- a/lib/clients/openai/completions.v +++ b/lib/clients/openai/completions.v @@ -63,8 +63,10 @@ pub fn (mut f OpenAI) chat_completion(model_type ModelType, msgs Messages) !Chat 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 diff --git a/lib/clients/openai/files.v b/lib/clients/openai/files.v index ee421b10..4d6d2d3d 100644 --- a/lib/clients/openai/files.v +++ b/lib/clients/openai/files.v @@ -11,7 +11,14 @@ const jsonl_mime_type = 'text/jsonl' pub struct FileUploadArgs { pub: filepath string - purpose string + purpose FilePurpose +} + +pub enum FilePurpose { + assistants + vision + batch + fine_tuning } pub struct File { @@ -51,7 +58,7 @@ pub fn (mut f OpenAI) upload_file(args FileUploadArgs) !File { 'file': [file_data] } 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')! 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' + } + } +} diff --git a/lib/clients/openai/model_enums.v b/lib/clients/openai/model_enums.v index ed390599..b4ff6273 100644 --- a/lib/clients/openai/model_enums.v +++ b/lib/clients/openai/model_enums.v @@ -1,6 +1,7 @@ module openai pub enum ModelType { + gpt_4o_2024_08_06 gpt_3_5_turbo gpt_4 gpt_4_0613 @@ -10,16 +11,17 @@ pub enum ModelType { gpt_3_5_turbo_16k gpt_3_5_turbo_16k_0613 whisper_1 + tts_1 } 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 { + .tts_1 { + 'tts-1' + } + .gpt_4o_2024_08_06 { + 'gpt-4o-2024-08-06' + } .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' + } + } +} diff --git a/lib/clients/openai/openai_factory_.v b/lib/clients/openai/openai_factory_.v index e203ca48..23ee6c0d 100644 --- a/lib/clients/openai/openai_factory_.v +++ b/lib/clients/openai/openai_factory_.v @@ -1,7 +1,7 @@ module openai -import freeflowuniverse.crystallib.core.base -import freeflowuniverse.crystallib.core.playbook +import freeflowuniverse.herolib.core.base +import freeflowuniverse.herolib.core.playbook __global ( openai_global map[string]&OpenAI diff --git a/lib/clients/openai/openai_model.v b/lib/clients/openai/openai_model.v index 96272e4e..4262b73d 100644 --- a/lib/clients/openai/openai_model.v +++ b/lib/clients/openai/openai_model.v @@ -22,8 +22,8 @@ pub fn heroscript_default() !string { pub struct OpenAI { pub mut: - name string = 'default' - key string @[secret] + name string = 'default' + api_key string @[secret] 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 mut mycfg := OpenAI{ name: p.get_default('name', 'default')! - key: p.get('key')! + api_key: p.get('api_key')! } set(mycfg)! } @@ -47,15 +47,13 @@ pub fn (mut client OpenAI) connection() !&httpconnection.HTTPConnection { mut c := client.conn or { mut c2 := httpconnection.new( name: 'openaiconnection_${client.name}' - url: 'https://openrouter.ai/api/v1' + url: 'https://api.openai.com/v1' cache: false - retry: 0 )! c2 } - c.default_header.set(.authorization, 'Bearer ${client.key}') - c.default_header.set(.content_type, 'application/json') + c.default_header.set(.authorization, 'Bearer ${client.api_key}') client.conn = c return c diff --git a/lib/clients/openai/readme.md b/lib/clients/openai/readme.md index ecf115a6..0b44cc19 100644 --- a/lib/clients/openai/readme.md +++ b/lib/clients/openai/readme.md @@ -1,14 +1,12 @@ # openai - - To get started ```vlang -import freeflowuniverse.crystallib.clients. openai +import freeflowuniverse.herolib.clients. openai mut client:= openai.get()! @@ -21,12 +19,9 @@ client... ## example heroscript - ```hero !!openai.configure secret: '...' host: 'localhost' port: 8888 ``` - - diff --git a/lib/clients/openai/test_client.v b/lib/clients/openai/test_client.v deleted file mode 100644 index 1435baf9..00000000 --- a/lib/clients/openai/test_client.v +++ /dev/null @@ -1,5 +0,0 @@ -module ourdb - -fn test_basic_operations() { - // mut client := get() -} diff --git a/lib/clients/openai/testdata/testfile.txt b/lib/clients/openai/testdata/testfile.txt new file mode 100644 index 00000000..95d09f2b --- /dev/null +++ b/lib/clients/openai/testdata/testfile.txt @@ -0,0 +1 @@ +hello world \ No newline at end of file diff --git a/lib/clients/openai/testdata/testfile2.txt b/lib/clients/openai/testdata/testfile2.txt new file mode 100644 index 00000000..dda5b160 --- /dev/null +++ b/lib/clients/openai/testdata/testfile2.txt @@ -0,0 +1 @@ +testfile2 content \ No newline at end of file