OpenAI SDK or LangChain: when a framework helps and when it gets in the way

One scenario written with the plain SDK and with LangChain: how much code, where debugging is easier and how to keep control of token spend.

5 min · · development · сравнение

The "openai sdk vs langchain" question usually comes up when you have to pick a foundation for a project: take a thin library and write the logic yourself, or add a framework with ready-made chains. The answer depends on what you are building, not on fashion. Below is one scenario written with the OpenAI SDK and with LangChain in Python. We compare code size, debugging and token control, and finish with a rule of thumb.

Both options use a fetch_... key and the address https://api.zukko.pro/v1. Zukko exposes an OpenAI-compatible API, so the only things you change are base_url and the model name.

What the OpenAI SDK is and how it differs from LangChain

The OpenAI SDK (the openai package) is a thin wrapper over HTTP requests: you build a list of messages, call a method, and get a reply plus a usage field with token counts. Whatever happens between calls is your code.

LangChain is a framework on top of such SDKs. It adds prompt templates, output parsers, chains, memory, vector store integrations and agents. The model is just one interchangeable component.

In short: the SDK gives you one call; LangChain gives you a frame for several calls and integrations. If you need one call, you do not need the frame. More on the SDK itself in Python SDK: a complete example.

One scenario, two ways

Scenario: take a customer message and return JSON with a category and a priority.

Plain SDK

bash
import json
from openai import OpenAI

client = OpenAI(base_url="https://api.zukko.pro/v1", api_key="fetch_...")

def classify(text: str) -> dict:
    resp = client.chat.completions.create(
        model="gpt-6.1-sol",
        messages=[
            {"role": "system", "content": "Return JSON: {\"category\": str, \"priority\": \"low|mid|high\"}"},
            {"role": "user", "content": text},
        ],
        response_format={"type": "json_object"},
    )
    print("tokens:", resp.usage.prompt_tokens, resp.usage.completion_tokens)
    return json.loads(resp.choices[0].message.content)

print(classify("The confirmation email never arrives, but the payment went through"))

Everything is visible: which messages went out, how many tokens were spent, what came back. Request parameters are described in the chat completions reference.

LangChain

bash
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser

llm = ChatOpenAI(
    model="gpt-6.1-sol",
    base_url="https://api.zukko.pro/v1",
    api_key="fetch_...",
)
prompt = ChatPromptTemplate.from_messages([
    ("system", "Return JSON: {{\"category\": str, \"priority\": \"low|mid|high\"}}"),
    ("human", "{text}"),
])
chain = prompt | llm | JsonOutputParser()

print(chain.invoke({"text": "The confirmation email never arrives, but the payment went through"}))

The line count is about the same. There is no saving in size; the benefit comes later, because the prompt | llm | parser chain is easy to extend with another step. The downside is that part of what happens is hidden: the final prompt text and usage have to be fetched separately (for example from AIMessage.usage_metadata if you call the model without the parser). LangChain class and package names change between versions, so check their documentation. Another walkthrough is in LangChain through a gateway.

Where a framework helps

  • Several providers in one codebase. If you want to switch between model families without rewriting calls. With Zukko that is already a one-line change of model, but LangChain offers ready abstractions.
  • RAG and document handling. File loaders, chunking and vector stores take a long time to write from scratch.
  • Agents with tools and state. If you need a graph of steps with branching, ready primitives save time.
  • The team already knows the framework. That is a legitimate reason: a shared style is worth more than theoretical purity.

Where a framework gets in the way

  • One or two model calls. The framework pulls in extra packages for something that fifteen lines can do.
  • Debugging. When an answer looks odd you need the exact messages sent to the API. In the plain SDK that is a dictionary in your code; in a framework it is the output of several layers.
  • Tokens and money. A chain or an agent makes as many calls as it needs, and each one carries the accumulated context. You only see it if you count.
  • Updates. Framework interfaces change, and code written a year ago may need fixes. A thin SDK has a smaller surface.

Token control: a worked example

Say a LangChain agent takes five steps and each step adds about 1000 tokens to the context. Inputs per step: 2000, 3000, 4000, 5000, 6000 tokens, 20,000 in total. Output is 300 tokens per step, 1500 in total.

For gpt-6.1-sol (prices as of October 18, 2026: $0.27 per 1M input, $1.32 per 1M output):

input: 20,000 × 0.27 / 1,000,000 = $0.0054 output: 1,500 × 1.32 / 1,000,000 = $0.00198 total: about $0.0074 per task

The sum is small, but if you had budgeted the task as "one call of 2000 tokens" ($0.00054 for input), the real input cost is ten times higher. So, whichever option you choose, it helps to:

  1. Log usage for every call (resp.usage in the SDK, usage_metadata on the message in LangChain).
  2. Watch spend in the dashboard under "Usage" and compare it with your estimate.
  3. Estimate request size up front with the token counting endpoint.

The current list of models and prices is GET https://api.zukko.pro/v1/models or the "Models" page in the dashboard.

A simple rule

SituationWhat to take
One to three model calls, clear logicplain OpenAI SDK
You need tight token control and easy debuggingplain SDK, add a framework later
RAG with several sources, ready loadersLangChain or similar
Agent with branching and statea framework, but log usage
An evening prototypewhatever is faster for you

Start with the SDK and add a framework once you know which of its parts you actually need. Removing a framework from a finished project is usually more expensive than adding one. Test the choice on your own task: for a small scenario writing both versions is not much work.

To try it, create a key in the dashboard and put it together with https://api.zukko.pro/v1 into any of the examples above. Python docs are here.

FAQ

Can I use the OpenAI SDK and LangChain together?

Yes. A common setup is LangChain for loading documents and search, with the model call itself made through the SDK or through ChatOpenAI with the same base_url. They do not conflict, and both go through one key.

Is LangChain slower than the plain SDK?

The framework layer adds a little processing overhead, but most of the time is the model's answer. We give no numbers: measure on your data. The difference is more visible in how many calls a chain makes.

Do I need to change code when switching models?

For both options it is enough to replace the model string, for example with claude-sonnet-5-5 or deepseek-v4.1-flash. If you rely on something specific to one model (tool format, cache), test that behavior separately.

What if I need the Anthropic format?

Then use the official Anthropic SDK with the address https://api.zukko.pro without /v1. It is a separate client; LangChain has its own package for it, but check its documentation.

zukko

Pay 25×
less

for Claude Code & ChatGPT

One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.

Get API keyDocumentation

api.zukko.pro · fetch_*

OpenAI SDK vs LangChain: which to pick for a project · Zukko