مقاله آموزشی

LangChain با وب‌سرویس هوش مصنوعی؛ یک base_url برای GPT، Claude و Gemini

نویسنده: تیم نِت اَرز ۹ دقیقه مطالعه ۱۶ بازدید

LangChain چارچوبی (فریم‌ورک) پایتونی است که کارهای تکراری ساخت برنامه با مدل زبانی را یک‌دست می‌کند: قالب پرامپت، زنجیره کردن چند قدم، خروجی ساختاریافته، و جست‌وجو در متن‌های خودتان پیش از جواب دادن (RAG). بیشتر آموزش‌های فارسی LangChain با کلید OpenAI شروع می‌شوند و از ایران همان قدم اول با خطای ۴۰۳ یا مشکل پرداخت متوقف می‌شود.

راه ساده‌تر این است که LangChain را به یک وب‌سرویس سازگار با OpenAI وصل کنید. وب‌سرویس هوش مصنوعی نِت اَرز همین کار را می‌کند و با یک کلید، مدل‌های GPT، Claude، Gemini و DeepSeek را در دسترس می‌گذارد؛ اعتبارش را هم به تومان می‌خرید. نمونه‌های پایین خلاصهٔ یک فایل اجراشدنی در مخزن متن‌باز ما در گیت‌هاب‌اند و لینکش را آخر مقاله گذاشته‌ایم.

نصب و اولین مدل

python3 -m venv .venv && source .venv/bin/activate
pip install "langchain-openai>=0.2" "langchain-core>=0.3"
export NETARZ_API_KEY="sk-ntz-v1-..."
import os
from langchain_openai import ChatOpenAI

BASE_URL = "https://netarz.ir/api/ai/v1"
KEY = os.environ["NETARZ_API_KEY"]

llm = ChatOpenAI(base_url=BASE_URL, api_key=KEY, model="gpt-4o-mini", max_tokens=400, timeout=120)

print(llm.invoke("در یک جمله بگویید LangChain به چه کاری می‌آید.").content)

کلاس ChatOpenAI از بستهٔ langchain-openai است و فقط دو پارامتر را از حالت پیش‌فرض عوض کرده‌ایم: base_url و api_key. max_tokens را هم همیشه بدهید؛ درگاه تا پایان هر جواب، به اندازهٔ همین سقف از اعتبار شما نگه می‌دارد، پس سقف کوچک‌تر یعنی مبلغ کمتری که معطل می‌ماند.

عوض کردن مدل با یک خط

فایدهٔ اصلی این ترکیب همین‌جا پیداست: برای امتحان مدل دیگر، کلاس و کتابخانه عوض نمی‌شود، فقط شناسه.

claude = ChatOpenAI(base_url=BASE_URL, api_key=KEY, model="openrouter/anthropic/claude-haiku-4.5", max_tokens=400)
gemini = ChatOpenAI(base_url=BASE_URL, api_key=KEY, model="gemini-3.5-flash", max_tokens=400)
کاردر LangChain معمولیروی درگاه نِت اَرز
مدل OpenAIChatOpenAI با کلید OpenAIChatOpenAI با base_url و کلید نِت اَرز
مدل Claudeبستهٔ langchain-anthropicهمان ChatOpenAI با شناسهٔ openrouter/anthropic/…
مدل Geminiبستهٔ langchain-google-genaiهمان ChatOpenAI با شناسهٔ gemini-…
امبدینگOpenAIEmbeddingsOpenAIEmbeddings با check_embedding_ctx_length=False

بسته‌های langchain-anthropic و langchain-google-genai به نشانی خود آن شرکت‌ها وصل می‌شوند و با این درگاه کار نمی‌کنند؛ همه‌چیز از راه ChatOpenAI می‌رود. فهرست زندهٔ شناسه‌ها را بدون کلید از GET /models?type=chat بگیرید و قیمت هر مدل را در صفحهٔ مدل‌ها ببینید. برای انتخاب بین آن‌ها، راهنمای انتخاب مدل برای پروژه معیارهای عملی را کنار هم گذاشته است.

زنجیره با قالب پرامپت

در LangChain قدم‌ها با علامت | به هم وصل می‌شوند: قالب پرامپت، بعد مدل، بعد تبدیل خروجی به متن ساده. نتیجه یک زنجیره است که مثل یک تابع صدایش می‌زنید:

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "شما ویراستار فارسی هستید. متن را روان و کوتاه کنید و «شما» خطاب کنید."),
    ("human", "{text}"),
])

chain = prompt | llm | StrOutputParser()
print(chain.invoke({"text": "کاربر گرامی، سفارش شما در حال پردازش می‌باشد."}))

# the same chain, streamed piece by piece
for piece in chain.stream({"text": "لطفاً جهت پیگیری با پشتیبانی تماس حاصل نمایید."}):
    print(piece, end="", flush=True)

با chain.stream همان زنجیره جواب را تکه‌به‌تکه برمی‌گرداند و لازم نیست چیزی در مدل عوض کنید. درگاه استریم را برای همهٔ سازنده‌ها پشتیبانی می‌کند و اگر اتصال وسط کار قطع شود، فقط بخشی که ساخته شده حساب می‌شود.

خروجی ساختاریافته با Pydantic

وقتی جواب مدل باید وارد کد شما شود، متن آزاد به کار نمی‌آید. with_structured_output یک کلاس Pydantic می‌گیرد و شیء همان کلاس را برمی‌گرداند:

from pydantic import BaseModel, Field

class Ticket(BaseModel):
    topic: str = Field(description="موضوع پیام در سه تا پنج کلمه")
    urgent: bool = Field(description="آیا مشتری منتظر پول یا سفارشش است؟")

triage = llm.with_structured_output(Ticket)
result = triage.invoke("دو روز است پول را پرداخت کرده‌ام و هنوز کدی نگرفته‌ام.")
print(result.topic, result.urgent)

این روش با مدل‌های OpenAI بهترین نتیجه را می‌دهد. برای مدل‌های دیگر، method="function_calling" را به with_structured_output بدهید تا از فراخوانی ابزار استفاده کند؛ در فهرست مدل‌ها، capabilities.tools می‌گوید کدام مدل ابزار را پشتیبانی می‌کند. منطق پشت این کار را در آموزش Function Calling باز کرده‌ایم.

RAG کوچک: جواب از روی متن خودتان

RAG یعنی پیش از پرسیدن از مدل، تکه‌های مرتبط را از متن‌های خودتان پیدا کنید و همراه پرسش بفرستید تا مدل از روی همان‌ها جواب بدهد، نه از حافظهٔ کلی‌اش. نسخهٔ کوچکش با یک انبار بردار (vector store) داخل حافظه:

from langchain_core.vectorstores import InMemoryVectorStore
from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(
    base_url=BASE_URL,
    api_key=KEY,
    model="text-embedding-3-small",
    check_embedding_ctx_length=False,  # send raw text, not OpenAI token ids
)

store = InMemoryVectorStore.from_texts(
    [
        "برای تحویل سفارش، یک بار احراز هویت لازم است.",
        "کش‌بک بعد از تکمیل سفارش حساب می‌شود و پس از دورهٔ انتظار به کیف پول می‌آید.",
        "کلید API را فقط روی سرور نگه دارید، نه در اپ موبایل.",
    ],
    embedding=embeddings,
)
retriever = store.as_retriever(search_kwargs={"k": 2})

rag_prompt = ChatPromptTemplate.from_messages([
    ("system", "فقط بر اساس این متن جواب بدهید و اگر جواب در متن نیست، بگویید نمی‌دانید:\n{context}"),
    ("human", "{question}"),
])

question = "کش‌بک کی به کیف پولم می‌آید؟"
context = "\n".join(doc.page_content for doc in retriever.invoke(question))
print((rag_prompt | claude).invoke({"context": context, "question": question}).content)

پارامتر check_embedding_ctx_length=False ریز است ولی مهم. OpenAIEmbeddings به‌طور پیش‌فرض متن را با توکنایزر OpenAI به شماره‌توکن تبدیل می‌کند و همان شماره‌ها را می‌فرستد. مدل‌های امبدینگ غیر OpenAI این شماره‌ها را نمی‌فهمند. با این گزینه، متن خام فرستاده می‌شود و با هر مدل امبدینگی کار می‌کند.

این نمونه برای فهمیدن مفهوم است. برای اسناد واقعی فارسی، مثل PDF قوانین یا راهنمای محصول، تکه‌بندی، نرمال‌سازی متن فارسی و یک پایگاه برداری ماندگار لازم دارید که در راهنمای RAG روی اسناد فارسی قدم‌به‌قدم آمده است.

حافظهٔ گفت‌وگو و مهار هزینه

هر درخواست برای مدل از صفر شروع می‌شود و گفت‌وگوی قبلی را فقط وقتی می‌بیند که همراه همان درخواست بفرستید. در LangChain جای این پیام‌ها در قالب پرامپت با MessagesPlaceholder مشخص می‌شود و فهرستش را خودتان نگه می‌دارید:

from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import MessagesPlaceholder

chat_prompt = ChatPromptTemplate.from_messages([
    ("system", "شما دستیار پشتیبانی یک فروشگاه هستید. کوتاه جواب بدهید."),
    MessagesPlaceholder("history"),
    ("human", "{question}"),
])
chat_chain = chat_prompt | llm | StrOutputParser()

history = []
for question in ["سفارشم کی تحویل می‌شود؟", "اگر تا فردا نرسید چه کنم؟"]:
    answer = chat_chain.invoke({"history": history[-12:], "question": question})
    history += [HumanMessage(question), AIMessage(answer)]
    print(answer)

برش history[-12:] فقط شش نوبت آخر را می‌فرستد. بدون آن، هر پیام از قبلی گران‌تر می‌شود، چون کل گفت‌وگو هر بار دوباره خوانده و حساب می‌شود. دو تنظیم دیگر هم خرج را قابل پیش‌بینی نگه می‌دارد: max_tokens روی هر مدل، و max_retries که در ChatOpenAI به‌طور پیش‌فرض چند بار تلاش دوباره می‌کند. برای کار دسته‌ای شبانه، در پنل برای کلید سقف هزینه هم تعیین کنید؛ با رسیدن به آن، درخواست‌ها با خطای ۴۰۲ متوقف می‌شوند و خرج از همان عدد بالاتر نمی‌رود.

کجا LangChain لازم نیست

اگر برنامهٔ شما فقط یک درخواست به مدل می‌فرستد و جواب را نشان می‌دهد، LangChain یک لایهٔ اضافه است که باید نسخه‌هایش را هم دنبال کنید. کتابخانهٔ رسمی openai با همان base_url کوتاه‌تر و قابل‌فهم‌تر است. LangChain وقتی ارزشش را پیدا می‌کند که چند قدم دارید، بین چند مدل جابه‌جا می‌شوید، یا بازیابی از متن‌های خودتان لازم است. در هر دو حالت، هزینه همان هزینهٔ توکن‌هاست؛ حساب توکن با مثال ریالی کمک می‌کند پیش از ساخت بدانید هر پرسش چقدر خرج دارد.

کد کامل در گیت‌هاب

فایل langchain/chat_and_rag.py همهٔ این قدم‌ها را یک جا دارد و با requirements.txt کنارش، با دو دستور اجرا می‌شود. در همان مخزن netarz/ai-api-examples، پوشهٔ python/ نمونه‌های بدون LangChain را دارد و providers/ برای هر سازنده یک نمونهٔ جدا با شناسهٔ درست مدل. مجوز مخزن MIT است.

مخزن ai-api-examples، با نمونه‌های ربات تلگرام و n8n، در صفحهٔ کدهای متن‌باز نِت اَرز معرفی شده است. مرجع کامل پارامترها و ابزارهای سازگار، از n8n تا Continue، در صفحهٔ SDKهای مستندات است. اگر با Claude شروع می‌کنید، API کلود در ایران نکته‌های همان مدل را دارد.

آموزش‌های کاربردی در گیسو

گیسو برند هوش مصنوعی نِت اَرز است، با همین حساب و همین اعتبار. این راهنماها قدم‌به‌قدم نشان می‌دهند همین کار را چطور با هوش مصنوعی انجام دهید.

پرسش‌های پرتکرار

LangChain را بدون کلید OpenAI می‌شود استفاده کرد؟

بله. ChatOpenAI را با base_url وب‌سرویس نِت اَرز و کلید خودتان بسازید. همان کلاس مدل‌های GPT، Claude، Gemini و DeepSeek را صدا می‌زند و فقط شناسهٔ model عوض می‌شود.

بستهٔ langchain-anthropic با این درگاه کار می‌کند؟

نه. آن بسته به نشانی Anthropic وصل می‌شود. Claude را با ChatOpenAI و شناسه‌ای که با openrouter/anthropic/ شروع می‌شود صدا بزنید.

چرا OpenAIEmbeddings با مدل غیر OpenAI خطا می‌دهد؟

چون به‌طور پیش‌فرض متن را به شماره‌توکن‌های OpenAI تبدیل می‌کند. check_embedding_ctx_length=False را بگذارید تا متن خام فرستاده شود.

برای یک چت‌بات ساده LangChain لازم است؟

معمولاً نه. برای یک درخواست و یک جواب، کتابخانهٔ رسمی openai با همان base_url کافی و ساده‌تر است. LangChain برای کار چندمرحله‌ای، جابه‌جایی بین مدل‌ها و RAG ارزش پیدا می‌کند.

محصولات مرتبط با این مقاله

مطالب مشابه

نظر خوانندگان

هنوز نظری ثبت نشده. اگر این مقاله پرسشتان را جواب داد یا جای چیزی در آن خالی ماند، همین‌جا بنویسید.

نظرتان را بنویسید

این مقاله چقدر به کارتان آمد؟ (اختیاری)

نظرها را پیش از انتشار بررسی می‌کنیم. نقد صریح مشکلی ندارد؛ تبلیغ و توهین منتشر نمی‌شود.