phase: 02-operations-maintenance
plan: 04
type: execute
wave: 1
depends_on: []
files_modified: [query.py, ingest.py]
autonomous: true
requirements: [QRY-04]

must_haves:
truths:
- "Ollama timeout returns a user-friendly error message, not a raw 500"
- "Ollama connection refused returns a clear error message"
- "Retry logic attempts the request again before failing"
- "Both ingest.py and query.py use the same timeout/retry pattern"
artifacts:
- path: "query.py"
provides: "Timeout + retry logic for Ollama embedding calls"
contains: "retry"
- path: "ingest.py"
provides: "Timeout + retry logic for Ollama embedding calls"
contains: "retry"
key_links:
- from: "query.py get_embedding()"
to: "OLLAMA_URL/api/embeddings"
via: "httpx with timeout and retry"
pattern: "retry|retries|attempt"
- from: "ingest.py get_embedding()"
to: "OLLAMA_URL/api/embeddings"
via: "httpx with timeout and retry"
pattern: "retry|retries|attempt"



Add timeout handling and retry logic to all Ollama HTTP calls in query.py and ingest.py so that slow or unavailable Ollama returns a clear error message instead of a raw 500 server error.

Purpose: QRY-04 requires timeout handling for slow Ollama responses. The crew should see "Ollama antwortet nicht" instead of an opaque traceback.
Output: Updated query.py and ingest.py with retry logic and user-friendly error messages.


@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md


@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md

def get_embedding(text: str) -> list[float]:
    resp = httpx.post(
        f"{OLLAMA_URL}/api/embeddings",
        json={"model": EMBED_MODEL, "prompt": text},
        timeout=60,
    )
    resp.raise_for_status()
    return resp.json()["embedding"]
def get_embedding(text: str) -> list[float]:
    resp = httpx.post(
        f"{OLLAMA_URL}/api/embeddings",
        json={"model": EMBED_MODEL, "prompt": text},
        timeout=60,
    )
    resp.raise_for_status()
    return resp.json()["embedding"]
@app.post("/query", response_model=QueryResponse)
def query(req: QueryRequest):
    try:
        answer = ask(req.question)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))



Task 1: Add timeout + retry to query.py get_embedding
query.py
query.py

Replace the get_embedding function in query.py with a version that has retry logic and clear error messages.

Add at module level (after existing constants):

OLLAMA_TIMEOUT = 30       # seconds per attempt
OLLAMA_MAX_RETRIES = 2    # retry once after first failure (total 2 attempts)
OLLAMA_RETRY_DELAY = 2    # seconds between retries

Add import time to imports.

Define a custom exception class before get_embedding:

class OllamaError(Exception):
    """Raised when Ollama is unreachable or times out."""
    pass

Replace get_embedding:

def get_embedding(text: str) -> list[float]:
    last_error = None
    for attempt in range(1, OLLAMA_MAX_RETRIES + 1):
        try:
            resp = httpx.post(
                f"{OLLAMA_URL}/api/embeddings",
                json={"model": EMBED_MODEL, "prompt": text},
                timeout=OLLAMA_TIMEOUT,
            )
            resp.raise_for_status()
            return resp.json()["embedding"]
        except httpx.TimeoutException:
            last_error = f"Ollama antwortet nicht (Timeout nach {OLLAMA_TIMEOUT}s, Versuch {attempt}/{OLLAMA_MAX_RETRIES})"
        except httpx.ConnectError:
            last_error = f"Ollama nicht erreichbar unter {OLLAMA_URL} (Versuch {attempt}/{OLLAMA_MAX_RETRIES})"
        except httpx.HTTPStatusError as e:
            last_error = f"Ollama Fehler: HTTP {e.response.status_code} (Versuch {attempt}/{OLLAMA_MAX_RETRIES})"
        except Exception as e:
            last_error = f"Ollama Fehler: {e} (Versuch {attempt}/{OLLAMA_MAX_RETRIES})"

        if attempt < OLLAMA_MAX_RETRIES:
            time.sleep(OLLAMA_RETRY_DELAY)

    raise OllamaError(last_error)

