@sx4im/skillcheck 0.1.0
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/LICENSE +21 -0
- package/METHODOLOGY.md +91 -0
- package/README.md +159 -0
- package/dist/bin/skillcheck.d.ts +2 -0
- package/dist/bin/skillcheck.js +8 -0
- package/dist/bin/skillcheck.js.map +1 -0
- package/dist/src/adapters/nvidia-nim.d.ts +30 -0
- package/dist/src/adapters/nvidia-nim.js +165 -0
- package/dist/src/adapters/nvidia-nim.js.map +1 -0
- package/dist/src/cache.d.ts +5 -0
- package/dist/src/cache.js +27 -0
- package/dist/src/cache.js.map +1 -0
- package/dist/src/cli.d.ts +1 -0
- package/dist/src/cli.js +146 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/corpus.d.ts +43 -0
- package/dist/src/corpus.js +233 -0
- package/dist/src/corpus.js.map +1 -0
- package/dist/src/deterministic.d.ts +7 -0
- package/dist/src/deterministic.js +25 -0
- package/dist/src/deterministic.js.map +1 -0
- package/dist/src/env.d.ts +12 -0
- package/dist/src/env.js +39 -0
- package/dist/src/env.js.map +1 -0
- package/dist/src/eval.d.ts +13 -0
- package/dist/src/eval.js +155 -0
- package/dist/src/eval.js.map +1 -0
- package/dist/src/generate.d.ts +9 -0
- package/dist/src/generate.js +94 -0
- package/dist/src/generate.js.map +1 -0
- package/dist/src/grade.d.ts +5 -0
- package/dist/src/grade.js +112 -0
- package/dist/src/grade.js.map +1 -0
- package/dist/src/hash.d.ts +2 -0
- package/dist/src/hash.js +8 -0
- package/dist/src/hash.js.map +1 -0
- package/dist/src/m0/hardcoded.d.ts +7 -0
- package/dist/src/m0/hardcoded.js +51 -0
- package/dist/src/m0/hardcoded.js.map +1 -0
- package/dist/src/m0/run.d.ts +38 -0
- package/dist/src/m0/run.js +102 -0
- package/dist/src/m0/run.js.map +1 -0
- package/dist/src/normalize.d.ts +2 -0
- package/dist/src/normalize.js +109 -0
- package/dist/src/normalize.js.map +1 -0
- package/dist/src/rot.d.ts +62 -0
- package/dist/src/rot.js +156 -0
- package/dist/src/rot.js.map +1 -0
- package/dist/src/run.d.ts +5 -0
- package/dist/src/run.js +47 -0
- package/dist/src/run.js.map +1 -0
- package/dist/src/score.d.ts +14 -0
- package/dist/src/score.js +59 -0
- package/dist/src/score.js.map +1 -0
- package/dist/src/types.d.ts +41 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/verify.d.ts +5 -0
- package/dist/src/verify.js +71 -0
- package/dist/src/verify.js.map +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Saim Shafique
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/METHODOLOGY.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Methodology
|
|
2
|
+
|
|
3
|
+
This document describes how `skillcheck` measures skill effectiveness in v1.
|
|
4
|
+
|
|
5
|
+
## What Is Measured
|
|
6
|
+
|
|
7
|
+
`skillcheck` measures the value of skill instructions under forced injection. The skill body is always injected into the runner model's context for the `with_skill` arm and omitted for the `no_skill` arm.
|
|
8
|
+
|
|
9
|
+
This is intentionally narrower than real agent behavior. It answers: "Does this skill content help when the model receives it?" It does not answer: "Will an agent trigger this skill at the right time?" Trigger-respecting evaluation is out of scope for v1.
|
|
10
|
+
|
|
11
|
+
## Skill Normalization
|
|
12
|
+
|
|
13
|
+
Supported input formats:
|
|
14
|
+
|
|
15
|
+
- `SKILL.md`
|
|
16
|
+
- `AGENTS.md`
|
|
17
|
+
- `.cursorrules`
|
|
18
|
+
- `CLAUDE.md` for the accessible `awesome-claude-md` seed corpus
|
|
19
|
+
|
|
20
|
+
Each skill is normalized into:
|
|
21
|
+
|
|
22
|
+
- `instructions`: full injected content
|
|
23
|
+
- `domain`: declared scope from front matter or inferred text
|
|
24
|
+
- `format`: source format
|
|
25
|
+
- `assets`: recorded sibling files, not executed in v1
|
|
26
|
+
- `commit_hash`: SHA-256 hash of the instruction body
|
|
27
|
+
|
|
28
|
+
## Task Generation
|
|
29
|
+
|
|
30
|
+
The task generator receives only the normalized `domain`. It never receives the instruction body.
|
|
31
|
+
|
|
32
|
+
For each run, the generator creates `2N` tasks and `skillcheck` deterministically samples `N` tasks. This reduces dependence on a single generation order while preserving reproducibility for the same domain and generator model.
|
|
33
|
+
|
|
34
|
+
## A/B Runner
|
|
35
|
+
|
|
36
|
+
For each task, the runner model is called in two arms:
|
|
37
|
+
|
|
38
|
+
- `with_skill`: system context includes the skill instructions
|
|
39
|
+
- `no_skill`: task prompt only
|
|
40
|
+
|
|
41
|
+
Each arm runs `K` trials. The default is `K=3`; single-trial results are not accepted as launch-quality evidence.
|
|
42
|
+
|
|
43
|
+
## Grading
|
|
44
|
+
|
|
45
|
+
Deterministic assertions run first when a task has one. Otherwise, the grader model receives the output and the criterion, but not the arm label. This keeps grading blind to whether an answer came from the skill-injected arm.
|
|
46
|
+
|
|
47
|
+
The generator, runner, and grader model IDs are separate environment variables so they can be swapped independently.
|
|
48
|
+
|
|
49
|
+
## Scoring
|
|
50
|
+
|
|
51
|
+
`effect_pp` is:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
mean(with_skill_pass_rate) - mean(no_skill_pass_rate)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The confidence interval is a paired bootstrap over task/trial observations. Verdicts are:
|
|
58
|
+
|
|
59
|
+
- `helps`: the full CI is above zero
|
|
60
|
+
- `placebo`: the CI overlaps zero
|
|
61
|
+
- `harms`: the full CI is below zero
|
|
62
|
+
|
|
63
|
+
Results also report token overhead and value per 1k extra prompt tokens.
|
|
64
|
+
|
|
65
|
+
## Reproducibility
|
|
66
|
+
|
|
67
|
+
Every published result records:
|
|
68
|
+
|
|
69
|
+
- skill source
|
|
70
|
+
- skill instruction hash
|
|
71
|
+
- task suite path
|
|
72
|
+
- runner, grader, and generator model IDs
|
|
73
|
+
- trial count and task count
|
|
74
|
+
- transcript hashes
|
|
75
|
+
- run date
|
|
76
|
+
|
|
77
|
+
`skillcheck verify <result.json>` reruns a sample of tasks and checks whether the measured effect remains inside the published confidence interval.
|
|
78
|
+
|
|
79
|
+
## Rot Detection
|
|
80
|
+
|
|
81
|
+
Rot detection groups result history by normalized skill name plus instruction hash. A skill is flagged as rot only when a previous result was `helps` and the latest result is `placebo` or `harms`.
|
|
82
|
+
|
|
83
|
+
The leaderboard renders the latest rot status and the per-skill history timeline.
|
|
84
|
+
|
|
85
|
+
## Known Limitations
|
|
86
|
+
|
|
87
|
+
- Forced injection does not measure trigger reliability.
|
|
88
|
+
- LLM-graded tasks inherit the limitations of the grader model, even with blind labels.
|
|
89
|
+
- Assets are recorded but not executed in v1.
|
|
90
|
+
- The v1 launch seed corpus is intentionally capped at 20 skills; it is launch evidence, not a comprehensive public corpus.
|
|
91
|
+
- NVIDIA NIM availability affects live corpus completion. Failed or interrupted runs are recorded separately from gate-passing evidence, and the M5 launch findings use only the completed Qwen Next launch run.
|
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# skillcheck
|
|
2
|
+
|
|
3
|
+
Measure whether an agent skill actually improves task performance.
|
|
4
|
+
|
|
5
|
+
`skillcheck` runs a forced-injection A/B evaluation: the runner model solves generated tasks with and without the skill instructions, then the result is scored as an effect in percentage points with a bootstrap confidence interval.
|
|
6
|
+
|
|
7
|
+
## Status
|
|
8
|
+
|
|
9
|
+
- M0-M4 gates have passed and are recorded in `BUILD-LOG.md`.
|
|
10
|
+
- M5 completed a 20-skill launch corpus in `results/launch/20260605T110514Z-qwen-next`.
|
|
11
|
+
- The launch used `qwen/qwen3-next-80b-a3b-instruct` for generator, grader, and runner after direct NVIDIA diagnostics showed the original StepFun, DeepSeek, and Mistral stack was not reliable enough to complete the corpus.
|
|
12
|
+
- Do not publish to npm until `RELEASE-CHECKLIST.md` is complete.
|
|
13
|
+
|
|
14
|
+
## Run
|
|
15
|
+
|
|
16
|
+
After npm publication, users can run the CLI with one command and no global install:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx --yes @sx4im/skillcheck@latest --help
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or install it globally:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g @sx4im/skillcheck
|
|
26
|
+
skillcheck --help
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Before npm publication, install the CLI from this GitHub repo for testing:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -g git+ssh://git@github.com/sx4im/skillcheck.git
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Remove the pre-publish GitHub install with:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm uninstall -g skillcheck
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If `npm install -g @sx4im/skillcheck` returns `404 Not Found`, the package has not been published to npm yet. The first successful `npm publish` registers the package name; there is no separate manual package-registration step.
|
|
42
|
+
|
|
43
|
+
Requires Node.js 20 or newer.
|
|
44
|
+
|
|
45
|
+
## Local Development
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm ci
|
|
49
|
+
npm run build
|
|
50
|
+
node dist/bin/skillcheck.js --help
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
You can also run the compiled CLI directly:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
node dist/bin/skillcheck.js eval fixtures/m2/strong-skill/SKILL.md --task-suite fixtures/m2/deterministic-tasks.json --tasks 3 --trials 3 --output /tmp/skillcheck-result.json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Environment
|
|
60
|
+
|
|
61
|
+
Copy `.env.example` to `.env` and set `NVIDIA_API_KEY`.
|
|
62
|
+
|
|
63
|
+
Required for live NVIDIA NIM calls:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
NVIDIA_API_KEY=...
|
|
67
|
+
NVIDIA_BASE_URL=https://integrate.api.nvidia.com/v1
|
|
68
|
+
NVIDIA_GENERATOR_MODEL=qwen/qwen3-next-80b-a3b-instruct
|
|
69
|
+
NVIDIA_GRADER_MODEL=qwen/qwen3-next-80b-a3b-instruct
|
|
70
|
+
NVIDIA_RUNNER_MODEL=qwen/qwen3-next-80b-a3b-instruct
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Recommended launch-run provider controls:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
NVIDIA_TIMEOUT_MS=120000
|
|
77
|
+
NVIDIA_REQUEST_DELAY_MS=5000
|
|
78
|
+
NVIDIA_MAX_ATTEMPTS=8
|
|
79
|
+
NVIDIA_MAX_RETRY_DELAY_MS=60000
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The final M5 run used `--concurrency 1` and a 5 second process-wide request delay. The completed run had 75 recovered NVIDIA connection retries and no `429` rate-limit lines.
|
|
83
|
+
|
|
84
|
+
## Commands
|
|
85
|
+
|
|
86
|
+
Evaluate one skill:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
skillcheck eval path/to/SKILL.md --tasks 10 --trials 3 --output results/my-run.json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Verify a published result:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
skillcheck verify results/my-run.json --sample 3
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Run the launch corpus:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
skillcheck corpus run --corpus corpus/launch-20.json --results results/launch/$(date -u +%Y%m%dT%H%M%SZ) --tasks 10 --trials 3 --concurrency 1
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Regenerate the launch-only rot report used by the leaderboard:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
skillcheck rot --results results/launch/20260605T110514Z-qwen-next --output results/rot/report.json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Build the static leaderboard:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npm run site:build
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
By default, the leaderboard reads the launch directory pointed to by `results/launch/latest-qwen-next-dir.txt`. Override with `SKILLCHECK_RESULTS_DIR` when you want to render a different result set.
|
|
117
|
+
|
|
118
|
+
## Methodology
|
|
119
|
+
|
|
120
|
+
The generator receives only the declared skill domain, never the full skill body. The runner is evaluated with and without forced skill injection. The grader is blind to arm labels, and deterministic assertions run before LLM grading when available.
|
|
121
|
+
|
|
122
|
+
Forced injection is the v1 default. It measures whether the skill content helps when injected; it does not test trigger reliability. See `METHODOLOGY.md`.
|
|
123
|
+
|
|
124
|
+
## Launch Findings
|
|
125
|
+
|
|
126
|
+
The M5 launch corpus measured 20 pinned seed skills:
|
|
127
|
+
|
|
128
|
+
- `helps`: 3 / 20, 15%.
|
|
129
|
+
- `placebo`: 11 / 20, 55%.
|
|
130
|
+
- `harms`: 6 / 20, 30%.
|
|
131
|
+
|
|
132
|
+
See `FINDINGS-DRAFT.md` for the current draft write-up and caveats.
|
|
133
|
+
|
|
134
|
+
## Release
|
|
135
|
+
|
|
136
|
+
Do not publish from this repo until `RELEASE-CHECKLIST.md` is complete and M5 final verification has passed.
|
|
137
|
+
|
|
138
|
+
To publish the public npm package, log in with the npm account that should own `skillcheck`, then run one of the publish flows below.
|
|
139
|
+
|
|
140
|
+
With npm WebAuthn/security-key 2FA enabled:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npm login
|
|
144
|
+
npm publish --access public
|
|
145
|
+
npm view @sx4im/skillcheck version
|
|
146
|
+
npx --yes @sx4im/skillcheck@latest --help
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
With an authenticator-app OTP:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm publish --access public --otp=123456
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Replace `123456` with the current 6-digit npm two-factor authentication code.
|
|
156
|
+
|
|
157
|
+
Without account 2FA, create a granular access token on npm with `Bypass two-factor authentication` enabled and `Read and write` access to all packages, then publish with that token.
|
|
158
|
+
|
|
159
|
+
If the package name is still free, `npm publish` creates/registers `@sx4im/skillcheck` automatically.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { main } from '../src/cli.js';
|
|
3
|
+
main(process.argv).catch((error) => {
|
|
4
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5
|
+
console.error(message);
|
|
6
|
+
process.exitCode = 1;
|
|
7
|
+
});
|
|
8
|
+
//# sourceMappingURL=skillcheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillcheck.js","sourceRoot":"","sources":["../../packages/cli/bin/skillcheck.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC1C,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACvB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ChatCompletionMessageParam } from 'openai/resources/chat/completions';
|
|
2
|
+
import type { NvidiaConfig } from '../env.js';
|
|
3
|
+
export interface CompletionRequest {
|
|
4
|
+
model: string;
|
|
5
|
+
messages: ChatCompletionMessageParam[];
|
|
6
|
+
temperature: number;
|
|
7
|
+
maxTokens: number;
|
|
8
|
+
responseFormat?: 'json_object';
|
|
9
|
+
chatTemplateKwargs?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export interface CompletionResponse {
|
|
12
|
+
content: string;
|
|
13
|
+
model: string;
|
|
14
|
+
usage?: {
|
|
15
|
+
promptTokens?: number;
|
|
16
|
+
completionTokens?: number;
|
|
17
|
+
totalTokens?: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export declare class NvidiaNimClient {
|
|
21
|
+
private static requestQueue;
|
|
22
|
+
private static lastRequestAt;
|
|
23
|
+
private readonly client;
|
|
24
|
+
private readonly requestDelayMs;
|
|
25
|
+
private readonly maxAttempts;
|
|
26
|
+
private readonly maxRetryDelayMs;
|
|
27
|
+
constructor(config: NvidiaConfig);
|
|
28
|
+
private runSerializedRequest;
|
|
29
|
+
complete(request: CompletionRequest): Promise<CompletionResponse>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
const RETRYABLE_STATUSES = new Set([408, 409, 429, 500, 502, 503, 504]);
|
|
3
|
+
function extractTextContent(content) {
|
|
4
|
+
if (typeof content === 'string') {
|
|
5
|
+
return content;
|
|
6
|
+
}
|
|
7
|
+
if (Array.isArray(content)) {
|
|
8
|
+
const parts = content
|
|
9
|
+
.map((part) => {
|
|
10
|
+
if (typeof part === 'string') {
|
|
11
|
+
return part;
|
|
12
|
+
}
|
|
13
|
+
if (typeof part === 'object' && part !== null && 'text' in part) {
|
|
14
|
+
const text = part.text;
|
|
15
|
+
return typeof text === 'string' ? text : '';
|
|
16
|
+
}
|
|
17
|
+
return '';
|
|
18
|
+
})
|
|
19
|
+
.join('');
|
|
20
|
+
return parts || undefined;
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
function extractMessageText(message) {
|
|
25
|
+
if (typeof message !== 'object' || message === null) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const record = message;
|
|
29
|
+
return (extractTextContent(record.content) ??
|
|
30
|
+
extractTextContent(record.reasoning_content) ??
|
|
31
|
+
extractTextContent(record.refusal));
|
|
32
|
+
}
|
|
33
|
+
function sleep(ms) {
|
|
34
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
35
|
+
}
|
|
36
|
+
function getStatus(error) {
|
|
37
|
+
if (typeof error === 'object' && error !== null && 'status' in error) {
|
|
38
|
+
const status = error.status;
|
|
39
|
+
return typeof status === 'number' ? status : undefined;
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
function getRetryAfterMs(error) {
|
|
44
|
+
if (typeof error !== 'object' || error === null || !('headers' in error)) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const headers = error.headers;
|
|
48
|
+
const retryAfter = headers instanceof Headers
|
|
49
|
+
? headers.get('retry-after')
|
|
50
|
+
: typeof headers === 'object' && headers !== null && 'retry-after' in headers
|
|
51
|
+
? headers['retry-after']
|
|
52
|
+
: undefined;
|
|
53
|
+
if (typeof retryAfter !== 'string') {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const seconds = Number(retryAfter);
|
|
57
|
+
if (Number.isFinite(seconds) && seconds > 0) {
|
|
58
|
+
return seconds * 1000;
|
|
59
|
+
}
|
|
60
|
+
const dateMs = Date.parse(retryAfter);
|
|
61
|
+
if (Number.isFinite(dateMs)) {
|
|
62
|
+
return Math.max(0, dateMs - Date.now());
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
function describeRetry(error, attempt, waitMs, maxAttempts) {
|
|
67
|
+
const status = getStatus(error);
|
|
68
|
+
const label = status === undefined ? 'connection' : `status ${status}`;
|
|
69
|
+
console.error(`[nim] retry ${attempt + 1}/${maxAttempts} after ${label}; waiting ${waitMs} ms`);
|
|
70
|
+
}
|
|
71
|
+
export class NvidiaNimClient {
|
|
72
|
+
static requestQueue = Promise.resolve();
|
|
73
|
+
static lastRequestAt = 0;
|
|
74
|
+
client;
|
|
75
|
+
requestDelayMs;
|
|
76
|
+
maxAttempts;
|
|
77
|
+
maxRetryDelayMs;
|
|
78
|
+
constructor(config) {
|
|
79
|
+
this.requestDelayMs = config.requestDelayMs;
|
|
80
|
+
this.maxAttempts = config.maxAttempts;
|
|
81
|
+
this.maxRetryDelayMs = config.maxRetryDelayMs;
|
|
82
|
+
this.client = new OpenAI({
|
|
83
|
+
apiKey: config.apiKey,
|
|
84
|
+
baseURL: config.baseUrl,
|
|
85
|
+
maxRetries: 0,
|
|
86
|
+
timeout: config.timeoutMs
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async runSerializedRequest(operation) {
|
|
90
|
+
const previousRequest = NvidiaNimClient.requestQueue;
|
|
91
|
+
let releaseCurrentRequest = () => undefined;
|
|
92
|
+
NvidiaNimClient.requestQueue = new Promise((resolve) => {
|
|
93
|
+
releaseCurrentRequest = resolve;
|
|
94
|
+
});
|
|
95
|
+
await previousRequest;
|
|
96
|
+
try {
|
|
97
|
+
if (this.requestDelayMs > 0) {
|
|
98
|
+
const elapsedMs = Date.now() - NvidiaNimClient.lastRequestAt;
|
|
99
|
+
const waitMs = Math.max(0, this.requestDelayMs - elapsedMs);
|
|
100
|
+
if (waitMs > 0) {
|
|
101
|
+
await sleep(waitMs);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
NvidiaNimClient.lastRequestAt = Date.now();
|
|
105
|
+
return await operation();
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
releaseCurrentRequest();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async complete(request) {
|
|
112
|
+
let lastError;
|
|
113
|
+
for (let attempt = 0; attempt < this.maxAttempts; attempt += 1) {
|
|
114
|
+
try {
|
|
115
|
+
const extraBody = request.chatTemplateKwargs
|
|
116
|
+
? {
|
|
117
|
+
chat_template_kwargs: request.chatTemplateKwargs
|
|
118
|
+
}
|
|
119
|
+
: undefined;
|
|
120
|
+
const requestBody = {
|
|
121
|
+
model: request.model,
|
|
122
|
+
messages: request.messages,
|
|
123
|
+
temperature: request.temperature,
|
|
124
|
+
max_tokens: request.maxTokens,
|
|
125
|
+
response_format: request.responseFormat ? { type: request.responseFormat } : undefined,
|
|
126
|
+
chat_template_kwargs: request.chatTemplateKwargs,
|
|
127
|
+
extra_body: extraBody,
|
|
128
|
+
stream: false
|
|
129
|
+
};
|
|
130
|
+
const response = (await this.runSerializedRequest(() => this.client.chat.completions.create(requestBody)));
|
|
131
|
+
const message = response.choices[0]?.message;
|
|
132
|
+
const content = extractMessageText(message);
|
|
133
|
+
if (content === undefined) {
|
|
134
|
+
const keys = message ? Object.keys(message).sort().join(',') : 'none';
|
|
135
|
+
throw new Error(`NVIDIA NIM response did not include text content; message keys: ${keys}`);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
content,
|
|
139
|
+
model: response.model,
|
|
140
|
+
usage: {
|
|
141
|
+
promptTokens: response.usage?.prompt_tokens,
|
|
142
|
+
completionTokens: response.usage?.completion_tokens,
|
|
143
|
+
totalTokens: response.usage?.total_tokens
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
lastError = error;
|
|
149
|
+
const status = getStatus(error);
|
|
150
|
+
const retryable = status === undefined || RETRYABLE_STATUSES.has(status);
|
|
151
|
+
if (!retryable || attempt === this.maxAttempts - 1) {
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
const retryAfterMs = getRetryAfterMs(error);
|
|
155
|
+
const baseDelayMs = status === 429 ? 5000 : 500;
|
|
156
|
+
const jitter = Math.floor(Math.random() * 500);
|
|
157
|
+
const waitMs = retryAfterMs ?? Math.min(this.maxRetryDelayMs, baseDelayMs * 2 ** attempt + jitter);
|
|
158
|
+
describeRetry(error, attempt, waitMs, this.maxAttempts);
|
|
159
|
+
await sleep(waitMs);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=nvidia-nim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nvidia-nim.js","sourceRoot":"","sources":["../../../packages/cli/src/adapters/nvidia-nim.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { hashJson } from './hash.js';
|
|
4
|
+
export class JsonCache {
|
|
5
|
+
rootDir;
|
|
6
|
+
constructor(rootDir = '.cache/skillcheck') {
|
|
7
|
+
this.rootDir = rootDir;
|
|
8
|
+
}
|
|
9
|
+
async getOrSet(namespace, keyParts, factory) {
|
|
10
|
+
const key = hashJson(keyParts);
|
|
11
|
+
const dir = path.join(this.rootDir, namespace);
|
|
12
|
+
const file = path.join(dir, `${key}.json`);
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(await readFile(file, 'utf8'));
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (error.code !== 'ENOENT') {
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const value = await factory();
|
|
22
|
+
await mkdir(dir, { recursive: true });
|
|
23
|
+
await writeFile(file, JSON.stringify(value, null, 2));
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../packages/cli/src/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,MAAM,OAAO,SAAS;IACS;IAA7B,YAA6B,UAAU,mBAAmB;QAA7B,YAAO,GAAP,OAAO,CAAsB;IAAG,CAAC;IAE9D,KAAK,CAAC,QAAQ,CAAI,SAAiB,EAAE,QAAiB,EAAE,OAAyB;QAC/E,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC;QAE3C,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAM,CAAC;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvD,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,OAAO,EAAE,CAAC;QAC9B,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACtD,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function main(argv: string[]): Promise<void>;
|