llm-output-guard 1.9.0 → 1.10.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/README.md +22 -4
- package/dist/agent-types-D310_xck.d.cts +97 -0
- package/dist/agent-types-D310_xck.d.ts +97 -0
- package/dist/agent.d.cts +3 -97
- package/dist/agent.d.ts +3 -97
- package/dist/ai-sdk.cjs +26 -0
- package/dist/ai-sdk.cjs.map +1 -1
- package/dist/ai-sdk.d.cts +18 -1
- package/dist/ai-sdk.d.ts +18 -1
- package/dist/ai-sdk.js +22 -2
- package/dist/ai-sdk.js.map +1 -1
- package/dist/anthropic.cjs +14 -0
- package/dist/anthropic.cjs.map +1 -1
- package/dist/anthropic.d.cts +18 -1
- package/dist/anthropic.d.ts +18 -1
- package/dist/anthropic.js +11 -3
- package/dist/anthropic.js.map +1 -1
- package/dist/bin.cjs +375 -7
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +375 -7
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-TMTDOMZV.js → chunk-ILRYA54W.js} +8 -3
- package/dist/chunk-ILRYA54W.js.map +1 -0
- package/dist/{chunk-5RDXIKYO.js → chunk-WKG4QRTI.js} +3 -3
- package/dist/{chunk-5RDXIKYO.js.map → chunk-WKG4QRTI.js.map} +1 -1
- package/dist/google.cjs +17 -0
- package/dist/google.cjs.map +1 -1
- package/dist/google.d.cts +17 -1
- package/dist/google.d.ts +17 -1
- package/dist/google.js +14 -3
- package/dist/google.js.map +1 -1
- package/dist/openai.cjs +34 -2
- package/dist/openai.cjs.map +1 -1
- package/dist/openai.d.cts +28 -1
- package/dist/openai.d.ts +28 -1
- package/dist/openai.js +31 -5
- package/dist/openai.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-TMTDOMZV.js.map +0 -1
package/README.md
CHANGED
|
@@ -73,9 +73,12 @@ every detector, running on your own pasted output. No API key, no request.
|
|
|
73
73
|
| `SCRIPT_MISMATCH` | Answered in the wrong alphabet | Share of letters outside expected scripts · opt-in |
|
|
74
74
|
| `LANG_MISMATCH` | Wrong language, same alphabet | Function-word profile · opt-in |
|
|
75
75
|
| `PROMPT_ECHO` | Returned your prompt instead of an answer | Share of output copied from the prompt · opt-in |
|
|
76
|
+
| `AGENT_LOOP` | An agent run circling instead of advancing | Exact periodicity across turns · `llm-output-guard/agent` |
|
|
76
77
|
|
|
77
78
|
Every detector runs even after one fails, so a verdict shows the whole picture
|
|
78
|
-
rather than whichever check happened to be ordered first.
|
|
79
|
+
rather than whichever check happened to be ordered first. All but the last read
|
|
80
|
+
one response; `AGENT_LOOP` reads a *sequence* of them and comes from its own
|
|
81
|
+
entry point, so `checkOutput` never returns it. Each returns **0–1, not
|
|
79
82
|
a boolean** — you pick the line.
|
|
80
83
|
|
|
81
84
|
**Full reference, with the measurements behind every default:
|
|
@@ -112,16 +115,23 @@ sequence.
|
|
|
112
115
|
|
|
113
116
|
```ts
|
|
114
117
|
import { createAgentGuard } from 'llm-output-guard/agent';
|
|
118
|
+
import { toTurn } from 'llm-output-guard/openai';
|
|
115
119
|
|
|
116
120
|
const guard = createAgentGuard();
|
|
117
121
|
|
|
118
122
|
while (!done) {
|
|
119
|
-
const
|
|
120
|
-
const verdict = guard.observe(
|
|
123
|
+
const completion = await client.chat.completions.create(params);
|
|
124
|
+
const verdict = guard.observe(toTurn(completion));
|
|
121
125
|
if (!verdict.ok) break; // the run is circling; stop paying for it
|
|
122
126
|
}
|
|
123
127
|
```
|
|
124
128
|
|
|
129
|
+
`toTurn` ships from all four adapter subpaths and reads whatever that provider
|
|
130
|
+
sends — a JSON-string argument from OpenAI, a parsed object from Anthropic,
|
|
131
|
+
thought parts excluded on Gemini. Map a field by hand and get it wrong and
|
|
132
|
+
nothing throws: every turn fingerprints differently, the score pins at 0.000,
|
|
133
|
+
and you have a guard that never runs.
|
|
134
|
+
|
|
125
135
|
`AGENT_LOOP` reads one axis nothing else here reads. It looks for an **exact**
|
|
126
136
|
repeating cycle of turns, which is what keeps it off the shapes that dominate
|
|
127
137
|
healthy agent traffic: twenty reads of twenty different files, an identical
|
|
@@ -235,6 +245,13 @@ responses you already have, then derive thresholds you can defend:
|
|
|
235
245
|
npx llm-output-guard check logs/*.txt --json | npx llm-output-guard calibrate --fpr 0.001
|
|
236
246
|
```
|
|
237
247
|
|
|
248
|
+
Agent runs calibrate the same way — `--trace` reads one run per line and scores
|
|
249
|
+
`AGENT_LOOP` instead of the per-response detectors:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
npx llm-output-guard check runs.jsonl --trace --json | npx llm-output-guard calibrate
|
|
253
|
+
```
|
|
254
|
+
|
|
238
255
|
`check` also works as a CI assertion — it exits 1 when anything is degenerate,
|
|
239
256
|
2 when the input cannot be read.
|
|
240
257
|
|
|
@@ -261,7 +278,8 @@ the public surface was frozen export by export in that release.
|
|
|
261
278
|
**The public API is** everything exported from `llm-output-guard`, plus
|
|
262
279
|
`outputGuard` / `OutputGuardOptions` / `DegenerateAction` from `./ai-sdk`,
|
|
263
280
|
`withOutputGuard` / `OutputGuardOptions` / `DegenerateAction` from `./openai`,
|
|
264
|
-
`./anthropic` and `./google`,
|
|
281
|
+
`./anthropic` and `./google`, `toTurn` from each of those four, and
|
|
282
|
+
`checkTrace` / `assertTrace` /
|
|
265
283
|
`createAgentGuard` / `agentLoopScore` / `agentLoopDetail` from `./agent`. Each
|
|
266
284
|
subpath is its own contract, so an option added to one is not
|
|
267
285
|
a promise about the others. Anything else is internal and may move in any
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shapes `./agent` reads.
|
|
3
|
+
*
|
|
4
|
+
* Structurally typed and provider-neutral, for the same reason the adapters
|
|
5
|
+
* are: a trace is assembled by the caller from whatever their framework hands
|
|
6
|
+
* back, and requiring a provider's own type here would make this subpath
|
|
7
|
+
* depend on a provider.
|
|
8
|
+
*/
|
|
9
|
+
/** One tool call in a turn. */
|
|
10
|
+
interface AgentToolCall {
|
|
11
|
+
/**
|
|
12
|
+
* The tool's name. OpenAI spells it `function.name`, Anthropic and the AI SDK
|
|
13
|
+
* spell it `name`, and the caller maps whichever they have.
|
|
14
|
+
*
|
|
15
|
+
* Optional because a streamed call can arrive before its name does. A call
|
|
16
|
+
* with no name still fingerprints -- by its arguments -- rather than being
|
|
17
|
+
* dropped, since two nameless calls with identical arguments are still the
|
|
18
|
+
* same call twice.
|
|
19
|
+
*/
|
|
20
|
+
name?: string | null;
|
|
21
|
+
/**
|
|
22
|
+
* The arguments, in any shape a provider sends: a parsed object, or the raw
|
|
23
|
+
* JSON string OpenAI uses. Both canonicalise to the same fingerprint, so a
|
|
24
|
+
* trace assembled from mixed sources still compares.
|
|
25
|
+
*/
|
|
26
|
+
arguments?: unknown;
|
|
27
|
+
}
|
|
28
|
+
/** One model response in an agent run. */
|
|
29
|
+
interface AgentTurn {
|
|
30
|
+
/** The prose the model produced, if any. */
|
|
31
|
+
text?: string | null;
|
|
32
|
+
/**
|
|
33
|
+
* The tool calls the model issued, if any.
|
|
34
|
+
*
|
|
35
|
+
* **A turn carrying tool calls is judged by them alone**, and its `text` is
|
|
36
|
+
* read as a preamble and ignored -- the same rule the single-response
|
|
37
|
+
* adapters already apply. See `internal/turn-fingerprint.ts` for the trap
|
|
38
|
+
* that rule exists to avoid.
|
|
39
|
+
*/
|
|
40
|
+
toolCalls?: readonly AgentToolCall[] | null;
|
|
41
|
+
}
|
|
42
|
+
interface AgentCheckOptions {
|
|
43
|
+
/**
|
|
44
|
+
* How many trailing turns to inspect. Default 12.
|
|
45
|
+
*
|
|
46
|
+
* The same idea as `TAIL_LOOP`'s tail: what matters is whether the agent is
|
|
47
|
+
* stuck *now*, not whether it repeated itself twenty turns ago and recovered.
|
|
48
|
+
* A trace shorter than the window is measured whole.
|
|
49
|
+
*
|
|
50
|
+
* It also bounds the longest cycle that can be found, at `window / minRepeats`
|
|
51
|
+
* turns -- 4 at the defaults. Raise both to catch longer orbits, and expect
|
|
52
|
+
* a loop at the very end of a long trace to score lower, because the score is
|
|
53
|
+
* coverage of the window rather than a count.
|
|
54
|
+
*/
|
|
55
|
+
window?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Turns required before this will judge at all. Default 4.
|
|
58
|
+
*
|
|
59
|
+
* Below it the detector abstains, which is the rule everywhere else in this
|
|
60
|
+
* package. Two identical turns is a retry, and three is a short poll; neither
|
|
61
|
+
* is evidence that an agent has stopped advancing.
|
|
62
|
+
*/
|
|
63
|
+
minTurns?: number;
|
|
64
|
+
/** How many times a block must repeat to count as a cycle. Default 3. */
|
|
65
|
+
minRepeats?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Longest cycle to look for, in turns. Default 4.
|
|
68
|
+
*
|
|
69
|
+
* Capped by `window / minRepeats` regardless, since a block cannot repeat
|
|
70
|
+
* three times inside a window that does not hold it three times.
|
|
71
|
+
*/
|
|
72
|
+
maxPeriod?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Cycle-coverage threshold. Set null to disable. Default 0.4.
|
|
75
|
+
*
|
|
76
|
+
* Measured on this repo's agent corpus: every healthy trace scores **0.000**,
|
|
77
|
+
* including the traps built to look like loops, and the weakest degenerate
|
|
78
|
+
* trace scores 0.455. The default sits in that gap, nearer the healthy side
|
|
79
|
+
* because nothing healthy approaches it.
|
|
80
|
+
*/
|
|
81
|
+
maxAgentLoop?: number | null;
|
|
82
|
+
/**
|
|
83
|
+
* Tools whose calls are dropped before fingerprinting. Empty by default.
|
|
84
|
+
*
|
|
85
|
+
* The escape hatch for a tool whose whole job is to be called repeatedly with
|
|
86
|
+
* identical arguments -- polling a job, sleeping, reading a clock. By shape
|
|
87
|
+
* those are indistinguishable from a loop, so naming them is the only honest
|
|
88
|
+
* way to separate them, in the same way `PROMPT_ECHO` cannot be pointed at a
|
|
89
|
+
* translate endpoint.
|
|
90
|
+
*
|
|
91
|
+
* A turn whose calls are *all* ignored drops out of the trace entirely rather
|
|
92
|
+
* than falling back to its preamble text.
|
|
93
|
+
*/
|
|
94
|
+
ignoreTools?: readonly string[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type { AgentTurn as A, AgentCheckOptions as a, AgentToolCall as b };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shapes `./agent` reads.
|
|
3
|
+
*
|
|
4
|
+
* Structurally typed and provider-neutral, for the same reason the adapters
|
|
5
|
+
* are: a trace is assembled by the caller from whatever their framework hands
|
|
6
|
+
* back, and requiring a provider's own type here would make this subpath
|
|
7
|
+
* depend on a provider.
|
|
8
|
+
*/
|
|
9
|
+
/** One tool call in a turn. */
|
|
10
|
+
interface AgentToolCall {
|
|
11
|
+
/**
|
|
12
|
+
* The tool's name. OpenAI spells it `function.name`, Anthropic and the AI SDK
|
|
13
|
+
* spell it `name`, and the caller maps whichever they have.
|
|
14
|
+
*
|
|
15
|
+
* Optional because a streamed call can arrive before its name does. A call
|
|
16
|
+
* with no name still fingerprints -- by its arguments -- rather than being
|
|
17
|
+
* dropped, since two nameless calls with identical arguments are still the
|
|
18
|
+
* same call twice.
|
|
19
|
+
*/
|
|
20
|
+
name?: string | null;
|
|
21
|
+
/**
|
|
22
|
+
* The arguments, in any shape a provider sends: a parsed object, or the raw
|
|
23
|
+
* JSON string OpenAI uses. Both canonicalise to the same fingerprint, so a
|
|
24
|
+
* trace assembled from mixed sources still compares.
|
|
25
|
+
*/
|
|
26
|
+
arguments?: unknown;
|
|
27
|
+
}
|
|
28
|
+
/** One model response in an agent run. */
|
|
29
|
+
interface AgentTurn {
|
|
30
|
+
/** The prose the model produced, if any. */
|
|
31
|
+
text?: string | null;
|
|
32
|
+
/**
|
|
33
|
+
* The tool calls the model issued, if any.
|
|
34
|
+
*
|
|
35
|
+
* **A turn carrying tool calls is judged by them alone**, and its `text` is
|
|
36
|
+
* read as a preamble and ignored -- the same rule the single-response
|
|
37
|
+
* adapters already apply. See `internal/turn-fingerprint.ts` for the trap
|
|
38
|
+
* that rule exists to avoid.
|
|
39
|
+
*/
|
|
40
|
+
toolCalls?: readonly AgentToolCall[] | null;
|
|
41
|
+
}
|
|
42
|
+
interface AgentCheckOptions {
|
|
43
|
+
/**
|
|
44
|
+
* How many trailing turns to inspect. Default 12.
|
|
45
|
+
*
|
|
46
|
+
* The same idea as `TAIL_LOOP`'s tail: what matters is whether the agent is
|
|
47
|
+
* stuck *now*, not whether it repeated itself twenty turns ago and recovered.
|
|
48
|
+
* A trace shorter than the window is measured whole.
|
|
49
|
+
*
|
|
50
|
+
* It also bounds the longest cycle that can be found, at `window / minRepeats`
|
|
51
|
+
* turns -- 4 at the defaults. Raise both to catch longer orbits, and expect
|
|
52
|
+
* a loop at the very end of a long trace to score lower, because the score is
|
|
53
|
+
* coverage of the window rather than a count.
|
|
54
|
+
*/
|
|
55
|
+
window?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Turns required before this will judge at all. Default 4.
|
|
58
|
+
*
|
|
59
|
+
* Below it the detector abstains, which is the rule everywhere else in this
|
|
60
|
+
* package. Two identical turns is a retry, and three is a short poll; neither
|
|
61
|
+
* is evidence that an agent has stopped advancing.
|
|
62
|
+
*/
|
|
63
|
+
minTurns?: number;
|
|
64
|
+
/** How many times a block must repeat to count as a cycle. Default 3. */
|
|
65
|
+
minRepeats?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Longest cycle to look for, in turns. Default 4.
|
|
68
|
+
*
|
|
69
|
+
* Capped by `window / minRepeats` regardless, since a block cannot repeat
|
|
70
|
+
* three times inside a window that does not hold it three times.
|
|
71
|
+
*/
|
|
72
|
+
maxPeriod?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Cycle-coverage threshold. Set null to disable. Default 0.4.
|
|
75
|
+
*
|
|
76
|
+
* Measured on this repo's agent corpus: every healthy trace scores **0.000**,
|
|
77
|
+
* including the traps built to look like loops, and the weakest degenerate
|
|
78
|
+
* trace scores 0.455. The default sits in that gap, nearer the healthy side
|
|
79
|
+
* because nothing healthy approaches it.
|
|
80
|
+
*/
|
|
81
|
+
maxAgentLoop?: number | null;
|
|
82
|
+
/**
|
|
83
|
+
* Tools whose calls are dropped before fingerprinting. Empty by default.
|
|
84
|
+
*
|
|
85
|
+
* The escape hatch for a tool whose whole job is to be called repeatedly with
|
|
86
|
+
* identical arguments -- polling a job, sleeping, reading a clock. By shape
|
|
87
|
+
* those are indistinguishable from a loop, so naming them is the only honest
|
|
88
|
+
* way to separate them, in the same way `PROMPT_ECHO` cannot be pointed at a
|
|
89
|
+
* translate endpoint.
|
|
90
|
+
*
|
|
91
|
+
* A turn whose calls are *all* ignored drops out of the trace entirely rather
|
|
92
|
+
* than falling back to its preamble text.
|
|
93
|
+
*/
|
|
94
|
+
ignoreTools?: readonly string[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type { AgentTurn as A, AgentCheckOptions as a, AgentToolCall as b };
|
package/dist/agent.d.cts
CHANGED
|
@@ -1,102 +1,8 @@
|
|
|
1
1
|
import { V as Verdict } from './types-CxKV_wpA.cjs';
|
|
2
|
+
import { A as AgentTurn, a as AgentCheckOptions } from './agent-types-D310_xck.cjs';
|
|
3
|
+
export { b as AgentToolCall } from './agent-types-D310_xck.cjs';
|
|
2
4
|
export { D as DegenerateOutputError } from './check-B7lLF8a8.cjs';
|
|
3
5
|
|
|
4
|
-
/**
|
|
5
|
-
* The shapes `./agent` reads.
|
|
6
|
-
*
|
|
7
|
-
* Structurally typed and provider-neutral, for the same reason the adapters
|
|
8
|
-
* are: a trace is assembled by the caller from whatever their framework hands
|
|
9
|
-
* back, and requiring a provider's own type here would make this subpath
|
|
10
|
-
* depend on a provider.
|
|
11
|
-
*/
|
|
12
|
-
/** One tool call in a turn. */
|
|
13
|
-
interface AgentToolCall {
|
|
14
|
-
/**
|
|
15
|
-
* The tool's name. OpenAI spells it `function.name`, Anthropic and the AI SDK
|
|
16
|
-
* spell it `name`, and the caller maps whichever they have.
|
|
17
|
-
*
|
|
18
|
-
* Optional because a streamed call can arrive before its name does. A call
|
|
19
|
-
* with no name still fingerprints -- by its arguments -- rather than being
|
|
20
|
-
* dropped, since two nameless calls with identical arguments are still the
|
|
21
|
-
* same call twice.
|
|
22
|
-
*/
|
|
23
|
-
name?: string | null;
|
|
24
|
-
/**
|
|
25
|
-
* The arguments, in any shape a provider sends: a parsed object, or the raw
|
|
26
|
-
* JSON string OpenAI uses. Both canonicalise to the same fingerprint, so a
|
|
27
|
-
* trace assembled from mixed sources still compares.
|
|
28
|
-
*/
|
|
29
|
-
arguments?: unknown;
|
|
30
|
-
}
|
|
31
|
-
/** One model response in an agent run. */
|
|
32
|
-
interface AgentTurn {
|
|
33
|
-
/** The prose the model produced, if any. */
|
|
34
|
-
text?: string | null;
|
|
35
|
-
/**
|
|
36
|
-
* The tool calls the model issued, if any.
|
|
37
|
-
*
|
|
38
|
-
* **A turn carrying tool calls is judged by them alone**, and its `text` is
|
|
39
|
-
* read as a preamble and ignored -- the same rule the single-response
|
|
40
|
-
* adapters already apply. See `internal/turn-fingerprint.ts` for the trap
|
|
41
|
-
* that rule exists to avoid.
|
|
42
|
-
*/
|
|
43
|
-
toolCalls?: readonly AgentToolCall[] | null;
|
|
44
|
-
}
|
|
45
|
-
interface AgentCheckOptions {
|
|
46
|
-
/**
|
|
47
|
-
* How many trailing turns to inspect. Default 12.
|
|
48
|
-
*
|
|
49
|
-
* The same idea as `TAIL_LOOP`'s tail: what matters is whether the agent is
|
|
50
|
-
* stuck *now*, not whether it repeated itself twenty turns ago and recovered.
|
|
51
|
-
* A trace shorter than the window is measured whole.
|
|
52
|
-
*
|
|
53
|
-
* It also bounds the longest cycle that can be found, at `window / minRepeats`
|
|
54
|
-
* turns -- 4 at the defaults. Raise both to catch longer orbits, and expect
|
|
55
|
-
* a loop at the very end of a long trace to score lower, because the score is
|
|
56
|
-
* coverage of the window rather than a count.
|
|
57
|
-
*/
|
|
58
|
-
window?: number;
|
|
59
|
-
/**
|
|
60
|
-
* Turns required before this will judge at all. Default 4.
|
|
61
|
-
*
|
|
62
|
-
* Below it the detector abstains, which is the rule everywhere else in this
|
|
63
|
-
* package. Two identical turns is a retry, and three is a short poll; neither
|
|
64
|
-
* is evidence that an agent has stopped advancing.
|
|
65
|
-
*/
|
|
66
|
-
minTurns?: number;
|
|
67
|
-
/** How many times a block must repeat to count as a cycle. Default 3. */
|
|
68
|
-
minRepeats?: number;
|
|
69
|
-
/**
|
|
70
|
-
* Longest cycle to look for, in turns. Default 4.
|
|
71
|
-
*
|
|
72
|
-
* Capped by `window / minRepeats` regardless, since a block cannot repeat
|
|
73
|
-
* three times inside a window that does not hold it three times.
|
|
74
|
-
*/
|
|
75
|
-
maxPeriod?: number;
|
|
76
|
-
/**
|
|
77
|
-
* Cycle-coverage threshold. Set null to disable. Default 0.4.
|
|
78
|
-
*
|
|
79
|
-
* Measured on this repo's agent corpus: every healthy trace scores **0.000**,
|
|
80
|
-
* including the traps built to look like loops, and the weakest degenerate
|
|
81
|
-
* trace scores 0.455. The default sits in that gap, nearer the healthy side
|
|
82
|
-
* because nothing healthy approaches it.
|
|
83
|
-
*/
|
|
84
|
-
maxAgentLoop?: number | null;
|
|
85
|
-
/**
|
|
86
|
-
* Tools whose calls are dropped before fingerprinting. Empty by default.
|
|
87
|
-
*
|
|
88
|
-
* The escape hatch for a tool whose whole job is to be called repeatedly with
|
|
89
|
-
* identical arguments -- polling a job, sleeping, reading a clock. By shape
|
|
90
|
-
* those are indistinguishable from a loop, so naming them is the only honest
|
|
91
|
-
* way to separate them, in the same way `PROMPT_ECHO` cannot be pointed at a
|
|
92
|
-
* translate endpoint.
|
|
93
|
-
*
|
|
94
|
-
* A turn whose calls are *all* ignored drops out of the trace entirely rather
|
|
95
|
-
* than falling back to its preamble text.
|
|
96
|
-
*/
|
|
97
|
-
ignoreTools?: readonly string[];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
6
|
/**
|
|
101
7
|
* The failure that every other detector in this package scores 0.000 on.
|
|
102
8
|
*
|
|
@@ -238,4 +144,4 @@ interface AgentGuard {
|
|
|
238
144
|
*/
|
|
239
145
|
declare function createAgentGuard(options?: AgentCheckOptions): AgentGuard;
|
|
240
146
|
|
|
241
|
-
export {
|
|
147
|
+
export { AgentCheckOptions, type AgentGuard, type AgentLoopResult, AgentTurn, agentLoopDetail, agentLoopScore, assertTrace, checkTrace, createAgentGuard };
|
package/dist/agent.d.ts
CHANGED
|
@@ -1,102 +1,8 @@
|
|
|
1
1
|
import { V as Verdict } from './types-CxKV_wpA.js';
|
|
2
|
+
import { A as AgentTurn, a as AgentCheckOptions } from './agent-types-D310_xck.js';
|
|
3
|
+
export { b as AgentToolCall } from './agent-types-D310_xck.js';
|
|
2
4
|
export { D as DegenerateOutputError } from './check-DiKuVvZM.js';
|
|
3
5
|
|
|
4
|
-
/**
|
|
5
|
-
* The shapes `./agent` reads.
|
|
6
|
-
*
|
|
7
|
-
* Structurally typed and provider-neutral, for the same reason the adapters
|
|
8
|
-
* are: a trace is assembled by the caller from whatever their framework hands
|
|
9
|
-
* back, and requiring a provider's own type here would make this subpath
|
|
10
|
-
* depend on a provider.
|
|
11
|
-
*/
|
|
12
|
-
/** One tool call in a turn. */
|
|
13
|
-
interface AgentToolCall {
|
|
14
|
-
/**
|
|
15
|
-
* The tool's name. OpenAI spells it `function.name`, Anthropic and the AI SDK
|
|
16
|
-
* spell it `name`, and the caller maps whichever they have.
|
|
17
|
-
*
|
|
18
|
-
* Optional because a streamed call can arrive before its name does. A call
|
|
19
|
-
* with no name still fingerprints -- by its arguments -- rather than being
|
|
20
|
-
* dropped, since two nameless calls with identical arguments are still the
|
|
21
|
-
* same call twice.
|
|
22
|
-
*/
|
|
23
|
-
name?: string | null;
|
|
24
|
-
/**
|
|
25
|
-
* The arguments, in any shape a provider sends: a parsed object, or the raw
|
|
26
|
-
* JSON string OpenAI uses. Both canonicalise to the same fingerprint, so a
|
|
27
|
-
* trace assembled from mixed sources still compares.
|
|
28
|
-
*/
|
|
29
|
-
arguments?: unknown;
|
|
30
|
-
}
|
|
31
|
-
/** One model response in an agent run. */
|
|
32
|
-
interface AgentTurn {
|
|
33
|
-
/** The prose the model produced, if any. */
|
|
34
|
-
text?: string | null;
|
|
35
|
-
/**
|
|
36
|
-
* The tool calls the model issued, if any.
|
|
37
|
-
*
|
|
38
|
-
* **A turn carrying tool calls is judged by them alone**, and its `text` is
|
|
39
|
-
* read as a preamble and ignored -- the same rule the single-response
|
|
40
|
-
* adapters already apply. See `internal/turn-fingerprint.ts` for the trap
|
|
41
|
-
* that rule exists to avoid.
|
|
42
|
-
*/
|
|
43
|
-
toolCalls?: readonly AgentToolCall[] | null;
|
|
44
|
-
}
|
|
45
|
-
interface AgentCheckOptions {
|
|
46
|
-
/**
|
|
47
|
-
* How many trailing turns to inspect. Default 12.
|
|
48
|
-
*
|
|
49
|
-
* The same idea as `TAIL_LOOP`'s tail: what matters is whether the agent is
|
|
50
|
-
* stuck *now*, not whether it repeated itself twenty turns ago and recovered.
|
|
51
|
-
* A trace shorter than the window is measured whole.
|
|
52
|
-
*
|
|
53
|
-
* It also bounds the longest cycle that can be found, at `window / minRepeats`
|
|
54
|
-
* turns -- 4 at the defaults. Raise both to catch longer orbits, and expect
|
|
55
|
-
* a loop at the very end of a long trace to score lower, because the score is
|
|
56
|
-
* coverage of the window rather than a count.
|
|
57
|
-
*/
|
|
58
|
-
window?: number;
|
|
59
|
-
/**
|
|
60
|
-
* Turns required before this will judge at all. Default 4.
|
|
61
|
-
*
|
|
62
|
-
* Below it the detector abstains, which is the rule everywhere else in this
|
|
63
|
-
* package. Two identical turns is a retry, and three is a short poll; neither
|
|
64
|
-
* is evidence that an agent has stopped advancing.
|
|
65
|
-
*/
|
|
66
|
-
minTurns?: number;
|
|
67
|
-
/** How many times a block must repeat to count as a cycle. Default 3. */
|
|
68
|
-
minRepeats?: number;
|
|
69
|
-
/**
|
|
70
|
-
* Longest cycle to look for, in turns. Default 4.
|
|
71
|
-
*
|
|
72
|
-
* Capped by `window / minRepeats` regardless, since a block cannot repeat
|
|
73
|
-
* three times inside a window that does not hold it three times.
|
|
74
|
-
*/
|
|
75
|
-
maxPeriod?: number;
|
|
76
|
-
/**
|
|
77
|
-
* Cycle-coverage threshold. Set null to disable. Default 0.4.
|
|
78
|
-
*
|
|
79
|
-
* Measured on this repo's agent corpus: every healthy trace scores **0.000**,
|
|
80
|
-
* including the traps built to look like loops, and the weakest degenerate
|
|
81
|
-
* trace scores 0.455. The default sits in that gap, nearer the healthy side
|
|
82
|
-
* because nothing healthy approaches it.
|
|
83
|
-
*/
|
|
84
|
-
maxAgentLoop?: number | null;
|
|
85
|
-
/**
|
|
86
|
-
* Tools whose calls are dropped before fingerprinting. Empty by default.
|
|
87
|
-
*
|
|
88
|
-
* The escape hatch for a tool whose whole job is to be called repeatedly with
|
|
89
|
-
* identical arguments -- polling a job, sleeping, reading a clock. By shape
|
|
90
|
-
* those are indistinguishable from a loop, so naming them is the only honest
|
|
91
|
-
* way to separate them, in the same way `PROMPT_ECHO` cannot be pointed at a
|
|
92
|
-
* translate endpoint.
|
|
93
|
-
*
|
|
94
|
-
* A turn whose calls are *all* ignored drops out of the trace entirely rather
|
|
95
|
-
* than falling back to its preamble text.
|
|
96
|
-
*/
|
|
97
|
-
ignoreTools?: readonly string[];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
6
|
/**
|
|
101
7
|
* The failure that every other detector in this package scores 0.000 on.
|
|
102
8
|
*
|
|
@@ -238,4 +144,4 @@ interface AgentGuard {
|
|
|
238
144
|
*/
|
|
239
145
|
declare function createAgentGuard(options?: AgentCheckOptions): AgentGuard;
|
|
240
146
|
|
|
241
|
-
export {
|
|
147
|
+
export { AgentCheckOptions, type AgentGuard, type AgentLoopResult, AgentTurn, agentLoopDetail, agentLoopScore, assertTrace, checkTrace, createAgentGuard };
|
package/dist/ai-sdk.cjs
CHANGED
|
@@ -748,6 +748,11 @@ function promptFromMessages(messages) {
|
|
|
748
748
|
return parts.join("\n\n");
|
|
749
749
|
}
|
|
750
750
|
|
|
751
|
+
// src/internal/as-array.ts
|
|
752
|
+
function asArray(value) {
|
|
753
|
+
return Array.isArray(value) ? value : [];
|
|
754
|
+
}
|
|
755
|
+
|
|
751
756
|
// src/ai-sdk.ts
|
|
752
757
|
var isToolPart = (part) => part.type.startsWith("tool-");
|
|
753
758
|
function finishReasonOf(value) {
|
|
@@ -863,7 +868,28 @@ function outputGuard(options = {}) {
|
|
|
863
868
|
}
|
|
864
869
|
};
|
|
865
870
|
}
|
|
871
|
+
function toTurn(result) {
|
|
872
|
+
if (result == null || typeof result !== "object") return {};
|
|
873
|
+
const value = result;
|
|
874
|
+
if (Array.isArray(value.content)) {
|
|
875
|
+
return {
|
|
876
|
+
text: value.content.filter((part) => part.type === "text").map((part) => part.text ?? "").join(""),
|
|
877
|
+
toolCalls: value.content.filter(isToolPart).map((part) => ({
|
|
878
|
+
name: part.toolName,
|
|
879
|
+
arguments: part.input ?? part.args
|
|
880
|
+
}))
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
return {
|
|
884
|
+
text: typeof value.text === "string" ? value.text : "",
|
|
885
|
+
toolCalls: asArray(value.toolCalls).map((call) => ({
|
|
886
|
+
name: call.toolName ?? call.name,
|
|
887
|
+
arguments: call.input ?? call.args
|
|
888
|
+
}))
|
|
889
|
+
};
|
|
890
|
+
}
|
|
866
891
|
|
|
867
892
|
exports.outputGuard = outputGuard;
|
|
893
|
+
exports.toTurn = toTurn;
|
|
868
894
|
//# sourceMappingURL=ai-sdk.cjs.map
|
|
869
895
|
//# sourceMappingURL=ai-sdk.cjs.map
|