વિનંતી સફળ રહી. પ્રતિસાદ (response) માન્ય JSON હતો. SDK શાંત રહ્યું. તેમ છતાં એપ્લિકેશન તૂટી પડી.

આ એ વાતની વાર્તા છે કે જ્યારે તમે LLM પ્રોવાઈડર બદલવાની પ્રક્રિયાને સ્ટ્રક્ચરલ જુગાર (structural gamble) ને બદલે માત્ર એક કોન્ફિગરેશન ફેરફાર તરીકે જુઓ છો ત્યારે શું થાય છે. તમે નવો base URL પેસ્ટ કરો છો, API key બદલો છો, અને વિનંતીના બોડી (request body) ને સમાન રાખો છો કારણ કે ડોક્યુમેન્ટેશન OpenAI-સુસંગત એન્ડપોઈન્ટનું વચન આપે છે. એક સામાન્ય "hello world" પ્રોમ્પ્ટ માટે, તે કામ કરે છે. તમે ઉજવણી કરો છો. પછી જ્યારે વાસ્તવિક ટ્રાફિક આવે છે, ત્યારે તિરાડો દેખાવા લાગે છે.

વાયર સુસંગતતાનો ભ્રમ

HTTP લેયર પર સુસંગતતા ઉપરછલ્લી છે. 200 સ્ટેટસ કોડ અને JSON બોડીનો અર્થ એ છે કે સર્વરે તમારો સંદેશ સ્વીકાર્યો છે. તેનો અર્થ એ નથી કે સર્વર પણ અગાઉના સર્વરની જેમ જ વિચારે છે. OpenAI-સુસંગત એન્ડપોઈન્ટ્સ વિનંતીનું માળખું (request shape) સમાન રાખે છે, પરંતુ તેઓ વર્તણૂક સંબંધિત કરાર (behavioral contract) શેર કરતા નથી. બે પ્રોવાઈડર્સ સમાન પેલોડ (payloads) સ્વીકારી શકે છે અને એવા જવાબો આપી શકે છે જે સૂક્ષ્મ અને વિનાશક રીતે અલગ હોય.

તમારો કોડ ધારણાઓ પર કામ કરે છે. તમે માનો છો કે message.content એક સ્ટ્રિંગ છે કારણ કે પહેલાં હંમેશા આવું જ હતું. તમે માનો છો કે ટૂલ કોલ (tool call) સાફ અને પાર્સ કરી શકાય તેવા JSON સાથે આવે છે. તમે માનો છો કે finish_reason એ જ સંકેત આપે છે જે તમે સમજો છો. આ ધારણાઓ ત્યાં સુધી અદ્રશ્ય રહે છે જ્યાં સુધી તે જીવલેણ ન બની જાય.

તે ક્રેસ (crash) વિશે વિચારો જેનાથી આ બધું શરૂ થયું:

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

આ લાઇન નિર્દોષ લાગે છે. તે અઠવાડિયા સુધી કામ કરતી હતી. પછી નવા પ્રોવાઈડરે ટૂલ કોલ રિટર્ન કર્યો. તે ક્ષણે, message.content ખાલી સ્ટ્રિંગ નહોતું. તે null હતું. વાસ્તવિક પેલોડ message.tool_calls ની અંદર હતો, પરંતુ પાર્સર પહેલેથી જ આગળ વધી ગયું હતું અને કંઈ ન હોવા છતાં .trim() કોલ કરી દીધું હતું. API એ કોઈ એરર ફેંક્યો નહીં. નેટવર્ક લેયરે કોઈ ફરિયાદ કરી નહીં. તમારા પોતાના પાર્સરે જ વિનંતીને ખતમ કરી દીધી.

જ્યાં પ્રોવાઈડર્સ શાંતિથી અલગ પડે છે

આ તફાવતો ચેન્જલોગ્સ (changelogs) માં જાહેરાત કરતા નથી. તેઓ રિસ્પોન્સ ઓબ્જેક્ટના ખૂણાઓમાં બેસીને એજ કેસ (edge cases) ની રાહ જોતા હોય છે.

