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.
- {amfs_http_server-0.3.4 → amfs_http_server-0.3.6}/PKG-INFO +1 -1
- {amfs_http_server-0.3.4 → amfs_http_server-0.3.6}/pyproject.toml +1 -1
- {amfs_http_server-0.3.4 → amfs_http_server-0.3.6}/src/amfs_http/pro_provider.py +36 -9
- {amfs_http_server-0.3.4 → amfs_http_server-0.3.6}/src/amfs_http/server.py +35 -3
- {amfs_http_server-0.3.4 → amfs_http_server-0.3.6}/.gitignore +0 -0
- {amfs_http_server-0.3.4 → amfs_http_server-0.3.6}/src/amfs_http/__init__.py +0 -0
- {amfs_http_server-0.3.4 → amfs_http_server-0.3.6}/src/amfs_http/auth.py +0 -0
- {amfs_http_server-0.3.4 → amfs_http_server-0.3.6}/src/amfs_http/models.py +0 -0
- {amfs_http_server-0.3.4 → amfs_http_server-0.3.6}/src/amfs_http/openrouter_proxy.py +0 -0
- {amfs_http_server-0.3.4 → amfs_http_server-0.3.6}/src/amfs_http/pro_proxy.py +0 -0
- {amfs_http_server-0.3.4 → amfs_http_server-0.3.6}/src/amfs_http/sse.py +0 -0
- {amfs_http_server-0.3.4 → amfs_http_server-0.3.6}/src/amfs_http/tenant_middleware.py +0 -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
|
|
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=
|
|
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
|
|
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
|
|
358
|
+
return _health_payload()
|
|
327
359
|
|
|
328
360
|
|
|
329
361
|
@app.get("/api/v1/auth/whoami")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|