Skip to content

fix(core): improve retrieval quality and pipeline observability#52

Merged
fvadicamo merged 33 commits into
developfrom
fix/conversation-persistence
Mar 25, 2026
Merged

fix(core): improve retrieval quality and pipeline observability#52
fvadicamo merged 33 commits into
developfrom
fix/conversation-persistence

Conversation

@fvadicamo

@fvadicamo fvadicamo commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix reranker score propagation - scores were discarded after reranking,
    making the relevance threshold operate on wrong values (BUG-015)
  • Switch default reranker to multilingual bge-reranker-v2-m3 (BUG-016)
  • Lower relevance threshold to 0.15 as safety net (DEBT-010)
  • Fix context window fallback silently truncating prompts for vLLM
    models not in litellm registry (BUG-017)
  • Add retrieval and e2e evaluation harness with 55-question bilingual
    dataset (TECH-002)
  • Fix conversation persistence - rows not created before first turn (BUG-014)
  • Rewrite system prompt to prevent RAG internals exposure
  • Wire VEKTRA_LLM_API_KEY and extra_body support for vLLM
  • Harden Docker supply chain (eliminate uv pip install outside lockfile)

Test plan

  • make lint passes
  • make test passes (587 passed, 2 pre-existing env-leak failures)
  • Deploy on Kalypso: bge-m3 loads, scores propagated (0.30-0.99 range)
  • Eval harness: factual 90% hit rate, reasoning 80%
  • Context window fix verified: 5/5 chunks in prompt (was 2/5)
  • Italian + English queries produce correct answers

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added evaluation tools for measuring retrieval quality and end-to-end RAG performance
    • Added support for explicit LLM context window configuration and extra request body parameters
    • Enabled local vLLM instance support with improved model configuration
  • Configuration Changes

    • Updated default reranker model to BAAI/bge-reranker-v2-m3
    • Lowered minimum relevance score threshold from 0.3 to 0.15
    • Improved reranker score handling with original similarity scores preserved
  • Documentation

    • Added API endpoint comparison guide (search vs. query)
    • Added PostgreSQL investigation procedures
    • Added local vLLM setup guidance
  • Bug Fixes

    • Improved token counting and context window fallback handling

fvadicamo and others added 22 commits March 24, 2026 18:04
The PersistentConversationStore was initialized but never registered
in the ProviderRegistry, causing GET /api/v1/conversations/{id} to
return 503 "not configured" even when VEKTRA_CONVERSATION_KEY was set.

Part of BUG-014.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tracks the missing create_conversation() call that causes
PersistentConversationStore to silently discard all turns.
Annotates BUG-010 as partially incomplete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Require consulting docs/reference/api.md or /openapi.json before
constructing any API call to prevent auth, parameter, and endpoint
mistakes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PersistentConversationStore.add_turn() requires an existing
ConversationOrm row, but no one was creating it. All turns were
silently discarded and multi-turn context was broken.

Changes:
- Add ensure_conversation() to PersistentConversationStore (upsert
  that creates the row only if it doesn't exist)
- Extend create_conversation() to accept an optional conversation_id
- Core API: create conversation before calling pipeline, using
  namespace_id and key_id from the auth layer
- Learn API: ensure conversation exists using sentinel key_id (JWT
  auth has no API key)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The system prompt told the LLM not to reference sources, but the
instruction was in English only and too generic. The model would
use translated equivalents like "brani forniti" in Italian, which
confuses users who don't know a retrieval system exists.

Changes to system.j2:
- Explicit blacklist of forbidden expressions in both English and
  Italian (extensible to other languages)
- Reframe assistant persona as "knowledgeable" rather than "with
  access to a knowledge base"
- Provide concrete alternative phrasings for partial-knowledge
  scenarios
- Structure instructions with clear sections for context handling
  and user communication

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previous prompt still allowed the LLM to say "the text I consulted"
and offer to "search for more" - both expose the retrieval mechanism
or make promises the system cannot keep.

Changes:
- Blacklist "consulted/reviewed" phrasing in addition to "provided"
- Explicitly forbid offering to search, look up, or provide more
  information later (the system has no such capability)
- Instruct to state incomplete knowledge plainly without hinting
  that more information exists elsewhere

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous structured prompt was too verbose for gpt-5-mini, which
ignored the blacklist and used "brani forniti" anyway. The word
"passage" in the prompt itself was being translated and echoed back.

Rewritten as a compact prompt with numbered critical rules. Removed
all words the model could recycle (passage, document, source). Added
"brani" to the forbidden list since gpt-5-mini translates "passage"
to this Italian word.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewritten based on research findings (see vektra-internal
20260324-rag-prompt-best-practices-research.md):
- 4 rules only (within compliance threshold for smaller models)
- Positive framing ("Sound like you simply know") instead of blacklists
- Reintroduce <source> clarification to prevent BUG-012 regression
- Add language-matching instruction (rule 4)
- Remove forbidden phrase lists (brittle, secondary defense)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
VEKTRA_LLM_API_KEY existed in config but was never passed to litellm,
forcing users to set provider-specific env vars (OPENAI_API_KEY) even
for OpenAI-compatible endpoints like vLLM.

Changes:
- LitellmProvider now passes config.api_key to litellm as api_key kwarg
- Add VEKTRA_LLM_EXTRA_BODY config field (JSON dict) for provider-
  specific params (e.g., chat_template_kwargs to disable thinking on
  Qwen3.5/DeepSeek-R1 via vLLM)
- Update configuration.md with vLLM setup instructions and correct
  VEKTRA_LLM_API_KEY documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move structlog import to top level (was inline in except block)
- Extract _LEARN_SENTINEL_KEY_ID as module-level constant
- Fix import ordering (ruff I001)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…l quality

BUG-015: reranker scores discarded (threshold applied to wrong scores)
BUG-016: English-only reranker on Italian content
TECH-002: RAG evaluation harness (50-question dataset, RAGAS)
DEBT-010: recalibrate relevance threshold with empirical data

All traced to analysis in vektra-internal/stack/20260324-reranker-threshold-gap-analysis.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ckfile

Pin litellm <1.82.7 (versions 1.82.7-1.82.8 contain credential stealer).
Replace direct uv pip install of qdrant-client/fastembed with lockfile-based
--extra sparse --extra qdrant. Drop unused torchvision. All dependencies
now verified against lockfile hashes via uv sync --frozen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RerankerService.rerank() was discarding reranker scores and returning
original cosine similarities, making the relevance threshold filter
operate on the wrong values. Now reranker scores replace SearchResult.score
with sigmoid normalization for cross-encoder logits. Original score
preserved in SearchResult.original_score for debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…016, DEBT-010)

