chatlas

Programming with LLMs in Python

One consistent interface to any LLM provider.

Hello, chatlas

import chatlas as ctl

chat = ctl.ChatAnthropic()
chat.chat("What is the most popular Python package for data analysis?")

That’s it. One import, two lines, streaming output.

Bring your own provider

# Hosted
chat = ctl.ChatAnthropic()          # Claude
chat = ctl.ChatOpenAI()             # GPT
chat = ctl.ChatGoogle()             # Gemini

# Local (no API key needed)
chat = ctl.ChatOllama()             # Ollama
chat = ctl.ChatLMStudio()           # LM Studio

# Pick via a single string
chat = ctl.ChatAuto("anthropic:claude-sonnet-4-6")

Same API, every provider. Use whatever you have today.

System prompt

chat = ctl.ChatAnthropic(
    system_prompt=(
        "You are a data science tutor. "
        "Explain concepts simply, using analogies from everyday life."
    ),
)

chat.chat("What is a p-value?")

The system prompt is your invisible hand — it shapes every response without the user seeing it.

Multi-turn: the chat remembers

chat.chat("What about confidence intervals?")
chat.chat("How do those two concepts relate?")

Chat objects are stateful — they accumulate conversation history.

chat.get_turns()          # inspect the history
chat.set_turns([])        # reset to start fresh

.chat() vs .stream()

.chat() — interactive

# Blocks, auto-echoes output
chat.chat("Explain PCA")

Great for notebooks and exploration.

.stream() — for apps

# Returns a generator you control
for chunk in chat.stream("Explain PCA"):
    print(chunk, end="")

Great for Shiny, FastAPI, etc.

Both have async variants: .chat_async() and .stream_async()

Tool calling: the big idea

LLMs can’t do things — but they can ask you to do things.

sequenceDiagram
    participant U as You
    participant L as LLM
    participant T as Your Tool

    U->>L: "What's the weather in Paris?"
    L->>T: get_weather(city="Paris")
    T-->>L: {"temp": 18, "condition": "cloudy"}
    L->>U: "It's 18°C and cloudy in Paris."

The model decides what to call. Your code executes it.

Your first tool

def get_current_weather(lat: float, lng: float):
    """Get the current weather for a location.

    Parameters
    ----------
    lat
        The latitude.
    lng
        The longitude.
    """
    import requests
    resp = requests.get(
        "https://api.open-meteo.com/v1/forecast",
        params={"latitude": lat, "longitude": lng, "current": "temperature_2m"},
    )
    return resp.json()["current"]

chat = ctl.ChatAnthropic()
chat.register_tool(get_current_weather)
chat.chat("What's the weather in Paris?")

What just happened?

When you ran chat.chat(...), chatlas ran this loop for you:

  1. Sent your message + the tool schema to the model
  2. Model responded: “call get_current_weather(48.85, 2.35)
  3. chatlas called your function with those arguments
  4. Sent the result back to the model
  5. Model responded with a natural language answer

Steps 2–4 can repeat — the model can call multiple tools in sequence.

Tools for data scientists

import pandas as pd

tips = pd.read_csv(
    "https://raw.githubusercontent.com/mwaskom/seaborn-data/master/tips.csv"
)

def query_tips(code: str):
    """Execute Python code to answer a question about the tips dataset.

    Parameters
    ----------
    code
        Python code to run. The `tips` DataFrame and `pd` (pandas) are available.
    """
    return str(eval(code, {"tips": tips, "pd": pd}))

chat = ctl.ChatAnthropic()
chat.register_tool(query_tips)
chat.chat("What's the average tip percentage by day of the week?")

Built-in tools

from chatlas import tool_web_search

chat = ctl.ChatAnthropic()
chat.register_tool(tool_web_search())

chat.chat("What were the top Python packages released this month?")

No API key, no configuration — the provider handles it natively.

Quick tour: more capabilities

Structured output

from pydantic import BaseModel

class Person(BaseModel):
    name: str
    age: int

chat.chat_structured(
    "Susan is 13 years old",
    data_model=Person,
)
# → Person(name='Susan', age=13)

MCP tools

await chat.register_mcp_tools_stdio_async(
    command="uvx",
    args=["mcp-server-fetch"],
)

Images & PDFs

chat.chat(
    ctl.content_image_file("chart.png"),
    "Describe this chart.",
)

🛠️ Hands-on: build with chatlas

~15 minutes — pick your adventure:

🧪 Option A: Extend an example

Start from the exercise file and:

  1. Add a system prompt that sets a persona
  2. Add a second tool (e.g., a unit converter, a dice roller, etc.)
  3. Bonus: add tool_web_search()

🤖 Option B: Vibe-code it

Open your favorite chatbot and prompt:

“Create a chatlas Python app that [your idea here]”

See how far you get — then read and fix what it generates.

Exercise file: exercises/01-chatlas-starter.py

☕ Break — 5 min