Azerbaijani Semantic Text Chunker

Advanced semantic text chunking model for Azerbaijani language based on XLM-RoBERTa

This model specifically trained for Azerbaijani texts, featuring semantic understanding, strict length control, and sliding window processing for documents of any size.

Features

  • Semantic Chunking: Understands context and meaning, not just sentence boundaries
  • Azerbaijani Specialized: Native support for Azerbaijani language nuances
  • Strict Length Control: Enforces maximum chunk size in tokens and/or characters
  • Sliding Window Processing: Handles documents of unlimited length (2K, 10K, 50K+ tokens)
  • Multiple Strategies: Optimal, conservative, and aggressive chunking modes
  • Flexible Limits: Supports token limits, character limits, or both combined
  • Detailed Analytics: Comprehensive chunking process diagnostics
  • RAG-Ready: Perfect for vector databases and retrieval systems

Performance

Our model achieves excellent results on Azerbaijani text segmentation:

Metric Score
Precision 0.7850
Recall 0.6485
F1-Score 0.7102
Accuracy 0.9980

Technical Architecture

Sliding Window Processing

The model uses an advanced sliding window approach to handle texts longer than the 512-token context window:

Text Length: 2048+ tokens Window Size: 510 tokens (512 - 2 special tokens) Stride: 255 tokens (50% overlap)

Window 1: tokens 0-510 Window 2: tokens 255-765 (255 token overlap) Window 3: tokens 510-1020 (255 token overlap) Window 4: tokens 765-1275 (255 token overlap) Window 5: tokens 1020-1530 (255 token overlap) Window 6: tokens 1275-1785 (255 token overlap) Window 7: tokens 1530-2040 (255 token overlap) Window 8: tokens 1785-2048 (final window)

Key advantages:

  • No length limitations: Process documents of any size
  • Intelligent overlap: Ensures no semantic boundaries are missed
  • Prediction fusion: Combines results using maximum confidence scores
  • Linear complexity: O(N) processing time regardless of document length

Length Control Algorithm

  • Strict enforcement: Never exceeds specified token/character limits
  • Smart boundary detection: Prefers natural separators (spaces, punctuation)
  • Fallback mechanisms: Intelligent splitting when no semantic boundaries found
  • Combined limits: Supports both token AND character limits simultaneously

Use Cases

Perfect for RAG Systems

  • Vector Databases: Ensure chunks fit embedding model limits
  • Search Applications: Optimal chunk sizes for retrieval
  • Question Answering: Maintain semantic coherence

Document Processing

  • Academic Papers: Respect section and paragraph boundaries
  • Legal Documents: Maintain clause integrity
  • News Articles: Preserve story flow and context

Content Management

  • CMS Integration: Automatic content segmentation
  • API Limits: Respect external service constraints
  • Storage Optimization: Consistent chunk sizes for databases

Quick Start

Installation

# Install requirements
pip install torch transformers numpy pandas

Basic Usage

import torch
import torch.nn.functional as F
from transformers import AutoTokenizer, AutoModelForTokenClassification
import numpy as np
from typing import List, Optional, Tuple

# Global priority constants
PRIORITY_TOKENS = "tokens"
PRIORITY_CHARS = "chars" 
PRIORITY_STRICT = "strict"

# Global configuration variables for easy modification
MODEL_PATH = "LocalDoc/semantic_chunker"
MAX_CHUNK_TOKENS = 400  # Single parameter for chunk size
TARGET_TOKENS = 256      # Single parameter for target size
MAX_CHUNK_CHARS = None  # No character limit by default
THRESHOLD = 0.12
PRIORITY = PRIORITY_STRICT

