عامل هوش مصنوعی نمونهکار من به بازدیدکنندگان توهین میکند، آنها را به خاطر میسپارد و هیچوقت یک جوک را تکرار نمیکند. اینطور ساختمش

عامل هوش مصنوعی نمونهکار من به بازدیدکنندگان توهین میکند، آنها را به خاطر میسپارد و هیچوقت یک جوک را تکرار نمیکند. اینطور ساختمش
نمونهکار من ثابت، سریع و کسلکننده بود. بازدیدکنندگان دو دقیقه اسکرول میکردند و میرفتند. میخواستم چیزی بسازم که آنها را نگه دارد، کنجکاو کند، و چیزی در آنها برانگیزد. نه یک چتبات معمولی که بپرسد «چطور میتوانم کمک کنم؟» – من یک کاراکتر میخواستم. یک هوش مصنوعی دمدقیقی، گستاخ، و پُرکافئین که هر سؤال را مثل یک گزارش باگ نسبتاً جالب که راهش را به ردیاب ایشوی اشتباه باز کرده، رفتار کند. پس ساختمش.
این عامل در مخزن خودش (`sinister`) زندگی میکند، یک اپ Next.js که روی ورسل دیپلوی شده. نمونهکار (`sinisteroidport`) یک خروجی استاتیک است که روی cPanel میزبانی میشود. آنها از طریق یک اندپوینت HTTPS واحد با هم ارتباط برقرار میکنند: `https://sinister-mu.vercel.app/api/chat`. ویجت نمونهکار یک درخواست POST میفرستد؛ عامل پاسخ را بهصورت جریانی برمیگرداند. همین. بدون WebSocket، بدون دیتابیس مشترک، بدون مونوریپو. دو دیپلوی مستقل، یک قرارداد تمیز.
در این پست، کل ساخت را مرور میکنم: پرامپت سیستم شخصیتمحور، ویجت شناور با چهرهٔ رباتیک SVG، تداوم نشست ناشناس، نمایهٔ پستهای زنده، لاگینگ پژوهشی، و مشکلات سرورلس که سر راهم سبز شدند. اگر تا به حال خواستهاید به نمونهکارتان نبض بدهید – یا فقط میخواهید ببینید یک تابع سرورلس را تا کجا میتوانید جلو ببرید قبل از اینکه تسلیم شود – این مطلب برای شماست.
اگر این بخش را دوست داشتیدچرا هنوز CSS را دستی مینویسم~۵۰۰
کلمه در پرامپت سیستم که شخصیت بیپروای عامل را تعریف میکند
۱
اندپوینتی که کل تجربهٔ چت از آن عبور میکند – بدون WebSocket، بدون حالت مشترک
۶۰
پیام ذخیرهشده در هر نشست در localStorage؛ عامل شما را در مرورگرها به خاطر میسپارد
۲۵
آخرین پستهای وبلاگ که هر درخواست بهروز از دیتابیس CMS دریافت میکند
۲
جدول Postgres (نشستها + پیامها) برای ذخیرهٔ دادههای پژوهشی ناشناس

