variably-sdk 2.2.0__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/PKG-INFO +1 -1
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/pyproject.toml +1 -1
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/setup.py +1 -1
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/client.py +99 -4
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/http_client.py +10 -5
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/types.py +16 -1
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/version.py +1 -1
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably_sdk.egg-info/PKG-INFO +1 -1
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/README.md +0 -0
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/setup.cfg +0 -0
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/__init__.py +0 -0
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/cache.py +0 -0
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/config.py +0 -0
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/errors.py +0 -0
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/logger.py +0 -0
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably_sdk.egg-info/SOURCES.txt +0 -0
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably_sdk.egg-info/dependency_links.txt +0 -0
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably_sdk.egg-info/requires.txt +0 -0
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably_sdk.egg-info/top_level.txt +0 -0
- {variably_sdk-2.2.0 → variably_sdk-2.4.0}/tests/test_streaming.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "variably-sdk"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.4.0"
|
|
8
8
|
description = "Official Python SDK for Variably feature flags, LLM experimentation, and prompt optimization platform"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.7"
|
|
@@ -14,6 +14,7 @@ from .types import (
|
|
|
14
14
|
EvaluateResponse,
|
|
15
15
|
Event,
|
|
16
16
|
FlagResult,
|
|
17
|
+
LogResult,
|
|
17
18
|
PromptVariant,
|
|
18
19
|
SDKMetrics,
|
|
19
20
|
StreamingEvaluateResponse,
|
|
@@ -480,8 +481,8 @@ class VariablyClient:
|
|
|
480
481
|
experiment_key: str,
|
|
481
482
|
user_context: UserContext,
|
|
482
483
|
input_variables: Dict[str, Any],
|
|
483
|
-
evaluation_mode: str = "full",
|
|
484
484
|
evaluation_context: Optional[Dict[str, Any]] = None,
|
|
485
|
+
**kwargs,
|
|
485
486
|
) -> EvaluateResponse:
|
|
486
487
|
"""Managed execution: Variably selects variant, calls LLM, evaluates."""
|
|
487
488
|
if not experiment_key or not isinstance(experiment_key, str):
|
|
@@ -494,7 +495,7 @@ class VariablyClient:
|
|
|
494
495
|
)
|
|
495
496
|
|
|
496
497
|
response = self.http_client.evaluate_prompt(
|
|
497
|
-
experiment_key, dict(user_context), input_variables,
|
|
498
|
+
experiment_key, dict(user_context), input_variables,
|
|
498
499
|
evaluation_context=evaluation_context,
|
|
499
500
|
)
|
|
500
501
|
|
|
@@ -515,8 +516,8 @@ class VariablyClient:
|
|
|
515
516
|
experiment_key: str,
|
|
516
517
|
user_context: UserContext,
|
|
517
518
|
input_variables: Dict[str, Any],
|
|
518
|
-
evaluation_mode: str = "full",
|
|
519
519
|
evaluation_context: Optional[Dict[str, Any]] = None,
|
|
520
|
+
**kwargs,
|
|
520
521
|
) -> StreamingEvaluateResponse:
|
|
521
522
|
"""Managed execution with streaming. Yields token strings.
|
|
522
523
|
|
|
@@ -537,7 +538,7 @@ class VariablyClient:
|
|
|
537
538
|
)
|
|
538
539
|
|
|
539
540
|
raw_response = self.http_client.evaluate_prompt_stream(
|
|
540
|
-
experiment_key, dict(user_context), input_variables,
|
|
541
|
+
experiment_key, dict(user_context), input_variables,
|
|
541
542
|
evaluation_context=evaluation_context,
|
|
542
543
|
)
|
|
543
544
|
|
|
@@ -595,6 +596,100 @@ class VariablyClient:
|
|
|
595
596
|
|
|
596
597
|
return SubmitResponseResult(status=result.get("status", "ok"))
|
|
597
598
|
|
|
599
|
+
def log(
|
|
600
|
+
self,
|
|
601
|
+
prompt: Union[str, List[Dict[str, Any]]],
|
|
602
|
+
response: str,
|
|
603
|
+
*,
|
|
604
|
+
conversation_history: Optional[List[Dict[str, Any]]] = None,
|
|
605
|
+
reference_materials: Optional[List[Dict[str, Any]]] = None,
|
|
606
|
+
retrieval_query: Optional[str] = None,
|
|
607
|
+
tags: Optional[List[str]] = None,
|
|
608
|
+
user_id: Optional[str] = None,
|
|
609
|
+
session_id: Optional[str] = None,
|
|
610
|
+
provider: Optional[str] = None,
|
|
611
|
+
model: Optional[str] = None,
|
|
612
|
+
latency_ms: Optional[int] = None,
|
|
613
|
+
prompt_tokens: Optional[int] = None,
|
|
614
|
+
completion_tokens: Optional[int] = None,
|
|
615
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
616
|
+
) -> LogResult:
|
|
617
|
+
"""Log a prompt/response pair for 43-dimension evaluation (Observe Mode).
|
|
618
|
+
|
|
619
|
+
Zero-friction entry point — no experiment creation needed. Just log your
|
|
620
|
+
prompts and responses and see quality scores in the dashboard.
|
|
621
|
+
|
|
622
|
+
Args:
|
|
623
|
+
prompt: The prompt sent to the LLM (str or messages list).
|
|
624
|
+
response: The LLM's response text.
|
|
625
|
+
conversation_history: Prior conversation turns for cross-turn coherence scoring.
|
|
626
|
+
reference_materials: Retrieved RAG chunks for grounding/hallucination scoring.
|
|
627
|
+
retrieval_query: The query sent to the retriever (for retrieval relevance scoring).
|
|
628
|
+
tags: Optional tags for grouping observations (e.g. ["production", "chatbot"]).
|
|
629
|
+
user_id: End-user ID for per-user analytics.
|
|
630
|
+
session_id: Session ID for multi-turn tracking.
|
|
631
|
+
provider: LLM provider name (e.g. "openai", "anthropic").
|
|
632
|
+
model: Model name (e.g. "gpt-4", "claude-3-opus").
|
|
633
|
+
latency_ms: LLM call latency in milliseconds.
|
|
634
|
+
prompt_tokens: Number of input tokens.
|
|
635
|
+
completion_tokens: Number of output tokens.
|
|
636
|
+
metadata: Arbitrary key-value metadata.
|
|
637
|
+
|
|
638
|
+
Returns:
|
|
639
|
+
LogResult with observation_id and status.
|
|
640
|
+
"""
|
|
641
|
+
# Normalize prompt to string
|
|
642
|
+
prompt_str = prompt if isinstance(prompt, str) else str(prompt)
|
|
643
|
+
|
|
644
|
+
data: Dict[str, Any] = {
|
|
645
|
+
"prompt": prompt_str,
|
|
646
|
+
"response": response,
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
# Build evaluation_context from multi-turn/RAG params
|
|
650
|
+
evaluation_context: Dict[str, Any] = {}
|
|
651
|
+
if conversation_history:
|
|
652
|
+
evaluation_context["conversation_history"] = conversation_history
|
|
653
|
+
if reference_materials:
|
|
654
|
+
evaluation_context["reference_materials"] = reference_materials
|
|
655
|
+
if retrieval_query:
|
|
656
|
+
evaluation_context["retrieval_query"] = retrieval_query
|
|
657
|
+
if evaluation_context:
|
|
658
|
+
data["evaluation_context"] = evaluation_context
|
|
659
|
+
|
|
660
|
+
if tags:
|
|
661
|
+
data["tags"] = tags
|
|
662
|
+
if user_id:
|
|
663
|
+
data["user_id"] = user_id
|
|
664
|
+
if session_id:
|
|
665
|
+
data["session_id"] = session_id
|
|
666
|
+
if provider:
|
|
667
|
+
data["provider"] = provider
|
|
668
|
+
if model:
|
|
669
|
+
data["model"] = model
|
|
670
|
+
if latency_ms is not None:
|
|
671
|
+
data["latency_ms"] = latency_ms
|
|
672
|
+
if prompt_tokens is not None:
|
|
673
|
+
data["prompt_tokens"] = prompt_tokens
|
|
674
|
+
if completion_tokens is not None:
|
|
675
|
+
data["completion_tokens"] = completion_tokens
|
|
676
|
+
if metadata:
|
|
677
|
+
data["metadata"] = metadata
|
|
678
|
+
|
|
679
|
+
try:
|
|
680
|
+
result = self.http_client.log_observation(data)
|
|
681
|
+
return LogResult(
|
|
682
|
+
observation_id=result.get("observation_id", ""),
|
|
683
|
+
experiment_id=result.get("experiment_id", ""),
|
|
684
|
+
status=result.get("status", "queued"),
|
|
685
|
+
)
|
|
686
|
+
except Exception as error:
|
|
687
|
+
self.logger.error(
|
|
688
|
+
"Observation logging failed",
|
|
689
|
+
{"error": str(error)},
|
|
690
|
+
)
|
|
691
|
+
raise
|
|
692
|
+
|
|
598
693
|
def clear_cache(self) -> None:
|
|
599
694
|
"""Clear the cache."""
|
|
600
695
|
self.cache.clear()
|
|
@@ -60,7 +60,7 @@ class HTTPClient:
|
|
|
60
60
|
session.headers.update({
|
|
61
61
|
"Content-Type": "application/json",
|
|
62
62
|
"X-API-Key": self.config.api_key,
|
|
63
|
-
"User-Agent": "Variably-Python-SDK/2.
|
|
63
|
+
"User-Agent": "Variably-Python-SDK/2.3.0",
|
|
64
64
|
})
|
|
65
65
|
|
|
66
66
|
return session
|
|
@@ -185,7 +185,6 @@ class HTTPClient:
|
|
|
185
185
|
def evaluate_prompt(
|
|
186
186
|
self, experiment_key: str, user_context: Dict[str, Any],
|
|
187
187
|
input_variables: Dict[str, Any],
|
|
188
|
-
evaluation_mode: str = "full",
|
|
189
188
|
evaluation_context: Optional[Dict[str, Any]] = None,
|
|
190
189
|
) -> Dict[str, Any]:
|
|
191
190
|
"""Managed execution: select variant, call LLM, evaluate."""
|
|
@@ -193,7 +192,6 @@ class HTTPClient:
|
|
|
193
192
|
"experiment_key": experiment_key,
|
|
194
193
|
"user_context": self._convert_user_context(user_context),
|
|
195
194
|
"input_variables": input_variables,
|
|
196
|
-
"evaluation_mode": evaluation_mode,
|
|
197
195
|
}
|
|
198
196
|
if evaluation_context:
|
|
199
197
|
request_data["evaluation_context"] = evaluation_context
|
|
@@ -208,7 +206,6 @@ class HTTPClient:
|
|
|
208
206
|
def evaluate_prompt_stream(
|
|
209
207
|
self, experiment_key: str, user_context: Dict[str, Any],
|
|
210
208
|
input_variables: Dict[str, Any],
|
|
211
|
-
evaluation_mode: str = "full",
|
|
212
209
|
evaluation_context: Optional[Dict[str, Any]] = None,
|
|
213
210
|
) -> requests.Response:
|
|
214
211
|
"""Managed execution with streaming: returns raw SSE response for token parsing."""
|
|
@@ -216,7 +213,6 @@ class HTTPClient:
|
|
|
216
213
|
"experiment_key": experiment_key,
|
|
217
214
|
"user_context": self._convert_user_context(user_context),
|
|
218
215
|
"input_variables": input_variables,
|
|
219
|
-
"evaluation_mode": evaluation_mode,
|
|
220
216
|
}
|
|
221
217
|
if evaluation_context:
|
|
222
218
|
request_data["evaluation_context"] = evaluation_context
|
|
@@ -247,6 +243,15 @@ class HTTPClient:
|
|
|
247
243
|
retryable=False,
|
|
248
244
|
)
|
|
249
245
|
|
|
246
|
+
def log_observation(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
247
|
+
"""Log a prompt/response pair for observe-mode evaluation."""
|
|
248
|
+
return self._make_request(
|
|
249
|
+
"POST",
|
|
250
|
+
"/api/v1/internal/sdk/observe/log",
|
|
251
|
+
json=data,
|
|
252
|
+
retryable=False,
|
|
253
|
+
)
|
|
254
|
+
|
|
250
255
|
def _make_request(
|
|
251
256
|
self,
|
|
252
257
|
method: str,
|
|
@@ -346,4 +346,19 @@ class SubmitResponseResult:
|
|
|
346
346
|
self.status = status
|
|
347
347
|
|
|
348
348
|
def __repr__(self) -> str:
|
|
349
|
-
return f"SubmitResponseResult(status='{self.status}')"
|
|
349
|
+
return f"SubmitResponseResult(status='{self.status}')"
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class LogResult:
|
|
353
|
+
"""Result from observe/log endpoint."""
|
|
354
|
+
|
|
355
|
+
def __init__(self, observation_id: str, experiment_id: str, status: str):
|
|
356
|
+
self.observation_id = observation_id
|
|
357
|
+
self.experiment_id = experiment_id
|
|
358
|
+
self.status = status
|
|
359
|
+
|
|
360
|
+
def __repr__(self) -> str:
|
|
361
|
+
return (
|
|
362
|
+
f"LogResult(observation_id='{self.observation_id}', "
|
|
363
|
+
f"experiment_id='{self.experiment_id}', status='{self.status}')"
|
|
364
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|