Skip to content

Commit b2f1cee

Browse files
committed
updated py examples
1 parent ae9a30f commit b2f1cee

4 files changed

Lines changed: 246 additions & 6 deletions

File tree

examples/python/comprehensive_sample.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
GetDocumentsOptions,
2323
MossClient,
2424
MutationOptions,
25+
ParseFileInput,
2526
QueryOptions,
2627
)
2728

@@ -344,6 +345,24 @@ async def comprehensive_moss_example():
344345
deleted = await client.delete_index(index_name)
345346
print(f"Index deleted: {deleted}")
346347

348+
# Step 13: File-based index creation (optional — requires a PDF path).
349+
# Set MOSS_SAMPLE_PDF_PATH to a local PDF file to run this step.
350+
pdf_path = os.getenv("MOSS_SAMPLE_PDF_PATH")
351+
if pdf_path:
352+
file_index = f"file-index-demo-{timestamp}"
353+
print(f"\nStep 13: Creating index from file '{pdf_path}'...")
354+
try:
355+
file_result = await client.create_index_from_files(
356+
file_index,
357+
[ParseFileInput(name="sample.pdf", content_type="application/pdf", path=pdf_path)],
358+
)
359+
print(f"File index created (job: {file_result.job_id}, docs: {file_result.doc_count})")
360+
await client.delete_index(file_index)
361+
except Exception as fe:
362+
print(f"File index step skipped: {fe}")
363+
else:
364+
print("\nStep 13: Skipped (set MOSS_SAMPLE_PDF_PATH to a PDF file to demo create_index_from_files)")
365+
347366
print("\nComprehensive Moss SDK Example Completed Successfully!")
348367
print("=" * 60)
349368
print("Summary of operations performed:")
@@ -356,6 +375,7 @@ async def comprehensive_moss_example():
356375
print(" - Multiple semantic search operations")
357376
print(" - Document deletion")
358377
print(" - Index cleanup")
378+
print(" - File-based index creation (create_index_from_files)")
359379
print(" - Comprehensive error handling")
360380

361381
except Exception as error:

examples/python/multi_index_search.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,17 @@ async def multi_index_search_sample() -> None:
118118
print(f" created: {all_indexes}")
119119

120120
# load_indexes is best-effort: failures on individual names do not
121-
# roll back the others. Inspect ``load_result.failed`` to see
122-
# which (if any) names failed and why; downstream ops should use
121+
# roll back the others. ``load_result.failed`` is a dict mapping
122+
# name -> error string; downstream ops should use
123123
# ``load_result.loaded`` rather than the original list.
124124
print("\n2. Bulk-loading all three with load_indexes...")
125125
load_result: LoadIndexesResult = await client.load_indexes(all_indexes)
126126
print(f" loaded: {load_result.loaded}")
127-
print(f" failed: {load_result.failed}")
127+
if load_result.failed:
128+
for name, err in load_result.failed.items():
129+
print(f" failed: {name}{err}")
130+
else:
131+
print(" failed: none")
128132

129133
print("\n3. Querying across all loaded indexes in one call.")
130134
print(" Each result is tagged with its source index_name.")

examples/python/session_sample.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import asyncio
1515
import os
1616
from dotenv import load_dotenv
17-
from moss import MossClient, DocumentInfo, QueryOptions
17+
from moss import MossClient, DocumentInfo, QueryOptions, GetDocumentsOptions
1818

1919
# Load environment variables
2020
load_dotenv()
@@ -42,27 +42,53 @@ async def session_sample():
4242
# auto-loads; otherwise the session starts empty. No cloud round trip on add/query.
4343
print(f"\nOpening session '{index_name}'...")
4444
session = await client.session(index_name=index_name)
45-
print(f"Session open ({session.doc_count} existing docs)")
45+
print(f"Session '{session.name}' open ({session.doc_count} existing docs)")
4646

4747
# Add documents as they arrive (e.g. transcript turns). Embedded locally.
4848
print("\nAdding documents locally...")
4949
added, updated = await session.add_docs([
5050
DocumentInfo(id="turn-1", text="Customer was charged twice for the March renewal."),
5151
DocumentInfo(id="turn-2", text="Agent confirmed a refund for the duplicate charge."),
5252
DocumentInfo(id="turn-3", text="Customer also asked to cancel auto-renew."),
53+
DocumentInfo(id="turn-4", text="Agent placed a cancellation request for auto-renew."),
5354
])
5455
print(f"{added} added, {updated} updated ({session.doc_count} total)")
5556

