@varia-bly/variably-sdk
Version:
Official JavaScript/TypeScript SDK for Variably feature flags, experimentation, LLM experiments with React hooks, and real-time dynamic configurations
337 lines • 10 kB
JavaScript
/**
* React Hooks for Variably LLM Experiments
* Easy-to-use React hooks for integrating LLM experiments into React applications
*/
import { useState, useEffect, useCallback, useRef } from 'react';
import { VariablyLLMClient } from './llm-client';
/**
* Hook for executing LLM prompts with automatic variant selection
*
* @example
* ```tsx
* const { execute, loading, response, error } = useLLMPrompt({
* client: llmClient,
* experimentId: 'exp_123',
* });
*
* const handleSubmit = async () => {
* const result = await execute({
* userContext: { userId: 'user_123' },
* variables: { query: userInput }
* });
* };
* ```
*/
export function useLLMPrompt(config) {
const [loading, setLoading] = useState(false);
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const abortControllerRef = useRef(null);
const execute = useCallback(async (request) => {
setLoading(true);
setError(null);
abortControllerRef.current = new AbortController();
try {
const result = await config.client.executeLLMPrompt({
experimentId: config.experimentId,
userContext: request.userContext || config.defaultUserContext,
variables: request.variables,
llmConfig: request.llmConfig,
includeEvaluation: config.includeEvaluation ?? request.includeEvaluation,
evaluationCriteria: config.evaluationCriteria || request.evaluationCriteria,
trackMetrics: config.trackMetrics ?? request.trackMetrics ?? true,
sessionId: request.sessionId,
conversationHistory: request.conversationHistory,
metadata: request.metadata,
});
setResponse(result);
config.onSuccess?.(result);
return result;
}
catch (err) {
const llmError = err;
setError(llmError);
config.onError?.(llmError);
throw err;
}
finally {
setLoading(false);
abortControllerRef.current = null;
}
}, [config]);
const cancel = useCallback(() => {
abortControllerRef.current?.abort();
setLoading(false);
}, []);
return {
execute,
cancel,
loading,
response,
error,
isSuccess: !loading && !error && !!response,
isError: !loading && !!error,
};
}
/**
* Hook for streaming LLM responses
*
* @example
* ```tsx
* const { stream, streaming, content, chunks, error } = useLLMStreaming({
* client: llmClient,
* experimentId: 'exp_123',
* });
*
* const handleStream = async () => {
* await stream({
* userContext: { userId: 'user_123' },
* variables: { query: userInput }
* });
* };
* ```
*/
export function useLLMStreaming(config) {
const [streaming, setStreaming] = useState(false);
const [content, setContent] = useState('');
const [chunks, setChunks] = useState([]);
const [error, setError] = useState(null);
const abortControllerRef = useRef(null);
const stream = useCallback(async (request) => {
setStreaming(true);
setError(null);
setContent('');
setChunks([]);
abortControllerRef.current = new AbortController();
const streamingOptions = {
enabled: true,
onChunk: (chunk) => {
setContent((prev) => prev + chunk.content);
setChunks((prev) => [...prev, chunk]);
config.onChunk?.(chunk);
},
onComplete: () => {
setStreaming(false);
config.onComplete?.();
},
onError: (err) => {
setError(err);
setStreaming(false);
config.onError?.(err);
},
};
try {
await config.client.executeLLMPromptStreaming({
experimentId: config.experimentId,
userContext: request.userContext || config.defaultUserContext,
variables: request.variables,
llmConfig: request.llmConfig,
sessionId: request.sessionId,
conversationHistory: request.conversationHistory,
metadata: request.metadata,
}, streamingOptions);
}
catch (err) {
const llmError = err;
setError(llmError);
config.onError?.(llmError);
}
finally {
abortControllerRef.current = null;
}
}, [config]);
const cancel = useCallback(() => {
abortControllerRef.current?.abort();
setStreaming(false);
}, []);
const reset = useCallback(() => {
setContent('');
setChunks([]);
setError(null);
setStreaming(false);
}, []);
return {
stream,
cancel,
reset,
streaming,
content,
chunks,
error,
isComplete: !streaming && chunks.length > 0 && chunks[chunks.length - 1]?.isFinal,
};
}
/**
* Hook for managing conversation context across multiple LLM calls
*
* @example
* ```tsx
* const {
* messages,
* addMessage,
* sendMessage,
* loading,
* clearHistory
* } = useConversation({
* client: llmClient,
* experimentId: 'exp_123',
* userId: 'user_123',
* });
*
* const handleSend = async () => {
* await sendMessage({ content: userInput, role: 'user' });
* };
* ```
*/
export function useConversation(config) {
const [messages, setMessages] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const sessionIdRef = useRef(config.sessionId || `session_${Date.now()}`);
const addMessage = useCallback((message) => {
setMessages((prev) => {
const updated = [...prev, message];
// Limit to maxMessages if specified
if (config.maxMessages && updated.length > config.maxMessages) {
return updated.slice(-config.maxMessages);
}
return updated;
});
}, [config.maxMessages]);
const sendMessage = useCallback(async (message) => {
const userMessage = {
role: message.role || 'user',
content: message.content
};
addMessage(userMessage);
setLoading(true);
setError(null);
try {
const response = await config.client.executeLLMPrompt({
experimentId: config.experimentId,
userContext: { userId: config.userId },
variables: message.variables || { query: message.content },
sessionId: sessionIdRef.current,
conversationHistory: messages.concat(userMessage),
trackMetrics: true,
});
const assistantMessage = {
role: 'assistant',
content: response.response.content,
};
addMessage(assistantMessage);
return response;
}
catch (err) {
const llmError = err;
setError(llmError);
throw err;
}
finally {
setLoading(false);
}
}, [config, messages, addMessage]);
const clearHistory = useCallback(() => {
setMessages([]);
setError(null);
// Generate new session ID
sessionIdRef.current = `session_${Date.now()}`;
}, []);
return {
messages,
addMessage,
sendMessage,
clearHistory,
loading,
error,
sessionId: sessionIdRef.current,
};
}
/**
* Hook for evaluating LLM responses
*
* @example
* ```tsx
* const { evaluate, loading, result, error } = useLLMEvaluation({
* client: llmClient,
* criteria: {
* dimensions: [
* { name: 'accuracy', weight: 0.5, method: 'automated' },
* { name: 'clarity', weight: 0.3, method: 'automated' },
* { name: 'relevance', weight: 0.2, method: 'automated' }
* ]
* }
* });
*
* const handleEvaluate = async () => {
* const evalResult = await evaluate({
* response: llmResponse,
* prompt: originalPrompt
* });
* };
* ```
*/
export function useLLMEvaluation(config) {
const [loading, setLoading] = useState(false);
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
const evaluate = useCallback(async (request) => {
setLoading(true);
setError(null);
try {
const evalResult = await config.client.evaluateResponse({
response: request.response,
prompt: request.prompt,
expectedOutput: request.expectedOutput,
criteria: config.criteria,
context: request.context,
});
setResult(evalResult);
return evalResult;
}
catch (err) {
const llmError = err;
setError(llmError);
throw err;
}
finally {
setLoading(false);
}
}, [config]);
return {
evaluate,
loading,
result,
error,
};
}
/**
* Hook for managing LLM client instance
*
* @example
* ```tsx
* const client = useLLMClient({
* apiKey: process.env.VARIABLY_API_KEY,
* baseUrl: 'https://api.variably.tech',
* llmConfig: {
* provider: 'openai',
* model: 'gpt-4',
* temperature: 0.7
* }
* });
* ```
*/
export function useLLMClient(config) {
const clientRef = useRef(null);
if (!clientRef.current) {
clientRef.current = new VariablyLLMClient(config);
}
useEffect(() => {
return () => {
// Cleanup if needed
clientRef.current = null;
};
}, []);
return clientRef.current;
}
//# sourceMappingURL=react-hooks.js.map