@genezio/signal
v0.1.1
Published
Node.js SDK for the Signal LLM Infrastructure Platform
Readme
@genezio/signal
Node.js SDK for the Signal LLM Infrastructure Platform.
Installation
npm install @genezio/signalQuick Start
import { Signal } from '@genezio/signal';
const signal = new Signal({ apiKey: 'sk-sig-...' });
// Execute a prompt
const result = await signal.ask('prompt-id', { variables: { name: 'world' } });
console.log(result.answer);
// Stream a response
for await (const chunk of await signal.ask('prompt-id', { variables: { name: 'world' }, stream: true })) {
if (chunk.type === 'delta') process.stdout.write(chunk.content ?? '');
if (chunk.type === 'done') console.log('\nDone:', chunk.answer);
}Configuration
const signal = new Signal({
apiKey: 'sk-sig-...', // Required — your Signal API key
baseUrl: 'https://custom.url', // Optional — defaults to https://signal.backend.genezio.ai
cacheTtl: 10, // Optional — cache TTL in seconds (default: 10)
});API Reference
signal.ask(promptId, options?)
Execute a prompt by ID. Returns the LLM response or a stream of chunks.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| promptId | string | required | The prompt ID to execute. |
| options.variables | Record<string, string> | optional | Template variables ({{name}} placeholders). |
| options.tag | string | optional | Version tag (defaults to "production"). |
| options.labels | string[] | optional | Labels to attach to the interaction (max 50). |
| options.stream | boolean | optional | Enable streaming (default: false). |
| options.mcpCredentials | Record<string, Record<string, string>> | optional | Per-request MCP server header overrides. Keys are MCP server names (as configured in the Signal UI). Values are objects mapping header names to their values — these merge with/override the headers stored in the server definition. Example: { "github": { "Authorization": "Bearer ghp_..." } } |
Non-streaming:
const result = await signal.ask('prompt-id', {
variables: { city: 'Paris' },
tag: 'production',
labels: ['user-query'],
});
// result: { answer: string, responseId: string | null }Streaming:
const stream = await signal.ask('prompt-id', {
variables: { city: 'Paris' },
stream: true,
});
for await (const chunk of stream) {
switch (chunk.type) {
case 'delta': // chunk.content — text fragment
case 'tool_call': // chunk.toolCall — { server, tool, arguments }
case 'tool_result':// chunk.toolResult — { server, tool, success, latencyMs }
case 'done': // chunk.answer, chunk.responseId, chunk.usage
case 'error': // chunk.error — error message
}
}signal.chat(options)
Execute a raw chat completion without a prompt template.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| options.provider | string | optional | LLM provider (e.g. "openai", "anthropic"). Required unless promptId is specified (inherited from prompt). |
| options.model | string | optional | Model identifier (e.g. "gpt-4o"). Required unless promptId is specified (inherited from prompt). |
| options.apiType | 'chat' \| 'responses' \| 'messages' | optional | API type. Required for providers with multiple types (e.g. OpenAI). Auto-detected when the provider supports only one. Inherited from prompt when promptId is specified. |
| options.systemMessage | string | optional | System message (max 100k chars). At least one of systemMessage or userMessage is required. |
| options.userMessage | string | optional | User message (max 100k chars). At least one of systemMessage or userMessage is required. |
| options.temperature | number | optional | Temperature (0–2). |
| options.labels | string[] | optional | Labels to attach. |
| options.tags | string[] | optional | Version tags. |
| options.promptId | string | optional | Associate with a prompt ID. |
| options.previousResponseId | string | optional | Previous response ID for multi-turn. |
| options.conversationId | string | optional | Conversation ID. |
| options.webSearch | boolean | optional | Enable web search (default: false). |
| options.stream | boolean | optional | Enable streaming (default: false). |
| options.mcpServers | string[] | optional | MCP server names to use. |
// Non-streaming
const result = await signal.chat({
provider: 'openai',
model: 'gpt-4o',
apiType: 'chat',
systemMessage: 'You are a helpful assistant.',
userMessage: 'What is 2+2?',
});
// result: { answer: string, responseId: string | null }
// Streaming
const stream = await signal.chat({
provider: 'anthropic',
model: 'claude-sonnet-4-20250514',
systemMessage: 'You are a helpful assistant.',
userMessage: 'Write a poem.',
stream: true,
});
for await (const chunk of stream) {
if (chunk.type === 'delta') process.stdout.write(chunk.content ?? '');
}Prompts
signal.prompts.get(promptId, options?)
Get a prompt's configuration. Defaults to the "production" tag if none specified.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| promptId | string | required | The prompt ID. |
| options.tag | string | optional | Version tag (defaults to "production"). |
const prompt = await signal.prompts.get('prompt-id');
// prompt: { promptId, promptName, provider, model, apiType, temperature,
// webSearch, responseFormat, tags, systemTemplate, userTemplate,
// mcpServers, toolChoice, maxToolRounds, cacheTtl }
// Same struct with a different tag
const staging = await signal.prompts.get('prompt-id', { tag: 'staging' });signal.prompts.resolve(promptId, options?)
Resolve a prompt's template without executing it. Returns the same shape as get() but with systemMessage/userMessage (rendered) instead of systemTemplate/userTemplate.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| options.variables | Record<string, string> | optional | Template variables. |
| options.tag | string | optional | Version tag (defaults to "production"). |
const resolved = await signal.prompts.resolve('prompt-id', {
variables: { name: 'world' },
tag: 'production',
});
// resolved: { promptId, promptName, provider, model, apiType, temperature,
// webSearch, responseFormat, tags, systemMessage, userMessage,
// cacheTtl }Prompt Versions
signal.prompts.versions.create(promptId, options)
Create a new version.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| options.provider | string | optional | LLM provider. Inherited from latest version if omitted. |
| options.model | string | optional | Model. Inherited from latest version if omitted. |
| options.apiType | 'chat' \| 'responses' \| 'messages' | optional | API type. Required for providers with multiple types (e.g. OpenAI). Auto-detected when the provider supports only one. |
| options.systemTemplate | string | optional | System template with {{var}} placeholders. |
| options.userTemplate | string | optional | User template. |
| options.temperature | number | optional | Temperature (0–2). Inherited from latest version if omitted. |
| options.webSearch | boolean | optional | Enable web search. Inherited from latest version if omitted. |
| options.tags | string[] | optional | Tags (auto-moved from other versions). |
| options.mcpServers | string[] | optional | MCP servers (max 20). Inherited from latest version if omitted. |
| options.toolChoice | 'auto' \| 'required' \| 'none' | optional | Tool choice. Inherited from latest version if omitted. |
| options.maxToolRounds | number | optional | Max tool rounds (1–50). Inherited from latest version if omitted. |
const version = await signal.prompts.versions.create('prompt-id', {
provider: 'openai',
model: 'gpt-4o',
apiType: 'chat',
systemTemplate: 'You are a helpful assistant for {{company}}.',
userTemplate: '{{question}}',
tags: ['production'],
});Interactions
signal.interactions.log(options)
Log an LLM interaction.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| options.conversationId | string | optional | Conversation ID. |
| options.interactionId | string | optional | Custom ID (auto-generated if omitted). |
| options.responseId | string | optional | Response ID. |
| options.previousResponseId | string | optional | Previous response ID. |
| options.provider | string | required with model | LLM provider (e.g. "openai"). |
| options.model | string | optional | Model used. |
| options.apiType | 'chat' \| 'responses' \| 'messages' | optional | API type used. |
| options.messages | ChatMessage[] | required | Conversation messages. |
| options.output | string | optional | Final output text. |
| options.tokensInput | number | optional | Input token count. |
| options.tokensOutput | number | optional | Output token count. |
| options.latencyMs | number | optional | Latency in milliseconds. |
| options.promptId | string | optional | Associated prompt ID. |
| options.tags | string[] | optional | Version tags (e.g. ["production"]). |
| options.labels | string[] | optional | Labels (max 50). |
| options.metadata | Record<string, unknown> | optional | Custom metadata. |
const result = await signal.interactions.log({
messages: [
{ role: 'system', content: 'You are helpful.' },
{ role: 'user', content: 'Hi!' },
{ role: 'assistant', content: 'Hello!' },
],
provider: 'openai',
model: 'gpt-4o',
tokensInput: 50,
tokensOutput: 10,
});
// result: { success: true, interactionId: '...' }signal.interactions.appendMessages(interactionId, messages)
Append messages to an existing interaction.
await signal.interactions.appendMessages('interaction-id', [
{ role: 'user', content: 'Follow-up question' },
{ role: 'assistant', content: 'Follow-up answer' },
]);signal.interactions.setOutput(interactionId, options)
Set or update the final output and token counts.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| options.output | string | required | Final output text. |
| options.tokensInput | number | optional | Input tokens. |
| options.tokensOutput | number | optional | Output tokens. |
| options.latencyMs | number | optional | Latency in ms. |
await signal.interactions.setOutput('interaction-id', {
output: 'Final answer.',
tokensInput: 100,
tokensOutput: 25,
latencyMs: 450,
});Feedback
signal.feedback.submit(responseId, options)
Submit or update feedback for a response.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| responseId | string | required | The response ID. |
| options.score | number | required | Score (1–5). |
| options.comment | string | optional | Comment. |
| options.amendedAnswer | string | optional | Corrected answer. |
await signal.feedback.submit('response-id', {
score: 5,
comment: 'Perfect answer!',
});signal.feedback.get(responseId)
Get feedback for a response.
const feedback = await signal.feedback.get('response-id');
// feedback: { responseId, score, comment, amendedAnswer, createdAt } | nullUsing Signal for Prompt Management Only
If you want to manage prompts in Signal but call the LLM yourself (e.g. with the OpenAI SDK directly), use resolve() to get the rendered messages and then log the interaction back for tracking and feedback.
import { Signal } from '@genezio/signal';
import OpenAI from 'openai';
const signal = new Signal({ apiKey: 'sk-sig-...' });
const openai = new OpenAI({ apiKey: 'sk-...' });
// 1. Resolve the prompt — variables are filled in, config is returned
const resolved = await signal.prompts.resolve('prompt-id', {
variables: { name: 'Alice', topic: 'weather' },
tag: 'production',
});
// 2. Call the LLM yourself
const completion = await openai.chat.completions.create({
model: resolved.model,
temperature: resolved.temperature ?? undefined,
messages: [
{ role: 'system', content: resolved.systemMessage },
{ role: 'user', content: resolved.userMessage },
],
});
const answer = completion.choices[0].message.content;
// 3. Log the interaction back to Signal for observability and feedback
await signal.interactions.log({
messages: [
{ role: 'system', content: resolved.systemMessage },
{ role: 'user', content: resolved.userMessage },
{ role: 'assistant', content: answer },
],
output: answer,
provider: resolved.provider,
model: resolved.model,
promptId: resolved.promptId,
tags: resolved.tags,
tokensInput: completion.usage?.prompt_tokens,
tokensOutput: completion.usage?.completion_tokens,
});Automatic Logging with signal.wrap()
Wrap an OpenAI or Anthropic SDK client to automatically log every LLM call to Signal — no manual interactions.log() needed.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| client | object | required | An OpenAI or Anthropic SDK client instance. |
| options.promptId | string | optional | Associate logged interactions with a prompt. |
| options.tags | string[] | optional | Version tags. |
| options.labels | string[] | optional | Labels. |
| options.metadata | object | optional | Custom metadata. |
| options.conversationId | string | optional | Conversation ID. |
import { Signal } from '@genezio/signal';
import OpenAI from 'openai';
const signal = new Signal({ apiKey: 'sk-sig-...' });
const openai = signal.wrap(new OpenAI({ apiKey: 'sk-...' }), {
promptId: 'my-prompt',
tags: ['production'],
});
// Works exactly like the normal OpenAI SDK — Signal logs it automatically
const completion = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'What is 2+2?' },
],
});
console.log(completion.choices[0].message.content);
// Signal automatically logs: messages, model, provider, tokens, latencySupported clients: OpenAI (chat.completions.create) and Anthropic (messages.create). Streaming calls pass through without logging in v1.
Function Tracing with signal.trace()
Wrap any async function to automatically log its inputs, output, and latency.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| fn | function | required | The async function to wrap. |
| options.promptId | string | optional | Associate with a prompt. |
| options.tags | string[] | optional | Version tags. |
| options.labels | string[] | optional | Labels. |
| options.metadata | object | optional | Custom metadata. |
const answerQuestion = signal.trace(async (question: string) => {
const completion = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: question }],
});
return completion.choices[0].message.content;
}, { promptId: 'qa-prompt' });
const answer = await answerQuestion('What is the capital of France?');
// Signal logs: input args, output, latency, function nameCache
The SDK caches GET requests and prompt resolve calls in memory with a configurable TTL (default: 10 seconds). Streaming responses and write operations are never cached.
// Invalidate cache for a specific prompt (resolve, get, versions)
signal.cache.invalidate('prompt-id');
// Clear the entire cache
signal.cache.clear();
Error Handling
All errors extend SignalError with status and code properties.
| Error Class | Status | When |
|-------------|--------|------|
| SignalValidationError | 400 | Invalid request body or parameters. |
| SignalAuthError | 401 | Missing or invalid API key. |
| SignalNotFoundError | 404 | Resource not found. |
| SignalRateLimitError | 429 | Too many requests. |
| SignalError | other | Any other server error. |
import { SignalNotFoundError } from '@genezio/signal';
try {
await signal.prompts.get('nonexistent');
} catch (err) {
if (err instanceof SignalNotFoundError) {
console.log('Prompt not found');
}
}Types
All TypeScript types are exported from the package:
import type {
SignalConfig,
AskOptions, AskResponse,
ChatOptions, ChatResponse,
StreamChunk,
PromptConfig, CreateVersionOptions,
ResolveOptions, ResolvedPrompt,
LogInteractionOptions, LogInteractionResponse,
SetOutputOptions, ChatMessage, ToolCall,
Feedback, SubmitFeedbackOptions,
} from '@genezio/signal';License
MIT
