@synova-cloud/sdk 1.6.0 → 1.8.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 CHANGED
@@ -174,6 +174,205 @@ if (response.type === 'image') {
174
174
  }
175
175
  ```
176
176
 
177
+ #### Structured Output (Typed Responses)
178
+
179
+ Get typed and validated responses from LLMs using JSON Schema.
180
+
181
+ First, install optional peer dependencies:
182
+ ```bash
183
+ npm install class-validator class-transformer
184
+ ```
185
+
186
+ Define a response class with decorators:
187
+ ```typescript
188
+ import { IsString, IsArray, IsNumber, Min, Max } from 'class-validator';
189
+ import { Description, Example, ArrayItems } from '@synova-cloud/sdk';
190
+
191
+ class TopicDto {
192
+ @IsString()
193
+ @Description('Article title for SEO')
194
+ @Example('10 Ways to Improve SQL Performance')
195
+ title: string;
196
+
197
+ @IsString()
198
+ @Description('Short description')
199
+ description: string;
200
+
201
+ @IsArray()
202
+ @ArrayItems(String)
203
+ @Description('SEO keywords')
204
+ keywords: string[];
205
+
206
+ @IsNumber()
207
+ @Min(1)
208
+ @Max(10)
209
+ @Description('Priority from 1 to 10')
210
+ priority: number;
211
+ }
212
+ ```
213
+
214
+ Execute with `responseClass` to get typed response:
215
+ ```typescript
216
+ const topic = await client.prompts.execute('prm_abc123', {
217
+ provider: 'openai',
218
+ model: 'gpt-4o',
219
+ responseClass: TopicDto,
220
+ });
221
+
222
+ // topic is typed as TopicDto
223
+ console.log(topic.title); // string
224
+ console.log(topic.keywords); // string[]
225
+ console.log(topic.priority); // number
226
+ ```
227
+
228
+ Works with `executeByTag` and `executeByVersion` too:
229
+ ```typescript
230
+ const topic = await client.prompts.executeByTag('prm_abc123', 'production', {
231
+ provider: 'openai',
232
+ model: 'gpt-4o',
233
+ responseClass: TopicDto,
234
+ });
235
+ ```
236
+
237
+ Disable validation if needed:
238
+ ```typescript
239
+ const topic = await client.prompts.execute('prm_abc123', {
240
+ provider: 'openai',
241
+ model: 'gpt-4o',
242
+ responseClass: TopicDto,
243
+ validate: false, // Skip class-validator validation
244
+ });
245
+ ```
246
+
247
+ **Available Schema Decorators:**
248
+
249
+ | Decorator | Description |
250
+ |-----------|-------------|
251
+ | `@Description(text)` | Adds description to help LLM |
252
+ | `@Example(...values)` | Adds example values |
253
+ | `@Default(value)` | Sets default value |
254
+ | `@ArrayItems(Type)` | Sets array item type |
255
+ | `@Format(format)` | Sets string format (email, uri, uuid, date-time) |
256
+ | `@Nullable()` | Marks as nullable |
257
+ | `@SchemaMin(n)` | Minimum number value |
258
+ | `@SchemaMax(n)` | Maximum number value |
259
+ | `@SchemaMinLength(n)` | Minimum string length |
260
+ | `@SchemaMaxLength(n)` | Maximum string length |
261
+ | `@SchemaPattern(regex)` | Regex pattern for string |
262
+ | `@SchemaMinItems(n)` | Minimum array length |
263
+ | `@SchemaMaxItems(n)` | Maximum array length |
264
+ | `@SchemaEnum(values)` | Allowed enum values |
265
+
266
+ ### Observability
267
+
268
+ Track and group your LLM calls using traces and spans. Each execution creates a span, and multiple spans can be grouped into a trace using `sessionId`.
269
+
270
+ #### Session-Based Tracing
271
+
272
+ Use `sessionId` to group related calls (e.g., a conversation) into a single trace:
273
+
274
+ ```typescript
275
+ const sessionId = 'chat_user123_conv1';
276
+
277
+ // First message - creates new trace
278
+ const response1 = await client.prompts.execute('prm_abc123', {
279
+ provider: 'openai',
280
+ model: 'gpt-4o',
281
+ sessionId,
282
+ variables: { topic: 'TypeScript' },
283
+ });
284
+
285
+ console.log(response1.traceId); // trc_xxx
286
+ console.log(response1.spanId); // spn_xxx
287
+
288
+ // Follow-up - same sessionId = same trace, new span
289
+ const response2 = await client.prompts.execute('prm_abc123', {
290
+ provider: 'openai',
291
+ model: 'gpt-4o',
292
+ sessionId,
293
+ messages: [
294
+ { role: 'assistant', content: response1.content },
295
+ { role: 'user', content: 'Tell me more' },
296
+ ],
297
+ });
298
+
299
+ // response2.traceId === response1.traceId (same trace)
300
+ // response2.spanId !== response1.spanId (new span)
301
+ ```
302
+
303
+ #### Response Properties
304
+
305
+ Every execution returns observability IDs:
306
+
307
+ | Property | Type | Description |
308
+ |----------|------|-------------|
309
+ | `spanDataId` | `string` | Execution data ID (messages, response, usage) |
310
+ | `traceId` | `string` | Trace ID (groups related calls) |
311
+ | `spanId` | `string` | Span ID (this specific call) |
312
+
313
+ #### Custom Span Tracking
314
+
315
+ Track tool calls, retrieval operations, and custom logic as spans within a trace.
316
+
317
+ **Manual approach:**
318
+
319
+ ```typescript
320
+ // Create span
321
+ const span = await client.spans.create(traceId, {
322
+ type: 'tool',
323
+ toolName: 'fetch_weather',
324
+ toolArguments: { city: 'NYC' },
325
+ parentSpanId: generationSpanId,
326
+ });
327
+
328
+ // Execute
329
+ const weather = await fetchWeather('NYC');
330
+
331
+ // End span
332
+ await client.spans.end(span.id, {
333
+ status: 'completed',
334
+ toolResult: weather,
335
+ });
336
+ ```
337
+
338
+ **Wrapper approach:**
339
+
340
+ ```typescript
341
+ // wrapTool() - for tools
342
+ const weather = await client.spans.wrapTool(
343
+ { traceId, toolName: 'fetch_weather', parentSpanId },
344
+ { city: 'NYC' },
345
+ async (args) => fetchWeather(args.city),
346
+ );
347
+
348
+ // wrap() - for custom/retriever/embedding
349
+ const docs = await client.spans.wrap(
350
+ { traceId, type: 'retriever', name: 'vector_search' },
351
+ { query: 'how to...', topK: 5 },
352
+ async () => vectorDb.search(query),
353
+ );
354
+ ```
355
+
356
+ Wrappers automatically handle errors and set `status: 'error'` with message.
357
+
358
+ #### Span Types
359
+
360
+ | Type | Use Case |
361
+ |------|----------|
362
+ | `generation` | LLM calls (auto-created by `execute()`) |
363
+ | `tool` | Tool/function calls |
364
+ | `retriever` | RAG document retrieval |
365
+ | `embedding` | Embedding generation |
366
+ | `custom` | Any custom operation |
367
+
368
+ #### Viewing Traces
369
+
370
+ View your traces in the [Synova Cloud Dashboard](https://app.synova.cloud) under the Observability section. Each trace shows:
371
+ - All spans (LLM calls) in the session
372
+ - Input/output for each span
373
+ - Token usage and latency
374
+ - Error details if any
375
+
177
376
  ### Models
178
377
 
179
378
  #### List All Models
@@ -268,28 +467,48 @@ console.log(response.content);
268
467
 
269
468
  ## Error Handling
270
469
 
271
- The SDK provides typed errors for different failure scenarios. All API errors extend `ApiSynovaError` and include full error details:
470
+ The SDK provides typed errors for different failure scenarios:
272
471
 
273
472
  ```typescript
