agentx-python 0.8.7__py3-none-any.whl → 0.8.9__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.
agentx/cli.py CHANGED
@@ -4,29 +4,43 @@ Evaluate, Monitor), published separately at github.com/AgentX-ai/AgentX-trace-ev
4
4
  wrapping a Bun-compiled TypeScript engine, not Python). That compiled engine binary is tens of
5
5
  megabytes; most `pip install agentx-python` installs are just this SDK talking to the hosted
6
6
  AgentX SaaS and would never touch it, so it isn't bundled in this package. Instead, this command
7
- downloads the matching release into ~/.agentx/bin the first time it's needed (mirroring
7
+ downloads the release into ~/.agentx/bin the first time it's needed (mirroring
8
8
  AgentX-trace-eval's own install.sh) and then hands off to the real `agentx-server` binary.
9
9
 
10
+ Versioning: each SDK release pins the engine release it was tested against
11
+ (agentx.version.ENGINE_VERSION); the launcher installs that pin and converges to it - if the
12
+ stamped install (~/.agentx/bin/.version) differs from the pin, it re-installs, so upgrading the
13
+ SDK upgrades the engine to the matching pair. `--update` force-reinstalls the resolved version
14
+ (recovering a broken or pre-stamp install). Set AGENTX_TRACE_EVAL_VERSION to a tag or `latest`
15
+ to override the pin; in `latest` mode the launcher trusts whatever is installed, prints a
16
+ fail-open notice when GitHub has something newer, and `--update` fetches it.
17
+
10
18
  Usage:
11
19
  agentx-trace-eval --dev
20
+ agentx-trace-eval --update --dev
21
+ AGENTX_TRACE_EVAL_VERSION=latest agentx-trace-eval --update --dev
12
22
  agentx-trace-eval --port 5000 --db-url postgres://...
13
23
  """
14
24
 
15
25
  import os
16
26
  import platform
27
+ import re
17
28
  import shutil
18
29
  import stat
19
30
  import sys
20
31
  import tarfile
21
32
  import tempfile
22
33
  from pathlib import Path
23
- from typing import Tuple
34
+ from typing import Optional, Tuple
24
35
 
25
36
  import requests
26
37
 
38
+ from agentx.version import ENGINE_VERSION
39
+
27
40
  REPO = "AgentX-ai/AgentX-trace-eval"
28
41
  INSTALL_DIR = Path(os.environ.get("AGENTX_INSTALL_DIR", str(Path.home() / ".agentx" / "bin")))
29
42
  _BIN_NAMES = ("agentx", "agentx-server", "agentx-engine")
43
+ _VERSION_STAMP = ".version"
30
44
 
31
45
 
32
46
  def _platform_tag() -> Tuple[str, str]:
@@ -57,12 +71,22 @@ def _release_url(asset: str, version: str) -> str:
57
71
  return f"https://github.com/{REPO}/releases/download/{version}/{asset}"
58
72
 
59
73
 
60
- def _download(url: str, dest: Path) -> None:
74
+ def _tag_from_release_url(url: str) -> Optional[str]:
75
+ # GitHub redirects releases/latest/download/... to releases/download/<tag>/...; the final
76
+ # URL is the one place the resolved tag shows up without a second API call.
77
+ match = re.search(r"/releases/download/([^/]+)/", url)
78
+ return match.group(1) if match else None
79
+
80
+
81
+ def _download(url: str, dest: Path) -> Optional[str]:
82
+ """Downloads url to dest; returns the release tag resolved from the final (post-redirect)
83
+ URL, or None if it can't be determined."""
61
84
  response = requests.get(url, stream=True, timeout=60)
62
85
  response.raise_for_status()
63
86
  with open(dest, "wb") as f:
64
87
  for chunk in response.iter_content(chunk_size=1 << 16):
65
88
  f.write(chunk)
89
+ return _tag_from_release_url(response.url)
66
90
 
67
91
 
68
92
  def _extract_tar(archive: Path, dest_dir: Path) -> None:
@@ -76,17 +100,42 @@ def _extract_tar(archive: Path, dest_dir: Path) -> None:
76
100
  tar.extractall(dest_dir) # Python < 3.12: filter kwarg doesn't exist yet
77
101
 
78
102
 
