
Data Extraction with LangChain and Ollama Locally
Getting typed rows out of a local LLM with Pydantic and LangChain.
Phanith LIM
An LLM will happily answer "give me the top 10 phones" in prose. What I want is rows I can put in a DataFrame.
Here is how I do that on my own machine: Pydantic describes the shape I expect, LangChain's output parser forces the model to answer in that shape, and pandas takes it from there. Nothing leaves the laptop.
1. Install
pip install langchain
pip install ollama
pip install pydantic
pip install pandas
You need Ollama running
Install Ollama, open it, and let the server start. If it is new to you, these command-line basics cover enough to get going.
2. Imports
from pydantic import BaseModel, Field
from langchain_ollama.llms import OllamaLLM
from langchain.output_parsers import PydanticOutputParser
from langchain.prompts import HumanMessagePromptTemplate, ChatPromptTemplate
import pandas as pd
3. Describe what you want back
The Pydantic model is the contract. Each description is a hint the model actually reads, so it is worth writing them properly.
class PhoneModel(BaseModel):
brand: str = Field(description="Brand of the phone")
model: str = Field(description="Model of the phone")
year: int = Field(description="Year of the phone")
price: float = Field(description="Price of the phone")
class PhonesModel(BaseModel):
phones: list[PhoneModel] = Field(description="List of phones")
4. Turn the schema into instructions
PydanticOutputParser writes the formatting instructions for you and parses the reply back into your objects.
output_parser = PydanticOutputParser(pydantic_object = PhonesModel)
format_instructions = output_parser.get_format_instructions()
print(format_instructions)
Print them once and read them. That text is what the model is being told.
5. Pick a local model
Pull whichever model you want from Ollama. I used llama3.2:3b here, with temperature at 0 so the output stays repeatable.
model = OllamaLLM(
model='llama3.2:3b',
temperature=0.0,
)
6. Chain prompt, model, and parser
The prompt carries my instruction plus the format instructions. The pipe operator puts the three pieces in a row, which is where the name LangChain comes from.
human_text = "{instruction}\n{format_instructions}"
message = HumanMessagePromptTemplate.from_template(human_text)
prompt = ChatPromptTemplate.from_messages([message])
chain = prompt | model | output_parser
products = chain.invoke({"instruction":"Give top 10 popular phone now", "format_instructions":format_instructions})
products is a PhonesModel, not a string. If the model answers in the wrong shape, the parser raises instead of quietly handing me junk.
7. Into a DataFrame
df = pd.DataFrame([product.model_dump() for product in products.phones])
df
| brand | model | year | price | |
|---|---|---|---|---|
| 0 | Apple | iPhone 13 | 2021 | 699.0 |
| 1 | Samsung | Galaxy S21 | 2021 | 799.0 |
| 2 | Pixel 6 | 2021 | 599.0 | |
| 3 | OnePlus | 9 Pro | 2021 | 969.0 |
| 4 | Xiaomi | Mi 11 | 2021 | 749.0 |
| 5 | Oppo | Find X3 Pro | 2021 | 1149.0 |
| 6 | Realme | GT 2 Pro | 2022 | 599.0 |
| 7 | Vivo | X70 Pro | 2021 | 899.0 |
| 8 | Sony | Xperia 1 III | 2021 | 1299.0 |
| 9 | Motorola | Edge 30 Pro | 2022 | 799.0 |
One thing worth saying out loud: the model made these numbers up from what it remembers. The schema guarantees the shape of the answer, never the truth of it. For anything that matters, check the values against a real source.
print("Thank you for reading!")