moss 1.0.0__py3-none-any.whl

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.
moss/__init__.py ADDED
@@ -0,0 +1,62 @@
1
+ """
2
+ Moss Semantic Search SDK
3
+
4
+ Powerful Python SDK for semantic search using state-of-the-art embedding models.
5
+
6
+ Example:
7
+ ```python
8
+ from moss import MossClient, DocumentInfo
9
+
10
+ client = MossClient('your-project-id', 'your-project-key')
11
+
12
+ docs = [DocumentInfo(id="1", text="Example document")]
13
+
14
+ result = await client.create_index('my-index', docs, 'moss-minilm')
15
+
16
+ await client.load_index('my-index')
17
+ results = await client.query('my-index', 'search query')
18
+ ```
19
+ """
20
+
21
+ from moss_core import (
22
+ DocumentInfo,
23
+ GetDocumentsOptions,
24
+ IndexInfo,
25
+ IndexStatus,
26
+ IndexStatusValues,
27
+ ModelRef,
28
+ MutationOptions,
29
+ MutationResult,
30
+ JobStatus,
31
+ JobPhase,
32
+ JobProgress,
33
+ JobStatusResponse,
34
+ QueryOptions,
35
+ QueryResultDocumentInfo,
36
+ SearchResult,
37
+ )
38
+
39
+ from .client.moss_client import MossClient
40
+
41
+ __version__ = "1.0.0"
42
+
43
+ __all__ = [
44
+ "MossClient",
45
+ # Core data types
46
+ "DocumentInfo",
47
+ "GetDocumentsOptions",
48
+ "IndexInfo",
49
+ "SearchResult",
50
+ "QueryResultDocumentInfo",
51
+ "ModelRef",
52
+ "IndexStatus",
53
+ "IndexStatusValues",
54
+ "QueryOptions",
55
+ # Mutation types
56
+ "MutationResult",
57
+ "MutationOptions",
58
+ "JobStatus",
59
+ "JobPhase",
60
+ "JobProgress",
61
+ "JobStatusResponse",
62
+ ]
moss/__init__.pyi ADDED
@@ -0,0 +1,248 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import ClassVar, Dict, List, Optional, Sequence
4
+
5
+
6
+ class MossClient:
7
+ """Semantic search client for vector similarity operations."""
8
+
9
+ DEFAULT_MODEL_ID: ClassVar[str]
10
+
11
+ def __init__(self, project_id: str, project_key: str) -> None: ...
12
+
13
+ async def create_index(
14
+ self,
15
+ name: str,
16
+ docs: List[DocumentInfo],
17
+ model_id: Optional[str] = ...,
18
+ ) -> MutationResult: ...
19
+
20
+ async def add_docs(
21
+ self,
22
+ name: str,
23
+ docs: List[DocumentInfo],
24
+ options: Optional[MutationOptions] = None,
25
+ ) -> MutationResult: ...
26
+
27
+ async def delete_docs(
28
+ self,
29
+ name: str,
30
+ doc_ids: List[str],
31
+ ) -> MutationResult: ...
32
+
33
+ async def get_job_status(self, job_id: str) -> JobStatusResponse: ...
34
+
35
+ async def get_index(self, name: str) -> IndexInfo: ...
36
+
37
+ async def list_indexes(self) -> List[IndexInfo]: ...
38
+
39
+ async def delete_index(self, name: str) -> bool: ...
40
+
41
+ async def get_docs(
42
+ self,
43
+ name: str,
44
+ options: Optional[GetDocumentsOptions] = None,
45
+ ) -> List[DocumentInfo]: ...
46
+
47
+ async def load_index(
48
+ self,
49
+ name: str,
50
+ auto_refresh: bool = False,
51
+ polling_interval_in_seconds: int = 600,
52
+ ) -> str: ...
53
+
54
+ async def unload_index(self, name: str) -> None: ...
55
+
56
+ async def query(
57
+ self,
58
+ name: str,
59
+ query: str,
60
+ options: Optional[QueryOptions] = None,
61
+ ) -> SearchResult: ...
62
+
63
+
64
+ class MutationResult:
65
+ """Return value from create_index/add_docs/delete_docs."""
66
+
67
+ job_id: str
68
+ index_name: str
69
+ doc_count: int
70
+
71
+
72
+ class MutationOptions:
73
+ """Options for add_docs (e.g. upsert behavior)."""
74
+
75
+ upsert: Optional[bool]
76
+
77
+ def __init__(self, upsert: Optional[bool] = None) -> None: ...
78
+
79
+
80
+ class GetDocumentsOptions:
81
+ """Options for get_docs (e.g. filter by document IDs)."""
82
+
83
+ doc_ids: Optional[List[str]]
84
+
85
+ def __init__(self, doc_ids: Optional[List[str]] = None) -> None: ...
86
+
87
+
88
+ class JobStatus:
89
+ """Enum-like class for job status values."""
90
+
91
+ PENDING_UPLOAD: ClassVar[str]
92
+ UPLOADING: ClassVar[str]
93
+ BUILDING: ClassVar[str]
94
+ COMPLETED: ClassVar[str]
95
+ FAILED: ClassVar[str]
96
+
97
+ value: str
98
+
99
+
100
+ class JobPhase:
101
+ """Enum-like class for job phase values."""
102
+
103
+ DOWNLOADING: ClassVar[str]
104
+ DESERIALIZING: ClassVar[str]
105
+ GENERATING_EMBEDDINGS: ClassVar[str]
106
+ BUILDING_INDEX: ClassVar[str]
107
+ UPLOADING: ClassVar[str]
108
+ CLEANUP: ClassVar[str]
109
+
110
+ value: str
111
+
112
+
113
+ class JobProgress:
114
+ """Progress update for a job."""
115
+
116
+ job_id: str
117
+ status: JobStatus
118
+ progress: float
119
+ current_phase: Optional[JobPhase]
120
+
121
+
122
+ class JobStatusResponse:
123
+ """Full status response from get_job_status."""
124
+
125
+ job_id: str
126
+ status: JobStatus
127
+ progress: float
128
+ current_phase: Optional[JobPhase]
129
+ error: Optional[str]
130
+ created_at: str
131
+ updated_at: str
132
+ completed_at: Optional[str]
133
+
134
+
135
+ class ModelRef:
136
+ id: str
137
+ version: str
138
+ def __init__(self, id: str, version: str) -> None: ...
139
+
140
+
141
+ class QueryResultDocumentInfo:
142
+ id: str
143
+ text: str
144
+ metadata: Optional[Dict[str, str]]
145
+ score: float
146
+ def __init__(
147
+ self,
148
+ id: str,
149
+ text: str,
150
+ metadata: Optional[Dict[str, str]] = ...,
151
+ score: float = ...,
152
+ ) -> None: ...
153
+
154
+
155
+ class DocumentInfo:
156
+ id: str
157
+ text: str
158
+ metadata: Optional[Dict[str, str]]
159
+ embedding: Optional[Sequence[float]]
160
+ def __init__(
161
+ self,
162
+ id: str,
163
+ text: str,
164
+ metadata: Optional[Dict[str, str]] = ...,
165
+ embedding: Optional[Sequence[float]] = ...,
166
+ ) -> None: ...
167
+
168
+
169
+ class QueryOptions:
170
+ embedding: Optional[Sequence[float]]
171
+ top_k: Optional[int]
172
+ alpha: Optional[float]
173
+ filter: Optional[dict]
174
+ def __init__(
175
+ self,
176
+ embedding: Optional[Sequence[float]] = ...,
177
+ top_k: Optional[int] = ...,
178
+ alpha: Optional[float] = ...,
179
+ filter: Optional[dict] = ...,
180
+ ) -> None: ...
181
+
182
+
183
+ class IndexInfo:
184
+ id: str
185
+ name: str
186
+ version: str
187
+ status: str
188
+ doc_count: int
189
+ created_at: str
190
+ updated_at: str
191
+ model: ModelRef
192
+ def __init__(
193
+ self,
194
+ id: str,
195
+ name: str,
196
+ version: str,
197
+ status: str,
198
+ doc_count: int,
199
+ created_at: str,
200
+ updated_at: str,
201
+ model: ModelRef,
202
+ ) -> None: ...
203
+
204
+
205
+ class SearchResult:
206
+ docs: List[QueryResultDocumentInfo]
207
+ query: str
208
+ index_name: Optional[str]
209
+ time_taken_ms: Optional[int]
210
+ def __init__(
211
+ self,
212
+ docs: List[QueryResultDocumentInfo],
213
+ query: str,
214
+ index_name: Optional[str] = None,
215
+ time_taken_ms: Optional[int] = None,
216
+ ) -> None: ...
217
+
218
+
219
+ class IndexStatus:
220
+ NotStarted: ClassVar[str]
221
+ Building: ClassVar[str]
222
+ Ready: ClassVar[str]
223
+ Failed: ClassVar[str]
224
+ def __init__(self, value: str) -> None: ...
225
+
226
+
227
+ IndexStatusValues: Dict[str, str]
228
+
229
+ __version__: str
230
+
231
+ __all__ = [
232
+ "MossClient",
233
+ "DocumentInfo",
234
+ "GetDocumentsOptions",
235
+ "IndexInfo",
236
+ "SearchResult",
237
+ "QueryResultDocumentInfo",
238
+ "ModelRef",
239
+ "IndexStatus",
240
+ "IndexStatusValues",
241
+ "QueryOptions",
242
+ "MutationResult",
243
+ "MutationOptions",
244
+ "JobStatus",
245
+ "JobPhase",
246
+ "JobProgress",
247
+ "JobStatusResponse",
248
+ ]
File without changes
@@ -0,0 +1,290 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ import os
6
+ import uuid
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ import httpx
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ from moss_core import (
14
+ CLOUD_API_MANAGE_URL,
15
+ ManageClient,
16
+ DocumentInfo,
17
+ GetDocumentsOptions,
18
+ IndexInfo,
19
+ IndexManager,
20
+ MutationOptions,
21
+ MutationResult,
22
+ JobStatusResponse,
23
+ QueryOptions,
24
+ QueryResultDocumentInfo,
25
+ SearchResult,
26
+ )
27
+
28
+
29
+ def _get_manage_url() -> str:
30
+ """Manage URL, overridable via env for local development."""
31
+ return os.getenv("MOSS_CLOUD_API_MANAGE_URL", CLOUD_API_MANAGE_URL)
32
+
33
+
34
+ def _get_query_url() -> str:
35
+ """Query URL, derived from manage URL or overridable via env."""
36
+ explicit = os.getenv("MOSS_CLOUD_QUERY_URL")
37
+ if explicit:
38
+ return explicit
39
+ return _get_manage_url().replace("/v1/manage", "/query")
40
+
41
+
42
+ class MossClient:
43
+ """
44
+ Semantic search client for vector similarity operations.
45
+
46
+ All mutations and reads go through the Rust ManageClient.
47
+ Querying uses the local IndexManager when the index is loaded,
48
+ otherwise falls back to the cloud query API.
49
+
50
+ Example:
51
+ ```python
52
+ from moss import MossClient, DocumentInfo
53
+
54
+ client = MossClient("project-id", "project-key")
55
+
56
+ docs = [DocumentInfo(id="1", text="Machine learning fundamentals")]
57
+ result = await client.create_index("my-index", docs, "moss-minilm")
58
+
59
+ await client.load_index("my-index")
60
+ results = await client.query("my-index", "AI and neural networks")
61
+ ```
62
+ """
63
+
64
+ DEFAULT_MODEL_ID = "moss-minilm"
65
+
66
+ def __init__(self, project_id: str, project_key: str) -> None:
67
+ self._project_id = project_id
68
+ self._project_key = project_key
69
+ self._client_id = str(uuid.uuid4())
70
+ manage_url = _get_manage_url()
71
+ self._manage = ManageClient(project_id, project_key, manage_url, self._client_id)
72
+ self._manager = IndexManager(project_id, project_key, manage_url, self._client_id)
73
+
74
+ # -- Mutations (via Rust ManageClient) --------------------------
75
+
76
+ async def create_index(
77
+ self,
78
+ name: str,
79
+ docs: List[DocumentInfo],
80
+ model_id: Optional[str] = None,
81
+ ) -> MutationResult:
82
+ """Create a new index and populate it with documents."""
83
+ resolved_model_id = self._resolve_model_id(docs, model_id)
84
+ return await asyncio.to_thread(
85
+ self._manage.create_index, name, docs, resolved_model_id,
86
+ )
87
+
88
+ async def add_docs(
89
+ self,
90
+ name: str,
91
+ docs: List[DocumentInfo],
92
+ options: Optional[MutationOptions] = None,
93
+ ) -> MutationResult:
94
+ """Add or update documents in an index."""
95
+ return await asyncio.to_thread(
96
+ self._manage.add_docs, name, docs, options,
97
+ )
98
+
99
+ async def delete_docs(
100
+ self,
101
+ name: str,
102
+ doc_ids: List[str],
103
+ ) -> MutationResult:
104
+ """Delete documents from an index by their IDs."""
105
+ return await asyncio.to_thread(
106
+ self._manage.delete_docs, name, doc_ids,
107
+ )
108
+
109
+ async def get_job_status(self, job_id: str) -> JobStatusResponse:
110
+ """Get the status of a bulk operation job."""
111
+ return await asyncio.to_thread(self._manage.get_job_status, job_id)
112
+
113
+ # -- Read operations (via Rust ManageClient) --------------------
114
+
115
+ async def get_index(self, name: str) -> IndexInfo:
116
+ """Get information about a specific index."""
117
+ return await asyncio.to_thread(self._manage.get_index, name)
118
+
119
+ async def list_indexes(self) -> List[IndexInfo]:
120
+ """List all indexes with their information."""
121
+ return await asyncio.to_thread(self._manage.list_indexes)
122
+
123
+ async def delete_index(self, name: str) -> bool:
124
+ """Delete an index and all its data."""
125
+ return await asyncio.to_thread(self._manage.delete_index, name)
126
+
127
+ async def get_docs(
128
+ self,
129
+ name: str,
130
+ options: Optional[GetDocumentsOptions] = None,
131
+ ) -> List[DocumentInfo]:
132
+ """Retrieve documents from an index."""
133
+ return await asyncio.to_thread(self._manage.get_docs, name, options)
134
+
135
+ # -- Index loading & querying -----------------------------------
136
+
137
+ async def load_index(
138
+ self,
139
+ name: str,
140
+ auto_refresh: bool = False,
141
+ polling_interval_in_seconds: int = 600,
142
+ ) -> str:
143
+ """
144
+ Downloads an index from the cloud into memory for fast local querying.
145
+
146
+ Without load_index(), query() falls back to the cloud API (~100-500ms).
147
+ With load_index(), queries run entirely in-memory (~1-10ms).
148
+ """
149
+ try:
150
+ await asyncio.to_thread(
151
+ self._manager.load_index, name, auto_refresh, polling_interval_in_seconds,
152
+ )
153
+ await asyncio.to_thread(self._manager.load_query_model, name)
154
+ return name
155
+ except RuntimeError as e:
156
+ raise RuntimeError(f"Failed to load index '{name}': {e}") from e
157
+
158
+ async def unload_index(self, name: str) -> None:
159
+ """Unload an index from memory."""
160
+ try:
161
+ await asyncio.to_thread(self._manager.unload_index, name)
162
+ except RuntimeError as e:
163
+ raise RuntimeError(f"Failed to unload index '{name}': {e}") from e
164
+
165
+ async def query(
166
+ self,
167
+ name: str,
168
+ query: str,
169
+ options: Optional[QueryOptions] = None,
170
+ ) -> SearchResult:
171
+ """
172
+ Perform a semantic similarity search.
173
+
174
+ If the index is loaded locally (via load_index), queries run in-memory.
175
+ Otherwise, falls back to the cloud query API.
176
+
177
+ Args:
178
+ options: Query options (top_k, alpha, embedding, filter). Example filter:
179
+ QueryOptions(filter={"$and": [
180
+ {"field": "city", "condition": {"$eq": "NYC"}},
181
+ {"field": "price", "condition": {"$lt": "50"}},
182
+ ]})
183
+ """
184
+ is_loaded = await asyncio.to_thread(self._manager.has_index, name)
185
+
186
+ if is_loaded:
187
+ return await self._query_local(name, query, options)
188
+
189
+ if getattr(options, "filter", None) is not None:
190
+ logger.warning(
191
+ "Metadata filter ignored: filtering is only supported for locally loaded indexes. "
192
+ "Call load_index('%s') first.",
193
+ name,
194
+ )
195
+ return await self._query_cloud(name, query, options)
196
+
197
+ # -- Internal ---------------------------------------------------
198
+
199
+ async def _query_local(
200
+ self,
201
+ name: str,
202
+ query: str,
203
+ options: Optional[QueryOptions],
204
+ ) -> SearchResult:
205
+ top_k = getattr(options, "top_k", None) or 5
206
+ alpha = getattr(options, "alpha", None) or 0.8
207
+ query_embedding = getattr(options, "embedding", None)
208
+ filter = getattr(options, "filter", None)
209
+
210
+ if query_embedding is None:
211
+ try:
212
+ return await asyncio.to_thread(
213
+ self._manager.query_text, name, query, top_k, alpha, filter,
214
+ )
215
+ except RuntimeError as e:
216
+ if "requires explicit query embeddings" in str(e):
217
+ raise ValueError(
218
+ "This index uses custom embeddings. "
219
+ "Query embeddings must be provided via QueryOptions.embedding."
220
+ ) from e
221
+ raise
222
+
223
+ return await asyncio.to_thread(
224
+ self._manager.query, name, query, list(query_embedding), top_k, alpha, filter,
225
+ )
226
+
227
+ async def _query_cloud(
228
+ self,
229
+ name: str,
230
+ query: str,
231
+ options: Optional[QueryOptions],
232
+ ) -> SearchResult:
233
+ """Fallback: query via the cloud API when the index is not loaded locally."""
234
+ top_k = getattr(options, "top_k", None) or 10
235
+ query_embedding = getattr(options, "embedding", None)
236
+
237
+ request_body: Dict[str, Any] = {
238
+ "query": query,
239
+ "indexName": name,
240
+ "projectId": self._project_id,
241
+ "projectKey": self._project_key,
242
+ "topK": top_k,
243
+ }
244
+ if query_embedding is not None:
245
+ request_body["queryEmbedding"] = list(query_embedding)
246
+
247
+ try:
248
+ async with httpx.AsyncClient(timeout=60.0) as client:
249
+ response = await client.post(
250
+ _get_query_url(),
251
+ headers={"Content-Type": "application/json"},
252
+ json=request_body,
253
+ )
254
+ if not response.is_success:
255
+ raise Exception(f"HTTP error! status: {response.status_code}")
256
+ data = response.json()
257
+ except httpx.RequestError as error:
258
+ raise Exception(f"Cloud query request failed: {str(error)}")
259
+
260
+ return self._dict_to_search_result(data)
261
+
262
+ @staticmethod
263
+ def _dict_to_search_result(data: dict) -> SearchResult:
264
+ docs = [
265
+ QueryResultDocumentInfo(
266
+ id=d.get("id", ""),
267
+ text=d.get("text", ""),
268
+ metadata=d.get("metadata"),
269
+ score=float(d.get("score", 0.0)),
270
+ )
271
+ for d in data.get("docs", [])
272
+ ]
273
+ return SearchResult(
274
+ docs=docs,
275
+ query=data.get("query", ""),
276
+ index_name=data.get("indexName"),
277
+ time_taken_ms=data.get("timeTakenMs"),
278
+ )
279
+
280
+ def _resolve_model_id(
281
+ self,
282
+ docs: List[DocumentInfo],
283
+ model_id: Optional[str],
284
+ ) -> str:
285
+ if model_id is not None:
286
+ return model_id
287
+ has_embeddings = any(
288
+ getattr(doc, "embedding", None) is not None for doc in docs
289
+ )
290
+ return "custom" if has_embeddings else self.DEFAULT_MODEL_ID
moss/py.typed ADDED
File without changes
@@ -0,0 +1 @@
1
+ """Internal services package."""
@@ -0,0 +1,285 @@
1
+ Metadata-Version: 2.4
2
+ Name: moss
3
+ Version: 1.0.0
4
+ Summary: Python SDK for semantic search with on-device AI capabilities
5
+ Author-email: "InferEdge Inc." <contact@usemoss.dev>
6
+ Project-URL: Homepage, https://github.com/usemoss/moss-samples
7
+ Project-URL: Repository, https://github.com/usemoss/moss-samples
8
+ Project-URL: Documentation, https://docs.usemoss.dev/
9
+ Keywords: search,semantic,embeddings,vector,usemoss,moss
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE.txt
23
+ Requires-Dist: typing-extensions>=4.0.0
24
+ Requires-Dist: httpx>=0.25.0
25
+ Requires-Dist: inferedge-moss-core==0.8.7
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.4.2; extra == "dev"
28
+ Requires-Dist: pytest-asyncio>=1.2.0; extra == "dev"
29
+ Requires-Dist: tox>=4.0.0; extra == "dev"
30
+ Requires-Dist: black>=25.9.0; extra == "dev"
31
+ Requires-Dist: isort>=7.0.0; extra == "dev"
32
+ Requires-Dist: flake8>=7.3.0; extra == "dev"
33
+ Requires-Dist: mypy>=1.18.2; extra == "dev"
34
+ Requires-Dist: python-dotenv>=1.0.0; extra == "dev"
35
+ Requires-Dist: pdoc>=15.0.4; extra == "dev"
36
+ Requires-Dist: pydoc-markdown>=4.8.2; extra == "dev"
37
+ Requires-Dist: griffe>=0.40.0; extra == "dev"
38
+ Requires-Dist: pyyaml>=6.0; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ # Moss client library for Python
42
+
43
+ `moss` enables **private, on-device semantic search** in your Python applications with cloud storage capabilities.
44
+
45
+ Built for developers who want **instant, memory-efficient, privacy-first AI features** with seamless cloud integration.
46
+
47
+ ## ✨ Features
48
+
49
+ - ⚡ **On-Device Vector Search** - Sub-millisecond retrieval with zero network latency
50
+ - 🔍 **Semantic, Keyword & Hybrid Search** - Embedding search blended with Keyword matching
51
+ - ☁️ **Cloud Storage Integration** - Automatic index synchronization with cloud storage
52
+ - 📦 **Multi-Index Support** - Manage multiple isolated search spaces
53
+ - 🛡️ **Privacy-First by Design** - Computation happens locally, only indexes sync to cloud
54
+ - 🚀 **High-Performance Rust Core** - Built on optimized Rust bindings for maximum speed
55
+ - 🧠 **Custom Embedding Overrides** - Provide your own document and query vectors when you need full control
56
+
57
+ ## 📦 Installation
58
+
59
+ ```bash
60
+ pip install moss
61
+ ```
62
+
63
+ ## 🚀 Quick Start
64
+
65
+ ```python
66
+ import asyncio
67
+ from moss import MossClient, DocumentInfo, QueryOptions
68
+
69
+ async def main():
70
+ # Initialize search client with project credentials
71
+ client = MossClient("your-project-id", "your-project-key")
72
+
73
+ # Prepare documents to index
74
+ documents = [
75
+ DocumentInfo(
76
+ id="doc1",
77
+ text="How do I track my order? You can track your order by logging into your account.",
78
+ metadata={"category": "shipping"}
79
+ ),
80
+ DocumentInfo(
81
+ id="doc2",
82
+ text="What is your return policy? We offer a 30-day return policy for most items.",
83
+ metadata={"category": "returns"}
84
+ ),
85
+ DocumentInfo(
86
+ id="doc3",
87
+ text="How can I change my shipping address? Contact our customer service team.",
88
+ metadata={"category": "support"}
89
+ )
90
+ ]
91
+
92
+ # Create an index with documents (syncs to cloud)
93
+ index_name = "faqs"
94
+ await client.create_index(index_name, documents) # Defaults to moss-minilm
95
+ print("Index created and synced to cloud!")
96
+
97
+ # Load the index (from cloud or local cache)
98
+ await client.load_index(index_name)
99
+
100
+ # Search the index
101
+ result = await client.query(
102
+ index_name,
103
+ "How do I return a damaged product?",
104
+ QueryOptions(top_k=3, alpha=0.6),
105
+ )
106
+
107
+ # Display results
108
+ print(f"Query: {result.query}")
109
+ for doc in result.docs:
110
+ print(f"Score: {doc.score:.4f}")
111
+ print(f"ID: {doc.id}")
112
+ print(f"Text: {doc.text}")
113
+ print("---")
114
+
115
+ asyncio.run(main())
116
+ ```
117
+
118
+ ## 🔥 Example Use Cases
119
+
120
+ - Smart knowledge base search with cloud backup
121
+ - Realtime Voice AI agents with persistent indexes
122
+ - Personal note-taking search with sync across devices
123
+ - Private in-app AI features with cloud storage
124
+ - Local semantic search in edge devices with cloud fallback
125
+
126
+ ## Available Models
127
+
128
+ - `moss-minilm`: Lightweight model optimized for speed and efficiency
129
+ - `moss-mediumlm`: Balanced model offering higher accuracy with reasonable performance
130
+
131
+ ## 🔧 Getting Started
132
+
133
+ ### Prerequisites
134
+
135
+ - Python 3.8 or higher
136
+ - Valid InferEdge project credentials
137
+
138
+ ### Environment Setup
139
+
140
+ 1. **Install the package:**
141
+
142
+ ```bash
143
+ pip install moss
144
+ ```
145
+
146
+ 2. **Get your credentials:**
147
+
148
+ Sign up at [InferEdge Platform](https://platform.inferedge.dev) to get your `project_id` and `project_key`.
149
+
150
+ 3. **Set up environment variables (optional):**
151
+
152
+ ```bash
153
+ export MOSS_PROJECT_ID="your-project-id"
154
+ export MOSS_PROJECT_KEY="your-project-key"
155
+ ```
156
+
157
+ ### Basic Usage
158
+
159
+ ```python
160
+ import asyncio
161
+ from moss import MossClient, DocumentInfo, QueryOptions
162
+
163
+ async def main():
164
+ # Initialize client
165
+ client = MossClient("your-project-id", "your-project-key")
166
+
167
+ # Create and populate an index
168
+ documents = [
169
+ DocumentInfo(id="1", text="Python is a programming language"),
170
+ DocumentInfo(id="2", text="Machine learning with Python is popular"),
171
+ ]
172
+
173
+ await client.create_index("my-docs", documents)
174
+ await client.load_index("my-docs")
175
+
176
+ # Search
177
+ results = await client.query(
178
+ "my-docs",
179
+ "programming language",
180
+ QueryOptions(alpha=1.0),
181
+ )
182
+ for doc in results.docs:
183
+ print(f"{doc.id}: {doc.text} (score: {doc.score:.3f})")
184
+
185
+ asyncio.run(main())
186
+ ```
187
+
188
+ ### Hybrid Search Controls
189
+
190
+ `alpha` lets you decide how much weight to give semantic similarity versus keyword relevance when running `query()`:
191
+
192
+ ```python
193
+ # Pure keyword search
194
+ await client.query("my-docs", "programming language", QueryOptions(alpha=0.0))
195
+
196
+ # Mixed results (default 0.8 => semantic heavy)
197
+ await client.query("my-docs", "programming language")
198
+
199
+ # Pure embedding search
200
+ await client.query("my-docs", "programming language", QueryOptions(alpha=1.0))
201
+ ```
202
+
203
+ Pick any value between 0.0 and 1.0 to tune the blend for your use case.
204
+
205
+ ### Metadata filtering
206
+
207
+ You can pass a metadata filter directly to `query()` after loading an index locally:
208
+
209
+ ```python
210
+ results = await client.query(
211
+ "my-docs",
212
+ "running shoes",
213
+ QueryOptions(top_k=5, alpha=0.6),
214
+ filter={
215
+ "$and": [
216
+ {"field": "category", "condition": {"$eq": "shoes"}},
217
+ {"field": "price", "condition": {"$lt": "100"}},
218
+ ]
219
+ },
220
+ )
221
+ ```
222
+
223
+ For a complete runnable example, see `python/user-facing-sdk/samples/metadata_filtering.py`.
224
+
225
+ ## 🧠 Providing custom embeddings
226
+
227
+ Already using your own embedding model? Supply vectors directly when managing
228
+ indexes and queries:
229
+
230
+ ```python
231
+ import asyncio
232
+
233
+ from moss import DocumentInfo, MossClient, QueryOptions
234
+
235
+
236
+ def my_embedding_model(text: str) -> list[float]:
237
+ """Placeholder for your custom embedding generator."""
238
+ ...
239
+
240
+
241
+ async def main() -> None:
242
+ client = MossClient("your-project-id", "your-project-key")
243
+
244
+ documents = [
245
+ DocumentInfo(
246
+ id="doc-1",
247
+ text="Attach a caller-provided embedding.",
248
+ embedding=my_embedding_model("doc-1"),
249
+ ),
250
+ DocumentInfo(
251
+ id="doc-2",
252
+ text="Fallback to the built-in model when the field is omitted.",
253
+ embedding=my_embedding_model("doc-2"),
254
+ ),
255
+ ]
256
+
257
+ await client.create_index("custom-embeddings", documents) # Defaults to moss-minilm
258
+ await client.load_index("custom-embeddings")
259
+
260
+ results = await client.query(
261
+ "custom-embeddings",
262
+ "<query text>",
263
+ QueryOptions(embedding=my_embedding_model("<query text>"), top_k=10),
264
+ )
265
+
266
+ print(results.docs[0].id, results.docs[0].score)
267
+
268
+
269
+ asyncio.run(main())
270
+ ```
271
+
272
+ Leaving the model argument undefined defaults to `moss-minilm`.
273
+ Pass `QueryOptions` to reuse your own embeddings or to override `top_k` on a per-query basis.
274
+
275
+ ## 📄 License
276
+
277
+ This package is licensed under the [PolyForm Shield License 1.0.0](./LICENSE.txt).
278
+
279
+ - ✅ Free for testing, evaluation, internal use, and modifications.
280
+ - ❌ Not permitted for production or competing commercial use.
281
+ - 📩 For commercial licenses, contact: <contact@usemoss.dev>
282
+
283
+ ## 📬 Contact
284
+
285
+ For support, commercial licensing, or partnership inquiries, contact us: [contact@usemoss.dev](mailto:contact@usemoss.dev)
@@ -0,0 +1,11 @@
1
+ moss/__init__.py,sha256=nDKEGYA-_subR84YyGeyWpSraqbbia6qRUVkn6I4UXw,1266
2
+ moss/__init__.pyi,sha256=PQGokkArNzroFjpGvMKFSCQDlzNgePqu4EYtRoX2yfs,5339
3
+ moss/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ moss/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ moss/client/moss_client.py,sha256=_UM9PclligFxChkZ0UreNi3e0MvyQXo50kWRL8BucdE,9829
6
+ moss/services/__init__.py,sha256=VbNr33QCf-t2exytCmtZt0JpaZdvv_pihNvFDRROwvM,33
7
+ moss-1.0.0.dist-info/licenses/LICENSE.txt,sha256=nHPtyobLLM12cc5izwX4NMoIzLf35_0Z9LI3dlCUhF4,5923
8
+ moss-1.0.0.dist-info/METADATA,sha256=1OZoZxUiKnbhmTijDHjO-NgOukMRMnu8_gEkk-P8OmM,8894
9
+ moss-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
10
+ moss-1.0.0.dist-info/top_level.txt,sha256=Ukhq5EDBYCpv9V5DNk9r4qcWGtmf4g4xt-rY1fFdbaQ,5
11
+ moss-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,168 @@
1
+ Copyright (c) 2025 InferEdge Inc.
2
+ Required Notice: Copyright InferEdge Inc. (https://inferedge.dev)
3
+ Licensor Line of Business: InferEdge MOSS Runtime (https://inferedge.dev)
4
+
5
+ # PolyForm Shield License 1.0.0
6
+
7
+ <https://polyformproject.org/licenses/shield/1.0.0>
8
+
9
+ ## Acceptance
10
+
11
+ In order to get any license under these terms, you must agree
12
+ to them as both strict obligations and conditions to all
13
+ your licenses.
14
+
15
+ ## Copyright License
16
+
17
+ The licensor grants you a copyright license for the
18
+ software to do everything you might do with the software
19
+ that would otherwise infringe the licensor's copyright
20
+ in it for any permitted purpose. However, you may
21
+ only distribute the software according to [Distribution
22
+ License](#distribution-license) and make changes or new works
23
+ based on the software according to [Changes and New Works
24
+ License](#changes-and-new-works-license).
25
+
26
+ ## Distribution License
27
+
28
+ The licensor grants you an additional copyright license
29
+ to distribute copies of the software. Your license
30
+ to distribute covers distributing the software with
31
+ changes and new works permitted by [Changes and New Works
32
+ License](#changes-and-new-works-license).
33
+
34
+ ## Notices
35
+
36
+ You must ensure that anyone who gets a copy of any part of
37
+ the software from you also gets a copy of these terms or the
38
+ URL for them above, as well as copies of any plain-text lines
39
+ beginning with `Required Notice:` that the licensor provided
40
+ with the software. For example:
41
+
42
+ > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
43
+
44
+ ## Changes and New Works License
45
+
46
+ The licensor grants you an additional copyright license to
47
+ make changes and new works based on the software for any
48
+ permitted purpose.
49
+
50
+ ## Patent License
51
+
52
+ The licensor grants you a patent license for the software that
53
+ covers patent claims the licensor can license, or becomes able
54
+ to license, that you would infringe by using the software.
55
+
56
+ ## Noncompete
57
+
58
+ Any purpose is a permitted purpose, except for providing any
59
+ product that competes with the software or any product the
60
+ licensor or any of its affiliates provides using the software.
61
+
62
+ ## Competition
63
+
64
+ Goods and services compete even when they provide functionality
65
+ through different kinds of interfaces or for different technical
66
+ platforms. Applications can compete with services, libraries
67
+ with plugins, frameworks with development tools, and so on,
68
+ even if they're written in different programming languages
69
+ or for different computer architectures. Goods and services
70
+ compete even when provided free of charge. If you market a
71
+ product as a practical substitute for the software or another
72
+ product, it definitely competes.
73
+
74
+ ## New Products
75
+
76
+ If you are using the software to provide a product that does
77
+ not compete, but the licensor or any of its affiliates brings
78
+ your product into competition by providing a new version of
79
+ the software or another product using the software, you may
80
+ continue using versions of the software available under these
81
+ terms beforehand to provide your competing product, but not
82
+ any later versions.
83
+
84
+ ## Discontinued Products
85
+
86
+ You may begin using the software to compete with a product
87
+ or service that the licensor or any of its affiliates has
88
+ stopped providing, unless the licensor includes a plain-text
89
+ line beginning with `Licensor Line of Business:` with the
90
+ software that mentions that line of business. For example:
91
+
92
+ > Licensor Line of Business: YoyodyneCMS Content Management
93
+ System (http://example.com/cms)
94
+
95
+ ## Sales of Business
96
+
97
+ If the licensor or any of its affiliates sells a line of
98
+ business developing the software or using the software
99
+ to provide a product, the buyer can also enforce
100
+ [Noncompete](#noncompete) for that product.
101
+
102
+ ## Fair Use
103
+
104
+ You may have "fair use" rights for the software under the
105
+ law. These terms do not limit them.
106
+
107
+ ## No Other Rights
108
+
109
+ These terms do not allow you to sublicense or transfer any of
110
+ your licenses to anyone else, or prevent the licensor from
111
+ granting licenses to anyone else. These terms do not imply
112
+ any other licenses.
113
+
114
+ ## Patent Defense
115
+
116
+ If you make any written claim that the software infringes or
117
+ contributes to infringement of any patent, your patent license
118
+ for the software granted under these terms ends immediately. If
119
+ your company makes such a claim, your patent license ends
120
+ immediately for work on behalf of your company.
121
+
122
+ ## Violations
123
+
124
+ The first time you are notified in writing that you have
125
+ violated any of these terms, or done anything with the software
126
+ not covered by your licenses, your licenses can nonetheless
127
+ continue if you come into full compliance with these terms,
128
+ and take practical steps to correct past violations, within
129
+ 32 days of receiving notice. Otherwise, all your licenses
130
+ end immediately.
131
+
132
+ ## No Liability
133
+
134
+ ***As far as the law allows, the software comes as is, without
135
+ any warranty or condition, and the licensor will not be liable
136
+ to you for any damages arising out of these terms or the use
137
+ or nature of the software, under any kind of legal claim.***
138
+
139
+ ## Definitions
140
+
141
+ The **licensor** is the individual or entity offering these
142
+ terms, and the **software** is the software the licensor makes
143
+ available under these terms.
144
+
145
+ A **product** can be a good or service, or a combination
146
+ of them.
147
+
148
+ **You** refers to the individual or entity agreeing to these
149
+ terms.
150
+
151
+ **Your company** is any legal entity, sole proprietorship,
152
+ or other kind of organization that you work for, plus all
153
+ its affiliates.
154
+
155
+ **Affiliates** means the other organizations than an
156
+ organization has control over, is under the control of, or is
157
+ under common control with.
158
+
159
+ **Control** means ownership of substantially all the assets of
160
+ an entity, or the power to direct its management and policies
161
+ by vote, contract, or otherwise. Control can be direct or
162
+ indirect.
163
+
164
+ **Your licenses** are all the licenses granted to you for the
165
+ software under these terms.
166
+
167
+ **Use** means anything you do with the software requiring one
168
+ of your licenses.
@@ -0,0 +1 @@
1
+ moss