103
+ def _stamped_version() -> Optional[str]:
104
+ try:
105
+ return (INSTALL_DIR / _VERSION_STAMP).read_text().strip() or None
106
+ except OSError:
107
+ return None
108
+
109
+
110
+ def _latest_release_tag() -> Optional[str]:
111
+ """The newest published tag, or None when it can't be determined (offline, rate-limited).
112
+ Fail-open by design: an update NOTICE must never break launching the engine."""
113
+ try:
114
+ response = requests.get(f"https://api.github.com/repos/{REPO}/releases/latest", timeout=3)
115
+ response.raise_for_status()
116
+ tag = response.json().get("tag_name")
117
+ return tag if isinstance(tag, str) and tag else None
118
+ except Exception:
119
+ return None
120
+
121
+
122
+ def resolve_version() -> str:
123
+ """The engine version this launcher should be running: the AGENTX_TRACE_EVAL_VERSION
124
+ override (a tag, or `latest`) when set, else the SDK's tested pin."""
125
+ return os.environ.get("AGENTX_TRACE_EVAL_VERSION") or ENGINE_VERSION
126
+
127
+
79
128
  def _install(version: str = "latest") -> None:
80
129
  os_name, arch = _platform_tag()
81
130
  INSTALL_DIR.mkdir(parents=True, exist_ok=True)
82
131
 
83
- print(f"agentx-trace-eval: downloading agentx ({os_name}/{arch})...", file=sys.stderr)
132
+ print(f"agentx-trace-eval: downloading agentx {version} ({os_name}/{arch})...", file=sys.stderr)
84
133
  with tempfile.TemporaryDirectory() as tmp:
85
134
  tmp_dir = Path(tmp)
86
135
  archive = tmp_dir / "agentx.tar.gz"
87
136
  url = _release_url(f"agentx_{os_name}_{arch}.tar.gz", version)
88
137
  try:
89
- _download(url, archive)
138
+ resolved_tag = _download(url, archive)
90
139
  except requests.HTTPError as exc:
91
140
  raise SystemExit(
92
141
  f"agentx-trace-eval: failed to download {url} ({exc}).\n"
@@ -108,6 +157,11 @@ def _install(version: str = "latest") -> None:
108
157
  if not found_any:
109
158
  raise SystemExit(f"agentx-trace-eval: downloaded archive from {url} didn't contain any of {_BIN_NAMES}")
110
159
 
160
+ stamp = resolved_tag or (version if version != "latest" else None)
161
+ if stamp:
162
+ (INSTALL_DIR / _VERSION_STAMP).write_text(stamp + "\n")
163
+ print(f"agentx-trace-eval: installed {stamp}", file=sys.stderr)
164
+
111
165
  if os.environ.get("AGENTX_TRACE_EVAL_SKIP_WEB"):
112
166
  return
113
167
 
@@ -118,7 +172,9 @@ def _install(version: str = "latest") -> None:
118
172
  web_dir = INSTALL_DIR / "web"
119
173
  with tempfile.TemporaryDirectory() as tmp:
120
174
  web_archive = Path(tmp) / "agentx-web.tar.gz"
121
- web_url = _release_url("agentx-web.tar.gz", version)
175
+ # Pin the dashboard to the tag the engine actually resolved to, so the two halves of one
176
+ # install can't skew (releases/latest assets are updated independently).
177
+ web_url = _release_url("agentx-web.tar.gz", stamp or version)
122
178
  try:
123
179
  _download(web_url, web_archive)
124
180
  except requests.HTTPError:
@@ -133,25 +189,61 @@ def _install(version: str = "latest") -> None:
133
189
  _extract_tar(web_archive, web_dir)
134
190
 
135
191
 
136
- def ensure_installed(version: str = "latest") -> Path:
137
- """Downloads agentx-server (+ its engine) into ~/.agentx/bin if not already present there.
192
+ def ensure_installed(version: Optional[str] = None, force: bool = False) -> Path:
193
+ """Installs agentx-server (+ its engine and dashboard) into ~/.agentx/bin when it's missing
194
+ there, when `force` is set, or when `version` is a specific tag that differs from the
195
+ stamped install - the convergence that makes an SDK upgrade carry its tested engine along.
196
+ `version` defaults to resolve_version() (the AGENTX_TRACE_EVAL_VERSION override, else the
197
+ SDK's pin); pass "latest" to trust whatever is installed and only fetch when missing.
138
198
  Returns the path to the agentx-server executable. Set AGENTX_INSTALL_DIR to change where
139
- this looks/installs; set AGENTX_TRACE_EVAL_VERSION to pin a release tag instead of latest."""
199
+ this looks/installs."""
200
+ if version is None:
201
+ version = resolve_version()
140
202
  server_path = INSTALL_DIR / "agentx-server"
141
- if not server_path.exists():
203
+ stamped = _stamped_version()
204
+ pin_mismatch = version != "latest" and stamped != version
205
+ if force or pin_mismatch or not server_path.exists():
142
206
  _install(version=version)
143
207
  return server_path
144
208
 
145
209
 
210
+ def _maybe_print_update_notice(installed: Optional[str]) -> None:
211
+ """Only relevant in `latest` mode, where nothing converges automatically - a quick fail-open
212
+ check tells the user when the world has moved on. (Pinned mode needs no notice: a pin
213
+ mismatch re-installs instead of nagging.)"""
214
+ latest = _latest_release_tag()
215
+ if installed is None:
216
+ print(
217
+ "agentx-trace-eval: installed engine version unknown"
218
+ + (f" (newest release is {latest})" if latest else "")
219
+ + "; run with --update to fetch the newest release",
220
+ file=sys.stderr,
221
+ )
222
+ elif latest and latest != installed:
223
+ print(
224
+ f"agentx-trace-eval: engine {installed} is installed but {latest} is available; "
225
+ "run with --update to upgrade",
226
+ file=sys.stderr,
227
+ )
228
+
229
+
146
230
  def main() -> None:
147
- version = os.environ.get("AGENTX_TRACE_EVAL_VERSION", "latest")
148
- server_path = ensure_installed(version=version)
231
+ args = sys.argv[1:]
232
+ force_update = "--update" in args
233
+ # --update belongs to this launcher, not to agentx-server - consume it before the handoff.
234
+ args = [a for a in args if a != "--update"]
235
+
236
+ version = resolve_version()
237
+ server_path = ensure_installed(version=version, force=force_update)
149
238
  if not server_path.exists():
150
239
  raise SystemExit(f"agentx-trace-eval: {server_path} still missing after install, giving up")
151
240
 
241
+ if not force_update and version == "latest":
242
+ _maybe_print_update_notice(_stamped_version())
243
+
152
244
  # os.execv replaces this process rather than spawning a subprocess: signals, stdio, and the
153
245
  # exit code all pass straight through to agentx-server, same as invoking it directly.
154
- os.execv(str(server_path), [str(server_path)] + sys.argv[1:])
246
+ os.execv(str(server_path), [str(server_path)] + args)
155
247
 
156
248
 
157
249
  if __name__ == "__main__":
agentx/version.py CHANGED
@@ -1 +1,7 @@
1
- VERSION = "0.8.7"
1
+ VERSION = "0.8.9"
2
+
3
+ # The AgentX-trace-eval release this SDK version is tested against - what `agentx-trace-eval`
4
+ # installs and converges to (see agentx/cli.py). Bump together with VERSION when releasing, so
5
+ # every published SDK names a known-good engine+dashboard pair. Users can override with
6
+ # AGENTX_TRACE_EVAL_VERSION=<tag|latest>.
7
+ ENGINE_VERSION = "v0.3.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentx-python
3
- Version: 0.8.7
3
+ Version: 0.8.9
4
4
  Summary: Official Python SDK for AgentX (https://www.agentx.so/)
5
5
  Home-page: https://github.com/AgentX-ai/AgentX-python
6
6
  Author: Robin Wang and AgentX Team
@@ -1,6 +1,6 @@
1
1
  agentx/__init__.py,sha256=4iMiGLNU4S-I-WyX0Z7l0BFUY_krVJ3seMQPqyO-ReA,602
2
2
  agentx/agentx.py,sha256=WuzVQtXSPqBhRwKxkUlQ8VG2cwvGpbMcti4n6aTNPwA,8816
3
- agentx/cli.py,sha256=ueKYPBhW8qd63nvQBQeZ4Mgz9NwEVaM0KH2WvLQ0bGs,6097
3
+ agentx/cli.py,sha256=yawLQLSeZ7KIl7ukgmly-lrYbx9GgKcaI9M4xamiMPw,10468
4
4
  agentx/exceptions.py,sha256=2tDLdZjriFBQbjgzyIcah12RazpbujhqZSRbSXgAml4,1377
5
5
  agentx/export.py,sha256=FNT00e-j9AQRkNDksUwgHJXgr8-XVPscEH4xUqJHmfM,4265
6
6
  agentx/feedback.py,sha256=hMPU3ivUHvhOXOH7gvT4Jy1yztVmoIMOw1MbUNRs5Gk,3643
@@ -10,7 +10,7 @@ agentx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  agentx/testing.py,sha256=0shZEid_vhJgBegpO_loUq6APYDxQrUgD3jvcRdIDV8,7372
11
11
  agentx/traces.py,sha256=sz9gxutlDKNgf0fdCkyzKsGwK5eMAJuIEl3NIwGrsuo,2287
12
12
  agentx/util.py,sha256=lt2Kpg4Fj8whrSuAhLEahcPEIYRVpK9RCC5y9ShdTJs,703
13
- agentx/version.py,sha256=kG_bb-Ryd4AoiM5iOElOryBDv_WEdw73ouYtbd5SIQE,18
13
+ agentx/version.py,sha256=NVkvAxXnY4aNvFKBaIaMdP2_Lq6r1q1gNdF9rkcPaJ0,364
14
14
  agentx/evaluations/__init__.py,sha256=Erv7RGFlRxGTG4rVb2uHhCqLLEX_iAWKqkZKzK6CumE,262
15
15
  agentx/evaluations/_term.py,sha256=WFpiNzdgDBeJJ-Gg-6X7TwwxllobuE4OqFUTuDQvS3Y,2529
16
16
  agentx/evaluations/client.py,sha256=miUmEq4IthUDl-QVAczqT7iaOu4X6QMbHhlm2rSSJpg,31239
@@ -63,9 +63,9 @@ agentx/tracing/ci_types.py,sha256=zFVcZGvc1qbVDdOlEPQ1i5R6terqJHxCzFF4ZjouxxI,14
63
63
  agentx/tracing/eval_scope.py,sha256=ElMbPxpqpQBVuaUnHNlw9RIQu71oyOpd0yR55bDH8eI,2144
64
64
  agentx/tracing/ingest_client.py,sha256=5X6RyxtMq1XYgOzsMd3b2f2qpmYpsMnhKm7NB3oup00,17664
65
65
  agentx/tracing/tracer.py,sha256=qQojU-T4O8IJKyf9vue0Em66qPBTZKl3FMyr3KJKPnI,53770
66
- agentx_python-0.8.7.dist-info/licenses/LICENSE,sha256=gZVsM-nLsE8vlaY6NXXsVoo6IlCClxkToAWmhgT3y_s,10762
67
- agentx_python-0.8.7.dist-info/METADATA,sha256=aZ0vtzylZ2kxkbb5f5jdSDaNmchZreVE-dzKnl33yCA,20992
68
- agentx_python-0.8.7.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
69
- agentx_python-0.8.7.dist-info/entry_points.txt,sha256=rQqF1JTY3T1yfviBU1r8PU-5mBdfOk6P4bZ1L4BskIM,172
70
- agentx_python-0.8.7.dist-info/top_level.txt,sha256=s-q-HB9Gb_QdrZNacSeQyF_c25gQooMy7DlxzgLOHPk,7
71
- agentx_python-0.8.7.dist-info/RECORD,,
66
+ agentx_python-0.8.9.dist-info/licenses/LICENSE,sha256=gZVsM-nLsE8vlaY6NXXsVoo6IlCClxkToAWmhgT3y_s,10762
67
+ agentx_python-0.8.9.dist-info/METADATA,sha256=MLSczgjVUoeyS3DniUVutzQ78YJqIsxc6P-WfMb5HzI,20992
68
+ agentx_python-0.8.9.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
69
+ agentx_python-0.8.9.dist-info/entry_points.txt,sha256=rQqF1JTY3T1yfviBU1r8PU-5mBdfOk6P4bZ1L4BskIM,172
70
+ agentx_python-0.8.9.dist-info/top_level.txt,sha256=s-q-HB9Gb_QdrZNacSeQyF_c25gQooMy7DlxzgLOHPk,7
71
+ agentx_python-0.8.9.dist-info/RECORD,,