Skip to content

Evaluation: Gemini batch integration (text)#867

Merged
vprashrex merged 18 commits into
mainfrom
feat/eval-gemini-integration
Jul 1, 2026
Merged

Evaluation: Gemini batch integration (text)#867
vprashrex merged 18 commits into
mainfrom
feat/eval-gemini-integration

Conversation

@vprashrex

@vprashrex vprashrex commented May 20, 2026

Copy link
Copy Markdown
Collaborator

Target issue is #879

Summary

  • Moved evaluation batch submission from the API request (sync, blocking) to a Celery worker (async).
  • Added Celery task run_evaluation_batch_submission, enqueue helper start_evaluation_batch_submission, and worker entrypoint execute_evaluation_batch_submission; API now only creates the EvaluationRun and queues the job.
  • Run is marked failed with an error message on timeout or any exception during submission.
  • Added Google Gemini support to evaluation batches (was OpenAI-only).
  • start_evaluation_batch now takes a params dict + provider and branches: OpenAI builds Responses JSONL via build_openai_evaluation_jsonl, Gemini builds contents/systemInstruction/generationConfig JSONL via build_google_evaluation_jsonl.
  • Provider validation widened to accept openai, openai-native, google-aistudio, google-aistudio-native.
  • Result polling picks the provider via _get_batch_provider; parse_evaluation_output now reads Gemini responses (key id, extract_text_from_response_dict, usageMetadata → usage for cost).
  • Moved blocking I/O in polling (download, dataset fetch, langfuse run, embeddings) to asyncio.to_thread.
  • Fixed Gemini batch upload mime type: jsonl → application/jsonl.
  • Added/updated tests for OpenAI + Google JSONL builders, evaluation processing, and Langfuse dataset-run handling.

Checklist

Before submitting a pull request, please ensure that you mark these task.

  • Ran fastapi run --reload app/main.py or docker compose up in the repository root and test.
  • If you've fixed a bug or added code that is tested and has test cases.

- Introduced `run_evaluation_batch_submission` task to handle evaluation batch submissions.
- Created `start_evaluation_batch_submission` utility to enqueue evaluation tasks.
- Updated `start_evaluation` to utilize the new Celery task for batch submissions.
- Enhanced error handling in evaluation run creation and processing.
- Added support for Google Gemini in evaluation batch processing.
- Refactored model configuration to support multiple providers and completion types.
- Improved logging for better traceability during evaluation processes.
- Updated tests to reflect changes in evaluation batch submission logic.
@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds provider-aware evaluation batch submission through Celery, with Gemini/OpenAI JSONL generation and result parsing. It also updates organization, project, and user-project lifecycle behavior to support active/inactive states, soft and hard deletes, and related access checks.

Changes

Multi-provider Evaluation Batch (Gemini + Async Celery)

Layer / File(s) Summary
Gemini MIME type, batch failure status, and Langfuse rate-limit handling
backend/app/core/batch/gemini.py, backend/app/core/batch/operations.py, backend/app/crud/evaluations/langfuse.py
Changes Gemini JSONL upload MIME type, adds failed provider status on batch creation errors, and special-cases Langfuse HTTP 429 during dataset trace creation.
Multi-provider JSONL builders and start_evaluation_batch rewrite
backend/app/crud/evaluations/batch.py
Adds provider-specific JSONL builders and rewrites evaluation batch startup to normalize provider names, map params, create the matching batch client, and start the batch with provider-specific config.
Provider parsing and batch result handling
backend/app/crud/evaluations/processing.py
Adds Gemini batch provider dispatch, Gemini usage extraction, provider-aware output parsing, and provider-aware result handling for main batch downloads.
Async thread offloading and polling updates
backend/app/crud/evaluations/processing.py
Moves blocking batch and Langfuse work into background threads, expands failure-state detection, and adds Langfuse flushing during project polling.
execute_evaluation_batch_submission Celery service function
backend/app/services/evaluations/batch_job.py
Adds a service that opens a SQLModel session, resolves the evaluation run and config, starts the evaluation batch, and handles timeout and general failure paths.
Celery task registration and enqueue helper
backend/app/celery/tasks/job_execution.py, backend/app/celery/utils.py
Registers the new Celery task and adds a helper that enqueues it with trace context and returns the task id.
validate_and_start_batch_evaluation: Celery queuing and multi-provider gating
backend/app/services/evaluations/evaluation.py
Validates supported providers, queues batch submission through Celery, derives trace context, and updates the run on queue failures.
API batch builder and submission tests
backend/app/tests/api/routes/test_evaluation.py
Updates OpenAI JSONL assertions, adds Gemini JSONL coverage, and expands batch-start tests for provider dispatch and failure handling.
Processing provider parsing and batch-provider tests
backend/app/tests/crud/evaluations/test_processing.py
Adds Gemini parsing tests, Gemini usage extraction tests, and batch-provider dispatch tests.
Service and Langfuse tests
backend/app/tests/crud/evaluations/test_evaluation_service_s3.py, backend/app/tests/crud/evaluations/test_langfuse.py
Adds rate-limit coverage for Langfuse trace creation and service-level tests for async submission validation, queue failure, and success paths.

