forked from innovacion/searchbox
Initial commit
This commit is contained in:
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Python-generated files
|
||||||
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv
|
||||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.13
|
||||||
26
pyproject.toml
Normal file
26
pyproject.toml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[project]
|
||||||
|
name = "qdrant-mcp"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = [
|
||||||
|
"fastmcp>=2.12.3",
|
||||||
|
"qdrant-client==1.13",
|
||||||
|
"vault-settings>=0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
qdrant-mcp = "qdrant_mcp:run"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["uv_build"]
|
||||||
|
build-backend = "uv_build"
|
||||||
|
|
||||||
|
[tool.vault-settings]
|
||||||
|
secret = "qdrant-mcp"
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"fastembed>=0.7.3",
|
||||||
|
]
|
||||||
16
scripts/client.py
Normal file
16
scripts/client.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import asyncio
|
||||||
|
from fastmcp import Client
|
||||||
|
from fastembed import TextEmbedding
|
||||||
|
|
||||||
|
|
||||||
|
embedding_model = TextEmbedding()
|
||||||
|
client = Client("http://localhost:8000/sse")
|
||||||
|
|
||||||
|
async def call_tool(input: str, collection: str):
|
||||||
|
embedding: list[float] = list(embedding_model.embed(input))[0].tolist()
|
||||||
|
|
||||||
|
async with client:
|
||||||
|
result = await client.call_tool("semantic_search", {"embedding": embedding, "collection": collection})
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
asyncio.run(call_tool("Dime sobre las cucarachas", "dummy_collection"))
|
||||||
40
scripts/create_dummy_collection.py
Normal file
40
scripts/create_dummy_collection.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from qdrant_client import QdrantClient
|
||||||
|
from fastembed import TextEmbedding
|
||||||
|
from qdrant_client.models import Distance, VectorParams, PointStruct
|
||||||
|
from qdrant_mcp.config import Settings
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
|
embedding_model = TextEmbedding()
|
||||||
|
client = QdrantClient(url=settings.url, api_key=settings.api_key)
|
||||||
|
|
||||||
|
documents: list[str] = [
|
||||||
|
"Rick es el mas guapo",
|
||||||
|
"Los pulpos tienen tres corazones y sangre azul",
|
||||||
|
"Las cucarachas pueden vivir hasta una semana sin cabeza",
|
||||||
|
"Los koalas tienen huellas dactilares casi idénticas a las humanas",
|
||||||
|
"La miel nunca se echa a perder, incluso después de miles de años"
|
||||||
|
]
|
||||||
|
embeddings = list(embedding_model.embed(documents))
|
||||||
|
size = len(embeddings[0])
|
||||||
|
|
||||||
|
_ = client.recreate_collection(
|
||||||
|
collection_name="dummy_collection",
|
||||||
|
vectors_config=VectorParams(
|
||||||
|
distance=Distance.COSINE,
|
||||||
|
size=size
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for idx, (emb, document) in enumerate(zip(embeddings, documents)):
|
||||||
|
_ = client.upsert(
|
||||||
|
collection_name="dummy_collection",
|
||||||
|
points=[
|
||||||
|
PointStruct(
|
||||||
|
id=idx,
|
||||||
|
vector=emb,
|
||||||
|
payload={
|
||||||
|
"text": document
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
4
src/qdrant_mcp/__init__.py
Normal file
4
src/qdrant_mcp/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from .main import mcp
|
||||||
|
|
||||||
|
def run():
|
||||||
|
mcp.run(transport="sse")
|
||||||
7
src/qdrant_mcp/config.py
Normal file
7
src/qdrant_mcp/config.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from pydantic import Field
|
||||||
|
from vault_settings import VaultSettings
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(VaultSettings):
|
||||||
|
url: str = Field(...)
|
||||||
|
api_key: str | None = None
|
||||||
35
src/qdrant_mcp/engine.py
Normal file
35
src/qdrant_mcp/engine.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from collections.abc import Sequence
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from qdrant_client import AsyncQdrantClient, models
|
||||||
|
|
||||||
|
from .config import Settings
|
||||||
|
from .models import SearchRow
|
||||||
|
|
||||||
|
|
||||||
|
class QdrantEngine:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.settings = Settings()
|
||||||
|
self.client = AsyncQdrantClient(
|
||||||
|
url=self.settings.url, api_key=self.settings.api_key
|
||||||
|
)
|
||||||
|
|
||||||
|
async def semantic_search(
|
||||||
|
self,
|
||||||
|
embedding: Sequence[float] | models.NamedVector,
|
||||||
|
collection: str,
|
||||||
|
limit: int = 10,
|
||||||
|
conditions: Any | None = None,
|
||||||
|
threshold: float | None = None,
|
||||||
|
) -> list[SearchRow]:
|
||||||
|
points = await self.client.search(
|
||||||
|
collection_name=collection,
|
||||||
|
query_vector=embedding,
|
||||||
|
query_filter=conditions,
|
||||||
|
limit=limit,
|
||||||
|
with_payload=True,
|
||||||
|
with_vectors=False,
|
||||||
|
score_threshold=threshold,
|
||||||
|
)
|
||||||
|
|
||||||
|
return [SearchRow(chunk_id=str(point.id), score=point.score, payload=point.payload) for point in points if point.payload is not None]
|
||||||
9
src/qdrant_mcp/main.py
Normal file
9
src/qdrant_mcp/main.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from fastmcp import FastMCP
|
||||||
|
|
||||||
|
from .engine import QdrantEngine
|
||||||
|
|
||||||
|
mcp = FastMCP("Qdrant MCP")
|
||||||
|
|
||||||
|
engine = QdrantEngine()
|
||||||
|
|
||||||
|
_ = mcp.tool(engine.semantic_search)
|
||||||
7
src/qdrant_mcp/models.py
Normal file
7
src/qdrant_mcp/models.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from typing import Any
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class SearchRow(BaseModel):
|
||||||
|
chunk_id: str
|
||||||
|
score: float
|
||||||
|
payload: dict[str, Any]
|
||||||
Reference in New Issue
Block a user