cortexops 0.1.0__tar.gz → 0.3.0__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.
- {cortexops-0.1.0 → cortexops-0.3.0}/.gitignore +33 -33
- {cortexops-0.1.0 → cortexops-0.3.0}/PKG-INFO +5 -6
- {cortexops-0.1.0 → cortexops-0.3.0}/cortexops/__init__.py +9 -3
- cortexops-0.3.0/cortexops/auth.py +149 -0
- {cortexops-0.1.0 → cortexops-0.3.0}/cortexops/cli.py +40 -2
- {cortexops-0.1.0 → cortexops-0.3.0}/cortexops/client.py +0 -1
- {cortexops-0.1.0 → cortexops-0.3.0}/cortexops/judge.py +0 -1
- {cortexops-0.1.0/cortexops → cortexops-0.3.0}/cortexops/metrics.py +1 -2
- {cortexops-0.1.0/cortexops → cortexops-0.3.0}/cortexops/models.py +3 -0
- cortexops-0.3.0/cortexops/tracer.py +696 -0
- {cortexops-0.1.0 → cortexops-0.3.0}/pyproject.toml +7 -8
- cortexops-0.3.0/tests/conftest.py +8 -0
- {cortexops-0.1.0/cortexops → cortexops-0.3.0}/tests/test_enhancements.py +178 -2
- cortexops-0.1.0/cortexops/cortexops/__init__.py +0 -58
- cortexops-0.1.0/cortexops/cortexops/cli.py +0 -195
- cortexops-0.1.0/cortexops/cortexops/client.py +0 -84
- cortexops-0.1.0/cortexops/cortexops/judge.py +0 -155
- cortexops-0.1.0/cortexops/cortexops/tracer.py +0 -210
- cortexops-0.1.0/cortexops/eval.py +0 -216
- cortexops-0.1.0/cortexops/metrics.py +0 -184
- cortexops-0.1.0/cortexops/models.py +0 -141
- cortexops-0.1.0/cortexops/tracer.py +0 -210
- cortexops-0.1.0/tests/__init__.py +0 -0
- cortexops-0.1.0/tests/test_cortexops.py +0 -211
- cortexops-0.1.0/tests/test_enhancements.py +0 -222
- {cortexops-0.1.0 → cortexops-0.3.0}/LICENSE +0 -0
- {cortexops-0.1.0 → cortexops-0.3.0}/README.md +0 -0
- {cortexops-0.1.0 → cortexops-0.3.0}/cortexops/LICENSE +0 -0
- {cortexops-0.1.0 → cortexops-0.3.0}/cortexops/README.md +0 -0
- {cortexops-0.1.0/cortexops → cortexops-0.3.0}/cortexops/eval.py +0 -0
- {cortexops-0.1.0 → cortexops-0.3.0}/cortexops/pyproject.toml +0 -0
- {cortexops-0.1.0/cortexops → cortexops-0.3.0}/tests/__init__.py +0 -0
- {cortexops-0.1.0/cortexops → cortexops-0.3.0}/tests/test_cortexops.py +0 -0
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
# Python
|
|
2
|
-
__pycache__/
|
|
3
|
-
*.py[cod]
|
|
4
|
-
*.pyo
|
|
5
|
-
.venv/
|
|
6
|
-
venv/
|
|
7
|
-
.env
|
|
8
|
-
*.egg-info/
|
|
9
|
-
dist/
|
|
10
|
-
build/
|
|
11
|
-
PKG-INFO
|
|
12
|
-
*.whl
|
|
13
|
-
*.tar.gz
|
|
14
|
-
|
|
15
|
-
# Test / lint caches
|
|
16
|
-
.pytest_cache/
|
|
17
|
-
.ruff_cache/
|
|
18
|
-
.mypy_cache/
|
|
19
|
-
|
|
20
|
-
# Package managers
|
|
21
|
-
uv.lock
|
|
22
|
-
.python-version
|
|
23
|
-
|
|
24
|
-
# Database
|
|
25
|
-
*.db
|
|
26
|
-
*.sqlite
|
|
27
|
-
|
|
28
|
-
# IDE
|
|
29
|
-
.vscode/
|
|
30
|
-
.idea/
|
|
31
|
-
|
|
32
|
-
# OS
|
|
33
|
-
.DS_Store
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
.venv/
|
|
6
|
+
venv/
|
|
7
|
+
.env
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
PKG-INFO
|
|
12
|
+
*.whl
|
|
13
|
+
*.tar.gz
|
|
14
|
+
|
|
15
|
+
# Test / lint caches
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
.mypy_cache/
|
|
19
|
+
|
|
20
|
+
# Package managers
|
|
21
|
+
uv.lock
|
|
22
|
+
.python-version
|
|
23
|
+
|
|
24
|
+
# Database
|
|
25
|
+
*.db
|
|
26
|
+
*.sqlite
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.vscode/
|
|
30
|
+
.idea/
|
|
31
|
+
|
|
32
|
+
# OS
|
|
33
|
+
.DS_Store
|
|
34
34
|
Thumbs.db
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cortexops
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Reliability infrastructure for AI agents — evaluation, observability, and regression testing
|
|
5
|
-
Project-URL: Homepage, https://
|
|
5
|
+
Project-URL: Homepage, https://getcortexops.com
|
|
6
6
|
Project-URL: Repository, https://github.com/ashishodu2023/cortexops
|
|
7
|
-
Project-URL: Documentation, https://docs.
|
|
7
|
+
Project-URL: Documentation, https://docs.getcortexops.com
|
|
8
8
|
Project-URL: Bug Tracker, https://github.com/ashishodu2023/cortexops/issues
|
|
9
9
|
Project-URL: Changelog, https://github.com/ashishodu2023/cortexops/releases
|
|
10
|
-
Author-email: Ashish <
|
|
10
|
+
Author-email: Ashish <ashish@getcortexops.com>
|
|
11
11
|
License: MIT License
|
|
12
12
|
|
|
13
13
|
Copyright (c) 2025 CortexOps Contributors
|
|
@@ -31,7 +31,7 @@ License: MIT License
|
|
|
31
31
|
SOFTWARE.
|
|
32
32
|
License-File: LICENSE
|
|
33
33
|
Keywords: agents,ai,autogen,crewai,evaluation,langgraph,llm,observability,testing
|
|
34
|
-
Classifier: Development Status ::
|
|
34
|
+
Classifier: Development Status :: 4 - Beta
|
|
35
35
|
Classifier: Intended Audience :: Developers
|
|
36
36
|
Classifier: License :: OSI Approved :: MIT License
|
|
37
37
|
Classifier: Operating System :: OS Independent
|
|
@@ -46,7 +46,6 @@ Classifier: Typing :: Typed
|
|
|
46
46
|
Requires-Python: >=3.10
|
|
47
47
|
Requires-Dist: pydantic>=2.0
|
|
48
48
|
Requires-Dist: pyyaml>=6.0
|
|
49
|
-
Requires-Dist: setuptools>=82.0.1
|
|
50
49
|
Provides-Extra: all
|
|
51
50
|
Requires-Dist: httpx>=0.27; extra == 'all'
|
|
52
51
|
Provides-Extra: dev
|
|
@@ -10,6 +10,7 @@ Quickstart:
|
|
|
10
10
|
print(results.summary())
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
+
from .auth import cmd_login, cmd_logout, cmd_whoami, load_credentials, save_credentials
|
|
13
14
|
from .client import CortexClient
|
|
14
15
|
from .eval import EvalSuite, EvalThresholdError
|
|
15
16
|
from .judge import LLMJudgeMetric
|
|
@@ -27,13 +28,13 @@ from .models import (
|
|
|
27
28
|
EvalSummary,
|
|
28
29
|
FailureKind,
|
|
29
30
|
RunStatus,
|
|
31
|
+
ToolCall,
|
|
30
32
|
Trace,
|
|
31
33
|
TraceNode,
|
|
32
|
-
ToolCall,
|
|
33
34
|
)
|
|
34
35
|
from .tracer import CortexTracer
|
|
35
36
|
|
|
36
|
-
__version__ = "0.
|
|
37
|
+
__version__ = "0.3.0"
|
|
37
38
|
|
|
38
39
|
__all__ = [
|
|
39
40
|
"CortexTracer",
|
|
@@ -55,4 +56,9 @@ __all__ = [
|
|
|
55
56
|
"CaseResult",
|
|
56
57
|
"FailureKind",
|
|
57
58
|
"RunStatus",
|
|
58
|
-
|
|
59
|
+
"cmd_login",
|
|
60
|
+
"cmd_logout",
|
|
61
|
+
"cmd_whoami",
|
|
62
|
+
"save_credentials",
|
|
63
|
+
"load_credentials",
|
|
64
|
+
]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cortexops login / logout / whoami — credential management.
|
|
3
|
+
Stores API key in ~/.cortexops/credentials (JSON).
|
|
4
|
+
Called by CLI and importable for programmatic use.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
_CREDENTIALS_DIR = Path.home() / ".cortexops"
|
|
14
|
+
_CREDENTIALS_FILE = _CREDENTIALS_DIR / "credentials"
|
|
15
|
+
_DEFAULT_API_URL = "https://api.getcortexops.com"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def save_credentials(api_key: str, project: str, api_url: str = _DEFAULT_API_URL) -> None:
|
|
19
|
+
"""Persist credentials to ~/.cortexops/credentials."""
|
|
20
|
+
_CREDENTIALS_DIR.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
21
|
+
creds = {"api_key": api_key, "project": project, "api_url": api_url}
|
|
22
|
+
_CREDENTIALS_FILE.write_text(json.dumps(creds, indent=2))
|
|
23
|
+
_CREDENTIALS_FILE.chmod(0o600) # owner read/write only — no secrets leak
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_credentials() -> dict | None:
|
|
27
|
+
"""Load credentials from ~/.cortexops/credentials. Returns None if not found."""
|
|
28
|
+
if not _CREDENTIALS_FILE.exists():
|
|
29
|
+
return None
|
|
30
|
+
try:
|
|
31
|
+
return json.loads(_CREDENTIALS_FILE.read_text())
|
|
32
|
+
except Exception:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def clear_credentials() -> None:
|
|
37
|
+
"""Remove stored credentials."""
|
|
38
|
+
if _CREDENTIALS_FILE.exists():
|
|
39
|
+
_CREDENTIALS_FILE.unlink()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def verify_key(api_key: str, api_url: str = _DEFAULT_API_URL) -> dict | None:
|
|
43
|
+
"""
|
|
44
|
+
Call GET /health with the key to verify it is valid.
|
|
45
|
+
Returns {"project": ..., "environment": ...} on success, None on failure.
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
import httpx
|
|
49
|
+
r = httpx.get(
|
|
50
|
+
f"{api_url.rstrip('/')}/health",
|
|
51
|
+
headers={"X-API-Key": api_key},
|
|
52
|
+
timeout=8.0,
|
|
53
|
+
)
|
|
54
|
+
if r.status_code == 200:
|
|
55
|
+
return r.json()
|
|
56
|
+
return None
|
|
57
|
+
except Exception:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def cmd_login(api_key: str | None = None, project: str | None = None,
|
|
62
|
+
api_url: str = _DEFAULT_API_URL) -> int:
|
|
63
|
+
"""
|
|
64
|
+
Interactive login flow. Called by `cortexops login`.
|
|
65
|
+
|
|
66
|
+
If api_key is not provided, prompts interactively.
|
|
67
|
+
Returns 0 on success, 1 on failure.
|
|
68
|
+
"""
|
|
69
|
+
print("CortexOps Login")
|
|
70
|
+
print("Get your API key at https://getcortexops.com/#pricing\n")
|
|
71
|
+
|
|
72
|
+
if not api_key:
|
|
73
|
+
try:
|
|
74
|
+
import getpass
|
|
75
|
+
api_key = getpass.getpass("API key (cxo-...): ").strip()
|
|
76
|
+
except KeyboardInterrupt:
|
|
77
|
+
print("\nCancelled.")
|
|
78
|
+
return 1
|
|
79
|
+
|
|
80
|
+
if not api_key.startswith("cxo-"):
|
|
81
|
+
print("Error: API key must start with 'cxo-'", file=sys.stderr)
|
|
82
|
+
return 1
|
|
83
|
+
|
|
84
|
+
if not project:
|
|
85
|
+
project = input("Default project name: ").strip() or "my-agent"
|
|
86
|
+
|
|
87
|
+
print(f"\nVerifying key against {api_url}...")
|
|
88
|
+
info = verify_key(api_key, api_url)
|
|
89
|
+
if info is None:
|
|
90
|
+
print(
|
|
91
|
+
"Warning: Could not verify key (API may be unreachable).\n"
|
|
92
|
+
"Saving credentials anyway — verify manually with: cortexops whoami",
|
|
93
|
+
file=sys.stderr,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
save_credentials(api_key, project, api_url)
|
|
97
|
+
masked = api_key[:8] + "..." + api_key[-4:]
|
|
98
|
+
print("\n✓ Logged in")
|
|
99
|
+
print(f" Key: {masked}")
|
|
100
|
+
print(f" Project: {project}")
|
|
101
|
+
print(" Stored: ~/.cortexops/credentials")
|
|
102
|
+
print("\nYou can now use CortexTracer without passing api_key:")
|
|
103
|
+
print(f" tracer = CortexTracer(project=\"{project}\")")
|
|
104
|
+
return 0
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def cmd_logout() -> int:
|
|
108
|
+
"""Called by `cortexops logout`."""
|
|
109
|
+
creds = load_credentials()
|
|
110
|
+
if not creds:
|
|
111
|
+
print("Not logged in.")
|
|
112
|
+
return 0
|
|
113
|
+
clear_credentials()
|
|
114
|
+
print("✓ Logged out — credentials removed from ~/.cortexops/credentials")
|
|
115
|
+
return 0
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def cmd_whoami(api_url: str | None = None) -> int:
|
|
119
|
+
"""Called by `cortexops whoami`."""
|
|
120
|
+
# Check env var first
|
|
121
|
+
env_key = os.getenv("CORTEXOPS_API_KEY")
|
|
122
|
+
file_creds = load_credentials()
|
|
123
|
+
|
|
124
|
+
if not env_key and not file_creds:
|
|
125
|
+
print("Not logged in.\nRun: cortexops login", file=sys.stderr)
|
|
126
|
+
return 1
|
|
127
|
+
|
|
128
|
+
if env_key:
|
|
129
|
+
print("API key source : CORTEXOPS_API_KEY (env)")
|
|
130
|
+
masked = env_key[:8] + "..." + env_key[-4:]
|
|
131
|
+
print(f"Key : {masked}")
|
|
132
|
+
url = api_url or os.getenv("CORTEXOPS_API_URL", _DEFAULT_API_URL)
|
|
133
|
+
else:
|
|
134
|
+
masked = file_creds["api_key"][:8] + "..." + file_creds["api_key"][-4:]
|
|
135
|
+
print("API key source : ~/.cortexops/credentials")
|
|
136
|
+
print(f"Key : {masked}")
|
|
137
|
+
print(f"Project : {file_creds.get('project', '—')}")
|
|
138
|
+
url = api_url or file_creds.get("api_url", _DEFAULT_API_URL)
|
|
139
|
+
env_key = file_creds["api_key"]
|
|
140
|
+
|
|
141
|
+
print(f"API URL : {url}")
|
|
142
|
+
print("\nVerifying...")
|
|
143
|
+
info = verify_key(env_key, url)
|
|
144
|
+
if info:
|
|
145
|
+
print(f"✓ Key is valid (API status: {info.get('status', 'ok')})")
|
|
146
|
+
else:
|
|
147
|
+
print("✗ Key verification failed — API unreachable or key invalid")
|
|
148
|
+
return 1
|
|
149
|
+
return 0
|
|
@@ -76,7 +76,8 @@ def cmd_eval_diff(args: argparse.Namespace) -> int:
|
|
|
76
76
|
regressions = diff.get("regressions", [])
|
|
77
77
|
improvements = diff.get("improvements", [])
|
|
78
78
|
|
|
79
|
-
sign
|
|
79
|
+
def sign(v):
|
|
80
|
+
return f"+{v:.1%}" if v >= 0 else f"{v:.1%}"
|
|
80
81
|
print(f"Diff: {args.run_a[:8]} → {args.run_b[:8]}")
|
|
81
82
|
print(f" Task completion : {sign(delta_tc)}")
|
|
82
83
|
print(f" Tool accuracy : {sign(delta_tool / 100)}")
|
|
@@ -137,6 +138,29 @@ def _load_agent(agent_path: str):
|
|
|
137
138
|
return getattr(module, attr)
|
|
138
139
|
|
|
139
140
|
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def cmd_login(args: argparse.Namespace) -> int:
|
|
144
|
+
"""cortexops login"""
|
|
145
|
+
from cortexops.auth import cmd_login as _login
|
|
146
|
+
return _login(
|
|
147
|
+
api_key=getattr(args, 'api_key', None),
|
|
148
|
+
project=getattr(args, 'project', None),
|
|
149
|
+
api_url=getattr(args, 'base_url', 'https://api.getcortexops.com'),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def cmd_logout(args: argparse.Namespace) -> int:
|
|
154
|
+
"""cortexops logout"""
|
|
155
|
+
from cortexops.auth import cmd_logout as _logout
|
|
156
|
+
return _logout()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def cmd_whoami(args: argparse.Namespace) -> int:
|
|
160
|
+
"""cortexops whoami"""
|
|
161
|
+
from cortexops.auth import cmd_whoami as _whoami
|
|
162
|
+
return _whoami(api_url=getattr(args, 'base_url', None))
|
|
163
|
+
|
|
140
164
|
def main() -> None:
|
|
141
165
|
parser = argparse.ArgumentParser(
|
|
142
166
|
prog="cortexops",
|
|
@@ -169,12 +193,26 @@ def main() -> None:
|
|
|
169
193
|
fail_p.add_argument("--api-key", default=None)
|
|
170
194
|
fail_p.add_argument("--base-url", default="https://api.cortexops.ai")
|
|
171
195
|
|
|
196
|
+
|
|
197
|
+
# ── login / logout / whoami ───────────────────────────────────────────
|
|
198
|
+
login_p = sub.add_parser("login", help="Save API key to ~/.cortexops/credentials")
|
|
199
|
+
login_p.add_argument("--api-key", default=None, help="cxo-... key (prompted if omitted)")
|
|
200
|
+
login_p.add_argument("--project", "-p", default=None, help="Default project name")
|
|
201
|
+
login_p.add_argument("--base-url", default="https://api.getcortexops.com")
|
|
202
|
+
|
|
203
|
+
sub.add_parser("logout", help="Remove stored credentials")
|
|
204
|
+
whoami_p = sub.add_parser("whoami", help="Show current credentials and verify key")
|
|
205
|
+
whoami_p.add_argument("--base-url", default=None)
|
|
206
|
+
|
|
172
207
|
# ── version ───────────────────────────────────────────────────────────
|
|
173
208
|
sub.add_parser("version", help="Print version and exit")
|
|
174
209
|
|
|
175
210
|
args = parser.parse_args()
|
|
176
211
|
|
|
177
212
|
handlers = {
|
|
213
|
+
("login", None): cmd_login,
|
|
214
|
+
("logout", None): cmd_logout,
|
|
215
|
+
("whoami", None): cmd_whoami,
|
|
178
216
|
("eval", "run"): cmd_eval_run,
|
|
179
217
|
("eval", "diff"): cmd_eval_diff,
|
|
180
218
|
("failures", None): cmd_failures,
|
|
@@ -192,4 +230,4 @@ def main() -> None:
|
|
|
192
230
|
|
|
193
231
|
|
|
194
232
|
if __name__ == "__main__":
|
|
195
|
-
main()
|
|
233
|
+
main()
|
|
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
from abc import ABC, abstractmethod
|
|
5
|
-
from typing import Any
|
|
6
5
|
|
|
7
6
|
from .models import CaseResult, EvalCase, FailureKind, Trace
|
|
8
7
|
|
|
@@ -139,7 +138,7 @@ class HallucinationMetric(Metric):
|
|
|
139
138
|
return 100.0, None, None
|
|
140
139
|
|
|
141
140
|
|
|
142
|
-
def compute_case_result(case: EvalCase, trace: Trace, extra_metrics:
|
|
141
|
+
def compute_case_result(case: EvalCase, trace: Trace, extra_metrics: list[Metric] | None = None) -> CaseResult:
|
|
143
142
|
metrics: list[Metric] = [
|
|
144
143
|
TaskCompletionMetric(),
|
|
145
144
|
ToolAccuracyMetric(),
|