Access, delete, and lifecycle rules

Layer / File(s) Summary
Delete request model and public exports
backend/app/models/message.py, backend/app/models/__init__.py
Adds the delete request model and re-exports it from the models package for route use.
Organization CRUD lifecycle helpers
backend/app/crud/organization.py
Adds organization lookup helpers, duplicate-name checks, cascade soft deletion, hard deletion, and active-state validation.
Project CRUD lifecycle helpers
backend/app/crud/project.py
Adds scoped project lookup helpers, active-state filtering, duplicate-name checks, and soft and hard deletion workflows.
User-project activation helpers
backend/app/crud/user_project.py
Adds helpers to deactivate or reactivate users based on accessible projects, returns project user IDs, and changes last-project removal to deactivate users instead of deleting them.
Auth and login access guards
backend/app/api/routes/auth.py, backend/app/api/routes/login.py
Rejects non-superuser login and verification flows when no active projects are available, and updates login token issuance to enforce the same project-access check.
Organization and project route updates
backend/app/api/routes/organization.py, backend/app/api/routes/project.py
Adds list filtering, inactive-record 404s, update conflict checks, activation side effects, and hard-versus-soft delete handling to organization and project routes.
API delete and list docs
backend/app/api/docs/organization/*, backend/app/api/docs/projects/*, backend/app/api/docs/user_project/delete.md
Expands organization, project, and user-project documentation to describe superuser requirements, filters, delete modes, and inactive-account behavior.
Route and CRUD tests for access and delete behavior
backend/app/tests/api/routes/*, backend/app/tests/api/test_auth.py, backend/app/tests/api/test_deps.py, backend/app/tests/crud/*, backend/app/tests/utils/test_data.py
Updates auth, login, project, organization, user, and user-project tests plus shared test data to set up active memberships and verify soft-delete, hard-delete, and inactive-state behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: enhancement, ready-for-review

Suggested reviewers: Prajna1999, Ayush8923, kartpop

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.28% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is clearly related to the PR, highlighting the Gemini batch integration, though it omits the async Celery submission scope.
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.
✨ 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 feat/eval-gemini-integration

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.

@vprashrex vprashrex changed the title feat: Add evaluation batch submission functionality Evaluation: Gemini batch integration (text) May 22, 2026
@vprashrex vprashrex linked an issue May 22, 2026 that may be closed by this pull request
@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

OpenAPI changes   🟢 1 non-breaking change

Tip

Safe to merge from an API-contract perspective.

Full changelog  ·  1
Method Path Change
🟢 POST /api/v1/llm/chain/sts added the new optional request property tts/anyOf[subschema #1: TTSBlockSpec]/params/anyOf[subschema #1: TTSLLMParams]/instructions

main8d9815e7 · generated by oasdiff

@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

@vprashrex vprashrex self-assigned this Jun 17, 2026
@vprashrex vprashrex requested review from AkhileshNegi and Prajna1999 and removed request for AkhileshNegi June 17, 2026 05:32
@vprashrex vprashrex added ready-for-review enhancement New feature or request labels Jun 17, 2026

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

🧹 Nitpick comments (3)
backend/app/crud/evaluations/langfuse.py (1)

158-170: 💤 Low value

Consider using WARNING level for rate-limit logs.

Rate-limit responses (429) are expected transient conditions, not errors in the application. Using logger.error may cause alert fatigue. logger.warning would be more appropriate since the code handles this gracefully by continuing to the next item.

💡 Suggested change
             if getattr(e, "status_code", None) == 429:
-                    logger.error(
+                    logger.warning(
                         f"[create_langfuse_dataset_run] Langfuse rate limit (429) | "
                         f"item_id={item_id}"
                     )
🤖 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 `@backend/app/crud/evaluations/langfuse.py` around lines 158 - 170, In the
exception handler of the create_langfuse_dataset_run function, the rate-limit
(429) response is being logged at the ERROR level, but this is an expected
transient condition that should not trigger alerts. Change the logger.error call
in the if block that checks for status_code == 429 to logger.warning instead,
since the code handles this gracefully by continuing to the next item.
backend/app/tests/api/routes/test_evaluation.py (1)

611-818: 💤 Low value

Consider adding type hints to mock parameters for full guideline compliance.

The coding guideline requires type hints on all function parameters. While the existing test suite doesn't type-hint mock parameters, adding them would align with the guideline. For example:

from unittest.mock import MagicMock

def test_example(self, mock_fetch: MagicMock, mock_map: MagicMock) -> None:

As per coding guidelines: **/*.py: Always add type hints to all function parameters and return values in Python

