From 3de58c43bd602ecf0e852760a443b656d0455922 Mon Sep 17 00:00:00 2001 From: supertick Date: Sun, 19 May 2024 08:30:21 -0700 Subject: [PATCH 1/3] Initial Release of LLM service --- .../org/myrobotlab/service/HttpClient.java | 24 +- src/main/java/org/myrobotlab/service/LLM.java | 398 ++++++++++++++++++ .../myrobotlab/service/config/LLMConfig.java | 49 +++ .../org/myrobotlab/service/meta/LLMMeta.java | 30 ++ src/main/resources/resource/LLM.png | Bin 0 -> 14844 bytes src/main/resources/resource/Ollama.png | Bin 0 -> 13745 bytes src/main/resources/resource/OpenAI.png | Bin 2309 -> 5687 bytes .../WebGui/app/service/js/HttpClientGui.js | 8 +- .../resource/WebGui/app/service/js/LLMGui.js | 128 ++++++ .../app/service/views/HttpClientGui.html | 10 +- .../WebGui/app/service/views/LLMGui.html | 155 +++++++ 11 files changed, 797 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/myrobotlab/service/LLM.java create mode 100644 src/main/java/org/myrobotlab/service/config/LLMConfig.java create mode 100644 src/main/java/org/myrobotlab/service/meta/LLMMeta.java create mode 100644 src/main/resources/resource/LLM.png create mode 100644 src/main/resources/resource/Ollama.png create mode 100644 src/main/resources/resource/WebGui/app/service/js/LLMGui.js create mode 100644 src/main/resources/resource/WebGui/app/service/views/LLMGui.html diff --git a/src/main/java/org/myrobotlab/service/HttpClient.java b/src/main/java/org/myrobotlab/service/HttpClient.java index bb43043861..ef5ffb7701 100644 --- a/src/main/java/org/myrobotlab/service/HttpClient.java +++ b/src/main/java/org/myrobotlab/service/HttpClient.java @@ -81,6 +81,20 @@ public class HttpClient extends Service implement private static final long serialVersionUID = 1L; transient CloseableHttpClient client; + + /** + * simple pojo for request data + */ + public class HttpRequestData { + public String url; + public String verb; + public String body; + public HttpRequestData(String verb, String url, String body) { + this.verb = verb; + this.url = url; + this.body = body; + } + } public HttpClient(String n, String id) { super(n, id); @@ -129,6 +143,8 @@ public void addHttpResponseListener(HttpResponseListener listener) { */ public String get(String url) throws ClientProtocolException, IOException { HttpData response = processResponse(new HttpGet(url)); + HttpRequestData rd = new HttpRequestData("GET", url, null); + invoke("publishHttpRequestData", rd); if (response.data != null) { return new String(response.data); } @@ -196,6 +212,8 @@ public String post(String url) throws ClientProtocolException, IOException { */ public String postJson(String auth, String url, String json) throws IOException { HttpPost request = new HttpPost(url); + HttpRequestData rd = new HttpRequestData("POST", url, json); + invoke("publishHttpRequestData", rd); StringEntity params = new StringEntity(json); if (auth != null) { request.addHeader("Authorization", "Bearer " + auth); @@ -323,7 +341,6 @@ public byte[] postBytes(String url, Map headers, byte[] data) th public HttpData processResponse(HttpUriRequest request) throws IOException { String url = request.getURI().toString(); HttpData data = new HttpData(url); - invoke("publishUrl", url); log.info("url [{}]", url); @@ -351,6 +368,11 @@ public HttpData processResponse(HttpUriRequest request) throws IOException { return data; } + + + public HttpRequestData publishHttpRequestData(HttpRequestData data) { + return data; + } /** * publishing point for any http request this is the asynchronous callback diff --git a/src/main/java/org/myrobotlab/service/LLM.java b/src/main/java/org/myrobotlab/service/LLM.java new file mode 100644 index 0000000000..6487711668 --- /dev/null +++ b/src/main/java/org/myrobotlab/service/LLM.java @@ -0,0 +1,398 @@ +package org.myrobotlab.service; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.myrobotlab.codec.CodecUtils; +import org.myrobotlab.framework.Service; +import org.myrobotlab.framework.StaticType; +import org.myrobotlab.framework.Status; +import org.myrobotlab.framework.interfaces.Attachable; +import org.myrobotlab.logging.Level; +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.programab.Response; +import org.myrobotlab.service.config.HttpClientConfig; +import org.myrobotlab.service.config.LLMConfig; +import org.myrobotlab.service.data.Utterance; +import org.myrobotlab.service.interfaces.ResponsePublisher; +import org.myrobotlab.service.interfaces.TextListener; +import org.myrobotlab.service.interfaces.TextPublisher; +import org.myrobotlab.service.interfaces.UtteranceListener; +import org.myrobotlab.service.interfaces.UtterancePublisher; +import org.slf4j.Logger; + +/** + * https://beta.openai.com/account/api-keys + * + * https://beta.openai.com/playground + * + * https://beta.openai.com/examples + * + *
+     * curl https://api.openai.com/v1/completions \
+      -H "Content-Type: application/json" \
+      -H "Authorization: Bearer $OPENAI_API_KEY" \
+      -d '{
+      "model": "text-ada-001",
+      "prompt": "why is the sun so bright ?\n\nThe sun is bright because it is a star. It is born on theentlement, or radiance, which is the product of the surface area of the sun's clouds and the surface area of the sun's atmosphere.",
+      "temperature": 0.7,
+      "max_tokens": 256,
+      "top_p": 1,
+      "frequency_penalty": 0,
+      "presence_penalty": 0
+    }'
+ * 
+ * 
+ * + * @author GroG + * + */ + +public class LLM extends Service implements TextListener, TextPublisher, UtterancePublisher, UtteranceListener, ResponsePublisher { + + private static final long serialVersionUID = 1L; + + public final static Logger log = LoggerFactory.getLogger(LLM.class); + + protected String currentChannel; + + protected String currentBotName; + + protected String currentChannelName; + + protected String currentChannelType; + + protected Map inputs = new LinkedHashMap<>(); + + List> userMessages = new ArrayList<>(); + + public void addInput(String key, Object value) { + inputs.put(key, value); + } + + public void removeInput(String key) { + inputs.remove(key); + } + + public Object getInput(String key) { + return inputs.get(key); + } + + public void clearInputs() { + inputs.clear(); + } + + public String createChatCompletionPayload(String model, String systemContent, String userContent, int n, float temperature, int maxTokens) { + try { + // Create the map to hold the request parameters + LinkedHashMap requestPayload = new LinkedHashMap<>(); + + // Add model to the map + requestPayload.put("model", model); + + // Create the messages array + LinkedHashMap systemMessage = new LinkedHashMap<>(); + systemMessage.put("role", "system"); + + // Get the current date + LocalDate currentDate = LocalDate.now(); + // Get the current time + LocalTime currentTime = LocalTime.now(); + // Get the current date and time + LocalDateTime currentDateTime = LocalDateTime.now(); + + // Format the date as a string + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + String dateString = currentDate.format(dateFormatter); + + // Format the time as a string in 12-hour format with AM/PM + DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("h:mm a"); + String timeString = currentTime.format(timeFormatter); + + // Format the full date with day of the week + DateTimeFormatter fullDateFormatter = DateTimeFormatter.ofPattern("EEEE MMMM d'th' yyyy h:mm a"); + String fullDateString = currentDateTime.format(fullDateFormatter); + + inputs.put("Date", dateString); + inputs.put("Time", timeString); + inputs.put("DateTime", fullDateString); + + for (String inputKey : inputs.keySet()) { + Object objectValue = inputs.get(inputKey); + if (objectValue != null) { + systemContent = systemContent.replace(String.format("{{%s}}", inputKey), objectValue.toString()); + } + } + systemMessage.put("content", systemContent); + + if (config.maxHistory == 0) { + // history is disabled + userMessages.clear(); + } else if (userMessages.size() > config.maxHistory) { + userMessages.remove(0); + } + LinkedHashMap userMessage = new LinkedHashMap<>(); + userMessage.put("role", "user"); + userMessage.put("content", userContent); + userMessages.add(userMessage); + + List> allMessages = new ArrayList<>(); + allMessages.add(systemMessage); + allMessages.addAll(userMessages); + + // make history + requestPayload.put("messages", allMessages); + + // Add n, temperature, and max_tokens to the map + requestPayload.put("n", n); + requestPayload.put("temperature", temperature); + requestPayload.put("max_tokens", maxTokens); + + return CodecUtils.toJson(requestPayload); + + } catch (Exception e) { + error(e); + return null; + } + } + + public LinkedHashMap createFunctionDefinition(String name, String description, LinkedHashMap parameters) { + LinkedHashMap functionDefinition = new LinkedHashMap<>(); + functionDefinition.put("name", name); + functionDefinition.put("description", description); + functionDefinition.put("parameters", parameters); + return functionDefinition; + } + + public LLM(String n, String id) { + super(n, id); + } + + public Response getResponse(String text) { + + try { + + invoke("publishRequest", text); + + if (text == null || text.trim().length() == 0) { + log.info("emtpy text, not responding"); + return null; + } + + String responseText = ""; + + if (config.sleepWord != null && text.contains(config.sleepWord) && !config.sleeping) { + sleep(); + responseText = "Ok, I will go to sleep"; + } + + if (!config.sleeping) { + // avoid 0,8000000 + String temp = String.format(Locale.US, "\t\"temperature\": %f\r\n", config.temperature); + + // chat completions + // String json = "{\r\n" + " \"model\": \"" + config.model + "\",\r\n" + + // " \"messages\": [{\"role\": \"user\", \"content\": \"" + text + + // "\"}],\r\n" + temp + " }"; + + String json = createChatCompletionPayload(config.model, config.system, text, 1, config.temperature, config.maxTokens); + + HttpClient http = (HttpClient) startPeer("http"); + + log.info("curl {} -d '{}'", config.url, json); + + String msg = http.postJson(config.password, config.url, json); + + Map payload = CodecUtils.fromJson(msg, new StaticType<>() { + }); + + @SuppressWarnings({ "unchecked", "rawtypes" }) + Map errors = (Map) payload.get("error"); + if (errors != null) { + error((String) errors.get("message")); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) + List choices = (List) payload.get("choices"); + if (choices != null && choices.size() > 0) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + Map textObject = (Map) choices.get(0); + responseText = (String) textObject.get("text"); + if (responseText == null) { + // /chat/completions + @SuppressWarnings({ "unchecked", "rawtypes" }) + Map content = (Map) textObject.get("message"); + // role=assistant + responseText = (String) content.get("content"); + } + + } else { + warn("no response for %s", text); + } + + } else { + log.info("sleeping waiting for wake word \"{}\"", config.wakeWord); + } + + if (config.wakeWord != null && text.contains(config.wakeWord) && config.sleeping) { + responseText = "Hello, I am awake"; + wake(); + } + + Response response = new Response("friend", getName(), responseText, null); + Utterance utterance = new Utterance(); + utterance.username = getName(); + utterance.text = responseText; + utterance.isBot = true; + utterance.channel = currentChannel; + utterance.channelType = currentChannelType; + utterance.channelBotName = currentBotName; + utterance.channelName = currentChannelName; + if (responseText != null && responseText.length() > 0) { + invoke("publishUtterance", utterance); + invoke("publishResponse", response); + invoke("publishText", responseText); + } + + return response; + + } catch (IOException e) { + error(e); + } + return null; + } + + /** + * Overridden error to also publish the errors probably would be a better + * solution to self subscribe to errors and have the subscriptions publish + * utterances/responses/text + */ + @Override + public Status error(String error) { + Status status = super.error(error); + invoke("publishText", error); + Response response = new Response("friend", getName(), error, null); + Utterance utterance = new Utterance(); + utterance.text = error; + invoke("publishUtterance", utterance); + invoke("publishResponse", response); + return status; + } + + public String publishRequest(String text) { + return text; + } + + public void setToken(String password) { + config.password = password; + } + + public String setEngine(String engine) { + config.model = engine; + return engine; + } + + @Override + public void onUtterance(Utterance utterance) throws Exception { + currentChannelType = utterance.channelType; + currentChannel = utterance.channel; + currentBotName = utterance.channelBotName; + currentChannelName = utterance.channelName; + // prevent bots going off the rails + if (utterance.isBot) { + log.info("Not responding to bots."); + return; + } + getResponse(utterance.text); + } + + @Override + public Utterance publishUtterance(Utterance utterance) { + return utterance; + } + + @Override + public String publishText(String text) { + return text; + } + + @Override + public void onText(String text) throws IOException { + getResponse(text); + } + + @Override + public Response publishResponse(Response response) { + return response; + } + + /** + * wakes the global session up + */ + public void wake() { + log.info("wake now"); + config.sleeping = false; + } + + /** + * sleeps the global session + */ + public void sleep() { + log.info("sleeping now"); + config.sleeping = true; + } + + @Override + public void attach(Attachable attachable) { + + /* + * if (attachable instanceof ResponseListener) { // this one is done + * correctly attachResponseListener(attachable.getName()); } else + */ + if (attachable instanceof TextPublisher) { + attachTextPublisher((TextPublisher) attachable); + } else if (attachable instanceof TextListener) { + addListener("publishText", attachable.getName(), "onText"); + } else if (attachable instanceof UtteranceListener) { + attachUtteranceListener(attachable.getName()); + } else { + log.error("don't know how to attach a {}", attachable.getName()); + } + } + + public static void main(String[] args) { + try { + + LoggingFactory.init(Level.INFO); + + // Runtime runtime = Runtime.getInstance(); + // Runtime.startConfig("gpt3-01"); + Runtime.start("llm", "LLM"); + + WebGui webgui = (WebGui) Runtime.create("webgui", "WebGui"); + webgui.autoStartBrowser(false); + webgui.startService(); + + /* + * Gpt3 i01_chatBot = (Gpt3) Runtime.start("i01.chatBot", "Gpt3"); + * + * bot.attach("i01.chatBot"); i01_chatBot.attach("bot"); + * + * i01_chatBot.getResponse("hi, how are you?"); + * + * Runtime.start("webgui", "WebGui"); + */ + + } catch (Exception e) { + log.error("main threw", e); + } + } +} diff --git a/src/main/java/org/myrobotlab/service/config/LLMConfig.java b/src/main/java/org/myrobotlab/service/config/LLMConfig.java new file mode 100644 index 0000000000..1e605f286c --- /dev/null +++ b/src/main/java/org/myrobotlab/service/config/LLMConfig.java @@ -0,0 +1,49 @@ +package org.myrobotlab.service.config; + +import org.myrobotlab.framework.Plan; + +public class LLMConfig extends ServiceConfig { + + public String currentUserName; + public String type; // Ollama or OpenAI + + /** + * current sleep/wake value + */ + public boolean sleeping = false; + + public int maxTokens = 256; + public float temperature = 0.7f; + /** + * number of history items to keep when re-submitting + */ + public int history = 5; + // public String url = "https://api.openai.com/v1/chat/completions"; + // http://localhost:11434/v1/chat/completions + public String url = null; + public String password = null; + public String model = "llama3"; //"gpt-3.5-turbo"; // "text-davinci-003" + public String wakeWord = "wake"; + public String sleepWord = "sleep"; + + /** + * maximum history of chats to re-submit + */ + public int maxHistory = 5; + + + /** + * static prefix to send to gpt3 + * e.g. " talk like a pirate when responding, " + */ + public String system = "You are a helpful robot."; + + @Override + public Plan getDefault(Plan plan, String name) { + super.getDefault(plan, name); + // http peer to retrieve emojis + addDefaultPeerConfig(plan, name, "http", "HttpClient"); + return plan; + } + +} diff --git a/src/main/java/org/myrobotlab/service/meta/LLMMeta.java b/src/main/java/org/myrobotlab/service/meta/LLMMeta.java new file mode 100644 index 0000000000..4c06cd3f18 --- /dev/null +++ b/src/main/java/org/myrobotlab/service/meta/LLMMeta.java @@ -0,0 +1,30 @@ +package org.myrobotlab.service.meta; + +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.meta.abstracts.MetaData; +import org.slf4j.Logger; + +public class LLMMeta extends MetaData { + private static final long serialVersionUID = 1L; + public final static Logger log = LoggerFactory.getLogger(LLMMeta.class); + + /** + * This class is contains all the meta data details of a service. It's peers, + * dependencies, and all other meta data related to the service. + * + */ + public LLMMeta() { + + // add a cool description + addDescription("Ollama or OpenApi inteface service"); + + // add it to one or many categories + addCategory("AI"); + + setCloudService(true); + + setSponsor("GroG"); + + } + +} diff --git a/src/main/resources/resource/LLM.png b/src/main/resources/resource/LLM.png new file mode 100644 index 0000000000000000000000000000000000000000..4a21bbcdf97989932522cf80a8eb36328acb5af2 GIT binary patch literal 14844 zcmeIYWl$Vl6E?g!1b5d2cUWw3cbDM7S=<(PcPEhGt|7Qv2<|SyU4sR;@RIxS`rbcJ z)%X5;wrXZ)&gs7T^woWOW~w?;MM)a<4bd9_0DvkhBcb;C&-Uj)gnm6Q8re?*0F={S z8rl#wV|NNiX9o)_TXPDCr=vNAxrdbn0N}Aylws*c!j~HQVv1V_>rqFt=;DP?_jJ!f zC6maZX~3n{G$@B1>9LoNQ268JF~{_GxG>kewFv`PQzo;&N%WYzU)jz5%vslX|Kr&+ z_FBo^nL~WW1g!viD0$VFd*A1Q?bA!>iTH7c!;#Pp(1SNcTeDHp$UYXZCD?)309qdhgHw(aoYikUH|t7To--fvv%>P-H- zvgmk@u>WJT(_sni_x=65svMHz$BdWXch;u@j1Tk=4=;Qb+1?W?lJTEa_HT=Y?00|n zAH$R9#eWSeGS|EF-@@AcZTD2Eab1X$n&o!x-nu7n2x4j7ZRNLL&zVt7C3xn}*7rOs zLBH*mJ5A3qn@HsU-MTFgHcnI|-&5r0d)AL`BVYaryq#glX(wU5j*I47`xXC^?*94f_vy%b&I?^tAPSA74)ZkPPY)LMq{l$mO&ZYo z_uh#T1OuHjI7CeHEbSO%vw$xx3Hol09v?E_CVYf-O;v)q7lqfC^Q(~|7a!ay_VNWf z(4ThB(oXb*4M1^nB#NPhuGCM;YQ;o8(ATJm3x?vT%kh2GP?r}BCvh3(OJh>`Nu2_g zV`xcHlV|LJ7vY@Km#=J?Tk}BzQ}nDSo^4CUVn+l_%ck zl)Bv%b`FB3%fsgQ6~Y%Yjn9w%yJIPL%nrZTEyn3wU)Wr__DiVNJt64o}4RO%P*r^Lw zpUp=>ep24D%j^ur0vu@03_}?RytP?+0@XOmBe?*o1hFtzhI~}d+=9` z)#TJmI|fi*#b7IN;lmTq{}%e01d;r-stSvKSlhQOBW%nytQ_P9Om?y+zgut*zp(Rf)>xuQG0?MPCjnOln?DwSkFDui8i%E79b4sW#7Q z@YcOi$f3)vqig7E)5_jZ`4ZAsq7aRNORNe)lyuWf<7S?zCdcpBNdLOWSR{Ig$v^Y2UTEf>QWEFnh0fX_WjIOc!E@@V26 zjD0EzYdBO|whnnS#PV&PM`x@|)!mD@qa6&ccZJVK#}}x_Z0L+D!kvx`BX*#p4^hzF zPRP|!TeQiMk(>$l_C!Zx)|Jee!V*2GC%uShRm{_1C?FPVjvtBSFZ?1Ej7i6T^jSn1 zeHwQlXn+JTv&4deGZ_)$qP}hg6lqobw%RL%>3$WxJOeF{Nv}nLAk>*XuFFYSBq;0a z7>XyC{c-X4=R5KE(=~;%2Xri-<_=WrYj!z<0980Av<9WoG@C*{PiTEFz~;7A$5ahu ztG;62#G2Vkv+G{HKZ33gt~C!{9?P-GE8>TM(0gJxDeuYfAG8nTegct(wM=uU;%22t zT}m-m9}0;jg{D8hi`i=On&ffRa1>xO&mKmQMPzb64iz#QV83Arg#WV6yMwbyMr>O0Y=fU?A@ISo)qLH&%-y1NFGLtM(tG2>1qz&%~K zP4s|YDBZ5?7=12!;jR3!B~OGdpq|GoWBQC&sM#s=;2}ve-ArNQL|g9vB2T zeAjs`zh0_Z*awixzg82)euso?rnuVw8Ch{Al}LNG4XJv}We$3%j5bvnW3bdI;+TU7 zE4v8>Hhn)vi6#(FNS-4l5W;d7#t**3iKmhBxLqD16d$tc^z)H zTuBjdyc89r#MFc$-?KCi^2t(x{7 zRf}AtR@8i6tXbb|)y^Kse^J7-6F2*489z0|pGdnG>>Qc=n2XO61TSw+UP^2C)qYB7 z){eEV%ya9fi$?)`F(w7{xm#3q%X92cv9lSX>}oeh^qM{Ekhucw5LX9c3nu_=Au@f^ z?~%DeEF$W(vXr(JRLc?QTjxh4a(w*)`rMq7oa>k(OfZ zd*hz;94AbXWI)$n=}(GiM`e7dlb4as$v3 z((dgypd#Td6 zMfPHX;MF6=Q+}HG0vP+E8Mavx!}Sx3#MLlp%zVmV<(zyKaBr#f$ zaac0$D>LWBUR8sBkQ^Z+3G&R@6-~H8JFE`Lx90S>!48JLr`(8wB`F=VGp<3DLT@8N zVcej+LWcDGTn$9dZHXvEsjnr1mXu~3Og>Tv zECe2m_(mBQLEKhO4y<+lS}{XfSTmva4q5tAm2uJWrWHMT*vS>4;s)#5u(jE!;zpI? zV=ie(G~$GlE?|^ah2-UM5uq?;FLP1kj(C*Oj=;psoSWkSZOT;Z(eJ14vY46umhr%E zv+Ca9?{PYsoUBmzL`i&ray)oPu8|ppU?0N6+`D)rVt2#GSasl84aVjX3Quw+r8!X{ zmI$2g5jTp5m(!Q4GS(&^oYQM=G6{~?^VQwI{iXy7?g$SS3VTu2QE#S+ccOY zj7?^(h6=dCzCp7m9i%CjQZl3x1mJFDh(%ptd$S%HaZT%{7b{jXY|TJt!4Bh2v8E!VkteUgzL%QT$R%*av6B_(|Ao%E4DStb_%b#B_Tp^MhJ9JY z{BAtmo+MS%@S6ZW&HCa4?{9FZ*6!(mj2RF7RVdgoM=ji?l8gmu(&1-L#W(2ci}lB009jwx&hig z&l5bvfRyju+nbnKXnb^!2Zhsj;gYe%zYz-LH^nD~*@;_O!*A$9C`RD1slt1yW<`1} zDl6*u6I%j;mpTo@?V$+sWHn%qIzI`)y=V_6zLkPy#w>W_U1zvq07;pEa3KE*M_U-o zmpvEx9)X)$g_EpF$`?Zw8+DRL1~O3$3wazVL(ma6LqBDQ7J15i)QnW~!4)fz&k~+D z^U9qm>zRSYAR|>W-4gYuE^fcrB&f|^ zoy*$-W`qFouTs?&X#HsLC{uf@_4eKrIMBZ_hzcBzDwB$6vqP)tr7c%kSd1F(cGVeq8jUR%8psb4a&S{I5vlCmNKj_UXcc3b3lUEJyGCYE?RZ93e1 ze0CIl*b0p*49&Kf$Z3IQ+nVWYU+{@nuD{9Ca)~8O_4o3eTuHc#QF|o9T%a8ZXu^=Q z;Gic#k!CTZVwOUk(f1U!O!#l#U(c+}PNyYz&K7pAlwrW9;H4AjTp_2hb@;=!uIC09 zH8*|2OD$`g`3$VINWgqD{vkwBpjJRcX*TeY`kSNZIjh)4TkdWoTm+Vm^E$)K49DHpUoCyxL*DH`x!h=dh)TI|(Ra7ROQtqVnQY z3SaL>Q4JSxFhm@fH1Q=Ef-nI<$t4P16r@ZERc-PiqEX~@o>1Bd*DRv!N2tIy8riua zz&oZvoci%&!1Q~B0G05#!V3>#IjN^kl}CNDF?e8%4b)0FoCw4-mg!qTMDwKlZ=etg zb^lszGJn^f+0^ixjd7jAt5`xH*5^710nnlg6ARoKUFh!-*&;z$2L&`uqYZE~S$|u;T&8Y{V>l%iMa2BDl z*N~;;9ChO(5s&?f+u^+z*rqgr$#K{BV5Ixh8ch3j8VvQ&B_u%(+K|W;3&P=C*hhQU zWH-&**x)u3b(Qg53MaWlRwaxLh6cP6TYo(o1>gF9CL>Y<>P8C_)uRyS0W*i-s%8(v z)ss%bcKS5=5s}apm~Wx-$y>zmF^#z}X>M1kM^}-~*x^^8&$lR=!~qqauUiAx{DDvU z3u;w1H3T^E1F>NCvTB4%cM%<-a|#wVv10@R%5yy?qtv#@lajRhUG^yWCepB=*Aw`UR@IIvW!i z;@YovR>n=^@;5X{+|@z|R+6elxkHLJs!{m6MyK*`n=tApphxE?J^Z%U=rCFIxR&AT z^IClNgJS#n@4}=3R-al_gy9wCpMT=;JfvDnJLfWnqy?l4Z2@nU*4c-`(_y4&%z_5p z2&`FV=P<&KpNeY4@VhPECW+l2YEd@R5IL8IsYb`g({Z&~skNUeIE9{4;OnrZbhw*& zT082rP%4p7VXWg<%TALiJBr)>umHZEO)T*kc`uS)tidd-&V`M z`Ma%gbL7as`9n!)RU7h#S`Y+vJ|(@ z<>|q+;*r%HB8q{&posbe7zzU+hQA0?i!Z*RR&-84Id;m?XAkFxAsk^~0`a^!={P^` zSZxKFeFl`xWD-CXS8||8J=OFlfF zOENhooeDc6Ky{&1Z;~RnW8Q7uNj!l_6lt@-Ogm*I;;ee)#G^gg$1HTG!{N6o@aa5% zRk4m2Bwh>0wF&n;DJ&`q!tVE6Rgn)$9y>;OiCpDw!m3|Tjm-KOXlp2D5>%c;Rt}aP zu_j~wrA`S4+xrboHvv$y+H^>aHsH3%pAJVVY*BX|XJD2FDao}x_OL#2KN$$wUN(0u zbOtM{105nzKQ>rN4`VzHJbrg(DmEY!Ep6uio_xaubKPQGY-*UowcXB(-5K@GEaM{8z&2|y#ow@*w=5$lw}G1s#><_82jp$|5wV9u=J%)HXMa zR{g8qj4+7_?-x1a!;X_9cInO~9p{cQK%u4~B@LU8`p|+cZ(uB1lqS~PZF7d}ZKWyE z3A;i|5-O$`TM0m%DZnhqZb@Po)Fo}DCUuo{!*199G)w#0UcNvrJWOJI3^8ao9~YpD zlQ~Jt5*VGT0zKxE1^}a?2DH<__6B@=yItsr-mh$`CQF^7Hk%1IOBuz+S-6sD9>V&K zumL&pg7)!3jynMWrlBSKCCk|un@`M9jWbHr91?OZ_MOfodvEr*Uq2n}mSr;OnOg)S zj23j$N~2==jt2^{n#%4Z+A@nqZVBK0kZ@%PW%J9&1Ex4s;-R*_HyA4b1Pg)zLR3n8 zN9j)HW4a6QHW^L|5kS{cGE%=SJXW!JuNySVMcElcQS6>CGqia)TXsrCjP-oMkDJ zwb;5KuKAWmZ#Ag&5>qCc&cTx)Bk><$<`BfaX;6+oaABGy*y$#lIX(BGM;KHc&~!kY zQa+M}bD*-y7$P&IBAn8p6hn31ta-wsF}z%DO;4uXVAHXm z2>smt-V~ddfLqmYSD-Ow8im4W3Vuyiy$!ed$r1tr(E6lnn0-_c*t6K{ZuM}{?y8eXiX?d+t$h>$0qBI42&T^D7Apb@0_a(+7|;54z~PF@WNnY{6&LdG;pW% zD8$V5w-`dIi1rBXn7WTvQCeE#&Q>JR$j1r}{Q4l$h$ZEPRIe2Hx6to3s8Ps|C)KE6 ze@rGvEzSRY6T_by*v{UEUax2uCI-_9cDY8^48jFGwPk8g@e*di#1G*EtkoKzV{)^I zvznm^y6+KjH$BcxBJxO&E8Ty<7hvO6UtM89go4c5Nx>&`gu!fHcW_qD7-bAR*}^lc zPDRGYX z4v2fNsBA~6b_D^ZHXbXCPTgGN%B$Q?K?wbwS-8v`7+8>bC}azIINgCv;cn*Dd;Ua> zF2~XtQZnqlPz8p7D^ zgK_M9H0vXu4;LQ%6{Jj>yq`oomF5CgVsi_7XrOHpD6PcB;bmz1Tc|?5mYEFC%BqJ1 z5;=0gmQ`@Qmn5gLi{;9a$bWZBk{&WBE%2+=h*HY&>mX-c&}239!TS>*wW`8j*+`Fc z5?KTujF^Eab@VQDFs*9cIhw0 z@ZxWOnj;pVm8g-)7sPL7&6i%n;I_4zcU}6cZ|+rnP}#l#*)+pKl5+wPALD0a1ZB!K zQ1#neh*#uGXwaJ=t;e7WVOIiZ!LZ<9o|#I|klpEuxVx)yu+t{}N{=hDz^_{Abd7+X z=vuNGw_K0ofXY#eo=HuxI6^8L#VHvTTw8XTc%v?-(kM6z>eT)4I27^1D0g9erc*ax z1|pz`kp2V$g2+fr@u0u3hO~OA4-kaA>2vwG*I9df^HugbS(zfHHfngQ4)}7ckbtPU z9|w5|>tq29v(T$XV1|@jegH*f(ZiZ)wm-~FB|L-UQNA)9%sgg=D-2*wJ*}nBIpqY* zvxCGde9L0jB!Yx!+Z(Zi^w=Vik3&$I+ljyS;Df*d1IpU1+<`Nl#MgT(JzM^x^0*&t z`txGiZdQz2a+OwG!;SMh47ksjh1?fkj?kP`R01@*+`1rfxlD!0w)E58gBvkTL2>;K zDq%_`A}V+6lTszY_!lKNT)Y*SeFw7+0fT9eJyokaaZRV4>#I%PmqhuR(!baR-t(?* zXLB4J@K1M17yg1N_XOKBvc90?PdDlHcv9dchPMw&XJxjXLN!k7pp7l|Ka?_xj#M#R$GAG<5B?SDb%H-WnMwW)KQhxZ{p!Nti+VT$v}Hld^XP~7q887syo z`RIMo{^;G=gBQ+{5H^RyK<@4v-OnW2_!-?{rOgVObWISdNZNn%QfT}_5-*7^<*~VG zLWsW>GlQ&dG7zMD%hzH{TJ)YOS0J{JG{lo4w+A7wJxB?0tul1xTC!rsRbbPL-cCw? z)18s{ho`&UwbJTJ+O1HUl)fG#SZaDku?S>-&L82wxN7d2tb7s?m zy%z)Kl2jKsxj>{rV-#v#rPt$A*;~<484s*YJqTEtlbSJ3P8^vHt;QJw>wSP?fL>WR z=_q@Mf}y7;MSw>7`vUJ$%~dQI*@9GI<`7>Sx++kE6M|=Q7?<}2n}G2H?{!B`x+lit zLB-oB`L7c!NS!ZXhN&AgYdaH7H|t%R;c(X;9%SnvAxZMhMe&DRtl@7@kKE5|MSJT_ z^paawn((4_hctr3j1Ta0=9>lL7`vz580-7Q6d=d`M=mB`qiWP4YQGtUk43q`qnB_Y ze#5?Rh@BtSSPWw0V!#OFnHt4r$F6$Pi9POYZpFE&gSkROl<5r zg&{`Ta#J?f6@Ph~$;%f~jFZw^#hq)$n~mM$IZYX-8?)`_9`0eS+aRt=OprDB6;^$K zaHid5x&2|)ux6V*xB9gZa`S1=6<*7AC#c^({6g}TrcjV4LY$AMpB62i3%B$SX2v!5 zK3iry^-#4ZJ{x82(f@WIkbz*!GMj5%>}@nVU#t)lEuO$=w{_~SaBfZ~Rf-eGt@mhB zpW1nT_tR?XNaEc)yKE*aP353mUmVuW=-X&&j`ufyHD=Tyr%pW>;m5Nl1d~wYt*)Jj zwm4Dsk9}~3O-f5@BAqrU0k?A(Isp;X8{93K(qy*;4MlO9=;`%eg3ieG*Q{XM=#1~e z!4JGAq&l_pOgz?HsS<)}V19hWU#CZ@%Rh{vuJxXJ`@$vl4>wBpxRBLmHc9Sb1m0x1 z-v*iX|1f~1ea3$$#Aq+B7XqYvQ`@qT<$L_tF zZjfVLgE83|kMZ@%iDlK3psYZD-TSYseybhM6b%Q90R$Zfh4tM&=0>U?=#@3#hkY=ZbArxVM>z`!+`yThkTa*F=h-;aA#dr&@BIN8UvO;)jJsG> z=-7C;HN2m%)xZWRXCtqYLqzoJEUPXtz+`sf+Er&6R*i9#ARRI+riI1`ZuTHI(+WH; zr+n1{mAC%9J-JY)s*ma9Wx%GZxHyt6G<9j~bygE2-RnMag9%Vf3sD*#oBg5tl)=e@lEuA56gX#Nk1vNcRCq>Zi!pv5qgMt}(eh+D zvY`a|FJ~5iH>57niTdn!AfX4X6<7h(<4IC_dz|H>LZmK#$iHUR4|+>rgWtkCPRpBo+d)_F)h$a zMopgtZXqa`JX#Y0-_n3oA5QcfruRp;?3AS3(+4#$=61J}@Y<^PN(W4cRA`yS0&lK$ zYNA|kpMsm_Pd`b(GompL=I753neV7K`8rQGw~q~CEIo4`U9NV2(ZKjnT`$9x zkqOgWGMjiSJ`&liCU>!YTF^>uQQ{1O#jAluqQaR~k%GfBskbitBs1l=(j&ZcN9|0% z!w{Tk3Z+?^Kq`Dk{&YIeq@dI++s%vNQF9V+oa>Dbp0|5^Tx`ss4R|4Jo?iNG4fE;# zUB}vdzO)FKxVhQhW|+*e1qMavu$GcPDW_P44sd39rKn@Hfp=iw;%#%XeFY_>{(gvf zV*`VLUhSrJpv@cP{dlWT(M9f+?p!{{|H13E3x~++wcAEpQ2}J?V8>!?=3rvZ;$i3b z+KmGM2nc&P8k^dfLnutlEv@VYsm|KEs3@$=1gYM0DY7X#ikpM2WW1cs)xDH7OucMO z`OK(4G?n^OQ; zfGljxk{(uW98^MYCvY+)!f$H?p4&~)hhdcn3R@PRQadI9||n3>>U5ndL{cm zBq3H7|03%@*7j%SFL(ZZA+PHH#QhKHfB62F@T-)fB1po))b-EuWF-Wt{@4eZIhb0R zf&RK==jG<+W@qPSW;X`%F#~}nrp(6dT&B$CW)|jL#%yfHyd2#B1|@6n0x`BXHU9(k z3eIBniet`U%3;pN#>ULS3*=-5@^JGooA8*KF!OO3bDIG9IJnHrP5%u-+1ctfD~)ac zeN}&;%wC~P*-be(Irz+(*)3j^18BkrWahPCH)Xcqfu>EU9#nu>N@v0z5rC??6>hZ4$4J$iyb%^mF zHraXDx%jx*xHx%%yxhE;T>lc%GIw@)&BQ;L>})KYf5ZI~7SO9Ruf!Vv$7Pc>c-wzvsNVxzpe7{L24)|CeBy@tSjUnV18aIXKyQ znSp#K2k6xn8xM$$jf(Z}4zvE*)&KBVfc5{wiNIe1|5gTG z_5L>YT3%kO73)9C)!#Y$L*xI$-``{L|IorK^#6?fSN#5uuK&^XUor4s3I8v<{zun; z#lU|h{J-q_|BbFU|N7uDw|{*La)14tiA|F!e*F|ea+J|=0RWJ({~S<&^h|=+K?I1b zq9nor940ygRv@C=r&loqSqV`M?Vbi5PYsO)!k5$04H1n@!o3bcv*=)^TDrc_u^^&^ z#wflLLF-rm@@)cb7h@p-MI>+I3*);j_Nx~dgZ3`6WR7Pr|t5@UL9b+t?j zMvUXL0!#9RhrsbnU|`_%^mO*xTF70ZJwElF3s)b za#mW!6c~{v1S5H<-gsMzT{4^p&JOjgqtG=*Z0Mc?qpFM^3F>057c z)$2o2D|N4x{9gc+g$`$dU5@R+YDr4{zOG zmy}d%(N~xv&*2SZKb^4_@9Aj?=*7u;9uRhgKzQSabVR&5gg8LD3R8NGd!Lx`+};u( ztE)IFC``HRjgyj*Sr8PrKb_VeQgVM%o0(3LqwZlFC)nbX<;qejIX!|8xNGKx%9*Ie zy-=~WWdhLU2+bTq0#r;)zF*tCH?bVsimNRD?Kf20BtJ4t5at9Pq!)q37LlRYfDs9n z>oa)S+p_?iX9HE~aHs%e@jPz@o@QKfzTC~RwybfL&QuQTyX^hcDVRE-nB=XCd4_rB zcRxv*iQZg^5dDd~nNSXF?FRfgdx2ih3fP;85p|rR%nP zr1|Ir9sYG~dZ`uUf*Rd_WOA9Tj#pea+hs(0hF4@%D@ph0Fy$=7Q(;^xo0%H9jAfEqT0t#F@l& zH9Kuj5dY3!c!^&&^)uOWsGA3JaY2bv8dD-&AYnx=DoJo>j1i8kef?5y!b;n5A!RpK zFqBN&mI`9)4-GA8v|YD1eaGl>9cQ;(D5T~HB1eRM>g|PP%yd_=TVqLD z39*#`8(oBa%)a8piO2870MMNEX@nj54wKF=WOQ`&TZz8eGW8aik>O#Y9AV3>bsdq) z%F6jQ#Jq%rL%^V&rx|yKCyB^HXLDl0C&n7ge zFc%*bNXJ`0KC<_h_e*K@hlGXO0Esf11-(LzR7xICH+vGFp(CPVGC9nnmLFY9+A}F+ zKJlF09?vUY+(;oRdS8xZR`LNRXBJA-xRxBd@i4?n+bV1p!9JKs$w^=lGF5aFPV%^5AfkBJ&#%SP zGb<}=oX$L^W(v&2z!Db`l{(3B)8w|=I-q>iPa@>jI6#o0KqHjHih;<4H zaZ{>GIzZ0?q1#FG>IWorD-1npCm%>xI zxSHBW0sV{L*-lPK7ldA(+-(-5B&1GPo0|M{b7Bw@y1_BZG8n2?MM+o{#xWOJznryZ z#~);EyK7A2t*ud{f?c5&IhV%tn%36qhZcl4LT`!=l0!p#SuRL2PwsulBvqA(H3S6( z&tcvpQZ5NG%Xg+WHlDP(QHs==n$*_L_YMr$G%Qug%PZ#y6--&%nZ6Nmb>)HzGR!U~ zh1$BnFDdeMdFc)eM%dooF38Wv5OHz2+jK(0u=1^F4N3Ie;=HJzWZM(Fw-v zB}D?!%EsYCdg9dd+9ov)i(c#O%*^HR9OE;H)LAltjeym#p6!@NM_Bpga{w!#399B( zr@uy>-}3_~jz$#m`MD>nqa%IBa^ff~>?cb6XFiwfBlkU8YI>YELh<`-%v6?dv#|y%I{@MJhDgpi$7JK<{$UQwh0)hq@ zok~!(hdupL^YD)m7}Gy~=&0)GsHx?D;NZgVZMbRVK-|utTmx^hkWu=@gXB4^ATEq zK0@ekE^XG9n8Nz3U#aC1e7#UqL?uU)f6(7AHOjaAUPCK_F5Oq-@eZk9yG%n4+!B!# zN&n;Z`E()=o6X4*20-kG{YEr}Ar^u`L4ZhkUO>f0834e*fK}1clMk2`@>4*R62ZWL zhK;Q->Z=Jc#0ou;7+iL@#}+$X>#&*2d5itpGP76P26d4FM;t`rHlr&fW?8Cq-f&`@6%Z`*9E8#K&7dO!yXcYzkOJ9zQA( z0G&xc3dPR!?uZBl6I0BDcvb&N<;jf8zJv)ozm1ny>&MmoiJaSykF{w-Lql3-P*6~T zP`{b&PCA`+RE#K~enmm;o-R8WBzsy;NYpLsm{($x5ZeJ_pv%k6w5oH2jbb0;w?9~yWOJ~D7*O!>-QT$uWv~J NSxF^{uVO|){|CUMZC?NY literal 0 HcmV?d00001 diff --git a/src/main/resources/resource/Ollama.png b/src/main/resources/resource/Ollama.png new file mode 100644 index 0000000000000000000000000000000000000000..338ac231c43a79f24ffc8cc1c5004c9958238ba5 GIT binary patch literal 13745 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 C6HUqh delta 1183 zcmV;Q1YrBOEQJz~BmwD>B_DrXvfCgGh2L34mw+UM#Bwm0neL#=?*WtKCjFoGsS`6X zM92qOpCI>!^ULQEKH;L0IV3gDCFh7Ml~lN5;PJZ3x>HQ+zAo8Q`2J)MvImi2l82|& z?O$Qn#{;)D=yWYPg`4p14?tVoe^OK~ar*$vzi(+fc0i8)rYoKPKoY$z$g- zyk`GCdPzRlZI_o^=9^Id{sifZUGB^9OF7Ub#Lp8ZH>q!vJVy61&g<=J%E)Nm6}6I0 zHyJn{NV%-XGMeF|7~6kp1cQlyK6W#co5z|8kpXsuh;dW{?_cY-Su#0Zc4u!WyZ{z(gCw3#A`zVlQk zV4ii2FebTqhZlg**xYm__|`W5_@RDgsV2D1G&^3f#^aJWlpcSzC5yAbPtjjnh=TPk zfD~bEMKB~F5HpEVrfAGYggiO`RYZ<`!UqVDDt83QNd)JSvGZIT&loLVR?a-lIGX?h z8wmT!L8t;&N{afi2axl6|}(Q~)% zz4UrgIB`;(>C`iycKTV)I#6pvMjATuu;HVOdZ$Kv+rG9xphlY-FH+Ob?$lrnW;Y3% z$B9m6AjXM6+$I4OG*4#HDJ7odCbL)=n?e~u>SPl-O=5pQm`-9HbZ7S<_gmb8`fqXL zUy%zF-M=6gfbLsv-=Ws$+1wVfYZuO(M#1jG{OL8Y*ZHGX|F(Y%y@lRFZ=tu)Tj(wH z9|#Tn@qm9~!&iwao<{*f^XdQq0flKpLr_UWLm+T+Z)Rz1WdHzpoPCiyNW)MRhX1CD z52_A!5OIIVP@ODD6>-!m6rn<>6U+>igB|L5Lw0ij-In$ZLMN=c5B95qEq|pB%zTnwYiW@qU|<`#xNd369&ot>3_TgL zDZ5gTmXOZ_?`QN)Szz!M=w0*X);!1Q1CXX!C2xR(Ltv~x+3P; $scope.maxRecords) { + $scope.utterances.shift() + } + $scope.$apply() + break + case "onRequest": + request = { username: "friend", text: data } + $scope.utterances.push(request) + // remove the beginning if we are at maxRecords + if ($scope.utterances.length > $scope.maxRecords) { + $scope.utterances.shift() + } + $scope.$apply() + break + case "onEpoch": + $scope.onEpoch = data + $scope.$apply() + break + default: + console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) + break + } + } + + $scope.onTypeChange = function () { + console.log("Type changed to:", $scope.type) + if ( + !$scope.service.config.url || + $scope.service.config.url == "" || + $scope.service.config.url == "https://api.openai.com/v1/chat/completions" || + $scope.service.config.url == "http://localhost:11434/v1/chat/completions" + ) { + if ($scope.type == "OpenAI") { + $scope.service.config.url = "https://api.openai.com/v1/chat/completions" + $scope.modelSelect = $scope.openaiModels + if ($scope.service.config.password == "Ollama") { + $scope.service.config.password = null + } + } else { + $scope.service.config.url = "http://localhost:11434/v1/chat/completions" + $scope.modelSelect = $scope.ollamaModels + if (!$scope.service.config.password || $scope.service.config.password == "") { + $scope.service.config.password = "Ollama" + } + } + } + } + + $scope.onSystemChange = function () { + $scope.service.config.system = $scope.systems[$scope.systemIndex] + } + + $scope.onModelChange = function () { + console.log("Model changed to:", $scope.selectedModel) + $scope.service.config.model = $scope.selectedModel + } + + $scope.saveValues = function () { + msg.send("apply", $scope.service.config) + msg.send("save") + } + + $scope.getResponse = function () { + $scope.saveValues() + msg.send("getResponse", $scope.text) + } + + msg.subscribe("publishRequest") + msg.subscribe("publishUtterance") + msg.subscribe(this) + }, +]) diff --git a/src/main/resources/resource/WebGui/app/service/views/HttpClientGui.html b/src/main/resources/resource/WebGui/app/service/views/HttpClientGui.html index 9388f2ec6b..e2bbcb4883 100644 --- a/src/main/resources/resource/WebGui/app/service/views/HttpClientGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/HttpClientGui.html @@ -1,10 +1,14 @@
- + + + - - + + + +
urlActionURLData
{{url}}
{{d.verb}}{{d.url}}{{d.body}}
diff --git a/src/main/resources/resource/WebGui/app/service/views/LLMGui.html b/src/main/resources/resource/WebGui/app/service/views/LLMGui.html new file mode 100644 index 0000000000..df2287c2a8 --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/views/LLMGui.html @@ -0,0 +1,155 @@ +
+ How to obtain an OpenAi api key + show + hide +
+
+ + + + Official Documentation : OpenAi Api Keys +
+
+
+ + +
+
+   +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
url +
+ +
+
+ +
+
model +
+ + +
+ +
temperatureTemperature is how creative the responses should be.
historyThe number of responses saved for context.
system prompt The system prompt gives the LLM context for the query.
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
wake wordWill wake the bot if sleeping.
sleep wordWill put the bot in sleep mode, non responsive, until the wake word is heard
api keyPassword for OpenAI or just Ollama if Ollama is used
+ + Save current configuration
+
+ +
+
+
+ + + + + + + + +
+ {{e.username}} + + {{e.channel}} + + [{{e.text}}] +
+
+
+
\ No newline at end of file From 679b9c3d7393d5af333223bd8fc05f8f66ad677d Mon Sep 17 00:00:00 2001 From: supertick Date: Sun, 19 May 2024 09:01:47 -0700 Subject: [PATCH 2/3] a little cleanup --- TODO.md | 2 ++ .../resource/WebGui/app/service/js/LLMGui.js | 5 ++++ .../WebGui/app/service/views/LLMGui.html | 28 +++++++++---------- 3 files changed, 21 insertions(+), 14 deletions(-) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000000..32bc2f9a2e --- /dev/null +++ b/TODO.md @@ -0,0 +1,2 @@ +- must make a $scope var onChange llm config "Must save to persist changes" +- https://platform.openai.com/docs/guides/text-to-speech?lang=curl diff --git a/src/main/resources/resource/WebGui/app/service/js/LLMGui.js b/src/main/resources/resource/WebGui/app/service/js/LLMGui.js index c5fa0422c0..c652c70fd1 100644 --- a/src/main/resources/resource/WebGui/app/service/js/LLMGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/LLMGui.js @@ -12,6 +12,7 @@ angular.module("mrlapp.service.LLMGui", []).controller("LLMGuiCtrl", [ $scope.openaiModels = ["gpt-4o", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo"] $scope.ollamaModels = ["llama3", "llama2", "phi3", "mistral", "jemma", "mixtral", "llava"] var first = true + $scope.dirty = false $scope.systems = [ "You are InMoov a humanoid robot assistant. Your answers are short and polite. The current date is {{Date}}. The current time is {{Time}}. You have a PIR sensor which determines if someone else is present, it is currently {{pirActive}}", @@ -37,6 +38,7 @@ angular.module("mrlapp.service.LLMGui", []).controller("LLMGuiCtrl", [ if (!service.config.url || service.config.url == "") { service.config.url = "http://localhost:11434/v1/chat/completions" + $scope.dirty = true } } @@ -80,6 +82,7 @@ angular.module("mrlapp.service.LLMGui", []).controller("LLMGuiCtrl", [ $scope.onTypeChange = function () { console.log("Type changed to:", $scope.type) + $scope.dirty = true if ( !$scope.service.config.url || $scope.service.config.url == "" || @@ -103,10 +106,12 @@ angular.module("mrlapp.service.LLMGui", []).controller("LLMGuiCtrl", [ } $scope.onSystemChange = function () { + $scope.dirty = true $scope.service.config.system = $scope.systems[$scope.systemIndex] } $scope.onModelChange = function () { + $scope.dirty = true console.log("Model changed to:", $scope.selectedModel) $scope.service.config.model = $scope.selectedModel } diff --git a/src/main/resources/resource/WebGui/app/service/views/LLMGui.html b/src/main/resources/resource/WebGui/app/service/views/LLMGui.html index df2287c2a8..95dbd85e68 100644 --- a/src/main/resources/resource/WebGui/app/service/views/LLMGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/LLMGui.html @@ -25,7 +25,7 @@
- + - + - + - + - + - + - + - + - + - + - + - + - - - + +
urlURL
@@ -43,7 +43,7 @@
modelModel
@@ -57,17 +57,17 @@
temperatureTemperature Temperature is how creative the responses should be.
historyHistory The number of responses saved for context.
system promptSystem Prompt The system prompt gives the LLM context for the query.
@@ -100,28 +100,28 @@
wake wordWake Word Will wake the bot if sleeping.Will wake the bot if it is in sleep mode.
sleep wordSleep Word Will put the bot in sleep mode, non responsive, until the wake word is heardWill put the bot into sleep mode, making it non-responsive until the wake word is heard.
api keyAPI Key Password for OpenAI or just Ollama if Ollama is usedPassword for OpenAI or just Ollama if Ollama is used.
- Save current configuration +
You must save configuration to make it persistent.
+
From ec1342fc1df36a154c27c0c8bbcc4632e9a4780c Mon Sep 17 00:00:00 2001 From: supertick Date: Tue, 21 May 2024 14:24:54 -0700 Subject: [PATCH 3/3] max history fix --- src/main/java/org/myrobotlab/service/LLM.java | 116 ++++++++---------- .../myrobotlab/service/config/LLMConfig.java | 5 +- .../WebGui/app/service/views/LLMGui.html | 4 +- 3 files changed, 56 insertions(+), 69 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/LLM.java b/src/main/java/org/myrobotlab/service/LLM.java index 6487711668..d6cc21b57a 100644 --- a/src/main/java/org/myrobotlab/service/LLM.java +++ b/src/main/java/org/myrobotlab/service/LLM.java @@ -93,77 +93,64 @@ public void clearInputs() { public String createChatCompletionPayload(String model, String systemContent, String userContent, int n, float temperature, int maxTokens) { try { - // Create the map to hold the request parameters - LinkedHashMap requestPayload = new LinkedHashMap<>(); - - // Add model to the map - requestPayload.put("model", model); - - // Create the messages array - LinkedHashMap systemMessage = new LinkedHashMap<>(); - systemMessage.put("role", "system"); - - // Get the current date - LocalDate currentDate = LocalDate.now(); - // Get the current time - LocalTime currentTime = LocalTime.now(); - // Get the current date and time - LocalDateTime currentDateTime = LocalDateTime.now(); - - // Format the date as a string - DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - String dateString = currentDate.format(dateFormatter); - - // Format the time as a string in 12-hour format with AM/PM - DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("h:mm a"); - String timeString = currentTime.format(timeFormatter); - - // Format the full date with day of the week - DateTimeFormatter fullDateFormatter = DateTimeFormatter.ofPattern("EEEE MMMM d'th' yyyy h:mm a"); - String fullDateString = currentDateTime.format(fullDateFormatter); - - inputs.put("Date", dateString); - inputs.put("Time", timeString); - inputs.put("DateTime", fullDateString); - - for (String inputKey : inputs.keySet()) { - Object objectValue = inputs.get(inputKey); - if (objectValue != null) { - systemContent = systemContent.replace(String.format("{{%s}}", inputKey), objectValue.toString()); + // Create the map to hold the request parameters + LinkedHashMap requestPayload = new LinkedHashMap<>(); + requestPayload.put("model", model); + + // Create and format date and time strings + LocalDateTime currentDateTime = LocalDateTime.now(); + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("h:mm a"); + DateTimeFormatter fullDateFormatter = DateTimeFormatter.ofPattern("EEEE MMMM d'th' yyyy h:mm a"); + + inputs.put("Date", currentDateTime.format(dateFormatter)); + inputs.put("Time", currentDateTime.format(timeFormatter)); + inputs.put("DateTime", currentDateTime.format(fullDateFormatter)); + + // Replace placeholders in system content + for (Map.Entry entry : inputs.entrySet()) { + if (entry.getValue() != null) { + systemContent = systemContent.replace(String.format("{{%s}}", entry.getKey()), entry.getValue().toString()); + } } - } - systemMessage.put("content", systemContent); - if (config.maxHistory == 0) { - // history is disabled - userMessages.clear(); - } else if (userMessages.size() > config.maxHistory) { - userMessages.remove(0); - } - LinkedHashMap userMessage = new LinkedHashMap<>(); - userMessage.put("role", "user"); - userMessage.put("content", userContent); - userMessages.add(userMessage); - - List> allMessages = new ArrayList<>(); - allMessages.add(systemMessage); - allMessages.addAll(userMessages); + // Create system message + LinkedHashMap systemMessage = new LinkedHashMap<>(); + systemMessage.put("role", "system"); + systemMessage.put("content", systemContent); + + // Handle message history + LinkedHashMap userMessage = new LinkedHashMap<>(); + userMessage.put("role", "user"); + userMessage.put("content", userContent); + userMessages.add(userMessage); + + if (config.maxHistory > 0) { + while (userMessages.size() > config.maxHistory) { + userMessages.remove(0); + } + } else { + userMessages.clear(); + } - // make history - requestPayload.put("messages", allMessages); + // Combine messages + List> allMessages = new ArrayList<>(); + allMessages.add(systemMessage); + allMessages.addAll(userMessages); + requestPayload.put("messages", allMessages); - // Add n, temperature, and max_tokens to the map - requestPayload.put("n", n); - requestPayload.put("temperature", temperature); - requestPayload.put("max_tokens", maxTokens); + // Add other parameters + requestPayload.put("n", n); + requestPayload.put("temperature", temperature); + requestPayload.put("max_tokens", maxTokens); - return CodecUtils.toJson(requestPayload); + return CodecUtils.toJson(requestPayload); } catch (Exception e) { - error(e); - return null; + error(e); + return null; } - } +} public LinkedHashMap createFunctionDefinition(String name, String description, LinkedHashMap parameters) { LinkedHashMap functionDefinition = new LinkedHashMap<>(); @@ -211,6 +198,9 @@ public Response getResponse(String text) { log.info("curl {} -d '{}'", config.url, json); String msg = http.postJson(config.password, config.url, json); + log.error("url: {}", config.url); + log.error("json: {}", json); + System.out.print(json); Map payload = CodecUtils.fromJson(msg, new StaticType<>() { }); diff --git a/src/main/java/org/myrobotlab/service/config/LLMConfig.java b/src/main/java/org/myrobotlab/service/config/LLMConfig.java index 1e605f286c..f8f08c265e 100644 --- a/src/main/java/org/myrobotlab/service/config/LLMConfig.java +++ b/src/main/java/org/myrobotlab/service/config/LLMConfig.java @@ -14,10 +14,7 @@ public class LLMConfig extends ServiceConfig { public int maxTokens = 256; public float temperature = 0.7f; - /** - * number of history items to keep when re-submitting - */ - public int history = 5; + // public String url = "https://api.openai.com/v1/chat/completions"; // http://localhost:11434/v1/chat/completions public String url = null; diff --git a/src/main/resources/resource/WebGui/app/service/views/LLMGui.html b/src/main/resources/resource/WebGui/app/service/views/LLMGui.html index 95dbd85e68..8ebd298055 100644 --- a/src/main/resources/resource/WebGui/app/service/views/LLMGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/LLMGui.html @@ -62,8 +62,8 @@
Temperature is how creative the responses should be.
HistoryMax History The number of responses saved for context.