[Devlime Development Log] Scrum AI’ni tanishtirish
1. Kirish
Scrum bilan ishlashda backlog elementlari va foydalanuvchi hikoyalarini yozish kutilganidan ko‘proq vaqt oladi. Yig‘ilish "Keyingi sprintda rezervatsiya funksiyasini qo‘shamiz" degan qaror bilan yakunlangan taqdirda ham, dasturchi uchun hali bajariladigan ishlar qoladi. Sarlavhani tartibga solish, mazmunni As-A / I-Want / So-That formatida yozish, qabul qilish mezonlarini qo‘shish, story point va qiymat ballarini kiritish kerak. Bu vazifalarning har biri alohida olganda tez bajariladigandek tuyuladi, ammo har bir sprintda ularni taxminan yigirma marta takrorlash vaziyatni o‘zgartiradi.
Shu sababli, Devlime’ning Scrum modulini ishlab chiqish jarayonida biz bu jarayonga sarflanadigan kuchni kamaytirishga qaror qildik. Dastlabki g‘oya oddiy edi: talablarni kiritish, UserStory qoralamasini yaratish va ball berishda ma’lumotnoma sifatida foydalanish uchun o‘tmishdagi o‘xshash ishlarni topish. Buning taxminan uch hafta vaqt olishini kutgandik.
Amalda esa ishlab chiqish kutilganidan boshqacha kechdi. AI API’ni integratsiya qilishning o‘zi qiyin bo‘lmadi; sinash va xatolarni tuzatish ishlarining aksariyati qaysi muammolarni AI’ga topshirish kerakligini aniqlash bilan bog‘liq bo‘ldi. Ushbu maqolada funksiyani qanday boshlaganimiz, qayerda muammolar yuzaga kelgani va arxitekturani qanday o‘zgartirganimizni bayon qilaman.
2. Texnologiya tanlovlari asoslari
2.1 Strukturaviy chiqish
Biz yaratgan birinchi funksiya UserStory qoralamasini generatsiya qilish edi. Foydalanuvchi kiritgan talablarni Claude’ga yuborib, JSON formatida olingan natija bilan ekranni to‘ldirdik. Dastlab bu yaxshi ishladi, ammo doim ham bir xil natija bermadi. Ba’zan javobdan oldin yoki keyin tushuntiruvchi jumlalar qo‘shilar, JSON atrofida Markdown kod bloklari paydo bo‘lar yoki ayrim maydonlar yetishmas edi.
Dastlab bunga javob satrini bevosita qayta ishlash orqali yechim topdik. Backtick belgilarini olib tashlash, JSON qayerdan boshlanishini aniqlash, yetishmayotgan maydonlar uchun standart qiymatlarni to‘ldirish va jarayon baribir muvaffaqiyatsiz bo‘lsa, istisno chiqarish uchun kodni bosqichma-bosqich qo‘shdik. Oxir-oqibat javoblarni tiklash kodi AI’ni chaqirish kodidan uzunroq bo‘lib ketdi. Faqat promptni takomillashtirishning cheklovlari bor, degan xulosaga keldik va ilova model chiqish formatini o‘zi boshqara oladigan usul kerakligini angladik.
Shu sababli Claude Java SDK taqdim etadigan strukturaviy chiqishdan foydalandik. Satr qabul qilib, uni o‘zimiz konvertatsiya qilish o‘rniga, server foydalanadigan DTO’ni bevosita javob sxemasi sifatida belgilaymiz.
public class UserStoryDraftAiSchema {
public boolean supported;
public String rejectionReason;
public List<TitleOption> titleOptions;
public List<NarrativeOption> narrativeOptions;
}
Avval yozgan parsing va tuzatish kodimizning katta qismini olib tashlashga muvaffaq bo‘ldik. Kod miqdorini kamaytirishdan ham muhimrog‘i, server foydalanadigan ma’lumotlar tuzilmasi va modeldan talab qilinadigan chiqish tuzilmasini yagona tur sifatida boshqara olish bo‘ldi. Bu funksiya uchun strukturaviy chiqishni tanlashimizga sabab ham shu. Bunga model yaxshiroq ishlagani uchun emas, balki ilova keraksiz ravishda qayta ishlashi kerak bo‘lgan kodni kamaytirgani uchun qaror qildik.
2.2 Model tanlovi
Dastlab sifatni ustuvor qo‘yib, claude-opus-5’dan foydalandik. Natijalarning o‘zi yaxshi edi, ammo uni haqiqiy ekranda ishlatganimizda javob vaqti muammoga aylandi. Har bir so‘rov taxminan 6–7 soniya davom etdi; bu funksiya sprint rejalashtirish vaqtida bir necha marta chaqirilishini hisobga olsak, ancha noqulay edi.
Shuning uchun claude-haiku’ga o‘tdik. Sifat sezilarli darajada pasayadi, deb kutgandik, ammo ikkala model natijalarini jamoaga ko‘rsatib, taqqoslaganimizda, ular orasidagi farqni aniq ajrata olishmadi. Bu funksiya murakkab mantiq yuritish yoki ijodiy generatsiyadan ko‘ra, kiritilgan talablarni belgilangan formatda tartibga solishni talab qilardi. Natijada javob vaqtini ham, chaqiruv xarajatlarini ham kamaytirdik va funksiya uchun mos modelni tanlash shunchaki qobiliyatliroq modelni tanlashdan yaxshiroq ekanini tasdiqladik.
2.3 Ball berish bo‘yicha tavsiya yondashuvi
Yo‘nalishi eng ko‘p o‘zgargan soha shu bo‘ldi. Dastlab qoralamalarni yaratganimiz kabi, story point’larni ham Claude’dan so‘rashni rejalashtirgandik. Natijalar mantiqan to‘g‘ri ko‘rindi, ammo vazifaga nima uchun 3 ball berilganini tushuntira olmadik. Bir xil talablarni bir necha marta yuborganimizda, bir safar natija 3 ball, boshqa safar esa 5 ball bo‘lishi mumkin edi. Natijalarni barqarorlashtirish uchun temperature qiymatini pasaytirish ham ball uchun asos bermadi.
Orqaga nazar tashlasak, bu kutiladigan natija edi. Claude Devlime jamoasi so‘nggi bir necha oy ichida qanday ishlarni bajarganini yoki jamoa o‘sha ishlarga qancha ball berganini bilmaydi. Story point’lar umumiy jihatdan to‘g‘ri javobga ega bo‘lgan qiymatlar emas, balki jamoaning o‘tmishdagi ishlariga nisbatan baholanadigan nisbiy qiymatlardir. Asoslovchi dalilsiz taqdim etilgan raqam ma’lumotnoma sifatida foydali emas. Bundan tashqari, u ekranda birinchi bo‘lib ko‘ringani uchungina muhokama uchun asosga aylanishi mumkin.
Shu sababli muammoni qayta belgiladik. Ball yaratish o‘rniga, o‘tmishdagi o‘xshash ishlarni topish va o‘sha elementlarga berilgan ballarni ko‘rsatish yondashuviga o‘tdik.
3. Amalga oshirish jarayoni
3.1 Generatsiya o‘rniga qidiruv
Voyage AI’ning embedding modelidan foydalanib, yakunlangan o‘tmishdagi BacklogItem’larning sarlavhalarini vektorlashtirdik va saqladik. Yangi talablar kelganda, ularni ham xuddi shu tarzda embedding’ga aylantiramiz, mavjud ma’lumotlar bilan kosinus o‘xshashligini taqqoslaymiz va eng o‘xshash uchta elementni tavsiya sifatida ishlatamiz.
[저장] 완료된 BacklogItem 제목 → Voyage Embedding → Vector 저장
[검색] 새로운 요구사항 → Voyage Embedding → Cosine Similarity → 유사 항목 Top 3
Ekranda nafaqat tavsiya etilgan ballar, balki asos bo‘lgan o‘tmishdagi elementlarning sarlavhalari va o‘sha paytda ularga berilgan ballar ham ko‘rsatiladi.
추천 참고 항목
1. 예약 화면에 사용자 검색 기능 추가 (Story Point 3)
2. 예약 상세 조회 기능 추가 (Story Point 3)
3. 예약 상태 변경 기능 추가 (Story Point 5)
Bu tuzilmada AI yakuniy javobni taqdim etmaydi. U faqat o‘tmishda o‘xshash ish mavjud bo‘lganini va ma’lum darajada baholanganini ko‘rsatadigan ma’lumotni taqdim etadi; yakuniy qarorni foydalanuvchi qabul qiladi. Asoslovchi dalillarni ko‘rsatish imkoniyati yondashuvimizni o‘zgartirishimizning eng katta sababi bo‘ldi.
3.2 So‘rov yo‘li
Anthropic API’ni to‘g‘ridan-to‘g‘ri React’dan chaqirishimiz mumkin edi. Biroq bu API Key’ni klientga oshkor qilib qo‘yar va chaqiruvlarni boshqarishni qiyinlashtirardi.
React → Scrum Backend → AI API
Frontend so‘rov uchun zarur bo‘lgan qiymatlargina uzatadi.
const draftUserStory = (params: {
rawInput: string;
productId: string;
epicId?: string;
}) => {
const query = { ...params };
return axios.post(url('/draft-user-story/fetch'), query);
};
Prompt, model tanlovi, chiqish sxemasi va qayta urinishlar kabi AI’ga aloqador barcha siyosatlar serverda boshqariladi. Ushbu sozlama bilan keyinchalik modelni almashtirsak ham, frontend’da deyarli hech qanday o‘zgarish bo‘lmaydi.
3.3 API Key’siz muhitlar
AI funksiyalaridan foydalanmaydigan dasturchilarning lokal muhitlarida Anthropic API Key bo‘lmaydi. Biroq server ishga tushganda Anthropic Client har doim yaratilsa, Key mavjud bo‘lmagan muhitlarda ilovaning o‘zi ishga tushmay qolishi mumkin. Shu sababli Bean’ni shartli ravishda ro‘yxatdan o‘tkazdik.
@Bean
@ConditionalOnProperty(name = "ANTHROPIC_API_KEY")
public AnthropicClient anthropicClient() {
return AnthropicOkHttpClient.fromEnv();
}
Client faqat API Key mavjud bo‘lgan muhitlarda yaratiladi. U mavjud bo‘lmagan muhitlarda faqat AI funksiyalari o‘chiriladi, qolgan funksionallik esa odatdagidek ishlaydi. Bitta AI funksiyasi butun jamoaning ishlab chiqish muhitiga ta’sir qilmasligi uchun ushbu chegarani yaratdik.
3.4 Sxema maydonlari tartibi
Strukturaviy chiqishga o‘tganimizdan keyin kutilmagan muammoga duch keldik: natijalar sxema maydonlari tartibiga qarab o‘zgarardi. Dastlab sarlavha variantlari birinchi bo‘lib generatsiya qilinishi kerakligini e’lon qilgandik. Natijada "Keyingi hafta jamoaviy kechki ovqat uchun joy tanlash" kabi talab deb hisoblash qiyin bo‘lgan kiritmalar uchun ham avval sarlavhalar generatsiya qilinardi. Kiritmani qayta ishlash mumkin yoki mumkin emasligini belgilaydigan supported maydoni keyinroq aniqlangani sababli, tizim ba’zan allaqachon generatsiya qilingan natijaga moslashish uchun kiritmani qo‘llab-quvvatlanadi deb baholardi.
Shuning uchun qaror qabul qilish uchun zarur bo‘lgan maydonlarni oldinga ko‘chirdik.
public boolean supported;
public String rejectionReason;
public List<TitleOption> titleOptions;
public List<NarrativeOption> narrativeOptions;
Tartibni shunday o‘zgartirdikki, tizim avval kiritma UserStory’ga aylantirilishi mumkin bo‘lgan talab ekanini aniqlaydi va qolgan mazmunni faqat natija true bo‘lgandagina generatsiya qiladi. Bu jarayon orqali sxema shunchaki DTO emasligini; u modelning chiqish jarayonini boshqaradigan prompt’ning ham bir qismi ekanini angladik.
4. Muammolarni hal qilish
Funksiya integratsiya qilinganidan keyin ham muammolar davom etdi. Quyidagi besh muammoning barchasi amalga oshirish yakunlangach, amaldagi foydalanish jarayonida aniqlandi.
4.1 Javob muvaffaqiyatli bo‘ldi, ammo mazmun bo‘sh edi
UserStory qoralamasidagi narrativeOptions bo‘sh massiv sifatida qaytariladigan muammo yuzaga keldi. HTTP Status 200 edi va serverda hech qanday istisno yuz bermadi, shuning uchun dastlab buni frontend muammosi deb o‘yladik. Haqiqiy javobni tekshirganimizda, generatsiya jarayonida u kesilib qolayotganini aniqladik.
Sabab maxTokens edi. Uni BacklogItem asosida 2048 qilib belgilagandik, ammo UserStory bir nechta sarlavha, har bir sarlavha uchun tavsif va qabul qilish mezonlarini generatsiya qilishi kerak bo‘lganligi sababli, ancha ko‘proq chiqish talab qilinardi. Qiymatni 4096 gacha oshirish muammoni hal qildi, ammo bundan ham muhimi, HTTP 200 AI javobi muvaffaqiyatli yakunlanganini anglatmasligi mumkinligini angladik. Endi javobning stop_reason qiymatini tekshiramiz va maksimal chiqish tokenlari chegarasiga yetilganda hodisani log’ga yozamiz.
4.2 Tavsiyalar soni nolga aylandi
Ball berish bo‘yicha tavsiya funksiyasini ishga tushirgach, ayrim loyihalarga hech qanday tavsiya berilmayotgani haqida fikr-mulohaza oldik. O‘xshashlik qidiruvi uchun mo‘ljallangan to‘plamda nolta element borligini aniqladik. Dastlab qidiruvni tanlangan issue bilan bir xil issue type’ga ega bo‘lgan yakunlangan elementlar bilan cheklagandik. Biroq amaldagi ma’lumotlarda ayrim turlar bo‘yicha yakunlangan elementlar kam edi, shuning uchun filtrlash taqqoslash uchun hech qanday ma’lumot qoldirmasdi.
Shu sababli type filtrini olib tashladik. Avval turli tiplarga ega ishlarning ballariga murojaat qilish noaniq bo‘ladi, deb o‘ylagandik, ammo bu funksiya ko‘rsatadigan ballar yakuniy qiymatlar emas, balki ma’lumotnomalardir. Turlar farq qilsa ham, o‘xshash sarlavhalar va ish mazmuni elementni foydali o‘tmishdagi misolga aylantirishi mumkin. Oxir-oqibat, aniqlik biroz pasayishi evaziga ham qidiruv natijalari mavjud bo‘lishini ta’minlashni tanladik.
4.3 Javobni kutish
Haiku’ga o‘tganimizdan keyin ham so‘rovdan javobgacha taxminan 2–4 soniya vaqt ketardi. Dastlab faqat Spinner’ni ko‘rsatdik, ammo amaliy foydalanishda bu vaqt kutilganidan uzoqroq tuyuldi. Shuning uchun generatsiya davomida holat xabarini bosqichma-bosqich o‘zgartirdik.
요구사항을 분석하고 있습니다.
UserStory 초안을 작성하고 있습니다.
인수 조건을 정리하고 있습니다.
Bu xabarlar modelning haqiqiy jarayoni bilan bog‘lanmagan. Biroq ular foydalanuvchilarning faqat Spinner’ga qarab o‘tiradigan vaqtini kamaytirishga yordam berdi. AI funksiyasining foydalanuvchi tajribasi faqat model javobining sifatiga bog‘liq emas; model javob berayotgan paytda foydalanuvchilarga nimani ko‘rsatishni belgilash ham funksiyaning bir qismidir.
5. Yakuniy arxitektura
Ushbu funksiyani yaratish jarayonida oxir-oqibat quyidagi arxitektura shakllandi.
사용자 → BFF → Scrum API
├─ Claude : UserStory 초안 생성
└─ Voyage : 과거 유사 항목 검색
Ikki funksiyani bir xil AI muammosi sifatida birlashtirmadik. Gaplar generatsiya qilish muammosini ma’lumotlarimizdan asoslovchi dalillarni topish muammosidan ajratdik: Claude UserStory qoralamalarini boshqaradi, Voyage embedding’lari esa o‘tmishdagi ishlarni qidirishni boshqaradi. Yakuniy qarorni foydalanuvchi qabul qiladi. Mas’uliyatlarni ajratganimizdan so‘ng, arxitektura asl holatidan ham soddaroq bo‘lib qoldi.
6. Olingan saboqlar
Uch hafta davomida bu funksiyani ishlab chiqishdan olgan eng katta sabog‘imiz shuki, AI funksiyasining qiyin qismi API chaqiruvining o‘zi emas. Claude API’ni chaqiradigan kodni kutilganidan tezroq yaratish mumkin. Ko‘p vaqt talab qilgan ishlar undan keyin boshlandi. Qaysi vazifalarni generativ modelga topshirish, qaysilarini esa o‘z ma’lumotlarimizdan topish kerakligini aniqlashimiz lozim edi. Muvaffaqiyatli HTTP javobini olganimizda ham haqiqiy mazmun to‘g‘ri ekanini tekshirishimiz va foydalanuvchilar modelning javob berish vaqtini qanday qabul qilishini hisobga olishimiz kerak edi.
Bu farqni, ayniqsa, ball tavsiyalari bilan ishlaganda yaqqol his qildim. Agar modeldan hech qanday dalilsiz hukm chiqarishni so‘rasangiz, u ishonarli javob berishi mumkin, ammo bu javob jamoamiz standartlariga mos keladimi-yo‘qmi — bu alohida masala. Aksincha, agar jamoada tarixiy ma’lumotlar mavjud bo‘lsa, modeldan javob yaratishni so‘rashdan ko‘ra, ushbu ma’lumotlarni qidirib, foydalanuvchiga ko‘rsatish ma’qulroq bo‘lishi mumkin. Oxir-oqibat, asosiy e’tibor faqat AI’ga nima qildirishga emas, avvalo bu haqiqatan ham AI yaratishi kerak bo‘lgan narsa yoki yo‘qligini aniqlashga qaratildi.
7. Xulosa
Avvaliga bu oddiy xususiyat bo‘ladi, deb o‘ylagandim. Talablarni kiritish, undan UserStory yaratishni so‘rash, o‘xshash vazifalarni topish va ularning ballarini ko‘rsatishning o‘zi kifoya, deb hisoblagandim. Biroq uni amalda yaratganimizdan so‘ng, yo‘nalishimizni ikki marta sezilarli darajada o‘zgartirdik. Birinchi o‘zgarish ball tavsiyalariga generatsiya muammosi sifatida yondashish bo‘ldi, ikkinchisi esa qidiruv aniqligini yaxshilashga urinib, nomzodlarni haddan tashqari cheklash edi. Bu muammolarning hech biri kodni noto‘g‘ri yozishdan kelib chiqmagan; ikkalasi ham dastlabki muammoni noto‘g‘ri belgilash natijasida yuzaga kelgan.
Ushbu ish davomida kelajakda AI xususiyatlarini yaratganimizda uchta jihatni doim tekshirib borishni rejalashtiryapman. Birinchidan, xususiyatga mos modelni tanlash. Ikkinchidan, serverda xarajat talab qiladigan chaqiruvlarni nazorat qilish. Uchinchidan, xususiyat yaratilgandan so‘ng jamoa a’zolarining undan o‘zlari foydalanishini ta’minlash.
Menimcha, oxirgi jihat ayniqsa muhim. Bu safar aniqlagan muammolarimizning aksariyatini ishlab chiqish jarayonida aniqlab bo‘lmas edi. Bo‘sh massiv muammosi, koreys tilidagi kiritish muammosi va qidiruv natijalari soni nolga teng bo‘lib qolishi bilan bog‘liq muammo faqat xususiyatdan amalda foydalanganimizdan keyin ayon bo‘ldi.
Oxir-oqibat, ushbu Scrum AI ishlab chiqish jarayonida eng ko‘p o‘zgargan narsa kod emas, balki muammoga qanday qarashimiz bo‘ldi. Kelajakda Devlime’ga AI xususiyatlarini qo‘shayotganimizda, avval model tanlash o‘rniga, muammo nimadan iboratligini belgilashdan va unga generatsiya, qidiruv yoki qoidalar yondashuvlaridan qaysi biri eng mos kelishini aniqlashdan boshlashni rejalashtiryapman.
Ushbu uzun postni o‘qiganingiz uchun rahmat.
jun