🤖 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 `@backend/app/tests/api/routes/test_evaluation.py` around lines 611 - 818, All
test methods in the TestBatchEvaluationJSONLBuilding class should comply with
the coding guideline requiring type hints on all function parameters. Add type
hints to the self parameter for all test methods (test_build_batch_jsonl_basic,
test_build_batch_jsonl_with_tools, test_build_batch_jsonl_minimal_config,
test_build_batch_jsonl_skips_empty_questions,
test_build_batch_jsonl_multiple_items,
test_build_batch_jsonl_temperature_included_when_explicitly_set,
test_build_batch_jsonl_temperature_excluded_when_not_set, and
test_build_batch_jsonl_temperature_zero_included_when_explicitly_set). If any of
these test methods use mock parameters (injected via pytest fixtures or as
function arguments), add appropriate type hints such as MagicMock from
unittest.mock to those parameters as well.

Source: Coding guidelines

backend/app/services/evaluations/batch_job.py (1)

21-30: ⚡ Quick win

Add type hints to function parameters and return value.

Per coding guidelines, all function parameters and return values need type hints. The task_instance parameter and **kwargs lack type annotations. Consider using Any for task_instance (matching the Celery task pattern) and a TypedDict or more specific return type.

+from typing import Any
+
 def execute_evaluation_batch_submission(
     project_id: int,
     job_id: str,
     task_id: str,
-    task_instance,
+    task_instance: Any,
     organization_id: int,
     config_id: str,
     config_version: int,
     **kwargs,
-) -> dict:
+) -> dict[str, Any]:
🤖 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 `@backend/app/services/evaluations/batch_job.py` around lines 21 - 30, Add
missing type hints to the execute_evaluation_batch_submission function. Annotate
the task_instance parameter with Any type to match Celery task patterns, add
type hints to the **kwargs parameter (also using Any), and consider defining the
return type more specifically than just dict by using a TypedDict or a more
precise type annotation that reflects what the function actually returns.

Source: Coding guidelines

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

Nitpick comments:
In `@backend/app/crud/evaluations/langfuse.py`:
- Around line 158-170: In the exception handler of the
create_langfuse_dataset_run function, the rate-limit (429) response is being
logged at the ERROR level, but this is an expected transient condition that
should not trigger alerts. Change the logger.error call in the if block that
checks for status_code == 429 to logger.warning instead, since the code handles
this gracefully by continuing to the next item.

In `@backend/app/services/evaluations/batch_job.py`:
- Around line 21-30: Add missing type hints to the
execute_evaluation_batch_submission function. Annotate the task_instance
parameter with Any type to match Celery task patterns, add type hints to the
**kwargs parameter (also using Any), and consider defining the return type more
specifically than just dict by using a TypedDict or a more precise type
annotation that reflects what the function actually returns.