ટૂલ-કોલ ફોર્મેટિંગ. એક પ્રોવાઈડર ટૂલ આર્ગ્યુમેન્ટ્સને પ્રી-વેલિડેટેડ JSON ઓબ્જેક્ટ તરીકે મોકલે છે. બીજો તેને એક ફીલ્ડની અંદર એસ્કેપ કરેલી સ્ટ્રિંગ (escaped string) તરીકે મોકલે છે. ત્રીજો કદાચ લાંબા ટૂલ કોલને મલ્ટિપલ સ્ટ્રીમિંગ ડેલ્ટામાં વિભાજિત કરી શકે છે, જેનાથી તમારે માળખું માન્ય છે કે નહીં તે જોવા માટે પણ ચંક્સ (chunks) ને બફર કરવા પડે છે. જો તમારી એપ્લિકેશન એક સિંગલ પાર્સ કરી શકાય તેવા બ્લોબ (blob) ની અપેક્ષા રાખતી હોય, તો તે અટકી જાય છે.

ફિનિશ રીઝન્સ (Finish reasons). OpenAI "stop", "length", "tool_calls", અને "content_filter" જેવા ચોક્કસ સ્ટ્રિંગ્સનો ઉપયોગ કરે છે. સુસંગત પ્રોવાઈડર "end_turn" રિટર્ન કરી શકે છે અથવા જ્યારે મોડેલ ટોકન સીમા પર પહોંચે ત્યારે તે ફીલ્ડને કાઢી પણ શકે છે. જો તમારું રિટ્રાય અથવા ફોલબેક લોજિક ટ્રંકેશન (truncation) શોધવા માટે "length" ની રાહ જોતું હોય, તો તે નિષ્ક્રિય રહેશે જ્યારે યુઝર અધૂરો જવાબ જોઈ રહ્યો હશે.

યુસેજ ફીલ્ડ્સ (Usage fields). કેટલાક પ્રોવાઈડર્સ લેટન્સી (latency) ઘટાડવા માટે સ્ટ્રીમિંગ રિસ્પોન્સમાંથી ટોકન કાઉન્ટ કાઢી નાખે છે. અન્ય લોકો માત્ર અંતિમ ચંકમાં યુસેજ ઉમેરે છે, અથવા નોન-સ્ટ્રીમિંગ કોલ્સમાં તેને સંપૂર્ણપણે કાઢી નાખે છે. જો તમે ગ્રાહકો પાસેથી પ્રતિ ટોકન ચાર્જ કરો છો અને તમારો એકાઉન્ટિંગ કોડ દરેક રિસ્પોન્સ ઓબ્જેક્ટમાં usage.total_tokens હોવાની અપેક્ષા રાખે છે, તો તમારું બિલિંગ પાઇપલાઇન શાંતિથી શૂન્ય રેકોર્ડ કરશે.

સ્ટ્રીમિંગ બિહેવિયર. સર્વર-સેન્ટ એવેન્ટ્સ (Server-sent events) પ્રમાણભૂત હોવા જોઈએ, છતાં પ્રોવાઈડર્સ અલગ-અલગ ફ્રીક્વન્સી પર બફર્સ ફ્લશ કરે છે. ઇવેન્ટ બાઉન્ડ્રીઝ અલગ હોય છે. એક પ્રોવાઈડર [DONE] સિગ્નલ સાથે સ્ટ્રીમ સમાપ્ત કરે છે. બીજો કોઈપણ સેન્ટિનલ (sentinel) વગર કનેક્શનને સીધું જ તોડી નાખે છે. જો તમારો ક્લાયન્ટ ચોક્કસ ક્લોઝિંગ માર્કરની રાહ જોતા બ્લોક થાય છે, તો તે હેંગ થઈ જાય છે.

ભૂલો અને ટાઈમઆઉટ (Errors and timeouts). એક પ્રોવાઈડર પાસેથી રેટ લિમિટ 429 સ્ટેટસ કોડ અને retry-after હેડર સાથે આવી શકે છે, અને બીજા પાસેથી અસ્પષ્ટ 502 તરીકે આવી શકે છે. કેટલાક પ્રોવાઈડર્સ વિનંતી સ્વીકારે છે અને પછી નેટવર્ક ટાઈમઆઉટ પહેલા બે મિનિટ સુધી શાંત થઈ જાય છે. OpenAI SDK જાદુઈ રીતે આને તમારા લોગ્સ અપેક્ષા રાખતા એક્સેપ્શન પ્રકારોમાં રૂપાંતરિત નહીં કરે.

અનિશ્ચિત આકારો માટે ડિફેન્સિવ પાર્સિંગ

ઉકેલ સ્કીમા (schema) પર વિશ્વાસ કરવાનો નથી. ઉકેલ દરેક રિસ્પોન્સને શંકાસ્પદ માનવાનો છે.

માનો નહીં કે content એક સ્ટ્રિંગ છે. તેને સ્પર્શ કરતા પહેલા તપાસો.

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

