5a558eb09e
TypeScript SDK Compatibility V1.x E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / TypeScript SDK Compatibility V1.x E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
TypeScript SDK E2E Tests / TypeScript SDK E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
Python SDK E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK E2E Tests / Python SDK E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK E2E Tests / build-opik (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Python SDK Compatibility V1.x E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK E2E Tests / build-opik (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer E2E Tests Python ${{matrix.python_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer Integration Smoke Tests (push) Has been cancelled
🐙 Code Quality / detect (push) Has been cancelled
🐙 Code Quality / lint (${{ matrix.leg.name }}) (push) Has been cancelled
🐙 Code Quality / summary (push) Has been cancelled
TypeScript SDK Library Integration Tests / Check Secrets (push) Has been cancelled
TypeScript SDK Library Integration Tests / opik-vercel (Vercel AI SDK / eve) (push) Has been cancelled
SDK Library Integration Tests Runner / Check Secrets (push) Has been cancelled
SDK Library Integration Tests Runner / Missed OpenAI API Key Warning (push) Has been cancelled
SDK Library Integration Tests Runner / Build (push) Has been cancelled
SDK Library Integration Tests Runner / openai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_legacy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / llama_index_tests (push) Has been cancelled
SDK Library Integration Tests Runner / anthropic_tests (push) Has been cancelled
SDK Library Integration Tests Runner / mistral_tests (push) Has been cancelled
SDK Library Integration Tests Runner / groq_tests (push) Has been cancelled
SDK Library Integration Tests Runner / aisuite_tests (push) Has been cancelled
SDK Library Integration Tests Runner / haystack_tests (push) Has been cancelled
SDK Library Integration Tests Runner / dspy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v1_tests (push) Has been cancelled
SDK Library Integration Tests Runner / genai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_legacy_1_3_0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / evaluation_metrics_tests (push) Has been cancelled
SDK Library Integration Tests Runner / bedrock_tests (push) Has been cancelled
SDK Library Integration Tests Runner / litellm_tests (push) Has been cancelled
SDK Library Integration Tests Runner / harbor_tests (push) Has been cancelled
SDK Library Integration Tests Runner / Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / render-equality (push) Has been cancelled
Opik Optimizer - Unit Tests / Opik Optimizer Unit Tests Python ${{matrix.python_version}} (push) Has been cancelled
Python BE E2E Tests / Python BE E2E (push) Has been cancelled
Python Backend Tests / run-python-backend-tests (push) Has been cancelled
Python SDK Unit Tests / Python SDK Unit Tests ${{matrix.python_version}} (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
SDK E2E Libraries Integration Tests / Check Secrets (push) Has been cancelled
SDK E2E Libraries Integration Tests / Missed OpenAI API Key Warning (push) Has been cancelled
SDK E2E Libraries Integration Tests / build-opik (push) Has been cancelled
SDK E2E Libraries Integration Tests / E2E Lib Integration Python ${{matrix.python_version}} (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-gemini) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-langchain) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-openai) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-otel) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-vercel) (push) Has been cancelled
TypeScript SDK Build & Publish / build-and-publish (push) Has been cancelled
TypeScript SDK Unit Tests / Test on Node ${{ matrix.node-version }} (push) Has been cancelled
Backend Tests / discover-tests (push) Has been cancelled
Backend Tests / ${{ matrix.name }} (push) Has been cancelled
Build and Publish SDK / build-and-publish (push) Has been cancelled
Build Opik Docker Images / set-version (push) Has been cancelled
Build Opik Docker Images / build-backend (push) Has been cancelled
Build Opik Docker Images / build-sandbox-executor-python (push) Has been cancelled
Build Opik Docker Images / build-python-backend (push) Has been cancelled
Build Opik Docker Images / build-frontend (push) Has been cancelled
Build Opik Docker Images / create-git-tag (push) Has been cancelled
ClickHouse Migration Cluster Check / validate-clickhouse-migrations (push) Has been cancelled
Docs - Publish / run (push) Has been cancelled
E2E Tests - Post Merge (v2) / 🧪 E2E v2 Tests (${{ github.event.inputs.tier || 't1' }}) (push) Has been cancelled
E2E Tests - Post Merge (v2) / 📢 Slack Notification (push) Has been cancelled
Frontend Unit Tests / Test on Node 20 (push) Has been cancelled
Guardrails E2E Tests / Select Python version matrix (push) Has been cancelled
Guardrails E2E Tests / Guardrails E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Guardrails E2E Tests / 📢 Slack Notification (push) Has been cancelled
Guardrails Backend Unit Tests / Guardrails Backend Unit Tests (push) Has been cancelled
Guardrails Backend Unit Tests / 📢 Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v3.21.0) (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v4.2.0) (push) Has been cancelled
Lint Opik Helm Chart / unittest-helm-chart (push) Has been cancelled
453 lines
15 KiB
Plaintext
453 lines
15 KiB
Plaintext
---
|
|
headline: Log conversations | Opik Documentation
|
|
og:description: Capture and analyze chat conversations on Opik, enabling better tracking
|
|
of user interactions and improving chatbot performance.
|
|
og:site_name: Opik Documentation
|
|
og:title: Log Conversations Effectively - Opik
|
|
title: Log conversations
|
|
canonical-url: https://www.comet.com/docs/opik/tracing/advanced/log_chat_conversations
|
|
---
|
|
|
|
You can log chat conversations to the Opik platform and track the full conversations
|
|
your users are having with your chatbot. Threads allow you to group related traces together, creating a conversational flow that makes it easy to review multi-turn interactions and track user sessions.
|
|
|
|
<Frame>
|
|
<img src="/img/tracing/chat_conversations.png" />
|
|
</Frame>
|
|
|
|
## Understanding Threads
|
|
|
|
Threads in Opik are collections of traces that are grouped together using a unique `thread_id`. This is particularly useful for:
|
|
|
|
- **Multi-turn conversations**: Track complete chat sessions between users and AI assistants
|
|
- **User sessions**: Group all interactions from a single user session
|
|
- **Conversational agents**: Follow the flow of agent interactions and tool usage
|
|
- **Workflow tracking**: Monitor complex workflows that span multiple function calls
|
|
|
|
The `thread_id` is a user-defined identifier that must be unique per project. All traces with the same `thread_id` will be grouped together and displayed as a single conversation thread in the Opik UI.
|
|
|
|
## Logging conversations
|
|
|
|
You can log chat conversations by specifying the `thread_id` parameter when using either the low level SDK, Python decorators, or integration libraries:
|
|
|
|
<CodeBlocks>
|
|
```typescript title="Typescript SDK" language="typescript"
|
|
import { Opik } from "opik";
|
|
|
|
const client = new Opik({
|
|
apiUrl: "https://www.comet.com/opik/api", // Only required if you are using Opik Cloud
|
|
apiKey: "your-api-key",
|
|
projectName: "your-project-name",
|
|
workspaceName: "your-workspace-name", // Optional
|
|
});
|
|
|
|
const threadId = "your-thread-id"; // any unique string per conversation
|
|
|
|
// Option A: set on trace creation
|
|
const trace = client.trace({
|
|
name: "chat turn",
|
|
input: { user: "Hi there" },
|
|
output: { assistant: "Hello!" },
|
|
threadId
|
|
});
|
|
```
|
|
|
|
```python title="Python decorators" language="python"
|
|
import opik
|
|
from opik import opik_context
|
|
|
|
@opik.track
|
|
def chat_message(input):
|
|
return "Opik is an Open Source GenAI platform"
|
|
|
|
chat_message("What is Opik ?", opik_args={"trace": {"thread_id": "f174a"}})
|
|
|
|
# Alternatively, using the opik_context module
|
|
@opik.track
|
|
def chat_message(input):
|
|
thread_id = "f174a"
|
|
opik_context.update_current_trace(
|
|
thread_id=thread_id
|
|
)
|
|
return "Opik is an Open Source GenAI platform"
|
|
|
|
chat_message("What is Opik ?", thread_id)
|
|
```
|
|
|
|
```python title="Python SDK"
|
|
import opik
|
|
|
|
opik_client = opik.Opik()
|
|
|
|
thread_id = "55d84"
|
|
|
|
# Log a first message
|
|
trace = opik_client.trace(
|
|
name="chat_conversation",
|
|
input="What is Opik?",
|
|
output="Opik is an Open Source GenAI platform",
|
|
thread_id=thread_id
|
|
)
|
|
```
|
|
|
|
```python title="LangGraph integration"
|
|
# LangGraph automatically uses its thread_id as Opik thread_id
|
|
from langgraph.graph import StateGraph
|
|
from opik.integrations.langchain import OpikTracer
|
|
|
|
# Create your LangGraph workflow
|
|
graph = StateGraph(...)
|
|
compiled_graph = graph.compile()
|
|
|
|
# Configure with thread_id for conversation tracking
|
|
thread_id = "user-conversation-789"
|
|
config = {
|
|
"callbacks": [OpikTracer(project_name="langgraph-conversations")],
|
|
"configurable": {"thread_id": thread_id}
|
|
}
|
|
|
|
# First turn in conversation
|
|
result1 = compiled_graph.invoke(
|
|
{"messages": [{"role": "user", "content": "What is machine learning?"}]},
|
|
config=config
|
|
)
|
|
|
|
# Follow-up turn in same conversation thread
|
|
result2 = compiled_graph.invoke(
|
|
{"messages": [{"role": "user", "content": "Can you give me an example?"}]},
|
|
config=config
|
|
)
|
|
```
|
|
|
|
```python title="ADK integration"
|
|
# ADK automatically maps session_id to thread_id
|
|
from opik.integrations.adk import OpikTracer
|
|
from google.adk import sessions as adk_sessions, runners as adk_runners
|
|
|
|
# Create ADK session for conversation tracking
|
|
session_service = adk_sessions.InMemorySessionService()
|
|
session = session_service.create_session_sync(
|
|
app_name="my_chatbot",
|
|
user_id="user_123",
|
|
session_id="conversation_456" # This becomes the thread_id in Opik
|
|
)
|
|
|
|
opik_tracer = OpikTracer(project_name="adk-conversations")
|
|
runner = adk_runners.Runner(
|
|
agent=your_agent,
|
|
app_name="my_chatbot",
|
|
session_service=session_service
|
|
)
|
|
|
|
# First message - automatically grouped by session_id
|
|
result1 = runner.run(
|
|
user_id="user_123",
|
|
session_id="conversation_456",
|
|
new_message="What is machine learning?"
|
|
)
|
|
|
|
# Follow-up message - same conversation thread
|
|
result2 = runner.run(
|
|
user_id="user_123",
|
|
session_id="conversation_456",
|
|
new_message="Can you give me an example?"
|
|
)
|
|
```
|
|
|
|
```python title="OpenAI Agents"
|
|
# Using trace context manager with group_id for threading
|
|
import uuid
|
|
from opik import trace
|
|
|
|
thread_id = str(uuid.uuid4())
|
|
|
|
# First conversation turn
|
|
with trace(workflow_name="Agent Conversation", group_id=thread_id):
|
|
result1 = await Runner.run(agent, "What is machine learning?")
|
|
print(result1.final_output)
|
|
|
|
# Follow-up turn in same conversation
|
|
with trace(workflow_name="Agent Conversation", group_id=thread_id):
|
|
# Continue conversation with context
|
|
new_input = result1.to_input_list() + [
|
|
{"role": "user", "content": "Can you give me an example?"}
|
|
]
|
|
result2 = await Runner.run(agent, new_input)
|
|
print(result2.final_output)
|
|
```
|
|
|
|
</CodeBlocks>
|
|
|
|
<Note>
|
|
The input to each trace will be displayed as the user message while the output will be displayed as the AI assistant
|
|
response.
|
|
</Note>
|
|
|
|
## Thread ID Best Practices
|
|
|
|
### Generating Thread IDs
|
|
|
|
Choose a thread ID strategy that fits your application:
|
|
|
|
<CodeBlocks>
|
|
```python title="User session based"
|
|
import uuid
|
|
import opik
|
|
|
|
# Generate unique thread ID per user session
|
|
user_id = "user_12345"
|
|
session_start_time = "2024-01-15T10:30:00Z"
|
|
thread_id = f"{user_id}-{session_start_time}"
|
|
|
|
@opik.track
|
|
def process_user_message(message, user_id):
|
|
return "Response to: " + message
|
|
|
|
process_user_message("What is Opik ?", opik_args={"trace": {"thread_id": thread_id}})
|
|
```
|
|
|
|
```python title="UUID based"
|
|
import uuid
|
|
import opik
|
|
|
|
# Generate random UUID for each conversation
|
|
thread_id = str(uuid.uuid4()) # e.g., "f47ac10b-58cc-4372-a567-0e02b2c3d479"
|
|
|
|
@opik.track
|
|
def start_conversation(initial_message):
|
|
return f"Processing: {initial_message}"
|
|
|
|
start_conversation("What is Opik ?", opik_args={"trace": {"thread_id": thread_id}})
|
|
```
|
|
|
|
```python title="Timestamp based"
|
|
import time
|
|
import opik
|
|
|
|
# Use timestamp for time-based grouping
|
|
thread_id = f"conversation-{int(time.time())}"
|
|
|
|
@opik.track
|
|
def handle_conversation_turn(message):
|
|
return f"Response to: {message}"
|
|
|
|
handle_conversation_turn("What is Opik ?", opik_args={"trace": {"thread_id": thread_id}})
|
|
```
|
|
|
|
</CodeBlocks>
|
|
|
|
### Integration-Specific Threading
|
|
|
|
Different integrations handle thread IDs in various ways:
|
|
|
|
<CodeBlocks>
|
|
```python title="LangChain"
|
|
from opik.integrations.langchain import OpikTracer
|
|
|
|
# Set thread_id at tracer level - applies to all traces
|
|
opik_tracer = OpikTracer(
|
|
project_name="my-chatbot",
|
|
thread_id="conversation-123"
|
|
)
|
|
|
|
# Or pass dynamically via metadata
|
|
chain.invoke(
|
|
{"input": "Hello"},
|
|
config={
|
|
"callbacks": [opik_tracer],
|
|
"metadata": {"thread_id": "dynamic-conversation-456"}
|
|
}
|
|
)
|
|
```
|
|
|
|
```python title="LangGraph"
|
|
from opik.integrations.langchain import OpikTracer
|
|
|
|
# LangGraph automatically uses its thread_id as Opik thread_id
|
|
thread_id = "langgraph-conversation-789"
|
|
config = {
|
|
"callbacks": [OpikTracer()],
|
|
"configurable": {"thread_id": thread_id}
|
|
}
|
|
|
|
result = compiled_graph.invoke(input_data, config=config)
|
|
```
|
|
|
|
```python title="OpenAI Agents"
|
|
import uuid
|
|
from opik import trace
|
|
|
|
# Use trace context manager with group_id for threading
|
|
thread_id = str(uuid.uuid4())
|
|
|
|
with trace(workflow_name="Agent Conversation", group_id=thread_id):
|
|
# All agent interactions within this context share the thread_id
|
|
result1 = await Runner.run(agent, "First question")
|
|
result2 = await Runner.run(agent, "Follow-up question")
|
|
```
|
|
|
|
```python title="GenAI"
|
|
from google import genai
|
|
from opik.integrations.genai import track_genai
|
|
|
|
client = genai.Client()
|
|
gemini_client = track_genai(client, project_name="opik_args demo")
|
|
|
|
response = gemini_client.models.generate_content(
|
|
model="gemini-2.0-flash",
|
|
contents="What is Opik?",
|
|
opik_args={"trace": {"thread_id": "f174a"}}
|
|
)
|
|
```
|
|
|
|
```python title="OpenAI"
|
|
import openai
|
|
|
|
from opik.integrations.openai import track_openai
|
|
|
|
client = openai.OpenAI()
|
|
wrapped_client = track_openai(
|
|
openai_client=client,
|
|
project_name="opik_args demo",
|
|
)
|
|
messages = [
|
|
{"role": "system", "content": "You are a helpful assistant."},
|
|
{"role": "user", "content": "What is Opik?"},
|
|
]
|
|
|
|
_ = wrapped_client.responses.create(
|
|
model="gpt-4o-mini",
|
|
input=messages,
|
|
opik_args={"trace": {"thread_id": "f174a"}}
|
|
)
|
|
```
|
|
|
|
</CodeBlocks>
|
|
|
|
## Reviewing conversations
|
|
|
|
Conversations can be viewed at a project level in the `threads` tab. All conversations are tracked and by clicking on the thread ID you will be able to
|
|
view the full conversation.
|
|
|
|
The thread view supports markdown making it easier for you to review the content that was returned to the user. If you would like to dig in deeper, you
|
|
can click on the `View trace` button to deepdive into how the AI assistant response was generated.
|
|
|
|
By clicking on the thumbs up or thumbs down icons, you can quickly rate the AI assistant response. This feedback score will be logged and associated to
|
|
the relevant trace. By switching to the trace view, you can review the full trace as well as add additional feedback scores through the annotation
|
|
functionality.
|
|
|
|
<Frame>
|
|
<img src="/img/tracing/chat_conversations_actions.png" />
|
|
</Frame>
|
|
|
|
## Scoring conversations
|
|
|
|
You can assign conversation-level feedback scores to threads at any time. Threads are aggregated traces
|
|
that are created when tracking agents or simply traces interconnected by a `thread_id`.
|
|
|
|
<Frame>
|
|
<img src="/img/tracing/chat_conversations_score.png" />
|
|
</Frame>
|
|
|
|
In the conversation list, you can see the feedback scores associated to each thread.
|
|
|
|
<Frame>
|
|
<img src="/img/tracing/chat_conversations_score_list.png" />
|
|
</Frame>
|
|
|
|
You can also tag a thread and add comments to it. This is useful to add additional context during the review process or investigate a specific conversation.
|
|
|
|
<Frame>
|
|
<img src="/img/tracing/chat_conversation_tags_comments.png" />
|
|
</Frame>
|
|
|
|
### Thread Online Scoring Rule Cooldown Period
|
|
|
|
For thread-level online evaluation rules (automatic scoring), Opik waits for a "cooldown period" after the last activity
|
|
in a thread before running the rules. This gives conversations time to settle before automatic evaluation.
|
|
|
|
<Note>
|
|
By default, the cooldown period is 15 minutes. You can change this value by setting the
|
|
`OPIK_TRACE_THREAD_TIMEOUT_TO_MARK_AS_INACTIVE` environment variable (if you are using the Opik self-hosted version).
|
|
On cloud, you can change this setting at workspace level under "Thread online scoring rule cooldown period".
|
|
</Note>
|
|
|
|
#### Behavior When Adding Traces to Existing Threads
|
|
|
|
When a new trace is added to an existing thread, the following happens:
|
|
|
|
- **Existing feedback scores are preserved**: Any manual feedback scores or online evaluation scores you have added remain intact.
|
|
- **The cooldown timer restarts**: The timer resets from the moment the new trace is added, ensuring online evaluation waits for the full cooldown period before scoring the updated thread.
|
|
- **Online evaluation re-runs**: Once the cooldown period expires, thread-level online scoring rules will automatically evaluate the complete conversation again. If a new score is logged with the same name as an existing score, the existing score is updated.
|
|
|
|
## Advanced Thread Features
|
|
|
|
### Filtering and Searching Threads
|
|
|
|
You can filter threads using the `thread_id` field in various Opik features:
|
|
|
|
#### In Data Export
|
|
|
|
When exporting data, you can filter by `thread_id` using these operators:
|
|
|
|
- `=` (equals), `!=` (not equals)
|
|
- `contains`, `not_contains`
|
|
- `starts_with`, `ends_with`
|
|
- `>`, `<` (lexicographic comparison)
|
|
|
|
#### In Thread Evaluation
|
|
|
|
You can evaluate entire conversation threads using the thread evaluation features. This is particularly useful for:
|
|
|
|
- Conversation quality assessment
|
|
- Multi-turn coherence evaluation
|
|
- User satisfaction scoring across complete interactions
|
|
|
|
### Thread Management
|
|
|
|
Threads can have traces added to them at any time, and you can add feedback scores, comments, and tags
|
|
to threads regardless of whether new traces are still being added.
|
|
|
|
### Programmatic Thread Management
|
|
|
|
You can also manage threads programmatically using the Opik SDK:
|
|
|
|
<CodeBlocks>
|
|
```python title="Python" language="python"
|
|
import opik
|
|
|
|
# Initialize client
|
|
client = opik.Opik()
|
|
|
|
# Search for threads by various criteria
|
|
threads = client.search_traces(
|
|
project_name="my-chatbot",
|
|
filter_string='thread_id contains "user-session"'
|
|
)
|
|
|
|
# Get specific thread content
|
|
for trace in threads:
|
|
if trace.thread_id:
|
|
thread_content = client.get_trace_content(trace.id)
|
|
print(f"Thread: {trace.thread_id}")
|
|
print(f"Input: {thread_content.input}")
|
|
print(f"Output: {thread_content.output}")
|
|
|
|
# Add feedback scores to thread traces
|
|
for trace in threads:
|
|
trace.log_feedback_score(
|
|
name="conversation_quality",
|
|
value=0.8,
|
|
reason="Good multi-turn conversation flow"
|
|
)
|
|
```
|
|
|
|
</CodeBlocks>
|
|
|
|
## Next steps
|
|
|
|
Once you have added observability to your multi-turn agent, why not:
|
|
|
|
1. [Run offline multi-turn conversation evaluation](/v1/evaluation/evaluate_threads)
|
|
2. [Create online evaluation rules](/v1/production/rules) to score your multi-turn conversations in
|
|
production |