In `@backend/app/tests/api/routes/test_evaluation.py`:
- Around line 611-818: All test methods in the TestBatchEvaluationJSONLBuilding
class should comply with the coding guideline requiring type hints on all
function parameters. Add type hints to the self parameter for all test methods
(test_build_batch_jsonl_basic, test_build_batch_jsonl_with_tools,
test_build_batch_jsonl_minimal_config,
test_build_batch_jsonl_skips_empty_questions,
test_build_batch_jsonl_multiple_items,
test_build_batch_jsonl_temperature_included_when_explicitly_set,
test_build_batch_jsonl_temperature_excluded_when_not_set, and
test_build_batch_jsonl_temperature_zero_included_when_explicitly_set). If any of
these test methods use mock parameters (injected via pytest fixtures or as
function arguments), add appropriate type hints such as MagicMock from
unittest.mock to those parameters as well.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d78ee05c-da68-4a57-8a2c-a6e4fe2ac00b

📥 Commits

Reviewing files that changed from the base of the PR and between fbe5e56 and 5c2435b.

📒 Files selected for processing (13)
  • backend/app/celery/tasks/job_execution.py
  • backend/app/celery/utils.py
  • backend/app/core/batch/gemini.py
  • backend/app/core/batch/operations.py
  • backend/app/crud/evaluations/batch.py
  • backend/app/crud/evaluations/langfuse.py
  • backend/app/crud/evaluations/processing.py
  • backend/app/services/evaluations/batch_job.py
  • backend/app/services/evaluations/evaluation.py
  • backend/app/tests/api/routes/test_evaluation.py
  • backend/app/tests/crud/evaluations/test_langfuse.py
  • backend/app/tests/crud/evaluations/test_processing.py
  • backend/app/tests/services/evaluations/test_evaluation_service_s3.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.

🧹 Nitpick comments (2)
backend/app/celery/tasks/job_execution.py (2)

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

Replace raw queue and priority literals with named constants.

The repeated "default" / 9 / 6 / 2 / 1 values make this routing policy easy to drift from the module docstring and harder to update consistently.

Suggested direction
+DEFAULT_TASK_QUEUE = "default"
+LLM_TASK_PRIORITY = 9
+FAST_EVAL_TASK_PRIORITY = 6
+STANDARD_TASK_PRIORITY = 2
+NOTIFICATION_TASK_PRIORITY = 1
+
-@celery_app.task(bind=True, queue="default", priority=9)
+@celery_app.task(
+    bind=True,
+    queue=DEFAULT_TASK_QUEUE,
+    priority=LLM_TASK_PRIORITY,
+)

As per coding guidelines, **/*.py: Do not use magic values in code; extract repeated literals into constants, Enum members, or settings.

Also applies to: 97-97, 115-115, 133-135, 151-151, 169-169, 189-189, 209-209, 229-229, 249-249, 269-269, 289-289, 309-309, 332-332, 354-355, 384-386

🤖 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 `@backend/app/celery/tasks/job_execution.py` at line 79, The task routing
decorators in job_execution.py use repeated raw queue and priority literals,
which should be centralized so the policy stays consistent with the module
docstring. Define named constants (or Enum/settings values) for the queue name
and each priority level, then update the celery_app.task declarations on the
affected task functions to use those symbols instead of hardcoded values. Keep
the existing routing behavior unchanged while making the constants the single
source of truth for all task decorators in this module.

Source: Coding guidelines


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

Type the bound task signatures fully.

run_guardrails_job still leaves self, **kwargs, and the return type untyped, and send_eval_completion_notification leaves self untyped with a very broad dict return. These wrappers already have concrete contracts, so please annotate them the same way run_evaluation_fast does.

Suggested direction
+from typing import Any
+
-def run_guardrails_job(self, project_id: int, job_id: str, trace_id: str, **kwargs):
+def run_guardrails_job(
+    self: Task,
+    project_id: int,
+    job_id: str,
+    trace_id: str,
+    **kwargs: object,
+) -> dict[str, Any]:
     ...

-def send_eval_completion_notification(self, evaluation_id: int) -> dict:
+def send_eval_completion_notification(
+    self: Task, evaluation_id: int
+) -> dict[str, Any]:
     ...

As per coding guidelines, **/*.py: Use type hints on every function parameter and return value; -> Any is not acceptable unless the type can be narrowed.

