Skip to content

Commit 3ab8fa2

Browse files
Asaif AliAsaif Ali
authored andcommitted
Replaced the local embedder and lance bd with hosted and qdrant db
1 parent 60f8a77 commit 3ab8fa2

11 files changed

Lines changed: 1096 additions & 928 deletions

File tree

.env.example

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,21 @@ OPENAI_BASE_URL=http://litellm:4000/v1
1313
OPENAI_API_KEY=
1414
OLLAMA_MODEL_ID=
1515
OLLAMA_HOST=http://host.docker.internal:11434
16-
OLLAMA_EMBEDDER_ID=
1716
VLLM_BASE_URL=http://host.docker.internal:8000/v1
18-
VLLM_EMBEDDING_BASE_URL=http://host.docker.internal:8000/v1
1917
VLLM_CHAT_MODEL_ID=
20-
VLLM_EMBED_MODEL_ID=
2118
VLLM_API_KEY=
2219
GOOGLE_API_KEY=
2320
HUGGINGFACE_API_KEY=
2421
HUGGINGFACE_MODEL_ID=
2522

2623
# Embeddings
27-
EMBED_MODEL_TYPE=fastembed
24+
QDRANT_CLOUD_INFERENCE=true
25+
QDRANT_URL=
26+
QDRANT_API_KEY=
27+
QDRANT_COLLECTION_PREFIX=legacylens
28+
QDRANT_DENSE_MODEL=sentence-transformers/all-MiniLM-L6-v2
29+
QDRANT_SPARSE_MODEL=qdrant/bm25
30+
QDRANT_DENSE_DIMENSIONS=384
2831

2932
# Database
3033
DATABASE_URL=

agent_service/app/application/agents/conversion/conversion_tools.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@
5353
logging.basicConfig(level=logging.INFO)
5454
_conversion_step_start_sent: set = set()
5555

