Skip to content

Commit

Permalink
memory: Add Zep Cloud integration (#746)
Browse files Browse the repository at this point in the history
* memory: Add Zep Cloud integration
  • Loading branch information
paul-paliychuk authored Jun 15, 2024
1 parent ade0795 commit 89195f8
Show file tree
Hide file tree
Showing 9 changed files with 492 additions and 4 deletions.
36 changes: 36 additions & 0 deletions chains/conversation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ import (
"strings"
"testing"

z "github.com/getzep/zep-go"
zClient "github.com/getzep/zep-go/client"
zOption "github.com/getzep/zep-go/option"
"github.com/stretchr/testify/require"
"github.com/tmc/langchaingo/llms/openai"
"github.com/tmc/langchaingo/memory"
"github.com/tmc/langchaingo/memory/zep"
)

func TestConversation(t *testing.T) {
Expand All @@ -29,6 +33,38 @@ func TestConversation(t *testing.T) {
require.True(t, strings.Contains(res, "Jim"), `result does not contain the keyword 'Jim'`)
}

func TestConversationWithZepMemory(t *testing.T) {
t.Parallel()

if openaiKey := os.Getenv("OPENAI_API_KEY"); openaiKey == "" {
t.Skip("OPENAI_API_KEY not set")
}
llm, err := openai.New()
require.NoError(t, err)

zc := zClient.NewClient(
zOption.WithAPIKey(os.Getenv("ZEP_API_KEY")),
)
sessionID := os.Getenv("ZEP_SESSION_ID")

c := NewConversation(
llm,
zep.NewMemory(
zc,
sessionID,
zep.WithMemoryType(z.MemoryGetRequestMemoryTypePerpetual),
zep.WithHumanPrefix("Joe"),
zep.WithAIPrefix("Robot"),
),
)
_, err = Run(context.Background(), c, "Hi! I'm Jim")
require.NoError(t, err)

res, err := Run(context.Background(), c, "What is my name?")
require.NoError(t, err)
require.True(t, strings.Contains(res, "Jim"), `result does not contain the keyword 'Jim'`)
}

func TestConversationWithChatLLM(t *testing.T) {
t.Parallel()

Expand Down
48 changes: 48 additions & 0 deletions examples/zep-memory-chain-example/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main

import (
"context"
"fmt"
"github.com/getzep/zep-go"
zepClient "github.com/getzep/zep-go/client"
zepOption "github.com/getzep/zep-go/option"
"github.com/tmc/langchaingo/chains"
"github.com/tmc/langchaingo/llms/openai"
zepLangchainMemory "github.com/tmc/langchaingo/memory/zep"
"os"
)

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

client := zepClient.NewClient(zepOption.WithAPIKey(os.Getenv("ZEP_API_KEY")))
sessionID := os.Getenv("ZEP_SESSION_ID")

llm, err := openai.New()
if err != nil {
fmt.Printf("Error: %s\n", err)
}

c := chains.NewConversation(
llm,
zepLangchainMemory.NewMemory(
client,
sessionID,
zepLangchainMemory.WithMemoryType(zep.MemoryGetRequestMemoryTypePerpetual),
zepLangchainMemory.WithReturnMessages(true),
zepLangchainMemory.WithAIPrefix("Robot"),
zepLangchainMemory.WithHumanPrefix("Joe"),
),
)
res, err := chains.Run(ctx, c, "Hi! I'm John Doe")
if err != nil {
fmt.Printf("Error: %s\n", err)
}
fmt.Printf("Response: %s\n", res)

res, err = chains.Run(ctx, c, "What is my name?")
if err != nil {
fmt.Printf("Error: %s\n", err)
}
fmt.Printf("Response: %s\n", res)
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/rs/zerolog v1.31.0 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
Expand Down Expand Up @@ -195,6 +194,7 @@ require (
github.com/cohere-ai/tokenizer v1.1.2
github.com/fatih/color v1.17.0
github.com/gage-technologies/mistral-go v1.0.0
github.com/getzep/zep-go v1.0.4
github.com/go-openapi/strfmt v0.21.3
github.com/go-sql-driver/mysql v1.7.1
github.com/gocolly/colly v1.2.0
Expand All @@ -214,6 +214,7 @@ require (
github.com/pinecone-io/go-pinecone v0.4.1
github.com/pkoukk/tiktoken-go v0.1.6
github.com/redis/rueidis v1.0.34
github.com/rs/zerolog v1.31.0
github.com/weaviate/weaviate v1.24.1
github.com/weaviate/weaviate-go-client/v4 v4.13.1
gitlab.com/golang-commonmark/markdown v0.0.0-20211110145824-bf3e522c626a
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk=
github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
github.com/getzep/zep-go v0.0.7 h1:7ZFTtXr6ZHXnFe/z3iK0RyhCVie6wPl2swC7alGlENs=
github.com/getzep/zep-go v0.0.7/go.mod h1:HC1Gz7oiyrzOTvzeKC4dQKUiUy87zpIJl0ZFXXdHuss=
github.com/getzep/zep-go v1.0.4 h1:09o26bPP2RAPKFjWuVWwUWLbtFDF/S8bfbilxzeZAAg=
github.com/getzep/zep-go v1.0.4/go.mod h1:HC1Gz7oiyrzOTvzeKC4dQKUiUy87zpIJl0ZFXXdHuss=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
Expand Down Expand Up @@ -367,6 +372,7 @@ github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
Expand Down
6 changes: 3 additions & 3 deletions memory/buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func (m *ConversationBuffer) SaveContext(
inputValues map[string]any,
outputValues map[string]any,
) error {
userInputValue, err := getInputValue(inputValues, m.InputKey)
userInputValue, err := GetInputValue(inputValues, m.InputKey)
if err != nil {
return err
}
Expand All @@ -86,7 +86,7 @@ func (m *ConversationBuffer) SaveContext(
return err
}

aiOutputValue, err := getInputValue(outputValues, m.OutputKey)
aiOutputValue, err := GetInputValue(outputValues, m.OutputKey)
if err != nil {
return err
}
Expand All @@ -107,7 +107,7 @@ func (m *ConversationBuffer) GetMemoryKey(context.Context) string {
return m.MemoryKey
}

func getInputValue(inputValues map[string]any, inputKey string) (string, error) {
func GetInputValue(inputValues map[string]any, inputKey string) (string, error) {
// If the input key is set, return the value in the inputValues with the input key.
if inputKey != "" {
inputValue, ok := inputValues[inputKey]
Expand Down
164 changes: 164 additions & 0 deletions memory/zep/zep_chat_history.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package zep

import (
"context"
"fmt"

"github.com/getzep/zep-go"
zepClient "github.com/getzep/zep-go/client"
"github.com/rs/zerolog/log"
"github.com/tmc/langchaingo/llms"
"github.com/tmc/langchaingo/schema"
)

// ChatMessageHistory is a struct that stores chat messages.
type ChatMessageHistory struct {
ZepClient *zepClient.Client
SessionID string
MemoryType zep.MemoryGetRequestMemoryType
HumanPrefix string
AIPrefix string
}

// Statically assert that ZepChatMessageHistory implement the chat message history interface.
var _ schema.ChatMessageHistory = &ChatMessageHistory{}

// NewZepChatMessageHistory creates a new ZepChatMessageHistory using chat message options.
func NewZepChatMessageHistory(zep *zepClient.Client, sessionID string, options ...ChatMessageHistoryOption) *ChatMessageHistory {
messageHistory := applyZepChatHistoryOptions(options...)
messageHistory.ZepClient = zep
messageHistory.SessionID = sessionID
return messageHistory
}

func (h *ChatMessageHistory) messagesFromZepMessages(zepMessages []*zep.Message) []llms.ChatMessage {
var chatMessages []llms.ChatMessage
for _, zepMessage := range zepMessages {
switch *zepMessage.RoleType { // nolint We do not store other message types in zep memory
case zep.RoleTypeUserRole:
chatMessages = append(chatMessages, llms.HumanChatMessage{Content: *zepMessage.Content})
case zep.RoleTypeAssistantRole:
chatMessages = append(chatMessages, llms.AIChatMessage{Content: *zepMessage.Content})
case zep.RoleTypeToolRole:
case zep.RoleTypeFunctionRole:
chatMessages = append(chatMessages, llms.ToolChatMessage{Content: *zepMessage.Content})
default:
log.Print(fmt.Errorf("unknown role: %s", *zepMessage.RoleType))
continue
}
}
return chatMessages
}

func (h *ChatMessageHistory) messagesToZepMessages(messages []llms.ChatMessage) []*zep.Message {
var zepMessages []*zep.Message //nolint We don't know the final size of the messages as some might be skipped due to unsupported role.
for _, m := range messages {
zepMessage := zep.Message{
Content: zep.String(m.GetContent()),
}
switch m.GetType() { // nolint We only expect to bring these three types into chat history
case llms.ChatMessageTypeHuman:
zepMessage.RoleType = zep.RoleTypeUserRole.Ptr()
if h.HumanPrefix != "" {
zepMessage.Role = zep.String(h.HumanPrefix)
}
case llms.ChatMessageTypeAI:
zepMessage.RoleType = zep.RoleTypeAssistantRole.Ptr()
if h.AIPrefix != "" {
zepMessage.Role = zep.String(h.AIPrefix)
}
case llms.ChatMessageTypeFunction:
zepMessage.RoleType = zep.RoleTypeFunctionRole.Ptr()
case llms.ChatMessageTypeTool:
zepMessage.RoleType = zep.RoleTypeToolRole.Ptr()
default:
log.Print(fmt.Errorf("unknown role: %s", *zepMessage.RoleType))
continue
}
zepMessages = append(zepMessages, &zepMessage)
}
return zepMessages
}

// Messages returns all messages stored.
func (h *ChatMessageHistory) Messages(ctx context.Context) ([]llms.ChatMessage, error) {
memory, err := h.ZepClient.Memory.Get(ctx, h.SessionID, &zep.MemoryGetRequest{
MemoryType: h.MemoryType.Ptr(),
})
if err != nil {
return nil, err
}
messages := h.messagesFromZepMessages(memory.Messages)
zepFacts := memory.Facts
systemPromptContent := ""
for _, fact := range zepFacts {
systemPromptContent += fmt.Sprintf("%s\n", fact)
}
if memory.Summary != nil && memory.Summary.Content != nil {
systemPromptContent += fmt.Sprintf("%s\n", *memory.Summary.Content)
}
if systemPromptContent != "" {
// Add system prompt to the beginning of the messages.
messages = append(
[]llms.ChatMessage{
llms.SystemChatMessage{
Content: systemPromptContent,
},
},
messages...,
)
}
return messages, nil
}

// AddAIMessage adds an AIMessage to the chat message history.
func (h *ChatMessageHistory) AddAIMessage(ctx context.Context, text string) error {
_, err := h.ZepClient.Memory.Add(ctx, h.SessionID, &zep.AddMemoryRequest{
Messages: h.messagesToZepMessages(
[]llms.ChatMessage{
llms.AIChatMessage{Content: text},
},
),
})
if err != nil {
return err
}
return nil
}

// AddUserMessage adds a user to the chat message history.
func (h *ChatMessageHistory) AddUserMessage(ctx context.Context, text string) error {
_, err := h.ZepClient.Memory.Add(ctx, h.SessionID, &zep.AddMemoryRequest{
Messages: h.messagesToZepMessages(
[]llms.ChatMessage{
llms.HumanChatMessage{Content: text},
},
),
})
if err != nil {
return err
}
return nil
}

func (h *ChatMessageHistory) Clear(ctx context.Context) error {
_, err := h.ZepClient.Memory.Delete(ctx, h.SessionID)
if err != nil {
return err
}
return nil
}

func (h *ChatMessageHistory) AddMessage(ctx context.Context, message llms.ChatMessage) error {
_, err := h.ZepClient.Memory.Add(ctx, h.SessionID, &zep.AddMemoryRequest{
Messages: h.messagesToZepMessages([]llms.ChatMessage{message}),
})
if err != nil {
return err
}
return nil
}

func (*ChatMessageHistory) SetMessages(_ context.Context, _ []llms.ChatMessage) error {
return nil
}
40 changes: 40 additions & 0 deletions memory/zep/zep_chat_history_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package zep

import "github.com/getzep/zep-go"

// ChatMessageHistoryOption is a function for creating new chat message history
// with other than the default values.
type ChatMessageHistoryOption func(m *ChatMessageHistory)

// WithChatHistoryMemoryType specifies zep memory type.
func WithChatHistoryMemoryType(memoryType zep.MemoryGetRequestMemoryType) ChatMessageHistoryOption {
return func(b *ChatMessageHistory) {
b.MemoryType = memoryType
}
}

// WithChatHistoryHumanPrefix is an option for specifying the human prefix. Will be passed as role for the message to zep.
func WithChatHistoryHumanPrefix(humanPrefix string) ChatMessageHistoryOption {
return func(b *ChatMessageHistory) {
b.HumanPrefix = humanPrefix
}
}

// WithChatHistoryAIPrefix is an option for specifying the AI prefix. Will be passed as role for the message to zep.
func WithChatHistoryAIPrefix(aiPrefix string) ChatMessageHistoryOption {
return func(b *ChatMessageHistory) {
b.AIPrefix = aiPrefix
}
}

func applyZepChatHistoryOptions(options ...ChatMessageHistoryOption) *ChatMessageHistory {
h := &ChatMessageHistory{
MemoryType: zep.MemoryGetRequestMemoryTypePerpetual,
}

for _, option := range options {
option(h)
}

return h
}
Loading

0 comments on commit 89195f8

Please sign in to comment.