shithub: oai

Download patch

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;
+}
--