ref: 8722f3bed54f9d2a2899f604261a0ac5c74d13eb
author: sirjofri <sirjofri@sirjofri.de>
date: Tue Dec 30 08:54:19 EST 2025
adds files
--- /dev/null
+++ b/Readme
@@ -1,0 +1,23 @@
+These tools use the Open AI API to do chat completions AI requests.
+
+It is tested using llama.cpp on a separate Windows machine.
+
+For testing, you can run this command on the llama machine:
+
+llama-server -m "models\gemma-3-4b-it-Q4_K_M.gguf" --ctx-size 0 --host 0.0.0.0 -n 200 --batch-size 8 --threads 8 --mlock --n-gpu-layers 20 --tensor-split 0.7,0.3
+
+(play around with the detail values until you get a stable environment)
+
+
+USAGE:
+
+oai [-k apikey] [-m model] baseurl
+
+baseurl is the http url without the v1/... stuff, with llama-server this is usually just http://server:8080.
+
+After that, you get a user: prompt for your user messages.
+
+
+LIBRARY:
+
+oai.h and oailib.c expose a simple data structure with a function for easy requests using the chat completions API. These are intended to be reused by different tools.
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,7 @@
+</$objtype/mkfile
+
+TARG=oai
+HFILES=oai.h
+OFILES=oailib.$O
+
+</sys/src/cmd/mkmany
--- /dev/null
+++ b/oai.c
@@ -1,0 +1,58 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "oai.h"
+
+static void
+usage(void)
+{+ fprint(2, "usage: %s [-k apikey] [-m model] baseurl\n", argv0);
+ exits("usage");+}
+
+void
+main(int argc, char **argv)
+{+ Biobuf *bin;
+ char *s;
+ ORequest req;
+ OResult res;
+
+ char *key = nil;
+
+ req.model = nil;
+
+ ARGBEGIN{+ case 'h':
+ usage();
+ case 'k':
+ key = EARGF(usage());
+ break;
+ case 'm':
+ req.model = EARGF(usage());
+ break;
+ }ARGEND;
+
+ if (argc != 1)
+ usage();
+
+ if (!initoai(argv[0], key))
+ usage();
+
+ bin = Bfdopen(0, OREAD);
+ assert(bin);
+
+ print("user: ");+ while (s = Brdstr(bin, '\n', 1)) {+ req.prompts = mallocz(sizeof(OPrompt), 1);
+ assert(req.prompts);
+ req.prompts->role = "user";
+ req.prompts->content = s;
+ res = makerequest(req);
+ print("%s: %s\n\n", res.role, res.message);+ free(req.prompts);
+ free(s);
+ print("user: ");+ }
+ exits(nil);
+}
--- /dev/null
+++ b/oai.h
@@ -1,0 +1,22 @@
+typedef struct OResult OResult;
+typedef struct ORequest ORequest;
+typedef struct OPrompt OPrompt;
+
+struct OPrompt {+ char *role;
+ char *content;
+ OPrompt *next;
+};
+
+struct ORequest {+ char *model;
+ OPrompt *prompts;
+};
+
+struct OResult {+ char *role;
+ char *message;
+};
+
+int initoai(char *baseurl, char *apikey);
+OResult makerequest(ORequest);
--- /dev/null
+++ b/oailib.c
@@ -1,0 +1,185 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <json.h>
+#include "oai.h"
+
+static char *baseurl = nil;
+static char *apikey = nil;
+
+int
+initoai(char *url, char *key)
+{+ baseurl = url;
+ apikey = key;
+
+ if (!baseurl) {+ werrstr("invalid baseurl");+ return 0;
+ }
+
+ JSONfmtinstall();
+
+ return 1;
+}
+
+static JSONEl*
+mkstrjson(char *name, char *value)
+{+ JSONEl *el;
+
+ el = mallocz(sizeof(JSONEl), 1);
+ assert(el);
+ el->name = strdup(name);
+ assert(el->name);
+ el->val = mallocz(sizeof(JSON), 1);
+ assert(el->val);
+ el->val->t = JSONString;
+ el->val->s = strdup(value);
+ assert(el->val->s);
+ return el;
+}
+
+static JSONEl*
+prompt2json(OPrompt *p)
+{+ JSONEl *el;
+
+ el = mallocz(sizeof(JSONEl), 1);
+ el->val = mallocz(sizeof(JSON), 1);
+ el->val->t = JSONObject;
+ el->val->first = mkstrjson("role", p->role);+ el->val->first->next = mkstrjson("content", p->content);+ return el;
+}
+
+static JSON*
+req2json(ORequest *req)
+{+ JSON *j;
+ JSONEl *el;
+ OPrompt *p;
+
+ j = mallocz(sizeof(JSON), 1);
+ assert(j);
+
+ j->t = JSONObject;
+
+ el = mallocz(sizeof(JSONEl), 1);
+ assert(el);
+ el->name = strdup("messages");+ assert(el->name);
+ el->val = mallocz(sizeof(JSON), 1);
+ assert(el->val);
+ el->val->t = JSONArray;
+ j->first = el;
+
+ for (p = req->prompts; p; p = p->next) {+ if (!el->val->first) {+ el->val->first = prompt2json(p);
+ el = el->val->first;
+ continue;
+ }
+ el->next = prompt2json(p);
+ el = el->next;
+ }
+
+ if (req->model) {+ j->first->next = mkstrjson("model", req->model);+ }
+
+ return j;
+}
+
+static OResult
+j2res(JSON *j)
+{+ OResult r;
+ JSON *choices;
+ JSON *message;
+ JSON *role;
+ JSON *content;
+
+ choices = jsonbyname(j, "choices");
+
+ if (!choices)
+ sysfatal("no choices");+ if (choices->t != JSONArray)
+ sysfatal("invalid response: choices not an array");+ if (!(choices->first && choices->first->val))
+ sysfatal("no response message");+
+ message = jsonbyname(choices->first->val, "message");
+ if (!message)
+ sysfatal("choice has no message");+ if (message->t != JSONObject)
+ sysfatal("message is not an object");+
+ role = jsonbyname(message, "role");
+ r.role = jsonstr(role);
+ if (!r.role)
+ sysfatal("choice has no role");+
+ content = jsonbyname(message, "content");
+ r.message = jsonstr(content);
+ if (!r.message)
+ sysfatal("choice has no content");+
+ r.role = strdup(r.role);
+ r.message = strdup(r.message);
+ return r;
+}
+
+OResult
+makerequest(ORequest req)
+{+ char buf[128];
+ int ctlfd, pbodyfd;
+ Biobuf *body;
+ char *s;
+ int n;
+ OResult ret;
+ JSON *jreq;
+ JSON *jres;
+
+ jreq = req2json(&req);
+
+ ctlfd = open("/mnt/web/clone", ORDWR);+ if (ctlfd < 0)
+ sysfatal("webfs ctl open: %r");+ if ((n = read(ctlfd, buf, sizeof buf)) < 0)
+ sysfatal("webfs ctl read: %r");+ buf[n] = 0;
+
+ n = atoi(buf);
+
+ fprint(ctlfd, "useragent 9front\n");
+ fprint(ctlfd, "contenttype application/json\n");
+ fprint(ctlfd, "headers Authorization: Bearer %s\n", apikey ? apikey : "no-key");
+ fprint(ctlfd, "baseurl %s\n", baseurl);
+ fprint(ctlfd, "url ./v1/chat/completions\n");
+
+ snprint(buf, sizeof buf, "/mnt/web/%d/postbody", n);
+ pbodyfd = open(buf, OWRITE);
+ if (pbodyfd < 0)
+ sysfatal("webfs pbody open: %r");+ fprint(pbodyfd, "%J", jreq);
+ close(pbodyfd);
+
+ snprint(buf, sizeof buf, "/mnt/web/%d/body", n);
+ body = Bopen(buf, OREAD);
+ if (!body)
+ sysfatal("webfs body open: %r");+
+ s = Brdstr(body, 0, 0);
+ Bterm(body);
+ close(ctlfd);
+
+ jres = jsonparse(s);
+ ret = j2res(jres);
+
+ jsonfree(jreq);
+ jsonfree(jres);
+
+ return ret;
+}
--
⑨