diff options
author | Grail Finder <wohilas@gmail.com> | 2024-11-21 20:16:47 +0300 |
---|---|---|
committer | Grail Finder <wohilas@gmail.com> | 2024-11-21 20:16:47 +0300 |
commit | cc84c037ece2a89424d490d9ee819f06cf4bb347 (patch) | |
tree | c2248cc666884bc475c32970a13d086b9aec159d | |
parent | c35af037203ac5c39a4f704d5343bc2b5cc56a0c (diff) |
Enha: match tool call with regexp; clear panics
-rw-r--r-- | README.md | 9 | ||||
-rw-r--r-- | bot.go | 49 | ||||
-rw-r--r-- | main.go | 21 | ||||
-rw-r--r-- | storage/storage.go | 4 | ||||
-rw-r--r-- | tools.go | 24 |
5 files changed, 55 insertions, 52 deletions
@@ -12,10 +12,13 @@ - basic tools: memorize and recall; - stop stream from the bot; + - sqlitedb instead of chatfiles; + -- define tools and sys prompt for them to be used; +- define tools and sys prompt for them to be used; + - sqlite for the bot memory; - fullscreen textarea option (bothersome to implement); +- add system prompt without tools (for mistral); - option to switch between predefined sys prompts; +- consider adding use /completion of llamacpp, since openai endpoint clearly has template|format issues; +- change temp, min-p and other params from tui; ### FIX: - bot responding (or haninging) blocks everything; + @@ -24,6 +27,6 @@ - Tab is needed to copy paste text into textarea box, use shift+tab to switch focus; (changed tp pgup) + - delete last msg: can have unexpected behavior (deletes what appears to be two messages if last bot msg was not generated (should only delete icon in that case)); - empty input to continue bot msg gens new msg index and bot icon; -- sometimes bots put additional info around the tool call, have a regexp to match tool call; -- remove all panics from code; +- sometimes bots put additional info around the tool call, have a regexp to match tool call; + +- remove all panics from code; + - new chat is not saved in db; @@ -24,14 +24,13 @@ var httpClient = http.Client{ var ( logger *slog.Logger APIURL = "http://localhost:8080/v1/chat/completions" - DB = map[string]map[string]any{} userRole = "user" assistantRole = "assistant" toolRole = "tool" assistantIcon = "<🤖>: " userIcon = "<user>: " - historyDir = "./history/" - // TODO: pass as an cli arg + // TODO: pass as an cli arg or have config + logFileName = "log.txt" showSystemMsgs bool chunkLimit = 1000 activeChatName string @@ -44,21 +43,12 @@ var ( {Role: "system", Content: systemMsg}, {Role: assistantRole, Content: defaultFirstMsg}, } - interruptResp = false + defaultStarterBytes, _ = json.Marshal(chatBody.Messages) + interruptResp = false ) // ==== -func getUserInput(userPrompt string) string { - fmt.Printf(userPrompt) - reader := bufio.NewReader(os.Stdin) - line, err := reader.ReadString('\n') - if err != nil { - panic(err) // think about it - } - return line -} - func formMsg(chatBody *models.ChatBody, newMsg, role string) io.Reader { if newMsg != "" { // otherwise let the bot continue newMsg := models.MessagesStory{Role: role, Content: newMsg} @@ -66,7 +56,8 @@ func formMsg(chatBody *models.ChatBody, newMsg, role string) io.Reader { } data, err := json.Marshal(chatBody) if err != nil { - panic(err) + logger.Error("failed to form a msg", "error", err) + return nil } return bytes.NewReader(data) } @@ -130,6 +121,9 @@ func sendMsgToLLM(body io.Reader) (any, error) { func chatRound(userMsg, role string, tv *tview.TextView) { botRespMode = true reader := formMsg(chatBody, userMsg, role) + if reader == nil { + return // any notification in that case? + } go sendMsgToLLM(reader) fmt.Fprintf(tv, fmt.Sprintf("(%d) ", len(chatBody.Messages))) fmt.Fprintf(tv, assistantIcon) @@ -161,18 +155,22 @@ out: } func findCall(msg string, tv *tview.TextView) { - prefix := "__tool_call__\n" - suffix := "\n__tool_call__" + // prefix := "__tool_call__\n" + // suffix := "\n__tool_call__" + // if !strings.HasPrefix(msg, prefix) || + // !strings.HasSuffix(msg, suffix) { + // return + // } + // jsStr := strings.TrimSuffix(strings.TrimPrefix(msg, prefix), suffix) fc := models.FuncCall{} - if !strings.HasPrefix(msg, prefix) || - !strings.HasSuffix(msg, suffix) { + jsStr := toolCallRE.FindString(msg) + if jsStr == "" { + // tool call not found return } - jsStr := strings.TrimSuffix(strings.TrimPrefix(msg, prefix), suffix) if err := json.Unmarshal([]byte(jsStr), &fc); err != nil { logger.Error("failed to unmarshal tool call", "error", err) return - // panic(err) } // call a func f, ok := fnMap[fc.Name] @@ -231,13 +229,10 @@ func textSliceToChat(chat []string) []models.MessagesStory { } func init() { - file, err := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + file, err := os.OpenFile(logFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - panic(err) - } - // create dir if does not exist - if err := os.MkdirAll(historyDir, os.ModePerm); err != nil { - panic(err) + logger.Error("failed to open log file", "error", err, "filename", logFileName) + return } logger = slog.New(slog.NewTextHandler(file, nil)) store = storage.NewProviderSQL("test.db", logger) @@ -2,7 +2,6 @@ package main import ( "elefant/models" - "encoding/json" "fmt" "strconv" "time" @@ -63,7 +62,7 @@ func main() { chatOpts := []string{"cancel", "new"} fList, err := loadHistoryChats() if err != nil { - panic(err) + logger.Error("failed to load chat history", "error", err) } chatOpts = append(chatOpts, fList...) chatActModal := tview.NewModal(). @@ -74,15 +73,10 @@ func main() { case "new": // set chat body chatBody.Messages = defaultStarter - // TODO: use predefined var since it is the same each time - msgsBytes, err := json.Marshal(chatBody.Messages) - if err != nil { - logger.Error(err.Error()) - } textView.SetText(chatToText(showSystemMsgs)) newChat := &models.Chat{ Name: fmt.Sprintf("%v_%v", "new", time.Now().Unix()), - Msgs: string(msgsBytes), + Msgs: string(defaultStarterBytes), } // activeChatName = path.Join(historyDir, fmt.Sprintf("%d_chat.json", time.Now().Unix())) activeChatName = newChat.Name @@ -166,10 +160,10 @@ func main() { textView.ScrollToEnd() app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyF1 { - // fList, err := listHistoryFiles(historyDir) fList, err := loadHistoryChats() if err != nil { - panic(err) + logger.Error("failed to load chat history", "error", err) + return nil } chatOpts = append(chatOpts, fList...) pages.AddPage("history", chatActModal, true, true) @@ -220,6 +214,10 @@ func main() { pages.AddPage("getIndex", indexPickWindow, true, true) return nil } + if event.Key() == tcell.KeyCtrlE { + textArea.SetText("pressed ctrl+e", true) + return nil + } // cannot send msg in editMode or botRespMode if event.Key() == tcell.KeyEscape && !editMode && !botRespMode { fromRow, fromColumn, _, _ := textArea.GetCursor() @@ -251,6 +249,7 @@ func main() { pages.AddPage("main", flex, true, true) if err := app.SetRoot(pages, true).EnableMouse(true).Run(); err != nil { - panic(err) + logger.Error("failed to start tview app", "error", err) + return } } diff --git a/storage/storage.go b/storage/storage.go index 7d0d941..08cd81a 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -69,9 +69,9 @@ func (p ProviderSQL) RemoveChat(id uint32) error { func NewProviderSQL(dbPath string, logger *slog.Logger) FullRepo { db, err := sqlx.Open("sqlite", dbPath) if err != nil { - panic(err) + logger.Error("failed to open db connection", "error", err) + return nil } - // get SQLite version p := ProviderSQL{db: db, logger: logger} p.Migrate() return p @@ -3,6 +3,7 @@ package main import ( "elefant/models" "encoding/json" + "regexp" "time" ) @@ -29,7 +30,8 @@ var ( // When making function call avoid typing anything else. 'tool' user will respond with the results of the call. // After that you are free to respond to the user. // ` - systemMsg = `You're a helpful assistant. + toolCallRE = regexp.MustCompile(`__tool_call__\s*([\s\S]*?)__tool_call__`) + systemMsg = `You're a helpful assistant. # Tools You can do functions call if needed. Your current tools: @@ -75,8 +77,8 @@ also: */ func memorise(args ...string) []byte { agent := assistantRole - if len(args) < 1 { - // TODO: log + if len(args) < 2 { + logger.Warn("not enough args to call memorise tool") return nil } memory := &models.Memory{ @@ -92,12 +94,13 @@ func memorise(args ...string) []byte { func recall(args ...string) []byte { agent := assistantRole if len(args) < 1 { - // TODO: log + logger.Warn("not enough args to call recall tool") return nil } mind, err := store.Recall(agent, args[0]) if err != nil { - panic(err) + logger.Error("failed to use tool", "error", err, "args", args) + return nil } return []byte(mind) } @@ -106,11 +109,13 @@ func recallTopics(args ...string) []byte { agent := assistantRole topics, err := store.RecallTopics(agent) if err != nil { - panic(err) + logger.Error("failed to use tool", "error", err, "args", args) + return nil } data, err := json.Marshal(topics) if err != nil { - panic(err) + logger.Error("failed to use tool", "error", err, "args", args) + return nil } return data } @@ -118,7 +123,7 @@ func recallTopics(args ...string) []byte { func fullMemoryLoad() {} // predifine funcs -func getUserDetails(id ...string) []byte { +func getUserDetails(args ...string) []byte { // db query // return DB[id[0]] m := map[string]any{ @@ -129,7 +134,8 @@ func getUserDetails(id ...string) []byte { } data, err := json.Marshal(m) if err != nil { - panic(err) + logger.Error("failed to use tool", "error", err, "args", args) + return nil } return data } |