Omnilude के ai-service ने Assistant / Thread / Run संरचना क्यों चुनी
शुरुआत
इस पोस्ट में मैं Omnilude के ai-service के implementation को साझा करना चाहता हूं। एक मशहूर कहावत है जिसे हममें से अधिकांश ने कम से कम एक बार जरूर सुना होगा: "पहिया फिर से मत बनाइए।" मैं भी इस बात से सहमत हूं। जब मैंने ai-service के AI execution मॉडल को डिज़ाइन किया, तब मैं पहले ही OpenAI की Assistants API (Beta) का उपयोग कर चुका था, और वह अनुभव बुरा नहीं था। उस समय मेरे पास उससे बेहतर कोई चमकदार विचार भी नहीं था, इसलिए शुरुआती संरचना लगभग imitation के काफ़ी करीब थी।
OpenAI की Assistants API की संरचना काफी सरल थी। उसमें assistant / thread / message / run / run step जैसा एक skeleton था, और Assistant को preset, Thread को conversation container, और Run को actual execution के रूप में समझा जा सकता था। OpenAI के official deep dive और FAQ में भी यह conceptual axis साफ दिखाई देता है।
लेकिन Omnilude ने इस संरचना को ज्यों का त्यों कॉपी नहीं किया। हम ऐसा service नहीं बना रहे थे जो सिर्फ OpenAI पर टिका हो। हमें एक ही platform के भीतर local models, OpenAI-compatible servers, Anthropic, Ollama, और LM Studio जैसी कई execution paths को संभालना था।
इसलिए Omnilude का ai-service OpenAI Assistants से concept तो लेता है, लेकिन इसकी असली implementation ज्यादा separated direction में गई। इस लेख में मैं उसी संरचना को आसान तरीके से समझाने की कोशिश करूंगा।
पहले, यह क्या store करता है?
Omnilude के ai-service में AI से जुड़े कई objects हैं। नाम देखकर यह थोड़ा जटिल लग सकता है, लेकिन roles को अलग करके देखें तो यह उतना कठिन नहीं है।
AiProvider: कौन-सा provider इस्तेमाल हो रहा है, यह store करता है। जैसे OpenAI, Anthropic, Ollama, LM Studio।AiApiKey: उस provider से जुड़ी authentication key को store करता है।AiModel: actual model information को store करता है, जैसे model name, reasoning support, pricing, और context size।AiAssistant: यह define करता है कि model किस उद्देश्य से और किस instruction के साथ इस्तेमाल होगा। यह execution preset है।AiThread,AiMessage,AiRun,AiRunStep: conversation और execution history को store करते हैं।AiAgent: upper layer जो कई assistants को मिलाकर workflow के रूप में चलाती है।
अगर इसे एक वाक्य में छोटा करूं, तो बात ऐसी है।
Provider और Model सामग्री हैं, Assistant recipe है, और Thread तथा Run असली order और cooking record हैं।
इसे diagram में देखें तो संबंध और आसानी से समझ आता है।
यही वह जगह भी है जहां यह OpenAI की पुरानी Assistants API से मिलता-जुलता दिखता है। Assistant, Thread, Message, Run, Run Step का skeleton काफ़ी similar है। फर्क यह है कि Omnilude provider / model / apiKey को Assistant के बाहर रखकर अलग asset की तरह manage करता है।
इसे इस तरह अलग क्यों किया गया?
कारण सीधा है। Omnilude में मैं Assistant को सिर्फ prompt के एक block की तरह नहीं देखना चाहता था।
मान लीजिए हम एक assistant बनाते हैं। तब हम वास्तव में सिर्फ instructions को store नहीं करना चाहते।
- कौन-सा provider इस्तेमाल होगा
- कौन-सा model इस्तेमाल होगा
- क्या reasoning supported है
- response format text होगा या json
- temperature और top-p कैसे सेट होंगे
- अगर बाद में model बदल भी जाए, तो original baseline कौन-सा model रहेगा
यह सोच actual AiAssistant entity में सीधे दिखाई देती है। modelId और primaryModelId अलग हैं, और apiKeyId तथा primaryApiKeyId भी अलग हैं। लेकिन इसका मतलब सिर्फ original baseline model को रिकॉर्ड रखना नहीं है। इससे भी ज्यादा महत्वपूर्ण कारण है fallback। अगर कोई provider panic state में चला जाए, कोई key block हो जाए, या अभी जुड़ा हुआ model unstable हो जाए, तो service को दूसरे model और key पर switch करके चलते रहना चाहिए। यानी current model और key को primary model और key से अलग रखकर failure की स्थिति में भी system को ज्यादा robust बनाया जा सकता है।
इसी वजह से assistant सिर्फ prompt template नहीं रहता। वह एक operable execution preset बन जाता है।
यही वह अहम जगह है जहां Omnilude, OpenAI Assistants से अलग हो जाता है। OpenAI का Assistant अक्सर model + instructions + tools को एक bundle की तरह दिखता था। Omnilude इसके विपरीत provider, model, और api key को पहले independent assets बनाता है, और फिर assistant को उनके ऊपर बनी composition layer की तरह डिज़ाइन करता है।
Assistant सिर्फ DB entity पर खत्म नहीं होता
यहां एक और महत्वपूर्ण कदम है। stored AiAssistant सीधे execute नहीं होता।
Actual execution से ठीक पहले AiAssistantService नीचे दी गई जानकारी को एक साथ combine करता है।
- assistant के अपने preset values
- connected model information
- connected provider information
- encrypted api key जो system में stored है
फिर इस result को RunnableAiAssistant नाम के execution object में बदला जाता है।
यह नाम काफी महत्वपूर्ण है। Omnilude में database में stored assistant और वास्तव में चलने वाला assistant अलग-अलग चीज़ें माने जाते हैं।
- DB-side
AiAssistant: configuration store करने वाली entity - execution-side
RunnableAiAssistant: model call के लिए तैयार object
इस separation के फायदे स्पष्ट हैं।
पहला, execution layer को JPA entities की चिंता नहीं करनी पड़ती।
दूसरा, provider-specific differences को RunnableAiModel implementations के अंदर छिपाया जा सकता है।
तीसरा, reasoning options, response format, और sampling parameters को execution समय एक ही जगह interpret किया जा सकता है।
साफ़ शब्दों में, Omnilude assistant को data की तरह store करने और उसे वास्तव में run करने की जिम्मेदारी अलग करता है।
असली call flow कैसे चलता है?
किसी assistant को सीधे execute करने का सबसे सरल flow मोटे तौर पर ऐसा दिखता है।
इस flow का सबसे अहम हिस्सा LlmGateway है। Omnilude में अब अधिकांश direct executions इसी gateway से गुजरते हैं।
और यह gateway जितना दिखता है उससे कहीं ज्यादा काम करता है।
- provider-specific Rate Limit लागू करना
- execution की शुरुआत और अंत को रिकॉर्ड करना
AiRunऔरAiRunStepबनाना और उनका state manage करना- request और response payloads रिकॉर्ड करना
- token और cost collect करना
यानी यह सिर्फ assistant.chat() को call नहीं करता, बल्कि इसे tracking के साथ platform execution के रूप में wrap करता है।
यह अंतर बहुत जल्दी महत्वपूर्ण हो जाता है। जैसे ही आप AI features को थोड़ा गंभीरता से इस्तेमाल करने लगते हैं, बाद में यह कम महत्वपूर्ण रह जाता है कि prompt क्या था, और ज्यादा महत्वपूर्ण हो जाता है कि किस assistant ने किस model के साथ, कितनी लागत पर, कैसा result दिया।
Thread, Message, और Run को क्यों रखा गया?
यहां बहुत से लोगों के मन में एक स्वाभाविक सवाल आता है।
अगर assistant को सीधे चलाया जा सकता है, तो Thread, Message, और Run को अलग क्यों रखा जाए?
मेरे हिसाब से Omnilude की संरचना में यह हिस्सा बहुत महत्वपूर्ण है।
Assistant सिर्फ एक preset है। लेकिन वास्तविक user experience सिर्फ preset पर खत्म नहीं होती। उपयोगकर्ता बातचीत जारी रखता है, messages जमा करता है, किसी खास बिंदु पर execution मांगता है, और result को फिर एक message के रूप में वापस पाता है।
Omnilude इस flow को ऐसे अलग करता है।
AiThread: conversation roomAiMessage: user और assistant के बीच हर turnAiRun: किसी specific message पर आधारित execution requestAiRunStep: उस execution का internal detailed trace
मेरे लिए यह OpenAI Assistants की दिखाई गई संरचना को वास्तविक product runtime में लाने का काफी practical तरीका है।
उदाहरण के लिए, client पहले thread बनाता है, उसके अंदर user message जोड़ता है, और फिर /ai-runs को call करता है। उस क्षण से यह सिर्फ chat completion call नहीं रहता। यह एक conversation context वाला execution unit बन जाता है।
यह क्यों महत्वपूर्ण है?
पहला, execution history को conversation से जोड़ा जा सकता है। दूसरा, उसी thread के भीतर किस point से फिर शुरू करना है, यह manage किया जा सकता है। तीसरा, title generation, favorites, और reset जैसी अतिरिक्त features को thread स्तर पर जोड़ा जा सकता है।
जितना ज्यादा AI को product बनाया जाता है, उतना ही इस तरह की interface एक single direct call से ज्यादा लंबे समय तक टिकती है।
लेकिन Run दबाने का मतलब हमेशा सिर्फ एक Assistant चलना नहीं है
यह भी वह जगह है जहां Omnilude, OpenAI Assistants से अलग दिखता है।
OpenAI Assistants का impression अपेक्षाकृत साफ था: Assistant है, Thread है, और Run उस assistant को thread पर execute करता है।
लेकिन Omnilude का /ai-runs अब ज्यादा workflow entry point जैसा है। वास्तविक implementation में AiRunController तुरंत model call नहीं करता। वह पहले task को DTE queue में डालता है। फिर ChatAgentTaskHandler उस task को उठाता है, thread messages पढ़ता है, और AiAgent के workflow को execute करता है।
यानी आज के Omnilude में Run सिर्फ "एक assistant चलाओ" तक सीमित नहीं है। कई मामलों में यह कई assistants वाले workflow को शुरू करने का starting point बन जाता है।
इस तरह देखने पर संरचना और साफ हो जाती है।
- Assistant: एक LLM preset
- Agent: कई assistants से बना workflow
- Run: उस workflow को वास्तव में शुरू करने वाली execution unit
इसी अंतर की वजह से Omnilude का ai-service सिर्फ assistant repository नहीं, बल्कि platform के ज्यादा करीब लगता है।
अभी सभी paths पूरी तरह एक जैसी नहीं हैं
यहां एक व्यावहारिक बात भी छोड़ना चाहता हूं। संरचना साफ होने के बावजूद, अभी सभी entry points बिल्कुल एक ही execution path का पालन नहीं करते।
उदाहरण के लिए, backoffice playground और direct inference वाले paths LlmGateway से गुजरते हैं, इसलिए tracking और cost logging अपेक्षाकृत अच्छी तरह जुड़ जाते हैं। इसके उलट internal system chat API /system/ai/chat थोड़ा ज्यादा direct path इस्तेमाल करता है। वहां assistant load होता है, rate limiter लागू होता है, और chat सीधे call होता है। इसलिए उसका स्वभाव gateway-based tracking path से थोड़ा अलग है।
मुझे यह बात उल्टा काफी स्वाभाविक लगती है। वास्तविक platforms अक्सर शुरुआत से किसी एक perfect pattern पर पूरी तरह व्यवस्थित नहीं होते। आम तौर पर वे usage बढ़ने के साथ धीरे-धीरे shared structure की ओर converge करते हैं।
महत्वपूर्ण बात यह है कि central axis पहले से तय हो चुकी है।
- management objects अलग हैं
- execution objects
RunnableAiAssistantकी ओर unify हो रहे हैं - conversation और execution history
Thread / Message / Run / RunStepके रूप में बची हुई है - upper-level orchestration की जिम्मेदारी
AiAgentके पास है
मुझे यह संरचना काफी पसंद है
जब मैं इस संरचना को दोबारा देखता हूं, तो Omnilude का ai-service सिर्फ ऐसा service नहीं लगता जो models को अच्छी तरह call करता है।
यह एक साथ चार काम करने की कोशिश कर रहा है।
- कई providers और models को एक platform के भीतर संभालना
- assistants को reusable execution presets में बदलना
- conversation और execution को thread/message/run संरचना में रखना
- आगे चलकर assistants को मिलाकर agent workflows तक बढ़ना
मुझे यह दिशा काफी व्यावहारिक लगती है। जब आप AI को किसी product में डालना शुरू करते हैं, तो अंत में जरूरत कुछ helper functions की नहीं होती जो models को call करें। जरूरत होती है एक executable interface की।
और यह interface काफी हद तक उस conceptual structure जैसा है जो OpenAI Assistants ने कभी दिखाया था। फर्क सिर्फ इतना है कि Omnilude एक कदम और आगे गया: provider और model को ज्यादा साफ़ तौर पर अलग किया, और tracking तथा workflow को design के भीतर ज्यादा गहराई से शामिल किया।
समापन
Omnilude के ai-service में AiAssistant सिर्फ prompt storage नहीं है। यह उससे कहीं ज्यादा execution preset के करीब है, जो तय करता है कि कौन-सा provider और model call होगा, कैसे call होगा, response किस format में वापस आएगा, और किस runtime पर उसका record रखा जाएगा।
और AiThread, AiMessage, AiRun, AiRunStep वह runtime interface हैं जो इस preset को वास्तविक product flow के भीतर काम करने देते हैं। जब इसके ऊपर AiAgent जुड़ जाता है, तो Omnilude का ai-service कुछ assistants को manage करने से आगे बढ़कर workflows को operate करने वाला platform बन जाता है।
अगली बार मैं यहीं से एक कदम आगे बढ़ते हुए यह कहानी लेकर आऊंगा कि assistants को जोड़कर agents कैसे बनाए जाते हैं।