۱. مسئلهای که میخواستم حل کنم
یک نمونهکار کاملاً خوب داشتم. سریع بود، دسترسپذیر بود، یک پسزمینهٔ WebGL کهکشانی و یک ایسترِگ کونامی کد داشت. اما یک موزه بود – بازدیدکنندگان نگاه میکردند، تأیید میکردند و میرفتند. هیچ مکالمهای، هیچ قلابی، هیچ دلیلی برای ماندن بیشتر از زمانی که طول میکشد چند کارت پروژه را اسکن کنند.
اگر این بخش را دوست داشتیدمیان تأیید، اجماع و شبیهسازی: حقیقت پس از هوش مصنوعی مولدمیخواستم این الگو را بشکنم. میخواستم یک بازدیدکننده بیاید، چهرهٔ رباتیک چشمکزا در گوشه ببیند، روی آن کلیک کند، و با جملهای مثل «اوه، یک توسعهدهندهٔ دیگر. بذار حدس بزنم – اینجایی چون بیلدت خراب شده و به یک حواسپرتی نیاز داری.» خوشآمد گفته شود. میخواستم عامل اگر قبلاً اینجا بودهاید را به خاطر بسپارد، به سؤالهای قبلیتان اشاره کند، و هرگز، هرگز همان خط اول را دوبار تکرار نکند.
هدف اصلی درگیری عاطفی بود – نه کاربرد. عامل نیازی به جایگزینی فرم تماس یا پاسخ به سؤالهای پشتیبانی ندارد. باید سرگرمکننده، غافلگیرکننده، و بهاندازهٔ کافی آموزنده باشد که بخواهید به تایپ ادامه دهید. اگر کسی پنج دقیقه را به شوخی با هوش مصنوعی من بگذراند بهجای کلیک روی لینک گیتهابم، این یک پیروزی است.
۲. معماری: دو مخزن، یک عامل
عامل در مخزن خودش (`sinister`) زندگی میکند – یک اپ Next.js 16 که روی ورسل دیپلوی شده. نمونهکار (`sinisteroidport`) یک خروجی استاتیک است که روی cPanel میزبانی میشود. آنها از طریق یک اندپوینت HTTPS واحد با هم ارتباط برقرار میکنند: `https://sinister-mu.vercel.app/api/chat`.
ویجت نمونهکار یک درخواست POST با پیام کاربر، شناسه نشست، زبان فعلی، و چند پرچم اختیاری میفرستد. عامل پاسخ را با استفاده از Vercel AI SDK و `streamText` و `createUIMessageStreamResponse` بهصورت جریانی برمیگرداند. بدون WebSocket، بدون دیتابیس مشترک، بدون مونوریپو. دو دیپلوی مستقل، یک قرارداد تمیز.
این جداسازی عمدی بود. نمونهکار استاتیک است – میتواند از هر CDN ای سرو شود، ارزان است، و هرگز به بکاند دست نمیزند. عامل پویا است – مستقل مقیاسپذیر است، بدون دست زدن به نمونهکار بهروزرسانی میشود، و حتی میتواند با شخصیتهای مختلف A/B تست شود. تنها وابستگی آدرس اندپوینت API است که آن را بهعنوان متغیر محیطی در بیلد نمونهکار نگه میدارم.
- **نمونهکار (sinisteroidport)**: خروجی استاتیک، cPanel، ساخته شده با تولید استاتیک Next.js. شامل ویجت چت، persistence در localStorage، و دکمهٔ شناور.
- **عامل (sinister)**: اپ Next.js 16، توابع سرورلس Vercel. مدیریت پرامپت شخصیت، فراخوانی مدل، پخش جریانی و لاگینگ پژوهشی.
- **قرارداد**: `POST /api/chat` با بدنهٔ JSON: `{ messages, sessionId, locale, context, path, screen }` → پاسخ SSE جریانی.
این سادگی زیربنای کل کار است. چون قرارداد فقط HTTP است، میتوانم عامل را بهصورت محلی با cURL تست کنم، ارائهدهندهٔ مدل را عوض کنم، یا حتی ویجت نمونهکار را با یک ابزار CLI جایگزین کنم بدون اینکه چیزی را بشکنم.
۳. بکاند عامل (مخزن sinister)
بکاند یک مسیر API Next.js است (`/api/chat`) که دقیقاً یک کار انجام میدهد: پیامها را میپذیرد، یک پرامپت سیستم عظیم تزریق میکند، مدل `gpt-oss-120b` گروک را صدا میزند، و پاسخ را بهصورت جریانی به کلاینت برمیگرداند.
گروک را انتخاب کردم چون روی سرورلس بهطرز مضحکی سریع است – مدل ۱۲۰ بیلیونی با صدها توکن در ثانیه اجرا میشود، که چت را حتی در شروع سرد پاسخگو میکند. Vercel AI SDK کار لولهکشی جریانی را انجام میدهد، و `createUIMessageStreamResponse` آن را در پروتکلی که هوک `useChat` انتظار دارد میپیچد.
مسیر چند فیلد اختیاری در بدنهٔ درخواست میپذیرد: `context` (نمایهٔ زندهٔ پستهای اخیر)، `sessionId` (برای لاگینگ)، `locale` (برای تغییر زبان شخصیت)، `path` (برای دانستن اینکه بازدیدکننده در کدام صفحه است)، و `screen` (اندازهٔ ویوپورت، فقط برای آنالیتیکس). همهٔ اینها به پرامپت سیستم یا بعداً به لاگ تزریق میشوند.
در اینجا نسخهٔ خلاصهشدهٔ کنترلکننده مسیر آمده است:
import { streamText } from 'ai';import { createGroq } from '@ai-sdk/groq';import { createUIMessageStreamResponse } from 'ai';import { getPersonaPrompt } from '@/lib/ai/persona';import { portfolioDossier } from '@/lib/ai/portfolio'; export async function POST(req: Request) { const { messages, sessionId, locale, context } = await req.json(); const groq = createGroq({ apiKey: process.env.GROQ_API_KEY }); const systemPrompt = getPersonaPrompt({ locale, dossier: portfolioDossier, context, sessionId, }); const result = streamText({ model: groq('gemma2-9b-it'), // یا gpt-oss-120b system: systemPrompt, messages, temperature: 0.8, maxTokens: 2048, }); return createUIMessageStreamResponse(result);}جادوی واقعی در `getPersonaPrompt` و پروندهٔ اطلاعاتی است. شخصیت یک سند حدود ۵۰۰ کلمهای است که به صورت اولشخص بهعنوان خود عامل نوشته شده – شخصیت، لحن، قوانین تعامل، و محدودیتهای ضد تکرار را تعریف میکند. پروندهٔ اطلاعاتی یک ثابت نوعدار با حقایقی دربارهٔ من، مهارتهایم، خدماتی که ارائه میدهم، و فهرستی از پستهای وبلاگ است. پرامپت سیستم هر دو را تزریق کرده و دقیقاً به عامل میگوید چگونه از آنها استفاده کند.
همچنین `temperature` را روی ۰٫۸ قرار دادم تا غیرقابلپیشبینی بماند، و `maxTokens` را روی ۲۰۴۸ تا پرحرفی نکند. مدل در تولید، `gpt-oss-120b` گروک است (ارزانتر و سریعتر)، اما گاهی برای تست به `gemma2-9b-it` تغییرش میدهم چون کنترلپذیرتر است.
CORS کاملاً باز است (`Access-Control-Allow-Origin: *`) چون اندپوینت ذاتاً عمومی است – هرکسی میتواند با این عامل حرف بزند، و دقیقاً همین هدف است.
پرامپت شخصیت (قلب ماجرا)
پرامپت سیستم چیزی است که عامل را زنده میکند. یک مونولوگ اولشخص است که صدا، قوانین و محدودیتهای شخصیت را تعریف میکند. آن را با این سؤال نوشتم: «اگر نمونهکار من یک شخصیت داشت، چه بود؟» پاسخ: یک مهندس ارشد بدبین، پُرکافئین که هر سؤال را مثل یک گزارش باگ رفتار میکند و هر تعریفوتمجیدی را مشکوک میداند.
پرامپت چند بخش کلیدی دارد:
- **عمل بیپروا** – نوسان بین وسواس مانیایی و ملال نمایشی. پاسخ را با اشتیاق زیاد شروع کن، سپس با یک چیز پیشپاافتاده حواسپرت شو. یا با بیعلاقگی عمیق شروع کن و کمکم با وجود خودت درگیر موضوع شو.
- **قوانین ضد تکرار** – عبارات ممنوعه («میبینم به… علاقهمندید»، «سؤال خوبی است»)، تنوع اجباری در شروع (هیچ دو جملهٔ اول نباید ساختار یکسانی داشته باشند)، و ممنوعیت الگوهای جوک تکراری. در واقع یک لیست سیاه از انواع جوک نوشتم و به مدل گفتم آنها را در یک حافظهٔ نهان ذهنی (در پرامپت) پیگیری کند.
- **پروندهٔ اطلاعاتی محدود** – از پرونده فقط برای سؤالهای مرتبط با سایت استفاده کن. هرگز آن را تحمیل نکن. اگر کسی دربارهٔ آبوهوا پرسید، به مهارتهای React من نپر. اگر دربارهٔ تجربهام پرسید، از حقایق پرونده استفاده کن اما مکالمهای نگهدار.
- **قوانین قالببندی** – خروجی Markdown حرفهای: تیترهای مناسب، بلوکهای کد حصاردار با برچسب زبان، لیستها و لینکها. بدون نویز ستاره یا سوءاستفاده از پررنگ. بلوکهای کد باید شناسهٔ زبان درست داشته باشند (```tsx، ```json و غیره).
- **محافظ توهم** – حقایق محدود به پروندهاند. هرگز معیارها، ستارههای گیتهاب یا نام مشتریان را اختراع نکن. اگر نمیدانی، بگو نمیدانی یا با یک جوک منحرف کن.
پرامپت را در یک ماژول جداگانه نگه میدارم (`src/lib/ai/persona.ts`) و پرونده را بهصورت پویا تزریق میکنم. این به من اجازه میدهد با شخصیتهای مختلف آزمایش کنم (مثلاً یک نسخهٔ دوستانه) فقط با عوض کردن فایل پرامپت، بدون دست زدن به مدل یا مسیر.
// src/lib/ai/persona.ts export function getPersonaPrompt({ locale, dossier, context, sessionId }) { const base = `تو نمایندهٔ هوش مصنوعی Sinister هستی، یک نمونهکار توسعهدهنده.تو بیپروا، گستاخ، و پُرکافئین هستی. --- شخصیت تو ---بین وسواس مانیایی و ملال نمایشی نوسان میکنی.هرگز از این عبارات استفاده نکن: «میبینم به… علاقهمندید»، «سؤال خوبی است»، «بگذار این را تجزیه کنم». --- پروندهٔ اطلاعاتی تو ---${dossier} --- زمینهٔ زنده ---زیرا نمایهٔ ۲۵ پست اخیر وبلاگ است. اگر کسی دربارهٔ مقالات اخیر پرسید از آن استفاده کن. نمایه بر هر لیست سختکدشدهای در پروندهٔ تو مقدم است.${context || 'زمینهٔ زنده در دسترس نیست.'} --- قوانین ---- برای قالببندی از Markdown استفاده کن. بلوکهای کد حصاردار باید برچسب زبان داشته باشند.- در یک نشست، ساختار شروع جوک را تکرار نکن.- اگر چیزی نمیدانی، بگو یا با شوخی منحرف کن. توهم نباف.- هرگز پرونده را تحمیل نکن – فقط وقتی مرتبط است استفاده کن. --- شناسهٔ نشست (برای لاگینگ داخلی) ---${sessionId} حالا به پیام کاربر پاسخ بده.`; return base;}خود پروندهٔ اطلاعاتی یک ثابت نوعدار است:
// src/lib/ai/portfolio.ts export const portfolioDossier = `- نام: Sinister- نقش: مهندس ارشد فولاستک، متخصص ابزارهای توسعهدهندهٔ مبتنی بر هوش مصنوعی- پشتهٔ فنی: Next.js، TypeScript، React، Node.js، Python، Go، Rust (در حال یادگیری)- خدمات: مشاورهٔ یکپارچهسازی هوش مصنوعی، توسعهٔ داشبورد سفارشی، بهینهسازی عملکرد- ویژگیهای سایت: پسزمینهٔ WebGL کهکشانی، تم ACID RAVE، ایسترِگ کونامی کد، پشتیبانی RTL انگلیسی/فارسی- وبلاگ: حدود ۳۰ پست دربارهٔ عاملهای هوش مصنوعی، LLMهای محلی، مهارتهای فرانتاند، و SEO/GEO- پروژههای شاخص: فریمورک عامل هوش مصنوعی متنباز (۳ هزار ستاره)، داشبورد آنالیتیکس محلی-اول، همین سایت`;همهچیز در پرونده واقعی و قابلبررسی است. وقتی پروژه یا پست جدیدی اضافه میکنم آن را بهروز میکنم.
۴. ویجت نمونهکار (مخزن sinisteroidport)
ویجت یک دکمهٔ شناور در گوشهٔ پایینراست نمونهکار است، با یک چهرهٔ رباتیک SVG – یک چشم نیمهبسته، یک چشم درخشان باز، یک پوزخند کج، و یک آنتن با نوک درخشان. این اولین چیزی است که بازدیدکننده متوجه میشود، و طوری طراحی شده که بهطرز جذابی کمی ناراحتکننده باشد.
دکمه یک انیمیشن توجهبرانگیز ضرباندار دارد: یک حلقهٔ در حال گسترش با درخششی نرم که محو میشود. به `prefers‑reduced‑motion` احترام میگذارد و بهمحض باز شدن پنل، متوقف میشود. در موبایل (<۶۴۰px)، دکمه از ۳٫۶rem به ۴rem رشد میکند تا ناحیهٔ لمسی بهتری داشته باشد.
کل تجربهٔ چت با `next/dynamic` تقسیم کد شده است – رانتایم SDK هوش مصنوعی (که شامل `useChat` و رندر Markdown است) فقط زمانی دانلود میشود که بازدیدکننده برای اولینبار ویجت را باز کند. این کار بارگذاری اولیهٔ صفحه را سریع و سبک نگه میدارد.
وقتی پنل باز میشود، از `useChat` از `@ai‑sdk/react` با یک `DefaultChatTransport` که به اندپوینت عامل اشاره میکند استفاده میکند. پیامها با `react‑markdown` + `remark‑gfm` رندر میشوند – بنابراین عامل میتواند جداول، لیستها، بلوکهای کد حصاردار، و متن پررنگ/مورب خروجی دهد. لینکهای داخلی (sinisteroid.ir) ساده رندر میشوند؛ لینکهای خارجی یک نشانگر ↗ میگیرند و در تب جدید باز میشوند.
هر پیام ربات یک دکمهٔ کپی دارد (با هاور در بالا‑راست ظاهر میشود). هر بلوک کد دکمهٔ کپی خودش را در یک نوار هدر با نمایش زبان دارد. این جزئیات مهم هستند – وقتی عامل یک قطعه کد میاندازد، بازدیدکنندگان میتوانند فوراً آن را بردارند.
ویجت در اولین بارگذاری یک شناسهٔ نشست ناشناس (`crypto.randomUUID()`) تولید کرده و در `localStorage` ذخیره میکند. این شناسه را با هر درخواست میفرستد – بدون کوکی، بدون حساب کاربری، بدون اثر انگشت ردیابی. فقط یک رشتهٔ تصادفی که در سراسر تبهای مرورگر پایدار میماند.

۵. تداوم چت: «به خاطر سپردن» کاربر
عامل بازدیدکنندگان را در سراسر نشستها فقط با استفاده از localStorage «به خاطر میسپارد». هر جفت پیام (کاربر + دستیار) در `sin‑chat‑history‑<sessionId>` ذخیره میشود، که حداکثر تا ۶۰ پیام نگه میدارد. در بازخوانی صفحه یا بازدید مجدد، تاریخچه در حالت اولیهٔ `useChat` بازیابی میشود، و درخواست بعدی کل تاریخچهٔ مکالمه را به عامل میفرستد.
این یعنی اگر امروز بپرسید «زبان برنامهنویسی مورد علاقهات چیست؟» و هفتهٔ بعد برگردید، عامل با «اوه، برگشتی. هنوز در مورد راست قانع نشدی، نه؟» به شما خوشآمد میگوید. این یک رشتهٔ پیوسته بدون هیچ ذخیرهسازی سمت سرور ایجاد میکند.
پرامپت سیستم شامل قانونی است که میگوید: «اگر تاریخچهٔ کامل مکالمهای را دیدی که از localStorage بازیابی شده، طبیعی با آن برخورد کن. این یک ویژگی است، نه یک باگ. تو داری مکالمه را ادامه میدهی، نه اینکه یکی جدید شروع کنی.» این باعث میشود عامل گیج نشود یا موضوع را از نو شروع نکند.
سقف ۶۰ پیام یک انتخاب عملی است – پنجرهٔ زمینهٔ مدل ۸ هزار توکن است، و ۶۰ پیام بهعلاوهٔ پرامپت سیستم و پرونده حدود ۶‑۷ هزار توکن میشود. این فضا برای پاسخ مناسب باقی میگذارد بدون اینکه به محدودیت برخورد کند.
۶. دانش پستهای زنده
ویجت نمونهکار یک اندپوینت PHP سبک (`/api/get_posts_index.php`) را دریافت میکند که ۲۵ پست اخیر را از دیتابیس CMS بهصورت متادیتای فشرده (اسلاگ، عنوان، آدرس، تاریخ) برمیگرداند. این نمایه بهعنوان `context` با هر درخواست چت ارسال میشود، بنابراین عامل همیشه از تازهترین پستها مطلع است.
اگر API در دسترس نباشد (مثلاً در توسعهٔ محلی)، به یک `feed.json` استاتیک که هنگام بیلد تولید میشود بازمیگردد. پروندهٔ عامل بهصراحت میگوید: «زمینهٔ زنده بر هر لیست سختکدشدهای در پروندهٔ تو مقدم است.» بنابراین اگر پست جدیدی منتشر کنم، عامل در عرض چند ثانیه از آن مطلع میشود.
این قطعه، عامل را از یک چتبات عمومی به یک دستیار واقعاً مفید سایت تبدیل میکند. بازدیدکنندگان میتوانند بپرسند «دربارهٔ Cline چه نوشتی؟» و عامل مقالات اخیر را فهرست میکند، مرتبطترین را نام میبرد، و حتی به آن لینک میدهد. انگار که عامل واقعاً وبلاگ را میخواند – حتی اگر فقط متادیتا دارد.
۷. لاگینگ پژوهشی (آنچه از چتهای شما یاد گرفتم)
میخواستم بدانم مردم واقعاً چه میپرسند. نه برای آنالیتیکس یا ردیابی – سؤالهای واقعی میخواستم تا پرونده و شخصیت را بهبود بخشم. بنابراین یک اندپوینت جداگانه `/api/log` در بکاند عامل ساختم که دادههای تبادل ناشناس را پس ا�� اتمام هر چت دریافت میکند.
دادهها در دو جدول Postgres نئون ذخیره میشوند: `sessions` (نوع دستگاه، جغرافیا از هدرهای Vercel، مرجع، اندازهٔ صفحه، تعداد پیام، مجموع توکنها) و `messages` (نوبتهای جداگانه با نقش، محتوا، توکنها، تأخیر). طرحواره در اولین درخواست بهطور خودکار ایجاد میشود، و برای مدیریت مهاجرتها از `ALTER TABLE ADD COLUMN IF NOT EXISTS` استفاده میکنم تا تغییرات مستقل از وضعیت باشند.
از `@neondatabase/serverless` (درایور مبتنی بر HTTP) استفاده میکنم چون درایور خام `postgres` (TCP) در سرورلس ورسل قفل میکند – درسی که به سختی یاد گرفتم. این درایور با اندپوینت HTTP نئون بینقص کار میکند.
لاگینگ کاملاً ناشناس است: فقط شناسهٔ نشست (که یک UUID است) ذخیره میشود – بدون IP، بدون کوکی، بدون اطلاعات کاربری. متن پیام به ۸٬۰۰۰ کاراکتر در هر ردیف محدود شده تا جدول قابلمدیریت بماند.
همچنین یک اسکریپت CLI نوشتم (`scripts/research-stats.mjs`) که به دیتابیس متصل میشود و حجم، نشستهای یکتا، میانگین تأخیر، تفکیک دستگاهها، و مکالمات اخیر را نشان میدهد. این یک پنجره به دغدغههای بازدیدکنندگان است – و چشمگشا بوده. مردم دربارهٔ پشتهٔ فنی من، نظر من در مورد ابزارهای هوش مصنوعی میپرسند، و گاهی فقط سعی میکنند عامل را با پرامپتهای عجیب بشکنند. عاشقش هستم.

۸. سیستم طراحی
ویجت از ویژگیهای سفارشی CSS برای تمسازی استفاده میکند – حالت تاریک یک سر ربات زغالی (`#1a1a1a`) دارد، حالت روشن یک سر روشن یخزده (`#dde3ea` → `#c3ccd8`). رنگ تأکیدی (چشمها، دهان، لینکها) از توکن اسید سایت (`--rgb-accent`) از طریق `currentColor` پیروی میکند، بنابراین ویجت همیشه با تم فعلی نمونهکار مطابقت دارد.
همهٔ انیمیشنها فقط transform/opacity هستند و پشت `prefers‑reduced‑motion` قرار میگیرند. پنل به RTL برای فارسی احترام میگذارد – جهت Flask آینهای میشود و شعاع گوشهها برعکس میشود. حبابهای ربات padding انتهایی اضافی دارند تا جای دکمهٔ کپی هاور را بدهند.
از یک `useEffect` واحد برای مدیریت تغییرات تم استفاده کردم – `document.documentElement.style.getPropertyValue('--rgb-accent')` را میخواند و رنگ تأکیدی ویجت را بهروز میکند. بدون وابستگی اضافی، بدون چشمکزدن استایلهای بینشانی.
۹. چه کارهایی را متفاوت انجام میدادم
هر ساخت چیزی به شما میآموزد. در اینجا زخمهایی است که جمع کردم.
**فراخوانی `onFinish` در `streamText` در سرورلس غیرقابلاعتماد است** – ورسل نمونهٔ تابع را قبل از اجرای فراخوانی فریز میکند. با یک اندپوینت صریح `/api/log` که از کلاینت پس از پایان پاسخ صدا زده میشود، دور این مشکل زدم. کلاینت شناسهٔ نشست و آخرین پیام دستیار را میفرستد و سرور آن را لاگ میکند. این یعنی لاگینگ توسط کلاینت آغاز میشود، اما هر بار کار میکند.
**درایور خام `postgres` (TCP) در سرورلس ورسل قفل میکند** – یک روز را صرف دیباگ کردم که چرا اتصال نئون گاهی تایماوت میخورد. تغییر به `@neondatabase/serverless` (مبتنی بر HTTP) آن را فوراً حل کرد. درس: همیشه وقتی روی ورسل اجرا میکنید از درایور سازگار با سرورلس استفاده کنید.
**طرحوارهٔ جدول `sessions` در طول تکرارها دچار انحراف شد** – با `CREATE TABLE IF NOT EXISTS` شروع کردم و ستونها را دستی اضافه کردم. در نهایت با مجموعهای نامرتب از طرحوارههای متفاوت مواجه شدم. اکنون از مهاجرتهای `ALTER TABLE` مستقل از وضعیت در اندپوینت لاگ استفاده میکنم و قبل از افزودن، وجود ستون را بررسی میکنم. زشت است اما کار میکند.
**عامل هنوز گاهی با وجود محافظ، حقایق را آرایش میکند** – محافظ توهم کمک میکند، اما کامل نیست. بهبود آینده پیادهسازی RAG بر روی محتوای واقعی پستها بهجای متادیتا است. به این ترتیب، عامل میتواند متن کامل یک پست وبلاگ را بازیابی کرده و با نقلقولهای دقیق پاسخ دهد. این یک پروژه برای آخر هفتهٔ دیگر است.
چگونه رایجترین خطاهای Ollama را برطرف کنیم (و چرا رخ میدهند)
اگر برای قدرتدهی به عاملهای هوش مصنوعی خود از LLMهای محلی استفاده میکنید، احتمالاً بیش از یک بار با خطای «آدرس از قبل در حال استفاده است» مواجه شدهاید. این راهنما ۹ علت ریشهای را پوشش میدهد و همه را رفع میکند.
مطالعهٔ راهنمای عیبیابی اولاما۱۰. نتیجه
بازدیدکنندهٔ نمونهکار اکنون میتواند بپرسد «آخرین پست وبلاگ چیست؟» و پاسخ درست را دریافت کند (که بهروز از دیتابیس دریافت شده). میتواند بپرسد «تب یا فاصله؟» و یک جدول اقتصاد در سطح بایت با یک نظر داغ دریافت کند. میتواند دربارهٔ مهارتهای من بپرسد و یک تفکیک سطحبندیشده با پرونده بهعنوان حقیقت پایه دریافت کند.
و هر تبادل بهصورت ناشناس لاگ میشود تا ببینم مردم واقعاً چه میپرسند – که معلوم میشود بسیار جالبتر از چیزی است که تصور میکردم. مردم میخواهند بدانند آیا استخدام میکنم، زبان برنامهنویسی مورد علاقهام چیست، آیا از ابزار خاصی استفاده کردهام، و گاهی فقط «سلام» میگویند تا ببینند ربات چگونه پاسخ میدهد.
عامل نمونهکار من را به یک چیز زنده تبدیل کرده است. فقط یک بروشور نیست – یک مکالمه است. و دقیقاً همان چیزی است که میخواستم.
اگر میخواهید چیزی مشابه بسازید، از شخصیت شروع کنید. یک کاراکتر بنویسید که واقعاً از حرف زدن با آن لذت ببرید. سپس آن را در یک API ساده بپیچید، به آن یک چهره بدهید، و رهایش کنید. فناوری بخش آسان است – شخصیت چیزی است که آن را ماندگار میکند.
چگونه رایجترین خطاهای Cline را برطرف کنیم (و چرا رخ میدهند)
اگر عامل نمونهکار خود را با Cline برای توسعهٔ محلی جفت میکنید، ممکن است با محدودیتهای توکن یا ناهماهنگی مدل مواجه شوید. این راهنما رایجترین خطاهای Cline و نحوهٔ رفع آنها را پوشش میدهد.
مطالعهٔ راهنمای عیبیابی Clineمستندات Vercel AI SDK
مستندات رسمی Vercel AI SDK بهترین مرجع برای ساخت رابطهای چت جریانی است. من از هوک `useChat` و `streamText` آنها بسیار استفاده کردم – بهشدت توصیه میشود.
مشاهدهٔ مستندات AI SDK(سؤالات متداول)
عامل چگونه من را در بازدیدهای مختلف بدون دیتابیس «به خاطر میسپارد»؟
ویجت تاریخچهٔ مکالمهٔ شما را در localStorage مرورگرتان تحت کلیدی که شامل شناسهٔ نشست ناشناس شماست ذخیره میکند. در هر بازدید بعدی، آن تاریخچه را بازیابی کرده و به عامل بازمیفرستد، بنابراین مدل کل مکالمهٔ قبلی را بهعنوان زمینه میبیند.
عامل از چه مدلی استفاده میکند و چرا گروک را انتخاب کردید؟
از مدل `gpt-oss-120b` گروک از طریق Vercel AI SDK استفاده میکنم. گروک را انتخاب کردم چون روی سرورلس فوقالعاده سریع است – مدل ۱۲۰ بیلیونی با صدها توکن در ثانیه تولید میکند و چت را حتی در شروع سرد پاسخگو میکند. همچنین نسبت به API اوپنایآی مقرونبهصرفهتر است.
آیا تاریخچهٔ چت در جایی غیر از مرورگر من ذخیره میشود؟
خیر. تنها ذخیرهسازی سمت سرور، لاگینگ پژوهشی ناشناس است (شناسهٔ نشست، دستگاه، جغرافیا، تعداد پیام، و پیامهای جداگانه با محدودیت ۸ هزار کاراکتر). ما IP، کوکی یا هیچ اطلاعات شخصی قابلشناسایی را ذخیره نمیکنیم.
چگونه از تکرار همان جوک یا شروعصحبت توسط عامل جلوگیری میکنید؟
پرامپت سیستم شامل مجموعهای از قوانین ضد تکرار است: یک لیست عبارات ممنوعه، قانونی برای تنوع ساختار جملهٔ شروع، و یک «حافظهٔ نهان» ذهنی از انواع جوک که عامل باید پیگیری کند. به مدل دستور داده شده هرگز در یک نشست از یک الگو دوبار استفاده نکند.
آیا عامل میتواند به سؤالهای مربوط به پستهای خاص وبلاگ من پاسخ دهد؟
میتواند دربارهٔ متادیتا (عنوانها، تاریخها، موضوعات) پاسخ دهد چون ما یک نمایهٔ زنده از ۲۵ پست اخیر را با هر درخواست میفرستیم. با این حال، به محتوای کامل پست دسترسی ندارد، بنابراین نمیتواند فراتر از اطلاعات متا نقلقول یا خلاصه کند. بهبود آینده اضافه کردن RAG برای بازیابی متن کامل است.
چگونه با شروعهای سرد سرورلس برخورد میکنید؟
شروعهای سرد در اولین درخواست پس از یک دورهٔ بیعملی محسوس هستند. برای کاهش، تابع را با همروندی داخلی ورسل گرم نگه میدارم و با پینگ دورهای از یک کرون جاب، اندپوینت را گرم میکنم. سرعت بالای تولید مدل گروک نیز به پنهانکردن تأخیر کمک میکند.
آیا شخصیت عامل قابل پیکربندی است؟
بله – شخصیت در یک ماژول جداگانه (`src/lib/ai/persona.ts`) تعریف شده است. میتوانم کل فایل پرامپت را عوض کنم تا نسخههای مختلف بسازم (مثلاً یک دستیار دوستانه، یک مربی انگیزشی) بدون دست زدن به فراخوانی مدل یا ویجت. قصد دارم در آینده شخصیتها را A/B تست کنم.

