1import torch
2import torch.nn.functional as F
3from transformers import AutoTokenizer, AutoModelForTokenClassification
4import numpy as np
5from typing import List, Optional, Tuple
6
7# Global priority constants
8PRIORITY_TOKENS = "tokens"
9PRIORITY_CHARS = "chars"
10PRIORITY_STRICT = "strict"
11
12# Global configuration variables for easy modification
13MODEL_PATH = "LocalDoc/semantic_chunker"
14MAX_CHUNK_TOKENS = 400 # Single parameter for chunk size
15TARGET_TOKENS = 256 # Single parameter for target size
16MAX_CHUNK_CHARS = None # No character limit by default
17THRESHOLD = 0.12
18PRIORITY = PRIORITY_STRICT
19
20# Dynamic calculation constants (avoid hardcoding)
21MIN_LENGTH_DIVISOR = 8 # target_tokens // MIN_LENGTH_DIVISOR for min chunk length
22MARGIN_RATIO = 0.25 # 25% margin for max_tokens validation
23MIN_MARGIN = 20 # Minimum margin when validating max_tokens
24ROUGH_TOKEN_MULTIPLIER = 1.3 # Rough token estimation multiplier
25LONG_TEXT_THRESHOLD = 400 # Threshold for long text processing
26BERT_MAX_LENGTH = 512 # Standard BERT model limit
27SEARCH_RANGE = 50 # Character search range for boundaries
28SENTENCE_SEARCH_RANGE = 100 # Range for sentence boundary search
29SPACE_SEARCH_RANGE = 50 # Range for space boundary search
30OPTIMAL_TARGET_RATIO = 0.9 # 90% of max as safety margin for merging
31GOOD_SIZE_MIN_RATIO = 0.7 # 70% of target for minimum good size
32MERGE_THRESHOLD_RATIO = 0.5 # 50% of target for merge threshold
33MERGE_ATTEMPT_RATIO = 0.6 # 60% of target for merge attempts
34OPTIMAL_MAX_RATIO = 1.1 # 110% of max for slight overflow
35MIN_ACCEPTABLE_RATIO = 0.3 # 30% of target for minimum acceptable size
36MAX_MERGE_RATIO = 1.5 # 150% of target for maximum merge size
37
38# Sample text for testing
39#SAMPLE_TEXT = """Azərbaycan, Qafqazın incisi olaraq, zəngin tarixi, unikal mədəniyyəti və möhtəşəm təbiəti ilə hər zaman diqqət çəkmişdir. Bu torpaqlar minillər boyu müxtəlif sivilizasiyaların qovuşağında yerləşmiş, onların izlərini özündə yaşatmışdır. Şərqlə Qərb arasında bir körpü rolunu oynayan Azərbaycan, özünün çoxəsrlik dövlətçilik ənənəsi ilə fəxr edir. Ölkənin paytaxtı Bakı, Xəzər dənizinin sahilində yerləşən, Qədim Şəhər (İçərişəhər) kimi UNESCO Dünya İrs Siyahısına daxil edilmiş tarixi məkanları və müasir memarlıq abidələri ilə bir araya gətirən canlı bir metropoldur. Müasir Bakının simvollarından olan Heydər Əliyev Mərkəzi, incəsənət, mədəniyyət və təhsilin mərkəzi kimi fəaliyyət göstərir, onun innovativ dizaynı ilə dünyanın diqqətini cəlb edir. Azərbaycanın təbiəti də heyrətamiz dərəcədə müxtəlifdir. Böyük Qafqaz dağlarının əzəməti, Kiçik Qafqazın mənzərəli vadiləri, Kür-Araz ovalığının bərəkəti və Xəzər dənizinin sahil zolağı ölkəyə özünəməxsus gözəllik qatır. Qax rayonundakı Laza kəndi, dağların əhatəsində yerləşən, təbii gözəlliyi ilə seçilən bir turizm mərkəzidir. Bu ərazilərdə füsunkar meşələr, bol sulu çaylar, şəlalələr və müalicəvi mineral bulaqlar mövcuddur. Göygöl Milli Parkı, Azərbaycanın ən gözəl təbii guşələrindən biridir, onun saf gölü və ətrafındakı meşələr, nadir bitki və heyvan növlərinə ev sahibliyi edir. Texnologiyanın sürətli inkişafı dövründə Azərbaycan da bu qlobal trenddən kənarda qalmayıb. Son illərdə ölkədə rəqəmsal transformasiya prosesləri sürətlənmiş, informasiya-kommunikasiya texnologiyaları (İKT) sektoruna böyük investisiyalar qoyulmuşdur. Bakıda "Hi-Tech Park" kimi müasir texnoloji mərkəzlər fəaliyyət göstərir, innovativ startaplara dəstək verilir. Süni intellekt (AI), maşın təlimi (ML) və böyük verilənlər (Big Data) kimi sahələr prioritet istiqamətlər olaraq müəyyən edilmişdir. Ölkənin gələcəyi üçün əsaslı önəm kəsb edən bu sahələr, təhsil və elmi araşdırmalarla sıx əlaqədardır. Universitetlərdə və elmi institutlarda İKT-nin müxtəlif istiqamətləri üzrə tədqiqatlar aparılır, gənc kadrlar hazırlanır. Virtual və artırılmış reallıq texnologiyaları da təhsil, mədəniyyət və turizm sahələrində tətbiq olunmağa başlanmışdır. "Azercosmos" ASC, peyk texnologiyaları sahəsində ölkənin potensialını artırır, bu da telekommunikasiya, naviqasiya və yerin uzaqdan müşahidəsi kimi sahələrdə əhəmiyyətli rol oynayır. Azərbaycanın tarixi boyu bir çox mühüm hadisələrə şahidlik etmişdir. Qədim zamanlarda Albaniya dövlətinin yaranması, İpək Yolunun bir hissəsinin bu ərazidən keçməsi, orta əsrlərdə Şirvanşahlar və Səfəvilər kimi güclü dövlətlərin hökmranlığı ölkənin siyasi və mədəni inkişafına təsir göstərmişdir. 1918-ci ildə Şərqdə ilk demokratik respublika olan Azərbaycan Xalq Cümhuriyyətinin qurulması, bu torpaqların müstəqillik uğrunda mübarizəsinin parlaq nümunəsidir. Sovet hakimiyyəti illərindəki çətinliklərə baxmayaraq, Azərbaycan 1991-ci ildə yenidən müstəqilliyini qazanmışdır. Ulu Öndər Heydər Əliyevin müstəqil Azərbaycanın təməlini qoyması və Prezident İlham Əliyevin rəhbərliyi ilə ölkənin inkişaf etməsi, bu günümüzdə də davam edən uğurlu siyasətin nəticəsidir. Qarabag müharibəsi və 2020-ci ildəki Vətən Müharibəsi, Azərbaycanın ərazi bütövlüyünün təmin edilməsində həlledici rol oynamışdır. Bu qələbələr, xalqımızın birliyini və gücünü bir daha təsdiqlədi. Gələcəyə baxış, Azərbaycan üçün böyük potensial vəd edir. Ölkə, enerji resursları baxımından zəngin olmaqla yanaşı, tranzit və logistika mərkəzi kimi də strateji əhəmiyyət kəsb edir. "Asrın Müqaviləsi"nin imzalanması, Xəzər dənizinin neft və qaz yataqlarının dünya bazarına çıxarılmasında mühüm rol oynamışdır. "Şərq-Qərb" və "Şimal-Cənub" beynəlxalq nəqliyyat dəhlizlərinin inkişafı, Azərbaycanın regionda aparıcı logistika mərkəzinə çevrilməsinə şərait yaradır. Yaşıl enerji və dayanıqlı inkişaf konsepsiyaları da ölkənin gələcək strategiyasının əsasını təşkil edir. Günəş və külək enerjisinin inkişafına böyük diqqət yetirilir, iqlim dəyişikliyinin təsirlərini azaltmaq üçün səylər artırılır. Təhsil sisteminin təkmilləşdirilməsi, gənclərin beynəlxalq standartlara uyğun kadr kimi yetişdirilməsi, elmi-texniki potensialın gücləndirilməsi gələcək uğurların təminatıdır. Şəhərlərin yenidən qurulması, infrastrukturun modernləşdirilməsi, sosial sahələrin inkişafı, bütün bunlar ölkənin gələcək tərəqqisi üçün atılan addımlardır. Azərbaycan, Avropa ilə inteqrasiya, beynəlxalq əməkdaşlığın gücləndirilməsi və regional sabitliyin təmin edilməsi istiqamətində də aktiv fəaliyyət göstərir. Sülh və əməkdaşlıq siyasəti, Azərbaycanın beynəlxalq aləmdəki mövqeyini daha da gücləndirir. İnformasiya Texnologiyaları Universiteti Azərbaycan Respublikasında informasiya cəmiyyəti quruculuğu istiqaməti üzrə yüksək hazırlığa malik kadr potensialının formalaşdırılmasını təmin edən ali təhsil müəssisəsi idi. Azərbaycan Respublikasının Prezidentinin Sərəncamı ilə 1 fevral 2013-cü ildə yaradılmışdır.[2] İnformasiya Texnologiyaları Universitetinin və Azərbaycan Respublikası Xarici İşlər Nazirliyinin Diplomatik Akademiyasının əsasında, 13 yanvar 2014-cü ildə "ADA" Universiteti yaradılması ilə fəaliyyəti başa çatmışdır.[3]Universitetin tarixi 2006-cı ildən başlayır. Belə ki, 2006-cı ilin martında Azərbaycan Xarici İşlər Nazirliyinin nəzdində Azərbaycan Diplomatik Akademiyası adı altında ali təhsil müəssisəsi yaradılmışdır. Azərbaycan Respublikası Prezidentinin 13 yanvar 2014-cü il tarixli Sərəncamı ilə Azərbaycan Respublikası Xarici İşlər Nazirliyinin Diplomatik Akademiyasının və Azərbaycan Respublikasında İnformasiya Texnologiyaları Universitetinin əsasında "ADA" Universiteti yaradılmışdır[4].Əsas məqsədi diplomatiya, ictimai münasibətlər, biznes, informasiya texnologiyaları və sistem mühəndisliyi üzrə qlobal liderlər hazırlamaqdır. Akademiyanın əsasını qoyan rektor, Azərbaycan Xarici İşlər Nazirinin müavini və Azərbaycanın ABŞ-dəki sabiq səfiri Hafiz Paşayevdir.[5]2012-ci ildə Bakı şəhərində "Dədə Qorqud" parkı yaxınlığında yerləşən yeni "Green and Smart" kampusuna köçmüşdür. 2009-cu ildən magistr pilləsi, 2011-ci ildən bakalavr pilləsində ali təhsil verir. XIV əsrdə Rəşidəddin Fəzlullah ibn Əbil-Xeyrə Əli Həmədaninin qələmə aldığı Cəmi ət-Təvarix (Tarix toplusu) adlı əsərinin "Mujallad-i Awwal" (Birinci Kitabı: Monqol tarixi)in "Bab-i Awwal" (Birinci Bölüm: Türk ve Monqol qəbilələrinin tarixi)ində monqolların yaradılış dastanı olaraq qeyd edilmiş əfsanə,[5][6][7] 17. yüzildə Şibanın nəvələrindən və Xivə xanlığının xanı olan Əbulqazi Bahadır xanın qələmə aldığı Şəcərəyi Türk adlı əsərdə də monqolların yaradılış dastanı olaraq qeyd edilmişdir, lakin bəzi mənbələrə görə də Türk dastanıdır.[6][7] Bəhsi keçən hər iki tarixi mənbədə Nekuz (Nüküz) və Qiyan (Kıyan) adlı qardaşlar ilə xanımları tatarlar tərəfindən məğlub edildikdən sonra Ərgənəqon (Farsca:ارگنه قون; Ergene Qon) adı verilən dar və sıldırım bir yerə getmiş, 400 ildə sülaləsi çoxalıb Ərgənəqondan çıxmşdır. Ərgənəqondan çıxdıqları zaman yol göstərənin Börteçine olduğu düşünülməkdədir.[7]Ancaq Göytürklərin diriliş dastanı ilə olan oxşarlıqları səbəb göstərərək Türklərə aid bir dastan olduğunu iddia edən tədqiqatçılar da var.[7][8] Talat Sait Halman isə mifoloji bir varlıq olan Bozqurdun müdafiəsi sayəsində soyunun tükənmə təhlükəsindən qurtulan və yenə Bozqurtlar sayəsində dağlarla əhatə olunmuş Ərgənəqon vadisindən çıxan bir Türk toplumunun hekayəsindən bəhs edildiyini iddia edir.[9] Digər görüşlərə görə isə Türklər və monqollar arasında bənzər olan əfsanələr vardır.[10] Əfsanə bəzən də Novruz ilə əlaqələndirilir.[11]"""
40SAMPLE_TEXT = """Azərbaycan Respublikası, Qafqaz regionunda yerləşən, unikal coğrafi mövqeyi, zəngin tarixi və çoxşaxəli mədəniyyəti ilə hər zaman özünəməxsus yer tutmuşdur. Bu torpaqlar minillər ərzində müxtəlif sivilizasiyaların, dövlətlərin və mədəniyyətlərin təsirinə məruz qalmış, özündə dərin izlər buraxmışdır. Şərqlə Qərb arasında körpü rolunu oynayan Azərbaycan, Şirvanşahlar, Səfəvilər, Qaraqoyunlular, Ağqoyunlular kimi güclü dövlətlərin mərkəzi olmuş, İpək Yolu üzərində strateji əhəmiyyət kəsb etmişdir. Müstəqilliyini bərpa etdikdən sonra, xüsusilə son illərdə, ölkə dinamik inkişaf yolu keçərək regionda aparıcı dövlətlərdən birinə çevrilmişdir. Bakı şəhəri, Azərbaycanın paytaxtı olaraq, Xəzər dənizinin sahilində yerləşən, qədimlik və müasirlik vəhdətini özündə yaşadan möhtəşəm bir məkandır. UNESCO Dünya İrs Siyahısına daxil edilmiş Qədim Şəhər (İçərişəhər) kompleksi, orta əsr memarlığının nadir nümunələrini özündə əks etdirir. Şirvanşahlar Sarayı, Qız Qalası kimi tarixi abidələr buranın qədim tarixindən xəbər verir. Eyni zamanda, Heydər Əliyev Mərkəzi, Alov Qüllələri, Bakı Abadlıq Kompleksi kimi müasir memarlıq inciləri şəhərə müasir və unikal görkəm qatır. Bu müasir tikililər, innovativ dizaynları və texnoloji həlləri ilə diqqət çəkir. Azərbaycanın təbiəti də onun mədəni zənginliyi qədər heyranedici və müxtəlifdir. Ölkə ərazisi, Böyük Qafqaz dağ sisteminin cənub-şərq yamaclarından Kiçik Qafqaz silsiləsinə, Kür-Araz ovalığından Lənkəran ovalığına qədər geniş diapazonda müxtəlif relyef formalarını əhatə edir. Bu coğrafi müxtəliflik, müxtəlif iqlim tiplərinin və zəngin biomüxtəlifliyin yaranmasına səbəb olmuşdur. Qəbələ, Şəki, Qax kimi şimal rayonları dağlıq və meşəlik əraziləri, bol bulaqları, ecazkar mənzərələri ilə tanınır. Lənkəran və Astara rayonları subtropik iqlimə malik olub, rütubətli meşələri və unikal flora və faunası ilə seçilir. Göygöl Milli Parkı, Şahdağ Milli Parkı kimi qorunan ərazilər, ölkənin təbiətini qorumaq, həm də elmi tədqiqatlar aparmaq üçün əhəmiyyətli mərkəzlərdir. Göygölün özü, onun ətrafındakı meşələr və dağlar, həm də bir çox əfsanə və rəvayətlərə məskən olmuşdur. Elm və texnologiya sahəsində Azərbaycanın nailiyyətləri də xüsusi qeyd olunmalıdır. Ölkədə İKT sektorunun inkişafına böyük önəm verilir. Rəqəmsal transformasiya, süni intellekt (AI), maşın təlimi (ML), böyük verilənlər (Big Data) texnologiyalarının tətbiqi prioritet istiqamətlərdir. Bakıda fəaliyyət göstərən "Hi-Tech Park" və "Sumqayıt Kimya Sənaye Parkı" kimi texnoloji və sənaye parkları, innovativ layihələrin həyata keçirilməsi, yeni texnologiyaların yaradılması və tətbiqi üçün əlverişli mühit yaradır. "Azercosmos" Açıq Səhmdar Cəmiyyəti, peyk texnologiyaları sahəsində ölkənin potensialını artıraraq, telekommunikasiya, naviqasiya, telekommunikasiya, yerin uzaqdan müşahidəsi və informasiya təhlükəsizliyi kimi sahələrdə mühüm rol oynayır. Universitetlərdə, xüsusilə də ADA Universiteti, İnformasiya Texnologiyaları Universiteti (indiki ADA Universitetinin tərkibində) kimi təhsil müəssisələrində İKT sahələri üzrə tədqiqatlar aparılır, yüksək ixtisaslı kadrlar hazırlanır. Virtual və artırılmış reallıq texnologiyaları da təhsil, mədəniyyət və turizm sahələrində geniş tətbiq olunur. Bu sahələrin inkişafı ölkənin gələcək dayanıqlı inkişafı üçün əsasdır. Azərbaycan tarixi boyu bir çox mühüm hadisələrə şahidlik etmişdir. Qədim dövrlərdə Midiya, Atropatena, Qafqaz Albaniyası kimi dövlətlərin yaranması, sonrakı dövrlərdə Arran, Şirvan, Gəncə xanlıqlarının mövcudiyyəti ölkənin dövlətçilik ənənəsini gücləndirmişdir. 1918-ci ildə Şərqdə ilk demokratik respublika olan Azərbaycan Xalq Cümhuriyyətinin qurulması, bu torpaqların azadlıqsevərliyinin və müstəqillik uğrunda mübarizəsinin parlaq bir nümunəsidir. 1920-ci ildə Aprel işğalından sonra Sovet hakimiyyəti qurulsa da, xalq heç vaxt öz milli kimliyindən və dövlətçilik arzularından vaz keçməmişdir. 1991-ci ildə Ümummilli Lider Heydər Əliyevin müdrik siyasəti və xalqın iradəsi sayəsində Azərbaycan yenidən müstəqilliyini qazanmışdır. Sonrakı illərdə, Prezident İlham Əliyevin rəhbərliyi altında ölkə sosial-iqtisadi, siyasi və hərbi sahələrdə böyük nailiyyətlər əldə etmişdir. 2020-ci ildə aparılan Vətən Müharibəsi nəticəsində ölkənin ərazi bütövlüyü tam təmin olunmuş, doğma torpaqlarımız 30 illik işğaldan azad edilmişdir. Bu qələbə, Azərbaycan xalqının birliyinin, gücünün və iradəsini bütün dünyaya bir daha sübut etmişdir. Azərbaycanın gələcəkə baxışı, həm ölkə daxilində, həm də beynəlxalq aləmdə özünü göstərir. Ölkə, enerji resursları (neft və qaz) ixracatçısı olmaqla yanaşı, yeni tranzit və logistika mərkəzi kimi də strateji əhəmiyyətini artırmışdır. "Asrın Müqaviləsi" və sonrakı neft-qaz layihələri ölkə iqtisadiyyatının inkişafına böyük təkan vermişdir. Bakı-Tbilisi-Qars dəmir yolu, "Şərq-Qərb" və "Şimal-Cənub" beynəlxalq nəqliyyat dəhlizlərinin mühüm hissəsi kimi, Azərbaycanın Avrasiya məkanında rolunu gücləndirmişdir. Yaşıl enerji və dayanıqlı inkişaf konsepsiyaları da ölkənin gələcək strategiyasının əsasını təşkil edir. Günəş və külək enerjisi potensialından səmərəli istifadə etmək, emissiyaları azaltmaq və iqlim dəyişikliyinin mənfi təsirlərini minimuma endirmək üçün ciddi səylər göstərilir. Təhsil sisteminin modernləşdirilməsi, gənclərin beynəlxalq səviyyədə rəqabətədavamlı kadr kimi yetişdirilməsi, elmi-texniki potensialın artırılması ölkənin gələcək uğurları üçün prioritetdir. Şəhərlərin, xüsusilə də regionların sosial-iqtisadi inkişafına yönəlmiş dövlət proqramları, infrastruktur layihələri, sosial təminatın gücləndirilməsi, bütün bunlar Azərbaycanın davamlı tərəqqisinin təminatıdır. Beynəlxalq əməkdaşlığın genişləndirilməsi, Avropa İttifaqı və digər beynəlxalq təşkilatlarla əlaqələrin möhkəmləndirilməsi, regional sabitliyin və təhlükəsizliyin təmin edilməsində aktiv rol oynamaq, Azərbaycanın xarici siyasətinin əsas istiqamətləridir. Sülhə və əməkdaşlığa əsaslanan bu siyasət, ölkənin beynəlxalq aləmdəki nüfuzunu daha da artırır. Tarixi və mədəni irsimizin qorunması da dövlətimizin prioritetlərindəndir. Naxçıvan Muxtar Respublikasında yerləşən Əshabi-Kəhf, Şəki Xan Sarayı, Qobustan qaya təsvirləri, Qarabağın mədəniyyət abidələri, milli musiqimizin (muğam) UNESCO tərəfindən qeyri-maddi mədəni irs siyahısına daxil edilməsi, Azərbaycanın zəngin mədəniyyətini dünyaya tanıtmaq istiqamətində atılan mühüm addımlardır. Qarabağın azad olunmasından sonra, işğaldan ziyan dəymiş mədəniyyət obyektlərinin, dini məbədlərin, xüsusilə də Şuşa şəhərinin bərpası və qorunması işlərinə başlanılmışdır. Şuşa, Azərbaycanın mədəniyyət paytaxtı elan edilmişdir və bu, onun milli-mənəvi dəyərlər sistemimizdəki xüsusi yerini bir daha təsdiqləyir. Arxeoloji qazıntılar, tarixi abidələrin restavrasiyası, muzeylərin fəaliyyətinin təkmilləşdirilməsi, yeni mədəniyyət mərkəzlərinin yaradılması, gənclərin milli mədəniyyətimizə marağının artırılması istiqamətində davamlı işlər aparılır. Bu işlər, gələcək nəsillərə örnək olaraq, ölkəmizin unikal mədəni irsinin qorunub saxlanılmasına və təbliğinə xidmət edir. Elmi tədqiqatlar sahəsində də böyük irəliləyişlər müşahidə olunur. Milli Elmlər Akademiyası (AMEA) və müxtəlif ali təhsil müəssisələrinin alimləri, fundamental və tətbiqi elmlərin müxtəlif sahələrində mühüm nailiyyətlər əldə edirlər. Fizika, kimya, biologiya, texnika elmləri, humanitar və sosial elmlər sahələrində aparılan tədqiqatlar, ölkənin elmi potensialını artırmaqla yanaşı, həm də qlobal elmi proseslərə töhfə verir. Xüsusilə nanotexnologiyalar, materialşünaslıq, süni intellekt, bioteknologiya, ekologiya və yer elmləri kimi müasir istiqamətlərdə aparılan tədqiqatlar, prioritet sahələr olaraq müəyyən edilmişdir. Gənc alimlərin hazırlanması, elmi-tədqiqat işlərinə cəlb edilməsi, beynəlxalq elmi əməkdaşlığın gücləndirilməsi, elmi jurnal və konfransların təşkili, müasir innovativ ideyaların reallaşdırılması üçün qrant proqramlarının maliyyələşdirilməsi, bütün bunlar elmin inkişafına dövlət qayğısının bariz nümunələridir. Akademik təqaüdlərin verilməsi, elmi müəssisələrin maddi-texniki bazasının gücləndirilməsi, beynəlxalq reytinqli jurnallarda məqalələrin dərc olunması üçün şərait yaradılması, həmçinin elmi nəticələrin sənaye və iqtisadiyyatla inteqrasiyası, ölkənin elmi-texniki potensialını gücləndirir. Tarixi Azərbaycan torpaqları, həm də zəngin folklora, ədəbiyyata və incəsənətə malikdir. Dədə Qorqud dastanları, Nizami Gəncəvinin, Füzulinin, Vahid, Nəsimi kimi dahi şairlərin əsərləri, Azərbaycan ədəbiyyatının qızıl fondunu təşkil edir. Muğam ifaçılıq sənəti, xalçaçılıq, metal üzərində işləmə sənəti, memarlıq məktəbləri, milli rəqs və musiqi janrları – bütün bunlar Azərbaycanın dünya mədəniyyətinə verdiyi töhfələrdir. Bu dəyərlərin qorunması, təbliği və gələcək nəsillərə ötürülməsi, dövlətimizin qarşısında duran mühüm vəzifələrdəndir. Mədəniyyət Nazirliyinin fəaliyyəti, muzeylərin, teatrların, musiqi kollektivlərinin işinin təkmilləşdirilməsi, xarici ölkələrdə Azərbaycan mədəniyyətinin təbliğ edilməsi, müxtəlif mədəni tədbirlərin – festivalların, sərgilərin, konsertlərin – təşkili, bütün bunlar ölkəmizin mədəni həyatının zənginliyini göstərir."""
41
42class AzerbaijaniTextChunker:
43 """Optimized Azerbaijani text chunker with single parameter configuration"""
44
45 def __init__(self, model_path: str = MODEL_PATH):
46 self.model_path = model_path
47 self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
48 self.tokenizer = None
49 self.model = None
50 self.is_loaded = False
51
52 def load_model(self) -> bool:
53 """Load the trained model"""
54 try:
55 self.tokenizer = AutoTokenizer.from_pretrained(
56 self.model_path, use_fast=True, trust_remote_code=False
57 )
58 self.model = AutoModelForTokenClassification.from_pretrained(
59 self.model_path, trust_remote_code=False
60 )
61 self.model.to(self.device)
62 self.model.eval()
63 self.is_loaded = True
64 return True
65 except Exception as e:
66 print(f"Error loading model: {e}")
67 return False
68
69 def chunk(self,
70 text: str,
71 max_chunk_tokens: Optional[int] = None,
72 target_tokens: Optional[int] = None,
73 max_chunk_chars: Optional[int] = None,
74 threshold: float = None,
75 priority: str = None) -> List[str]:
76 """
77 Chunk text into semantic segments with optimal sizing
78
79 Args:
80 text: Input text to chunk
81 max_chunk_tokens: Maximum tokens per chunk (default: from global config)
82 target_tokens: Target optimal size in tokens (default: from global config)
83 max_chunk_chars: Maximum characters per chunk (default: from global config)
84 threshold: Confidence threshold for splitting (default: from global config)
85 priority: Which limit to prioritize when both are set (default: from global config)
86
87 Returns:
88 List of optimally-sized text chunks
89 """
90 if not self.is_loaded:
91 if not self.load_model():
92 return [text]
93
94 # Use global configuration as defaults
95 if max_chunk_tokens is None:
96 max_chunk_tokens = MAX_CHUNK_TOKENS
97 if target_tokens is None:
98 target_tokens = TARGET_TOKENS
99 if max_chunk_chars is None:
100 max_chunk_chars = MAX_CHUNK_CHARS
101 if threshold is None:
102 threshold = THRESHOLD
103 if priority is None:
104 priority = PRIORITY
105
106 # Dynamic validation and minimum length calculation
107 dynamic_min_length = max(10, target_tokens // MIN_LENGTH_DIVISOR)
108
109 if not text or len(text.strip()) < dynamic_min_length:
110 return [text.strip()] if text.strip() else []
111
112 text = text.strip()
113
114 # Ensure max >= target with reasonable margin
115 if max_chunk_tokens < target_tokens:
116 margin = max(target_tokens * MARGIN_RATIO, MIN_MARGIN)
117 max_chunk_tokens = target_tokens + int(margin)
118
119 # Step 1: Get all semantic boundaries from the model
120 semantic_boundaries = self._get_semantic_boundaries_fixed(text, threshold)
121
122 # Step 2: Split text at semantic boundaries (no limits yet)
123 initial_chunks = self._split_at_boundaries(text, semantic_boundaries)
124
125 # Step 3: Optimize chunk sizes with dynamic parameters
126 optimized_chunks = self._optimize_chunk_sizes(
127 initial_chunks, max_chunk_tokens, target_tokens
128 )
129
130 # Step 4: Apply additional limits if specified
131 if max_chunk_chars is not None:
132 final_chunks = self._apply_char_limits(optimized_chunks, max_chunk_chars)
133 else:
134 final_chunks = optimized_chunks
135
136 return self._clean_and_validate_chunks(final_chunks, target_tokens, max_chunk_tokens)
137
138 def _get_semantic_boundaries_fixed(self, text: str, threshold: float) -> List[int]:
139 """Get all semantic boundaries without sequence length warnings"""
140
141 # Check text length first and use chunked approach if needed
142 rough_token_count = len(text.split()) * ROUGH_TOKEN_MULTIPLIER
143
144 if rough_token_count > LONG_TEXT_THRESHOLD:
145 return self._get_boundaries_for_long_text(text, threshold)
146
147 # For shorter texts, use standard approach with truncation
148 full_encoding = self.tokenizer(
149 text,
150 return_tensors="pt",
151 return_offsets_mapping=True,
152 add_special_tokens=True,
153 truncation=True, # Enable truncation to avoid warnings
154 max_length=BERT_MAX_LENGTH, # Standard BERT limit
155 padding=False
156 )
157
158 input_ids = full_encoding['input_ids'][0]
159 offset_mapping = full_encoding['offset_mapping'][0]
160
161 # Remove special tokens (CLS at start, SEP at end)
162 content_input_ids = input_ids[1:-1]
163 content_offsets = offset_mapping[1:-1]
164
165 if len(content_input_ids) == 0:
166 return []
167
168 # Process with single window since we truncated
169 boundaries = self._process_single_window_safe(
170 text, input_ids, content_offsets, threshold
171 )
172
173 # Remove duplicates and sort
174 boundaries = sorted(list(set(boundaries)))
175
176 # Remove boundaries at text start/end
177 boundaries = [b for b in boundaries if 0 < b < len(text)]
178
179 return boundaries
180
181 def _get_boundaries_for_long_text(self, text: str, threshold: float) -> List[int]:
182 """Handle long texts by processing in semantic chunks"""
183
184 # Split text into rough sections first (by sentences)
185 import re
186 sentences = re.split(r'[.!?]+', text)
187
188 all_boundaries = []
189 current_pos = 0
190
191 # Process each section that fits in model limits
192 current_section = ""
193
194 for sentence in sentences:
195 test_section = current_section + sentence + "."
196 rough_tokens = len(test_section.split()) * ROUGH_TOKEN_MULTIPLIER
197
198 if rough_tokens > LONG_TEXT_THRESHOLD and current_section:
199 # Process current section
200 section_boundaries = self._process_text_section(
201 current_section, current_pos, threshold
202 )
203 all_boundaries.extend(section_boundaries)
204
205 # Start new section
206 current_pos += len(current_section)
207 current_section = sentence + "."
208 else:
209 current_section = test_section
210
211 # Process remaining section
212 if current_section:
213 section_boundaries = self._process_text_section(
214 current_section, current_pos, threshold
215 )
216 all_boundaries.extend(section_boundaries)
217
218 return sorted(list(set(all_boundaries)))
219
220 def _process_text_section(self, section: str, offset: int, threshold: float) -> List[int]:
221 """Process a section of text that fits in model limits"""
222
223 try:
224 # Tokenize section (should be safe now)
225 encoding = self.tokenizer(
226 section,
227 return_tensors="pt",
228 return_offsets_mapping=True,
229 add_special_tokens=True,
230 truncation=True,
231 max_length=BERT_MAX_LENGTH
232 )
233
234 input_ids = encoding['input_ids'][0]
235 offset_mapping = encoding['offset_mapping'][0]
236
237 # Remove special tokens
238 content_offsets = offset_mapping[1:-1]
239
240 # Get model predictions
241 with torch.no_grad():
242 outputs = self.model(encoding['input_ids'].to(self.device))
243 probabilities = F.softmax(outputs.logits, dim=-1)
244
245 # Extract B-CHUNK probabilities
246 b_chunk_id = self.model.config.label2id.get("B-CHUNK", 1)
247 chunk_probs = probabilities[0, 1:-1, b_chunk_id].cpu().numpy()
248
249 # Find boundaries
250 boundaries = []
251 for i, (prob, char_offset) in enumerate(zip(chunk_probs, content_offsets)):
252 if prob > threshold:
253 char_start = char_offset[0].item()
254 char_end = char_offset[1].item()
255
256 # Find clean boundary in full text
257 boundary_pos = self._find_clean_boundary_global(
258 section, char_start, char_end, offset
259 )
260 if boundary_pos is not None and boundary_pos > offset:
261 boundaries.append(boundary_pos)
262
263 return boundaries
264
265 except Exception as e:
266 print(f"Warning: Error processing section: {e}")
267 return []
268
269 def _find_clean_boundary_global(self, section: str, char_start: int,
270 char_end: int, global_offset: int) -> Optional[int]:
271 """Find clean boundary and return global position"""
272
273 local_boundary = self._find_clean_boundary(section, char_start, char_end)
274 if local_boundary is not None:
275 global_pos = global_offset + local_boundary
276
277 # Additional validation: ensure we don't cut words in half
278 if global_pos > 0 and global_pos < len(section) + global_offset:
279 return global_pos
280 return None
281
282 def _process_single_window_safe(self, text: str, input_ids: torch.Tensor,
283 content_offsets: torch.Tensor, threshold: float) -> List[int]:
284 """Process text that fits in a single window safely"""
285
286 try:
287 # Get model prediction
288 with torch.no_grad():
289 input_ids_batch = input_ids.unsqueeze(0).to(self.device)
290 outputs = self.model(input_ids_batch)
291 probabilities = F.softmax(outputs.logits, dim=-1)
292
293 # Extract B-CHUNK probabilities for content tokens only
294 b_chunk_id = self.model.config.label2id.get("B-CHUNK", 1)
295 chunk_probs = probabilities[0, 1:-1, b_chunk_id].cpu().numpy()
296
297 # Find boundaries
298 boundaries = []
299 for i, (prob, offset) in enumerate(zip(chunk_probs, content_offsets)):
300 if prob > threshold:
301 char_start = offset[0].item()
302 char_end = offset[1].item()
303
304 # Find clean boundary position
305 boundary_pos = self._find_clean_boundary(text, char_start, char_end)
306 if boundary_pos is not None and boundary_pos > 0:
307 boundaries.append(boundary_pos)
308
309 return boundaries
310
311 except Exception as e:
312 print(f"Warning: Error in single window processing: {e}")
313 return []
314
315 def _find_clean_boundary(self, text: str, char_start: int, char_end: int) -> Optional[int]:
316 """Find a clean boundary near the predicted position, prioritizing sentence starts"""
317
318 # Ensure positions are within text bounds
319 char_start = max(0, min(char_start, len(text) - 1))
320 char_end = max(0, min(char_end, len(text)))
321
322 # Search range around the token
323 search_start = max(0, char_start - SEARCH_RANGE)
324 search_end = min(len(text), char_end + SEARCH_RANGE)
325
326 # Priority 1: Look for sentence endings followed by capital letters (forward search)
327 for i in range(char_start, search_end):
328 if i < len(text) and text[i] in '.!?':
329 # Look for the start of next sentence
330 boundary = i + 1
331 # Skip whitespace
332 while boundary < len(text) and text[boundary] in ' \t\n':
333 boundary += 1
334 # Check if next character is uppercase (start of sentence)
335 if boundary < len(text) and (text[boundary].isupper() or text[boundary].isdigit()):
336 return boundary
337
338 # Priority 2: Search backwards for sentence endings followed by capitals
339 for i in range(char_start - 1, search_start - 1, -1):
340 if i >= 0 and text[i] in '.!?':
341 boundary = i + 1
342 # Skip whitespace
343 while boundary < len(text) and text[boundary] in ' \t\n':
344 boundary += 1
345 # Check if next character is uppercase (start of sentence)
346 if boundary < len(text) and (text[boundary].isupper() or text[boundary].isdigit()):
347 return boundary
348
349 # Priority 3: Word boundaries (spaces) only if followed by capital
350 for i in range(char_start, search_end):
351 if i < len(text) and text[i] in ' \t':
352 boundary = i + 1
353 while boundary < len(text) and text[boundary] in ' \t':
354 boundary += 1
355 # Only use if followed by capital letter
356 if boundary < len(text) and text[boundary].isupper():
357 return boundary
358
359 # Fallback: use the end of the token
360 fallback_pos = char_end if char_end <= len(text) else len(text)
361 return fallback_pos
362
363 def _split_at_boundaries(self, text: str, boundaries: List[int]) -> List[str]:
364 """Split text at boundaries ensuring no gaps or overlaps and proper sentence starts"""
365 if not boundaries:
366 return [text]
367
368 chunks = []
369 start = 0
370
371 for boundary in boundaries:
372 # Ensure boundary is within text
373 boundary = min(boundary, len(text))
374
375 if start < boundary:
376 chunk = text[start:boundary].strip()
377 if chunk: # Only add non-empty chunks
378 chunks.append(chunk)
379 start = boundary
380
381 # Add remaining text
382 if start < len(text):
383 remaining = text[start:].strip()
384 if remaining:
385 chunks.append(remaining)
386
387 return chunks
388
389 def _optimize_chunk_sizes(self, chunks: List[str], max_tokens: int, target_tokens: int) -> List[str]:
390 """Fully dynamic chunk size optimization"""
391 if not chunks:
392 return []
393
394 # Calculate dynamic thresholds based on target
395 good_size_min = target_tokens * GOOD_SIZE_MIN_RATIO
396 good_size_max = max_tokens # Use actual max
397 merge_threshold = target_tokens * MERGE_THRESHOLD_RATIO
398
399 optimized = []
400 i = 0
401
402 while i < len(chunks):
403 current_chunk = chunks[i]
404 current_tokens = self._count_tokens(current_chunk)
405
406 # If chunk is in good size range, keep it
407 if good_size_min <= current_tokens <= good_size_max:
408 optimized.append(current_chunk)
409 i += 1
410 continue
411
412 # If chunk is too large, split it
413 if current_tokens > good_size_max:
414 split_chunks = self._split_large_chunk_dynamic(
415 current_chunk, max_tokens, target_tokens
416 )
417 optimized.extend(split_chunks)
418 i += 1
419 continue
420
421 # If chunk is too small, try to merge with next chunks
422 if current_tokens < merge_threshold:
423 merged_chunk, chunks_consumed = self._merge_small_chunks_dynamic(
424 chunks[i:], max_tokens, target_tokens
425 )
426 optimized.append(merged_chunk)
427 i += chunks_consumed
428 continue
429
430 # Default: keep the chunk (it's in acceptable range)
431 optimized.append(current_chunk)
432 i += 1
433
434 return optimized
435
436 def _count_tokens(self, text: str) -> int:
437 """Count tokens in text"""
438 if not text.strip():
439 return 0
440 encoding = self.tokenizer(text, add_special_tokens=False)
441 return len(encoding['input_ids'])
442
443 def _merge_small_chunks_dynamic(self, chunks: List[str], max_tokens: int, target_tokens: int) -> Tuple[str, int]:
444 """Dynamic merging based on target size"""
445 if not chunks:
446 return "", 0
447
448 merged = chunks[0]
449 consumed = 1
450 merged_tokens = self._count_tokens(merged)
451
452 # Dynamic target: aim for target_tokens but stop before max_tokens
453 optimal_target = min(target_tokens, max_tokens * OPTIMAL_TARGET_RATIO)
454
455 # Try to merge with following chunks
456 for i in range(1, len(chunks)):
457 candidate = merged + " " + chunks[i]
458 candidate_tokens = self._count_tokens(candidate)
459
460 # Stop if we exceed max tokens
461 if candidate_tokens > max_tokens:
462 break
463
464 # Merge and continue
465 merged = candidate
466 merged_tokens = candidate_tokens
467 consumed += 1
468
469 # Stop if we reached optimal target
470 if merged_tokens >= optimal_target:
471 break
472
473 return merged, consumed
474
475 def _split_large_chunk_dynamic(self, chunk: str, max_tokens: int, target_tokens: int) -> List[str]:
476 """Dynamic splitting based on target size"""
477 result = []
478 remaining = chunk
479
480 while remaining:
481 current_tokens = self._count_tokens(remaining)
482
483 # If remaining fits in max size, add it
484 if current_tokens <= max_tokens:
485 result.append(remaining)
486 break
487
488 # Find optimal split point (prefer target_tokens, but respect max_tokens)
489 optimal_split = min(target_tokens, max_tokens * OPTIMAL_TARGET_RATIO)
490 split_pos = self._find_optimal_split_position(remaining, optimal_split, max_tokens)
491
492 if split_pos > 0 and split_pos < len(remaining):
493 chunk_part = remaining[:split_pos].strip()
494 if chunk_part:
495 result.append(chunk_part)
496 remaining = remaining[split_pos:].strip()
497 else:
498 # Fallback: force split at max_tokens
499 split_pos = self._find_token_split_position(remaining, max_tokens)
500 if split_pos > 0:
501 chunk_part = remaining[:split_pos].strip()
502 if chunk_part:
503 result.append(chunk_part)
504 remaining = remaining[split_pos:].strip()
505 else:
506 # Last resort: take the remaining text
507 result.append(remaining)
508 break
509
510 return [r for r in result if r.strip()]
511
512 def _find_optimal_split_position(self, text: str, target_tokens: int, max_tokens: int) -> int:
513 """Find optimal split position aiming for target_tokens but not exceeding max_tokens"""
514 # Binary search for position closest to target_tokens
515 left, right = 0, len(text)
516 best_pos = 0
517 best_tokens = 0
518
519 while left <= right:
520 mid = (left + right) // 2
521 test_text = text[:mid]
522
523 if not test_text.strip():
524 left = mid + 1
525 continue
526
527 tokens = self._count_tokens(test_text)
528
529 if tokens <= max_tokens:
530 # This position is valid, check if it's closer to target
531 if abs(tokens - target_tokens) < abs(best_tokens - target_tokens) or best_tokens == 0:
532 best_pos = mid
533 best_tokens = tokens
534
535 if tokens < target_tokens:
536 left = mid + 1 # Try to get closer to target
537 else:
538 break # We've reached or exceeded target
539 else:
540 right = mid - 1
541
542 # Refine to find clean boundary
543 if best_pos > 0:
544 clean_pos = self._find_char_split_position(text, best_pos)
545 return clean_pos
546
547 return best_pos
548
549 def _find_token_split_position(self, text: str, max_tokens: int) -> int:
550 """Find a good position to split text within token limit"""
551 # Binary search approach for accurate token-based splitting
552 left, right = 0, len(text)
553 best_pos = 0
554
555 while left <= right:
556 mid = (left + right) // 2
557
558 # Test if text[:mid] fits within token limit
559 test_text = text[:mid]
560 if not test_text.strip():
561 left = mid + 1
562 continue
563
564 encoding = self.tokenizer(test_text, add_special_tokens=False)
565 token_count = len(encoding['input_ids'])
566
567 if token_count <= max_tokens:
568 best_pos = mid
569 left = mid + 1
570 else:
571 right = mid - 1
572
573 return best_pos
574
575 def _find_char_split_position(self, text: str, max_chars: int) -> int:
576 """Find a good position to split text within character limit"""
577 if max_chars >= len(text):
578 return len(text)
579
580 # Look for sentence endings before the limit
581 search_start = max(0, max_chars - SENTENCE_SEARCH_RANGE)
582 for i in range(min(max_chars, len(text)) - 1, search_start, -1):
583 if i < len(text) and text[i] in '.!?':
584 # Skip forward past any whitespace
585 boundary = i + 1
586 while boundary < len(text) and text[boundary] in ' \t\n':
587 boundary += 1
588 return min(boundary, len(text))
589
590 # Look for spaces before the limit
591 search_start = max(0, max_chars - SPACE_SEARCH_RANGE)
592 for i in range(min(max_chars, len(text)) - 1, search_start, -1):
593 if i < len(text) and text[i] in ' \t':
594 return i + 1
595
596 # Fallback: split at the limit
597 return min(max_chars, len(text))
598
599 def _apply_char_limits(self, chunks: List[str], max_chars: int) -> List[str]:
600 """Apply character limits to optimized chunks"""
601 result = []
602
603 def _apply_char_limits(self, chunks: List[str], max_chars: int) -> List[str]:
604 """Apply character limits to optimized chunks"""
605 result = []
606
607 for chunk in chunks:
608 if len(chunk) <= max_chars:
609 result.append(chunk)
610 else:
611 # Split by character limit
612 sub_chunks = self._split_by_chars(chunk, max_chars)
613 result.extend(sub_chunks)
614
615 return result
616
617 def _split_by_chars(self, chunk: str, max_chars: int) -> List[str]:
618 """Split chunk by character limit only"""
619 result = []
620 remaining = chunk
621
622 while remaining:
623 if len(remaining) <= max_chars:
624 result.append(remaining)
625 break
626
627 # Find a good split point within char limit
628 split_pos = self._find_char_split_position(remaining, max_chars)
629
630 if split_pos > 0 and split_pos < len(remaining):
631 result.append(remaining[:split_pos].strip())
632 remaining = remaining[split_pos:].strip()
633 else:
634 # Fallback: force split at limit
635 result.append(remaining[:max_chars].strip())
636 remaining = remaining[max_chars:].strip()
637
638 return [r for r in result if r.strip()]
639
640 def _clean_and_validate_chunks(self, chunks: List[str], target_tokens: int, max_tokens: int) -> List[str]:
641 """Dynamic cleaning and validation - fully adaptive to target size"""
642
643 if not chunks:
644 return []
645
646 # Dynamic thresholds based on target
647 dynamic_min_length = max(10, target_tokens // MIN_LENGTH_DIVISOR)
648 merge_threshold = target_tokens * MERGE_ATTEMPT_RATIO
649 optimal_max = max_tokens * OPTIMAL_MAX_RATIO
650
651 # Remove very small chunks first
652 cleaned = [chunk.strip() for chunk in chunks if len(chunk.strip()) >= dynamic_min_length]
653
654 if not cleaned:
655 return chunks # Return original if all chunks are removed
656
657 # Dynamic merging for better chunk sizes
658 final = []
659 i = 0
660
661 while i < len(cleaned):
662 current = cleaned[i]
663 current_tokens = self._count_tokens(current)
664
665 # If current chunk is small, try to merge
666 if current_tokens < merge_threshold:
667 merged_successfully = False
668
669 # Try to merge with previous chunk if it exists and is not too large
670 if final:
671 prev_tokens = self._count_tokens(final[-1])
672 combined = final[-1] + " " + current
673 combined_tokens = self._count_tokens(combined)
674
675 if combined_tokens <= optimal_max and combined_tokens <= target_tokens * MAX_MERGE_RATIO:
676 final[-1] = combined
677 merged_successfully = True
678
679 # If couldn't merge with previous, try to merge with next chunks
680 if not merged_successfully and i + 1 < len(cleaned):
681 merged_chunk, chunks_consumed = self._merge_small_chunks_dynamic(
682 cleaned[i:], max_tokens, target_tokens
683 )
684 final.append(merged_chunk)
685 i += chunks_consumed
686 continue
687
688 # If still couldn't merge, add as is (unless it's too small)
689 if not merged_successfully:
690 min_acceptable = target_tokens * MIN_ACCEPTABLE_RATIO
691 if current_tokens >= min_acceptable:
692 final.append(current)
693 elif final: # Try one more time to merge with previous
694 prev_tokens = self._count_tokens(final[-1])
695 combined = final[-1] + " " + current
696 combined_tokens = self._count_tokens(combined)
697 if combined_tokens <= optimal_max:
698 final[-1] = combined
699 else:
700 final.append(current) # Keep as separate chunk
701 else:
702 final.append(current) # First chunk, keep it
703 else:
704 final.append(current)
705
706 i += 1
707
708 return final
709
710
711def chunk_azerbaijani_text(text: str,
712 model_path: str = MODEL_PATH,
713 max_chunk_tokens: Optional[int] = MAX_CHUNK_TOKENS,
714 target_tokens: Optional[int] = TARGET_TOKENS,
715 max_chunk_chars: Optional[int] = MAX_CHUNK_CHARS,
716 threshold: float = THRESHOLD,
717 priority: str = PRIORITY) -> List[str]:
718 """
719 Simplified function to chunk Azerbaijani text with single parameter configuration
720
721 Args:
722 text: Input text to chunk
723 model_path: HuggingFace model path
724 max_chunk_tokens: Maximum tokens per chunk
725 target_tokens: Target optimal size in tokens
726 max_chunk_chars: Maximum characters per chunk
727 threshold: Confidence threshold for splitting
728 priority: Which limit to prioritize when both are set
729
730 Returns:
731 List of optimally-sized text chunks
732 """
733 chunker = AzerbaijaniTextChunker(model_path)
734 return chunker.chunk(text, max_chunk_tokens, target_tokens, max_chunk_chars, threshold, priority)
735
736
737def main():
738 """Main function demonstrating the simplified chunker"""
739
740 print("=== SIMPLIFIED AZERBAIJANI TEXT CHUNKER ===\n")
741
742 # Show current configuration
743 print("CURRENT CONFIGURATION:")
744 print(f"Model path: {MODEL_PATH}")
745 print(f"Max tokens: {MAX_CHUNK_TOKENS}")
746 print(f"Target tokens: {TARGET_TOKENS}")
747 print(f"Max chars: {MAX_CHUNK_CHARS}")
748 print(f"Threshold: {THRESHOLD}")
749 print(f"Priority: {PRIORITY}")
750 print("="*60 + "\n")
751
752 # Basic chunking with global settings
753 print(f"Chunking with global settings (target ~{TARGET_TOKENS} tokens, max {MAX_CHUNK_TOKENS}):")
754 chunks = chunk_azerbaijani_text(SAMPLE_TEXT)
755
756 total_tokens = 0
757 text_preview_main = 80 # Preview length for main chunks
758
759 for i, chunk in enumerate(chunks, 1):
760 try:
761 from transformers import AutoTokenizer
762 tokenizer = AutoTokenizer.from_pretrained('xlm-roberta-base')
763 tokens = tokenizer(chunk, add_special_tokens=False)['input_ids']
764 token_count = len(tokens)
765 total_tokens += token_count
766 except:
767 token_count = "N/A"
768
769 print(f"{i}. [{len(chunk)} chars, {token_count} tokens]")
770 print(f" {chunk[:text_preview_main]}{'...' if len(chunk) > text_preview_main else ''}\n")
771
772 print(f"Total chunks: {len(chunks)}")
773 if total_tokens > 0:
774 print(f"Total tokens: {total_tokens}")
775 print(f"Average tokens per chunk: {total_tokens/len(chunks):.1f}")
776
777 print("="*60 + "\n")
778
779
780
781# Example usage
782if __name__ == "__main__":
783 main()