Skip to content

fix: default all LLM backends to deterministic greedy decoding - #426

Merged
Anket11 merged 1 commit into
mainfrom
development
Jul 31, 2026
Merged

fix: default all LLM backends to deterministic greedy decoding#426
Anket11 merged 1 commit into
mainfrom
development

Conversation

@phanindra-max

Copy link
Copy Markdown
Member

Every backend previously inherited its provider's default temperature. Set temperature 0.0 and wire GENERATION_SEED through the router to all backends that accept one. Expose temperature and seed on SkillExtractorRefactored. Add tests/test_determinism.py covering decoding defaults, request payloads, router forwarding, and alignment stability. Correct the paper's FAISS description from approximate to exact and add a reference configuration to the README.

Addresses JOSS review issue #424.

Every backend previously inherited its provider's default temperature.
Set temperature 0.0 and wire GENERATION_SEED through the router to all
backends that accept one. Expose temperature and seed on
SkillExtractorRefactored. Add tests/test_determinism.py covering decoding
defaults, request payloads, router forwarding, and alignment stability.
Correct the paper's FAISS description from approximate to exact and add a
reference configuration to the README.

Addresses JOSS review issue #424.
Copilot AI review requested due to automatic review settings July 28, 2026 03:34
@github-actions

Copy link
Copy Markdown

Code Hygiene

The following items are defined but not yet in active use. Routine cleanup — no impact on functionality.

  • laiser/llm_methods.py:88 — unused variable 'args' (100% confidence)
  • laiser/llm_models/llama_cpp_handler.py:47 — unused variable 'chat_format' (100% confidence)
  • laiser/llm_models/llama_cpp_handler.py:88 — unused variable 'exc' (100% confidence)
  • laiser/llm_models/llama_cpp_handler.py:88 — unused variable 'exc_type' (100% confidence)
  • laiser/llm_models/llama_cpp_handler.py:88 — unused variable 'tb' (100% confidence)
  • laiser/llm_models/llm_router.py:71 — unused variable 'args' (100% confidence)
  • laiser/llm_models/llm_router.py:86 — unused variable 'args' (100% confidence)
  • laiser/services.py:132 — unused variable 'raw_description' (100% confidence)

@github-actions

Copy link
Copy Markdown

Complexity Analysis

Average: C — Moderate (14.0)

Hotspots tracked below. These are functional, well-exercised components — listed for visibility as the codebase scales.

Grade Name Type Score
F FAISSIndexManager.initialize_index Method 51
E SkillExtractionService.extract_and_align_core Method 36
E FAISSIndexManager.search_similar_skills Method 32
D ResponseParser._parse_skills_from_response Method 24
D FAISSIndexManager Class 22
C ResponseParser.parse_knowledge_task_response Method 20
C _BaseTaxonomyFAISSIndexManager.search_similar Method 18
C ResponseParser Class 17
C ResponseParser.parse_ksa_extraction_response Method 16
C ResponseParser.parse_skill_extraction_response Method 15
C openai_generate Function 14
C LLMRouter._initialize_components Method 14
C parse_output_vllm Function 13
C AlignmentService.align Method 11
C anthropic_generate Function 11
B SkillExtractionService._deduplicate Method 10
B GeminiAPI.generate_with_config Method 10
B load_model_from_vllm Function 10
B _BaseTaxonomyFAISSIndexManager.initialize_index Method 9
B ResponseParser.parse_ksa_details_response Method 8
B SkillExtractionService Class 8
B _BaseTaxonomyFAISSIndexManager Class 8
B SkillExtractorRefactored._expand_allowed_sources Method 7
B get_completion_vllm Function 7
B llama_cpp_chat Function 7
B AlignmentService Class 6
B SkillExtractionService._normalize_mixed_concept_rows Method 6
B fetch_model_output Function 6
B GeminiAPI Class 6
B llm_generate Function 6
B LlamaCppBackend.__init__ Method 6

@github-actions

Copy link
Copy Markdown

📦 Preview package published!

Install and test this PR:

pip install --index-url https://test.pypi.org/simple/ laiser-preview==0.5.dev20260728033459

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR standardizes deterministic LLM decoding across LAiSER’s supported backends to improve reproducibility (greedy decoding by default) and documents these guarantees for the JOSS review. It also adds a dedicated determinism test suite and updates the paper’s FAISS description to reflect exact retrieval.

Changes:

  • Introduces config-level deterministic decoding defaults (DEFAULT_TEMPERATURE=0.0, DEFAULT_TOP_P=1.0) and a shared GENERATION_SEED, and threads temperature/seed through SkillExtractorRefactoredSkillExtractionServiceLLMRouter.
  • Updates OpenAI, Anthropic, Gemini, local Transformers/vLLM, and llama.cpp backends to default to greedy decoding and (where supported) accept/forward a seed.
  • Adds tests/test_determinism.py and updates paper.md with a reproducibility section and corrected FAISS index description.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/test_determinism.py Adds regression tests for deterministic decoding defaults, request payloads, router forwarding, and alignment stability.
