Feat: add tool reminder bind

This commit is contained in:
Grail Finder
2025-02-08 18:28:47 +03:00
parent 884004a855
commit c857661393
7 changed files with 33 additions and 61 deletions

View File

@@ -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;

39
bot.go
View File

@@ -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{

10
llm.go
View File

@@ -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 {

View File

@@ -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{}
)

View File

@@ -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",
},
}
}

View File

@@ -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>
[

10
tui.go
View File

@@ -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