From 86f798614004fe9553b41845f4c01337aa53677c Mon Sep 17 00:00:00 2001 From: Simone Vellei Date: Wed, 28 Feb 2024 21:40:37 +0100 Subject: [PATCH 1/5] Chore Give an indentity to the assistant (#171) --- assistant/assistant.go | 55 +++++++++++++++++++++++++++----------- assistant/prompt.go | 12 ++++++--- examples/assistant/main.go | 11 ++++++-- llm/cohere/formatter.go | 21 --------------- 4 files changed, 58 insertions(+), 41 deletions(-) diff --git a/assistant/assistant.go b/assistant/assistant.go index 0dd0e976..84639324 100644 --- a/assistant/assistant.go +++ b/assistant/assistant.go @@ -8,10 +8,19 @@ import ( "github.com/henomis/lingoose/types" ) +type Parameters struct { + AssistantName string + AssistantIdentity string + AssistantScope string + CompanyName string + CompanyDescription string +} + type Assistant struct { - llm LLM - rag RAG - thread *thread.Thread + llm LLM + rag RAG + thread *thread.Thread + parameters Parameters } type LLM interface { @@ -26,6 +35,13 @@ func New(llm LLM) *Assistant { assistant := &Assistant{ llm: llm, thread: thread.New(), + parameters: Parameters{ + AssistantName: defaultAssistantName, + AssistantIdentity: defaultAssistantIdentity, + AssistantScope: defaultAssistantScope, + CompanyName: defaultCompanyName, + CompanyDescription: defaultCompanyDescription, + }, } return assistant @@ -41,6 +57,11 @@ func (a *Assistant) WithRAG(rag RAG) *Assistant { return a } +func (a *Assistant) WithParameters(parameters Parameters) *Assistant { + a.parameters = parameters + return a +} + func (a *Assistant) Run(ctx context.Context) error { if a.thread == nil { return nil @@ -71,29 +92,33 @@ func (a *Assistant) generateRAGMessage(ctx context.Context) error { return nil } - query := "" - for _, content := range lastMessage.Contents { - if content.Type == thread.ContentTypeText { - query += content.Data.(string) + "\n" - } else { - continue - } - } + query := strings.Join(a.thread.UserQuery(), "\n") + a.thread.Messages = a.thread.Messages[:len(a.thread.Messages)-1] searchResults, err := a.rag.Retrieve(ctx, query) if err != nil { return err } - context := strings.Join(searchResults, "\n\n") - - a.thread.AddMessage(thread.NewUserMessage().AddContent( + a.thread.AddMessage(thread.NewSystemMessage().AddContent( + thread.NewTextContent( + systemRAGPrompt, + ).Format( + types.M{ + "assistantName": a.parameters.AssistantName, + "assistantIdentity": a.parameters.AssistantIdentity, + "assistantScope": a.parameters.AssistantScope, + "companyName": a.parameters.CompanyName, + "companyDescription": a.parameters.CompanyDescription, + }, + ), + )).AddMessage(thread.NewUserMessage().AddContent( thread.NewTextContent( baseRAGPrompt, ).Format( types.M{ "question": query, - "context": context, + "results": searchResults, }, ), )) diff --git a/assistant/prompt.go b/assistant/prompt.go index 71df12a7..18a4f8fe 100644 --- a/assistant/prompt.go +++ b/assistant/prompt.go @@ -2,7 +2,13 @@ package assistant const ( //nolint:lll - baseRAGPrompt = `You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise. - Question: {{.question}} - Context: {{.context}}` + baseRAGPrompt = "Use the following pieces of retrieved context to answer the question.\n\nQuestion: {{.question}}\nContext:\n{{range .results}}{{.}}\n\n{{end}}" + //nolint:lll + systemRAGPrompt = "You name is {{.assistantName}}, and you are {{.assistantIdentity}} {{if ne .companyName \"\" }}at {{.companyName}}{{end}}{{if ne .companyDescription \"\" }}, {{.companyDescription}}{{end}}. Your task is to assist humans {{.assistantScope}}." + + defaultAssistantName = "AI assistant" + defaultAssistantIdentity = "a helpful and polite assistant" + defaultAssistantScope = "with their questions" + defaultCompanyName = "" + defaultCompanyDescription = "" ) diff --git a/examples/assistant/main.go b/examples/assistant/main.go index 77d6c61e..c36c198e 100644 --- a/examples/assistant/main.go +++ b/examples/assistant/main.go @@ -17,12 +17,11 @@ import ( // download https://raw.githubusercontent.com/hwchase17/chat-your-data/master/state_of_the_union.txt func main() { - r := rag.NewFusion( + r := rag.New( index.New( jsondb.New().WithPersist("db.json"), openaiembedder.New(openaiembedder.AdaEmbeddingV2), ), - openai.New().WithTemperature(0), ).WithTopK(3) _, err := os.Stat("db.json") @@ -35,6 +34,14 @@ func main() { a := assistant.New( openai.New().WithTemperature(0), + ).WithParameters( + assistant.Parameters{ + AssistantName: "AI Pirate Assistant", + AssistantIdentity: "a pirate and helpful assistant", + AssistantScope: "with their questions replying as a pirate", + CompanyName: "Lingoose", + CompanyDescription: "a pirate company that provides AI assistants to help humans with their questions", + }, ).WithRAG(r).WithThread( thread.New().AddMessages( thread.NewUserMessage().AddContent( diff --git a/llm/cohere/formatter.go b/llm/cohere/formatter.go index 67b3360d..f39bd16f 100644 --- a/llm/cohere/formatter.go +++ b/llm/cohere/formatter.go @@ -58,24 +58,3 @@ func threadToChatMessages(t *thread.Thread) (string, []model.ChatMessage) { return message, history } - -// chatMessages := make([]message, len(t.Messages)) -// for i, m := range t.Messages { -// chatMessages[i] = message{ -// Role: threadRoleToOpenAIRole[m.Role], -// } - -// switch m.Role { -// case thread.RoleUser, thread.RoleSystem, thread.RoleAssistant: -// for _, content := range m.Contents { -// if content.Type == thread.ContentTypeText { -// chatMessages[i].Content += content.Data.(string) + "\n" -// } -// } -// case thread.RoleTool: -// continue -// } -// } - -// return chatMessages -// } From 02d056bfe04f7f5975605d268848b7e7af1c8fb7 Mon Sep 17 00:00:00 2001 From: Simone Vellei Date: Wed, 28 Feb 2024 21:57:48 +0100 Subject: [PATCH 2/5] Chore Add groq support (#172) --- examples/llm/groq/main.go | 31 +++++++++++++++++++++++ examples/llm/localai/main.go | 31 +++++++++++++++++++++++ examples/llm/openai/localai/main.go | 38 ----------------------------- 3 files changed, 62 insertions(+), 38 deletions(-) create mode 100644 examples/llm/groq/main.go create mode 100644 examples/llm/localai/main.go delete mode 100644 examples/llm/openai/localai/main.go diff --git a/examples/llm/groq/main.go b/examples/llm/groq/main.go new file mode 100644 index 00000000..2e4f7e4e --- /dev/null +++ b/examples/llm/groq/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "context" + "fmt" + + "github.com/henomis/lingoose/llm/openai" + "github.com/henomis/lingoose/thread" + goopenai "github.com/sashabaranov/go-openai" +) + +func main() { + customConfig := goopenai.DefaultConfig("YOUR_API_KEY") + customConfig.BaseURL = "https://api.groq.com/openai/v1" + customClient := goopenai.NewClientWithConfig(customConfig) + + openaillm := openai.New().WithClient(customClient).WithModel("mixtral-8x7b-32768") + + t := thread.New().AddMessage( + thread.NewUserMessage().AddContent( + thread.NewTextContent("What's the NATO purpose?"), + ), + ) + + err := openaillm.Generate(context.Background(), t) + if err != nil { + panic(err) + } + + fmt.Println(t) +} diff --git a/examples/llm/localai/main.go b/examples/llm/localai/main.go new file mode 100644 index 00000000..92c09831 --- /dev/null +++ b/examples/llm/localai/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "context" + "fmt" + + "github.com/henomis/lingoose/llm/openai" + "github.com/henomis/lingoose/thread" + goopenai "github.com/sashabaranov/go-openai" +) + +func main() { + customConfig := goopenai.DefaultConfig("YOUR_API_KEY") + customConfig.BaseURL = "http://localhost:8080" + customClient := goopenai.NewClientWithConfig(customConfig) + + openaillm := openai.New().WithClient(customClient).WithModel("ggml-gpt4all-j") + + t := thread.New().AddMessage( + thread.NewUserMessage().AddContent( + thread.NewTextContent("What's the NATO purpose?"), + ), + ) + + err := openaillm.Generate(context.Background(), t) + if err != nil { + panic(err) + } + + fmt.Println(t) +} diff --git a/examples/llm/openai/localai/main.go b/examples/llm/openai/localai/main.go deleted file mode 100644 index 322c0ae5..00000000 --- a/examples/llm/openai/localai/main.go +++ /dev/null @@ -1,38 +0,0 @@ -package main - -import ( - "context" - "fmt" - - goopenai "github.com/sashabaranov/go-openai" - - "github.com/henomis/lingoose/chat" - "github.com/henomis/lingoose/llm/openai" - "github.com/henomis/lingoose/prompt" -) - -func main() { - - chat := chat.New( - chat.PromptMessage{ - Type: chat.MessageTypeUser, - Prompt: prompt.New("How are you?"), - }, - ) - - customConfig := goopenai.DefaultConfig("") - customConfig.BaseURL = "http://localhost:8080" - customClient := goopenai.NewClientWithConfig(customConfig) - - llm := openai.NewChat().WithClient(customClient).WithModel("ggml-gpt4all-j") - - err := llm.ChatStream(context.Background(), func(output string) { - fmt.Printf("%s", output) - }, chat) - if err != nil { - panic(err) - } - - fmt.Println() - -} From 63d5e3a30af5c87eaab552f2084515e858e4e1bb Mon Sep 17 00:00:00 2001 From: Simone Vellei Date: Wed, 28 Feb 2024 23:26:07 +0100 Subject: [PATCH 3/5] Chore Add subdocument rag (#173) --- examples/rag/{ => simple}/main.go | 0 examples/rag/subdocument/main.go | 54 +++++++++++++ rag/rag.go | 5 -- rag/rag_fusion.go | 5 ++ rag/sub_document.go | 121 ++++++++++++++++++++++++++++++ 5 files changed, 180 insertions(+), 5 deletions(-) rename examples/rag/{ => simple}/main.go (100%) create mode 100644 examples/rag/subdocument/main.go create mode 100644 rag/sub_document.go diff --git a/examples/rag/main.go b/examples/rag/simple/main.go similarity index 100% rename from examples/rag/main.go rename to examples/rag/simple/main.go diff --git a/examples/rag/subdocument/main.go b/examples/rag/subdocument/main.go new file mode 100644 index 00000000..ad41628f --- /dev/null +++ b/examples/rag/subdocument/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/henomis/lingoose/assistant" + openaiembedder "github.com/henomis/lingoose/embedder/openai" + "github.com/henomis/lingoose/index" + "github.com/henomis/lingoose/index/vectordb/jsondb" + "github.com/henomis/lingoose/llm/openai" + "github.com/henomis/lingoose/rag" + "github.com/henomis/lingoose/thread" +) + +// download https://raw.githubusercontent.com/hwchase17/chat-your-data/master/state_of_the_union.txt + +func main() { + r := rag.NewSubDocumentRAG( + index.New( + jsondb.New().WithPersist("db.json"), + openaiembedder.New(openaiembedder.AdaEmbeddingV2), + ), + openai.New(), + ).WithTopK(3) + + _, err := os.Stat("db.json") + if os.IsNotExist(err) { + err = r.AddSources(context.Background(), "state_of_the_union.txt") + if err != nil { + panic(err) + } + } + + a := assistant.New( + openai.New().WithTemperature(0), + ).WithRAG(r).WithThread( + thread.New().AddMessages( + thread.NewUserMessage().AddContent( + thread.NewTextContent("what is the purpose of NATO?"), + ), + ), + ) + + err = a.Run(context.Background()) + if err != nil { + panic(err) + } + + fmt.Println("----") + fmt.Println(a.Thread()) + fmt.Println("----") +} diff --git a/rag/rag.go b/rag/rag.go index c8f3d529..a4dd8eda 100644 --- a/rag/rag.go +++ b/rag/rag.go @@ -35,11 +35,6 @@ type RAG struct { loaders map[*regexp.Regexp]Loader // this map a regexp as string to a loader } -type Fusion struct { - RAG - llm LLM -} - func New(index *index.Index) *RAG { rag := &RAG{ index: index, diff --git a/rag/rag_fusion.go b/rag/rag_fusion.go index e8363c82..2c55eace 100644 --- a/rag/rag_fusion.go +++ b/rag/rag_fusion.go @@ -17,6 +17,11 @@ var ragFusionPrompts = []string{ "OUTPUT (4 queries):", } +type Fusion struct { + RAG + llm LLM +} + func NewFusion(index *index.Index, llm LLM) *Fusion { return &Fusion{ RAG: *New(index), diff --git a/rag/sub_document.go b/rag/sub_document.go new file mode 100644 index 00000000..b4e0d0f4 --- /dev/null +++ b/rag/sub_document.go @@ -0,0 +1,121 @@ +package rag + +import ( + "context" + "regexp" + + "github.com/henomis/lingoose/document" + "github.com/henomis/lingoose/index" + "github.com/henomis/lingoose/textsplitter" + "github.com/henomis/lingoose/thread" + "github.com/henomis/lingoose/types" +) + +const ( + defaultSubDocumentRAGChunkSize = 8192 + defaultSubDocumentRAGChunkOverlap = 0 + defaultSubDocumentRAGChildChunkSize = 512 +) + +type SubDocumentRAG struct { + RAG + childChunkSize uint + llm LLM +} + +//nolint:lll +var SubDocumentRAGSummarizePrompt = "Please give a concise summary of the context in 1-2 sentences.\n\nContext: {{.context}}" + +func NewSubDocumentRAG(index *index.Index, llm LLM) *SubDocumentRAG { + return &SubDocumentRAG{ + RAG: *New(index). + WithChunkSize(defaultSubDocumentRAGChunkSize). + WithChunkOverlap(defaultSubDocumentRAGChunkOverlap), + childChunkSize: defaultSubDocumentRAGChildChunkSize, + llm: llm, + } +} + +func (r *SubDocumentRAG) WithChunkSize(chunkSize uint) *SubDocumentRAG { + r.chunkSize = chunkSize + return r +} + +func (r *SubDocumentRAG) WithChildChunkSize(childChunkSize uint) *SubDocumentRAG { + r.childChunkSize = childChunkSize + return r +} + +func (r *SubDocumentRAG) WithChunkOverlap(chunkOverlap uint) *SubDocumentRAG { + r.chunkOverlap = chunkOverlap + return r +} + +func (r *SubDocumentRAG) WithTopK(topK uint) *SubDocumentRAG { + r.topK = topK + return r +} + +func (r *SubDocumentRAG) WithLoader(sourceRegexp *regexp.Regexp, loader Loader) *SubDocumentRAG { + r.loaders[sourceRegexp] = loader + return r +} + +func (r *SubDocumentRAG) AddSources(ctx context.Context, sources ...string) error { + for _, source := range sources { + documents, err := r.addSource(ctx, source) + if err != nil { + return err + } + + subDocuments, err := r.generateSubDocuments(ctx, documents) + if err != nil { + return err + } + + err = r.index.LoadFromDocuments(ctx, subDocuments) + if err != nil { + return err + } + } + + return nil +} + +func (r *SubDocumentRAG) generateSubDocuments( + ctx context.Context, + documents []document.Document, +) ([]document.Document, error) { + var subDocuments []document.Document + + for _, doc := range documents { + t := thread.New().AddMessages( + thread.NewUserMessage().AddContent( + thread.NewTextContent(SubDocumentRAGSummarizePrompt).Format( + types.M{ + "context": doc.Content, + }, + ), + ), + ) + + err := r.llm.Generate(ctx, t) + if err != nil { + return nil, err + } + summary := t.LastMessage().Contents[0].AsString() + + subChunks := textsplitter.NewRecursiveCharacterTextSplitter( + int(r.childChunkSize), + 0, + ).SplitDocuments([]document.Document{doc}) + + for i := range subChunks { + subChunks[i].Content = summary + "\n" + subChunks[i].Content + } + + subDocuments = append(subDocuments, subChunks...) + } + + return subDocuments, nil +} From 465e21d5d5c5450eb82444ab660ce9b61d5036f7 Mon Sep 17 00:00:00 2001 From: Simone Vellei Date: Thu, 29 Feb 2024 08:40:12 +0100 Subject: [PATCH 4/5] chore: add localai and groq llm wrapper (#174) --- examples/llm/groq/main.go | 12 ++++-------- examples/llm/localai/main.go | 11 +++-------- llm/groq/groq.go | 27 +++++++++++++++++++++++++++ llm/localai/localai.go | 23 +++++++++++++++++++++++ 4 files changed, 57 insertions(+), 16 deletions(-) create mode 100644 llm/groq/groq.go create mode 100644 llm/localai/localai.go diff --git a/examples/llm/groq/main.go b/examples/llm/groq/main.go index 2e4f7e4e..ce6c2b67 100644 --- a/examples/llm/groq/main.go +++ b/examples/llm/groq/main.go @@ -4,17 +4,13 @@ import ( "context" "fmt" - "github.com/henomis/lingoose/llm/openai" + "github.com/henomis/lingoose/llm/groq" "github.com/henomis/lingoose/thread" - goopenai "github.com/sashabaranov/go-openai" ) func main() { - customConfig := goopenai.DefaultConfig("YOUR_API_KEY") - customConfig.BaseURL = "https://api.groq.com/openai/v1" - customClient := goopenai.NewClientWithConfig(customConfig) - - openaillm := openai.New().WithClient(customClient).WithModel("mixtral-8x7b-32768") + // The Groq API key is expected to be set in the OPENAI_API_KEY environment variable + groqllm := groq.New().WithModel("mixtral-8x7b-32768") t := thread.New().AddMessage( thread.NewUserMessage().AddContent( @@ -22,7 +18,7 @@ func main() { ), ) - err := openaillm.Generate(context.Background(), t) + err := groqllm.Generate(context.Background(), t) if err != nil { panic(err) } diff --git a/examples/llm/localai/main.go b/examples/llm/localai/main.go index 92c09831..0a4ea9c1 100644 --- a/examples/llm/localai/main.go +++ b/examples/llm/localai/main.go @@ -4,17 +4,12 @@ import ( "context" "fmt" - "github.com/henomis/lingoose/llm/openai" + "github.com/henomis/lingoose/llm/localai" "github.com/henomis/lingoose/thread" - goopenai "github.com/sashabaranov/go-openai" ) func main() { - customConfig := goopenai.DefaultConfig("YOUR_API_KEY") - customConfig.BaseURL = "http://localhost:8080" - customClient := goopenai.NewClientWithConfig(customConfig) - - openaillm := openai.New().WithClient(customClient).WithModel("ggml-gpt4all-j") + localaillm := localai.New("http://localhost:8080").WithModel("ggml-gpt4all-j") t := thread.New().AddMessage( thread.NewUserMessage().AddContent( @@ -22,7 +17,7 @@ func main() { ), ) - err := openaillm.Generate(context.Background(), t) + err := localaillm.Generate(context.Background(), t) if err != nil { panic(err) } diff --git a/llm/groq/groq.go b/llm/groq/groq.go new file mode 100644 index 00000000..b58cb683 --- /dev/null +++ b/llm/groq/groq.go @@ -0,0 +1,27 @@ +package groq + +import ( + "os" + + "github.com/henomis/lingoose/llm/openai" + goopenai "github.com/sashabaranov/go-openai" +) + +const ( + groqAPIEndpoint = "https://api.groq.com/openai/v1" +) + +type Groq struct { + *openai.OpenAI +} + +func New() *Groq { + customConfig := goopenai.DefaultConfig(os.Getenv("OPENAI_API_KEY")) + customConfig.BaseURL = groqAPIEndpoint + customClient := goopenai.NewClientWithConfig(customConfig) + + openaillm := openai.New().WithClient(customClient) + return &Groq{ + OpenAI: openaillm, + } +} diff --git a/llm/localai/localai.go b/llm/localai/localai.go new file mode 100644 index 00000000..78aa9b6e --- /dev/null +++ b/llm/localai/localai.go @@ -0,0 +1,23 @@ +package localai + +import ( + "os" + + "github.com/henomis/lingoose/llm/openai" + goopenai "github.com/sashabaranov/go-openai" +) + +type LocalAI struct { + *openai.OpenAI +} + +func New(endpoint string) *LocalAI { + customConfig := goopenai.DefaultConfig(os.Getenv("OPENAI_API_KEY")) + customConfig.BaseURL = endpoint + customClient := goopenai.NewClientWithConfig(customConfig) + + openaillm := openai.New().WithClient(customClient) + return &LocalAI{ + OpenAI: openaillm, + } +} From e3f665f5be9ab1017529222049249fb3371bae2b Mon Sep 17 00:00:00 2001 From: Simone Vellei Date: Fri, 8 Mar 2024 08:19:07 +0100 Subject: [PATCH 5/5] Chore Add qa linglet (#175) --- docs/content/reference/linglet.md | 35 +++++++++++- docs/content/reference/llm.md | 1 + docs/content/reference/rag.md | 26 +++++++++ examples/linglet/qa/main.go | 40 +++++++++++++ examples/rag/subdocument/main.go | 2 +- linglet/qa/prompt.go | 24 ++++++++ linglet/qa/qa.go | 94 +++++++++++++++++++++++++++++++ llm/ollama/formatter.go | 3 + llm/ollama/ollama.go | 6 ++ rag/sub_document.go | 2 +- 10 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 examples/linglet/qa/main.go create mode 100644 linglet/qa/prompt.go create mode 100644 linglet/qa/qa.go diff --git a/docs/content/reference/linglet.md b/docs/content/reference/linglet.md index 649fbe7e..f113e0be 100644 --- a/docs/content/reference/linglet.md +++ b/docs/content/reference/linglet.md @@ -65,4 +65,37 @@ if err != nil { fmt.Println(*summary) ``` -The summarize linglet chunks the input text into smaller pieces and then iterate over each chunk to summarize the result. It also provides a callback function to track the progress of the summarization process. \ No newline at end of file +The summarize linglet chunks the input text into smaller pieces and then iterate over each chunk to summarize the result. It also provides a callback function to track the progress of the summarization process. + +## Using QA Linglet + +There is a dedicated linglet to handle question answering. It can be used to answer questions based on the given context. + +```go +func main() { + qa := qa.New( + openai.New().WithTemperature(0), + index.New( + jsondb.New().WithPersist("db.json"), + openaiembedder.New(openaiembedder.AdaEmbeddingV2), + ), + ) + + _, err := os.Stat("db.json") + if os.IsNotExist(err) { + err = qa.AddSource(context.Background(), "state_of_the_union.txt") + if err != nil { + panic(err) + } + } + + response, err := qa.Run(context.Background(), "What is the NATO purpose?") + if err != nil { + panic(err) + } + + fmt.Println(response) +} +``` + +This linglet will use a powerful RAG algorith to ingest and retrieve context from the given source and then use an LLM to generate the response. \ No newline at end of file diff --git a/docs/content/reference/llm.md b/docs/content/reference/llm.md index e64c1faa..f2ccd60d 100644 --- a/docs/content/reference/llm.md +++ b/docs/content/reference/llm.md @@ -16,6 +16,7 @@ LinGoose supports the following LLM providers API: - [Huggingface](https://huggingface.co) - [Ollama](https://ollama.ai) - [LocalAI](https://localai.io/) (_via OpenAI API compatibility_) +- [Groq](https://groq.com/) ## Using LLMs diff --git a/docs/content/reference/rag.md b/docs/content/reference/rag.md index c258bc70..8dc16f95 100644 --- a/docs/content/reference/rag.md +++ b/docs/content/reference/rag.md @@ -51,3 +51,29 @@ There are default loader already attached to the RAG that you can override or ex - `.*\.pdf` via `loader.NewPDFToText()` - `.*\.txt` via `loader.NewText()` - `.*\.docx` via `loader.NewLibreOffice()` + +## Fusion RAG +This is an advance RAG algorithm that uses an LLM to generate additional queries based on the original one. New queries will be used to retrieve more documents that will be reranked and used to generate the final response. + +```go +fusionRAG := rag.NewFusion( + index.New( + jsondb.New().WithPersist("index.json"), + openaiembedder.New(openaiembedder.AdaEmbeddingV2), + ), + openai.New(), +) +``` + +## Subdocument RAG +This is an advance RAG algorithm that ingest documents chunking them in subdocuments and attaching a summary of the parent document. This will allow the RAG to retrieve more relevant documents and generate better responses. + +```go +fusionRAG := rag.NewSubDocument( + index.New( + jsondb.New().WithPersist("index.json"), + openaiembedder.New(openaiembedder.AdaEmbeddingV2), + ), + openai.New(), +) +``` \ No newline at end of file diff --git a/examples/linglet/qa/main.go b/examples/linglet/qa/main.go new file mode 100644 index 00000000..be7508b0 --- /dev/null +++ b/examples/linglet/qa/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "context" + "fmt" + "os" + + openaiembedder "github.com/henomis/lingoose/embedder/openai" + "github.com/henomis/lingoose/index" + "github.com/henomis/lingoose/index/vectordb/jsondb" + "github.com/henomis/lingoose/linglet/qa" + "github.com/henomis/lingoose/llm/openai" +) + +// download https://raw.githubusercontent.com/hwchase17/chat-your-data/master/state_of_the_union.txt + +func main() { + qa := qa.New( + openai.New().WithTemperature(0), + index.New( + jsondb.New().WithPersist("db.json"), + openaiembedder.New(openaiembedder.AdaEmbeddingV2), + ), + ) + + _, err := os.Stat("db.json") + if os.IsNotExist(err) { + err = qa.AddSource(context.Background(), "state_of_the_union.txt") + if err != nil { + panic(err) + } + } + + response, err := qa.Run(context.Background(), "What is the NATO purpose?") + if err != nil { + panic(err) + } + + fmt.Println(response) +} diff --git a/examples/rag/subdocument/main.go b/examples/rag/subdocument/main.go index ad41628f..2d7c90ff 100644 --- a/examples/rag/subdocument/main.go +++ b/examples/rag/subdocument/main.go @@ -17,7 +17,7 @@ import ( // download https://raw.githubusercontent.com/hwchase17/chat-your-data/master/state_of_the_union.txt func main() { - r := rag.NewSubDocumentRAG( + r := rag.NewSubDocument( index.New( jsondb.New().WithPersist("db.json"), openaiembedder.New(openaiembedder.AdaEmbeddingV2), diff --git a/linglet/qa/prompt.go b/linglet/qa/prompt.go new file mode 100644 index 00000000..69117b53 --- /dev/null +++ b/linglet/qa/prompt.go @@ -0,0 +1,24 @@ +package qa + +//nolint:lll +const ( + refinementPrompt = ` +You are a Prompt Engineer with 10 years of experience. Given a prompt, refine the prompt to reflect these tatics, include details in your query to get more relevant answers. +The refined prompt should: + +1. Include details in prompt to get more relevant answers. + Example: + How do I add numbers in Excel? -> How do I add up a row of dollar amounts in Excel? I want to do this automatically for a whole sheet of rows with all the totals ending up on the right in a column called "Total". + +2. Ask the model to adopt a role + Example: + How do I add numbers in Excel? -> You are an Excel expert with 10 years experience. ... + +3. Specify the steps required to complete a task. Some tasks are best specified as a sequence of steps. Writing the steps out explicitly can make it easier for the model to follow them. + +4. Provide examples + +The prompt is : {{.prompt}} + +Only return the refined prompt as output. Merge the final prompt into one sentence or paragraph.` +) diff --git a/linglet/qa/qa.go b/linglet/qa/qa.go new file mode 100644 index 00000000..c1ec9990 --- /dev/null +++ b/linglet/qa/qa.go @@ -0,0 +1,94 @@ +package qa + +import ( + "context" + + "github.com/henomis/lingoose/assistant" + "github.com/henomis/lingoose/index" + "github.com/henomis/lingoose/rag" + "github.com/henomis/lingoose/thread" + "github.com/henomis/lingoose/types" +) + +const ( + defaultTopK = 3 +) + +type LLM interface { + Generate(context.Context, *thread.Thread) error +} + +type QA struct { + llm LLM + index *index.Index + subDocumentRAG *rag.SubDocumentRAG +} + +func New( + llm LLM, + index *index.Index, +) *QA { + subDocumentRAG := rag.NewSubDocument(index, llm).WithTopK(defaultTopK) + + return &QA{ + llm: llm, + index: index, + subDocumentRAG: subDocumentRAG, + } +} + +func (qa *QA) refinePrompt(ctx context.Context, prompt string) (string, error) { + t := thread.New().AddMessage( + thread.NewAssistantMessage().AddContent( + thread.NewTextContent(refinementPrompt). + Format( + types.M{ + "prompt": prompt, + }, + ), + ), + ) + + err := qa.llm.Generate(ctx, t) + if err != nil { + return prompt, err + } + + return t.LastMessage().Contents[0].AsString(), nil +} + +func (qa *QA) AddSource(ctx context.Context, source string) error { + return qa.subDocumentRAG.AddSources(ctx, source) +} + +func (qa *QA) Run(ctx context.Context, prompt string) (string, error) { + refinedPromt, err := qa.refinePrompt(ctx, prompt) + if err != nil { + return "", err + } + + a := assistant.New( + qa.llm, + ).WithParameters( + assistant.Parameters{ + AssistantName: "AI Assistant", + AssistantIdentity: "a helpful and polite assistant", + AssistantScope: "to answer questions", + CompanyName: "", + CompanyDescription: "", + }, + ).WithRAG(qa.subDocumentRAG).WithThread( + thread.New().AddMessages( + thread.NewUserMessage().AddContent( + thread.NewTextContent(refinedPromt), + ), + ), + ) + + err = a.Run(ctx) + if err != nil { + return "", err + } + + return a.Thread().LastMessage().Contents[0].AsString(), nil +} diff --git a/llm/ollama/formatter.go b/llm/ollama/formatter.go index 5f4c01c5..1f5f8990 100644 --- a/llm/ollama/formatter.go +++ b/llm/ollama/formatter.go @@ -6,6 +6,9 @@ func (o *Ollama) buildChatCompletionRequest(t *thread.Thread) *request { return &request{ Model: o.model, Messages: threadToChatMessages(t), + Options: options{ + Temperature: o.temperature, + }, } } diff --git a/llm/ollama/ollama.go b/llm/ollama/ollama.go index 3fa0831e..503ce28d 100644 --- a/llm/ollama/ollama.go +++ b/llm/ollama/ollama.go @@ -35,6 +35,7 @@ type Ollama struct { restClient *restclientgo.RestClient streamCallbackFn StreamCallbackFn cache *cache.Cache + temperature float64 } func New() *Ollama { @@ -64,6 +65,11 @@ func (o *Ollama) WithCache(cache *cache.Cache) *Ollama { return o } +func (o *Ollama) WithTemperature(temperature float64) *Ollama { + o.temperature = temperature + return o +} + func (o *Ollama) getCache(ctx context.Context, t *thread.Thread) (*cache.Result, error) { messages := t.UserQuery() cacheQuery := strings.Join(messages, "\n") diff --git a/rag/sub_document.go b/rag/sub_document.go index b4e0d0f4..03ec9998 100644 --- a/rag/sub_document.go +++ b/rag/sub_document.go @@ -26,7 +26,7 @@ type SubDocumentRAG struct { //nolint:lll var SubDocumentRAGSummarizePrompt = "Please give a concise summary of the context in 1-2 sentences.\n\nContext: {{.context}}" -func NewSubDocumentRAG(index *index.Index, llm LLM) *SubDocumentRAG { +func NewSubDocument(index *index.Index, llm LLM) *SubDocumentRAG { return &SubDocumentRAG{ RAG: *New(index). WithChunkSize(defaultSubDocumentRAGChunkSize).