LangChain Integration Guide (Python)
IndieML integrates natively into LangChain as a custom DocumentTransformer.
It intercepts chunked data before the embedding step, scoring and dropping low-substance artifacts to save vector space and compute.
Before starting, you need an API key to authenticate the connection. Click here to get your free API key. Keep the key handy for the configuration step.
Install the IndieML SDK alongside your LangChain environment:
pip install indieml langchain-core
Drop this code directly into your project:
class IndieMLFilter(BaseDocumentTransformer): """Filters LangChain documents based on IndieML substance scores.""" def __init__(self, api_key: str, threshold: float = 0.5): self.api = Substance(api_key=api_key) self.threshold = threshold def transform_documents(self, documents: list[Document], **kwargs) -> list[Document]: texts = [doc.page_content for doc in documents] batch_results = self.api.score_batch(texts) clean_docs = [] for doc, result in zip(documents, batch_results): score = result.get("substance_score", 0.0) if score >= self.threshold: doc.metadata["indieml_score"] = score clean_docs.append(doc) return clean_docs
Because IndieML operates as a semantic filter, it should be placed in your ingestion pipeline immediately after chunking and before embedding. By ensuring your chunks are a uniform length, you neutralize any natural verbosity bias in the scoring model.
from langchain_community.document_loaders import WebBaseLoader from langchain_text_splitters import RecursiveCharacterTextSplitter # ... (assume IndieMLFilter is imported) # 1. Load your messy data loader = WebBaseLoader("https://example.com/messy-data") documents = loader.load() # 2. Split into uniform chunks to normalize the evaluation # (150-300 characters is typical for most applications.) # (The API is capped at 1,000 characters.) text_splitter = RecursiveCharacterTextSplitter(chunk_size=150, chunk_overlap=15) split_docs = text_splitter.split_documents(documents) # 3. Apply the filter # (Tune the threshold based on your specific document type and chunk size) indieml = IndieMLFilter(api_key="YOUR_API_KEY", threshold=0.5) clean_docs = indieml.transform_documents(split_docs) # 4. Pass clean_docs to the next step in your pipeline.
While RAG ingestion is the most common use case, this filter pattern is highly versatile. You can apply the same logic to Agent Memory Filtering (filtering out blank or error-filled returns before they bloat the context window), Prompt Gating (intercepting low-context user inputs before paying for LLM inference), Dataset Curation (automatically dropping low-substance artifacts from raw text before fine-tuning a model), or Web Crawling (discarding boilerplate navigation menus and footer text during live extraction).
This is a fully runnable script that scrapes a real-world essay by Paul Graham, splits it into uniform 150-character chunks, and filters out the low-substance noise. You can copy and paste the code, add your API key, and run this locally to see how the API handles live data.
# Import the Libraries import requests from bs4 import BeautifulSoup from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_core.documents import Document, BaseDocumentTransformer from indieml import Substance # Configure the Substance Filter class IndieMLFilter(BaseDocumentTransformer): """Filters LangChain documents based on IndieML substance scores.""" def __init__(self, api_key: str, threshold: float = 0.5): self.api = Substance(api_key=api_key) self.threshold = threshold def transform_documents(self, documents: list[Document], **kwargs) -> list[Document]: texts = [doc.page_content for doc in documents] batch_results = self.api.score_batch(texts) clean_docs = [] for doc, result in zip(documents, batch_results): score = result.get("substance_score", 0.0) if score >= self.threshold: doc.metadata["indieml_score"] = score clean_docs.append(doc) return clean_docs # Retrieve and Process the Data response = requests.get( "https://paulgraham.com/startupideas.html", headers={"User-Agent": "IndieML-TestBot/1.0"} ) soup = BeautifulSoup(response.content, "html.parser") text_content = soup.get_text(separator=' ', strip=True) # Split into chunks raw_document = Document(page_content=text_content, metadata={"source": "paulgraham.com"}) text_splitter = RecursiveCharacterTextSplitter(chunk_size=150, chunk_overlap=15) split_docs = text_splitter.split_documents([raw_document]) print(f"Total chunks created: {len(split_docs)}") # Score and filter print("Sending batch to live IndieML API...") indieml = IndieMLFilter(api_key="YOUR_API_KEY", threshold=0.5) clean_docs = indieml.transform_documents(split_docs) print(f"\nFinal Result: Kept {len(clean_docs)} out of {len(split_docs)} chunks.")