skills/jeremylongshore/claude-code-plugins-plus-skills/langchain-reference-architecture

langchain-reference-architecture

Installation
SKILL.md

LangChain Reference Architecture

Overview

Production architectural patterns for LangChain: layered project structure, provider abstraction for vendor flexibility, chain registry for dynamic management, RAG architecture, and multi-agent orchestration.

Layered Architecture

src/
├── api/                    # HTTP layer (Express/Fastify/FastAPI)
│   ├── routes/
│   │   ├── chat.ts         # POST /api/chat, /api/chat/stream
│   │   └── documents.ts    # POST /api/documents/ingest
│   └── middleware/
│       ├── auth.ts         # JWT/OAuth validation
│       └── rateLimit.ts    # Per-user rate limiting
├── core/                   # Business logic (pure, testable)
│   ├── chains/
│   │   ├── summarize.ts    # Summarize chain factory
│   │   ├── qa.ts           # Q&A chain factory
│   │   └── rag.ts          # RAG chain factory
│   ├── agents/
│   │   └── assistant.ts    # Agent with tools
│   └── tools/
│       ├── calculator.ts
│       └── search.ts
├── infra/                  # External integrations
│   ├── llm/
│   │   └── factory.ts      # LLM provider factory
│   ├── vectorStore/
│   │   └── pinecone.ts     # Vector store setup
│   └── cache/
│       └── redis.ts        # Response caching
├── config/
│   ├── index.ts            # Config loader + validation
│   └── models.ts           # Model configurations
└── index.ts                # App entry point

Provider Abstraction (LLM Factory)

// src/infra/llm/factory.ts
import { ChatOpenAI } from "@langchain/openai";
import { ChatAnthropic } from "@langchain/anthropic";
import { BaseChatModel } from "@langchain/core/language_models/chat_models";

type Provider = "openai" | "anthropic";

interface ModelConfig {
  provider: Provider;
  model: string;
  temperature?: number;
  maxRetries?: number;
  timeout?: number;
}

const DEFAULT_CONFIG: Partial<ModelConfig> = {
  temperature: 0,
  maxRetries: 3,
  timeout: 30000,
};

export function createModel(config: ModelConfig): BaseChatModel {
  const merged = { ...DEFAULT_CONFIG, ...config };

  switch (merged.provider) {
    case "openai":
      return new ChatOpenAI({
        model: merged.model,
        temperature: merged.temperature,
        maxRetries: merged.maxRetries,
        timeout: merged.timeout,
      });

    case "anthropic":
      return new ChatAnthropic({
        model: merged.model,
        temperature: merged.temperature,
        maxRetries: merged.maxRetries,
      });

    default:
      throw new Error(`Unknown provider: ${merged.provider}`);
  }
}

// Usage: swap providers without touching chain code
const model = createModel({
  provider: "openai",
  model: "gpt-4o-mini",
});

Chain Registry

// src/core/chains/registry.ts
import { Runnable } from "@langchain/core/runnables";

class ChainRegistry {
  private chains = new Map<string, Runnable>();

  register(name: string, chain: Runnable) {
    this.chains.set(name, chain);
  }

  get(name: string): Runnable {
    const chain = this.chains.get(name);
    if (!chain) throw new Error(`Chain not found: ${name}`);
    return chain;
  }

  list(): string[] {
    return Array.from(this.chains.keys());
  }
}

export const registry = new ChainRegistry();

// At startup:
registry.register("summarize", summarizeChain);
registry.register("qa", qaChain);
registry.register("rag", ragChain);

// In API routes:
app.post("/api/chain/:name/invoke", async (req, res) => {
  const chain = registry.get(req.params.name);
  const result = await chain.invoke(req.body.input);
  res.json({ result });
});

RAG Architecture

// src/core/chains/rag.ts
import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai";
import { PineconeStore } from "@langchain/pinecone";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { RunnableSequence, RunnablePassthrough } from "@langchain/core/runnables";

