Skip to content

Commit

Permalink
Chore Add qa linglet (#175)
Browse files Browse the repository at this point in the history
  • Loading branch information
henomis authored Mar 8, 2024
1 parent 465e21d commit e3f665f
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 3 deletions.
35 changes: 34 additions & 1 deletion docs/content/reference/linglet.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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.
1 change: 1 addition & 0 deletions docs/content/reference/llm.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
26 changes: 26 additions & 0 deletions docs/content/reference/rag.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)
```
40 changes: 40 additions & 0 deletions examples/linglet/qa/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
2 changes: 1 addition & 1 deletion examples/rag/subdocument/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
24 changes: 24 additions & 0 deletions linglet/qa/prompt.go
Original file line number Diff line number Diff line change
@@ -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.`
)
94 changes: 94 additions & 0 deletions linglet/qa/qa.go
Original file line number Diff line number Diff line change
@@ -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
}
3 changes: 3 additions & 0 deletions llm/ollama/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}
}

Expand Down
6 changes: 6 additions & 0 deletions llm/ollama/ollama.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Ollama struct {
restClient *restclientgo.RestClient
streamCallbackFn StreamCallbackFn
cache *cache.Cache
temperature float64
}

func New() *Ollama {
Expand Down Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion rag/sub_document.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down

0 comments on commit e3f665f

Please sign in to comment.