The short answer: three settings on ChatOpenAI
If you searched for chat open ai langchain, you probably have a LangChain project that already uses ChatOpenAI and you want it to talk to a different backend. That is a configuration change, not a rewrite. The API on this site speaks the OpenAI chat completions protocol, so LangChain treats it like any other OpenAI-style server.
You need the base URL, your API key and the model ID uncensored. Install the packages first:
pip install -U langchain-openai langgraphThen create the model object:
import os
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nofilteraichat.com/v1",
api_key=os.environ["NOFILTER_API_KEY"],
temperature=0.8,
)
reply = llm.invoke("Write a two-sentence villain monologue for a heist novel.")
print(reply.content)That is the whole integration. Every LangChain feature built on ChatOpenAI now runs against the uncensored model: chains, retrievers that call the chat model, output parsers and agents. If you do not have a key yet, the API key page explains sign-up and the one-click trial credit.
Keep the key out of your code
Put the key in an environment variable and read it at start-up. On Linux or macOS:
export NOFILTER_API_KEY="paste-your-key-here"Create one shared client module for the project, for example llm.py that builds the model once and exports it. Every chain then imports the same object, and a later change of key, timeout or temperature happens in a single place. This also makes it easy to keep a second, colder instance for tool steps and a warmer one for creative writing, both pointing at the same endpoint with different settings.
One pitfall catches many people: ChatOpenAI also reads OPENAI_API_KEY and OPENAI_API_BASE from the environment. If those variables are set for another project, a half-configured object may send your request to the wrong server. Passing base_url and api_key explicitly, as above, removes the ambiguity.
You can confirm the connection without LangChain at all. The models endpoint should return a single entry:
curl https://api.nofilteraichat.com/v1/models \
-H "Authorization: Bearer $NOFILTER_API_KEY"If that works but LangChain fails, the problem is in your Python environment, not the key. If both fail with 401, check for a stray space or newline in the key. The quickstart lists the exact request format for comparison.
Streaming and token usage
Chat UIs feel better when tokens appear as they are generated. LangChain exposes that through .stream(), and the server sends standard SSE chunks when stream: true is set under the hood.
from langchain_openai import ChatOpenAI
import os
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nofilteraichat.com/v1",
api_key=os.environ["NOFILTER_API_KEY"],
stream_usage=True,
)
final = None
for chunk in llm.stream("Describe a rainy harbour town in four sentences."):
print(chunk.content, end="", flush=True)
final = chunk if final is None else final + chunk
print()
print(final.usage_metadata)With stream_usage=True the last chunk carries input and output token counts, which the API sends at the end of the stream. Keep those numbers. They are the cheapest way to see what an agent really costs, and we use them in the cost section below.
One thing to remember: a streamed request holds its concurrency slot until the stream finishes. If you abandon a stream without closing it, that slot stays busy for longer than you expect.
Node and TypeScript
The JavaScript package uses a slightly different option name. The base URL goes inside configuration:
npm install @langchain/openai @langchain/coreimport { ChatOpenAI } from "@langchain/openai";
const llm = new ChatOpenAI({
model: "uncensored",
apiKey: process.env.NOFILTER_API_KEY,
configuration: { baseURL: "https://api.nofilteraichat.com/v1" },
temperature: 0.8,
});
const res = await llm.invoke("Give me three names for a smuggler's ship.");
console.log(res.content);The TypeScript types for the response are the usual LangChain message classes, so existing code that reads res.content or res.usage_metadata keeps working. When you run this inside a server route, build the model once at module scope rather than per request. That avoids creating a new HTTP client for every call and keeps your concurrency easy to reason about.
Everything else, including .stream(), .bindTools() and .withStructuredOutput(), behaves the same way as in Python. Temperature, top_p, stop sequences, seed and the penalty options are passed through to the model, so you can set them on the constructor like you would for any other OpenAI-style backend.
Tool calling and a small agent
The API supports tool calling: the tools and tool_choice fields, streamed tool_calls, and tool results returned as role: "tool" messages. LangGraph's prebuilt agent uses exactly that loop. Here is an agent with one tool:
import os
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
@tool
def roll_dice(sides: int) -> int:
"""Roll a die with the given number of sides."""
import random
return random.randint(1, sides)
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nofilteraichat.com/v1",
api_key=os.environ["NOFILTER_API_KEY"],
)
agent = create_react_agent(llm, [roll_dice])
out = agent.invoke(
{"messages": [("user", "Roll a twenty-sided die twice and tell me the total.")]}
)
print(out["messages"][-1].content)Tool use with open-weight models is less predictable than with a hosted flagship, so treat it as something to test. Write tool descriptions in plain words, keep argument schemas small, and set temperature low (0.2 to 0.4) for agent steps that must produce valid arguments. If a call comes back malformed, LangChain surfaces it as an invalid tool call, and a retry with a clearer description usually fixes it.
Keep the agent loop bounded. LangGraph has a recursion limit, and you should set it to a number you can afford. If an agent can call tools forever, a bad prompt can burn through credit while you are away from the keyboard. A limit of 10 to 15 steps is a sensible default for most tasks, and you can pass it in the config when you invoke the agent.
Structured output
JSON mode is available through response_format. In LangChain, ask for it with the JSON mode method and name the keys in your prompt:
from pydantic import BaseModel
from langchain_openai import ChatOpenAI
import os
class Character(BaseModel):
name: str
role: str
secret: str
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nofilteraichat.com/v1",
api_key=os.environ["NOFILTER_API_KEY"],
)
structured = llm.with_structured_output(Character, method="json_mode")
print(structured.invoke(
"Return JSON with keys name, role and secret for a noir detective."
))Rate limits, retries and cost
A full key allows 8 concurrent requests and 300 requests per minute. A trial key allows 2 and 60. When you exceed either, the API answers with HTTP 429 and an error code: rate_limited for requests per minute, concurrency for too many at once. Temporary overload also returns 429, with a Retry-After: 5 header.
LangChain gives you two knobs for this. max_retries on the model retries failed calls with backoff, and max_concurrency in the run config caps parallel batch work:
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nofilteraichat.com/v1",
api_key=os.environ["NOFILTER_API_KEY"],
max_retries=4,
timeout=120,
)
answers = llm.batch(
["Prompt one", "Prompt two", "Prompt three", "Prompt four"],
config={"max_concurrency": 6},
)Setting max_concurrency to 6 on a full key leaves headroom for a second process. Remember that each agent step is a separate request, and a streamed step occupies its slot until the stream ends.
What an agent run costs
Pricing is $0.15 per 1M input tokens and $0.50 per 1M output tokens. Say an agent step sends 3,000 tokens of history, tools and instructions, and the model writes 500 tokens. The input costs 3,000 × $0.15 / 1,000,000 = $0.00045. The output costs 500 × $0.50 / 1,000,000 = $0.00025. One step is $0.0007. A ten-step run is $0.007, and 1,000 such runs cost $7.00.
Agents resend their growing history on every step, so input tokens dominate. Trimming old tool output is the cheapest optimisation you have. Failed or refused requests are not charged. Details of top-ups and bonuses are on the pricing page.
Common problems
- 404 on every call. The base URL must end in
/v1and must not include/chat/completions. LangChain appends the path itself. - Model not found. The only model ID is
uncensored. Names like gpt-4o will not work. - Context errors. The window is 100,000 tokens for prompt plus completion. Long retrieval chains can exceed it. Summarise or trim before you send.
- Request too large. The body limit is 8 MB, which matters if you stuff whole documents into a prompt.
- Embeddings fail. This API is text chat only. Keep your embedding model on another provider and use this endpoint for generation.
The same pattern works for other frameworks and chat front ends; the integration guide covers more clients. For anything unusual, the support page is the place to start.
