LangChain
Connect LangChain to Mixen — pay from your balance in roubles, a catalog of 105+ models through the familiar ChatOpenAI.
Installation
Section titled “Installation”pip install -U langchain-openaiConfiguration
Section titled “Configuration”Mixen is OpenAI-protocol compatible — point ChatOpenAI at our base URL and pass your key:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI( model="openai/gpt-5.6-sol", base_url="https://api.mixen.ai/v1", api_key="your-api-key",)
print(llm.invoke("What is vector search?").content)Streaming — deltas arrive as the model generates:
for chunk in llm.stream("Explain RAG in three paragraphs"): print(chunk.content, end="", flush=True)Key points:
- In current
langchain-openaiversions the parameters arebase_urlandapi_key;openai_api_baseandopenai_api_keyare legacy aliases. - Instead of arguments you can set the
OPENAI_BASE_URLandOPENAI_API_KEYenvironment variables — explicit arguments take priority. - Tools work:
llm.bind_tools([...])— our/v1/chat/completionssupports tool calling.
Scenario: a chain with a system prompt and history
Section titled “Scenario: a chain with a system prompt and history”A bare invoke is fine for checking the key. A product gets an LCEL chain: a prompt template, the model, and an output parser joined with the | operator:
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholderfrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.messages import HumanMessage, AIMessagefrom langchain_openai import ChatOpenAI
llm = ChatOpenAI( model="anthropic/claude-sonnet-5", base_url="https://api.mixen.ai/v1", api_key="your-api-key",)
prompt = ChatPromptTemplate.from_messages([ ("system", "You are a tea shop consultant. Answer briefly: " "the variety, its taste, how to brew it. Never invent inventory items."), MessagesPlaceholder("history"), ("human", "{input}"),])
chain = prompt | llm | StrOutputParser()
history: list = []
def ask(question: str) -> str: answer = chain.invoke({"history": history, "input": question}) history.extend([HumanMessage(content=question), AIMessage(content=answer)]) return answer
print(ask("Suggest a tea for an evening session"))print(ask("Is there one like it but less astringent?")) # "like it" comes from historyWhat matters here:
StrOutputParserunwraps theAIMessage— the output is a plain string.MessagesPlaceholder("history")inserts the message list between the system prompt and the question; this example tracks history by hand — swap it forRunnableWithMessageHistorywhen you outgrow that.- The system prompt comes first and never changes — that’s deliberate: a stable prefix lands in the prompt cache (see economics).
Streaming and async
Section titled “Streaming and async”The same result streamed — straight on the chain:
for chunk in chain.astream({"history": history, "input": question}): print(chunk, end="", flush=True)And a fully async version — for services on asyncio:
import asyncio
async def main() -> None: answer = await chain.ainvoke({"history": history, "input": question}) print(answer)
asyncio.run(main())Our streaming is real: tokens leave the model as they are generated, so time-to-first-token doesn’t depend on the answer’s length.
RAG on our embeddings
Section titled “RAG on our embeddings”Embeddings and reranking over the same key are covered in the separate RAG guide: an index on POST /v1/embeddings, retrieval, LLM-as-judge reranking, and citations in the answer. The answer model there is the same ChatOpenAI with our base_url.
Model choice and economics
Section titled “Model choice and economics”| Task | Model | Price per 1M tokens |
|---|---|---|
| Bulk calls: classification, extraction, drafts | z-ai/glm-5.3-flash |
8.4 ₽ input / 27.9 ₽ output |
| Hard tasks, long context, careful code | anthropic/claude-sonnet-5 |
213.9 / 1069.5 ₽ |
The prompt cache is the main savings lever in chains with history: every next turn repeats the system prompt and past messages, and that repeating prefix is billed at ~10% of the input price (for claude-sonnet-5 — 21.4 ₽ per 1M instead of 213.9 ₽, for glm-5.3-flash — 1.7 ₽). The cache lives 5 minutes with an extension from every hit, so a dense dialog gets cheaper turn by turn. The rule is the same as in the example above: the unchanging parts at the start of the prompt, the new content at the end.
Reasoning depth is regulated by the API’s reasoning_effort parameter (from off to max: off, low, medium, high, xhigh, max). The allowed levels of a particular model are in GET /v1/models → capabilities.reasoning_efforts. Remember that reasoning eats into max_tokens: set a generous ceiling for long structured output.
Recommended models
Section titled “Recommended models”| Model | ID |
|---|---|
| GPT-5.6 Sol | openai/gpt-5.6-sol |
| Claude Opus 5 | anthropic/claude-opus-5 |
| GLM 5.3 | z-ai/glm-5.3 |
| DeepSeek V4 Pro | deepseek/deepseek-v4-pro |
| Kimi K3 | moonshotai/kimi-k3 |
Full list — in the catalog.
Troubleshooting
Section titled “Troubleshooting”- 401 / Invalid API Key — the key was copied incompletely or is not a Mixen API key; issue a new one in the dashboard, it starts with
mxn-. - “model not found” — pass the model ID from the catalog verbatim, including the vendor prefix (
openai/…,anthropic/…). - Requests go out with the wrong key — if
api_keyis not passed, the SDK falls back to theOPENAI_API_KEYenvironment variable (e.g. a real OpenAI key), and Mixen replies with 401. - The answer got cut off mid-sentence — on reasoning models the thinking part spends
max_tokens; raise the ceiling and checkfinish_reason("length"means truncation, not a finished thought). - Streaming delivers everything at once — check that the request goes through
stream()/astream(), not aninvoke()with manual slicing of the finished string.