sharepoint-knowledge 0.1.0__tar.gz → 0.2.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.
Files changed (21) hide show
  1. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/PKG-INFO +3 -3
  2. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/pyproject.toml +3 -3
  3. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/src/sharepoint_knowledge/cli_sp.py +4 -3
  4. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/src/sharepoint_knowledge/crawl_sp.py +11 -5
  5. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/src/sharepoint_knowledge/server_sp.py +26 -4
  6. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/src/sharepoint_knowledge.egg-info/PKG-INFO +3 -3
  7. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/src/sharepoint_knowledge.egg-info/requires.txt +2 -2
  8. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/tests/test_client_sp.py +0 -1
  9. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/tests/test_crawl_sp.py +0 -1
  10. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/tests/test_server_flow_sp.py +6 -0
  11. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/README.md +0 -0
  12. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/setup.cfg +0 -0
  13. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/src/sharepoint_knowledge/__init__.py +0 -0
  14. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/src/sharepoint_knowledge/auth_sp.py +0 -0
  15. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/src/sharepoint_knowledge/client_sp.py +0 -0
  16. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/src/sharepoint_knowledge/settings_sp.py +0 -0
  17. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/src/sharepoint_knowledge.egg-info/SOURCES.txt +0 -0
  18. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/src/sharepoint_knowledge.egg-info/dependency_links.txt +0 -0
  19. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/src/sharepoint_knowledge.egg-info/entry_points.txt +0 -0
  20. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/src/sharepoint_knowledge.egg-info/top_level.txt +0 -0
  21. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.0}/tests/test_agent_doc_consistency.py +0 -0
@@ -1,17 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sharepoint-knowledge
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: SharePoint connector for the grounded-Q&A engine — site pages + document libraries, Entra device-code sign-in, local models, cited answers
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/nkunal1992/agents
7
7
  Requires-Python: >=3.11
8
8
  Description-Content-Type: text/markdown
9
- Requires-Dist: confluence-knowledge[files]>=0.4.1
9
+ Requires-Dist: confluence-knowledge[files]>=0.5.0
10
10
  Requires-Dist: msal>=1.31
11
11
  Requires-Dist: httpx>=0.27
12
12
  Requires-Dist: mcp>=1.8
13
13
  Provides-Extra: models
14
- Requires-Dist: confluence-knowledge[models]>=0.4.1; extra == "models"
14
+ Requires-Dist: confluence-knowledge[models]>=0.5.0; extra == "models"
15
15
  Provides-Extra: dev
16
16
  Requires-Dist: pytest>=8.0; extra == "dev"
17
17
  Requires-Dist: ruff>=0.5; extra == "dev"
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sharepoint-knowledge"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "SharePoint connector for the grounded-Q&A engine — site pages + document libraries, Entra device-code sign-in, local models, cited answers"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
11
11
  license = {text = "Apache-2.0"}
12
12
  dependencies = [
13
- "confluence-knowledge[files]>=0.4.1",
13
+ "confluence-knowledge[files]>=0.5.0",
14
14
  "msal>=1.31",
15
15
  "httpx>=0.27",
16
16
  "mcp>=1.8",
@@ -18,7 +18,7 @@ dependencies = [
18
18
 
19
19
  [project.optional-dependencies]
20
20
  models = [
21
- "confluence-knowledge[models]>=0.4.1",
21
+ "confluence-knowledge[models]>=0.5.0",
22
22
  ]
23
23
  dev = [
24
24
  "pytest>=8.0",
@@ -84,7 +84,7 @@ def _setup() -> int:
84
84
  ensure_gitignore(workdir(), [AUTH_FILE_NAME,
85
85
  f"{INDEX_DIR_NAME}/"])
86
86
 
87
- if input("\nDownload the two local models now (~175 MB, one-time)? "
87
+ if input("\nDownload the local models now (~1.3 GB, one-time)? "
88
88
  "[Y/n] ").strip().lower() not in ("n", "no"):
89
89
  for line in prepare_models(home()):
90
90
  print(line)
@@ -152,8 +152,9 @@ def main() -> None:
152
152
  return
153
153
  r = subprocess.run(cmd, check=False)
154
154
  if r.returncode == 0:
155
- print("\nupdated — models are pinned and unchanged; "
156
- "run `sharepoint-knowledge doctor` to confirm")
155
+ print("\nupdated — run `sharepoint-knowledge prepare-models` "
156
+ "(new releases may pin new model artifacts) then "
157
+ "`sharepoint-knowledge doctor`")
157
158
  sys.exit(r.returncode)
158
159
  if args.cmd == "register":
159
160
  results = _register(
@@ -23,6 +23,8 @@ from confluence_knowledge.settings import SetupError
23
23
  MAX_FILE_BYTES = 25 * 1024 * 1024
24
24
  MAX_FILES = 20_000
25
25
 
26
+ from confluence_knowledge.retrieval import EMBED_MODEL_ID as _MODEL_ID
27
+
26
28
 
27
29
  def resolve_root(client: ConfluenceClient, url: str) -> tuple[str, str]:
28
30
  """SharePoint URL → (root queue entry, site webUrl for citations).
@@ -77,10 +79,11 @@ def _paged(client: ConfluenceClient, path: str, params: dict | None = None):
77
79
 
78
80
  class SPCrawler:
79
81
  def __init__(self, client: ConfluenceClient, index: Index, embedder=None,
80
- downloader=None):
82
+ downloader=None, sparse=None):
81
83
  self.client = client
82
84
  self.index = index
83
85
  self.embedder = embedder
86
+ self.sparse = sparse
84
87
  self.downloader = downloader # injected for tests; real one in server
85
88
 
86
89
  # ---- expansions ----
@@ -255,20 +258,23 @@ class SPCrawler:
255
258
  continue
256
259
  for c in chunks:
257
260
  c.chunk_id = f"{pid}:{page['content_hash'][:8]}:{c.ordinal}"
258
- vecs = None
261
+ vecs, sparse_vecs = None, None
259
262
  if self.embedder is not None and chunks:
260
- vecs = self.embedder.embed(
261
- [f"{c.heading_path}\n{c.text}" for c in chunks])
263
+ texts = [f"{c.heading_path}\n{c.text}" for c in chunks]
264
+ vecs = self.embedder.embed(texts)
265
+ if self.sparse is not None:
266
+ sparse_vecs = self.sparse.embed(texts)
262
267
  self.index.upsert_page({
263
268
  "page_id": pid, "parent_id": None, "title": page["title"],
264
269
  "url": page["url"], "level": levels.get(pid, 0),
265
270
  "version": page["version"],
266
271
  "content_hash": page["content_hash"],
267
- "warnings": page["warnings"]}, chunks, vecs)
272
+ "warnings": page["warnings"]}, chunks, vecs, sparse_vecs)
268
273
  seen.add(pid)
269
274
 
270
275
  self._checkpoint(job_id, queue, errors, seen, levels, warns)
271
276
  self.index.publish(seen)
277
+ self.index.set_model_id(_MODEL_ID)
272
278
  self.index.save_job(job_id, "done" if not queue else "running",
273
279
  queue, errors)
274
280
  return {"job_id": job_id,
@@ -10,8 +10,10 @@ import threading
10
10
  from confluence_knowledge.client import AccessError, SecurityException
11
11
  from confluence_knowledge.index import Index
12
12
  from confluence_knowledge.retrieval import (
13
+ EMBED_MODEL_ID,
13
14
  LocalEmbedder,
14
15
  LocalReranker,
16
+ LocalSparseEmbedder,
15
17
  check_citations,
16
18
  retrieve,
17
19
  )
@@ -31,6 +33,7 @@ _indexes: dict[str, Index] = {}
31
33
  _clients: dict[str, object] = {}
32
34
  _EMBEDDER = None
33
35
  _RERANKER = None
36
+ _SPARSE = None
34
37
  _model_lock = threading.Lock()
35
38
  _models_ready = threading.Event()
36
39
  _warm_lock = threading.Lock()
@@ -62,10 +65,19 @@ def _reranker():
62
65
  return _RERANKER
63
66
 
64
67
 
68
+ def _sparse():
69
+ global _SPARSE
70
+ with _model_lock:
71
+ if _SPARSE is None:
72
+ _SPARSE = LocalSparseEmbedder(home(), APP_NAME)
73
+ return _SPARSE
74
+
75
+
65
76
  def _warm_models() -> None:
66
77
  try:
67
78
  _embedder()
68
79
  _reranker()
80
+ _sparse()
69
81
  except SetupError:
70
82
  pass
71
83
  finally:
@@ -140,8 +152,10 @@ def status() -> dict:
140
152
  return {
141
153
  "configured": configured,
142
154
  "profile": cfg,
143
- "embedding_model_ready": (models / "all-MiniLM-L6-v2").exists(),
144
- "reranker_model_ready": (models / "ms-marco-MiniLM-L6-v2").exists(),
155
+ "embedding_model_ready": (models / "bge-m3").exists(),
156
+ "reranker_model_ready": (models / "bge-reranker-v2-m3-ONNX").exists()
157
+ or (models / "bge-reranker-base-ONNX").exists(),
158
+ "sparse_model_ready": (models / "fastembed").exists(),
145
159
  "models_loaded": _models_ready.is_set() and _EMBEDDER is not None,
146
160
  "index_root": str(index_root()),
147
161
  "live_scopes": sorted(_scopes),
@@ -166,6 +180,7 @@ def connect(root: str) -> dict:
166
180
  _scopes[handle] = {"key": scope_key, "root_entry": root_entry,
167
181
  "site_web": site_web, "client": cli}
168
182
  _start_warmup()
183
+ stale = idx.model_id() is not None and idx.model_id() != EMBED_MODEL_ID
169
184
  return {
170
185
  "scope_handle": handle,
171
186
  "root": root_entry,
@@ -173,6 +188,9 @@ def connect(root: str) -> dict:
173
188
  "cached": st["pages"] > 0,
174
189
  "cached_pages": st["pages"],
175
190
  "cached_as_of": st["as_of"],
191
+ "stale_model": stale,
192
+ "note": ("index was built with an older model — run start_crawl once "
193
+ "to rebuild it with the current models" if stale else None),
176
194
  }
177
195
 
178
196
 
@@ -186,7 +204,7 @@ def _worker_loop(s: dict, job_id: str) -> None:
186
204
  try:
187
205
  _models_ready.wait()
188
206
  c = SPCrawler(s["client"], idx, embedder=_embedder(),
189
- downloader=download_file)
207
+ downloader=download_file, sparse=_sparse())
190
208
  r = c.step(job_id)
191
209
  while r["state"] == "running":
192
210
  r = c.step(job_id)
@@ -280,7 +298,11 @@ def search(scope_handle: str, question: str) -> dict:
280
298
  "note": "nothing indexed — run a crawl first"}
281
299
  if not _models_loaded(10.0):
282
300
  return {"evidence": [], "coverage": "loading", "note": LOADING_NOTE}
283
- ev = retrieve(idx, question, _embedder(), _reranker())
301
+ if idx.model_id() is not None and idx.model_id() != EMBED_MODEL_ID:
302
+ return {"evidence": [], "coverage": "stale_model",
303
+ "note": "this index was built with an older embedding model "
304
+ "— run start_crawl once to rebuild it, then search again"}
305
+ ev = retrieve(idx, question, _embedder(), _reranker(), sparse=_sparse())
284
306
  set_id = f"ev:{len(_evidence_sets)}:{s['key'][:8]}"
285
307
  _evidence_sets[set_id] = ev
286
308
  return {
@@ -1,17 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sharepoint-knowledge
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: SharePoint connector for the grounded-Q&A engine — site pages + document libraries, Entra device-code sign-in, local models, cited answers
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/nkunal1992/agents
7
7
  Requires-Python: >=3.11
8
8
  Description-Content-Type: text/markdown
9
- Requires-Dist: confluence-knowledge[files]>=0.4.1
9
+ Requires-Dist: confluence-knowledge[files]>=0.5.0
10
10
  Requires-Dist: msal>=1.31
11
11
  Requires-Dist: httpx>=0.27
12
12
  Requires-Dist: mcp>=1.8
13
13
  Provides-Extra: models
14
- Requires-Dist: confluence-knowledge[models]>=0.4.1; extra == "models"
14
+ Requires-Dist: confluence-knowledge[models]>=0.5.0; extra == "models"
15
15
  Provides-Extra: dev
16
16
  Requires-Dist: pytest>=8.0; extra == "dev"
17
17
  Requires-Dist: ruff>=0.5; extra == "dev"
@@ -1,4 +1,4 @@
1
- confluence-knowledge[files]>=0.4.1
1
+ confluence-knowledge[files]>=0.5.0
2
2
  msal>=1.31
3
3
  httpx>=0.27
4
4
  mcp>=1.8
@@ -12,4 +12,4 @@ openpyxl>=3.1
12
12
  pypdf>=5.0
13
13
 
14
14
  [models]
15
- confluence-knowledge[models]>=0.4.1
15
+ confluence-knowledge[models]>=0.5.0
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
 
4
4
  import pytest
5
5
  from confluence_knowledge.client import SecurityException
6
-
7
6
  from sharepoint_knowledge.client_sp import GRAPH_RULES, download_file, make_client
8
7
 
9
8
 
@@ -11,7 +11,6 @@ import pytest
11
11
  sys.path.insert(0, str(Path(__file__).parent))
12
12
  from _fake_graph import BLOBS, DRIVE_ID, LIST_ID, SITE_ID, FakeGraph, site_routes
13
13
  from confluence_knowledge.index import Index
14
-
15
14
  from sharepoint_knowledge.crawl_sp import SPCrawler
16
15
 
17
16
 
@@ -25,6 +25,11 @@ class FakeReranker:
25
25
  return np.arange(len(docs), dtype=np.float32)[::-1]
26
26
 
27
27
 
28
+ class FakeSparse:
29
+ def embed(self, texts):
30
+ return [{} for _ in texts]
31
+
32
+
28
33
  @pytest.fixture()
29
34
  def sp_env(tmp_path, monkeypatch):
30
35
  monkeypatch.setenv("SP_KNOWLEDGE_HOME", str(tmp_path / "home"))
@@ -40,6 +45,7 @@ def sp_env(tmp_path, monkeypatch):
40
45
 
41
46
  server_sp._EMBEDDER = FakeEmbedder()
42
47
  server_sp._RERANKER = FakeReranker()
48
+ server_sp._SPARSE = FakeSparse()
43
49
  server_sp._models_ready.set()
44
50
 
45
51
  # the real client goes through httpx — replace it with the fake