LlamaIndex vs LangChain: Which to Use, With the Same Twitter (X) RAG Pipeline Built in Both

By TwitterAPI.io Team•16 min read
Diagram: X posts from the twitterapi.io advanced_search endpoint feed two pipelines. LlamaIndex 0.14.25: Document with text and metadata, SentenceSplitter at 1024 tokens, VectorStoreIndex, query engine or FunctionAgent. LangChain 1.4.9: Document with page_content, RecursiveCharacterTextSplitter at 4000 characters, InMemoryVectorStore, retriever tool and create_agent. Footer: default ingest of 232 posts, LlamaIndex embeds 35,581 tokens with metadata, LangChain 22,707, 1.57x.
One loader, two frameworks. Diagram; the footer numbers come from the study on this page (collected in 2026).
TL;DR
  • Short answer: LlamaIndex for data-heavy retrieval (readers, node parsers, indexes, query engines, LlamaParse for documents); LangChain for the agent and app layer (create_agent, middleware, LangSmith). Both do RAG and agents, both MIT, both Python 3.10+.
  • Versions checked on PyPI this session: llama-index / llama-index-core 0.14.25, llama-index-workflows 2.26.1; langchain 1.4.9, langchain-core 1.7.6, langgraph 1.2.20. LangChain is past 1.0; LlamaIndex is still on 0.x.
  • Measured default behavior: the same 232 X posts produced 232 nodes in LlamaIndex and 232 chunks in LangChain (no post is longer than either default chunk), but LlamaIndex embedded 1.57x the tokens because metadata goes into the vector by default; 36.2% of its embedded tokens were metadata.
  • Do not glue posts into one document: joining an account's posts into one big document (what LlamaIndex's TwitterTweetReader returns) made LangChain's default splitter cut 17 of 232 posts in half; LlamaIndex's overlap cut 1. One post per document avoids both.
  • On X, LangChain is the bigger conversation: about 194.1 posts a day vs 28.7 for LlamaIndex; 23.2% of LlamaIndex posts also named LangChain, only 1.7% the other way. @llama_index posts mostly about document parsing (82.5%), @LangChain about agents (62.2%).
  • X data bill for either pipeline: the twitterapi.io search endpoint returns posts at $0.15 per 1,000 tweets; the official X API pay-per-use rate card lists $0.005 per post read, $5 per 1,000 (about 33x). This study read 868 tweets for about $0.13.

Most LlamaIndex vs LangChain articles still describe the 2023 split: LlamaIndex indexes data, LangChain chains prompts. Both projects have moved since then. LangChain shipped 1.0 and rebuilt its agents on LangGraph; LlamaIndex added event-driven Workflows and agents, and its company now leads with document parsing. Code samples in many comparisons still import classes that no longer exist, such as GPTSimpleVectorIndex. This page uses the versions on PyPI today and code we ran against them.

The angle: one small, real RAG job built twice. We load recent X (Twitter) posts through one REST loader, index them in LlamaIndex and in LangChain with each framework's defaults, ask the same question, then turn each into an agent. Then we measure what the defaults actually do to the data, and what developers and the two projects themselves post about on X. If you are choosing between LangChain and LangGraph rather than LangChain and LlamaIndex, read our LangChain vs LangGraph comparison instead; this page does not repeat it. For a LangChain-only integration guide (document loader, @tool functions, MCP), see the LangChain + Twitter (X) guide.

Tokens LlamaIndex embeds vs LangChain for the same X posts, default settings

1.57x (35,581 vs 22,707 tokens for 232 posts), because LlamaIndex puts metadata into the vector by default; 36.2% of its embedded tokens were metadata (collected in 2026).

01 — Section

LlamaIndex vs LangChain at a glance

Both libraries are open source under the MIT license, both require Python 3.10 or newer (PyPI metadata), and both now ship RAG building blocks and an agent runtime. The difference is emphasis, and it shows in how each project describes itself. The LlamaIndex GitHub repository's description reads "LlamaIndex is the document processing platform for AI"; its framework docs say "a Python toolkit for building LLM-powered agents over your data". The LangChain repository's description reads "The agent engineering platform".

