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.
Files changed (20) hide show
  1. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/PKG-INFO +1 -1
  2. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/pyproject.toml +1 -1
  3. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/setup.py +1 -1
  4. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/client.py +99 -4
  5. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/http_client.py +10 -5
  6. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/types.py +16 -1
  7. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/version.py +1 -1
  8. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably_sdk.egg-info/PKG-INFO +1 -1
  9. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/README.md +0 -0
  10. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/setup.cfg +0 -0
  11. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/__init__.py +0 -0
  12. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/cache.py +0 -0
  13. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/config.py +0 -0
  14. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/errors.py +0 -0
  15. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably/logger.py +0 -0
  16. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably_sdk.egg-info/SOURCES.txt +0 -0
  17. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably_sdk.egg-info/dependency_links.txt +0 -0
  18. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably_sdk.egg-info/requires.txt +0 -0
  19. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/src/variably_sdk.egg-info/top_level.txt +0 -0
  20. {variably_sdk-2.2.0 → variably_sdk-2.4.0}/tests/test_streaming.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: variably-sdk
3
- Version: 2.2.0
3
+ Version: 2.4.0
4
4
  Summary: Official Python SDK for Variably feature flags, LLM experimentation, and prompt optimization platform
5
5
  Author: Variably
6
6
  Author-email: Variably <support@variably.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "variably-sdk"
7
- version = "2.2.0"
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"
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="variably",
5
- version="1.0.0",
5
+ version="2.4.0",
6
6
  description="Variably Python SDK for LLM experimentation platform",
7
7
  author="Variably",
8
8
  packages=find_packages(),
@@ -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, evaluation_mode,
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, evaluation_mode,
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.1.0",
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
+ )
@@ -1,3 +1,3 @@
1
1
  """Version information for Variably SDK."""
2
2
 
3
- __version__ = "2.1.0"
3
+ __version__ = "2.3.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: variably-sdk
3
- Version: 2.2.0
3
+ Version: 2.4.0
4
4
  Summary: Official Python SDK for Variably feature flags, LLM experimentation, and prompt optimization platform
5
5
  Author: Variably
6
6
  Author-email: Variably <support@variably.com>
File without changes
File without changes