Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat v0.3.0 #205

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ bin/
llama.cpp/
whisper.cpp/

*/.hugo_build.lock
*/.hugo_build.lock
docs/public/
75 changes: 69 additions & 6 deletions assistant/assistant.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package assistant

import (
"context"
"fmt"
"strings"

obs "github.com/henomis/lingoose/observer"
Expand All @@ -22,11 +23,16 @@ type observer interface {
SpanEnd(s *obs.Span) (*obs.Span, error)
}

const (
DefaultMaxIterations = 3
)

type Assistant struct {
llm LLM
rag RAG
thread *thread.Thread
parameters Parameters
llm LLM
rag RAG
thread *thread.Thread
parameters Parameters
maxIterations uint
}

type LLM interface {
Expand All @@ -48,6 +54,7 @@ func New(llm LLM) *Assistant {
CompanyName: defaultCompanyName,
CompanyDescription: defaultCompanyDescription,
},
maxIterations: DefaultMaxIterations,
}

return assistant
Expand Down Expand Up @@ -83,14 +90,41 @@ func (a *Assistant) Run(ctx context.Context) error {
if errGenerate != nil {
return errGenerate
}
} else {
a.injectSystemMessage()
}

for i := 0; i < int(a.maxIterations); i++ {
err = a.runIteration(ctx, i)
if err != nil {
return err
}

if a.thread.LastMessage().Role != thread.RoleTool {
break
}
}

err = a.stopObserveSpan(ctx, spanAssistant)
if err != nil {
return err
}

return nil
}