LlamaIndexLangChain
Core package todayllama-index 0.14.25 (llama-index-core 0.14.25)langchain 1.4.9 (langchain-core 1.7.6)
Center of gravityreaders, node parsers, indexes, query enginescreate_agent, tools, middleware, model providers
Agent runtimeFunctionAgent / AgentWorkflow on llama-index-workflows 2.26.1create_agent, built on langgraph 1.2.20
Hosted productLlamaParse (formerly LlamaCloud): parsing, extractionLangSmith: tracing, evals, deployment
Default splitterSentenceSplitter, 1,024 tokens, 200 overlapRecursiveCharacterTextSplitter, 4,000 characters, 200 overlap
Metadata in the embeddingyes, by defaultno, page_content only
X / Twitter readerllama-index-readers-twitter 0.6.0 (needs an X bearer token)TwitterTweetLoader in langchain-community 0.4.2

Which to pick. If your app is mostly "put these documents (PDFs, tickets, posts) behind a question box", LlamaIndex gets you to a tuned retriever with less code and has more retrieval knobs in the core package: node parsers, node postprocessors, response synthesizers, metadata filters. If your app is mostly "an agent that calls tools, some of which happen to retrieve", LangChain gets you a production agent loop with middleware (summarization, human-in-the-loop and PII middleware ship in the package), many model providers behind init_chat_model, and LangSmith traces. If you need custom control flow on top of that, that is LangGraph, covered in the LangChain vs LangGraph comparison.

Popularity, as data rather than opinion. On GitHub at the time of writing, langchain-ai/langchain showed 149,138 stars and run-llama/llama_index 52,835 (GitHub API, this session). On X, the official @LangChain account has 272,874 followers and @llama_index 122,322 (twitterapi.io /twitter/user/info, collected in 2026). Stars measure attention, not fit, so treat them as a tiebreaker at most.

02 — Section

What each framework is for in 2026

LlamaIndex. The framework docs list six component groups: data connectors, indexes and vector stores, query and chat engines, agents and tools, workflows, and models, evaluation and observability. The data path is the mature part: a Document becomes Node objects through a node parser, nodes go into a VectorStoreIndex (or a summary, keyword or property-graph index), and index.as_query_engine() retrieves, post-processes and synthesizes an answer. Agents are FunctionAgent and AgentWorkflow, both running on llama-index-workflows, an "event-driven, async-first, step-based" runtime (its PyPI summary). The hosted platform formerly called LlamaCloud has been renamed LlamaParse, according to the framework docs, and the recommended install line now reads pip install llama-index "llama-cloud>=2.8".

LangChain. Since 1.0 the langchain package is deliberately small: create_agent ("a minimal, highly configurable agent harness", per its docs), chat-model and embedding interfaces, tools and middleware. Integrations live in partner packages (langchain-openai, langchain-anthropic, ...) and in langchain-community. Retrieval pieces are still there (Document, text splitters in langchain-text-splitters 1.1.3, vector stores, retrievers), but the docs present them as building blocks you wire into an agent rather than as a query engine. Its docs state that "LangChain's agents are built on top of LangGraph" and that "Deep Agents is built on LangChain agents".

Where they overlap. Both can load documents, split them, embed them into a vector store and answer questions with citations. Both can run a tool-calling agent. Both speak MCP through an adapter package. For a small RAG app, either is enough, and the choice is about which abstraction you would rather debug at 2 a.m.: a query engine with retrieval knobs, or an agent loop with middleware.

Where they differ in practice. LlamaIndex treats metadata as part of the content (it is prepended to the text sent to the embedding model and to the LLM unless you exclude keys), which helps retrieval when the metadata is meaningful and wastes tokens when it is not. LangChain keeps metadata beside the content and embeds only page_content. The study below measures what that means on real X posts.

03 — Section

The same X-posts RAG pipeline, built in both

