amfs-http-server 0.3.4__tar.gz → 0.3.6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amfs-http-server
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: AMFS HTTP/REST API server with SSE support
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "amfs-http-server"
3
- version = "0.3.4"
3
+ version = "0.3.6"
4
4
  description = "AMFS HTTP/REST API server with SSE support"
5
5
  requires-python = ">=3.11"
6
6
  license = "Apache-2.0"
@@ -14,7 +14,13 @@ Everything here is soft/optional and fail-open:
14
14
 
15
15
  Env:
16
16
  - AMFS_PROXY_PRO_RETRIEVAL "false" to force the OSS search path (default true)
17
- - AMFS_PROXY_PRO_RERANK "false" to skip cross-encoder rerank (default true)
17
+ - AMFS_PROXY_PRO_RERANK "false" to skip reranking entirely (default true)
18
+ - AMFS_LLM_RERANK "true" to use the LLM listwise reranker (Pro tier) —
19
+ the lever that reaches >90% adversarial Hit@1. Costs
20
+ one LLM call per retrieval, so it is opt-in. Needs
21
+ AMFS_LLM_RERANK_BASE_URL/API_KEY (e.g. OpenRouter).
22
+ When unset/unavailable, falls back to the offline
23
+ cross-encoder.
18
24
 
19
25
  NOTE(integration-test): the Pro retrieval path requires the amfs-internal packages
20
26
  and a populated adapter; it is exercised in the amfs-internal retrieval eval, not
@@ -35,6 +41,34 @@ PRO_RERANK = os.environ.get("AMFS_PROXY_PRO_RERANK", "true").lower() == "true"
35
41
  _cache: dict[str, Any] = {}
36
42
 
37
43
 
44
+ def _build_reranker() -> Any | None:
45
+ """Build the strongest available reranker.
46
+
47
+ Prefers the LLM listwise reranker (Pro tier) when ``AMFS_LLM_RERANK=true`` and
48
+ an OpenAI-compatible client/key are configured — it resolves confusable facts
49
+ under adversarial paraphrase and is the lever for top-1 accuracy, at the cost
50
+ of one LLM call per retrieval. Otherwise falls back to the offline
51
+ ``CrossEncoderReranker`` (single-digit ms, zero egress). Returns ``None`` only
52
+ if neither is importable.
53
+ """
54
+ try:
55
+ from amfs_retrieval import LLMReranker
56
+
57
+ llm = LLMReranker() # reads AMFS_LLM_RERANK* env
58
+ if llm.available:
59
+ logger.info("pro_provider: LLM listwise reranker enabled for proxy")
60
+ return llm
61
+ except Exception: # noqa: BLE001 - LLM rerank optional
62
+ logger.debug("pro_provider: LLM reranker unavailable", exc_info=True)
63
+ try:
64
+ from amfs_retrieval import CrossEncoderReranker
65
+
66
+ return CrossEncoderReranker()
67
+ except Exception: # noqa: BLE001 - rerank optional
68
+ logger.debug("pro_provider: reranker unavailable", exc_info=True)
69
+ return None
70
+
71
+
38
72
  def build_pro_retriever(adapter: Any) -> Any | None:
39
73
  """Build a Pro MultiStrategyRetriever over ``adapter``, or None if unavailable.
40
74
 
@@ -48,14 +82,7 @@ def build_pro_retriever(adapter: Any) -> Any | None:
48
82
  try:
49
83
  from amfs_retrieval import MultiStrategyRetriever, create_pro_embedder
50
84
 
51
- reranker = None
52
- if PRO_RERANK:
53
- try:
54
- from amfs_retrieval import CrossEncoderReranker
55
-
56
- reranker = CrossEncoderReranker()
57
- except Exception: # noqa: BLE001 - rerank optional
58
- logger.debug("pro_provider: reranker unavailable", exc_info=True)
85
+ reranker = _build_reranker() if PRO_RERANK else None
59
86
 
60
87
  retriever = MultiStrategyRetriever(
61
88
  adapter, embedder=create_pro_embedder(), reranker=reranker
@@ -66,6 +66,27 @@ from amfs_http.sse import SSEManager
66
66
 
67
67
  logger = logging.getLogger(__name__)
68
68
 
69
+
70
+ def _server_version() -> str:
71
+ """Return the deployed amfs-http-server package version.
72
+
73
+ Sourced from installed package metadata so it changes on every release —
74
+ unlike the per-entry ``amfs_version`` schema tag. This is the value to use
75
+ when telling deploys/revisions apart.
76
+ """
77
+ try:
78
+ from importlib.metadata import version
79
+
80
+ return version("amfs-http-server")
81
+ except Exception:
82
+ return "unknown"
83
+
84
+
85
+ # Set by the deploy pipeline (e.g. the git SHA) so a running revision is
86
+ # identifiable even between version bumps. Empty when not provided.
87
+ _BUILD_SHA = os.environ.get("AMFS_BUILD_SHA", "")
88
+ _SCHEMA_VERSION = MemoryEntry.model_fields["amfs_version"].default
89
+
69
90
  # ── Async adapter (hot-path, non-blocking) ──────────────────────────
70
91
  _async_adapter = None # AsyncPostgresAdapter | None, set in lifespan
71
92
 
@@ -94,7 +115,7 @@ async def _lifespan(application: FastAPI): # noqa: ARG001
94
115
  app = FastAPI(
95
116
  title="AMFS HTTP API",
96
117
  description="Agent Memory File System — REST API with SSE support",
97
- version="0.1.0",
118
+ version=_server_version(),
98
119
  lifespan=_lifespan,
99
120
  )
100
121
 
@@ -316,14 +337,25 @@ def _ensure_agent_owner(
316
337
  # ──────────────────────────────────────────────────────────────────────
317
338
 
318
339
 
340
+ def _health_payload() -> dict[str, str]:
341
+ payload = {
342
+ "status": "ok",
343
+ "version": _server_version(), # deploy/package version — changes per release
344
+ "schema_version": _SCHEMA_VERSION, # per-entry MemoryEntry schema tag
345
+ }
346
+ if _BUILD_SHA:
347
+ payload["build"] = _BUILD_SHA
348
+ return payload
349
+
350
+
319
351
  @app.get("/health")
320
352
  async def health() -> dict[str, str]:
321
- return {"status": "ok"}
353
+ return _health_payload()
322
354
 
323
355
 
324
356
  @app.get("/api/v1/health")
325
357
  async def health_v1() -> dict[str, str]:
326
- return {"status": "ok"}
358
+ return _health_payload()
327
359
 
328
360
 
329
361
  @app.get("/api/v1/auth/whoami")