Skip to content

refactor main.py and separate concerns - #96

Merged
JonathanPschl merged 3 commits into
mainfrom
refactor/gen-ai-SOC
Jul 10, 2026
Merged

refactor main.py and separate concerns#96
JonathanPschl merged 3 commits into
mainfrom
refactor/gen-ai-SOC

Conversation

@JonathanPschl

@JonathanPschl JonathanPschl commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Closes #89

gen-ai/main.py had grown into a single 312-line file mixing five unrelated concerns. This splits it into focused, single-responsibility modules with a clean acyclic dependency graph, no behavior or HTTP contract changes.

Module Responsibility
main.py app factory + Prometheus instrumentator + router wiring
config.py provider constants, load_dotenv(), Provider type
schemas.py Pydantic request/response models
prompts.py system prompts, DIETARY_RULES, build_system_prompt
llm.py provider selection, OpenAI client, chat completion, JSON parsing, fallback data
routers.py APIRouter for /health, /api/ai/parse, /api/ai/merge

Summary by CodeRabbit

  • New Features

    • Added AI-powered ingredient parsing from recipe text.
    • Added dietary restriction handling with ingredient alternatives.
    • Added recipe merging with duplicate ingredient consolidation and metric quantity conversion.
    • Added support for OpenAI, Logos, and local model providers.
    • Added health-check functionality and structured API responses.
    • Added fallback ingredient results when AI services or responses are unavailable.
  • Bug Fixes

    • Improved handling of empty, invalid, or partially formatted AI responses.
  • Tests

    • Expanded coverage reporting across configuration, prompts, schemas, providers, routing, and AI response handling.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change extracts LLM configuration, schemas, prompts, provider helpers, and FastAPI routes from main.py into dedicated modules, updates application wiring, and adjusts tests and coverage configuration for the new structure.

Changes

LLM API refactor