Default reranker changed from flashrank/ms-marco-MiniLM-L-12-v2
(English-only) to cross-encoder/BAAI/bge-reranker-v2-m3 (multilingual).
Threshold lowered from 0.3 to 0.15 as interim safety net - top-k is the
primary chunk count control. Existing flashrank config remains supported
for English-only lightweight deployments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add eval scripts that call the Vektra API and compute retrieval quality
metrics (hit rate, MRR, precision@k) and e2e metrics (answer rate,
latency percentiles). Includes seed dataset with 5 questions, Makefile
targets (eval-retrieval, eval-e2e), and JSONL results output. Full
50-question dataset to be curated on Kalypso with live content.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tion

The /api/v1/search endpoint returns raw vector scores without reranker.
Add --use-query to call /api/v1/query instead, evaluating retrieval
quality after reranking. Handles field name differences between the
two endpoints (text_snippet vs snippet).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tion

Dataset covers Italian Constitution and UDHR excerpts:
- 20 factual (15 IT + 5 EN)
- 15 reasoning (10 IT + 5 EN)
- 10 multi-chunk (cross-document)
- 10 adversarial (unanswerable from content)

Baseline with bge-reranker-v2-m3 at threshold 0.15:
  factual 90%, reasoning 80%, multi-chunk 10%, adversarial 0%

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eted

BUG-017: context window fallback silently truncates prompts for vLLM
models not in litellm registry. Discovered via conversation analysis -
budget allocator fits only 2/5 chunks when using 4096 default instead
of actual 32768. Fix: VEKTRA_LLM_CONTEXT_WINDOW env var + warn on
fallback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ty gaps

Four gaps found during post-hoc conversation analysis: no API to read
turns (encrypted, DB-only), query traces not persisted for streaming,
response_id not populated in conversation_turns, no admin trace lookup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Document key pitfalls: encrypted conversation turns, namespace_id
column naming, Qdrant as vector store (not pgvector), and the
fundamental difference between /search (raw vector) and /query
(full pipeline with reranker/threshold/LLM).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
litellm.get_max_tokens() silently falls back to 4096 for models not in
its registry (all local vLLM models), causing the token budget allocator
to discard most retrieved chunks. Add VEKTRA_LLM_CONTEXT_WINDOW env var
as explicit override, and emit structlog warnings (once per model) when
context window or token count fall back to defaults.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@fvadicamo fvadicamo added bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request component:core vektra-core component labels Mar 25, 2026
@github-actions github-actions Bot added component:shared vektra-shared component infra Infrastructure, Docker, deployment labels Mar 25, 2026
@coderabbitai

coderabbitai Bot commented Mar 25, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@fvadicamo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 56 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 68aae954-a638-4d43-8784-0e7a5a4c44ea

📥 Commits

Reviewing files that changed from the base of the PR and between a483f1e and 4f2fa6a.

📒 Files selected for processing (2)
  • docs/reference/configuration.md
  • scripts/eval_retrieval.py
📝 Walkthrough

Walkthrough

Comprehensive updates introducing evaluation harnesses for retrieval and end-to-end RAG, enhancing LLM and reranker configuration, propagating reranker scores with original score preservation, improving conversation persistence with ensure semantics, updating provider defaults, refactoring Docker dependencies, and expanding documentation with debugging guidance.

Changes