The job: answer "what are people building with these frameworks this week, and what problems do they report?" from recent X posts. The loader is shared, so the comparison isolates the frameworks. It calls the twitterapi.io search endpoint /twitter/tweet/advanced_search with queryType=Latest, reads the top-level tweets[] array, and follows has_next_page / next_cursor (20 posts a page). Each post keeps text, author.userName, createdAt, url, likeCount and viewCount. No X developer account is involved; the key goes in an X-API-Key header.

LlamaIndex version. Each post becomes one Document with metadata. VectorStoreIndex.from_documents() runs the default SentenceSplitter (no post we sampled comes close to its 1,024-token chunk size, so each post is one node), embeds the nodes and keeps them in memory. as_query_engine(similarity_top_k=8) retrieves eight posts and writes the answer. Note the two exclusion lists: without them, the url, timestamp, like and view counts go into every vector.

LangChain version. Each post becomes one langchain_core.documents.Document. InMemoryVectorStore.from_documents() embeds page_content only. There is no query engine in LangChain 1.x core, so the retrieval step and the prompt are explicit: similarity_search(question, k=8), format the hits with their authors, and send them to a chat model from init_chat_model. More lines, and every one is visible.

The full script is in the code block at the end of the page. We ran it offline against both libraries at the versions above with fake embeddings and a fake model (MockEmbedding / MockLLM in LlamaIndex, DeterministicFakeEmbedding / FakeListChatModel in LangChain) and against two real pages of search results, to check every import and call. With real OpenAI keys it prints one answer per framework.

StepLlamaIndex 0.14.25LangChain 1.4.9
Wrap a postDocument(text, metadata, id_)Document(page_content, metadata, id)
Splitimplicit, SentenceSplitter inside from_documentsoptional; posts are already small
IndexVectorStoreIndex.from_documents(docs)InMemoryVectorStore.from_documents(docs, embedding=...)
Askindex.as_query_engine(similarity_top_k=8).query(q)similarity_search(q, k=8) + your prompt + init_chat_model(...)
Non-blank lines in the function below2123 (the prompt is yours)

Retrieval quality. With one post per document and the same embedding model, the two retrievers rank the same vectors, so differences come only from what text each framework embeds. LlamaIndex's default puts author: ..., url: https://x.com/... and counts into every vector; for semantic questions that is noise, which is why the script excludes those keys and keeps the author. We did not run a graded retrieval benchmark with a paid embedding model for this page, and we do not claim one framework retrieves better.

04 — Section

What the defaults do to X posts: 232 posts measured

We took the 232 original posts (reposts removed) from the recent timelines of @llama_index and @LangChain and ingested them into both frameworks with default settings, offline, counting tokens with the cl100k_base encoding. Median post length was 300.5 characters, the 95th percentile 875, the longest 1,211, so neither default splitter cut any post: 232 nodes in LlamaIndex, 232 chunks in LangChain.

Measure, same posts and metadataLangChain defaultLlamaIndex defaultLlamaIndex, keys excluded
Chunks / nodes232232232
Median tokens embedded per post81.513786.5
95th percentile tokens per post224280not measured
Total tokens embedded22,70735,58123,867

The gap is metadata. LlamaIndex renders metadata as key: value lines above the text (MetadataMode.EMBED), so author, timestamp, URL, like count and view count added 36.2% to the tokens it embeds, 1.57x LangChain's total. Setting excluded_embed_metadata_keys=["created_at", "url", "likes", "views"] (keeping the author) brought it to 23,867, within 5.1% of LangChain. On a few hundred posts this is cents; on a corpus of millions of posts it is a real line on the embedding bill and a measurable drift in what the vectors represent.

