How to Troubleshoot Retrieval Trajectory Visualization in OpenViking for Debugging
To troubleshoot retrieval trajectory visualization in OpenViking, ensure you use RetrieverMode.THINKING when calling the retriever, verify that QueryResult.thinking_trace contains events, and validate that all event payloads use JSON-serializable basic types.
OpenViking's hierarchical retrieval engine generates a detailed thinking trace that records every step of the search process, enabling developers to debug why specific documents were retrieved or excluded. Understanding how to extract and visualize this trajectory is essential for optimizing retrieval performance and diagnosing complex query issues. This guide walks through the architecture, common pitfalls, and practical techniques to troubleshoot retrieval trajectory visualization in OpenViking using the actual source code implementation.
Understanding the Thinking Trace Architecture
The thinking trace system is defined in openviking_cli/retrieve/types.py and consists of several interconnected classes that capture the retrieval trajectory:
TraceEventType(lines 22-44): An enumeration defining event kinds such asSEARCH_DIRECTORY_START,EMBEDDING_SCORES, andCONVERGENCE_CHECK.TraceEvent(lines 46-65): An immutable record containing the event type, timestamp, message, and payload data.ThinkingTrace(lines 130-200): A thread-safe container that collectsTraceEventobjects and provides serialization viato_dict()and human-readable formatting viaget_trace_messages().QueryResult(lines 96-108): Embeds aThinkingTraceinstance, making the trajectory accessible after retrieval completes.
The trace is only constructed when the retriever operates in RetrieverMode.THINKING (the default mode). When using RetrieverMode.QUICK, the system omits trace generation entirely for performance optimization.
How the Trace Is Populated During Retrieval
During query execution, HierarchicalRetriever._recursive_search (located in openviking/retrieve/hierarchical_retriever.py, lines 75-140) populates the trace by calling thinking_trace.add_event at specific logical boundaries:
- Search initialization: Adds
SEARCH_DIRECTORY_STARTwhen entering a new directory. - Directory results: Logs
SEARCH_DIRECTORY_RESULTwith the count of children examined. - Scoring phases: Records both
EMBEDDING_SCORESandRERANK_SCORESalongside document snippets. - Candidate filtering: Emits
CANDIDATE_SELECTEDfor passing documents orCANDIDATE_EXCLUDEDfor filtered items. - Convergence tracking: Appends
CONVERGENCE_CHECKduring each top-K comparison round, concluding withSEARCH_CONVERGED.
Each event creation passes through ThinkingTrace.add_event (lines 151-167), which calculates relative timestamps using time.time() - self.start_time and stores events in a thread-safe queue.Queue.
Common Troubleshooting Scenarios
When debugging retrieval trajectory visualization, you may encounter these specific issues:
Empty thinking_trace.events list
- Cause: The retriever executed in
RetrieverMode.QUICKor theThinkingTraceobject was never attached toQueryResult. - Solution: Verify that
mode=RetrieverMode.THINKINGis explicitly set when callingretriever.retrieve(), and confirm thatresult.thinking_traceexists before accessingresult.thinking_trace.events.
Missing RERANK_SCORES events
- Cause: The rerank client is not configured (
self._rerank_clientisNone). - Solution: Supply a valid
RerankConfigwhen initializingHierarchicalRetriever:retriever = HierarchicalRetriever(storage, embedder, rerank_config).
Timestamps appearing out of order
- Cause: Multiple queries are sharing the same
ThinkingTraceinstance across concurrent executions. - Solution: Ensure each query receives its own
ThinkingTraceinstance. The standardQueryResultcreation handles this automatically, but manual instantiation requires fresh objects per request.
Excessive trace size
- Cause: The
pre_filter_limitparameter (calculated asmax(limit * 2, 20)) is too large, causing examination of hundreds of children. - Solution: Reduce the
limitparameter passed toretrieve()or modifypre_filter_limitin_recursive_searchto constrain candidate volume.
Absence of convergence events
- Cause: The retrieval may have converged in the first round, which is normal behavior.
- Solution: Check
result.thinking_trace.get_trace_messages()for "Convergence check" entries. A singleCONVERGENCE_CHECKfollowed by immediate convergence is valid.
JSON serialization failures
- Cause: Event payloads (
datafields) contain non-serializable objects like custom classes. - Solution: Ensure all
datadictionaries contain only basic types (strings, integers, floats, lists, and dictionaries). Useevent.to_dict()for safe serialization.
Practical Debugging Examples
Enabling Full Trace Capture
To capture the complete retrieval trajectory, explicitly use RetrieverMode.THINKING and access the trace through the returned QueryResult:
from openviking_cli.retrieve.types import RetrieverMode, TypedQuery, ContextType
from openviking.retrieve.hierarchical_retriever import HierarchicalRetriever
# Initialize components
retriever = HierarchicalRetriever(storage, embedder, rerank_config)
typed_query = TypedQuery(
query="How to fix a broken pipeline?",
context_type=ContextType.MEMORY,
intent="debug pipeline",
priority=1,
target_directories=[],
)
# Execute with thinking mode enabled
result = await retriever.retrieve(
query=typed_query,
ctx=request_ctx,
limit=5,
mode=RetrieverMode.THINKING,
)
# Display human-readable trace
for line in result.thinking_trace.get_trace_messages():
print(line)
Key implementation detail: The thinking_trace attribute is only populated when RetrieverMode.THINKING is specified, as implemented in the retrieval logic at openviking/retrieve/hierarchical_retriever.py.
Serializing Traces for External Visualizers
Export the trajectory to JSON for analysis in tools like D3.js or custom dashboards:
import json
# Convert to dictionary format
trace_dict = result.thinking_trace.to_dict()
# Export to file
with open("retrieval_trace.json", "w") as f:
json.dump(trace_dict, f, indent=2)
The resulting JSON structure contains an events array with detailed step records and a statistics object summarizing total events, duration, directories searched, and convergence rounds:
{
"events": [
{
"event_type": "search_directory_start",
"timestamp": 0.001,
"message": "Entering URI: viking://user/xyz/memories",
"data": {}
}
],
"statistics": {
"total_events": 27,
"duration_seconds": 0.842,
"convergence_rounds": 1
}
}
Injecting Custom Debug Events
For advanced debugging, inject custom events directly into the trace within your retrieval logic:
# Inside HierarchicalRetriever._recursive_search or custom subclasses
if custom_heuristic_score > threshold:
thinking_trace.add_event(
event_type=TraceEventType.CANDIDATE_SELECTED,
message="Custom heuristic kept candidate",
data={"custom_score": custom_heuristic_score, "heuristic": "semantic_similarity"},
query_id=None,
)
This technique allows you to track business-specific logic alongside the standard retrieval trajectory.
Summary
- Use
RetrieverMode.THINKING: The trace only generates when this mode is explicitly set during theretrieve()call. - Check
QueryResult.thinking_trace: Always verify the trace object exists before attempting visualization or serialization. - Validate payload types: Ensure all event data contains JSON-serializable basic types to prevent export failures.
- Configure reranking: Supply a valid
RerankConfigto populateRERANK_SCORESevents in the trajectory. - Monitor limits: Large
limitvalues increase trace verbosity; tunepre_filter_limitto balance detail and performance. - Leverage
to_dict(): Use this method for safe JSON serialization when integrating with external visualization tools.
Frequently Asked Questions
Why is my thinking_trace empty after retrieval?
The trace remains empty when the retriever executes in RetrieverMode.QUICK, which skips event logging for performance reasons. Ensure you pass mode=RetrieverMode.THINKING to the retrieve() method. Additionally, verify that the QueryResult object was properly constructed with a ThinkingTrace instance, as defined in openviking_cli/retrieve/types.py lines 96-108.
How do I fix JSON serialization errors when exporting traces?
Serialization fails when event payloads contain non-basic Python types such as custom class instances or datetime objects. According to the TraceEvent implementation in openviking_cli/retrieve/types.py lines 46-65, the data field must contain only strings, integers, floats, lists, or dictionaries. Use the to_dict() method on the ThinkingTrace object, which ensures all nested TraceEvent objects convert to serializable dictionaries.
Can I use the thinking trace in production environments?
While RetrieverMode.THINKING provides detailed debugging information, it adds memory overhead by storing all events in a thread-safe queue. For high-throughput production systems, use RetrieverMode.QUICK to disable trace generation entirely. If selective logging is required, instantiate a custom ThinkingTrace only for specific high-value queries rather than enabling it globally.
Where are rerank scores logged in the trace?
Rerank scores appear as RERANK_SCORES events within the thinking trace, logged by HierarchicalRetriever._recursive_search in openviking/retrieve/hierarchical_retriever.py. These events only appear when a valid RerankConfig is provided to the retriever constructor and the rerank client successfully executes. If missing, check that self._rerank_client is not None and that the reranking service is accessible.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →