نقاط رئيسية
- استخدم ChatOpenAI مع ضبط base_url لتشير إلى الـ API، وضبط النموذج على uncensored.
- تسير سلاسل العمل، والبث المتدفق، واستدعاء الدوال، وإخراج JSON عبر مسار إكمال المحادثات القياسي.
- احدد أقصى حد للتزامن في الدفعات على 8 للمفتاح الكامل، أو 2 لمفتاح التجربة، لتجنب أخطاء 429.
- هذا نموذج مستضاف واحد، وليس موجهاً، لذا لا يوجد تبديل للنماذج حسب المزود.
الإعداد المختصر
السؤال المعتاد حول langchain + llm هو ما إذا كنت بحاجة إلى تكامل خاص لنقطة نهاية تابعة لطرف ثالث. لا تحتاج إلى ذلك. يقبل غلاف المحادثات الخاص بـ LangChain لـ OpenAI عنوان URL أساسي مخصص، وهذا الـ API يتبع تنسيق إكمال المحادثات الخاص بـ OpenAI، لذا فإن الفئة القياسية تعمل. قم بتثبيت الحزمة، ومرر ثلاث قيم، وستحصل على كائن نموذج محادثات يمكنك إدراجه في أي سلسلة.
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)
احصل على المفتاح من صفحة مفتاح API. يمكن للمستخدمين الجدد المطالبة برصيد تجريبي مجاني بقيمة $0.50 هناك بنقرة واحدة بعد تسجيل الدخول، وهو ما يكفي لتشغيل كل شيء في هذا المقال عدة مرات. معرف النموذج هو دائماً uncensored، وتذهب الطلبات إلى POST /v1/chat/completions.
ملاحظة حول اسم الموقع: على الرغم من وجود كلمة "router" في النطاق، فهذا نموذج مفتوح الأوزان واحد تشغله مزود مستقل، وليس موجهاً عبر مزودين وليس أي API رسمي لأي مزود. لا تختار بين نماذج هنا. يحدد كودك نموذجاً واحداً، وهذا هو النموذج الذي سيعطي الإجابة.
أبقِ المفتاح خارج الكود المصدري
تضمين المفتاح (Hard-coding) يعمل للاختبار الأول ويصبح مشكلة في المرة الأولى التي تقوم فيها برفع الملف. اقرأه من البيئة (environment) بدلاً من ذلك. تبحث مكتبات OpenAI التي تغلفها LangChain عن OPENAI_API_KEY و OPENAI_BASE_URL افتراضياً، لذا يمكنك تعيينها في وحدة التحكم (shell) وبناء النموذج بعدد أقل من الحجج.
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"],
)
تمرير عنوان URL الأساسي صراحةً، كما هو موضح أعلاه، له ميزة واحدة. إذا كان مشروع آخر على نفس الجهاز يستخدم خدمة OpenAI الحقيقية ويعتمد على متغير البيئة نفسه، فإن وسيطك الصريح يمنع التصادم بين الاثنين.
سلسلة أولى مع قالب الموجّه
تستخدم معظم الأعمال الحقيقية قالب الموجّه، والنموذج، ومحلل الإخراج متصلين بمؤشر الأنبوب. هذا هو نمط لغة تعبير LangChain، ولا يحتاج إلى شيء خاص لنقطة نهاية مخصصة.
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"}))
النموذج لا يرفض المحتوى للاستخدام القانوني للبالغين، وهو أمر مهم عندما تولد سلسلتك الخيال، أو مشاهد لعب الأدوار، أو حوار الرفيق الذي سيرفضه نموذج أكثر حذراً. القاعدة الثابتة الوحيدة هي أن محتوى الجنس الذي ينطوي على القاصرين لا يتم إنتاجه أبداً. قم ببناء التحقق من الإدخال الخاص بك فوق ذلك إذا كان لمنتجك قواعد أكثر صرامة.
البث المتدفق والاستخدام
يدعم الـ API البث المتدفق عبر أحداث الموجه للخدمة (SSE) مع stream: true، ويُبلغ عن الاستخدام في نهاية البث. في LangChain، اتصل بـ .stream() وتكرر عبر القطع.
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)
تفاصيل يجب وضعها في الاعتبار: يشغل الطلب المتدفق واحداً من فتحات التزامن الخاصة بك حتى ينتهي البث. إذا فتح واجهة المستخدم الخاصة بك بثاً وتاه المستخدم دون إغلاقه، تظل هذه الفتحة مشغولة. لديك 8 طلبات متزامنة على المفتاح الكامل، لذا يمكن لبضع بثات مهجورة أن تحجب الطلبات الجديدة. ألغِ المكرر (iterator) عندما ينقطع اتصال العميل.
استدعاء الدوال (Tool calling) والوكلاء (Agents)
استدعاء الدوال مدعوم: تمرر tools و اختياريًا tool_choice، ويرد النموذج بـ tool_calls (التي يمكن بثها أيضاً)، وترسل النتائج مرة أخرى كرسائل مع role: "tool". في LangChain، يتعامل bind_tools مع تنسيق الواجهة لك.
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"]))
لحلقة وكيل كاملة، تقوم مساعدي الوكيل في LangChain أو LangGraph بتشغيل هذه الدورة لك: استدعاء النموذج، تشغيل الأداة المطلوبة، إرفاق رسالة الأداة، الاستدعاء مرة أخرى. يتبع استخدام الأدوات الشكل القياسي لـ OpenAI، لذا فإن نفس الكود الذي ستكتبه لأي نقطة نهاية متوافقة يعمل. كما هو الحال مع أي نموذج مفتوح الأوزان، اختبر مخططات الأدوات الخاصة بك. اجعل أسماء الحجج بسيطة، والأوصاف قصيرة، وقائمة الأدوات صغيرة، لأن القوائم الطويلة تعطي النماذج الأضعف مجالاً أكبر لاختيار الأداة الخاطئة.
المخرجات المهيكلة بوضع JSON
وضع JSON متاح من خلال response_format: {"type": "json_object"}. في LangChain يمكنك تمريره كحجة للنموذج وتحليل النتيجة بنفسك، مما يمنحك التحكم في التحقق من الصحة وإعادة المحاولة.
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"])
قل كلمة "JSON" في الموجّه ووصف المفاتيح التي تريدها. يجعل وضع JSON الإخراج JSON صالحاً، لكنه لا يضمن المخطط الخاص بك بالضبط، لذا تحقق من المفاتيح بعد التحليل وأعد المحاولة عند الفشل. لأن الطلبات الفاشلة مجانية، فإن حلقة إعادة المحاولة للإخراج غير الصحيح تكلف قليلاً.
التزامن، إعادة المحاولة، وحدّ المعدل
يجعل LangChain من السهل توزيع العمل باستخدام .batch()، وهذا هو المكان الذي يواجه فيه معظم الناس حدّ المعدل الأول. يسمح المفتاح الكامل بـ 8 طلبات متزامنة و 300 طلب في الدقيقة. يسمح مفتاح التجربة بـ 2 طلب متزامن و 60 في الدقيقة. تجاوز ذلك وسيقابل الـ API بـ HTTP 429. رمز الخطأ هو rate_limited عندما تتجاوز الطلبات في الدقيقة، و concurrency عندما تعمل طلبات كثيرة في وقت واحد. في حالة الحمل الزائد المؤقت، تحصل أيضاً على 429 مع رأس Retry-After: 5.
حدد أقصى حد التزامن في الدفعات بنفسك، ودع العميل يعيد المحاولة مع التراجع:
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))
البقاء على 6 على المفتاح الكامل يترك هامشاً للطلبات الأخرى من نفس الحساب. على مفتاح التجربة استخدم 2. إذا رأيت أخطاء concurrency، ابحث عن البث التي لم تنته أبداً، حيث تحتفظ كل منها بفتحة. تحمل كل استجابة X-Request-Id، وهي القيمة التي يجب ذكرها إذا اتصلت بالدعم، و X-Balance-USD، التي تظهر رصيدك المتبقي.
الاستدعاءات غير المتزامنة لتطبيقات الويب
في خادم الويب، نادراً ما ترغب في استخدام استدعاءات مغلقة (blocking). لكل نموذج محادثة في LangChain نظائر غير متزامنة لطرقه، وهي ainvoke، وastream، وabatch، وتعمل ضد نقطة النهاية هذه بنفس طريقة النسخ المتزامنة.
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())
في خدمة غير متزامنة، قيّد عدد الاستدعاءات النشطة باستخدام إشارة (semaphore) بحجم مفتاحك، على سبيل المثال 8 لمفتاح كامل. يحافظ ذلك على عدم ملء نقطة النهاية الخاصة بتطبيقك لكل الشرائط وإهمال الأخرى. التقط الاستثناءات حول الاستدعاء، وسجّل رأس الـ X-Request-Id عندما يكون متاحاً، واعرض رسالة إعادة محاولة قصيرة للمستخدم بدلاً من تتبع المكدس.
نصيحة للتصحيح: إذا تصرف السلسلة بشكل غريب، اطبع الرسائل الدقيقة التي يتلقاها النموذج. أحياناً تُدرج قوالب الموجّه مسافات بيضاء إضافية أو تحذف متغيراً، ولا يستطيع النموذج الإجابة إلا بما تم إرساله إليه. تُظهر مفاتيح التفصيل (verbose) والتصحيح في LangChain، أو تتبع LangSmith إذا كنت تستخدمه بالفعل، قائمة الرسائل النهائية.
ما تعنيه نافذة السياق للسلاسل
نافذة السياق هي 100,000 رمز (token) للموجّه والإكمال معاً، وmax_tokens اختياري. هذا الحجم كبير بما يكفي لسلاسل الاسترجاع التي تملأ العديد من المستندات في موجّه واحد، أو لذاكرة محادثة تحتفظ بسجل طويل. كما أنه كبير بما يكفي ليكون مكلفاً إذا أعدت إرسال السجل الكامل في كل خطوة دون تقليمه.
حساب سريع. خطوة استرجاع ترسل 12,000 رمز مدخل وتعيد 800 تكلف 0.012 × $0.15 + 0.0008 × $0.50 = $0.0018 + $0.0004 = $0.0022. كرر ذلك 1,000 مرة وتبلغ التكلفة $2.20. الأسعار هي $0.15 لكل مليون رمز مدخل و$0.50 لكل مليون رمز مخرج، بدون اشتراك، لذا تدفع فقط مقابل ما تستخدمه السلسلة فعلياً. صفحة الأسعار تحتوي خيارات شحن الرصيد، ودليل البدء السريع يدرج كل المعاملات المدعومة. لا تُستخدم الموجّهات في التدريب.