57+
# Retrieve all documents currently in the session index.
58+
print("\nRetrieving all session documents...")
59+
all_docs = await session.get_docs()
60+
print(f"Found {len(all_docs)} docs:")
61+
for doc in all_docs:
62+
print(f" [{doc.id}] {doc.text}")
63+
64+
# Retrieve specific documents by ID.
65+
print("\nRetrieving specific documents by ID...")
66+
specific = await session.get_docs(GetDocumentsOptions(doc_ids=["turn-1", "turn-3"]))
67+
print(f"Fetched {len(specific)} docs:")
68+
for doc in specific:
69+
print(f" [{doc.id}] {doc.text}")
70+
5671
# Query the in-memory session (~1-10 ms, no network).
5772
print("\nQuerying the session...")
5873
results = await session.query("what did the customer want refunded", QueryOptions(top_k=3))
5974
for doc in results.docs:
6075
print(f" [{doc.id}] {doc.score:.3f} {doc.text}")
6176

77+
# Delete a document that is no longer needed.
78+
print("\nDeleting 'turn-4' from session...")
79+
deleted_count = await session.delete_docs(["turn-4"])
80+
print(f"Deleted {deleted_count} doc(s) ({session.doc_count} remaining)")
81+
82+
# Query again to confirm the deletion.
83+
print("\nQuerying after deletion...")
84+
results_after = await session.query("auto-renew cancellation", QueryOptions(top_k=3))
85+
for doc in results_after.docs:
86+
print(f" [{doc.id}] {doc.score:.3f} {doc.text}")
87+
6288
# Push the session to the cloud so another agent or device can resume it.
6389
print("\nPushing session to the cloud...")
6490
pushed = await session.push_index()
65-
print(f"Pushed {pushed.doc_count} docs (job {pushed.job_id})")
91+
print(f"Pushed {pushed.doc_count} docs to '{pushed.index_name}' (job {pushed.job_id}, status: {pushed.status})")
6692

