MongoDB Vector Search를 LangChainGo 와 통합하여 대규모 언어 모델(LLM) 애플리케이션을 빌드 하고 RAG(검색 강화 생성)를 구현 수 있습니다. 이 튜토리얼에서는 MongoDB Vector Search를 LangChainGo와 함께 사용하여 데이터에서 시맨틱 검색 수행하고 RAG 구현 빌드 방법을 보여 줍니다. 구체적으로 다음 조치를 수행합니다.
환경을 설정합니다.
사용자 지정 데이터를 MongoDB 에 저장합니다.
데이터에 MongoDB Vector Search 인덱스 생성합니다.
다음 벡터 검색 쿼리를 실행합니다.
시맨틱 검색.
메타데이터 사전 필터링을 통한 시맨틱 검색.
MongoDB Vector Search를 사용하여 데이터에 대한 질문에 답변 RAG 를 구현합니다.
배경
LangChainGo는 LangChain의 Go 프로그래밍 언어 구현으로, 커뮤니티 주도로 개발된 LangChain 프레임워크의 타사 포트입니다.
LangChain은 '체인'을 사용하여 LLM 애플리케이션 생성을 간소화하는 오픈 소스 프레임워크 입니다. 체인은 RAG를 포함한 다양한 AI 사용 사례에 결합할 수 있는 LangChain 전용 구성 요소입니다.
MongoDB Vector Search를 LangChain과 통합하면 MongoDB 벡터 데이터베이스로 사용하고 MongoDB Vector Search를 사용하여 데이터에서 의미적으로 유사한 문서를 검색하여 RAG 를 구현. RAG에 대해 자세히 학습 MongoDB 사용한 RAG(검색 보강 생성)를 참조하세요.
LangChainGo는 AI 애플리케이션을 위한 LLM의 오케스트레이션을 용이하게 하여 LangChain의 기능을 고 (Go) 에코시스템 에 도입합니다. 또한 개발자가 MongoDB 포함하여 선호하는 벡터 저장소 호환 데이터베이스에 연결할 수 있습니다.
절차
전제 조건
이 튜토리얼을 완료하려면 다음 조건을 충족해야 합니다.
다음 MongoDB cluster 유형 중 하나입니다.
MongoDB 버전 6.0.11을 실행하는 Atlas 클러스터 7.0.2 또는 그 이상. IP 주소가 Atlas 프로젝트의 액세스 목록에 포함되어 있는지 확인하세요.
Atlas CLI 사용하여 생성된 로컬 Atlas 배포서버 입니다. 자세히 학습 로컬 Atlas 배포 만들기를 참조하세요.
검색 및 벡터 검색이 설치된 MongoDB Community 또는 Enterprise 클러스터.
OpenAI API 키입니다. API 요청에 사용할 수 있는 크레딧이 있는 OpenAI 계정이 있어야 합니다. OpenAI 계정 등록에 대해 자세히 학습하려면 OpenAI API 웹사이트를 참조하세요.
Voyage AI API 키입니다. 계정과 API 키를 만들려면 Voyage AI 웹사이트참조하세요.
Go 프로젝트를 실행하기 위한 터미널 및 코드 편집기입니다.
컴퓨터에 고 (Go) 설치 합니다.
환경 설정
이 튜토리얼을 실행하려면 먼저 환경을 설정해야 합니다. 다음 단계를 완료하여 환경을 설정하세요.
종속 요소를 설치합니다.
다음 명령을 실행합니다.
go get github.com/joho/godotenv go get github.com/tmc/langchaingo/chains go get github.com/tmc/langchaingo/llms go get github.com/tmc/langchaingo/prompts go get github.com/tmc/langchaingo/vectorstores/mongovector go get github.com/tmc/langchaingo/embeddings/voyageai go get go.mongodb.org/mongo-driver/v2/mongo go mod tidy
환경 변수를 초기화합니다.
langchaingo-mongodb 프로젝트 디렉토리 에서 .env 파일 만들고 다음 줄을 추가합니다.
OPENAI_API_KEY="<openai-api-key>" VOYAGEAI_API_KEY="<voyage-api-key>" MONGODB_URI="<connection-string>"
자리 표시자 값을 MongoDB cluster 의 OpenAI API 키, Voyage AI API 키 및 SRV 연결 문자열 로 바꿉니다. 연결 문자열 다음 형식을 사용해야 합니다.
mongodb+srv://<username>:<password>@<cluster-name>.mongodb.net/<dbname>
MongoDB 벡터 저장소로 사용
이 섹션에서는 사용자 지정 데이터를 MongoDB 에 로드하고 MongoDB 벡터 저장소라고도 하는 벡터 데이터베이스로 인스턴스화하는 비동기 함수를 정의합니다.
다음 종속성을 가져옵니다.
main.go 파일 의 맨 위에 다음 가져오기를 추가합니다.
package main import ( "context" "log" "os" "github.com/joho/godotenv" "github.com/tmc/langchaingo/embeddings/voyageai" "github.com/tmc/langchaingo/schema" "github.com/tmc/langchaingo/vectorstores/mongovector" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" )
벡터 저장소 세부 사항을 정의합니다.
다음 코드는 이러한 작업을 수행합니다.
다음을 지정하여 Atlas를 벡터 저장소로 구성합니다.
다음을 수행하여 사용자 지정 데이터를 준비합니다.
각 문서에 대한 텍스트를 정의합니다.
LangChainGo의
mongovector패키지를 사용하여 텍스트의 임베딩을 생성합니다. 이 패키지는 MongoDB에 문서 임베딩을 저장하고 저장된 임베딩을 검색할 수 있게 합니다.텍스트, 임베딩, 메타데이터를 포함하는 문서를 생성합니다.
구성된 문서를 Atlas에 삽입한 후, 벡터 저장소를 인스턴스화합니다.
다음 코드를 main.go 파일에 붙여넣습니다.
// Defines the document structure type Document struct { PageContent string `bson:"text"` Embedding []float32 `bson:"embedding"` Metadata map[string]string `bson:"metadata"` } func main() { const ( voyageAIEmbeddingDim = 1024 similarityAlgorithm = "dotProduct" indexName = "vector_index" databaseName = "langchaingo_db" collectionName = "test" ) if err := godotenv.Load(); err != nil { log.Fatal("No .env file found") } // Loads the MongoDB URI from environment uri := os.Getenv("MONGODB_URI") if uri == "" { log.Fatal("Set your 'MONGODB_URI' environment variable in the .env file") } // Loads the API key from environment voyageApiKey := os.Getenv("VOYAGEAI_API_KEY") if voyageApiKey == "" { log.Fatal("Set your VOYAGEAI_API_KEY environment variable in the .env file") } // Connects to MongoDB cluster client, err := mongo.Connect(options.Client().ApplyURI(uri)) if err != nil { log.Fatalf("Failed to connect to server: %v", err) } defer func() { if err := client.Disconnect(context.Background()); err != nil { log.Fatalf("Error disconnecting the client: %v", err) } }() log.Println("Connected to MongoDB.") // Selects the database and collection coll := client.Database(databaseName).Collection(collectionName) // Creates an embedder client embedder, err := voyageai.NewVoyageAI( voyageai.WithModel("voyage-3-large"), ) if err != nil { log.Fatalf("Failed to create an embedder: %v", err) } // Creates a new MongoDB vector store store := mongovector.New(coll, embedder, mongovector.WithIndex(indexName), mongovector.WithPath("embeddings")) // Checks if the collection is empty, and if empty, adds documents to the MongoDB vector store if isCollectionEmpty(coll) { documents := []schema.Document{ { PageContent: "Proper tuber planting involves site selection, proper timing, and exceptional care. Choose spots with well-drained soil and adequate sun exposure. Tubers are generally planted in spring, but depending on the plant, timing varies. Always plant with the eyes facing upward at a depth two to three times the tuber's height. Ensure 4 inch spacing between small tubers, expand to 12 inches for large ones. Adequate moisture is needed, yet do not overwater. Mulching can help preserve moisture and prevent weed growth.", Metadata: map[string]any{ "author": "A", "type": "post", }, }, { PageContent: "Successful oil painting necessitates patience, proper equipment, and technique. Begin with a carefully prepared, primed canvas. Sketch your composition lightly before applying paint. Use high-quality brushes and oils to create vibrant, long-lasting artworks. Remember to paint 'fat over lean,' meaning each subsequent layer should contain more oil to prevent cracking. Allow each layer to dry before applying another. Clean your brushes often and avoid solvents that might damage them. Finally, always work in a well-ventilated space.", Metadata: map[string]any{ "author": "B", "type": "post", }, }, { PageContent: "For a natural lawn, selection of the right grass type suitable for your climate is crucial. Balanced watering, generally 1 to 1.5 inches per week, is important; overwatering invites disease. Opt for organic fertilizers over synthetic versions to provide necessary nutrients and improve soil structure. Regular lawn aeration helps root growth and prevents soil compaction. Practice natural pest control and consider overseeding to maintain a dense sward, which naturally combats weeds and pest.", Metadata: map[string]any{ "author": "C", "type": "post", }, }, } _, err := store.AddDocuments(context.Background(), documents) if err != nil { log.Fatalf("Error adding documents: %v", err) } log.Printf("Successfully added %d documents to the collection.\n", len(documents)) } else { log.Println("Documents already exist in the collection, skipping document addition.") } } func isCollectionEmpty(coll *mongo.Collection) bool { count, err := coll.EstimatedDocumentCount(context.Background()) if err != nil { log.Fatalf("Failed to count documents in the collection: %v", err) } return count == 0 }
고 (Go) 프로젝트 실행합니다.
파일 저장한 후 다음 명령을 실행 데이터를 MongoDB 에 로드합니다.
go run main.go
Connected to MongoDB Atlas. Successfully added 3 documents to the collection.
팁
main.go를 실행 후 Atlas 사용하는 경우 Atlas UI의 langchaingo_db.test 네임스페이스 로 이동하여 벡터 임베딩을 확인할 수 있습니다.
MongoDB Vector Search 인덱스 만들기
벡터 저장 에서 벡터 검색 쿼리를 활성화 하려면 langchaingo_db.test 컬렉션 에 MongoDB Vector Search 인덱스 만듭니다.
main.go 파일 의 맨 위에 다음 가져오기를 추가합니다.
import ( // Other imports... "fmt" "time" "go.mongodb.org/mongo-driver/v2/bson" )
main() 함수 외부의 main.go 파일 에서 다음 함수를 정의합니다. 다음 함수는 MongoDB 컬렉션 에 대한 벡터 검색 인덱스 생성하고 관리 .
SearchIndexExists함수는 지정된 이름의 검색 인덱스 존재하고 쿼리 가능한지 확인합니다.CreateVectorSearchIndex함수는 지정된 컬렉션 에 벡터 검색 인덱스 만듭니다. 이 함수는 인덱스 생성되어 쿼리할 수 있을 때까지 차단됩니다.
// Checks if the search index exists func SearchIndexExists(ctx context.Context, coll *mongo.Collection, idx string) (bool, error) { log.Println("Checking if search index exists.") view := coll.SearchIndexes() siOpts := options.SearchIndexes().SetName(idx).SetType("vectorSearch") cursor, err := view.List(ctx, siOpts) if err != nil { return false, fmt.Errorf("failed to list search indexes: %w", err) } for cursor.Next(ctx) { index := struct { Name string `bson:"name"` Queryable bool `bson:"queryable"` }{} if err := cursor.Decode(&index); err != nil { return false, fmt.Errorf("failed to decode search index: %w", err) } if index.Name == idx && index.Queryable { return true, nil } } if err := cursor.Err(); err != nil { return false, fmt.Errorf("cursor error: %w", err) } return false, nil } // Creates a vector search index. This function blocks until the index has been // created. func CreateVectorSearchIndex( ctx context.Context, coll *mongo.Collection, idxName string, voyageAIEmbeddingDim int, similarityAlgorithm string, ) (string, error) { type vectorField struct { Type string `bson:"type,omitempty"` Path string `bson:"path,omitempty"` NumDimensions int `bson:"numDimensions,omitempty"` Similarity string `bson:"similarity,omitempty"` } fields := []vectorField{ { Type: "vector", Path: "embeddings", NumDimensions: voyageAIEmbeddingDim, Similarity: similarityAlgorithm, }, { Type: "filter", Path: "metadata.author", }, { Type: "filter", Path: "metadata.type", }, } def := struct { Fields []vectorField `bson:"fields"` }{ Fields: fields, } log.Println("Creating vector search index...") view := coll.SearchIndexes() siOpts := options.SearchIndexes().SetName(idxName).SetType("vectorSearch") searchName, err := view.CreateOne(ctx, mongo.SearchIndexModel{Definition: def, Options: siOpts}) if err != nil { return "", fmt.Errorf("failed to create the search index: %w", err) } // Awaits the creation of the index var doc bson.Raw for doc == nil { cursor, err := view.List(ctx, options.SearchIndexes().SetName(searchName)) if err != nil { return "", fmt.Errorf("failed to list search indexes: %w", err) } if !cursor.Next(ctx) { break } name := cursor.Current.Lookup("name").StringValue() queryable := cursor.Current.Lookup("queryable").Boolean() if name == searchName && queryable { doc = cursor.Current } else { time.Sleep(5 * time.Second) } } return searchName, nil }
main() 함수에서 앞의 함수를 호출하여 벡터 저장 컬렉션 및 인덱스 만듭니다. main() 함수 끝에 다음 코드를 추가합니다.
// SearchIndexExists will return true if the provided index is defined for the // collection. This operation blocks until the search completes. if ok, _ := SearchIndexExists(context.Background(), coll, indexName); !ok { // Creates the vector store collection err = client.Database(databaseName).CreateCollection(context.Background(), collectionName) if err != nil { log.Fatalf("failed to create vector store collection: %v", err) } _, err = CreateVectorSearchIndex(context.Background(), coll, indexName, voyageAIEmbeddingDim, similarityAlgorithm) if err != nil { log.Fatalf("failed to create index: %v", err) } log.Println("Successfully created vector search index.") } else { log.Println("Vector search index already exists.") }
파일 저장한 후 다음 명령을 실행 MongoDB Vector Search 인덱스 생성합니다.
go run main.go
Checking if search index exists. Creating vector search index... Successfully created vector search index.
팁
main.go를 실행한 후 클러스터의 langchaingo_db.test 컬렉션으로 이동하여 Atlas UI에서 벡터 검색 인덱스를 볼 수 있습니다.
Vector Search 쿼리 실행
이 섹션에서는 벡터화된 데이터에서 실행할 수 있는 다양한 쿼리를 보여줍니다. 이제 인덱스를 만들었으므로 벡터 검색 쿼리를 실행할 수 있습니다.
Basic Semantic Search 또는 Semantic Search with Filtering 탭을 선택하여 해당 코드를 확인합니다.
메인 함수에 다음 코드를 추가한 다음 파일을 저장하세요.
시맨틱 검색은 쿼리와 의미적으로 관련된 정보를 조회합니다. 다음 코드는 SimilaritySearch() 메서드를 사용하여 "Prevent
weeds" 문자열에 대한 시맨틱 검색을 수행하고 결과를 첫 번째 문서로 제한합니다.
// Performs basic semantic search docs, err := store.SimilaritySearch(context.Background(), "Prevent weeds", 1) if err != nil { fmt.Println("Error performing search:", err) } fmt.Println("Semantic Search Results:", docs)
다음 명령을 실행하여 쿼리를 실행합니다.
go run main.go
Semantic Search Results: [{For a natural lawn, selection of the right grass type suitable for your climate is crucial. Balanced watering, generally 1 to 1.5 inches per week, is important; overwatering invites disease. Opt for organic fertilizers over synthetic versions to provide necessary nutrients and improve soil structure. Regular lawn aeration helps root growth and prevents soil compaction. Practice natural pest control and consider overseeding to maintain a dense sward, which naturally combats weeds and pest. map[author:C type:post] 0.69752026}]
인덱스된 필드를 컬렉션의 다른 값과 비교하는 MQL 일치 표현식을 사용하여 데이터를 사전 필터링할 수 있습니다. 필터링하려는 모든 메타데이터 필드를 filter 유형으로 인덱싱해야 합니다. 자세한 내용은 벡터 검색을 위해 필드를 인덱싱하는 방법을 참조하세요.
메인 함수에 다음 코드를 추가한 다음 파일을 저장하세요.
다음 코드에서는 SimilaritySearch() 메서드를 사용하여 문자열 "Tulip care"에 대한 시맨틱 검색을 수행합니다. 다음 매개변수를 지정합니다.
1으로 반환할 문서 수점수 임계값이
0.60입니다.
필터하다 metadata.type:
post 와 일치하고 점수 임계값을 포함하는 문서 반환합니다.
// Performs semantic search with metadata filter filter := map[string]interface{}{ "metadata.type": "post", } docs, err := store.SimilaritySearch(context.Background(), "Tulip care", 1, vectorstores.WithScoreThreshold(0.60), vectorstores.WithFilters(filter)) if err != nil { fmt.Println("Error performing search:", err) } fmt.Println("Filter Search Results:", docs)
다음 명령을 실행하여 쿼리를 실행합니다.
go run main.go
Filter Search Results: [{Proper tuber planting involves site selection, proper timing, and exceptional care. Choose spots with well-drained soil and adequate sun exposure. Tubers are generally planted in spring, but depending on the plant, timing varies. Always plant with the eyes facing upward at a depth two to three times the tuber's height. Ensure 4 inch spacing between small tubers, expand to 12 inches for large ones. Adequate moisture is needed, yet do not overwater. Mulching can help preserve moisture and prevent weed growth. map[author:A type:post] 0.64432365}]
데이터에 대한 질문에 답변
이 섹션에서는 MongoDB Vector Search 및 LangChainGo를 사용하여 RAG를 구현 설명합니다. 이제 MongoDB Vector Search를 사용하여 의미적으로 유사한 문서를 조회 했으므로, 다음 코드 예시 사용하여 LLM이 MongoDB Vector Search에서 반환된 문서에 대한 질문에 답변 하도록 프롬프트를 표시합니다.
메인 함수 끝에 다음 코드를 추가하고 파일 저장합니다.
이 코드는 다음을 수행합니다.
의미론적으로 유사한 문서를 쿼리 리트리버로 MongoDB Vector Search를 인스턴스화합니다.
검색된 문서를 쿼리 의 컨텍스트로 사용하도록 LLM에 지시하는 LangChainGo 프롬프트 템플릿을 정의합니다. LangChainGo는 이러한 문서를
{{.context}}입력 변수에, 쿼리{{.question}}변수에 채웁니다.OpenAI의 채팅 모델을 사용하여 제공된 프롬프트 템플릿에 따라 컨텍스트 인식 응답을 생성하는 체인을 구성합니다.
프롬프트와 검색기를 사용하여 관련 컨텍스트를 수집하고 초보자를 위한 그림에 관한 샘플 쿼리를 체인에 보냅니다.
LLM의 응답과 컨텍스트로 사용된 문서를 반환하고 출력합니다.
// Implements RAG to answer questions on your data optionsVector := []vectorstores.Option{ vectorstores.WithScoreThreshold(0.60), } retriever := vectorstores.ToRetriever(&store, 1, optionsVector...) // Loads OpenAI API key from environment openaiApiKey := os.Getenv("OPENAI_API_KEY") if openaiApiKey == "" { log.Fatal("Set your OPENAI_API_KEY environment variable in the .env file") } // Creates an OpenAI LLM client llm, err := openai.New(openai.WithToken(openaiApiKey), openai.WithModel("gpt-4o"), openai.WithEmbeddingModel("voyage-3-large")) if err != nil { log.Fatalf("Failed to create an LLM client: %v", err) } prompt := prompts.NewPromptTemplate( `Answer the question based on the following context: {{.context}} Question: {{.question}}`, []string{"context", "question"}, ) llmChain := chains.NewLLMChain(llm, prompt) ctx := context.Background() const question = "How do I get started painting?" documents, err := retriever.GetRelevantDocuments(ctx, question) if err != nil { log.Fatalf("Failed to retrieve documents: %v", err) } var contextBuilder strings.Builder for i, document := range documents { contextBuilder.WriteString(fmt.Sprintf("Document %d: %s\n", i+1, document.PageContent)) } contextStr := contextBuilder.String() inputs := map[string]interface{}{ "context": contextStr, "question": question, } out, err := chains.Call(ctx, llmChain, inputs) if err != nil { log.Fatalf("Failed to run LLM chain: %v", err) } log.Println("Source documents:") for i, doc := range documents { log.Printf("Document %d: %s\n", i+1, doc.PageContent) } responseText, ok := out["text"].(string) if !ok { log.Println("Unexpected response type") return } log.Println("Question:", question) log.Println("Generated Answer:", responseText)
다음 명령을 실행하여 파일을 실행합니다.
파일을 저장한 후 다음 명령을 실행합니다. 생성된 응답은 다를 수 있습니다.
go run main.go
Source documents: Document 1: "Successful oil painting necessitates patience, proper equipment, and technique. Begin with a carefully prepared, primed canvas. Sketch your composition lightly before applying paint. Use high-quality brushes and oils to create vibrant, long-lasting artworks. Remember to paint 'fat over lean,' meaning each subsequent layer should contain more oil to prevent cracking. Allow each layer to dry before applying another. Clean your brushes often and avoid solvents that might damage them. Finally, always work in a well-ventilated space." Question: How do I get started painting? Generated Answer: To get started painting, you should begin with a carefully prepared, primed canvas. Sketch your composition lightly before applying paint. Use high-quality brushes and oils to create vibrant, long-lasting artworks. Remember to paint 'fat over lean,' meaning each subsequent layer should contain more oil to prevent cracking. Allow each layer to dry before applying another. Clean your brushes often and avoid solvents that might damage them. Finally, always work in a well-ventilated space.
전제 조건
이 튜토리얼을 완료하려면 다음 조건을 충족해야 합니다.
다음 MongoDB cluster 유형 중 하나입니다.
MongoDB 버전 6.0.11을 실행하는 Atlas 클러스터 7.0.2 또는 그 이상. IP 주소가 Atlas 프로젝트의 액세스 목록에 포함되어 있는지 확인하세요.
Atlas CLI 사용하여 생성된 로컬 Atlas 배포서버 입니다. 자세히 학습 로컬 Atlas 배포 만들기를 참조하세요.
검색 및 벡터 검색이 설치된 MongoDB Community 또는 Enterprise 클러스터.
OpenAI API 키입니다. API 요청에 사용할 수 있는 크레딧이 있는 OpenAI 계정이 있어야 합니다. OpenAI 계정 등록에 대해 자세히 학습하려면 OpenAI API 웹사이트를 참조하세요.
Go 프로젝트를 실행하기 위한 터미널 및 코드 편집기입니다.
컴퓨터에 고 (Go) 설치 합니다.
환경 설정
이 튜토리얼을 실행하려면 먼저 환경을 설정해야 합니다. 다음 단계를 완료하여 환경을 설정하세요.
환경 변수를 초기화합니다.
langchaingo-mongodb 프로젝트 디렉토리 에서 .env 파일 만들고 다음 줄을 추가합니다.
OPENAI_API_KEY="<api-key>" MONGODB_URI="<connection-string>"
자리 표시자 값을 MongoDB cluster 의 OpenAI API 키 및 SRV 연결 문자열 로 바꿉니다. 연결 문자열 다음 형식을 사용해야 합니다.
mongodb+srv://<username>:<password>@<cluster-name>.mongodb.net/<dbname>
MongoDB 벡터 저장소로 사용
이 섹션에서는 사용자 지정 데이터를 MongoDB 에 로드하고 MongoDB 벡터 저장소라고도 하는 벡터 데이터베이스로 인스턴스화하는 비동기 함수를 정의합니다.
다음 종속성을 가져옵니다.
main.go 파일 의 맨 위에 다음 가져오기를 추가합니다.
package main import ( "context" "log" "os" "github.com/joho/godotenv" "github.com/tmc/langchaingo/embeddings" "github.com/tmc/langchaingo/llms/openai" "github.com/tmc/langchaingo/schema" "github.com/tmc/langchaingo/vectorstores/mongovector" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" )
벡터 저장소 세부 사항을 정의합니다.
다음 코드는 이러한 작업을 수행합니다.
다음을 지정하여 Atlas를 벡터 저장소로 구성합니다.
다음을 수행하여 사용자 지정 데이터를 준비합니다.
각 문서에 대한 텍스트를 정의합니다.
LangChainGo의
mongovector패키지를 사용하여 텍스트의 임베딩을 생성합니다. 이 패키지는 MongoDB에 문서 임베딩을 저장하고 저장된 임베딩을 검색할 수 있게 합니다.텍스트, 임베딩, 메타데이터를 포함하는 문서를 생성합니다.
구성된 문서를 Atlas에 삽입한 후, 벡터 저장소를 인스턴스화합니다.
다음 코드를 main.go 파일에 붙여넣습니다.
// Defines the document structure type Document struct { PageContent string `bson:"text"` Embedding []float32 `bson:"embedding"` Metadata map[string]string `bson:"metadata"` } func main() { const ( openAIEmbeddingModel = "text-embedding-3-small" openAIEmbeddingDim = 1536 similarityAlgorithm = "dotProduct" indexName = "vector_index" databaseName = "langchaingo_db" collectionName = "test" ) if err := godotenv.Load(); err != nil { log.Fatal("No .env file found") } // Loads the MongoDB URI from environment uri := os.Getenv("MONGODB_URI") if uri == "" { log.Fatal("Set your 'MONGODB_URI' environment variable in the .env file") } // Loads the API key from environment apiKey := os.Getenv("OPENAI_API_KEY") if apiKey == "" { log.Fatal("Set your OPENAI_API_KEY environment variable in the .env file") } // Connects to MongoDB client, err := mongo.Connect(options.Client().ApplyURI(uri)) if err != nil { log.Fatalf("Failed to connect to server: %v", err) } defer func() { if err := client.Disconnect(context.Background()); err != nil { log.Fatalf("Error disconnecting the client: %v", err) } }() log.Println("Connected to MongoDB.") // Selects the database and collection coll := client.Database(databaseName).Collection(collectionName) // Creates an OpenAI LLM embedder client llm, err := openai.New(openai.WithEmbeddingModel(openAIEmbeddingModel)) if err != nil { log.Fatalf("Failed to create an embedder client: %v", err) } // Creates an embedder from the embedder client embedder, err := embeddings.NewEmbedder(llm) if err != nil { log.Fatalf("Failed to create an embedder: %v", err) } // Creates a new MongoDB vector store store := mongovector.New(coll, embedder, mongovector.WithIndex(indexName), mongovector.WithPath("embeddings")) // Checks if the collection is empty, and if empty, adds documents to the MongoDB database vector store if isCollectionEmpty(coll) { documents := []schema.Document{ { PageContent: "Proper tuber planting involves site selection, proper timing, and exceptional care. Choose spots with well-drained soil and adequate sun exposure. Tubers are generally planted in spring, but depending on the plant, timing varies. Always plant with the eyes facing upward at a depth two to three times the tuber's height. Ensure 4 inch spacing between small tubers, expand to 12 inches for large ones. Adequate moisture is needed, yet do not overwater. Mulching can help preserve moisture and prevent weed growth.", Metadata: map[string]any{ "author": "A", "type": "post", }, }, { PageContent: "Successful oil painting necessitates patience, proper equipment, and technique. Begin with a carefully prepared, primed canvas. Sketch your composition lightly before applying paint. Use high-quality brushes and oils to create vibrant, long-lasting artworks. Remember to paint 'fat over lean,' meaning each subsequent layer should contain more oil to prevent cracking. Allow each layer to dry before applying another. Clean your brushes often and avoid solvents that might damage them. Finally, always work in a well-ventilated space.", Metadata: map[string]any{ "author": "B", "type": "post", }, }, { PageContent: "For a natural lawn, selection of the right grass type suitable for your climate is crucial. Balanced watering, generally 1 to 1.5 inches per week, is important; overwatering invites disease. Opt for organic fertilizers over synthetic versions to provide necessary nutrients and improve soil structure. Regular lawn aeration helps root growth and prevents soil compaction. Practice natural pest control and consider overseeding to maintain a dense sward, which naturally combats weeds and pest.", Metadata: map[string]any{ "author": "C", "type": "post", }, }, } _, err := store.AddDocuments(context.Background(), documents) if err != nil { log.Fatalf("Error adding documents: %v", err) } log.Printf("Successfully added %d documents to the collection.\n", len(documents)) } else { log.Println("Documents already exist in the collection, skipping document addition.") } } func isCollectionEmpty(coll *mongo.Collection) bool { count, err := coll.EstimatedDocumentCount(context.Background()) if err != nil { log.Fatalf("Failed to count documents in the collection: %v", err) } return count == 0 }
고 (Go) 프로젝트 실행합니다.
파일 저장한 후 다음 명령을 실행 데이터를 MongoDB 에 로드합니다.
go run main.go
Connected to MongoDB Atlas. Successfully added 3 documents to the collection.
팁
main.go를 실행 후 Atlas 사용하는 경우 Atlas UI의 langchaingo_db.test 네임스페이스 로 이동하여 벡터 임베딩을 확인할 수 있습니다.
MongoDB Vector Search 인덱스 만들기
벡터 저장 에서 벡터 검색 쿼리를 활성화 하려면 langchaingo_db.test 컬렉션 에 MongoDB Vector Search 인덱스 만듭니다.
main.go 파일 의 맨 위에 다음 가져오기를 추가합니다.
import ( // Other imports... "fmt" "time" "go.mongodb.org/mongo-driver/v2/bson" )
main() 함수 외부의 main.go 파일 에서 다음 함수를 정의합니다. 다음 함수는 MongoDB 컬렉션 에 대한 벡터 검색 인덱스 생성하고 관리 .
SearchIndexExists함수는 지정된 이름의 검색 인덱스 존재하고 쿼리 가능한지 확인합니다.CreateVectorSearchIndex함수는 지정된 컬렉션 에 벡터 검색 인덱스 만듭니다. 이 함수는 인덱스 생성되어 쿼리할 수 있을 때까지 차단됩니다.
// Checks if the search index exists func SearchIndexExists(ctx context.Context, coll *mongo.Collection, idx string) (bool, error) { log.Println("Checking if search index exists.") view := coll.SearchIndexes() siOpts := options.SearchIndexes().SetName(idx).SetType("vectorSearch") cursor, err := view.List(ctx, siOpts) if err != nil { return false, fmt.Errorf("failed to list search indexes: %w", err) } for cursor.Next(ctx) { index := struct { Name string `bson:"name"` Queryable bool `bson:"queryable"` }{} if err := cursor.Decode(&index); err != nil { return false, fmt.Errorf("failed to decode search index: %w", err) } if index.Name == idx && index.Queryable { return true, nil } } if err := cursor.Err(); err != nil { return false, fmt.Errorf("cursor error: %w", err) } return false, nil } // Creates a vector search index. This function blocks until the index has been // created. func CreateVectorSearchIndex( ctx context.Context, coll *mongo.Collection, idxName string, openAIEmbeddingDim int, similarityAlgorithm string, ) (string, error) { type vectorField struct { Type string `bson:"type,omitempty"` Path string `bson:"path,omitempty"` NumDimensions int `bson:"numDimensions,omitempty"` Similarity string `bson:"similarity,omitempty"` } fields := []vectorField{ { Type: "vector", Path: "embeddings", NumDimensions: openAIEmbeddingDim, Similarity: similarityAlgorithm, }, { Type: "filter", Path: "metadata.author", }, { Type: "filter", Path: "metadata.type", }, } def := struct { Fields []vectorField `bson:"fields"` }{ Fields: fields, } log.Println("Creating vector search index...") view := coll.SearchIndexes() siOpts := options.SearchIndexes().SetName(idxName).SetType("vectorSearch") searchName, err := view.CreateOne(ctx, mongo.SearchIndexModel{Definition: def, Options: siOpts}) if err != nil { return "", fmt.Errorf("failed to create the search index: %w", err) } // Awaits the creation of the index var doc bson.Raw for doc == nil { cursor, err := view.List(ctx, options.SearchIndexes().SetName(searchName)) if err != nil { return "", fmt.Errorf("failed to list search indexes: %w", err) } if !cursor.Next(ctx) { break } name := cursor.Current.Lookup("name").StringValue() queryable := cursor.Current.Lookup("queryable").Boolean() if name == searchName && queryable { doc = cursor.Current } else { time.Sleep(5 * time.Second) } } return searchName, nil }
main() 함수에서 앞의 함수를 호출하여 벡터 저장 컬렉션 및 인덱스 만듭니다. main() 함수 끝에 다음 코드를 추가합니다.
// SearchIndexExists will return true if the provided index is defined for the // collection. This operation blocks until the search completes. if ok, _ := SearchIndexExists(context.Background(), coll, indexName); !ok { // Creates the vector store collection err = client.Database(databaseName).CreateCollection(context.Background(), collectionName) if err != nil { log.Fatalf("failed to create vector store collection: %v", err) } _, err = CreateVectorSearchIndex(context.Background(), coll, indexName, openAIEmbeddingDim, similarityAlgorithm) if err != nil { log.Fatalf("failed to create index: %v", err) } log.Println("Successfully created vector search index.") } else { log.Println("Vector search index already exists.") }
파일 저장한 후 다음 명령을 실행 MongoDB Vector Search 인덱스 생성합니다.
go run main.go
Checking if search index exists. Creating vector search index... Successfully created vector search index.
팁
main.go를 실행한 후 클러스터의 langchaingo_db.test 컬렉션으로 이동하여 Atlas UI에서 벡터 검색 인덱스를 볼 수 있습니다.
Vector Search 쿼리 실행
이 섹션에서는 벡터화된 데이터에서 실행할 수 있는 다양한 쿼리를 보여줍니다. 이제 인덱스를 만들었으므로 벡터 검색 쿼리를 실행할 수 있습니다.
Basic Semantic Search 또는 Semantic Search with Filtering 탭을 선택하여 해당 코드를 확인합니다.
메인 함수에 다음 코드를 추가한 다음 파일을 저장하세요.
시맨틱 검색은 쿼리와 의미적으로 관련된 정보를 조회합니다. 다음 코드는 SimilaritySearch() 메서드를 사용하여 "Prevent
weeds" 문자열에 대한 시맨틱 검색을 수행하고 결과를 첫 번째 문서로 제한합니다.
// Performs basic semantic search docs, err := store.SimilaritySearch(context.Background(), "Prevent weeds", 1) if err != nil { fmt.Println("Error performing search:", err) } fmt.Println("Semantic Search Results:", docs)
다음 명령을 실행하여 쿼리를 실행합니다.
go run main.go
Semantic Search Results: [{For a natural lawn, selection of the right grass type suitable for your climate is crucial. Balanced watering, generally 1 to 1.5 inches per week, is important; overwatering invites disease. Opt for organic fertilizers over synthetic versions to provide necessary nutrients and improve soil structure. Regular lawn aeration helps root growth and prevents soil compaction. Practice natural pest control and consider overseeding to maintain a dense sward, which naturally combats weeds and pest. map[author:C type:post] 0.69752026}]
인덱스된 필드를 컬렉션의 다른 값과 비교하는 MQL 일치 표현식을 사용하여 데이터를 사전 필터링할 수 있습니다. 필터링하려는 모든 메타데이터 필드를 filter 유형으로 인덱싱해야 합니다. 자세한 내용은 벡터 검색을 위해 필드를 인덱싱하는 방법을 참조하세요.
메인 함수에 다음 코드를 추가한 다음 파일을 저장하세요.
다음 코드에서는 SimilaritySearch() 메서드를 사용하여 문자열 "Tulip care"에 대한 시맨틱 검색을 수행합니다. 다음 매개변수를 지정합니다.
1으로 반환할 문서 수점수 임계값이
0.60입니다.
필터하다 metadata.type:
post 와 일치하고 점수 임계값을 포함하는 문서 반환합니다.
// Performs semantic search with metadata filter filter := map[string]interface{}{ "metadata.type": "post", } docs, err := store.SimilaritySearch(context.Background(), "Tulip care", 1, vectorstores.WithScoreThreshold(0.60), vectorstores.WithFilters(filter)) if err != nil { fmt.Println("Error performing search:", err) } fmt.Println("Filter Search Results:", docs)
다음 명령을 실행하여 쿼리를 실행합니다.
go run main.go
Filter Search Results: [{Proper tuber planting involves site selection, proper timing, and exceptional care. Choose spots with well-drained soil and adequate sun exposure. Tubers are generally planted in spring, but depending on the plant, timing varies. Always plant with the eyes facing upward at a depth two to three times the tuber's height. Ensure 4 inch spacing between small tubers, expand to 12 inches for large ones. Adequate moisture is needed, yet do not overwater. Mulching can help preserve moisture and prevent weed growth. map[author:A type:post] 0.64432365}]
데이터에 대한 질문에 답변
이 섹션에서는 MongoDB Vector Search 및 LangChainGo를 사용하여 RAG를 구현 설명합니다. 이제 MongoDB Vector Search를 사용하여 의미적으로 유사한 문서를 조회 했으므로, 다음 코드 예시 사용하여 LLM이 MongoDB Vector Search에서 반환된 문서에 대한 질문에 답변 하도록 프롬프트를 표시합니다.
메인 함수 끝에 다음 코드를 추가하고 파일 저장합니다.
이 코드는 다음을 수행합니다.
의미론적으로 유사한 문서를 쿼리 리트리버로 MongoDB Vector Search를 인스턴스화합니다.
검색된 문서를 쿼리 의 컨텍스트로 사용하도록 LLM에 지시하는 LangChainGo 프롬프트 템플릿을 정의합니다. LangChainGo는 이러한 문서를
{{.context}}입력 변수에, 쿼리{{.question}}변수에 채웁니다.OpenAI의 채팅 모델을 사용하여 제공된 프롬프트 템플릿에 따라 컨텍스트 인식 응답을 생성하는 체인을 구성합니다.
프롬프트와 검색기를 사용하여 관련 컨텍스트를 수집하고 초보자를 위한 그림에 관한 샘플 쿼리를 체인에 보냅니다.
LLM의 응답과 컨텍스트로 사용된 문서를 반환하고 출력합니다.
// Implements RAG to answer questions on your data optionsVector := []vectorstores.Option{ vectorstores.WithScoreThreshold(0.60), } retriever := vectorstores.ToRetriever(&store, 1, optionsVector...) prompt := prompts.NewPromptTemplate( `Answer the question based on the following context: {{.context}} Question: {{.question}}`, []string{"context", "question"}, ) llmChain := chains.NewLLMChain(llm, prompt) ctx := context.Background() const question = "How do I get started painting?" documents, err := retriever.GetRelevantDocuments(ctx, question) if err != nil { log.Fatalf("Failed to retrieve documents: %v", err) } var contextBuilder strings.Builder for i, document := range documents { contextBuilder.WriteString(fmt.Sprintf("Document %d: %s\n", i+1, document.PageContent)) } contextStr := contextBuilder.String() inputs := map[string]interface{}{ "context": contextStr, "question": question, } out, err := chains.Call(ctx, llmChain, inputs) if err != nil { log.Fatalf("Failed to run LLM chain: %v", err) } log.Println("Source documents:") for i, doc := range documents { log.Printf("Document %d: %s\n", i+1, doc.PageContent) } responseText, ok := out["text"].(string) if !ok { log.Println("Unexpected response type") return } log.Println("Question:", question) log.Println("Generated Answer:", responseText)
다음 명령을 실행하여 파일을 실행합니다.
파일을 저장한 후 다음 명령을 실행합니다. 생성된 응답은 다를 수 있습니다.
go run main.go
Source documents: Document 1: "Successful oil painting necessitates patience, proper equipment, and technique. Begin with a carefully prepared, primed canvas. Sketch your composition lightly before applying paint. Use high-quality brushes and oils to create vibrant, long-lasting artworks. Remember to paint 'fat over lean,' meaning each subsequent layer should contain more oil to prevent cracking. Allow each layer to dry before applying another. Clean your brushes often and avoid solvents that might damage them. Finally, always work in a well-ventilated space." Question: How do I get started painting? Generated Answer: To get started painting, you should begin with a carefully prepared, primed canvas. Sketch your composition lightly before applying paint. Use high-quality brushes and oils to create vibrant, long-lasting artworks. Remember to paint 'fat over lean,' meaning each subsequent layer should contain more oil to prevent cracking. Allow each layer to dry before applying another. Clean your brushes often and avoid solvents that might damage them. Finally, always work in a well-ventilated space.
이 튜토리얼을 완료했다면 MongoDB Vector Search와 LangChainGo를 성공적으로 통합하여 RAG 애플리케이션 빌드 되었습니다. 다음을 완료했습니다:
애플리케이션 지원 데 필요한 환경 시작 및 구성
사용자 지정 데이터를 MongoDB 에 저장하고 MongoDB 벡터 저장 로 인스턴스화
데이터에 MongoDB Vector Search 인덱스 구축하여 시맨틱 검색 기능을 지원합니다.
벡터 임베딩을 사용하여 의미적으로 관련성 있는 데이터 조회
메타데이터 필터를 통합하여 검색 결과 향상
데이터를 기반으로 질문에 의미 있는 답변을 제공하기 위해 MongoDB Vector Search를 사용하여 RAG 워크플로를 구현했습니다.
다음 단계
MongoDB Vector Search를 시작하는 방법에 대해 자세히 학습하려면 MongoDB Vector Search 빠른 시작을 참조한 다음 드롭다운 메뉴에서 Go 를 선택합니다.
벡터 임베딩에 대해 자세히 알아보려면 벡터 임베딩 생성 방법을 참조하고 드롭다운 메뉴에서 Go를 선택하세요.
LangChainGo와 Hugging Face를 통합하는 방법을 학습하려면 MongoDB 사용한 RAG(검색 보강 생성)를 참조하세요.
API 키나 크레딧 없이 RAG를 구현 방법을학습 MongoDB Vector Search를 사용하여 로컬 RAG 구현 구축하기를 참조하세요.
MongoDB는 다음과 같은 개발자 리소스도 제공합니다.
팁
LangChainGo, OpenAI, MongoDB 통합에 대해 자세히 학습하려면 OpenAI 임베딩을 통해 MongoDB Atlas 벡터 스토어로 사용하기를 참조하세요.