diff --git a/ddtrace/llmobs/_writer.py b/ddtrace/llmobs/_writer.py index 08b27451ffd..8678932d35f 100644 --- a/ddtrace/llmobs/_writer.py +++ b/ddtrace/llmobs/_writer.py @@ -304,7 +304,7 @@ def periodic(self) -> None: ) def _send_payload(self, payload: bytes, num_events: int): - conn = get_connection(self._intake) + conn = get_connection(self._intake, timeout=self._timeout) try: conn.request("POST", self._endpoint, payload, self._headers) resp = conn.getresponse() diff --git a/releasenotes/notes/fix-llmobs-writer-respect-timeout-cd84c70e0d69e371.yaml b/releasenotes/notes/fix-llmobs-writer-respect-timeout-cd84c70e0d69e371.yaml new file mode 100644 index 00000000000..980d35b9e22 --- /dev/null +++ b/releasenotes/notes/fix-llmobs-writer-respect-timeout-cd84c70e0d69e371.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + LLM Observability: Fixes an issue where the writer ignored its configured request + timeout (``_DD_LLMOBS_WRITER_TIMEOUT``, default 5 seconds) and instead used the 2 second + connection default, causing intermittent ``TimeoutError`` and dropped span and evaluation + metric events on high-latency connections to the agent or intake. diff --git a/tests/llmobs/test_llmobs_span_agentless_writer.py b/tests/llmobs/test_llmobs_span_agentless_writer.py index cf2269e3baa..a94049399f6 100644 --- a/tests/llmobs/test_llmobs_span_agentless_writer.py +++ b/tests/llmobs/test_llmobs_span_agentless_writer.py @@ -136,6 +136,18 @@ def log_message(self, *args): ) +@mock.patch("ddtrace.llmobs._writer.get_connection") +def test_send_payload_uses_configured_timeout(mock_get_connection, mock_writer_logs): + """Regression: _send_payload must open the connection with the writer's configured + timeout (self._timeout), not get_connection's DEFAULT_TIMEOUT (2.0s). + """ + mock_get_connection.return_value.getresponse.return_value.status = 200 + llmobs_span_writer = LLMObsSpanWriter(1, 5.0, is_agentless=True, _site=DD_SITE, _api_key=DD_API_KEY) + llmobs_span_writer.enqueue(_completion_event()) + llmobs_span_writer.periodic() + mock_get_connection.assert_called_once_with(llmobs_span_writer._intake, timeout=5.0) + + def test_send_completion_no_api_key(mock_writer_logs): with override_global_config(dict(_dd_api_key="")): llmobs_span_writer = LLMObsSpanWriter(interval=1, timeout=1, is_agentless=True, _site=DD_SITE, _api_key="")