diff options
author | Grail Finder <wohilas@gmail.com> | 2025-02-08 18:28:47 +0300 |
---|---|---|
committer | Grail Finder <wohilas@gmail.com> | 2025-02-08 18:28:47 +0300 |
commit | c85766139371bb4324826fa8716b3478eea898c1 (patch) | |
tree | 2b58fcff3c79751a4d7e5034e035f6f270cb8bc8 | |
parent | 884004a855980444319769d9b10f9cf6e3ba33cd (diff) |
Feat: add tool reminder bind
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | bot.go | 39 | ||||
-rw-r--r-- | llm.go | 10 | ||||
-rw-r--r-- | main.go | 2 | ||||
-rw-r--r-- | models/models.go | 23 | ||||
-rw-r--r-- | tools.go | 6 | ||||
-rw-r--r-- | tui.go | 10 |
7 files changed, 33 insertions, 61 deletions
@@ -42,6 +42,7 @@ - lets say we have two (or more) agents with the same name across multiple chats. These agents go and ask db for topics they memorised. Now they can access topics that aren't meant for them. (so memory should have an option: shareable; that indicates if that memory can be shared across chats); - server mode: no tui but api calls with the func calling, rag, other middleware; - boolean flag to use/not use tools. I see it as a msg from a tool to an llm "Hey, it might be good idea to use me!"; +- multirole support? ### FIX: - bot responding (or hanging) blocks everything; + @@ -70,3 +71,6 @@ - add retry on failed call (and EOF); - model info shold be an event and show disconnect status when fails; - message editing broke ( runtime error: index out of range [-1]); out of index; +- sql memory upsert fails with msg="failed to insert memory" query="INSERT INTO memories (agent, topic, mind) VALUES (:agent, :topic, :mind) RETURNING *;" error="constraint failed: UNIQUE constraint failed: memories.agent, memories.topic (1555); +- F5 broke formatting and messages somehow; +- F4 after edit mode no colors; @@ -13,7 +13,6 @@ import ( "net/http" "os" "path" - "regexp" "strings" "time" @@ -44,6 +43,7 @@ var ( "min_p": 0.05, "n_predict": -1.0, } + toolUseText = "consider making a tool call." ) func fetchModelName() *models.LLMModels { @@ -290,35 +290,6 @@ func removeThinking(chatBody *models.ChatBody) { chatBody.Messages = msgs } -// what is the purpose of this func? -// is there a case where using text from widget is more appropriate than chatbody.messages? -func textToMsgs(text string) []models.RoleMsg { - lines := strings.Split(text, "\n") - roleRE := regexp.MustCompile(`^\(\d+\) <.*>:`) - resp := []models.RoleMsg{} - oldrole := "" - for _, line := range lines { - if roleRE.MatchString(line) { - // extract role - role := "" - // if role changes - if role != oldrole { - oldrole = role - // newmsg - msg := models.RoleMsg{ - Role: role, - } - resp = append(resp, msg) - } - resp[len(resp)-1].Content += "\n" + line - } - } - if len(resp) != 0 { - resp[0].Content = strings.TrimPrefix(resp[0].Content, "\n") - } - return resp -} - func applyCharCard(cc *models.CharCard) { cfg.AssistantRole = cc.Role history, err := loadAgentsLastChat(cfg.AssistantRole) @@ -355,14 +326,6 @@ func charToStart(agentName string) bool { return true } -func runModelNameTicker(n time.Duration) { - ticker := time.NewTicker(n) - for { - fetchModelName() - <-ticker.C - } -} - func init() { cfg = config.LoadConfigOrDefault("config.toml") defaultStarter = []models.RoleMsg{ @@ -51,8 +51,11 @@ func (lcp LlamaCPPeer) FormMsg(msg, role string) (io.Reader, error) { messages[i] = m.ToPrompt() } prompt := strings.Join(messages, "\n") + if cfg.ToolUse && msg != "" { + prompt += "\n" + cfg.ToolRole + ":\n" + toolSysMsg + } botMsgStart := "\n" + cfg.AssistantRole + ":\n" - payload := models.NewLCPReq(prompt+botMsgStart, role, defaultLCPProps) + payload := models.NewLCPReq(prompt+botMsgStart, cfg, defaultLCPProps) data, err := json.Marshal(payload) if err != nil { logger.Error("failed to form a msg", "error", err) @@ -106,6 +109,11 @@ func (op OpenAIer) FormMsg(msg, role string) (io.Reader, error) { ragMsg := models.RoleMsg{Role: cfg.ToolRole, Content: ragResp} chatBody.Messages = append(chatBody.Messages, ragMsg) } + if cfg.ToolUse { + toolMsg := models.RoleMsg{Role: cfg.ToolRole, + Content: toolSysMsg} + chatBody.Messages = append(chatBody.Messages, toolMsg) + } } data, err := json.Marshal(chatBody) if err != nil { @@ -12,7 +12,7 @@ var ( botRespMode = false editMode = false selectedIndex = int(-1) - indexLine = "F12 to show keys help; bot resp mode: %v; char: %s; chat: %s; RAGEnabled: %v; toolUseAdviced: %v; model: %s\nAPI_URL: %s" + indexLine = "F12 to show keys help | bot resp mode: %v (F6) | char: %s (ctrl+s) | chat: %s (F1) | RAGEnabled: %v (F11) | toolUseAdviced: %v (ctrl+k) | model: %s (ctrl+l)\nAPI_URL: %s (ctrl+v)" focusSwitcher = map[tview.Primitive]tview.Primitive{} ) diff --git a/models/models.go b/models/models.go index c760569..ceb98fd 100644 --- a/models/models.go +++ b/models/models.go @@ -58,19 +58,9 @@ type RoleMsg struct { func (m RoleMsg) ToText(i int, cfg *config.Config) string { icon := fmt.Sprintf("(%d)", i) - if !strings.HasPrefix(m.Content, cfg.UserRole+":") && !strings.HasPrefix(m.Content, cfg.AssistantRole+":") { - switch m.Role { - case "assistant": - icon = fmt.Sprintf("(%d) <%s>: ", i, cfg.AssistantRole) - case "user": - icon = fmt.Sprintf("(%d) <%s>: ", i, cfg.UserRole) - case "system": - icon = fmt.Sprintf("(%d) <system>: ", i) - case "tool": - icon = fmt.Sprintf("(%d) <%s>: ", i, cfg.ToolRole) - default: - icon = fmt.Sprintf("(%d) <%s>: ", i, m.Role) - } + // check if already has role annotation (/completion makes them) + if !strings.HasPrefix(m.Content, m.Role+":") { + icon = fmt.Sprintf("(%d) <%s>: ", i, m.Role) } textMsg := fmt.Sprintf("[-:-:b]%s[-:-:-]\n%s\n", icon, m.Content) return strings.ReplaceAll(textMsg, "\n\n", "\n") @@ -178,7 +168,7 @@ type LlamaCPPReq struct { // Samplers string `json:"samplers"` } -func NewLCPReq(prompt, role string, props map[string]float32) LlamaCPPReq { +func NewLCPReq(prompt string, cfg *config.Config, props map[string]float32) LlamaCPPReq { return LlamaCPPReq{ Stream: true, Prompt: prompt, @@ -188,7 +178,10 @@ func NewLCPReq(prompt, role string, props map[string]float32) LlamaCPPReq { DryMultiplier: props["dry_multiplier"], MinP: props["min_p"], NPredict: int32(props["n_predict"]), - Stop: []string{role + ":\n", "<|im_end|>"}, + Stop: []string{ + cfg.UserRole + ":\n", "<|im_end|>", + cfg.ToolRole + ":\n", + }, } } @@ -14,12 +14,8 @@ var ( starRE = regexp.MustCompile(`(\*.*?\*)`) thinkRE = regexp.MustCompile(`(<think>.*?</think>)`) codeBlockRE = regexp.MustCompile(`(?s)\x60{3}(?:.*?)\n(.*?)\n\s*\x60{3}\s*`) - // codeBlockRE = regexp.MustCompile("```\s*([\s\S]*?)```") - // codeBlockRE = regexp.MustCompile(`(\x60\x60\x60.*?\x60\x60\x60)`) basicSysMsg = `Large Language Model that helps user with any of his requests.` - toolSysMsg = `You're a helpful assistant. -# Tools -You can do functions call if needed. + toolSysMsg = `You can do functions call if needed. Your current tools: <tools> [ @@ -63,6 +63,7 @@ var ( [yellow]Ctrl+r[white]: menu of files that can be loaded in vector db (RAG) [yellow]Ctrl+t[white]: remove thinking (<think>) and tool messages from context (delete from chat) [yellow]Ctrl+l[white]: update connected model name (llamacpp) +[yellow]Ctrl+k[white]: switch tool use (recommend tool use to llm after user msg) Press Enter to go back ` @@ -218,12 +219,13 @@ func init() { flex = tview.NewFlex().SetDirection(tview.FlexRow). AddItem(textView, 0, 40, false). AddItem(textArea, 0, 10, true). - AddItem(position, 0, 1, false) + AddItem(position, 0, 2, false) editArea = tview.NewTextArea(). SetPlaceholder("Replace msg...") editArea.SetBorder(true).SetTitle("input") editArea.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyEscape && editMode { + defer colorText() editedMsg := editArea.GetText() if editedMsg == "" { if err := notifyUser("edit", "no edit provided"); err != nil { @@ -543,6 +545,12 @@ func init() { updateStatusLine() return nil } + if event.Key() == tcell.KeyCtrlK { + // add message from tools + cfg.ToolUse = !cfg.ToolUse + updateStatusLine() + return nil + } if event.Key() == tcell.KeyCtrlR && cfg.HFToken != "" { // rag load // menu of the text files from defined rag directory |