77from agno .models .openai import OpenAIChat
88from app .infrastructure .utils .llm_gateway_context import get_llm_gateway_token
99from agno .models .huggingface import HuggingFace
10- from agno .knowledge .embedder .openai import OpenAIEmbedder
11- from agno .knowledge .embedder .google import GeminiEmbedder
12- from agno .knowledge .embedder .ollama import OllamaEmbedder
13- from agno .knowledge .embedder .fastembed import FastEmbedEmbedder
14- import hashlib
15- import math
1610
1711# from agno.models.vllm import VLLM
1812from openai import AsyncOpenAI , OpenAI
3024OPENAI_BASE_URL = os .getenv ("LLM_BASE_URL" ) or os .getenv ("OPENAI_BASE_URL" )
3125LLM_GATEWAY_URL = os .getenv ("LLM_GATEWAY_URL" , "https://portfolio-llm-gateway.onrender.com/v1" ).strip ()
3226LLM_GATEWAY_TIMEOUT = float (os .getenv ("LLM_GATEWAY_TIMEOUT" , "180" ))
33- EMBED_MODEL_TYPE = os .getenv ("EMBED_MODEL_TYPE" , "fastembed" ).strip ().lower ()
34- EMBED_DIMENSIONS = int (os .getenv ("EMBED_DIMENSIONS" , "384" ))
35- EMBEDDING_BASE_URL = os .getenv ("EMBEDDING_BASE_URL" )
3627VLLM_BASE_URL = os .getenv ("VLLM_BASE_URL" )
3728VLLM_CHAT_MODEL_ID = os .getenv ("VLLM_CHAT_MODEL_ID" )
38- VLLM_EMBED_MODEL_ID = os .getenv ("VLLM_EMBED_MODEL_ID" )
39- VLLM_EMBEDDING_BASE_URL = os .getenv ("VLLM_EMBEDDING_BASE_URL" )
4029VLLM_API_KEY = os .getenv ("VLLM_API_KEY" ) or "local"
4130# --------------------------------------------------
42- # Embedding: Local vLLM
43- # --------------------------------------------------
44- class VLLMEmbedder :
45- """Custom embedder for vLLM OpenAI-compatible embedding endpoint"""
46- def __init__ (self , base_url : str , model : str ):
47- logger .info ("🚀 Initializing VLLMEmbedder" )
48- self .client = OpenAI (
49- base_url = base_url ,
50- api_key = VLLM_API_KEY
51- )
52- self .model = model
53- # Detect embedding dimension
54- test = self .client .embeddings .create (
55- model = self .model ,
56- input = "test"
57- )
58- self .dimensions = len (test .data [0 ].embedding )
59- logger .info (f"✅ Embedding dimension detected: { self .dimensions } " )
60- def embed_documents (self , texts ):
61- logger .info (f"📥 Embedding documents | count={ len (texts )} " )
62- response = self .client .embeddings .create (
63- model = self .model ,
64- input = texts
65- )
66- return [d .embedding for d in response .data ]
67- def embed_query (self , text ):
68- logger .debug (f"🔍 Embedding query" )
69- response = self .client .embeddings .create (
70- model = self .model ,
71- input = text
72- )
73- return response .data [0 ].embedding
74- # AGNO compatibility
75- def get_embedding (self , text ):
76- return self .embed_query (text )
77- def get_embedding_and_usage (self , text ):
78- embedding = self .embed_query (text )
79- usage = {
80- "prompt_tokens" : 0 ,
81- "total_tokens" : 0
82- }
83- return embedding , usage
84- # --------------------------------------------------
8531# Message Normalization (vLLM compatibility)
8632# --------------------------------------------------
8733def normalize_messages (messages ):
@@ -179,117 +125,59 @@ def get_async_client(self):
179125 return super ().get_async_client ()
180126
181127
182- class LowMemoryHashEmbedder :
183- """Dependency-free fallback embedder for small Render instances.
184-
185- Produces deterministic, normalized vectors using hashed token/character
186- features. It avoids loading an ONNX embedding model into the service.
187- For this demo-oriented deployment, LanceDB can still perform vector/
188- lexical retrieval without the memory spike caused by FastEmbed startup.
189- """
190-
191- def __init__ (self , dimensions : int = 384 ):
192- self .id = "portfolio-hash-embedder"
193- self .dimensions = dimensions
194-
195- @staticmethod
196- def _tokens (text : str ):
197- text = str (text or "" ).lower ()
198- tokens = []
199- cur = []
200- for ch in text :
201- if ch .isalnum () or ch == "_" :
202- cur .append (ch )
203- elif cur :
204- tokens .append ("" .join (cur ))
205- cur = []
206- if cur :
207- tokens .append ("" .join (cur ))
208- return tokens
209-
210- def get_embedding (self , text ):
211- vec = [0.0 ] * self .dimensions
212- tokens = self ._tokens (text )
213- if not tokens :
214- return vec
215- for token in tokens :
216- for feature in (token , token [:3 ], token [- 3 :]):
217- digest = hashlib .blake2b (feature .encode ("utf-8" ), digest_size = 8 ).digest ()
218- idx = int .from_bytes (digest , "little" ) % self .dimensions
219- sign = 1.0 if (digest [0 ] & 1 ) else - 1.0
220- vec [idx ] += sign
221- norm = math .sqrt (sum (x * x for x in vec )) or 1.0
222- return [x / norm for x in vec ]
223-
224- def get_embedding_and_usage (self , text ):
225- return self .get_embedding (text ), {"prompt_tokens" : 0 , "total_tokens" : 0 }
226-
227- def embed_documents (self , texts ):
228- return [self .get_embedding (t ) for t in texts ]
229-
230-
231- def create_embedder ():
232- if EMBED_MODEL_TYPE in {"hash" , "low_memory" , "low-memory" }:
233- logger .warning ("Using low-memory deterministic embeddings for constrained deployment" )
234- return LowMemoryHashEmbedder (dimensions = EMBED_DIMENSIONS )
235- return FastEmbedEmbedder (id = os .getenv ("FASTEMBED_MODEL" , "BAAI/bge-small-en-v1.5" ), dimensions = EMBED_DIMENSIONS )
236128
237129# --------------------------------------------------
238130# Model Factory
239131# --------------------------------------------------
240- def create_model_and_embedder ():
241- """Factory to initialize model + embedder based on MODEL_TYPE """
132+ def create_model ():
133+ """Initialize only the chat model. KB embeddings live in Qdrant Cloud. """
242134 if MODEL_TYPE == "OpenAI" :
243135 model = GatewayAwareOpenAIChat (
244136 id = OPENAI_MODEL_ID ,
245137 api_key = os .getenv ("OPENAI_API_KEY" ),
246138 base_url = OPENAI_BASE_URL ,
247- temperature = 0.1
139+ temperature = 0.1 ,
248140 )
249- embedder = create_embedder ()
250- logger .info ("Using OpenAI-compatible model with constrained local embeddings" )
141+ logger .info ("Using OpenAI-compatible model with request-scoped gateway support" )
251142 elif MODEL_TYPE == "VLLM" :
252143 if not VLLM_CHAT_MODEL_ID or not VLLM_BASE_URL :
253144 raise ValueError ("VLLM config missing" )
254145 model = VLLMCompatibleOpenAIChat (
255146 id = VLLM_CHAT_MODEL_ID ,
256147 base_url = VLLM_BASE_URL ,
257148 api_key = VLLM_API_KEY ,
258- temperature = 0.1
149+ temperature = 0.1 ,
259150 )
260- embedder = create_embedder ()
261- logger .info (f"✅ Using vLLM model: { VLLM_CHAT_MODEL_ID } " )
151+ logger .info ("Using vLLM model: %s" , VLLM_CHAT_MODEL_ID )
262152 elif MODEL_TYPE == "Gemini" :
263153 model = Gemini (
264154 id = os .getenv ("GEMINI_MODEL_ID" ),
265155 api_key = os .getenv ("GOOGLE_API_KEY" ),
266- temperature = 0.1
156+ temperature = 0.1 ,
267157 )
268- embedder = create_embedder ()
269- logger .info ("✅ Using Gemini model" )
158+ logger .info ("Using Gemini model" )
270159 elif MODEL_TYPE == "Ollama" :
271160 model = Ollama (
272161 id = os .getenv ("OLLAMA_MODEL_ID" ),
273162 host = os .getenv ("OLLAMA_HOST" ),
274163 supports_native_structured_outputs = True ,
275164 supports_json_schema_outputs = True ,
276- options = {"temperature" : 0.1 , "num_ctx" : 256000 }
165+ options = {"temperature" : 0.1 , "num_ctx" : 256000 },
277166 )
278- embedder = FastEmbedEmbedder ()
279- logger .info ("✅ Using Ollama model" )
167+ logger .info ("Using Ollama model" )
280168 elif MODEL_TYPE == "HuggingFace" :
281169 model = HuggingFace (
282170 id = os .getenv ("HUGGINGFACE_MODEL_ID" ),
283171 api_key = os .getenv ("HUGGINGFACE_API_KEY" ),
284172 supports_native_structured_outputs = True ,
285173 supports_json_schema_outputs = True ,
286174 )
287- embedder = create_embedder ()
288- logger .info ("✅ Using configured local embeddings" )
175+ logger .info ("Using configured HuggingFace chat model" )
289176 else :
290177 raise ValueError (f"Unsupported MODEL_TYPE: { MODEL_TYPE } " )
291- return model , embedder
292- # --------------------------------------------------
293- # Initialize
294- # --------------------------------------------------
295- model , model_embedder = create_model_and_embedder ()
178+ return model
179+
180+
181+ model = create_model ()
182+ # Backward-compatibility alias only. No local embedding model is instantiated.
183+ model_embedder = None
0 commit comments