درس ۱۱ از ۱۲، بخش ۳: مهندسی پرامپت
فرمت چت، نقشها و حافظهی گفتگو
در این درس
تا اینجا به مدل فقط یک پیام کاربر میدادیم. مدلهای گفتگو با لیستی از پیامها کار میکنند و هر پیام یک نقش (role) دارد:
| نقش | کارش |
|---|---|
system |
شخصیت، قواعد و محدودیتهای دستیار را تعیین میکند. کاربر نهایی معمولاً آن را نمیبیند. |
user |
پیام کاربر |
assistant |
پاسخهای قبلی مدل |
اینها همان چیزهایی هستند که در درس ۳ دیدیم با توکنهای ویژهی قالب چت به یک متن واحد تبدیل میشوند.
نوتبوکهای این درس
def get_completion_from_messages(messages, model="ollama/gemma3:4b", temperature=0):
response = completion(model=model, messages=messages, temperature=temperature)
return response.choices[0].message.content
پیام سیستم: شخصیت دستیار#
messages = [{"role": "user", "content": "پایتخت مریخ کجاست؟ کوتاه بگو"}]
مریخ در حال حاضر پایتخت رسمی ندارد، زیرا هیچ کشور یا دولت انسانی مستقر نیست که آن را تعیین کرده باشد.
حالا همان سؤال با یک پیام سیستم:
messages = [
{"role": "system", "content": "تو یک رمان نویس داستان های علمی تخیلی هستی که بسیار خلاقی و همیشه در تخیلات سیر میکنی"},
{"role": "user", "content": "پایتخت مریخ کجاست؟ کوتاه بگو"},
]
(با صدایی که انگار از میان وزش بادهای مریخی میآید، و با نگاهی پر از رؤیاپردازی…) پایتخت؟ این یک مفهوم است، نه یک نقطه جغرافیایی روی نقشه. …
یا یک دستیار «که مثل سهراب سپهری حرف میزند». جواب لطیفهی «چرا مرغ از جاده رد شد؟» با دمای ۱:
(مکثی طولانی… انگار که به آسمان نگاه میکنی، نه به جاده.) مرغ رد نشد. مرغ هم هرگز از چیزی فرار نمیکند؛ او فقط در حال گذر است. …
در برنامهی واقعی، پیام سیستم جای قواعد است: «فقط دربارهی محصولات فروشگاه جواب بده»، «همیشه فارسی جواب بده»، «اگر سؤال پزشکی بود، کاربر را به پزشک ارجاع بده».
few-shot در قالب گفتگو#
مثالهای few-shot را میشود به شکل نوبتهای قبلی گفتگو داد. به مدل نشان میدهیم «در گذشته» چطور جواب داده است:
messages = [
{"role": "system", "content": "تو یک sentiment analyser هستی. حس کاربر به محصول را مشخص میکنی."},
{"role": "user", "content": "خیلی قیمت خوبی داشت. توصیه میکنم"},
{"role": "assistant", "content": "Sentiment: Positive"},
{"role": "user", "content": "وقتی نظراتو خوندم نوشته بود این مسواک واترجت ۱ ماه شارژ نگه میداره؛ اما ۲ روز یه بار شارژ خالی میکنه"},
]
Sentiment: Negative
قالب Sentiment: … را هیچجا توضیح ندادیم. مدل آن را از پاسخ «قبلی» خودش یاد گرفت.
حافظه یعنی فرستادن دوبارهی تاریخچه#
messages = [
{"role": "system", "content": "تو یک دستیار دوستانه هستی. فقط به فارسی پاسخ بده."},
{"role": "user", "content": "سلام! اسم من علیرضا است"},
]
# ← «سلام علیرضا! خیلی خوشحالم که با تو آشنا میشم. …»
messages = [
{"role": "system", "content": "تو یک دستیار دوستانه هستی. فقط به فارسی پاسخ بده."},
{"role": "user", "content": "ممنون میشم بهم بگی اسم من چیه :)"},
]
# ← «متاسفم، متاسفانه نمیتوانم اسم شما را بدانم! …»
مدل اسم را «فراموش» نکرده؛ هیچوقت به خاطرش نسپرده بود. LLM حالت (state) ندارد. هر فراخوانی مستقل است و مدل فقط چیزی را میبیند که در همان درخواست فرستاده شده. چتباتهایی مثل ChatGPT در هر نوبت کل تاریخچهی گفتگو را دوباره برای مدل میفرستند:
messages = [
{"role": "system", "content": "تو یک دستیار دوستانه هستی. فقط به فارسی پاسخ بده."},
{"role": "user", "content": "سلام! اسم من علیرضا است"},
{"role": "assistant", "content": "سلام علیرضا، خوشحالم که با شما صحبت میکنم. چطور میتوانم به شما کمک کنم؟"},
{"role": "user", "content": "ممنون میشم بهم بگی اسم من چیه :)"},
]
# ← «اسم شما علیرضا هست! 😊»
نتیجهی مهم این است که هر چه گفتگو طولانیتر شود، هر درخواست توکن بیشتری دارد؛ هم گرانتر و کندتر میشود و هم بالاخره به سقف پنجرهی کانتکست میرسد. راهحلهایی مثل نگه داشتن فقط چند پیام آخر یا خلاصه کردن پیامهای قدیمی را در LangChain میبینیم.
ربات سفارش پیتزا#
با همین ایده یک ربات سفارشگیر کامل میسازیم. پیام سیستم، منو و رفتار را تعیین میکند و یک لیست به نام context همهی پیامها را نگه میدارد:
context = [{"role": "system", "content": """
تو یک ربات سفارشگیر خودکار برای یک رستوران 'پیتزای ما' هستی.
ابتدا به مشتری خوشآمد میگویی، سپس سفارش را میگیری،
و بعد میپرسی که سفارش تحویل است یا حضوری.
صبر میکنی تا سفارش کامل شود، سپس آن را خلاصه میکنی و یک بار دیگر چک میکنی
که آیا مشتری چیز دیگری میخواهد اضافه کند یا نه. …
منو شامل: …
"""}]
def collect_messages(user_text):
context.append({"role": "user", "content": user_text})
reply = get_completion_from_messages(context)
context.append({"role": "assistant", "content": reply}) # حافظه
return reply
در نوتبوک، رابط کاربری با کتابخانهی panel ساخته شده است: یک جعبهی متن و یک دکمه که collect_messages را صدا میزند و گفتگو را نمایش میدهد.
وقتی سفارش تمام شد، یک پیام سیستم دیگر به کپی تاریخچه اضافه میکنیم تا خلاصهی ساختیافتهی سفارش را برای سیستم آشپزخانه بگیریم:
messages = context.copy()
messages.append({"role": "system", "content": """
Create a JSON summary of the previous food order. **Strictly** specify the price of each item separately.
Fields should include: 1) pizza, including size 2) list of drinks 3) list of appetizers 4) total price (only integer)
All field names (keys) must be in English, but all string values must be in Persian."""})
print(get_completion_from_messages(messages, temperature=0))
{
"pizza": [
{"name": "پیتزا رست بیف", "size": "بزرگ", "price": 720000},
{"name": "پیتزا مرغ و اسفناج", "size": "متوسط", "price": 470000}
],
"drinks": [],
"appetizers": [],
"total_price": 1190000
}
یک گفتگوی آزاد به دادهای تبدیل شد که مستقیم در سیستم سفارش ثبت میشود.
نقشدهی (Role Prompting)#
نقشی که در پیام سیستم تعیین میکنید، سطح، لحن و عمق پاسخ را عوض میکند. یک سؤال یکسان، «عوارض جانبی ایبوپروفن چیست؟»، با سه پیام سیستم انگلیسی:
system_prompt_doctor = (
"You are a general practitioner (family doctor) talking to a patient. "
"Explain things in simple, everyday language and give practical advice. "
"Always respond in Persian (Farsi), regardless of the language of the question."
)
system_prompt_pharmacologist = (
"You are a pharmacologist explaining drug mechanisms to a medical colleague. "
"Use precise pharmacological and mechanistic terminology (e.g. enzyme inhibition, receptor effects). "
"Always respond in Persian (Farsi), regardless of the language of the question."
)
system_prompt_writer = (
"You are a medical writer creating patient-friendly educational content. "
"Use very simple, short sentences that a non-medical person can easily understand, and avoid jargon. "
"Always respond in Persian (Farsi), regardless of the language of the question."
)
- پزشک عمومی: «سلام … خیلی خوب است که این سؤال را پرسیدید.» بعد عوارض شایع، عوارض کمتر شایع و راههای پیشگیری را میگوید؛ در حد یک ویزیت معمولی.
- فارماکولوژیست: از اینکه ایبوپروفن یک NSAID است و آنزیمهای COX را مهار میکند حرف میزند. دقیق است، اما برای بیمار قابلفهم نیست.
- نویسندهی محتوای پزشکی: فهرست کوتاه و سادهای از عوارض، مثل مشکل معده و سردرد، بدون هیچ اصطلاح تخصصی.
پیام سیستم انگلیسی است و جواب فارسی. این ترکیب رایجی است، چون مدلها دستورهای انگلیسی را معمولاً دقیقتر اجرا میکنند. جملهی «Always respond in Persian» زبان پاسخ را تضمین میکند.
نقش را دقیق تعریف کنید
هر چه نقش دقیقتر باشد، دانش مرتبط بیشتری فعال میشود:
- بهجای «پژوهشگر» در کار منابع انسانی: «کارشناس تخصصی آموزش و توسعهی منابع انسانی»
- بهجای «نویسنده» برای تبلیغات: «کپیرایتر ارشد»
- بهجای «تحلیلگر مالی» برای بازار ایران: «تحلیلگر دارای مجوز از سازمان بورس و اوراق بهادار». این نقش مدل را به سمت قواعدی مثل ساعت معاملات و دامنهی نوسان بورس تهران میبرد.
خلاصه#
- پیامها نقش دارند:
systemبرای شخصیت و قواعد،userوassistantبرای گفتگو. - مثالهای few-shot را میشود به شکل نوبتهای قبلی گفتگو داد.
- مدل حافظه ندارد. حافظه یعنی فرستادن دوبارهی تاریخچه، که با طولانی شدن گفتگو گران میشود.
- با یک پیام سیستم اضافه، گفتگوی آزاد به JSON ساختیافته تبدیل میشود.
- نقشدهی سطح و لحن پاسخ را تعیین میکند. نقش را دقیق و تخصصی تعریف کنید.