sharepoint-knowledge 0.2.2__tar.gz → 0.2.4__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.2.2 → sharepoint_knowledge-0.2.4}/PKG-INFO +2 -2
  2. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/pyproject.toml +2 -2
  3. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/src/sharepoint_knowledge/crawl_sp.py +11 -1
  4. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/src/sharepoint_knowledge/server_sp.py +49 -8
  5. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/src/sharepoint_knowledge.egg-info/PKG-INFO +2 -2
  6. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/src/sharepoint_knowledge.egg-info/requires.txt +1 -1
  7. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/tests/test_agent_doc_consistency.py +21 -0
  8. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/README.md +0 -0
  9. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/setup.cfg +0 -0
  10. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/src/sharepoint_knowledge/__init__.py +0 -0
  11. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/src/sharepoint_knowledge/auth_sp.py +0 -0
  12. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/src/sharepoint_knowledge/cli_sp.py +0 -0
  13. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/src/sharepoint_knowledge/client_sp.py +0 -0
  14. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/src/sharepoint_knowledge/settings_sp.py +0 -0
  15. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/src/sharepoint_knowledge.egg-info/SOURCES.txt +0 -0
  16. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/src/sharepoint_knowledge.egg-info/dependency_links.txt +0 -0
  17. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/src/sharepoint_knowledge.egg-info/entry_points.txt +0 -0
  18. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/src/sharepoint_knowledge.egg-info/top_level.txt +0 -0
  19. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/tests/test_client_sp.py +0 -0
  20. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/tests/test_crawl_sp.py +0 -0
  21. {sharepoint_knowledge-0.2.2 → sharepoint_knowledge-0.2.4}/tests/test_server_flow_sp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sharepoint-knowledge
