Files
Chinmay V cb535d033c fix(llm): send the configured temperature on local inference servers (#4634)
## Description

Fixes #4631.

With a local provider and `LLM_TEMPERATURE` unset, cognee sends no
`temperature` at all, so extraction runs at whatever the model itself
defaults to. For several Ollama models that is `1.0`.
`docs/ollama_models.md` already tells users extraction wants `0.0`, but
nothing in the default path does it.

**Why the gate exists, and why it is too wide.**
`fold_sampling_params_into_llm_args` only folds `llm_temperature` when
it is in `model_fields_set`, and its docstring gives the reason: the
default gpt-5 family rejects any temperature other than the provider
default, so an unset field must not silently send `0.0`. That reason is
real, but it is a property of the hosted OpenAI reasoning models, not of
every provider. The gate applies to all of them, so Ollama, llama.cpp
and LM Studio inherit a workaround for a restriction they do not have.
`llm_temperature` already defaults to `0.0`, so the value was there the
whole time; only the gate stopped it.

**The fix** is one condition, reusing the predicate this file already
has:

```python
if "llm_temperature" in self.model_fields_set or is_local_llm(
    self.llm_provider, self.llm_model
):
    folded["temperature"] = self.llm_temperature
```

`is_local_llm` is not new here. The validator immediately below,
`default_local_rate_limit_budget`, already uses it to give local servers
a different default for the same class of reason: a default shaped for
cloud endpoints being wrong for local ones. This follows that precedent
rather than adding a new axis, and it runs after
`infer_provider_from_model`, the same precondition that validator
documents.

**Two things I deliberately did not do.**

I did not invert the gate to "everything except gpt-5", which is
arguably the more principled shape since gpt-5 is the actual constraint
named in the docstring. It would start sending `0.0` to every Anthropic,
Gemini, Mistral and Bedrock user who never asked for it, which is a far
larger behaviour change than this bug justifies.

I did not widen `is_local_llm`. It excludes vLLM because vLLM batches
like a cloud endpoint, and that exclusion was written for rate limiting
rather than for temperature, so a vLLM endpoint would in fact accept the
field. I kept the existing predicate anyway: a vLLM user setting
`LLM_TEMPERATURE` explicitly already works today, and splitting the two
meanings apart is a bigger change than this bug needs. Happy to split it
if you would rather.

**Docs.** The change makes two existing statements wrong, so both are
updated here. `.env.template` said an unset value uses the provider's
default, now qualified for local servers. `docs/ollama_models.md`
troubleshooting item 2 told users to set `LLM_TEMPERATURE=0.0`
themselves, which is now the default, so the item became a check rather
than an instruction. This is option (a) from the issue; it is not an
alternative to the code change, it is required by it.

## Acceptance Criteria

- A local provider with `LLM_TEMPERATURE` unset sends `temperature:
0.0`; hosted providers, vLLM included, are unchanged.
- Explicit configuration still wins: `LLM_TEMPERATURE` at any value is
folded as before, and a `temperature` key in `LLM_ARGS` still takes
precedence over the dedicated field.
- Added to `cognee/tests/unit/infrastructure/llm/test_llm_config.py`,
beside the existing sampling-param tests:
- `test_local_provider_folds_unset_temperature` (ollama, llama_cpp,
lm_studio) — the bug
- `test_non_local_provider_still_omits_unset_temperature` (gpt-5, two
vLLM spellings) — pins the boundary
  - `test_explicit_temperature_wins_on_local_provider`
  - `test_llm_args_temperature_wins_over_local_default`
- Verified fail-first by reverting only `config.py` and keeping the
tests: exactly the three `test_local_provider_folds_unset_temperature`
cases fail. The other four pass either way, since they pin behaviour
this change does not alter.
- The existing `test_unset_temperature_is_not_folded_into_llm_args`
builds a default config, which is the gpt-5 path, and still passes
untouched.
- `unit/infrastructure/llm/` + `unit/modules/retrieval/` run like for
like against `dev`: the failure sets diff identical, with +8 passes for
the tests added. The 10 failures in that set are pre-existing on `dev`.

## Type of Change
- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Code refactoring
- [ ] Other (please specify):

## Screenshots

See the attached run.

## Pre-submission Checklist
- [x] **I have tested my changes thoroughly before submitting this PR**
(See `CONTRIBUTING.md`)
- [x] **This PR contains minimal changes necessary to address the
issue/feature**
- [x] My code follows the project's coding standards and style
guidelines
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] I have added necessary documentation (if applicable)
- [x] All new and existing tests pass — with the caveat noted above,
that 10 tests in `unit/modules/retrieval/` already fail on `dev` in my
environment and are unchanged by this PR
- [x] I have searched existing PRs to ensure this change hasn't been
submitted already
- [x] I have linked any relevant issues in the description
- [x] My commits have clear and descriptive messages

## DCO Affirmation

I affirm that all code in every commit of this pull request conforms to
the terms of the Topoteretes Developer Certificate of Origin.

Signed-off-by: Chinmay V <chinmayv095@gmail.com>
2026-08-23 19:51:34 +02:00
..