கோரிக்கை வெற்றிபெற்றது. பதில் சரியான JSON ஆக இருந்தது. SDK அமைதியாகவே இருந்தது. இருப்பினும், application முடங்கியது.

ஒரு LLM provider-ஐ மாற்றுவதை, ஒரு structural gamble-ஆகக் கருதாமல், ஒரு configuration change-ஆகக் கருதிச் செயல்படும்போது என்ன நடக்கும் என்பதன் கதை இது. நீங்கள் ஒரு புதிய base URL-ஐப் பதிவிடுகிறீர்கள், API key-ஐ மாற்றுகிறீர்கள், மேலும் ஆவணங்கள் ஒரு OpenAI-compatible endpoint-ஐத் தருவதாக உறுதியளிப்பதால், request body-யை அப்படியே வைத்திருக்கிறீர்கள். ஒரு சாதாரண "hello world" prompt-க்கு, இது வேலை செய்கிறது. நீங்கள் கொண்டாடுகிறீர்கள். பிறகு உண்மையான traffic வரும்போது, விரிசல்கள் தெரியத் தொடங்குகின்றன.

இணக்கத்தன்மையின் மாயை

HTTP நிலையில் உள்ள இணக்கத்தன்மை மேலோட்டமானது. ஒரு 200 status code மற்றும் ஒரு JSON body என்பது சர்வர் உங்கள் செய்தியை ஏற்றுக்கொண்டதைக் குறிக்கிறது. ஆனால் சர்வர் முந்தைய சர்வரைப் போலவே சிந்திக்கிறது என்று அர்த்தமல்ல. OpenAI-compatible endpoints ஒரே மாதிரியான request அமைப்பைப் பகிர்ந்து கொள்ளலாம், ஆனால் அவை ஒரே மாதிரியான செயல்பாட்டு ஒப்பந்தத்தைப் (behavioral contract) பகிர்ந்து கொள்வதில்லை. இரண்டு providers ஒரே மாதிரியான payloads-களைப் பெற்றுக்கொண்டு, நுணுக்கமான மற்றும் அழிவுகரமான வழிகளில் மாறுபட்ட பதில்களைத் தரக்கூடும்.

உங்கள் code சில அனுமானங்களைச் செய்கிறது. முன்பு எப்போதும் இருந்ததால், message.content ஒரு string என்று நீங்கள் கருதுகிறீர்கள். ஒரு tool call சுத்தமான, parse செய்யக்கூடிய JSON உடன் வரும் என்று கருதுகிறீர்கள். finish_reason நீங்கள் நினைப்பது போலவே சிக்னல் கொடுக்கும் என்று கருதுகிறீர்கள். இந்த அனுமானங்கள் உயிருக்கு ஆபத்தானதாக மாறும் வரை கண்ணுக்குத் தெரிவதில்லை.

எல்லாவற்றையும் தொடங்கி வைத்த அந்த முடங்கலை (crash) கவனியுங்கள்:

const text = response.choices[0].message.content.trim();

இந்த வரி பார்ப்பதற்குத் தவறு செய்யாதது போலத் தெரியும். இது வாரக்கணக்கில் வேலை செய்தது. பிறகு புதிய provider ஒரு tool call-ஐத் திருப்பி அனுப்பினார். அந்தத் தருணத்தில், message.content என்பது ஒரு காலியான string ஆக இருக்கவில்லை. அது null ஆக இருந்தது. உண்மையான payload message.tool_calls-க்குள் இருந்தது, ஆனால் parser ஏற்கனவே அடுத்த கட்டத்திற்குச் சென்றுவிட்டதால், எதன் மீதும் .trim() முறையைப் பயன்படுத்த முயன்றது. API எந்தத் தவறும் காட்டவில்லை. Network layer எந்தப் புகாரும் தெரிவிக்கவில்லை. உங்கள் சொந்த parser தான் அந்த request-ஐ அழித்தது.

வழங்குநர்கள் எங்கே அமைதியாக வேறுபடுகிறார்கள்

இந்த வேறுபாடுகள் changelogs-களில் அறிவிக்கப்படுவதில்லை. அவை response object-ன் ஓரங்களில், விளிம்புநிலைச் சூழல்களுக்காக (edge cases) காத்திருக்கின்றன.

