Skip to content

cleaned up gen ai services#123

Open
paulwiese wants to merge 4 commits into
mainfrom
optimized-llm-config
Open

cleaned up gen ai services#123
paulwiese wants to merge 4 commits into
mainfrom
optimized-llm-config

Conversation

@paulwiese

@paulwiese paulwiese commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Cleaned up LLM config by using with_structured_output pattern.

Fixed: Only recipe title was sent to LLM before.

Ran experiments:
Logos needs more than 60s if reasoning_effort is set to medium or high.
For google_genai the thinking_level is also set to low as it appears to be balancing the tradeoff between inference time and quality/reliability best.
Kept generated recipes at 3 as more recipes cause longer inference time especially for Logos.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Infrastructure / CI
  • Documentation

API changes

  • This PR does not affect the API
  • This PR changes the API → api/openapi.yaml updated and api/scripts/gen-all.sh re-run

Definition of Done

  • CI passes
  • Pre-commit hooks pass locally
  • Relevant tests added or updated

Summary by CodeRabbit

  • New Features
    • AI help responses and generated recipes now use schema-validated structured output for more consistent results.
    • Recipe generation reliably includes ingredients, steps, and nutrients when available.
  • Bug Fixes
    • Reduced failures from fragile JSON/code-fence parsing by removing manual response cleanup.
    • Improved how recipe-related context is assembled for help requests.
  • Chores / Tests
    • Updated dependency versions for the LangChain and Pydantic stack.
    • Refreshed endpoint tests to match the structured LLM interaction model.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@paulwiese, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 28 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c7e8c8c9-ed46-47cb-8eed-ab4d39ef2a42

📥 Commits

Reviewing files that changed from the base of the PR and between 651ad4f and 1a504d4.

📒 Files selected for processing (1)
  • services/py-recipe-service/main.py
📝 Walkthrough

Walkthrough

Both services switch to LangChain structured output with local Pydantic schemas, update LLM provider settings, and adjust tests to mock structured runnable responses instead of raw JSON content.

Changes

Help Service Structured Output

Layer / File(s) Summary
Local response schema and LLM setup
services/py-help-service/main.py
Adds LocalHelpResponse, types the injected llm as BaseChatModel, and changes provider kwargs to use thinking_level.
Prompt assembly and structured response
services/py-help-service/main.py
Builds recipe_ctx from request data, invokes the structured LLM, and returns the structured model payload.
Structured-output test harness
services/py-help-service/tests/test_help_service.py
Updates the help service tests to mock with_structured_output, return LocalHelpResponse, and keep the existing endpoint assertions.

Recipe Service Structured Output

Layer / File(s) Summary
Local recipe schemas and LLM config
services/py-recipe-service/main.py, services/py-recipe-service/requirements.txt
Adds local recipe schema classes, types the injected llm, changes provider configuration, and updates pinned dependencies.
Structured recipe generation
services/py-recipe-service/main.py
Uses llm.with_structured_output(RecipeListWrapper), converts structured items into RecipeInput dicts, and removes manual JSON parsing.
Structured-output test harness
services/py-recipe-service/tests/test_recipe_service.py
Updates the recipe service tests to mock structured LLM output, return typed recipe wrappers, and keep the existing endpoint checks.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is generic and doesn't clearly describe the main changes to structured LLM output and service-specific inference settings. Use a specific title like "Use structured LLM output in py help and recipe services".
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch optimized-llm-config

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.

@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: 3

🤖 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 `@services/py-recipe-service/main.py`:
- Around line 37-49: Keep LocalRecipeInput aligned with the public /ai/recipes
contract by removing nutrients from the schema or making sure it is not included
in the response path; update the RecipeInput.from_dict(...) usage in the
recipe-building flow so only title, ingredients, instructions, and portions are
serialized into the returned object. Check the LocalRecipeInput and
RecipeInput.from_dict symbols together to ensure the downstream response shape
stays unchanged unless the API contract is intentionally updated.
- Around line 208-216: The recipe-service test double is still mocking direct
LLM invocation, but `create_recipe()` now uses
`llm.with_structured_output(RecipeListWrapper).ainvoke(...)`. Update the test in
`test_recipe_service.py` so `mock_llm.with_structured_output()` returns a
runnable/mock that exposes `ainvoke`, and move the `ainvoke`
expectation/assertion to that returned object instead of `mock_llm` directly.
Use the `create_recipe` flow and `RecipeListWrapper` path to locate the change.
- Around line 155-157: The Gemini provider setup in init_chat_model is passing
an unsupported thinking_level kwarg through kwargs, which can break startup with
langchain-google-genai==2.1.12. Remove the thinking_level assignment in
main.py’s model initialization path, or update the dependency to a version that
supports it, and make sure the google_genai branch only forwards accepted
parameters.
🪄 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: 29f99c9f-899d-4705-9071-2295d0bdaef6

📥 Commits

Reviewing files that changed from the base of the PR and between ddeedef and af0ef01.

📒 Files selected for processing (2)
  • services/py-help-service/main.py
  • services/py-recipe-service/main.py

Comment thread services/py-recipe-service/main.py Outdated
Comment thread services/py-recipe-service/main.py
Comment thread services/py-recipe-service/main.py

@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 (1)
services/py-help-service/tests/test_help_service.py (1)

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

Consider asserting with_structured_output is called with LocalHelpResponse.

The mock accepts any argument to with_structured_output, so the test wouldn't catch a regression where the endpoint passes the wrong schema class. Could add mock_llm.with_structured_output.assert_called_once_with(LocalHelpResponse).

Also applies to: 78-78

