| name | openevidence-sdk-patterns |
| description | OpenEvidence SDK patterns and best practices for clinical AI integration.
Use when implementing advanced SDK features, optimizing API usage,
or following clinical decision support best practices.
Trigger with phrases like "openevidence patterns", "openevidence best practices",
"openevidence sdk", "clinical ai patterns".
|
| allowed-tools | Read, Write, Edit |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
OpenEvidence SDK Patterns
Overview
Best practices and design patterns for building production clinical decision support with OpenEvidence.
Prerequisites
- Completed
openevidence-install-auth setup
- Understanding of async/await patterns
- Familiarity with clinical workflows
Core Patterns
Pattern 1: Client Singleton with Dependency Injection
import { OpenEvidenceClient } from '@openevidence/sdk';
export interface OpenEvidenceConfig {
apiKey: string;
orgId: string;
baseUrl?: string;
timeout?: number;
retries?: number;
}
export class OpenEvidenceClientFactory {
private static instance: OpenEvidenceClient;
private static config: OpenEvidenceConfig;
static configure(config: OpenEvidenceConfig): void {
this.config = config;
this.instance = null!;
}
static getClient(): OpenEvidenceClient {
if (!this.instance) {
if (!this.config) {
throw new Error('OpenEvidence client not configured. Call configure() first.');
}
this.instance = new OpenEvidenceClient(this.config);
}
return this.instance;
}
static setClient(client: OpenEvidenceClient): void {
this.instance = client;
}
}
Pattern 2: Typed Clinical Queries
export type ClinicalSpecialty =
| 'internal-medicine'
| 'emergency-medicine'
| 'cardiology'
| 'oncology'
| 'neurology'
| 'pediatrics'
| 'psychiatry'
| 'surgery'
| 'family-medicine'
| 'pharmacology';
export type QueryUrgency = 'stat' | 'urgent' | 'routine' | 'research';
export interface ClinicalContext {
specialty: ClinicalSpecialty;
urgency: QueryUrgency;
patientAge?: number;
patientSex?: 'male' | 'female' | 'other';
relevantConditions?: string[];
currentMedications?: string[];
}
export interface QueryOptions {
maxCitations?: number;
includeGuidelines?: boolean;
includeDrugInfo?: boolean;
preferredSources?: string[];
}
export interface {
: ;
: ;
?: ;
}
Pattern 3: Query Builder Pattern
import { TypedClinicalQuery, ClinicalContext, QueryOptions } from './types';
export class ClinicalQueryBuilder {
private query: Partial<TypedClinicalQuery> = {};
question(q: string): this {
this.query.question = q;
return this;
}
specialty(s: ClinicalContext['specialty']): this {
this.query.context = { ...this.query.context, specialty: s } as ClinicalContext;
return this;
}
urgency(u: ClinicalContext['urgency']): this {
this.query.context = { ...this.query.context, urgency: u } ;
;
}
(: , : | | ): {
.. = {
.....,
: age,
: sex,
} ;
;
}
(: []): {
.. = {
.....,
: conditions,
} ;
;
}
(: []): {
.. = {
.....,
: meds,
} ;
;
}
(: ): {
.. = { ....., : n };
;
}
(): {
.. = { ....., : };
;
}
(): {
(!..) ();
(!..?.) ();
(!..?.) ();
. ;
}
}
query = ()
.()
.()
.()
.(, )
.([, ])
.()
.()
.();
Pattern 4: Response Transformer
interface RawOpenEvidenceResponse {
answer: string;
citations: any[];
confidence: number;
raw_data: any;
}
export interface FormattedClinicalAnswer {
summary: string;
detailedAnswer: string;
keyPoints: string[];
evidence: {
source: string;
strength: 'high' | 'moderate' | 'low';
year: number;
}[];
confidence: {
score: number;
level: 'high' | 'moderate' | 'low';
};
disclaimer: string;
}
export function transformResponse(raw: RawOpenEvidenceResponse): FormattedClinicalAnswer {
const keyPoints = extractKeyPoints(raw.answer);
return {
summary: raw.answer.split()[] + ,
: raw.,
keyPoints,
: raw..( ({
: c.,
: (c),
: c.,
})),
: {
: raw.,
: raw. > ? : raw. > ? : ,
},
: ,
};
}
(): [] {
lines = answer.();
lines
.( .(l.()))
.( l.(, ).())
.( l. > );
}
(): | | {
highImpactSources = [, , , , ];
(highImpactSources.( citation.?.(s))) ;
(citation. >= ().() - ) ;
;
}
Pattern 5: Caching Strategy
import { createHash } from 'crypto';
interface CacheEntry<T> {
data: T;
timestamp: number;
ttl: number;
}
export class ClinicalQueryCache {
private cache = new Map<string, CacheEntry<any>>();
private defaultTTL = 3600000;
private generateKey(query: any): string {
const normalized = JSON.stringify(query, Object.keys(query).sort());
return createHash('sha256').update(normalized).digest('hex');
}
get<T>(query: any): T | null {
const key = this.generateKey(query);
const entry = this.cache.get(key);
if (!entry) ;
(.() - entry. > entry.) {
..(key);
;
}
entry. T;
}
set<T>(: , : T, ?: ): {
key = .(query);
..(key, {
data,
: .(),
: ttl || .,
});
}
(: ): {
key = .(query);
..(key);
}
(): {
..();
}
}
Output
- Typed client factory with DI support
- Query builder for complex clinical queries
- Response transformer for consistent output
- Caching layer for performance
Error Handling
| Pattern | Use Case | Benefit |
|---|
| Singleton | Single client instance | Memory efficiency |
| Builder | Complex queries | Type safety, readability |
| Transformer | Response normalization | Consistent UI layer |
| Cache | Repeated queries | Reduced API calls, latency |
Examples
Complete Service Implementation
import { OpenEvidenceClientFactory } from '../openevidence/client-factory';
import { ClinicalQueryBuilder } from '../openevidence/query-builder';
import { transformResponse, FormattedClinicalAnswer } from '../openevidence/response-transformer';
import { ClinicalQueryCache } from '../openevidence/cache';
const cache = new ClinicalQueryCache();
export async function getClinicalEvidence(
question: string,
specialty: string,
options?: { useCache?: boolean }
): Promise<FormattedClinicalAnswer> {
const query = new ClinicalQueryBuilder()
.question(question)
.specialty(specialty as any)
.urgency('routine')
.includeGuidelines()
.build();
if (options?.useCache !== false) {
const cached = cache.<>(query);
(cached) cached;
}
client = .();
response = client.(query);
formatted = (response);
cache.(query, formatted);
formatted;
}
Resources
Next Steps
For core clinical query workflow, see openevidence-core-workflow-a.