માનો નહીં કે ટૂલ આર્ગ્યુમેન્ટ્સ માન્ય JSON છે. મોડેલ એક ક્રિયાનું સૂચન કરે છે. તમારો કોડ નક્કી કરવો જોઈએ કે તે સૂચન અમલમાં મૂકવા માટે પૂરતું સુરક્ષિત છે કે નહીં. દરેક ટૂલ આર્ગ્યુમેન્ટ પાર્સિંગને try-catch માં લપેટો (wrap કરો). જો JSON.parse એરર ફેંકે, તો ટૂલ કોલને ખોટી રીતે બનાવેલા કચરા તરીકે ગણો અને તેને ફેઈલ્યોર હેન્ડલર (failure handler) તરફ મોકલો. ભૂલથી બનાવેલ બ્રેકેટ અથવા ખૂટતો અવતરણ ચિહ્ન (quote) ક્યારેય અનહેન્ડલ્ડ એક્સેપ્શન તરીકે ઉપર ન આવવો જોઈએ.

જો tool_calls અસ્તિત્વમાં હોય પરંતુ content ખૂટતું હોય, તો તમારી એપ્લિકેશને સ્ટેટ ટ્રાન્ઝિશન (state transition) ઓળખવું જોઈએ. યુઝરને ચેટ રિપ્લાય મળ્યો નથી. સિસ્ટમને વર્ક ઓર્ડર મળ્યો છે. તે બે અલગ અલગ માર્ગો છે, અને તમારો રાઉટર સ્ટ્રિંગ મેનિપ્યુલેશન કરવાનો પ્રયાસ કરતા પહેલા તફાવત જાણવો જોઈએ.

ડિપ્લોય કરતા પહેલા બિહેવિયરલ ટેસ્ટ્સ

"hi" મેસેજ સાથે એન્ડપોઈન્ટને પિંગ કરવું એ સાબિત કરે છે કે નેટવર્ક કામ કરે છે. તે તમારી એપ્લિકેશન વિશે કંઈ જ સાબિત કરતું નથી.

પ્રોડક્શન ટ્રાફિકને રીડાયરેક્ટ કરતા પહેલા, નવા પ્રોવાઈડર સામે લક્ષિત બિહેવિયરલ ટેસ્ટ સૂટ (behavioral test suite) ચલાવો:

  • સામાન્ય ટેક્સ્ટ રિસ્પોન્સ. ખાતરી કરો કે content અસ્તિત્વ ધરાવે છે, તે સ્ટ્રિંગ છે, અને કાસ્ટિંગ એરર (casting errors) વગર તમારા સેનિટાઈઝેશન પાઈપલાઈનમાંથી પસાર થઈ શકે છે.
  • ફોર્સ્ડ ટૂલ કોલ (Forced tool call). tool_choice ને required પર સેટ કરો. પ્રોવાઈડર તેનું પાલન કરે છે તેની ખાતરી કરો, અને તપાસો કે content null, ખાલી સ્ટ્રિંગ, અથવા મિસિંગ કી તરીકે આવે છે કે નહીં. આ દરેક સ્ટેટ માટે અલગ હેન્ડલરની જરૂર છે.
  • મલફોર્મ્ડ ટૂલ આર્ગ્યુમેન્ટ્સ (Malformed tool arguments). એવા સીનરીયો ઉમેરો જ્યાં મોડેલ ટૂલ આર્ગ્યુમેન્ટ્સની અંદર બ્રોકન JSON રિટર્ન કરે છે. ખાતરી કરો કે તમારું પાર્સર વર્કરને ક્રેશ કરવાને બદલે તેને યોગ્ય રીતે રિજેક્ટ કરે છે.
  • ટોકન લિમિટની નજીક રિસ્પોન્સ. કોન્ટેક્સ્ટ વિન્ડોને (context window) છેડે સુધી લઈ જાઓ. finish_reason તપાસો. જો ટ્રંકેશન (truncation) થાય ત્યારે પ્રોવાઈડર કંઈક અણધાર્યું રિટર્ન કરે છે, તો તમારા સમરાઈઝેશન અથવા રીટ્રાય લોજિકને કેવી રીતે પ્રતિક્રિયા આપવી તે ખબર હોવી જોઈએ.

આ ઇન્ટિગ્રેશન ટેસ્ટ છે, યુનિટ ટેસ્ટ નથી. તેઓ તમારા કોડ અને પ્રોવાઈડરના વ્યક્તિત્વ (personality) વચ્ચેના વાસ્તવિક સંબંધોનું પરીક્ષણ કરે છે. માઈગ્રેશન પૂર્ણ થયું તેમ કહેતા પહેલા આ ટેસ્ટ પાસ કરો.

