verifiers 0.3.2.dev199__py3-none-any.whl → 0.3.2.dev203__py3-none-any.whl

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.
@@ -6,8 +6,9 @@ pool scales with."""
6
6
  import asyncio
7
7
  import contextlib
8
8
  import logging
9
+ import random
9
10
  from collections.abc import AsyncIterator
10
- from typing import Literal
11
+ from typing import TYPE_CHECKING, Literal
11
12
 
12
13
  from verifiers.v1.interception.tunnel.base import BaseTunnelConfig, Tunnel
13
14
  from verifiers.v1.runtimes.limiters import CreationLimiter
@@ -15,6 +16,9 @@ from verifiers.v1.utils.aio import run_shielded
15
16
  from verifiers.v1.utils.prime import ensure_prime_auth
16
17
  from verifiers.v1.utils.scope import run_scope
17
18
 
19
+ if TYPE_CHECKING:
20
+ from prime_tunnel import Tunnel as TunnelClient
21
+
18
22
  logger = logging.getLogger(__name__)
19
23
 
20
24
 
@@ -40,6 +44,45 @@ def tunnel_limiter() -> CreationLimiter:
40
44
  return CreationLimiter("prime-tunnel", run_scope(), _TUNNELS_PER_MIN / 60)
41
45
 
42
46
 
47
+ # How often a held tunnel's state is checked with the tunnel service. The service marks a
48
+ # tunnel disconnected about a minute after its frpc stops answering; two such checks in a
49
+ # row restart frpc.
50
+ CHECK_SECONDS = 20
51
+
52
+
53
+ async def _watch(client: "TunnelClient", url: str) -> None:
54
+ """Keep a held tunnel up. A tunnel service hiccup can leave frpc running while the
55
+ service has it disconnected for good, and every request to the URL then hangs or
56
+ 404s; frpc can also exit. Either way, restart it."""
57
+ strikes = 0
58
+ while True:
59
+ await asyncio.sleep(CHECK_SECONDS * random.uniform(0.75, 1.25))
60
+ if client.is_running:
61
+ try:
62
+ info = await client._client.get_tunnel(client.tunnel_id)
63
+ except Exception as e: # noqa: BLE001 - the service is unreachable: no verdict
64
+ logger.debug("tunnel %s: state check failed: %s", url, e)
65
+ continue
66
+ if info is None:
67
+ logger.error("tunnel %s: registration gone; cannot repair", url)
68
+ return
69
+ strikes = strikes + 1 if info.status == "disconnected" else 0
70
+ if strikes < 2:
71
+ continue
72
+ logger.warning(
73
+ "tunnel %s: %s; restarting frpc",
74
+ url,
75
+ "disconnected" if client.is_running else "frpc exited",
76
+ )
77
+ try:
78
+ await client.restart()
79
+ except Exception as e: # noqa: BLE001 - tried again on the next check
80
+ logger.warning("tunnel %s: frpc restart failed: %s", url, e)
81
+ else:
82
+ logger.warning("tunnel %s: back up", url)
83
+ strikes = 0
84
+
85
+
43
86
  class PrimeTunnelConfig(BaseTunnelConfig):
44
87
  """Expose the host interception port via `prime_tunnel` (frpc). No fields — the tunnel
45
88
  service mints a fresh public URL per exposed port."""
@@ -57,7 +100,8 @@ class PrimeTunnel(Tunnel[PrimeTunnelConfig]):
57
100
  """Bridge the host `port` to a public URL via prime_tunnel (frpc). Tunnel creation
58
101
  is network-bound and rate-capped (512/min, run-wide via the shared
59
102
  `tunnel_limiter`), so transient failures are retried; a terminal one raises
60
- `TunnelError`. The tunnel is torn down on exit."""
103
+ `TunnelError`. While held, frpc is restarted if the tunnel goes down. The tunnel is
104
+ torn down on exit."""
61
105
  from prime_tunnel import Tunnel as TunnelClient
62
106
 
63
107
  from verifiers.v1.errors import TunnelError
@@ -72,10 +116,17 @@ class PrimeTunnel(Tunnel[PrimeTunnelConfig]):
72
116
  url = str(await client.start()).rstrip("/")
73
117
  except Exception as e:
74
118
  raise TunnelError(f"{label} failed: {e}") from e
119
+ watch = asyncio.create_task(_watch(client, url))
120
+
121
+ async def close() -> None:
122
+ watch.cancel() # first, so it can't start a new frpc after the stop
123
+ await asyncio.wait({watch})
124
+ await asyncio.to_thread(client.sync_stop)
125
+
75
126
  try:
76
127
  yield url
77
128
  finally:
78
- # Run the synchronous stop to completion even under cancellation (`run_shielded`
79
- # re-raises the cancellation after); tunnel-stop failures are best-effort.
129
+ # Run the stop to completion even under cancellation (`run_shielded` re-raises
130
+ # the cancellation after); tunnel-stop failures are best-effort.
80
131
  with contextlib.suppress(Exception):
