neurocore-skill-qdrant 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- neurocore_skill_qdrant-0.1.0/.gitignore +152 -0
- neurocore_skill_qdrant-0.1.0/PKG-INFO +47 -0
- neurocore_skill_qdrant-0.1.0/README.md +24 -0
- neurocore_skill_qdrant-0.1.0/pyproject.toml +39 -0
- neurocore_skill_qdrant-0.1.0/src/neurocore_skill_qdrant/__init__.py +5 -0
- neurocore_skill_qdrant-0.1.0/src/neurocore_skill_qdrant/skill.py +80 -0
- neurocore_skill_qdrant-0.1.0/tests/test_skill.py +39 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
*.manifest
|
|
31
|
+
*.spec
|
|
32
|
+
|
|
33
|
+
# Installer logs
|
|
34
|
+
pip-log.txt
|
|
35
|
+
pip-delete-this-directory.txt
|
|
36
|
+
|
|
37
|
+
# Unit test / coverage reports
|
|
38
|
+
htmlcov/
|
|
39
|
+
.tox/
|
|
40
|
+
.nox/
|
|
41
|
+
.coverage
|
|
42
|
+
.coverage.*
|
|
43
|
+
.cache
|
|
44
|
+
nosetests.xml
|
|
45
|
+
coverage.xml
|
|
46
|
+
*.cover
|
|
47
|
+
*.py,cover
|
|
48
|
+
.hypothesis/
|
|
49
|
+
.pytest_cache/
|
|
50
|
+
cover/
|
|
51
|
+
|
|
52
|
+
# Translations
|
|
53
|
+
*.mo
|
|
54
|
+
*.pot
|
|
55
|
+
|
|
56
|
+
# Django stuff:
|
|
57
|
+
*.log
|
|
58
|
+
local_settings.py
|
|
59
|
+
db.sqlite3
|
|
60
|
+
db.sqlite3-journal
|
|
61
|
+
|
|
62
|
+
# Flask stuff:
|
|
63
|
+
instance/
|
|
64
|
+
.webassets-cache
|
|
65
|
+
|
|
66
|
+
# Scrapy stuff:
|
|
67
|
+
.scrapy
|
|
68
|
+
|
|
69
|
+
# Sphinx documentation
|
|
70
|
+
docs/_build/
|
|
71
|
+
|
|
72
|
+
# PyBuilder
|
|
73
|
+
.pybuilder/
|
|
74
|
+
target/
|
|
75
|
+
|
|
76
|
+
# Jupyter Notebook
|
|
77
|
+
.ipynb_checkpoints
|
|
78
|
+
|
|
79
|
+
# IPython
|
|
80
|
+
profile_default/
|
|
81
|
+
ipython_config.py
|
|
82
|
+
|
|
83
|
+
# pyenv
|
|
84
|
+
.python-version
|
|
85
|
+
|
|
86
|
+
# pipenv
|
|
87
|
+
Pipfile.lock
|
|
88
|
+
|
|
89
|
+
# poetry
|
|
90
|
+
poetry.lock
|
|
91
|
+
|
|
92
|
+
# pdm
|
|
93
|
+
.pdm.toml
|
|
94
|
+
.pdm-python
|
|
95
|
+
.pdm-build/
|
|
96
|
+
|
|
97
|
+
# PEP 582
|
|
98
|
+
__pypackages__/
|
|
99
|
+
|
|
100
|
+
# Celery stuff
|
|
101
|
+
celerybeat-schedule
|
|
102
|
+
celerybeat.pid
|
|
103
|
+
|
|
104
|
+
# SageMath parsed files
|
|
105
|
+
*.sage.py
|
|
106
|
+
|
|
107
|
+
# Environments
|
|
108
|
+
.env
|
|
109
|
+
.venv
|
|
110
|
+
env/
|
|
111
|
+
venv/
|
|
112
|
+
ENV/
|
|
113
|
+
env.bak/
|
|
114
|
+
venv.bak/
|
|
115
|
+
|
|
116
|
+
# Spyder project settings
|
|
117
|
+
.spyderproject
|
|
118
|
+
.spyproject
|
|
119
|
+
|
|
120
|
+
# Rope project settings
|
|
121
|
+
.ropeproject
|
|
122
|
+
|
|
123
|
+
# mkdocs documentation
|
|
124
|
+
/site
|
|
125
|
+
|
|
126
|
+
# mypy
|
|
127
|
+
.mypy_cache/
|
|
128
|
+
.dmypy.json
|
|
129
|
+
dmypy.json
|
|
130
|
+
|
|
131
|
+
# Pyre type checker
|
|
132
|
+
.pyre/
|
|
133
|
+
|
|
134
|
+
# pytype static type analyzer
|
|
135
|
+
.pytype/
|
|
136
|
+
|
|
137
|
+
# Cython debug symbols
|
|
138
|
+
cython_debug/
|
|
139
|
+
|
|
140
|
+
# Ruff
|
|
141
|
+
.ruff_cache/
|
|
142
|
+
|
|
143
|
+
# IDEs and editors
|
|
144
|
+
.idea/
|
|
145
|
+
.vscode/
|
|
146
|
+
*.swp
|
|
147
|
+
*.swo
|
|
148
|
+
*~
|
|
149
|
+
|
|
150
|
+
# OS files
|
|
151
|
+
.DS_Store
|
|
152
|
+
Thumbs.db
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: neurocore-skill-qdrant
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Qdrant vector-search skill for NeuroCore
|
|
5
|
+
Author: NeuroCore Contributors
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Keywords: ai,neurocore,qdrant,rag,skill,vector
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Python: >=3.13
|
|
15
|
+
Requires-Dist: neurocore-ai>=0.2.0
|
|
16
|
+
Requires-Dist: qdrant-client>=1.10
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest>=9.0.2; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# neurocore-skill-qdrant
|
|
25
|
+
|
|
26
|
+
Vector similarity search for NeuroCore over [Qdrant](https://qdrant.tech).
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install neurocore-skill-qdrant
|
|
30
|
+
docker run -p 6333:6333 qdrant/qdrant
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
components:
|
|
35
|
+
- name: retrieve
|
|
36
|
+
type: qdrant
|
|
37
|
+
config:
|
|
38
|
+
collection: documents
|
|
39
|
+
top_k: 5
|
|
40
|
+
flow:
|
|
41
|
+
type: sequential
|
|
42
|
+
steps:
|
|
43
|
+
- component: retrieve
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Reads `query_vector` (a list of floats from your embedder); writes
|
|
47
|
+
`qdrant_results` (a list of `{id, score, payload}`).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# neurocore-skill-qdrant
|
|
2
|
+
|
|
3
|
+
Vector similarity search for NeuroCore over [Qdrant](https://qdrant.tech).
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install neurocore-skill-qdrant
|
|
7
|
+
docker run -p 6333:6333 qdrant/qdrant
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```yaml
|
|
11
|
+
components:
|
|
12
|
+
- name: retrieve
|
|
13
|
+
type: qdrant
|
|
14
|
+
config:
|
|
15
|
+
collection: documents
|
|
16
|
+
top_k: 5
|
|
17
|
+
flow:
|
|
18
|
+
type: sequential
|
|
19
|
+
steps:
|
|
20
|
+
- component: retrieve
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Reads `query_vector` (a list of floats from your embedder); writes
|
|
24
|
+
`qdrant_results` (a list of `{id, score, payload}`).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "neurocore-skill-qdrant"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Qdrant vector-search skill for NeuroCore"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
requires-python = ">=3.13"
|
|
12
|
+
authors = [{ name = "NeuroCore Contributors" }]
|
|
13
|
+
keywords = ["neurocore", "skill", "qdrant", "vector", "rag", "ai"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: Apache Software License",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
20
|
+
"Typing :: Typed",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"neurocore-ai>=0.2.0",
|
|
24
|
+
"qdrant-client>=1.10",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = ["pytest>=9.0.2", "pytest-asyncio>=0.24", "ruff>=0.8", "mypy>=1.8"]
|
|
29
|
+
|
|
30
|
+
[project.entry-points."neurocore.skills"]
|
|
31
|
+
qdrant = "neurocore_skill_qdrant:QdrantSkill"
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["src/neurocore_skill_qdrant"]
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
testpaths = ["tests"]
|
|
38
|
+
pythonpath = ["src"]
|
|
39
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""QdrantSkill — vector similarity search over a Qdrant collection.
|
|
2
|
+
|
|
3
|
+
Reads a query vector from ``query_vector`` (list[float]) and writes hits to
|
|
4
|
+
``qdrant_results``.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from flowengine import FlowContext
|
|
14
|
+
|
|
15
|
+
from neurocore import AsyncSkill, SkillMeta
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class QdrantSkill(AsyncSkill):
|
|
21
|
+
"""Async vector search against Qdrant."""
|
|
22
|
+
|
|
23
|
+
skill_meta = SkillMeta(
|
|
24
|
+
name="qdrant",
|
|
25
|
+
version="0.1.0",
|
|
26
|
+
description="Vector similarity search over a Qdrant collection",
|
|
27
|
+
author="NeuroCore Contributors",
|
|
28
|
+
requires=["qdrant-client>=1.10"],
|
|
29
|
+
provides=["qdrant_results"],
|
|
30
|
+
consumes=["query_vector"],
|
|
31
|
+
tags=["vector", "rag", "retrieval"],
|
|
32
|
+
max_retries=2,
|
|
33
|
+
config_schema={
|
|
34
|
+
"required": ["collection"],
|
|
35
|
+
"properties": {
|
|
36
|
+
"url": {"type": "string", "description": "Qdrant URL."},
|
|
37
|
+
"api_key": {"type": "string"},
|
|
38
|
+
"collection": {"type": "string"},
|
|
39
|
+
"top_k": {"type": "integer"},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def _build_client(self) -> Any:
|
|
45
|
+
from qdrant_client import QdrantClient # type: ignore[import-untyped]
|
|
46
|
+
|
|
47
|
+
url = self.config.get("url") or os.environ.get(
|
|
48
|
+
"QDRANT_URL", "http://localhost:6333"
|
|
49
|
+
)
|
|
50
|
+
api_key = self.config.get("api_key") or os.environ.get("QDRANT_API_KEY")
|
|
51
|
+
return QdrantClient(url=url, api_key=api_key)
|
|
52
|
+
|
|
53
|
+
async def _search(self, vector: list[float]) -> list[dict[str, Any]]:
|
|
54
|
+
"""Run a similarity query and return hit dicts."""
|
|
55
|
+
client = self._build_client()
|
|
56
|
+
collection = self.config["collection"]
|
|
57
|
+
top_k = int(self.config.get("top_k", 5))
|
|
58
|
+
|
|
59
|
+
def _run() -> list[dict[str, Any]]:
|
|
60
|
+
hits = client.query_points(
|
|
61
|
+
collection_name=collection, query=vector, limit=top_k
|
|
62
|
+
).points
|
|
63
|
+
return [
|
|
64
|
+
{"id": h.id, "score": h.score, "payload": h.payload} for h in hits
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
return await asyncio.to_thread(_run)
|
|
68
|
+
|
|
69
|
+
async def process(self, context: FlowContext) -> FlowContext:
|
|
70
|
+
vector = context.get("query_vector")
|
|
71
|
+
if not vector:
|
|
72
|
+
logger.warning("QdrantSkill: 'query_vector' is empty.")
|
|
73
|
+
context.set("qdrant_results", [])
|
|
74
|
+
return context
|
|
75
|
+
try:
|
|
76
|
+
context.set("qdrant_results", await self._search(list(vector)))
|
|
77
|
+
except Exception as exc: # noqa: BLE001
|
|
78
|
+
logger.error("QdrantSkill search failed: %s", exc, exc_info=True)
|
|
79
|
+
context.set("qdrant_results", [])
|
|
80
|
+
return context
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Tests for QdrantSkill (search mocked via the _search seam)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from flowengine import FlowContext
|
|
5
|
+
from neurocore_skill_qdrant import QdrantSkill
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_skill_meta():
|
|
9
|
+
meta = QdrantSkill.skill_meta
|
|
10
|
+
assert meta.name == "qdrant"
|
|
11
|
+
assert "qdrant_results" in meta.provides
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_validate_config_requires_collection():
|
|
15
|
+
skill = QdrantSkill()
|
|
16
|
+
skill.init({})
|
|
17
|
+
assert any("collection" in e for e in skill.validate_config())
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def test_process_sets_results(monkeypatch):
|
|
21
|
+
skill = QdrantSkill()
|
|
22
|
+
skill.init({"collection": "docs"})
|
|
23
|
+
|
|
24
|
+
async def fake_search(vector):
|
|
25
|
+
assert vector == [0.1, 0.2]
|
|
26
|
+
return [{"id": 1, "score": 0.9, "payload": {"text": "hi"}}]
|
|
27
|
+
|
|
28
|
+
monkeypatch.setattr(skill, "_search", fake_search)
|
|
29
|
+
ctx = FlowContext()
|
|
30
|
+
ctx.set("query_vector", [0.1, 0.2])
|
|
31
|
+
out = await skill.process(ctx)
|
|
32
|
+
assert out.get("qdrant_results")[0]["score"] == 0.9
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def test_empty_vector_returns_empty():
|
|
36
|
+
skill = QdrantSkill()
|
|
37
|
+
skill.init({"collection": "docs"})
|
|
38
|
+
out = await skill.process(FlowContext())
|
|
39
|
+
assert out.get("qdrant_results") == []
|