Skip to content

Commit 8352201

Browse files
feat(langsmith.py): add per request sampling_rate support
allows setting langsmith sampling rate per team/per key Closes LIT-879
1 parent e5c1d09 commit 8352201

File tree

4 files changed

+66
-69
lines changed

4 files changed

+66
-69
lines changed

litellm/integrations/langsmith.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -78,26 +78,14 @@ def get_credentials_from_env(
7878
langsmith_base_url: Optional[str] = None,
7979
) -> LangsmithCredentialsObject:
8080
_credentials_api_key = langsmith_api_key or os.getenv("LANGSMITH_API_KEY")
81-
if _credentials_api_key is None:
82-
raise Exception(
83-
"Invalid Langsmith API Key given. _credentials_api_key=None."
84-
)
8581
_credentials_project = (
8682
langsmith_project or os.getenv("LANGSMITH_PROJECT") or "litellm-completion"
8783
)
88-
if _credentials_project is None:
89-
raise Exception(
90-
"Invalid Langsmith API Key given. _credentials_project=None."
91-
)
9284
_credentials_base_url = (
9385
langsmith_base_url
9486
or os.getenv("LANGSMITH_BASE_URL")
9587
or "https://api.smith.langchain.com"
9688
)
97-
if _credentials_base_url is None:
98-
raise Exception(
99-
"Invalid Langsmith API Key given. _credentials_base_url=None."
100-
)
10189

