npm package: @sentygent/sdk
Description: Sentygent SDK for AI agent observability
Latest version: 0.1.1

# @sentygent/sdk

TypeScript SDK for [Sentygent](https://sentygent.com) — AI Observability & Evaluation Platform.

Instrument your AI agents and pipelines to capture LLM calls, tool executions, RAG retrievals, and conversation lifecycle events. Works with **any LLM provider**.

## Installation

```bash
npm install @sentygent/sdk
```

### Peer Dependencies (optional)

Install only the SDKs you use for auto-instrumentation:

```bash
# For Anthropic auto-instrumentation
npm install @anthropic-ai/sdk

# For AWS Bedrock auto-instrumentation
npm install @aws-sdk/client-bedrock-runtime
```

## Quick Start

```typescript
import { SentygentClient } from '@sentygent/sdk';

const sentygent = new SentygentClient({
  apiKey: process.env.SENTYGENT_API_KEY!,
  agent: 'my-agent',
});

await sentygent.trace('conversation-123', async (span) => {
  span.captureLifecycle('conversation_start');
  // Your agent logic here...
  span.captureLifecycle('conversation_end');
});
```

---

## Provider Support

Sentygent works with any LLM provider. The integration method depends on your provider:

| Provider | Method | What you get |
|----------|--------|-------------|
| **Anthropic** | `instrumentAnthropic()` — zero-code | Automatic capture of all `messages.create` and `messages.stream` calls |
| **AWS Bedrock** | `instrumentBedrock()` — zero-code | Automatic capture of `ConverseCommand`, `InvokeModelCommand` + streaming variants |
| **OpenAI** | `span.captureLLM()` — typed helper | You provide `execute` + `extractUsage`. SDK handles timing, errors, event structure |
| **Cohere, Mistral, Groq, Together, etc.** | `span.captureLLM()` — typed helper | Same as OpenAI — works with any provider that returns token counts |
| **Ollama, vLLM, local models** | `span.captureLLM()` — typed helper | `extractUsage` can return `null` if provider doesn't expose tokens. Timing still captured |

### Auto-Instrumentation: Anthropic

Wrap the Anthropic client once. All LLM calls are captured automatically — model, tokens, cost, latency.

```typescript
import Anthropic from '@anthropic-ai/sdk';
import { SentygentClient, instrumentAnthropic } from '@sentygent/sdk';

const sentygent = new SentygentClient({
  apiKey: process.env.SENTYGENT_API_KEY!,
  agent: 'my-agent',
});

const anthropic = instrumentAnthropic(new Anthropic(), sentygent);

// Use as normal — all calls are captured automatically
await sentygent.trace('conv-123', async () => {
  const message = await anthropic.messages.create({
    model: 'claude-sonnet-4-20250514',
    max_tokens: 1024,
    messages: [{ role: 'user', content: 'Hello!' }],
  });
});
```

Streaming is also supported:

```typescript
await sentygent.trace('conv-123', async () => {
  const stream = anthropic.messages.stream({
    model: 'claude-sonnet-4-20250514',
    max_tokens: 1024,
    messages: [{ role: 'user', content: 'Tell me a story' }],
  });

  for await (const event of stream) {
    // Process stream events...
  }

  const message = await stream.finalMessage();
});
```

### Auto-Instrumentation: AWS Bedrock

```typescript
import { BedrockRuntimeClient, ConverseCommand } from '@aws-sdk/client-bedrock-runtime';
import { SentygentClient, instrumentBedrock } from '@sentygent/sdk';

const sentygent = new SentygentClient({
  apiKey: process.env.SENTYGENT_API_KEY!,
  agent: 'my-agent',
});

const bedrock = instrumentBedrock(
  new BedrockRuntimeClient({ region: 'eu-west-1' }),
  sentygent,
);

await sentygent.trace('conv-123', async () => {
  await bedrock.send(
    new ConverseCommand({
      modelId: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
      messages: [{ role: 'user', content: [{ text: 'Hello!' }] }],
    }),
  );
});
```

### Typed Helper: Any Provider (OpenAI, Cohere, Mistral, Groq, etc.)

Use `span.captureLLM()` to capture calls to any LLM provider. You provide the execute function and a callback to extract token usage.

```typescript
import OpenAI from 'openai';

const openai = new OpenAI();

await sentygent.trace('conv-123', async (span) => {
  span.captureLifecycle('conversation_start');

  const result = await span.captureLLM({
    provider: 'openai',
    model: 'gpt-4o',
    execute: () => openai.chat.completions.create({
      model: 'gpt-4o',
      messages: [{ role: 'user', content: 'Hello!' }],
    }),
    extractUsage: (r) => ({
      promptTokens: r.usage?.prompt_tokens ?? 0,
      completionTokens: r.usage?.completion_tokens ?? 0,
    }),
    cost: 0.003, // Optional: pre-computed cost in euros (overrides pricing table)
  });

  span.captureLifecycle('conversation_end');
});
```

**Provider values:** `'anthropic'`, `'bedrock'`, `'openai'`, or `'other'` for any unlisted provider.

**If your provider doesn't expose token counts** (e.g. some local models), return `null` from `extractUsage`:

```typescript
await span.captureLLM({
  provider: 'other',
  model: 'llama-3-70b',
  execute: () => ollama.chat({ model: 'llama3', messages }),
  extractUsage: () => null, // Tokens unavailable — timing still captured
});
```

---

## Capturing Tool Calls

```typescript
await span.captureTool({
  name: 'web_search',
  input: { query: 'latest news' },
  execute: () => searchAPI.search('latest news'),
  tags: { step: 'search' }, // Optional dimensional tags
});
```

## Capturing RAG / Retrieval

For RAG, vector search, or any retrieval step:

```typescript
await span.captureRetrieval({
  provider: 'bedrock-kb',        // or 'pinecone', 'opensearch', etc.
  query: 'leadership styles',
  execute: () => knowledgeBase.retrieve(query),
  extractResults: (r) => ({
    resultsCount: r.chunks.length,
    relevantCount: r.chunks.filter(c => c.score >= 0.5).length,
    meanScore: avg(r.chunks.map(c => c.score)),
    topScore: Math.max(...r.chunks.map(c => c.score)),
  }),
  searchType: 'hybrid',           // Optional: 'semantic', 'keyword', 'hybrid'
  cost: 0.0001,                   // Optional: retrieval cost in euros
  tags: { courseId: 'lid-101' },   // Optional dimensional tags
});
```

---

## Tags (Dimensional Metadata)

Tags let you segment metrics by any dimension: course, intent, pipeline step, model version, etc.

```typescript
// Tags on individual events
await span.captureLLM({
  provider: 'anthropic',
  model: 'claude-sonnet-4-20250514',
  execute: () => anthropic.messages.create({ ... }),
  extractUsage: (r) => ({ ... }),
  tags: { step: 'classify', intent: 'chat' },
});

// Tags on spans (inherited by all events from that span)
const child = span.child('analysis', { tags: { step: 'analyze' } });
await child.captureLLM({ ... }); // inherits step=analyze

// Tags at trace level (inherited by all spans and events)
await sentygent.trace('conv-123', async (span) => { ... }, {
  tags: { courseId: 'liderazgo-101' },
});
```

Tags are visible in the dashboard trace view as pill badges on each event.

---

## Request-Response Pipelines

For pipeline architectures where each HTTP request is a complete trace, use `request()` instead of `trace()`. It automatically emits `conversation_start` and `conversation_end`, which triggers quality scoring.

```typescript
app.post('/api/chat', async (req, res) => {
  const answer = await sentygent.request(req.body.sessionId, async (span) => {
    // No need to manually emit conversation_start/end
    const intent = await span.captureLLM({ tags: { step: 'classify' }, ... });
    const chunks = await span.captureRetrieval({ ... });
    const response = await span.captureLLM({ tags: { step: 'generate' }, ... });
    return response;
  }, { tags: { courseId: req.body.courseId } });

  res.json(answer);
});
```

---

## Multi-Agent Tracing

For systems where multiple agents collaborate in a single conversation:

```typescript
const sentygent = new SentygentClient({ agent: 'orchestrator' });

await sentygent.trace('conv-123', async (span) => {
  span.captureLifecycle('conversation_start');

  // Sub-agent with different slug
  const searchSpan = span.child('search', { agent: 'search-agent' });
  await searchSpan.captureLLM({ ... });
  await searchSpan.captureTool({ ... });

  // Another sub-agent
  const analysisSpan = span.child('analysis', { agent: 'analysis-agent' });
  await analysisSpan.captureLLM({ ... });

  // Orchestrator composes final response
  await span.captureLLM({ ... });
  span.captureLifecycle('conversation_end');
});
```

The dashboard groups spans by agent with color-coded timeline and per-agent cost breakdown.

---

## Child Spans

```typescript
await sentygent.trace('conv-123', async (span) => {
  const child = span.child('planning-step');
  await child.captureLLM({ ... });

  const toolSpan = span.child('tool-execution');
  await toolSpan.captureTool({ ... });
});
```

---

## Configuration

```typescript
const sentygent = new SentygentClient({
  apiKey: 'snt_...',            // Required: your API key
  agent: 'my-agent',           // Required: registered agent slug
  environment: 'production',   // Default: 'production'
  endpoint: 'https://api.sentygent.com', // Default API endpoint
  flushInterval: 5000,         // Flush every 5s (default)
  maxBatchSize: 100,           // Events per batch (default)
  maxBufferSize: 10000,        // Max buffered events (default)
  retryAttempts: 3,            // Retry failed sends (default)
  debug: false,                // Enable debug logging
  disabled: false,             // Kill switch — disables all operations
});
```

## Graceful Shutdown

The SDK registers `beforeExit` and `SIGTERM` handlers to flush remaining events. You can also call shutdown manually:

```typescript
await sentygent.shutdown();
```

## Error Handling

The SDK never throws from transport operations. If the API is unreachable after retries, a warning is logged and the SDK continues. Your application is never affected by SDK failures.

## Troubleshooting

**Events not appearing in dashboard:**
- Verify your API key is correct
- Check that the agent slug matches a registered agent
- Enable `debug: true` for detailed logging
- Ensure `shutdown()` is called before process exit

**High memory usage:**
- Reduce `maxBufferSize` if your agent generates many events
- Reduce `flushInterval` to send events more frequently