This ensures:
- Timeout of 30s per attempt (down from 60s — fail faster)
- 2 total attempts with 2s delay between
- Clear German error messages for timeout, connection refused, HTTP errors
- OllamaError propagates up to api.py where it becomes the HTTPException detail string


cd /Users/bmt/Documents/RKL-OZM-Chat-Hook-v.0.0 && python -c "import ast; ast.parse(open('query.py').read()); print('syntax ok')" && grep -q 'OllamaError' query.py && grep -q 'OLLAMA_TIMEOUT' query.py && grep -q 'OLLAMA_MAX_RETRIES' query.py && grep -q 'TimeoutException' query.py && grep -q 'ConnectError' query.py && echo 'all checks passed'


- grep "OllamaError" query.py returns a match (custom exception)
- grep "OLLAMA_TIMEOUT" query.py returns a match (configurable timeout)
- grep "OLLAMA_MAX_RETRIES" query.py returns a match (retry count)
- grep "TimeoutException" query.py returns a match (timeout handling)
- grep "ConnectError" query.py returns a match (connection error handling)
- grep "Ollama antwortet nicht" query.py returns a match (user-friendly message)
- python -c "import ast; ast.parse(open('query.py').read())" succeeds

query.py get_embedding retries once on failure with clear German error messages for timeout, connection refused, and HTTP errors.


Task 2: Add timeout + retry to ingest.py get_embedding
ingest.py
ingest.py, query.py

Apply the same timeout + retry pattern to ingest.py's get_embedding function. The implementation is identical to what was done in query.py Task 1.

Add at module level (after existing constants):

OLLAMA_TIMEOUT = 30
OLLAMA_MAX_RETRIES = 2
OLLAMA_RETRY_DELAY = 2

Add import time to imports.

Add the same OllamaError exception class:

class OllamaError(Exception):
    """Raised when Ollama is unreachable or times out."""
    pass

Replace get_embedding with the identical retry-aware version from query.py (same function body — handles TimeoutException, ConnectError, HTTPStatusError with German messages, retries with delay).

Note: Both files have their own copy of get_embedding (not shared). This is intentional — they are standalone scripts that can run independently. Keep them as separate copies, not a shared module.


cd /Users/bmt/Documents/RKL-OZM-Chat-Hook-v.0.0 && python -c "import ast; ast.parse(open('ingest.py').read()); print('syntax ok')" && grep -q 'OllamaError' ingest.py && grep -q 'OLLAMA_TIMEOUT' ingest.py && grep -q 'OLLAMA_MAX_RETRIES' ingest.py && grep -q 'TimeoutException' ingest.py && grep -q 'ConnectError' ingest.py && echo 'all checks passed'


- grep "OllamaError" ingest.py returns a match (custom exception)
- grep "OLLAMA_TIMEOUT" ingest.py returns a match (configurable timeout)
- grep "OLLAMA_MAX_RETRIES" ingest.py returns a match (retry count)
- grep "TimeoutException" ingest.py returns a match (timeout handling)
- grep "ConnectError" ingest.py returns a match (connection error handling)
- grep "Ollama antwortet nicht" ingest.py returns a match (user-friendly message)
- python -c "import ast; ast.parse(open('ingest.py').read())" succeeds

ingest.py get_embedding has the same retry + timeout logic as query.py. Both files handle Ollama failures with clear, user-friendly German error messages instead of raw 500 tracebacks.


- Both query.py and ingest.py parse as valid Python
- Both contain OllamaError, retry logic, and user-friendly German error messages
- Timeout is 30s (not 60s), retries is 2


Ollama timeout or connection failure in either query.py or ingest.py produces a clear German error message (e.g., "Ollama antwortet nicht") instead of a raw 500 traceback. Retry logic attempts the call twice before giving up.

After completion, create `.planning/phases/02-operations-maintenance/02-04-SUMMARY.md`