UNPKG

@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
/** * 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