@modelrelay/sdk 1.3.3 → 1.10.3
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 +263 -141
- package/dist/index.cjs +3476 -1008
- package/dist/index.d.cts +2199 -321
- package/dist/index.d.ts +2199 -321
- package/dist/index.js +3441 -1008
- package/package.json +15 -3
package/README.md
CHANGED
|
@@ -1,40 +1,220 @@
|
|
|
1
1
|
# ModelRelay TypeScript SDK
|
|
2
2
|
|
|
3
|
-
The ModelRelay TypeScript SDK is a **responses-first**, **streaming-first** client for building cross-provider LLM features without committing to any single vendor API.
|
|
4
|
-
|
|
5
|
-
It's designed to feel great in TypeScript:
|
|
6
|
-
- One fluent builder for **streaming/non-streaming**, **text/structured**, and **customer-attributed** requests
|
|
7
|
-
- Structured outputs powered by Zod schemas with validation and retry
|
|
8
|
-
- A practical tool-use toolkit for "LLM + tools" apps
|
|
9
|
-
|
|
10
3
|
```bash
|
|
11
|
-
npm install @modelrelay/sdk
|
|
12
|
-
# or
|
|
13
4
|
bun add @modelrelay/sdk
|
|
14
5
|
```
|
|
15
6
|
|
|
16
|
-
##
|
|
7
|
+
## Token Providers (Automatic Bearer Auth)
|
|
8
|
+
|
|
9
|
+
Use token providers when you want the SDK to automatically obtain/refresh **bearer tokens** for data-plane calls like `/responses` and `/runs`.
|
|
10
|
+
|
|
11
|
+
### OIDC id_token → customer bearer token (exchange)
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { ModelRelay, OIDCExchangeTokenProvider, parseSecretKey } from "@modelrelay/sdk";
|
|
15
|
+
|
|
16
|
+
const tokenProvider = new OIDCExchangeTokenProvider({
|
|
17
|
+
apiKey: parseSecretKey(process.env.MODELRELAY_API_KEY!),
|
|
18
|
+
idTokenProvider: async () => {
|
|
19
|
+
// Return an OIDC id_token from your auth system (web login, device flow, etc).
|
|
20
|
+
return process.env.OIDC_ID_TOKEN!;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const mr = new ModelRelay({ tokenProvider });
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If you need an `id_token` in a CLI-like context, you can use the OAuth device flow helper:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { runOAuthDeviceFlowForIDToken } from "@modelrelay/sdk";
|
|
31
|
+
|
|
32
|
+
const idToken = await runOAuthDeviceFlowForIDToken({
|
|
33
|
+
deviceAuthorizationEndpoint: "https://issuer.example.com/oauth/device/code",
|
|
34
|
+
tokenEndpoint: "https://issuer.example.com/oauth/token",
|
|
35
|
+
clientId: "your-client-id",
|
|
36
|
+
scope: "openid email profile",
|
|
37
|
+
onUserCode: ({ verificationUri, userCode }) => {
|
|
38
|
+
console.log(`Open ${verificationUri} and enter code: ${userCode}`);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Secret key → customer bearer token (mint)
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { CustomerTokenProvider, ModelRelay } from "@modelrelay/sdk";
|
|
47
|
+
|
|
48
|
+
const tokenProvider = new CustomerTokenProvider({
|
|
49
|
+
secretKey: process.env.MODELRELAY_API_KEY!,
|
|
50
|
+
request: { customerId: "customer_..." },
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const mr = new ModelRelay({ tokenProvider });
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Streaming Responses
|
|
17
57
|
|
|
18
58
|
```ts
|
|
19
59
|
import { ModelRelay } from "@modelrelay/sdk";
|
|
20
60
|
|
|
21
|
-
const mr = ModelRelay.fromSecretKey(
|
|
61
|
+
const mr = ModelRelay.fromSecretKey("mr_sk_...");
|
|
62
|
+
|
|
63
|
+
const req = mr.responses
|
|
64
|
+
.new()
|
|
65
|
+
.model("claude-sonnet-4-20250514")
|
|
66
|
+
.user("Hello")
|
|
67
|
+
.build();
|
|
22
68
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
.
|
|
28
|
-
|
|
29
|
-
|
|
69
|
+
const stream = await mr.responses.stream(req);
|
|
70
|
+
|
|
71
|
+
for await (const event of stream) {
|
|
72
|
+
if (event.type === "message_delta" && event.textDelta) {
|
|
73
|
+
process.stdout.write(event.textDelta);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Customer-Scoped Convenience
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { ModelRelay } from "@modelrelay/sdk";
|
|
82
|
+
|
|
83
|
+
const mr = ModelRelay.fromSecretKey("mr_sk_...");
|
|
84
|
+
const customer = mr.forCustomer("customer_abc123");
|
|
85
|
+
|
|
86
|
+
const text = await customer.responses.text(
|
|
87
|
+
"You are a helpful assistant.",
|
|
88
|
+
"Summarize Q4 results",
|
|
30
89
|
);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
You can also stream structured JSON for a specific customer:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { z } from "zod";
|
|
96
|
+
import { ModelRelay, outputFormatFromZod } from "@modelrelay/sdk";
|
|
31
97
|
|
|
32
|
-
|
|
98
|
+
const mr = ModelRelay.fromSecretKey("mr_sk_...");
|
|
99
|
+
const customer = mr.forCustomer("customer_abc123");
|
|
100
|
+
|
|
101
|
+
const schema = z.object({
|
|
102
|
+
summary: z.string(),
|
|
103
|
+
highlights: z.array(z.string()),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const req = customer.responses
|
|
107
|
+
.new()
|
|
108
|
+
.outputFormat(outputFormatFromZod(schema))
|
|
109
|
+
.system("You are a helpful assistant.")
|
|
110
|
+
.user("Summarize Q4 results")
|
|
111
|
+
.build();
|
|
112
|
+
|
|
113
|
+
const stream = await customer.responses.streamJSON<z.infer<typeof schema>>(req);
|
|
114
|
+
for await (const event of stream) {
|
|
115
|
+
if (event.type === "completion") {
|
|
116
|
+
console.log(event.payload);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
You can also pass a single object to `textForCustomer`:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const text = await mr.responses.textForCustomer({
|
|
125
|
+
customerId: "customer_abc123",
|
|
126
|
+
system: "You are a helpful assistant.",
|
|
127
|
+
user: "Summarize Q4 results",
|
|
128
|
+
});
|
|
33
129
|
```
|
|
34
130
|
|
|
131
|
+
## Workflow Runs (workflow.v0)
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import {
|
|
135
|
+
ModelRelay,
|
|
136
|
+
type LLMResponsesBindingV0,
|
|
137
|
+
parseNodeId,
|
|
138
|
+
parseOutputName,
|
|
139
|
+
parseSecretKey,
|
|
140
|
+
workflowV0,
|
|
141
|
+
} from "@modelrelay/sdk";
|
|
142
|
+
|
|
143
|
+
const mr = new ModelRelay({ key: parseSecretKey("mr_sk_...") });
|
|
144
|
+
|
|
145
|
+
const spec = workflowV0()
|
|
146
|
+
.name("multi_agent_v0_example")
|
|
147
|
+
.execution({ max_parallelism: 3, node_timeout_ms: 20_000, run_timeout_ms: 30_000 })
|
|
148
|
+
.llmResponses(parseNodeId("agent_a"), {
|
|
149
|
+
model: "claude-sonnet-4-20250514",
|
|
150
|
+
input: [
|
|
151
|
+
{ type: "message", role: "system", content: [{ type: "text", text: "You are Agent A." }] },
|
|
152
|
+
{ type: "message", role: "user", content: [{ type: "text", text: "Write 3 ideas for a landing page." }] },
|
|
153
|
+
],
|
|
154
|
+
})
|
|
155
|
+
.llmResponses(parseNodeId("agent_b"), {
|
|
156
|
+
model: "claude-sonnet-4-20250514",
|
|
157
|
+
input: [
|
|
158
|
+
{ type: "message", role: "system", content: [{ type: "text", text: "You are Agent B." }] },
|
|
159
|
+
{ type: "message", role: "user", content: [{ type: "text", text: "Write 3 objections a user might have." }] },
|
|
160
|
+
],
|
|
161
|
+
})
|
|
162
|
+
.llmResponses(parseNodeId("agent_c"), {
|
|
163
|
+
model: "claude-sonnet-4-20250514",
|
|
164
|
+
input: [
|
|
165
|
+
{ type: "message", role: "system", content: [{ type: "text", text: "You are Agent C." }] },
|
|
166
|
+
{ type: "message", role: "user", content: [{ type: "text", text: "Write 3 alternative headlines." }] },
|
|
167
|
+
],
|
|
168
|
+
})
|
|
169
|
+
.joinAll(parseNodeId("join"))
|
|
170
|
+
.llmResponses(
|
|
171
|
+
parseNodeId("aggregate"),
|
|
172
|
+
{
|
|
173
|
+
model: "claude-sonnet-4-20250514",
|
|
174
|
+
input: [
|
|
175
|
+
{
|
|
176
|
+
type: "message",
|
|
177
|
+
role: "system",
|
|
178
|
+
content: [{ type: "text", text: "Synthesize the best answer from the following agent outputs (JSON)." }],
|
|
179
|
+
},
|
|
180
|
+
{ type: "message", role: "user", content: [{ type: "text", text: "" }] }, // overwritten by bindings
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
// Bind the join output into the aggregator prompt (fan-in).
|
|
185
|
+
bindings: [
|
|
186
|
+
{
|
|
187
|
+
from: parseNodeId("join"),
|
|
188
|
+
to: "/input/1/content/0/text",
|
|
189
|
+
encoding: "json_string",
|
|
190
|
+
} satisfies LLMResponsesBindingV0,
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
)
|
|
194
|
+
.edge(parseNodeId("agent_a"), parseNodeId("join"))
|
|
195
|
+
.edge(parseNodeId("agent_b"), parseNodeId("join"))
|
|
196
|
+
.edge(parseNodeId("agent_c"), parseNodeId("join"))
|
|
197
|
+
.edge(parseNodeId("join"), parseNodeId("aggregate"))
|
|
198
|
+
.output(parseOutputName("result"), parseNodeId("aggregate"))
|
|
199
|
+
.build();
|
|
200
|
+
|
|
201
|
+
const { run_id } = await mr.runs.create(spec);
|
|
202
|
+
|
|
203
|
+
const events = await mr.runs.events(run_id);
|
|
204
|
+
for await (const ev of events) {
|
|
205
|
+
if (ev.type === "run_completed") {
|
|
206
|
+
const status = await mr.runs.get(run_id);
|
|
207
|
+
console.log("outputs:", status.outputs);
|
|
208
|
+
console.log("cost_summary:", status.cost_summary);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
See the full example in `sdk/ts/examples/workflows_multi_agent.ts`.
|
|
214
|
+
|
|
35
215
|
## Chat-Like Text Helpers
|
|
36
216
|
|
|
37
|
-
For the most common path (**system + user → assistant text**)
|
|
217
|
+
For the most common path (**system + user → assistant text**):
|
|
38
218
|
|
|
39
219
|
```ts
|
|
40
220
|
const text = await mr.responses.text(
|
|
@@ -48,16 +228,14 @@ console.log(text);
|
|
|
48
228
|
For customer-attributed requests where the backend selects the model:
|
|
49
229
|
|
|
50
230
|
```ts
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
"
|
|
231
|
+
const text = await mr.responses.textForCustomer(
|
|
232
|
+
"customer-123",
|
|
233
|
+
"Answer concisely.",
|
|
234
|
+
"Say hi.",
|
|
55
235
|
);
|
|
56
236
|
```
|
|
57
237
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
Stream text deltas for real-time output:
|
|
238
|
+
To stream only message text deltas:
|
|
61
239
|
|
|
62
240
|
```ts
|
|
63
241
|
const deltas = await mr.responses.streamTextDeltas(
|
|
@@ -70,31 +248,14 @@ for await (const delta of deltas) {
|
|
|
70
248
|
}
|
|
71
249
|
```
|
|
72
250
|
|
|
73
|
-
For full control, stream typed events:
|
|
74
|
-
|
|
75
|
-
```ts
|
|
76
|
-
const req = mr.responses
|
|
77
|
-
.new()
|
|
78
|
-
.model("claude-sonnet-4-20250514")
|
|
79
|
-
.user("Hello")
|
|
80
|
-
.build();
|
|
81
|
-
|
|
82
|
-
const stream = await mr.responses.stream(req);
|
|
83
|
-
|
|
84
|
-
for await (const event of stream) {
|
|
85
|
-
if (event.type === "message_delta" && event.textDelta) {
|
|
86
|
-
process.stdout.write(event.textDelta);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
251
|
## Structured Outputs with Zod
|
|
92
252
|
|
|
93
|
-
Get typed, validated responses from the model:
|
|
94
|
-
|
|
95
253
|
```ts
|
|
254
|
+
import { ModelRelay, parseSecretKey } from "@modelrelay/sdk";
|
|
96
255
|
import { z } from "zod";
|
|
97
256
|
|
|
257
|
+
const mr = new ModelRelay({ key: parseSecretKey("mr_sk_...") });
|
|
258
|
+
|
|
98
259
|
const Person = z.object({
|
|
99
260
|
name: z.string(),
|
|
100
261
|
age: z.number(),
|
|
@@ -109,11 +270,16 @@ const result = await mr.responses.structured(
|
|
|
109
270
|
console.log(result.value); // { name: "John Doe", age: 30 }
|
|
110
271
|
```
|
|
111
272
|
|
|
112
|
-
|
|
273
|
+
## Streaming Structured Outputs
|
|
113
274
|
|
|
114
275
|
Build progressive UIs that render fields as they complete:
|
|
115
276
|
|
|
116
277
|
```ts
|
|
278
|
+
import { ModelRelay, parseSecretKey } from "@modelrelay/sdk";
|
|
279
|
+
import { z } from "zod";
|
|
280
|
+
|
|
281
|
+
const mr = new ModelRelay({ key: parseSecretKey("mr_sk_...") });
|
|
282
|
+
|
|
117
283
|
const Article = z.object({
|
|
118
284
|
title: z.string(),
|
|
119
285
|
summary: z.string(),
|
|
@@ -126,12 +292,15 @@ const stream = await mr.responses.streamStructured(
|
|
|
126
292
|
);
|
|
127
293
|
|
|
128
294
|
for await (const event of stream) {
|
|
295
|
+
// Render fields as soon as they're complete
|
|
129
296
|
if (event.completeFields.has("title")) {
|
|
130
297
|
renderTitle(event.payload.title); // Safe to display
|
|
131
298
|
}
|
|
132
299
|
if (event.completeFields.has("summary")) {
|
|
133
300
|
renderSummary(event.payload.summary);
|
|
134
301
|
}
|
|
302
|
+
|
|
303
|
+
// Show streaming preview of incomplete fields
|
|
135
304
|
if (!event.completeFields.has("body")) {
|
|
136
305
|
renderBodyPreview(event.payload.body + "▋");
|
|
137
306
|
}
|
|
@@ -140,7 +309,7 @@ for await (const event of stream) {
|
|
|
140
309
|
|
|
141
310
|
## Customer-Attributed Requests
|
|
142
311
|
|
|
143
|
-
For metered billing, use `customerId()
|
|
312
|
+
For metered billing, use `customerId()` — the customer's subscription tier determines the model and `model` can be omitted:
|
|
144
313
|
|
|
145
314
|
```ts
|
|
146
315
|
const req = mr.responses
|
|
@@ -152,122 +321,64 @@ const req = mr.responses
|
|
|
152
321
|
const stream = await mr.responses.stream(req);
|
|
153
322
|
```
|
|
154
323
|
|
|
155
|
-
Or use the convenience method:
|
|
156
|
-
|
|
157
|
-
```ts
|
|
158
|
-
const text = await mr.responses.textForCustomer(
|
|
159
|
-
"customer-123",
|
|
160
|
-
"Answer concisely.",
|
|
161
|
-
"Say hi.",
|
|
162
|
-
);
|
|
163
|
-
```
|
|
164
|
-
|
|
165
324
|
## Customer Management (Backend)
|
|
166
325
|
|
|
167
326
|
```ts
|
|
168
327
|
// Create/update customer
|
|
169
328
|
const customer = await mr.customers.upsert({
|
|
170
|
-
tier_id: "tier-uuid",
|
|
171
329
|
external_id: "your-user-id",
|
|
172
330
|
email: "user@example.com",
|
|
173
331
|
});
|
|
174
332
|
|
|
175
333
|
// Create checkout session for subscription billing
|
|
176
|
-
const session = await mr.customers.
|
|
334
|
+
const session = await mr.customers.subscribe(customer.customer.id, {
|
|
335
|
+
tier_id: "tier-uuid",
|
|
177
336
|
success_url: "https://myapp.com/success",
|
|
178
337
|
cancel_url: "https://myapp.com/cancel",
|
|
179
338
|
});
|
|
180
339
|
|
|
181
340
|
// Check subscription status
|
|
182
|
-
const status = await mr.customers.getSubscription(customer.id);
|
|
341
|
+
const status = await mr.customers.getSubscription(customer.customer.id);
|
|
183
342
|
```
|
|
184
343
|
|
|
185
|
-
##
|
|
344
|
+
## Error Handling
|
|
186
345
|
|
|
187
|
-
|
|
346
|
+
Errors are typed so callers can branch cleanly:
|
|
188
347
|
|
|
189
348
|
```ts
|
|
190
|
-
import {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
{
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
{
|
|
220
|
-
bindings: [
|
|
221
|
-
{ from: parseNodeId("join"), to: "/input/1/content/0/text", encoding: "json_string" } satisfies LLMResponsesBindingV0,
|
|
222
|
-
],
|
|
223
|
-
},
|
|
224
|
-
)
|
|
225
|
-
.edge(parseNodeId("agent_a"), parseNodeId("join"))
|
|
226
|
-
.edge(parseNodeId("agent_b"), parseNodeId("join"))
|
|
227
|
-
.edge(parseNodeId("join"), parseNodeId("aggregate"))
|
|
228
|
-
.output(parseOutputName("result"), parseNodeId("aggregate"))
|
|
229
|
-
.build();
|
|
230
|
-
|
|
231
|
-
const { run_id } = await mr.runs.create(spec);
|
|
232
|
-
|
|
233
|
-
for await (const ev of await mr.runs.events(run_id)) {
|
|
234
|
-
if (ev.type === "run_completed") {
|
|
235
|
-
const status = await mr.runs.get(run_id);
|
|
236
|
-
console.log("outputs:", status.outputs);
|
|
349
|
+
import {
|
|
350
|
+
ModelRelay,
|
|
351
|
+
APIError,
|
|
352
|
+
TransportError,
|
|
353
|
+
StreamTimeoutError,
|
|
354
|
+
ConfigError,
|
|
355
|
+
} from "@modelrelay/sdk";
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
const response = await mr.responses.text(
|
|
359
|
+
"claude-sonnet-4-20250514",
|
|
360
|
+
"You are helpful.",
|
|
361
|
+
"Hello!"
|
|
362
|
+
);
|
|
363
|
+
} catch (error) {
|
|
364
|
+
if (error instanceof APIError) {
|
|
365
|
+
console.log("Status:", error.status);
|
|
366
|
+
console.log("Code:", error.code);
|
|
367
|
+
console.log("Message:", error.message);
|
|
368
|
+
|
|
369
|
+
if (error.isRateLimit()) {
|
|
370
|
+
// Back off and retry
|
|
371
|
+
} else if (error.isUnauthorized()) {
|
|
372
|
+
// Re-authenticate
|
|
373
|
+
}
|
|
374
|
+
} else if (error instanceof TransportError) {
|
|
375
|
+
console.log("Network error:", error.message);
|
|
376
|
+
} else if (error instanceof StreamTimeoutError) {
|
|
377
|
+
console.log("Stream timeout:", error.streamKind); // "ttft" | "idle" | "total"
|
|
237
378
|
}
|
|
238
379
|
}
|
|
239
380
|
```
|
|
240
381
|
|
|
241
|
-
## Token Providers (Advanced)
|
|
242
|
-
|
|
243
|
-
For automatic bearer token management in data-plane calls:
|
|
244
|
-
|
|
245
|
-
### Secret key → customer bearer token
|
|
246
|
-
|
|
247
|
-
```ts
|
|
248
|
-
import { CustomerTokenProvider, ModelRelay } from "@modelrelay/sdk";
|
|
249
|
-
|
|
250
|
-
const tokenProvider = new CustomerTokenProvider({
|
|
251
|
-
secretKey: process.env.MODELRELAY_API_KEY!,
|
|
252
|
-
request: { projectId: "proj_...", customerId: "cust_..." },
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
const mr = new ModelRelay({ tokenProvider });
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
### OIDC exchange
|
|
259
|
-
|
|
260
|
-
```ts
|
|
261
|
-
import { ModelRelay, OIDCExchangeTokenProvider, parseSecretKey } from "@modelrelay/sdk";
|
|
262
|
-
|
|
263
|
-
const tokenProvider = new OIDCExchangeTokenProvider({
|
|
264
|
-
apiKey: parseSecretKey(process.env.MODELRELAY_API_KEY!),
|
|
265
|
-
idTokenProvider: async () => process.env.OIDC_ID_TOKEN!,
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
const mr = new ModelRelay({ tokenProvider });
|
|
269
|
-
```
|
|
270
|
-
|
|
271
382
|
## Configuration
|
|
272
383
|
|
|
273
384
|
```ts
|
|
@@ -278,3 +389,14 @@ const mr = new ModelRelay({
|
|
|
278
389
|
retry: { maxAttempts: 3 },
|
|
279
390
|
});
|
|
280
391
|
```
|
|
392
|
+
|
|
393
|
+
## Documentation
|
|
394
|
+
|
|
395
|
+
For detailed guides and API reference, visit [docs.modelrelay.ai](https://docs.modelrelay.ai):
|
|
396
|
+
|
|
397
|
+
- [First Request](https://docs.modelrelay.ai/getting-started/first-request) — Make your first API call
|
|
398
|
+
- [Streaming](https://docs.modelrelay.ai/guides/streaming) — Real-time response streaming
|
|
399
|
+
- [Structured Output](https://docs.modelrelay.ai/guides/structured-output) — Get typed JSON responses
|
|
400
|
+
- [Tool Use](https://docs.modelrelay.ai/guides/tools) — Let models call functions
|
|
401
|
+
- [Error Handling](https://docs.modelrelay.ai/guides/error-handling) — Handle errors gracefully
|
|
402
|
+
- [Workflows](https://docs.modelrelay.ai/guides/workflows) — Multi-step AI pipelines
|