LangChain Go (langchaingo) is the Go port of the LangChain framework, providing high-level abstractions for building applications powered by large language models. It offers a composable set of interfaces for LLMs, embeddings, vector stores, document loaders, and chains — letting Go developers build RAG pipelines, chatbots, and agent systems without implementing boilerplate integration code for each AI provider.
As a practical example, a semantic search platform can use langchaingo to wire together Ollama models for text embedding, Chroma as a vector store, and custom document loaders for ingesting data from database tables. The framework’s provider-agnostic interfaces mean the same application code works whether embeddings come from Ollama running locally or from a cloud API — only the initialization changes.
This topic covers the langchaingo architecture: the core interfaces (LLM, Embedder, VectorStore, Retriever), how to configure providers (Ollama, OpenAI, Chroma), building RAG chains that connect document retrieval to LLM generation, and the practical patterns for deploying langchaingo applications in Go services. The framework’s design philosophy follows Go idioms — explicit error handling, interface-based composition, and minimal magic — which makes it a natural fit for systems-level AI applications.
LangChain Go defines core interfaces — llms.Model for text generation, embeddings.Embedder for vector embeddings, vectorstores.VectorStore for similarity search — that abstract away provider-specific details. An application written against these interfaces can switch from Ollama to OpenAI to Anthropic by changing only the initialization code. This is achieved through Go’s structural typing: any type that implements the interface methods satisfies the interface, with no explicit registration required.
Chains are the primary composition primitive in langchaingo. A chain takes input, processes it through a sequence of steps (prompt formatting, LLM call, output parsing), and returns structured output. The chains.NewRetrievalQAFromLLM chain, for example, combines a retriever, a prompt template, and an LLM into a single callable unit that answers questions using retrieved context. Chains can be nested: the output of one chain becomes the input to another, building complex pipelines from simple, testable components.
Raw data must be transformed into documents before it can be embedded and stored. Document loaders read data from sources (files, databases, APIs) into schema.Document objects. Text splitters then divide large documents into chunks that fit within embedding model context windows. The chunking strategy significantly impacts retrieval quality: too-large chunks dilute relevance signals, while too-small chunks lose context. LangChain Go provides recursive character splitting, token-based splitting, and custom splitter interfaces.
The vectorstores.VectorStore interface provides AddDocuments and SimilaritySearch methods that work across backends. langchaingo ships with implementations for Chroma, Pinecone, Weaviate, and pgvector among others. Each implementation handles the specifics of connecting to the backend, formatting embedding vectors, and translating search parameters. The abstraction means switching vector stores is a configuration change, not a code rewrite — critical when evaluating different backends for cost, latency, or feature requirements.
Ollama provides a local inference server for running LLMs and embedding models without cloud API costs or data privacy concerns. langchaingo’s Ollama provider connects to the local Ollama server over HTTP, supporting both text generation (llms/ollama) and embeddings (embeddings/ollama). This enables fully offline development and testing of LLM applications, with the option to swap to cloud providers for production deployment. Model selection is a string parameter — switching from llama3 to mistral requires changing one line.
A langchaingo RAG application follows a standard three-phase architecture: ingestion, indexing, and retrieval-augmented generation.
Documents are loaded from source systems using document loaders. For a semantic search platform, this means reading records from database tables and converting them to schema.Document objects with content text and metadata (source table, record ID, timestamp). A text splitter divides large documents into chunks of 500-1000 tokens with 50-100 token overlap. The overlap ensures that information spanning chunk boundaries isn’t lost during retrieval.
Each document chunk is passed to the embedder, which calls the configured model (e.g., Ollama’s nomic-embed-text) to generate a vector representation. The document and its embedding are then stored in the vector store. langchaingo batches embedding requests to reduce round trips — rather than embedding one document at a time, it sends batches of 100+ documents in a single API call. The vector store handles deduplication based on document metadata, so re-indexing the same source data is idempotent.
When a user submits a query, the RAG chain: (1) embeds the query using the same model used for document indexing, (2) performs a similarity search against the vector store to retrieve the top-K relevant chunks, (3) formats a prompt that includes the retrieved chunks as context along with the user’s question, and (4) sends the formatted prompt to the LLM for answer generation. The LLM sees both the question and the relevant context, enabling it to ground its answer in the retrieved documents rather than relying solely on its parametric knowledge.
A complete RAG pipeline using local Ollama for embeddings and generation, with Chroma as the vector store.
package main
import (
"context"
"fmt"
"log"
"github.com/tmc/langchaingo/chains"
"github.com/tmc/langchaingo/embeddings"
"github.com/tmc/langchaingo/llms/ollama"
"github.com/tmc/langchaingo/schema"
"github.com/tmc/langchaingo/vectorstores"
"github.com/tmc/langchaingo/vectorstores/chroma"
)
func main() {
ctx := context.Background()
// Initialize Ollama LLM for text generation
llm, err := ollama.New(ollama.WithModel("llama3"))
if err != nil {
log.Fatal(err)
}
// Initialize Ollama embedder for vector embeddings
embedder, err := embeddings.NewEmbedder(
ollama.New(ollama.WithModel("nomic-embed-text")),
)
if err != nil {
log.Fatal(err)
}
// Connect to Chroma vector store
store, err := chroma.New(
chroma.WithChromaURL("http://localhost:8000"),
chroma.WithEmbedder(embedder),
chroma.WithNameSpace("mydb-docs"),
)
if err != nil {
log.Fatal(err)
}
// Index documents
docs := []schema.Document{
{PageContent: "ExampleDB uses B+ trees for primary key indexing...", Metadata: map[string]any{"source": "architecture.md"}},
{PageContent: "The query planner optimizes JOIN operations...", Metadata: map[string]any{"source": "query-engine.md"}},
}
_, err = store.AddDocuments(ctx, docs)
if err != nil {
log.Fatal(err)
}
// Create a retrieval QA chain
qaChain := chains.NewRetrievalQAFromLLM(
llm,
vectorstores.ToRetriever(store, 5), // top-5 results
)
// Ask a question
answer, err := chains.Run(ctx, qaChain, "How does ExampleDB handle indexing?")
if err != nil {
log.Fatal(err)
}
fmt.Println(answer)
}
A document loader that reads records from a database table and converts them to langchaingo documents.
package loaders
import (
"context"
"fmt"
"github.com/tmc/langchaingo/schema"
"github.com/tmc/langchaingo/textsplitter"
)
type DatabaseLoader struct {
client *DatabaseClient
tableName string
columns []string // columns to concatenate as document content
}
func NewDatabaseLoader(client *DatabaseClient, table string, cols []string) *DatabaseLoader {
return &DatabaseLoader{client: client, tableName: table, columns: cols}
}
func (l *DatabaseLoader) Load(ctx context.Context) ([]schema.Document, error) {
rows, err := l.client.ScanTable(ctx, l.tableName)
if err != nil {
return nil, fmt.Errorf("scan table %s: %w", l.tableName, err)
}
var docs []schema.Document
for _, row := range rows {
// Concatenate selected columns into page content
var content string
for _, col := range l.columns {
if val, ok := row[col]; ok {
content += fmt.Sprintf("%s: %v\n", col, val)
}
}
docs = append(docs, schema.Document{
PageContent: content,
Metadata: map[string]any{
"table": l.tableName,
"row_id": row["_id"],
},
})
}
return docs, nil
}
func (l *DatabaseLoader) LoadAndSplit(ctx context.Context) ([]schema.Document, error) {
docs, err := l.Load(ctx)
if err != nil {
return nil, err
}
splitter := textsplitter.NewRecursiveCharacter(
textsplitter.WithChunkSize(800),
textsplitter.WithChunkOverlap(100),
)
return textsplitter.SplitDocuments(splitter, docs)
}
A semantic search platform can use langchaingo to build a semantic search interface over data stored in database tables. Users describe what they’re looking for in natural language, and the system retrieves relevant records using vector similarity rather than exact keyword matching. This is particularly valuable for searching unstructured text fields like descriptions, comments, and log messages where exact keyword queries often miss relevant results.
By combining the RAG chain with a chat history memory, langchaingo enables a conversational assistant that can answer follow-up questions with context. The memory stores previous turns of the conversation, so the model understands references like “it,” “that table,” or “the same query.” The retriever fetches fresh context for each turn, grounding the conversation in actual data rather than the model’s recollection of earlier retrieval results.
langchaingo chains can generate documentation from source code and database schemas. A loader reads Go source files and SQL schemas, a chain formats a prompt asking the LLM to explain each component, and the output is rendered as markdown documentation. By retrieving related existing documentation during generation, the chain ensures consistency in terminology and style across the generated docs.
nomic-embed-text but queries are embedded with all-minilm, similarity scores are meaningless. Always use the same model for both operations and store the model name as metadata in the vector store for verification.vectorstores.WithFilters(map[string]any{"source": "api-docs"})) to constrain search to relevant subsets before ranking by similarity.AddDocuments batches automatically, but custom code that calls the embedder directly should batch requests explicitly.ollama pull model-name before starting the application, and set appropriate HTTP client timeouts (30-60 seconds) for the first request.mydb-docs-nomic-embed) to prevent cross-contamination.