Also applies to: 388-388

🤖 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 `@backend/app/celery/tasks/job_execution.py` at line 135, The bound Celery
wrapper signatures are still partially untyped: `run_guardrails_job` needs a
typed `self`, a typed `**kwargs`, and an explicit return annotation, and
`send_eval_completion_notification` should also type `self` and narrow its
`dict` return to the concrete shape it returns. Update these in
`job_execution.py` to match the typing style used by `run_evaluation_fast`,
using the existing task/function names to locate the wrappers and fully
annotating every parameter and return value.

Source: Coding guidelines

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

Nitpick comments:
In `@backend/app/celery/tasks/job_execution.py`:
- Line 79: The task routing decorators in job_execution.py use repeated raw
queue and priority literals, which should be centralized so the policy stays
consistent with the module docstring. Define named constants (or Enum/settings
values) for the queue name and each priority level, then update the
celery_app.task declarations on the affected task functions to use those symbols
instead of hardcoded values. Keep the existing routing behavior unchanged while
making the constants the single source of truth for all task decorators in this
module.
- Line 135: The bound Celery wrapper signatures are still partially untyped:
`run_guardrails_job` needs a typed `self`, a typed `**kwargs`, and an explicit
return annotation, and `send_eval_completion_notification` should also type
`self` and narrow its `dict` return to the concrete shape it returns. Update
these in `job_execution.py` to match the typing style used by
`run_evaluation_fast`, using the existing task/function names to locate the
wrappers and fully annotating every parameter and return value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7a46f97b-e4da-449f-9da1-dc2d10234d68

📥 Commits

Reviewing files that changed from the base of the PR and between 5c2435b and 708edfc.

📒 Files selected for processing (7)
  • backend/app/celery/tasks/job_execution.py
  • backend/app/celery/utils.py
  • backend/app/crud/evaluations/langfuse.py
  • backend/app/crud/evaluations/processing.py
  • backend/app/services/evaluations/evaluation.py
  • backend/app/tests/crud/evaluations/test_langfuse.py
  • backend/app/tests/crud/evaluations/test_processing.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • backend/app/celery/utils.py
  • backend/app/crud/evaluations/langfuse.py
  • backend/app/services/evaluations/evaluation.py
  • backend/app/tests/crud/evaluations/test_processing.py
  • backend/app/crud/evaluations/processing.py

vprashrex and others added 2 commits July 1, 2026 07:55
…alidation for unsupported instructions; Update evaluation process to enforce text completion type
Co-authored-by: Prashant Vasudevan <71649489+vprashrex@users.noreply.github.com>
voice: str = DEFAULT_TTS_VOICE
language: str | None = None
response_format: Literal["mp3", "wav", "ogg"] | None = "wav"
instructions: str | None = Field(default=None, exclude=True)

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.

not sure why instructions are inside TTSLLMParams. May be a typo?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

one bug I found .. when I am trying to create config for text to speech from kaapi console ui .. the frontend used to send the instructions field .. and the backend tend to reject it .. which becomes very difficult to create tts configuration from ui .. so added this line to handle it.. empty instruction from frontend will be handled properly

f"item_id={item_id} | {e}",
exc_info=True,
)
if getattr(e, "status_code", None) == 429:

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.

nitpick: If we can follow the same error handling standards as used inside providers

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

thanks will check

output = json.loads(output)
except (json.JSONDecodeError, ValueError):
try:
output = ast.literal_eval(output)

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.

not sure if these errors need to be handled specifically

# Providers whose configs can run through the async batch evaluation path.
# Mirrors app.services.assessment.service._SUPPORTED_BATCH_PROVIDERS — the
# native variants forward raw params but still submit via the same batch API.
_SUPPORTED_BATCH_PROVIDERS = {

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.

THis is already inside registry.py and constants.py

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

it is specifically for those providers which support batch

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/app/api/routes/auth.py (1)

99-125: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Move the no-project guard before user activation.

Both flows commit user.is_active = True before the new 403. An inactive user with no active projects is rejected but still reactivated, undoing the lifecycle deactivation. Compute available_projects and raise first; only activate after the guard passes.

Suggested ordering
+    available_projects = get_user_accessible_projects(session=session, user_id=user.id)
+
+    if not user.is_superuser and not available_projects:
+        logger.info(
+            f"[google_auth] User has no accessible projects | user_id: {user.id}"
+        )
+        raise HTTPException(
+            status_code=status.HTTP_403_FORBIDDEN,
+            detail="You are not assigned to any active project. Please contact your administrator.",
+        )
+
     # Activate user on first Google login
     if not user.is_active:
         user.is_active = True
         session.add(user)
         session.commit()
         session.refresh(user)
         logger.info(f"[google_auth] User activated on first login | user_id: {user.id}")

Apply the same ordering in verify_magic_link.

Also applies to: 388-409

🤖 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 `@backend/app/api/routes/auth.py` around lines 99 - 125, The no-project guard
is happening after user activation, so an inactive user with zero accessible
projects gets reactivated before the 403 is raised. In the Google login flow
around get_user_accessible_projects and the user.is_active commit, move the
available_projects check and HTTPException before any activation/commit logic,
then only set user.is_active=True after the guard passes. Apply the same
ordering in verify_magic_link so both auth paths behave consistently.
🧹 Nitpick comments (3)
backend/app/api/routes/login.py (1)

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

Extract the repeated no-active-project error detail.

This detail string is repeated across login, Google auth, and magic-link auth, so future copy changes can drift. Put it behind a shared constant and reuse it. As per coding guidelines, do not use magic values in code; extract repeated literals into constants, Enum members, or settings.

🤖 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 `@backend/app/api/routes/login.py` around lines 45 - 51, The no-active-project
HTTPException detail is duplicated across the auth flows, so extract that
literal into a shared constant and reuse it from the login path. Update the
login logic around the user/project check to reference the shared symbol instead
of embedding the string, and apply the same constant in the Google auth and
magic-link auth handlers to keep the message consistent.

Source: Coding guidelines

backend/app/crud/project.py (1)

58-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename the plural fetch helper to list_projects_by_organization.

get_projects_by_organization returns a list, so the helper should use the list_* prefix and callers should be updated with the new name. As per coding guidelines, “use list_* for plural fetch functions, get_* for single-item lookups.”

🤖 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 `@backend/app/crud/project.py` around lines 58 - 64, Rename the plural fetch
helper currently named get_projects_by_organization to
list_projects_by_organization in the project CRUD module. Update any callers to
use the new list_* name, since this function returns a list of Project objects;
keep the same parameters and behavior, and use the new symbol name consistently
wherever it is referenced.

Source: Coding guidelines

backend/app/crud/user_project.py (1)

146-152: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename the plural ID fetch helper to list_user_ids_for_project.

This returns multiple user IDs, so the helper should use the list_* prefix and its route callers should be updated. As per coding guidelines, “use list_* for plural fetch functions, get_* for single-item lookups.”

🤖 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 `@backend/app/crud/user_project.py` around lines 146 - 152, The helper
currently named get_user_ids_for_project returns multiple IDs, so rename it to
list_user_ids_for_project to match the list_* naming convention for plural
fetches. Update every caller in the route layer and elsewhere to use the new
function name, keeping the behavior in the UserProject/session query unchanged.

Source: Coding guidelines

🤖 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 `@backend/app/api/routes/organization.py`:
- Line 57: Add a stable ordering to the organizations query before applying
pagination. In the organizations route where the `statement =
select(Organization).where(*filters).offset(skip).limit(limit)` query is built,
add an `order_by` on a deterministic key such as `Organization.id` so
`skip`/`limit` returns consistent pages across requests.

In `@backend/app/api/routes/project.py`:
- Around line 43-55: The changed route handlers are missing explicit return type
annotations even though they return APIResponse[...] values. Update
read_projects and the other affected route handler in the same module to include
precise return annotations matching the APIResponse type they produce, keeping
all parameters fully typed. Use the existing handler names to locate the
functions and ensure the signatures satisfy the project’s type-hinting
guideline.

In `@backend/app/crud/user_project.py`:
- Around line 117-118: The logging in deactivate_users_without_projects and the
related reactivation helper is exposing raw user ID lists, which should be
avoided. Update the logger.info calls in user_project.py to report counts or
summary totals instead of the full deactivated/reactivated identifier arrays,
and keep the existing helper names ([deactivate_users_without_projects] and the
corresponding reactivation function) so the log context remains clear without
leaking identifiers.

---

Outside diff comments:
In `@backend/app/api/routes/auth.py`:
- Around line 99-125: The no-project guard is happening after user activation,
so an inactive user with zero accessible projects gets reactivated before the
403 is raised. In the Google login flow around get_user_accessible_projects and
the user.is_active commit, move the available_projects check and HTTPException
before any activation/commit logic, then only set user.is_active=True after the
guard passes. Apply the same ordering in verify_magic_link so both auth paths
behave consistently.

---

Nitpick comments:
In `@backend/app/api/routes/login.py`:
- Around line 45-51: The no-active-project HTTPException detail is duplicated
across the auth flows, so extract that literal into a shared constant and reuse
it from the login path. Update the login logic around the user/project check to
reference the shared symbol instead of embedding the string, and apply the same
constant in the Google auth and magic-link auth handlers to keep the message
consistent.

In `@backend/app/crud/project.py`:
- Around line 58-64: Rename the plural fetch helper currently named
get_projects_by_organization to list_projects_by_organization in the project
CRUD module. Update any callers to use the new list_* name, since this function
returns a list of Project objects; keep the same parameters and behavior, and
use the new symbol name consistently wherever it is referenced.

In `@backend/app/crud/user_project.py`:
- Around line 146-152: The helper currently named get_user_ids_for_project
returns multiple IDs, so rename it to list_user_ids_for_project to match the
list_* naming convention for plural fetches. Update every caller in the route
layer and elsewhere to use the new function name, keeping the behavior in the
UserProject/session query unchanged.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6ae00a84-c289-485f-9d4c-fa137271dd20

📥 Commits

Reviewing files that changed from the base of the PR and between 4c6a4cd and 658cfaf.

📒 Files selected for processing (25)
  • backend/app/api/docs/organization/delete.md
  • backend/app/api/docs/organization/list.md
  • backend/app/api/docs/projects/delete.md
  • backend/app/api/docs/projects/list.md
  • backend/app/api/docs/projects/list_by_org.md
  • backend/app/api/docs/user_project/delete.md
  • backend/app/api/routes/auth.py
  • backend/app/api/routes/login.py
  • backend/app/api/routes/organization.py
  • backend/app/api/routes/project.py
  • backend/app/crud/organization.py
  • backend/app/crud/project.py
  • backend/app/crud/user_project.py
  • backend/app/models/__init__.py
  • backend/app/models/message.py
  • backend/app/tests/api/routes/test_login.py
  • backend/app/tests/api/routes/test_org.py
  • backend/app/tests/api/routes/test_project.py
  • backend/app/tests/api/routes/test_users.py
  • backend/app/tests/api/test_auth.py
  • backend/app/tests/api/test_deps.py
  • backend/app/tests/crud/test_org.py
  • backend/app/tests/crud/test_project.py
  • backend/app/tests/crud/test_user_project.py
  • backend/app/tests/utils/test_data.py
✅ Files skipped from review due to trivial changes (4)
  • backend/app/api/docs/user_project/delete.md
  • backend/app/api/docs/organization/list.md
  • backend/app/api/docs/projects/list_by_org.md
  • backend/app/api/docs/projects/list.md

count = session.exec(count_statement).one()

statement = select(Organization).offset(skip).limit(limit)
statement = select(Organization).where(*filters).offset(skip).limit(limit)

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C3 'read_organizations|/organizations|has_more|skip|limit|order_by' backend/app/api/routes/organization.py backend/app/tests

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 50388


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== organization route =="
sed -n '1,90p' backend/app/api/routes/organization.py | nl -ba | sed -n '1,90p'

echo
echo "== list routes using offset/limit with order_by =="
rg -n -C2 'select\(.*\)\.where\(\*?filters?\).*offset\(skip\)\.limit\(limit\)|\.offset\(skip\)\.limit\(limit\)|\.order_by\(' backend/app/api/routes -g '*.py'

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 234


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== backend/app/api/routes/organization.py (lines 37-62) =="
sed -n '37,62p' backend/app/api/routes/organization.py

echo
echo "== pagination tests for organizations =="
sed -n '120,150p' backend/app/tests/api/routes/test_org.py

echo
echo "== nearby list route pattern with stable ordering =="
rg -n -C2 '\.order_by\(' backend/app/api/routes -g '*.py'

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 2631


Add a stable sort before paginating /organizations. skip/limit without order_by can return inconsistent pages across requests; sort by a stable key like Organization.id first.

Suggested fix
-    statement = select(Organization).where(*filters).offset(skip).limit(limit)
+    statement = (
+        select(Organization)
+        .where(*filters)
+        .order_by(Organization.id)
+        .offset(skip)
+        .limit(limit)
+    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
statement = select(Organization).where(*filters).offset(skip).limit(limit)
statement = (
select(Organization)
.where(*filters)
.order_by(Organization.id)
.offset(skip)
.limit(limit)
)
🤖 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 `@backend/app/api/routes/organization.py` at line 57, Add a stable ordering to
the organizations query before applying pagination. In the organizations route
where the `statement =
select(Organization).where(*filters).offset(skip).limit(limit)` query is built,
add an `order_by` on a deterministic key such as `Organization.id` so
`skip`/`limit` returns consistent pages across requests.

Comment on lines 43 to 55
def read_projects(
session: SessionDep,
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=100),
search: str
| None = Query(
None, description="Case-insensitive substring match on the project name"
),
is_active: bool = Query(
True,
description="Filter by active status. Pass false to list soft-deleted projects.",
),
):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add return annotations to the changed route handlers.

Both handlers return APIResponse[...]; annotate them explicitly. As per coding guidelines, “Use type hints on every function parameter and return value; -> Any is not acceptable unless the type can be narrowed.”

Proposed fix
 def read_projects(
     session: SessionDep,
@@
     is_active: bool = Query(
         True,
         description="Filter by active status. Pass false to list soft-deleted projects.",
     ),
-):
+) -> APIResponse[list[ProjectPublic]]:
@@
 def delete_project_endpoint(
     session: SessionDep,
     project_id: int,
     body: DeleteRequest | None = None,
-):
+) -> APIResponse[None]:

Also applies to: 166-170

🤖 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 `@backend/app/api/routes/project.py` around lines 43 - 55, The changed route
handlers are missing explicit return type annotations even though they return
APIResponse[...] values. Update read_projects and the other affected route
handler in the same module to include precise return annotations matching the
APIResponse type they produce, keeping all parameters fully typed. Use the
existing handler names to locate the functions and ensure the signatures satisfy
the project’s type-hinting guideline.

Source: Coding guidelines

Comment on lines +117 to +118
logger.info(
f"[deactivate_users_without_projects] Users deactivated | user_ids: {deactivated}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Avoid logging raw user identifiers.

These helpers can log full user ID lists during bulk deactivation/reactivation. Prefer counts to reduce privacy exposure and log volume.

Proposed fix
         session.flush()
         logger.info(
-            f"[deactivate_users_without_projects] Users deactivated | user_ids: {deactivated}"
+            f"[deactivate_users_without_projects] Users deactivated | deactivated_users: {len(deactivated)}"
         )
@@
         session.flush()
         logger.info(
-            f"[reactivate_users_with_access] Users reactivated | user_ids: {reactivated}"
+            f"[reactivate_users_with_access] Users reactivated | reactivated_users: {len(reactivated)}"
         )

Also applies to: 140-141

🤖 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 `@backend/app/crud/user_project.py` around lines 117 - 118, The logging in
deactivate_users_without_projects and the related reactivation helper is
exposing raw user ID lists, which should be avoided. Update the logger.info
calls in user_project.py to report counts or summary totals instead of the full
deactivated/reactivated identifier arrays, and keep the existing helper names
([deactivate_users_without_projects] and the corresponding reactivation
function) so the log context remains clear without leaking identifiers.

@vprashrex vprashrex merged commit 261f512 into main Jul 1, 2026
3 checks passed
@vprashrex vprashrex deleted the feat/eval-gemini-integration branch July 1, 2026 18:17
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 1.3.0-main.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Evaluation: Add Gemini model support

4 participants