---
title: "LangChain OpenAI compatible API: uncensored models"
canonical: https://uncensoredllm.ai/guides/langchain
generated: 2026-11-09T16:02:06.237Z
source: https://uncensoredllm.ai/guides/langchain
base_url: https://api.uncensoredllm.ai/v1
---

# LangChain and LangGraph with an uncensored model

ChatOpenAI with base_url and api_key in Python, configuration.baseURL in JavaScript, an uncensored model id, and the same instance handed to create_agent for a tool-calling agent. Token counts on every message; the cost per agent turn and the errors.

Steps checked November 2, 2026 · prices as of November 9, 2026

LangChain’s `ChatOpenAI` takes a base URL and a key, so an uncensored build answers through the same class, chains and agents your code already uses. One constructor in Python or JavaScript, the model id from the [models page](https://uncensoredllm.ai/models), and `create_agent` runs its tool loop on it.

## Where the settings are

There is no settings screen. The endpoint and the key are constructor arguments on the OpenAI chat model class: `ChatOpenAI` from `langchain-openai` in Python, `ChatOpenAI` from `@langchain/openai` in JavaScript. Python takes `base_url` and `api_key`; JavaScript takes `apiKey` and a `configuration` object with `baseURL`. `init_chat_model` and `initChatModel` take the same two values with `model_provider="openai"`.

Agents reuse that instance. LangChain 1.x builds them with `create_agent` (`createAgent` in JavaScript); LangGraph’s `create_react_agent` is the earlier name and now points at it. Both take the model object, so the endpoint is configured once.

## Steps

1. Install the chat model package, and the agent package if you want the tool loop:

```bash
pip install -U langchain langchain-openai
```

```bash
npm install langchain @langchain/openai @langchain/core zod
```

1. Create a key in the [console](https://app.uncensoredllm.ai/console) and put it in an environment variable. The code reads the variable; it never holds the key.

```bash
export UNCENSOREDLLM_API_KEY="sk-ullm-live-…"
```

1. Python: construct `ChatOpenAI` with the model id, the key and this base URL. `invoke` returns an `AIMessage` whose `usage_metadata` carries the tokens you were billed for; `stream` yields chunks as the model writes. The ids on this page (glm-5.3-uncensored, glm-5.3-flash-uncensored, deepseek-v4.1-flash-abliterated) are the three LangChain users call most here; any id on the [models page](https://uncensoredllm.ai/models) goes in the same way.

```
import os
from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    model="glm-5.3-uncensored",
    api_key=os.environ["UNCENSOREDLLM_API_KEY"],
    base_url="https://api.uncensoredllm.ai/v1",
    max_tokens=1024,
    stream_usage=True,  # token counts on streamed answers too; off by default once base_url is set
)

response = model.invoke("Explain, in three sentences, when a bloom filter beats a hash set.")
print(response.content)
print(response.usage_metadata)  # {'input_tokens': …, 'output_tokens': …, 'total_tokens': …}

for chunk in model.stream("List the POSIX signals a daemon should handle."):
    print(chunk.content, end="", flush=True)
```

`init_chat_model` builds the same object from a provider name, which suits code that switches models from config:

```
from langchain.chat_models import init_chat_model

model = init_chat_model(
    model="glm-5.3-flash-uncensored",
    model_provider="openai",
    base_url="https://api.uncensoredllm.ai/v1",
    api_key=os.environ["UNCENSOREDLLM_API_KEY"],
)
```

The LangChain docs also let the environment carry both values: `api_key` falls back to `OPENAI_API_KEY`, and `base_url` to `OPENAI_API_BASE`, then `OPENAI_BASE_URL`. With these two exported, `ChatOpenAI(model="…")` alone reaches the API. Keep the explicit arguments in code that also talks to OpenAI, or the two will share one base URL.

```bash
export OPENAI_API_KEY="sk-ullm-live-…"
export OPENAI_BASE_URL="https://api.uncensoredllm.ai/v1"
```

1. JavaScript: the same three values, with the URL under `configuration.baseURL`. The `configuration` object is passed to the OpenAI SDK client, so its other client options go there too.

```
import { ChatOpenAI } from "@langchain/openai";

const model = new ChatOpenAI({
  model: "glm-5.3-uncensored",
  apiKey: process.env.UNCENSOREDLLM_API_KEY,
  configuration: { baseURL: "https://api.uncensoredllm.ai/v1" },
  maxTokens: 1024,
});

const response = await model.invoke("Explain, in three sentences, when a bloom filter beats a hash set.");
console.log(response.content);
console.log(response.usage_metadata); // { input_tokens, output_tokens, total_tokens }

const stream = await model.stream("List the POSIX signals a daemon should handle.");
for await (const chunk of stream) process.stdout.write(String(chunk.content));
```

1. Agent: hand the instance to `create_agent` with your tools. The loop calls the model, runs the tool it asked for, appends the result and calls again until the model answers in text. Use a build whose model page lists tool calls; the table below says which.

```
import os
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI

@tool
def read_file(path: str) -> str:
    """Read a UTF-8 text file and return its contents."""
    with open(path, encoding="utf-8") as f:
        return f.read()

model = ChatOpenAI(
    model="glm-5.3-uncensored",
    api_key=os.environ["UNCENSOREDLLM_API_KEY"],
    base_url="https://api.uncensoredllm.ai/v1",
)

agent = create_agent(
    model=model,
    tools=[read_file],
    system_prompt="You review Python code. Read a file before you comment on it.",
)

result = agent.invoke({"messages": [{"role": "user", "content": "Review ./app.py for bugs."}]})
print(result["messages"][-1].content)
```

Code on LangGraph’s `create_react_agent` keeps working with the same `model` and `tools` arguments (its system prompt argument is `prompt`); the deprecation notice asks for the import above. The JavaScript agent is the same shape:

```
import { readFile } from "node:fs/promises";
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";
import * as z from "zod";

const readTextFile = tool(async ({ path }) => readFile(path, "utf8"), {
  name: "read_file",
  description: "Read a UTF-8 text file and return its contents.",
  schema: z.object({ path: z.string() }),
});

const model = new ChatOpenAI({
  model: "glm-5.3-uncensored",
  apiKey: process.env.UNCENSOREDLLM_API_KEY,
  configuration: { baseURL: "https://api.uncensoredllm.ai/v1" },
});

const agent = createAgent({
  model,
  tools: [readTextFile],
  systemPrompt: "You review TypeScript code. Read a file before you comment on it.",
});

const result = await agent.invoke({ messages: [{ role: "user", content: "Review ./app.ts for bugs." }] });
console.log(result.messages.at(-1)?.content);
```

## What each setting does

- model: glm-5.3-uncensored the id as the models page spells it; sent as the model field of each request
- api_key / apiKey: os.environ["UNCENSOREDLLM_API_KEY"] the whole sk-ullm- key, sent as Authorization: Bearer; unset, Python falls back to OPENAI_API_KEY
- base_url / configuration.baseURL: https://api.uncensoredllm.ai/v1 where the requests go; the client appends /chat/completions, so the /v1 at the end matters
- max_tokens / maxTokens: 1024 the cap on output tokens per answer; the model page gives each build’s maximum
- stream_usage: True Python only: asks for the usage chunk on streams. LangChain turns it off once base_url is set; this API answers it, so turn it back on. JavaScript sends it by default (streamUsage)
- streaming: False makes invoke stream internally and assemble the answer; stream() streams either way
- temperature, timeout, max_retries: optional sampling temperature, the request timeout, and how many times the client retries a failed call
- tools / system_prompt: create_agent the functions the agent may call, and the message put first in every call; createAgent spells it systemPrompt

## Picking a model

`ChatOpenAI` sends OpenAI-format requests, and every build on the models page answers in that format. For plain `invoke` and `stream` any id works. The agent loop needs tool calls, and so does `bind_tools` and the default `with_structured_output` method, which is function calling. An agent turn carries the conversation, the files read and every tool result so far, so input tokens are most of the bill and a build with a low input price costs the least per turn.

| Model | One agent turn | Tool calls |
| --- | --- | --- |
| [GLM-5.3 Uncensored](https://uncensoredllm.ai/models/glm-5.3-uncensored) | $0.01 | [model page](https://uncensoredllm.ai/models/glm-5.3-uncensored) |
| [GLM-5.3 Flash Uncensored](https://uncensoredllm.ai/models/glm-5.3-flash-uncensored) | $0.0047 | [model page](https://uncensoredllm.ai/models/glm-5.3-flash-uncensored) |
| [DeepSeek V4.1 Flash Abliterated](https://uncensoredllm.ai/models/deepseek-v4.1-flash-abliterated) | $0.04 | [model page](https://uncensoredllm.ai/models/deepseek-v4.1-flash-abliterated) |
| [DeepSeek V4 Pro Uncensored](https://uncensoredllm.ai/models/deepseek-v4-pro-uncensored) | $0.04 | [model page](https://uncensoredllm.ai/models/deepseek-v4-pro-uncensored) |
| [GLM-5.3 Abliterated](https://uncensoredllm.ai/models/glm-5.3-abliterated) | $0.09 | [model page](https://uncensoredllm.ai/models/glm-5.3-abliterated) |
| [GLM-5.3 Flash Code Uncensored](https://uncensoredllm.ai/models/glm-5.3-flash-code-uncensored) | $0.25 | [model page](https://uncensoredllm.ai/models/glm-5.3-flash-code-uncensored) |

40,000 input and 1,200 output tokens at the catalog prices on 2026-11-09. Tool calls as the catalog publishes them; where it does not, the model page says.

| Model | Build | Input $/M | Output $/M |
| --- | --- | --- | --- |
| [GLM-5.3 Uncensored](https://uncensoredllm.ai/models/glm-5.3-uncensored) glm-5.3-uncensored | Uncensored | $0.282 | $0.846 |
| [GLM-5.3 Flash Uncensored](https://uncensoredllm.ai/models/glm-5.3-flash-uncensored) glm-5.3-flash-uncensored | Uncensored | $0.1053 | $0.3685 |
| [DeepSeek V4.1 Flash Abliterated](https://uncensoredllm.ai/models/deepseek-v4.1-flash-abliterated) deepseek-v4.1-flash-abliterated | Abliterated | $0.8488 | $1.6975 |
| [DeepSeek V4 Pro Uncensored](https://uncensoredllm.ai/models/deepseek-v4-pro-uncensored) deepseek-v4-pro-uncensored | Uncensored | $0.97 | $1.94 |
| [GLM-5.3 Abliterated](https://uncensoredllm.ai/models/glm-5.3-abliterated) glm-5.3-abliterated | Abliterated | $2.1825 | $4.365 |
| [GLM-5.3 Flash Code Uncensored](https://uncensoredllm.ai/models/glm-5.3-flash-code-uncensored) glm-5.3-flash-code-uncensored | Uncensored | $6.00 | $6.00 |

Prices per million tokens, read from the live catalog on 2026-11-09.

For coding agents specifically, [the uncensored coding AI guide](https://uncensoredllm.ai/guides/uncensored-coding-ai) compares the code builds and costs a full coding day on each. The same ids work in [Cursor](https://uncensoredllm.ai/guides/cursor), [OpenCode](https://uncensoredllm.ai/guides/opencode) and, through a router, [Claude Code](https://uncensoredllm.ai/guides/claude-code).

## Check it

List the models your key can call; the id you put in `model` should be in the answer:

```bash
curl https://api.uncensoredllm.ai/v1/models -H "Authorization: Bearer sk-ullm-live-…"
```

Then send one request the way `ChatOpenAI` does. `-i` prints the headers: `x-uplink-request-id` names the request, and on a non-streamed answer `x-uplink-charge` is what it cost in USD.

```bash
curl -i https://api.uncensoredllm.ai/v1/chat/completions \
  -H "Authorization: Bearer sk-ullm-live-…" \
  -H "Content-Type: application/json" \
  -d '{"model": "glm-5.3-uncensored", "messages": [{"role": "user", "content": "Reply with the single word ok."}]}'
```

Then run the Python or JavaScript snippet. `usage_metadata` on the response holds the input and output tokens the request was billed on, and the same request shows on the console’s usage page with its charge. On a stream, the counts arrive on the last chunk, and only with `stream_usage=True` in Python.

## Errors and fixes

- `401 invalid_api_key`: the key is wrong or revoked, or `UNCENSOREDLLM_API_KEY` was not set in the shell that ran the script. Export it again, or copy the key again from the console. A 401 whose body names OpenAI means `base_url` was never set, so the request went to OpenAI with this key.
- `404 model_not_found`: the id in `model` is not on the models page. Check the spelling against the page. `404 route_not_found` means the request hit a path the API does not serve: a base URL missing its `/v1`, so the client posted to `/chat/completions` at the host root, or `/responses` because `use_responses_api=True` or a Responses-only feature such as `reasoning` or a built-in tool was set. This API serves `/v1/chat/completions`; leave those off.
- `402 insufficient_balance`: the balance does not cover the request. Top up in the console. `402 monthly_limit_reached` means the key hit the monthly spend limit you set on it.
- `403 scope_denied`: the key is limited to certain models and this is not one of them.
- `429 rate_limited`: the key hit its rate limit. The response carries `retry-after`; `max_retries` makes the client wait and try again on its own.
- `usage_metadata` is `None` on a Python stream: LangChain leaves `stream_usage` off whenever `base_url` is set. Pass `stream_usage=True` to the constructor or to the `stream` call.
- The agent answers in prose and never calls a tool, or the request fails on the `tools` field: the build does not take tool calls. Switch to one whose model page lists them. The same applies to `with_structured_output` on its default method; `method="json_schema"` needs a build that takes `response_format` instead.
- Output stops mid-sentence with `finish_reason` of `length`: `max_tokens` is too low for the answer. Raise it, up to the maximum on the model page.

## Questions

### Does LangChain use the OpenAI API?

ChatOpenAI speaks the OpenAI request format, and goes to OpenAI’s servers only when no base URL is set. With base_url in Python or configuration.baseURL in JavaScript pointed at this API and a key from the console, the same class, chains and agents run on an uncensored build and no OpenAI account is involved.

### Does LangChain support the OpenAI Responses API?

ChatOpenAI can route to it, with use_responses_api=True or when a Responses-only feature such as reasoning or a built-in tool is set. This API serves Chat Completions, which is ChatOpenAI’s default route, so leave those options off; invoke, stream, bind_tools and create_agent all run on Chat Completions.

### What models are available through LangChain here?

Every id on the models page: each answers /v1/chat/completions in the OpenAI format with the same key, so any of them goes into the model argument as the page spells it. For the agent loop, bind_tools or the default structured output method, pick a build whose page lists tool calls; the table above says which. See the models page.

### What is an OpenAI compatible API?

An endpoint that accepts the OpenAI request shape at /v1/chat/completions and answers in the OpenAI response shape, so a client written for OpenAI works by changing the base URL and the key. This API is one; LangChain’s ChatOpenAI is such a client, and usage_metadata on each answer carries the tokens the request was billed on, priced per token from your prepaid balance.

Every model above is on the [uncensored model list](https://uncensoredllm.ai/models) with its live price per million tokens. [Per-token pricing](https://uncensoredllm.ai/pricing) sets the whole catalog side by side, and the [family directory](https://uncensoredllm.ai/uncensored) groups the builds by base model.

[Get a key](https://app.uncensoredllm.ai/console) Prepaid from $10, no subscription.

## New models and price changes, once a month.

New uncensored builds with their prices, price moves, and what the daily model check found.
