fix(observability): persist QueryTrace and add admin conversation turns endpoint#53
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rns endpoint (BUG-013, DEBT-011)
Wire AnalyticsService.store_trace() in both core and learn API layers
(non-streaming and streaming paths). Add VEKTRA_ANALYTICS_STORE_TRACES
config flag (auto: on in dev, off in prod). Populate response_id in
conversation_turns. Add trace_from_dict() helper in vektra_shared for
SSE trace reconstruction. Add GET /api/v1/admin/conversations/{id}/turns
endpoint with pgp_sym_decrypt for admin access to conversation content.
Add get_turns_detail() to PersistentConversationStore.
Plan: .s2s/plans/20260328-core-trace-observability.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…is session New entries from end-to-end testing and RAG prompt research: - BUG-019: llm_model inconsistent between streaming/non-streaming traces - BUG-020: system prompt conflicts with multi-turn history (high priority) - DEBT-012: token usage in conversation turns (low, needs use case) - DEBT-013: VEKTRA_RERANK_TOP_K is dead config - DEBT-014: include all reranker scores in QueryTrace - DEBT-015: persist rewritten query in traces (eval mode) - FEAT-018: exclude previously retrieved chunks in multi-turn - FEAT-019: full prompt observability in eval mode - FEAT-020: configurable prompt grounding mode strict/hybrid - DOCS-009: document Phase 2 API endpoints in api.md Update .env.example with context window documentation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Mark BUG-013 and DEBT-011 as in_progress (code committed on branch)
- FEAT-020: strengthen hybrid mode fallback wording ("100% sure", from
Anthropic best practice) and add transparency note for e-learning
- FEAT-020: add acceptance criterion for context.j2 XML format update
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
conversation.j2 template and render_conversation() are dead code since Phase 1 Wave 3 - pipelines use native chat messages for history instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extend FEAT-020 with per-namespace override via namespace metadata JSONB. Enables university experiments where some courses use hybrid mode (LLM knowledge + RAG) and others use strict (RAG only). Update system.j2 proposal to handle no-context case (hybrid answers from training, strict returns "no information"). Add has_context conditional in template. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per-namespace citations_enabled flag to make Rule 1 (hide sources) optional. When enabled, system prompt instructs inline [id] citations matching doc elements. Requires propagating filename/page through SearchResult to template and rendering citations in widget. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds best-effort QueryTrace persistence (streaming and non-streaming), a new admin endpoint to retrieve decrypted conversation turns, propagates response_id through conversation-turn persistence, and introduces a tri-state setting to control trace storage with a development fallback; also updates .env.example documentation and adds trace serialization/deserialization helpers and tests. Changes
Sequence DiagramssequenceDiagram
participant Client
participant API as Query API
participant Pipeline
participant AnalyticsSvc as Analytics Service
participant DB
rect rgba(100, 150, 200, 0.5)
Note over Client,DB: Non-Streaming Trace Persistence
Client->>API: POST /query (store_traces_enabled=true)
API->>Pipeline: execute(query_req)
Pipeline-->>API: (response, trace)
API->>AnalyticsSvc: store_trace(sess, trace, namespace=...)
AnalyticsSvc->>DB: INSERT trace record
DB-->>AnalyticsSvc: success
AnalyticsSvc-->>API: ack
API-->>Client: response
end
rect rgba(200, 100, 150, 0.5)
Note over Client,DB: Streaming Trace Persistence (SSE)
Client->>API: POST /query (stream=true)
API->>Pipeline: stream(query_req)
loop For each chunk
Pipeline-->>API: chunk (data)
alt chunk.type == "trace"
API->>AnalyticsSvc: store_trace(sess, trace_from_dict(data), namespace=...)
AnalyticsSvc->>DB: INSERT trace record
DB-->>AnalyticsSvc: ack
end
API-->>Client: chunk (SSE)
end
end
rect rgba(150, 200, 100, 0.5)
Note over Client,DB: Conversation Turn Retrieval
Client->>API: GET /admin/conversations/{id}/turns
API->>API: verify admin scope
API->>ConversationStore: get_turns_detail(id)
ConversationStore->>DB: SELECT turns for conversation
DB-->>ConversationStore: turn records
ConversationStore-->>API: [decrypted turns + metadata]
API-->>Client: [ConversationTurnDetail, ...]
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request implements QueryTrace persistence and enhances conversation observability across the system. Key changes include the addition of an admin endpoint for retrieving decrypted conversation turns, the integration of best-effort trace storage within the core and learn API layers for both streaming and non-streaming paths, and the propagation of response_id to conversation turns. Additionally, a new implementation plan for trace observability was added and the backlog was updated with several planned features. Feedback identifies a performance improvement opportunity by removing a redundant database check for conversation existence in the conversation store.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
vektra-core/src/vektra_core/pipeline.py (1)
562-566: Make conversation-turn writes best-effort (consistency with advanced pipeline).Consider guarding these
add_turnwrites so a storage error does not fail an otherwise successful response.Proposed refactor
- if query.conversation_id is not None: - await self._conversation_store.add_turn( - query.conversation_id, - query.question, - answer, - response_id=response_id, - ) + if query.conversation_id is not None: + try: + await self._conversation_store.add_turn( + query.conversation_id, + query.question, + answer, + response_id=response_id, + ) + except Exception as exc: + log.warning("conversation_turn_store_failed", error=str(exc))- if query.conversation_id is not None and full_answer: - await self._conversation_store.add_turn( - query.conversation_id, - query.question, - full_answer, - response_id=response_id, - ) + if query.conversation_id is not None and full_answer: + try: + await self._conversation_store.add_turn( + query.conversation_id, + query.question, + full_answer, + response_id=response_id, + ) + except Exception as exc: + log.warning("conversation_turn_store_failed", error=str(exc))Also applies to: 871-875
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vektra-core/src/vektra_core/pipeline.py` around lines 562 - 566, The call to add_turn when writing conversation turns (the invocation that passes query.conversation_id, query.question, answer, response_id) should be made best-effort so storage errors don't break a successful response; wrap the add_turn call in a try/except that catches storage/IO errors, log the exception (with context including conversation_id and response_id) and do not re-raise, and apply the same pattern to the other add_turn site around the lines referenced (the second occurrence that uses the same parameters).vektra-core/tests/test_trace_persistence.py (1)
92-262: Mirror these persistence cases forvektra_learn.api.course_query.This suite only pins
vektra_core.api.query, but the PR adds near-identicalstore_tracelogic in the learn endpoint too. The hidden unit failures show that path already diverged, so please add the same enabled/disabled/failure cases there to keep the two handlers in sync.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vektra-core/tests/test_trace_persistence.py` around lines 92 - 262, Add three tests mirroring the ones in vektra_core/tests/test_trace_persistence.py but targeting vektra_learn.api.course_query: create test_store_trace_called_when_enabled, test_store_trace_not_called_when_disabled, and test_store_trace_failure_does_not_propagate for the course_query handler; each should mock an AsyncMock analytics_service with store_trace, a mock pipeline that returns (response, trace) using _make_trace(), set request.app.state.registry to return the pipeline and safeguard mocks, toggle app_state.store_traces_enabled, attach app_state.analytics_service and app_state.db_session_factory (for the enabled and failure cases), call vektra_learn.api.course_query(body, request, key_info) and assert store_trace called with the trace and namespace when enabled, not called when disabled, and that exceptions raised by analytics_service.store_trace do not propagate (handler still returns a result) for the failure case.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@vektra-learn/src/vektra_learn/api.py`:
- Around line 597-608: The persistence block assumes trace is present and
dereferences trace.response_id in the exception handler; update the logic inside
the if _store_traces and _analytics_svc and _db_factory: guard the persistence
attempt with an explicit check that trace is not None (e.g., only enter async
with _db_factory() when trace is truthy) and change the except logger call
(structlog.get_logger(__name__).warning) to avoid dereferencing
trace.response_id by using a safe lookup (e.g., include
response_id=getattr(trace, "response_id", None) or a descriptive message when
trace is missing) so a swallowed analytics error cannot raise when trace is
None.
---
Nitpick comments:
In `@vektra-core/src/vektra_core/pipeline.py`:
- Around line 562-566: The call to add_turn when writing conversation turns (the
invocation that passes query.conversation_id, query.question, answer,
response_id) should be made best-effort so storage errors don't break a
successful response; wrap the add_turn call in a try/except that catches
storage/IO errors, log the exception (with context including conversation_id and
response_id) and do not re-raise, and apply the same pattern to the other
add_turn site around the lines referenced (the second occurrence that uses the
same parameters).
In `@vektra-core/tests/test_trace_persistence.py`:
- Around line 92-262: Add three tests mirroring the ones in
vektra_core/tests/test_trace_persistence.py but targeting
vektra_learn.api.course_query: create test_store_trace_called_when_enabled,
test_store_trace_not_called_when_disabled, and
test_store_trace_failure_does_not_propagate for the course_query handler; each
should mock an AsyncMock analytics_service with store_trace, a mock pipeline
that returns (response, trace) using _make_trace(), set
request.app.state.registry to return the pipeline and safeguard mocks, toggle
app_state.store_traces_enabled, attach app_state.analytics_service and
app_state.db_session_factory (for the enabled and failure cases), call
vektra_learn.api.course_query(body, request, key_info) and assert store_trace
called with the trace and namespace when enabled, not called when disabled, and
that exceptions raised by analytics_service.store_trace do not propagate
(handler still returns a result) for the failure case.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 76b5f269-0cf4-4c30-8d61-b543017df222
⛔ Files ignored due to path filters (2)
.s2s/BACKLOG.mdis excluded by!.s2s/**.s2s/plans/20260328-core-trace-observability.mdis excluded by!.s2s/**
📒 Files selected for processing (13)
.env.examplevektra-admin/src/vektra_admin/api.pyvektra-admin/tests/test_admin_turns.pyvektra-app/src/vektra_app/main.pyvektra-core/src/vektra_core/advanced_pipeline.pyvektra-core/src/vektra_core/api.pyvektra-core/src/vektra_core/conversation.pyvektra-core/src/vektra_core/pipeline.pyvektra-core/tests/test_conversation.pyvektra-core/tests/test_trace_persistence.pyvektra-learn/src/vektra_learn/api.pyvektra-shared/src/vektra_shared/config.pyvektra-shared/src/vektra_shared/types.py
- Guard trace persistence with `trace is not None` check (CodeRabbit critical) - Use `is True` for store_traces_enabled to prevent MagicMock truthiness in test mocks (fixes CI vektra-learn test failures) - Wrap SimpleQueryPipeline add_turn in try/except for best-effort consistency with AdvancedQueryPipeline (CodeRabbit nitpick) - Add learn endpoint trace persistence tests (CodeRabbit nitpick) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Fixed in b6262cd. Addresses CodeRabbit nitpick on pipeline.py:562-566,871-875: wrapped SimpleQueryPipeline add_turn calls in try/except for best-effort consistency with AdvancedQueryPipeline. Also added learn endpoint trace persistence tests (test_trace_persistence.py in vektra-learn). |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
vektra-learn/tests/test_trace_persistence.py (1)
37-68: Consider adding a return type hint for clarity.The function returns a tuple
(app_state, trace, response)but this isn't documented. A type hint would improve discoverability.♻️ Suggested improvement
def _make_app_state( *, store_traces: bool, analytics_svc: AsyncMock | None = None, db_factory: MagicMock | None = None, pipeline: AsyncMock | None = None, -) -> MagicMock: +) -> tuple[MagicMock, QueryTrace, MagicMock]: """Build a mock app.state with registry and services."""🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vektra-learn/tests/test_trace_persistence.py` around lines 37 - 68, The helper _make_app_state currently returns a tuple (app_state, trace, response) but lacks a return type hint; update its signature to include an explicit return type such as -> tuple[MagicMock, TraceType, ResponseType] (or the concrete test types used) so callers and readers know the expected types; ensure to import or reference the correct types (e.g., MagicMock, the Trace and Response classes or test fixtures) and update any type aliases if needed to keep static typing consistent with registry, pipeline, and db_factory mocks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@vektra-learn/tests/test_trace_persistence.py`:
- Around line 37-68: The helper _make_app_state currently returns a tuple
(app_state, trace, response) but lacks a return type hint; update its signature
to include an explicit return type such as -> tuple[MagicMock, TraceType,
ResponseType] (or the concrete test types used) so callers and readers know the
expected types; ensure to import or reference the correct types (e.g.,
MagicMock, the Trace and Response classes or test fixtures) and update any type
aliases if needed to keep static typing consistent with registry, pipeline, and
db_factory mocks.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 31863875-5e9f-4371-85bf-65883571a253
📒 Files selected for processing (4)
vektra-core/src/vektra_core/api.pyvektra-core/src/vektra_core/pipeline.pyvektra-learn/src/vektra_learn/api.pyvektra-learn/tests/test_trace_persistence.py
🚧 Files skipped from review as they are similar to previous changes (1)
- vektra-core/src/vektra_core/pipeline.py
|
Won't fix: CodeRabbit nitpick on vektra-learn/tests/test_trace_persistence.py:37-68 (return type hint on test helper). The function is a test fixture helper, adding explicit tuple type hints provides minimal value here. |
- Update 20260328-core-trace-observability: mark completed (PR #53) - Add 20260325-rag-retrieval-quality: BUG-015/016, TECH-002, DEBT-010 - Add 20260403-prompt-grounding-mode: FEAT-020, DEBT-016, BUG-020 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mark 10 backlog items as completed with PR references: - PR #53: BUG-013, DEBT-011 - PR #54: BUG-020, FEAT-020, DEBT-016 - PR #55: DEBT-014, BUG-019, DEBT-015, DEBT-009, FEAT-019 Also add content assertion to eval_mode prompt test per review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Reranker scores for all candidates in QueryTrace, gated behind eval_mode (DEBT-014) - Consistent llm_model in streaming via CompletionChunk.model (BUG-019) - Rewritten query text in traces when eval_mode=true (DEBT-015) - Debug-level query logging via VEKTRA_DEBUG_LOG_QUERIES (DEBT-009) - Full prompt messages in traces when eval_mode=true (FEAT-019) - Backlog status updated for 10 items across PRs #53-#55 Reviews: 10/10 addressed (Gemini 3, CodeRabbit 7) Tests: 22 pipeline + 15 reranker + 8 litellm + 4 config passed Refs: DEBT-014, BUG-019, DEBT-015, DEBT-009, FEAT-019
What
Fix for QueryTrace never persisted to database (BUG-013) and conversation observability gaps (DEBT-011).
Problem
AnalyticsService.store_trace()existed and was tested but never called by any pipeline or endpoint. Thequery_tracestable was always empty, making post-hoc diagnosis of query behavior impossible. Additionally,response_idinconversation_turnswas never populated, and no admin endpoint existed to read decrypted conversation content.Solution
store_trace()in both core and learn API layers (non-streaming and streaming paths)VEKTRA_ANALYTICS_STORE_TRACESconfig flag (auto: on in dev, off in prod)response_idinconversation_turnsviaadd_turn()keyword argtrace_from_dict()helper invektra_sharedfor SSE trace reconstructionGET /api/v1/admin/conversations/{id}/turnsendpoint with pgp_sym_decryptget_turns_detail()toPersistentConversationStoreAlso includes backlog updates from analysis session: 13 new entries (BUG-019/020, DEBT-012 to 016, DOCS-009, FEAT-018 to 021) and .env.example context window documentation.
Plan:
.s2s/plans/20260328-core-trace-observability.mdTesting
Checklist
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Chores