56+
def _ensure_qdrant_kb_ready() -> None:
57+
if kb.source_knowledge is None or kb.target_knowledge is None:
58+
kb.init_knowledge_bases()
59+
60+
5661
_folder_goals_repo: Optional[IFolderStructureGoalsRepository] = None
5762
_json_artifact_repo: Optional[IJsonArtifactRepository] = None
5863
__all__=[
Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,15 @@
1-
from agno.knowledge.knowledge import Knowledge
2-
from agno.vectordb.lancedb import LanceDb, SearchType
3-
from agno.db.sqlite import SqliteDb
4-
from app.infrastructure.agents_backend.model_provider import model_embedder
5-
from app.infrastructure.utils.file_utils import get_migration_directory
6-
7-
source_knowledge: Knowledge = None
8-
target_knowledge: Knowledge = None
9-
10-
def init_knowledge_bases() -> None:
11-
global source_knowledge, target_knowledge
12-
base = get_migration_directory("", "")
13-
base.mkdir(parents=True, exist_ok=True)
14-
source_knowledge = Knowledge(
15-
contents_db=SqliteDb(db_file=str(base / "source_knowledge.db")),
16-
vector_db=LanceDb(
17-
uri=str(base / "lancedb"),
18-
table_name="source_table",
19-
search_type=SearchType.hybrid,
20-
embedder=model_embedder,
21-
),
22-
)
23-
target_knowledge = Knowledge(
24-
contents_db=SqliteDb(db_file=str(base / "target_knowledge.db")),
25-
vector_db=LanceDb(
26-
uri=str(base / "lancedb"),
27-
table_name="target_table",
28-
search_type=SearchType.hybrid,
29-
embedder=model_embedder,
30-
),
31-
)
1+
from app.infrastructure.agents_backend.qdrant_knowledge import QdrantKnowledgeBase, _collection_name
2+
3+
source_knowledge: QdrantKnowledgeBase | None = None
4+
target_knowledge: QdrantKnowledgeBase | None = None
5+
6+
7+
def init_knowledge_bases() -> None:
8+
global source_knowledge, target_knowledge
9+
source_knowledge = QdrantKnowledgeBase(_collection_name("source"))
10+
target_knowledge = QdrantKnowledgeBase(_collection_name("target"))
11+
12+
# Legacy agent_setup imports this name, even though the current KB work is
13+
# performed through the workflow/tool layer. Keep the symbol to preserve the
14+
# public import contract without adding another local vector-store object.
15+
kb_agent = None

agent_service/app/application/agents/knowledge_base/knowledge_base_tools.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import os
2-
import lancedb
32
import logging
43
import json
54
from typing import Optional, Any, Tuple
@@ -25,6 +24,11 @@
2524
logger = logging.getLogger(__name__)
2625
logging.basicConfig(level=logging.INFO)
2726

27+
def _ensure_qdrant_kb_ready() -> None:
28+
if kb.source_knowledge is None or kb.target_knowledge is None:
29+
kb.init_knowledge_bases()
30+
31+
2832
_kb_event_helper = MigrationEventHelper(
2933
agent_name="knowledge_base",
3034
event_name="knowledge_base_started",
@@ -50,6 +54,7 @@ def _get_json_artifact_repo() -> IJsonArtifactRepository:
5054
return _json_artifact_repo
5155

5256
def load_json_to_knowledge(step_input: StepInput) -> StepOutput:
57+
_ensure_qdrant_kb_ready()
5358
"""Load data from a JSON file and add it to the knowledge base."""
5459
try:
5560
migration_dir = get_migration_directory("", "")

agent_service/app/application/agents/planning/planning_tools.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
get_folder_structure_goals_repository,
4040
)
4141
from app.infrastructure.utils.enums.migration_event import MigrationEvent
42-
from app.infrastructure.agents_backend.model_provider import model_embedder
4342
from app.infrastructure.utils.Constants.agent_event import AgentEventMessages
4443
from app.infrastructure.utils.Agent_helpers.planning_helper import *
4544
from app.infrastructure.utils.token_tracker import track_tokens
@@ -64,6 +63,11 @@
6463
MODEL_TYPE = os.getenv("MODEL_TYPE", "OpenAI")
6564
PLANNING_LLM_TIMEOUT_SEC = int(os.getenv("PLANNING_LLM_TIMEOUT_SEC", "180"))
6665

66+
def _ensure_qdrant_kb_ready() -> None:
67+
if kb.source_knowledge is None or kb.target_knowledge is None:
68+
kb.init_knowledge_bases()
69+
70+
6771
_file_mapping_repo: Optional[IFileMappingRepository] = None
6872
_folder_goals_repo: Optional[IFolderStructureGoalsRepository] = None
6973
_json_artifact_repo: Optional[IJsonArtifactRepository] = None
@@ -204,6 +208,7 @@ def _get_migration_scan_repo() -> IMigrationScanResultRepository:
204208

205209

206210
def get_symbol_module_meta(step_input: StepInput) -> StepOutput:
211+
_ensure_qdrant_kb_ready()
207212
"""Fetch all symbols and modules from Knowledge Base"""
208213
# ── Skip if plan already exists ───────────────────────────────────────
209214
existing = _load_existing_plan()
@@ -330,6 +335,7 @@ def get_symbol_module_meta(step_input: StepInput) -> StepOutput:
330335
)
331336

332337
def generate_dependency_plan(step_input: StepInput) -> StepOutput:
338+
_ensure_qdrant_kb_ready()
333339
"""
334340
Reads source symbols and dependencies from step output (get_symbol_module_meta),
335341
calls dependency_agent to predict the target dependency file,
@@ -794,6 +800,7 @@ def generate_symbols_transformation(step_input: StepInput) -> StepOutput:
794800
return StepOutput(content={"transformations": transformations, "naming": all_naming})
795801

796802
def generate_migration_plan(step_input: StepInput) -> StepOutput:
803+
_ensure_qdrant_kb_ready()
797804
"""
798805
Step 3: pure-Python plan assembly — no LLM call.
799806
"""

agent_service/app/infrastructure/agents_backend/model_provider.py

Lines changed: 17 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,6 @@
77
from agno.models.openai import OpenAIChat
88
from app.infrastructure.utils.llm_gateway_context import get_llm_gateway_token
99
from 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
1812
from openai import AsyncOpenAI, OpenAI
@@ -30,58 +24,10 @@
3024
OPENAI_BASE_URL = os.getenv("LLM_BASE_URL") or os.getenv("OPENAI_BASE_URL")
3125
LLM_GATEWAY_URL = os.getenv("LLM_GATEWAY_URL", "https://portfolio-llm-gateway.onrender.com/v1").strip()
3226
LLM_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")
3627
VLLM_BASE_URL = os.getenv("VLLM_BASE_URL")
3728
VLLM_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")
4029
VLLM_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
# --------------------------------------------------
8733
def 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

Comments
 (0)