Cohort / File(s) Summary
Documentation & Configuration Guides
.claude/CLAUDE.md, docs/reference/configuration.md
Added API checklist, Postgres investigation procedures, and endpoint comparison guide; documented new LLM configuration fields (extra_body, context_window), updated reranker defaults (cross-encoder, BAAI/bge-reranker-v2-m3), and added local vLLM instance guidance.
Environment & Dependency Configuration
.env.example, vektra-core/pyproject.toml, Dockerfile
Updated default min relevance score from 0.3 to 0.15, switched reranker to cross-encoder with BAAI/bge-reranker-v2-m3 model, added VEKTRA_LLM_CONTEXT_WINDOW placeholder; tightened litellm version constraint to <1.82.7; refactored Dockerfile Phase 2 to use conditional uv sync with extras instead of explicit pip installs.
Build & Test Targets
Makefile, .gitignore
Added eval-retrieval and eval-e2e targets; added pattern to ignore evaluation result files (tests/eval/results_*.jsonl).
Evaluation Harness Framework
scripts/eval_retrieval.py, scripts/eval_e2e.py, tests/eval/dataset.jsonl
Implemented two evaluation harnesses: retrieval-only (hit rate, MRR, precision@k, score distribution) and end-to-end RAG (latency, source metrics, answer extraction); added 60-record JSONL dataset with multi-language, multi-category evaluation questions.
Conversation Persistence & Management
vektra-core/src/vektra_core/conversation.py, vektra-core/src/vektra_core/api.py, vektra-app/src/vektra_app/main.py, vektra-learn/src/vektra_learn/api.py
Enhanced PersistentConversationStore with optional conversation_id parameter and new ensure_conversation method; updated query endpoint to resolve conversation_id before request construction; registered conversation_store in provider registry; wired conversation creation in learn service.
LLM Configuration & Integration
vektra-shared/src/vektra_shared/config.py, vektra-core/src/vektra_core/providers/litellm_provider.py
Added LLMConfig.extra_body and context_window fields with environment variable mappings; updated LitellmProvider to conditionally inject api_key and extra_body into constructor kwargs.
Reranker Score Propagation
vektra-core/src/vektra_core/reranker.py, vektra-shared/src/vektra_shared/types.py
Changed reranker to propagate provider scores onto SearchResult.score while preserving original vector similarity in new original_score field; added sigmoid normalization for cross-encoder logits; updated default model from cross-encoder/ms-marco-MiniLM-L-6-v2 to BAAI/bge-reranker-v2-m3.
Pipeline Context Window Resolution
vektra-core/src/vektra_core/pipeline.py, vektra-core/src/vektra_core/advanced_pipeline.py
Reworked context window resolution to accept and prioritize configured window value; added deduplication logic for token count and context window fallback warnings; updated both SimpleQueryPipeline and AdvancedQueryPipeline to pass configured context window.
System Prompt Template
vektra-core/src/vektra_core/templates/system.j2
Updated instructions to frame retrieved content as "reference material," require responding only from provided context, answer what is possible while explicitly stating missing information, and enforce response language matching user input.
Configuration & Type Defaults
vektra-shared/src/vektra_shared/config.py, vektra-shared/tests/test_config.py
Updated min_relevance_score default from 0.3 to 0.15, reranker provider from flashrank to cross-encoder, and reranker model to BAAI/bge-reranker-v2-m3; updated corresponding test expectations.
Pipeline & Reranker Tests
vektra-core/tests/test_pipeline.py, vektra-core/tests/test_reranker.py
Added tests for context window fallback behavior, token count estimation, reranker score propagation, and sigmoid normalization of cross-encoder logits; updated mock data shapes and expected default models.
CI/CD Workflow
.github/workflows/integration.yml
Increased job timeout from 20 to 30 minutes; restricted latency measurement step to push events only (skip on pull requests).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Client as HTTP Client
    participant API as /api/v1/query
    participant Store as PersistentConversationStore
    participant DB as PostgreSQL
    
    User->>Client: POST query with conversation_id (optional)
    Client->>API: Request with body.conversation_id
    API->>Store: Get registry & check if store exists
    alt conversation_id is None
        API->>Store: create_conversation(namespace_id, key_id)
        Store->>DB: INSERT INTO conversations
        DB-->>Store: Return generated UUID
        Store-->>API: Return new conversation_id
    else conversation_id provided
        API->>Store: ensure_conversation(conversation_id, namespace_id, key_id)
        Store->>DB: INSERT ... ON CONFLICT DO NOTHING
        DB-->>Store: Confirm (new or existing row)
        Store-->>API: Return (void)
    end
    API->>API: Build QueryRequest with resolved conversation_id
    API-->>Client: Continue RAG pipeline processing
Loading
sequenceDiagram
    participant Script as eval_retrieval.py
    participant Client as httpx.Client
    participant Search as /api/v1/search or /api/v1/query
    participant Reranker as RerankerService
    
    Script->>Client: Load dataset & initialize client
    loop For each question in dataset
        Client->>Search: POST with question + expected_keywords
        Search->>Reranker: Retrieve & rerank results
        Reranker-->>Search: Return ranked results with scores
        Search-->>Client: HTTP response with results/sources
        Client->>Client: Extract retrieved items & compute metrics
        Client->>Client: Check keyword match, compute MRR & precision@k
    end
    Script->>Script: Aggregate: hit_rate, mean_MRR, mean_precision, score percentiles
    Script->>Script: Print summary with optional category/language breakdowns
    Script->>Script: Write JSONL output with per-question metrics
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

ci, evaluation, feature, configuration

Poem

🐰 Wiggles whiskers at evals so grand,
Rerankers now dance hand in hand!
Conversations persist, context gleams bright,
Configs perfected—a rabbity delight! ✨
New defaults hop in with care,
Building RAG magic in the air! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.98% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(core): improve retrieval quality and pipeline observability' directly and concisely summarizes the main objectives of the changeset, covering the core improvements across retrieval quality (reranker defaults, relevance threshold, score propagation), context window handling, and observability (evaluation harnesses). It matches the primary changes in the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/conversation-persistence

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the core retrieval-augmented generation (RAG) pipeline by improving the accuracy of content retrieval and the overall observability of the system. It addresses several critical issues related to reranker score handling, multilingual support, and LLM context window management, leading to more reliable and performant query responses. Additionally, it introduces robust evaluation tools and refines system configurations and documentation to streamline development and debugging.