81
- await run_shielded(asyncio.to_thread(client.sync_stop))
132
+ await run_shielded(close())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: verifiers
3
- Version: 0.3.2.dev199
3
+ Version: 0.3.2.dev203
4
4
  Summary: Verifiers: Environments for LLM Reinforcement Learning
5
5
  Project-URL: Homepage, https://github.com/primeintellect-ai/verifiers
6
6
  Project-URL: Documentation, https://github.com/primeintellect-ai/verifiers
@@ -37,7 +37,7 @@ Requires-Dist: openai<3.0.0,>=2.54.0
37
37
  Requires-Dist: prime-pydantic-config[toml]>=0.4.3
38
38
  Requires-Dist: prime-runs>=0.1.4
39
39
  Requires-Dist: prime-sandboxes>=0.4.2
40
- Requires-Dist: prime-tunnel>=0.1.12
40
+ Requires-Dist: prime-tunnel>=0.1.13
41
41
  Requires-Dist: pydantic>=2.12.3
42
42
  Requires-Dist: pyzmq>=27.1.0
43
43
  Requires-Dist: renderers[multimodal]>=0.1.12.dev2
@@ -130,7 +130,7 @@ verifiers/v1/interception/server.py,sha256=yeKMo3gVZl4G_iXpY2PWkwnwGBlfeb3uOhnc7
130
130
  verifiers/v1/interception/tunnel/__init__.py,sha256=eVNZJszj6myrKhx9jmJz7VRd9qlfj0AUvUZwI2bXEj8,893
131
131
  verifiers/v1/interception/tunnel/base.py,sha256=DZB6uPLwM4Qy7n7m0vKeyg-Px87MhsmpYTpe2vpPEng,1998
132
132
  verifiers/v1/interception/tunnel/custom.py,sha256=yL4UbGf4bAuMsxk42mEP-t1wITQCT8BE9FHQIz0qRMA,1708
133
- verifiers/v1/interception/tunnel/prime.py,sha256=D1p0ecM0h6IVeYPD-1Xp605nx5OYcMwKqeUfqQtfq2s,3390
133
+ verifiers/v1/interception/tunnel/prime.py,sha256=4RAn1eO9cyijfsgaE6cJmn_eFkReDMDiQAXZQ2UZBr8,5426
134
134
  verifiers/v1/judges/__init__.py,sha256=z-16awBqEZJrlVYIb9HD-rk12RaABh6jodJgwX4blnA,410
135
135
  verifiers/v1/judges/decision.py,sha256=U8BKuzJVMWfX76fa6jaG_rmcOhzMkZoXSnkUuHKpoV4,3991
136
136
  verifiers/v1/judges/reference.py,sha256=iEVHw-iJ6dbwLOOqrvWrEMJOcUEYJ0YspT04rZz7Sd4,3868
@@ -196,8 +196,8 @@ verifiers/v1/utils/scope.py,sha256=bzUEiWDOVdbj7dXOwGOlzsQ_-Xu0j9NR5TalQB-8TS8,8
196
196
  verifiers/v1/utils/score.py,sha256=493yJVMw8teCu9JxapxMFPFzI0hNUqdo0Y2nGW4kckk,6200
197
197
  verifiers/v1/utils/trace_store.py,sha256=RsCDonGR-bs-tvEstcppiJm3jFmAdIiG83fA5KtCbAw,2801
198
198
  verifiers/v1/utils/version.py,sha256=-obEo_-l9-D8FLef4hYxncOe-uJpxrM1g2Hig_37Sgs,1607
199
- verifiers-0.3.2.dev199.dist-info/METADATA,sha256=lgUJ7FxzxG4uAUS0yIAONykYcBi0cvlbZCZFouEgiqA,4228
200
- verifiers-0.3.2.dev199.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
201
- verifiers-0.3.2.dev199.dist-info/entry_points.txt,sha256=iugElcdWPKbQM7uFF0lZ8iUpHsNr17-BwEAAjJWxV3U,259
202
- verifiers-0.3.2.dev199.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
203
- verifiers-0.3.2.dev199.dist-info/RECORD,,
199
+ verifiers-0.3.2.dev203.dist-info/METADATA,sha256=l2QZRoOww5BGE-nOyicc5K8BnYwILZ8OlVcwbMPFsEM,4228
200
+ verifiers-0.3.2.dev203.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
201
+ verifiers-0.3.2.dev203.dist-info/entry_points.txt,sha256=iugElcdWPKbQM7uFF0lZ8iUpHsNr17-BwEAAjJWxV3U,259
202
+ verifiers-0.3.2.dev203.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
203
+ verifiers-0.3.2.dev203.dist-info/RECORD,,