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