export function createRAGChain(vectorStore: PineconeStore) {
  const retriever = vectorStore.asRetriever({ k: 4 });
  const model = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 });

  const prompt = ChatPromptTemplate.fromTemplate(`
Answer based only on the provided context.
If the answer is not in the context, say "I don't have that information."

Context:
{context}

Question: {question}`);

  return RunnableSequence.from([
    {
      context: retriever.pipe(
        (docs) => docs.map((d: any) => d.pageContent).join("\n\n")
      ),
      question: new RunnablePassthrough(),
    },
    prompt,
    model,
    new StringOutputParser(),
  ]);
}

Multi-Agent Orchestration

// src/core/agents/orchestrator.ts
import { ChatOpenAI } from "@langchain/openai";
import { createToolCallingAgent, AgentExecutor } from "langchain/agents";
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";

interface SpecializedAgent {
  name: string;
  description: string;
  executor: AgentExecutor;
}

class AgentOrchestrator {
  private agents: SpecializedAgent[] = [];
  private router: any;

  register(agent: SpecializedAgent) {
    this.agents.push(agent);
  }

  async route(input: string): Promise<string> {
    // Use LLM to pick the right agent
    const model = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 });
    const agentList = this.agents
      .map((a) => `- ${a.name}: ${a.description}`)
      .join("\n");

    const routerPrompt = ChatPromptTemplate.fromTemplate(`
Given these specialized agents:
${agentList}

Which agent should handle this request? Reply with just the agent name.
Request: {input}`);

    const routerChain = routerPrompt.pipe(model).pipe(new StringOutputParser());
    const agentName = (await routerChain.invoke({ input })).trim();

    const agent = this.agents.find((a) => a.name === agentName);
    if (!agent) {
      return `No agent found for: ${input}`;
    }

    const result = await agent.executor.invoke({ input, chat_history: [] });
    return result.output;
  }
}

// Usage
const orchestrator = new AgentOrchestrator();
orchestrator.register({
  name: "code-reviewer",
  description: "Reviews code for bugs and best practices",
  executor: codeReviewAgent,
});
orchestrator.register({
  name: "data-analyst",
  description: "Analyzes data and generates reports",
  executor: dataAnalystAgent,
});

Configuration-Driven Design

// src/config/index.ts
import { z } from "zod";
import "dotenv/config";

const ConfigSchema = z.object({
  llm: z.object({
    provider: z.enum(["openai", "anthropic"]),
    model: z.string(),
    temperature: z.number().min(0).max(2).default(0),
    maxRetries: z.number().default(3),
  }),
  vectorStore: z.object({
    provider: z.enum(["pinecone", "faiss", "memory"]),
    indexName: z.string().optional(),
  }),
  server: z.object({
    port: z.number().default(8000),
    cors: z.boolean().default(true),
  }),
  langsmith: z.object({
    enabled: z.boolean().default(false),
    project: z.string().default("default"),
  }),
});

export type Config = z.infer<typeof ConfigSchema>;

export function loadConfig(): Config {
  return ConfigSchema.parse({
    llm: {
      provider: process.env.LLM_PROVIDER ?? "openai",
      model: process.env.LLM_MODEL ?? "gpt-4o-mini",
      temperature: Number(process.env.LLM_TEMPERATURE ?? 0),
    },
    vectorStore: {
      provider: process.env.VECTOR_STORE_PROVIDER ?? "memory",
      indexName: process.env.PINECONE_INDEX,
    },
    server: {
      port: Number(process.env.PORT ?? 8000),
    },
    langsmith: {
      enabled: process.env.LANGSMITH_TRACING === "true",
      project: process.env.LANGSMITH_PROJECT ?? "default",
    },
  });
}

Error Handling

Issue Cause Fix
Circular imports Wrong layering Core should never import from API layer
Provider not found Unknown in factory Add to factory switch statement
Chain not registered Missing startup init Register all chains in app bootstrap
Config validation fail Missing env var Add to .env.example, validate on startup

Resources

Next Steps

Use langchain-multi-env-setup for environment management.

Weekly Installs
25
GitHub Stars
2.1K
First Seen
Feb 18, 2026