Skip to content

Instantly share code, notes, and snippets.

@hodzanassredin
Created May 11, 2025 17:03
Show Gist options
  • Save hodzanassredin/15461c63ce08f33a705bc7fc972dacb1 to your computer and use it in GitHub Desktop.
Save hodzanassredin/15461c63ce08f33a705bc7fc972dacb1 to your computer and use it in GitHub Desktop.
llm client in black box builder
MODULE LlmOpenAI;
IMPORT
TFLog := AosTFLog,
AosStreams,
AosStrings,
CryptoBase64,
Dialog,
Files,
HttpClient,
HttpParser,
HttpTools,
Meta,
StdLog,
Strings,
W3cDStrings,
W3cJSON,
W3cObjects,
W3cStreams;
VAR
log: TFLog.Log;
CONST
Ok* = HttpParser.OK;
BadAuth* = HttpParser.Forbidden;
TYPE
Message* = POINTER TO RECORD
role*, content*: POINTER TO ARRAY OF CHAR;
END;
ChatLog* = POINTER TO RECORD
msg*: Message;
next*: ChatLog;
END;
CompletionParams* = POINTER TO RECORD
model*: ARRAY 32 OF CHAR;
temperature*, top_p*: REAL;
n*, max_tokens*: INTEGER;
END;
Client* = POINTER TO RECORD
url: ARRAY 256 OF CHAR; (* Base URL *)
token: ARRAY 128 OF SHORTCHAR; (* API token *)
client: HttpClient.HTTPConnection;
END;
W3cStreamReader = POINTER TO RECORD(W3cStreams.Reader)
r: AosStreams.Reader;
END;
W3cStreamWriter = POINTER TO RECORD(W3cStreams.Writer)
w: AosStreams.Writer;
END;
PROCEDURE (this: W3cStreamReader) Get (): CHAR;
VAR ch16: INTEGER;
BEGIN
IF (this.r.res = AosStreams.Ok) & (this.r.UTF8Char(ch16)) THEN
RETURN CHR(ch16);
ELSE
this.ok := FALSE;
RETURN 0X
END
END Get;
PROCEDURE (this: W3cStreamWriter) Char* (ch: CHAR), EXTENSIBLE;
BEGIN
this.ok := this.w.UTF8Char(ORD(ch));
END Char;
PROCEDURE NewClient* (token, url: ARRAY OF CHAR): Client;
VAR c: Client;
BEGIN
NEW(c);
c.token := SHORT(token);
c.url := SHORT(url);
RETURN c;
END NewClient;
PROCEDURE (c: Client) Close*, NEW;
BEGIN
c.client.Close();
END Close;
PROCEDURE NewChatLog* (): ChatLog;
VAR chatLog: ChatLog;
BEGIN
NEW(chatLog);
chatLog.msg := NIL;
RETURN chatLog;
END NewChatLog;
PROCEDURE (log: ChatLog) AddMessage* (role, content: ARRAY OF CHAR), NEW;
VAR msg: Message; curr: ChatLog;
BEGIN
NEW(msg);
NEW(msg.role, LEN(role$));
msg.role^ := role$;
NEW(msg.content, LEN(content$));
msg.content^ := content$;
curr := log;
WHILE curr.next # NIL DO
curr := curr.next;
END;
curr.msg := msg;
END AddMessage;
PROCEDURE NewJSONString (str: ARRAY OF CHAR): W3cJSON.String;
VAR
res: W3cJSON.String;
BEGIN
NEW(res); res.val := W3cDStrings.NewString(str$);
RETURN res;
END NewJSONString;
PROCEDURE NewJSONNumberFromReal (num: REAL): W3cJSON.Number;
VAR
res: W3cJSON.Number;
BEGIN
NEW(res);
res.isInt := FALSE;
res.realVal := num;
RETURN res;
END NewJSONNumberFromReal;
PROCEDURE NewJSONNumberFromInt (num: INTEGER): W3cJSON.Number;
VAR
res: W3cJSON.Number;
BEGIN
NEW(res);
res.isInt := TRUE;
res.intVal := num;
RETURN res;
END NewJSONNumberFromInt;
PROCEDURE ToJSONMessage (msg: Message): W3cJSON.Object;
VAR obj: W3cJSON.Object; str: W3cJSON.String;
BEGIN
NEW(obj); obj.entries := W3cObjects.NewArrayDict();
obj.entries.Add("role", NewJSONString(msg.role));
obj.entries.Add("content", NewJSONString(msg.content));
RETURN obj;
END ToJSONMessage;
PROCEDURE (log: ChatLog) WriteTo (params: CompletionParams; stream: AosStreams.Writer), NEW;
VAR
cur: ChatLog;
req: W3cJSON.Object;
arr: W3cJSON.Array;
i: INTEGER;
writer: W3cStreamWriter;
BEGIN
NEW(req); req.entries := W3cObjects.NewArrayDict();
NEW(arr); arr.elems := W3cObjects.NewList();
cur := log;
WHILE cur # NIL DO
arr.elems.Add(ToJSONMessage(cur.msg));
cur := cur.next;
END;
req.entries.Add("messages", arr);
req.entries.Add("model", NewJSONString(params.model));
req.entries.Add("temperature", NewJSONNumberFromReal(params.temperature));
req.entries.Add("max_tokens", NewJSONNumberFromInt(params.max_tokens));
req.entries.Add("top_p", NewJSONNumberFromReal(params.top_p));
req.entries.Add("n", NewJSONNumberFromInt(params.n));
NEW(writer);
writer.w := stream;
W3cJSON.Write(req, writer, "");
END WriteTo;
PROCEDURE (c: Client) ChatCompletion* (params: CompletionParams; chatLog: ChatLog; OUT res: INTEGER): AosStreams.Reader, NEW;
VAR
payload, response: AosStreams.Reader;
buf: AosStreams.Buffer;
w: AosStreams.Writer;
authHeader: ARRAY 256 OF SHORTCHAR;
BEGIN
NEW(c.client); c.client.New();
(* Установка заголовков *)
c.client.requestHeader.useragent := "BlackBox/0.1";
HttpParser.SetAdditionalFieldValue(c.client.requestHeader.additionalFields, "Content-Type", "application/json;charset=UTF-8");
(* Авторизация через Bearer Token *)
authHeader := "Bearer " + c.token$;
HttpParser.SetAdditionalFieldValue(c.client.requestHeader.additionalFields, "Authorization", authHeader);
(* Запись в поток *)
NEW(buf); buf.Init(1024);
w := buf.GetWriter();
(* Формирование JSON тела запроса *)
chatLog.WriteTo(params, w);
w.Update();
payload := buf.GetReader();
(* Выполнение POST-запроса *)
c.client.Post(SHORT(c.url), "", "application/json", payload, buf.GetLength(), response, res);
res := c.client.responseHeader.statuscode;
RETURN response;
END ChatCompletion;
PROCEDURE JsonObjToRecord (obj: W3cJSON.Object; rec: ANYPTR);
VAR
it: Meta.Item; sc: Meta.Scanner; nm: Meta.Name;
isOk: BOOLEAN; tmpInt: INTEGER; tmpStr: POINTER TO ARRAY OF CHAR;
res: INTEGER;
any: ANYPTR;
BEGIN
Meta.GetItem(rec, it);
sc.ConnectTo(it);
sc.Scan;
ASSERT(~sc.eos);
WHILE ~sc.eos DO
sc.GetObjName(nm);
(*Log.String(nm); Log.Int(sc.this.obj); Log.Int(sc.this.typ); Log.Ln;*)
CASE sc.this.typ OF
| Meta.arrTyp:
tmpStr := obj.entries.Get(nm)(W3cJSON.String).val;
sc.this.PutStringVal(tmpStr, isOk);
| Meta.intTyp:
tmpInt := SHORT(obj.entries.Get(nm)(W3cJSON.Number).intVal);
sc.this.PutIntVal(tmpInt);
END;
sc.Scan;
END;
END JsonObjToRecord;
(* Парсинг ответа от OpenAI *)
PROCEDURE ParseResponse (response: AosStreams.Reader): Message;
VAR
jsonParser: W3cJSON.Parser;
val: W3cJSON.Value;
obj, choiceObj, messageObj: W3cJSON.Object;
arr: W3cJSON.Array;
enum: W3cObjects.Enumerator;
msg: Message;
in: W3cStreamReader; err: W3cJSON.ErrorHandler;
BEGIN
NEW(in); in.r := response;
NEW(jsonParser); jsonParser.Init(in, err);
jsonParser.SetStringPooling(W3cJSON.DefaultStringPooling);
val := jsonParser.Parse();
obj := val(W3cJSON.Object);
(* Получаем первый элемент из массива choices *)
arr := obj.entries.Get("choices")(W3cJSON.Array);
enum := arr.elems.GetEnumerator();
IF enum.HasMoreElements() THEN
choiceObj := enum.GetNext()(W3cJSON.Object);
messageObj := choiceObj.entries.Get("message")(W3cJSON.Object);
NEW(msg);
JsonObjToRecord(messageObj, msg);
ELSE
msg := NIL;
END;
RETURN msg;
END ParseResponse;
BEGIN
NEW(log); log.Init("OpenAI Client");
log.SetLogToOut(TRUE);
CLOSE
log.Close();
END LlmOpenAI.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment