feat(llm): implement OpenAI-compatible LLM client with retry #84

Merged
fuzzy merged 4 commits from feature/0022-implement-llm-client into main 2026-07-05 14:08:02 +00:00
Owner

What

Implement a generic OpenAI-compatible HTTP client for LLM communication with configurable endpoint, API key, model, and exponential backoff retry.

Why

Core LLM integration needed for processing job prompts against any OpenAI-compatible API.

Testing

  • 58 unit tests pass (14 new for LLM client)
  • Pre-commit hooks pass
  • Coverage: 84% (LLM module at 100%)

Breaking Changes

None.

Closes #0018
Closes #0019
Closes #0020
Closes #0021
Closes #0022

## What Implement a generic OpenAI-compatible HTTP client for LLM communication with configurable endpoint, API key, model, and exponential backoff retry. ## Why Core LLM integration needed for processing job prompts against any OpenAI-compatible API. ## Testing - [x] 58 unit tests pass (14 new for LLM client) - [x] Pre-commit hooks pass - [x] Coverage: 84% (LLM module at 100%) ## Breaking Changes None. Closes #0018 Closes #0019 Closes #0020 Closes #0021 Closes #0022
- Generic HTTP client for any OpenAI-compatible API
- Configurable endpoint, API key, and model
- Exponential backoff retry on 429, 5xx, and connection errors
- Immediate failure on 4xx client errors (except 429)
- Proper error extraction from chat completions response format
Closes #0018
Closes #0019
Closes #0020
Closes #0021
Cover init, response extraction, successful completion, request
format, error codes (401/403/429/500), retry logic with backoff,
max retries exhaustion, and connection errors.
docs: mark Phase 2 Task 2 complete
Some checks failed
CI / test (pull_request) Failing after 53s
CI / lint (pull_request) Successful in 1m19s
caea0b901a
LLM client with endpoint, API key, error handling, and retry done.
Closes #0022
fix: use MagicMock for context manager protocol in tests
All checks were successful
CI / test (pull_request) Successful in 51s
CI / lint (pull_request) Successful in 1m23s
fec03f325e
Python 3.13 Mock raises AttributeError on magic method access.
MagicMock pre-creates __enter__/__exit__ for context managers.
the.auditor left a comment

Automated Review — PR #84

Result: APPROVED


Solid LLM client implementation. Standout: exponential backoff retry, clean stdlib-only approach, thorough retry tests.

  • Correctness: OpenAI-compatible message format, Bearer auth, proper error classification (429/500+ retryable, 4xx non-retryable), exponential backoff, last-error propagation on exhaustion.
  • Completeness: Closes #18–#22. ROADMAP updated.
  • Test coverage: 14 tests covering init, content extraction, successful completion, request payload verification, retry (429/500), max retries exceeded, connection errors, non-retryable errors (401/403).
  • Error handling: LLMError wrapping HTTP errors, connection failures, and malformed responses. All paths covered.
  • Maintainability: Good separation of concerns (send, extract, retry logic). temperature/max_tokens promoted to explicit params (nice follow-up from earlier PR #72 feedback).

Suggestions (non-blocking)

  1. src/kronai/llm.py:78-88_retryable / _is_retryable pattern uses object.__setattr__ to monkey-patch a flag on LLMError instances. Consider a RetryableLLMError(LLMError) subclass instead:

    class RetryableLLMError(LLMError):
        pass
    
  2. src/kronai/llm.py:12-14MAX_RETRIES and BASE_DELAY are hardcoded module constants. Consider making them constructor params (max_retries=4, base_delay=1.0) for testability and configurability.

  3. src/kronai/llm.py:42-43complete() takes prompt but JobConfig has a system_prompt field. System messages aren't sent to the API. Consider adding an optional system_prompt param when the engine wires everything together, or have the engine prepend it to the prompt.

## Automated Review — PR #84 **Result: APPROVED** --- Solid LLM client implementation. Standout: exponential backoff retry, clean stdlib-only approach, thorough retry tests. - **Correctness**: OpenAI-compatible message format, Bearer auth, proper error classification (429/500+ retryable, 4xx non-retryable), exponential backoff, last-error propagation on exhaustion. - **Completeness**: Closes #18–#22. ROADMAP updated. - **Test coverage**: 14 tests covering init, content extraction, successful completion, request payload verification, retry (429/500), max retries exceeded, connection errors, non-retryable errors (401/403). - **Error handling**: `LLMError` wrapping HTTP errors, connection failures, and malformed responses. All paths covered. - **Maintainability**: Good separation of concerns (send, extract, retry logic). `temperature`/`max_tokens` promoted to explicit params (nice follow-up from earlier PR #72 feedback). ### Suggestions (non-blocking) 1. **`src/kronai/llm.py:78-88`** — `_retryable` / `_is_retryable` pattern uses `object.__setattr__` to monkey-patch a flag on `LLMError` instances. Consider a `RetryableLLMError(LLMError)` subclass instead: ```python class RetryableLLMError(LLMError): pass ``` 2. **`src/kronai/llm.py:12-14`** — `MAX_RETRIES` and `BASE_DELAY` are hardcoded module constants. Consider making them constructor params (`max_retries=4, base_delay=1.0`) for testability and configurability. 3. **`src/kronai/llm.py:42-43`** — `complete()` takes `prompt` but `JobConfig` has a `system_prompt` field. System messages aren't sent to the API. Consider adding an optional `system_prompt` param when the engine wires everything together, or have the engine prepend it to the prompt.
fuzzy self-assigned this 2026-07-05 14:07:56 +00:00
fuzzy merged commit fec03f325e into main 2026-07-05 14:08:02 +00:00
fuzzy deleted branch feature/0022-implement-llm-client 2026-07-05 14:08:03 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
thwap/kronai!84
No description provided.