pytest.ini Registers a new determinism marker.
paper.md Updates FAISS discussion (exact vs approximate) and documents reproducibility strategy.
laiser/skill_extractor_refactored.py Exposes temperature and seed on the public extractor API and forwards to services.
laiser/services.py Stores and forwards temperature/seed into the LLM router initialization.
laiser/llm_models/openai.py Adds default deterministic decoding params and includes them in the request payload.
laiser/llm_models/llm_router.py Adds router-level temperature/seed and forwards them to the active backend.
laiser/llm_models/llama_cpp_handler.py Defaults llama.cpp temperature to greedy; forwards seed when supported by installed version.
laiser/llm_models/hugging_face_llm.py Adds deterministic defaults + optional seeding behavior for local Transformers and vLLM.
laiser/llm_models/gemini.py Defaults Gemini config to deterministic temperature and forwards seed when supported.
laiser/llm_models/anthropic.py Adds deterministic default temperature and optional top_p emission.
laiser/llm_methods.py Aligns local generation helpers with deterministic config defaults; vLLM sampling params now include temperature/top_p/seed.
laiser/config.py Adds DEFAULT_TEMPERATURE and DEFAULT_TOP_P (env-overridable) and documents determinism defaults.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 147 to +151
if self.backend == "llama_cpp":
print("LLMRouter: routing request to llama_cpp backend")
return llama_cpp_chat(prompt, self.llm)
return llama_cpp_chat(
prompt, self.llm, temperature=kwargs["temperature"], seed=self.seed
)
Comment on lines 153 to +156
print("LLMRouter: routing request to vLLM/transformer backend")
return llm_generate_vllm(prompt, self.llm)
return llm_generate_vllm(
prompt, self.llm, temperature=kwargs["temperature"], seed=self.seed
)
Comment thread tests/test_determinism.py
Comment on lines +19 to +26
import importlib
import inspect

import pandas as pd
import pytest

from laiser.config import DEFAULT_TEMPERATURE, DEFAULT_TOP_P, GENERATION_SEED

@Anket11

Anket11 commented Jul 31, 2026

Copy link
Copy Markdown
Member

LGTM!! Ship it.

@Anket11
Anket11 merged commit 553242c into main Jul 31, 2026
17 of 21 checks passed
@phanindra-max phanindra-max self-assigned this Aug 4, 2026
phanindra-max added a commit that referenced this pull request Aug 4, 2026
LLMRouter.generate() sent every non-Gemini, non-OpenAI, non-llama.cpp
request to llm_generate_vllm(), regardless of what _initialize_components()
had loaded. On CPU, and whenever vLLM initialization fails, initialization
falls back to a Transformers tokenizer and model and leaves self.llm as
None, so generation passed None into the vLLM backend and failed. The
deterministic decoding defaults added to llm_generate() in #426 were
therefore correct but dormant, and paper.md could not claim a working
fallback.

Local dispatch now branches on what is loaded: the vLLM engine when one is
present, the Transformers model otherwise, and LAiSERError when neither is,
rather than calling a backend with None.

Two defects on that newly reachable path are fixed alongside it:

- llm_generate() capped generation at 100 new tokens, which truncates
  skill-extraction JSON mid-object. It now accepts max_new_tokens and
  defaults to config.MAX_NEW_TOKENS.
- llm_generate() decoded the full output sequence, returning the prompt
  echoed back along with the completion, so ResponseParser would have
  received the prompt's own JSON examples alongside the model's answer.
  Only the newly generated tokens are decoded now, matching the vLLM path.

Also addresses the remaining Copilot review comments on #426:

- generate() documents that explicit kwargs take precedence, but the
  llama.cpp and local branches read self.seed directly and discarded any
  caller-supplied seed. Seed now flows through kwargs.setdefault() like
  temperature.
- The openai_generate ImportError branch defined no fallback, so a missing
  dependency raised NameError at call time instead of a useful message.
- test_config_defaults_are_deterministic asserted against constants that
  laiser.config reads from LAISER_TEMPERATURE and LAISER_TOP_P at import
  time, so a runner exporting either turned the guarantee into a false
  pass. It now reloads the module with those overrides removed.
- pytest.ini registered a determinism marker that no test applied; it is
  now applied at module level.

Adds four LLMRouter tests covering vLLM forwarding, Transformers dispatch,
caller seed override, and the no-model error. Three of the four fail
against the previous router.

Documents the reference configuration in README.md, which the description
of #426 promised but the diff did not contain.

Refs #424, #426.
phanindra-max pushed a commit that referenced this pull request Aug 4, 2026
…h the code

Addresses the two parts of JOSS review issue #424 that PR #426 left
untouched, and corrects a backend description that the code has since
outgrown.

Summary. The reviewer noted it was too technical to be understood by a
reader without a technical background. It opened on a two-stage LLM and
FAISS pipeline and closed with pip invocations. It now opens on the problem
in plain language, that the same skill is described differently in a job
posting, a syllabus and a credential, which defeats analysis at scale;
explains what a skill taxonomy is before naming one; states who the tool is
for and what they get from it; and defers pipeline internals and
installation to later sections and the repository.

Abbreviations. The reviewer asked for elaborate forms before first use.
ESCO, FAISS, NER and spaCy are now introduced in full at first use. KSA,
SCQF and LLM were already expanded. Every abbreviation in the manuscript
was checked for an expansion at or before its first bare occurrence.

Backends. The State of the field section named only vLLM, Transformers and
the Gemini API, and claimed automatic fallback between them. The router
also dispatches to llama.cpp and the OpenAI API, and the fallback claim was
untrue until a649ea2 made the vLLM to Transformers fallback reachable. Both
sections now list the backends the router actually dispatches to and state
the fallback explicitly. Anthropic is omitted deliberately: anthropic.py
exists but is not wired into the router, so naming it would overclaim.

The determinism half of #424 was addressed in #426 and a649ea2.

Refs #424.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants