LangChain, LlamaIndex, Vercel AI SDK, Cursor and Open WebUI
Point LangChain, LlamaIndex, Vercel AI SDK, Cursor, Continue and Open WebUI at uttapen with one base URL and an sk-up- key. Verified configs.
Updated: September 7, 2026
Anything that speaks "OpenAI-compatible" works with uttapen: the URL https://api.uttapen.ir/v1, a key that starts with sk-up-, and model ids in the form provider/model. The LangChain, LlamaIndex and Vercel AI SDK snippets on this page were run against the gateway and verified. For Cursor, Continue and Open WebUI the exact field names come from those tools' own documentation and may differ slightly in your version; we flag the places where we are not certain.
LangChain (Python)
pip install langchain-openai
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
llm = ChatOpenAI(
model="openai/gpt-5-mini",
base_url="https://api.uttapen.ir/v1",
api_key="sk-up-...",
max_tokens=500,
)
print(llm.invoke("Name three advantages of Postgres.").content)
for chunk in llm.stream("Write one short motivational sentence."):
print(chunk.content, end="", flush=True)
emb = OpenAIEmbeddings(
model="openai/text-embedding-3-small",
base_url="https://api.uttapen.ir/v1",
api_key="sk-up-...",
check_embedding_ctx_length=False,
)
vec = emb.embed_query("lease agreement")
check_embedding_ctx_length=False is required, because LangChain chunks the text with OpenAI's local tokenizer by default, which is wrong for non-OpenAI models. Tool calling with llm.bind_tools([...]) and structured output with llm.with_structured_output(Schema) behave exactly as they do against OpenAI. In LangChain.js the same values go to new ChatOpenAI({ model, apiKey, configuration: { baseURL } }).
LlamaIndex (Python)
pip install llama-index-llms-openai-like llama-index-embeddings-openai
from llama_index.llms.openai_like import OpenAILike
from llama_index.core.llms import ChatMessage
llm = OpenAILike(
model="openai/gpt-5-mini",
api_base="https://api.uttapen.ir/v1",
api_key="sk-up-...",
is_chat_model=True,
is_function_calling_model=True,
context_window=128000,
)
print(llm.complete("Name three advantages of Postgres.").text)
for chunk in llm.stream_chat([ChatMessage(role="user", content="Hello")]):
print(chunk.delta, end="", flush=True)
Use OpenAILike, not OpenAI: the latter validates the model id against a fixed list of OpenAI models and rejects anything shaped like openai/…. is_chat_model=True is mandatory. For embeddings, OpenAIEmbedding(model_name=..., api_base=..., api_key=...) from the llama-index-embeddings-openai package works; if it rejects the model name, use OpenAILikeEmbedding from llama-index-embeddings-openai-like.
Vercel AI SDK (Node and Next.js)
npm install ai @ai-sdk/openai-compatible
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { generateText, streamText, embed } from "ai";
const uttapen = createOpenAICompatible({
name: "uttapen",
baseURL: "https://api.uttapen.ir/v1",
apiKey: process.env.UTTAPEN_API_KEY,
includeUsage: true,
});
const { text, usage } = await generateText({
model: uttapen("openai/gpt-5-mini"),
prompt: "Name three advantages of Postgres.",
maxOutputTokens: 300,
});
const result = streamText({ model: uttapen("anthropic/claude-sonnet-4.5"), prompt: "Hello" });
for await (const delta of result.textStream) process.stdout.write(delta);
const { embedding } = await embed({
model: uttapen.embeddingModel("openai/text-embedding-3-small"),
value: "lease agreement",
});
In a Next.js route handler, return result.toUIMessageStreamResponse() (or toTextStreamResponse()) and use useChat on the client, so the key never leaves the server. For reasoning and other provider-specific fields use providerOptions: { uttapen: { reasoning: { effort: "low" } } }. @ai-sdk/openai (the OpenAI-specific package) also accepts a baseURL, but some of its features — the Responses API — do not work against uttapen; openai-compatible is the safe choice.
Cursor
In Cursor Settings → Models:
- Replace the OpenAI key with your
sk-up-…key. - Turn on Override OpenAI Base URL and set it to
https://api.uttapen.ir/v1. - Use "Add model" and type the model id exactly:
anthropic/claude-sonnet-4.5,openai/gpt-5,deepseek/deepseek-chat. - Turn Cursor's built-in models off so requests don't accidentally go to Cursor's own servers.
With a custom base URL, Cursor disables some of its own features (Tab completion, Composer with proprietary models); that is a Cursor limitation. The exact option names have changed between versions — if you don't see "Override OpenAI Base URL", look for the base URL field just under the OpenAI API Key on the same screen.
Continue (VS Code and JetBrains)
In ~/.continue/config.yaml:
models:
- name: Claude Sonnet via uttapen
provider: openai
model: anthropic/claude-sonnet-4.5
apiBase: https://api.uttapen.ir/v1
apiKey: sk-up-...
roles: [chat, edit]
- name: GPT-5 mini via uttapen
provider: openai
model: openai/gpt-5-mini
apiBase: https://api.uttapen.ir/v1
apiKey: sk-up-...
roles: [chat, autocomplete]
- name: Embeddings via uttapen
provider: openai
model: openai/text-embedding-3-small
apiBase: https://api.uttapen.ir/v1
apiKey: sk-up-...
roles: [embed]
provider: openai with a custom apiBase is correct. If you are still on the older config.json, the same fields live under apiBase inside the models array. Check the roles field and its values against the docs for your version; older releases had separate tabAutocompleteModel and embeddingsProvider entries.
Open WebUI
In Admin Panel → Settings → Connections, under OpenAI API:
- API Base URL:
https://api.uttapen.ir/v1 - API Key:
sk-up-...
Once saved, Open WebUI pulls the model list from GET /v1/models and all 400+ models show up in the picker; you can narrow that down under Settings → Models. If you run it in Docker, the same two values can be passed as the OPENAI_API_BASE_URL and OPENAI_API_KEY environment variables. Open WebUI sends extra requests for conversation titling and search — pick a cheap model (openai/gpt-5-nano, for instance) as the "Task model" under Settings → Interface so you don't pay for those without noticing.
Other tools
Anything that accepts a "custom OpenAI endpoint" works with the same three values: aider (--openai-api-base https://api.uttapen.ir/v1 --model openai/anthropic/claude-sonnet-4.5 — aider itself requires the leading openai/ prefix for a custom endpoint, followed by the full model id), Cline, Zed, LibreChat, n8n (an OpenAI-type credential with a base URL), Dify, Flowise. The shared rules:
- If the tool appends
/v1itself and you get a404, give it the URL without/v1. A quickcurl https://api.uttapen.ir/v1/modelstells you which form is right. - If the tool reads the model list from
GET /v1/models, everything is automatic; if not, type the id exactly and in lowercase. - Tools built on the Responses API (some recent Codex CLI versions) do not work yet; switch them to chat completions mode in their settings.
- Give each tool its own key with a monthly cap, so you can see what each one spends under
group_by=key(API keys).