-
Notifications
You must be signed in to change notification settings - Fork 10
feat(cron): Clean up LLM call rows #1201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
64505e1
afa509c
726a851
da21bd8
2343610
49afb1e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -110,3 +110,5 @@ SMTP_PASSWORD= | |
| EMAILS_FROM_EMAIL= | ||
| EMAILS_FROM_NAME=Kaapi | ||
| FRONTEND_HOST= | ||
|
|
||
| DELETE_ROLLING_WINDOW_HOURS= | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
| import os | ||
| import secrets | ||
| import warnings | ||
| from datetime import timedelta | ||
| from typing import Any, Literal, Self | ||
|
|
||
| from pydantic import ( | ||
|
|
@@ -196,10 +197,13 @@ def AWS_S3_BUCKET(self) -> str: | |
| EVAL_FAST_STALL_THRESHOLD_MINUTES: int = 15 | ||
| PENDING_JOB_QUERY_TIMEOUT_MS: int = 1000 | ||
|
|
||
| # AI-assisted prompt improvement settings. | ||
| # See docs/srd-ai-prompt-improvement.md for the full design rationale. | ||
| # Platform-owned Anthropic key shared by every org/project for this feature, | ||
| # so prompt improvement works without per-project credentials. | ||
| DELETE_ROLLING_WINDOW_HOURS: int = 168 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🤖 get_repo_knowledge executed:
Length of output: 6427 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- config.py relevant symbols ---'
rg -n -C 8 'DELETE_ROLLING_WINDOW_HOURS|DELETE_ROLLING_WINDOW_TIMEDELTA|from pydantic|class .*Settings|BaseSettings' backend/app/core/config.py
printf '%s\n' '--- retention service relevant symbols ---'
rg -n -C 12 'DELETE_ROLLING_WINDOW_TIMEDELTA|cutoff|redact|redaction' backend/app/services/llm/retention.py
printf '%s\n' '--- callers and configuration references ---'
rg -n -C 3 'DELETE_ROLLING_WINDOW_HOURS|DELETE_ROLLING_WINDOW_TIMEDELTA' backendRepository: ProjectTech4DevAI/kaapi-backend Length of output: 6348 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- redact_llm_call_batch implementation ---'
rg -n -C 14 'def redact_llm_call_batch|redact_llm_call_batch' backend/app/crud/llm.py
printf '%s\n' '--- redaction fields and timestamp model definitions ---'
rg -n -C 8 'deleted_at|input|content|created_at|redact' backend/app/models/llm backend/app/crud/llm.pyRepository: ProjectTech4DevAI/kaapi-backend Length of output: 50387 Reject negative retention windows. If Proposed fix- DELETE_ROLLING_WINDOW_HOURS: int = 168
+ DELETE_ROLLING_WINDOW_HOURS: int = Field(default=168, ge=0)Import 🤖 Prompt for AI Agents
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No-op There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Please add You are interacting with an AI system. |
||
|
|
||
| @computed_field # type: ignore[prop-decorator] | ||
| @property | ||
| def DELETE_ROLLING_WINDOW_TIMEDELTA(self) -> timedelta: | ||
| return timedelta(hours=self.DELETE_ROLLING_WINDOW_HOURS) | ||
|
|
||
| ANTHROPIC_API_KEY: str = "" | ||
| PROMPT_IMPROVEMENT_MODEL: str = "claude-opus-4-8" | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,9 +1,11 @@ | ||||||||||||||||||||||
| import logging | ||||||||||||||||||||||
| import base64 | ||||||||||||||||||||||
| import json | ||||||||||||||||||||||
| from datetime import datetime | ||||||||||||||||||||||
| from uuid import UUID | ||||||||||||||||||||||
| from typing import Any, Literal | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from sqlalchemy import text | ||||||||||||||||||||||
| from sqlmodel import Session, select | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from app.core.util import now | ||||||||||||||||||||||
|
|
@@ -335,6 +337,38 @@ def get_llm_call_by_id( | |||||||||||||||||||||
| return session.exec(statement).first() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| REDACTED_SENTINEL = "[redacted]" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| REDACT_LLM_CALLS_SQL = text( | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| UPDATE llm_call | ||||||||||||||||||||||
| SET input = :redacted_sentinel, | ||||||||||||||||||||||
| content = CASE | ||||||||||||||||||||||
| WHEN jsonb_typeof(content) = 'object' | ||||||||||||||||||||||
| THEN jsonb_set(content, '{content,value}', to_jsonb(CAST(:redacted_sentinel AS text)), false) | ||||||||||||||||||||||
| ELSE content | ||||||||||||||||||||||
| END | ||||||||||||||||||||||
| WHERE updated_at <= :cutoff | ||||||||||||||||||||||
| AND (input <> :redacted_sentinel OR content -> 'content' ->> 'value' IS DISTINCT FROM :redacted_sentinel) | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Make the eligibility predicate match the guarded redaction path. For JSONB scalar Gate the content condition on an existing redactable nested value. Use Proposed fix- AND (input <> :redacted_sentinel OR content -> 'content' ->> 'value' IS DISTINCT FROM :redacted_sentinel)
+ AND (
+ input IS DISTINCT FROM :redacted_sentinel
+ OR (
+ jsonb_typeof(content) = 'object'
+ AND jsonb_typeof(content -> 'content') = 'object'
+ AND content -> 'content' ? 'value'
+ AND content -> 'content' ->> 'value' IS DISTINCT FROM :redacted_sentinel
+ )
+ )📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def redact_llm_calls(*, session: Session, cutoff: datetime) -> int: | ||||||||||||||||||||||
| result = session.connection().execute( | ||||||||||||||||||||||
| REDACT_LLM_CALLS_SQL, | ||||||||||||||||||||||
| {"cutoff": cutoff, "redacted_sentinel": REDACTED_SENTINEL}, | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| session.commit() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| logger.info( | ||||||||||||||||||||||
| f"[redact_llm_calls] Redacted rows | rows: {result.rowcount} | " | ||||||||||||||||||||||
| f"cutoff: {cutoff.isoformat()}" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return result.rowcount | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def get_llm_calls_by_job_id( | ||||||||||||||||||||||
| session: Session, job_id: UUID, project_id: int | ||||||||||||||||||||||
| ) -> list[LlmCall]: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import logging | ||
|
|
||
| from sqlmodel import Session | ||
|
|
||
| from app.core.config import settings | ||
| from app.core.util import now | ||
| from app.crud.llm import redact_llm_calls | ||
| from app.models.llm.response import LlmCallRedactionResult | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def redact_aged_llm_calls(*, session: Session) -> LlmCallRedactionResult: | ||
| cutoff = now() - settings.DELETE_ROLLING_WINDOW_TIMEDELTA | ||
|
|
||
| logger.info( | ||
| f"[redact_aged_llm_calls] Starting redaction | cutoff: {cutoff.isoformat()}" | ||
| ) | ||
|
|
||
| rows_redacted = redact_llm_calls(session=session, cutoff=cutoff) | ||
|
|
||
| logger.info( | ||
| f"[redact_aged_llm_calls] Completed | rows_redacted: {rows_redacted} | " | ||
| f"cutoff: {cutoff.isoformat()}" | ||
| ) | ||
|
|
||
| return LlmCallRedactionResult(rows_redacted=rows_redacted, cutoff=cutoff) |
There was a problem hiding this comment.
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
🔎 Supported by static analysis
🤖 get_repo_knowledge executed:
get_repo_knowledge ProjectTech4DevAI/kaapi-backend /tmp/coderabbit-repo-knowledge/projecttech4devai-kaapi-backend-9f364c5e/learningsLength of output: 2917
🏁 Script executed:
Repository: ProjectTech4DevAI/kaapi-backend
Length of output: 7019
🏁 Script executed:
Repository: ProjectTech4DevAI/kaapi-backend
Length of output: 293
Confirm the intended schedule
0 9 * * *runs at 09:00 inAsia/Kolkata, which is 03:30 UTC. If the retention job must remain at 03:00 UTC, use30 8 * * *or retain the UTC timezone.🤖 Prompt for AI Agents