LangChain Template¶
Production-ready starter for chains and agents using LangChain.
Create Project¶
Project Structure¶
my-agent/
├── app.py # FastAgentic entry point
├── chains/
│ ├── __init__.py
│ └── qa.py # RAG chain
├── agents/
│ ├── __init__.py
│ └── assistant.py # Tool-using agent
├── tools/
│ ├── __init__.py
│ └── custom.py # Custom tools
├── models/
│ ├── inputs.py
│ └── outputs.py
├── config/
│ └── settings.yaml
├── tests/
│ ├── test_chains.py
│ └── test_contracts.py
├── .env.example
├── Dockerfile
├── docker-compose.yml
├── k8s/
│ └── *.yaml
└── pyproject.toml
Core Files¶
app.py¶
"""FastAgentic application entry point."""
import os
from fastagentic import App
from fastagentic.protocols import enable_mcp, enable_a2a
from fastagentic.adapters.langchain import LangChainAdapter
from chains.qa import qa_chain
from agents.assistant import assistant_agent
from models.inputs import QuestionInput, AssistantInput
from models.outputs import AnswerOutput, AssistantOutput
app = App(
title="Knowledge Assistant",
version="1.0.0",
description="RAG-powered Q&A and tool-using assistant",
durable_store=os.getenv("DURABLE_STORE", "redis://localhost:6379"),
)
enable_mcp(app, tasks_enabled=True)
enable_a2a(app)
# RAG chain endpoint
@app.agent_endpoint(
path="/qa",
runnable=LangChainAdapter(qa_chain),
input_model=QuestionInput,
output_model=AnswerOutput,
stream=True,
mcp_tool="ask_question",
a2a_skill="knowledge-qa",
)
async def question_answer(question: QuestionInput) -> AnswerOutput:
"""Answer questions using RAG."""
...
# Agent endpoint
@app.agent_endpoint(
path="/assist",
runnable=LangChainAdapter(
assistant_agent,
stream_tokens=True,
include_tool_calls=True,
),
input_model=AssistantInput,
output_model=AssistantOutput,
stream=True,
durable=True,
mcp_tool="assistant",
a2a_skill="general-assistant",
)
async def assistant(request: AssistantInput) -> AssistantOutput:
"""General-purpose assistant with tools."""
...
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
chains/qa.py¶
"""RAG question-answering chain using LCEL."""
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
# Initialize components
llm = ChatOpenAI(model="gpt-4o", streaming=True)
embeddings = OpenAIEmbeddings()
# Vector store (configure path via env)
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings,
)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
# Prompt template
prompt = ChatPromptTemplate.from_messages([
("system", """You are a helpful assistant that answers questions based on
the provided context. If the context doesn't contain relevant information,
say so clearly. Always cite your sources.
Context:
{context}"""),
("human", "{question}"),
])
def format_docs(docs):
"""Format retrieved documents."""
return "\n\n".join([
f"[Source: {d.metadata.get('source', 'unknown')}]\n{d.page_content}"
for d in docs
])
# Build LCEL chain
qa_chain = (
RunnableParallel(
context=retriever | format_docs,
question=RunnablePassthrough(),
)
| prompt
| llm
| StrOutputParser()
)
agents/assistant.py¶
"""Tool-using assistant agent."""
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
from langchain.agents import create_openai_tools_agent, AgentExecutor
from tools.custom import calculator, web_search, code_executor
# Initialize LLM
llm = ChatOpenAI(model="gpt-4o", streaming=True)
# Agent prompt
prompt = ChatPromptTemplate.from_messages([
("system", """You are a helpful assistant with access to various tools.
Use tools when needed to provide accurate and helpful responses.
Always explain your reasoning."""),
MessagesPlaceholder(variable_name="chat_history", optional=True),
("human", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
])
# Available tools
tools = [calculator, web_search, code_executor]
# Create agent
agent = create_openai_tools_agent(llm, tools, prompt)
# Create executor
assistant_agent = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors=True,
max_iterations=10,
return_intermediate_steps=True,
)
tools/custom.py¶
"""Custom tools for the assistant."""
from langchain_core.tools import tool
from langchain_community.tools.tavily_search import TavilySearchResults
@tool
def calculator(expression: str) -> str:
"""Evaluate a mathematical expression.
Args:
expression: A mathematical expression to evaluate (e.g., "2 + 2 * 3")
"""
try:
# Safe evaluation
result = eval(expression, {"__builtins__": {}}, {})
return str(result)
except Exception as e:
return f"Error: {e}"
@tool
def code_executor(code: str, language: str = "python") -> str:
"""Execute code in a sandboxed environment.
Args:
code: The code to execute
language: Programming language (python, javascript)
"""
# Use sandboxed execution (e.g., Docker, E2B)
from sandbox import execute
return execute(code, language)
# Tavily search
web_search = TavilySearchResults(max_results=5)
models/inputs.py¶
"""Input models."""
from pydantic import BaseModel, Field
class QuestionInput(BaseModel):
"""Question for RAG Q&A."""
question: str = Field(description="The question to answer")
filters: dict | None = Field(
default=None,
description="Optional metadata filters for retrieval",
)
class AssistantInput(BaseModel):
"""Input for general assistant."""
input: str = Field(description="User message or request")
chat_history: list[dict] | None = Field(
default=None,
description="Previous conversation messages",
)
models/outputs.py¶
"""Output models."""
from pydantic import BaseModel, Field
class AnswerOutput(BaseModel):
"""RAG answer output."""
answer: str = Field(description="The answer to the question")
sources: list[str] = Field(
default_factory=list,
description="Sources used for the answer",
)
class AssistantOutput(BaseModel):
"""Assistant response output."""
output: str = Field(description="Assistant's response")
intermediate_steps: list[dict] | None = Field(
default=None,
description="Tool calls and results",
)
tests/test_chains.py¶
"""Chain tests."""
import pytest
from unittest.mock import patch, MagicMock
from chains.qa import qa_chain
from agents.assistant import assistant_agent
@pytest.mark.asyncio
async def test_qa_chain_structure():
"""Test QA chain has correct structure."""
# Chain should have retriever, prompt, llm, parser
assert qa_chain.first is not None
assert qa_chain.last is not None
@pytest.mark.asyncio
@patch("chains.qa.retriever")
async def test_qa_chain_execution(mock_retriever):
"""Test QA chain execution."""
mock_retriever.invoke.return_value = [
MagicMock(page_content="Test content", metadata={"source": "test"})
]
result = await qa_chain.ainvoke({"question": "What is X?"})
assert isinstance(result, str)
@pytest.mark.asyncio
async def test_assistant_has_tools():
"""Test assistant has correct tools."""
assert len(assistant_agent.tools) == 3
tool_names = [t.name for t in assistant_agent.tools]
assert "calculator" in tool_names
assert "web_search" in tool_names
Configuration¶
.env.example¶
# LLM Provider
OPENAI_API_KEY=sk-...
# Search API
TAVILY_API_KEY=tvly-...
# Vector Store
CHROMA_PERSIST_DIR=./chroma_db
# Durable Store
DURABLE_STORE=redis://localhost:6379
# Application
LOG_LEVEL=INFO
pyproject.toml¶
[project]
name = "my-agent"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"fastagentic[langchain]>=0.2.0",
"langchain>=0.3.0",
"langchain-openai>=0.2.0",
"langchain-community>=0.3.0",
"chromadb>=0.5.0",
"tavily-python>=0.5.0",
]
Running¶
# Install
pip install -e ".[dev]"
# Configure
cp .env.example .env
# Index documents (for RAG)
python scripts/index_docs.py
# Run
fastagentic run --reload
API Usage¶
RAG Q&A¶
curl -X POST http://localhost:8000/qa \
-H "Content-Type: application/json" \
-d '{"question": "What is the return policy?"}'
Stream Q&A¶
curl -N http://localhost:8000/qa/stream \
-H "Accept: text/event-stream" \
-d '{"question": "Explain the architecture"}'
# Streams tokens:
# event: token
# data: {"content": "The", "delta": "The"}
# event: token
# data: {"content": "The architecture", "delta": " architecture"}
Assistant with Tools¶
curl -X POST http://localhost:8000/assist \
-H "Content-Type: application/json" \
-d '{
"input": "Calculate 15% tip on $85.50, then search for tipping etiquette"
}'
# Response includes tool calls:
{
"output": "The 15% tip would be $12.83...",
"intermediate_steps": [
{"tool": "calculator", "input": "85.50 * 0.15", "output": "12.825"},
{"tool": "web_search", "input": "tipping etiquette", "output": "..."}
]
}
Migrating from LangServe¶
If you have existing LangServe deployments, FastAgentic provides an easy migration path:
Before (LangServe)¶
from fastapi import FastAPI
from langserve import add_routes
app = FastAPI()
add_routes(app, qa_chain, path="/qa")
After (FastAgentic)¶
from fastagentic import App
from fastagentic.adapters.langchain import LangChainAdapter
app = App(title="My App")
@app.agent_endpoint(
path="/qa",
runnable=LangChainAdapter(qa_chain),
stream=True,
mcp_tool="qa", # Now also exposed via MCP
a2a_skill="qa-agent", # And A2A
)
async def qa(input: Input) -> Output:
...
Migration Benefits¶
| Feature | LangServe | FastAgentic |
|---|---|---|
| REST API | Yes | Yes |
| Streaming | SSE only | SSE + WebSocket + MCP |
| MCP Protocol | No | Yes |
| A2A Protocol | No | Yes |
| Authentication | Manual | Built-in OAuth2/OIDC |
| Checkpointing | No | Yes |
| Cost Tracking | No | Yes |
| Observability | Manual | Built-in OTEL |
LCEL Composition¶
FastAgentic preserves full LCEL functionality:
from langchain_core.runnables import RunnableParallel, RunnableLambda
# Complex chain with parallel execution
complex_chain = (
RunnableParallel(
summary=summary_chain,
keywords=keyword_chain,
sentiment=sentiment_chain,
)
| RunnableLambda(combine_results)
| output_chain
)
# Deploy with FastAgentic
@app.agent_endpoint(
path="/analyze",
runnable=LangChainAdapter(complex_chain),
)
async def analyze(input: AnalysisInput) -> AnalysisOutput:
...
Next Steps¶
- LangChain Adapter - Full adapter documentation
- Protocol Support - MCP and A2A details
- Operations Guide - Production deployment