Highlights

  • Retrieval Quality Improvements: Reranker scores are now correctly propagated and normalized, addressing a bug where relevance thresholds were applied to incorrect values. The default reranker has been switched to a multilingual model (BAAI/bge-reranker-v2-m3) to improve performance on non-English content. The minimum relevance score has been lowered to 0.15 as a safety net.
  • Pipeline Observability & Robustness: A critical bug causing silent truncation of prompts for vLLM models not in the litellm registry has been fixed by introducing a configurable context window and logging warnings for fallbacks. Conversation persistence has been resolved, ensuring conversation rows are created before the first turn. New evaluation harnesses (retrieval-only and end-to-end) have been added with a bilingual dataset to systematically measure RAG quality.
  • Developer Experience & Configuration: The system prompt has been rewritten to prevent exposure of RAG internals to the user. Support for vLLM has been enhanced by wiring VEKTRA_LLM_API_KEY and extra_body parameters. Docker supply chain has been hardened by eliminating uv pip install outside the lockfile, and documentation for API interaction and database investigation has been improved.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request significantly enhances the RAG pipeline by improving conversation persistence, reranking, and evaluation capabilities. Key changes include implementing conversation row creation and ensuring existence in the API layer for multi-turn queries (BUG-014), propagating and normalizing reranker scores (BUG-015), and updating the default reranker model to a multilingual one (BAAI/bge-reranker-v2-m3) for better performance on diverse content (BUG-016). It also addresses the silent truncation of prompts by adding VEKTRA_LLM_CONTEXT_WINDOW and logging warnings for context window and token counting fallbacks (BUG-017). Furthermore, a RAG evaluation harness (TECH-002) has been added with new Makefile targets and scripts for both retrieval-only and end-to-end evaluations, alongside recalibrating the VEKTRA_MIN_RELEVANCE_SCORE to 0.15 (DEBT-010). Documentation for configuration and internal development has been updated, and Dockerfile dependencies simplified. Review feedback highlights the need to ensure thread-safety for the new warning mechanisms in _count_tokens_impl and _context_window_impl to prevent race conditions when logging fallback warnings, and to clarify the bash code block syntax in the configuration documentation.

Comment thread docs/reference/configuration.md
Comment thread vektra-core/src/vektra_core/pipeline.py
Comment thread vektra-core/src/vektra_core/pipeline.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
vektra-shared/src/vektra_shared/config.py (1)

36-55: ⚠️ Potential issue | 🟠 Major

VEKTRA_LLM_CONTEXT_WINDOW is still dropped by as_llm_config().

LLMConfig can parse the new env var, but VektraSettings never stores it and as_llm_config() never forwards it. In the normal settings path, Line 539 passes VEKTRA_LLM_EXTRA_BODY while VEKTRA_LLM_CONTEXT_WINDOW disappears, so the new vLLM override still falls back to 4096. Please also validate Line 51 with ge=1 so bad values fail fast instead of corrupting token budgeting.

🛠️ Suggested fix
 class LLMConfig(BaseSettings):
@@
     context_window: int | None = Field(
         None,
         alias="VEKTRA_LLM_CONTEXT_WINDOW",
+        ge=1,
         description="Context window size in tokens. Required for models not in litellm's registry (e.g. local vLLM). If unset, litellm lookup is attempted with a 4096-token fallback.",
     )
@@
     llm_api_key: str | None = Field(None, alias="VEKTRA_LLM_API_KEY")
     llm_api_base: str | None = Field(None, alias="VEKTRA_LLM_API_BASE")
     llm_extra_body: dict[str, Any] | None = Field(None, alias="VEKTRA_LLM_EXTRA_BODY")
+    llm_context_window: int | None = Field(None, alias="VEKTRA_LLM_CONTEXT_WINDOW")
     llm_fallback_model: str | None = Field(None, alias="VEKTRA_LLM_FALLBACK_MODEL")
@@
                 "VEKTRA_LLM_API_KEY": self.llm_api_key,
                 "VEKTRA_LLM_API_BASE": self.llm_api_base,
                 "VEKTRA_LLM_EXTRA_BODY": self.llm_extra_body,
+                "VEKTRA_LLM_CONTEXT_WINDOW": self.llm_context_window,
                 "VEKTRA_LLM_FALLBACK_MODEL": self.llm_fallback_model,
                 "VEKTRA_LLM_FALLBACK_TIMEOUT_MS": self.llm_fallback_timeout_ms,
                 "VEKTRA_LLM_CONTEXT_ONLY_ENABLED": self.llm_context_only_enabled,

