@amedia/parnassus-client
v1.1.9
Published
Client library for integrating Parnassus templates with LLM API
Maintainers
Readme
@amedia/parnassus-client
En enkel klient for å integrere Parnassus-templates med LLM API. Denne pakken gjør det lett å:
- Hente templates fra Parnassus
- Fylle ut placeholders med data
- Formatere som et LLM API-request
- Sende request til LLM API
Installasjon
npm install @amedia/parnassus-clientGrunnleggende bruk
Enkel eksempel
import { ParnassusClient } from '@amedia/parnassus-client';
// Opprett klient
const client = new ParnassusClient();
// Bruk execute() for komplett workflow
const response = await client.execute('task-123', {
'placeholder-id-1': 'Min tekst her',
'placeholder-id-2': 'Mer data her',
});
// Hent ut innholdet
const content = response.choices[0].message.content;
console.log(content);Med JSON-parsing
// For JSON-responser, bruk executeJson()
const jsonResult = await client.executeJson('task-123', {
'article-text': 'En lang artikkel...',
'keywords': 'sport, fotball, tippeligaen',
});
// jsonResult er allerede parsed
console.log(jsonResult);Med egendefinert modell
const response = await client.execute(
'task-123',
{
'input-text': 'Min tekst',
},
{
model: 'gpt-4o' // eller 'o3-mini', 'claude-3-opus', etc.
}
);🚧 Utvikling med egne prompts
For utvikling og testing kan du bruke inline prompts uten Parnassus templates. Merk: Flytter til Parnassus templates før produksjon for bedre håndtering og versjonering!
// Enkelt eksempel - for utvikling/testing
const response = await client.executeDev({
system: 'Du er en hjelpsom assistent som svarer kort og konsist.',
user: 'Hva er hovedstaden i Norge?',
model: 'gpt-4o' // valgfri - bruker defaultModel hvis ikke satt
});
// Med JSON response - også for utvikling
const result = await client.executeDevJson({
system: 'Analyser følgende tekst og returner JSON.',
user: 'Teksten: "Dette var en fantastisk film!" Sentiment?',
responseFormat: {
type: 'json-schema',
schema: {
type: 'object',
properties: {
sentiment: { type: 'string' },
confidence: { type: 'number' }
},
required: ['sentiment', 'confidence']
}
}
});
console.log(result); // { sentiment: "positive", confidence: 0.95 }⚠️ Produksjon: Opprett templates i Parnassus og bruk execute() for bedre versjonering, testing og vedlikehold.
Denne funksjonen støtter også alle de samme avanserte features:
const response = await client.executeDev({
system: 'Du er en ekspert på norsk litteratur.',
user: 'Fortell om Knut Hamsun'
}, {
timeout: 30000,
retry: { retries: 3 },
onProgress: {
onSendingRequest: () => console.log('Sender dev prompt...')
}
});Avanserte features
🔄 Automatisk retry med exponential backoff
Klienten har innebygd automatisk retry for alle nettverks-requests. Dette håndterer transiente feil som nettverksendringer, timeouts og server-feil:
// Standard konfigurasjon (kan overstyres i constructor)
const client = new ParnassusClient({
fetchRetry: {
maxRetries: 3, // Prøv inntil 3 ganger
initialDelay: 100, // Start med 100ms delay
maxDelay: 1000, // Aldri vent mer enn 1s
jitter: true // Legg til tilfeldig jitter
},
debug: true // Logg retry-forsøk
});
// Eksempel på automatisk retry ved ERR_NETWORK_CHANGED:
// [fetchWithRetry] Attempt 1/4 failed with error: ERR_NETWORK_CHANGED. Retrying in 120ms...
// [fetchWithRetry] Attempt 2/4 failed with error: ERR_NETWORK_CHANGED. Retrying in 256ms...
// ✅ Success på forsøk 3!Retry-logikk:
- ✅ Retries på: Nettverksfeil (ERR_NETWORK_CHANGED, timeouts, ECONNRESET), 5xx server-feil, 429 Too Many Requests
- ❌ Ingen retry på: 4xx klient-feil (unntatt 429)
- 📈 Exponential backoff: 100ms → 200ms → 400ms → 800ms (med jitter)
- 🎲 Jitter forhindrer "thundering herd" ved samtidig retry
Du kan også konfigurere retry på application-nivå:
const response = await client.execute('task-123', values, {
retry: {
retries: 5, // Flere forsøk for kritiske operasjoner
retryDelay: 2000,
exponentialBackoff: true
}
});⏱️ Timeout
Sett en timeout for å unngå at requester henger for lenge:
const response = await client.execute('task-123', values, {
timeout: 30000, // 30 sekunder
});🚫 Abort/Cancel
Avbryt en request manuelt:
const controller = new AbortController();
// Start request
const promise = client.execute('task-123', values, {
signal: controller.signal,
});
// Avbryt når du vil
setTimeout(() => controller.abort(), 5000);
try {
const response = await promise;
} catch (error) {
console.log('Request avbrutt');
}🔄 Retry med exponential backoff
Automatisk retry ved feil:
const response = await client.execute('task-123', values, {
retry: {
retries: 3, // Prøv inntil 3 ganger
retryDelay: 1000, // Start med 1 sekund
exponentialBackoff: true // Doble delay for hver retry
}
});Egendefinert retry-logikk:
const response = await client.execute('task-123', values, {
retry: {
retries: 5,
retryDelay: 500,
shouldRetry: (error, attempt) => {
// Kun retry på spesifikke feil
return error.message.includes('rate limit');
}
}
});📊 Progress callbacks
Få tilbakemelding underveis i prosessen:
const response = await client.execute('task-123', values, {
onProgress: {
onFetchingTemplate: () => console.log('📥 Henter template...'),
onTemplateFetched: (template) => console.log('✅ Template hentet:', template.name),
onFillingTemplate: () => console.log('✏️ Fyller ut template...'),
onTemplateFilled: (prompt) => console.log('📝 Prompt klar'),
onSendingRequest: () => console.log('🚀 Sender til Ivar...'),
onReceivingResponse: () => console.log('⏳ Venter på svar...'),
onResponseReceived: (response) => console.log('✨ Svar mottatt!'),
onParsingResponse: () => console.log('🔍 Parser JSON...')
}
});🌊 Streaming
For å få innhold underveis mens LLM genererer:
for await (const chunk of client.executeStream('task-123', values)) {
if (chunk.type === 'content') {
process.stdout.write(chunk.content);
} else if (chunk.type === 'done') {
console.log('\n✅ Ferdig!');
} else if (chunk.type === 'error') {
console.error('❌ Feil:', chunk.error);
}
}Med progress callbacks og timeout:
for await (const chunk of client.executeStream('task-123', values, {
timeout: 60000,
onProgress: {
onSendingRequest: () => console.log('Starter streaming...')
}
})) {
// Håndter chunks
}🎯 Kombinert eksempel
Alt sammen:
const controller = new AbortController();
try {
const response = await client.execute('task-123', {
'article': 'Lang tekst...'
}, {
model: 'gpt-4o',
timeout: 30000,
signal: controller.signal,
retry: {
retries: 3,
retryDelay: 1000,
exponentialBackoff: true
},
onProgress: {
onFetchingTemplate: () => console.log('Starter...'),
onSendingRequest: () => console.log('Sender request...'),
onResponseReceived: () => console.log('Svar mottatt!')
}
});
console.log('Resultat:', response.choices[0].message.content);
} catch (error) {
if (error instanceof TemplateNotFoundError) {
console.error('Template finnes ikke');
} else if (error instanceof LLMApiError) {
console.error('LLM API feil:', error.statusCode);
}
}Avansert bruk
Steg-for-steg
For mer kontroll kan du bruke de individuelle metodene:
const client = new ParnassusClient();
// 1. Hent template
const template = await client.getTemplate('task-123');
// 2. Fyll ut template med dine verdier
const filledPrompt = client.fillTemplate(template, {
'article-text': 'Min artikkel...',
'tone': 'formell',
});
// 3. Opprett LLM request objekt
const llmRequest = client.createLLMRequest(template, filledPrompt, 'gpt-4o');
// 4. Send til LLM API
const response = await client.sendRequest(llmRequest);
// 5. Parse JSON hvis nødvendig
if (template.responseFormat.type === 'json-schema') {
const jsonData = client.parseJsonResponse(response);
console.log(jsonData);
}Egendefinert konfigurasjon
const client = new ParnassusClient({
parnassusBaseUrl: 'https://services.api.no/api/parnassus/v1',
llmApiUrl: 'https://services.api.no/api/auris/v1/ivar',
defaultModel: 'gpt-4o',
});import { ParnassusClient } from '@amedia/parnassus-client';
async function summarizeArticle(articleText: string) {
const client = new ParnassusClient();
try {
// Hent og kjør template i ett kall
const response = await client.executeJson('summary-task-42', {
'article-text': articleText,
});
// response er allerede parsed JSON
console.log('Sammendrag:', response.summary);
console.log('Nøkkelord:', response.keywords);
return response;
} catch (error) {
console.error('Feil ved sammendrag:', error);
throw error;
}
}
// Bruk
const result = await summarizeArticle('En lang artikkel om...');API
ParnassusClient
Constructor
new ParnassusClient(config?: ParnassusClientConfig)Config options:
parnassusBaseUrl?: string- Base URL for Parnassus API (default:'https://services.api.no/api/parnassus/v1')llmApiUrl?: string- Base URL for LLM API (default:'https://services.api.no/api/auris/v1/ivar')defaultModel?: string- Default LLM model (default:'gpt-4o')fetchRetry?: FetchRetryConfig- Configuration for automatic retry of failed requestsmaxRetries?: number- Maximum retry attempts (default: 3)initialDelay?: number- Initial delay before first retry in ms (default: 100)maxDelay?: number- Maximum delay between retries in ms (default: 1000)jitter?: boolean- Add random jitter to prevent thundering herd (default: true)
debug?: boolean- Enable debug logging including retry attempts (default: false)headers?: Record<string, string>- Custom headers to include in all requestssuppressDevWarning?: boolean- Suppress warning when using dev methods (default: false)
Metoder
execute(taskId, values, options?)
Komplett workflow i ett kall.
- taskId:
string | number- ID til Parnassus task - values:
PlaceholderValues- Object med placeholder-verdier - options:
ExecuteOptions?- Valgfrie innstillingermodel?: string- Override default modeltimeout?: number- Timeout i millisekundersignal?: AbortSignal- Abort signal for cancellationretry?: RetryOptions- Retry konfigurasjonretries?: number- Antall retry-forsøk (default: 0)retryDelay?: number- Delay mellom retries i ms (default: 1000)exponentialBackoff?: boolean- Bruk exponential backoff (default: true)shouldRetry?: (error, attempt) => boolean- Custom retry-logikk
onProgress?: ProgressCallbacks- Callbacks for fremgangonFetchingTemplate?: () => voidonTemplateFetched?: (template) => voidonFillingTemplate?: () => voidonTemplateFilled?: (prompt) => voidonSendingRequest?: () => voidonReceivingResponse?: () => voidonResponseReceived?: (response) => voidonParsingResponse?: () => void
- Returns:
Promise<LLMResponse>
executeJson(taskId, values, options?)
Som execute(), men parser JSON automatisk.
- Returns:
Promise<unknown>- Parsed JSON fra response
executeStream(taskId, values, options?)
Streaming versjon - yield chunks underveis.
- Returns:
AsyncGenerator<StreamChunk>- Stream av chunks- Chunk types:
'content' | 'done' | 'error'
- Chunk types:
getTemplate(taskId, signal?)
Hent en template fra Parnassus.
- taskId:
string | number- ID til Parnassus task - signal:
AbortSignal?- Valgfri abort signal - Returns:
Promise<ParnassusTemplate>
fillTemplate(template, values)
Fyll ut placeholders i en template.
- template:
ParnassusTemplate- Template fra Parnassus - values:
PlaceholderValues- Verdier for hver placeholder - Returns:
string- Ferdig utfylt prompt
createLLMRequest(template, userPrompt, model?)
Opprett et LLM request objekt.
- template:
ParnassusTemplate- Template med system prompt og response format - userPrompt:
string- Ferdig utfylt user prompt - model:
string?- Valgfri modell - Returns:
LLMRequest
sendRequest(llmRequest, signal?)
Send request til LLM API.
- llmRequest:
LLMRequest- Request objekt - signal:
AbortSignal?- Valgfri abort signal - Returns:
Promise<LLMResponse>
parseJsonResponse(llmResponse)
Parse JSON fra LLM response (håndterer både raw JSON og markdown-wrapped JSON).
- llmResponse:
LLMResponse- Response fra LLM API - Returns:
unknown- Parsed JSON
executeDev(prompt, options?)
Kjør inline prompts uten Parnassus template (kun for utvikling/testing).
- prompt:
DevPrompt- Dev prompt definisjonsystem?: string- Valgfri system promptuser: string- User prompt (påkrevd)model?: string- Valgfri modell overrideresponseFormat?: object- Valgfri JSON schema for response
- options:
Omit<ExecuteOptions, 'model'>- Alle ExecuteOptions unntatt model - Returns:
Promise<LLMResponse> - ⚠️ Merk: Flytt til Parnassus templates før produksjon!
executeDevJson(prompt, options?)
Som executeDev(), men parser JSON automatisk.
- Returns:
Promise<unknown>- Parsed JSON fra response
TypeScript
Pakken er skrevet i TypeScript og inkluderer alle type-definisjoner. Du får full IntelliSense og type-checking.
import type {
ParnassusTemplate,
LLMRequest,
LLMResponse,
PlaceholderValues,
ExecuteOptions,
StreamChunk,
ProgressCallbacks,
RetryOptions,
} from '@amedia/parnassus-client';Features
✅ Enkel API - Ett kall for hele workflowen
✅ TypeScript - Full type-safety og IntelliSense
✅ Automatisk retry - Håndterer transiente nettverksfeil (ERR_NETWORK_CHANGED, timeouts, 5xx)
✅ Exponential backoff - Smart retry-strategi med jitter for å unngå overbelastning
✅ Konfigurerbar retry - Tilpass retry-logikk for dine behov
✅ Timeout support - Avbryt requests som tar for lang tid
✅ Abort/Cancel - Manuell avbrytelse av requests
✅ Progress callbacks - Få tilbakemelding underveis
✅ Streaming - Motta innhold mens det genereres
✅ Error handling - Spesifikke error-typer for bedre håndtering
✅ Validering - Validerer templates før bruk
✅ JSON parsing - Håndterer både raw og markdown-wrapped JSON
✅ Debug logging - Detaljert logging av retry-forsøk og API-kall
Feilhåndtering
Pakken eksporterer spesifikke error-typer for bedre feilhåndtering:
import {
ParnassusClient,
TemplateNotFoundError,
LLMApiError,
TemplateValidationError
} from '@amedia/parnassus-client';
try {
const response = await client.execute('task-123', { input: 'test' });
} catch (error) {
if (error instanceof TemplateNotFoundError) {
console.error('Template finnes ikke (404)');
} else if (error instanceof LLMApiError) {
console.error('LLM API feil:', error.statusCode, error.message);
} else if (error instanceof TemplateValidationError) {
console.error('Ugyldig template struktur');
} else if (error.name === 'AbortError') {
console.error('Request ble avbrutt');
}
}Lisens
MIT