func (a *Assistant) runIteration(ctx context.Context, iteration int) error {
ctx, spanIteration, err := a.startObserveSpan(ctx, fmt.Sprintf("iteration-%d", iteration+1))
if err != nil {
return err
}

err = a.llm.Generate(ctx, a.thread)
if err != nil {
return err
}

err = a.stopObserveSpan(ctx, spanAssistant)
err = a.stopObserveSpan(ctx, spanIteration)
if err != nil {
return err
}
Expand Down Expand Up @@ -123,7 +157,7 @@ func (a *Assistant) generateRAGMessage(ctx context.Context) error {

a.thread.AddMessage(thread.NewSystemMessage().AddContent(
thread.NewTextContent(
systemRAGPrompt,
systemPrompt,
).Format(
types.M{
"assistantName": a.parameters.AssistantName,
Expand All @@ -147,6 +181,11 @@ func (a *Assistant) generateRAGMessage(ctx context.Context) error {
return nil
}

func (a *Assistant) WithMaxIterations(maxIterations uint) *Assistant {
a.maxIterations = maxIterations
return a
}

func (a *Assistant) startObserveSpan(ctx context.Context, name string) (context.Context, *obs.Span, error) {
o, ok := obs.ContextValueObserverInstance(ctx).(observer)
if o == nil || !ok {
Expand Down Expand Up @@ -183,3 +222,27 @@ func (a *Assistant) stopObserveSpan(ctx context.Context, span *obs.Span) error {
_, err := o.SpanEnd(span)
return err
}

func (a *Assistant) injectSystemMessage() {
for _, message := range a.thread.Messages {
if message.Role == thread.RoleSystem {
return
}
}

systemMessage := thread.NewSystemMessage().AddContent(
thread.NewTextContent(
systemPrompt,
).Format(
types.M{
"assistantName": a.parameters.AssistantName,
"assistantIdentity": a.parameters.AssistantIdentity,
"assistantScope": a.parameters.AssistantScope,
"companyName": a.parameters.CompanyName,
"companyDescription": a.parameters.CompanyDescription,
},
),
)

a.thread.Messages = append([]*thread.Message{systemMessage}, a.thread.Messages...)
}
2 changes: 1 addition & 1 deletion assistant/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const (
//nolint:lll
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}}."
systemPrompt = "{{if ne .assistantName \"\"}}You name is {{.assistantName}}, {{end}}{{if ne .assistantIdentity \"\"}}you are {{.assistantIdentity}}.{{end}} {{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"
Expand Down
35 changes: 34 additions & 1 deletion docs/content/reference/assistant.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,37 @@ if err != nil {
fmt.Println(myAssistant.Thread())
```

We can define the LinGoose `Assistant` as a `Thread` runner with an optional `RAG` component that will help to produce the response.
We can define the LinGoose `Assistant` as a `Thread` runner with an optional `RAG` component that will help to produce the response.

## Assistant as Agent

The `Assistant` can be used as an agent in a conversation. It can be used to automate tasks, answer questions, and provide information.

```go
auto := "auto"
myAgent := assistant.New(
openai.New().WithModel(openai.GPT4o).WithToolChoice(&auto).WithTools(
pythontool.New(),
serpapitool.New(),
),
).WithParameters(
assistant.Parameters{
AssistantName: "AI Assistant",
AssistantIdentity: "an helpful assistant",
AssistantScope: "with their questions.",
CompanyName: "",
CompanyDescription: "",
},
).WithThread(
thread.New().AddMessages(
thread.NewUserMessage().AddContent(
thread.NewTextContent("calculate the average temperature in celsius degrees of New York, Rome, and Tokyo."),
),
),
).WithMaxIterations(10)

err := myAgent.Run(context.Background())
if err != nil {
panic(err)
}
```
2 changes: 1 addition & 1 deletion docs/content/reference/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "LinGoose Examples"
description:
linkTitle: "Examples"
menu: { main: { parent: 'reference', weight: -88 } }
menu: { main: { parent: 'reference', weight: -87 } }
---

LinGoose provides a number of examples to help you get started with building your own AI app. You can use these examples as a reference to understand how to build your own assistant.
Expand Down
2 changes: 1 addition & 1 deletion docs/content/reference/linglet.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "LinGoose Linglets"
description:
linkTitle: "Linglets"
menu: { main: { parent: 'reference', weight: -89 } }
menu: { main: { parent: 'reference', weight: -88 } }
---

Linglets are pre-built LinGoose Assistants with a specific purpose. They are designed to be used as a starting point for building your own AI app. You can use them as a reference to understand how to build your own assistant.
Expand Down
2 changes: 1 addition & 1 deletion docs/content/reference/observer.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: "Observer"
title: "Observe and Analyze LLM Applications"
description:
linkTitle: "Observer"
menu: { main: { parent: 'reference', weight: -92 } }
Expand Down
52 changes: 52 additions & 0 deletions docs/content/reference/tool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
title: "Performing tasks with Tools"
description:
linkTitle: "Tool"
menu: { main: { parent: 'reference', weight: -89 } }
---

Tools are components that can be used to perform specific tasks. They can be used to automate, answer questions, and provide information. LinGoose offers a variety of tools that can be used to perform different actions.

## Available Tools

- *Python*: It can be used to run Python code and get the output.
- *SerpApi*: It can be used to get search results from Google and other search engines.
- *Dall-e*: It can be used to generate images based on text descriptions.
- *DuckDuckGo*: It can be used to get search results from DuckDuckGo.
- *RAG*: It can be used to retrieve relevant documents based on a query.
- *LLM*: It can be used to generate text based on a prompt.
- *Shell*: It can be used to run shell commands and get the output.


## Using Tools

LinGoose tools can be used to perform specific tasks. Here is an example of using the `Python` and `serpapi` tools to get information and run Python code and get the output.

```go
auto := "auto"
myAgent := assistant.New(
openai.New().WithModel(openai.GPT4o).WithToolChoice(&auto).WithTools(
pythontool.New(),
serpapitool.New(),
),
).WithParameters(
assistant.Parameters{
AssistantName: "AI Assistant",
AssistantIdentity: "an helpful assistant",
AssistantScope: "with their questions.",
CompanyName: "",
CompanyDescription: "",
},
).WithThread(
thread.New().AddMessages(
thread.NewUserMessage().AddContent(
thread.NewTextContent("calculate the average temperature in celsius degrees of New York, Rome, and Tokyo."),
),
),
).WithMaxIterations(10)

err := myAgent.Run(context.Background())
if err != nil {
panic(err)
}
```
59 changes: 59 additions & 0 deletions examples/assistant/agent/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package main

import (
"context"
"fmt"

"github.com/henomis/lingoose/assistant"
"github.com/henomis/lingoose/llm/openai"
"github.com/henomis/lingoose/observer"
"github.com/henomis/lingoose/observer/langfuse"
"github.com/henomis/lingoose/thread"

humantool "github.com/henomis/lingoose/tool/human"
pythontool "github.com/henomis/lingoose/tool/python"
serpapitool "github.com/henomis/lingoose/tool/serpapi"
)

func main() {
ctx := context.Background()

langfuseObserver := langfuse.New(ctx)
trace, err := langfuseObserver.Trace(&observer.Trace{Name: "Italian guests calculator"})
if err != nil {
panic(err)
}

ctx = observer.ContextWithObserverInstance(ctx, langfuseObserver)
ctx = observer.ContextWithTraceID(ctx, trace.ID)

auto := "auto"
myAssistant := assistant.New(
openai.New().WithModel(openai.GPT4o).WithToolChoice(&auto).WithTools(
pythontool.New(),
serpapitool.New(),
humantool.New(),
),
).WithParameters(
assistant.Parameters{
AssistantName: "AI Assistant",
AssistantIdentity: "a helpful assistant",
AssistantScope: "answering questions",
},
).WithThread(
thread.New().AddMessages(
thread.NewUserMessage().AddContent(
thread.NewTextContent("search the top 3 italian dishes and then their costs, then ask the user's budget in euros and calculate how many guests can be invited for each dish"),
),
),
).WithMaxIterations(10)

err = myAssistant.Run(ctx)
if err != nil {
panic(err)
}

fmt.Println(myAssistant.Thread())

langfuseObserver.Flush(ctx)
}
File renamed without changes.
24 changes: 12 additions & 12 deletions examples/llm/openai/thread/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package main

import (
"context"
"encoding/json"
"fmt"
"strings"

"github.com/henomis/lingoose/llm/openai"
"github.com/henomis/lingoose/thread"
"github.com/henomis/lingoose/tool/dalle"
"github.com/henomis/lingoose/transformer"
)

Expand All @@ -32,31 +33,30 @@ func newStr(str string) *string {

func main() {
openaillm := openai.New().WithModel(openai.GPT4o)
openaillm.WithToolChoice(newStr("auto"))
err := openaillm.BindFunction(
crateImage,
"createImage",
"use this function to create an image from a description",
)
if err != nil {
panic(err)
}
openaillm.WithToolChoice(newStr("auto")).WithTools(dalle.New())

t := thread.New().AddMessage(
thread.NewUserMessage().AddContent(
thread.NewTextContent("Please, create an image that inspires you"),
),
)

err = openaillm.Generate(context.Background(), t)
err := openaillm.Generate(context.Background(), t)
if err != nil {
panic(err)
}

if t.LastMessage().Role == thread.RoleTool {
var output dalle.Output

err = json.Unmarshal([]byte(t.LastMessage().Contents[0].AsToolResponseData().Result), &output)
if err != nil {
panic(err)
}

t.AddMessage(thread.NewUserMessage().AddContent(
thread.NewImageContentFromURL(
strings.ReplaceAll(t.LastMessage().Contents[0].AsToolResponseData().Result, `"`, ""),
output.ImageURL,
),
).AddContent(
thread.NewTextContent("can you describe the image?"),
Expand Down
Loading
Loading