Run LLMs Locally with Ollama for Private Inference
Install and use Ollama to run open-source LLMs locally with Python, including streaming, embeddings, function calling, and model management without API costs
Ollama runs open-source LLMs (Llama 3, Mistral, Phi-3, etc.) on your machine. No API keys, no per-token costs, no data leaving your network. Below: installing Ollama, running models with Python, streaming responses, generating embeddings, and function calling — all locally.
When to Use This
- Privacy-sensitive applications where data cannot leave your machine
- Development and prototyping without API costs
- Offline or air-gapped environments
- Running custom or fine-tuned models
Prerequisites
- Python 3.10+
- Ollama installed (
curl -fsSL https://ollama.com/install.sh | shon Linux/macOS, or download from ollama.com for Windows) - 8GB+ RAM (16GB+ recommended for larger models)
Solution
1. Install Ollama and Pull a Model
# Install Ollama (Linux/macOS)
curl -fsSL https://ollama.com/install.sh | sh
# Pull a model (this downloads it to your machine)
ollama pull llama3
# List installed models
ollama list
2. Install Python Client
pip install ollama
3. Basic Chat
import ollama
response = ollama.chat(
model="llama3",
messages=[
{"role": "system", "content": "You are a helpful coding assistant."},
{"role": "user", "content": "Explain Redis cache-aside pattern in 2 sentences."},
],
)
print(response["message"]["content"])
4. Streaming Responses
def stream_chat(model: str, message: str) -> None:
"""Stream tokens from Ollama in real-time."""
stream = ollama.chat(
model=model,
messages=[{"role": "user", "content": message}],
stream=True,
)
for chunk in stream:
content = chunk["message"]["content"]
print(content, end="", flush=True)
print()
stream_chat("llama3", "Write a Python function to reverse a linked list.")
5. Multi-Turn Conversation
class OllamaChat:
def __init__(self, model: str = "llama3", system_prompt: str = ""):
self.model = model
self.messages = []
if system_prompt:
self.messages.append({"role": "system", "content": system_prompt})
def chat(self, user_input: str) -> str:
"""Send a message and get a response, maintaining history."""
self.messages.append({"role": "user", "content": user_input})
response = ollama.chat(
model=self.model,
messages=self.messages,
)
assistant_msg = response["message"]["content"]
self.messages.append({"role": "assistant", "content": assistant_msg})
return assistant_msg
def clear_history(self) -> None:
"""Clear conversation history."""
system = self.messages[0] if self.messages and self.messages[0]["role"] == "system" else None
self.messages = []
if system:
self.messages.append(system)
# Usage
chat = OllamaChat(model="llama3", system_prompt="You are a Python expert.")
print(chat.chat("What is a decorator?"))
print(chat.chat("Show me an example."))
print(chat.chat("How do I pass arguments to it?"))
6. Generate Embeddings
def generate_embedding(text: str, model: str = "nomic-embed-text") -> list[float]:
"""Generate embeddings using a local Ollama model.
Args:
text: Input text.
model: Embedding model name.
Returns:
Embedding vector.
"""
response = ollama.embeddings(model=model, prompt=text)
return response["embedding"]
# First pull the embedding model
# ollama pull nomic-embed-text
embedding = generate_embedding("Redis is an in-memory data store.")
print(f"Embedding dimensions: {len(embedding)}")
7. Function Calling with Ollama
import json
def function_calling_chat(model: str, message: str, tools: list[dict]) -> dict:
"""Chat with function calling support.
Args:
model: Ollama model name.
message: User message.
tools: List of tool definitions.
Returns:
Response dict with content or tool calls.
"""
response = ollama.chat(
model=model,
messages=[{"role": "user", "content": message}],
tools=tools,
)
return response["message"]
# Define tools
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
},
"required": ["city"],
},
},
},
]
result = function_calling_chat("llama3", "What's the weather in Madrid?", tools)
if result.get("tool_calls"):
for call in result["tool_calls"]:
print(f"Tool: {call['function']['name']}")
print(f"Args: {call['function']['arguments']}")
else:
print(f"Response: {result['content']}")
8. Model Management
def list_models() -> list[dict]:
"""List all installed Ollama models."""
response = ollama.list()
return [
{
"name": m["name"],
"size": m["size"] / 1e9, # GB
"modified": m["modified_at"],
}
for m in response["models"]
]
def pull_model(model_name: str) -> None:
"""Pull (download) a model."""
for progress in ollama.pull(model_name, stream=True):
if progress.get("status"):
print(f"\r{progress['status']}", end="", flush=True)
print()
def delete_model(model_name: str) -> None:
"""Delete a model to free disk space."""
ollama.delete(model_name)
print(f"Deleted {model_name}")
# List installed models
for model in list_models():
print(f"{model['name']}: {model['size']:.1f} GB")
How It Works
- Ollama server runs as a local process (default port 11434). The Python client sends HTTP requests to this server.
- Model pulling downloads GGUF (quantized) model files to
~/.ollama/models/. Quantization reduces model size and memory usage while maintaining reasonable quality. - Streaming uses Server-Sent Events — the server sends tokens as they are generated, and the client yields them as chunks.
- Embeddings use specialized embedding models (like
nomic-embed-text) that are optimized for producing vector representations, not text generation. - Function calling — Ollama parses tool definitions and can return structured tool call requests. The model decides whether to call a tool or answer directly.
Variants
Using with LangChain
from langchain_ollama import ChatOllama, OllamaEmbeddings
llm = ChatOllama(model="llama3", temperature=0.3)
embeddings = OllamaEmbeddings(model="nomic-embed-text")
# Use in LangChain chains
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
chain = (
ChatPromptTemplate.from_template("Explain {topic} briefly.")
| llm
| StrOutputParser()
)
result = chain.invoke({"topic": "vector databases"})
Async API
import asyncio
from ollama import AsyncClient
async def async_chat():
client = AsyncClient()
response = await client.chat(
model="llama3",
messages=[{"role": "user", "content": "Hello!"}],
)
return response["message"]["content"]
result = asyncio.run(async_chat())
Custom Modelfile (Fine-Tuned Models)
# Create a Modelfile
cat > Modelfile <<EOF
FROM llama3
SYSTEM "You are a senior DevOps engineer. Answer concisely."
PARAMETER temperature 0.3
PARAMETER top_p 0.9
EOF
# Build custom model
ollama create devops-assistant -f Modelfile
response = ollama.chat(
model="devops-assistant",
messages=[{"role": "user", "content": "How to optimize Docker build times?"}],
)
print(response["message"]["content"])
Batch Processing
def batch_generate(
prompts: list[str],
model: str = "llama3",
) -> list[str]:
"""Generate responses for multiple prompts."""
results = []
for prompt in prompts:
response = ollama.generate(
model=model,
prompt=prompt,
options={"temperature": 0},
)
results.append(response["response"])
return results
summaries = batch_generate([
"Summarize: Redis is an in-memory data structure store.",
"Summarize: PostgreSQL is a relational database with ACID compliance.",
])
Best Practices
-
For a deeper guide, see a Local RAG Pipeline with ChromaDB and Sentence Transformers.
-
Start with
llama3(8B) — good balance of quality and speed; needs ~5GB RAM -
Use
temperature=0for factual tasks — code generation, data extraction, classification -
Pull embedding models separately —
nomic-embed-textis optimized for embeddings, not chat -
Monitor RAM usage — models are loaded into memory; running multiple models simultaneously can cause OOM
Common Mistakes
- Not pulling the model first —
ollama pullmust be run before using a model in Python - Using too large a model — Llama 3 70B needs 40GB+ RAM; start with 8B models
- Not setting
stream=Truefor long outputs — non-streaming waits for the full response, which can take minutes - Mixing embedding and chat models — chat models produce poor embeddings; use dedicated embedding models
Frequently Asked Questions
Which model should I start with?
llama3 (8B) for general chat, phi3 (3.8B) for lightweight tasks, mistral (7B) for code, nomic-embed-text for embeddings.
How much RAM do I need?
8B models need ~5GB, 13B models need ~8GB, 70B models need ~40GB. Quantized (Q4) models use less.
Can I use Ollama in production?
Yes. Ollama is production-ready. For multi-user setups, run Ollama on a dedicated GPU server and connect via HTTP.
How does Ollama compare to OpenAI?
Ollama is free and private but slower and less capable than GPT-4o. Use Ollama for privacy, cost savings, or offline use. Use OpenAI for maximum quality.
Related Resources
Stream LLM Output with Server-Sent Events (SSE)
Stream LLM responses to clients in real-time using Server-Sent Events with FastAPI, OpenAI streaming, and async generators for token-by-token output
Recipea Local RAG Pipeline with ChromaDB and Sentence Transformers
Implement retrieval-augmented generation locally with ChromaDB, sentence-transformers embeddings, and LLM generation without external API dependencies
RecipeCompose LCEL Chains in LangChain for Multi-Step LLM
Build composable LLM pipelines with LangChain Expression Language (LCEL) using pipes, parallel execution, and custom runnable components
RecipeBuild Stateful AI Agents with LangGraph State Machines
Create multi-step AI agents with LangGraph using state machines, conditional edges, tool calling, and human-in-the-loop checkpoints for production workflows
GuideLocal LLM Deployment: Ollama, vLLM & llama.cpp
Deploy large language models locally and on-premise. Covers Ollama, vLLM, llama.cpp, quantization, GPU sizing, API serving, Docker, and local vs cloud.