# Dynamic calculation constants (avoid hardcoding)
MIN_LENGTH_DIVISOR = 8      # target_tokens // MIN_LENGTH_DIVISOR for min chunk length
MARGIN_RATIO = 0.25         # 25% margin for max_tokens validation
MIN_MARGIN = 20             # Minimum margin when validating max_tokens
ROUGH_TOKEN_MULTIPLIER = 1.3  # Rough token estimation multiplier
LONG_TEXT_THRESHOLD = 400   # Threshold for long text processing
BERT_MAX_LENGTH = 512       # Standard BERT model limit
SEARCH_RANGE = 50           # Character search range for boundaries
SENTENCE_SEARCH_RANGE = 100 # Range for sentence boundary search
SPACE_SEARCH_RANGE = 50     # Range for space boundary search
OPTIMAL_TARGET_RATIO = 0.9  # 90% of max as safety margin for merging
GOOD_SIZE_MIN_RATIO = 0.7   # 70% of target for minimum good size
MERGE_THRESHOLD_RATIO = 0.5 # 50% of target for merge threshold
MERGE_ATTEMPT_RATIO = 0.6   # 60% of target for merge attempts
OPTIMAL_MAX_RATIO = 1.1     # 110% of max for slight overflow
MIN_ACCEPTABLE_RATIO = 0.3  # 30% of target for minimum acceptable size
MAX_MERGE_RATIO = 1.5       # 150% of target for maximum merge size

