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))
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
- 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
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.
- 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
- 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.