amfs-http-server 0.3.49__tar.gz → 0.3.51__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,11 +1,11 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: amfs-http-server
3
- Version: 0.3.49
3
+ Version: 0.3.51
4
4
  Summary: AMFS HTTP/REST API server with SSE support
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
7
- Requires-Dist: amfs-core>=0.3.41
8
- Requires-Dist: amfs>=0.5.33
7
+ Requires-Dist: amfs-core>=0.3.43
8
+ Requires-Dist: amfs>=0.5.35
9
9
  Requires-Dist: fastapi>=0.115
10
10
  Requires-Dist: httpx>=0.27
11
11
  Requires-Dist: sse-starlette>=2.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "amfs-http-server"
3
- version = "0.3.49"
3
+ version = "0.3.51"
4
4
  description = "AMFS HTTP/REST API server with SSE support"
5
5
  requires-python = ">=3.11"
6
6
  license = "Apache-2.0"
@@ -13,7 +13,7 @@ dependencies = [
13
13
  # this tree is what test_dependency_floors forbids.
14
14
  # 0.5.18 for AgentMemory.as_agent, which the agent detail page calls to list
15
15
  # an agent's entries as that agent rather than as the server.
16
- "amfs>=0.5.33",
16
+ "amfs>=0.5.35",
17
17
  # 0.3.23 is real in the silent way: the write path calls
18
18
  # adapter.scope_counts(), which is a default on AdapterABC from 0.3.23. On an
19
19
  # older core the adapters that do not override it — filesystem, S3 — have no
@@ -27,7 +27,7 @@ dependencies = [
27
27
  # unexpected keyword, _scope_block swallows it, and the block disappears —
28
28
  # while an adapter that ignored the argument instead would name another
29
29
  # agent's private keys. Either way the floor is what makes the rule real.
30
- "amfs-core>=0.3.41",
30
+ "amfs-core>=0.3.43",
31
31
  "fastapi>=0.115",
32
32
  # 0.37 is where uvicorn.run() grew timeout_worker_healthcheck, which
33
33
  # main() passes unconditionally; on anything older that is a TypeError
@@ -1261,9 +1261,37 @@ def _labels_are_classes(rows: Sequence[Mapping[str, Any]]) -> bool:
1261
1261
  return len(set(labels)) / len(labels) <= LABELS_MAX_DISTINCT_RATIO
1262
1262
 
1263
1263
 
1264
+ #: Words that carry no class information in a situation label: what the
1265
+ #: label is *of* ("ticket", "issue"), not what it is about. Dropped before
1266
+ #: labels are compared, so ``card declined ticket`` and ``card declined`` are
1267
+ #: one record.
1268
+ _SITUATION_NOISE = frozenset({
1269
+ "a", "an", "the", "of", "on", "in", "for", "with", "and", "or", "to", "at", "by",
1270
+ "ticket", "tickets", "issue", "issues", "problem", "problems", "case", "cases",
1271
+ "request", "requests", "task", "tasks", "failure", "failures", "error", "errors",
1272
+ })
1273
+
1274
+
1264
1275
  def _fold_situation(value: Any) -> str:
1265
- """A situation label as compared: case- and whitespace-folded."""
1266
- return " ".join(str(value or "").split()).casefold()
1276
+ """A situation label as compared.
1277
+
1278
+ Case, whitespace and punctuation are folded (``Card-Declined`` is ``card
1279
+ declined``), noise words that say what a label is of rather than what it
1280
+ is about are dropped (``card declined ticket``), and the words are compared
1281
+ as a set (``declined card``). Nothing is stemmed: ``ios`` and ``android``,
1282
+ ``ui`` and ``backend`` must stay apart, and the classes a label is meant
1283
+ to separate often differ by exactly one word. Measured on the CL support
1284
+ harness (2026-09-26): a model asked to label the kind of ticket wrote the
1285
+ same class as ``card declined``, ``Card-Declined`` and ``declined card
1286
+ ticket`` across episodes; compared as typed, the class's record split
1287
+ three ways and the one-loss turn never fired.
1288
+ """
1289
+ text = "".join(ch if ch.isalnum() else " " for ch in str(value or "").casefold())
1290
+ words = [w for w in text.split() if w not in _SITUATION_NOISE]
1291
+ if not words:
1292
+ # A label made only of noise words is still a label: compare it as typed.
1293
+ words = text.split()
1294
+ return " ".join(sorted(set(words)))
1267
1295
 
1268
1296
 
1269
1297
  def _nearby_record(