feat(llm): Introduce type safety checks - #1089
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe PR introduces typed Kaapi completion variants, centralizes parameter normalization, moves STS language validation into request models, updates execution paths and tests, and centralizes persisted LLM audio output resolution. ChangesLLM configuration and execution
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
OpenAPI changes 🔴 8 breaking changesCaution Downstream consumers may need an update before merging. Breaking changes ·
|
| Method | Path | Change | |
|---|---|---|---|
| 🔴 | POST |
/api/v1/configs |
added the new required request property config_blob/completion/anyOf[subschema #3: ProxyCompletionConfig -> subschema #5: ProxyCompletionConfig]/params/client_llm_url |
| 🔴 | POST |
/api/v1/configs |
removed #/components/schemas/KaapiCompletionConfig from the config_blob/completion request property anyOf list |
| 🔴 | POST |
/api/v1/llm/call |
added the new required request property config/blob/anyOf[subschema #1: ConfigBlob]/completion/anyOf[subschema #3: ProxyCompletionConfig -> subschema #5: ProxyCompletionConfig]/params/client_llm_url |
| 🔴 | POST |
/api/v1/llm/call |
removed #/components/schemas/KaapiCompletionConfig from the config/blob/anyOf[subschema #1: ConfigBlob]/completion request property anyOf list |
| 🔴 | POST |
/api/v1/llm/chain |
added the new required request property blocks/items/config/blob/anyOf[subschema #1: ConfigBlob]/completion/anyOf[subschema #3: ProxyCompletionConfig -> subschema #5: ProxyCompletionConfig]/params/client_llm_url |
| 🔴 | POST |
/api/v1/llm/chain |
removed #/components/schemas/KaapiCompletionConfig from the blocks/items/config/blob/anyOf[subschema #1: ConfigBlob]/completion request property anyOf list |
| 🔴 | POST |
/api/v1/llm/chain/sts |
request property input_language was restricted to a list of enum values |
| 🔴 | POST |
/api/v1/llm/chain/sts |
request property output_language/anyOf[subschema #1]/ was restricted to a list of enum values |
Full changelog · 62
| Method | Path | Change | |
|---|---|---|---|
| 🔴 | POST |
/api/v1/configs |
added the new required request property config_blob/completion/anyOf[subschema #3: ProxyCompletionConfig -> subschema #5: ProxyCompletionConfig]/params/client_llm_url |
| 🔴 | POST |
/api/v1/configs |
removed #/components/schemas/KaapiCompletionConfig from the config_blob/completion request property anyOf list |
| 🔴 | POST |
/api/v1/llm/call |
added the new required request property config/blob/anyOf[subschema #1: ConfigBlob]/completion/anyOf[subschema #3: ProxyCompletionConfig -> subschema #5: ProxyCompletionConfig]/params/client_llm_url |
| 🔴 | POST |
/api/v1/llm/call |
removed #/components/schemas/KaapiCompletionConfig from the config/blob/anyOf[subschema #1: ConfigBlob]/completion request property anyOf list |
| 🔴 | POST |
/api/v1/llm/chain |
added the new required request property blocks/items/config/blob/anyOf[subschema #1: ConfigBlob]/completion/anyOf[subschema #3: ProxyCompletionConfig -> subschema #5: ProxyCompletionConfig]/params/client_llm_url |
| 🔴 | POST |
/api/v1/llm/chain |
removed #/components/schemas/KaapiCompletionConfig from the blocks/items/config/blob/anyOf[subschema #1: ConfigBlob]/completion request property anyOf list |
| 🔴 | POST |
/api/v1/llm/chain/sts |
request property input_language was restricted to a list of enum values |
| 🔴 | POST |
/api/v1/llm/chain/sts |
request property output_language/anyOf[subschema #1]/ was restricted to a list of enum values |
| 🟢 | — |
— |
removed the schema KaapiCompletionConfig |
| 🟢 | POST |
/api/v1/configs |
added #/components/schemas/KaapiTextCompletionConfig, #/components/schemas/KaapiSTTCompletionConfig, #/components/schemas/KaapiTTSCompletionConfig to the config_blob/completion request property anyOf list |
| 🟢 | POST |
/api/v1/llm/call |
added #/components/schemas/KaapiTextCompletionConfig, #/components/schemas/KaapiSTTCompletionConfig, #/components/schemas/KaapiTTSCompletionConfig to the config/blob/anyOf[subschema #1: ConfigBlob]/completion request property anyOf list |
| 🟢 | POST |
/api/v1/llm/chain |
added #/components/schemas/KaapiTextCompletionConfig, #/components/schemas/KaapiSTTCompletionConfig, #/components/schemas/KaapiTTSCompletionConfig to the blocks/items/config/blob/anyOf[subschema #1: ConfigBlob]/completion request property anyOf list |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new as-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new as-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new auto enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new auto enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new bn-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new bn-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new brx-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new brx-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new doi-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new doi-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new en-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new en-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new gu-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new gu-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new hi-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new hi-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new kn-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new kn-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new kok-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new kok-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new ks-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new ks-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new mai-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new mai-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new ml-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new ml-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new mni-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new mni-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new mr-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new mr-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new ne-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new ne-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new od-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new od-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new pa-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new pa-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new sa-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new sa-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new sat-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new sat-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new sd-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new sd-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new ta-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new ta-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new te-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new te-IN enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new unknown enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new unknown enum value to the request property output_language/anyOf[subschema #1]/ |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new ur-IN enum value to the request property input_language |
| 🟢 | POST |
/api/v1/llm/chain/sts |
added the new ur-IN enum value to the request property output_language/anyOf[subschema #1]/ |
main ↔ 0d46ee82 · generated by oasdiff
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
backend/app/tests/models/llm/test_request.py (1)
10-17: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep
build_kaapi_completion_configcall sites aligned with the typed contract.The builder signature accepts
Providervalues,CompletionType, and typed params models, but these tests pass raw strings and plain dictionaries. Update the affected tests to useProvider.OPENAI/Provider.GOOGLE,CompletionType.TEXT, andTextLLMParams, or narrow the builder contract if raw dicts are intentional.🤖 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/models/llm/test_request.py` around lines 10 - 17, Update build_kaapi_completion_config call sites to satisfy its typed contract: in backend/app/tests/models/llm/test_request.py lines 10-17 and backend/app/tests/services/llm/test_mappers.py lines 895-899, use Provider.OPENAI or Provider.GOOGLE, CompletionType.TEXT, and TextLLMParams instead of raw strings and dictionaries; preserve the existing test scenarios without narrowing the builder contract.Source: Coding guidelines
🧹 Nitpick comments (5)
backend/app/api/routes/llm.py (1)
35-65: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMove output resolution to an LLM service.
This route now owns storage access, URI conversion, and model validation. Extract
_resolve_llm_outputinto the LLM service layer; keep the route limited to request/response orchestration. As per coding guidelines, API routes expose REST endpoints while services contain business logic.🤖 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/llm.py` around lines 35 - 65, Move the _resolve_llm_output function and its storage, URI conversion, presigned-URL error handling, and LLMOutput validation logic into the LLM service layer. Update the route to call the service-level resolver, leaving the endpoint responsible only for request/response orchestration and removing its direct business-logic dependencies.Source: Coding guidelines
backend/app/models/llm/request.py (3)
345-347: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse
X | Yinstead ofUnion[...]insideAnnotated[...].Static analysis flags both discriminated-union type aliases for the deprecated
Union[...]syntax; the codebase already targets Python 3.11+, so the PEP 604|syntax works fine here (including insideAnnotated).♻️ Suggested fix
KaapiCompletionConfig = Annotated[ - Union[ - KaapiTextCompletionConfig, KaapiSTTCompletionConfig, KaapiTTSCompletionConfig - ], + KaapiTextCompletionConfig | KaapiSTTCompletionConfig | KaapiTTSCompletionConfig, Field(discriminator="type"), ] ... CompletionConfig = Annotated[ - Union[ - NativeCompletionConfig, - KaapiTextCompletionConfig, - KaapiSTTCompletionConfig, - KaapiTTSCompletionConfig, - ProxyCompletionConfig, - ], + NativeCompletionConfig + | KaapiTextCompletionConfig + | KaapiSTTCompletionConfig + | KaapiTTSCompletionConfig + | ProxyCompletionConfig, Field(discriminator="provider"), ]Also applies to: 397-403
🤖 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/models/llm/request.py` around lines 345 - 347, Replace the Union[...] syntax in both discriminated-union type aliases around the Kaapi completion configurations with PEP 604 | syntax, preserving the existing Annotated metadata and member types.Source: Linters/SAST tools
308-320: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicate
_default_providervalidator between STT and TTS variants.The identical
model_validator(mode="after")defaultingprovidertoProvider.GOOGLEis copy-pasted betweenKaapiSTTCompletionConfigandKaapiTTSCompletionConfig. SinceKaapiTextCompletionConfigintentionally omits this default, it can't simply move to_KaapiCompletionConfigBase, but it could be extracted into a small mixin/shared helper that only STT/TTS opt into, to avoid future drift between the two copies.♻️ Example extraction
+class _DefaultsToGoogleMixin: + `@model_validator`(mode="after") + def _default_provider(self) -> Self: + if self.provider is None: + self.provider = Provider.GOOGLE + return self + + -class KaapiSTTCompletionConfig(_KaapiCompletionConfigBase): +class KaapiSTTCompletionConfig(_KaapiCompletionConfigBase, _DefaultsToGoogleMixin): type: Literal[CompletionType.STT] = Field( ..., description="Completion config type. Params schema varies by type" ) params: STTLLMParams = Field( ..., description="Kaapi-standardized parameters mapped to provider-specific API" ) - - `@model_validator`(mode="after") - def _default_provider(self) -> Self: - if self.provider is None: - self.provider = Provider.GOOGLE - return selfAlso applies to: 323-335
🤖 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/models/llm/request.py` around lines 308 - 320, Extract the duplicated _default_provider model validator from KaapiSTTCompletionConfig and KaapiTTSCompletionConfig into a shared mixin or helper, then have only those STT and TTS configuration classes opt into it. Preserve KaapiTextCompletionConfig’s behavior of leaving provider unset and keep the existing Provider.GOOGLE default behavior unchanged.
1074-1096: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the repeated
("auto", "unknown")sentinel tuple into a named constant.The same magic tuple appears in both
normalize_language_casingandvalidate_output_language. As per coding guidelines, "Do not use magic values; extract repeated literals into constants, enums, or settings." A shared module-level constant (e.g.STT_ONLY_LANGUAGE_SENTINELS = ("auto", "unknown")) would remove the duplication and keep the two validators in sync if a sentinel is ever added/removed.🤖 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/models/llm/request.py` around lines 1074 - 1096, Define a shared module-level constant for the STT-only language sentinels ("auto", "unknown"), then update normalize_language_casing and validate_output_language to reference it instead of repeating the tuple. Preserve the existing sentinel handling and validation behavior.Source: Coding guidelines
backend/app/api/routes/llm_sts.py (1)
109-122: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueNarrow
providertype hint to match the callee's contract.
_inline_call_config'sprovider: str | Noneis wider thanbuild_kaapi_completion_config'sprovider: KaapiProvider | None. Since this is an internal typed-safety refactor, tightening the hint here would keep the type-checking benefit end-to-end.🤖 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/llm_sts.py` around lines 109 - 122, The _inline_call_config provider parameter is broader than build_kaapi_completion_config accepts. Narrow its type annotation from str | None to KaapiProvider | None, using the existing KaapiProvider symbol and preserving the provider forwarding behavior.
🤖 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/llm.py`:
- Around line 59-61: Update the logger.warning call in _resolve_llm_output so
its prefix uses _resolve_llm_output instead of get_llm_call_status, while
preserving the existing error details and job_id context.
- Line 57: Extract the 3600-second expiration value used in the
storage.get_signed_url call within the surrounding route logic into a clearly
named, unit-suffixed constant, then pass that constant as expires_in. Keep the
existing one-hour TTL behavior unchanged.
- Around line 35-40: Update _resolve_llm_output to use the narrowest concrete
type for raw_content based on the validated LLM response schema, replacing
unconstrained dict, and change its return annotation from LLMOutput | None to
LLMOutput. Preserve the existing validation behavior where valid input returns
LLMOutput and invalid input raises.
In `@backend/app/crud/assessment/batch.py`:
- Around line 417-423: Update the typed-parameter normalization in the batch
completion flow to call model_dump with exclude_unset=True alongside
exclude_none=True, preserving only explicitly provided Kaapi values such as
temperature. Ensure the nearby Temperature suppression checks in the same
normalization logic are based on provided values, not default-populated fields,
while leaving native dict handling unchanged.
In `@backend/app/models/llm/constants.py`:
- Around line 66-68: Update the comments above STSLanguageCode to identify this
Literal alias as the single source of truth for accepted speech-to-speech
language codes, and state that SUPPORTED_LANGUAGE_CODES is derived from it via
get_args. Do not describe SUPPORTED_LANGUAGE_CODES as authoritative.
In `@backend/app/services/llm/jobs.py`:
- Around line 837-847: Update the guardrail-direct-response path before the
branch around config_blob.completion.params.get("model") to support typed Kaapi
text, STT, and TTS configurations. Normalize those configs with
transform_kaapi_config_to_native or convert params through kaapi_params_as_dict
before accessing model, while preserving the existing guarded-response behavior.
---
Outside diff comments:
In `@backend/app/tests/models/llm/test_request.py`:
- Around line 10-17: Update build_kaapi_completion_config call sites to satisfy
its typed contract: in backend/app/tests/models/llm/test_request.py lines 10-17
and backend/app/tests/services/llm/test_mappers.py lines 895-899, use
Provider.OPENAI or Provider.GOOGLE, CompletionType.TEXT, and TextLLMParams
instead of raw strings and dictionaries; preserve the existing test scenarios
without narrowing the builder contract.
---
Nitpick comments:
In `@backend/app/api/routes/llm_sts.py`:
- Around line 109-122: The _inline_call_config provider parameter is broader
than build_kaapi_completion_config accepts. Narrow its type annotation from str
| None to KaapiProvider | None, using the existing KaapiProvider symbol and
preserving the provider forwarding behavior.
In `@backend/app/api/routes/llm.py`:
- Around line 35-65: Move the _resolve_llm_output function and its storage, URI
conversion, presigned-URL error handling, and LLMOutput validation logic into
the LLM service layer. Update the route to call the service-level resolver,
leaving the endpoint responsible only for request/response orchestration and
removing its direct business-logic dependencies.
In `@backend/app/models/llm/request.py`:
- Around line 345-347: Replace the Union[...] syntax in both discriminated-union
type aliases around the Kaapi completion configurations with PEP 604 | syntax,
preserving the existing Annotated metadata and member types.
- Around line 308-320: Extract the duplicated _default_provider model validator
from KaapiSTTCompletionConfig and KaapiTTSCompletionConfig into a shared mixin
or helper, then have only those STT and TTS configuration classes opt into it.
Preserve KaapiTextCompletionConfig’s behavior of leaving provider unset and keep
the existing Provider.GOOGLE default behavior unchanged.
- Around line 1074-1096: Define a shared module-level constant for the STT-only
language sentinels ("auto", "unknown"), then update normalize_language_casing
and validate_output_language to reference it instead of repeating the tuple.
Preserve the existing sentinel handling and validation behavior.
🪄 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 Plus
Run ID: f7a0b7f7-4cae-4c8a-bb6c-1c2830b7950f
📒 Files selected for processing (26)
backend/app/api/routes/llm.pybackend/app/api/routes/llm_sts.pybackend/app/core/langfuse/langfuse.pybackend/app/crud/assessment/batch.pybackend/app/crud/evaluations/core.pybackend/app/crud/model_config.pybackend/app/models/llm/__init__.pybackend/app/models/llm/constants.pybackend/app/models/llm/request.pybackend/app/services/evaluations/batch_job.pybackend/app/services/llm/chain/utils.pybackend/app/services/llm/jobs.pybackend/app/services/llm/mappers.pybackend/app/tests/api/routes/configs/test_version.pybackend/app/tests/api/routes/test_evaluation_fast.pybackend/app/tests/api/routes/test_evaluation_v2.pybackend/app/tests/api/routes/test_improve_prompt.pybackend/app/tests/api/routes/test_llm.pybackend/app/tests/crud/evaluations/test_fast_judge.pybackend/app/tests/crud/test_llm.pybackend/app/tests/models/llm/test_request.pybackend/app/tests/services/llm/test_jobs.pybackend/app/tests/services/llm/test_mappers.pybackend/app/tests/services/llm/test_sts.pybackend/app/tests/utils/llm.pybackend/app/tests/utils/test_data.py
| def _resolve_llm_output( | ||
| raw_content: dict, | ||
| project_id: int, | ||
| session: Session, | ||
| job_id: UUID, | ||
| ) -> LLMOutput | None: |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Narrow the helper contract.
dict is unconstrained, and this function never returns None: validation either returns LLMOutput or raises. As per coding guidelines, every parameter and return value needs a narrow type.
Proposed fix
def _resolve_llm_output(
- raw_content: dict,
+ raw_content: dict[str, object],
project_id: int,
session: Session,
job_id: UUID,
-) -> LLMOutput | None:
+) -> LLMOutput:📝 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.
| def _resolve_llm_output( | |
| raw_content: dict, | |
| project_id: int, | |
| session: Session, | |
| job_id: UUID, | |
| ) -> LLMOutput | None: | |
| def _resolve_llm_output( | |
| raw_content: dict[str, object], | |
| project_id: int, | |
| session: Session, | |
| job_id: UUID, | |
| ) -> LLMOutput: |
🤖 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/llm.py` around lines 35 - 40, Update
_resolve_llm_output to use the narrowest concrete type for raw_content based on
the validated LLM response schema, replacing unconstrained dict, and change its
return annotation from LLMOutput | None to LLMOutput. Preserve the existing
validation behavior where valid input returns LLMOutput and invalid input
raises.
Source: Coding guidelines
| s3_path = inner.get("value", "") | ||
| try: | ||
| storage = get_cloud_storage(session, project_id) | ||
| inner["value"] = storage.get_signed_url(s3_path, expires_in=3600) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Name the presigned-URL TTL.
3600 is an undocumented policy value. Extract it into a clearly unit-suffixed constant. As per coding guidelines, do not use magic values.
Proposed fix
_LLM_OUTPUT_ADAPTER: TypeAdapter[LLMOutput] = TypeAdapter(LLMOutput)
+PRESIGNED_AUDIO_URL_TTL_SECONDS = 3_600
- inner["value"] = storage.get_signed_url(s3_path, expires_in=3600)
+ inner["value"] = storage.get_signed_url(
+ s3_path, expires_in=PRESIGNED_AUDIO_URL_TTL_SECONDS
+ )📝 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.
| inner["value"] = storage.get_signed_url(s3_path, expires_in=3600) | |
| _PRESIGNED_AUDIO_URL_TTL_SECONDS = 3_600 | |
| inner["value"] = storage.get_signed_url( | |
| s3_path, expires_in=PRESIGNED_AUDIO_URL_TTL_SECONDS | |
| ) |
🤖 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/llm.py` at line 57, Extract the 3600-second expiration
value used in the storage.get_signed_url call within the surrounding route logic
into a clearly named, unit-suffixed constant, then pass that constant as
expires_in. Keep the existing one-hour TTL behavior unchanged.
Source: Coding guidelines
| logger.warning( | ||
| f"[get_llm_call_status] Failed to generate presigned URL for audio: {e} | job_id={job_id}" | ||
| ) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use the helper’s name in this log prefix.
This warning originates in _resolve_llm_output, not get_llm_call_status; the current prefix misattributes presigning failures. As per coding guidelines, every log line must be prefixed with its function name.
🤖 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/llm.py` around lines 59 - 61, Update the
logger.warning call in _resolve_llm_output so its prefix uses
_resolve_llm_output instead of get_llm_call_status, while preserving the
existing error details and job_id context.
Source: Coding guidelines
| # Native params are a plain dict; Kaapi params are now a typed submodel. | ||
| raw_params = completion.params | ||
| params = ( | ||
| dict(raw_params) | ||
| if isinstance(raw_params, dict) | ||
| else raw_params.model_dump(exclude_none=True) | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Locate file =="
git ls-files | rg '(^|/)backend/app/crud/assessment/batch\.py$|TextLLMParams|completion\.params|model_dump' || true
echo
echo "== Relevant snippets =="
if [ -f backend/app/crud/assessment/batch.py ]; then
nl -ba backend/app/crud/assessment/batch.py | sed -n '380,450p'
fi
echo
echo "== Search TextLLMParams definition/usages =="
rg -n "class TextLLMParams|TextLLMParams|model_dump\\(|params\\.model_dump" backend/app -S || true
echo
echo "== Pydantic/model validation =="
rg -n "from pydantic import|import pydantic|BaseModel|Field\\(" backend/app -S || trueRepository: ProjectTech4DevAI/kaapi-backend
Length of output: 290
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== File exists and line range =="
head -n 450 backend/app/crud/assessment/batch.py | tail -n 80
echo
echo "== Locate TextLLMParams definition/usages =="
grep -RIn "class TextLLMParams\|TextLLMParams\|model_dump\|completion\.params" backend/app -S || true
echo
echo "== Pydantic deps/framework snippets =="
grep -RIn "from pydantic import\|import pydantic\|BaseModel\|Field(" backend/app pyproject.toml requirements*.txt poetry.lock -S 2>/dev/null || trueRepository: ProjectTech4DevAI/kaapi-backend
Length of output: 3254
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== TextLLMParams definition/usages =="
grep -RIn "class TextLLMParams\|TextLLMParams\|model_dump\|completion\.params" backend/app || true
echo
echo "== Pydantic deps/framework snippets =="
grep -RIn "from pydantic import\|import pydantic\|BaseModel\|Field(" backend/app pyproject.toml requirements*.txt poetry.lock 2>/dev/null || true
echo
echo "== Behavioral probe for Pydantic exclude_none vs exclude_unset =="
python3 - <<'PY'
import sys
print("python", sys.version)
try:
import pydantic
from pydantic import BaseModel, Field
print("pydantic", pydantic.__version__)
except Exception as e:
print("pydantic unavailable:", type(e).__name__, str(e))
sys.exit(0)
class Params(BaseModel):
temperature: float = 0.1
top_p: float | None = None
p = Params()
print("exclude_none:", p.model_dump(exclude_none=True))
print("exclude_unset:", p.model_dump(exclude_unset=True))
print("both:", p.model_dump(exclude_none=True, exclude_unset=True))
PYRepository: ProjectTech4DevAI/kaapi-backend
Length of output: 50387
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== TextLLMParams definition =="
sed -n '1,90p' backend/app/models/llm/request.py
echo
echo "== Assessment model params default =="
sed -n '300,340p' backend/app/models/assessment.py
echo
echo "== Mapper behavior for unset temperature =="
sed -n '1,90p' backend/app/services/llm/mappers.py
sed -n '1,180p' backend/app/tests/services/llm/test_mappers.py
echo
echo "== Pydantic behavioral probe =="
python3 - <<'PY'
import sys
print("python", sys.version)
try:
import pydantic
from pydantic import BaseModel, Field
print("pydantic", pydantic.__version__)
except Exception as e:
print("pydantic unavailable:", type(e).__name__, str(e))
sys.exit(0)
class Params(BaseModel):
temperature: float = 0.1
top_p: float | None = None
p = Params()
print("exclude_none:", p.model_dump(exclude_none=True))
print("exclude_unset:", p.model_dump(exclude_unset=True))
print("both:", p.model_dump(exclude_none=True, exclude_unset=True))
PYRepository: ProjectTech4DevAI/kaapi-backend
Length of output: 13820
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pydantic import BaseModel, Field
class Params(BaseModel):
temperature: float | None = 0.1
top_p: float | None = None
p = Params()
print("exclude_none:", p.model_dump(exclude_none=True))
print("exclude_unset:", p.model_dump(exclude_unset=True))
print("both:", p.model_dump(exclude_none=True, exclude_unset=True))
PYRepository: ProjectTech4DevAI/kaapi-backend
Length of output: 290
🌐 Web query:
Pydantic V2 model_dump exclude_none exclude_unset defaults unset fields
💡 Result:
In Pydantic V2, the model_dump method (and model_dump_json) provides specific parameters to control which fields are included in the serialized output. These parameters are essential for tasks like partial updates (PATCH requests) where you only want to send fields that the user explicitly provided [1][2]. Key Parameters for Field Exclusion: exclude_unset: When set to True, this excludes any fields that were not explicitly set during model instantiation [1][3]. Pydantic tracks which fields were provided using the model_fields_set property [1][4]. If you instantiate a model and only provide one field, only that field will appear in the output when exclude_unset=True [1]. Note that if you modify a field on the instance after it has been created, it is considered set and will be included [1][4]. exclude_none: When set to True, this excludes any fields whose value is None, regardless of whether they were explicitly set or assigned a default [1][3]. This is useful for cleaning up outputs by removing null values [5]. exclude_defaults: When set to True, this excludes any fields whose value is equal to their default value (using the == equality operator) [1][4]. Distinction: exclude_unset focuses on the origin of the value (was it provided at creation?) [1]. exclude_none focuses on the actual value itself (is it None?) [1]. exclude_defaults focuses on whether the current value matches the defined default value [1]. These parameters can be used independently or in combination to precisely tailor the serialization output [1][2]. If a field is explicitly set to None, exclude_unset=True will keep it in the output, while exclude_none=True will remove it [5][6]. If you need to detect if a field was missing versus set to None, you can inspect the model_fields_set attribute directly [6].
Citations:
- 1: https://pydantic.dev/docs/validation/latest/concepts/serialization
- 2: https://schoolofweb.net/en/books/modern-python/pydantic-v2-deep/
- 3: https://pydantic.dev/docs/validation/2.0/api/pydantic/main/
- 4: https://github.com/pydantic/pydantic/blob/main/docs/concepts/serialization.md
- 5: https://stackoverflow.com/questions/73198957/how-to-exclude-optional-unset-values-from-a-pydantic-model-using-fastapi
- 6: https://stackoverflow.com/questions/66229384/pydantic-detect-if-a-field-value-is-missing-or-given-as-null
Preserve unset Kaapi params during batch normalization.
TextLLMParams.temperature defaults to 0.1, so exclude_none=True serializes it as an explicit temperature when the typed config omits it. That normalized temperature=0.1 then overrides provider/model defaults, unlike native dict params. Add exclude_unset=True here, and keep Temperature suppression checks keyed to provided values rather than fields with defaults.
🤖 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/assessment/batch.py` around lines 417 - 423, Update the
typed-parameter normalization in the batch completion flow to call model_dump
with exclude_unset=True alongside exclude_none=True, preserving only explicitly
provided Kaapi values such as temperature. Ensure the nearby Temperature
suppression checks in the same normalization logic are based on provided values,
not default-populated fields, while leaving native dict handling unchanged.
| # BCP-47 language codes accepted by the speech-to-speech endpoint (STT input / | ||
| # TTS output). Single source of truth: `SUPPORTED_LANGUAGE_CODES` in | ||
| # `app/services/llm/chain/utils.py` derives from this via `get_args`. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Make the alias the documented source of truth.
This comment calls SUPPORTED_LANGUAGE_CODES authoritative while also stating that it is derived from STSLanguageCode. Since utils.py derives the set via get_args, describe this Literal as the source of truth to avoid future edits to the wrong declaration.
Suggested wording
-# TTS output). Single source of truth: `SUPPORTED_LANGUAGE_CODES` in
-# `app/services/llm/chain/utils.py` derives from this via `get_args`.
+# TTS output). This Literal is the single source of truth; `SUPPORTED_LANGUAGE_CODES`
+# in `app/services/llm/chain/utils.py` is derived from it via `get_args`.📝 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.
| # BCP-47 language codes accepted by the speech-to-speech endpoint (STT input / | |
| # TTS output). Single source of truth: `SUPPORTED_LANGUAGE_CODES` in | |
| # `app/services/llm/chain/utils.py` derives from this via `get_args`. | |
| # BCP-47 language codes accepted by the speech-to-speech endpoint (STT input / | |
| # TTS output). This Literal is the single source of truth; `SUPPORTED_LANGUAGE_CODES` | |
| # in `app/services/llm/chain/utils.py` is derived from it via `get_args`. |
🤖 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/models/llm/constants.py` around lines 66 - 68, Update the
comments above STSLanguageCode to identify this Literal alias as the single
source of truth for accepted speech-to-speech language codes, and state that
SUPPORTED_LANGUAGE_CODES is derived from it via get_args. Do not describe
SUPPORTED_LANGUAGE_CODES as authoritative.
| if isinstance( | ||
| completion_config, | ||
| ( | ||
| KaapiTextCompletionConfig, | ||
| KaapiSTTCompletionConfig, | ||
| KaapiTTSCompletionConfig, | ||
| ), | ||
| ): | ||
| completion_config, warnings = transform_kaapi_config_to_native( | ||
| session=session, kaapi_config=completion_config | ||
| ) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Normalize typed parameters before the guardrail short-circuit.
The new Kaapi configs store params as typed models, but the earlier guardrail-direct-response branch still calls config_blob.completion.params.get("model") at Line 585 before this conversion. A guardrail hit with a typed config can therefore raise AttributeError instead of returning the guarded response. Normalize parameters before that branch or use kaapi_params_as_dict there.
Based on the supplied typed config definitions, params is a typed model for the Kaapi text/STT/TTS variants.
🤖 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/llm/jobs.py` around lines 837 - 847, Update the
guardrail-direct-response path before the branch around
config_blob.completion.params.get("model") to support typed Kaapi text, STT, and
TTS configurations. Normalize those configs with
transform_kaapi_config_to_native or convert params through kaapi_params_as_dict
before accessing model, while preserving the existing guarded-response behavior.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Issue
Closes #PLEASE_TYPE_ISSUE_NUMBER
Summary
Checklist
Before submitting a pull request, please ensure that you mark these task.
fastapi run --reload app/main.pyordocker compose upin the repository root and test.Notes
Please add here if any other information is required for the reviewer.
Original PR description
Issue
Closes #PLEASE_TYPE_ISSUE_NUMBER
Summary
Explain the motivation for making this change. What existing problem does the pull request solve?
Checklist
Before submitting a pull request, please ensure that you mark these task.
fastapi run --reload app/main.pyordocker compose upin the repository root and test.Notes
Please add here if any other information is required for the reviewer.
Summary by CodeRabbit
New Features
Bug Fixes