mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
* Fix duplicate imports in SAP embedding transformation * fix: add missing prompt_spec parameter to HumanloopLogger.get_chat_completion_prompt - Add prompt_spec: Optional[PromptSpec] = None parameter to match base class signature - Import PromptSpec from litellm.types.prompts.init_prompts - Pass prompt_spec to super().get_chat_completion_prompt() call - Fixes mypy type error: Signature incompatible with supertype CustomLogger * fix: add missing parameters to AnthropicCacheControlHook.async_get_chat_completion_prompt - Add ignore_prompt_manager_model and ignore_prompt_manager_optional_params parameters - Change litellm_logging_obj type from Any to LiteLLMLoggingObj using TYPE_CHECKING pattern - Pass all parameters including prompt_spec to get_chat_completion_prompt call - Fixes mypy type errors: Signature incompatible with supertype CustomLogger and PromptManagementBase * fix: add missing parameters to DotpromptManager.async_get_chat_completion_prompt - Add ignore_prompt_manager_model and ignore_prompt_manager_optional_params parameters - Change litellm_logging_obj type from Any to LiteLLMLoggingObj using TYPE_CHECKING pattern - Pass all parameters including ignore flags to PromptManagementBase.async_get_chat_completion_prompt - Fixes mypy type errors: Signature incompatible with supertype CustomLogger and PromptManagementBase * fix: document envs * fix: add missing parameters to LangfusePromptManagement.async_get_chat_completion_prompt - Add ignore_prompt_manager_model and ignore_prompt_manager_optional_params parameters - Pass all parameters including prompt_spec and ignore flags to get_chat_completion_prompt - Fixes mypy type errors: Signature incompatible with supertype CustomLogger and PromptManagementBase * fix: add missing parameters to prompt management async methods (Category 1) - vector_store_pre_call_hook: add ignore_prompt_manager_model, ignore_prompt_manager_optional_params, prompt_spec - gitlab_prompt_manager: add ignore parameters, fix litellm_logging_obj type - bitbucket_prompt_manager: add ignore parameters, fix litellm_logging_obj type - proxy/custom_prompt_management: add prompt_spec parameter - Fixes mypy type errors: Signature incompatible with supertype * fix: fix arize_phoenix_prompt_manager and custom_prompt_management (Category 2) - arize_phoenix_prompt_manager: add prompt_spec to all methods, fix prompt_id types, implement async_compile_prompt_helper - custom_prompt_management: implement async_compile_prompt_helper abstract method - Fixes mypy type errors: Signature incompatible with supertype and abstract method errors * fix: fix obvious type errors (Category 3 - Quick Wins) - langfuse: change 'callable' to 'Callable' type annotation - presidio: add type narrowing check for Choices vs StreamingChoices - StreamingChoices doesn't have .message attribute, only Choices does - Add hasattr check before accessing choice.message - Fixes mypy type errors: callable? not callable and union-attr errors * fix: handle expires_after None in Azure files handler (Todo 14) - Extract logic to _prepare_create_file_data helper method - Remove expires_after from dict if None to match SDK's Omit pattern - Add type ignore for FileExpiresAfter -> file_create_params.ExpiresAfter mismatch - Fixes mypy error: Argument expires_after has incompatible type * fix: change purpose parameter type to OpenAIFilesPurpose (Todo 18) - Import OpenAIFilesPurpose in storage_backend_service.py - Change upload_file_to_storage_backend purpose parameter from str to OpenAIFilesPurpose - Change _create_file_object_with_storage_metadata purpose parameter from str to OpenAIFilesPurpose - Fixes mypy error: Argument purpose has incompatible type str; expected Literal type - Purpose is already validated in files_endpoints.py before reaching these functions * fix: handle UploadFile | str type for expires_after form fields (Todo 19) - Validate expires_after[anchor] and expires_after[seconds] are strings, not UploadFiles - Validate anchor equals 'created_at' before using literal in TypedDict - Use literal 'created_at' (not variable) in FileExpiresAfter to satisfy Literal type - Add proper error handling for invalid anchor values and int conversion - Fixes mypy errors: Incompatible types for anchor and seconds in FileExpiresAfter * fix: add type narrowing for expires_after_seconds_str to fix mypy error - Add assert statement after UploadFile validation to help mypy narrow type - Use validated variable with explicit str type annotation - Fixes: Argument of type 'UploadFile | str' cannot be assigned to int() * fix: trigger async_success_handler for MCP tool calls to enable cost tracking and logging - Set call_type to CallTypes.call_mcp_tool.value before calling async_success_handler - Update mcp_tool_call_metadata with cost info when server is found - Call async_success_handler to build standard_logging_object and trigger callbacks - Fixes test_mcp_cost_tracking by ensuring standard_logging_payload is populated * refactor: use positive isinstance check for safer type narrowing - Replace assert with positive isinstance(..., str) check - Matches codebase pattern (see pass_through_endpoints.py) - Safer than assert: assertions can be disabled with -O flag - Mypy properly narrows type after positive isinstance check - More explicit and readable than assert statement * fix: add missing REDIS_DAILY_AGENT_SPEND_UPDATE_QUEUE to ServiceTypes enum (Todo 17) - Add REDIS_DAILY_AGENT_SPEND_UPDATE_QUEUE enum value following the pattern of other daily spend queues - Add corresponding entry to DEFAULT_SERVICE_CONFIGS with GAUGE metrics - Fixes mypy error: 'type[ServiceTypes]' has no attribute 'REDIS_DAILY_AGENT_SPEND_UPDATE_QUEUE' - This enum value is already used in redis_update_buffer.py for agent spend tracking
208 lines
6.9 KiB
Python
208 lines
6.9 KiB
Python
"""
|
|
Humanloop integration
|
|
|
|
https://humanloop.com/
|
|
"""
|
|
|
|
from typing import Any, Dict, List, Optional, Tuple, Union, cast
|
|
|
|
import httpx
|
|
from typing_extensions import TypedDict
|
|
|
|
import litellm
|
|
from litellm.caching import DualCache
|
|
from litellm.llms.custom_httpx.http_handler import _get_httpx_client
|
|
from litellm.secret_managers.main import get_secret_str
|
|
from litellm.types.llms.openai import AllMessageValues
|
|
from litellm.types.prompts.init_prompts import PromptSpec
|
|
from litellm.types.utils import StandardCallbackDynamicParams
|
|
|
|
from .custom_logger import CustomLogger
|
|
|
|
|
|
class PromptManagementClient(TypedDict):
|
|
prompt_id: str
|
|
prompt_template: List[AllMessageValues]
|
|
model: Optional[str]
|
|
optional_params: Optional[Dict[str, Any]]
|
|
|
|
|
|
class HumanLoopPromptManager(DualCache):
|
|
@property
|
|
def integration_name(self):
|
|
return "humanloop"
|
|
|
|
def _get_prompt_from_id_cache(
|
|
self, humanloop_prompt_id: str
|
|
) -> Optional[PromptManagementClient]:
|
|
return cast(
|
|
Optional[PromptManagementClient], self.get_cache(key=humanloop_prompt_id)
|
|
)
|
|
|
|
def _compile_prompt_helper(
|
|
self, prompt_template: List[AllMessageValues], prompt_variables: Dict[str, Any]
|
|
) -> List[AllMessageValues]:
|
|
"""
|
|
Helper function to compile the prompt by substituting variables in the template.
|
|
|
|
Args:
|
|
prompt_template: List[AllMessageValues]
|
|
prompt_variables (dict): A dictionary of variables to substitute into the prompt template.
|
|
|
|
Returns:
|
|
list: A list of dictionaries with variables substituted.
|
|
"""
|
|
compiled_prompts: List[AllMessageValues] = []
|
|
|
|
for template in prompt_template:
|
|
tc = template.get("content")
|
|
if tc and isinstance(tc, str):
|
|
formatted_template = tc.replace("{{", "{").replace("}}", "}")
|
|
compiled_content = formatted_template.format(**prompt_variables)
|
|
template["content"] = compiled_content
|
|
compiled_prompts.append(template)
|
|
|
|
return compiled_prompts
|
|
|
|
def _get_prompt_from_id_api(
|
|
self, humanloop_prompt_id: str, humanloop_api_key: str
|
|
) -> PromptManagementClient:
|
|
client = _get_httpx_client()
|
|
|
|
base_url = "https://api.humanloop.com/v5/prompts/{}".format(humanloop_prompt_id)
|
|
|
|
response = client.get(
|
|
url=base_url,
|
|
headers={
|
|
"X-Api-Key": humanloop_api_key,
|
|
"Content-Type": "application/json",
|
|
},
|
|
)
|
|
|
|
try:
|
|
response.raise_for_status()
|
|
except httpx.HTTPStatusError as e:
|
|
raise Exception(f"Error getting prompt from Humanloop: {e.response.text}")
|
|
|
|
json_response = response.json()
|
|
template_message = json_response["template"]
|
|
if isinstance(template_message, dict):
|
|
template_messages = [template_message]
|
|
elif isinstance(template_message, list):
|
|
template_messages = template_message
|
|
else:
|
|
raise ValueError(f"Invalid template message type: {type(template_message)}")
|
|
template_model = json_response["model"]
|
|
optional_params = {}
|
|
for k, v in json_response.items():
|
|
if k in litellm.OPENAI_CHAT_COMPLETION_PARAMS:
|
|
optional_params[k] = v
|
|
return PromptManagementClient(
|
|
prompt_id=humanloop_prompt_id,
|
|
prompt_template=cast(List[AllMessageValues], template_messages),
|
|
model=template_model,
|
|
optional_params=optional_params,
|
|
)
|
|
|
|
def _get_prompt_from_id(
|
|
self, humanloop_prompt_id: str, humanloop_api_key: str
|
|
) -> PromptManagementClient:
|
|
prompt = self._get_prompt_from_id_cache(humanloop_prompt_id)
|
|
if prompt is None:
|
|
prompt = self._get_prompt_from_id_api(
|
|
humanloop_prompt_id, humanloop_api_key
|
|
)
|
|
self.set_cache(
|
|
key=humanloop_prompt_id,
|
|
value=prompt,
|
|
ttl=litellm.HUMANLOOP_PROMPT_CACHE_TTL_SECONDS,
|
|
)
|
|
return prompt
|
|
|
|
def compile_prompt(
|
|
self,
|
|
prompt_template: List[AllMessageValues],
|
|
prompt_variables: Optional[dict],
|
|
) -> List[AllMessageValues]:
|
|
compiled_prompt: Optional[Union[str, list]] = None
|
|
|
|
if prompt_variables is None:
|
|
prompt_variables = {}
|
|
|
|
compiled_prompt = self._compile_prompt_helper(
|
|
prompt_template=prompt_template,
|
|
prompt_variables=prompt_variables,
|
|
)
|
|
|
|
return compiled_prompt
|
|
|
|
def _get_model_from_prompt(
|
|
self, prompt_management_client: PromptManagementClient, model: str
|
|
) -> str:
|
|
if prompt_management_client["model"] is not None:
|
|
return prompt_management_client["model"]
|
|
else:
|
|
return model.replace("{}/".format(self.integration_name), "")
|
|
|
|
|
|
prompt_manager = HumanLoopPromptManager()
|
|
|
|
|
|
class HumanloopLogger(CustomLogger):
|
|
def get_chat_completion_prompt(
|
|
self,
|
|
model: str,
|
|
messages: List[AllMessageValues],
|
|
non_default_params: dict,
|
|
prompt_id: Optional[str],
|
|
prompt_variables: Optional[dict],
|
|
dynamic_callback_params: StandardCallbackDynamicParams,
|
|
prompt_spec: Optional[PromptSpec] = None,
|
|
prompt_label: Optional[str] = None,
|
|
prompt_version: Optional[int] = None,
|
|
ignore_prompt_manager_model: Optional[bool] = False,
|
|
ignore_prompt_manager_optional_params: Optional[bool] = False,
|
|
) -> Tuple[
|
|
str,
|
|
List[AllMessageValues],
|
|
dict,
|
|
]:
|
|
humanloop_api_key = dynamic_callback_params.get(
|
|
"humanloop_api_key"
|
|
) or get_secret_str("HUMANLOOP_API_KEY")
|
|
|
|
if prompt_id is None:
|
|
raise ValueError("prompt_id is required for Humanloop integration")
|
|
|
|
if humanloop_api_key is None:
|
|
return super().get_chat_completion_prompt(
|
|
model=model,
|
|
messages=messages,
|
|
non_default_params=non_default_params,
|
|
prompt_id=prompt_id,
|
|
prompt_variables=prompt_variables,
|
|
dynamic_callback_params=dynamic_callback_params,
|
|
prompt_spec=prompt_spec,
|
|
)
|
|
|
|
prompt_template = prompt_manager._get_prompt_from_id(
|
|
humanloop_prompt_id=prompt_id, humanloop_api_key=humanloop_api_key
|
|
)
|
|
|
|
updated_messages = prompt_manager.compile_prompt(
|
|
prompt_template=prompt_template["prompt_template"],
|
|
prompt_variables=prompt_variables,
|
|
)
|
|
|
|
prompt_template_optional_params = prompt_template["optional_params"] or {}
|
|
|
|
updated_non_default_params = {
|
|
**non_default_params,
|
|
**prompt_template_optional_params,
|
|
}
|
|
|
|
model = prompt_manager._get_model_from_prompt(
|
|
prompt_management_client=prompt_template, model=model
|
|
)
|
|
|
|
return model, updated_messages, updated_non_default_params
|