diff --git a/lib/crewai/src/crewai/llms/base_llm.py b/lib/crewai/src/crewai/llms/base_llm.py index 94d5eb6b9f..d8cd59032f 100644 --- a/lib/crewai/src/crewai/llms/base_llm.py +++ b/lib/crewai/src/crewai/llms/base_llm.py @@ -168,6 +168,13 @@ class BaseLLM(BaseModel, ABC): ) additional_params: dict[str, Any] = Field(default_factory=dict) + def __repr__(self) -> str: + """Return a representation with sensitive fields masked.""" + d = self.model_dump() + if d.get("api_key"): + d["api_key"] = "***" + return f"{self.__class__.__name__}({d})" + @field_serializer("response_format", when_used="json", check_fields=False) def _serialize_response_format(self, value: Any) -> Any: return serialize_model_class(value) diff --git a/lib/crewai/tests/test_custom_llm.py b/lib/crewai/tests/test_custom_llm.py index af07cfd03f..8cab5a28d3 100644 --- a/lib/crewai/tests/test_custom_llm.py +++ b/lib/crewai/tests/test_custom_llm.py @@ -75,10 +75,31 @@ def get_context_window_size(self) -> int: """ return 4096 - async def acall(self, messages, tools=None, callbacks=None, available_functions=None, from_task=None, from_agent=None, response_model=None): + async def acall( + self, + messages, + tools=None, + callbacks=None, + available_functions=None, + from_task=None, + from_agent=None, + response_model=None, + ): + """Raise because async calls are not implemented for this test LLM.""" raise NotImplementedError +def test_base_llm_repr_masks_api_key(): + """Test that BaseLLM repr does not expose the API key.""" + llm = CustomLLM() + llm.api_key = "sk-secret" + + representation = repr(llm) + + assert "sk-secret" not in representation + assert "'api_key': '***'" in representation + + @pytest.mark.vcr() def test_custom_llm_implementation(): """Test that a custom LLM implementation works with create_llm."""