274
473
  import {
275
474
  SynovaCloudSdk,
276
- SynovaError,
277
- ApiSynovaError,
475
+ ExecutionSynovaError,
476
+ ValidationSynovaError,
278
477
  AuthSynovaError,
279
478
  NotFoundSynovaError,
280
479
  RateLimitSynovaError,
281
480
  ServerSynovaError,
282
481
  TimeoutSynovaError,
283
482
  NetworkSynovaError,
483
+ ApiSynovaError,
284
484
  } from '@synova-cloud/sdk';
285
485
 
286
486
  try {
287
487
  const response = await client.prompts.execute('prm_abc123', {
288
488
  provider: 'openai',
289
489
  model: 'gpt-4o',
290
- variables: { name: 'World' },
490
+ responseClass: TopicDto,
291
491
  });
292
492
  } catch (error) {
493
+ // LLM execution error (rate limit, invalid key, context too long, etc.)
494
+ if (error instanceof ExecutionSynovaError) {
495
+ console.error(`LLM error [${error.code}]: ${error.message}`);
496
+ console.error(`Provider: ${error.provider}`);
497
+ console.error(`Retryable: ${error.retryable}`);
498
+ if (error.retryAfterMs) {
499
+ console.error(`Retry after: ${error.retryAfterMs}ms`);
500
+ }
501
+ }
502
+
503
+ // Validation error (response doesn't match class-validator constraints)
504
+ if (error instanceof ValidationSynovaError) {
505
+ console.error('Validation failed:');
506
+ for (const v of error.violations) {
507
+ console.error(` ${v.property}: ${Object.values(v.constraints).join(', ')}`);
508
+ }
509
+ }
510
+
511
+ // API errors
293
512
  if (error instanceof AuthSynovaError) {
294
513
  console.error('Invalid API key');
295
514
  } else if (error instanceof NotFoundSynovaError) {
@@ -303,10 +522,8 @@ try {
303
522
  } else if (error instanceof NetworkSynovaError) {
304
523
  console.error(`Network error: ${error.message}`);
305
524
  } else if (error instanceof ApiSynovaError) {
306
- // All API errors have these properties:
307
525
  console.error(`API error [${error.code}]: ${error.message}`);
308
526
  console.error(`Request ID: ${error.requestId}`);
309
- console.error(`Details:`, error.details);
310
527
  }
311
528
  }
312
529
  ```
@@ -412,14 +629,25 @@ import type {
412
629
  ISynovaPromptVariable,
413
630
  ISynovaGetPromptOptions,
414
631
  // Execution
415
- ISynovaExecuteOptions,
416
- ISynovaExecuteResponse,
417
- ISynovaUsage,
632
+ ISynovaExecuteOptions, // includes sessionId
633
+ ISynovaExecuteTypedOptions,
634
+ ISynovaExecuteResponse, // includes spanDataId, traceId, spanId
635
+ ISynovaExecutionUsage,
418
636
  ISynovaExecutionError,
419
637
  // Messages
420
638
  ISynovaMessage,
421
639
  TSynovaMessageRole,
422
640
  TSynovaResponseType,
641
+ // Spans
642
+ ISynovaSpan,
643
+ ISynovaSpanData,
644
+ ISynovaCreateSpanOptions,
645
+ ISynovaEndSpanOptions,
646
+ ISynovaWrapOptions,
647
+ ISynovaWrapToolOptions,
648
+ TSynovaSpanType,
649
+ TSynovaSpanStatus,
650
+ TSynovaSpanLevel,
423
651
  // Files
424
652
  ISynovaFileAttachment,
425
653
  ISynovaFileThumbnails,
@@ -435,6 +663,13 @@ import type {
435
663
  ISynovaModelsResponse,
436
664
  ISynovaListModelsOptions,
437
665
  TSynovaModelType,
666
+ // Schema
667
+ IJsonSchema,
668
+ TJsonSchemaType,
669
+ TJsonSchemaFormat,
670
+ TClassConstructor,
671
+ // Errors
672
+ IValidationViolation,
438
673
  } from '@synova-cloud/sdk';
439
674
  ```
440
675
 
@@ -452,6 +687,15 @@ const client = new SynovaCloudSdk('your-api-key');
452
687
 
453
688
  - Node.js 18+ (uses native `fetch`)
454
689
 
690
+ ### Optional Peer Dependencies
691
+
692
+ For structured output with typed responses:
693
+ ```bash
694
+ npm install class-validator class-transformer
695
+ ```
696
+
697
+ These are optional - the SDK works without them, but `responseClass` feature requires them.
698
+
455
699
  ## License
456
700
 
457
701
  MIT