@spendgraph/llms 0.2.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 +354 -0
- package/dist/adapters/anthropic.d.ts +2 -0
- package/dist/adapters/anthropic.js +101 -0
- package/dist/adapters/bedrock.d.ts +2 -0
- package/dist/adapters/bedrock.js +88 -0
- package/dist/adapters/gemini.d.ts +2 -0
- package/dist/adapters/gemini.js +87 -0
- package/dist/adapters/index.d.ts +5 -0
- package/dist/adapters/index.js +36 -0
- package/dist/adapters/openai.d.ts +2 -0
- package/dist/adapters/openai.js +71 -0
- package/dist/adapters/perplexity.d.ts +2 -0
- package/dist/adapters/perplexity.js +92 -0
- package/dist/adapters/responses.d.ts +2 -0
- package/dist/adapters/responses.js +100 -0
- package/dist/affordable.d.ts +44 -0
- package/dist/affordable.js +87 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +7 -0
- package/dist/llm/drivers.d.ts +36 -0
- package/dist/llm/drivers.js +121 -0
- package/dist/llm/index.d.ts +2 -0
- package/dist/llm/index.js +1 -0
- package/dist/llm/invoke.d.ts +3 -0
- package/dist/llm/invoke.js +24 -0
- package/dist/llm/llm.d.ts +50 -0
- package/dist/llm/llm.js +238 -0
- package/dist/llm/schema/index.d.ts +1 -0
- package/dist/llm/schema/index.js +1 -0
- package/dist/llm/schema/schema.d.ts +45 -0
- package/dist/llm/schema/schema.js +80 -0
- package/dist/llm/types.d.ts +62 -0
- package/dist/llm/types.js +1 -0
- package/dist/model.d.ts +7 -0
- package/dist/model.js +22 -0
- package/dist/read.d.ts +10 -0
- package/dist/read.js +18 -0
- package/dist/report/index.d.ts +2 -0
- package/dist/report/index.js +1 -0
- package/dist/report/ingest.d.ts +15 -0
- package/dist/report/ingest.js +29 -0
- package/dist/report/types.d.ts +30 -0
- package/dist/report/types.js +1 -0
- package/dist/request.d.ts +26 -0
- package/dist/request.js +32 -0
- package/dist/stream.d.ts +26 -0
- package/dist/stream.js +71 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.js +1 -0
- package/dist/usage.d.ts +22 -0
- package/dist/usage.js +76 -0
- package/package.json +52 -0
package/dist/stream.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { adapterFor, adapterOfShape } from "./adapters/index.js";
|
|
2
|
+
import { pricingId } from "./model.js";
|
|
3
|
+
import { UnknownReplyError } from "./read.js";
|
|
4
|
+
import { emptyUsage, parseArgs, reply } from "./usage.js";
|
|
5
|
+
function newState() {
|
|
6
|
+
return { text: "", model: "", usage: emptyUsage(), tools: new Map() };
|
|
7
|
+
}
|
|
8
|
+
const FAILED = new Set(["refusal", "content_filter", "content_filtered", "SAFETY", "failed"]);
|
|
9
|
+
function finish(state, provider, opts) {
|
|
10
|
+
const toolCalls = [...state.tools.values()].map((pending) => ({
|
|
11
|
+
id: pending.id,
|
|
12
|
+
name: pending.name,
|
|
13
|
+
args: parseArgs(pending.json),
|
|
14
|
+
}));
|
|
15
|
+
const refused = state.stopReason ? FAILED.has(state.stopReason) : false;
|
|
16
|
+
return reply({
|
|
17
|
+
output: state.text,
|
|
18
|
+
model: opts.model ?? pricingId(provider, state.model),
|
|
19
|
+
usage: state.usage,
|
|
20
|
+
toolCalls,
|
|
21
|
+
citations: state.citations,
|
|
22
|
+
stopReason: state.stopReason,
|
|
23
|
+
status: refused ? "failed" : "completed",
|
|
24
|
+
error: refused ? `stopped: ${state.stopReason}` : undefined,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
class LlmStream {
|
|
28
|
+
source;
|
|
29
|
+
opts;
|
|
30
|
+
state = newState();
|
|
31
|
+
adapter;
|
|
32
|
+
done = false;
|
|
33
|
+
constructor(source, opts = {}) {
|
|
34
|
+
this.source = source;
|
|
35
|
+
this.opts = opts;
|
|
36
|
+
const chosen = opts.shape
|
|
37
|
+
? adapterOfShape(opts.shape)
|
|
38
|
+
: opts.provider
|
|
39
|
+
? adapterFor(opts.provider)
|
|
40
|
+
: undefined;
|
|
41
|
+
if (!chosen)
|
|
42
|
+
throw new UnknownReplyError();
|
|
43
|
+
this.adapter = chosen;
|
|
44
|
+
}
|
|
45
|
+
async *[Symbol.asyncIterator]() {
|
|
46
|
+
for await (const event of this.source) {
|
|
47
|
+
const delta = this.adapter.chunk(event, this.state);
|
|
48
|
+
if (!delta)
|
|
49
|
+
continue;
|
|
50
|
+
this.opts.onText?.(delta);
|
|
51
|
+
yield delta;
|
|
52
|
+
}
|
|
53
|
+
this.done = true;
|
|
54
|
+
}
|
|
55
|
+
/** Drains any events not yet read, then assembles the reply. */
|
|
56
|
+
async reply() {
|
|
57
|
+
if (!this.done) {
|
|
58
|
+
for await (const _ of this)
|
|
59
|
+
void _;
|
|
60
|
+
}
|
|
61
|
+
return finish(this.state, this.opts.provider ?? this.adapter.provider, this.opts);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** A stream to iterate for text deltas, then ask for the finished reply. */
|
|
65
|
+
export function stream(source, opts = {}) {
|
|
66
|
+
return new LlmStream(source, opts);
|
|
67
|
+
}
|
|
68
|
+
/** Consumes a stream whole, calling `onText` per delta. */
|
|
69
|
+
export function collect(source, opts = {}) {
|
|
70
|
+
return new LlmStream(source, opts).reply();
|
|
71
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One turn. Structurally the `Message` `@spendgraph/prompt` renders.
|
|
3
|
+
*
|
|
4
|
+
* `assistant` is here for conversation history — prior turns replayed to the
|
|
5
|
+
* model. A prompt's own wording still renders a system turn and a user turn.
|
|
6
|
+
*/
|
|
7
|
+
export interface PromptMessage {
|
|
8
|
+
role: "system" | "user" | "assistant";
|
|
9
|
+
content: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* One model call's result. Structurally the `TraceOutcome` that harness's
|
|
13
|
+
* `prompt.trace()` accepts, so a reply can be returned straight from it.
|
|
14
|
+
*/
|
|
15
|
+
export interface CallOutcome {
|
|
16
|
+
output: string;
|
|
17
|
+
model: string;
|
|
18
|
+
status?: "completed" | "failed";
|
|
19
|
+
error?: string;
|
|
20
|
+
inputTokens?: number;
|
|
21
|
+
outputTokens?: number;
|
|
22
|
+
cacheReadTokens?: number;
|
|
23
|
+
cacheWriteTokens?: number;
|
|
24
|
+
citationTokens?: number;
|
|
25
|
+
reasoningTokens?: number;
|
|
26
|
+
}
|
|
27
|
+
/** A slug from `model_pricing.provider`. Open — the table grows with ingest. */
|
|
28
|
+
export type Provider = "anthropic" | "openai" | "bedrock" | "gemini" | "openrouter" | "zai" | "groq" | "together_ai" | "deepseek" | "mistral" | "xai" | "azure" | "fireworks_ai" | "perplexity" | "ollama" | "vertex_ai" | (string & {});
|
|
29
|
+
export interface LlmUsage {
|
|
30
|
+
inputTokens: number;
|
|
31
|
+
outputTokens: number;
|
|
32
|
+
cacheReadTokens: number;
|
|
33
|
+
cacheWriteTokens: number;
|
|
34
|
+
/** Billed on their own by search-grounded providers. Zero everywhere else. */
|
|
35
|
+
citationTokens: number;
|
|
36
|
+
reasoningTokens: number;
|
|
37
|
+
}
|
|
38
|
+
/** One call the model wants made, with its arguments already parsed. */
|
|
39
|
+
export interface LlmToolCall {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
args: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
/** A provider reply in the shape `prompt.trace()` returns. */
|
|
45
|
+
export interface LlmReply extends CallOutcome {
|
|
46
|
+
usage: LlmUsage;
|
|
47
|
+
toolCalls: LlmToolCall[];
|
|
48
|
+
/** Source URLs, from search-grounded providers that return them. */
|
|
49
|
+
citations?: string[];
|
|
50
|
+
stopReason?: string;
|
|
51
|
+
latencyMs?: number;
|
|
52
|
+
/** Set when usage was recorded. */
|
|
53
|
+
eventId?: string;
|
|
54
|
+
/** The object asked for, when `schema` was passed. Parsed already. */
|
|
55
|
+
data?: unknown;
|
|
56
|
+
}
|
|
57
|
+
export interface ReadOptions {
|
|
58
|
+
/** Pricing slug. Required for OpenAI-compatible gateways, which share a shape. */
|
|
59
|
+
provider?: Provider;
|
|
60
|
+
/** Replaces the id the reply reported. */
|
|
61
|
+
model?: string;
|
|
62
|
+
}
|
|
63
|
+
/** Mutable accumulator a stream folds into, owned by the collector. */
|
|
64
|
+
export interface StreamState {
|
|
65
|
+
text: string;
|
|
66
|
+
model: string;
|
|
67
|
+
stopReason?: string;
|
|
68
|
+
citations?: string[];
|
|
69
|
+
usage: LlmUsage;
|
|
70
|
+
tools: Map<string, {
|
|
71
|
+
id: string;
|
|
72
|
+
name: string;
|
|
73
|
+
json: string;
|
|
74
|
+
}>;
|
|
75
|
+
}
|
|
76
|
+
export interface Adapter {
|
|
77
|
+
provider: Provider;
|
|
78
|
+
detect(value: unknown): boolean;
|
|
79
|
+
read(reply: unknown, opts?: ReadOptions): LlmReply;
|
|
80
|
+
/** Folds one stream event into `state` and returns the text it added. */
|
|
81
|
+
chunk(event: unknown, state: StreamState): string;
|
|
82
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/usage.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { LlmReply, LlmToolCall, LlmUsage } from "./types.js";
|
|
2
|
+
export declare function emptyUsage(): LlmUsage;
|
|
3
|
+
/** A token count from any provider, coerced to a non-negative integer. */
|
|
4
|
+
export declare function count(value: unknown): number;
|
|
5
|
+
export declare function addUsage(into: LlmUsage, more: Partial<LlmUsage>): LlmUsage;
|
|
6
|
+
/**
|
|
7
|
+
* Overwrites each field the provider reported, leaving the rest. Stream events
|
|
8
|
+
* restate running totals rather than sending increments.
|
|
9
|
+
*/
|
|
10
|
+
export declare function setUsage(into: LlmUsage, more: Partial<LlmUsage>): LlmUsage;
|
|
11
|
+
export declare function reply(parts: {
|
|
12
|
+
output: string;
|
|
13
|
+
model: string;
|
|
14
|
+
usage: LlmUsage;
|
|
15
|
+
toolCalls?: LlmToolCall[];
|
|
16
|
+
citations?: string[];
|
|
17
|
+
stopReason?: string;
|
|
18
|
+
status?: "completed" | "failed";
|
|
19
|
+
error?: string;
|
|
20
|
+
}): LlmReply;
|
|
21
|
+
/** Tool arguments, which providers send as a JSON string. Malformed reads `{}`. */
|
|
22
|
+
export declare function parseArgs(raw: unknown): Record<string, unknown>;
|
package/dist/usage.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const USAGE_KEYS = [
|
|
2
|
+
"inputTokens",
|
|
3
|
+
"outputTokens",
|
|
4
|
+
"cacheReadTokens",
|
|
5
|
+
"cacheWriteTokens",
|
|
6
|
+
"citationTokens",
|
|
7
|
+
"reasoningTokens",
|
|
8
|
+
];
|
|
9
|
+
export function emptyUsage() {
|
|
10
|
+
return {
|
|
11
|
+
inputTokens: 0,
|
|
12
|
+
outputTokens: 0,
|
|
13
|
+
cacheReadTokens: 0,
|
|
14
|
+
cacheWriteTokens: 0,
|
|
15
|
+
citationTokens: 0,
|
|
16
|
+
reasoningTokens: 0,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** A token count from any provider, coerced to a non-negative integer. */
|
|
20
|
+
export function count(value) {
|
|
21
|
+
const n = typeof value === "string" ? Number(value) : value;
|
|
22
|
+
return typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.round(n) : 0;
|
|
23
|
+
}
|
|
24
|
+
export function addUsage(into, more) {
|
|
25
|
+
into.inputTokens += more.inputTokens ?? 0;
|
|
26
|
+
into.outputTokens += more.outputTokens ?? 0;
|
|
27
|
+
into.cacheReadTokens += more.cacheReadTokens ?? 0;
|
|
28
|
+
into.cacheWriteTokens += more.cacheWriteTokens ?? 0;
|
|
29
|
+
into.citationTokens += more.citationTokens ?? 0;
|
|
30
|
+
into.reasoningTokens += more.reasoningTokens ?? 0;
|
|
31
|
+
return into;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Overwrites each field the provider reported, leaving the rest. Stream events
|
|
35
|
+
* restate running totals rather than sending increments.
|
|
36
|
+
*/
|
|
37
|
+
export function setUsage(into, more) {
|
|
38
|
+
for (const key of USAGE_KEYS) {
|
|
39
|
+
const value = more[key] ?? 0;
|
|
40
|
+
if (value > 0)
|
|
41
|
+
into[key] = value;
|
|
42
|
+
}
|
|
43
|
+
return into;
|
|
44
|
+
}
|
|
45
|
+
export function reply(parts) {
|
|
46
|
+
return {
|
|
47
|
+
output: parts.output,
|
|
48
|
+
model: parts.model,
|
|
49
|
+
status: parts.status ?? "completed",
|
|
50
|
+
error: parts.error,
|
|
51
|
+
inputTokens: parts.usage.inputTokens,
|
|
52
|
+
outputTokens: parts.usage.outputTokens,
|
|
53
|
+
cacheReadTokens: parts.usage.cacheReadTokens,
|
|
54
|
+
cacheWriteTokens: parts.usage.cacheWriteTokens,
|
|
55
|
+
citationTokens: parts.usage.citationTokens,
|
|
56
|
+
reasoningTokens: parts.usage.reasoningTokens,
|
|
57
|
+
usage: parts.usage,
|
|
58
|
+
toolCalls: parts.toolCalls ?? [],
|
|
59
|
+
...(parts.citations?.length ? { citations: parts.citations } : {}),
|
|
60
|
+
stopReason: parts.stopReason,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Tool arguments, which providers send as a JSON string. Malformed reads `{}`. */
|
|
64
|
+
export function parseArgs(raw) {
|
|
65
|
+
if (raw && typeof raw === "object")
|
|
66
|
+
return raw;
|
|
67
|
+
if (typeof raw !== "string" || !raw.trim())
|
|
68
|
+
return {};
|
|
69
|
+
try {
|
|
70
|
+
const parsed = JSON.parse(raw);
|
|
71
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@spendgraph/llms",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Read any LLM provider reply into one shape. Records usage through @spendgraph/sdk.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/fnLog0/spendgraph.git",
|
|
9
|
+
"directory": "packages/llms"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"llm",
|
|
13
|
+
"anthropic",
|
|
14
|
+
"openai",
|
|
15
|
+
"openrouter",
|
|
16
|
+
"bedrock",
|
|
17
|
+
"gemini",
|
|
18
|
+
"streaming",
|
|
19
|
+
"tokens"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./package.json": "./package.json"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc -p tsconfig.json",
|
|
37
|
+
"test": "npm run build && vitest run",
|
|
38
|
+
"test:live": "npm run build && vitest run src/tests/live"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"typescript": "^5"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@spendgraph/sdk": "^0.2.0"
|
|
51
|
+
}
|
|
52
|
+
}
|