@jaex/dstsx 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2089 -0
- package/dist/index.cjs +2883 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1597 -0
- package/dist/index.d.ts +1597 -0
- package/dist/index.js +2781 -0
- package/dist/index.js.map +1 -0
- package/package.json +113 -0
package/README.md
ADDED
|
@@ -0,0 +1,2089 @@
|
|
|
1
|
+
# DSTsx
|
|
2
|
+
|
|
3
|
+
> A TypeScript-first port of [DSPy](https://github.com/stanfordnlp/dspy) — Declarative Self-improving Language Programs.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/dstsx)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](#)
|
|
8
|
+
|
|
9
|
+
DSTsx lets you build **typed, composable LM pipelines** in TypeScript and then **optimize** their prompts and few-shot examples automatically—no manual prompt engineering required.
|
|
10
|
+
|
|
11
|
+
> **LLM / AI assistant?** See [`llms.txt`](./llms.txt) for a machine-readable index, or browse the focused [`docs/`](./docs/) directory for topic-specific reference files.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Table of Contents
|
|
16
|
+
|
|
17
|
+
1. [Installation](#installation)
|
|
18
|
+
2. [Quick Start](#quick-start)
|
|
19
|
+
3. [Core Concepts](#core-concepts)
|
|
20
|
+
4. [API Reference](#api-reference)
|
|
21
|
+
- [Signatures](#signatures)
|
|
22
|
+
- [Primitives — Example, Prediction, Trace](#primitives--example-prediction-trace)
|
|
23
|
+
- [Language Model Adapters](#language-model-adapters)
|
|
24
|
+
- [Settings & Context](#settings--context)
|
|
25
|
+
- [Modules](#modules)
|
|
26
|
+
- [Retrievers](#retrievers)
|
|
27
|
+
- [Optimizers](#optimizers)
|
|
28
|
+
- [Evaluation](#evaluation)
|
|
29
|
+
- [Assertions & Suggestions](#assertions--suggestions)
|
|
30
|
+
5. [V2 APIs](#v2-apis)
|
|
31
|
+
- [TypedPredictor & TypedChainOfThought](#typedpredictor--typedchainofthought)
|
|
32
|
+
- [Parallel Module](#parallel-module)
|
|
33
|
+
- [Refine Module](#refine-module)
|
|
34
|
+
- [majority() Helper](#majority-helper)
|
|
35
|
+
- [BootstrapFewShotWithOptuna](#bootstrapfewshotwithoptuna)
|
|
36
|
+
- [Disk-Persistent LM Cache](#disk-persistent-lm-cache)
|
|
37
|
+
- [MCP Integration](#mcp-integration)
|
|
38
|
+
- [LM Streaming](#lm-streaming)
|
|
39
|
+
- [NativeReAct](#nativereact)
|
|
40
|
+
- [Image — Multi-modal Support](#image--multi-modal-support)
|
|
41
|
+
- [BootstrapFinetune](#bootstrapfinetune)
|
|
42
|
+
- [GRPO Optimizer](#grpo-optimizer)
|
|
43
|
+
- [SIMBA Optimizer](#simba-optimizer)
|
|
44
|
+
- [AvatarOptimizer](#avataroptimizer)
|
|
45
|
+
- [Experiment Tracking](#experiment-tracking)
|
|
46
|
+
- [Worker-Thread ProgramOfThought](#worker-thread-programofthought)
|
|
47
|
+
6. [End-to-End Examples](#end-to-end-examples)
|
|
48
|
+
7. [V2 Roadmap](#v2-roadmap)
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install dstsx
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Install provider SDK peer dependencies only for the adapters you use:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# OpenAI
|
|
62
|
+
npm install openai
|
|
63
|
+
|
|
64
|
+
# Anthropic
|
|
65
|
+
npm install @anthropic-ai/sdk
|
|
66
|
+
|
|
67
|
+
# Cohere
|
|
68
|
+
npm install cohere-ai
|
|
69
|
+
|
|
70
|
+
# Google Generative AI
|
|
71
|
+
npm install @google/generative-ai
|
|
72
|
+
|
|
73
|
+
# Vector store retrievers (pick what you need)
|
|
74
|
+
npm install @pinecone-database/pinecone
|
|
75
|
+
npm install chromadb
|
|
76
|
+
npm install @qdrant/js-client-rest
|
|
77
|
+
npm install weaviate-client
|
|
78
|
+
|
|
79
|
+
# MCP (Model Context Protocol) integration — optional
|
|
80
|
+
npm install @modelcontextprotocol/sdk
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Quick Start
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { settings, OpenAI, Predict } from "dstsx";
|
|
89
|
+
|
|
90
|
+
// 1. Configure the global LM
|
|
91
|
+
settings.configure({ lm: new OpenAI({ model: "gpt-4o" }) });
|
|
92
|
+
|
|
93
|
+
// 2. Define a module
|
|
94
|
+
const qa = new Predict("question -> answer");
|
|
95
|
+
|
|
96
|
+
// 3. Run it
|
|
97
|
+
const result = await qa.forward({ question: "What is the capital of France?" });
|
|
98
|
+
console.log(result.get("answer")); // "Paris"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Core Concepts
|
|
104
|
+
|
|
105
|
+
| Concept | Description |
|
|
106
|
+
|---|---|
|
|
107
|
+
| **Signature** | Typed interface (inputs → outputs) for one LM call |
|
|
108
|
+
| **Example** | Immutable key-value training record |
|
|
109
|
+
| **Prediction** | Module output extending Example with `completions` |
|
|
110
|
+
| **Trace** | Record of a single LM call (inputs, outputs, usage, latency) |
|
|
111
|
+
| **LM** | Abstract language model adapter |
|
|
112
|
+
| **Module** | Composable unit containing one or more LM calls |
|
|
113
|
+
| **Retriever** | Abstract vector-store backend |
|
|
114
|
+
| **Optimizer** | Automatically tunes demos/instructions of a Module |
|
|
115
|
+
| **Metric** | Scoring function for Evaluate and Optimizers |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## API Reference
|
|
120
|
+
|
|
121
|
+
### Signatures
|
|
122
|
+
|
|
123
|
+
Signatures declare the **typed input/output interface** for a single LM call.
|
|
124
|
+
|
|
125
|
+
#### `Signature.from(shorthand, instructions?)`
|
|
126
|
+
|
|
127
|
+
Parse a shorthand string. Use `->` to separate inputs from outputs; suffix `?` for optional fields.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { Signature } from "dstsx";
|
|
131
|
+
|
|
132
|
+
// Simple shorthand
|
|
133
|
+
const sig = Signature.from("question -> answer");
|
|
134
|
+
|
|
135
|
+
// Multiple fields, optional field, instructions
|
|
136
|
+
const sig2 = Signature.from(
|
|
137
|
+
"context, question -> answer, confidence?",
|
|
138
|
+
"Answer based only on the provided context."
|
|
139
|
+
);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### `new Signature(meta)`
|
|
143
|
+
|
|
144
|
+
Construct a signature explicitly with full field metadata.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { Signature, InputField, OutputField } from "dstsx";
|
|
148
|
+
|
|
149
|
+
const sig = new Signature({
|
|
150
|
+
inputs: new Map([
|
|
151
|
+
["context", InputField({ description: "Background passages" })],
|
|
152
|
+
["question", InputField({ description: "The question to answer" })],
|
|
153
|
+
]),
|
|
154
|
+
outputs: new Map([
|
|
155
|
+
["answer", OutputField({ description: "Concise factual answer", type: "string" })],
|
|
156
|
+
]),
|
|
157
|
+
instructions: "Answer using only the context provided.",
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### `InputField(meta?)` / `OutputField(meta?)`
|
|
162
|
+
|
|
163
|
+
Builder helpers that return a `FieldMeta` descriptor.
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import { InputField, OutputField } from "dstsx";
|
|
167
|
+
|
|
168
|
+
const field = InputField({
|
|
169
|
+
description: "The user's question",
|
|
170
|
+
prefix: "Q:", // optional prompt prefix
|
|
171
|
+
format: "markdown", // optional format hint
|
|
172
|
+
optional: true, // field may be absent
|
|
173
|
+
type: "string", // "string" | "number" | "boolean" | "string[]" | "object"
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### `FieldMeta` interface
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
interface FieldMeta {
|
|
181
|
+
description?: string;
|
|
182
|
+
prefix?: string;
|
|
183
|
+
format?: string;
|
|
184
|
+
optional?: boolean;
|
|
185
|
+
type?: "string" | "number" | "boolean" | "string[]" | "object";
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Signature mutation helpers (return new Signature, never mutate)
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
const base = Signature.from("question -> answer");
|
|
193
|
+
|
|
194
|
+
// Add or override fields / instructions
|
|
195
|
+
const extended = base.with({ instructions: "Be concise." });
|
|
196
|
+
|
|
197
|
+
// Add a single input field
|
|
198
|
+
const withCtx = base.withInput("context", { description: "Background text" });
|
|
199
|
+
|
|
200
|
+
// Add a single output field
|
|
201
|
+
const withConf = base.withOutput("confidence", { type: "number" });
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### Serialization
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
const json = sig.toJSON(); // → plain object
|
|
208
|
+
const sig2 = Signature.fromJSON(json); // → Signature
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
### Primitives — Example, Prediction, Trace
|
|
214
|
+
|
|
215
|
+
#### `Example`
|
|
216
|
+
|
|
217
|
+
Immutable record of named values used as training data or module inputs.
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import { Example } from "dstsx";
|
|
221
|
+
|
|
222
|
+
const ex = new Example({ question: "What is 2+2?", answer: "4" });
|
|
223
|
+
|
|
224
|
+
ex.get("question"); // "What is 2+2?"
|
|
225
|
+
ex.toDict(); // { question: "What is 2+2?", answer: "4" }
|
|
226
|
+
ex.toJSON(); // same as toDict()
|
|
227
|
+
|
|
228
|
+
// Non-mutating copy with overrides
|
|
229
|
+
const updated = ex.with({ answer: "four" });
|
|
230
|
+
|
|
231
|
+
// Filtered views
|
|
232
|
+
const inputOnly = ex.inputs(["question"]); // Example { question: ... }
|
|
233
|
+
const labelOnly = ex.labels(["question"]); // Example { answer: ... }
|
|
234
|
+
|
|
235
|
+
// Deserialize
|
|
236
|
+
const ex2 = Example.fromDict({ question: "Hi", answer: "Hello" });
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### `Prediction`
|
|
240
|
+
|
|
241
|
+
Extends `Example` and adds `completions` for multi-output calls (`n > 1`).
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { Prediction } from "dstsx";
|
|
245
|
+
|
|
246
|
+
const pred = new Prediction(
|
|
247
|
+
{ answer: "42" },
|
|
248
|
+
[{ answer: "42" }, { answer: "forty-two" }], // completions
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
pred.get("answer"); // "42"
|
|
252
|
+
pred.getTyped<string>("answer"); // "42" — typed cast
|
|
253
|
+
pred.completions; // ReadonlyArray of all candidates
|
|
254
|
+
pred.toJSON(); // { answer: "42", completions: [...] }
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
#### `Trace`
|
|
258
|
+
|
|
259
|
+
Recorded per LM call. See [History](#history) for how to read traces.
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
interface Trace {
|
|
263
|
+
signature: Signature;
|
|
264
|
+
inputs: Record<string, unknown>;
|
|
265
|
+
outputs: Record<string, unknown>;
|
|
266
|
+
usage: { promptTokens: number; completionTokens: number; totalTokens: number } | null;
|
|
267
|
+
latencyMs: number;
|
|
268
|
+
timestamp: string; // ISO-8601
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
### Language Model Adapters
|
|
275
|
+
|
|
276
|
+
All adapters extend `LM` and share the same `call()` interface.
|
|
277
|
+
|
|
278
|
+
#### Abstract `LM`
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
abstract class LM {
|
|
282
|
+
readonly model: string;
|
|
283
|
+
|
|
284
|
+
// Call the LM with a string prompt or chat messages
|
|
285
|
+
async call(prompt: string | Message[], config?: LMCallConfig): Promise<LMResponse>;
|
|
286
|
+
|
|
287
|
+
// Counters (non-cached calls only)
|
|
288
|
+
get requestCount(): number;
|
|
289
|
+
get tokenUsage(): { promptTokens: number; completionTokens: number; totalTokens: number };
|
|
290
|
+
|
|
291
|
+
// Clear in-memory LRU response cache
|
|
292
|
+
clearCache(): void;
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
##### `LMCallConfig`
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
interface LMCallConfig {
|
|
300
|
+
model?: string; // override model per call
|
|
301
|
+
temperature?: number; // 0–2
|
|
302
|
+
maxTokens?: number;
|
|
303
|
+
stop?: string[];
|
|
304
|
+
n?: number; // number of completions (default 1)
|
|
305
|
+
cacheKey?: string; // manual cache key override
|
|
306
|
+
extra?: Record<string, unknown>; // provider pass-through
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
##### `LMResponse`
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
interface LMResponse {
|
|
314
|
+
text: string; // primary completion
|
|
315
|
+
texts: string[]; // all completions when n > 1
|
|
316
|
+
usage: TokenUsage | null;
|
|
317
|
+
raw: unknown; // raw provider response
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
#### `OpenAI`
|
|
324
|
+
|
|
325
|
+
Requires: `npm install openai`
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
import { OpenAI } from "dstsx";
|
|
329
|
+
|
|
330
|
+
const lm = new OpenAI({
|
|
331
|
+
model: "gpt-4o", // default "gpt-4o"
|
|
332
|
+
apiKey: "sk-...", // or set OPENAI_API_KEY env var
|
|
333
|
+
baseURL: "https://...", // optional custom endpoint
|
|
334
|
+
maxRetries: 3,
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
#### `Anthropic`
|
|
341
|
+
|
|
342
|
+
Requires: `npm install @anthropic-ai/sdk`
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
import { Anthropic } from "dstsx";
|
|
346
|
+
|
|
347
|
+
const lm = new Anthropic({
|
|
348
|
+
model: "claude-3-5-sonnet-20241022", // default
|
|
349
|
+
apiKey: "...", // or ANTHROPIC_API_KEY
|
|
350
|
+
maxRetries: 3,
|
|
351
|
+
});
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
#### `Cohere`
|
|
357
|
+
|
|
358
|
+
Requires: `npm install cohere-ai`
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
import { Cohere } from "dstsx";
|
|
362
|
+
|
|
363
|
+
const lm = new Cohere({
|
|
364
|
+
model: "command-r-plus", // default
|
|
365
|
+
apiKey: "...", // or COHERE_API_KEY
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
#### `GoogleAI`
|
|
372
|
+
|
|
373
|
+
Requires: `npm install @google/generative-ai`
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
import { GoogleAI } from "dstsx";
|
|
377
|
+
|
|
378
|
+
const lm = new GoogleAI({
|
|
379
|
+
model: "gemini-1.5-pro", // default
|
|
380
|
+
apiKey: "...", // or GOOGLE_API_KEY
|
|
381
|
+
});
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
#### `Ollama`
|
|
387
|
+
|
|
388
|
+
No extra package required — communicates with the Ollama REST API.
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
import { Ollama } from "dstsx";
|
|
392
|
+
|
|
393
|
+
const lm = new Ollama({
|
|
394
|
+
model: "llama3", // default
|
|
395
|
+
baseURL: "http://localhost:11434", // default
|
|
396
|
+
});
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
#### `LMStudio`
|
|
402
|
+
|
|
403
|
+
No extra package required — uses LM Studio's OpenAI-compatible `/v1` endpoint.
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
import { LMStudio } from "dstsx";
|
|
407
|
+
|
|
408
|
+
const lm = new LMStudio({
|
|
409
|
+
model: "local-model",
|
|
410
|
+
baseURL: "http://localhost:1234/v1", // default
|
|
411
|
+
});
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
#### `HuggingFace`
|
|
417
|
+
|
|
418
|
+
No extra package required — calls the HuggingFace Inference API directly.
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
import { HuggingFace } from "dstsx";
|
|
422
|
+
|
|
423
|
+
const lm = new HuggingFace({
|
|
424
|
+
model: "mistralai/Mistral-7B-Instruct-v0.3", // default
|
|
425
|
+
apiKey: "...", // or HF_API_KEY
|
|
426
|
+
endpointURL: "https://my-dedicated-endpoint.com", // optional
|
|
427
|
+
});
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
#### `MockLM`
|
|
433
|
+
|
|
434
|
+
Deterministic lookup-map LM for unit testing.
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
import { MockLM } from "dstsx";
|
|
438
|
+
|
|
439
|
+
const lm = new MockLM(
|
|
440
|
+
{
|
|
441
|
+
// prompt substring → response
|
|
442
|
+
"What is 2+2?": "answer: 4",
|
|
443
|
+
},
|
|
444
|
+
"answer: unknown", // fallback when no match (optional; throws if omitted)
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
// Add responses at runtime
|
|
448
|
+
lm.addResponse("What is the capital of France?", "answer: Paris");
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
### Settings & Context
|
|
454
|
+
|
|
455
|
+
The `settings` singleton controls global defaults.
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
import { settings } from "dstsx";
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
#### `settings.configure(options)`
|
|
462
|
+
|
|
463
|
+
Merge options into global settings (existing keys are overwritten; omitted keys unchanged).
|
|
464
|
+
|
|
465
|
+
```ts
|
|
466
|
+
settings.configure({
|
|
467
|
+
lm: new OpenAI({ model: "gpt-4o" }),
|
|
468
|
+
rm: new ColBERTv2("http://localhost:8893"),
|
|
469
|
+
lmConfig: { temperature: 0.0, maxTokens: 512 },
|
|
470
|
+
logLevel: "warn", // "silent" | "error" | "warn" | "info" | "debug"
|
|
471
|
+
cacheDir: "./.dstsx", // for future disk caching
|
|
472
|
+
});
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
#### `settings.reset()`
|
|
476
|
+
|
|
477
|
+
Reset all global settings to defaults.
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
settings.reset();
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
#### `settings.inspect()`
|
|
484
|
+
|
|
485
|
+
Return a deep-frozen snapshot of currently effective settings.
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
const snap = settings.inspect();
|
|
489
|
+
console.log(snap.lm?.model);
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
#### `settings.context(overrides, fn)` — Per-request isolation
|
|
493
|
+
|
|
494
|
+
Run `fn` inside an `AsyncLocalStorage` scope. Concurrent requests each get their own isolated settings and never interfere.
|
|
495
|
+
|
|
496
|
+
```ts
|
|
497
|
+
// In an Express/Fastify handler:
|
|
498
|
+
app.post("/answer", async (req, res) => {
|
|
499
|
+
const answer = await settings.context(
|
|
500
|
+
{ lm: new OpenAI({ model: "gpt-4o-mini" }) },
|
|
501
|
+
() => program.forward({ question: req.body.question }),
|
|
502
|
+
);
|
|
503
|
+
res.json(answer.toJSON());
|
|
504
|
+
});
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
#### `SettingsOptions` type
|
|
508
|
+
|
|
509
|
+
```ts
|
|
510
|
+
interface SettingsOptions {
|
|
511
|
+
lm?: LM;
|
|
512
|
+
rm?: Retriever;
|
|
513
|
+
lmConfig?: LMCallConfig;
|
|
514
|
+
logLevel?: "silent" | "error" | "warn" | "info" | "debug";
|
|
515
|
+
cacheDir?: string;
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
### Modules
|
|
522
|
+
|
|
523
|
+
All modules extend `Module` and expose a `forward()` method.
|
|
524
|
+
|
|
525
|
+
#### Abstract `Module`
|
|
526
|
+
|
|
527
|
+
```ts
|
|
528
|
+
abstract class Module {
|
|
529
|
+
abstract forward(...args: unknown[]): Promise<Prediction>;
|
|
530
|
+
|
|
531
|
+
// Recursively list all Predict sub-modules
|
|
532
|
+
namedPredictors(): Array<[string, Module]>;
|
|
533
|
+
|
|
534
|
+
// Serialize/deserialize learnable parameters (demos, instructions)
|
|
535
|
+
dump(): Record<string, unknown>;
|
|
536
|
+
load(state: Record<string, unknown>): void;
|
|
537
|
+
|
|
538
|
+
// Deep-clone the module (used by optimizers)
|
|
539
|
+
clone(): this;
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
**Saving and loading a compiled program:**
|
|
544
|
+
|
|
545
|
+
```ts
|
|
546
|
+
import { writeFileSync, readFileSync } from "fs";
|
|
547
|
+
|
|
548
|
+
// Save
|
|
549
|
+
const state = program.dump();
|
|
550
|
+
writeFileSync("program.json", JSON.stringify(state, null, 2));
|
|
551
|
+
|
|
552
|
+
// Load into a fresh instance
|
|
553
|
+
const fresh = new MyProgram();
|
|
554
|
+
fresh.load(JSON.parse(readFileSync("program.json", "utf8")));
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
#### `Predict`
|
|
560
|
+
|
|
561
|
+
The fundamental module — formats a prompt and calls the LM.
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
import { Predict } from "dstsx";
|
|
565
|
+
|
|
566
|
+
const qa = new Predict("question -> answer");
|
|
567
|
+
|
|
568
|
+
// Or with a full Signature object
|
|
569
|
+
const qa2 = new Predict(Signature.from("context, question -> answer"));
|
|
570
|
+
|
|
571
|
+
// Forward
|
|
572
|
+
const result = await qa.forward({ question: "What is 9 × 7?" });
|
|
573
|
+
console.log(result.get("answer")); // "63"
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
**Learnable parameters** (mutated by optimizers):
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
qa.demos // Example[] — few-shot demonstrations
|
|
580
|
+
qa.instructions // string | undefined — system instruction override
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
**Multiple completions (`n > 1`):**
|
|
584
|
+
|
|
585
|
+
```ts
|
|
586
|
+
settings.configure({ lmConfig: { n: 5 } });
|
|
587
|
+
const result = await qa.forward({ question: "Name a color." });
|
|
588
|
+
console.log(result.completions); // 5 candidate answers
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
#### `ChainOfThought`
|
|
594
|
+
|
|
595
|
+
Prepends a hidden `rationale` output field so the LM reasons step-by-step before answering.
|
|
596
|
+
|
|
597
|
+
```ts
|
|
598
|
+
import { ChainOfThought } from "dstsx";
|
|
599
|
+
|
|
600
|
+
const cot = new ChainOfThought("question -> answer");
|
|
601
|
+
|
|
602
|
+
// Optional: customize the rationale description
|
|
603
|
+
const cot2 = new ChainOfThought("question -> answer", {
|
|
604
|
+
rationaleDescription: "Think aloud to solve the problem",
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
const result = await cot.forward({ question: "If Alice has 3 apples and gets 5 more, how many does she have?" });
|
|
608
|
+
console.log(result.get("answer")); // "8" — rationale is internal
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
613
|
+
#### `ChainOfThoughtWithHint`
|
|
614
|
+
|
|
615
|
+
Extends `ChainOfThought` with an optional `hint` input.
|
|
616
|
+
|
|
617
|
+
```ts
|
|
618
|
+
import { ChainOfThoughtWithHint } from "dstsx";
|
|
619
|
+
|
|
620
|
+
const cot = new ChainOfThoughtWithHint("question -> answer");
|
|
621
|
+
|
|
622
|
+
const result = await cot.forward({
|
|
623
|
+
question: "What is the chemical formula for water?",
|
|
624
|
+
hint: "It involves hydrogen and oxygen.",
|
|
625
|
+
});
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
#### `MultiChainComparison`
|
|
631
|
+
|
|
632
|
+
Runs a signature `M` times and picks the best completion via a final aggregation call.
|
|
633
|
+
|
|
634
|
+
```ts
|
|
635
|
+
import { MultiChainComparison } from "dstsx";
|
|
636
|
+
|
|
637
|
+
const mcc = new MultiChainComparison("question -> answer", /* M= */ 3);
|
|
638
|
+
const result = await mcc.forward({ question: "What is 7 × 8?" });
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
#### `ReAct`
|
|
644
|
+
|
|
645
|
+
Reasoning + Acting loop (Yao et al., 2022). Alternates Thought → Action → Observation until the LM emits `Finish[answer]` or `maxIter` is reached.
|
|
646
|
+
|
|
647
|
+
```ts
|
|
648
|
+
import { ReAct, type Tool } from "dstsx";
|
|
649
|
+
|
|
650
|
+
const searchTool: Tool = {
|
|
651
|
+
name: "search",
|
|
652
|
+
description: "Search the web for current information",
|
|
653
|
+
fn: async (query: string) => {
|
|
654
|
+
// Your real implementation here
|
|
655
|
+
return `Search results for: ${query}`;
|
|
656
|
+
},
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
const agent = new ReAct(
|
|
660
|
+
"question -> answer",
|
|
661
|
+
[searchTool],
|
|
662
|
+
/* maxIter= */ 5,
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
const result = await agent.forward({ question: "Who won the 2024 US election?" });
|
|
666
|
+
console.log(result.get("answer"));
|
|
667
|
+
console.log(result.get("trajectory")); // full thought/action/observation log
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
**`Tool` interface:**
|
|
671
|
+
|
|
672
|
+
```ts
|
|
673
|
+
interface Tool {
|
|
674
|
+
name: string;
|
|
675
|
+
description: string;
|
|
676
|
+
fn: (args: string) => Promise<string>;
|
|
677
|
+
}
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
|
|
682
|
+
#### `ProgramOfThought`
|
|
683
|
+
|
|
684
|
+
Generates JavaScript code, executes it in a `new Function()` context, self-corrects on errors, and returns the result.
|
|
685
|
+
|
|
686
|
+
> ⚠️ **Security**: Code runs in the current process. Do NOT use with untrusted user inputs in production without an additional sandboxing layer (e.g. a Worker thread).
|
|
687
|
+
|
|
688
|
+
```ts
|
|
689
|
+
import { ProgramOfThought } from "dstsx";
|
|
690
|
+
|
|
691
|
+
const pot = new ProgramOfThought(
|
|
692
|
+
"question -> answer",
|
|
693
|
+
/* maxAttempts= */ 3,
|
|
694
|
+
/* timeoutMs= */ 5_000,
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
const result = await pot.forward({ question: "What is the 10th Fibonacci number?" });
|
|
698
|
+
console.log(result.get("answer")); // "55"
|
|
699
|
+
console.log(result.get("code")); // the generated JS code
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
---
|
|
703
|
+
|
|
704
|
+
#### `Retrieve`
|
|
705
|
+
|
|
706
|
+
Calls the globally configured retriever and returns `passages`.
|
|
707
|
+
|
|
708
|
+
```ts
|
|
709
|
+
import { Retrieve, ColBERTv2, settings } from "dstsx";
|
|
710
|
+
|
|
711
|
+
settings.configure({ rm: new ColBERTv2("http://localhost:8893") });
|
|
712
|
+
|
|
713
|
+
const retrieve = new Retrieve(/* k= */ 3);
|
|
714
|
+
const result = await retrieve.forward("What is DSPy?");
|
|
715
|
+
|
|
716
|
+
const passages: string[] = result.get("passages") as string[];
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
---
|
|
720
|
+
|
|
721
|
+
#### `Retry`
|
|
722
|
+
|
|
723
|
+
Wraps any module and retries on `AssertionError` (thrown by `Assert()`), feeding the error message back as `feedback`.
|
|
724
|
+
|
|
725
|
+
```ts
|
|
726
|
+
import { Retry, Assert, Predict } from "dstsx";
|
|
727
|
+
|
|
728
|
+
const qa = new Predict("question, feedback? -> answer");
|
|
729
|
+
|
|
730
|
+
const retrying = new Retry(qa, /* maxAttempts= */ 3);
|
|
731
|
+
|
|
732
|
+
const result = await retrying.forward({
|
|
733
|
+
question: "Give a one-word answer: what color is the sky?",
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
// Use Assert inside a custom module to trigger retry
|
|
737
|
+
class CheckedQA extends Module {
|
|
738
|
+
predict = new Predict("question -> answer");
|
|
739
|
+
async forward(inputs: { question: string }) {
|
|
740
|
+
const result = await this.predict.forward(inputs);
|
|
741
|
+
Assert(
|
|
742
|
+
String(result.get("answer")).length > 0,
|
|
743
|
+
"Answer must not be empty"
|
|
744
|
+
);
|
|
745
|
+
return result;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
---
|
|
751
|
+
|
|
752
|
+
#### `BestOfN`
|
|
753
|
+
|
|
754
|
+
Runs `N` copies of a module in parallel and selects the best via `reduceFunc` (defaults to first result).
|
|
755
|
+
|
|
756
|
+
```ts
|
|
757
|
+
import { BestOfN, Predict } from "dstsx";
|
|
758
|
+
|
|
759
|
+
const qa = new Predict("question -> answer");
|
|
760
|
+
const best = new BestOfN(qa, /* N= */ 5, (predictions) => {
|
|
761
|
+
// Pick the longest answer as a proxy for quality
|
|
762
|
+
return predictions.reduce((a, b) =>
|
|
763
|
+
String(b.get("answer")).length > String(a.get("answer")).length ? b : a
|
|
764
|
+
);
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
const result = await best.forward({ question: "Explain gravity." });
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
---
|
|
771
|
+
|
|
772
|
+
#### `Ensemble`
|
|
773
|
+
|
|
774
|
+
Combines multiple pre-built module instances via a reduce function.
|
|
775
|
+
|
|
776
|
+
```ts
|
|
777
|
+
import { Ensemble, ChainOfThought } from "dstsx";
|
|
778
|
+
|
|
779
|
+
const m1 = new ChainOfThought("question -> answer");
|
|
780
|
+
const m2 = new ChainOfThought("question -> answer");
|
|
781
|
+
|
|
782
|
+
const ensemble = new Ensemble(
|
|
783
|
+
[m1, m2],
|
|
784
|
+
(predictions) => predictions[0]!, // custom vote/merge logic
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
const result = await ensemble.forward({ question: "Is TypeScript better than JavaScript?" });
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
---
|
|
791
|
+
|
|
792
|
+
### Retrievers
|
|
793
|
+
|
|
794
|
+
All retrievers extend `Retriever` and implement `retrieve(query, k)`.
|
|
795
|
+
|
|
796
|
+
#### Abstract `Retriever`
|
|
797
|
+
|
|
798
|
+
```ts
|
|
799
|
+
abstract class Retriever {
|
|
800
|
+
abstract retrieve(query: string, k: number): Promise<string[]>;
|
|
801
|
+
}
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
#### `ColBERTv2`
|
|
805
|
+
|
|
806
|
+
```ts
|
|
807
|
+
import { ColBERTv2 } from "dstsx";
|
|
808
|
+
|
|
809
|
+
const rm = new ColBERTv2("http://localhost:8893");
|
|
810
|
+
// or with options:
|
|
811
|
+
const rm2 = new ColBERTv2({ url: "http://localhost:8893" });
|
|
812
|
+
|
|
813
|
+
const passages = await rm.retrieve("What is photosynthesis?", 3);
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
#### `PineconeRM`
|
|
817
|
+
|
|
818
|
+
Requires: `npm install @pinecone-database/pinecone`
|
|
819
|
+
|
|
820
|
+
```ts
|
|
821
|
+
import { PineconeRM } from "dstsx";
|
|
822
|
+
|
|
823
|
+
const rm = new PineconeRM({
|
|
824
|
+
indexName: "my-index",
|
|
825
|
+
apiKey: "...", // or PINECONE_API_KEY
|
|
826
|
+
namespace: "default",
|
|
827
|
+
embeddingFn: async (text) => myEmbedModel.embed(text),
|
|
828
|
+
});
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
#### `ChromadbRM`
|
|
832
|
+
|
|
833
|
+
Requires: `npm install chromadb`
|
|
834
|
+
|
|
835
|
+
```ts
|
|
836
|
+
import { ChromadbRM } from "dstsx";
|
|
837
|
+
|
|
838
|
+
const rm = new ChromadbRM({
|
|
839
|
+
collectionName: "my-collection",
|
|
840
|
+
url: "http://localhost:8000", // default
|
|
841
|
+
embeddingFn: async (texts) => myEmbedModel.embedBatch(texts),
|
|
842
|
+
});
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
#### `QdrantRM`
|
|
846
|
+
|
|
847
|
+
Requires: `npm install @qdrant/js-client-rest`
|
|
848
|
+
|
|
849
|
+
```ts
|
|
850
|
+
import { QdrantRM } from "dstsx";
|
|
851
|
+
|
|
852
|
+
const rm = new QdrantRM({
|
|
853
|
+
url: "http://localhost:6333",
|
|
854
|
+
collectionName: "my-collection",
|
|
855
|
+
embeddingFn: async (text) => myEmbedModel.embed(text),
|
|
856
|
+
});
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
#### `WeaviateRM`
|
|
860
|
+
|
|
861
|
+
Requires: `npm install weaviate-client`
|
|
862
|
+
|
|
863
|
+
```ts
|
|
864
|
+
import { WeaviateRM } from "dstsx";
|
|
865
|
+
|
|
866
|
+
const rm = new WeaviateRM({
|
|
867
|
+
url: "http://localhost:8080",
|
|
868
|
+
className: "Document",
|
|
869
|
+
textField: "content",
|
|
870
|
+
embeddingFn: async (text) => myEmbedModel.embed(text),
|
|
871
|
+
});
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
#### `FaissRM`
|
|
875
|
+
|
|
876
|
+
Requires: `npm install faiss-node` (optional peer dep)
|
|
877
|
+
|
|
878
|
+
```ts
|
|
879
|
+
import { FaissRM } from "dstsx";
|
|
880
|
+
|
|
881
|
+
const rm = new FaissRM({
|
|
882
|
+
passages: ["passage 1", "passage 2"],
|
|
883
|
+
embeddingFn: async (text) => myEmbedModel.embed(text),
|
|
884
|
+
});
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
#### `YouRM`
|
|
888
|
+
|
|
889
|
+
```ts
|
|
890
|
+
import { YouRM } from "dstsx";
|
|
891
|
+
|
|
892
|
+
const rm = new YouRM({
|
|
893
|
+
apiKey: "...", // or YDC_API_KEY
|
|
894
|
+
k: 3,
|
|
895
|
+
});
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
#### `MockRetriever`
|
|
899
|
+
|
|
900
|
+
For unit testing.
|
|
901
|
+
|
|
902
|
+
```ts
|
|
903
|
+
import { MockRetriever } from "dstsx";
|
|
904
|
+
|
|
905
|
+
const rm = new MockRetriever([
|
|
906
|
+
"The capital of France is Paris.",
|
|
907
|
+
"Paris is located in northern France.",
|
|
908
|
+
"France is a country in Western Europe.",
|
|
909
|
+
]);
|
|
910
|
+
|
|
911
|
+
const passages = await rm.retrieve("capital of France", 2);
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
---
|
|
915
|
+
|
|
916
|
+
### Optimizers
|
|
917
|
+
|
|
918
|
+
Optimizers automatically tune a module's few-shot `demos` and/or `instructions`. All optimizers implement:
|
|
919
|
+
|
|
920
|
+
```ts
|
|
921
|
+
abstract class Optimizer {
|
|
922
|
+
abstract compile(
|
|
923
|
+
student: Module,
|
|
924
|
+
trainset: Example[],
|
|
925
|
+
metric: Metric,
|
|
926
|
+
): Promise<Module>;
|
|
927
|
+
}
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
- The returned module is a **new clone**; the original `student` is never mutated.
|
|
931
|
+
- Pass a `valset` where supported to evaluate on held-out data.
|
|
932
|
+
|
|
933
|
+
---
|
|
934
|
+
|
|
935
|
+
#### `LabeledFewShot`
|
|
936
|
+
|
|
937
|
+
Directly assigns labeled examples as `demos` on every `Predict` sub-module (no LM calls).
|
|
938
|
+
|
|
939
|
+
```ts
|
|
940
|
+
import { LabeledFewShot } from "dstsx";
|
|
941
|
+
|
|
942
|
+
const optimizer = new LabeledFewShot(/* k= */ 16);
|
|
943
|
+
const optimized = await optimizer.compile(program, trainset, metric);
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
---
|
|
947
|
+
|
|
948
|
+
#### `BootstrapFewShot`
|
|
949
|
+
|
|
950
|
+
Runs the student (or an optional `teacher`) on `trainset`, collects successful traces via `metric`, and uses them as `demos`.
|
|
951
|
+
|
|
952
|
+
```ts
|
|
953
|
+
import { BootstrapFewShot } from "dstsx";
|
|
954
|
+
|
|
955
|
+
const optimizer = new BootstrapFewShot({
|
|
956
|
+
maxBootstrappedDemos: 4, // max demos collected per predictor
|
|
957
|
+
maxLabeledDemos: 16, // max labeled fallback demos
|
|
958
|
+
teacher: expertProgram, // optional; defaults to student
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
const optimized = await optimizer.compile(program, trainset, exactMatch("answer"));
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
---
|
|
965
|
+
|
|
966
|
+
#### `BootstrapFewShotWithRandomSearch`
|
|
967
|
+
|
|
968
|
+
Extends `BootstrapFewShot` — tries `numCandidatePrograms` random demo subsets and selects the best by validation score.
|
|
969
|
+
|
|
970
|
+
```ts
|
|
971
|
+
import { BootstrapFewShotWithRandomSearch } from "dstsx";
|
|
972
|
+
|
|
973
|
+
const optimizer = new BootstrapFewShotWithRandomSearch({
|
|
974
|
+
maxBootstrappedDemos: 4,
|
|
975
|
+
numCandidatePrograms: 8, // number of random subsets to evaluate
|
|
976
|
+
valset: valExamples, // optional held-out set
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
const optimized = await optimizer.compile(program, trainset, metric);
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
---
|
|
983
|
+
|
|
984
|
+
#### `COPRO` (Collaborative Prompt Optimizer)
|
|
985
|
+
|
|
986
|
+
Uses the LM to propose instruction improvements for each `Predict` sub-module and selects the best combination by metric score.
|
|
987
|
+
|
|
988
|
+
```ts
|
|
989
|
+
import { COPRO } from "dstsx";
|
|
990
|
+
|
|
991
|
+
const optimizer = new COPRO({
|
|
992
|
+
breadth: 5, // instruction candidates per predictor per round
|
|
993
|
+
depth: 3, // refinement rounds
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
const optimized = await optimizer.compile(program, trainset, metric);
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
---
|
|
1000
|
+
|
|
1001
|
+
#### `MIPRO` (Multi-stage Instruction Prompt Optimizer)
|
|
1002
|
+
|
|
1003
|
+
Combines COPRO-style instruction proposals with `BootstrapFewShotWithRandomSearch` to jointly optimize instructions _and_ demonstrations.
|
|
1004
|
+
|
|
1005
|
+
```ts
|
|
1006
|
+
import { MIPRO } from "dstsx";
|
|
1007
|
+
|
|
1008
|
+
const optimizer = new MIPRO({
|
|
1009
|
+
numCandidates: 5, // instruction candidates per predictor
|
|
1010
|
+
initTemperature: 0.9,
|
|
1011
|
+
numCandidatePrograms: 8, // demo subsets to evaluate
|
|
1012
|
+
verbose: true,
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
const optimized = await optimizer.compile(program, trainset, metric);
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
---
|
|
1019
|
+
|
|
1020
|
+
#### `KNNFewShot`
|
|
1021
|
+
|
|
1022
|
+
Selects demonstrations **at inference time** using k-nearest-neighbour search over the training set embeddings (dynamic few-shot).
|
|
1023
|
+
|
|
1024
|
+
```ts
|
|
1025
|
+
import { KNNFewShot } from "dstsx";
|
|
1026
|
+
|
|
1027
|
+
const optimizer = new KNNFewShot({
|
|
1028
|
+
k: 3,
|
|
1029
|
+
embeddingFn: async (text) => myEmbedModel.embed(text), // required
|
|
1030
|
+
keyField: "question", // which field to embed (default: all fields joined)
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
const optimized = await optimizer.compile(program, trainset, metric);
|
|
1034
|
+
// At inference time, each forward() call auto-selects the 3 most similar demos
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
---
|
|
1038
|
+
|
|
1039
|
+
#### `EnsembleOptimizer`
|
|
1040
|
+
|
|
1041
|
+
Wraps a program with an optional reduce function. Primarily useful for building multi-program ensembles.
|
|
1042
|
+
|
|
1043
|
+
```ts
|
|
1044
|
+
import { EnsembleOptimizer } from "dstsx";
|
|
1045
|
+
|
|
1046
|
+
const optimizer = new EnsembleOptimizer({
|
|
1047
|
+
reduceFunc: (predictions) => predictions[0]!,
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
const wrapped = await optimizer.compile(program, trainset, metric);
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
---
|
|
1054
|
+
|
|
1055
|
+
### Evaluation
|
|
1056
|
+
|
|
1057
|
+
#### `evaluate(program, examples, metric, options?)`
|
|
1058
|
+
|
|
1059
|
+
Run `program` on every example and aggregate scores.
|
|
1060
|
+
|
|
1061
|
+
```ts
|
|
1062
|
+
import { evaluate, exactMatch } from "dstsx";
|
|
1063
|
+
|
|
1064
|
+
const result = await evaluate(
|
|
1065
|
+
program,
|
|
1066
|
+
devset,
|
|
1067
|
+
exactMatch("answer"), // built-in metric
|
|
1068
|
+
{
|
|
1069
|
+
numThreads: 4, // parallel evaluation (default: 1)
|
|
1070
|
+
displayProgress: true, // log progress to console
|
|
1071
|
+
},
|
|
1072
|
+
);
|
|
1073
|
+
|
|
1074
|
+
console.log(`Score: ${(result.score * 100).toFixed(1)}%`);
|
|
1075
|
+
console.log(`Passed: ${result.numPassed}/${result.total}`);
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
##### `EvaluationResult`
|
|
1079
|
+
|
|
1080
|
+
```ts
|
|
1081
|
+
interface EvaluationResult {
|
|
1082
|
+
score: number; // average metric score (0–1)
|
|
1083
|
+
numPassed: number;
|
|
1084
|
+
total: number;
|
|
1085
|
+
results: ExampleResult[]; // per-example breakdown
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
interface ExampleResult {
|
|
1089
|
+
example: Example;
|
|
1090
|
+
prediction: Prediction;
|
|
1091
|
+
score: number;
|
|
1092
|
+
passed: boolean;
|
|
1093
|
+
}
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
---
|
|
1097
|
+
|
|
1098
|
+
#### Built-in Metrics
|
|
1099
|
+
|
|
1100
|
+
All metrics implement `Metric`:
|
|
1101
|
+
|
|
1102
|
+
```ts
|
|
1103
|
+
type Metric = (
|
|
1104
|
+
example: Example,
|
|
1105
|
+
prediction: Prediction,
|
|
1106
|
+
trace?: Trace[],
|
|
1107
|
+
) => number | boolean;
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
| Factory | Description |
|
|
1111
|
+
|---|---|
|
|
1112
|
+
| `exactMatch(field?, caseSensitive?)` | 1 if prediction exactly matches example (case-insensitive by default) |
|
|
1113
|
+
| `f1(field?)` | Token-level F1 (word overlap), useful for QA |
|
|
1114
|
+
| `passAtK(innerMetric, k)` | 1 if any of the top-k completions pass `innerMetric` |
|
|
1115
|
+
| `bleu(field?)` | Simplified BLEU (1-gram + 2-gram precision) |
|
|
1116
|
+
| `rouge(field?)` | ROUGE-L (LCS-based F1) |
|
|
1117
|
+
|
|
1118
|
+
```ts
|
|
1119
|
+
import { exactMatch, f1, passAtK, bleu, rouge } from "dstsx";
|
|
1120
|
+
|
|
1121
|
+
// Exact match on "answer" field (default)
|
|
1122
|
+
const em = exactMatch();
|
|
1123
|
+
|
|
1124
|
+
// Case-sensitive exact match on a custom field
|
|
1125
|
+
const em2 = exactMatch("label", true);
|
|
1126
|
+
|
|
1127
|
+
// Token F1
|
|
1128
|
+
const f1Metric = f1("answer");
|
|
1129
|
+
|
|
1130
|
+
// Pass if any of the 5 completions give exact match
|
|
1131
|
+
const p5 = passAtK(exactMatch(), 5);
|
|
1132
|
+
|
|
1133
|
+
// BLEU / ROUGE
|
|
1134
|
+
const bleuMetric = bleu("answer");
|
|
1135
|
+
const rougeMetric = rouge("answer");
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
---
|
|
1139
|
+
|
|
1140
|
+
### Assertions & Suggestions
|
|
1141
|
+
|
|
1142
|
+
#### `Assert(condition, message?)`
|
|
1143
|
+
|
|
1144
|
+
Throws `AssertionError` if `condition` is falsy. Caught and retried by `Retry`.
|
|
1145
|
+
|
|
1146
|
+
```ts
|
|
1147
|
+
import { Assert } from "dstsx";
|
|
1148
|
+
|
|
1149
|
+
Assert(result.get("answer") !== "", "Answer must not be empty");
|
|
1150
|
+
Assert(typeof result.get("score") === "number", "Score must be a number");
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
#### `Suggest(condition, message?)`
|
|
1154
|
+
|
|
1155
|
+
Logs a `console.warn` if `condition` is falsy but does **not** throw — the pipeline continues.
|
|
1156
|
+
|
|
1157
|
+
```ts
|
|
1158
|
+
import { Suggest } from "dstsx";
|
|
1159
|
+
|
|
1160
|
+
Suggest(result.get("confidence") === "high", "Low confidence in answer");
|
|
1161
|
+
```
|
|
1162
|
+
|
|
1163
|
+
#### `AssertionError`
|
|
1164
|
+
|
|
1165
|
+
The typed error class thrown by `Assert`. Caught by `Retry`.
|
|
1166
|
+
|
|
1167
|
+
```ts
|
|
1168
|
+
import { AssertionError } from "dstsx";
|
|
1169
|
+
|
|
1170
|
+
try {
|
|
1171
|
+
await program.forward(inputs);
|
|
1172
|
+
} catch (err) {
|
|
1173
|
+
if (err instanceof AssertionError) {
|
|
1174
|
+
console.warn("Assertion failed:", err.message);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
```
|
|
1178
|
+
|
|
1179
|
+
---
|
|
1180
|
+
|
|
1181
|
+
## V2 APIs
|
|
1182
|
+
|
|
1183
|
+
The following features are implemented in DSTsx v2.
|
|
1184
|
+
|
|
1185
|
+
### `TypedPredictor` & `TypedChainOfThought`
|
|
1186
|
+
|
|
1187
|
+
Structured JSON output with optional schema validation. Works without any extra
|
|
1188
|
+
dependencies — pass a [Zod](https://github.com/colinhacks/zod) schema for
|
|
1189
|
+
runtime validation.
|
|
1190
|
+
|
|
1191
|
+
#### `TypedPrediction<T>`
|
|
1192
|
+
|
|
1193
|
+
Extends `Prediction` and adds a `.typed` field with the validated/parsed type.
|
|
1194
|
+
|
|
1195
|
+
```ts
|
|
1196
|
+
import { TypedPredictor } from "dstsx";
|
|
1197
|
+
|
|
1198
|
+
// Without schema — output is parsed as plain JSON
|
|
1199
|
+
const qa = new TypedPredictor("question -> answer");
|
|
1200
|
+
const result = await qa.forward({ question: "What is π?" });
|
|
1201
|
+
const typed = result.typed as { answer: string };
|
|
1202
|
+
console.log(typed.answer);
|
|
1203
|
+
```
|
|
1204
|
+
|
|
1205
|
+
With a Zod schema (`npm install zod` first):
|
|
1206
|
+
|
|
1207
|
+
```ts
|
|
1208
|
+
import { z } from "zod";
|
|
1209
|
+
import { TypedPredictor, TypedChainOfThought } from "dstsx";
|
|
1210
|
+
|
|
1211
|
+
const AnswerSchema = z.object({
|
|
1212
|
+
answer: z.string(),
|
|
1213
|
+
confidence: z.number().min(0).max(1),
|
|
1214
|
+
sources: z.array(z.string()).optional(),
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
const qa = new TypedPredictor("question -> answer", AnswerSchema, { maxRetries: 3 });
|
|
1218
|
+
const result = await qa.forward({ question: "What is 2 + 2?" });
|
|
1219
|
+
|
|
1220
|
+
// result.typed is z.infer<typeof AnswerSchema>
|
|
1221
|
+
console.log(result.typed.confidence); // 0.98 (number, validated)
|
|
1222
|
+
```
|
|
1223
|
+
|
|
1224
|
+
`TypedChainOfThought` adds a hidden `rationale` step before producing the JSON:
|
|
1225
|
+
|
|
1226
|
+
```ts
|
|
1227
|
+
const cot = new TypedChainOfThought("question -> answer", AnswerSchema);
|
|
1228
|
+
const result = await cot.forward({ question: "Explain gravity briefly." });
|
|
1229
|
+
```
|
|
1230
|
+
|
|
1231
|
+
**Constructor options:**
|
|
1232
|
+
|
|
1233
|
+
```ts
|
|
1234
|
+
new TypedPredictor(signature, schema?, { maxRetries?: number })
|
|
1235
|
+
// maxRetries: how many times to retry on parse/schema failure (default: 3)
|
|
1236
|
+
```
|
|
1237
|
+
|
|
1238
|
+
---
|
|
1239
|
+
|
|
1240
|
+
### `Parallel` Module
|
|
1241
|
+
|
|
1242
|
+
Runs multiple modules concurrently with `Promise.all` and returns all results.
|
|
1243
|
+
|
|
1244
|
+
```ts
|
|
1245
|
+
import { Parallel, Predict, ChainOfThought } from "dstsx";
|
|
1246
|
+
|
|
1247
|
+
const pipeline = new Parallel([
|
|
1248
|
+
new Predict("question -> answer"),
|
|
1249
|
+
new ChainOfThought("question -> answer"),
|
|
1250
|
+
], { timeoutMs: 10_000 }); // optional per-module timeout
|
|
1251
|
+
|
|
1252
|
+
// run() returns Prediction[] — one per module
|
|
1253
|
+
const [directAnswer, cotAnswer] = await pipeline.run({ question: "What is π?" });
|
|
1254
|
+
|
|
1255
|
+
// forward() returns the first prediction (for Module interface compat)
|
|
1256
|
+
const first = await pipeline.forward({ question: "What is π?" });
|
|
1257
|
+
```
|
|
1258
|
+
|
|
1259
|
+
**Constructor:**
|
|
1260
|
+
|
|
1261
|
+
```ts
|
|
1262
|
+
new Parallel(modules: Module[], options?: { timeoutMs?: number })
|
|
1263
|
+
```
|
|
1264
|
+
|
|
1265
|
+
| Method | Returns | Description |
|
|
1266
|
+
|---|---|---|
|
|
1267
|
+
| `run(...args)` | `Promise<Prediction[]>` | All module outputs in order |
|
|
1268
|
+
| `forward(...args)` | `Promise<Prediction>` | First module output (Module compat) |
|
|
1269
|
+
|
|
1270
|
+
---
|
|
1271
|
+
|
|
1272
|
+
### `Refine` Module
|
|
1273
|
+
|
|
1274
|
+
Self-critique / iterative refinement loop. After each inner module run, a built-in
|
|
1275
|
+
critic predictor evaluates the output and feeds improvement suggestions back.
|
|
1276
|
+
|
|
1277
|
+
```ts
|
|
1278
|
+
import { Refine, Predict } from "dstsx";
|
|
1279
|
+
|
|
1280
|
+
const writer = new Predict("topic, feedback? -> essay");
|
|
1281
|
+
|
|
1282
|
+
const refined = new Refine(writer, {
|
|
1283
|
+
maxRefinements: 2,
|
|
1284
|
+
feedbackField: "feedback", // injected field name for critique
|
|
1285
|
+
stopCondition: (pred) =>
|
|
1286
|
+
String(pred.get("essay")).length > 500, // stop early if long enough
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
const result = await refined.forward({ topic: "Climate change" });
|
|
1290
|
+
console.log(result.get("essay"));
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
**Constructor:**
|
|
1294
|
+
|
|
1295
|
+
```ts
|
|
1296
|
+
new Refine(inner: Module, options?: {
|
|
1297
|
+
maxRefinements?: number; // default: 2
|
|
1298
|
+
feedbackField?: string; // default: "feedback"
|
|
1299
|
+
stopCondition?: (p: Prediction) => boolean; // optional early-exit check
|
|
1300
|
+
})
|
|
1301
|
+
```
|
|
1302
|
+
|
|
1303
|
+
The critic calls `Predict("output -> critique, is_satisfactory")`.
|
|
1304
|
+
If `is_satisfactory` is `"yes"` or `"true"`, refinement stops early.
|
|
1305
|
+
|
|
1306
|
+
---
|
|
1307
|
+
|
|
1308
|
+
### `majority()` Helper
|
|
1309
|
+
|
|
1310
|
+
Votes across multiple `Prediction` instances by the most common value for a given
|
|
1311
|
+
field. Useful as a `reduceFunc` in `BestOfN` and `Ensemble`.
|
|
1312
|
+
|
|
1313
|
+
```ts
|
|
1314
|
+
import { majority, BestOfN, Predict } from "dstsx";
|
|
1315
|
+
|
|
1316
|
+
const qa = new Predict("question -> answer");
|
|
1317
|
+
|
|
1318
|
+
// Run 5 times and pick the most common answer
|
|
1319
|
+
const voted = new BestOfN(qa, 5, majority("answer"));
|
|
1320
|
+
const result = await voted.forward({ question: "What color is the sky?" });
|
|
1321
|
+
console.log(result.get("answer")); // most frequently returned answer
|
|
1322
|
+
```
|
|
1323
|
+
|
|
1324
|
+
```ts
|
|
1325
|
+
// Standalone usage
|
|
1326
|
+
import { majority } from "dstsx";
|
|
1327
|
+
|
|
1328
|
+
const reducer = majority("answer");
|
|
1329
|
+
const best = reducer([pred1, pred2, pred3]); // Prediction with the most common "answer"
|
|
1330
|
+
```
|
|
1331
|
+
|
|
1332
|
+
---
|
|
1333
|
+
|
|
1334
|
+
### `BootstrapFewShotWithOptuna`
|
|
1335
|
+
|
|
1336
|
+
Extends `BootstrapFewShot` with a pure-TypeScript TPE (Tree-structured Parzen
|
|
1337
|
+
Estimator) that searches demo subsets across `numTrials` iterations, learning
|
|
1338
|
+
from past trial outcomes to find the best configuration — no external
|
|
1339
|
+
dependencies required.
|
|
1340
|
+
|
|
1341
|
+
```ts
|
|
1342
|
+
import { BootstrapFewShotWithOptuna } from "dstsx";
|
|
1343
|
+
|
|
1344
|
+
const optimizer = new BootstrapFewShotWithOptuna({
|
|
1345
|
+
maxBootstrappedDemos: 4,
|
|
1346
|
+
numTrials: 20, // number of TPE search trials
|
|
1347
|
+
valset: valExamples, // optional held-out validation set
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
const optimized = await optimizer.compile(program, trainset, metric);
|
|
1351
|
+
```
|
|
1352
|
+
|
|
1353
|
+
**How it works:** First runs `BootstrapFewShot` to collect candidate demos. Then
|
|
1354
|
+
runs `numTrials` iterations where each trial samples a demo subset using TPE:
|
|
1355
|
+
the top 25 % of past trials form the "good" pool, sampled with 70 % probability,
|
|
1356
|
+
biased mutations towards the best configurations found so far.
|
|
1357
|
+
|
|
1358
|
+
---
|
|
1359
|
+
|
|
1360
|
+
### Disk-Persistent LM Cache
|
|
1361
|
+
|
|
1362
|
+
LM adapters now accept a `cacheDir` option. Responses are persisted as JSON
|
|
1363
|
+
files named by a SHA-256 hash of the prompt, surviving process restarts.
|
|
1364
|
+
|
|
1365
|
+
```ts
|
|
1366
|
+
import { OpenAI, MockLM } from "dstsx";
|
|
1367
|
+
|
|
1368
|
+
// Any LM adapter — just pass cacheDir
|
|
1369
|
+
const lm = new OpenAI({
|
|
1370
|
+
model: "gpt-4o",
|
|
1371
|
+
cacheDir: "./.dstsx-cache", // optional disk persistence
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
// Or with MockLM for testing disk cache behavior
|
|
1375
|
+
const mockLm = new MockLM({ "q": "a" }, undefined, { cacheDir: "/tmp/test-cache" });
|
|
1376
|
+
```
|
|
1377
|
+
|
|
1378
|
+
The disk cache is checked **after** the in-memory LRU cache. On a hit the
|
|
1379
|
+
response is also written back into the in-memory cache. TTL and max-size
|
|
1380
|
+
eviction apply to both layers.
|
|
1381
|
+
|
|
1382
|
+
`DiskCache` is also exported for custom use:
|
|
1383
|
+
|
|
1384
|
+
```ts
|
|
1385
|
+
import { DiskCache } from "dstsx";
|
|
1386
|
+
|
|
1387
|
+
const cache = new DiskCache(
|
|
1388
|
+
"./.dstsx-cache", // directory (created automatically)
|
|
1389
|
+
500, // maxSize (files); default 500
|
|
1390
|
+
3_600_000, // ttlMs; default undefined (no TTL)
|
|
1391
|
+
);
|
|
1392
|
+
|
|
1393
|
+
cache.set("myKey", lmResponse);
|
|
1394
|
+
const cached = cache.get("myKey");
|
|
1395
|
+
cache.clear(); // delete all cache files
|
|
1396
|
+
```
|
|
1397
|
+
|
|
1398
|
+
---
|
|
1399
|
+
|
|
1400
|
+
### MCP Integration
|
|
1401
|
+
|
|
1402
|
+
DSTsx integrates with the [Model Context Protocol](https://modelcontextprotocol.io/)
|
|
1403
|
+
(MCP) in two directions:
|
|
1404
|
+
|
|
1405
|
+
1. **Use MCP servers as tools** inside ReAct agents (`MCPToolAdapter`)
|
|
1406
|
+
2. **Expose DSTsx modules as MCP tools** (`DSTsxMCPServer`)
|
|
1407
|
+
|
|
1408
|
+
Optional peer dependency: `npm install @modelcontextprotocol/sdk`
|
|
1409
|
+
|
|
1410
|
+
---
|
|
1411
|
+
|
|
1412
|
+
#### `MCPToolAdapter` — consume MCP servers in ReAct
|
|
1413
|
+
|
|
1414
|
+
Wraps the tools from an MCP server as DSTsx `Tool` objects for use with `ReAct`.
|
|
1415
|
+
|
|
1416
|
+
```ts
|
|
1417
|
+
import { MCPToolAdapter, ReAct } from "dstsx";
|
|
1418
|
+
|
|
1419
|
+
const adapter = new MCPToolAdapter({
|
|
1420
|
+
// Test mode: supply tool definitions + call handler without a live server
|
|
1421
|
+
tools: [
|
|
1422
|
+
{
|
|
1423
|
+
name: "weather",
|
|
1424
|
+
description: "Get current weather for a city",
|
|
1425
|
+
inputSchema: {
|
|
1426
|
+
type: "object",
|
|
1427
|
+
properties: { city: { type: "string" } },
|
|
1428
|
+
required: ["city"],
|
|
1429
|
+
},
|
|
1430
|
+
},
|
|
1431
|
+
],
|
|
1432
|
+
callHandler: async (name, args) => {
|
|
1433
|
+
if (name === "weather") return `Sunny in ${args["city"] as string}`;
|
|
1434
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
1435
|
+
},
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
const tools = await adapter.getTools();
|
|
1439
|
+
const agent = new ReAct("question -> answer", tools, 5);
|
|
1440
|
+
const result = await agent.forward({ question: "What is the weather in Paris?" });
|
|
1441
|
+
```
|
|
1442
|
+
|
|
1443
|
+
When `@modelcontextprotocol/sdk` is installed, a live SSE/stdio connection can
|
|
1444
|
+
be established by setting `serverUrl` (full live-connection implementation is in
|
|
1445
|
+
the [v2 roadmap](./V2_ROADMAP.md#live-mcp-connection)).
|
|
1446
|
+
|
|
1447
|
+
---
|
|
1448
|
+
|
|
1449
|
+
#### `DSTsxMCPServer` — expose DSTsx modules as MCP tools
|
|
1450
|
+
|
|
1451
|
+
Register any DSTsx module and serve it as an MCP-compatible tool.
|
|
1452
|
+
|
|
1453
|
+
```ts
|
|
1454
|
+
import { DSTsxMCPServer, ChainOfThought, settings, OpenAI } from "dstsx";
|
|
1455
|
+
|
|
1456
|
+
settings.configure({ lm: new OpenAI({ model: "gpt-4o" }) });
|
|
1457
|
+
|
|
1458
|
+
const qa = new ChainOfThought("context, question -> answer");
|
|
1459
|
+
|
|
1460
|
+
const server = new DSTsxMCPServer();
|
|
1461
|
+
server.registerModule(
|
|
1462
|
+
"qa", // tool name
|
|
1463
|
+
"Answer questions using chain-of-thought reasoning",
|
|
1464
|
+
qa,
|
|
1465
|
+
["context", "question"], // input field names
|
|
1466
|
+
);
|
|
1467
|
+
|
|
1468
|
+
// List tools (for MCP handshake)
|
|
1469
|
+
const toolDefs = server.getToolDefinitions();
|
|
1470
|
+
/*
|
|
1471
|
+
[{
|
|
1472
|
+
name: "qa",
|
|
1473
|
+
description: "...",
|
|
1474
|
+
inputSchema: { type: "object", properties: { context: { type: "string" }, ... } },
|
|
1475
|
+
}]
|
|
1476
|
+
*/
|
|
1477
|
+
|
|
1478
|
+
// Handle a tool call
|
|
1479
|
+
const result = await server.callTool("qa", {
|
|
1480
|
+
context: "Paris is the capital of France.",
|
|
1481
|
+
question: "What is the capital of France?",
|
|
1482
|
+
});
|
|
1483
|
+
// result is the Prediction.toJSON() object
|
|
1484
|
+
|
|
1485
|
+
// With @modelcontextprotocol/sdk installed, launch a stdio MCP server:
|
|
1486
|
+
// await server.createStdioServer();
|
|
1487
|
+
```
|
|
1488
|
+
|
|
1489
|
+
**MCPTool type:**
|
|
1490
|
+
|
|
1491
|
+
```ts
|
|
1492
|
+
interface MCPTool {
|
|
1493
|
+
name: string;
|
|
1494
|
+
description: string;
|
|
1495
|
+
inputSchema: {
|
|
1496
|
+
type: "object";
|
|
1497
|
+
properties: Record<string, { type: string; description?: string }>;
|
|
1498
|
+
required?: string[];
|
|
1499
|
+
};
|
|
1500
|
+
handler: (inputs: Record<string, unknown>) => Promise<unknown>;
|
|
1501
|
+
}
|
|
1502
|
+
```
|
|
1503
|
+
|
|
1504
|
+
**`DSTsxMCPServer` methods:**
|
|
1505
|
+
|
|
1506
|
+
| Method | Description |
|
|
1507
|
+
|---|---|
|
|
1508
|
+
| `registerModule(name, desc, module, fields)` | Register a module as an MCP tool |
|
|
1509
|
+
| `getToolDefinitions()` | Return all registered `MCPTool[]` |
|
|
1510
|
+
| `callTool(name, inputs)` | Invoke a registered tool by name |
|
|
1511
|
+
| `createStdioServer()` | Start an MCP stdio server (requires SDK) |
|
|
1512
|
+
|
|
1513
|
+
---
|
|
1514
|
+
|
|
1515
|
+
### LM Streaming
|
|
1516
|
+
|
|
1517
|
+
Stream LM responses token by token using `AsyncGenerator`. All adapters provide
|
|
1518
|
+
a default fallback that returns the full response as a single chunk. Real
|
|
1519
|
+
streaming is implemented for `OpenAI` and `Anthropic`.
|
|
1520
|
+
|
|
1521
|
+
```ts
|
|
1522
|
+
import { settings, OpenAI, Predict } from "dstsx";
|
|
1523
|
+
|
|
1524
|
+
settings.configure({ lm: new OpenAI({ model: "gpt-4o" }) });
|
|
1525
|
+
|
|
1526
|
+
const qa = new Predict("question -> answer");
|
|
1527
|
+
|
|
1528
|
+
// Stream via Predict
|
|
1529
|
+
for await (const chunk of qa.stream({ question: "Tell me a story." })) {
|
|
1530
|
+
process.stdout.write(chunk.delta);
|
|
1531
|
+
if (chunk.done) break;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// Stream directly from LM
|
|
1535
|
+
const lm = new OpenAI({ model: "gpt-4o" });
|
|
1536
|
+
for await (const chunk of lm.stream("What is TypeScript?")) {
|
|
1537
|
+
process.stdout.write(chunk.delta);
|
|
1538
|
+
}
|
|
1539
|
+
```
|
|
1540
|
+
|
|
1541
|
+
**`StreamChunk` type:**
|
|
1542
|
+
|
|
1543
|
+
```ts
|
|
1544
|
+
interface StreamChunk {
|
|
1545
|
+
delta: string; // Incremental text
|
|
1546
|
+
done: boolean; // True on the final chunk
|
|
1547
|
+
raw: unknown; // Raw provider chunk
|
|
1548
|
+
}
|
|
1549
|
+
```
|
|
1550
|
+
|
|
1551
|
+
| Method | Available on | Description |
|
|
1552
|
+
|---|---|---|
|
|
1553
|
+
| `lm.stream(prompt, config?)` | `LM` (all adapters) | Stream from LM (fallback on unsupported) |
|
|
1554
|
+
| `predict.stream(inputs)` | `Predict` | Stream from a Predict module |
|
|
1555
|
+
|
|
1556
|
+
---
|
|
1557
|
+
|
|
1558
|
+
### NativeReAct
|
|
1559
|
+
|
|
1560
|
+
A `ReAct` variant that uses provider-native function/tool calling (OpenAI tools
|
|
1561
|
+
API, Anthropic tool_use) instead of text-based action parsing. Falls back to
|
|
1562
|
+
text format for adapters that don't support native calling.
|
|
1563
|
+
|
|
1564
|
+
```ts
|
|
1565
|
+
import { NativeReAct, settings, OpenAI } from "dstsx";
|
|
1566
|
+
import type { Tool } from "dstsx";
|
|
1567
|
+
|
|
1568
|
+
settings.configure({ lm: new OpenAI({ model: "gpt-4o" }) });
|
|
1569
|
+
|
|
1570
|
+
const tools: Tool[] = [
|
|
1571
|
+
{
|
|
1572
|
+
name: "search",
|
|
1573
|
+
description: "Search the web for information",
|
|
1574
|
+
fn: async (args) => {
|
|
1575
|
+
const { query } = JSON.parse(args) as { query: string };
|
|
1576
|
+
return `Results for: ${query}`;
|
|
1577
|
+
},
|
|
1578
|
+
},
|
|
1579
|
+
];
|
|
1580
|
+
|
|
1581
|
+
const agent = new NativeReAct("question -> answer", tools, 5);
|
|
1582
|
+
const result = await agent.forward({ question: "What is the capital of France?" });
|
|
1583
|
+
console.log(result.get("answer")); // "Paris"
|
|
1584
|
+
```
|
|
1585
|
+
|
|
1586
|
+
**Constructor:**
|
|
1587
|
+
|
|
1588
|
+
```ts
|
|
1589
|
+
new NativeReAct(
|
|
1590
|
+
signature: string,
|
|
1591
|
+
tools: Tool[],
|
|
1592
|
+
maxIter?: number, // default: 5
|
|
1593
|
+
)
|
|
1594
|
+
```
|
|
1595
|
+
|
|
1596
|
+
NativeReAct wraps tool calls using the OpenAI `tools` format (an array of
|
|
1597
|
+
`{ type: "function", function: { name, description, parameters } }` objects),
|
|
1598
|
+
which is also accepted by other compatible providers.
|
|
1599
|
+
|
|
1600
|
+
---
|
|
1601
|
+
|
|
1602
|
+
### Image — Multi-modal Support
|
|
1603
|
+
|
|
1604
|
+
The `Image` primitive enables passing images to vision-capable LMs as field
|
|
1605
|
+
values in any `Predict` or `TypedPredictor` call.
|
|
1606
|
+
|
|
1607
|
+
```ts
|
|
1608
|
+
import { Image, Predict, settings, OpenAI } from "dstsx";
|
|
1609
|
+
|
|
1610
|
+
settings.configure({ lm: new OpenAI({ model: "gpt-4o" }) });
|
|
1611
|
+
|
|
1612
|
+
const captioner = new Predict("image, question -> caption");
|
|
1613
|
+
|
|
1614
|
+
// From a URL (lazy — no download at construction time)
|
|
1615
|
+
const img1 = Image.fromURL("https://example.com/photo.jpg");
|
|
1616
|
+
const result1 = await captioner.forward({ image: img1, question: "Describe this image." });
|
|
1617
|
+
|
|
1618
|
+
// From base64 data
|
|
1619
|
+
const img2 = Image.fromBase64(base64String, "image/png");
|
|
1620
|
+
|
|
1621
|
+
// From a local file (read synchronously)
|
|
1622
|
+
const img3 = Image.fromFile("./photo.jpg");
|
|
1623
|
+
```
|
|
1624
|
+
|
|
1625
|
+
**Static factory methods:**
|
|
1626
|
+
|
|
1627
|
+
| Method | Description |
|
|
1628
|
+
|---|---|
|
|
1629
|
+
| `Image.fromURL(url)` | Image at a remote URL |
|
|
1630
|
+
| `Image.fromBase64(data, mimeType?)` | Inline base64 (default: `"image/jpeg"`) |
|
|
1631
|
+
| `Image.fromFile(path, mimeType?)` | Local file read synchronously; auto-detects MIME from extension |
|
|
1632
|
+
|
|
1633
|
+
**Serialization helpers (used by adapters internally):**
|
|
1634
|
+
|
|
1635
|
+
```ts
|
|
1636
|
+
img.toOpenAIContentPart() // { type: "image_url", image_url: { url } }
|
|
1637
|
+
img.toAnthropicContentBlock() // { type: "image", source: { ... } }
|
|
1638
|
+
img.toString() // "[Image: https://...]" — used in text prompts
|
|
1639
|
+
```
|
|
1640
|
+
|
|
1641
|
+
**Supported MIME types:** `"image/jpeg"`, `"image/png"`, `"image/gif"`, `"image/webp"`
|
|
1642
|
+
|
|
1643
|
+
---
|
|
1644
|
+
|
|
1645
|
+
### BootstrapFinetune
|
|
1646
|
+
|
|
1647
|
+
Extends `BootstrapFewShot` to collect execution traces and export them as a
|
|
1648
|
+
JSONL fine-tuning dataset.
|
|
1649
|
+
|
|
1650
|
+
```ts
|
|
1651
|
+
import { BootstrapFinetune } from "dstsx";
|
|
1652
|
+
|
|
1653
|
+
const optimizer = new BootstrapFinetune({
|
|
1654
|
+
exportPath: "./finetune_data.jsonl", // default
|
|
1655
|
+
format: "openai", // "openai" | "generic"
|
|
1656
|
+
maxBootstrappedDemos: 4,
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
const compiled = await optimizer.compile(program, trainset, metric);
|
|
1660
|
+
// "./finetune_data.jsonl" now contains one JSON object per line:
|
|
1661
|
+
// {"messages": [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}]}
|
|
1662
|
+
```
|
|
1663
|
+
|
|
1664
|
+
**Options:**
|
|
1665
|
+
|
|
1666
|
+
| Option | Type | Default | Description |
|
|
1667
|
+
|---|---|---|---|
|
|
1668
|
+
| `exportPath` | `string` | `"./finetune_data.jsonl"` | Output file path |
|
|
1669
|
+
| `format` | `"openai" \| "generic"` | `"openai"` | JSONL line format |
|
|
1670
|
+
| `maxBootstrappedDemos` | `number` | `4` | Demos to bootstrap per predictor |
|
|
1671
|
+
|
|
1672
|
+
**Format examples:**
|
|
1673
|
+
|
|
1674
|
+
`"openai"` (suitable for OpenAI fine-tuning):
|
|
1675
|
+
```jsonl
|
|
1676
|
+
{"messages": [{"role": "user", "content": "question: What is 2+2?"}, {"role": "assistant", "content": "answer: 4"}]}
|
|
1677
|
+
```
|
|
1678
|
+
|
|
1679
|
+
`"generic"` (plain prompt/completion):
|
|
1680
|
+
```jsonl
|
|
1681
|
+
{"prompt": "question: What is 2+2?", "completion": "answer: 4"}
|
|
1682
|
+
```
|
|
1683
|
+
|
|
1684
|
+
---
|
|
1685
|
+
|
|
1686
|
+
### GRPO Optimizer
|
|
1687
|
+
|
|
1688
|
+
Group Relative Policy Optimization — mirrors `dspy.GRPO`. Iteratively generates
|
|
1689
|
+
groups of candidate instruction variants, evaluates them relative to each other,
|
|
1690
|
+
and converges toward the best-scoring configuration.
|
|
1691
|
+
|
|
1692
|
+
```ts
|
|
1693
|
+
import { GRPO } from "dstsx";
|
|
1694
|
+
|
|
1695
|
+
const optimizer = new GRPO({
|
|
1696
|
+
numSteps: 20, // optimization iterations
|
|
1697
|
+
groupSize: 8, // candidates per group
|
|
1698
|
+
temperature: 1.0, // sampling temperature
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
const optimized = await optimizer.compile(program, trainset, metric);
|
|
1702
|
+
```
|
|
1703
|
+
|
|
1704
|
+
**How it works:** Each step generates `groupSize` instruction alternatives using
|
|
1705
|
+
the configured LM at the specified temperature. All candidates are evaluated on
|
|
1706
|
+
the training set. The relative advantage of each is computed as
|
|
1707
|
+
`(score − mean) / std`. The best-scoring candidate becomes the new baseline for
|
|
1708
|
+
the next step.
|
|
1709
|
+
|
|
1710
|
+
**Options:**
|
|
1711
|
+
|
|
1712
|
+
| Option | Type | Default |
|
|
1713
|
+
|---|---|---|
|
|
1714
|
+
| `numSteps` | `number` | `20` |
|
|
1715
|
+
| `groupSize` | `number` | `8` |
|
|
1716
|
+
| `temperature` | `number` | `1.0` |
|
|
1717
|
+
| `maxLabeledDemos` | `number` | `16` |
|
|
1718
|
+
|
|
1719
|
+
---
|
|
1720
|
+
|
|
1721
|
+
### SIMBA Optimizer
|
|
1722
|
+
|
|
1723
|
+
SIMBA (Stochastic Introspective Mini-Batch Ascent) — mirrors `dspy.SIMBA`. A
|
|
1724
|
+
lightweight stochastic optimizer well-suited for small training sets.
|
|
1725
|
+
|
|
1726
|
+
```ts
|
|
1727
|
+
import { SIMBA } from "dstsx";
|
|
1728
|
+
|
|
1729
|
+
const optimizer = new SIMBA({
|
|
1730
|
+
numIter: 10, // iterations
|
|
1731
|
+
batchSize: 8, // mini-batch size per evaluation
|
|
1732
|
+
maxBootstrappedDemos: 4,
|
|
1733
|
+
});
|
|
1734
|
+
|
|
1735
|
+
const optimized = await optimizer.compile(program, trainset, metric);
|
|
1736
|
+
```
|
|
1737
|
+
|
|
1738
|
+
**How it works:** Initializes with `BootstrapFewShot`, then for each iteration
|
|
1739
|
+
draws a random mini-batch (Fisher-Yates shuffle), proposes a demo-subset
|
|
1740
|
+
candidate, evaluates it on the batch, and accepts it if it improves on the
|
|
1741
|
+
current best score.
|
|
1742
|
+
|
|
1743
|
+
**Options:**
|
|
1744
|
+
|
|
1745
|
+
| Option | Type | Default |
|
|
1746
|
+
|---|---|---|
|
|
1747
|
+
| `numIter` | `number` | `10` |
|
|
1748
|
+
| `batchSize` | `number` | `8` |
|
|
1749
|
+
| `maxBootstrappedDemos` | `number` | `4` |
|
|
1750
|
+
|
|
1751
|
+
---
|
|
1752
|
+
|
|
1753
|
+
### AvatarOptimizer
|
|
1754
|
+
|
|
1755
|
+
Iteratively proposes and evaluates "avatar" role descriptions (persona prefixes)
|
|
1756
|
+
for each `Predict` module, selecting the instruction that scores highest on the
|
|
1757
|
+
training set.
|
|
1758
|
+
|
|
1759
|
+
```ts
|
|
1760
|
+
import { AvatarOptimizer } from "dstsx";
|
|
1761
|
+
|
|
1762
|
+
const optimizer = new AvatarOptimizer({
|
|
1763
|
+
numAvatars: 4, // candidate personas per predictor
|
|
1764
|
+
maxLabeledDemos: 8,
|
|
1765
|
+
});
|
|
1766
|
+
|
|
1767
|
+
const optimized = await optimizer.compile(program, trainset, metric);
|
|
1768
|
+
```
|
|
1769
|
+
|
|
1770
|
+
**How it works:** For each `Predict` predictor in the program, asks the
|
|
1771
|
+
configured LM to generate `numAvatars` distinct role/persona descriptions
|
|
1772
|
+
(e.g. "You are an expert doctor…"). Each persona is prepended to the predictor's
|
|
1773
|
+
instructions and scored on the training set. The best persona is kept.
|
|
1774
|
+
|
|
1775
|
+
**Options:**
|
|
1776
|
+
|
|
1777
|
+
| Option | Type | Default |
|
|
1778
|
+
|---|---|---|
|
|
1779
|
+
| `numAvatars` | `number` | `4` |
|
|
1780
|
+
| `maxLabeledDemos` | `number` | `8` |
|
|
1781
|
+
|
|
1782
|
+
---
|
|
1783
|
+
|
|
1784
|
+
### Experiment Tracking
|
|
1785
|
+
|
|
1786
|
+
Track optimizer progress (steps, scores, best candidates) to console, JSON
|
|
1787
|
+
files, or custom backends.
|
|
1788
|
+
|
|
1789
|
+
```ts
|
|
1790
|
+
import { ConsoleTracker, JsonFileTracker, GRPO, SIMBA, AvatarOptimizer } from "dstsx";
|
|
1791
|
+
|
|
1792
|
+
// Log to console
|
|
1793
|
+
const consoleTracker = new ConsoleTracker();
|
|
1794
|
+
// [STEP] step=1 score=0.7500
|
|
1795
|
+
// [BEST] step=3 score=0.8750
|
|
1796
|
+
|
|
1797
|
+
// Log to JSONL file
|
|
1798
|
+
const fileTracker = new JsonFileTracker("./runs/exp1.jsonl");
|
|
1799
|
+
await fileTracker.flush(); // flush buffer to disk
|
|
1800
|
+
|
|
1801
|
+
// Pass to any optimizer
|
|
1802
|
+
const optimizer = new GRPO({ numSteps: 10 });
|
|
1803
|
+
// (trackers are accepted as options on GRPO, SIMBA, AvatarOptimizer)
|
|
1804
|
+
```
|
|
1805
|
+
|
|
1806
|
+
**Custom tracker — extend `Tracker`:**
|
|
1807
|
+
|
|
1808
|
+
```ts
|
|
1809
|
+
import { Tracker } from "dstsx";
|
|
1810
|
+
import type { TrackerEvent } from "dstsx";
|
|
1811
|
+
|
|
1812
|
+
class MLflowTracker extends Tracker {
|
|
1813
|
+
log(event: TrackerEvent): void {
|
|
1814
|
+
// Send to MLflow REST API, W&B, etc.
|
|
1815
|
+
console.log("mlflow.log_metric", event.score);
|
|
1816
|
+
}
|
|
1817
|
+
async flush(): Promise<void> {}
|
|
1818
|
+
}
|
|
1819
|
+
```
|
|
1820
|
+
|
|
1821
|
+
**`TrackerEvent` type:**
|
|
1822
|
+
|
|
1823
|
+
```ts
|
|
1824
|
+
interface TrackerEvent {
|
|
1825
|
+
type: "step" | "trial" | "best" | "done";
|
|
1826
|
+
step?: number;
|
|
1827
|
+
score?: number;
|
|
1828
|
+
metadata?: Record<string, unknown>;
|
|
1829
|
+
}
|
|
1830
|
+
```
|
|
1831
|
+
|
|
1832
|
+
**Exported classes:** `Tracker` (abstract), `ConsoleTracker`, `JsonFileTracker`
|
|
1833
|
+
|
|
1834
|
+
---
|
|
1835
|
+
|
|
1836
|
+
### Worker-Thread `ProgramOfThought`
|
|
1837
|
+
|
|
1838
|
+
`ProgramOfThought` now supports a `sandbox` option to run generated code in a
|
|
1839
|
+
Node.js Worker thread instead of `new Function()` in the main process, providing
|
|
1840
|
+
stronger isolation.
|
|
1841
|
+
|
|
1842
|
+
```ts
|
|
1843
|
+
import { ProgramOfThought } from "dstsx";
|
|
1844
|
+
|
|
1845
|
+
// Default: "function" (same process, backwards-compatible)
|
|
1846
|
+
const pot = new ProgramOfThought("question -> answer");
|
|
1847
|
+
|
|
1848
|
+
// Worker thread isolation (Node 18+)
|
|
1849
|
+
const potWorker = new ProgramOfThought(
|
|
1850
|
+
"question -> answer",
|
|
1851
|
+
3, // maxAttempts
|
|
1852
|
+
5_000, // timeoutMs
|
|
1853
|
+
"worker" // sandbox mode
|
|
1854
|
+
);
|
|
1855
|
+
const result = await potWorker.forward({ question: "What is 2 ** 10?" });
|
|
1856
|
+
console.log(result.get("answer")); // "1024"
|
|
1857
|
+
|
|
1858
|
+
// Disable timeout/sandbox entirely (not recommended for untrusted input)
|
|
1859
|
+
const potNone = new ProgramOfThought("question -> answer", 3, 5_000, "none");
|
|
1860
|
+
```
|
|
1861
|
+
|
|
1862
|
+
**Sandbox modes:**
|
|
1863
|
+
|
|
1864
|
+
| Mode | Isolation | True Cancellation | Notes |
|
|
1865
|
+
|---|---|---|---|
|
|
1866
|
+
| `"function"` | None — runs in main process | No | **Default.** Backwards-compatible. |
|
|
1867
|
+
| `"worker"` | Node.js Worker thread | Yes (terminate on timeout) | Requires Node 18+. |
|
|
1868
|
+
| `"none"` | None — no timeout applied | N/A | Fastest; do not use with untrusted code. |
|
|
1869
|
+
|
|
1870
|
+
---
|
|
1871
|
+
|
|
1872
|
+
## End-to-End Examples
|
|
1873
|
+
|
|
1874
|
+
### 1. Simple Q&A
|
|
1875
|
+
|
|
1876
|
+
```ts
|
|
1877
|
+
import { settings, OpenAI, Predict } from "dstsx";
|
|
1878
|
+
|
|
1879
|
+
settings.configure({ lm: new OpenAI({ model: "gpt-4o-mini" }) });
|
|
1880
|
+
|
|
1881
|
+
const qa = new Predict("question -> answer");
|
|
1882
|
+
|
|
1883
|
+
const result = await qa.forward({ question: "What is the speed of light?" });
|
|
1884
|
+
console.log(result.get("answer"));
|
|
1885
|
+
```
|
|
1886
|
+
|
|
1887
|
+
---
|
|
1888
|
+
|
|
1889
|
+
### 2. Retrieval-Augmented Generation (RAG)
|
|
1890
|
+
|
|
1891
|
+
```ts
|
|
1892
|
+
import {
|
|
1893
|
+
Module, Retrieve, ChainOfThought, ColBERTv2,
|
|
1894
|
+
settings, OpenAI, type Prediction,
|
|
1895
|
+
} from "dstsx";
|
|
1896
|
+
|
|
1897
|
+
settings.configure({
|
|
1898
|
+
lm: new OpenAI({ model: "gpt-4o" }),
|
|
1899
|
+
rm: new ColBERTv2("http://localhost:8893"),
|
|
1900
|
+
});
|
|
1901
|
+
|
|
1902
|
+
class RAG extends Module {
|
|
1903
|
+
retrieve = new Retrieve(3);
|
|
1904
|
+
generate = new ChainOfThought("context, question -> answer");
|
|
1905
|
+
|
|
1906
|
+
async forward(inputs: { question: string }): Promise<Prediction> {
|
|
1907
|
+
const { passages } = (await this.retrieve.forward(inputs.question)).toDict() as { passages: string[] };
|
|
1908
|
+
return this.generate.forward({
|
|
1909
|
+
context: passages.join("\n"),
|
|
1910
|
+
question: inputs.question,
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
const rag = new RAG();
|
|
1916
|
+
const result = await rag.forward({ question: "What is the capital of Germany?" });
|
|
1917
|
+
console.log(result.get("answer")); // "Berlin"
|
|
1918
|
+
```
|
|
1919
|
+
|
|
1920
|
+
---
|
|
1921
|
+
|
|
1922
|
+
### 3. Optimizing with BootstrapFewShot
|
|
1923
|
+
|
|
1924
|
+
```ts
|
|
1925
|
+
import {
|
|
1926
|
+
settings, MockLM, Predict, Module, BootstrapFewShot,
|
|
1927
|
+
Example, evaluate, exactMatch, type Prediction,
|
|
1928
|
+
} from "dstsx";
|
|
1929
|
+
|
|
1930
|
+
settings.configure({ lm: new MockLM({}, "answer: 42") });
|
|
1931
|
+
|
|
1932
|
+
class QA extends Module {
|
|
1933
|
+
predict = new Predict("question -> answer");
|
|
1934
|
+
async forward(inputs: { question: string }): Promise<Prediction> {
|
|
1935
|
+
return this.predict.forward(inputs);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
const trainset = [
|
|
1940
|
+
new Example({ question: "What is 6 × 7?", answer: "42" }),
|
|
1941
|
+
new Example({ question: "What is 8 × 8?", answer: "64" }),
|
|
1942
|
+
];
|
|
1943
|
+
|
|
1944
|
+
const optimizer = new BootstrapFewShot({ maxBootstrappedDemos: 2 });
|
|
1945
|
+
const optimized = await optimizer.compile(new QA(), trainset, exactMatch("answer"));
|
|
1946
|
+
|
|
1947
|
+
// Persist
|
|
1948
|
+
import { writeFileSync } from "fs";
|
|
1949
|
+
writeFileSync("qa_optimized.json", JSON.stringify(optimized.dump(), null, 2));
|
|
1950
|
+
```
|
|
1951
|
+
|
|
1952
|
+
---
|
|
1953
|
+
|
|
1954
|
+
### 4. ReAct Agent
|
|
1955
|
+
|
|
1956
|
+
```ts
|
|
1957
|
+
import { settings, OpenAI, ReAct, type Tool } from "dstsx";
|
|
1958
|
+
|
|
1959
|
+
settings.configure({ lm: new OpenAI({ model: "gpt-4o" }) });
|
|
1960
|
+
|
|
1961
|
+
const tools: Tool[] = [
|
|
1962
|
+
{
|
|
1963
|
+
name: "calculator",
|
|
1964
|
+
description: "Evaluates a mathematical expression and returns the numeric result",
|
|
1965
|
+
fn: async (expr) => String(Function(`"use strict"; return (${expr})`)()),
|
|
1966
|
+
},
|
|
1967
|
+
{
|
|
1968
|
+
name: "lookup",
|
|
1969
|
+
description: "Looks up a fact in the knowledge base",
|
|
1970
|
+
fn: async (query) => `Fact about ${query}: (result from KB)`,
|
|
1971
|
+
},
|
|
1972
|
+
];
|
|
1973
|
+
|
|
1974
|
+
const agent = new ReAct("question -> answer", tools, /* maxIter= */ 6);
|
|
1975
|
+
const result = await agent.forward({ question: "What is (123 * 456) + 789?" });
|
|
1976
|
+
console.log(result.get("answer"));
|
|
1977
|
+
console.log(result.get("trajectory"));
|
|
1978
|
+
```
|
|
1979
|
+
|
|
1980
|
+
---
|
|
1981
|
+
|
|
1982
|
+
### 5. Assertions with Retry
|
|
1983
|
+
|
|
1984
|
+
```ts
|
|
1985
|
+
import {
|
|
1986
|
+
settings, MockLM, Module, Predict, Retry, Assert, type Prediction,
|
|
1987
|
+
} from "dstsx";
|
|
1988
|
+
|
|
1989
|
+
settings.configure({ lm: new MockLM({}, "answer: Paris") });
|
|
1990
|
+
|
|
1991
|
+
class CapitalQA extends Module {
|
|
1992
|
+
predict = new Predict("question, feedback? -> answer");
|
|
1993
|
+
|
|
1994
|
+
async forward(inputs: { question: string }): Promise<Prediction> {
|
|
1995
|
+
const result = await this.predict.forward(inputs);
|
|
1996
|
+
Assert(
|
|
1997
|
+
String(result.get("answer")).trim().length > 0,
|
|
1998
|
+
"Answer must not be empty"
|
|
1999
|
+
);
|
|
2000
|
+
return result;
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
const retrying = new Retry(new CapitalQA(), 3);
|
|
2005
|
+
const result = await retrying.forward({ question: "What is the capital of France?" });
|
|
2006
|
+
console.log(result.get("answer")); // "Paris"
|
|
2007
|
+
```
|
|
2008
|
+
|
|
2009
|
+
---
|
|
2010
|
+
|
|
2011
|
+
### 6. Per-Request LM Override (server environments)
|
|
2012
|
+
|
|
2013
|
+
```ts
|
|
2014
|
+
import express from "express";
|
|
2015
|
+
import { settings, OpenAI, Predict } from "dstsx";
|
|
2016
|
+
|
|
2017
|
+
const app = express();
|
|
2018
|
+
const qa = new Predict("question -> answer");
|
|
2019
|
+
const gpt4 = new OpenAI({ model: "gpt-4o" });
|
|
2020
|
+
const gptMini = new OpenAI({ model: "gpt-4o-mini" });
|
|
2021
|
+
|
|
2022
|
+
settings.configure({ lm: gpt4 }); // global default
|
|
2023
|
+
|
|
2024
|
+
app.get("/fast", async (req, res) => {
|
|
2025
|
+
// Override LM for this request only — concurrent requests never interfere
|
|
2026
|
+
const result = await settings.context(
|
|
2027
|
+
{ lm: gptMini },
|
|
2028
|
+
() => qa.forward({ question: req.query["q"] as string }),
|
|
2029
|
+
);
|
|
2030
|
+
res.json(result.toJSON());
|
|
2031
|
+
});
|
|
2032
|
+
|
|
2033
|
+
app.listen(3000);
|
|
2034
|
+
```
|
|
2035
|
+
|
|
2036
|
+
---
|
|
2037
|
+
|
|
2038
|
+
## V2 Roadmap
|
|
2039
|
+
|
|
2040
|
+
## Contributing & Releases
|
|
2041
|
+
|
|
2042
|
+
To publish new versions of `dstsx`, follow the workflow below:
|
|
2043
|
+
|
|
2044
|
+
1. Update the package version (`npm version patch`, `minor`, or `major`).
|
|
2045
|
+
2. Push commits and tags to GitHub (`git push && git push --tags`).
|
|
2046
|
+
3. Create a GitHub release for the new tag (manual or via `gh` CLI). The
|
|
2047
|
+
existing GitHub Action (`.github/workflows/publish.yml`) triggers on a
|
|
2048
|
+
**published** release and will run tests + build and then publish the
|
|
2049
|
+
package to npm using the `NPM_TOKEN` secret.
|
|
2050
|
+
4. For local manual publishes you can run `npm publish`; `prepublishOnly` runs
|
|
2051
|
+
the test suite and builds automatically.
|
|
2052
|
+
|
|
2053
|
+
Contributions are welcome! Please open issues or pull requests against the
|
|
2054
|
+
GitHub repository.
|
|
2055
|
+
|
|
2056
|
+
|
|
2057
|
+
## V2 Roadmap
|
|
2058
|
+
|
|
2059
|
+
The following features are ✅ **implemented in v2**. See [V2_ROADMAP.md](./V2_ROADMAP.md) for details.
|
|
2060
|
+
|
|
2061
|
+
| Feature | DSPy Equivalent | Status |
|
|
2062
|
+
|---|---|---|
|
|
2063
|
+
| **`TypedPredictor`** — JSON-schema + optional Zod validation | `dspy.TypedPredictor`, `dspy.TypedChainOfThought` | ✅ v2 |
|
|
2064
|
+
| **`Parallel`** module — fan-out / fan-in concurrency | `dspy.Parallel` | ✅ v2 |
|
|
2065
|
+
| **`Refine`** module — self-critique loop | — | ✅ v2 |
|
|
2066
|
+
| **`majority()`** helper — vote across Predictions | `dspy.majority` | ✅ v2 |
|
|
2067
|
+
| **`BootstrapFewShotWithOptuna`** — TPE Bayesian search | `dspy.BootstrapFewShotWithOptuna` | ✅ v2 |
|
|
2068
|
+
| **Disk-persistent LM cache** — file-based LRU | `dspy.cache` | ✅ v2 |
|
|
2069
|
+
| **MCP Integration** — `MCPToolAdapter` + `DSTsxMCPServer` | — | ✅ v2 |
|
|
2070
|
+
| **LM Streaming** — `lm.stream()`, `predict.stream()` | `dspy.streamify` | ✅ v2 |
|
|
2071
|
+
| **`NativeReAct`** — OpenAI functions / Anthropic tool use | `dspy.Tool` (v2) | ✅ v2 |
|
|
2072
|
+
| **`Image`** — multi-modal image inputs | `dspy.Image` | ✅ v2 |
|
|
2073
|
+
| **`BootstrapFinetune`** — JSONL fine-tuning export | `dspy.BootstrapFinetune` | ✅ v2 |
|
|
2074
|
+
| **`GRPO`** optimizer — group relative policy optimization | `dspy.GRPO` | ✅ v2 |
|
|
2075
|
+
| **`SIMBA`** optimizer — stochastic mini-batch ascent | `dspy.SIMBA` | ✅ v2 |
|
|
2076
|
+
| **`AvatarOptimizer`** — role/persona prompt optimization | `dspy.AvatarOptimizer` | ✅ v2 |
|
|
2077
|
+
| **Experiment Tracking** — `ConsoleTracker`, `JsonFileTracker` | `dspy.MLflow` | ✅ v2 |
|
|
2078
|
+
| **Worker-thread `ProgramOfThought`** — `sandbox: "worker"` | — | ✅ v2 |
|
|
2079
|
+
| **Typedoc config** — `typedoc.json` + `npm run docs` | — | ✅ v2 |
|
|
2080
|
+
| **GitHub Actions** — CI + npm publish workflows | — | ✅ v2 |
|
|
2081
|
+
| Cross-language trace sharing | — | 🔭 Stretch |
|
|
2082
|
+
| Browser-native bundle (`dstsx/browser`) | — | 🔭 Stretch |
|
|
2083
|
+
| HTTP module serving (REST endpoint) | — | 🔭 Stretch |
|
|
2084
|
+
|
|
2085
|
+
---
|
|
2086
|
+
|
|
2087
|
+
## License
|
|
2088
|
+
|
|
2089
|
+
MIT
|