Layer / File(s) Summary
Provider contracts and prompt schemas
gen-ai/config.py, gen-ai/schemas.py, gen-ai/prompts.py
Adds environment-based provider settings, Pydantic request/response models, ingredient fields, parsing prompts, dietary rules, and recipe-merge instructions.
Provider clients and JSON processing
gen-ai/llm.py
Centralizes provider normalization, OpenAI-compatible client creation, deterministic completions, fallback ingredients, and JSON parsing.
FastAPI route integration
gen-ai/routers.py, gen-ai/main.py
Adds health, parse, and merge routes with fallback handling, then registers the shared router from the FastAPI bootstrap module.
Test and coverage wiring
gen-ai/tests/*, gen-ai/pytest.ini
Updates imports and mocks for the extracted modules and expands coverage targets beyond main.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Router
  participant LLM
  participant Provider
  Client->>Router: POST /api/ai/parse
  Router->>LLM: Normalize provider and create completion
  LLM->>Provider: Send prompt with JSON response settings
  Provider-->>LLM: Return model content
  LLM-->>Router: Parse JSON content
  Router-->>Client: Return GenerateResponse
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main refactor: splitting main.py into focused modules and separating concerns.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/gen-ai-SOC

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@timn21 timn21 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

looks good

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
gen-ai/llm.py (1)

20-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Sort __all__ to satisfy Ruff RUF022.

Ruff flags __all__ as unsorted. Apply isort-style alphabetical ordering.

♻️ Proposed fix
 __all__ = [
-    "OpenAI",
-    "OpenAIError",
-    "NO_LLM_NOTE",
-    "CANNED_INGREDIENTS",
-    "get_client",
-    "openai_available",
-    "normalize_provider",
-    "create_chat_completion",
-    "parse_json_content",
+    "CANNED_INGREDIENTS",
+    "NO_LLM_NOTE",
+    "OpenAI",
+    "OpenAIError",
+    "create_chat_completion",
+    "get_client",
+    "normalize_provider",
+    "openai_available",
+    "parse_json_content",
 ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gen-ai/llm.py` around lines 20 - 30, Sort the symbols in the module-level
__all__ list alphabetically in isort/Ruff RUF022 order, preserving all existing
exports.

Source: Linters/SAST tools

gen-ai/routers.py (1)

45-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add structured logging on fallback paths.

Errors are silently swallowed and replaced with fallback data. Without logging, production debugging is difficult since the client always receives a 200.

♻️ Suggested addition for both fallback blocks
 import logging
+
+logger = logging.getLogger(__name__)

In each except block:

     except (OpenAIError, KeyError, TypeError, ValueError):
+        logger.exception("LLM call failed, returning fallback response")
         return GenerateResponse(dish=request.dish, ingredients=CANNED_INGREDIENTS, note=NO_LLM_NOTE)

Also applies to: 69-72

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gen-ai/routers.py` around lines 45 - 46, Add structured logging to both
fallback exception handlers in the relevant router endpoint, including the
caught exception and useful request context before returning the canned
GenerateResponse; preserve the existing fallback response behavior while
ensuring both the handler around lines 45-46 and the additional block around
lines 69-72 emit logs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@gen-ai/routers.py`:
- Line 3: Replace HTTPException with TypeError in both exception clauses within
the relevant router functions, including the handlers around
normalize_provider/build_system_prompt and
create_chat_completion/parse_json_content/Ingredient construction, and remove
the now-unused HTTPException import from fastapi.

---

Nitpick comments:
In `@gen-ai/llm.py`:
- Around line 20-30: Sort the symbols in the module-level __all__ list
alphabetically in isort/Ruff RUF022 order, preserving all existing exports.

In `@gen-ai/routers.py`:
- Around line 45-46: Add structured logging to both fallback exception handlers
in the relevant router endpoint, including the caught exception and useful
request context before returning the canned GenerateResponse; preserve the
existing fallback response behavior while ensuring both the handler around lines
45-46 and the additional block around lines 69-72 emit logs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b98d8881-3824-4805-ba5a-5b0b0997291f

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb6b11 and b901701.

📒 Files selected for processing (13)
  • gen-ai/config.py
  • gen-ai/llm.py
  • gen-ai/main.py
  • gen-ai/prompts.py
  • gen-ai/pytest.ini
  • gen-ai/routers.py
  • gen-ai/schemas.py
  • gen-ai/tests/conftest.py
  • gen-ai/tests/test_endpoint_merge.py
  • gen-ai/tests/test_endpoint_parse.py
  • gen-ai/tests/test_parse_json_content.py
  • gen-ai/tests/test_prompts.py
  • gen-ai/tests/test_provider.py

Comment thread gen-ai/routers.py
@@ -0,0 +1,74 @@
import json

from fastapi import APIRouter, HTTPException

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

HTTPException is caught but never raised; TypeError is missing from the except clause.

None of the functions called in the try blocks (normalize_provider, build_system_prompt, create_chat_completion, parse_json_content, Ingredient(**item)) raise HTTPException. Conversely, if the LLM returns a non-dict structure (e.g., a top-level JSON array or {"ingredients": [1, 2, 3]}), data["ingredients"] or Ingredient(**item) will raise TypeError, which is not caught and will produce a 500 instead of falling back.

🛡️ Proposed fix: replace HTTPException with TypeError and remove unused import
-from fastapi import APIRouter, HTTPException
+from fastapi import APIRouter

Apply to both except clauses (lines 45 and 69):

-    except (HTTPException, OpenAIError, KeyError, ValueError):
+    except (OpenAIError, KeyError, TypeError, ValueError):

Also applies to: 45-45, 69-69

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gen-ai/routers.py` at line 3, Replace HTTPException with TypeError in both
exception clauses within the relevant router functions, including the handlers
around normalize_provider/build_system_prompt and
create_chat_completion/parse_json_content/Ingredient construction, and remove
the now-unused HTTPException import from fastapi.

@JonathanPschl
JonathanPschl merged commit 04f2f40 into main Jul 10, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve gen-ai structure

2 participants