Tool-call formatting. ஒரு provider tool arguments-களை முன்கூட்டியே சரிபார்க்கப்பட்ட (pre-validated) JSON object ஆக அனுப்புகிறார். மற்றொருவர் அவற்றை ஒரு field-க்குள் இருக்கும் escaped string ஆக அனுப்புகிறார். மூன்றாவது ஒருவரோ, ஒரு நீண்ட tool call-ஐப் பல streaming deltas-க்களாகப் பிரிக்கலாம், இதனால் அமைப்பு சரியானதா என்பதைப் பார்ப்பதற்கு முன்பே நீங்கள் chunks-களை buffer செய்ய வேண்டியிருக்கும். உங்கள் application ஒரு parse செய்யக்கூடிய ஒற்றை blob-ஐ எதிர்பார்க்கிறது என்றால், அது செயலிழந்துவிடும்.

Finish reasons. OpenAI "stop", "length", "tool_calls", மற்றும் "content_filter" போன்ற குறிப்பிட்ட strings-களைப் பயன்படுத்துகிறது. ஒரு இணக்கமான provider "end_turn" என்று பதிலளிக்கலாம் அல்லது மாடல் token ceiling-ஐ எட்டும்போது அந்த field-ஐத் தவிர்க்கலாம். உங்கள் retry அல்லது fallback logic, தகவல் துண்டிக்கப்படுவதைக் கண்டறிய "length" என்பதற்காகக் காத்திருந்தால், பயனர் ஒரு பாதியிலேயே முடிந்த பதிலைப் பார்க்கும்போது, உங்கள் அமைப்புச் செயல்படாமல் காத்திருக்கும்.

Usage fields. சில providers தாமதத்தைக் (latency) குறைக்க streaming responses-லிருந்து token எண்ணிக்கையை நீக்குகிறார்கள். மற்றவர்கள் usage-ஐ இறுதி chunk-இல் மட்டும் இணைக்கிறார்கள் அல்லது non-streaming அழைப்புகளில் அதைத் தவிர்க்கிறார்கள். நீங்கள் வாடிக்கையாளர்களுக்கு ஒவ்வொரு token-க்கும் கட்டணம் வசூலிப்பவர் என்றால், மற்றும் உங்கள் accounting code ஒவ்வொரு response object-லும் usage.total_tokens இருக்கும் என்று எதிர்பார்த்தால், உங்கள் billing pipeline அமைதியாக பூஜ்ஜியங்களையே பதிவு செய்யும்.

Streaming behavior. Server-sent events தரநிலையாக இருக்க வேண்டும், இருப்பினும் providers வெவ்வேறு அதிர்வெண்களில் (frequencies) buffers-களை flush செய்கிறார்கள். Event எல்லைகள் மாறுபடுகின்றன. ஒரு provider [DONE] சிக்னலுடன் ஒரு stream-ஐ முடிக்கிறார். மற்றொருவர் எந்த ஒரு sentinel சிக்னலும் இன்றி இணைப்பைத் துண்டிக்கிறார். உங்கள் client ஒரு குறிப்பிட்ட closing marker-க்காகக் காத்திருந்தால், அது முடங்கிவிடும் (hang).

Errors and timeouts. ஒரு provider-இடமிருந்து rate limit என்பது retry-after header உடன் கூடிய 429 ஆக வரலாம், மற்றொருவரிடமிருந்து தெளிவற்ற 502 ஆக வரலாம். சில providers கோரிக்கையை ஏற்றுக்கொண்டு, network timeout ஆவதற்கு முன் இரண்டு நிமிடங்கள் அமைதியாக இருக்கலாம். உங்கள் logs எதிர்பார்க்கும் exception வகைகளாக இவற்றை OpenAI SDK தானாகவே மாற்றாது.

கணிக்க முடியாத வடிவங்களுக்கான பாதுகாப்புப் பகுப்பாய்வு

தீர்வு schema-வை நம்புவது அல்ல. ஒவ்வொரு பதிலையும் ஒரு சந்தேகத்திற்குரிய ஒன்றாகக் கருதுவதே தீர்வு.

content ஒரு string என்று assumptions செய்யாதீர்கள். அதைத் தொடங்குவதற்கு முன் சரிபார்க்கவும்.

