harness-evolver 3.0.2 → 3.0.4
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.
- package/bin/install.js +64 -25
- package/package.json +1 -1
- package/skills/setup/SKILL.md +25 -2
- package/tools/read_results.py +25 -0
- package/tools/run_eval.py +37 -0
- package/tools/setup.py +52 -0
package/bin/install.js
CHANGED
|
@@ -216,45 +216,84 @@ function installPythonDeps() {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
async function configureLangSmith(rl) {
|
|
219
|
-
console.log(`\n ${
|
|
219
|
+
console.log(`\n ${BOLD}${GREEN}LangSmith Configuration${RESET} ${DIM}(required)${RESET}\n`);
|
|
220
220
|
|
|
221
|
-
// Check if already configured
|
|
222
221
|
const langsmithCredsDir = process.platform === "darwin"
|
|
223
222
|
? path.join(HOME, "Library", "Application Support", "langsmith-cli")
|
|
224
223
|
: path.join(HOME, ".config", "langsmith-cli");
|
|
225
224
|
const langsmithCredsFile = path.join(langsmithCredsDir, "credentials");
|
|
225
|
+
const hasLangsmithCli = checkCommand("langsmith-cli --version");
|
|
226
|
+
|
|
227
|
+
// --- Step 1: API Key ---
|
|
228
|
+
let hasKey = false;
|
|
226
229
|
|
|
227
|
-
// Check env var
|
|
228
230
|
if (process.env.LANGSMITH_API_KEY) {
|
|
229
231
|
console.log(` ${GREEN}✓${RESET} LANGSMITH_API_KEY found in environment`);
|
|
230
|
-
|
|
232
|
+
hasKey = true;
|
|
233
|
+
} else if (fs.existsSync(langsmithCredsFile)) {
|
|
234
|
+
try {
|
|
235
|
+
const content = fs.readFileSync(langsmithCredsFile, "utf8");
|
|
236
|
+
if (content.includes("LANGSMITH_API_KEY=lsv2_")) {
|
|
237
|
+
console.log(` ${GREEN}✓${RESET} API key found in credentials file`);
|
|
238
|
+
hasKey = true;
|
|
239
|
+
}
|
|
240
|
+
} catch {}
|
|
231
241
|
}
|
|
232
242
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
console.log(` ${
|
|
236
|
-
|
|
243
|
+
if (!hasKey) {
|
|
244
|
+
console.log(` ${BOLD}LangSmith API Key${RESET} — get yours at ${DIM}https://smith.langchain.com/settings${RESET}`);
|
|
245
|
+
console.log(` ${DIM}LangSmith is required. The evolver won't work without it.${RESET}\n`);
|
|
246
|
+
|
|
247
|
+
// Keep asking until they provide a key or explicitly skip
|
|
248
|
+
let attempts = 0;
|
|
249
|
+
while (!hasKey && attempts < 3) {
|
|
250
|
+
const apiKey = await ask(rl, ` ${YELLOW}Paste your LangSmith API key (lsv2_pt_...):${RESET} `);
|
|
251
|
+
const key = apiKey.trim();
|
|
252
|
+
|
|
253
|
+
if (key && key.startsWith("lsv2_")) {
|
|
254
|
+
try {
|
|
255
|
+
fs.mkdirSync(langsmithCredsDir, { recursive: true });
|
|
256
|
+
fs.writeFileSync(langsmithCredsFile, `LANGSMITH_API_KEY=${key}\n`);
|
|
257
|
+
console.log(` ${GREEN}✓${RESET} API key saved`);
|
|
258
|
+
hasKey = true;
|
|
259
|
+
} catch {
|
|
260
|
+
console.log(` ${RED}Failed to save.${RESET} Add to your shell: export LANGSMITH_API_KEY=${key}`);
|
|
261
|
+
hasKey = true; // they have the key, just couldn't save
|
|
262
|
+
}
|
|
263
|
+
} else if (key) {
|
|
264
|
+
console.log(` ${YELLOW}Invalid — LangSmith keys start with lsv2_${RESET}`);
|
|
265
|
+
attempts++;
|
|
266
|
+
} else {
|
|
267
|
+
// Empty input — skip
|
|
268
|
+
console.log(`\n ${RED}WARNING:${RESET} No API key configured.`);
|
|
269
|
+
console.log(` ${BOLD}/evolver:setup will not work${RESET} until you set LANGSMITH_API_KEY.`);
|
|
270
|
+
console.log(` Run: ${DIM}export LANGSMITH_API_KEY=lsv2_pt_your_key${RESET}\n`);
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
237
274
|
}
|
|
238
275
|
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
276
|
+
// --- Step 2: langsmith-cli ---
|
|
277
|
+
if (hasLangsmithCli) {
|
|
278
|
+
console.log(` ${GREEN}✓${RESET} langsmith-cli installed`);
|
|
279
|
+
} else {
|
|
280
|
+
console.log(`\n ${BOLD}langsmith-cli${RESET} — optional but useful for debugging traces`);
|
|
281
|
+
console.log(` ${DIM}Quick project listing, trace inspection, run stats from terminal.${RESET}`);
|
|
282
|
+
const lsCliAnswer = await ask(rl, `\n ${YELLOW}Install langsmith-cli? [Y/n]:${RESET} `);
|
|
283
|
+
if (lsCliAnswer.trim().toLowerCase() !== "n") {
|
|
284
|
+
console.log(`\n Installing langsmith-cli...`);
|
|
285
|
+
try {
|
|
286
|
+
execSync("uv tool install langsmith-cli 2>/dev/null || pip install langsmith-cli 2>/dev/null || pip3 install langsmith-cli", { stdio: "pipe", timeout: 60000 });
|
|
287
|
+
console.log(` ${GREEN}✓${RESET} langsmith-cli installed`);
|
|
244
288
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
289
|
+
// If we have a key, auto-authenticate
|
|
290
|
+
if (hasKey && fs.existsSync(langsmithCredsFile)) {
|
|
291
|
+
console.log(` ${GREEN}✓${RESET} langsmith-cli auto-authenticated (credentials file exists)`);
|
|
292
|
+
}
|
|
293
|
+
} catch {
|
|
294
|
+
console.log(` ${YELLOW}!${RESET} Could not install. Try manually: ${DIM}uv tool install langsmith-cli${RESET}`);
|
|
295
|
+
}
|
|
252
296
|
}
|
|
253
|
-
} else if (key) {
|
|
254
|
-
console.log(` ${YELLOW}Doesn't look like a LangSmith key (should start with lsv2_).${RESET}`);
|
|
255
|
-
console.log(` Add to your shell: ${BOLD}export LANGSMITH_API_KEY=your_key${RESET}`);
|
|
256
|
-
} else {
|
|
257
|
-
console.log(` ${YELLOW}Skipped.${RESET} You must set LANGSMITH_API_KEY before using /evolver:setup`);
|
|
258
297
|
}
|
|
259
298
|
}
|
|
260
299
|
|
package/package.json
CHANGED
package/skills/setup/SKILL.md
CHANGED
|
@@ -11,8 +11,31 @@ Set up the Harness Evolver v3 in a project. Explores the codebase, configures La
|
|
|
11
11
|
|
|
12
12
|
## Prerequisites
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
Check for LangSmith API key — it can be in the environment, the credentials file, or .env:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
python3 -c "
|
|
18
|
+
import os, platform
|
|
19
|
+
key = os.environ.get('LANGSMITH_API_KEY', '')
|
|
20
|
+
if not key:
|
|
21
|
+
creds = os.path.expanduser('~/Library/Application Support/langsmith-cli/credentials') if platform.system() == 'Darwin' else os.path.expanduser('~/.config/langsmith-cli/credentials')
|
|
22
|
+
if os.path.exists(creds):
|
|
23
|
+
for line in open(creds):
|
|
24
|
+
if line.strip().startswith('LANGSMITH_API_KEY='):
|
|
25
|
+
key = line.strip().split('=',1)[1].strip()
|
|
26
|
+
if not key and os.path.exists('.env'):
|
|
27
|
+
for line in open('.env'):
|
|
28
|
+
if line.strip().startswith('LANGSMITH_API_KEY=') and not line.strip().startswith('#'):
|
|
29
|
+
key = line.strip().split('=',1)[1].strip().strip('\"').strip(\"'\")
|
|
30
|
+
print('OK' if key else 'MISSING')
|
|
31
|
+
"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
If `MISSING`: "Set your LangSmith API key: `export LANGSMITH_API_KEY=lsv2_pt_...` or run `npx harness-evolver@latest` to configure."
|
|
35
|
+
|
|
36
|
+
The tools auto-load the key from the credentials file, but the env var takes precedence.
|
|
37
|
+
|
|
38
|
+
Python 3.10+ with `langsmith` and `openevals` packages must be installed:
|
|
16
39
|
|
|
17
40
|
```bash
|
|
18
41
|
pip install langsmith openevals 2>/dev/null || uv pip install langsmith openevals
|
package/tools/read_results.py
CHANGED
|
@@ -21,9 +21,33 @@ Requires: pip install langsmith
|
|
|
21
21
|
import argparse
|
|
22
22
|
import json
|
|
23
23
|
import os
|
|
24
|
+
import platform
|
|
24
25
|
import sys
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
def ensure_langsmith_api_key():
|
|
29
|
+
"""Load LANGSMITH_API_KEY from credentials file if not in env."""
|
|
30
|
+
if os.environ.get("LANGSMITH_API_KEY"):
|
|
31
|
+
return True
|
|
32
|
+
if platform.system() == "Darwin":
|
|
33
|
+
creds_path = os.path.expanduser("~/Library/Application Support/langsmith-cli/credentials")
|
|
34
|
+
else:
|
|
35
|
+
creds_path = os.path.expanduser("~/.config/langsmith-cli/credentials")
|
|
36
|
+
if os.path.exists(creds_path):
|
|
37
|
+
try:
|
|
38
|
+
with open(creds_path) as f:
|
|
39
|
+
for line in f:
|
|
40
|
+
line = line.strip()
|
|
41
|
+
if line.startswith("LANGSMITH_API_KEY="):
|
|
42
|
+
key = line.split("=", 1)[1].strip()
|
|
43
|
+
if key:
|
|
44
|
+
os.environ["LANGSMITH_API_KEY"] = key
|
|
45
|
+
return True
|
|
46
|
+
except OSError:
|
|
47
|
+
pass
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
|
|
27
51
|
def read_experiment(client, experiment_name):
|
|
28
52
|
"""Read results from a single LangSmith experiment."""
|
|
29
53
|
try:
|
|
@@ -184,6 +208,7 @@ def main():
|
|
|
184
208
|
parser.add_argument("--output", default=None, help="Output JSON path")
|
|
185
209
|
parser.add_argument("--format", default="json", choices=["json", "markdown"], help="Output format")
|
|
186
210
|
args = parser.parse_args()
|
|
211
|
+
ensure_langsmith_api_key()
|
|
187
212
|
|
|
188
213
|
from langsmith import Client
|
|
189
214
|
client = Client()
|
package/tools/run_eval.py
CHANGED
|
@@ -17,11 +17,47 @@ Requires: pip install langsmith openevals
|
|
|
17
17
|
import argparse
|
|
18
18
|
import json
|
|
19
19
|
import os
|
|
20
|
+
import platform
|
|
20
21
|
import subprocess
|
|
21
22
|
import sys
|
|
22
23
|
import tempfile
|
|
23
24
|
|
|
24
25
|
|
|
26
|
+
def ensure_langsmith_api_key():
|
|
27
|
+
"""Load LANGSMITH_API_KEY from credentials file if not in env."""
|
|
28
|
+
if os.environ.get("LANGSMITH_API_KEY"):
|
|
29
|
+
return True
|
|
30
|
+
if platform.system() == "Darwin":
|
|
31
|
+
creds_path = os.path.expanduser("~/Library/Application Support/langsmith-cli/credentials")
|
|
32
|
+
else:
|
|
33
|
+
creds_path = os.path.expanduser("~/.config/langsmith-cli/credentials")
|
|
34
|
+
if os.path.exists(creds_path):
|
|
35
|
+
try:
|
|
36
|
+
with open(creds_path) as f:
|
|
37
|
+
for line in f:
|
|
38
|
+
line = line.strip()
|
|
39
|
+
if line.startswith("LANGSMITH_API_KEY="):
|
|
40
|
+
key = line.split("=", 1)[1].strip()
|
|
41
|
+
if key:
|
|
42
|
+
os.environ["LANGSMITH_API_KEY"] = key
|
|
43
|
+
return True
|
|
44
|
+
except OSError:
|
|
45
|
+
pass
|
|
46
|
+
if os.path.exists(".env"):
|
|
47
|
+
try:
|
|
48
|
+
with open(".env") as f:
|
|
49
|
+
for line in f:
|
|
50
|
+
line = line.strip()
|
|
51
|
+
if line.startswith("LANGSMITH_API_KEY=") and not line.startswith("#"):
|
|
52
|
+
key = line.split("=", 1)[1].strip().strip("'\"")
|
|
53
|
+
if key:
|
|
54
|
+
os.environ["LANGSMITH_API_KEY"] = key
|
|
55
|
+
return True
|
|
56
|
+
except OSError:
|
|
57
|
+
pass
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
|
|
25
61
|
def make_target(entry_point, cwd):
|
|
26
62
|
"""Create a target function that runs the agent from a specific directory."""
|
|
27
63
|
def target(inputs):
|
|
@@ -132,6 +168,7 @@ def main():
|
|
|
132
168
|
config = json.load(f)
|
|
133
169
|
|
|
134
170
|
os.environ["EVAL_TASK_TIMEOUT"] = str(args.timeout)
|
|
171
|
+
ensure_langsmith_api_key()
|
|
135
172
|
|
|
136
173
|
from langsmith import Client
|
|
137
174
|
client = Client()
|
package/tools/setup.py
CHANGED
|
@@ -25,12 +25,58 @@ Requires: pip install langsmith openevals
|
|
|
25
25
|
import argparse
|
|
26
26
|
import json
|
|
27
27
|
import os
|
|
28
|
+
import platform
|
|
28
29
|
import subprocess
|
|
29
30
|
import sys
|
|
30
31
|
import tempfile
|
|
31
32
|
from datetime import datetime, timezone
|
|
32
33
|
|
|
33
34
|
|
|
35
|
+
def ensure_langsmith_api_key():
|
|
36
|
+
"""Load LANGSMITH_API_KEY from credentials file if not in env.
|
|
37
|
+
|
|
38
|
+
The installer saves the key to the langsmith-cli credentials file,
|
|
39
|
+
but the SDK only reads the env var. This bridges the gap.
|
|
40
|
+
"""
|
|
41
|
+
if os.environ.get("LANGSMITH_API_KEY"):
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
# Platform-specific credentials path (matches langsmith-cli)
|
|
45
|
+
if platform.system() == "Darwin":
|
|
46
|
+
creds_path = os.path.expanduser("~/Library/Application Support/langsmith-cli/credentials")
|
|
47
|
+
else:
|
|
48
|
+
creds_path = os.path.expanduser("~/.config/langsmith-cli/credentials")
|
|
49
|
+
|
|
50
|
+
if os.path.exists(creds_path):
|
|
51
|
+
try:
|
|
52
|
+
with open(creds_path) as f:
|
|
53
|
+
for line in f:
|
|
54
|
+
line = line.strip()
|
|
55
|
+
if line.startswith("LANGSMITH_API_KEY="):
|
|
56
|
+
key = line.split("=", 1)[1].strip()
|
|
57
|
+
if key:
|
|
58
|
+
os.environ["LANGSMITH_API_KEY"] = key
|
|
59
|
+
return True
|
|
60
|
+
except OSError:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
# Also check .env in current directory
|
|
64
|
+
if os.path.exists(".env"):
|
|
65
|
+
try:
|
|
66
|
+
with open(".env") as f:
|
|
67
|
+
for line in f:
|
|
68
|
+
line = line.strip()
|
|
69
|
+
if line.startswith("LANGSMITH_API_KEY=") and not line.startswith("#"):
|
|
70
|
+
key = line.split("=", 1)[1].strip().strip("'\"")
|
|
71
|
+
if key:
|
|
72
|
+
os.environ["LANGSMITH_API_KEY"] = key
|
|
73
|
+
return True
|
|
74
|
+
except OSError:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
|
|
34
80
|
def check_dependencies():
|
|
35
81
|
"""Verify langsmith and openevals are installed."""
|
|
36
82
|
missing = []
|
|
@@ -294,6 +340,12 @@ def main():
|
|
|
294
340
|
print(f"Install with: pip install {' '.join(missing)}", file=sys.stderr)
|
|
295
341
|
sys.exit(1)
|
|
296
342
|
|
|
343
|
+
# Load API key from credentials file if not in env
|
|
344
|
+
if not ensure_langsmith_api_key():
|
|
345
|
+
print("LANGSMITH_API_KEY not found in environment, credentials file, or .env", file=sys.stderr)
|
|
346
|
+
print("Set it with: export LANGSMITH_API_KEY=lsv2_pt_...", file=sys.stderr)
|
|
347
|
+
sys.exit(1)
|
|
348
|
+
|
|
297
349
|
from langsmith import Client
|
|
298
350
|
client = Client()
|
|
299
351
|
|