The bigger trap is the document boundary. LlamaIndex's own X reader (TwitterTweetReader in llama-index-readers-twitter 0.6.0) fetches a handle's posts with tweepy and joins them into a single Document per handle, with no per-post metadata (we read its source this session). We reproduced that: one big document per account, then each framework's default splitter. For @llama_index (114 posts), LlamaIndex made 20 chunks and cut 1 post across a boundary; LangChain made 17 chunks and cut 13. For @LangChain (118 posts), 0 cut in LlamaIndex vs 4 in LangChain. LlamaIndex's overlap is 200 tokens and LangChain's is 200 characters, and a token averages several characters of English text, so LlamaIndex's chunks share much more text at each boundary; that explains most of the difference.

What to do with that. Load one post per document in either framework, keep author and timestamp as metadata, and decide explicitly which keys go into the vector. Then chunking is a non-issue for X data, and retrieval quality depends on your embedding model and your query, not on the framework.

Bar chart of tokens sent to the embedding model for the same 232 X posts: LangChain default 22,707 tokens (median 81.5 per post), LlamaIndex default 35,581 (median 137), LlamaIndex with excluded_embed_metadata_keys 23,867 (median 86.5). 36.2% of LlamaIndex's default tokens are metadata.
Same posts, same metadata: LlamaIndex embeds 1.57x the tokens by default. Posts from @llama_index and @LangChain, collected in 2026.
05 — Section

Turning each pipeline into an agent

A query engine answers one question. An agent decides when to search, can search twice with different wording, and can combine X results with other tools. Both frameworks make the step small, and the shapes are close: wrap the retriever as a tool, give the agent a model and a system prompt.

LlamaIndex: QueryEngineTool.from_defaults(query_engine, name, description) turns the query engine into a tool; FunctionAgent(tools=[...], llm=..., system_prompt=...) runs it, and await agent.run(user_msg=...) returns the answer. The tool already synthesizes an answer from retrieved posts, so the agent gets a summary back, not raw posts. Multi-agent handoffs use AgentWorkflow with can_handoff_to.

LangChain: a plain function decorated with @tool (from langchain.tools) returns the retrieved posts as text; create_agent("openai:gpt-4.1-mini", tools=[...], system_prompt=...) builds the agent, and agent.invoke({"messages": [...]}) runs it. The agent sees raw posts and writes the answer itself. Add middleware (SummarizationMiddleware, HumanInTheLoopMiddleware, PIIMiddleware ship in langchain.agents.middleware); add a checkpointer for memory. Anything beyond that loop is LangGraph territory.

Both snippets below assume the index and store objects built by the full script at the end of the page. We checked the imports and signatures against llama-index-core 0.14.25 and langchain 1.4.9.

python
# LlamaIndex: wrap the index from ask_llamaindex() as a tool, hand it to FunctionAgent
import asyncio
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.core.tools import QueryEngineTool
from llama_index.llms.openai import OpenAI

x_tool = QueryEngineTool.from_defaults(
    index.as_query_engine(similarity_top_k=8),
    name="x_posts",
    description="Search recent X posts about LlamaIndex and LangChain. Input: a question.",
)
agent = FunctionAgent(
    tools=[x_tool],
    llm=OpenAI(model="gpt-4.1-mini"),
    system_prompt="Answer from the x_posts tool only and cite the @author.",
)

async def main():
    print(await agent.run(user_msg="Which integrations do people complain about?"))

asyncio.run(main())


# LangChain: a @tool over the vector store from ask_langchain(), passed to create_agent
from langchain.agents import create_agent
from langchain.tools import tool

@tool
def x_posts(question: str) -> str:
    """Search recent X posts about LlamaIndex and LangChain. Input: a question."""
    hits = store.similarity_search(question, k=8)
    return "\n\n".join(f"@{d.metadata['author']}: {d.page_content}" for d in hits)

agent = create_agent(
    "openai:gpt-4.1-mini",
    tools=[x_posts],
    system_prompt="Answer from the x_posts tool only and cite the @author.",
)
result = agent.invoke({"messages": [{"role": "user", "content": "Which integrations do people complain about?"}]})
print(result["messages"][-1].content)
06 — Section

Using LlamaIndex and LangChain together

"Use both" is common advice, and the X data agrees that developers mention them together: 23.2% of recent posts naming LlamaIndex also named LangChain. The usual split is LlamaIndex (or LlamaParse) for ingestion and retrieval, LangChain or LangGraph for the agent. Three ways to wire it:

Retriever as a LangChain tool. Build the LlamaIndex index, then expose index.as_retriever() inside a LangChain @tool function that returns node texts. This is the lightest coupling: two libraries in one process, one plain function between them.

Share a vector store, not objects. Point both frameworks at the same external store (Chroma, Qdrant, pgvector) through their own integrations (llama-index-vector-stores-chroma 0.6.0 and langchain-chroma 1.1.0 on PyPI today). Check that both write and read the same text and metadata fields; LlamaIndex stores node JSON in its own layout.

Share tools over MCP, in separate environments. We hit a real conflict here: llama-index-tools-mcp 0.6.0 requires mcp>=2.0.0,<3 while langchain-mcp-adapters 0.3.2 requires mcp>=1.24.0,<2 (PyPI metadata), and pip could not install both in one Python 3.12 virtualenv. If you want MCP tools in both frameworks, run them as separate services, or pin one adapter to an older release until both move to the same mcp major.

07 — Section

Connecting either framework to X data: REST loader or MCP

There are three ways to get X posts into these frameworks, and two of them need an X developer app. llama-index-readers-twitter calls X's own API through tweepy with your bearer token (get_users_tweets), so you pay X's rate card and get one document per handle. LangChain's TwitterTweetLoader in langchain-community also goes through tweepy and your own X credentials, calling the legacy v1.1 user_timeline method (we read its source this session); our LangChain + Twitter guide covers its current state in detail.

REST loader (used above). Call /twitter/tweet/advanced_search for a query or /twitter/user/last_tweets for an account's timeline, map each post to a document, done. Works the same in both frameworks, keeps one post per document, and keeps the fields you choose.

Hosted MCP server. The twitterapi.io MCP server at https://mcp.twitterapi.io/mcp exposes 12 read-only tools (search, profiles, timelines, replies, followers, trends) and takes the API key as Authorization: Bearer <key> (an X-API-Key header also works), per docs.twitterapi.io. In LlamaIndex, BasicMCPClient(url, headers=...) plus McpToolSpec(client).to_tool_list_async() gives FunctionAgent the tools; in LangChain, MultiServerMCPClient with "transport": "streamable_http" and get_tools() gives create_agent the tools. Use MCP when the agent should decide what to fetch; use the REST loader when you are building an index.

RouteX account or app neededShape of documentsCost of X data
llama-index-readers-twitter (tweepy)yes, your bearer tokenone document per handle, text onlyX pay-per-use: $0.005 per post read
TwitterTweetLoader (tweepy, v1.1 user_timeline)yes, your own credentialsone document per postX rate card
REST loader on twitterapi.ionoone per post, your metadata$0.15 per 1,000 tweets returned
twitterapi.io MCP servernotool results the agent readssame per-tweet pricing
python
# Same hosted MCP server, two adapters. They need different `mcp` majors, so use two virtualenvs.
import asyncio, os
KEY = os.environ["TWITTERAPI_IO_API_KEY"]
URL = "https://mcp.twitterapi.io/mcp"

# env 1: pip install llama-index-core llama-index-tools-mcp llama-index-llms-openai
async def llamaindex_tools():
    from llama_index.tools.mcp import BasicMCPClient, McpToolSpec
    client = BasicMCPClient(URL, headers={"Authorization": f"Bearer {KEY}"})
    return await McpToolSpec(client=client).to_tool_list_async()   # pass to FunctionAgent(tools=...)

# env 2: pip install langchain langchain-mcp-adapters langchain-openai
async def langchain_tools():
    from langchain_mcp_adapters.client import MultiServerMCPClient
    client = MultiServerMCPClient({
        "twitterapi": {"transport": "streamable_http", "url": URL,
                       "headers": {"Authorization": f"Bearer {KEY}"}},
    })
    return await client.get_tools()                                # pass to create_agent(tools=...)

