التثبيت وأول طلب
أنشئ بيئة افتراضية وثبّت الـ SDK. يُفترض استخدام الإصدار 1 أو أحدث من حزمة openai طوال الوقت.
python -m venv .venv
source .venv/bin/activate
pip install --upgrade openaiاحصل على مفتاح من صفحة مفتاح API؛ يمكن للمستخدمين الجدد الحصول على رصيد تجريبي مجاني بقيمة $0.50 بنقرة واحدة، صالح لمدة 7 أيام ولا يتطلب بطاقة. احتفظ بالمفتاح في متغير بيئة، ثم قم بإجراء أول طلب:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.glmapi.cc/v1",
api_key=os.environ["GLM_API_KEY"],
)
resp = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": "You are a concise writing assistant."},
{"role": "user", "content": "Suggest five titles for a space-western novel."},
],
temperature=0.8,
)
print(resp.choices[0].message.content)
print(resp.usage)هذا هو التكامل بأكمله. الخدمة هي نقطة نهاية مستقلة ومتوافقة مع OpenAI لنموذج واحد مفتوح الأوزان، لذا فهي ليست الـ API الرسمي الخاص بالبائع وراء GLM أو أي طرف آخر. يوجد فقط POST /v1/chat/completions و GET /v1/models. يُدرج دليل البدء السريع حقول الطلب المدعومة.
تساعدك عادتان صغيرتان منذ البداية. أولاً، استدعِ client.models.list() مرة واحدة في اختبار الدخان: فإنه يستدعي GET /v1/models، مما يثبت أن المفتاح، وعنوان URL الأساسي، ومسار الشبكة يعملون جميعاً قبل أن تنفق رمزاً (token) واحداً على إكمال النص. ثانياً، اطبع resp.usage أثناء التطوير. تظهر عدّادات رموز (token) الموجّه وإكمال النص ما إذا كان الموجّه النظامي أكبر مما توقعت، وعند $0.15 لكل مليون رمز (token) مدخل و$0.50 لكل مليون رمز (token) مخرج، يمكنك حساب تكلفة الطلب في ذهنك.
محادثة متعددة الجولات وإعدادات العيّنة
الـ API عديم الحالة، لذا تحتفظ بقائمة المحادثة بنفسك وترسلها في كل مرة. فئة صغيرة تكفي:
class Chat:
def __init__(self, client, system, max_turns=12):
self.client = client
self.system = system
self.max_turns = max_turns
self.history = []
def say(self, text):
self.history.append({"role": "user", "content": text})
window = self.history[-self.max_turns * 2:]
resp = self.client.chat.completions.create(
model="uncensored",
messages=[{"role": "system", "content": self.system}] + window,
temperature=0.9,
top_p=0.95,
frequency_penalty=0.3,
)
reply = resp.choices[0].message.content
self.history.append({"role": "assistant", "content": reply})
return replyتحتفظ الشريحة self.history[-self.max_turns * 2:] بآخر اثني عشر زوجاً من رسائل المستخدم والمساعد. قد يحتاج التطبيق الحقيقي أيضاً إلى تثبيت بعض الرسائل الأولى، مثل ورقة الشخصية أو الحقائق الرئيسية، وتلخيص الجزء الأوسط. يؤدي التقليم إلى آخر 12 دوراً إلى إبقاء تكلفة الإدخال ثابتة مع نمو المحادثة. نافذة السياق هي 100,000 رمز (token) للموجّه وإكمال النص معاً، لذا فإن هذا خيار تكلفي أكثر من كونه ضرورة. تُمرَّر المعلمات temperature، top_p، stop، seed ومعلمات العقوبة كما هي. اضبط seed عندما تريد تشغيلات قابلة للتكرار أثناء اختبار الموجّه، واترك max_tokens خارجاً ما لم ترد تحديد الحد الأقصى للطول.
استجابات البث المتدفق
يستخدم البث أحداثاً مرسلة من الخادم ويعطيك الرموز كما يتم توليدها، وهو أمر مهم لأي أداة تفاعلية. مرر stream=True وتكرّر:
stream = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Describe a harbour at dawn in 120 words."}],
stream=True,
)
parts = []
for chunk in stream:
if not chunk.choices:
continue
delta = chunk.choices[0].delta.content
if delta:
parts.append(delta)
print(delta, end="", flush=True)
text = "".join(parts)الحاجز على chunk.choices مهم لأن الشريحة النهائية تحمل الاستخدام وقد تحتوي على قائمة خيارات فارغة. نقطة تشغيلية واحدة: يحتفظ الطلب المتدفق بخانة التزامن الخاصة به حتى ينتهي البث. لف الحلقة في كتلة try مع عبارة finally تغلق البث إذا أثار رمزك استثناءً في منتصف الطريق، على سبيل المثال عندما ينقطع العميل. أنهِ أو أغلق البث دائماً، وإلا فإن الحلقة التي تتخلى عن البث ستصل إلى حد التزامن أثناء القيام بعمل غير مفيد.
وضع JSON
عندما تغذي المخرجات كوداً آخر، اطلب كائن JSON. اذكر JSON في الموجّه أيضاً، حتى يعرف النموذج الشكل الذي تتوقعه:
import json
resp = client.chat.completions.create(
model="uncensored",
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": "Reply with a JSON object only."},
{"role": "user", "content": "Give a character with keys name, age, motto."},
],
)
data = json.loads(resp.choices[0].message.content)
print(data["name"])لا يزال عليك تغليف json.loads في كتلة try في كود الإنتاج. وضع JSON يجعل المخرج الصحيح محتملاً، وقد يترك الطلب المقطوع بواسطة تسلسل توقف أو حدك الخاص كائنًا مقصوصاً. إعادة المحاولة مرة واحدة بنفس الإدخال هي حل رخيص، وبما أن الطلبات الفاشلة لا تُحسب، فإن تكلفة إعادة المحاولة هي فقط تكلفة الطلب الذي ينجح.
استدعاء الدوال
يتبع استدعاء الدوال شكل OpenAI: تصف الدوال في tools، يعيد النموذج tool_calls، تقوم بتشغيلها وترسل النتائج مرة أخرى مع الدور tool. رحلة ذهاب وإياب كاملة:
import json
tools = [{
"type": "function",
"function": {
"name": "roll_dice",
"description": "Roll a die with the given number of sides.",
"parameters": {
"type": "object",
"properties": {"sides": {"type": "integer"}},
"required": ["sides"],
},
},
}]
messages = [{"role": "user", "content": "Roll a twenty-sided die for me."}]
first = client.chat.completions.create(
model="uncensored", messages=messages, tools=tools, tool_choice="auto"
)
msg = first.choices[0].message
if msg.tool_calls:
messages.append(msg)
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = {"value": 17, "sides": args["sides"]} # replace with real logic
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result),
})
final = client.chat.completions.create(
model="uncensored", messages=messages, tools=tools
)
print(final.choices[0].message.content)
else:
print(msg.content)الحلقة أهم من تفاصيل النرد. يطلب النموذج استدعاءً، يشغّل كودك الاستدعاء، وتضيف كل من رسالة المساعد ونتيجة الأداة قبل السؤال مرة أخرى. إذا تخطيت رسالة المساعد فلن يجد tool_call_id في النتيجة ما يطابقه، وهو السبب المعتاد لرد ثانٍ محير. مع تعيين tool_choice إلى auto، يقرر النموذج ما إذا كان سيستدعي أداة أم لا، لذا تعامل دائماً مع الفرع الذي يجيب فيه مباشرة، كما يفعل الكود أعلاه. يمكن أن تحمل الاستجابات المتدفقة tool_calls أيضاً، وتُسلم على شكل قطع تقوم بدمجها حسب الفهرس قبل تحليل الحجج.
تحقق من صحة الحجج قبل اتخاذ إجراء عليها. يمكن أن ينتج النموذج استدعاءً معطلاً، ويجب أن يعامل رمزك الحجج كمدخلات غير موثوقة، تماماً مثل أي شيء يأتي من مستخدم.
الدفعات غير المتزامنة ضمن الحدود
لعدة موجّهات في وقت واحد، استخدم AsyncOpenAI مع سيمافور يطابق حد التزامن الخاص بك. يسمح المفتاح الكامل بـ 8 طلبات متزامنة، ومفتاح التجربة 2:
import asyncio, os
from openai import AsyncOpenAI
client = AsyncOpenAI(
base_url="https://api.glmapi.cc/v1",
api_key=os.environ["GLM_API_KEY"],
)
gate = asyncio.Semaphore(8) # use 2 on a trial key
async def one(prompt):
async with gate:
r = await client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": prompt}],
)
return r.choices[0].message.content
async def main():
prompts = [f"Write a one-line tagline for product {i}." for i in range(40)]
results = await asyncio.gather(*(one(p) for p in prompts))
print(len(results))
asyncio.run(main())تتعامل السيمافور مع الطلبات المتزامنة ولكن ليس حدّ 300 طلب في الدقيقة. للدفعات الطويلة، أضف تأخيرًا صغيرًا أو مقيدًا لحوض الرموز (token-bucket). عند $0.15 لكل مليون رمز (token) مدخل و$0.50 لكل مليون رمز (token) مخرج، تكلف 40 طلبًا لكتابة شعارات بحوالي 60 رمز (token) مدخل و25 مخرج $0.00036 + $0.0005 = $0.00086 في المجموع، لذا فإن الحدود، وليس السعر، هي التي تحدد تصميم الدفعة. انظر إلى صفحة التسعير للشحن.
حدود المعدل، إعادة المحاولة، والرؤوس
عند تجاوز الحد، يعيد الـ API HTTP 429. رمز الخطأ هو rate_limited لعدد كبير جداً من الطلبات في الدقيقة و concurrency لعدد كبير جداً في وقت واحد. يؤدي الحمل الزائد المؤقت أيضاً إلى إرجاع 429 مع رأس Retry-After: 5. يعيد الـ SDK محاولة بعض الأخطاء بمفرده ويمكنك تعيين max_retries على العميل، لكن المعالجة الصريحة أكثر وضوحاً:
import time
from openai import RateLimitError
def ask(prompt, tries=4):
for attempt in range(tries):
try:
raw = client.chat.completions.with_raw_response.create(
model="uncensored",
messages=[{"role": "user", "content": prompt}],
)
print("balance:", raw.headers.get("X-Balance-USD"))
return raw.parse().choices[0].message.content
except RateLimitError as e:
wait = float(e.response.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
raise RuntimeError("still rate limited")هنا يعطي with_raw_response الوصول إلى X-Balance-USD، X-Request-Id ورؤوس حد المعدل. سجّل معرف الطلب مع أي خطأ تريد الإبلاغ عنه. لا تضع المفتاح في دفتر ملاحظات تقوم برفعه أو في كود الواجهة الأمامية.
الاختبار دون إنفاق الرصيد، والأخطاء الشائعة
معظم تكامل بايثون عبارة عن أعمال توصيل، وأعمال التوصيل لا تحتاج إلى نموذج مباشر. ضع استدعاء الشبكة خلف دالة واحدة، ويمكن لاختباراتك استبدالها. باستخدام المكتبة القياسية فقط:
from unittest.mock import MagicMock
def reply_text(client, prompt):
r = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": prompt}],
)
return r.choices[0].message.content.strip()
def test_reply_text_strips_whitespace():
fake = MagicMock()
fake.chat.completions.create.return_value.choices = [
MagicMock(message=MagicMock(content=" hello "))
]
assert reply_text(fake, "hi") == "hello"قم بتشغيل عدد قليل من المكالمات الحقيقية فقط عند الحواف: اختبار دخان واحد لكل نشر، بالإضافة إلى تشغيل دوري لمجموعة الموجّهات المحفوظة الخاصة بك. يكلف اختبار دخان واحد بحوالي 100 رمز إدخال و20 رمز إخراج $0.000015 + $0.00001 = $0.000025، وهو شيء لا يُذكر، والطلبات الفاشلة لا تُحاسب عليها على الإطلاق. رصيد التجربة بقيمة $0.50 أكبر بكثير مما تحتاجه مجموعة الاختبار.
لأعمال الموجّه، ثبّت seed و temperature بحيث يمكنك مقارنة نسختين من موجّه النظام بشروط متساوية. تغيير كل من الموجّه وإعدادات أخذ العينات بين التشغيلات يجعل النتائج غير قابلة للقراءة. احفظ ناتج كل تشغيل مع معرف الطلب من رأس X-Request-Id، ويمكنك تتبع إجابة مفاجئة لاحقًا.
أخطاء بايثون الشائعة
هذه تسبب معظم أسئلة الدعم التي تأتي من مستخدمي بايثون:
- تمرير المفتاح كـ
OPENAI_API_KEYفقط. إذا قمت بتعيينbase_urlفي الكود لكن نسيت متغير المفتاح، فإن الـ SDK يرسل مفتاحًا فارغًا أو خاطئًا. مررapi_keyصراحةً. - نسيان
/v1. ينتهي عنوان URL الأساسي بـ/v1. يضيف الـ SDK/chat/completionsبنفسه. - استخدام اسم نموذج من خدمة أخرى. فقط
uncensoredصالح هنا. - التزامن غير المحدود. يؤدي استخدام
asyncio.gatherلأكثر من ألف كوروتين بدون سيمافور إلى إنتاج جدار من أخطاءconcurrency. - معاملة الرد المتدفق كسلسلة واحدة. اجمع أجزاء البيانات، وتخطَّ القطع ذات قائمة
choicesالفارغة. - تجاهل حد السياق. يجب أن يتسع السجل والإكمال في 100,000 رمز. قم بتقليم الدورات القديمة قبل الوصول إليها.
لشحن الرصيد وقائمة الأسعار الحالية، انظر إلى صفحة الأسعار. الخدمة نصية فقط، لذا لا توجد استدعاءات للصور أو الصوت أو التضمين لتوصيلها، ولا تُستخدم الموجّهات للتدريب.
المزيد من الأدلة
أسئلة وأجوبة
ما هي حزمة بايثون التي أحتاجها؟
حزمة openai القياسية، الإصدار 1 أو أحدث. اضبط base_url على https://api.glmapi.cc/v1 والنموذج إلى uncensored.
هل يعمل البث المتدفق؟
نعم. مرر stream=True وكرّر عبر الأجزاء. ينتهي البث بمعلومات الاستخدام، ويشغل خانة تزامن حتى ينتهي.
كم عدد الطلبات التي يمكنني تشغيلها بالتزامن؟
يسمح المفتاح الكامل بـ 8 طلبات متزامنة و300 في الدقيقة. يسمح مفتاح التجربة بـ 2 و60.
هل يمكنني استخدام LangChain أو LiteLLM بدلاً من ذلك؟
يمكن لكليهما التحدث إلى نقطة نهاية متوافقة مع OpenAI عندما تزودها بعنوان URL الأساسي والمفتاح واسم النموذج uncensored. تحقق من وثائق كل مشروع للحصول على أسماء المعاملات الدقيقة.
مفتاحك على بُعد نموذج واحد
أنشئ حساباً، وانسخ المفتاح، ثم غيّر URL الأساسي. هذه هي الإعدادات الكاملة.