એક ઇન્ટરનલ કોન્ટ્રાક્ટ બનાવો

પ્રોવાઈડરના તફાવતો તમારી નેટવર્ક બોર્ડર પર જ અટકી જવા જોઈએ. તેમને બિઝનેસ લોજિકમાં લીક ન થવા દો.

એક નોર્મલાઈઝેશન લેયર (normalization layer) બનાવો જે રો (raw) SDK રિસ્પોન્સ લે છે અને એક એવું ઓબ્જેક્ટ આપે છે જે તમારી એપ્લિકેશન ખરેખર માલિકી ધરાવે છે. પ્રોવાઈડર-વિશિષ્ટ વિચિત્રતાઓને (eccentricities) સ્થિર આંતરિક ફોર્મેટમાં મેપ કરો. જો પ્રોવાઈડર A ટૂલ આર્ગ્યુમેન્ટ્સને સ્ટ્રિંગ તરીકે રિટર્ન કરે છે અને પ્રોવાઈડર B ઓબ્જેક્ટ્સ રિટર્ન કરે છે, તો તમારું મેપર બંનેને તમારા પોતાના ToolRequest સ્ટ્રક્ચરમાં ફ્લેટન કરે છે. જો યુસેજ (usage) ખૂટતું હોય, તો તમારું મેપર કાં તો તેનો અંદાજ લગાવે છે અથવા ગેપને ફ્લેગ કરે છે, પરંતુ તે ક્યારેય undefined ને તમારા કોસ્ટ-ટ્રેકિંગ મોડ્યુલ્સમાં પ્રવેશવા દેતું નથી.

જો finish_reason નોન-સ્ટાન્ડર્ડ હોય, તો તેને તમારા પોતાના ટર્મિનલ સ્ટેટ્સના enum માં અનુવાદિત કરો: COMPLETE, TRUNCATED, TOOL_CALL, FILTERED. તમારી એપ આ ક્લીન એબ્સ્ટ્રેક્શનના આધારે શું કરવું તે નક્કી કરવી જોઈએ, ત્રીજા પક્ષના સર્વર પરથી રો સ્ટ્રિંગ્સ તપાસીને નહીં.

આ લેયર પ્રોવાઈડર બદલવાની પ્રક્રિયાને અણધાર્યા પ્રશ્નોના સામનો કરવાના ખેલમાંથી (whack-a-mole) માત્ર એક ફાઇલના ફેરફારમાં ફેરવી દે છે. તમે મેપર ફરીથી લખો છો, બિહેવિયરલ ટેસ્ટ ચલાવો છો, અને આગળ વધો છો. તમારી એપ્લિકેશન અસ્પૃશ્ય રહે છે.

ડિપેન્ડન્સી અપગ્રેડ, કોન્ફિગ ટ્વીક (Config Tweak) નહીં

LLM પ્રોવાઈડર્સ બદલવા એ CDN એન્ડપોઈન્ટ્સ બદલવા જેવું નથી. તે તમારા ડેટાબેઝને PostgreSQL થી MySQL માં બદલવા જેવું છે. તમે ક્યારેય એવું માની لنહોતા કે સમાન કનેક્શન સ્ટ્રિંગનો અર્થ સમાન ક્વેરી બિહેવિયર છે. તમે લોકિંગ સેમેન્ટિક્સ (locking semantics), માઈગ્રેશન પાથ અને ઇન્ડેક્સિંગ વિચિત્રતાઓની તપાસ કરશો. LLMs પણ તેટલા જ આદરને પાત્ર છે. તેઓ સ્ટાન્ડર્ડ API તરીકે દેખાતી સંભવિતતા આધારિત (probabilistic) સિસ્ટમ્સ છે, અને તેમના રિસ્પોન્સમાં ફોર્મેટિંગ, ટ્રંકેશન અને કંટ્રોલ ફ્લો વિશે એવી ધારણાઓ હોય છે જે એક પણ નેટવર્ક એરર આપ્યા વિના તમારી એપ્લિકેશનને તોડી શકે છે.

બગ ક્યારેય કનેક્શનમાં નહોતો. તે એ ધારણામાં હતો કે સુસંગતતા (compatibility) એટલે સમાનતા. તે નથી. આકારને વેલિડેટ કરો. એજ કેસ ટેસ્ટ કરો. કોન્ટ્રાક્ટની જવાબદારી લો.


સ્ત્રોત: The Bug Only Happened After I Switched LLM Providers

કમ્યુનિટી: GyaanSetu AI on Telegram