Puntos clave
- Usa ChatOpenAI con base_url configurado a la API y model configurado a uncensored.
- Las cadenas, el streaming, las llamadas a funciones y la salida JSON pasan todas por la ruta estándar de chat completions.
- Limita la concurrencia por lotes a 8 con una clave completa, o a 2 con una clave de prueba, para evitar errores 429.
- Este es un modelo alojado único, no un router, por lo que no hay cambio de modelo por proveedor.
La configuración rápida
La pregunta habitual sobre langchain + llm es si necesitas una integración especial para un endpoint de terceros. No la necesitas. El adaptador de chat de OpenAI de LangChain acepta una base URL personalizada, y esta API sigue el formato de chat completions de OpenAI, por lo que la clase estándar funciona. Instala el paquete, pasa tres valores y tendrás un objeto de modelo de chat que puedes insertar en cualquier cadena.
pip install -U langchain-openai langchain-core
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nsfwllmrouter.com/v1",
api_key="YOUR_API_KEY",
temperature=0.7,
)
print(llm.invoke("Give me one sentence about LangChain.").content)
Obtén la clave desde la página clave de API. Los nuevos usuarios pueden reclamar $0.50 de crédito de prueba con un solo clic después de registrarse, lo cual es suficiente para ejecutar todo lo de este artículo muchas veces. El ID del modelo es siempre uncensored, y las peticiones van a POST /v1/chat/completions.
Una nota sobre el nombre del sitio: a pesar de la palabra "router" en el dominio, este es un único modelo de pesos abiertos gestionado por un proveedor independiente, no un router entre proveedores ni la API oficial de ningún proveedor. No eliges entre modelos aquí. Tu código nombra un modelo, y ese es el que responde.
Mantén la clave fuera del código fuente
Codificar una clave funciona para una primera prueba y se convierte en un problema la primera vez que confirmas el archivo. Léela desde el entorno en su lugar. Las bibliotecas de cliente de OpenAI que envuelve LangChain buscan OPENAI_API_KEY y OPENAI_BASE_URL por defecto, por lo que puedes establecerlas en tu shell y construir el modelo con menos argumentos.
export OPENAI_API_KEY="YOUR_API_KEY"
export OPENAI_BASE_URL="https://api.nsfwllmrouter.com/v1"
import os
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url=os.environ.get("OPENAI_BASE_URL", "https://api.nsfwllmrouter.com/v1"),
api_key=os.environ["OPENAI_API_KEY"],
)
Pasar la base URL explícitamente, como arriba, tiene una ventaja. Si otro proyecto en la misma máquina usa el servicio real de OpenAI y depende de la misma variable de entorno, tu argumento explícito evita que ambos colisionen.
Una primera cadena con una plantilla de prompt
La mayoría del trabajo real usa una plantilla de prompt, el modelo y un analizador de salida unidos con el operador pipe. Este es el estilo de LangChain Expression Language, y no necesita nada especial para un endpoint personalizado.
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nsfwllmrouter.com/v1",
api_key="YOUR_API_KEY",
)
prompt = ChatPromptTemplate.from_messages([
("system", "You write short story openings in the style the user asks for."),
("human", "Genre: {genre}\nSetting: {setting}\nWrite three sentences."),
])
chain = prompt | llm | StrOutputParser()
print(chain.invoke({"genre": "noir mystery", "setting": "a rainy harbor town"}))
El modelo no tiene rechazos de contenido para uso adulto lícito, lo cual importa cuando tu cadena genera ficción, escenas de roleplay o diálogo de compañero que un modelo más cauteloso rechazaría. La única regla fija es que nunca se produce contenido sexual que involucre menores de edad. Construye tu propia validación de entrada sobre eso si tu producto tiene reglas más estrictas.
Streaming y uso
La API admite streaming sobre eventos enviados por el servidor con stream: true, y el uso se informa al final del stream. En LangChain llamas a .stream() e iteras sobre los fragmentos.
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nsfwllmrouter.com/v1",
api_key="YOUR_API_KEY",
)
for chunk in llm.stream("Write a four-line poem about debugging at night."):
print(chunk.content, end="", flush=True)
Un detalle a tener en cuenta: una petición en streaming ocupa una de tus ranuras de concurrencia hasta que termina el stream. Si tu interfaz de usuario abre un stream y el usuario se va sin cerrarlo, esa ranura permanece ocupada. Con una clave completa tienes 8 peticiones simultáneas, por lo que un puñado de streams abandonados pueden bloquear nuevas peticiones. Cancela el iterador cuando el cliente se desconecte.
Llamadas a funciones y agentes
Las llamadas a funciones son compatibles: pasas tools y opcionalmente tool_choice, el modelo responde con tool_calls (que también pueden transmitirse), y envías los resultados de vuelta como mensajes con role: "tool". En LangChain, bind_tools maneja el formato de conexión por ti.
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
@tool
def word_count(text: str) -> int:
'Count the words in a piece of text.'
return len(text.split())
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nsfwllmrouter.com/v1",
api_key="YOUR_API_KEY",
)
llm_with_tools = llm.bind_tools([word_count])
msg = llm_with_tools.invoke("How many words are in: the quick brown fox?")
for call in msg.tool_calls:
print(call["name"], call["args"], word_count.invoke(call["args"]))
Para un ciclo completo de agente, los ayudantes de agente de LangChain o LangGraph ejecutan este ciclo por ti: llama al modelo, ejecuta la herramienta solicitada, adjunta el mensaje de herramienta, llama de nuevo. El uso de herramientas sigue la forma estándar de OpenAI, por lo que el mismo código que escribirías para cualquier endpoint compatible funciona. Como con cualquier modelo de pesos abiertos, prueba tus esquemas de herramientas específicos. Mantén los nombres de argumentos claros, las descripciones cortas y la lista de herramientas pequeña, ya que las listas largas dan a los modelos más débiles más margen para elegir el incorrecto.
Salida estructurada con modo JSON
El modo JSON está disponible a través de response_format: {"type": "json_object"}. En LangChain puedes pasarlo como argumento de modelo y analizar el resultado tú mismo, lo que te da control sobre la validación y los reintentos.
import json
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nsfwllmrouter.com/v1",
api_key="YOUR_API_KEY",
model_kwargs={"response_format": {"type": "json_object"}},
)
msg = llm.invoke(
"Return a JSON object with keys title and tags (a list of three strings) "
"for an article about hiking."
)
data = json.loads(msg.content)
print(data["title"], data["tags"])
Di la palabra "JSON" en el prompt y describe las claves que deseas. El modo JSON hace que la salida sea JSON válido, pero no garantiza tu esquema exacto, así que verifica las claves después de analizar y reintenta en caso de fallo. Como las peticiones fallidas no se cobran, un bucle de reintento para salida con formato incorrecto cuesta poco.
Concurrencia, reintentos y límites de velocidad
LangChain facilita repartir el trabajo con .batch(), y ahí es donde la mayoría de la gente se topa con su primer límite de tasa. Una clave completa permite 8 peticiones concurrentes y 300 por minuto. Una clave de prueba permite 2 peticiones concurrentes y 60 por minuto. Si te pasas, la API responde con HTTP 429. El código de error es rate_limited cuando excedes las peticiones por minuto, y concurrency cuando hay demasiadas peticiones ejecutándose a la vez. En caso de sobrecarga temporal, también recibes un 429 con un encabezado Retry-After: 5.
Establece la concurrencia por lotes tú mismo, y deja que el cliente reintente con retroceso:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nsfwllmrouter.com/v1",
api_key="YOUR_API_KEY",
max_retries=4,
timeout=120,
)
inputs = [f"Summarize idea number {i} in one line." for i in range(40)]
results = llm.batch(inputs, config={"max_concurrency": 6})
print(len(results))
Mantenerse en 6 con una clave completa deja margen para otras peticiones de la misma cuenta. Con una clave de prueba usa 2. Si aún ves errores concurrency, busca streams que nunca terminaron, ya que cada uno mantiene una ranura. Cada respuesta lleva X-Request-Id, que es el valor a citar si contactas con soporte, y X-Balance-USD, que muestra tu crédito restante.
Llamadas asíncronas para aplicaciones web
Dentro de un servidor web rara vez quieres llamadas bloqueantes. Cada modelo de chat de LangChain tiene gemelos asíncronos de sus métodos, nombrados ainvoke, astream y abatch, y funcionan contra este endpoint de la misma manera que las versiones sincrónicas.
import asyncio
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nsfwllmrouter.com/v1",
api_key="YOUR_API_KEY",
)
async def main():
async for chunk in llm.astream("Describe a quiet harbor at dawn in two sentences."):
print(chunk.content, end="", flush=True)
asyncio.run(main())
En un servicio asíncrono, controla el número de llamadas en curso con un semáforo dimensionado a tu clave, por ejemplo 8 con una clave completa. Eso evita que un endpoint ocupado de tu propia aplicación llene todas las ranuras y deje sin servicio a las demás. Captura excepciones alrededor de la llamada, registra el encabezado X-Request-Id cuando lo tengas, y muestra al usuario un mensaje de reintento corto en lugar de un seguimiento de pila.
Consejo de depuración: si una cadena se comporta de forma extraña, imprime los mensajes exactos que recibe el modelo. Las plantillas de prompt a veces insertan espacios en blanco adicionales o pierden una variable, y el modelo solo puede responder lo que se le envió. Los modos verbose y debug de LangChain, o el rastreo de LangSmith si ya lo usas, muestran la lista de mensajes final.
Qué significa la ventana de contexto para las cadenas
La ventana de contexto es de 100,000 tokens para prompt y completion juntos, y max_tokens es opcional. Eso es suficiente para cadenas de recuperación que meten muchos documentos en un solo prompt, o para una memoria de conversación que mantiene un historial largo. También es lo suficientemente grande como para resultar caro si reenvías un historial completo en cada turno sin recortarlo.
Un cálculo rápido. Un paso de recuperación que envía 12.000 tokens de entrada y recibe 800 de vuelta cuesta 0,012 x $0,15 + 0,0008 x $0,50 = $0,0018 + $0,0004 = $0,0022. Ejecútalo 1.000 veces y pagarás $2,20. La tarifa es de $0,15 por 1M de tokens de entrada y $0,50 por 1M de tokens de salida, sin suscripción, así que pagas solo por lo que la cadena realmente usa. La página de precios tiene las opciones de recarga, y la documentación de inicio rápido lista todos los parámetros compatibles. Los prompts no se usan para entrenamiento.