6793
print("\nSample completed successfully!")
6894

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
from __future__ import annotations
2+
3+
import asyncio
4+
from typing import List, Optional, Tuple
5+
6+
from moss_core import (
7+
AddDocumentsOptions as _RustAddDocumentsOptions,
8+
DocumentInfo,
9+
GetDocumentsOptions,
10+
MutationOptions,
11+
QueryOptions,
12+
SearchResult,
13+
SessionIndex as _RustSessionIndex,
14+
PushIndexResult,
15+
)
16+
17+
18+
class SessionIndex:
19+
"""
20+
A local in-session index for real-time indexing and querying.
21+
22+
All operations (add_docs, delete_docs, query) run entirely in-memory
23+
with no cloud round trips. Call push_index() at session end to persist
24+
the index to the cloud for bookkeeping and future retrieval.
25+
26+
Usage telemetry is tracked and reported automatically in the background.
27+
28+
Example:
29+
```python
30+
# Auto-loads from cloud if the index exists, starts fresh if not
31+
session = await client.session(index_name="session-abc")
32+
33+
await session.add_docs([DocumentInfo(id="1", text="Customer asked about billing")])
34+
results = await session.query("billing question")
35+
36+
result = await session.push_index()
37+
# optionally: await client.get_job_status(result.job_id)
38+
```
39+
"""
40+
41+
def __init__(self, name: str, model_id: str, _inner: "_RustSessionIndex") -> None:
42+
self._model_id = model_id
43+
self._inner = _inner
44+
45+
@classmethod
46+
def _create(
47+
cls,
48+
name: str,
49+
model_id: str,
50+
project_id: str,
51+
project_key: str,
52+
client_id: Optional[str] = None,
53+
) -> "SessionIndex":
54+
inner = _RustSessionIndex(name, model_id, project_id, project_key, client_id)
55+
return cls(name, model_id, inner)
56+
57+
@property
58+
def name(self) -> str:
59+
"""The index name."""
60+
return self._inner.name
61+
62+
@property
63+
def doc_count(self) -> int:
64+
"""Number of documents in the local session index."""
65+
return self._inner.doc_count
66+
67+
async def add_docs(
68+
self,
69+
docs: List[DocumentInfo],
70+
options: Optional[MutationOptions] = None,
71+
) -> Tuple[int, int]:
72+
"""
73+
Add or update documents in the local session index.
74+
75+
Embeddings are generated locally via Rust core — no cloud round trip.
76+
77+
Args:
78+
docs: Documents to add. When using model_id='custom', each doc
79+
must have .embedding set.
80+
options: Mutation options (e.g. upsert behavior).
81+
82+
Returns:
83+
Tuple of (added_count, updated_count).
84+
"""
85+
rust_opts = None
86+
if options is not None:
87+
upsert = bool(options.upsert) if options.upsert is not None else True
88+
rust_opts = _RustAddDocumentsOptions(upsert=upsert)
89+
if self._model_id == "custom":
90+
embeddings = self._get_custom_embeddings(docs)
91+
return await asyncio.to_thread(
92+
self._inner.add_docs,
93+
docs,
94+
embeddings,
95+
rust_opts,
96+
)
97+
return await asyncio.to_thread(self._inner.add_docs_text, docs, rust_opts)
98+
99+
async def delete_docs(self, doc_ids: List[str]) -> int:
100+
"""
101+
Delete documents from the local session index by their IDs.
102+
103+
Returns:
104+
Number of documents deleted.
105+
"""
106+
return await asyncio.to_thread(self._inner.delete_docs, doc_ids)
107+
108+
async def get_docs(
109+
self,
110+
options: Optional[GetDocumentsOptions] = None,
111+
) -> List[DocumentInfo]:
112+
"""Retrieve documents from the local session index."""
113+
return await asyncio.to_thread(self._inner.get_docs, options)
114+
115+
async def query(
116+
self,
117+
query: str,
118+
options: Optional[QueryOptions] = None,
119+
) -> SearchResult:
120+
"""
121+
Perform a semantic search over the local session index.
122+
123+
Runs entirely in-memory (~1-10ms). No cloud call.
124+
125+
Args:
126+
query: The search query text.
127+
options: Query options (top_k, alpha, embedding, filter). Example filter:
128+
QueryOptions(filter={"$and": [
129+
{"field": "type", "condition": {"$eq": "faq"}},
130+
{"field": "priority", "condition": {"$gt": "5"}},
131+
]})
132+
133+
Returns:
134+
SearchResult with scored documents.
135+
"""
136+
top_k = getattr(options, "top_k", None)
137+
top_k = top_k if top_k is not None else 5
138+
alpha = getattr(options, "alpha", None)
139+
alpha = alpha if alpha is not None else 0.8
140+
query_embedding = getattr(options, "embedding", None)
141+
filter = getattr(options, "filter", None)
142+
143+
if query_embedding is None:
144+
if self._model_id == "custom":
145+
raise ValueError(
146+
"This session uses custom embeddings. "
147+
"Provide a query embedding via QueryOptions.embedding."
148+
)
149+
return await asyncio.to_thread(
150+
self._inner.query_text,
151+
query,
152+
top_k,
153+
alpha,
154+
filter,
155+
)
156+
157+
return await asyncio.to_thread(
158+
self._inner.query,
159+
query,
160+
top_k,
161+
list(query_embedding),
162+
alpha,
163+
filter,
164+
)
165+
166+
async def push_index(self) -> PushIndexResult:
167+
"""
168+
Push the local session index to the cloud.
169+
170+
Sends all documents with their locally-computed embeddings to the
171+
backend. The cloud index is created or replaced if one already exists
172+
with the same name. No server-side re-embedding occurs.
173+
174+
Returns:
175+
PushIndexResult with job_id and status.
176+
"""
177+
return await asyncio.to_thread(self._inner.push_index)
178+
179+
async def _get_embedding_service(self) -> None:
180+
if self._model_id != "custom":
181+
await asyncio.to_thread(self._inner.load_model)
182+
183+
def _get_custom_embeddings(self, docs: List[DocumentInfo]) -> List[List[float]]:
184+
missing = [doc.id for doc in docs if not getattr(doc, "embedding", None)]
185+
if missing:
186+
raise ValueError(
187+
f"Documents missing .embedding for custom model: {missing}. "
188+
"All documents must have .embedding set when using model_id='custom'."
189+
)
190+
return [list(doc.embedding) for doc in docs] # type: ignore[arg-type]

0 commit comments

Comments
 (0)