129 lines
4.6 KiB
Python
129 lines
4.6 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
from typing import Any, Awaitable, Callable, Dict, Iterable, List, Optional
|
||
|
|
from typing_extensions import deprecated
|
||
|
|
|
||
|
|
from pydantic import Field, field_serializer, field_validator
|
||
|
|
|
||
|
|
from guardrails.classes.history.inputs import Inputs
|
||
|
|
from guardrails.classes.generic.arbitrary_model import ArbitraryModel
|
||
|
|
from guardrails.prompt.base_prompt import BasePrompt
|
||
|
|
|
||
|
|
|
||
|
|
class CallInputs(Inputs, ArbitraryModel):
|
||
|
|
"""CallInputs represent the input data that is passed into the Guard from
|
||
|
|
the user.
|
||
|
|
|
||
|
|
Inherits from Inputs with the below overrides and additional
|
||
|
|
attributes.
|
||
|
|
"""
|
||
|
|
|
||
|
|
llm_api: Optional[Callable[[Any], Awaitable[Any]]] = Field(
|
||
|
|
description="The LLM function provided by the user"
|
||
|
|
"during Guard.__call__ or Guard.parse.",
|
||
|
|
default=None,
|
||
|
|
alias="llmApi",
|
||
|
|
)
|
||
|
|
llm_output: Optional[str] = Field(
|
||
|
|
default=None,
|
||
|
|
description="The string output from an external LLM call provided by the user"
|
||
|
|
" via Guard.parse.",
|
||
|
|
alias="llmOutput",
|
||
|
|
)
|
||
|
|
messages: Optional[list[dict[str, str]]] = Field(
|
||
|
|
description="The messages as provided by the user.", default=None
|
||
|
|
)
|
||
|
|
prompt_params: Optional[Dict[str, Any]] = Field(
|
||
|
|
default=None,
|
||
|
|
description="Parameters to be formatted into the messages.",
|
||
|
|
alias="promptParams",
|
||
|
|
)
|
||
|
|
num_reasks: Optional[int] = Field(
|
||
|
|
default=None,
|
||
|
|
description="The total number of times the LLM can be called to correct output"
|
||
|
|
" excluding the initial call.",
|
||
|
|
alias="numReasks",
|
||
|
|
)
|
||
|
|
metadata: Optional[Dict[str, Any]] = Field(
|
||
|
|
default=None,
|
||
|
|
description="Additional data to be used by Validators during execution time.",
|
||
|
|
)
|
||
|
|
full_schema_reask: Optional[bool] = Field(
|
||
|
|
default=None,
|
||
|
|
description="Whether to perform reasks for the entire schema rather than for"
|
||
|
|
" individual fields.",
|
||
|
|
alias="fullSchemaReask",
|
||
|
|
)
|
||
|
|
stream: Optional[bool] = Field(
|
||
|
|
default=None, description="Whether to use streaming."
|
||
|
|
)
|
||
|
|
args: List[Any] = Field(
|
||
|
|
description="Additional arguments for the LLM as provided by the user.",
|
||
|
|
default_factory=list,
|
||
|
|
)
|
||
|
|
kwargs: Dict[str, Any] = Field(
|
||
|
|
description="Additional keyword-arguments for the LLM as provided by the user.",
|
||
|
|
default_factory=dict,
|
||
|
|
)
|
||
|
|
|
||
|
|
@field_serializer("llm_api")
|
||
|
|
def serialize_llm_api(
|
||
|
|
self, llm_api: Callable[[Any], Awaitable[Any]] | None
|
||
|
|
) -> str | None:
|
||
|
|
if llm_api:
|
||
|
|
return str(llm_api)
|
||
|
|
return None
|
||
|
|
|
||
|
|
@field_validator("llm_api", mode="before")
|
||
|
|
@classmethod
|
||
|
|
def deserialize_llm_api(
|
||
|
|
cls, llm_api: Any
|
||
|
|
) -> Callable[[Any], Awaitable[Any]] | None:
|
||
|
|
if callable(llm_api):
|
||
|
|
return llm_api # type: ignore
|
||
|
|
# Note: We can potentially identify the correct
|
||
|
|
# PrompCallable Class and reconstruct it,
|
||
|
|
# but the previous implementation always just returned None.
|
||
|
|
return None
|
||
|
|
|
||
|
|
@field_validator("messages", mode="before")
|
||
|
|
@classmethod
|
||
|
|
def deserialize_messages(cls, messages: Any) -> list[dict[str, str]] | None:
|
||
|
|
if messages is not None and isinstance(messages, Iterable):
|
||
|
|
serialized_messages = []
|
||
|
|
for msg in messages:
|
||
|
|
ser_msg = {**msg}
|
||
|
|
content = ser_msg.get("content")
|
||
|
|
if content:
|
||
|
|
ser_msg["content"] = (
|
||
|
|
content.source if isinstance(content, BasePrompt) else content
|
||
|
|
)
|
||
|
|
serialized_messages.append(ser_msg)
|
||
|
|
return serialized_messages
|
||
|
|
return None
|
||
|
|
|
||
|
|
@field_serializer("kwargs")
|
||
|
|
def serialize_kwargs(self, kwargs: dict[str, Any]) -> dict[str, Any]:
|
||
|
|
redacted_kwargs = {}
|
||
|
|
for k, v in kwargs.items():
|
||
|
|
if ("key" in k.lower() or "token" in k.lower()) and isinstance(v, str):
|
||
|
|
redaction_length = len(v) - 4
|
||
|
|
stars = "*" * redaction_length
|
||
|
|
redacted_kwargs[k] = f"{stars}{v[-4:]}"
|
||
|
|
else:
|
||
|
|
redacted_kwargs[k] = v
|
||
|
|
return redacted_kwargs
|
||
|
|
|
||
|
|
@deprecated("Use CallInputs.model_dump() instead.")
|
||
|
|
def to_dict(self) -> Dict[str, Any]:
|
||
|
|
return self.model_dump(exclude_none=True, by_alias=True)
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
@deprecated("Use CallInputs.model_validate() instead.")
|
||
|
|
def from_interface(cls, i_call_inputs: Any) -> "CallInputs":
|
||
|
|
return cls.model_validate(i_call_inputs)
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
@deprecated("Use CallInputs.model_validate() instead.")
|
||
|
|
def from_dict(cls, obj: Any):
|
||
|
|
return cls.model_validate(obj)
|