Also applies to: 430-430, 532-543

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vektra-shared/src/vektra_shared/config.py` around lines 36 - 55,
VektraSettings currently defines context_window but never stores/forwards it to
LLMConfig in as_llm_config(), so VEKTRA_LLM_CONTEXT_WINDOW is dropped; update
VektraSettings to include context_window (matching the Field name used in the
diff) and modify as_llm_config() to pass context_window through alongside
extra_body and fallback_model into the returned LLMConfig, and add validation to
the Field declaration (context_window: int | None = Field(..., ge=1)) so
invalid/non-positive values fail fast.
docs/reference/configuration.md (1)

25-32: ⚠️ Potential issue | 🟡 Minor

Add VEKTRA_LLM_EXTRA_BODY to the main LLM reference table.

The local vLLM section introduces this knob, but the primary config matrix still skips it, so operators scanning the reference table will miss the new setting.

📘 Suggested doc update
 | `VEKTRA_LLM_PROVIDER` | str | (required) | LLM model in litellm format |
 | `VEKTRA_LLM_API_KEY` | str | - | API key for the LLM provider. Not needed for Ollama. |
 | `VEKTRA_LLM_API_BASE` | str | - | Custom API base URL for OpenAI-compatible providers (e.g., vLLM, LMStudio) |
+| `VEKTRA_LLM_EXTRA_BODY` | json | - | Extra JSON body passed to litellm (for example `{"chat_template_kwargs": {"enable_thinking": false}}` for vLLM) |
 | `VEKTRA_LLM_FALLBACK_MODEL` | str | - | Fallback model when the primary times out |
 | `VEKTRA_LLM_FALLBACK_TIMEOUT_MS` | int | `60000` | Milliseconds before switching to fallback model |
 | `VEKTRA_LLM_CONTEXT_WINDOW` | int | - | Context window size in tokens. Required for models not in litellm's registry (e.g. local vLLM). If unset, litellm lookup with 4096 fallback (warns on fallback) |
As per coding guidelines, "use VEKTRA_LLM_EXTRA_BODY to disable “thinking mode” when needed".

Also applies to: 42-50

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/reference/configuration.md` around lines 25 - 32, Add a new row for
VEKTRA_LLM_EXTRA_BODY to the main LLM configuration table so operators see the
knob in the primary matrix; update the table that lists environment variables
(the one containing VEKTRA_LLM_PROVIDER, VEKTRA_LLM_API_KEY, etc.) to include a
row for `VEKTRA_LLM_EXTRA_BODY` (type: str, default: - or provide recommended
default), with a short description like "Extra JSON body appended to LLM
requests; used to disable 'thinking mode' per coding guidelines"; also mirror
this addition in the related LLM config section referenced later (the local vLLM
area) so both tables are consistent.
🧹 Nitpick comments (2)
.claude/CLAUDE.md (1)

20-20: Avoid hardcoded Qdrant connection details in docs.

Hardcoding port 10633 and collection vektra can go stale across environments. Prefer referencing the configured env vars/settings (or saying “check runtime config”) to keep this accurate.

Suggested doc tweak
-- **Postgres holds metadata/text, Qdrant holds vectors**: chunk text is in `document_chunks` (Postgres), but vector search runs against Qdrant (port 10633, collection `vektra`). To inspect vectors or search results, query Qdrant REST API directly. The Qdrant payload uses `namespace_id` as the namespace filter field.
+- **Postgres holds metadata/text, Qdrant holds vectors**: chunk text is in `document_chunks` (Postgres), but vector search runs against Qdrant (use the configured host/port and collection from environment/runtime config). To inspect vectors or search results, query Qdrant REST API directly. The Qdrant payload uses `namespace_id` as the namespace filter field.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/CLAUDE.md at line 20, The docs currently hardcode Qdrant connection
details ("port 10633" and collection `vektra`) which may vary by environment;
update the text that references Qdrant (the sentence mentioning Qdrant port
10633, collection `vektra`, and the use of `namespace_id` and `document_chunks`)
to instead point readers to the runtime/config settings or environment variables
used by the app (e.g., reference the configured Qdrant host/port and collection
name in config/env) and suggest checking runtime config to determine the actual
collection/port for their environment.
Dockerfile (1)

73-76: Minor: Redundant uv sync when INSTALL_UNSTRUCTURED=true.

When INSTALL_UNSTRUCTURED=true, this runs uv sync twice—once without --extra ocr and once with it. You could restructure to avoid the duplicate work:

♻️ Suggested optimization
-RUN uv sync --frozen --no-editable --no-dev --extra sparse --extra qdrant \
-    && if [ "$INSTALL_UNSTRUCTURED" = "true" ]; then \
-       uv sync --frozen --no-editable --no-dev --extra sparse --extra qdrant --extra ocr; \
-    fi
+RUN if [ "$INSTALL_UNSTRUCTURED" = "true" ]; then \
+       uv sync --frozen --no-editable --no-dev --extra sparse --extra qdrant --extra ocr; \
+    else \
+       uv sync --frozen --no-editable --no-dev --extra sparse --extra qdrant; \
+    fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 73 - 76, The Dockerfile currently runs `uv sync`
twice (first unconditionally, then again with `--extra ocr` when
`INSTALL_UNSTRUCTURED=true`), causing redundant work; change the logic to
compute the extras/options before calling `uv sync` and invoke `uv sync` only
once: build an extras/options variable that always includes `--extra sparse
--extra qdrant` and conditionally appends `--extra ocr` when
`INSTALL_UNSTRUCTURED` is "true", then call `uv sync --frozen --no-editable
--no-dev` with that single assembled extras string so `uv sync` runs only once.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/CLAUDE.md:
- Line 35: The sentence is ambiguous about “retrieval quality” vs “end-to-end
answer quality”; update the text to explicitly state that `/api/v1/search`
should be used to evaluate raw retrieval quality (vector similarity only) and
`/api/v1/query` should be used to evaluate full pipeline or end-to-end answer
quality (retrieval plus reranking, prompt/LLM behavior, and post-processing), so
replace or augment the current line with a clarifying phrase that contrasts
`/api/v1/search` (raw retrieval) and `/api/v1/query` (full pipeline behavior).

In `@scripts/eval_e2e.py`:
- Around line 67-89: The except block currently marks transport/API failures as
no_relevant_context=True and omits duration_ms; change the except handler for
the client.post block to compute duration_ms = int((time.monotonic() - t0) *
1000) and pass that into the returned E2EResult, and do NOT set
no_relevant_context=True (set it to False or leave as failure-specific value) so
failed requests are not treated as "no relevant context" answers; update the
E2EResult construction (referencing t0 and E2EResult) to include duration_ms and
the error string while keeping num_sources as appropriate.

