درس ۲ از ۷، مقدمات

پیام‌ها، استریم، batch و مصرف توکن

متنی ۲۰ دقیقه به‌روز شده در ۱۱ آبان ۱۴۰۵

در این درس
  1. سه نوع پیام
  2. متادیتای پاسخ
  3. استریم
  4. batch: چند درخواست هم‌زمان
  5. شمارش توکن‌ها در چند فراخوانی
  6. خلاصه

نوت‌بوک این درس

04_01-Models_Messages_StructuredOutput.ipynb (بخش‌های ۴، ۶ و ۸)

سه نوع پیام#

در LangChain همه‌چیز با پیام کار می‌کند و این پیام‌ها یک‌به‌یک به نقش‌های API اوپن‌ای‌آی نگاشت می‌شوند:

کلاس نقش کاربرد
SystemMessage system شخصیت و قواعد دستیار
HumanMessage user پیام کاربر
AIMessage assistant پاسخ مدل؛ خروجی invoke هم از همین نوع است
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage, AIMessage, SystemMessage

model = init_chat_model("gpt-5.2", model_provider="openai", temperature=0)

conversation = [
    SystemMessage("You are a helpful Persian-speaking assistant."),
    HumanMessage("سلام! اسم من علی است."),
    AIMessage("سلام علی! چطور می‌تونم کمکت کنم؟"),
    HumanMessage("اسم من چیه؟"),
]
response = model.invoke(conversation)
print(response.content)   # اسم شما **علی** است.
print(type(response))     # <class 'langchain_core.messages.ai.AIMessage'>

همان‌طور که در درس حافظه‌ی دوره‌ی اول دیدیم، مدل اسم را فقط به این دلیل می‌داند که کل گفتگو را دوباره فرستاده‌ایم.

LangChain دست شما را نمی‌بندد. اگر به قالب OpenAI عادت دارید، دیکشنری هم پذیرفته می‌شود:

messages = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "What is 2+2?"},
]
print(model.invoke(messages).content)   # 2 + 2 = 4

متادیتای پاسخ#

AIMessage علاوه بر متن، اطلاعات دیگری هم دارد:

response = model.invoke("Tell me a joke")
print(response.content)
print(response.response_metadata.get("model_name"))
print(response.usage_metadata)
Why don't skeletons fight each other?
They don't have the guts.
gpt-5.2-2025-12-11
{'input_tokens': 10, 'output_tokens': 20, 'total_tokens': 30,
 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}
  • response_metadata: اطلاعات خام ارائه‌دهنده، مثل نام دقیق نسخه‌ی مدل و finish_reason.
  • usage_metadata: تعداد توکن‌ها با قالب یکسان برای همه‌ی ارائه‌دهنده‌ها. reasoning تعداد توکن‌های استدلال مدل‌های استدلالی است و cache_read توکن‌هایی که از کش پرامپت خوانده شده‌اند و معمولاً ارزان‌ترند.

استریم#

for chunk in model.stream("یک بلاگ پست ۲ پاراگرافی در مورد RAG بنویس"):
    print(chunk.content, end="", flush=True)

هر chunk یک AIMessageChunk است که فقط تکه‌ی تازه‌ی متن را دارد. کدی که در دوره‌ی قبل برای Ollama و LiteLLM جداگانه نوشتیم، اینجا برای همه‌ی ارائه‌دهنده‌ها یکی است.

استریم به ارائه‌دهنده هم بستگی دارد

همه‌ی سرویس‌ها واقعاً توکن‌به‌توکن نمی‌فرستند. در کلاس دیدیم که از طریق یک واسط، متن گاهی تکه‌های بزرگ‌تر و با مکث می‌رسید، در حالی که اتصال مستقیم به OpenAI روان‌تر بود. کد شما درست است؛ رفتار به سرویس بستگی دارد.

batch: چند درخواست هم‌زمان#

responses = model.batch([
    "What is Python?",
    "What is JavaScript?",
    "What is Rust?",
])
for i, r in enumerate(responses, 1):
    print(f"Response {i}: {r.content[:80]}...")

batch درخواست‌ها را موازی می‌فرستد و نتیجه‌ها را به همان ترتیب ورودی برمی‌گرداند. برای صد سؤال کوتاه، تفاوتش با یک حلقه‌ی for و invoke چشمگیر است. یک بار هر دو را امتحان کنید و زمانشان را مقایسه کنید.

با Ollama این افزایش سرعت کمتر است. یک کارت گرافیک خانگی درخواست‌ها را واقعاً موازی پردازش نمی‌کند، پس سه برابر سریع‌تر نمی‌شود. سرویس‌های آنلاین و موتورهایی مثل vLLM برای پردازش هم‌زمان ساخته شده‌اند. اگر محدودیت نرخ دارید، با config={"max_concurrency": 5} تعداد درخواست‌های هم‌زمان را محدود کنید.

شمارش توکن‌ها در چند فراخوانی#

در برنامه‌ای که چند بار و با چند مدل مختلف LLM صدا می‌زند، و بعداً در ایجنت‌ها، جمع زدن دستی usage_metadata سخت است. get_usage_metadata_callback همه‌ی فراخوانی‌های داخل یک بلوک را به تفکیک مدل جمع می‌زند:

from langchain_core.callbacks import get_usage_metadata_callback

model_gpt4o = init_chat_model("gpt-4o", model_provider="openai")
model_gpt52 = init_chat_model("gpt-5.2", model_provider="openai")

with get_usage_metadata_callback() as cb:
    model_gpt4o.invoke("Hello!")
    model_gpt4o.invoke("what is capital of Iran!")
    model_gpt52.invoke("What is the capital of France?")

print(cb.usage_metadata)
{'gpt-4o-2024-08-06':  {'input_tokens': 22, 'output_tokens': 16, 'total_tokens': 38, …},
 'gpt-5.2-2025-12-11': {'input_tokens': 13, 'output_tokens': 5,  'total_tokens': 18, …}}

چون قیمت هر مدل فرق دارد، داشتن مصرف به تفکیک مدل برای حساب هزینه ضروری است.

خلاصه#

  • SystemMessage، HumanMessage و AIMessage همان نقش‌های system، user و assistant هستند. دیکشنری هم پذیرفته می‌شود.
  • usage_metadata شمار توکن‌ها را با قالب یکسان برای همه‌ی ارائه‌دهنده‌ها می‌دهد و get_usage_metadata_callback آن را در چند فراخوانی جمع می‌زند.
  • stream برای نمایش هم‌زمان با تولید، و batch برای اجرای موازی چند درخواست است.