print([t.name for t in asyncio.run(langchain_tools())])
08 — Section

How developers and the two projects talk about it on X

We searched X for each framework's name and read both official accounts' recent timelines. Posts naming LangChain arrived at about 8.09 an hour (194.1 a day): 240 posts covered 29.7 hours. Posts naming LlamaIndex arrived at about 1.2 an hour (28.7 a day): 207 posts covered 173.0 hours. That is roughly 6.8 times the volume for LangChain. Most of both conversations is small accounts and replies: median likes 0 and 1, replies 42.9% and 52.7%.

Search on X, Latestposts naming LlamaIndexposts naming LangChain
Posts per day (sample)28.7194.1
Also name the other framework23.2%1.7%
Mention agents34.8%55.0%
Mention RAG, retrieval or vectors23.2%14.2%
Mention document parsing, PDFs or extraction34.8%5.0%
Mention LangGraph10.1%13.3%
Mention MCP7.2%5.8%

The asymmetry is the useful part. LlamaIndex comes up next to LangChain (23.2%) far more often than the reverse (1.7%), which fits how people use it: as the retrieval or parsing piece inside a stack whose default is LangChain. And agents are the top topic in both conversations, which is why "LlamaIndex is only for RAG" no longer describes what its users discuss.

The official accounts show where each company is steering. @llama_index posted 200 times over 170.8 days (1.17 a day including reposts) and 82.5% of those posts touch document parsing, OCR or extraction, against 12.0% for RAG or retrieval. @LangChain posted 188 times in 30 days (6.26 a day): 62.2% about agents, 23.4% about LangSmith, evals or tracing, 1.6% about document parsing. Engagement per original post was similar: median 27 likes and 10,144 views for @llama_index, 30.5 likes and 8,216 views for @LangChain.

Grouped bar chart of topics in recent posts by @llama_index (200 posts) and @LangChain (188 posts): document parsing 82.5% vs 1.6%, agents 53.0% vs 62.2%, RAG or retrieval 12.0% vs 3.2%, LangSmith, evals or tracing 7.0% vs 23.4%, LangGraph 0.0% vs 3.7%, MCP 2.0% vs 0.0%.
What the two projects post about on X. Keyword classes on post text; a post can match several. Recent timelines, collected in 2026.
09 — Section

What the X data costs, and how we collected this

Either pipeline reads X posts the same way, so the X data bill does not depend on the framework. The twitterapi.io search and timeline endpoints charge 15 credits per tweet returned, $0.15 per 1,000 tweets; you pay only for the data returned, and a search that finds nothing costs the 15-credit minimum ($0.00015). Reading 1,000 posts through the official X API pay-per-use rate card costs $5 ($0.005 per post read, docs.x.com/x-api/getting-started/pricing), about 33x more. This whole study read 868 tweets for about $0.13; the same reads on X's rate card would be about $4.34. Refreshing an index of 500 posts a day is about $0.08 a day of X data. Embedding and model costs are separate and set by your model provider.

Rate limits: 0.2 requests per second before the first payment, 20 once paid. New accounts that pass the automatic bot check get up to $1 of free credits (at most 100,000 credits, no card, one per person or network, expiring after 31 days); purchased credits never expire. Plans and volume pricing are on the pricing page.

How we collected this. Question: how do developers and the two projects talk about LlamaIndex vs LangChain on X, and what do the frameworks' defaults do to X posts? Accounts: @llama_index and @LangChain, both organization accounts (the older handle @LangChainAI returned "user not found"). Queries: llamaindex OR llama_index OR "llama index" -is:retweet and langchain -is:retweet, queryType=Latest. Window: the most recent posts at collection time, collected in 2026.

Sample: 200 + 188 timeline posts and 240 + 240 search posts (33 LlamaIndex search results that still carried a repost object were dropped), 47 requests in total. Topics are keyword classes on post text (for example, "agent" or "agentic" for agents; "parse", "OCR", "PDF", "document" or "extract" for parsing), so a post can match several and sarcasm or context is not understood. Ingest test: llama-index-core 0.14.25 and langchain-text-splitters 1.1.3 with default settings, offline, no embedding model called; tokens counted with cl100k_base.