In `@scripts/eval_retrieval.py`:
- Around line 29-41: The current aggregation/print_summary logic averages in
QuestionResult rows that have no ground-truth (keyword-less), which biases
metrics; update the aggregation in print_summary() (and any summary/total
computation paths that consume QuestionResult) to first filter out results where
QuestionResult.num_relevant == 0 or QuestionResult.error indicates "no ground
truth" (i.e., the branch that marked entries as having no ground truth), compute
hit/mrr/precision_at_k and breakdowns only over the filtered list, adjust totals
and per-category counts to reflect the number of scored items, and include a
separate printed count of excluded/unscored rows so users know how many were
omitted.

In `@vektra-core/src/vektra_core/api.py`:
- Around line 211-239: The generated server-side conversation_id created in the
conversation store block (conversation_id, conv_store.create_conversation /
conv_store.ensure_conversation) must be threaded through the SSE path so clients
receive it; update the call to _sse_generator(...) to accept an additional
conversation_id argument (or context object) and modify _sse_generator to
include that conversation_id in the final "done" SSE event payload, ensuring the
same conversation_id is passed into the QueryRequest and into the SSE generator
so streamed first turns return the newly created ID.

In `@vektra-core/src/vektra_core/reranker.py`:
- Around line 70-80: The normalization decision should be based on the score
provider semantics, not the observed numeric range: replace the heuristic that
sets needs_sigmoid by value range with a provider-based check (e.g., inspect the
ranking metadata or a provider/type field on ranked or each SearchResult) and
consistently apply _sigmoid when the provider is a cross-encoder (or other
unnormalized scorer); update the logic around needs_sigmoid,
top_items/raw_scores and the normalization step (where normalized =
_sigmoid(raw_score) if needs_sigmoid else raw_score) so
_apply_retrieval_filter() always compares thresholds against the correctly
normalized scores for that provider. Ensure you reference the provider/type
field available on ranked or SearchResult to decide normalization rather than
using any s < 0.0 or s > 1.0 checks.

In `@vektra-core/tests/test_pipeline.py`:
- Around line 494-528: Update the two "warns_once" tests to assert that the
first invocation actually emits the warning and the second does not: for
test_context_window_fallback_warns_once, call
_context_window_impl("test/warn-once-model"), capture output with
capsys.readouterr() and assert that captured.out contains
"context_window_fallback", then call _context_window_impl again and assert the
subsequent captured.out does not contain it; similarly for
test_count_tokens_fallback_warns_once, call _count_tokens_impl(mock_llm,
"test/token-once-model", "first"), assert the first captured output contains
"token_count_fallback", then call it a second time and assert the second
captured output does not contain the token_count_fallback string; keep the
existing set discard/reset logic (_context_window_fallback_warned and
_token_count_fallback_warned) and use capsys.readouterr() to separate first and
second captures.

---

Outside diff comments:
In `@docs/reference/configuration.md`:
- Around line 25-32: Add a new row for VEKTRA_LLM_EXTRA_BODY to the main LLM
configuration table so operators see the knob in the primary matrix; update the
table that lists environment variables (the one containing VEKTRA_LLM_PROVIDER,
VEKTRA_LLM_API_KEY, etc.) to include a row for `VEKTRA_LLM_EXTRA_BODY` (type:
str, default: - or provide recommended default), with a short description like
"Extra JSON body appended to LLM requests; used to disable 'thinking mode' per
coding guidelines"; also mirror this addition in the related LLM config section
referenced later (the local vLLM area) so both tables are consistent.

In `@vektra-shared/src/vektra_shared/config.py`:
- Around line 36-55: VektraSettings currently defines context_window but never
stores/forwards it to LLMConfig in as_llm_config(), so VEKTRA_LLM_CONTEXT_WINDOW
is dropped; update VektraSettings to include context_window (matching the Field
name used in the diff) and modify as_llm_config() to pass context_window through
alongside extra_body and fallback_model into the returned LLMConfig, and add
validation to the Field declaration (context_window: int | None = Field(...,
ge=1)) so invalid/non-positive values fail fast.

---

Nitpick comments:
In @.claude/CLAUDE.md:
- Line 20: The docs currently hardcode Qdrant connection details ("port 10633"
and collection `vektra`) which may vary by environment; update the text that
references Qdrant (the sentence mentioning Qdrant port 10633, collection
`vektra`, and the use of `namespace_id` and `document_chunks`) to instead point
readers to the runtime/config settings or environment variables used by the app
(e.g., reference the configured Qdrant host/port and collection name in
config/env) and suggest checking runtime config to determine the actual
collection/port for their environment.

In `@Dockerfile`:
- Around line 73-76: The Dockerfile currently runs `uv sync` twice (first
unconditionally, then again with `--extra ocr` when
`INSTALL_UNSTRUCTURED=true`), causing redundant work; change the logic to
compute the extras/options before calling `uv sync` and invoke `uv sync` only
once: build an extras/options variable that always includes `--extra sparse
--extra qdrant` and conditionally appends `--extra ocr` when
`INSTALL_UNSTRUCTURED` is "true", then call `uv sync --frozen --no-editable
--no-dev` with that single assembled extras string so `uv sync` runs only once.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ac00851e-5ec3-498d-8746-3431c6f02bfb

📥 Commits

Reviewing files that changed from the base of the PR and between e35cc7f and 6685f7f.

