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 +62 -0
- moss/__init__.pyi +248 -0
- moss/client/__init__.py +0 -0
- moss/client/moss_client.py +290 -0
- moss/py.typed +0 -0
- moss/services/__init__.py +1 -0
- moss-1.0.0.dist-info/METADATA +285 -0
- moss-1.0.0.dist-info/RECORD +11 -0
- moss-1.0.0.dist-info/WHEEL +5 -0
- moss-1.0.0.dist-info/licenses/LICENSE.txt +168 -0
- moss-1.0.0.dist-info/top_level.txt +1 -0
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
|
+
]
|
moss/client/__init__.py
ADDED
|
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,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
|