3
- Version: 0.2.2
3
+ Version: 0.2.4
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
@@ -11,7 +11,7 @@ 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.5.2; extra == "models"
14
+ Requires-Dist: confluence-knowledge[models]>=0.5.4; 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,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sharepoint-knowledge"
7
- version = "0.2.2"
7
+ version = "0.2.4"
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"
@@ -18,7 +18,7 @@ dependencies = [
18
18
 
19
19
  [project.optional-dependencies]
20
20
  models = [
21
- "confluence-knowledge[models]>=0.5.2",
21
+ "confluence-knowledge[models]>=0.5.4",
22
22
  ]
23
23
  dev = [
24
24
  "pytest>=8.0",
@@ -197,6 +197,9 @@ class SPCrawler:
197
197
 
198
198
  queue: list[str] = job["queue"]
199
199
  errors: list[str] = job["errors"]
200
+ # deny/gone errors drop the item from the snapshot; transient
201
+ # failures keep the previously-indexed content
202
+ keep_failed: set[str] = set()
200
203
  # Mid-crawl model change: already-embedded pages live in the OLD
201
204
  # vector space — finishing would publish mixed dims. Wipe + fail;
202
205
  # start_crawl rebuilds under the new model.
@@ -264,6 +267,13 @@ class SPCrawler:
264
267
  continue
265
268
  except AccessError as e:
266
269
  errors.append(f"{entry}: {e}")
270
+ if not any(m in str(e) for m in
271
+ ("401", "403", "404", "permission", "not found")):
272
+ # seen stores the built page_id — page: / file: prefixed
273
+ last = entry.split(":")[-1]
274
+ keep_failed.add(
275
+ f"page:{last}" if entry.startswith("page:")
276
+ else f"file:{last}")
267
277
  continue
268
278
  pid = page["page_id"]
269
279
  if pid in seen:
@@ -285,7 +295,7 @@ class SPCrawler:
285
295
  seen.add(pid)
286
296
 
287
297
  self._checkpoint(job_id, queue, errors, seen, levels, warns)
288
- self.index.publish(seen)
298
+ self.index.publish(seen | keep_failed)
289
299
  self.index.set_model_id(_MODEL_ID)
290
300
  self.index.save_job(job_id, "done" if not queue else "running",
291
301
  queue, errors)
@@ -8,6 +8,7 @@ import logging
8
8
  import threading
9
9
 
10
10
  from confluence_knowledge.client import AccessError, SecurityException
11
+ from confluence_knowledge.feedback import FeedbackStore
11
12
  from confluence_knowledge.index import Index
12
13
  from confluence_knowledge.retrieval import (
13
14
  MODEL_MARKER as EMBED_MODEL_ID,
@@ -30,8 +31,9 @@ from .settings_sp import APP_NAME, SPProfile, graph_base, home, index_root, work
30
31
  mcp = FastMCP("sharepoint-knowledge")
31
32
 
32
33
  _scopes: dict[str, dict] = {}
33
- _evidence_sets: dict[str, list[dict]] = {}
34
+ _evidence_sets: dict[str, dict] = {} # set_id → {scope, ev}
34
35
  _indexes: dict[str, Index] = {}
36
+ _feedback: dict[str, FeedbackStore] = {}
35
37
  _clients: dict[str, object] = {}
36
38
  _EMBEDDER = None
37
39
  _RERANKER = None
@@ -50,6 +52,13 @@ def _index_for(scope_key: str) -> Index:
50
52
  idx = _indexes[scope_key] = Index(index_root() / f"{scope_key}.db")
51
53
  return idx
52
54
 
55
+ def _feedback_for(scope_key: str) -> FeedbackStore:
56
+ st = _feedback.get(scope_key)
57
+ if st is None:
58
+ st = _feedback[scope_key] = FeedbackStore(
59
+ index_root(), scope_key, "sharepoint-knowledge")
60
+ return st
61
+
53
62
 
54
63
  def _embedder():
55
64
  global _EMBEDDER
@@ -309,17 +318,25 @@ def search(scope_handle: str, question: str) -> dict:
309
318
  return {"evidence": [], "coverage": "stale_model",
310
319
  "note": "this index was built with an older embedding model "
311
320
  "— run start_crawl once to rebuild it, then search again"}
312
- ev = retrieve(idx, question, _embedder(), _reranker(), sparse=_sparse())
321
+ emb = _embedder()
322
+ ev = retrieve(idx, question, emb, _reranker(), sparse=_sparse())
323
+ corrections = _feedback_for(s["key"]).match(emb.embed([question])[0])
313
324
  set_id = f"ev:{len(_evidence_sets)}:{s['key'][:8]}"
314
- _evidence_sets[set_id] = ev
325
+ # bound the session cache — sets are never reused after the turn ends
326
+ while len(_evidence_sets) >= 32:
327
+ _evidence_sets.pop(next(iter(_evidence_sets)))
328
+ _evidence_sets[set_id] = {"scope": s["key"], "ev": ev}
315
329
  return {
316
330
  "evidence_set": set_id,
317
331
  "coverage": _coverage(idx),
318
332
  "as_of": st["as_of"],
333
+ "user_corrections": corrections,
319
334
  "evidence": [
320
335
  {"chunk_id": e["chunk_id"], "page_title": e["page_title"],
321
336
  "page_url": e["page_url"], "heading": e["heading"],
322
- "score": e.get("rerank_score"), "text": e["text"][:2000]}
337
+ "score": e.get("rerank_score"), "text": e["text"][:12000],
338
+ "low_confidence": e.get("low_confidence", False),
339
+ "truncated": len(e["text"]) > 12000}
323
340
  for e in ev
324
341
  ],
325
342
  }
@@ -342,15 +359,39 @@ def get_evidence(scope_handle: str, chunk_ids: list[str]) -> dict:
342
359
  return {"chunks": out}
343
360
 
344
361
 
362
+ @mcp.tool()
363
+
364
+
365
+ @mcp.tool()
366
+ def submit_feedback(scope_handle: str, question: str, correction: str,
367
+ note: str = "") -> dict:
368
+ """Store a colleague correction — disclosed alongside the docs on
369
+ similar future questions (never overrides them)."""
370
+ s = _scope(scope_handle)
371
+ if not _models_loaded(0):
372
+ return {"stored": False, "note": LOADING_NOTE}
373
+ qvec = _embedder().embed([question])[0]
374
+ entry = _feedback_for(s["key"]).add(question, qvec, correction, note)
375
+ return {"stored": entry["id"],
376
+ "note": "will be disclosed on similar questions"}
377
+
378
+
379
+ @mcp.tool()
380
+ def feedback_report(scope_handle: str) -> dict:
381
+ """Aggregated corrections — which topics keep getting corrected points
382
+ at outdated/confusing documentation."""
383
+ s = _scope(scope_handle)
384
+ return _feedback_for(s["key"]).report()
385
+
345
386
  @mcp.tool()
346
387
  def verify_citations(scope_handle: str, evidence_set: str,
347
388
  mappings: list[dict]) -> dict:
348
- _scope(scope_handle)
349
- ev = _evidence_sets.get(evidence_set)
350
- if ev is None or not evidence_set.endswith(_scopes[scope_handle]["key"][:8]):
389
+ s = _scope(scope_handle)
390
+ rec = _evidence_sets.get(evidence_set)
391
+ if rec is None or rec["scope"] != s["key"]:
351
392
  raise SecurityException(
352
393
  "unknown or cross-scope evidence set — run search again and cite its evidence_set")
353
- return check_citations(ev, mappings)
394
+ return check_citations(rec["ev"], mappings)
354
395
 
355
396
 
356
397
  def main() -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sharepoint-knowledge
3
- Version: 0.2.2
3
+ Version: 0.2.4
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
@@ -11,7 +11,7 @@ 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.5.2; extra == "models"
14
+ Requires-Dist: confluence-knowledge[models]>=0.5.4; 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"
@@ -12,4 +12,4 @@ openpyxl>=3.1
12
12
  pypdf>=5.0
13
13
 
14
14
  [models]
15
- confluence-knowledge[models]>=0.5.2
15
+ confluence-knowledge[models]>=0.5.4
@@ -27,3 +27,24 @@ def test_agent_references_repo_files_that_exist():
27
27
  for m in re.findall(r"prompts/([\w.-]+)", doc):
28
28
  assert (REPO / "prompts" / m).exists(), \
29
29
  f"agent references missing prompts/{m}"
30
+
31
+
32
+ def test_agent_frontmatter_tools_exist():
33
+ """Every tool named in the agent's frontmatter must be a real
34
+ @mcp.tool() in the server source."""
35
+ if not AGENT.exists():
36
+ import pytest
37
+ pytest.skip("agent file not in this checkout layout")
38
+ doc = AGENT.read_text()
39
+ tools_line = re.search(r'tools:\s*\[([^\]]+)\]', doc).group(1)
40
+ app = AGENT.stem.replace(".agent", "")
41
+ declared = {t.strip().strip('"').split("/")[1]
42
+ for t in tools_line.split(",") if f"{app}/" in t}
43
+ srv_name = {"local-knowledge": "server_local",
44
+ "sharepoint-knowledge": "server_sp",
45
+ "confluence-knowledge": "server"}[app]
46
+ pkg = app.replace("-", "_")
47
+ srv = (Path(__file__).parents[1] / "src" / pkg / f"{srv_name}.py").read_text()
48
+ real = set(re.findall(r"@mcp\.tool\(\)\s*\ndef\s+(\w+)", srv))
49
+ missing = declared - real
50
+ assert not missing, f"agent frontmatter names tools not in server: {missing}"