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