What the sample cannot show: a search sample of a day or a week is a snapshot, not a trend; the counts depend on how often people type each name; the topic classes are crude; and the ingest test measures what gets embedded, not answer quality. Privacy: aggregates only. Search results (mostly private individuals) were never stored in the repository; the de-identified rows we keep have no IDs, URLs, text or handles. The study files are data/studies/llamaindex-vs-langchain/ in our site repository.

python
"""Answer questions over recent X posts with LlamaIndex AND LangChain, from one loader.

pip install requests llama-index-core llama-index-embeddings-openai llama-index-llms-openai \
    langchain langchain-openai
export TWITTERAPI_IO_API_KEY=...   # twitterapi.io key, no X developer account needed
export OPENAI_API_KEY=...         # embeddings + answer model for both frameworks
"""
import os

import requests

BASE = "https://api.twitterapi.io"
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_API_KEY"]}
QUERY = "(llamaindex OR langchain) lang:en -is:retweet"
QUESTION = "What are people building with these frameworks this week, and what problems do they report?"


def fetch_posts(query: str, max_pages: int = 5) -> list[dict]:
    """advanced_search, newest first; 20 posts a page, follow next_cursor."""
    posts, cursor = [], ""
    for _ in range(max_pages):
        r = requests.get(
            f"{BASE}/twitter/tweet/advanced_search",
            headers=HEADERS,
            params={"query": query, "queryType": "Latest", "cursor": cursor},
            timeout=30,
        )
        r.raise_for_status()
        body = r.json()
        for t in body.get("tweets", []):
            posts.append({
                "id": t["id"],
                "text": t.get("text") or "",
                "author": (t.get("author") or {}).get("userName", ""),
                "created_at": t.get("createdAt", ""),
                "url": t.get("url", ""),
                "likes": t.get("likeCount", 0),
                "views": t.get("viewCount", 0) or 0,
            })
        cursor = body.get("next_cursor") or ""
        if not body.get("has_next_page") or not cursor:
            break
    return posts


def ask_llamaindex(posts: list[dict], question: str) -> str:
    from llama_index.core import Document, Settings, VectorStoreIndex
    from llama_index.embeddings.openai import OpenAIEmbedding
    from llama_index.llms.openai import OpenAI

    Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
    Settings.llm = OpenAI(model="gpt-4.1-mini")
    docs = [
        Document(
            text=p["text"],
            id_=p["id"],
            metadata={k: p[k] for k in ("author", "created_at", "url", "likes", "views")},
            # LlamaIndex embeds metadata by default; keep only the author in the vector
            excluded_embed_metadata_keys=["created_at", "url", "likes", "views"],
            excluded_llm_metadata_keys=["likes", "views"],
        )
        for p in posts
    ]
    index = VectorStoreIndex.from_documents(docs)  # one post = one node at the default 1,024-token chunk size
    engine = index.as_query_engine(similarity_top_k=8)
    return str(engine.query(question))


def ask_langchain(posts: list[dict], question: str) -> str:
    from langchain.chat_models import init_chat_model
    from langchain_core.documents import Document
    from langchain_core.vectorstores import InMemoryVectorStore
    from langchain_openai import OpenAIEmbeddings

    docs = [
        Document(
            page_content=p["text"],
            id=p["id"],
            metadata={k: p[k] for k in ("author", "created_at", "url", "likes", "views")},
        )
        for p in posts
    ]
    store = InMemoryVectorStore.from_documents(docs, embedding=OpenAIEmbeddings(model="text-embedding-3-small"))
    hits = store.similarity_search(question, k=8)
    context = "\n\n".join(f"@{d.metadata['author']} ({d.metadata['created_at']}): {d.page_content}" for d in hits)
    llm = init_chat_model("openai:gpt-4.1-mini")
    msg = llm.invoke([
        {"role": "system", "content": "Answer only from the X posts provided. Cite the @author for each claim."},
        {"role": "user", "content": f"Posts:\n{context}\n\nQuestion: {question}"},
    ])
    return msg.content