const content = response.choices?.[0]?.message?.content;
const text = typeof content === "string" ? content.trim() : "";

Tool arguments சரியான JSON என்று assumptions செய்யாதீர்கள். மாடல் ஒரு செயலை முன்மொழிகிறது. அந்த முன்மொழிவுச் செயல்படுத்தத் தகுந்த பாதுகாப்பானதுதானா என்பதை உங்கள் code தீர்மானிக்க வேண்டும். ஒவ்வொரு tool argument parse-ஐயும் ஒரு try-catch-க்குள் வைக்கவும். JSON.parse ஒரு error-ஐத் தூண்டினால், அந்த tool call-ஐ தவறான குப்பையாகக் கருதி ஒரு failure handler-க்கு அனுப்பவும். ஒரு தவறான அடைப்புக்குறி (bracket) அல்லது விடுபட்ட மேற்கோள் குறி (quote) ஒருபோதும் unhandled exception ஆக மேலே வரக்கூடாது.

tool_calls இருந்து content இல்லையென்றால், உங்கள் application ஒரு நிலை மாற்றத்தை (state transition) அடையாளம் காண வேண்டும். பயனர் ஒரு chat பதிலைப் பெறவில்லை. சிஸ்டம் ஒரு பணி ஆணையைப் (work order) பெற்றுள்ளது. அவை இரண்டு வெவ்வேறு பாதைகள், மேலும் உங்கள் router string manipulation-ஐத் தொடங்குவதற்கு முன்பே

ஒரு "hi" செய்தியுடன் endpoint-ஐ ping செய்வது நெட்வொர்க் வேலை செய்கிறது என்பதை மட்டுமே நிரூபிக்கும். அது உங்கள் பயன்பாட்டைப் (application) பற்றி எதையும் நிரூபிக்காது.

நீங்கள் production traffic-ஐ மாற்றியமைப்பதற்கு முன், புதிய provider-க்கு எதிராக ஒரு இலக்கு சார்ந்த நடத்தை சோதனைத் தொகுப்பை (behavioral test suite) இயக்கவும்:

  • சாதாரண உரை பதில் (Normal text response). content இருப்பதை, அது ஒரு string என்பதையும், casting பிழைகள் இன்றி உங்கள் sanitization pipeline வழியாகச் செல்ல முடியும் என்பதையும் சரிபார்க்கவும்.
  • கட்டாயப்படுத்தப்பட்ட tool call. tool_choice-ஐ required என்று அமைக்கவும். provider அதை மதிக்கிறதா என்பதை உறுதிப்படுத்தவும், மேலும் content என்பது null, ஒரு வெற்றுச் சரம் (empty string), அல்லது விடுபட்ட சாவியாக (missing key) வருகிறதா என்று சரிபார்க்கவும். இந்த ஒவ்வொரு நிலைகளுக்கும் தனித்தனி handler தேவைப்படும்.
  • தவறான tool arguments. மாடல் tool arguments-க்குள் சிதைந்த JSON-ஐத் திருப்பி அனுப்பும் சூழல்களைச் சோதிக்கவும். உங்கள் parser, worker-ஐ முடக்குவதற்குப் பதிலாக, அவற்றை முறையாக நிராகரிப்பதை உறுதி செய்யவும்.
  • token limit-க்கு அருகில் உள்ள பதில். context window-வை அதன் எல்லை வரை கொண்டு செல்லவும். finish_reason-ஐச் சரிபார்க்கவும். truncation நடக்கும்போது provider எதிர்பாராத ஒன்றை வழங்கினால், உங்கள் summarization அல்லது retry logic எவ்வாறு செயல்பட வேண்டும் என்பதைத் தெரிந்திருக்க வேண்டும்.

இவை integration tests, unit tests அல்ல. இவை உங்கள் குறியீட்டிற்கும் (code) provider-ன் செயல்பாட்டுத் தன்மைக்கும் (personality) இடையிலான உண்மையான உறவைச் சோதிக்கின்றன. இடமாற்றத்தை (migration) முடிந்தது என்று சொல்வதற்கு முன் இவற்றை வெற்றிகரமாக முடிக்கவும்.

ஒரு உள் ஒப்பந்தத்தை (Internal Contract) உருவாக்கவும்