10290
return LangsmithCredentialsObject(
10391
LANGSMITH_API_KEY=_credentials_api_key,
@@ -202,12 +190,7 @@ def _prepare_log_data(
202190

203191
def log_success_event(self, kwargs, response_obj, start_time, end_time):
204192
try:
205-
sampling_rate = (
206-
float(os.getenv("LANGSMITH_SAMPLING_RATE")) # type: ignore
207-
if os.getenv("LANGSMITH_SAMPLING_RATE") is not None
208-
and os.getenv("LANGSMITH_SAMPLING_RATE").strip().isdigit() # type: ignore
209-
else 1.0
210-
)
193+
sampling_rate = self._get_sampling_rate_to_use_for_request(kwargs=kwargs)
211194
random_sample = random.random()
212195
if random_sample > sampling_rate:
213196
verbose_logger.info(
@@ -221,6 +204,7 @@ def log_success_event(self, kwargs, response_obj, start_time, end_time):
221204
kwargs,
222205
response_obj,
223206
)
207+
224208
credentials = self._get_credentials_to_use_for_request(kwargs=kwargs)
225209
data = self._prepare_log_data(
226210
kwargs=kwargs,
@@ -247,7 +231,7 @@ def log_success_event(self, kwargs, response_obj, start_time, end_time):
247231

248232
async def async_log_success_event(self, kwargs, response_obj, start_time, end_time):
249233
try:
250-
sampling_rate = self.sampling_rate
234+
sampling_rate = self._get_sampling_rate_to_use_for_request(kwargs=kwargs)
251235
random_sample = random.random()
252236
if random_sample > sampling_rate:
253237
verbose_logger.info(
@@ -288,7 +272,7 @@ async def async_log_success_event(self, kwargs, response_obj, start_time, end_ti
288272
)
289273

290274
async def async_log_failure_event(self, kwargs, response_obj, start_time, end_time):
291-
sampling_rate = self.sampling_rate
275+
sampling_rate = self._get_sampling_rate_to_use_for_request(kwargs=kwargs)
292276
random_sample = random.random()
293277
if random_sample > sampling_rate:
294278
verbose_logger.info(
@@ -419,6 +403,17 @@ def _group_batches_by_credentials(self) -> Dict[CredentialsKey, BatchGroup]:
419403

420404
for queue_object in self.log_queue:
421405
credentials = queue_object["credentials"]
406+
# if credential missing, skip - log warning
407+
if (
408+
credentials["LANGSMITH_API_KEY"] is None
409+
or credentials["LANGSMITH_PROJECT"] is None
410+
):
411+
verbose_logger.warning(
412+
"Langsmith Logging - credentials missing - api_key: %s, project: %s",
413+
credentials["LANGSMITH_API_KEY"],
414+
credentials["LANGSMITH_PROJECT"],
415+
)
416+
continue
422417
key = CredentialsKey(
423418
api_key=credentials["LANGSMITH_API_KEY"],
424419
project=credentials["LANGSMITH_PROJECT"],
@@ -434,6 +429,19 @@ def _group_batches_by_credentials(self) -> Dict[CredentialsKey, BatchGroup]:
434429

435430
return log_queue_by_credentials
436431

432+
def _get_sampling_rate_to_use_for_request(self, kwargs: Dict[str, Any]) -> float:
433+
standard_callback_dynamic_params: Optional[StandardCallbackDynamicParams] = (
434+
kwargs.get("standard_callback_dynamic_params", None)
435+
)
436+
sampling_rate: float = self.sampling_rate
437+
if standard_callback_dynamic_params is not None:
438+
_sampling_rate = standard_callback_dynamic_params.get(
439+
"langsmith_sampling_rate"
440+
)
441+
if _sampling_rate is not None:
442+
sampling_rate = float(_sampling_rate)
443+
return sampling_rate
444+
437445
def _get_credentials_to_use_for_request(
438446
self, kwargs: Dict[str, Any]
439447
) -> LangsmithCredentialsObject:

litellm/proxy/_new_secret_config.yaml

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,3 @@ model_list:
1616
api_base: "https://webhook.site/2f385e05-00aa-402b-86d1-efc9261471a5"
1717
api_key: dummy
1818

19-
20-
21-
22-
guardrails:
23-
- guardrail_name: "intel-bedrock-guard-cfg"
24-
litellm_params:
25-
guardrail: bedrock
26-
mode: [pre_call, post_call]
27-
guardrailIdentifier: "1234"
28-
guardrailVersion: "1"
29-
aws_access_key_id: "os.environ/AWS_ACCESS_KEY_ID"
30-
aws_secret_access_key: "os.environ/AWS_SECRET_ACCESS_KEY"
31-
aws_bedrock_runtime_endpoint: "os.environ/AWS_BEDROCK_RUNTIME_ENDPOINT"
32-
default_on: true

litellm/router.py

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -359,9 +359,9 @@ def __init__( # noqa: PLR0915
359359
) # names of models under litellm_params. ex. azure/chatgpt-v-2
360360
self.deployment_latency_map = {}
361361
### CACHING ###
362-
cache_type: Literal[
363-
"local", "redis", "redis-semantic", "s3", "disk"
364-
] = "local" # default to an in-memory cache
362+
cache_type: Literal["local", "redis", "redis-semantic", "s3", "disk"] = (
363+
"local" # default to an in-memory cache
364+
)
365365
redis_cache = None
366366
cache_config: Dict[str, Any] = {}
367367

@@ -403,9 +403,9 @@ def __init__( # noqa: PLR0915
403403
self.default_max_parallel_requests = default_max_parallel_requests
404404
self.provider_default_deployment_ids: List[str] = []
405405
self.pattern_router = PatternMatchRouter()
406-
self.team_pattern_routers: Dict[
407-
str, PatternMatchRouter
408-
] = {} # {"TEAM_ID": PatternMatchRouter}
406+
self.team_pattern_routers: Dict[str, PatternMatchRouter] = (
407+
{}
408+
) # {"TEAM_ID": PatternMatchRouter}
409409
self.auto_routers: Dict[str, "AutoRouter"] = {}
410410

411411
if model_list is not None:
@@ -587,9 +587,9 @@ def __init__( # noqa: PLR0915
587587
)
588588
)
589589

590-
self.model_group_retry_policy: Optional[
591-
Dict[str, RetryPolicy]
592-
] = model_group_retry_policy
590+
self.model_group_retry_policy: Optional[Dict[str, RetryPolicy]] = (
591+
model_group_retry_policy
592+
)
593593

594594
self.allowed_fails_policy: Optional[AllowedFailsPolicy] = None
595595
if allowed_fails_policy is not None:
@@ -1211,7 +1211,10 @@ async def stream_with_fallbacks():
12111211

12121212
async def _acompletion(
12131213
self, model: str, messages: List[Dict[str, str]], **kwargs
1214-
) -> Union[ModelResponse, CustomStreamWrapper,]:
1214+
) -> Union[
1215+
ModelResponse,
1216+
CustomStreamWrapper,
1217+
]:
12151218
"""
12161219
- Get an available deployment
12171220
- call it with a semaphore over the call
@@ -3155,9 +3158,9 @@ async def create_file_for_deployment(deployment: dict) -> OpenAIFileObject:
31553158
healthy_deployments=healthy_deployments, responses=responses
31563159
)
31573160
returned_response = cast(OpenAIFileObject, responses[0])
3158-
returned_response._hidden_params[
3159-
"model_file_id_mapping"
3160-
] = model_file_id_mapping
3161+
returned_response._hidden_params["model_file_id_mapping"] = (
3162+
model_file_id_mapping
3163+
)
31613164
return returned_response
31623165
except Exception as e:
31633166
verbose_router_logger.exception(
@@ -3720,11 +3723,11 @@ async def async_function_with_fallbacks_common_utils( # noqa: PLR0915
37203723

37213724
if isinstance(e, litellm.ContextWindowExceededError):
37223725
if context_window_fallbacks is not None:
3723-
context_window_fallback_model_group: Optional[
3724-
List[str]
3725-
] = self._get_fallback_model_group_from_fallbacks(
3726-
fallbacks=context_window_fallbacks,
3727-
model_group=model_group,
3726+
context_window_fallback_model_group: Optional[List[str]] = (
3727+
self._get_fallback_model_group_from_fallbacks(
3728+
fallbacks=context_window_fallbacks,
3729+
model_group=model_group,
3730+
)
37283731
)
37293732
if context_window_fallback_model_group is None:
37303733
raise original_exception
@@ -3756,11 +3759,11 @@ async def async_function_with_fallbacks_common_utils( # noqa: PLR0915
37563759
e.message += "\n{}".format(error_message)
37573760
elif isinstance(e, litellm.ContentPolicyViolationError):
37583761
if content_policy_fallbacks is not None:
3759-
content_policy_fallback_model_group: Optional[
3760-
List[str]
3761-
] = self._get_fallback_model_group_from_fallbacks(
3762-
fallbacks=content_policy_fallbacks,
3763-
model_group=model_group,
3762+
content_policy_fallback_model_group: Optional[List[str]] = (
3763+
self._get_fallback_model_group_from_fallbacks(
3764+
fallbacks=content_policy_fallbacks,
3765+
model_group=model_group,
3766+
)
37643767
)
37653768
if content_policy_fallback_model_group is None:
37663769
raise original_exception
@@ -4414,7 +4417,7 @@ async def deployment_callback_on_success(
44144417
return tpm_key
44154418

44164419
except Exception as e:
4417-
verbose_router_logger.exception(
4420+
verbose_router_logger.debug(
44184421
"litellm.router.Router::deployment_callback_on_success(): Exception occured - {}".format(
44194422
str(e)
44204423
)
@@ -4992,26 +4995,26 @@ def init_auto_router_deployment(self, deployment: Deployment):
49924995
"""
49934996
from litellm.router_strategy.auto_router.auto_router import AutoRouter
49944997

4995-
auto_router_config_path: Optional[
4996-
str
4997-
] = deployment.litellm_params.auto_router_config_path
4998+
auto_router_config_path: Optional[str] = (
4999+
deployment.litellm_params.auto_router_config_path
5000+
)
49985001
auto_router_config: Optional[str] = deployment.litellm_params.auto_router_config
49995002
if auto_router_config_path is None and auto_router_config is None:
50005003
raise ValueError(
50015004
"auto_router_config_path or auto_router_config is required for auto-router deployments. Please set it in the litellm_params"
50025005
)
50035006

5004-
default_model: Optional[
5005-
str
5006-
] = deployment.litellm_params.auto_router_default_model
5007+
default_model: Optional[str] = (
5008+
deployment.litellm_params.auto_router_default_model
5009+
)
50075010
if default_model is None:
50085011
raise ValueError(
50095012
"auto_router_default_model is required for auto-router deployments. Please set it in the litellm_params"
50105013
)
50115014

5012-
embedding_model: Optional[
5013-
str
5014-
] = deployment.litellm_params.auto_router_embedding_model
5015+
embedding_model: Optional[str] = (
5016+
deployment.litellm_params.auto_router_embedding_model
5017+
)
50155018
if embedding_model is None:
50165019
raise ValueError(
50175020
"auto_router_embedding_model is required for auto-router deployments. Please set it in the litellm_params"

litellm/types/integrations/langsmith.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ class LangsmithInputs(BaseModel):
2828

2929

3030
class LangsmithCredentialsObject(TypedDict):
31-
LANGSMITH_API_KEY: str
32-
LANGSMITH_PROJECT: str
31+
LANGSMITH_API_KEY: Optional[str]
32+
LANGSMITH_PROJECT: Optional[str]
3333
LANGSMITH_BASE_URL: str
3434

3535

0 commit comments

Comments
 (0)