sharepoint-knowledge 0.1.0__tar.gz → 0.2.1__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.1}/PKG-INFO +3 -3
  2. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/pyproject.toml +3 -3
  3. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/src/sharepoint_knowledge/cli_sp.py +12 -4
  4. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/src/sharepoint_knowledge/crawl_sp.py +11 -5
  5. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/src/sharepoint_knowledge/server_sp.py +33 -6
  6. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/src/sharepoint_knowledge.egg-info/PKG-INFO +3 -3
  7. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/src/sharepoint_knowledge.egg-info/requires.txt +2 -2
  8. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/tests/test_client_sp.py +0 -1
  9. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/tests/test_crawl_sp.py +0 -1
  10. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/tests/test_server_flow_sp.py +6 -0
  11. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/README.md +0 -0
  12. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/setup.cfg +0 -0
  13. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/src/sharepoint_knowledge/__init__.py +0 -0
  14. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/src/sharepoint_knowledge/auth_sp.py +0 -0
  15. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/src/sharepoint_knowledge/client_sp.py +0 -0
  16. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/src/sharepoint_knowledge/settings_sp.py +0 -0
  17. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/src/sharepoint_knowledge.egg-info/SOURCES.txt +0 -0
  18. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/src/sharepoint_knowledge.egg-info/dependency_links.txt +0 -0
  19. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/src/sharepoint_knowledge.egg-info/entry_points.txt +0 -0
  20. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/src/sharepoint_knowledge.egg-info/top_level.txt +0 -0
  21. {sharepoint_knowledge-0.1.0 → sharepoint_knowledge-0.2.1}/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.1
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.1
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.1; 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.1"
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.1",
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.1",
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)
@@ -99,6 +99,13 @@ def _doctor() -> int:
99
99
  from .server_sp import status
100
100
  s = status()
101
101
  print(json.dumps(s, indent=2))
102
+ bad = [c for c in s.get("checks", []) if not c["ok"]]
103
+ if bad:
104
+ print("\nhealth flags:")
105
+ for c in bad:
106
+ print(f" ✗ {c['flag']}: {c['detail']}")
107
+ if c.get("remediation"):
108
+ print(f" → {c['remediation']}")
102
109
  upd = check("sharepoint-knowledge")
103
110
  if upd["installed"]:
104
111
  line = f"version: {upd['installed']}"
@@ -110,7 +117,7 @@ def _doctor() -> int:
110
117
  if upd["update_available"]:
111
118
  print("update available: run `sharepoint-knowledge update` "
112
119
  "(or pip install -U sharepoint-knowledge[models])")
113
- return 0
120
+ return 4 if bad else 0
114
121
 
115
122
 
116
123
  def main() -> None:
@@ -152,8 +159,9 @@ def main() -> None:
152
159
  return
153
160
  r = subprocess.run(cmd, check=False)
154
161
  if r.returncode == 0:
155
- print("\nupdated — models are pinned and unchanged; "
156
- "run `sharepoint-knowledge doctor` to confirm")
162
+ print("\nupdated — run `sharepoint-knowledge prepare-models` "
163
+ "(new releases may pin new model artifacts) then "
164
+ "`sharepoint-knowledge doctor`")
157
165
  sys.exit(r.returncode)
158
166
  if args.cmd == "register":
159
167
  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
  )
@@ -21,7 +23,7 @@ from mcp.server.fastmcp import FastMCP
21
23
  from .auth_sp import acquire_token
22
24
  from .client_sp import download_file, make_client
23
25
  from .crawl_sp import SPCrawler, resolve_root
24
- from .settings_sp import APP_NAME, SPProfile, graph_base, home, index_root
26
+ from .settings_sp import APP_NAME, SPProfile, graph_base, home, index_root, workdir
25
27
 
26
28
  mcp = FastMCP("sharepoint-knowledge")
27
29
 
@@ -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:
@@ -114,7 +126,6 @@ def _scope(scope_handle: str) -> dict:
114
126
  @mcp.tool()
115
127
  def status() -> dict:
116
128
  """Report setup readiness — profile present, token reachable, models."""
117
- models = home() / "models"
118
129
  try:
119
130
  profile = SPProfile.load()
120
131
  try:
@@ -137,11 +148,19 @@ def status() -> dict:
137
148
  except AccessError as e:
138
149
  cfg["auth"] = str(e)
139
150
  configured = False
151
+ from confluence_knowledge.health import health_checks
152
+ checks = health_checks(home(), workdir(), APP_NAME, "sharepoint-knowledge",
153
+ index_root=index_root())
154
+ checks.append({
155
+ "flag": "creds_valid", "ok": configured,
156
+ "detail": cfg.get("auth") if configured
157
+ else cfg.get("auth") or "no profile",
158
+ "remediation": None if configured
159
+ else "sharepoint-knowledge setup (re-sign-in)"})
140
160
  return {
141
161
  "configured": configured,
142
162
  "profile": cfg,
143
- "embedding_model_ready": (models / "all-MiniLM-L6-v2").exists(),
144
- "reranker_model_ready": (models / "ms-marco-MiniLM-L6-v2").exists(),
163
+ "checks": checks,
145
164
  "models_loaded": _models_ready.is_set() and _EMBEDDER is not None,
146
165
  "index_root": str(index_root()),
147
166
  "live_scopes": sorted(_scopes),
@@ -166,6 +185,7 @@ def connect(root: str) -> dict:
166
185
  _scopes[handle] = {"key": scope_key, "root_entry": root_entry,
167
186
  "site_web": site_web, "client": cli}
168
187
  _start_warmup()
188
+ stale = idx.model_id() is not None and idx.model_id() != EMBED_MODEL_ID
169
189
  return {
170
190
  "scope_handle": handle,
171
191
  "root": root_entry,
@@ -173,6 +193,9 @@ def connect(root: str) -> dict:
173
193
  "cached": st["pages"] > 0,
174
194
  "cached_pages": st["pages"],
175
195
  "cached_as_of": st["as_of"],
196
+ "stale_model": stale,
197
+ "note": ("index was built with an older model — run start_crawl once "
198
+ "to rebuild it with the current models" if stale else None),
176
199
  }
177
200
 
178
201
 
@@ -186,7 +209,7 @@ def _worker_loop(s: dict, job_id: str) -> None:
186
209
  try:
187
210
  _models_ready.wait()
188
211
  c = SPCrawler(s["client"], idx, embedder=_embedder(),
189
- downloader=download_file)
212
+ downloader=download_file, sparse=_sparse())
190
213
  r = c.step(job_id)
191
214
  while r["state"] == "running":
192
215
  r = c.step(job_id)
@@ -280,7 +303,11 @@ def search(scope_handle: str, question: str) -> dict:
280
303
  "note": "nothing indexed — run a crawl first"}
281
304
  if not _models_loaded(10.0):
282
305
  return {"evidence": [], "coverage": "loading", "note": LOADING_NOTE}
283
- ev = retrieve(idx, question, _embedder(), _reranker())
306
+ if idx.model_id() is not None and idx.model_id() != EMBED_MODEL_ID:
307
+ return {"evidence": [], "coverage": "stale_model",
308
+ "note": "this index was built with an older embedding model "
309
+ "— run start_crawl once to rebuild it, then search again"}
310
+ ev = retrieve(idx, question, _embedder(), _reranker(), sparse=_sparse())
284
311
  set_id = f"ev:{len(_evidence_sets)}:{s['key'][:8]}"
285
312
  _evidence_sets[set_id] = ev
286
313
  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.1
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.1
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.1; 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.1
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.1
@@ -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