@semiont/inference 0.5.25 → 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 +14 -8
- package/dist/index.d.ts +71 -32
- package/dist/index.js +205 -94
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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/).
|
|
@@ -106,20 +106,26 @@ interface InferenceResponse {
|
|
|
106
106
|
}
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
###
|
|
109
|
+
### Structured generation
|
|
110
110
|
|
|
111
|
-
|
|
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:
|
|
112
112
|
|
|
113
113
|
```typescript
|
|
114
|
-
const
|
|
115
|
-
|
|
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
|
+
});
|
|
116
120
|
```
|
|
117
121
|
|
|
118
122
|
Each implementation honors the contract with its provider's mechanism:
|
|
119
|
-
- **Ollama**: grammar-constrained sampling — the request's `format` field carries
|
|
120
|
-
- **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).
|
|
121
127
|
|
|
122
|
-
Current callers all expect arrays (entity extraction, motivation detection). If an object-emitting caller appears,
|
|
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).
|
|
123
129
|
|
|
124
130
|
### Provider limits
|
|
125
131
|
|
package/dist/index.d.ts
CHANGED
|
@@ -5,27 +5,36 @@ 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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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;
|
|
29
38
|
}
|
|
30
39
|
/**
|
|
31
40
|
* A provider's actual ceilings for the configured model, discovered from the
|
|
@@ -60,11 +69,25 @@ interface InferenceClient {
|
|
|
60
69
|
/**
|
|
61
70
|
* Generate text from a prompt (simple interface)
|
|
62
71
|
*/
|
|
63
|
-
generateText(prompt: string, maxTokens: number, temperature: number
|
|
72
|
+
generateText(prompt: string, maxTokens: number, temperature: number): Promise<string>;
|
|
64
73
|
/**
|
|
65
74
|
* Generate text with detailed response information
|
|
66
75
|
*/
|
|
67
|
-
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>>;
|
|
68
91
|
}
|
|
69
92
|
|
|
70
93
|
type InferenceClientType = 'anthropic' | 'ollama';
|
|
@@ -82,13 +105,18 @@ declare class AnthropicInferenceClient implements InferenceClient {
|
|
|
82
105
|
readonly modelId: string;
|
|
83
106
|
private client;
|
|
84
107
|
private logger?;
|
|
85
|
-
private
|
|
108
|
+
private discoveryPromise?;
|
|
86
109
|
constructor(apiKey: string, model: string, baseURL?: string, logger?: Logger);
|
|
87
110
|
limits(): Promise<InferenceLimits>;
|
|
88
|
-
private
|
|
111
|
+
private discover;
|
|
112
|
+
private discoverModel;
|
|
89
113
|
private requestMessage;
|
|
90
|
-
generateText(prompt: string, maxTokens: number, temperature: number
|
|
91
|
-
generateTextWithMetadata(prompt: string, maxTokens: number, temperature: number
|
|
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;
|
|
92
120
|
}
|
|
93
121
|
|
|
94
122
|
declare class OllamaInferenceClient implements InferenceClient {
|
|
@@ -100,8 +128,10 @@ declare class OllamaInferenceClient implements InferenceClient {
|
|
|
100
128
|
constructor(model: string, baseURL?: string, logger?: Logger);
|
|
101
129
|
limits(): Promise<InferenceLimits>;
|
|
102
130
|
private discoverLimits;
|
|
103
|
-
generateText(prompt: string, maxTokens: number, temperature: number
|
|
104
|
-
generateTextWithMetadata(prompt: string, maxTokens: number, temperature: number
|
|
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;
|
|
105
135
|
}
|
|
106
136
|
|
|
107
137
|
declare class MockInferenceClient implements InferenceClient {
|
|
@@ -115,15 +145,24 @@ declare class MockInferenceClient implements InferenceClient {
|
|
|
115
145
|
prompt: string;
|
|
116
146
|
maxTokens: number;
|
|
117
147
|
temperature: number;
|
|
118
|
-
|
|
148
|
+
elementSchema?: ElementSchema;
|
|
119
149
|
}>;
|
|
120
150
|
constructor(responses?: string[], stopReasons?: string[], limits?: InferenceLimits);
|
|
121
151
|
limits(): Promise<InferenceLimits>;
|
|
122
|
-
generateText(prompt: string, maxTokens: number, temperature: number
|
|
123
|
-
generateTextWithMetadata(prompt: string, maxTokens: number, temperature: number
|
|
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;
|
|
124
163
|
reset(): void;
|
|
125
164
|
setResponses(responses: string[], stopReasons?: string[]): void;
|
|
126
165
|
}
|
|
127
166
|
|
|
128
167
|
export { AnthropicInferenceClient, MockInferenceClient, OllamaInferenceClient, createInferenceClient };
|
|
129
|
-
export type { InferenceClient, InferenceClientConfig, InferenceClientType, InferenceLimits,
|
|
168
|
+
export type { ElementSchema, InferenceClient, InferenceClientConfig, InferenceClientType, InferenceLimits, InferenceResponse, StructuredResponse };
|
package/dist/index.js
CHANGED
|
@@ -1,26 +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
5
|
var NONSTREAMING_MAX_OUTPUT_TOKENS = Math.floor(128e3 / 6);
|
|
5
|
-
var JSON_ARRAY_TOOL = {
|
|
6
|
-
name: "emit_json_array",
|
|
7
|
-
description: 'Return your entire answer by calling this tool. Put the JSON array of results under the "items" property, and emit no prose.',
|
|
8
|
-
input_schema: {
|
|
9
|
-
type: "object",
|
|
10
|
-
properties: {
|
|
11
|
-
// Element shape is unconstrained here — the prompt carries the per-element
|
|
12
|
-
// schema; the tool only enforces that the top-level result is an array.
|
|
13
|
-
items: { type: "array", items: {} }
|
|
14
|
-
},
|
|
15
|
-
required: ["items"]
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
6
|
var AnthropicInferenceClient = class {
|
|
19
7
|
type = "anthropic";
|
|
20
8
|
modelId;
|
|
21
9
|
client;
|
|
22
10
|
logger;
|
|
23
|
-
|
|
11
|
+
discoveryPromise;
|
|
24
12
|
constructor(apiKey, model, baseURL, logger) {
|
|
25
13
|
this.client = new Anthropic({
|
|
26
14
|
apiKey,
|
|
@@ -30,15 +18,18 @@ var AnthropicInferenceClient = class {
|
|
|
30
18
|
this.logger = logger;
|
|
31
19
|
}
|
|
32
20
|
limits() {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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;
|
|
36
27
|
throw err;
|
|
37
28
|
});
|
|
38
29
|
}
|
|
39
|
-
return this.
|
|
30
|
+
return this.discoveryPromise;
|
|
40
31
|
}
|
|
41
|
-
async
|
|
32
|
+
async discoverModel() {
|
|
42
33
|
const info = await this.client.models.retrieve(this.modelId).catch((err) => {
|
|
43
34
|
throw new Error(
|
|
44
35
|
`Failed to discover model limits for '${this.modelId}' from the Models API`,
|
|
@@ -48,7 +39,12 @@ var AnthropicInferenceClient = class {
|
|
|
48
39
|
if (info.max_input_tokens == null || info.max_tokens == null) {
|
|
49
40
|
throw new Error(`Models API reports no context/output ceilings for '${this.modelId}'`);
|
|
50
41
|
}
|
|
51
|
-
|
|
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
|
+
};
|
|
52
48
|
}
|
|
53
49
|
requestMessage(params) {
|
|
54
50
|
if (params.max_tokens > NONSTREAMING_MAX_OUTPUT_TOKENS) {
|
|
@@ -56,85 +52,116 @@ var AnthropicInferenceClient = class {
|
|
|
56
52
|
}
|
|
57
53
|
return this.client.messages.create(params);
|
|
58
54
|
}
|
|
59
|
-
async generateText(prompt, maxTokens, temperature
|
|
60
|
-
const response = await this.generateTextWithMetadata(prompt, maxTokens, temperature
|
|
55
|
+
async generateText(prompt, maxTokens, temperature) {
|
|
56
|
+
const response = await this.generateTextWithMetadata(prompt, maxTokens, temperature);
|
|
61
57
|
return response.text;
|
|
62
58
|
}
|
|
63
|
-
async generateTextWithMetadata(prompt, maxTokens, temperature
|
|
64
|
-
const jsonMode = options?.format === "json";
|
|
59
|
+
async generateTextWithMetadata(prompt, maxTokens, temperature) {
|
|
65
60
|
this.logger?.debug("Generating text with inference client", {
|
|
66
61
|
model: this.modelId,
|
|
67
62
|
promptLength: prompt.length,
|
|
68
63
|
maxTokens,
|
|
69
|
-
temperature
|
|
70
|
-
format: options?.format
|
|
64
|
+
temperature
|
|
71
65
|
});
|
|
72
66
|
const params = {
|
|
73
67
|
model: this.modelId,
|
|
74
68
|
max_tokens: maxTokens,
|
|
75
69
|
temperature,
|
|
76
|
-
messages: [{ role: "user", content: prompt }]
|
|
77
|
-
// JSON mode → force the structured-output tool. No prefill assistant
|
|
78
|
-
// turn: the constraint now lives in the tool call, not in free text.
|
|
79
|
-
...jsonMode ? { tools: [JSON_ARRAY_TOOL], tool_choice: { type: "tool", name: JSON_ARRAY_TOOL.name } } : {}
|
|
70
|
+
messages: [{ role: "user", content: prompt }]
|
|
80
71
|
};
|
|
81
72
|
const start = performance.now();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
provider: this.type,
|
|
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", {
|
|
88
78
|
model: this.modelId,
|
|
89
|
-
|
|
90
|
-
outcome: "error"
|
|
79
|
+
contentTypes: response.content.map((c) => c.type)
|
|
91
80
|
});
|
|
92
|
-
throw
|
|
81
|
+
throw new Error("No text content in inference response");
|
|
93
82
|
}
|
|
94
|
-
|
|
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", {
|
|
95
93
|
model: this.modelId,
|
|
96
|
-
|
|
94
|
+
textLength: text.length,
|
|
97
95
|
stopReason: response.stop_reason
|
|
98
96
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
outputTokens: response.usage?.output_tokens
|
|
130
|
-
});
|
|
131
|
-
this.logger?.error("No text content in inference response", {
|
|
132
|
-
model: this.modelId,
|
|
133
|
-
contentTypes: response.content.map((c) => c.type)
|
|
134
|
-
});
|
|
135
|
-
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
|
+
}
|
|
136
127
|
}
|
|
137
|
-
|
|
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
|
+
);
|
|
138
165
|
}
|
|
139
166
|
recordInferenceUsage({
|
|
140
167
|
provider: this.type,
|
|
@@ -144,20 +171,44 @@ var AnthropicInferenceClient = class {
|
|
|
144
171
|
inputTokens: response.usage?.input_tokens,
|
|
145
172
|
outputTokens: response.usage?.output_tokens
|
|
146
173
|
});
|
|
147
|
-
this.logger?.info("
|
|
174
|
+
this.logger?.info("Structured generation completed", {
|
|
148
175
|
model: this.modelId,
|
|
149
|
-
|
|
176
|
+
items: parsed.length,
|
|
150
177
|
stopReason: response.stop_reason
|
|
151
178
|
});
|
|
152
179
|
return {
|
|
153
|
-
|
|
180
|
+
items: parsed,
|
|
154
181
|
stopReason: response.stop_reason || "unknown"
|
|
155
182
|
};
|
|
156
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
|
+
}
|
|
157
208
|
};
|
|
158
209
|
|
|
159
210
|
// src/implementations/ollama.ts
|
|
160
|
-
import { estimateTokens, isNumber, isObject } from "@semiont/core";
|
|
211
|
+
import { estimateTokens, isNumber, isObject as isObject2 } from "@semiont/core";
|
|
161
212
|
import { recordInferenceUsage as recordInferenceUsage2 } from "@semiont/observability";
|
|
162
213
|
var NUM_CTX_ESTIMATE_SLACK = 0.2;
|
|
163
214
|
var NUM_CTX_TEMPLATE_ALLOWANCE = 64;
|
|
@@ -193,24 +244,55 @@ var OllamaInferenceClient = class {
|
|
|
193
244
|
);
|
|
194
245
|
}
|
|
195
246
|
const data = await res.json();
|
|
196
|
-
const modelInfo =
|
|
247
|
+
const modelInfo = isObject2(data) && isObject2(data["model_info"]) ? data["model_info"] : void 0;
|
|
197
248
|
const contextTokens = readContextLength(modelInfo);
|
|
198
249
|
if (contextTokens === void 0) {
|
|
199
250
|
throw new Error(`/api/show reports no context length for '${this.modelId}'`);
|
|
200
251
|
}
|
|
201
252
|
return { contextTokens, maxOutputTokens: contextTokens };
|
|
202
253
|
}
|
|
203
|
-
async generateText(prompt, maxTokens, temperature
|
|
204
|
-
const response = await this.generateTextWithMetadata(prompt, maxTokens, temperature
|
|
254
|
+
async generateText(prompt, maxTokens, temperature) {
|
|
255
|
+
const response = await this.generateTextWithMetadata(prompt, maxTokens, temperature);
|
|
205
256
|
return response.text;
|
|
206
257
|
}
|
|
207
|
-
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) {
|
|
208
290
|
this.logger?.debug("Generating text with Ollama", {
|
|
209
291
|
model: this.modelId,
|
|
210
292
|
promptLength: prompt.length,
|
|
211
293
|
maxTokens,
|
|
212
294
|
temperature,
|
|
213
|
-
|
|
295
|
+
structured: elementSchema !== void 0
|
|
214
296
|
});
|
|
215
297
|
const limits = await this.limits();
|
|
216
298
|
const promptTokens = estimateTokens(prompt);
|
|
@@ -236,8 +318,8 @@ var OllamaInferenceClient = class {
|
|
|
236
318
|
temperature
|
|
237
319
|
}
|
|
238
320
|
};
|
|
239
|
-
if (
|
|
240
|
-
body["format"] = { type: "array", items:
|
|
321
|
+
if (elementSchema !== void 0) {
|
|
322
|
+
body["format"] = { type: "array", items: elementSchema };
|
|
241
323
|
}
|
|
242
324
|
let res;
|
|
243
325
|
try {
|
|
@@ -375,12 +457,41 @@ var MockInferenceClient = class {
|
|
|
375
457
|
async limits() {
|
|
376
458
|
return this.injectedLimits;
|
|
377
459
|
}
|
|
378
|
-
async generateText(prompt, maxTokens, temperature
|
|
379
|
-
const response = await this.generateTextWithMetadata(prompt, maxTokens, temperature
|
|
460
|
+
async generateText(prompt, maxTokens, temperature) {
|
|
461
|
+
const response = await this.generateTextWithMetadata(prompt, maxTokens, temperature);
|
|
380
462
|
return response.text;
|
|
381
463
|
}
|
|
382
|
-
async generateTextWithMetadata(prompt, maxTokens, temperature
|
|
383
|
-
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() {
|
|
384
495
|
const text = this.responses[this.responseIndex];
|
|
385
496
|
const stopReason = this.stopReasons[this.responseIndex] || "end_turn";
|
|
386
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, InferenceLimits, InferenceOptions, InferenceResponse } 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// 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 private limitsPromise?: Promise<InferenceLimits>;\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 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 // The Models API publishes the actual ceilings per model — no\n // hand-maintained table to go stale when a new model ships.\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 return { contextTokens: info.max_input_tokens, maxOutputTokens: info.max_tokens };\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, 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 params: Anthropic.MessageCreateParamsNonStreaming = {\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\n const start = performance.now();\n let response: Anthropic.Message;\n try {\n response = 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 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 { estimateTokens, isNumber, isObject } from '@semiont/core';\nimport type { Logger } from '@semiont/core';\nimport { recordInferenceUsage } from '@semiont/observability';\nimport { InferenceClient, InferenceLimits, InferenceOptions, InferenceResponse } 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, 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 // 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 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 num_ctx: numCtx,\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\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 { InferenceClient, InferenceLimits, InferenceOptions, InferenceResponse } 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; options?: InferenceOptions }> = [];\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, 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":";AAEA,OAAO,eAAe;AACtB,SAAS,gBAA6B;AACtC,SAAS,4BAA4B;AASrC,IAAM,iCAAiC,KAAK,MAAM,QAAU,CAAC;AAyBtD,IAAM,2BAAN,MAA0D;AAAA,EACtD,OAAO;AAAA,EACP;AAAA,EACD;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,QAAgB,OAAe,SAAkB,QAAiB;AAC5E,SAAK,SAAS,IAAI,UAAU;AAAA,MAC1B;AAAA,MACA,SAAS,WAAW;AAAA,IACtB,CAAC;AACD,SAAK,UAAU;AACf,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,SAAmC;AACjC,WAAO,KAAK,SAAS,EAAE,KAAK,OAAK,EAAE,MAAM;AAAA,EAC3C;AAAA,EAEQ,WAAoC;AAC1C,QAAI,CAAC,KAAK,kBAAkB;AAC1B,WAAK,mBAAmB,KAAK,cAAc,EAAE,MAAM,CAAC,QAAiB;AAGnE,aAAK,mBAAmB;AACxB,cAAM;AAAA,MACR,CAAC;AAAA,IACH;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,gBAAyC;AAMrD,UAAM,OAAO,MAAM,KAAK,OAAO,OAAO,SAAS,KAAK,OAAO,EAAE,MAAM,CAAC,QAAiB;AACnF,YAAM,IAAI;AAAA,QACR,wCAAwC,KAAK,OAAO;AAAA,QACpD,EAAE,OAAO,IAAI;AAAA,MACf;AAAA,IACF,CAAC;AACD,QAAI,KAAK,oBAAoB,QAAQ,KAAK,cAAc,MAAM;AAC5D,YAAM,IAAI,MAAM,sDAAsD,KAAK,OAAO,GAAG;AAAA,IACvF;AAIA,UAAM,MAAe;AACrB,UAAM,6BACJ,SAAS,GAAG,KACZ,SAAS,IAAI,cAAc,CAAC,KAC5B,SAAS,IAAI,cAAc,EAAE,oBAAoB,CAAC,KAClD,IAAI,cAAc,EAAE,oBAAoB,EAAE,WAAW,MAAM;AAC7D,WAAO;AAAA,MACL,QAAQ,EAAE,eAAe,KAAK,kBAAkB,iBAAiB,KAAK,WAAW;AAAA,MACjF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,eAAe,QAA+E;AACpG,QAAI,OAAO,aAAa,gCAAgC;AACtD,aAAO,KAAK,OAAO,SAAS,OAAO,MAAM,EAAE,aAAa;AAAA,IAC1D;AACA,WAAO,KAAK,OAAO,SAAS,OAAO,MAAM;AAAA,EAC3C;AAAA,EAEA,MAAM,aAAa,QAAgB,WAAmB,aAAsC;AAC1F,UAAM,WAAW,MAAM,KAAK,yBAAyB,QAAQ,WAAW,WAAW;AACnF,WAAO,SAAS;AAAA,EAClB;AAAA,EAEA,MAAM,yBAAyB,QAAgB,WAAmB,aAAiD;AACjH,SAAK,QAAQ,MAAM,yCAAyC;AAAA,MAC1D,OAAO,KAAK;AAAA,MACZ,cAAc,OAAO;AAAA,MACrB;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,SAAoD;AAAA,MACxD,OAAO,KAAK;AAAA,MACZ,YAAY;AAAA,MACZ;AAAA,MACA,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAC;AAAA,IAC9C;AAEA,UAAM,QAAQ,YAAY,IAAI;AAC9B,UAAM,WAAW,MAAM,KAAK,gBAAgB,QAAQ,KAAK;AAEzD,UAAM,cAAc,SAAS,QAAQ,KAAK,OAAK,EAAE,SAAS,MAAM;AAChE,QAAI,CAAC,eAAe,YAAY,SAAS,QAAQ;AAC/C,WAAK,YAAY,OAAO,QAAQ;AAChC,WAAK,QAAQ,MAAM,yCAAyC;AAAA,QAC1D,OAAO,KAAK;AAAA,QACZ,cAAc,SAAS,QAAQ,IAAI,OAAK,EAAE,IAAI;AAAA,MAChD,CAAC;AACD,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AACA,UAAM,OAAO,YAAY;AAEzB,yBAAqB;AAAA,MACnB,UAAU,KAAK;AAAA,MACf,OAAO,KAAK;AAAA,MACZ,YAAY,YAAY,IAAI,IAAI;AAAA,MAChC,SAAS;AAAA,MACT,aAAa,SAAS,OAAO;AAAA,MAC7B,cAAc,SAAS,OAAO;AAAA,IAChC,CAAC;AAED,SAAK,QAAQ,KAAK,6BAA6B;AAAA,MAC7C,OAAO,KAAK;AAAA,MACZ,YAAY,KAAK;AAAA,MACjB,YAAY,SAAS;AAAA,IACvB,CAAC;AAED,WAAO;AAAA,MACL;AAAA,MACA,YAAY,SAAS,eAAe;AAAA,IACtC;AAAA,EACF;AAAA,EAEA,MAAM,mBACJ,QACA,WACA,aACA,eACgC;AAOhC,UAAM,YAAY,MAAM,KAAK,SAAS;AACtC,QAAI,CAAC,UAAU,4BAA4B;AACzC,YAAM,IAAI;AAAA,QACR,UAAU,KAAK,OAAO;AAAA,MAMxB;AAAA,IACF;AAEA,SAAK,QAAQ,MAAM,sDAAsD;AAAA,MACvE,OAAO,KAAK;AAAA,MACZ,cAAc,OAAO;AAAA,MACrB;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,SAAoD;AAAA,MACxD,OAAO,KAAK;AAAA,MACZ,YAAY;AAAA,MACZ;AAAA,MACA,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAC;AAAA;AAAA;AAAA,MAG5C,eAAe;AAAA,QACb,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,QAAQ,EAAE,MAAM,SAAS,OAAO,cAAc;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,QAAQ,YAAY,IAAI;AAC9B,UAAM,WAAW,MAAM,KAAK,gBAAgB,QAAQ,KAAK;AAEzD,UAAM,cAAc,SAAS,QAAQ,KAAK,OAAK,EAAE,SAAS,MAAM;AAChE,QAAI,CAAC,eAAe,YAAY,SAAS,QAAQ;AAC/C,WAAK,YAAY,OAAO,QAAQ;AAChC,WAAK,QAAQ,MAAM,oDAAoD;AAAA,QACrE,OAAO,KAAK;AAAA,QACZ,cAAc,SAAS,QAAQ,IAAI,OAAK,EAAE,IAAI;AAAA,MAChD,CAAC;AACD,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACpE;AAOA,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,YAAY,IAAI;AAAA,IACtC,SAAS,KAAK;AACZ,WAAK,YAAY,OAAO,QAAQ;AAChC,WAAK,QAAQ,MAAM,yCAAyC;AAAA,QAC1D,OAAO,KAAK;AAAA,QACZ,YAAY,YAAY,KAAK;AAAA,QAC7B,YAAY,SAAS;AAAA,MACvB,CAAC;AACD,YAAM,IAAI;AAAA,QACR,mFAAmF,SAAS,WAAW;AAAA,QACvG,EAAE,OAAO,IAAI;AAAA,MACf;AAAA,IACF;AACA,QAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,WAAK,YAAY,OAAO,QAAQ;AAChC,WAAK,QAAQ,MAAM,yCAAyC;AAAA,QAC1D,OAAO,KAAK;AAAA,QACZ,YAAY,OAAO;AAAA,QACnB,YAAY,SAAS;AAAA,MACvB,CAAC;AACD,YAAM,IAAI;AAAA,QACR,oDAAoD,OAAO,MAAM,gCAAgC,SAAS,WAAW;AAAA,MACvH;AAAA,IACF;AAEA,yBAAqB;AAAA,MACnB,UAAU,KAAK;AAAA,MACf,OAAO,KAAK;AAAA,MACZ,YAAY,YAAY,IAAI,IAAI;AAAA,MAChC,SAAS;AAAA,MACT,aAAa,SAAS,OAAO;AAAA,MAC7B,cAAc,SAAS,OAAO;AAAA,IAChC,CAAC;AAED,SAAK,QAAQ,KAAK,mCAAmC;AAAA,MACnD,OAAO,KAAK;AAAA,MACZ,OAAO,OAAO;AAAA,MACd,YAAY,SAAS;AAAA,IACvB,CAAC;AAED,WAAO;AAAA,MACL,OAAO;AAAA,MACP,YAAY,SAAS,eAAe;AAAA,IACtC;AAAA,EACF;AAAA;AAAA,EAGA,MAAc,gBAAgB,QAAmD,OAA2C;AAC1H,QAAI;AACF,aAAO,MAAM,KAAK,eAAe,MAAM;AAAA,IACzC,SAAS,KAAK;AACZ,2BAAqB;AAAA,QACnB,UAAU,KAAK;AAAA,QACf,OAAO,KAAK;AAAA,QACZ,YAAY,YAAY,IAAI,IAAI;AAAA,QAChC,SAAS;AAAA,MACX,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEQ,YAAY,OAAe,UAAmC;AACpE,yBAAqB;AAAA,MACnB,UAAU,KAAK;AAAA,MACf,OAAO,KAAK;AAAA,MACZ,YAAY,YAAY,IAAI,IAAI;AAAA,MAChC,SAAS;AAAA,MACT,aAAa,SAAS,OAAO;AAAA,MAC7B,cAAc,SAAS,OAAO;AAAA,IAChC,CAAC;AAAA,EACH;AACF;;;ACxSA,SAAS,gBAAgB,UAAU,YAAAA,iBAAgB;AAEnD,SAAS,wBAAAC,6BAA4B;AASrC,IAAM,yBAAyB;AAC/B,IAAM,6BAA6B;AAY5B,IAAM,wBAAN,MAAuD;AAAA,EACnD,OAAO;AAAA,EACP;AAAA,EACD;AAAA,EACA;AAAA,EAEA;AAAA,EAER,YAAY,OAAe,SAAkB,QAAiB;AAC5D,SAAK,WAAW,WAAW,0BAA0B,QAAQ,QAAQ,EAAE;AACvE,SAAK,UAAU;AACf,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,SAAmC;AACjC,QAAI,CAAC,KAAK,eAAe;AACvB,WAAK,gBAAgB,KAAK,eAAe,EAAE,MAAM,CAAC,QAAiB;AAGjE,aAAK,gBAAgB;AACrB,cAAM;AAAA,MACR,CAAC;AAAA,IACH;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,iBAA2C;AACvD,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,aAAa;AAAA,MAClD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,KAAK,QAAQ,CAAC;AAAA,IAC9C,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI;AAAA,QACR,uDAAuD,IAAI,MAAM,SAAS,KAAK,OAAO;AAAA,MACxF;AAAA,IACF;AACA,UAAM,OAAgB,MAAM,IAAI,KAAK;AACrC,UAAM,YAAYD,UAAS,IAAI,KAAKA,UAAS,KAAK,YAAY,CAAC,IAAI,KAAK,YAAY,IAAI;AACxF,UAAM,gBAAgB,kBAAkB,SAAS;AACjD,QAAI,kBAAkB,QAAW;AAC/B,YAAM,IAAI,MAAM,4CAA4C,KAAK,OAAO,GAAG;AAAA,IAC7E;AAIA,WAAO,EAAE,eAAe,iBAAiB,cAAc;AAAA,EACzD;AAAA,EAEA,MAAM,aAAa,QAAgB,WAAmB,aAAsC;AAC1F,UAAM,WAAW,MAAM,KAAK,yBAAyB,QAAQ,WAAW,WAAW;AACnF,WAAO,SAAS;AAAA,EAClB;AAAA,EAEA,MAAM,yBAAyB,QAAgB,WAAmB,aAAiD;AACjH,WAAO,KAAK,SAAS,QAAQ,WAAW,aAAa,MAAS;AAAA,EAChE;AAAA,EAEA,MAAM,mBACJ,QACA,WACA,aACA,eACgC;AAOhC,UAAM,WAAW,MAAM,KAAK,SAAS,QAAQ,WAAW,aAAa,aAAa;AAElF,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,SAAS,IAAI;AAAA,IACnC,SAAS,KAAK;AACZ,WAAK,QAAQ,MAAM,yCAAyC;AAAA,QAC1D,OAAO,KAAK;AAAA,QACZ,YAAY,SAAS,KAAK;AAAA,QAC1B,YAAY,SAAS;AAAA,MACvB,CAAC;AACD,YAAM,IAAI;AAAA,QACR,mFAAmF,SAAS,UAAU;AAAA,QACtG,EAAE,OAAO,IAAI;AAAA,MACf;AAAA,IACF;AACA,QAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,WAAK,QAAQ,MAAM,yCAAyC;AAAA,QAC1D,OAAO,KAAK;AAAA,QACZ,YAAY,OAAO;AAAA,QACnB,YAAY,SAAS;AAAA,MACvB,CAAC;AACD,YAAM,IAAI;AAAA,QACR,oDAAoD,OAAO,MAAM,gCAAgC,SAAS,UAAU;AAAA,MACtH;AAAA,IACF;AAEA,WAAO,EAAE,OAAO,QAAe,YAAY,SAAS,WAAW;AAAA,EACjE;AAAA,EAEA,MAAc,SACZ,QACA,WACA,aACA,eAC4B;AAC5B,SAAK,QAAQ,MAAM,+BAA+B;AAAA,MAChD,OAAO,KAAK;AAAA,MACZ,cAAc,OAAO;AAAA,MACrB;AAAA,MACA;AAAA,MACA,YAAY,kBAAkB;AAAA,IAChC,CAAC;AAMD,UAAM,SAAS,MAAM,KAAK,OAAO;AACjC,UAAM,eAAe,eAAe,MAAM;AAC1C,QAAI,eAAe,YAAY,OAAO,eAAe;AACnD,YAAM,IAAI;AAAA,QACR,YAAY,YAAY,6BAA6B,SAAS,iBAC1D,KAAK,OAAO,qBAAqB,OAAO,aAAa;AAAA,MAC3D;AAAA,IACF;AACA,UAAM,SAAS,KAAK;AAAA,MAClB,OAAO;AAAA,MACP,eAAe,YACX,KAAK,KAAK,eAAe,sBAAsB,IAAI;AAAA,IACzD;AAEA,UAAM,MAAM,GAAG,KAAK,OAAO;AAC3B,UAAM,QAAQ,YAAY,IAAI;AAS9B,UAAM,OAAgC;AAAA,MACpC,OAAO,KAAK;AAAA,MACZ;AAAA,MACA,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,SAAS;AAAA,QACP,aAAa;AAAA,QACb,SAAS;AAAA,QACT;AAAA,MACF;AAAA,IACF;AACA,QAAI,kBAAkB,QAAW;AAC/B,WAAK,QAAQ,IAAI,EAAE,MAAM,SAAS,OAAO,cAAc;AAAA,IACzD;AAEA,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,MAAM,KAAK;AAAA,QACrB,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,MAAAC,sBAAqB;AAAA,QACnB,UAAU,KAAK;AAAA,QACf,OAAO,KAAK;AAAA,QACZ,YAAY,YAAY,IAAI,IAAI;AAAA,QAChC,SAAS;AAAA,MACX,CAAC;AACD,YAAM;AAAA,IACR;AAEA,QAAI,CAAC,IAAI,IAAI;AACX,MAAAA,sBAAqB;AAAA,QACnB,UAAU,KAAK;AAAA,QACf,OAAO,KAAK;AAAA,QACZ,YAAY,YAAY,IAAI,IAAI;AAAA,QAChC,SAAS;AAAA,MACX,CAAC;AACD,YAAMC,QAAO,MAAM,IAAI,KAAK;AAC5B,WAAK,QAAQ,MAAM,oBAAoB;AAAA,QACrC,OAAO,KAAK;AAAA,QACZ,QAAQ,IAAI;AAAA,QACZ,MAAAA;AAAA,MACF,CAAC;AACD,YAAM,IAAI,MAAM,qBAAqB,IAAI,MAAM,MAAMA,KAAI,EAAE;AAAA,IAC7D;AAEA,UAAM,OAAO,MAAM,IAAI,KAAK;AAE5B,QAAI,CAAC,KAAK,UAAU;AAClB,MAAAD,sBAAqB;AAAA,QACnB,UAAU,KAAK;AAAA,QACf,OAAO,KAAK;AAAA,QACZ,YAAY,YAAY,IAAI,IAAI;AAAA,QAChC,SAAS;AAAA,QACT,aAAa,KAAK;AAAA,QAClB,cAAc,KAAK;AAAA,MACrB,CAAC;AACD,WAAK,QAAQ,MAAM,8BAA8B,EAAE,OAAO,KAAK,QAAQ,CAAC;AACxE,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AAEA,IAAAA,sBAAqB;AAAA,MACnB,UAAU,KAAK;AAAA,MACf,OAAO,KAAK;AAAA,MACZ,YAAY,YAAY,IAAI,IAAI;AAAA,MAChC,SAAS;AAAA,MACT,aAAa,KAAK;AAAA,MAClB,cAAc,KAAK;AAAA,IACrB,CAAC;AAED,UAAM,aAAa,cAAc,KAAK,WAAW;AAEjD,SAAK,QAAQ,KAAK,6BAA6B;AAAA,MAC7C,OAAO,KAAK;AAAA,MACZ,YAAY,KAAK,SAAS;AAAA,MAC1B;AAAA,IACF,CAAC;AAED,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX;AAAA,IACF;AAAA,EACF;AACF;AAQA,SAAS,kBAAkB,WAAoE;AAC7F,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,OAAO,UAAU,sBAAsB;AAC7C,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAM,SAAS,UAAU,GAAG,IAAI,iBAAiB;AACjD,QAAI,SAAS,MAAM,KAAK,SAAS,EAAG,QAAO;AAAA,EAC7C;AACA,QAAM,cAAc,OAAO,KAAK,SAAS,EAAE,KAAK,OAAK,EAAE,SAAS,iBAAiB,CAAC;AAClF,MAAI,gBAAgB,QAAW;AAC7B,UAAM,WAAW,UAAU,WAAW;AACtC,QAAI,SAAS,QAAQ,KAAK,WAAW,EAAG,QAAO;AAAA,EACjD;AACA,SAAO;AACT;AAEA,SAAS,cAAc,YAAwC;AAC7D,UAAQ,YAAY;AAAA,IAClB,KAAK;AAAQ,aAAO;AAAA,IACpB,KAAK;AAAU,aAAO;AAAA,IACtB;AAAS,aAAO,cAAc;AAAA,EAChC;AACF;;;AC1QO,SAAS,sBAAsB,QAA+B,QAAkC;AACrG,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK,aAAa;AAChB,UAAI,CAAC,OAAO,UAAU,OAAO,OAAO,KAAK,MAAM,IAAI;AACjD,cAAM,IAAI,MAAM,mDAAmD;AAAA,MACrE;AACA,aAAO,IAAI;AAAA,QACT,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO,YAAY,OAAO;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AAAA,IAEA,KAAK,UAAU;AACb,aAAO,IAAI;AAAA,QACT,OAAO;AAAA,QACP,OAAO,YAAY,OAAO;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AAAA,IAEA;AACE,YAAM,IAAI,MAAM,sCAAsC,OAAO,IAAI,EAAE;AAAA,EACvE;AACF;;;ACpCA,IAAM,kBAAmC;AAAA,EACvC,eAAe;AAAA,EACf,iBAAiB;AACnB;AAEO,IAAM,sBAAN,MAAqD;AAAA,EACjD,OAAO;AAAA,EACP,UAAU;AAAA,EACX,YAAsB,CAAC;AAAA,EACvB,gBAAwB;AAAA,EACxB,cAAwB,CAAC;AAAA,EACzB;AAAA,EACD,QAA0G,CAAC;AAAA,EAElH,YAAY,YAAsB,CAAC,eAAe,GAAG,aAAwB,QAA0B;AACrG,SAAK,YAAY;AACjB,SAAK,cAAc,eAAe,UAAU,IAAI,MAAM,UAAU;AAChE,SAAK,iBAAiB,UAAU;AAAA,EAClC;AAAA,EAEA,MAAM,SAAmC;AACvC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,aAAa,QAAgB,WAAmB,aAAsC;AAC1F,UAAM,WAAW,MAAM,KAAK,yBAAyB,QAAQ,WAAW,WAAW;AACnF,WAAO,SAAS;AAAA,EAClB;AAAA,EAEA,MAAM,yBAAyB,QAAgB,WAAmB,aAAiD;AACjH,SAAK,MAAM,KAAK,EAAE,QAAQ,WAAW,YAAY,CAAC;AAClD,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,mBACJ,QACA,WACA,aACA,eACgC;AAChC,SAAK,MAAM,KAAK,EAAE,QAAQ,WAAW,aAAa,cAAc,CAAC;AACjE,UAAM,EAAE,MAAM,WAAW,IAAI,KAAK,aAAa;AAE/C,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,IAAI;AAAA,IAC1B,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,mFAAmF,UAAU;AAAA,QAC7F,EAAE,OAAO,IAAI;AAAA,MACf;AAAA,IACF;AACA,QAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,YAAM,IAAI;AAAA,QACR,oDAAoD,OAAO,MAAM,gCAAgC,UAAU;AAAA,MAC7G;AAAA,IACF;AACA,WAAO,EAAE,OAAO,QAAe,WAAW;AAAA,EAC5C;AAAA,EAEQ,eAAkC;AACxC,UAAM,OAAO,KAAK,UAAU,KAAK,aAAa;AAC9C,UAAM,aAAa,KAAK,YAAY,KAAK,aAAa,KAAK;AAE3D,QAAI,KAAK,gBAAgB,KAAK,UAAU,SAAS,GAAG;AAClD,WAAK;AAAA,IACP;AAEA,WAAO,EAAE,MAAM,WAAW;AAAA,EAC5B;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,QAAQ,CAAC;AACd,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,aAAa,WAAqB,aAA8B;AAC9D,SAAK,YAAY;AACjB,SAAK,cAAc,eAAe,UAAU,IAAI,MAAM,UAAU;AAChE,SAAK,gBAAgB;AAAA,EACvB;AACF;","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
|
},
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sdk": "^0.115.0",
|
|
31
|
-
"@semiont/core": "0.5.
|
|
32
|
-
"@semiont/observability": "0.5.
|
|
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",
|