From ab941c588ae28567912cb7a33c4dfc70df01bca9 Mon Sep 17 00:00:00 2001 From: Franklin <5235904+mrFranklin@users.noreply.github.com> Date: Fri, 19 Sep 2025 17:27:51 +0800 Subject: [PATCH 1/7] doc: navigate to the correct location --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 46acf6cef0f..f74889fbb27 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ LiteLLM manages: - Retry/fallback logic across multiple deployments (e.g. Azure/OpenAI) - [Router](https://docs.litellm.ai/docs/routing) - Set Budgets & Rate limits per project, api key, model [LiteLLM Proxy Server (LLM Gateway)](https://docs.litellm.ai/docs/simple_proxy) -[**Jump to LiteLLM Proxy (LLM Gateway) Docs**](https://github.com/BerriAI/litellm?tab=readme-ov-file#openai-proxy---docs)
+[**Jump to LiteLLM Proxy (LLM Gateway) Docs**](https://github.com/BerriAI/litellm?tab=readme-ov-file#litellm-proxy-server-llm-gateway---docs)
[**Jump to Supported LLM Providers**](https://github.com/BerriAI/litellm?tab=readme-ov-file#supported-providers-docs) 🚨 **Stable Release:** Use docker images with the `-stable` tag. These have undergone 12 hour load tests, before being published. [More information about the release cycle here](https://docs.litellm.ai/docs/proxy/release_cycle) From c9b4a6a54993beafdf100fc17b39bb7d9be7ce22 Mon Sep 17 00:00:00 2001 From: mrFranklin Date: Wed, 24 Sep 2025 21:15:28 +0800 Subject: [PATCH 2/7] chore: update OpikLogger class for improved configuration handling --- litellm/integrations/opik/opik.py | 46 ++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/litellm/integrations/opik/opik.py b/litellm/integrations/opik/opik.py index 9f90d2384d8..94fbcfe2016 100644 --- a/litellm/integrations/opik/opik.py +++ b/litellm/integrations/opik/opik.py @@ -192,9 +192,22 @@ class OpikLogger(CustomBatchLogger): # Extract opik metadata litellm_opik_metadata = litellm_params_metadata.get("opik", {}) + + # Use standard_logging_object to create metadata and input/output data + standard_logging_object = kwargs.get("standard_logging_object", None) + if standard_logging_object is None: + verbose_logger.debug( + "OpikLogger skipping event; no standard_logging_object found" + ) + return [] + + # Update litellm_opik_metadata with opik metadata from requester + litellm_opik_metadata.update(standard_logging_object.get("metadata", {}).get("requester_metadata", {}).get("opik", {})) + verbose_logger.debug( f"litellm_opik_metadata - {json.dumps(litellm_opik_metadata, default=str)}" ) + project_name = litellm_opik_metadata.get("project_name", self.opik_project_name) # Extract trace_id and parent_span_id @@ -208,19 +221,36 @@ class OpikLogger(CustomBatchLogger): else: trace_id = None parent_span_id = None + # Create Opik tags opik_tags = litellm_opik_metadata.get("tags", []) if kwargs.get("custom_llm_provider"): opik_tags.append(kwargs["custom_llm_provider"]) + # Create thread id + thread_id = litellm_opik_metadata.get("thread_id", None) - # Use standard_logging_object to create metadata and input/output data - standard_logging_object = kwargs.get("standard_logging_object", None) - if standard_logging_object is None: - verbose_logger.debug( - "OpikLogger skipping event; no standard_logging_object found" - ) - return [] + # Override with any opik_ headers from proxy request + proxy_headers = (_litellm_params.get("proxy_server_request", {}).get("headers", {}) or {}) + for key, value in proxy_headers.items(): + if key.startswith("opik_"): + param_key = key.replace("opik_", "", 1) + if param_key == "project_name" and value: + project_name = value + elif param_key == "thread_id" and value: + thread_id = value + elif param_key == "trace_id" and value: + trace_id = value + elif param_key == "parent_span_id" and value: + parent_span_id = value + elif param_key == "tags" and value: + try: + parsed_tags = json.loads(value) + if isinstance(parsed_tags, list): + opik_tags.extend(parsed_tags) + except (json.JSONDecodeError, TypeError): + pass + # Create input and output data input_data = standard_logging_object.get("messages", {}) output_data = standard_logging_object.get("response", {}) @@ -298,6 +328,7 @@ class OpikLogger(CustomBatchLogger): "output": output_data, "metadata": metadata, "tags": opik_tags, + "thread_id": thread_id, } ) @@ -319,6 +350,7 @@ class OpikLogger(CustomBatchLogger): "output": output_data, "metadata": metadata, "tags": opik_tags, + "thread_id": thread_id, "usage": usage, } ) From 02a41d76be96663a768459dd2e5274514c836b75 Mon Sep 17 00:00:00 2001 From: mrFranklin <5235904+mrFranklin@users.noreply.github.com> Date: Thu, 25 Sep 2025 14:23:00 +0800 Subject: [PATCH 3/7] fix: add default value to pass the opik test --- litellm/integrations/opik/opik.py | 13 ++++++++----- tests/local_testing/test_opik.py | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/litellm/integrations/opik/opik.py b/litellm/integrations/opik/opik.py index 94fbcfe2016..5d98f220f38 100644 --- a/litellm/integrations/opik/opik.py +++ b/litellm/integrations/opik/opik.py @@ -202,7 +202,10 @@ class OpikLogger(CustomBatchLogger): return [] # Update litellm_opik_metadata with opik metadata from requester - litellm_opik_metadata.update(standard_logging_object.get("metadata", {}).get("requester_metadata", {}).get("opik", {})) + standard_logging_metadata = standard_logging_object.get("metadata", {}) or {} + requester_metadata = standard_logging_metadata.get("requester_metadata", {}) or {} + requester_opik_metadata = requester_metadata.get("opik", {}) or {} + litellm_opik_metadata.update(requester_opik_metadata) verbose_logger.debug( f"litellm_opik_metadata - {json.dumps(litellm_opik_metadata, default=str)}" @@ -230,7 +233,8 @@ class OpikLogger(CustomBatchLogger): thread_id = litellm_opik_metadata.get("thread_id", None) # Override with any opik_ headers from proxy request - proxy_headers = (_litellm_params.get("proxy_server_request", {}).get("headers", {}) or {}) + proxy_server_request = _litellm_params.get("proxy_server_request", {}) or {} + proxy_headers = proxy_server_request.get("headers", {}) or {} for key, value in proxy_headers.items(): if key.startswith("opik_"): param_key = key.replace("opik_", "", 1) @@ -249,8 +253,7 @@ class OpikLogger(CustomBatchLogger): opik_tags.extend(parsed_tags) except (json.JSONDecodeError, TypeError): pass - - + # Create input and output data input_data = standard_logging_object.get("messages", {}) output_data = standard_logging_object.get("response", {}) @@ -273,7 +276,7 @@ class OpikLogger(CustomBatchLogger): del metadata["current_span_data"] metadata["created_from"] = "litellm" - metadata.update(standard_logging_object.get("metadata", {})) + metadata.update(standard_logging_metadata) if "call_type" in standard_logging_object: metadata["type"] = standard_logging_object["call_type"] if "status" in standard_logging_object: diff --git a/tests/local_testing/test_opik.py b/tests/local_testing/test_opik.py index 62c17fbb2d5..d43b7558f54 100644 --- a/tests/local_testing/test_opik.py +++ b/tests/local_testing/test_opik.py @@ -122,7 +122,7 @@ def test_sync_opik_logging_http_request(): ) # Need to wait for a short amount of time as the log_success callback is called in a different thread - time.sleep(1) + time.sleep(3) # Check that 5 spans and 5 traces were sent assert mock_post.call_count == 10, f"Expected 10 HTTP requests, but got {mock_post.call_count}" From f221ce58668a4f20f9f3619480730c8527f477e1 Mon Sep 17 00:00:00 2001 From: mrFranklin <5235904+mrFranklin@users.noreply.github.com> Date: Thu, 25 Sep 2025 14:28:49 +0800 Subject: [PATCH 4/7] chore: add a comment explaining why update one second to three second --- tests/local_testing/test_opik.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/local_testing/test_opik.py b/tests/local_testing/test_opik.py index d43b7558f54..7e3fa84df1a 100644 --- a/tests/local_testing/test_opik.py +++ b/tests/local_testing/test_opik.py @@ -120,8 +120,8 @@ def test_sync_opik_logging_http_request(): temperature=0.2, mock_response="This is a mock response", ) - - # Need to wait for a short amount of time as the log_success callback is called in a different thread + + # Need to wait for a short amount of time as the log_success callback is called in a different thread. One or two seconds is often not enough. time.sleep(3) # Check that 5 spans and 5 traces were sent From dd4fa371fa59ef635a86ea3788639c456019fa59 Mon Sep 17 00:00:00 2001 From: mrFranklin <5235904+mrFranklin@users.noreply.github.com> Date: Thu, 25 Sep 2025 15:54:50 +0800 Subject: [PATCH 5/7] fix: trace not pass through when trace_id has settled --- litellm/integrations/opik/opik.py | 38 ++++++++++++++----------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/litellm/integrations/opik/opik.py b/litellm/integrations/opik/opik.py index 5d98f220f38..9fa3482f663 100644 --- a/litellm/integrations/opik/opik.py +++ b/litellm/integrations/opik/opik.py @@ -224,12 +224,13 @@ class OpikLogger(CustomBatchLogger): else: trace_id = None parent_span_id = None - + # Create Opik tags opik_tags = litellm_opik_metadata.get("tags", []) if kwargs.get("custom_llm_provider"): opik_tags.append(kwargs["custom_llm_provider"]) - # Create thread id + + # Get thread_id if present thread_id = litellm_opik_metadata.get("thread_id", None) # Override with any opik_ headers from proxy request @@ -242,10 +243,6 @@ class OpikLogger(CustomBatchLogger): project_name = value elif param_key == "thread_id" and value: thread_id = value - elif param_key == "trace_id" and value: - trace_id = value - elif param_key == "parent_span_id" and value: - parent_span_id = value elif param_key == "tags" and value: try: parsed_tags = json.loads(value) @@ -319,21 +316,20 @@ class OpikLogger(CustomBatchLogger): verbose_logger.debug( f"OpikLogger creating payload for trace with id {trace_id}" ) - - payload.append( - { - "project_name": project_name, - "id": trace_id, - "name": trace_name, - "start_time": start_time.astimezone(timezone.utc).isoformat().replace("+00:00", "Z"), - "end_time": end_time.astimezone(timezone.utc).isoformat().replace("+00:00", "Z"), - "input": input_data, - "output": output_data, - "metadata": metadata, - "tags": opik_tags, - "thread_id": thread_id, - } - ) + payload.append( + { + "project_name": project_name, + "id": trace_id, + "name": trace_name, + "start_time": start_time.astimezone(timezone.utc).isoformat().replace("+00:00", "Z"), + "end_time": end_time.astimezone(timezone.utc).isoformat().replace("+00:00", "Z"), + "input": input_data, + "output": output_data, + "metadata": metadata, + "tags": opik_tags, + "thread_id": thread_id, + } + ) span_id = create_uuid7() verbose_logger.debug( From 7f06b7bb4e75e889b76e73ffd093c9d5193769a0 Mon Sep 17 00:00:00 2001 From: mrFranklin <5235904+mrFranklin@users.noreply.github.com> Date: Thu, 25 Sep 2025 16:26:34 +0800 Subject: [PATCH 6/7] doc: update opik integration doc --- .../docs/observability/opik_integration.md | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/docs/my-website/docs/observability/opik_integration.md b/docs/my-website/docs/observability/opik_integration.md index b4bcef53937..d30af7d2cb7 100644 --- a/docs/my-website/docs/observability/opik_integration.md +++ b/docs/my-website/docs/observability/opik_integration.md @@ -140,6 +140,7 @@ These can be passed inside metadata with the `opik` key. - `project_name` - Name of the Opik project to send data to. - `current_span_data` - The current span data to be used for tracing. - `tags` - Tags to be used for tracing. +- `thread_id` - The thread id to group together multiple related traces. ### Usage @@ -159,8 +160,10 @@ response = litellm.completion( messages=messages, metadata = { "opik": { + "project_name": "your-opik-project-name", "current_span_data": get_current_span_data(), "tags": ["streaming-test"], + "thread_id": "your-thread-id" }, } ) @@ -174,7 +177,7 @@ curl -L -X POST 'http://0.0.0.0:4000/v1/chat/completions' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer sk-1234' \ -d '{ - "model": "gpt-3.5-turbo-testing", + "model": "gpt-3.5-turbo", "messages": [ { "role": "user", @@ -183,8 +186,10 @@ curl -L -X POST 'http://0.0.0.0:4000/v1/chat/completions' \ ], "metadata": { "opik": { + "project_name": "your-opik-project-name", "current_span_data": "...", "tags": ["streaming-test"], + "thread_id": "your-thread-id" }, } }' @@ -193,14 +198,25 @@ curl -L -X POST 'http://0.0.0.0:4000/v1/chat/completions' \ +You can also pass the fields as part of the request header with a `opik_*` prefix: - - - - - - - +```shell +curl --location --request POST 'http://0.0.0.0:4000/chat/completions' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer sk-1234' \ + --header 'opik_project_name: your-opik-project-name' \ + --header 'opik_thread_id: your-thread-id' \ + --header 'opik_tags: ["streaming-test"]' \ + --data '{ + "model": "gpt-3.5-turbo", + "messages": [ + { + "role": "user", + "content": "What's the weather like in Boston today?" + } + ] +}' +``` From f717e53d3f3a938b4c846e7ec4a4edee68abac6b Mon Sep 17 00:00:00 2001 From: mrFranklin <5235904+mrFranklin@users.noreply.github.com> Date: Thu, 25 Sep 2025 16:28:20 +0800 Subject: [PATCH 7/7] doc: update opik integration doc --- docs/my-website/docs/observability/opik_integration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/my-website/docs/observability/opik_integration.md b/docs/my-website/docs/observability/opik_integration.md index d30af7d2cb7..1ba1c2de210 100644 --- a/docs/my-website/docs/observability/opik_integration.md +++ b/docs/my-website/docs/observability/opik_integration.md @@ -198,6 +198,8 @@ curl -L -X POST 'http://0.0.0.0:4000/v1/chat/completions' \ + + You can also pass the fields as part of the request header with a `opik_*` prefix: ```shell