Provider வேறுபாடுகள் உங்கள் நெட்வொர்க் எல்லையிலேயே நின்றுவிட வேண்டும். அவை business logic-க்குள் கசிய அனுமதிக்காதீர்கள்.

raw SDK response-ஐப் பெற்று, உங்கள் application உண்மையில் வைத்திருக்கும் ஒரு object-ஐ வெளியிடும் ஒரு normalization layer-ஐ உருவாக்கவும். Provider-க்கு உரித்தான தனித்துவமான மாற்றங்களை (eccentricities) ஒரு நிலையான உள் வடிவத்திற்கு (stable internal format) மாற்றவும். Provider A, tool arguments-களை strings ஆகவும், Provider B, objects ஆகவும் வழங்கினால், உங்கள் mapper இரண்டையும் உங்கள் சொந்த ToolRequest கட்டமைப்பிற்குள் மாற்ற வேண்டும். பயன்பாடு (usage) விடுபட்டிருந்தால், உங்கள் mapper அதை மதிப்பிடலாம் அல்லது அந்த இடைவெளியைக் குறிக்கலாம், ஆனால் அது உங்கள் cost-tracking modules-க்குள் undefined கசிய அனுமதிக்காது.

finish_reason என்பது தரநிலைக்கு அப்பாற்பட்டதாக இருந்தால், அதை உங்கள் சொந்த terminal states enum-ஆக மாற்றவும்: COMPLETE, TRUNCATED, TOOL_CALL, FILTERED. உங்கள் app ஒரு மூன்றாம் தரப்பு சேவையகத்திலிருந்து (third-party server) வரும் raw strings-களைப் பகுப்பாய்வு செய்வதன் மூலம் அல்லாமல், இந்தத் தெளிவான சுருக்கங்களின் (abstractions) அடிப்படையில் என்ன செய்ய வேண்டும் என்பதைத் தீர்மானிக்க வேண்டும்.

இந்த layer, provider-களை மாற்றுவதை ஒரு தொடர்ச்சியான போராட்டமாக இல்லாமல், ஒரு கோப்பை (single-file) மாற்றுவது போன்ற எளிமையான செயலாக மாற்றுகிறது. நீங்கள் mapper-ஐ மீண்டும் எழுத வேண்டும், behavioral tests-களை இயக்க வேண்டும், அவ்வளவுதான். உங்கள் application மாற்றமின்றி அப்படியே இருக்கும்.

ஒரு Dependency Upgrade, வெறும் Config Tweak அல்ல

LLM provider-களை மாற்றுவது என்பது CDN endpoints-களை மாற்றுவது போன்றது அல்ல. இது உங்கள் database-ஐ PostgreSQL-லிருந்து MySQL-க்கு மாற்றுவதற்கு நெருக்கமானது. ஒரே connection string இருந்தால் ஒரே மாதிரியான query behavior இருக்கும் என்று நீங்கள் ஒருபோதும் நினைக்க மாட்டீர்கள். நீங்கள் locking semantics, migration paths மற்றும் indexing quirks ஆகியவற்றைச் சோதிப்பீர்கள். LLM-களும் அதே மரியாதையைப் பெறுகின்றன. அவை standard APIs போலத் தோற்றமளிக்கும் நிகழ்தகவு அமைப்புகள் (probabilistic systems), மேலும் அவற்றின் பதில்கள் formatting, truncation மற்றும் control flow பற்றிய அனுமானங்களைக் கொண்டுள்ளன, அவை ஒரு நெட்வொர்க் பிழை கூட ஏற்படாமல் உங்கள் application-ஐச் சிதைக்கக்கூடும்.

அந்தப் பிழை (bug) இணைப்பில் (connection) இல்லை. இணக்கத்தன்மை (compatibility) என்பது சமமான தன்மை (sameness) என்று நீங்கள் எடுத்த தவறான அனுமானத்தில்தான் இருந்தது. அது அவ்வாறு இல்லை. அமைப்பை (shape) சரிபார்க்கவும். விளிம்புகளை (edges) சோதிக்கவும். ஒப்பந்தத்தை (contract) நீங்களே நிர்வகிக்கவும்.


Source: The Bug Only Happened After I Switched LLM Providers

Community: GyaanSetu AI on Telegram