# Sample text for testing
#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]"""
SAMPLE_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."""

class AzerbaijaniTextChunker:
    """Optimized Azerbaijani text chunker with single parameter configuration"""
    
    def __init__(self, model_path: str = MODEL_PATH):
        self.model_path = model_path
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        self.tokenizer = None
        self.model = None
        self.is_loaded = False
    
    def load_model(self) -> bool:
        """Load the trained model"""
        try:
            self.tokenizer = AutoTokenizer.from_pretrained(
                self.model_path, use_fast=True, trust_remote_code=False
            )
            self.model = AutoModelForTokenClassification.from_pretrained(
                self.model_path, trust_remote_code=False
            )
            self.model.to(self.device)
            self.model.eval()
            self.is_loaded = True
            return True
        except Exception as e:
            print(f"Error loading model: {e}")
            return False
    
    def chunk(self, 
              text: str,
              max_chunk_tokens: Optional[int] = None,
              target_tokens: Optional[int] = None,
              max_chunk_chars: Optional[int] = None,
              threshold: float = None,
              priority: str = None) -> List[str]:
        """
        Chunk text into semantic segments with optimal sizing
        
        Args:
            text: Input text to chunk
            max_chunk_tokens: Maximum tokens per chunk (default: from global config)
            target_tokens: Target optimal size in tokens (default: from global config)
            max_chunk_chars: Maximum characters per chunk (default: from global config)  
            threshold: Confidence threshold for splitting (default: from global config)
            priority: Which limit to prioritize when both are set (default: from global config)
            
        Returns:
            List of optimally-sized text chunks
        """
        if not self.is_loaded:
            if not self.load_model():
                return [text]
        
        # Use global configuration as defaults
        if max_chunk_tokens is None:
            max_chunk_tokens = MAX_CHUNK_TOKENS
        if target_tokens is None:
            target_tokens = TARGET_TOKENS
        if max_chunk_chars is None:
            max_chunk_chars = MAX_CHUNK_CHARS
        if threshold is None:
            threshold = THRESHOLD
        if priority is None:
            priority = PRIORITY
        
        # Dynamic validation and minimum length calculation
        dynamic_min_length = max(10, target_tokens // MIN_LENGTH_DIVISOR)
        
        if not text or len(text.strip()) < dynamic_min_length:
            return [text.strip()] if text.strip() else []
        
        text = text.strip()
        
        # Ensure max >= target with reasonable margin
        if max_chunk_tokens < target_tokens:
            margin = max(target_tokens * MARGIN_RATIO, MIN_MARGIN)
            max_chunk_tokens = target_tokens + int(margin)
        
        # Step 1: Get all semantic boundaries from the model
        semantic_boundaries = self._get_semantic_boundaries_fixed(text, threshold)
        
        # Step 2: Split text at semantic boundaries (no limits yet)
        initial_chunks = self._split_at_boundaries(text, semantic_boundaries)
        
        # Step 3: Optimize chunk sizes with dynamic parameters
        optimized_chunks = self._optimize_chunk_sizes(
            initial_chunks, max_chunk_tokens, target_tokens
        )
        
        # Step 4: Apply additional limits if specified
        if max_chunk_chars is not None:
            final_chunks = self._apply_char_limits(optimized_chunks, max_chunk_chars)
        else:
            final_chunks = optimized_chunks
        
        return self._clean_and_validate_chunks(final_chunks, target_tokens, max_chunk_tokens)
    
    def _get_semantic_boundaries_fixed(self, text: str, threshold: float) -> List[int]:
        """Get all semantic boundaries without sequence length warnings"""
        
        # Check text length first and use chunked approach if needed
        rough_token_count = len(text.split()) * ROUGH_TOKEN_MULTIPLIER
        
        if rough_token_count > LONG_TEXT_THRESHOLD:
            return self._get_boundaries_for_long_text(text, threshold)
        
        # For shorter texts, use standard approach with truncation
        full_encoding = self.tokenizer(
            text, 
            return_tensors="pt", 
            return_offsets_mapping=True,
            add_special_tokens=True,
            truncation=True,  # Enable truncation to avoid warnings
            max_length=BERT_MAX_LENGTH,   # Standard BERT limit
            padding=False
        )
        
        input_ids = full_encoding['input_ids'][0]
        offset_mapping = full_encoding['offset_mapping'][0]
        
        # Remove special tokens (CLS at start, SEP at end)
        content_input_ids = input_ids[1:-1]
        content_offsets = offset_mapping[1:-1]
        
        if len(content_input_ids) == 0:
            return []
        
        # Process with single window since we truncated
        boundaries = self._process_single_window_safe(
            text, input_ids, content_offsets, threshold
        )
        
        # Remove duplicates and sort
        boundaries = sorted(list(set(boundaries)))
        
        # Remove boundaries at text start/end
        boundaries = [b for b in boundaries if 0 < b < len(text)]
        
        return boundaries
    
    def _get_boundaries_for_long_text(self, text: str, threshold: float) -> List[int]:
        """Handle long texts by processing in semantic chunks"""
        
        # Split text into rough sections first (by sentences)
        import re
        sentences = re.split(r'[.!?]+', text)
        
        all_boundaries = []
        current_pos = 0
        
        # Process each section that fits in model limits
        current_section = ""
        
        for sentence in sentences:
            test_section = current_section + sentence + "."
            rough_tokens = len(test_section.split()) * ROUGH_TOKEN_MULTIPLIER
            
            if rough_tokens > LONG_TEXT_THRESHOLD and current_section:
                # Process current section
                section_boundaries = self._process_text_section(
                    current_section, current_pos, threshold
                )
                all_boundaries.extend(section_boundaries)
                
                # Start new section
                current_pos += len(current_section)
                current_section = sentence + "."
            else:
                current_section = test_section
        
        # Process remaining section
        if current_section:
            section_boundaries = self._process_text_section(
                current_section, current_pos, threshold
            )
            all_boundaries.extend(section_boundaries)
        
        return sorted(list(set(all_boundaries)))
    
    def _process_text_section(self, section: str, offset: int, threshold: float) -> List[int]:
        """Process a section of text that fits in model limits"""
        
        try:
            # Tokenize section (should be safe now)
            encoding = self.tokenizer(
                section,
                return_tensors="pt",
                return_offsets_mapping=True,
                add_special_tokens=True,
                truncation=True,
                max_length=BERT_MAX_LENGTH
            )
            
            input_ids = encoding['input_ids'][0]
            offset_mapping = encoding['offset_mapping'][0]
            
            # Remove special tokens
            content_offsets = offset_mapping[1:-1]
            
            # Get model predictions
            with torch.no_grad():
                outputs = self.model(encoding['input_ids'].to(self.device))
                probabilities = F.softmax(outputs.logits, dim=-1)
                
                # Extract B-CHUNK probabilities
                b_chunk_id = self.model.config.label2id.get("B-CHUNK", 1)
                chunk_probs = probabilities[0, 1:-1, b_chunk_id].cpu().numpy()
            
            # Find boundaries
            boundaries = []
            for i, (prob, char_offset) in enumerate(zip(chunk_probs, content_offsets)):
                if prob > threshold:
                    char_start = char_offset[0].item()
                    char_end = char_offset[1].item()
                    
                    # Find clean boundary in full text
                    boundary_pos = self._find_clean_boundary_global(
                        section, char_start, char_end, offset
                    )
                    if boundary_pos is not None and boundary_pos > offset:
                        boundaries.append(boundary_pos)
            
            return boundaries
            
        except Exception as e:
            print(f"Warning: Error processing section: {e}")
            return []
    
    def _find_clean_boundary_global(self, section: str, char_start: int, 
                                   char_end: int, global_offset: int) -> Optional[int]:
        """Find clean boundary and return global position"""
        
        local_boundary = self._find_clean_boundary(section, char_start, char_end)
        if local_boundary is not None:
            global_pos = global_offset + local_boundary
            
            # Additional validation: ensure we don't cut words in half
            if global_pos > 0 and global_pos < len(section) + global_offset:
                return global_pos
        return None
    
    def _process_single_window_safe(self, text: str, input_ids: torch.Tensor, 
                                   content_offsets: torch.Tensor, threshold: float) -> List[int]:
        """Process text that fits in a single window safely"""
        
        try:
            # Get model prediction
            with torch.no_grad():
                input_ids_batch = input_ids.unsqueeze(0).to(self.device)
                outputs = self.model(input_ids_batch)
                probabilities = F.softmax(outputs.logits, dim=-1)
                
                # Extract B-CHUNK probabilities for content tokens only
                b_chunk_id = self.model.config.label2id.get("B-CHUNK", 1)
                chunk_probs = probabilities[0, 1:-1, b_chunk_id].cpu().numpy()
            
            # Find boundaries
            boundaries = []
            for i, (prob, offset) in enumerate(zip(chunk_probs, content_offsets)):
                if prob > threshold:
                    char_start = offset[0].item()
                    char_end = offset[1].item()
                    
                    # Find clean boundary position
                    boundary_pos = self._find_clean_boundary(text, char_start, char_end)
                    if boundary_pos is not None and boundary_pos > 0:
                        boundaries.append(boundary_pos)
            
            return boundaries
            
        except Exception as e:
            print(f"Warning: Error in single window processing: {e}")
            return []
    
    def _find_clean_boundary(self, text: str, char_start: int, char_end: int) -> Optional[int]:
        """Find a clean boundary near the predicted position, prioritizing sentence starts"""
        
        # Ensure positions are within text bounds
        char_start = max(0, min(char_start, len(text) - 1))
        char_end = max(0, min(char_end, len(text)))
        
        # Search range around the token
        search_start = max(0, char_start - SEARCH_RANGE)
        search_end = min(len(text), char_end + SEARCH_RANGE)
        
        # Priority 1: Look for sentence endings followed by capital letters (forward search)
        for i in range(char_start, search_end):
            if i < len(text) and text[i] in '.!?':
                # Look for the start of next sentence
                boundary = i + 1
                # Skip whitespace
                while boundary < len(text) and text[boundary] in ' \t\n':
                    boundary += 1
                # Check if next character is uppercase (start of sentence)
                if boundary < len(text) and (text[boundary].isupper() or text[boundary].isdigit()):
                    return boundary
        
        # Priority 2: Search backwards for sentence endings followed by capitals
        for i in range(char_start - 1, search_start - 1, -1):
            if i >= 0 and text[i] in '.!?':
                boundary = i + 1
                # Skip whitespace
                while boundary < len(text) and text[boundary] in ' \t\n':
                    boundary += 1
                # Check if next character is uppercase (start of sentence)
                if boundary < len(text) and (text[boundary].isupper() or text[boundary].isdigit()):
                    return boundary
        
        # Priority 3: Word boundaries (spaces) only if followed by capital
        for i in range(char_start, search_end):
            if i < len(text) and text[i] in ' \t':
                boundary = i + 1
                while boundary < len(text) and text[boundary] in ' \t':
                    boundary += 1
                # Only use if followed by capital letter
                if boundary < len(text) and text[boundary].isupper():
                    return boundary
        
        # Fallback: use the end of the token
        fallback_pos = char_end if char_end <= len(text) else len(text)
        return fallback_pos
    
    def _split_at_boundaries(self, text: str, boundaries: List[int]) -> List[str]:
        """Split text at boundaries ensuring no gaps or overlaps and proper sentence starts"""
        if not boundaries:
            return [text]
        
        chunks = []
        start = 0
        
        for boundary in boundaries:
            # Ensure boundary is within text
            boundary = min(boundary, len(text))
            
            if start < boundary:
                chunk = text[start:boundary].strip()
                if chunk:  # Only add non-empty chunks
                    chunks.append(chunk)
            start = boundary
        
        # Add remaining text
        if start < len(text):
            remaining = text[start:].strip()
            if remaining:
                chunks.append(remaining)
        
        return chunks
    
    def _optimize_chunk_sizes(self, chunks: List[str], max_tokens: int, target_tokens: int) -> List[str]:
        """Fully dynamic chunk size optimization"""
        if not chunks:
            return []
        
        # Calculate dynamic thresholds based on target
        good_size_min = target_tokens * GOOD_SIZE_MIN_RATIO
        good_size_max = max_tokens  # Use actual max
        merge_threshold = target_tokens * MERGE_THRESHOLD_RATIO
        
        optimized = []
        i = 0
        
        while i < len(chunks):
            current_chunk = chunks[i]
            current_tokens = self._count_tokens(current_chunk)
            
            # If chunk is in good size range, keep it
            if good_size_min <= current_tokens <= good_size_max:
                optimized.append(current_chunk)
                i += 1
                continue
            
            # If chunk is too large, split it
            if current_tokens > good_size_max:
                split_chunks = self._split_large_chunk_dynamic(
                    current_chunk, max_tokens, target_tokens
                )
                optimized.extend(split_chunks)
                i += 1
                continue
            
            # If chunk is too small, try to merge with next chunks
            if current_tokens < merge_threshold:
                merged_chunk, chunks_consumed = self._merge_small_chunks_dynamic(
                    chunks[i:], max_tokens, target_tokens
                )
                optimized.append(merged_chunk)
                i += chunks_consumed
                continue
            
            # Default: keep the chunk (it's in acceptable range)
            optimized.append(current_chunk)
            i += 1
        
        return optimized
    
    def _count_tokens(self, text: str) -> int:
        """Count tokens in text"""
        if not text.strip():
            return 0
        encoding = self.tokenizer(text, add_special_tokens=False)
        return len(encoding['input_ids'])
    
    def _merge_small_chunks_dynamic(self, chunks: List[str], max_tokens: int, target_tokens: int) -> Tuple[str, int]:
        """Dynamic merging based on target size"""
        if not chunks:
            return "", 0
        
        merged = chunks[0]
        consumed = 1
        merged_tokens = self._count_tokens(merged)
        
        # Dynamic target: aim for target_tokens but stop before max_tokens
        optimal_target = min(target_tokens, max_tokens * OPTIMAL_TARGET_RATIO)
        
        # Try to merge with following chunks
        for i in range(1, len(chunks)):
            candidate = merged + " " + chunks[i]
            candidate_tokens = self._count_tokens(candidate)
            
            # Stop if we exceed max tokens
            if candidate_tokens > max_tokens:
                break
            
            # Merge and continue
            merged = candidate
            merged_tokens = candidate_tokens
            consumed += 1
            
            # Stop if we reached optimal target
            if merged_tokens >= optimal_target:
                break
        
        return merged, consumed
    
    def _split_large_chunk_dynamic(self, chunk: str, max_tokens: int, target_tokens: int) -> List[str]:
        """Dynamic splitting based on target size"""
        result = []
        remaining = chunk
        
        while remaining:
            current_tokens = self._count_tokens(remaining)
            
            # If remaining fits in max size, add it
            if current_tokens <= max_tokens:
                result.append(remaining)
                break
            
            # Find optimal split point (prefer target_tokens, but respect max_tokens)
            optimal_split = min(target_tokens, max_tokens * OPTIMAL_TARGET_RATIO)
            split_pos = self._find_optimal_split_position(remaining, optimal_split, max_tokens)
            
            if split_pos > 0 and split_pos < len(remaining):
                chunk_part = remaining[:split_pos].strip()
                if chunk_part:
                    result.append(chunk_part)
                remaining = remaining[split_pos:].strip()
            else:
                # Fallback: force split at max_tokens
                split_pos = self._find_token_split_position(remaining, max_tokens)
                if split_pos > 0:
                    chunk_part = remaining[:split_pos].strip()
                    if chunk_part:
                        result.append(chunk_part)
                    remaining = remaining[split_pos:].strip()
                else:
                    # Last resort: take the remaining text
                    result.append(remaining)
                    break
        
        return [r for r in result if r.strip()]
    
    def _find_optimal_split_position(self, text: str, target_tokens: int, max_tokens: int) -> int:
        """Find optimal split position aiming for target_tokens but not exceeding max_tokens"""
        # Binary search for position closest to target_tokens
        left, right = 0, len(text)
        best_pos = 0
        best_tokens = 0
        
        while left <= right:
            mid = (left + right) // 2
            test_text = text[:mid]
            
            if not test_text.strip():
                left = mid + 1
                continue
            
            tokens = self._count_tokens(test_text)
            
            if tokens <= max_tokens:
                # This position is valid, check if it's closer to target
                if abs(tokens - target_tokens) < abs(best_tokens - target_tokens) or best_tokens == 0:
                    best_pos = mid
                    best_tokens = tokens
                
                if tokens < target_tokens:
                    left = mid + 1  # Try to get closer to target
                else:
                    break  # We've reached or exceeded target
            else:
                right = mid - 1
        
        # Refine to find clean boundary
        if best_pos > 0:
            clean_pos = self._find_char_split_position(text, best_pos)
            return clean_pos
        
        return best_pos
    
    def _find_token_split_position(self, text: str, max_tokens: int) -> int:
        """Find a good position to split text within token limit"""
        # Binary search approach for accurate token-based splitting
        left, right = 0, len(text)
        best_pos = 0
        
        while left <= right:
            mid = (left + right) // 2
            
            # Test if text[:mid] fits within token limit
            test_text = text[:mid]
            if not test_text.strip():
                left = mid + 1
                continue
                
            encoding = self.tokenizer(test_text, add_special_tokens=False)
            token_count = len(encoding['input_ids'])
            
            if token_count <= max_tokens:
                best_pos = mid
                left = mid + 1
            else:
                right = mid - 1
        
        return best_pos
    
    def _find_char_split_position(self, text: str, max_chars: int) -> int:
        """Find a good position to split text within character limit"""
        if max_chars >= len(text):
            return len(text)
        
        # Look for sentence endings before the limit
        search_start = max(0, max_chars - SENTENCE_SEARCH_RANGE)
        for i in range(min(max_chars, len(text)) - 1, search_start, -1):
            if i < len(text) and text[i] in '.!?':
                # Skip forward past any whitespace
                boundary = i + 1
                while boundary < len(text) and text[boundary] in ' \t\n':
                    boundary += 1
                return min(boundary, len(text))
        
        # Look for spaces before the limit
        search_start = max(0, max_chars - SPACE_SEARCH_RANGE)
        for i in range(min(max_chars, len(text)) - 1, search_start, -1):
            if i < len(text) and text[i] in ' \t':
                return i + 1
        
        # Fallback: split at the limit
        return min(max_chars, len(text))
    
    def _apply_char_limits(self, chunks: List[str], max_chars: int) -> List[str]:
        """Apply character limits to optimized chunks"""
        result = []
        
    def _apply_char_limits(self, chunks: List[str], max_chars: int) -> List[str]:
        """Apply character limits to optimized chunks"""
        result = []
        
        for chunk in chunks:
            if len(chunk) <= max_chars:
                result.append(chunk)
            else:
                # Split by character limit
                sub_chunks = self._split_by_chars(chunk, max_chars)
                result.extend(sub_chunks)
        
        return result
    
    def _split_by_chars(self, chunk: str, max_chars: int) -> List[str]:
        """Split chunk by character limit only"""
        result = []
        remaining = chunk
        
        while remaining:
            if len(remaining) <= max_chars:
                result.append(remaining)
                break
            
            # Find a good split point within char limit
            split_pos = self._find_char_split_position(remaining, max_chars)
            
            if split_pos > 0 and split_pos < len(remaining):
                result.append(remaining[:split_pos].strip())
                remaining = remaining[split_pos:].strip()
            else:
                # Fallback: force split at limit
                result.append(remaining[:max_chars].strip())
                remaining = remaining[max_chars:].strip()
        
        return [r for r in result if r.strip()]
    
    def _clean_and_validate_chunks(self, chunks: List[str], target_tokens: int, max_tokens: int) -> List[str]:
        """Dynamic cleaning and validation - fully adaptive to target size"""
        
        if not chunks:
            return []
        
        # Dynamic thresholds based on target
        dynamic_min_length = max(10, target_tokens // MIN_LENGTH_DIVISOR)
        merge_threshold = target_tokens * MERGE_ATTEMPT_RATIO
        optimal_max = max_tokens * OPTIMAL_MAX_RATIO
        
        # Remove very small chunks first
        cleaned = [chunk.strip() for chunk in chunks if len(chunk.strip()) >= dynamic_min_length]
        
        if not cleaned:
            return chunks  # Return original if all chunks are removed
        
        # Dynamic merging for better chunk sizes
        final = []
        i = 0
        
        while i < len(cleaned):
            current = cleaned[i]
            current_tokens = self._count_tokens(current)
            
            # If current chunk is small, try to merge
            if current_tokens < merge_threshold:
                merged_successfully = False
                
                # Try to merge with previous chunk if it exists and is not too large
                if final:
                    prev_tokens = self._count_tokens(final[-1])
                    combined = final[-1] + " " + current
                    combined_tokens = self._count_tokens(combined)
                    
                    if combined_tokens <= optimal_max and combined_tokens <= target_tokens * MAX_MERGE_RATIO:
                        final[-1] = combined
                        merged_successfully = True
                
                # If couldn't merge with previous, try to merge with next chunks
                if not merged_successfully and i + 1 < len(cleaned):
                    merged_chunk, chunks_consumed = self._merge_small_chunks_dynamic(
                        cleaned[i:], max_tokens, target_tokens
                    )
                    final.append(merged_chunk)
                    i += chunks_consumed
                    continue
                
                # If still couldn't merge, add as is (unless it's too small)
                if not merged_successfully:
                    min_acceptable = target_tokens * MIN_ACCEPTABLE_RATIO
                    if current_tokens >= min_acceptable:
                        final.append(current)
                    elif final:  # Try one more time to merge with previous
                        prev_tokens = self._count_tokens(final[-1])
                        combined = final[-1] + " " + current
                        combined_tokens = self._count_tokens(combined)
                        if combined_tokens <= optimal_max:
                            final[-1] = combined
                        else:
                            final.append(current)  # Keep as separate chunk
                    else:
                        final.append(current)  # First chunk, keep it
            else:
                final.append(current)
            
            i += 1
        
        return final


def chunk_azerbaijani_text(text: str, 
                          model_path: str = MODEL_PATH,
                          max_chunk_tokens: Optional[int] = MAX_CHUNK_TOKENS,
                          target_tokens: Optional[int] = TARGET_TOKENS,
                          max_chunk_chars: Optional[int] = MAX_CHUNK_CHARS,
                          threshold: float = THRESHOLD,
                          priority: str = PRIORITY) -> List[str]:
    """
    Simplified function to chunk Azerbaijani text with single parameter configuration
    
    Args:
        text: Input text to chunk
        model_path: HuggingFace model path
        max_chunk_tokens: Maximum tokens per chunk
        target_tokens: Target optimal size in tokens
        max_chunk_chars: Maximum characters per chunk
        threshold: Confidence threshold for splitting
        priority: Which limit to prioritize when both are set
    
    Returns:
        List of optimally-sized text chunks
    """
    chunker = AzerbaijaniTextChunker(model_path)
    return chunker.chunk(text, max_chunk_tokens, target_tokens, max_chunk_chars, threshold, priority)


def main():
    """Main function demonstrating the simplified chunker"""
    
    print("=== SIMPLIFIED AZERBAIJANI TEXT CHUNKER ===\n")
    
    # Show current configuration
    print("CURRENT CONFIGURATION:")
    print(f"Model path: {MODEL_PATH}")
    print(f"Max tokens: {MAX_CHUNK_TOKENS}")
    print(f"Target tokens: {TARGET_TOKENS}")
    print(f"Max chars: {MAX_CHUNK_CHARS}")
    print(f"Threshold: {THRESHOLD}")
    print(f"Priority: {PRIORITY}")
    print("="*60 + "\n")
    
    # Basic chunking with global settings
    print(f"Chunking with global settings (target ~{TARGET_TOKENS} tokens, max {MAX_CHUNK_TOKENS}):")
    chunks = chunk_azerbaijani_text(SAMPLE_TEXT)
    
    total_tokens = 0
    text_preview_main = 80  # Preview length for main chunks
    
    for i, chunk in enumerate(chunks, 1):
        try:
            from transformers import AutoTokenizer
            tokenizer = AutoTokenizer.from_pretrained('xlm-roberta-base')
            tokens = tokenizer(chunk, add_special_tokens=False)['input_ids']
            token_count = len(tokens)
            total_tokens += token_count
        except:
            token_count = "N/A"
        
        print(f"{i}. [{len(chunk)} chars, {token_count} tokens]")
        print(f"   {chunk[:text_preview_main]}{'...' if len(chunk) > text_preview_main else ''}\n")
    
    print(f"Total chunks: {len(chunks)}")
    if total_tokens > 0:
        print(f"Total tokens: {total_tokens}")
        print(f"Average tokens per chunk: {total_tokens/len(chunks):.1f}")
    
    print("="*60 + "\n")



# Example usage
if __name__ == "__main__":
    main()
# Results

Chunking with global settings (target ~256 tokens, max 400):
1. [1247 chars, 288 tokens]
   Azərbaycan Respublikası, Qafqaz regionunda yerləşən, unikal coğrafi mövqeyi, zən...

2. [1007 chars, 247 tokens]
   Azərbaycanın təbiəti də onun mədəni zənginliyi qədər heyranedici və müxtəlifdir....

3. [1027 chars, 197 tokens]
   Rəqəmsal transformasiya, süni intellekt (AI), maşın təlimi (ML), böyük verilənlə...

4. [1128 chars, 222 tokens]
   Azərbaycan tarixi boyu bir çox mühüm hadisələrə şahidlik etmişdir. Qədim dövrlər...

5. [1194 chars, 256 tokens]
   Azərbaycanın gələcəkə baxışı, həm ölkə daxilində, həm də beynəlxalq aləmdə özünü...

6. [1412 chars, 277 tokens]
   Beynəlxalq əməkdaşlığın genişləndirilməsi, Avropa İttifaqı və digər beynəlxalq t...

7. [1224 chars, 229 tokens]
   Elmi tədqiqatlar sahəsində də böyük irəliləyişlər müşahidə olunur. Milli Elmlər ...

8. [857 chars, 179 tokens]
   Tarixi Azərbaycan torpaqları, həm də zəngin folklora, ədəbiyyata və incəsənətə m...

Total chunks: 8
Total tokens: 1895
Average tokens per chunk: 236.9

Quick Configuration for Chunking

Global Parameters

Parameter Value Description
MAX_CHUNK_TOKENS 300 Maximum tokens per chunk (must be < model context window)
TARGET_TOKENS 200 Target optimal chunk size
THRESHOLD 0.12 Semantic boundary confidence (0.10–0.20)

Configuration for 384-token Embedding Models

Recommended Settings

Parameter Value Notes
MAX_CHUNK_TOKENS 300 78% of context window (384 × 0.78)
TARGET_TOKENS 200 Two-thirds of max tokens
THRESHOLD 0.12 Balanced segmentation

Conservative Settings

Parameter Value Notes
MAX_CHUNK_TOKENS 256 ~67% of context window
TARGET_TOKENS 180 ~70% of max tokens
THRESHOLD 0.15 Fewer, larger chunks

Aggressive Settings

Parameter Value Notes
MAX_CHUNK_TOKENS 320 83% of context window
TARGET_TOKENS 220 Close to max tokens
THRESHOLD 0.10 More boundaries, finer segmentation

Key Rules

  • MAX_CHUNK_TOKENS should be 20–25% less than the embedding model’s context window
  • TARGET_TOKENS should be 60–80% of MAX_CHUNK_TOKENS
  • THRESHOLD controls granularity:
    • Lower → more chunks
    • Higher → fewer chunks
  • Always test with your specific embedding model and adjust as needed

Expected Results

  • Average chunk size: ~TARGET_TOKENS ± 20%
  • All chunks: < MAX_CHUNK_TOKENS
  • Semantic boundaries preserved
  • No text loss or duplication

Chunking Strategies

Optimal Strategy (Default)

  • Threshold: 0.13
  • Best for: General purpose, balanced precision/recall
  • Typical output: 3–5 chunks for medium texts

Conservative Strategy

  • Threshold: 0.3
  • Best for: Longer chunks, fewer segments
  • Typical output: 1–3 chunks for medium texts

Aggressive Strategy

  • Threshold: 0.05
  • Best for: Fine-grained segmentation
  • Typical output: 5–10 chunks for medium texts

CC BY 4.0 License — What It Allows

The Creative Commons Attribution 4.0 International (CC BY 4.0) license allows:

You are free to use, modify, and distribute the model — even for commercial purposes — as long as you give proper credit to the original creator.

For more information, please refer to the CC BY 4.0 license.

Contact

For more information, questions, or issues, please contact LocalDoc at [v.resad.89@gmail.com].

Downloads last month
25
Safetensors
Model size
277M params
Tensor type
F32
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for LocalDoc/semantic_chunker

Finetuned
(3194)
this model