@semiont/inference 0.5.24 → 0.5.26
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 +34 -9
- package/dist/index.d.ts +102 -29
- package/dist/index.js +298 -90
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ This package provides the **core AI primitives** for the Semiont platform:
|
|
|
12
12
|
- The `InferenceClient` interface (provider abstraction)
|
|
13
13
|
- Client implementations for Anthropic and Ollama, plus a scripted mock for tests
|
|
14
14
|
- A `createInferenceClient()` factory that selects the implementation from config
|
|
15
|
-
- Cross-provider
|
|
15
|
+
- Cross-provider structured generation (`generateStructured` — parsed elements or a throw, never a silent `[]`)
|
|
16
16
|
- Usage metrics via `@semiont/observability`
|
|
17
17
|
|
|
18
18
|
For **application-specific AI logic** (semantic processing, prompt engineering, response parsing), see [@semiont/make-meaning](../make-meaning/).
|
|
@@ -90,34 +90,59 @@ interface InferenceClient {
|
|
|
90
90
|
readonly type: string; // 'anthropic' | 'ollama' | 'mock'
|
|
91
91
|
readonly modelId: string; // configured model name
|
|
92
92
|
|
|
93
|
+
limits(): Promise<InferenceLimits>;
|
|
93
94
|
generateText(prompt, maxTokens, temperature, options?): Promise<string>;
|
|
94
95
|
generateTextWithMetadata(prompt, maxTokens, temperature, options?): Promise<InferenceResponse>;
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
interface InferenceLimits {
|
|
99
|
+
contextTokens: number; // context window (Anthropic: max input; Ollama: shared input+output)
|
|
100
|
+
maxOutputTokens: number; // max output per generation (Ollama mirrors the shared window here)
|
|
101
|
+
}
|
|
102
|
+
|
|
97
103
|
interface InferenceResponse {
|
|
98
104
|
text: string;
|
|
99
105
|
stopReason: 'end_turn' | 'max_tokens' | 'stop_sequence' | string;
|
|
100
106
|
}
|
|
101
107
|
```
|
|
102
108
|
|
|
103
|
-
###
|
|
109
|
+
### Structured generation
|
|
104
110
|
|
|
105
|
-
|
|
111
|
+
`generateStructured(prompt, maxTokens, temperature, elementSchema)` returns **parsed array elements**, not text — the JSON guarantee lives in the return type (`StructuredResponse<T> = { items: T[]; stopReason }`), not in a comment:
|
|
106
112
|
|
|
107
113
|
```typescript
|
|
108
|
-
const
|
|
109
|
-
|
|
114
|
+
const { items } = await client.generateStructured<Entity>(prompt, 1000, 0, {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: { exact: { type: 'string' } },
|
|
117
|
+
required: ['exact'],
|
|
118
|
+
additionalProperties: false,
|
|
119
|
+
});
|
|
110
120
|
```
|
|
111
121
|
|
|
112
122
|
Each implementation honors the contract with its provider's mechanism:
|
|
113
|
-
- **Ollama**: grammar-constrained sampling — the request's `format` field carries
|
|
114
|
-
- **Anthropic**:
|
|
123
|
+
- **Ollama**: grammar-constrained sampling — the request's `format` field carries the caller's element schema wrapped in an array schema.
|
|
124
|
+
- **Anthropic**: response-level structured output — `output_config.format` carries the caller's element schema under an **array root** (accepted on both live-config models; `.plans/spikes/output-config-array-root.md`), so the response text IS the schema-conforming JSON. No tools, no wrapper, no unwrap.
|
|
125
|
+
|
|
126
|
+
A response that cannot be read as an array — the SDK delivering unparsed tool input as a string, a missing array, an unhonoured grammar — **throws** (`Structured response could not be read: …`). It is never coerced to `[]`: an empty extraction is a legitimate, distinct outcome, and conflating the two silently discards real data (STRUCTURED-INFERENCE).
|
|
127
|
+
|
|
128
|
+
Current callers all expect arrays (entity extraction, motivation detection). If an object-emitting caller appears, `generateStructured` grows a sibling, not an option — see the notes in [src/interface.ts](src/interface.ts).
|
|
129
|
+
|
|
130
|
+
### Provider limits
|
|
131
|
+
|
|
132
|
+
`limits()` publishes the provider's **actual** context/output ceilings for the configured model — discovered from the provider itself, never hand-maintained constants:
|
|
133
|
+
|
|
134
|
+
- **Anthropic**: the Models API (`models.retrieve`) — `max_input_tokens` / `max_tokens`.
|
|
135
|
+
- **Ollama**: `POST /api/show` — the model's context window. Input and output share that window, so it is published as both fields (`maxOutputTokens === contextTokens` signals a shared window).
|
|
136
|
+
|
|
137
|
+
Discovery is lazy and cached per client; a failed discovery is **not** cached — the next call retries. When ceilings cannot be determined (unknown model, endpoint unreachable), `limits()` **throws**: fail-loud, never a guessed floor.
|
|
115
138
|
|
|
116
|
-
|
|
139
|
+
Two request-time behaviors ride on the limits:
|
|
140
|
+
- **Ollama sets `num_ctx` explicitly** on every generate request — sized to the prompt estimate + output budget, capped at the model window. Without it, Ollama's model-*default* window silently clips large prompts. A request that genuinely cannot fit **throws** instead of being clipped.
|
|
141
|
+
- **Anthropic streams internally** above the SDK's non-streaming output ceiling (≈21K tokens) — same interface, same response shape.
|
|
117
142
|
|
|
118
143
|
### `MockInferenceClient`
|
|
119
144
|
|
|
120
|
-
A scripted test double ([src/implementations/mock.ts](src/implementations/mock.ts)): construct it with a list of canned responses, then inspect `calls` (recorded prompt/maxTokens/temperature/options per invocation). `reset()` and `setResponses()` helpers included.
|
|
145
|
+
A scripted test double ([src/implementations/mock.ts](src/implementations/mock.ts)): construct it with a list of canned responses, then inspect `calls` (recorded prompt/maxTokens/temperature/options per invocation). `reset()` and `setResponses()` helpers included. An optional third constructor argument injects `InferenceLimits` for chunking/budget tests; the default is generous (1M/1M) so ordinary tests never trip window guards.
|
|
121
146
|
|
|
122
147
|
```typescript
|
|
123
148
|
import { MockInferenceClient } from '@semiont/inference';
|
package/dist/index.d.ts
CHANGED
|
@@ -5,41 +5,89 @@ interface InferenceResponse {
|
|
|
5
5
|
stopReason: 'end_turn' | 'max_tokens' | 'stop_sequence' | string;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Raw JSON Schema for ONE array element of a structured generation — a plain
|
|
9
|
+
* object, not a TS type and not a validator instance. Both providers consume
|
|
10
|
+
* JSON Schema directly (Anthropic nests it under the forced tool's
|
|
11
|
+
* `input_schema`; Ollama sends it as `format: { type: 'array', items: … }`),
|
|
12
|
+
* so anything richer would abstract one shape with two consumers.
|
|
13
|
+
*
|
|
14
|
+
* Constrain it to what both providers enforce: objects,
|
|
15
|
+
* `string`/`number`/`boolean`/`null`, `enum`, `const`, `required`, and
|
|
16
|
+
* `additionalProperties: false`. Numeric and string constraints (`minimum`,
|
|
17
|
+
* `maxLength`) are NOT enforced by Anthropic strict mode — declaring them
|
|
18
|
+
* buys nothing and misleads the reader.
|
|
11
19
|
*/
|
|
12
|
-
|
|
20
|
+
type ElementSchema = Record<string, unknown>;
|
|
21
|
+
/**
|
|
22
|
+
* A structured generation's result: the elements the model produced, plus the
|
|
23
|
+
* provider's stop reason (consumers gate on 'max_tokens' — truncation is data
|
|
24
|
+
* loss, not "fewer items").
|
|
25
|
+
*
|
|
26
|
+
* `items` is `T[]`, never a string: there is no representable value meaning
|
|
27
|
+
* "here is some text I could not read." An implementation that cannot deliver
|
|
28
|
+
* the array THROWS — failure is distinct from empty by construction.
|
|
29
|
+
*
|
|
30
|
+
* `T` is a caller assertion, not a runtime guarantee: nothing verifies the
|
|
31
|
+
* element schema and `T` agree, and the type parameter is erased. Declare the
|
|
32
|
+
* schema and `T` adjacently at the call site so drift is visible in one
|
|
33
|
+
* place, and keep per-element structural guards on the consuming side.
|
|
34
|
+
*/
|
|
35
|
+
interface StructuredResponse<T> {
|
|
36
|
+
items: T[];
|
|
37
|
+
stopReason: 'end_turn' | 'max_tokens' | 'stop_sequence' | string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A provider's actual ceilings for the configured model, discovered from the
|
|
41
|
+
* provider itself (Anthropic Models API; Ollama `/api/show`) — never
|
|
42
|
+
* hand-maintained constants. Detection budget arithmetic derives from these.
|
|
43
|
+
*/
|
|
44
|
+
interface InferenceLimits {
|
|
13
45
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* serializes as escaped JSON) and unwraps the array on return.
|
|
20
|
-
* Callers can rely on the returned `text` being a top-level JSON
|
|
21
|
-
* array regardless of provider.
|
|
22
|
-
*
|
|
23
|
-
* Current callers all expect arrays (entity extraction, motivation
|
|
24
|
-
* detection). If an object-emitting caller appears, this option
|
|
25
|
-
* grows a `root: 'array' | 'object'` field; do not silently drop
|
|
26
|
-
* the constraint.
|
|
46
|
+
* The context window in tokens. Semantics differ by provider shape:
|
|
47
|
+
* Anthropic reports maximum *input* tokens (output has its own ceiling);
|
|
48
|
+
* Ollama reports the *shared* input+output window and mirrors it in
|
|
49
|
+
* `maxOutputTokens` (there is no separate output ceiling), so
|
|
50
|
+
* `maxOutputTokens === contextTokens` signals a shared window.
|
|
27
51
|
*/
|
|
28
|
-
|
|
52
|
+
contextTokens: number;
|
|
53
|
+
/** Maximum output tokens per generation. */
|
|
54
|
+
maxOutputTokens: number;
|
|
29
55
|
}
|
|
30
56
|
interface InferenceClient {
|
|
31
57
|
/** Provider type identifier (e.g. 'anthropic', 'ollama') */
|
|
32
58
|
readonly type: string;
|
|
33
59
|
/** Model identifier used for generation (e.g. 'claude-opus-4-6', 'llama3') */
|
|
34
60
|
readonly modelId: string;
|
|
61
|
+
/**
|
|
62
|
+
* The provider's actual context/output ceilings for `modelId`. Discovered
|
|
63
|
+
* lazily on first call and cached for the client's lifetime; a failed
|
|
64
|
+
* discovery is NOT cached — the next call retries. Throws when the ceilings
|
|
65
|
+
* cannot be determined (unknown model, discovery endpoint unreachable):
|
|
66
|
+
* fail-loud, never a guessed floor.
|
|
67
|
+
*/
|
|
68
|
+
limits(): Promise<InferenceLimits>;
|
|
35
69
|
/**
|
|
36
70
|
* Generate text from a prompt (simple interface)
|
|
37
71
|
*/
|
|
38
|
-
generateText(prompt: string, maxTokens: number, temperature: number
|
|
72
|
+
generateText(prompt: string, maxTokens: number, temperature: number): Promise<string>;
|
|
39
73
|
/**
|
|
40
74
|
* Generate text with detailed response information
|
|
41
75
|
*/
|
|
42
|
-
generateTextWithMetadata(prompt: string, maxTokens: number, temperature: number
|
|
76
|
+
generateTextWithMetadata(prompt: string, maxTokens: number, temperature: number): Promise<InferenceResponse>;
|
|
77
|
+
/**
|
|
78
|
+
* Generate a JSON array whose elements satisfy `elementSchema`, as parsed
|
|
79
|
+
* values — the structured counterpart of `generateTextWithMetadata`, and
|
|
80
|
+
* the ONLY generation surface detection may use.
|
|
81
|
+
*
|
|
82
|
+
* The return type carries the guarantee the old `format: 'json'` option
|
|
83
|
+
* left in a comment: callers receive `T[]` or an exception. When the
|
|
84
|
+
* provider's answer cannot be read as an array (the SDK hands tool input
|
|
85
|
+
* over as an unparsed string, the response is missing the array, the
|
|
86
|
+
* grammar was not honoured), implementations THROW a
|
|
87
|
+
* "Structured response could not be read" error — they never coerce to
|
|
88
|
+
* `[]`, because empty is a legitimate, distinct outcome.
|
|
89
|
+
*/
|
|
90
|
+
generateStructured<T>(prompt: string, maxTokens: number, temperature: number, elementSchema: ElementSchema): Promise<StructuredResponse<T>>;
|
|
43
91
|
}
|
|
44
92
|
|
|
45
93
|
type InferenceClientType = 'anthropic' | 'ollama';
|
|
@@ -57,9 +105,18 @@ declare class AnthropicInferenceClient implements InferenceClient {
|
|
|
57
105
|
readonly modelId: string;
|
|
58
106
|
private client;
|
|
59
107
|
private logger?;
|
|
108
|
+
private discoveryPromise?;
|
|
60
109
|
constructor(apiKey: string, model: string, baseURL?: string, logger?: Logger);
|
|
61
|
-
|
|
62
|
-
|
|
110
|
+
limits(): Promise<InferenceLimits>;
|
|
111
|
+
private discover;
|
|
112
|
+
private discoverModel;
|
|
113
|
+
private requestMessage;
|
|
114
|
+
generateText(prompt: string, maxTokens: number, temperature: number): Promise<string>;
|
|
115
|
+
generateTextWithMetadata(prompt: string, maxTokens: number, temperature: number): Promise<InferenceResponse>;
|
|
116
|
+
generateStructured<T>(prompt: string, maxTokens: number, temperature: number, elementSchema: ElementSchema): Promise<StructuredResponse<T>>;
|
|
117
|
+
/** Issue the request, recording an error metric if the transport throws. */
|
|
118
|
+
private recordedRequest;
|
|
119
|
+
private recordError;
|
|
63
120
|
}
|
|
64
121
|
|
|
65
122
|
declare class OllamaInferenceClient implements InferenceClient {
|
|
@@ -67,9 +124,14 @@ declare class OllamaInferenceClient implements InferenceClient {
|
|
|
67
124
|
readonly modelId: string;
|
|
68
125
|
private baseURL;
|
|
69
126
|
private logger?;
|
|
127
|
+
private limitsPromise?;
|
|
70
128
|
constructor(model: string, baseURL?: string, logger?: Logger);
|
|
71
|
-
|
|
72
|
-
|
|
129
|
+
limits(): Promise<InferenceLimits>;
|
|
130
|
+
private discoverLimits;
|
|
131
|
+
generateText(prompt: string, maxTokens: number, temperature: number): Promise<string>;
|
|
132
|
+
generateTextWithMetadata(prompt: string, maxTokens: number, temperature: number): Promise<InferenceResponse>;
|
|
133
|
+
generateStructured<T>(prompt: string, maxTokens: number, temperature: number, elementSchema: ElementSchema): Promise<StructuredResponse<T>>;
|
|
134
|
+
private generate;
|
|
73
135
|
}
|
|
74
136
|
|
|
75
137
|
declare class MockInferenceClient implements InferenceClient {
|
|
@@ -78,18 +140,29 @@ declare class MockInferenceClient implements InferenceClient {
|
|
|
78
140
|
private responses;
|
|
79
141
|
private responseIndex;
|
|
80
142
|
private stopReasons;
|
|
143
|
+
private injectedLimits;
|
|
81
144
|
calls: Array<{
|
|
82
145
|
prompt: string;
|
|
83
146
|
maxTokens: number;
|
|
84
147
|
temperature: number;
|
|
85
|
-
|
|
148
|
+
elementSchema?: ElementSchema;
|
|
86
149
|
}>;
|
|
87
|
-
constructor(responses?: string[], stopReasons?: string[]);
|
|
88
|
-
|
|
89
|
-
|
|
150
|
+
constructor(responses?: string[], stopReasons?: string[], limits?: InferenceLimits);
|
|
151
|
+
limits(): Promise<InferenceLimits>;
|
|
152
|
+
generateText(prompt: string, maxTokens: number, temperature: number): Promise<string>;
|
|
153
|
+
generateTextWithMetadata(prompt: string, maxTokens: number, temperature: number): Promise<InferenceResponse>;
|
|
154
|
+
/**
|
|
155
|
+
* Structured surface: pops the same responses queue and PARSES the entry,
|
|
156
|
+
* mirroring the real contract — a queued string that is not a JSON array
|
|
157
|
+
* throws "could not be read", so tests inject the malformed shape simply by
|
|
158
|
+
* queuing it (`setResponses(['not json'])`). The element schema is recorded
|
|
159
|
+
* on `calls` so tests can assert what the caller declared.
|
|
160
|
+
*/
|
|
161
|
+
generateStructured<T>(prompt: string, maxTokens: number, temperature: number, elementSchema: ElementSchema): Promise<StructuredResponse<T>>;
|
|
162
|
+
private nextResponse;
|
|
90
163
|
reset(): void;
|
|
91
164
|
setResponses(responses: string[], stopReasons?: string[]): void;
|
|
92
165
|
}
|
|
93
166
|
|
|
94
167
|
export { AnthropicInferenceClient, MockInferenceClient, OllamaInferenceClient, createInferenceClient };
|
|
95
|
-
export type { InferenceClient, InferenceClientConfig, InferenceClientType,
|
|
168
|
+
export type { ElementSchema, InferenceClient, InferenceClientConfig, InferenceClientType, InferenceLimits, InferenceResponse, StructuredResponse };
|
package/dist/index.js
CHANGED
|
@@ -1,24 +1,14 @@
|
|
|
1
1
|
// src/implementations/anthropic.ts
|
|
2
2
|
import Anthropic from "@anthropic-ai/sdk";
|
|
3
|
+
import { isObject } from "@semiont/core";
|
|
3
4
|
import { recordInferenceUsage } from "@semiont/observability";
|
|
4
|
-
var
|
|
5
|
-
name: "emit_json_array",
|
|
6
|
-
description: 'Return your entire answer by calling this tool. Put the JSON array of results under the "items" property, and emit no prose.',
|
|
7
|
-
input_schema: {
|
|
8
|
-
type: "object",
|
|
9
|
-
properties: {
|
|
10
|
-
// Element shape is unconstrained here — the prompt carries the per-element
|
|
11
|
-
// schema; the tool only enforces that the top-level result is an array.
|
|
12
|
-
items: { type: "array", items: {} }
|
|
13
|
-
},
|
|
14
|
-
required: ["items"]
|
|
15
|
-
}
|
|
16
|
-
};
|
|
5
|
+
var NONSTREAMING_MAX_OUTPUT_TOKENS = Math.floor(128e3 / 6);
|
|
17
6
|
var AnthropicInferenceClient = class {
|
|
18
7
|
type = "anthropic";
|
|
19
8
|
modelId;
|
|
20
9
|
client;
|
|
21
10
|
logger;
|
|
11
|
+
discoveryPromise;
|
|
22
12
|
constructor(apiKey, model, baseURL, logger) {
|
|
23
13
|
this.client = new Anthropic({
|
|
24
14
|
apiKey,
|
|
@@ -27,84 +17,151 @@ var AnthropicInferenceClient = class {
|
|
|
27
17
|
this.modelId = model;
|
|
28
18
|
this.logger = logger;
|
|
29
19
|
}
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
limits() {
|
|
21
|
+
return this.discover().then((d) => d.limits);
|
|
22
|
+
}
|
|
23
|
+
discover() {
|
|
24
|
+
if (!this.discoveryPromise) {
|
|
25
|
+
this.discoveryPromise = this.discoverModel().catch((err) => {
|
|
26
|
+
this.discoveryPromise = void 0;
|
|
27
|
+
throw err;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return this.discoveryPromise;
|
|
31
|
+
}
|
|
32
|
+
async discoverModel() {
|
|
33
|
+
const info = await this.client.models.retrieve(this.modelId).catch((err) => {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Failed to discover model limits for '${this.modelId}' from the Models API`,
|
|
36
|
+
{ cause: err }
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
if (info.max_input_tokens == null || info.max_tokens == null) {
|
|
40
|
+
throw new Error(`Models API reports no context/output ceilings for '${this.modelId}'`);
|
|
41
|
+
}
|
|
42
|
+
const raw = info;
|
|
43
|
+
const structuredOutputsSupported = isObject(raw) && isObject(raw["capabilities"]) && isObject(raw["capabilities"]["structured_outputs"]) && raw["capabilities"]["structured_outputs"]["supported"] === true;
|
|
44
|
+
return {
|
|
45
|
+
limits: { contextTokens: info.max_input_tokens, maxOutputTokens: info.max_tokens },
|
|
46
|
+
structuredOutputsSupported
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
requestMessage(params) {
|
|
50
|
+
if (params.max_tokens > NONSTREAMING_MAX_OUTPUT_TOKENS) {
|
|
51
|
+
return this.client.messages.stream(params).finalMessage();
|
|
52
|
+
}
|
|
53
|
+
return this.client.messages.create(params);
|
|
54
|
+
}
|
|
55
|
+
async generateText(prompt, maxTokens, temperature) {
|
|
56
|
+
const response = await this.generateTextWithMetadata(prompt, maxTokens, temperature);
|
|
32
57
|
return response.text;
|
|
33
58
|
}
|
|
34
|
-
async generateTextWithMetadata(prompt, maxTokens, temperature
|
|
35
|
-
const jsonMode = options?.format === "json";
|
|
59
|
+
async generateTextWithMetadata(prompt, maxTokens, temperature) {
|
|
36
60
|
this.logger?.debug("Generating text with inference client", {
|
|
37
61
|
model: this.modelId,
|
|
38
62
|
promptLength: prompt.length,
|
|
39
63
|
maxTokens,
|
|
40
|
-
temperature
|
|
41
|
-
format: options?.format
|
|
64
|
+
temperature
|
|
42
65
|
});
|
|
66
|
+
const params = {
|
|
67
|
+
model: this.modelId,
|
|
68
|
+
max_tokens: maxTokens,
|
|
69
|
+
temperature,
|
|
70
|
+
messages: [{ role: "user", content: prompt }]
|
|
71
|
+
};
|
|
43
72
|
const start = performance.now();
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
73
|
+
const response = await this.recordedRequest(params, start);
|
|
74
|
+
const textContent = response.content.find((c) => c.type === "text");
|
|
75
|
+
if (!textContent || textContent.type !== "text") {
|
|
76
|
+
this.recordError(start, response);
|
|
77
|
+
this.logger?.error("No text content in inference response", {
|
|
47
78
|
model: this.modelId,
|
|
48
|
-
|
|
49
|
-
temperature,
|
|
50
|
-
messages: [{ role: "user", content: prompt }],
|
|
51
|
-
// JSON mode → force the structured-output tool. No prefill assistant
|
|
52
|
-
// turn: the constraint now lives in the tool call, not in free text.
|
|
53
|
-
...jsonMode ? { tools: [JSON_ARRAY_TOOL], tool_choice: { type: "tool", name: JSON_ARRAY_TOOL.name } } : {}
|
|
79
|
+
contentTypes: response.content.map((c) => c.type)
|
|
54
80
|
});
|
|
55
|
-
|
|
56
|
-
recordInferenceUsage({
|
|
57
|
-
provider: this.type,
|
|
58
|
-
model: this.modelId,
|
|
59
|
-
durationMs: performance.now() - start,
|
|
60
|
-
outcome: "error"
|
|
61
|
-
});
|
|
62
|
-
throw err;
|
|
81
|
+
throw new Error("No text content in inference response");
|
|
63
82
|
}
|
|
64
|
-
|
|
83
|
+
const text = textContent.text;
|
|
84
|
+
recordInferenceUsage({
|
|
85
|
+
provider: this.type,
|
|
86
|
+
model: this.modelId,
|
|
87
|
+
durationMs: performance.now() - start,
|
|
88
|
+
outcome: "success",
|
|
89
|
+
inputTokens: response.usage?.input_tokens,
|
|
90
|
+
outputTokens: response.usage?.output_tokens
|
|
91
|
+
});
|
|
92
|
+
this.logger?.info("Text generation completed", {
|
|
65
93
|
model: this.modelId,
|
|
66
|
-
|
|
94
|
+
textLength: text.length,
|
|
67
95
|
stopReason: response.stop_reason
|
|
68
96
|
});
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
outputTokens: response.usage?.output_tokens
|
|
100
|
-
});
|
|
101
|
-
this.logger?.error("No text content in inference response", {
|
|
102
|
-
model: this.modelId,
|
|
103
|
-
contentTypes: response.content.map((c) => c.type)
|
|
104
|
-
});
|
|
105
|
-
throw new Error("No text content in inference response");
|
|
97
|
+
return {
|
|
98
|
+
text,
|
|
99
|
+
stopReason: response.stop_reason || "unknown"
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
async generateStructured(prompt, maxTokens, temperature, elementSchema) {
|
|
103
|
+
const discovery = await this.discover();
|
|
104
|
+
if (!discovery.structuredOutputsSupported) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Model '${this.modelId}' does not report support for strict structured outputs (Models API capabilities.structured_outputs) \u2014 refusing rather than degrading to unconstrained tool use, which silently discards unreadable results. Re-point the inference.model key that pins this worker/actor in .semiont/semiontconfig/*.toml (e.g. environments.<env>.workers.<job-type>.inference.model) at a model that reports supported: true.`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
this.logger?.debug("Generating structured output with inference client", {
|
|
110
|
+
model: this.modelId,
|
|
111
|
+
promptLength: prompt.length,
|
|
112
|
+
maxTokens,
|
|
113
|
+
temperature
|
|
114
|
+
});
|
|
115
|
+
const params = {
|
|
116
|
+
model: this.modelId,
|
|
117
|
+
max_tokens: maxTokens,
|
|
118
|
+
temperature,
|
|
119
|
+
messages: [{ role: "user", content: prompt }],
|
|
120
|
+
// Response-level structured output with an ARRAY root: the response
|
|
121
|
+
// text IS the schema-conforming JSON. No tools, no prefill.
|
|
122
|
+
output_config: {
|
|
123
|
+
format: {
|
|
124
|
+
type: "json_schema",
|
|
125
|
+
schema: { type: "array", items: elementSchema }
|
|
126
|
+
}
|
|
106
127
|
}
|
|
107
|
-
|
|
128
|
+
};
|
|
129
|
+
const start = performance.now();
|
|
130
|
+
const response = await this.recordedRequest(params, start);
|
|
131
|
+
const textContent = response.content.find((c) => c.type === "text");
|
|
132
|
+
if (!textContent || textContent.type !== "text") {
|
|
133
|
+
this.recordError(start, response);
|
|
134
|
+
this.logger?.error("No text content in structured inference response", {
|
|
135
|
+
model: this.modelId,
|
|
136
|
+
contentTypes: response.content.map((c) => c.type)
|
|
137
|
+
});
|
|
138
|
+
throw new Error("No text content in structured inference response");
|
|
139
|
+
}
|
|
140
|
+
let parsed;
|
|
141
|
+
try {
|
|
142
|
+
parsed = JSON.parse(textContent.text);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
this.recordError(start, response);
|
|
145
|
+
this.logger?.error("Structured response could not be read", {
|
|
146
|
+
model: this.modelId,
|
|
147
|
+
textLength: textContent.text.length,
|
|
148
|
+
stopReason: response.stop_reason
|
|
149
|
+
});
|
|
150
|
+
throw new Error(
|
|
151
|
+
`Structured response could not be read: response is not valid JSON (stop_reason: ${response.stop_reason})`,
|
|
152
|
+
{ cause: err }
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (!Array.isArray(parsed)) {
|
|
156
|
+
this.recordError(start, response);
|
|
157
|
+
this.logger?.error("Structured response could not be read", {
|
|
158
|
+
model: this.modelId,
|
|
159
|
+
parsedType: typeof parsed,
|
|
160
|
+
stopReason: response.stop_reason
|
|
161
|
+
});
|
|
162
|
+
throw new Error(
|
|
163
|
+
`Structured response could not be read: parsed to ${typeof parsed}, not an array (stop_reason: ${response.stop_reason})`
|
|
164
|
+
);
|
|
108
165
|
}
|
|
109
166
|
recordInferenceUsage({
|
|
110
167
|
provider: this.type,
|
|
@@ -114,42 +171,140 @@ var AnthropicInferenceClient = class {
|
|
|
114
171
|
inputTokens: response.usage?.input_tokens,
|
|
115
172
|
outputTokens: response.usage?.output_tokens
|
|
116
173
|
});
|
|
117
|
-
this.logger?.info("
|
|
174
|
+
this.logger?.info("Structured generation completed", {
|
|
118
175
|
model: this.modelId,
|
|
119
|
-
|
|
176
|
+
items: parsed.length,
|
|
120
177
|
stopReason: response.stop_reason
|
|
121
178
|
});
|
|
122
179
|
return {
|
|
123
|
-
|
|
180
|
+
items: parsed,
|
|
124
181
|
stopReason: response.stop_reason || "unknown"
|
|
125
182
|
};
|
|
126
183
|
}
|
|
184
|
+
/** Issue the request, recording an error metric if the transport throws. */
|
|
185
|
+
async recordedRequest(params, start) {
|
|
186
|
+
try {
|
|
187
|
+
return await this.requestMessage(params);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
recordInferenceUsage({
|
|
190
|
+
provider: this.type,
|
|
191
|
+
model: this.modelId,
|
|
192
|
+
durationMs: performance.now() - start,
|
|
193
|
+
outcome: "error"
|
|
194
|
+
});
|
|
195
|
+
throw err;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
recordError(start, response) {
|
|
199
|
+
recordInferenceUsage({
|
|
200
|
+
provider: this.type,
|
|
201
|
+
model: this.modelId,
|
|
202
|
+
durationMs: performance.now() - start,
|
|
203
|
+
outcome: "error",
|
|
204
|
+
inputTokens: response.usage?.input_tokens,
|
|
205
|
+
outputTokens: response.usage?.output_tokens
|
|
206
|
+
});
|
|
207
|
+
}
|
|
127
208
|
};
|
|
128
209
|
|
|
129
210
|
// src/implementations/ollama.ts
|
|
211
|
+
import { estimateTokens, isNumber, isObject as isObject2 } from "@semiont/core";
|
|
130
212
|
import { recordInferenceUsage as recordInferenceUsage2 } from "@semiont/observability";
|
|
213
|
+
var NUM_CTX_ESTIMATE_SLACK = 0.2;
|
|
214
|
+
var NUM_CTX_TEMPLATE_ALLOWANCE = 64;
|
|
131
215
|
var OllamaInferenceClient = class {
|
|
132
216
|
type = "ollama";
|
|
133
217
|
modelId;
|
|
134
218
|
baseURL;
|
|
135
219
|
logger;
|
|
220
|
+
limitsPromise;
|
|
136
221
|
constructor(model, baseURL, logger) {
|
|
137
222
|
this.baseURL = (baseURL || "http://localhost:11434").replace(/\/+$/, "");
|
|
138
223
|
this.modelId = model;
|
|
139
224
|
this.logger = logger;
|
|
140
225
|
}
|
|
141
|
-
|
|
142
|
-
|
|
226
|
+
limits() {
|
|
227
|
+
if (!this.limitsPromise) {
|
|
228
|
+
this.limitsPromise = this.discoverLimits().catch((err) => {
|
|
229
|
+
this.limitsPromise = void 0;
|
|
230
|
+
throw err;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return this.limitsPromise;
|
|
234
|
+
}
|
|
235
|
+
async discoverLimits() {
|
|
236
|
+
const res = await fetch(`${this.baseURL}/api/show`, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers: { "Content-Type": "application/json" },
|
|
239
|
+
body: JSON.stringify({ model: this.modelId })
|
|
240
|
+
});
|
|
241
|
+
if (!res.ok) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`Failed to discover model limits: /api/show returned ${res.status} for '${this.modelId}'`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
const data = await res.json();
|
|
247
|
+
const modelInfo = isObject2(data) && isObject2(data["model_info"]) ? data["model_info"] : void 0;
|
|
248
|
+
const contextTokens = readContextLength(modelInfo);
|
|
249
|
+
if (contextTokens === void 0) {
|
|
250
|
+
throw new Error(`/api/show reports no context length for '${this.modelId}'`);
|
|
251
|
+
}
|
|
252
|
+
return { contextTokens, maxOutputTokens: contextTokens };
|
|
253
|
+
}
|
|
254
|
+
async generateText(prompt, maxTokens, temperature) {
|
|
255
|
+
const response = await this.generateTextWithMetadata(prompt, maxTokens, temperature);
|
|
143
256
|
return response.text;
|
|
144
257
|
}
|
|
145
|
-
async generateTextWithMetadata(prompt, maxTokens, temperature
|
|
258
|
+
async generateTextWithMetadata(prompt, maxTokens, temperature) {
|
|
259
|
+
return this.generate(prompt, maxTokens, temperature, void 0);
|
|
260
|
+
}
|
|
261
|
+
async generateStructured(prompt, maxTokens, temperature, elementSchema) {
|
|
262
|
+
const response = await this.generate(prompt, maxTokens, temperature, elementSchema);
|
|
263
|
+
let parsed;
|
|
264
|
+
try {
|
|
265
|
+
parsed = JSON.parse(response.text);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
this.logger?.error("Structured response could not be read", {
|
|
268
|
+
model: this.modelId,
|
|
269
|
+
textLength: response.text.length,
|
|
270
|
+
stopReason: response.stopReason
|
|
271
|
+
});
|
|
272
|
+
throw new Error(
|
|
273
|
+
`Structured response could not be read: response is not valid JSON (stop_reason: ${response.stopReason})`,
|
|
274
|
+
{ cause: err }
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
if (!Array.isArray(parsed)) {
|
|
278
|
+
this.logger?.error("Structured response could not be read", {
|
|
279
|
+
model: this.modelId,
|
|
280
|
+
parsedType: typeof parsed,
|
|
281
|
+
stopReason: response.stopReason
|
|
282
|
+
});
|
|
283
|
+
throw new Error(
|
|
284
|
+
`Structured response could not be read: parsed to ${typeof parsed}, not an array (stop_reason: ${response.stopReason})`
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
return { items: parsed, stopReason: response.stopReason };
|
|
288
|
+
}
|
|
289
|
+
async generate(prompt, maxTokens, temperature, elementSchema) {
|
|
146
290
|
this.logger?.debug("Generating text with Ollama", {
|
|
147
291
|
model: this.modelId,
|
|
148
292
|
promptLength: prompt.length,
|
|
149
293
|
maxTokens,
|
|
150
294
|
temperature,
|
|
151
|
-
|
|
295
|
+
structured: elementSchema !== void 0
|
|
152
296
|
});
|
|
297
|
+
const limits = await this.limits();
|
|
298
|
+
const promptTokens = estimateTokens(prompt);
|
|
299
|
+
if (promptTokens + maxTokens > limits.contextTokens) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Prompt (~${promptTokens} tokens) + output budget (${maxTokens}) exceed the '${this.modelId}' context window (${limits.contextTokens} tokens)`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const numCtx = Math.min(
|
|
305
|
+
limits.contextTokens,
|
|
306
|
+
promptTokens + maxTokens + Math.ceil(promptTokens * NUM_CTX_ESTIMATE_SLACK) + NUM_CTX_TEMPLATE_ALLOWANCE
|
|
307
|
+
);
|
|
153
308
|
const url = `${this.baseURL}/api/generate`;
|
|
154
309
|
const start = performance.now();
|
|
155
310
|
const body = {
|
|
@@ -159,11 +314,12 @@ var OllamaInferenceClient = class {
|
|
|
159
314
|
think: false,
|
|
160
315
|
options: {
|
|
161
316
|
num_predict: maxTokens,
|
|
317
|
+
num_ctx: numCtx,
|
|
162
318
|
temperature
|
|
163
319
|
}
|
|
164
320
|
};
|
|
165
|
-
if (
|
|
166
|
-
body["format"] = { type: "array", items:
|
|
321
|
+
if (elementSchema !== void 0) {
|
|
322
|
+
body["format"] = { type: "array", items: elementSchema };
|
|
167
323
|
}
|
|
168
324
|
let res;
|
|
169
325
|
try {
|
|
@@ -229,6 +385,20 @@ var OllamaInferenceClient = class {
|
|
|
229
385
|
};
|
|
230
386
|
}
|
|
231
387
|
};
|
|
388
|
+
function readContextLength(modelInfo) {
|
|
389
|
+
if (!modelInfo) return void 0;
|
|
390
|
+
const arch = modelInfo["general.architecture"];
|
|
391
|
+
if (typeof arch === "string") {
|
|
392
|
+
const direct = modelInfo[`${arch}.context_length`];
|
|
393
|
+
if (isNumber(direct) && direct > 0) return direct;
|
|
394
|
+
}
|
|
395
|
+
const fallbackKey = Object.keys(modelInfo).find((k) => k.endsWith(".context_length"));
|
|
396
|
+
if (fallbackKey !== void 0) {
|
|
397
|
+
const fallback = modelInfo[fallbackKey];
|
|
398
|
+
if (isNumber(fallback) && fallback > 0) return fallback;
|
|
399
|
+
}
|
|
400
|
+
return void 0;
|
|
401
|
+
}
|
|
232
402
|
function mapStopReason(doneReason) {
|
|
233
403
|
switch (doneReason) {
|
|
234
404
|
case "stop":
|
|
@@ -267,23 +437,61 @@ function createInferenceClient(config, logger) {
|
|
|
267
437
|
}
|
|
268
438
|
|
|
269
439
|
// src/implementations/mock.ts
|
|
440
|
+
var GENEROUS_LIMITS = {
|
|
441
|
+
contextTokens: 1e6,
|
|
442
|
+
maxOutputTokens: 1e6
|
|
443
|
+
};
|
|
270
444
|
var MockInferenceClient = class {
|
|
271
445
|
type = "mock";
|
|
272
446
|
modelId = "mock-model";
|
|
273
447
|
responses = [];
|
|
274
448
|
responseIndex = 0;
|
|
275
449
|
stopReasons = [];
|
|
450
|
+
injectedLimits;
|
|
276
451
|
calls = [];
|
|
277
|
-
constructor(responses = ["Mock response"], stopReasons) {
|
|
452
|
+
constructor(responses = ["Mock response"], stopReasons, limits) {
|
|
278
453
|
this.responses = responses;
|
|
279
454
|
this.stopReasons = stopReasons || responses.map(() => "end_turn");
|
|
455
|
+
this.injectedLimits = limits ?? GENEROUS_LIMITS;
|
|
280
456
|
}
|
|
281
|
-
async
|
|
282
|
-
|
|
457
|
+
async limits() {
|
|
458
|
+
return this.injectedLimits;
|
|
459
|
+
}
|
|
460
|
+
async generateText(prompt, maxTokens, temperature) {
|
|
461
|
+
const response = await this.generateTextWithMetadata(prompt, maxTokens, temperature);
|
|
283
462
|
return response.text;
|
|
284
463
|
}
|
|
285
|
-
async generateTextWithMetadata(prompt, maxTokens, temperature
|
|
286
|
-
this.calls.push({ prompt, maxTokens, temperature
|
|
464
|
+
async generateTextWithMetadata(prompt, maxTokens, temperature) {
|
|
465
|
+
this.calls.push({ prompt, maxTokens, temperature });
|
|
466
|
+
return this.nextResponse();
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Structured surface: pops the same responses queue and PARSES the entry,
|
|
470
|
+
* mirroring the real contract — a queued string that is not a JSON array
|
|
471
|
+
* throws "could not be read", so tests inject the malformed shape simply by
|
|
472
|
+
* queuing it (`setResponses(['not json'])`). The element schema is recorded
|
|
473
|
+
* on `calls` so tests can assert what the caller declared.
|
|
474
|
+
*/
|
|
475
|
+
async generateStructured(prompt, maxTokens, temperature, elementSchema) {
|
|
476
|
+
this.calls.push({ prompt, maxTokens, temperature, elementSchema });
|
|
477
|
+
const { text, stopReason } = this.nextResponse();
|
|
478
|
+
let parsed;
|
|
479
|
+
try {
|
|
480
|
+
parsed = JSON.parse(text);
|
|
481
|
+
} catch (err) {
|
|
482
|
+
throw new Error(
|
|
483
|
+
`Structured response could not be read: response is not valid JSON (stop_reason: ${stopReason})`,
|
|
484
|
+
{ cause: err }
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
if (!Array.isArray(parsed)) {
|
|
488
|
+
throw new Error(
|
|
489
|
+
`Structured response could not be read: parsed to ${typeof parsed}, not an array (stop_reason: ${stopReason})`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
return { items: parsed, stopReason };
|
|
493
|
+
}
|
|
494
|
+
nextResponse() {
|
|
287
495
|
const text = this.responses[this.responseIndex];
|
|
288
496
|
const stopReason = this.stopReasons[this.responseIndex] || "end_turn";
|
|
289
497
|
if (this.responseIndex < this.responses.length - 1) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/implementations/anthropic.ts","../src/implementations/ollama.ts","../src/factory.ts","../src/implementations/mock.ts"],"sourcesContent":["// Anthropic Claude implementation of InferenceClient interface\n\nimport Anthropic from '@anthropic-ai/sdk';\nimport type { Logger } from '@semiont/core';\nimport { recordInferenceUsage } from '@semiont/observability';\nimport { InferenceClient, InferenceOptions, InferenceResponse } from '../interface.js';\n\n// Forced-tool channel for JSON mode. Anthropic has no grammar-constrained\n// sampling like Ollama's `format`; the equivalent hard guarantee is a *tool\n// call*. We offer exactly one tool and force it via `tool_choice`, so the model\n// must answer by filling the tool's input — which the API serializes as\n// properly-escaped JSON. That kills both free-text failure modes at the source:\n// trailing prose after the `]` (variant 1) and an unescaped `\"` inside a string\n// (variant 2), neither of which a prefill could prevent.\n//\n// A tool's input must be an *object*, so the array is carried under `items`\n// and unwrapped on return (see generateTextWithMetadata) — the caller still\n// receives a top-level JSON array in `text`, exactly as on Ollama.\nconst JSON_ARRAY_TOOL: Anthropic.Tool = {\n name: 'emit_json_array',\n description:\n 'Return your entire answer by calling this tool. Put the JSON array of results under the \"items\" property, and emit no prose.',\n input_schema: {\n type: 'object',\n properties: {\n // Element shape is unconstrained here — the prompt carries the per-element\n // schema; the tool only enforces that the top-level result is an array.\n items: { type: 'array', items: {} },\n },\n required: ['items'],\n },\n};\n\nexport class AnthropicInferenceClient implements InferenceClient {\n readonly type = 'anthropic' as const;\n readonly modelId: string;\n private client: Anthropic;\n private logger?: Logger;\n\n constructor(apiKey: string, model: string, baseURL?: string, logger?: Logger) {\n this.client = new Anthropic({\n apiKey,\n baseURL: baseURL || 'https://api.anthropic.com',\n });\n this.modelId = model;\n this.logger = logger;\n }\n\n async generateText(prompt: string, maxTokens: number, temperature: number, options?: InferenceOptions): Promise<string> {\n const response = await this.generateTextWithMetadata(prompt, maxTokens, temperature, options);\n return response.text;\n }\n\n async generateTextWithMetadata(prompt: string, maxTokens: number, temperature: number, options?: InferenceOptions): Promise<InferenceResponse> {\n const jsonMode = options?.format === 'json';\n\n this.logger?.debug('Generating text with inference client', {\n model: this.modelId,\n promptLength: prompt.length,\n maxTokens,\n temperature,\n format: options?.format,\n });\n\n const start = performance.now();\n let response: Awaited<ReturnType<typeof this.client.messages.create>>;\n try {\n response = await this.client.messages.create({\n model: this.modelId,\n max_tokens: maxTokens,\n temperature,\n messages: [{ role: 'user', content: prompt }],\n // JSON mode → force the structured-output tool. No prefill assistant\n // turn: the constraint now lives in the tool call, not in free text.\n ...(jsonMode\n ? { tools: [JSON_ARRAY_TOOL], tool_choice: { type: 'tool' as const, name: JSON_ARRAY_TOOL.name } }\n : {}),\n });\n } catch (err) {\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'error',\n });\n throw err;\n }\n\n this.logger?.debug('Inference response received', {\n model: this.modelId,\n contentBlocks: response.content.length,\n stopReason: response.stop_reason\n });\n\n let text: string;\n if (jsonMode) {\n // The answer arrives as a tool_use block, not text. Unwrap the `items`\n // array and re-serialize it so `text` is a complete, parseable top-level\n // JSON array — the cross-provider contract every consumer reads.\n const toolUse = response.content.find(c => c.type === 'tool_use');\n if (!toolUse || toolUse.type !== 'tool_use') {\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'error',\n inputTokens: response.usage?.input_tokens,\n outputTokens: response.usage?.output_tokens,\n });\n this.logger?.error('No tool_use content in inference response', {\n model: this.modelId,\n contentTypes: response.content.map(c => c.type)\n });\n throw new Error('No tool_use content in inference response');\n }\n // `input` is typed `unknown` by the SDK. A truncated (`max_tokens`)\n // response may carry partial or absent `items` — fall back to the partial\n // array, or `[]` if absent; the consumer flags truncation via stopReason.\n const input = toolUse.input as { items?: unknown };\n const items = Array.isArray(input.items) ? input.items : [];\n text = JSON.stringify(items);\n } else {\n const textContent = response.content.find(c => c.type === 'text');\n if (!textContent || textContent.type !== 'text') {\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'error',\n inputTokens: response.usage?.input_tokens,\n outputTokens: response.usage?.output_tokens,\n });\n this.logger?.error('No text content in inference response', {\n model: this.modelId,\n contentTypes: response.content.map(c => c.type)\n });\n throw new Error('No text content in inference response');\n }\n text = textContent.text;\n }\n\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'success',\n inputTokens: response.usage?.input_tokens,\n outputTokens: response.usage?.output_tokens,\n });\n\n this.logger?.info('Text generation completed', {\n model: this.modelId,\n textLength: text.length,\n stopReason: response.stop_reason\n });\n\n return {\n text,\n stopReason: response.stop_reason || 'unknown'\n };\n }\n}\n","// Ollama implementation of InferenceClient interface\n// Uses native Ollama HTTP API (no SDK dependency)\n\nimport type { Logger } from '@semiont/core';\nimport { recordInferenceUsage } from '@semiont/observability';\nimport { InferenceClient, InferenceOptions, InferenceResponse } from '../interface.js';\n\ninterface OllamaGenerateResponse {\n response: string;\n done: boolean;\n done_reason?: string;\n /** Number of prompt tokens evaluated. Available on most Ollama versions. */\n prompt_eval_count?: number;\n /** Number of tokens generated. */\n eval_count?: number;\n}\n\nexport class OllamaInferenceClient implements InferenceClient {\n readonly type = 'ollama' as const;\n readonly modelId: string;\n private baseURL: string;\n private logger?: Logger;\n\n constructor(model: string, baseURL?: string, logger?: Logger) {\n this.baseURL = (baseURL || 'http://localhost:11434').replace(/\\/+$/, '');\n this.modelId = model;\n this.logger = logger;\n }\n\n async generateText(prompt: string, maxTokens: number, temperature: number, options?: InferenceOptions): Promise<string> {\n const response = await this.generateTextWithMetadata(prompt, maxTokens, temperature, options);\n return response.text;\n }\n\n async generateTextWithMetadata(prompt: string, maxTokens: number, temperature: number, options?: InferenceOptions): Promise<InferenceResponse> {\n this.logger?.debug('Generating text with Ollama', {\n model: this.modelId,\n promptLength: prompt.length,\n maxTokens,\n temperature,\n format: options?.format,\n });\n\n const url = `${this.baseURL}/api/generate`;\n const start = performance.now();\n\n // Ollama's `format` parameter accepts either the literal string\n // `\"json\"` (any valid JSON, including objects, numbers, etc.) or a\n // JSON schema (constrains the top-level shape). The contract on the\n // inference side is \"parseable JSON array,\" so we pass a minimal\n // array schema rather than the bare `\"json\"` string — without it,\n // the model can satisfy \"valid JSON\" by emitting `{\"entities\": [...]}`\n // and break every consumer that expects to call `.map` on the\n // top-level value. The schema's `items: {}` keeps element shape\n // unconstrained — the prompt still carries the per-element schema;\n // we only enforce the outer array.\n const body: Record<string, unknown> = {\n model: this.modelId,\n prompt,\n stream: false,\n think: false,\n options: {\n num_predict: maxTokens,\n temperature,\n },\n };\n if (options?.format === 'json') {\n body['format'] = { type: 'array', items: {} };\n }\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n } catch (err) {\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'error',\n });\n throw err;\n }\n\n if (!res.ok) {\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'error',\n });\n const body = await res.text();\n this.logger?.error('Ollama API error', {\n model: this.modelId,\n status: res.status,\n body,\n });\n throw new Error(`Ollama API error (${res.status}): ${body}`);\n }\n\n const data = await res.json() as OllamaGenerateResponse;\n\n if (!data.response) {\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'error',\n inputTokens: data.prompt_eval_count,\n outputTokens: data.eval_count,\n });\n this.logger?.error('Empty response from Ollama', { model: this.modelId });\n throw new Error('Empty response from Ollama');\n }\n\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'success',\n inputTokens: data.prompt_eval_count,\n outputTokens: data.eval_count,\n });\n\n const stopReason = mapStopReason(data.done_reason);\n\n this.logger?.info('Text generation completed', {\n model: this.modelId,\n textLength: data.response.length,\n stopReason,\n });\n\n return {\n text: data.response,\n stopReason,\n };\n }\n}\n\nfunction mapStopReason(doneReason: string | undefined): string {\n switch (doneReason) {\n case 'stop': return 'end_turn';\n case 'length': return 'max_tokens';\n default: return doneReason || 'unknown';\n }\n}\n","// Factory for creating inference client instances based on configuration\n\nimport type { Logger } from '@semiont/core';\nimport { InferenceClient } from './interface.js';\nimport { AnthropicInferenceClient } from './implementations/anthropic.js';\nimport { OllamaInferenceClient } from './implementations/ollama.js';\n\nexport type InferenceClientType = 'anthropic' | 'ollama';\n\nexport interface InferenceClientConfig {\n type: InferenceClientType;\n apiKey?: string;\n model: string;\n endpoint?: string;\n baseURL?: string;\n}\n\nexport function createInferenceClient(config: InferenceClientConfig, logger?: Logger): InferenceClient {\n switch (config.type) {\n case 'anthropic': {\n if (!config.apiKey || config.apiKey.trim() === '') {\n throw new Error('apiKey is required for Anthropic inference client');\n }\n return new AnthropicInferenceClient(\n config.apiKey,\n config.model,\n config.endpoint || config.baseURL,\n logger\n );\n }\n\n case 'ollama': {\n return new OllamaInferenceClient(\n config.model,\n config.endpoint || config.baseURL,\n logger\n );\n }\n\n default:\n throw new Error(`Unsupported inference client type: ${config.type}`);\n }\n}\n","// Mock implementation of InferenceClient for testing\n\nimport { InferenceClient, InferenceOptions, InferenceResponse } from '../interface.js';\n\nexport class MockInferenceClient implements InferenceClient {\n readonly type = 'mock' as const;\n readonly modelId = 'mock-model' as const;\n private responses: string[] = [];\n private responseIndex: number = 0;\n private stopReasons: string[] = [];\n public calls: Array<{ prompt: string; maxTokens: number; temperature: number; options?: InferenceOptions }> = [];\n\n constructor(responses: string[] = ['Mock response'], stopReasons?: string[]) {\n this.responses = responses;\n this.stopReasons = stopReasons || responses.map(() => 'end_turn');\n }\n\n async generateText(prompt: string, maxTokens: number, temperature: number, options?: InferenceOptions): Promise<string> {\n const response = await this.generateTextWithMetadata(prompt, maxTokens, temperature, options);\n return response.text;\n }\n\n async generateTextWithMetadata(prompt: string, maxTokens: number, temperature: number, options?: InferenceOptions): Promise<InferenceResponse> {\n this.calls.push({ prompt, maxTokens, temperature, ...(options ? { options } : {}) });\n\n const text = this.responses[this.responseIndex];\n const stopReason = this.stopReasons[this.responseIndex] || 'end_turn';\n\n if (this.responseIndex < this.responses.length - 1) {\n this.responseIndex++;\n }\n\n return { text, stopReason };\n }\n\n // Test helper methods\n reset(): void {\n this.calls = [];\n this.responseIndex = 0;\n }\n\n setResponses(responses: string[], stopReasons?: string[]): void {\n this.responses = responses;\n this.stopReasons = stopReasons || responses.map(() => 'end_turn');\n this.responseIndex = 0;\n }\n}\n"],"mappings":"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","names":["recordInferenceUsage","body"]}
|
|
1
|
+
{"version":3,"sources":["../src/implementations/anthropic.ts","../src/implementations/ollama.ts","../src/factory.ts","../src/implementations/mock.ts"],"sourcesContent":["// Anthropic Claude implementation of InferenceClient interface\n\nimport Anthropic from '@anthropic-ai/sdk';\nimport { isObject, type Logger } from '@semiont/core';\nimport { recordInferenceUsage } from '@semiont/observability';\nimport { ElementSchema, InferenceClient, InferenceLimits, InferenceResponse, StructuredResponse } from '../interface.js';\n\n// The SDK refuses non-streaming create() calls whose projected duration\n// exceeds its 10-minute timeout: it throws when\n// (60min × max_tokens) / 128_000 > 10min, i.e. above 128_000/6 ≈ 21,333\n// output tokens (client.js, calculateNonstreamingTimeout). Above that we\n// stream internally and assemble the final message — same request shape,\n// same response handling, same interface.\nconst NONSTREAMING_MAX_OUTPUT_TOKENS = Math.floor(128_000 / 6);\n\n// Structured generation rides `output_config.format` — response-level\n// structured output: the response TEXT is the schema-conforming JSON, with a\n// top-level ARRAY root (accepted on both live-config models — spike\n// 2026-08-06, `.plans/spikes/output-config-array-root.md`). This replaced\n// the pre-structured-outputs scaffolding: a forced `emit_json_array` tool\n// whose object-only input required an `items` wrapper and an unwrap — and\n// the unwrap was the exact line that silently coerced an unreadable payload\n// to `[]` (STRUCTURED-INFERENCE §Problem). There is no tool-input\n// accumulation step left for the SDK to hand over unparsed; the read path is\n// now the same parse-and-verify shape as Ollama's.\n\n/**\n * Everything one Models API call teaches us about the configured model. The\n * capability stays PRIVATE to this client: its only consumer is the gate in\n * `generateStructured`, so it does not cross the `InferenceClient` interface\n * (no speculative surface — widen `InferenceLimits` only when an external\n * consumer exists).\n */\ninterface ModelDiscovery {\n limits: InferenceLimits;\n structuredOutputsSupported: boolean;\n}\n\nexport class AnthropicInferenceClient implements InferenceClient {\n readonly type = 'anthropic' as const;\n readonly modelId: string;\n private client: Anthropic;\n private logger?: Logger;\n private discoveryPromise?: Promise<ModelDiscovery>;\n\n constructor(apiKey: string, model: string, baseURL?: string, logger?: Logger) {\n this.client = new Anthropic({\n apiKey,\n baseURL: baseURL || 'https://api.anthropic.com',\n });\n this.modelId = model;\n this.logger = logger;\n }\n\n limits(): Promise<InferenceLimits> {\n return this.discover().then(d => d.limits);\n }\n\n private discover(): Promise<ModelDiscovery> {\n if (!this.discoveryPromise) {\n this.discoveryPromise = this.discoverModel().catch((err: unknown) => {\n // Never cache a failed discovery — a transient outage would otherwise\n // pin every future call to the same rejection.\n this.discoveryPromise = undefined;\n throw err;\n });\n }\n return this.discoveryPromise;\n }\n\n private async discoverModel(): Promise<ModelDiscovery> {\n // The Models API publishes the actual ceilings AND capabilities per\n // model — no hand-maintained table to go stale when a new model ships,\n // and the API's own metadata outranks documentation prose when the two\n // disagree (measured: the docs' supported-model list was stale while\n // `capabilities.structured_outputs.supported` was correct).\n const info = await this.client.models.retrieve(this.modelId).catch((err: unknown) => {\n throw new Error(\n `Failed to discover model limits for '${this.modelId}' from the Models API`,\n { cause: err },\n );\n });\n if (info.max_input_tokens == null || info.max_tokens == null) {\n throw new Error(`Models API reports no context/output ceilings for '${this.modelId}'`);\n }\n // `capabilities` is not declared on the SDK's ModelInfo type — narrow\n // through the core guards rather than casting. Absent metadata reads as\n // unsupported: the gate then refuses loudly (D4), never guesses.\n const raw: unknown = info;\n const structuredOutputsSupported =\n isObject(raw) &&\n isObject(raw['capabilities']) &&\n isObject(raw['capabilities']['structured_outputs']) &&\n raw['capabilities']['structured_outputs']['supported'] === true;\n return {\n limits: { contextTokens: info.max_input_tokens, maxOutputTokens: info.max_tokens },\n structuredOutputsSupported,\n };\n }\n\n private requestMessage(params: Anthropic.MessageCreateParamsNonStreaming): Promise<Anthropic.Message> {\n if (params.max_tokens > NONSTREAMING_MAX_OUTPUT_TOKENS) {\n return this.client.messages.stream(params).finalMessage();\n }\n return this.client.messages.create(params);\n }\n\n async generateText(prompt: string, maxTokens: number, temperature: number): Promise<string> {\n const response = await this.generateTextWithMetadata(prompt, maxTokens, temperature);\n return response.text;\n }\n\n async generateTextWithMetadata(prompt: string, maxTokens: number, temperature: number): Promise<InferenceResponse> {\n this.logger?.debug('Generating text with inference client', {\n model: this.modelId,\n promptLength: prompt.length,\n maxTokens,\n temperature,\n });\n\n const params: Anthropic.MessageCreateParamsNonStreaming = {\n model: this.modelId,\n max_tokens: maxTokens,\n temperature,\n messages: [{ role: 'user', content: prompt }],\n };\n\n const start = performance.now();\n const response = await this.recordedRequest(params, start);\n\n const textContent = response.content.find(c => c.type === 'text');\n if (!textContent || textContent.type !== 'text') {\n this.recordError(start, response);\n this.logger?.error('No text content in inference response', {\n model: this.modelId,\n contentTypes: response.content.map(c => c.type)\n });\n throw new Error('No text content in inference response');\n }\n const text = textContent.text;\n\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'success',\n inputTokens: response.usage?.input_tokens,\n outputTokens: response.usage?.output_tokens,\n });\n\n this.logger?.info('Text generation completed', {\n model: this.modelId,\n textLength: text.length,\n stopReason: response.stop_reason\n });\n\n return {\n text,\n stopReason: response.stop_reason || 'unknown'\n };\n }\n\n async generateStructured<T>(\n prompt: string,\n maxTokens: number,\n temperature: number,\n elementSchema: ElementSchema,\n ): Promise<StructuredResponse<T>> {\n // Capability gate (D3/D4): model choice is deployment config, so the\n // client asks the provider whether the configured model can honour\n // strictness — and REFUSES when it cannot. Silent fallback to\n // unconstrained tool use is exactly the behaviour that turned 202 real\n // entities into a green empty job. The discovery is the same cached\n // Models API call `limits()` uses; no extra round trip.\n const discovery = await this.discover();\n if (!discovery.structuredOutputsSupported) {\n throw new Error(\n `Model '${this.modelId}' does not report support for strict structured outputs ` +\n `(Models API capabilities.structured_outputs) — refusing rather than degrading to ` +\n `unconstrained tool use, which silently discards unreadable results. Re-point the ` +\n `inference.model key that pins this worker/actor in .semiont/semiontconfig/*.toml ` +\n `(e.g. environments.<env>.workers.<job-type>.inference.model) at a model that ` +\n `reports supported: true.`,\n );\n }\n\n this.logger?.debug('Generating structured output with inference client', {\n model: this.modelId,\n promptLength: prompt.length,\n maxTokens,\n temperature,\n });\n\n const params: Anthropic.MessageCreateParamsNonStreaming = {\n model: this.modelId,\n max_tokens: maxTokens,\n temperature,\n messages: [{ role: 'user', content: prompt }],\n // Response-level structured output with an ARRAY root: the response\n // text IS the schema-conforming JSON. No tools, no prefill.\n output_config: {\n format: {\n type: 'json_schema',\n schema: { type: 'array', items: elementSchema },\n },\n },\n };\n\n const start = performance.now();\n const response = await this.recordedRequest(params, start);\n\n const textContent = response.content.find(c => c.type === 'text');\n if (!textContent || textContent.type !== 'text') {\n this.recordError(start, response);\n this.logger?.error('No text content in structured inference response', {\n model: this.modelId,\n contentTypes: response.content.map(c => c.type)\n });\n throw new Error('No text content in structured inference response');\n }\n\n // Anything that does not read as an array is a THROW, never a coerced\n // `[]` — \"we could not read the model\" must never be conflated with\n // \"the model found nothing\": that conflation is what silently discarded\n // 202 real entities as a green empty result. A truncated (`max_tokens`)\n // response surfaces here too, as unparseable JSON naming its stop_reason.\n let parsed: unknown;\n try {\n parsed = JSON.parse(textContent.text);\n } catch (err) {\n this.recordError(start, response);\n this.logger?.error('Structured response could not be read', {\n model: this.modelId,\n textLength: textContent.text.length,\n stopReason: response.stop_reason,\n });\n throw new Error(\n `Structured response could not be read: response is not valid JSON (stop_reason: ${response.stop_reason})`,\n { cause: err },\n );\n }\n if (!Array.isArray(parsed)) {\n this.recordError(start, response);\n this.logger?.error('Structured response could not be read', {\n model: this.modelId,\n parsedType: typeof parsed,\n stopReason: response.stop_reason,\n });\n throw new Error(\n `Structured response could not be read: parsed to ${typeof parsed}, not an array (stop_reason: ${response.stop_reason})`,\n );\n }\n\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'success',\n inputTokens: response.usage?.input_tokens,\n outputTokens: response.usage?.output_tokens,\n });\n\n this.logger?.info('Structured generation completed', {\n model: this.modelId,\n items: parsed.length,\n stopReason: response.stop_reason\n });\n\n return {\n items: parsed as T[],\n stopReason: response.stop_reason || 'unknown',\n };\n }\n\n /** Issue the request, recording an error metric if the transport throws. */\n private async recordedRequest(params: Anthropic.MessageCreateParamsNonStreaming, start: number): Promise<Anthropic.Message> {\n try {\n return await this.requestMessage(params);\n } catch (err) {\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'error',\n });\n throw err;\n }\n }\n\n private recordError(start: number, response: Anthropic.Message): void {\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'error',\n inputTokens: response.usage?.input_tokens,\n outputTokens: response.usage?.output_tokens,\n });\n }\n}\n","// Ollama implementation of InferenceClient interface\n// Uses native Ollama HTTP API (no SDK dependency)\n\nimport { estimateTokens, isNumber, isObject } from '@semiont/core';\nimport type { Logger } from '@semiont/core';\nimport { recordInferenceUsage } from '@semiont/observability';\nimport { ElementSchema, InferenceClient, InferenceLimits, InferenceResponse, StructuredResponse } from '../interface.js';\n\n// Slack added to the chars/4 prompt estimate when sizing `num_ctx`:\n// proportional to the estimate (the heuristic's error grows with prompt size)\n// plus a small fixed allowance for the model's chat template. The risk profile\n// is asymmetric — an undersized window silently clips input (the exact hole\n// managed num_ctx exists to close) while an oversized one only costs memory —\n// so the slack leans generous. Always capped at the model's real window.\nconst NUM_CTX_ESTIMATE_SLACK = 0.2;\nconst NUM_CTX_TEMPLATE_ALLOWANCE = 64;\n\ninterface OllamaGenerateResponse {\n response: string;\n done: boolean;\n done_reason?: string;\n /** Number of prompt tokens evaluated. Available on most Ollama versions. */\n prompt_eval_count?: number;\n /** Number of tokens generated. */\n eval_count?: number;\n}\n\nexport class OllamaInferenceClient implements InferenceClient {\n readonly type = 'ollama' as const;\n readonly modelId: string;\n private baseURL: string;\n private logger?: Logger;\n\n private limitsPromise?: Promise<InferenceLimits>;\n\n constructor(model: string, baseURL?: string, logger?: Logger) {\n this.baseURL = (baseURL || 'http://localhost:11434').replace(/\\/+$/, '');\n this.modelId = model;\n this.logger = logger;\n }\n\n limits(): Promise<InferenceLimits> {\n if (!this.limitsPromise) {\n this.limitsPromise = this.discoverLimits().catch((err: unknown) => {\n // Never cache a failed discovery — a transient outage would otherwise\n // pin every future call to the same rejection.\n this.limitsPromise = undefined;\n throw err;\n });\n }\n return this.limitsPromise;\n }\n\n private async discoverLimits(): Promise<InferenceLimits> {\n const res = await fetch(`${this.baseURL}/api/show`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ model: this.modelId }),\n });\n if (!res.ok) {\n throw new Error(\n `Failed to discover model limits: /api/show returned ${res.status} for '${this.modelId}'`,\n );\n }\n const data: unknown = await res.json();\n const modelInfo = isObject(data) && isObject(data['model_info']) ? data['model_info'] : undefined;\n const contextTokens = readContextLength(modelInfo);\n if (contextTokens === undefined) {\n throw new Error(`/api/show reports no context length for '${this.modelId}'`);\n }\n // Shared window: input and output draw from the same context — there is\n // no separate output ceiling, so the window is published as both (the\n // `maxOutputTokens === contextTokens` shape consumers key the split on).\n return { contextTokens, maxOutputTokens: contextTokens };\n }\n\n async generateText(prompt: string, maxTokens: number, temperature: number): Promise<string> {\n const response = await this.generateTextWithMetadata(prompt, maxTokens, temperature);\n return response.text;\n }\n\n async generateTextWithMetadata(prompt: string, maxTokens: number, temperature: number): Promise<InferenceResponse> {\n return this.generate(prompt, maxTokens, temperature, undefined);\n }\n\n async generateStructured<T>(\n prompt: string,\n maxTokens: number,\n temperature: number,\n elementSchema: ElementSchema,\n ): Promise<StructuredResponse<T>> {\n // Grammar-constrained sampling: the schema goes to Ollama's `format`\n // parameter, which constrains generation itself — same mechanism as the\n // old bare array schema, now element-typed. The response text is then\n // parsed here, and anything that does not read as an array is a THROW,\n // never a coerced [] — \"could not read the model\" must stay distinct\n // from \"the model found nothing.\"\n const response = await this.generate(prompt, maxTokens, temperature, elementSchema);\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(response.text);\n } catch (err) {\n this.logger?.error('Structured response could not be read', {\n model: this.modelId,\n textLength: response.text.length,\n stopReason: response.stopReason,\n });\n throw new Error(\n `Structured response could not be read: response is not valid JSON (stop_reason: ${response.stopReason})`,\n { cause: err },\n );\n }\n if (!Array.isArray(parsed)) {\n this.logger?.error('Structured response could not be read', {\n model: this.modelId,\n parsedType: typeof parsed,\n stopReason: response.stopReason,\n });\n throw new Error(\n `Structured response could not be read: parsed to ${typeof parsed}, not an array (stop_reason: ${response.stopReason})`,\n );\n }\n\n return { items: parsed as T[], stopReason: response.stopReason };\n }\n\n private async generate(\n prompt: string,\n maxTokens: number,\n temperature: number,\n elementSchema: ElementSchema | undefined,\n ): Promise<InferenceResponse> {\n this.logger?.debug('Generating text with Ollama', {\n model: this.modelId,\n promptLength: prompt.length,\n maxTokens,\n temperature,\n structured: elementSchema !== undefined,\n });\n\n // Managed context window: size num_ctx to cover this request, capped at\n // the model's discovered window. Without an explicit num_ctx Ollama uses\n // the model's *default* window and SILENTLY CLIPS any prompt beyond it —\n // input loss with no error (found 2026-07-30).\n const limits = await this.limits();\n const promptTokens = estimateTokens(prompt);\n if (promptTokens + maxTokens > limits.contextTokens) {\n throw new Error(\n `Prompt (~${promptTokens} tokens) + output budget (${maxTokens}) exceed the ` +\n `'${this.modelId}' context window (${limits.contextTokens} tokens)`,\n );\n }\n const numCtx = Math.min(\n limits.contextTokens,\n promptTokens + maxTokens\n + Math.ceil(promptTokens * NUM_CTX_ESTIMATE_SLACK) + NUM_CTX_TEMPLATE_ALLOWANCE,\n );\n\n const url = `${this.baseURL}/api/generate`;\n const start = performance.now();\n\n // Ollama's `format` parameter accepts either the literal string\n // `\"json\"` (any valid JSON, including objects, numbers, etc.) or a\n // JSON schema (constrains the top-level shape). The structured contract\n // is \"an array of elements matching the caller's schema,\" so we pass an\n // array schema wrapping it — the bare `\"json\"` string would let the\n // model satisfy \"valid JSON\" with `{\"entities\": [...]}` and break every\n // consumer that maps over the top-level value.\n const body: Record<string, unknown> = {\n model: this.modelId,\n prompt,\n stream: false,\n think: false,\n options: {\n num_predict: maxTokens,\n num_ctx: numCtx,\n temperature,\n },\n };\n if (elementSchema !== undefined) {\n body['format'] = { type: 'array', items: elementSchema };\n }\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n } catch (err) {\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'error',\n });\n throw err;\n }\n\n if (!res.ok) {\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'error',\n });\n const body = await res.text();\n this.logger?.error('Ollama API error', {\n model: this.modelId,\n status: res.status,\n body,\n });\n throw new Error(`Ollama API error (${res.status}): ${body}`);\n }\n\n const data = await res.json() as OllamaGenerateResponse;\n\n if (!data.response) {\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'error',\n inputTokens: data.prompt_eval_count,\n outputTokens: data.eval_count,\n });\n this.logger?.error('Empty response from Ollama', { model: this.modelId });\n throw new Error('Empty response from Ollama');\n }\n\n recordInferenceUsage({\n provider: this.type,\n model: this.modelId,\n durationMs: performance.now() - start,\n outcome: 'success',\n inputTokens: data.prompt_eval_count,\n outputTokens: data.eval_count,\n });\n\n const stopReason = mapStopReason(data.done_reason);\n\n this.logger?.info('Text generation completed', {\n model: this.modelId,\n textLength: data.response.length,\n stopReason,\n });\n\n return {\n text: data.response,\n stopReason,\n };\n }\n}\n\n/**\n * The context length lives in `model_info` under an architecture-prefixed key\n * (e.g. `llama.context_length`); `general.architecture` names the prefix.\n * Falls back to any `*.context_length` key for models whose metadata omits\n * the architecture field.\n */\nfunction readContextLength(modelInfo: Record<string, unknown> | undefined): number | undefined {\n if (!modelInfo) return undefined;\n const arch = modelInfo['general.architecture'];\n if (typeof arch === 'string') {\n const direct = modelInfo[`${arch}.context_length`];\n if (isNumber(direct) && direct > 0) return direct;\n }\n const fallbackKey = Object.keys(modelInfo).find(k => k.endsWith('.context_length'));\n if (fallbackKey !== undefined) {\n const fallback = modelInfo[fallbackKey];\n if (isNumber(fallback) && fallback > 0) return fallback;\n }\n return undefined;\n}\n\nfunction mapStopReason(doneReason: string | undefined): string {\n switch (doneReason) {\n case 'stop': return 'end_turn';\n case 'length': return 'max_tokens';\n default: return doneReason || 'unknown';\n }\n}\n","// Factory for creating inference client instances based on configuration\n\nimport type { Logger } from '@semiont/core';\nimport { InferenceClient } from './interface.js';\nimport { AnthropicInferenceClient } from './implementations/anthropic.js';\nimport { OllamaInferenceClient } from './implementations/ollama.js';\n\nexport type InferenceClientType = 'anthropic' | 'ollama';\n\nexport interface InferenceClientConfig {\n type: InferenceClientType;\n apiKey?: string;\n model: string;\n endpoint?: string;\n baseURL?: string;\n}\n\nexport function createInferenceClient(config: InferenceClientConfig, logger?: Logger): InferenceClient {\n switch (config.type) {\n case 'anthropic': {\n if (!config.apiKey || config.apiKey.trim() === '') {\n throw new Error('apiKey is required for Anthropic inference client');\n }\n return new AnthropicInferenceClient(\n config.apiKey,\n config.model,\n config.endpoint || config.baseURL,\n logger\n );\n }\n\n case 'ollama': {\n return new OllamaInferenceClient(\n config.model,\n config.endpoint || config.baseURL,\n logger\n );\n }\n\n default:\n throw new Error(`Unsupported inference client type: ${config.type}`);\n }\n}\n","// Mock implementation of InferenceClient for testing\n\nimport { ElementSchema, InferenceClient, InferenceLimits, InferenceResponse, StructuredResponse } from '../interface.js';\n\n// Generous defaults so existing consumers never trip chunking or window\n// guards unless a test injects tighter limits deliberately.\nconst GENEROUS_LIMITS: InferenceLimits = {\n contextTokens: 1_000_000,\n maxOutputTokens: 1_000_000,\n};\n\nexport class MockInferenceClient implements InferenceClient {\n readonly type = 'mock' as const;\n readonly modelId = 'mock-model' as const;\n private responses: string[] = [];\n private responseIndex: number = 0;\n private stopReasons: string[] = [];\n private injectedLimits: InferenceLimits;\n public calls: Array<{ prompt: string; maxTokens: number; temperature: number; elementSchema?: ElementSchema }> = [];\n\n constructor(responses: string[] = ['Mock response'], stopReasons?: string[], limits?: InferenceLimits) {\n this.responses = responses;\n this.stopReasons = stopReasons || responses.map(() => 'end_turn');\n this.injectedLimits = limits ?? GENEROUS_LIMITS;\n }\n\n async limits(): Promise<InferenceLimits> {\n return this.injectedLimits;\n }\n\n async generateText(prompt: string, maxTokens: number, temperature: number): Promise<string> {\n const response = await this.generateTextWithMetadata(prompt, maxTokens, temperature);\n return response.text;\n }\n\n async generateTextWithMetadata(prompt: string, maxTokens: number, temperature: number): Promise<InferenceResponse> {\n this.calls.push({ prompt, maxTokens, temperature });\n return this.nextResponse();\n }\n\n /**\n * Structured surface: pops the same responses queue and PARSES the entry,\n * mirroring the real contract — a queued string that is not a JSON array\n * throws \"could not be read\", so tests inject the malformed shape simply by\n * queuing it (`setResponses(['not json'])`). The element schema is recorded\n * on `calls` so tests can assert what the caller declared.\n */\n async generateStructured<T>(\n prompt: string,\n maxTokens: number,\n temperature: number,\n elementSchema: ElementSchema,\n ): Promise<StructuredResponse<T>> {\n this.calls.push({ prompt, maxTokens, temperature, elementSchema });\n const { text, stopReason } = this.nextResponse();\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (err) {\n throw new Error(\n `Structured response could not be read: response is not valid JSON (stop_reason: ${stopReason})`,\n { cause: err },\n );\n }\n if (!Array.isArray(parsed)) {\n throw new Error(\n `Structured response could not be read: parsed to ${typeof parsed}, not an array (stop_reason: ${stopReason})`,\n );\n }\n return { items: parsed as T[], stopReason };\n }\n\n private nextResponse(): InferenceResponse {\n const text = this.responses[this.responseIndex];\n const stopReason = this.stopReasons[this.responseIndex] || 'end_turn';\n\n if (this.responseIndex < this.responses.length - 1) {\n this.responseIndex++;\n }\n\n return { text, stopReason };\n }\n\n // Test helper methods\n reset(): void {\n this.calls = [];\n this.responseIndex = 0;\n }\n\n setResponses(responses: string[], stopReasons?: string[]): void {\n this.responses = responses;\n this.stopReasons = stopReasons || responses.map(() => 'end_turn');\n this.responseIndex = 0;\n }\n}\n"],"mappings":"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","names":["isObject","recordInferenceUsage","body"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@semiont/inference",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.26",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=24.0.0"
|
|
6
6
|
},
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"test:coverage": "vitest run --coverage"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@anthropic-ai/sdk": "^0.
|
|
31
|
-
"@semiont/core": "0.5.
|
|
32
|
-
"@semiont/observability": "0.5.
|
|
30
|
+
"@anthropic-ai/sdk": "^0.115.0",
|
|
31
|
+
"@semiont/core": "0.5.26",
|
|
32
|
+
"@semiont/observability": "0.5.26"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@vitest/coverage-v8": "^4.1.8",
|
|
36
|
-
"rollup": "^4.
|
|
36
|
+
"rollup": "^4.62.3",
|
|
37
37
|
"rollup-plugin-dts": "^6.4.1",
|
|
38
38
|
"tsup": "^8.0.1",
|
|
39
39
|
"typescript": "^6.0.2",
|