🤖 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 `@services/py-help-service/tests/test_help_service.py` at line 41, The tests
around the help service should verify that the LLM wrapper is configured with
the correct response schema, not just any schema. Update the relevant test cases
that use mock_llm.with_structured_output and assert it is called once with
LocalHelpResponse so a wrong schema class in the endpoint would fail the test.
🤖 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 `@services/py-recipe-service/main.py`:
- Line 7: Handle the omitted nutrients case before constructing RecipeInput: in
the code path that calls r.model_dump() and RecipeInput.from_dict(), make sure a
None nutrients value is treated the same as a missing key so
RecipeNutrients.from_dict is never called with None. Update the relevant
conversion logic around RecipeInput.from_dict and the RecipeNutrients parsing
branch to skip or remove nutrients when it is None, preserving existing behavior
for present nutrient data.

---

Nitpick comments:
In `@services/py-help-service/tests/test_help_service.py`:
- Line 41: The tests around the help service should verify that the LLM wrapper
is configured with the correct response schema, not just any schema. Update the
relevant test cases that use mock_llm.with_structured_output and assert it is
called once with LocalHelpResponse so a wrong schema class in the endpoint would
fail the test.
🪄 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: c26af8af-befb-4b60-a10a-fed94f962d35

📥 Commits

Reviewing files that changed from the base of the PR and between af0ef01 and 651ad4f.

📒 Files selected for processing (6)
  • services/py-help-service/main.py
  • services/py-help-service/requirements.txt
  • services/py-help-service/tests/test_help_service.py
  • services/py-recipe-service/main.py
  • services/py-recipe-service/requirements.txt
  • services/py-recipe-service/tests/test_recipe_service.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • services/py-help-service/main.py

Comment thread services/py-recipe-service/main.py Outdated
import time
import traceback
from typing import Any
from typing import Any, List, Optional

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
ast-grep run --pattern $'def generate_recipes($$$) {
  $$$
}' --lang python services/py-recipe-service/main.py

rg -n -B3 -A10 'RecipeInput\.from_dict|\.nutrients' services/py-recipe-service/main.py

Repository: AET-DevOps26/team-devsecops

Length of output: 984


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- main.py lines 1-120 ---'
sed -n '1,120p' services/py-recipe-service/main.py

echo
echo '--- main.py lines 170-235 ---'
sed -n '170,235p' services/py-recipe-service/main.py

echo
echo '--- nutrients references ---'
rg -n 'nutrients|LocalRecipeInput|RecipeInput\.from_dict|model_dump\(\)' services/py-recipe-service/main.py

Repository: AET-DevOps26/team-devsecops

Length of output: 6215


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the model definitions and conversion logic around the suspected None path.
sed -n '1,120p' services/py-recipe-service/main.py
printf '\n---\n'
sed -n '170,235p' services/py-recipe-service/main.py
printf '\n---\n'
rg -n 'nutrients|LocalRecipeInput|RecipeInput\.from_dict|model_dump\(\)' services/py-recipe-service/main.py

Repository: AET-DevOps26/team-devsecops

Length of output: 6136


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the structured-output schema and any conversion code that handles nutrients.
rg -n -A8 -B8 'class LocalRecipeInput|class LocalRecipeNutrients|with_structured_output|RecipeInput\.from_dict|nutrients' services/py-recipe-service/main.py

Repository: AET-DevOps26/team-devsecops

Length of output: 2048


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the RecipeInput type and its from_dict implementation.
rg -n -A20 -B10 'class RecipeInput|def from_dict|RecipeInput\(' .

Repository: AET-DevOps26/team-devsecops

Length of output: 50384


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the exact RecipeInput deserialization path used by services/py-recipe-service/main.py.
recipe_file='services/py-recipe-service/client/cooking_assistant_gen_ai_services_api_internal_client/models/recipe_input.py'
wc -l "$recipe_file"
printf '\n---\n'
sed -n '1,220p' "$recipe_file"

Repository: AET-DevOps26/team-devsecops

Length of output: 3015


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Narrowly inspect the deserializer and its handling of optional nutrients.
python3 - <<'PY'
from pathlib import Path
p = Path("services/py-recipe-service/client/cooking_assistant_gen_ai_services_api_internal_client/models/recipe_input.py")
text = p.read_text()
for needle in ["def from_dict", "nutrients", "RecipeNutrients.from_dict", "if nutrients is not UNSET", "if nutrients is not None"]:
    idx = text.find(needle)
    print(f"\n--- {needle} ---")
    if idx == -1:
        print("not found")
        continue
    start = max(0, text.rfind("\n", 0, idx-200))
    end = min(len(text), idx+700)
    print(text[start:end])
PY

Repository: AET-DevOps26/team-devsecops

Length of output: 3580


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether model_dump() includes None by default in the available Pydantic version.
python3 - <<'PY'
from pydantic import BaseModel
from typing import Optional

class Child(BaseModel):
    x: int

class Parent(BaseModel):
    child: Optional[Child] = None

print(Parent().model_dump())
print(Parent(child=None).model_dump())
PY

Repository: AET-DevOps26/team-devsecops

Length of output: 197


Handle nutrients=None before building RecipeInput. r.model_dump() emits "nutrients": None for omitted optional fields, but RecipeInput.from_dict() only treats a missing key as absent; None reaches RecipeNutrients.from_dict(None) and raises.

🤖 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 `@services/py-recipe-service/main.py` at line 7, Handle the omitted nutrients
case before constructing RecipeInput: in the code path that calls r.model_dump()
and RecipeInput.from_dict(), make sure a None nutrients value is treated the
same as a missing key so RecipeNutrients.from_dict is never called with None.
Update the relevant conversion logic around RecipeInput.from_dict and the
RecipeNutrients parsing branch to skip or remove nutrients when it is None,
preserving existing behavior for present nutrient data.

@paulwiese paulwiese requested review from imol-ai and jschoedl July 2, 2026 22:50
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.

1 participant