vantageai-agent 0.1.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.
- vantageai_agent-0.1.0/PKG-INFO +167 -0
- vantageai_agent-0.1.0/README.md +137 -0
- vantageai_agent-0.1.0/pyproject.toml +45 -0
- vantageai_agent-0.1.0/setup.cfg +4 -0
- vantageai_agent-0.1.0/tests/test_anomaly.py +31 -0
- vantageai_agent-0.1.0/tests/test_backends.py +89 -0
- vantageai_agent-0.1.0/tests/test_bugs.py +127 -0
- vantageai_agent-0.1.0/tests/test_classifier.py +211 -0
- vantageai_agent-0.1.0/tests/test_cost_tracker.py +131 -0
- vantageai_agent-0.1.0/tests/test_hooks.py +93 -0
- vantageai_agent-0.1.0/tests/test_integration.py +634 -0
- vantageai_agent-0.1.0/tests/test_optimizer.py +152 -0
- vantageai_agent-0.1.0/tests/test_permissions.py +146 -0
- vantageai_agent-0.1.0/tests/test_pricing.py +123 -0
- vantageai_agent-0.1.0/tests/test_recommendations.py +248 -0
- vantageai_agent-0.1.0/tests/test_rendering.py +562 -0
- vantageai_agent-0.1.0/tests/test_session.py +149 -0
- vantageai_agent-0.1.0/tests/test_tools.py +251 -0
- vantageai_agent-0.1.0/tests/test_tracker.py +88 -0
- vantageai_agent-0.1.0/vantage_agent/__init__.py +2 -0
- vantageai_agent-0.1.0/vantage_agent/anomaly.py +61 -0
- vantageai_agent-0.1.0/vantage_agent/api_client.py +267 -0
- vantageai_agent-0.1.0/vantage_agent/backends/__init__.py +52 -0
- vantageai_agent-0.1.0/vantage_agent/backends/api_backend.py +48 -0
- vantageai_agent-0.1.0/vantage_agent/backends/base.py +83 -0
- vantageai_agent-0.1.0/vantage_agent/backends/claude_backend.py +63 -0
- vantageai_agent-0.1.0/vantage_agent/backends/codex_backend.py +63 -0
- vantageai_agent-0.1.0/vantage_agent/backends/gemini_backend.py +63 -0
- vantageai_agent-0.1.0/vantage_agent/classifier.py +136 -0
- vantageai_agent-0.1.0/vantage_agent/cli.py +339 -0
- vantageai_agent-0.1.0/vantage_agent/cost_tracker.py +66 -0
- vantageai_agent-0.1.0/vantage_agent/hooks.py +131 -0
- vantageai_agent-0.1.0/vantage_agent/optimizer.py +232 -0
- vantageai_agent-0.1.0/vantage_agent/permissions.py +140 -0
- vantageai_agent-0.1.0/vantage_agent/pricing.py +117 -0
- vantageai_agent-0.1.0/vantage_agent/recommendations.py +532 -0
- vantageai_agent-0.1.0/vantage_agent/renderer.py +146 -0
- vantageai_agent-0.1.0/vantage_agent/session.py +198 -0
- vantageai_agent-0.1.0/vantage_agent/session_store.py +51 -0
- vantageai_agent-0.1.0/vantage_agent/tool_registry.py +42 -0
- vantageai_agent-0.1.0/vantage_agent/tools.py +297 -0
- vantageai_agent-0.1.0/vantage_agent/tracker.py +189 -0
- vantageai_agent-0.1.0/vantageai_agent.egg-info/PKG-INFO +167 -0
- vantageai_agent-0.1.0/vantageai_agent.egg-info/SOURCES.txt +46 -0
- vantageai_agent-0.1.0/vantageai_agent.egg-info/dependency_links.txt +1 -0
- vantageai_agent-0.1.0/vantageai_agent.egg-info/entry_points.txt +2 -0
- vantageai_agent-0.1.0/vantageai_agent.egg-info/requires.txt +6 -0
- vantageai_agent-0.1.0/vantageai_agent.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vantageai-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI coding agent with per-tool permissions, cost tracking, and token optimization
|
|
5
|
+
Author-email: Aman Jain <aman@vantageaiops.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://vantageaiops.com
|
|
8
|
+
Project-URL: Documentation, https://vantageaiops.com/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/amanjain/vantageai
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/amanjain/vantageai/issues
|
|
11
|
+
Keywords: ai,agent,cost-tracking,claude,codex,gemini,llm
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: anthropic>=0.40.0
|
|
26
|
+
Requires-Dist: rich>=13.0.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
30
|
+
|
|
31
|
+
# Vantage Agent
|
|
32
|
+
|
|
33
|
+
AI coding agent with per-tool permissions, cost tracking, prompt optimization, and anomaly detection. Works across Claude (API), Claude Code, Codex CLI, and Gemini CLI.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install vantageai-agent
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Requires Python 3.9+.
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
### Interactive REPL
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Set your API key, then launch
|
|
49
|
+
export ANTHROPIC_API_KEY=sk-...
|
|
50
|
+
vantageai-agent
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### One-shot prompt
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
vantageai-agent "explain the main function in cli.py"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Pipe mode
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
echo "summarize this file" | vantageai-agent
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Backends
|
|
66
|
+
|
|
67
|
+
Vantage auto-detects the best backend. You can override with `--backend`:
|
|
68
|
+
|
|
69
|
+
| Backend | Description | Requires |
|
|
70
|
+
|---------|-------------|---------|
|
|
71
|
+
| `api` | Direct Anthropic API (exact token counts) | `ANTHROPIC_API_KEY` |
|
|
72
|
+
| `claude` | Claude Code subprocess (free tier / subscription) | `claude` binary |
|
|
73
|
+
| `codex` | OpenAI Codex CLI subprocess | `codex` binary |
|
|
74
|
+
| `gemini` | Gemini CLI subprocess | `gemini` binary |
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
vantageai-agent --backend claude "refactor this function"
|
|
78
|
+
vantageai-agent --backend codex "write unit tests for auth.py"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Auto-detect priority: `VANTAGE_BACKEND` env → `ANTHROPIC_API_KEY` → `claude` binary → `codex` binary → `gemini` binary.
|
|
82
|
+
|
|
83
|
+
## Session Management
|
|
84
|
+
|
|
85
|
+
Sessions are persisted to `~/.vantage/sessions/`. Resume any previous session:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# List sessions and costs
|
|
89
|
+
vantageai-agent summary
|
|
90
|
+
|
|
91
|
+
# Resume by session ID
|
|
92
|
+
vantageai-agent --resume abc12345
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## REPL Commands
|
|
96
|
+
|
|
97
|
+
| Command | Description |
|
|
98
|
+
|---------|-------------|
|
|
99
|
+
| `/help` | Show all commands |
|
|
100
|
+
| `/tools` | Show tool approval status |
|
|
101
|
+
| `/allow Bash,Write` | Approve specific tools |
|
|
102
|
+
| `/allow all` | Approve all tools for this session |
|
|
103
|
+
| `/cost` | Show current session cost |
|
|
104
|
+
| `/optimize on\|off` | Toggle prompt optimization |
|
|
105
|
+
| `/model claude-opus-4-6` | Switch model mid-session |
|
|
106
|
+
| `/reset` | Clear history, permissions, cost |
|
|
107
|
+
| `/quit` | Exit (shows final cost summary) |
|
|
108
|
+
|
|
109
|
+
## Cost Tracking
|
|
110
|
+
|
|
111
|
+
Every turn shows token usage and cost. Exit shows a full session summary:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
+----- Cost Summary -----+
|
|
115
|
+
Model: claude-sonnet-4-6
|
|
116
|
+
Input tokens: 1,234
|
|
117
|
+
Output tokens: 456
|
|
118
|
+
Cost: $0.0123
|
|
119
|
+
Session total: $0.0456
|
|
120
|
+
Prompts: 3
|
|
121
|
+
+-------------------------+
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
For Claude Code / Codex / Gemini (subscription or free tier), costs are labeled `~$0.00 (free tier)` or `~$0.0123 (estimated)`.
|
|
125
|
+
|
|
126
|
+
## VantageAI Dashboard
|
|
127
|
+
|
|
128
|
+
Send telemetry to your VantageAI dashboard for cross-session cost analysis:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
export VANTAGE_API_KEY=vnt_...
|
|
132
|
+
vantageai-agent
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Or pass inline: `vantageai-agent --vantage-key vnt_...`
|
|
136
|
+
|
|
137
|
+
## All Flags
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
vantageai-agent [OPTIONS] [PROMPT]
|
|
141
|
+
|
|
142
|
+
PROMPT One-shot prompt (omit for interactive REPL)
|
|
143
|
+
|
|
144
|
+
--backend api | claude | codex | gemini (auto-detected)
|
|
145
|
+
--model Model ID (default: claude-sonnet-4-6)
|
|
146
|
+
--max-tokens Max output tokens (default: 16384)
|
|
147
|
+
--resume SESSION_ID Resume a previous session
|
|
148
|
+
--api-key Anthropic API key (or ANTHROPIC_API_KEY env)
|
|
149
|
+
--vantage-key VantageAI dashboard API key (or VANTAGE_API_KEY env)
|
|
150
|
+
--system Custom system prompt
|
|
151
|
+
--no-optimize Disable prompt optimization
|
|
152
|
+
--cwd Set working directory
|
|
153
|
+
--debug Enable debug output
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Environment Variables
|
|
157
|
+
|
|
158
|
+
| Variable | Description |
|
|
159
|
+
|----------|-------------|
|
|
160
|
+
| `ANTHROPIC_API_KEY` | Anthropic API key |
|
|
161
|
+
| `VANTAGE_API_KEY` | VantageAI dashboard key |
|
|
162
|
+
| `VANTAGE_BACKEND` | Force backend (api/claude/codex/gemini) |
|
|
163
|
+
| `VANTAGE_MODEL` | Default model override |
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Vantage Agent
|
|
2
|
+
|
|
3
|
+
AI coding agent with per-tool permissions, cost tracking, prompt optimization, and anomaly detection. Works across Claude (API), Claude Code, Codex CLI, and Gemini CLI.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install vantageai-agent
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Python 3.9+.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### Interactive REPL
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Set your API key, then launch
|
|
19
|
+
export ANTHROPIC_API_KEY=sk-...
|
|
20
|
+
vantageai-agent
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### One-shot prompt
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
vantageai-agent "explain the main function in cli.py"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Pipe mode
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
echo "summarize this file" | vantageai-agent
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Backends
|
|
36
|
+
|
|
37
|
+
Vantage auto-detects the best backend. You can override with `--backend`:
|
|
38
|
+
|
|
39
|
+
| Backend | Description | Requires |
|
|
40
|
+
|---------|-------------|---------|
|
|
41
|
+
| `api` | Direct Anthropic API (exact token counts) | `ANTHROPIC_API_KEY` |
|
|
42
|
+
| `claude` | Claude Code subprocess (free tier / subscription) | `claude` binary |
|
|
43
|
+
| `codex` | OpenAI Codex CLI subprocess | `codex` binary |
|
|
44
|
+
| `gemini` | Gemini CLI subprocess | `gemini` binary |
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
vantageai-agent --backend claude "refactor this function"
|
|
48
|
+
vantageai-agent --backend codex "write unit tests for auth.py"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Auto-detect priority: `VANTAGE_BACKEND` env → `ANTHROPIC_API_KEY` → `claude` binary → `codex` binary → `gemini` binary.
|
|
52
|
+
|
|
53
|
+
## Session Management
|
|
54
|
+
|
|
55
|
+
Sessions are persisted to `~/.vantage/sessions/`. Resume any previous session:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# List sessions and costs
|
|
59
|
+
vantageai-agent summary
|
|
60
|
+
|
|
61
|
+
# Resume by session ID
|
|
62
|
+
vantageai-agent --resume abc12345
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## REPL Commands
|
|
66
|
+
|
|
67
|
+
| Command | Description |
|
|
68
|
+
|---------|-------------|
|
|
69
|
+
| `/help` | Show all commands |
|
|
70
|
+
| `/tools` | Show tool approval status |
|
|
71
|
+
| `/allow Bash,Write` | Approve specific tools |
|
|
72
|
+
| `/allow all` | Approve all tools for this session |
|
|
73
|
+
| `/cost` | Show current session cost |
|
|
74
|
+
| `/optimize on\|off` | Toggle prompt optimization |
|
|
75
|
+
| `/model claude-opus-4-6` | Switch model mid-session |
|
|
76
|
+
| `/reset` | Clear history, permissions, cost |
|
|
77
|
+
| `/quit` | Exit (shows final cost summary) |
|
|
78
|
+
|
|
79
|
+
## Cost Tracking
|
|
80
|
+
|
|
81
|
+
Every turn shows token usage and cost. Exit shows a full session summary:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
+----- Cost Summary -----+
|
|
85
|
+
Model: claude-sonnet-4-6
|
|
86
|
+
Input tokens: 1,234
|
|
87
|
+
Output tokens: 456
|
|
88
|
+
Cost: $0.0123
|
|
89
|
+
Session total: $0.0456
|
|
90
|
+
Prompts: 3
|
|
91
|
+
+-------------------------+
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For Claude Code / Codex / Gemini (subscription or free tier), costs are labeled `~$0.00 (free tier)` or `~$0.0123 (estimated)`.
|
|
95
|
+
|
|
96
|
+
## VantageAI Dashboard
|
|
97
|
+
|
|
98
|
+
Send telemetry to your VantageAI dashboard for cross-session cost analysis:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
export VANTAGE_API_KEY=vnt_...
|
|
102
|
+
vantageai-agent
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Or pass inline: `vantageai-agent --vantage-key vnt_...`
|
|
106
|
+
|
|
107
|
+
## All Flags
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
vantageai-agent [OPTIONS] [PROMPT]
|
|
111
|
+
|
|
112
|
+
PROMPT One-shot prompt (omit for interactive REPL)
|
|
113
|
+
|
|
114
|
+
--backend api | claude | codex | gemini (auto-detected)
|
|
115
|
+
--model Model ID (default: claude-sonnet-4-6)
|
|
116
|
+
--max-tokens Max output tokens (default: 16384)
|
|
117
|
+
--resume SESSION_ID Resume a previous session
|
|
118
|
+
--api-key Anthropic API key (or ANTHROPIC_API_KEY env)
|
|
119
|
+
--vantage-key VantageAI dashboard API key (or VANTAGE_API_KEY env)
|
|
120
|
+
--system Custom system prompt
|
|
121
|
+
--no-optimize Disable prompt optimization
|
|
122
|
+
--cwd Set working directory
|
|
123
|
+
--debug Enable debug output
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Environment Variables
|
|
127
|
+
|
|
128
|
+
| Variable | Description |
|
|
129
|
+
|----------|-------------|
|
|
130
|
+
| `ANTHROPIC_API_KEY` | Anthropic API key |
|
|
131
|
+
| `VANTAGE_API_KEY` | VantageAI dashboard key |
|
|
132
|
+
| `VANTAGE_BACKEND` | Force backend (api/claude/codex/gemini) |
|
|
133
|
+
| `VANTAGE_MODEL` | Default model override |
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=42", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vantageai-agent"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "AI coding agent with per-tool permissions, cost tracking, and token optimization"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "Aman Jain", email = "aman@vantageaiops.com" }]
|
|
13
|
+
keywords = ["ai", "agent", "cost-tracking", "claude", "codex", "gemini", "llm"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
25
|
+
"Topic :: Utilities",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"anthropic>=0.40.0",
|
|
29
|
+
"rich>=13.0.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://vantageaiops.com"
|
|
34
|
+
Documentation = "https://vantageaiops.com/docs"
|
|
35
|
+
Repository = "https://github.com/amanjain/vantageai"
|
|
36
|
+
"Bug Tracker" = "https://github.com/amanjain/vantageai/issues"
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
dev = ["pytest>=7.0", "pytest-asyncio>=0.21"]
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
vantageai-agent = "vantage_agent.cli:main"
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
include = ["vantage_agent*"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Tests for anomaly.py — cost anomaly detection."""
|
|
2
|
+
import pytest
|
|
3
|
+
from vantage_agent.anomaly import check_cost_anomaly
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestCheckCostAnomaly:
|
|
7
|
+
def test_no_anomaly_with_few_priors(self):
|
|
8
|
+
assert check_cost_anomaly(0.10, 0.05, 1) is False
|
|
9
|
+
|
|
10
|
+
def test_no_anomaly_zero_prior(self):
|
|
11
|
+
assert check_cost_anomaly(0.10, 0.0, 5) is False
|
|
12
|
+
|
|
13
|
+
def test_no_anomaly_normal_cost(self):
|
|
14
|
+
# avg = 0.10/5 = 0.02, current 0.03 < 0.06 (3x)
|
|
15
|
+
assert check_cost_anomaly(0.03, 0.10, 5) is False
|
|
16
|
+
|
|
17
|
+
def test_anomaly_detected(self):
|
|
18
|
+
# avg = 0.10/5 = 0.02, current 0.10 > 0.06 (3x)
|
|
19
|
+
assert check_cost_anomaly(0.10, 0.10, 5) is True
|
|
20
|
+
|
|
21
|
+
def test_no_anomaly_below_min_avg(self):
|
|
22
|
+
# avg = 0.001/5 = 0.0002 < MIN_AVG_COST
|
|
23
|
+
assert check_cost_anomaly(0.01, 0.001, 5) is False
|
|
24
|
+
|
|
25
|
+
def test_exactly_3x_not_anomaly(self):
|
|
26
|
+
# avg = 0.10/5 = 0.02, current 0.06 == 3x (not >)
|
|
27
|
+
assert check_cost_anomaly(0.06, 0.10, 5) is False
|
|
28
|
+
|
|
29
|
+
def test_just_over_3x_is_anomaly(self):
|
|
30
|
+
# avg = 0.10/5 = 0.02, current 0.0601 > 0.06
|
|
31
|
+
assert check_cost_anomaly(0.0601, 0.10, 5) is True
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Tests for Backend implementations, auto-detect logic, and ToolRegistry."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from vantage_agent.backends import create_backend, auto_detect_backend
|
|
10
|
+
from vantage_agent.backends.base import BackendCapabilities, BackendResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_auto_detect_api_key_returns_api():
|
|
14
|
+
with patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-test", "VANTAGE_BACKEND": ""}):
|
|
15
|
+
name = auto_detect_backend()
|
|
16
|
+
assert name == "api"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_auto_detect_claude_binary_returns_claude():
|
|
20
|
+
with patch.dict(os.environ, {"ANTHROPIC_API_KEY": "", "VANTAGE_BACKEND": ""}):
|
|
21
|
+
with patch("shutil.which", side_effect=lambda b: "/usr/bin/claude" if b == "claude" else None):
|
|
22
|
+
name = auto_detect_backend()
|
|
23
|
+
assert name == "claude"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_auto_detect_no_binaries_raises_error():
|
|
27
|
+
with patch.dict(os.environ, {"ANTHROPIC_API_KEY": "", "VANTAGE_BACKEND": ""}):
|
|
28
|
+
with patch("shutil.which", return_value=None):
|
|
29
|
+
with pytest.raises(RuntimeError, match="No backend found"):
|
|
30
|
+
auto_detect_backend()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_explicit_env_var_overrides_autodetect():
|
|
34
|
+
with patch.dict(os.environ, {"VANTAGE_BACKEND": "gemini", "ANTHROPIC_API_KEY": "sk-test"}):
|
|
35
|
+
name = auto_detect_backend()
|
|
36
|
+
assert name == "gemini"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_backend_capabilities_declared():
|
|
40
|
+
"""Every backend must declare all 4 capability fields."""
|
|
41
|
+
for name in ("api", "claude", "codex", "gemini"):
|
|
42
|
+
backend = create_backend(name)
|
|
43
|
+
caps = backend.capabilities
|
|
44
|
+
assert isinstance(caps.supports_process, bool)
|
|
45
|
+
assert caps.token_count in ("exact", "estimated", "free_tier")
|
|
46
|
+
assert caps.tool_format in ("anthropic", "openai", "google")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_api_backend_not_supports_process():
|
|
50
|
+
backend = create_backend("api")
|
|
51
|
+
assert backend.capabilities.supports_process is False
|
|
52
|
+
assert backend.capabilities.token_count == "exact"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_cli_backends_support_process():
|
|
56
|
+
for name in ("claude", "codex", "gemini"):
|
|
57
|
+
backend = create_backend(name)
|
|
58
|
+
assert backend.capabilities.supports_process is True
|
|
59
|
+
assert backend.capabilities.token_count == "estimated"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_unknown_backend_raises():
|
|
63
|
+
with pytest.raises(ValueError, match="Unknown backend"):
|
|
64
|
+
create_backend("llama")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_tool_registry_translates_anthropic_to_openai():
|
|
68
|
+
from vantage_agent.tool_registry import ToolRegistry
|
|
69
|
+
tools = ToolRegistry.for_format("openai")
|
|
70
|
+
assert len(tools) > 0
|
|
71
|
+
for tool in tools:
|
|
72
|
+
assert "function" in tool, f"OpenAI format missing 'function' key: {tool}"
|
|
73
|
+
assert tool["type"] == "function"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_tool_registry_translates_anthropic_to_google():
|
|
77
|
+
from vantage_agent.tool_registry import ToolRegistry
|
|
78
|
+
tools = ToolRegistry.for_format("google")
|
|
79
|
+
assert len(tools) > 0
|
|
80
|
+
for tool in tools:
|
|
81
|
+
assert "name" in tool
|
|
82
|
+
assert "description" in tool
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_tool_registry_anthropic_passthrough():
|
|
86
|
+
from vantage_agent.tool_registry import ToolRegistry
|
|
87
|
+
from vantage_agent.tools import TOOL_DEFINITIONS
|
|
88
|
+
tools = ToolRegistry.for_format("anthropic")
|
|
89
|
+
assert tools == list(TOOL_DEFINITIONS)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Regression tests for 5 pre-existing bugs."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import hashlib
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from vantage_agent.optimizer import optimize_prompt
|
|
11
|
+
from vantage_agent.tracker import Tracker, TrackerConfig
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
# Bug 1: Semantic corruption — "whether or not"
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
def test_whether_or_not_preserves_question_intent():
|
|
19
|
+
"""'whether or not' must NOT be stripped — changes question to directive."""
|
|
20
|
+
original = "Tell me whether or not to delete the file"
|
|
21
|
+
result = optimize_prompt(original)
|
|
22
|
+
optimized = result.optimized if hasattr(result, "optimized") else str(result)
|
|
23
|
+
assert "whether" in optimized.lower(), (
|
|
24
|
+
f"'whether or not' was incorrectly stripped. Got: {optimized!r}"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# Bug 2: anonymized privacy mode not implemented
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
def _make_tracker(privacy: str) -> Tracker:
|
|
33
|
+
cfg = TrackerConfig(api_key="test-key", privacy=privacy)
|
|
34
|
+
return Tracker(cfg)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_anonymized_mode_strips_agent_name_and_hashes_event_id():
|
|
38
|
+
"""anonymized mode must strip agent_name/team and hash event_id."""
|
|
39
|
+
tracker = _make_tracker("anonymized")
|
|
40
|
+
tracker.record(
|
|
41
|
+
model="claude-sonnet-4-6",
|
|
42
|
+
input_tokens=100,
|
|
43
|
+
output_tokens=50,
|
|
44
|
+
cost_usd=0.001,
|
|
45
|
+
latency_ms=200,
|
|
46
|
+
agent_name="my-team-agent",
|
|
47
|
+
)
|
|
48
|
+
with tracker._lock:
|
|
49
|
+
event = tracker._queue[0]
|
|
50
|
+
|
|
51
|
+
assert event.agent_name == "", (
|
|
52
|
+
f"agent_name not stripped in anonymized mode: {event.agent_name!r}"
|
|
53
|
+
)
|
|
54
|
+
assert event.team == "", (
|
|
55
|
+
f"team not stripped in anonymized mode: {event.team!r}"
|
|
56
|
+
)
|
|
57
|
+
assert len(event.event_id) == 64 and all(
|
|
58
|
+
c in "0123456789abcdef" for c in event.event_id
|
|
59
|
+
), f"event_id not hashed in anonymized mode: {event.event_id!r}"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
# Bug 3: Unknown agent defaults to "anthropic" provider
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
def test_unknown_agent_provider_defaults_to_unknown_not_anthropic():
|
|
67
|
+
"""Unrecognised agent_name must map to 'unknown', not 'anthropic'."""
|
|
68
|
+
tracker = _make_tracker("full")
|
|
69
|
+
tracker.record(
|
|
70
|
+
model="gpt-4o",
|
|
71
|
+
input_tokens=100,
|
|
72
|
+
output_tokens=50,
|
|
73
|
+
cost_usd=0.002,
|
|
74
|
+
latency_ms=300,
|
|
75
|
+
agent_name="some-random-gpt-tool",
|
|
76
|
+
)
|
|
77
|
+
with tracker._lock:
|
|
78
|
+
event = tracker._queue[0]
|
|
79
|
+
assert event.provider == "unknown", (
|
|
80
|
+
f"Unknown agent mapped to provider {event.provider!r} instead of 'unknown'"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
# Bug 4: Pricing dictionaries inconsistent
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
def test_pricing_dictionaries_consistent():
|
|
89
|
+
"""cost_tracker.py and pricing.py must use the same model key for claude-haiku-4-5."""
|
|
90
|
+
from vantage_agent.pricing import MODEL_PRICES
|
|
91
|
+
from vantage_agent.cost_tracker import MODEL_PRICING
|
|
92
|
+
|
|
93
|
+
haiku_key_in_pricing = any("haiku-4-5" in k for k in MODEL_PRICES)
|
|
94
|
+
haiku_key_in_tracker = any("haiku-4-5" in k for k in MODEL_PRICING)
|
|
95
|
+
assert haiku_key_in_pricing, "claude-haiku-4-5 not found in pricing.MODEL_PRICES"
|
|
96
|
+
assert haiku_key_in_tracker, "claude-haiku-4-5 not found in cost_tracker.MODEL_PRICING"
|
|
97
|
+
|
|
98
|
+
pricing_key = next(k for k in MODEL_PRICES if "haiku-4-5" in k)
|
|
99
|
+
tracker_key = next(k for k in MODEL_PRICING if "haiku-4-5" in k)
|
|
100
|
+
assert pricing_key == tracker_key, (
|
|
101
|
+
f"Key mismatch: pricing.py uses {pricing_key!r}, cost_tracker.py uses {tracker_key!r}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# Bug 5: Flush-before-success loses events
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def test_flush_retains_events_on_network_error():
|
|
110
|
+
"""Events must NOT be cleared from queue if the HTTP POST fails."""
|
|
111
|
+
tracker = _make_tracker("full")
|
|
112
|
+
tracker.record(
|
|
113
|
+
model="claude-sonnet-4-6",
|
|
114
|
+
input_tokens=100,
|
|
115
|
+
output_tokens=50,
|
|
116
|
+
cost_usd=0.001,
|
|
117
|
+
latency_ms=100,
|
|
118
|
+
agent_name="vantage-agent",
|
|
119
|
+
)
|
|
120
|
+
assert len(tracker._queue) == 1
|
|
121
|
+
|
|
122
|
+
with patch("httpx.post", side_effect=httpx.ConnectError("timeout")):
|
|
123
|
+
tracker.flush()
|
|
124
|
+
|
|
125
|
+
assert len(tracker._queue) == 1, (
|
|
126
|
+
"Events were lost from queue after a failed HTTP POST"
|
|
127
|
+
)
|