if __name__ == "__main__":
    posts = fetch_posts(QUERY)
    print(f"{len(posts)} posts loaded (about ${len(posts) * 0.00015:.4f} of X data at $0.15 per 1,000 tweets)")
    print("\n--- LlamaIndex ---\n", ask_llamaindex(posts, QUESTION))
    print("\n--- LangChain ---\n", ask_langchain(posts, QUESTION))

Researching this?Free starter credits cover end-to-end testing — Google sign-in, no card. Get a key.

10 — Questions

Questions readers ask

Is LlamaIndex better than LangChain for RAG?

Neither is better in general. LlamaIndex has more retrieval machinery in its core package (node parsers, postprocessors, response synthesizers, a query engine in one call), so a tuned RAG app usually takes less code. LangChain makes the retrieval step explicit and fits it into an agent. With one document per item and the same embedding model, both retrieve from the same vectors; what changes results is what you embed and how you chunk, which you control in both.

Can I use LlamaIndex and LangChain together?

Yes, and many developers do: 23.2% of recent X posts naming LlamaIndex also named LangChain in our sample. The light way is a LangChain @tool that calls a LlamaIndex retriever. A shared external vector store also works. If you want MCP tools in both, note that llama-index-tools-mcp 0.6.0 and langchain-mcp-adapters 0.3.2 require different mcp major versions and could not be installed in one environment when we tried.

Is LlamaIndex only for RAG now?

No. Its docs describe the framework as "our original open-source toolkit for RAG and agents", and it ships FunctionAgent and AgentWorkflow on the llama-index-workflows runtime. The company's hosted platform, formerly LlamaCloud, is now named LlamaParse and focuses on document parsing and extraction.

What is the difference between LangChain and LangGraph, and does it matter here?

LangChain's agents are built on LangGraph; LangGraph is the lower-level runtime for custom control flow. For a LlamaIndex comparison, the relevant layer is LangChain's create_agent. Our LangChain vs LangGraph page covers when to drop down to LangGraph.

Which versions are current?

On PyPI at the time of writing: llama-index and llama-index-core 0.14.25, llama-index-workflows 2.26.1; langchain 1.4.9, langchain-core 1.7.6, langgraph 1.2.20, langchain-community 0.4.2. Both require Python 3.10 or newer. Pin versions in production; both projects release often.

Why does LlamaIndex use more embedding tokens?

By default a LlamaIndex Document includes its metadata in the text sent to the embedding model (MetadataMode.EMBED). On 232 X posts with author, date, URL, likes and views as metadata, that made 36.2% of the embedded tokens metadata, 1.57x LangChain's total. Set excluded_embed_metadata_keys (and excluded_llm_metadata_keys) on each document to control it.

How do I load Twitter (X) posts into LlamaIndex or LangChain without an X developer account?

Call a REST endpoint such as twitterapi.io's /twitter/tweet/advanced_search or /twitter/user/last_tweets with an X-API-Key header and map each post to a document, as in the script on this page, or connect the hosted MCP server at https://mcp.twitterapi.io/mcp. The built-in X readers in both ecosystems go through tweepy and need your own X credentials.

Which one should a beginner learn first?

Start from the problem. If you need a question box over your own documents this week, LlamaIndex's VectorStoreIndex and as_query_engine() get you there in a few lines. If you are building an agent that uses tools, start with LangChain's create_agent. The concepts (documents, embeddings, retrievers, tools) transfer between them.

11 — Further reading

Continue

Sources & further reading
More from this series

Writing about something you built with TwitterAPI.io? Our community rewards program pays API credits for honest tutorials, build logs and videos.

Build it

Stop reading. Start building.

Starter credits cover real testing on real data. Google sign-in, no card, no application queue.

Get an API key

Already building with TwitterAPI.io? Publish an honest tutorial or build log about it and earn up to $200 a month in API credits.