⛔ Files ignored due to path filters (2)
  • .s2s/BACKLOG.md is excluded by !.s2s/**
  • uv.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (24)
  • .claude/CLAUDE.md
  • .env.example
  • .gitignore
  • Dockerfile
  • Makefile
  • docs/reference/configuration.md
  • scripts/eval_e2e.py
  • scripts/eval_retrieval.py
  • tests/eval/dataset.jsonl
  • vektra-app/src/vektra_app/main.py
  • vektra-core/pyproject.toml
  • vektra-core/src/vektra_core/advanced_pipeline.py
  • vektra-core/src/vektra_core/api.py
  • vektra-core/src/vektra_core/conversation.py
  • vektra-core/src/vektra_core/pipeline.py
  • vektra-core/src/vektra_core/providers/litellm_provider.py
  • vektra-core/src/vektra_core/reranker.py
  • vektra-core/src/vektra_core/templates/system.j2
  • vektra-core/tests/test_pipeline.py
  • vektra-core/tests/test_reranker.py
  • vektra-learn/src/vektra_learn/api.py
  • vektra-shared/src/vektra_shared/config.py
  • vektra-shared/src/vektra_shared/types.py
  • vektra-shared/tests/test_config.py

Comment thread .claude/CLAUDE.md Outdated
Comment thread scripts/eval_e2e.py
Comment thread scripts/eval_retrieval.py
Comment thread vektra-core/src/vektra_core/api.py
Comment thread vektra-core/src/vektra_core/conversation.py
Comment thread vektra-core/src/vektra_core/reranker.py
Comment thread vektra-core/tests/test_pipeline.py Outdated
fvadicamo and others added 7 commits March 25, 2026 18:54
context_window was defined in LLMConfig but not stored in VektraSettings
or forwarded by as_llm_config(). Worked by accident via pydantic-settings
env fallback, but broke when passing values programmatically. Add
llm_context_window field and forward it explicitly. Also add ge=1
validation to reject invalid values.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adversarial questions (no expected_keywords) were averaged into hit
rate, MRR, and precision, biasing all metrics downward. Add
has_ground_truth flag and compute metrics only over scored entries.
Unscored count shown separately in summary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests only verified the second call was silent but not that the first
call actually emitted the warning. If logging broke entirely, both
tests would still pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
API/network errors were incorrectly classified as no_relevant_context,
inflating the no-context count and omitting duration_ms.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Missing from the primary config matrix, only documented in the local
vLLM section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reference config env vars instead of hardcoding port 10633 and
collection name. Clarify wording for when to use each endpoint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Was running uv sync twice: once without --extra ocr, then again with
it. Restructured to run once with the correct extras.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@fvadicamo

Copy link
Copy Markdown
Contributor Author

Fixed in f31b588. Added llm_context_window to VektraSettings and forwarded via as_llm_config(). Added ge=1 validation. Addresses outside-diff comment on vektra-shared/src/vektra_shared/config.py:36-55.

@fvadicamo

Copy link
Copy Markdown
Contributor Author

Fixed in fe910a4. Added VEKTRA_LLM_EXTRA_BODY row to the main LLM config table. Addresses outside-diff comment on docs/reference/configuration.md:25-32.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
docs/reference/configuration.md (1)

45-51: Consider adding shell quoting guidance for JSON values.

The VEKTRA_LLM_EXTRA_BODY example shows raw JSON. In shell environments, this requires quoting (e.g., VEKTRA_LLM_EXTRA_BODY='{"chat_template_kwargs": {"enable_thinking": false}}'), while Docker Compose .env files may handle it differently. A brief note on quoting could prevent user confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/reference/configuration.md` around lines 45 - 51, Update the
VEKTRA_LLM_EXTRA_BODY example to warn about shell quoting: explain that JSON
must be quoted in shell environments (e.g., use single quotes around the JSON
string) and note that Docker Compose .env files may handle quoting differently;
reference the VEKTRA_LLM_EXTRA_BODY variable name and the example block so
readers know to use something like
VEKTRA_LLM_EXTRA_BODY='{"chat_template_kwargs": {"enable_thinking": false}}' in
shells while calling out Docker Compose .env behavior.
scripts/eval_retrieval.py (2)

107-119: Consider setting has_ground_truth=False for error results.

Error results default to has_ground_truth=True, but they're filtered out by r.error is None before the scored/unscored split. While this works correctly, explicitly setting has_ground_truth=False would make the intent clearer and prevent accidental inclusion if filtering logic changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/eval_retrieval.py` around lines 107 - 119, The except block returning
a QuestionResult currently omits has_ground_truth; update the error return in
the except to include has_ground_truth=False so that the error result explicitly
marks no ground truth (modify the QuestionResult construction inside the except
in scripts/eval_retrieval.py where QuestionResult(...) is returned for
exceptions, adding has_ground_truth=False alongside the existing fields like
id=question_id, question=question, category=category, language=language,
error=str(e)).

271-272: Inconsistent JSON encoding between eval scripts.

eval_e2e.py uses json.dumps(row, ensure_ascii=False) (line 171) for proper Unicode output, but this script uses json.dumps(row) which escapes non-ASCII characters. For a bilingual evaluation dataset, consider using ensure_ascii=False here as well for consistent and readable JSONL output.

♻️ Suggested fix
-            f.write(json.dumps(row) + "\n")
+            f.write(json.dumps(row, ensure_ascii=False) + "\n")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/eval_retrieval.py` around lines 271 - 272, The JSONL output here uses
json.dumps(row) which escapes non-ASCII characters; update the call to
json.dumps(row, ensure_ascii=False) where the row is written (the json.dumps
invocation used with f.write) so Unicode is preserved and matches the behavior
in eval_e2e.py (keeping the rest of the row population like row["error"]
unchanged).
scripts/eval_e2e.py (1)

122-124: Minor: percentile index calculation could be more precise.

The p95 index calculation int(len(durations) * 0.95) truncates rather than rounding, which may select a slightly lower percentile for small datasets. For an evaluation harness this is acceptable, but min(int(len(durations) * 0.95), len(durations) - 1) would be safer to prevent potential index-out-of-bounds on edge cases.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/eval_e2e.py` around lines 122 - 124, The p95 index calculation can be
out-of-bounds for small datasets; update the p95 computation that uses durations
and p95 to clamp the index: compute idx = min(int(len(durations) * 0.95),
len(durations) - 1) (or use round if preferred) and then set p95 =
durations[idx]; ensure you apply this guard where durations is built from valid
(the sorted(r.duration_ms for r in valid)) so an empty/small durations list
cannot cause an IndexError.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/reference/configuration.md`:
- Around line 45-51: Update the VEKTRA_LLM_EXTRA_BODY example to warn about
shell quoting: explain that JSON must be quoted in shell environments (e.g., use
single quotes around the JSON string) and note that Docker Compose .env files
may handle quoting differently; reference the VEKTRA_LLM_EXTRA_BODY variable
name and the example block so readers know to use something like
VEKTRA_LLM_EXTRA_BODY='{"chat_template_kwargs": {"enable_thinking": false}}' in
shells while calling out Docker Compose .env behavior.

In `@scripts/eval_e2e.py`:
- Around line 122-124: The p95 index calculation can be out-of-bounds for small
datasets; update the p95 computation that uses durations and p95 to clamp the
index: compute idx = min(int(len(durations) * 0.95), len(durations) - 1) (or use
round if preferred) and then set p95 = durations[idx]; ensure you apply this
guard where durations is built from valid (the sorted(r.duration_ms for r in
valid)) so an empty/small durations list cannot cause an IndexError.

In `@scripts/eval_retrieval.py`:
- Around line 107-119: The except block returning a QuestionResult currently
omits has_ground_truth; update the error return in the except to include
has_ground_truth=False so that the error result explicitly marks no ground truth
(modify the QuestionResult construction inside the except in
scripts/eval_retrieval.py where QuestionResult(...) is returned for exceptions,
adding has_ground_truth=False alongside the existing fields like id=question_id,
question=question, category=category, language=language, error=str(e)).
- Around line 271-272: The JSONL output here uses json.dumps(row) which escapes
non-ASCII characters; update the call to json.dumps(row, ensure_ascii=False)
where the row is written (the json.dumps invocation used with f.write) so
Unicode is preserved and matches the behavior in eval_e2e.py (keeping the rest
of the row population like row["error"] unchanged).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7c9950f8-b106-4293-a829-400d5043472d

📥 Commits

Reviewing files that changed from the base of the PR and between 6685f7f and 1a8eb14.

📒 Files selected for processing (7)
  • .claude/CLAUDE.md
  • Dockerfile
  • docs/reference/configuration.md
  • scripts/eval_e2e.py
  • scripts/eval_retrieval.py
  • vektra-core/tests/test_pipeline.py
  • vektra-shared/src/vektra_shared/config.py
✅ Files skipped from review due to trivial changes (1)
  • .claude/CLAUDE.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • Dockerfile
  • vektra-core/tests/test_pipeline.py
  • vektra-shared/src/vektra_shared/config.py

fvadicamo and others added 3 commits March 25, 2026 19:23
Streaming path does not emit server-generated conversation_id in any
SSE event. Direct API consumers using SSE without pre-generating an
ID cannot discover which conversation to continue.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cross-encoder reranker (bge-m3) takes ~4s per query vs ~50ms for
flashrank. 100 queries caused the integration job to exceed its
20-minute timeout. 20 queries is sufficient for p50/p95 on a
non-blocking warn-only measurement.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Query latency measurement (100 queries with cross-encoder reranker)
takes ~7 minutes and is warn-only. No reason to run on every PR push.
Now runs only on push to develop/main (post-merge). Restored 100
queries and raised job timeout to 30 minutes to accommodate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the ci CI/CD and toolchain changes label Mar 25, 2026
- Add shell quoting note for VEKTRA_LLM_EXTRA_BODY JSON values
- Set has_ground_truth=False on error results (defensive)
- Use ensure_ascii=False in eval_retrieval JSONL output (matches eval_e2e)
- Add VEKTRA_LLM_CONTEXT_WINDOW to vLLM example block

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@fvadicamo

Copy link
Copy Markdown
Contributor Author

Addressed 3 of 4 nitpicks from review round 2 in 4f2fa6a:

  • configuration.md: added shell quoting note for JSON env vars
  • eval_retrieval.py: set has_ground_truth=False on error results, use ensure_ascii=False for JSONL

Skipped #4 (p95 index clamp): int(len * 0.95) truncates, so for any len >= 1 the index is valid. The early return on evaluated == 0 guarantees len >= 1.

@fvadicamo fvadicamo merged commit e9d7bd0 into develop Mar 25, 2026
19 checks passed
@fvadicamo fvadicamo deleted the fix/conversation-persistence branch March 25, 2026 21:42
@fvadicamo fvadicamo self-assigned this Apr 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ci CI/CD and toolchain changes component:core vektra-core component component:shared vektra-shared component documentation Improvements or additions to documentation enhancement New feature or request infra Infrastructure, Docker, deployment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant