Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughThis PR introduces Redis-backed DevCycle feature flagging with local rule evaluation, extends the configuration system to support user-context-aware AB testing and targeting, integrates Sarvam TTS/STT providers, adds Soniox STT with context parsing, separates HTTP sessions for Daily.co proxy compatibility, and implements background task scheduling with Langfuse monitoring. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Webhook as DevCycle Webhook
participant Handler as Webhook Handler<br/>(devcycle.py)
participant CDN as DevCycle CDN
participant Redis as Redis Store
participant Evaluator as Rule Evaluator<br/>(rule_evaluator.py)
participant Config as Config System
Webhook->>Handler: POST /webhook<br/>(feature_key, event_type)
Handler->>Handler: verify_webhook_secret()
Handler->>CDN: Fetch full config
CDN-->>Handler: All flags + targeting rules
Handler->>Redis: Store flags_with_targeting
activate Redis
Redis-->>Handler: Atomic update complete
deactivate Redis
Handler-->>Webhook: 200 OK<br/>(feature_key, duration)
Note over Config,Evaluator: On config request...
Config->>Redis: get_config(key,<br/>user_id, user_email,<br/>custom_data)
Redis-->>Config: flags_with_targeting
Config->>Evaluator: get_variation_for_user()
Evaluator->>Evaluator: get_user_bucket()<br/>(SHA256)
Evaluator->>Evaluator: user_matches_audience()
Evaluator->>Evaluator: get_variation_from<br/>_distribution()
Evaluator-->>Config: variation_value
Config-->>Config: Return user-specific<br/>or global value
rect rgb(220, 240, 255)
Note over Evaluator: Deterministic bucketing (0-99)<br/>Email/user_id/customData targeting<br/>Cached audience matching
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas requiring extra attention:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
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 |
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
.env.example (1)
182-212: Unresolved merge conflicts make the example file unusable.The
.env.examplefile contains multiple unresolved Git merge conflict markers (lines 182-188 and 196-212). Developers copying this file as a template will encounter parsing issues and confusion.Resolve the conflicts by keeping the intended configuration. Based on the PR context, this likely means keeping all the new variables:
-<<<<<<< HEAD DEVCYCLE_SERVER_KEY= # Your DevCycle server key for feature flag management DEVCYCLE_WEBHOOK_SECRET= # Secret token for DevCycle webhook authentication (query param) -======= ->>>>>>> 6906c02 (adding redis support) # Redis Configuration # Simple configuration - only these variables needed REDIS_HOST=localhost REDIS_PORT=6379 # Optional: For Redis cluster setup REDIS_CLUSTER_NODES=redis1:6379,redis2:6380,redis3:6381 -<<<<<<< HEAD LANGFUSE_EVALUATORS="breeze buddy outcome correctness, latency evaluator" # name of the evaluator SLACK_WEBHOOK_URL="" # slack webhook url to send alerts SLACK_TAG_USERS="@john.doe,@jane.smith,@dev.team" # comma-separated list of users to tag in Slack alerts # Background Tasks Configuration ENABLE_BACKGROUND_TASKS=true # Enable/disable the entire background task scheduler (default: true) BACKGROUND_TASKS_LOOP_INTERVAL_SECONDS=60 # How often the scheduler checks for tasks to run (in seconds, default: 60) # Langfuse Score Monitoring Configuration ENABLE_BB_LANGFUSE_MONITORING_LOOP=false # Enable/disable Langfuse score monitoring task only (default: false) SCORE_CHECK_INTERVAL_SECONDS=600 # Check interval for Langfuse scores (10 minutes) -DAILY_SUMMARY_HOUR = "21" # Hour of the day (0-23) to send daily summary alerts (default: 21 for 9 PM) -======= ->>>>>>> 6906c02 (adding redis support) +DAILY_SUMMARY_HOUR="21" # Hour of the day (0-23) to send daily summary alerts (default: 21 for 9 PM)Note: Also fix
DAILY_SUMMARY_HOUR = "21"toDAILY_SUMMARY_HOUR="21"(remove spaces around=) for proper.envsyntax.app/core/config/static.py (1)
480-509: Unresolved merge conflict will cause application startup failure.The file contains Git merge conflict markers that produce Python syntax errors, as confirmed by static analysis (Ruff:
invalid-syntax). The application will fail to import this module.Apply this diff to resolve the conflict:
REDIS_TTL = int(os.getenv("REDIS_TTL", "3600")) # Default TTL in seconds (1 hour) -<<<<<<< HEAD:app/core/config/static.py # DevCycle Configuration DEVCYCLE_WEBHOOK_SECRET = os.getenv("DEVCYCLE_WEBHOOK_SECRET", "") DEVCYCLE_SERVER_KEY = os.getenv("DEVCYCLE_SERVER_KEY", "") # Langfuse Score Monitoring Configuration LANGFUSE_EVALUATORS = [ name.strip() for name in os.environ.get("LANGFUSE_EVALUATORS", "").split(",") if name.strip() ] # Slack Webhook Configuration SLACK_WEBHOOK_URL = os.environ.get("SLACK_WEBHOOK_URL", "") SLACK_TAG_USERS = os.environ.get("SLACK_TAG_USERS", "narsimha.reddy") BACKGROUND_TASKS_LOOP_INTERVAL_SECONDS = int( os.environ.get("BACKGROUND_TASKS_LOOP_INTERVAL_SECONDS", "60") ) # How often the scheduler checks tasks (in seconds) # Langfuse Score Monitoring Configuration ENABLE_BB_LANGFUSE_MONITORING_LOOP = ( os.environ.get("ENABLE_BB_LANGFUSE_MONITORING_LOOP", "false").lower() == "true" ) SCORE_CHECK_INTERVAL_SECONDS = int( os.environ.get("SCORE_CHECK_INTERVAL_SECONDS", "600") ) # 10 minutes -======= ->>>>>>> 6906c02 (adding redis support):app/core/config.pyapp/services/redis/client.py (3)
9-32: Critical: Unresolved git merge conflicts will cause syntax errors.This file contains unresolved merge conflict markers (
<<<<<<< HEAD,=======,>>>>>>>). The code will fail to parse and execute. These conflicts must be resolved before merging.The conflicts appear throughout the file:
- Lines 9-32: Import statements
- Lines 72-148:
_create_clientmethod- Lines 191-218:
closemethod- Lines 246-271:
setmethod signature- Lines 322-344:
incr/expiremethods- Lines 372-377:
close_redis_connectionsfunction
72-109: Resolve cluster initialization logic conflict.The HEAD branch uses
ClusterNodeobjects and checksREDIS_CLUSTER_NODESexplicitly, while the other branch uses simple dict nodes and checks node count. The HEAD approach appears more intentional for cluster mode detection.Recommended resolution - keep HEAD's explicit cluster mode detection:
# Determine if we should use cluster mode # Use cluster if: explicitly set OR multiple nodes configured use_cluster = len(startup_nodes) and REDIS_CLUSTER_NODES if use_cluster: # CLUSTER MODE with ClusterNode objects ... else: # SINGLE NODE MODE ...
246-271: Resolvesetmethod signature conflict.HEAD includes
nx(set-if-not-exists) andex(expiration) parameters which provide important functionality for distributed locking and cache management. The simpler branch loses this capability.Recommended: Keep HEAD's version with
nxandexparameters for full Redis SET functionality.app/ai/voice/agents/automatic/tts/__init__.py (1)
94-140: Critical: Additional merge conflict in Sarvam TTS service construction.This section contains another unresolved merge conflict between two approaches:
- HEAD branch: Uses
build_sarvam_tts(SarvamTTSConfig(...))pattern- Feature branch: Directly constructs
SarvamTTSServicewithInputParamsBased on the relevant code snippets showing
build_sarvam_ttsexists inapp/ai/voice/agents/automatic/tts/sarvam.py, you should resolve the conflict by choosing one consistent approach.
🧹 Nitpick comments (16)
app/core/config/static.py (1)
494-495: Consider using an empty default forSLACK_TAG_USERS.The default value
"narsimha.reddy"appears to be a developer-specific setting that may not be appropriate for all deployments.-SLACK_TAG_USERS = os.environ.get("SLACK_TAG_USERS", "narsimha.reddy") +SLACK_TAG_USERS = os.environ.get("SLACK_TAG_USERS", "")docs/DEVCYCLE_ARCHITECTURE.md (3)
28-44: Add a language specifier to the fenced code block.This ASCII diagram code block should have a language specifier for consistency. Consider using
textorplaintextas the language identifier.-``` +```text DevCycle Dashboard Change
341-363: Add a language specifier to the architecture diagram code block.Same as above, add
textorplaintextas the language identifier for the ASCII diagram.
506-508: Use proper Markdown link syntax for bare URLs.Convert bare URLs to proper Markdown links for better rendering and accessibility.
Apply this diff:
-- DevCycle CDN: `https://config-cdn.devcycle.com/config/v1/server/{key}.json` -- DevCycle Docs: https://docs.devcycle.com -- Redis Sentinel: https://redis.io/topics/sentinel -- Python Multiprocessing: https://docs.python.org/3/library/multiprocessing.html +- DevCycle CDN: `https://config-cdn.devcycle.com/config/v1/server/{key}.json` +- [DevCycle Docs](https://docs.devcycle.com) +- [Redis Sentinel](https://redis.io/topics/sentinel) +- [Python Multiprocessing](https://docs.python.org/3/library/multiprocessing.html)app/agents/voice/automatic/stt/__init__.py (1)
104-110: Same defensive check recommended for translation terms.Similar to
general_items, add validation to avoidKeyErrorifsourceortargetkeys are missing.translation_terms_objects = None if translation_terms_items: translation_terms_objects = [ SonioxContextTranslationTerm( source=item["source"], target=item["target"] ) for item in translation_terms_items + if "source" in item and "target" in item ]app/services/redis/client.py (1)
322-344: Keepincrandexpiremethods from HEAD.The
incrandexpiremethods are useful for rate limiting, counters, and TTL management. These should be retained in the final resolution.DAILY_PROXY_FIX.md (1)
12-15: Add language specifier to the error log code block.The error symptoms code block should specify a language for proper syntax highlighting. Use
textorlogas the identifier.-``` +```text Time out joining https://juspay.daily.co/GAXJSKUVsCPEt7Xh4R5Xapp/services/live_config/store.py (1)
459-467: PotentialNonevalue when key doesn't exist in flags_dict.If
normalized_keywas never added toflags_dictearlier in the flow (e.g., the feature has targeting but no global distribution),flags_dict.get(normalized_key, {}).get("value")will returnNone, which may not be the intended behavior.Consider whether a missing global value should be logged or handled differently.
# Store targeting data for this feature (only if we have variations) if variation_values: + existing_value = flags_dict.get(normalized_key, {}).get("value") + if existing_value is None: + logger.debug(f"Targeting data for {normalized_key} has no global value") flags_dict[normalized_key] = { - "value": flags_dict.get(normalized_key, {}).get( - "value" - ), # Preserve global value + "value": existing_value, # Preserve global value (may be None) "has_targeting": True, "targets": targets, "variation_values": variation_values, }app/examples/hybrid_ab_testing_examples.py (1)
64-68: Remove extraneousfprefix from strings without placeholders.Multiple print statements use f-strings without any placeholders (lines 64, 98, 132, 159, 160, 197, 224, 255, 295, 335, 336, 365). While harmless, they should be regular strings for consistency.
Example fix:
- print(f"Example 1: Voice Agent Integration") + print("Example 1: Voice Agent Integration")app/core/config/user_config.py (3)
14-14: Wildcard import may re-export unintended symbols.The
from app.core.config.static import *imports all symbols, which may pollute the module namespace with unintended exports. If the intention is to re-export static config, consider explicit imports or documenting this behavior.
79-90: Useisfor type comparisons instead of==.Type comparisons should use
israther than==for correctness and clarity. While==works for built-in types,isis the idiomatic approach.if user_value is not None: # Convert to expected type - if return_type == bool: + if return_type is bool: return bool(user_value) - elif return_type == str: + elif return_type is str: return str(user_value) - elif return_type == int: + elif return_type is int: return int(user_value) - elif return_type == float: + elif return_type is float: return float(user_value) else: return user_value
85-88: Type conversion may raise ValueError for invalid inputs.The direct type conversions (
int(user_value),float(user_value)) can raiseValueErrorif the value cannot be converted (e.g.,int("abc")). Consider wrapping in try/except or using theconvert_typeutility fromstore.pywhich handles this.+from app.services.live_config.utils import convert_type + # In get_config_for_user: if user_value is not None: - # Convert to expected type - if return_type is bool: - return bool(user_value) - elif return_type is str: - return str(user_value) - elif return_type is int: - return int(user_value) - elif return_type is float: - return float(user_value) - else: - return user_value + converted = convert_type(user_value, return_type) + if converted is not None: + return converted + # Fall through to global config if conversion failsapp/services/live_config/rule_evaluator.py (3)
24-26: Docstring inaccuracy: This is FIFO eviction, not LRU.The comment says "LRU-like cache" but the implementation uses
next(iter(_bucket_cache))which removes the oldest-inserted key (FIFO), not the least-recently-used. For true LRU behavior, consider usingfunctools.lru_cacheorcollections.OrderedDictwith move-to-end on access.
352-406: Code duplication withget_variation_for_user.
evaluate_for_user(lines 380-406) duplicates the targeting/variation assignment logic fromget_variation_for_user(lines 276-303). Consider refactoring to haveevaluate_for_usercallget_variation_for_userinternally to reduce duplication.async def evaluate_for_user( feature_key: str, user_id: str, user_email: Optional[str] = None, custom_data: Optional[Dict[str, Any]] = None, default_value: Any = None, ) -> Any: - """ - Evaluate feature flag value for a specific user. - Optimized to reuse cached flag data, avoiding duplicate Redis calls. - """ - # Get all flags once (cached) - all_flags = await _get_all_flags_cached() + """Evaluate feature flag value for a specific user.""" + all_flags = await _get_all_flags_from_redis() if not all_flags: return None flag_data = all_flags.get(feature_key) if not flag_data or not isinstance(flag_data, dict): return None - # Early return if no targeting if not flag_data.get("has_targeting"): return None - targets = flag_data.get("targets", []) - if not targets: - return None - - # Inline variation assignment (avoid separate function call) - variation_id = None - - # 1. Check targeting rules - for target in targets: - audience = target.get("audience") - if not audience: - continue - - if user_matches_audience(audience, user_id, user_email, custom_data): - distribution = target.get("distribution", []) - if distribution: - variation_id = get_variation_from_distribution( - distribution, user_id, feature_key - ) - break - - # 2. Fall back to default distribution - if not variation_id: - for target in targets: - if not target.get("audience"): - distribution = target.get("distribution", []) - if distribution: - variation_id = get_variation_from_distribution( - distribution, user_id, feature_key - ) - break + variation_id = await get_variation_for_user( + feature_key, user_id, user_email, custom_data + ) if not variation_id: return None - # Get value for variation (no additional Redis call) variation_values = flag_data.get("variation_values", {}) - value = variation_values.get(variation_id, default_value) - - logger.debug( - f"{feature_key} for user {user_email or user_id[:8]}: " - f"variation={variation_id}, value={value}" - ) - - return value + return variation_values.get(variation_id, default_value)
470-511: Multiple redundant Redis calls for the same data.This debugging function makes at least 3 Redis calls: one in
get_variation_for_user(line 481), one inget_value_for_user(line 485), and another direct call (line 495). Consider fetching flags once and reusing the data.app/core/config/dynamic.py (1)
46-53: Inconsistencies with other config accessors.
These functions lack
user_id/user_email/custom_dataparameters unlike all other accessors. If intentional (truly global settings), consider adding a comment explaining why.Line 48 uses string
"false"as default while other bool functions useFalsedirectly (e.g., line 22, 64, 177). UseFalsefor consistency.async def ENABLE_BACKGROUND_TASKS() -> bool: """Returns ENABLE_BACKGROUND_TASKS from Redis""" - return await get_config("ENABLE_BACKGROUND_TASKS", "false", bool) + return await get_config("ENABLE_BACKGROUND_TASKS", False, bool)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
.env.example(2 hunks)DAILY_PROXY_FIX.md(1 hunks)app/agents/voice/automatic/stt/__init__.py(1 hunks)app/ai/voice/agents/automatic/__init__.py(1 hunks)app/ai/voice/agents/automatic/tts/__init__.py(3 hunks)app/ai/voice/agents/automatic/utils/common.py(1 hunks)app/api/routers/devcycle.py(3 hunks)app/core/config/dynamic.py(2 hunks)app/core/config/static.py(2 hunks)app/core/config/user_config.py(1 hunks)app/core/logger/__init__.py(1 hunks)app/examples/hybrid_ab_testing_examples.py(1 hunks)app/main.py(3 hunks)app/services/live_config/rule_evaluator.py(1 hunks)app/services/live_config/store.py(6 hunks)app/services/redis/client.py(13 hunks)docs/DEVCYCLE_ARCHITECTURE.md(1 hunks)requirements.txt(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-12T10:56:55.990Z
Learnt from: Swetha-160303
Repo: juspay/clairvoyance PR: 424
File: app/ai/voice/agents/automatic/tts/__init__.py:132-152
Timestamp: 2025-12-12T10:56:55.990Z
Learning: In app/ai/voice/agents/automatic/tts/__init__.py, FishAudioTTSService does not support the text_filters parameter, unlike ElevenLabsTTSService and GoogleTTSService which do support it.
Applied to files:
app/ai/voice/agents/automatic/tts/__init__.py
🧬 Code graph analysis (7)
app/ai/voice/agents/automatic/utils/common.py (1)
app/ai/voice/tts/sarvam.py (1)
get_sarvam_language(28-43)
app/ai/voice/agents/automatic/__init__.py (1)
app/core/config/dynamic.py (1)
ENABLE_BREEZE_MCP(56-69)
app/api/routers/devcycle.py (2)
app/services/redis/client.py (1)
get(236-244)app/services/live_config/store.py (1)
fetch_and_update_feature_flags(85-222)
app/ai/voice/agents/automatic/tts/__init__.py (5)
app/ai/voice/agents/automatic/types/models.py (2)
TTSProvider(8-11)VoiceName(14-17)app/ai/voice/tts/elevenlabs.py (2)
ElevenLabsConfig(15-23)build_elevenlabs_tts(26-40)app/ai/voice/tts/google.py (2)
GoogleConfig(15-21)build_google_tts(24-34)app/ai/voice/agents/automatic/utils/common.py (2)
SarvamServiceType(9-13)get_sarvam_language(16-55)app/core/config/dynamic.py (5)
SARVAM_TTS_LANGUAGE_CODE(105-118)SARVAM_TTS_MODEL(137-150)SARVAM_TTS_VOICE_ID(153-166)SARVAM_TTS_PITCH(201-214)SARVAM_TTS_PACE(217-230)
app/agents/voice/automatic/stt/__init__.py (4)
app/ai/voice/agents/automatic/types/models.py (1)
VoiceName(14-17)app/ai/voice/agents/automatic/utils/common.py (2)
SarvamServiceType(9-13)get_sarvam_language(16-55)app/core/config/dynamic.py (5)
SARVAM_STT_HIGH_VAD_SENSITIVITY(185-198)SARVAM_STT_LANGUAGE_CODE(89-102)SARVAM_STT_MODEL(73-86)SARVAM_STT_PROMPT(121-134)SARVAM_STT_VAD_SIGNALS(169-182)app/services/redis/client.py (1)
get(236-244)
app/core/config/user_config.py (3)
app/services/live_config/rule_evaluator.py (2)
get_value_for_user(424-435)is_feature_enabled(438-462)app/services/live_config/store.py (1)
get_config(273-342)app/core/config/dynamic.py (7)
BREEZE_MCP_ENDPOINT_PATH(30-43)DAILY_SUMMARY_HOUR(51-53)ENABLE_BREEZE_MCP(56-69)ENABLE_FAL_SMART_TURN(14-27)SARVAM_STT_MODEL(73-86)SARVAM_TTS_MODEL(137-150)SARVAM_TTS_VOICE_ID(153-166)
app/core/config/dynamic.py (1)
app/services/live_config/store.py (1)
get_config(273-342)
🪛 dotenv-linter (4.0.0)
.env.example
[warning] 182-182: [KeyWithoutValue] The <<<<<<< HEAD key should be with a value or have an equal sign
(KeyWithoutValue)
[warning] 182-182: [LeadingCharacter] Invalid leading character detected
(LeadingCharacter)
[warning] 183-183: [SpaceCharacter] The line has spaces around equal sign
(SpaceCharacter)
[warning] 183-183: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
[warning] 184-184: [SpaceCharacter] The line has spaces around equal sign
(SpaceCharacter)
[warning] 184-184: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
[warning] 186-186: [ExtraBlankLine] Extra blank line detected
(ExtraBlankLine)
[warning] 187-187: [LeadingCharacter] Invalid leading character detected
(LeadingCharacter)
[warning] 188-188: [IncorrectDelimiter] The >>>>>>> 6906c02 (adding redis support) key has incorrect delimiter
(IncorrectDelimiter)
[warning] 188-188: [KeyWithoutValue] The >>>>>>> 6906c02 (adding redis support) key should be with a value or have an equal sign
(KeyWithoutValue)
[warning] 188-188: [LeadingCharacter] Invalid leading character detected
(LeadingCharacter)
[warning] 188-188: [LowercaseKey] The >>>>>>> 6906c02 (adding redis support) key should be in uppercase
(LowercaseKey)
[warning] 196-196: [DuplicatedKey] The <<<<<<< HEAD key is duplicated
(DuplicatedKey)
[warning] 196-196: [KeyWithoutValue] The <<<<<<< HEAD key should be with a value or have an equal sign
(KeyWithoutValue)
[warning] 196-196: [LeadingCharacter] Invalid leading character detected
(LeadingCharacter)
[warning] 196-196: [UnorderedKey] The <<<<<<< HEAD key should go before the REDIS_CLUSTER_NODES key
(UnorderedKey)
[warning] 211-211: [DuplicatedKey] The key is duplicated
(DuplicatedKey)
[warning] 211-211: [LeadingCharacter] Invalid leading character detected
(LeadingCharacter)
[warning] 211-211: [UnorderedKey] The key should go before the LANGFUSE_EVALUATORS key
(UnorderedKey)
[warning] 212-212: [DuplicatedKey] The >>>>>>> 6906c02 (adding redis support) key is duplicated
(DuplicatedKey)
[warning] 212-212: [IncorrectDelimiter] The >>>>>>> 6906c02 (adding redis support) key has incorrect delimiter
(IncorrectDelimiter)
[warning] 212-212: [KeyWithoutValue] The >>>>>>> 6906c02 (adding redis support) key should be with a value or have an equal sign
(KeyWithoutValue)
[warning] 212-212: [LeadingCharacter] Invalid leading character detected
(LeadingCharacter)
[warning] 212-212: [LowercaseKey] The >>>>>>> 6906c02 (adding redis support) key should be in uppercase
(LowercaseKey)
[warning] 212-212: [UnorderedKey] The >>>>>>> 6906c02 (adding redis support) key should go before the LANGFUSE_EVALUATORS key
(UnorderedKey)
🪛 LanguageTool
docs/DEVCYCLE_ARCHITECTURE.md
[grammar] ~204-~204: Ensure spelling is correct
Context: ...p. Why Rejected: - Latency: 50-100ms API call during voice session (unaccept...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
[style] ~317-~317: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ...s Sentinel/Cluster) 3. Config size: Very large configs (>10MB) may impact Redis perfor...
(EN_WEAK_ADJECTIVE)
[style] ~513-~513: Some style guides suggest that commas should set off the year in a month-day-year date.
Context: ...*: 1.0 Last Updated: December 14, 2024 Author: Clairvoyance Engineering ...
(MISSING_COMMA_AFTER_YEAR)
🪛 markdownlint-cli2 (0.18.1)
docs/DEVCYCLE_ARCHITECTURE.md
28-28: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
341-341: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
506-506: Bare URL used
(MD034, no-bare-urls)
507-507: Bare URL used
(MD034, no-bare-urls)
508-508: Bare URL used
(MD034, no-bare-urls)
DAILY_PROXY_FIX.md
12-12: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🪛 Ruff (0.14.8)
app/examples/hybrid_ab_testing_examples.py
64-64: f-string without any placeholders
Remove extraneous f prefix
(F541)
98-98: f-string without any placeholders
Remove extraneous f prefix
(F541)
132-132: f-string without any placeholders
Remove extraneous f prefix
(F541)
159-159: f-string without any placeholders
Remove extraneous f prefix
(F541)
160-160: f-string without any placeholders
Remove extraneous f prefix
(F541)
197-197: f-string without any placeholders
Remove extraneous f prefix
(F541)
224-224: f-string without any placeholders
Remove extraneous f prefix
(F541)
255-255: f-string without any placeholders
Remove extraneous f prefix
(F541)
295-295: f-string without any placeholders
Remove extraneous f prefix
(F541)
335-335: f-string without any placeholders
Remove extraneous f prefix
(F541)
336-336: f-string without any placeholders
Remove extraneous f prefix
(F541)
365-365: f-string without any placeholders
Remove extraneous f prefix
(F541)
413-413: Do not catch blind exception: Exception
(BLE001)
app/services/live_config/store.py
314-314: Do not catch blind exception: Exception
(BLE001)
371-371: Consider moving this statement to an else block
(TRY300)
app/services/live_config/rule_evaluator.py
510-510: Do not use bare except
(E722)
510-511: try-except-pass detected, consider logging the exception
(S110)
app/api/routers/devcycle.py
127-127: Do not catch blind exception: Exception
(BLE001)
app/core/logger/__init__.py
67-67: Expected a statement
(invalid-syntax)
67-67: Expected a statement
(invalid-syntax)
67-67: Expected a statement
(invalid-syntax)
67-67: Expected a statement
(invalid-syntax)
70-70: Expected a statement
(invalid-syntax)
70-70: Expected a statement
(invalid-syntax)
70-70: Expected a statement
(invalid-syntax)
70-70: Expected a statement
(invalid-syntax)
70-71: Expected a statement
(invalid-syntax)
72-72: Expected a statement
(invalid-syntax)
72-72: Expected a statement
(invalid-syntax)
72-72: Expected a statement
(invalid-syntax)
72-72: Expected a statement
(invalid-syntax)
72-72: Expected ,, found name
(invalid-syntax)
72-72: Expected ,, found name
(invalid-syntax)
72-72: Expected ,, found name
(invalid-syntax)
72-72: Expected ,, found name
(invalid-syntax)
72-72: Unparenthesized generator expression cannot be used here
(invalid-syntax)
72-72: Invalid assignment target
(invalid-syntax)
72-72: Expected in, found )
(invalid-syntax)
app/services/redis/client.py
9-9: Expected a statement
(invalid-syntax)
9-9: Expected a statement
(invalid-syntax)
9-9: Expected a statement
(invalid-syntax)
9-9: Expected a statement
(invalid-syntax)
23-23: Expected a statement
(invalid-syntax)
23-23: Expected a statement
(invalid-syntax)
23-23: Expected a statement
(invalid-syntax)
23-23: Expected a statement
(invalid-syntax)
23-24: Expected a statement
(invalid-syntax)
32-32: Expected a statement
(invalid-syntax)
32-32: Expected a statement
(invalid-syntax)
32-32: Expected a statement
(invalid-syntax)
32-32: Expected a statement
(invalid-syntax)
32-32: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
32-32: Expected ,, found name
(invalid-syntax)
32-32: Expected ,, found name
(invalid-syntax)
72-72: Expected except or finally after try block
(invalid-syntax)
72-72: Expected a statement
(invalid-syntax)
72-72: Expected a statement
(invalid-syntax)
72-72: Expected a statement
(invalid-syntax)
104-104: Expected a statement
(invalid-syntax)
104-104: Expected a statement
(invalid-syntax)
104-104: Expected a statement
(invalid-syntax)
104-104: Expected a statement
(invalid-syntax)
104-105: Expected a statement
(invalid-syntax)
106-106: Unexpected indentation
(invalid-syntax)
109-109: Expected a statement
(invalid-syntax)
109-109: Expected a statement
(invalid-syntax)
109-109: Expected a statement
(invalid-syntax)
109-109: Expected a statement
(invalid-syntax)
109-109: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
109-109: Expected ,, found name
(invalid-syntax)
109-109: Expected ,, found name
(invalid-syntax)
119-119: Expected a statement
(invalid-syntax)
119-119: Expected a statement
(invalid-syntax)
119-119: Expected a statement
(invalid-syntax)
119-119: Expected a statement
(invalid-syntax)
130-130: Expected a statement
(invalid-syntax)
130-130: Expected a statement
(invalid-syntax)
130-130: Expected a statement
(invalid-syntax)
130-130: Expected a statement
(invalid-syntax)
130-131: Expected a statement
(invalid-syntax)
131-131: Unexpected indentation
(invalid-syntax)
134-134: unindent does not match any outer indentation level
(invalid-syntax)
134-134: Expected a statement
(invalid-syntax)
134-134: Expected a statement
(invalid-syntax)
134-135: Expected a statement
(invalid-syntax)
135-135: Unexpected indentation
(invalid-syntax)
146-146: unindent does not match any outer indentation level
(invalid-syntax)
146-146: Expected a statement
(invalid-syntax)
146-146: Expected a statement
(invalid-syntax)
146-147: Expected an expression
(invalid-syntax)
147-147: Unexpected indentation
(invalid-syntax)
148-148: Expected a statement
(invalid-syntax)
148-148: Expected a statement
(invalid-syntax)
148-148: Expected a statement
(invalid-syntax)
148-148: Expected a statement
(invalid-syntax)
148-148: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
148-148: Expected ,, found name
(invalid-syntax)
148-148: Expected ,, found name
(invalid-syntax)
191-191: Expected a statement
(invalid-syntax)
191-191: Expected a statement
(invalid-syntax)
191-191: Expected a statement
(invalid-syntax)
191-191: Expected a statement
(invalid-syntax)
207-207: Expected a statement
(invalid-syntax)
207-207: Expected a statement
(invalid-syntax)
207-207: Expected a statement
(invalid-syntax)
207-207: Expected a statement
(invalid-syntax)
207-208: Expected a statement
(invalid-syntax)
208-208: Unexpected indentation
(invalid-syntax)
218-218: Expected a statement
(invalid-syntax)
218-218: Expected a statement
(invalid-syntax)
218-218: Expected a statement
(invalid-syntax)
218-218: Expected a statement
(invalid-syntax)
218-218: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
218-218: Expected ,, found name
(invalid-syntax)
218-218: Expected ,, found name
(invalid-syntax)
246-246: Expected a statement
(invalid-syntax)
246-246: Expected a statement
(invalid-syntax)
246-246: Expected a statement
(invalid-syntax)
246-246: Expected a statement
(invalid-syntax)
265-265: Expected except or finally after try block
(invalid-syntax)
265-265: Expected a statement
(invalid-syntax)
265-265: Expected a statement
(invalid-syntax)
265-265: Expected a statement
(invalid-syntax)
265-266: Expected a statement
(invalid-syntax)
266-266: Unexpected indentation
(invalid-syntax)
271-271: Expected except or finally after try block
(invalid-syntax)
271-271: Expected a statement
(invalid-syntax)
271-271: Expected a statement
(invalid-syntax)
271-271: Expected a statement
(invalid-syntax)
271-271: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
271-271: Expected ,, found name
(invalid-syntax)
271-271: Expected ,, found name
(invalid-syntax)
322-322: Expected a statement
(invalid-syntax)
322-322: Expected a statement
(invalid-syntax)
322-322: Expected a statement
(invalid-syntax)
322-322: Expected a statement
(invalid-syntax)
343-343: Expected a statement
(invalid-syntax)
343-343: Expected a statement
(invalid-syntax)
343-343: Expected a statement
(invalid-syntax)
343-343: Expected a statement
(invalid-syntax)
343-344: Expected a statement
(invalid-syntax)
344-344: Expected a statement
(invalid-syntax)
344-344: Expected a statement
(invalid-syntax)
344-344: Expected a statement
(invalid-syntax)
344-344: Expected a statement
(invalid-syntax)
344-344: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
344-344: Expected ,, found name
(invalid-syntax)
344-344: Expected ,, found name
(invalid-syntax)
372-372: Expected a statement
(invalid-syntax)
372-372: Expected a statement
(invalid-syntax)
372-372: Expected a statement
(invalid-syntax)
372-372: Expected a statement
(invalid-syntax)
373-373: Unexpected indentation
(invalid-syntax)
374-374: Expected a statement
(invalid-syntax)
374-374: Expected a statement
(invalid-syntax)
374-374: Expected a statement
(invalid-syntax)
374-374: Expected a statement
(invalid-syntax)
374-375: Expected a statement
(invalid-syntax)
375-375: Unexpected indentation
(invalid-syntax)
376-376: Expected a statement
(invalid-syntax)
376-376: Expected a statement
(invalid-syntax)
376-376: Expected a statement
(invalid-syntax)
376-376: Expected a statement
(invalid-syntax)
376-376: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
376-376: Expected ,, found name
(invalid-syntax)
376-376: Expected ,, found name
(invalid-syntax)
app/ai/voice/agents/automatic/tts/__init__.py
3-3: Expected a statement
(invalid-syntax)
3-3: Expected a statement
(invalid-syntax)
3-3: Expected a statement
(invalid-syntax)
3-3: Expected a statement
(invalid-syntax)
4-4: Expected a statement
(invalid-syntax)
4-4: Expected a statement
(invalid-syntax)
4-4: Expected a statement
(invalid-syntax)
4-4: Expected a statement
(invalid-syntax)
4-5: Expected a statement
(invalid-syntax)
9-9: Expected a statement
(invalid-syntax)
9-9: Expected a statement
(invalid-syntax)
9-9: Expected a statement
(invalid-syntax)
9-9: Expected a statement
(invalid-syntax)
9-9: Invalid annotated assignment target
(invalid-syntax)
9-9: Expected ,, found name
(invalid-syntax)
9-9: Expected ,, found name
(invalid-syntax)
9-9: Expected ,, found name
(invalid-syntax)
9-9: Expected ,, found name
(invalid-syntax)
9-9: Unparenthesized generator expression cannot be used here
(invalid-syntax)
9-9: Invalid assignment target
(invalid-syntax)
9-9: Expected in, found )
(invalid-syntax)
16-16: Expected a statement
(invalid-syntax)
16-16: Expected a statement
(invalid-syntax)
16-16: Expected a statement
(invalid-syntax)
16-16: Expected a statement
(invalid-syntax)
26-26: Expected ,, found :
(invalid-syntax)
26-26: Expected ,, found /
(invalid-syntax)
26-26: Expected ,, found /
(invalid-syntax)
26-26: Expected ,, found /
(invalid-syntax)
26-26: Expected ,, found /
(invalid-syntax)
26-26: Expected ,, found /
(invalid-syntax)
26-26: Expected ,, found .
(invalid-syntax)
27-27: Expected ,, found ==
(invalid-syntax)
27-27: Expected ,, found ==
(invalid-syntax)
27-27: Expected ,, found ==
(invalid-syntax)
27-27: Expected ,, found =
(invalid-syntax)
27-27: Expected an import name or a ')'
(invalid-syntax)
28-28: Expected ,, found >>
(invalid-syntax)
28-28: Expected ,, found >>
(invalid-syntax)
28-28: Expected ,, found >>
(invalid-syntax)
28-28: Expected ,, found >
(invalid-syntax)
28-28: Expected an import name or a ')'
(invalid-syntax)
30-30: Expected ,, found and
(invalid-syntax)
30-30: Expected an import name or a ')'
(invalid-syntax)
31-31: Expected ,, found .
(invalid-syntax)
31-31: Expected ,, found .
(invalid-syntax)
31-31: Expected ,, found .
(invalid-syntax)
31-31: Expected ,, found .
(invalid-syntax)
32-32: Expected ,, found .
(invalid-syntax)
32-32: Expected ,, found .
(invalid-syntax)
32-32: Expected ,, found .
(invalid-syntax)
32-32: Expected ,, found .
(invalid-syntax)
32-32: Expected ,, found .
(invalid-syntax)
36-37: Expected ), found newline
(invalid-syntax)
37-37: Unexpected indentation
(invalid-syntax)
37-37: Expected an expression
(invalid-syntax)
37-38: Expected in, found newline
(invalid-syntax)
38-38: Expected an indented block after for statement
(invalid-syntax)
38-38: Expected a module name
(invalid-syntax)
38-39: Expected one or more symbol names after import
(invalid-syntax)
40-40: Expected an import name
(invalid-syntax)
40-41: Expected one or more symbol names after import
(invalid-syntax)
94-94: Expected a statement
(invalid-syntax)
94-94: Expected a statement
(invalid-syntax)
94-94: Expected a statement
(invalid-syntax)
94-94: Expected a statement
(invalid-syntax)
95-95: Expected a statement
(invalid-syntax)
95-95: Expected a statement
(invalid-syntax)
95-95: Expected a statement
(invalid-syntax)
95-95: Expected a statement
(invalid-syntax)
95-96: Expected a statement
(invalid-syntax)
97-97: Expected a statement
(invalid-syntax)
97-97: Expected a statement
(invalid-syntax)
97-97: Expected a statement
(invalid-syntax)
97-97: Expected a statement
(invalid-syntax)
97-97: Invalid annotated assignment target
(invalid-syntax)
97-97: Expected ,, found name
(invalid-syntax)
97-97: Expected ,, found name
(invalid-syntax)
97-97: Expected ,, found name
(invalid-syntax)
97-97: Expected ,, found name
(invalid-syntax)
97-97: Unparenthesized generator expression cannot be used here
(invalid-syntax)
97-97: Invalid assignment target
(invalid-syntax)
97-97: Expected in, found )
(invalid-syntax)
98-98: Unexpected indentation
(invalid-syntax)
104-104: Expected a statement
(invalid-syntax)
104-104: Expected a statement
(invalid-syntax)
104-104: Expected a statement
(invalid-syntax)
104-104: Expected a statement
(invalid-syntax)
116-116: Expected a statement
(invalid-syntax)
116-116: Expected a statement
(invalid-syntax)
116-116: Expected a statement
(invalid-syntax)
116-116: Expected a statement
(invalid-syntax)
116-117: Expected a statement
(invalid-syntax)
118-118: Unexpected indentation
(invalid-syntax)
140-140: Expected a statement
(invalid-syntax)
140-140: Expected a statement
(invalid-syntax)
140-140: Expected a statement
(invalid-syntax)
140-140: Expected a statement
(invalid-syntax)
140-140: Invalid annotated assignment target
(invalid-syntax)
140-140: Expected ,, found name
(invalid-syntax)
140-140: Expected ,, found name
(invalid-syntax)
140-140: Expected ,, found name
(invalid-syntax)
140-140: Expected ,, found name
(invalid-syntax)
140-140: Unparenthesized generator expression cannot be used here
(invalid-syntax)
140-140: Invalid assignment target
(invalid-syntax)
140-140: Expected in, found )
(invalid-syntax)
app/main.py
65-65: Expected a statement
(invalid-syntax)
65-65: Expected a statement
(invalid-syntax)
65-65: Expected a statement
(invalid-syntax)
65-65: Expected a statement
(invalid-syntax)
68-68: Expected a statement
(invalid-syntax)
68-68: Expected a statement
(invalid-syntax)
68-68: Expected a statement
(invalid-syntax)
68-68: Expected a statement
(invalid-syntax)
68-69: Expected a statement
(invalid-syntax)
69-69: Expected a statement
(invalid-syntax)
69-69: Expected a statement
(invalid-syntax)
69-69: Expected a statement
(invalid-syntax)
69-69: Expected a statement
(invalid-syntax)
69-69: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
69-69: Expected ,, found name
(invalid-syntax)
69-69: Expected ,, found name
(invalid-syntax)
106-106: Expected except or finally after try block
(invalid-syntax)
106-106: Expected a statement
(invalid-syntax)
106-106: Expected a statement
(invalid-syntax)
106-106: Expected a statement
(invalid-syntax)
107-107: Unexpected indentation
(invalid-syntax)
108-108: Expected a statement
(invalid-syntax)
108-108: Expected a statement
(invalid-syntax)
108-108: Expected a statement
(invalid-syntax)
108-108: Expected a statement
(invalid-syntax)
108-109: Expected a statement
(invalid-syntax)
109-109: Unexpected indentation
(invalid-syntax)
110-110: Expected a statement
(invalid-syntax)
110-110: Expected a statement
(invalid-syntax)
110-110: Expected a statement
(invalid-syntax)
110-110: Expected a statement
(invalid-syntax)
110-110: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
110-110: Expected ,, found name
(invalid-syntax)
110-110: Expected ,, found name
(invalid-syntax)
111-111: Unexpected indentation
(invalid-syntax)
112-112: unindent does not match any outer indentation level
(invalid-syntax)
112-112: Expected a statement
(invalid-syntax)
112-112: Expected a statement
(invalid-syntax)
112-113: Expected a statement
(invalid-syntax)
113-113: Unexpected indentation
(invalid-syntax)
114-114: unindent does not match any outer indentation level
(invalid-syntax)
114-114: Expected a statement
(invalid-syntax)
114-114: Expected a statement
(invalid-syntax)
114-115: Expected an expression
(invalid-syntax)
115-115: Unexpected indentation
(invalid-syntax)
117-117: Expected a statement
(invalid-syntax)
117-117: Expected a statement
(invalid-syntax)
117-117: Expected a statement
(invalid-syntax)
117-117: Expected a statement
(invalid-syntax)
120-120: Unexpected indentation
(invalid-syntax)
124-124: Expected a statement
(invalid-syntax)
124-124: Expected a statement
(invalid-syntax)
124-124: Expected a statement
(invalid-syntax)
124-124: Expected a statement
(invalid-syntax)
124-125: Expected a statement
(invalid-syntax)
125-125: Expected a statement
(invalid-syntax)
125-125: Expected a statement
(invalid-syntax)
125-125: Expected a statement
(invalid-syntax)
125-125: Expected a statement
(invalid-syntax)
125-125: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
125-125: Expected ,, found name
(invalid-syntax)
125-125: Expected ,, found name
(invalid-syntax)
127-127: Unexpected indentation
(invalid-syntax)
app/agents/voice/automatic/stt/__init__.py
127-127: Consider moving this statement to an else block
(TRY300)
129-129: Do not catch blind exception: Exception
(BLE001)
146-148: Avoid specifying long messages outside the exception class
(TRY003)
165-165: Avoid specifying long messages outside the exception class
(TRY003)
210-212: Avoid specifying long messages outside the exception class
(TRY003)
223-225: Avoid specifying long messages outside the exception class
(TRY003)
240-240: Avoid specifying long messages outside the exception class
(TRY003)
273-273: Avoid specifying long messages outside the exception class
(TRY003)
app/core/config/static.py
480-480: Expected a statement
(invalid-syntax)
480-480: Expected a statement
(invalid-syntax)
480-480: Expected a statement
(invalid-syntax)
480-480: Expected a statement
(invalid-syntax)
508-508: Expected a statement
(invalid-syntax)
508-508: Expected a statement
(invalid-syntax)
508-508: Expected a statement
(invalid-syntax)
508-508: Expected a statement
(invalid-syntax)
508-509: Expected a statement
(invalid-syntax)
509-509: Expected a statement
(invalid-syntax)
509-509: Expected a statement
(invalid-syntax)
509-509: Expected a statement
(invalid-syntax)
509-509: Expected a statement
(invalid-syntax)
509-509: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
509-509: Invalid annotated assignment target
(invalid-syntax)
509-509: Expected ,, found name
(invalid-syntax)
509-509: Expected ,, found name
(invalid-syntax)
app/core/config/user_config.py
14-14: from app.core.config.static import * used; unable to detect undefined names
(F403)
81-81: Use is and is not for type comparisons, or isinstance() for isinstance checks
(E721)
83-83: Use is and is not for type comparisons, or isinstance() for isinstance checks
(E721)
85-85: Use is and is not for type comparisons, or isinstance() for isinstance checks
(E721)
87-87: Use is and is not for type comparisons, or isinstance() for isinstance checks
(E721)
92-92: Do not catch blind exception: Exception
(BLE001)
233-250: __all__ is not sorted
Apply an isort-style sorting to __all__
(RUF022)
🔇 Additional comments (20)
app/ai/voice/agents/automatic/__init__.py (1)
279-296: LGTM! User-context-aware feature flag evaluation.The priority-based user identification (email → name → session_id) provides good stickiness for AB testing while ensuring a fallback for anonymous users. The
custom_datapayload correctly includes relevant targeting attributes.app/ai/voice/agents/automatic/utils/common.py (1)
9-13: LGTM! Clean enum definition for service type differentiation.The
SarvamServiceTypeenum cleanly separates TTS and STT concerns, enabling the function to apply appropriate fallback strategies for each service type.app/agents/voice/automatic/stt/__init__.py (1)
309-317: LGTM - Good fallback to Google STT.The default case properly logs the choice and returns a configured Google STT service with sensible defaults.
app/api/routers/devcycle.py (4)
24-42: LGTM - Timing-safe webhook secret verification.Good use of
hmac.compare_digestto prevent timing attacks on the secret comparison. The logging of misconfiguration is also helpful for debugging.
34-36: Consider fail-closed behavior when webhook secret is not configured.Currently, if
DEVCYCLE_WEBHOOK_SECRETis not set, the function returnsFalse, which correctly rejects the request. However, this means the webhook endpoint is effectively disabled until the secret is configured.Verify this is the intended behavior. If webhooks should work without authentication in development environments, consider adding an explicit configuration option like
DEVCYCLE_WEBHOOK_AUTH_ENABLED.
75-94: Good observability improvements.The addition of
start_timefor timing, logging ofchanged_keyandevent_type, and inclusion of[PRESENT]/[MISSING]in auth failure logs significantly improves debugging capability.
127-140: Broad exception catch is acceptable here.While Ruff flags this as BLE001, this is a top-level error handler in an HTTP endpoint where catching all exceptions and returning a structured error response is appropriate. The exception is logged with context, and a proper HTTP response is returned.
app/services/live_config/store.py (4)
115-127: Good optimization with pre-built variation lookup table.The O(1) variation lookup via
variation_lookupdict is a nice performance improvement over nested loops.
190-193: Early return optimization may have unexpected behavior with nested dicts.The comparison
old_flags == new_flags_with_targetingperforms a deep equality check on potentially large nested dictionaries. While this works correctly, be aware that:
- Dict comparison can be expensive for large flag sets
- Order-sensitive comparisons in
targetslists might cause false negativesThis is acceptable for the optimization goal but worth noting.
298-317: User-level evaluation with graceful fallback looks good.The try/except pattern with fallback to global config is appropriate for resilience. The broad
Exceptioncatch (line 314) is acceptable here since we're implementing a fallback pattern and logging the warning.
366-371: Backward compatibility handling for flag structure is correct.The logic correctly handles both the new dict-with-value format and the legacy direct-value format.
app/examples/hybrid_ab_testing_examples.py (1)
390-421: Example runner with comprehensive coverage looks good.The
main()function demonstrates all the hybrid AB testing scenarios clearly. The broad exception handler is appropriate for a demo script to show the error gracefully.app/core/config/user_config.py (2)
107-132: User-level feature flag wrappers are well-designed.The convenience wrappers like
ENABLE_BREEZE_MCP_FOR_USERprovide a clean API that mirrors the global config pattern while adding user context. Good API design.
221-230: Backward compatibility imports are intentionally placed at bottom.The imports at the bottom avoid circular import issues while maintaining backward compatibility. This pattern is acceptable.
app/services/live_config/rule_evaluator.py (3)
76-165: LGTM!The targeting condition matchers handle null checks properly and support the expected operators consistently.
168-222: LGTM!The audience matching with short-circuit evaluation for AND/OR operators is well-implemented and performant.
307-344: LGTM!The distribution logic with cumulative percentages and fallback handling is correct.
app/core/config/dynamic.py (3)
1-11: LGTM!Clear documentation explaining the optional user context parameters for AB testing support.
14-43: LGTM!Consistent pattern for user-context-aware config accessors.
56-246: LGTM!All Sarvam config accessors and
ENABLE_TRACINGfollow the consistent user-context-aware pattern correctly.
| if general_items: | ||
| general_objects = [ | ||
| SonioxContextGeneralItem(key=item["key"], value=item["value"]) | ||
| for item in general_items | ||
| ] |
There was a problem hiding this comment.
Potential KeyError masked by broad exception catch.
If general_items contains dictionaries missing "key" or "value" keys, a KeyError will be raised but caught by the broad except Exception on line 129. Consider validating the structure or catching specific exceptions.
general_objects = None
if general_items:
- general_objects = [
- SonioxContextGeneralItem(key=item["key"], value=item["value"])
- for item in general_items
- ]
+ general_objects = [
+ SonioxContextGeneralItem(key=item["key"], value=item["value"])
+ for item in general_items
+ if "key" in item and "value" in item
+ ]| sarvam_stt_model = await SARVAM_STT_MODEL() | ||
| sarvam_stt_language_code = await SARVAM_STT_LANGUAGE_CODE() | ||
| sarvam_stt_prompt = await SARVAM_STT_PROMPT() | ||
| sarvam_stt_vad_signals = await SARVAM_STT_VAD_SIGNALS() | ||
| sarvam_stt_high_vad_sensitivity = await SARVAM_STT_HIGH_VAD_SENSITIVITY() |
There was a problem hiding this comment.
User context not passed to dynamic config functions.
The SARVAM_STT_MODEL() and related functions accept user_id, user_email, and custom_data parameters for user-level targeting (as shown in the relevant code snippets), but they're called without arguments here. This bypasses any user-specific A/B testing or targeting configured in DevCycle.
Consider accepting user context in get_stt_service and passing it through:
-async def get_stt_service(voice_name: Optional[str] = None):
+async def get_stt_service(
+ voice_name: Optional[str] = None,
+ user_id: Optional[str] = None,
+ user_email: Optional[str] = None,
+ custom_data: Optional[Dict[str, Any]] = None,
+):
...
- sarvam_stt_model = await SARVAM_STT_MODEL()
- sarvam_stt_language_code = await SARVAM_STT_LANGUAGE_CODE()
- sarvam_stt_prompt = await SARVAM_STT_PROMPT()
- sarvam_stt_vad_signals = await SARVAM_STT_VAD_SIGNALS()
- sarvam_stt_high_vad_sensitivity = await SARVAM_STT_HIGH_VAD_SENSITIVITY()
+ sarvam_stt_model = await SARVAM_STT_MODEL(user_id, user_email, custom_data)
+ sarvam_stt_language_code = await SARVAM_STT_LANGUAGE_CODE(user_id, user_email, custom_data)
+ sarvam_stt_prompt = await SARVAM_STT_PROMPT(user_id, user_email, custom_data)
+ sarvam_stt_vad_signals = await SARVAM_STT_VAD_SIGNALS(user_id, user_email, custom_data)
+ sarvam_stt_high_vad_sensitivity = await SARVAM_STT_HIGH_VAD_SENSITIVITY(user_id, user_email, custom_data)Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/agents/voice/automatic/stt/__init__.py around lines 168–172 the dynamic
config functions SARVAM_STT_MODEL, SARVAM_STT_LANGUAGE_CODE, SARVAM_STT_PROMPT,
SARVAM_STT_VAD_SIGNALS, and SARVAM_STT_HIGH_VAD_SENSITIVITY are being invoked
without user context, so user-level targeting/A-B rules are bypassed; update the
get_stt_service signature to accept optional user context (user_id, user_email,
custom_data), thread those parameters through to every call site of
get_stt_service, and pass them into each dynamic config call (e.g.,
SARVAM_STT_MODEL(user_id, user_email, custom_data)) so DevCycle receives the
user context; ensure defaulting when context is absent and update any
callers/tests accordingly.
| if SONIOX_LANGUAGE_HINTS: | ||
| lang_list = [lang.strip() for lang in SONIOX_LANGUAGE_HINTS.split(",")] | ||
| language_hints = [Language(lang) for lang in lang_list if lang] |
There was a problem hiding this comment.
Unhandled ValueError when parsing invalid language hints.
Language(lang) will raise ValueError if lang is not a valid enum value. Since this parses user-provided environment variable content, invalid values could crash the service initialization.
Apply this diff to handle invalid language hints gracefully:
language_hints = None
if SONIOX_LANGUAGE_HINTS:
lang_list = [lang.strip() for lang in SONIOX_LANGUAGE_HINTS.split(",")]
- language_hints = [Language(lang) for lang in lang_list if lang]
+ language_hints = []
+ for lang in lang_list:
+ if lang:
+ try:
+ language_hints.append(Language(lang))
+ except ValueError:
+ logger.warning(f"Invalid Soniox language hint: {lang}, skipping")
+ language_hints = language_hints if language_hints else NoneCommittable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/agents/voice/automatic/stt/__init__.py around lines 277 to 279, parsing
SONIOX_LANGUAGE_HINTS uses Language(lang) which raises ValueError for invalid
enum values; update the code to catch and ignore invalid entries (optionally
logging a warning) so a malformed environment variable doesn't crash
initialization—iterate the split language list, try to construct Language(lang)
inside a try/except ValueError block, append only successful conversions to
language_hints, and skip or log invalid items.
| <<<<<<< HEAD:app/ai/voice/agents/automatic/tts/__init__.py | ||
| ======= | ||
| from pipecat.services.elevenlabs.tts import ElevenLabsTTSService | ||
| from pipecat.services.google.tts import GoogleTTSService | ||
| from pipecat.services.sarvam.tts import SarvamTTSService | ||
|
|
||
| >>>>>>> d5f9259 (Integration of the sarvam ai for stt and tts):app/agents/voice/automatic/tts/__init__.py | ||
| from pipecat.transcriptions.language import Language | ||
|
|
||
| from app.ai.voice.agents.automatic.features.charts.highlight_filter import ( | ||
| HighlightedChartTextFilter, | ||
| ) | ||
|
|
||
| <<<<<<< HEAD:app/ai/voice/agents/automatic/tts/__init__.py | ||
| from app.ai.voice.agents.automatic.types import TTSProvider, VoiceName | ||
| from app.ai.voice.tts import ( | ||
| ElevenLabsConfig, | ||
| GoogleConfig, | ||
| Integration, | ||
| SarvamServiceType, | ||
| SarvamTTSConfig, | ||
| TTSProvider, | ||
| VoiceName, | ||
| :app/agents/voice/automatic/tts/__init__.py, | ||
| =======, | ||
| >>>>>>>, | ||
| ai, | ||
| and, | ||
| app.agents.voice.automatic.types, | ||
| app.agents.voice.automatic.utils.common, | ||
| build_elevenlabs_tts, | ||
| build_google_tts, | ||
| build_sarvam_tts, | ||
| d5f9259, | ||
| for, | ||
| from, | ||
| get_sarvam_language, | ||
| import, | ||
| of, | ||
| sarvam, | ||
| stt, | ||
| the, | ||
| tts, | ||
| ) |
There was a problem hiding this comment.
Critical: Unresolved git merge conflict markers break this file.
This file contains multiple unresolved merge conflict markers (<<<<<<< HEAD, =======, >>>>>>>) that make it completely unparsable. The imports section (lines 3-46) and the Sarvam TTS logic (lines 94-140) are corrupted.
The file will fail to import with syntax errors. Please resolve the merge conflicts before proceeding.
🧰 Tools
🪛 Ruff (0.14.8)
3-3: Expected a statement
(invalid-syntax)
3-3: Expected a statement
(invalid-syntax)
3-3: Expected a statement
(invalid-syntax)
3-3: Expected a statement
(invalid-syntax)
4-4: Expected a statement
(invalid-syntax)
4-4: Expected a statement
(invalid-syntax)
4-4: Expected a statement
(invalid-syntax)
4-4: Expected a statement
(invalid-syntax)
4-5: Expected a statement
(invalid-syntax)
9-9: Expected a statement
(invalid-syntax)
9-9: Expected a statement
(invalid-syntax)
9-9: Expected a statement
(invalid-syntax)
9-9: Expected a statement
(invalid-syntax)
9-9: Invalid annotated assignment target
(invalid-syntax)
9-9: Expected ,, found name
(invalid-syntax)
9-9: Expected ,, found name
(invalid-syntax)
9-9: Expected ,, found name
(invalid-syntax)
9-9: Expected ,, found name
(invalid-syntax)
9-9: Unparenthesized generator expression cannot be used here
(invalid-syntax)
9-9: Invalid assignment target
(invalid-syntax)
9-9: Expected in, found )
(invalid-syntax)
16-16: Expected a statement
(invalid-syntax)
16-16: Expected a statement
(invalid-syntax)
16-16: Expected a statement
(invalid-syntax)
16-16: Expected a statement
(invalid-syntax)
26-26: Expected ,, found :
(invalid-syntax)
26-26: Expected ,, found /
(invalid-syntax)
26-26: Expected ,, found /
(invalid-syntax)
26-26: Expected ,, found /
(invalid-syntax)
26-26: Expected ,, found /
(invalid-syntax)
26-26: Expected ,, found /
(invalid-syntax)
26-26: Expected ,, found .
(invalid-syntax)
27-27: Expected ,, found ==
(invalid-syntax)
27-27: Expected ,, found ==
(invalid-syntax)
27-27: Expected ,, found ==
(invalid-syntax)
27-27: Expected ,, found =
(invalid-syntax)
27-27: Expected an import name or a ')'
(invalid-syntax)
28-28: Expected ,, found >>
(invalid-syntax)
28-28: Expected ,, found >>
(invalid-syntax)
28-28: Expected ,, found >>
(invalid-syntax)
28-28: Expected ,, found >
(invalid-syntax)
28-28: Expected an import name or a ')'
(invalid-syntax)
30-30: Expected ,, found and
(invalid-syntax)
30-30: Expected an import name or a ')'
(invalid-syntax)
31-31: Expected ,, found .
(invalid-syntax)
31-31: Expected ,, found .
(invalid-syntax)
31-31: Expected ,, found .
(invalid-syntax)
31-31: Expected ,, found .
(invalid-syntax)
32-32: Expected ,, found .
(invalid-syntax)
32-32: Expected ,, found .
(invalid-syntax)
32-32: Expected ,, found .
(invalid-syntax)
32-32: Expected ,, found .
(invalid-syntax)
32-32: Expected ,, found .
(invalid-syntax)
36-37: Expected ), found newline
(invalid-syntax)
37-37: Unexpected indentation
(invalid-syntax)
37-37: Expected an expression
(invalid-syntax)
37-38: Expected in, found newline
(invalid-syntax)
38-38: Expected an indented block after for statement
(invalid-syntax)
38-38: Expected a module name
(invalid-syntax)
38-39: Expected one or more symbol names after import
(invalid-syntax)
40-40: Expected an import name
(invalid-syntax)
40-41: Expected one or more symbol names after import
(invalid-syntax)
46-46: Expected a statement
(invalid-syntax)
🤖 Prompt for AI Agents
In app/ai/voice/agents/automatic/tts/__init__.py around lines 3-46 (and also
address corrupted logic around lines 94-140), there are unresolved Git merge
conflict markers (<<<<<<<, =======, >>>>>>>) that make the file unparsable;
remove all conflict markers, choose and keep the correct import lines (merge the
needed TTS service imports: ElevenLabsTTSService, GoogleTTSService,
SarvamTTSService and the correct Language, HighlightedChartTextFilter,
TTSProvider, VoiceName and config/build function imports) so there are no
duplicate or garbled tokens, and restore the Sarvam TTS logic block (lines
~94-140) to a single coherent implementation (remove leftover branch labels and
stray text), then run lint/type checks and unit tests to verify the module
imports and Sarvam integration load without syntax errors.
| def get_sarvam_language( | ||
| language_code: Optional[str], | ||
| service_type: SarvamServiceType, | ||
| ) -> Optional[Language]: | ||
| """ | ||
| Convert SARVAM language code to Language enum with appropriate fallbacks. | ||
|
|
||
| Args: | ||
| language_code: Language code string (e.g., "en-IN", "hi-IN") | ||
| service_type: Service type (TTS or STT) to determine fallback behavior | ||
|
|
||
| Returns: | ||
| - For TTS: Language enum value, fallback to EN_IN if invalid | ||
| - For STT: Language enum value, or None if invalid/not provided | ||
| """ | ||
| if language_code: | ||
| try: | ||
| return Language(language_code) | ||
| except ValueError: | ||
| # Different fallback behavior for TTS vs STT | ||
| if service_type == SarvamServiceType.TTS: | ||
| logger.warning( | ||
| f"Invalid TTS language code: {language_code}, falling back to EN_IN" | ||
| ) | ||
| return Language.EN_IN | ||
| else: | ||
| logger.warning( | ||
| f"Invalid STT language code: {language_code}, returning None" | ||
| ) | ||
| return None | ||
| else: | ||
| # No language code provided | ||
| if service_type == SarvamServiceType.TTS: | ||
| logger.warning( | ||
| "No SARVAM TTS language code provided, falling back to EN_IN" | ||
| ) | ||
| return Language.EN_IN | ||
| else: | ||
| logger.debug("No SARVAM STT language code provided, returning None") | ||
| return None |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "get_sarvam_language" --type=py -B2 -A5Repository: juspay/clairvoyance
Length of output: 7457
🏁 Script executed:
cat -n app/ai/voice/tts/sarvam.py | head -45Repository: juspay/clairvoyance
Length of output: 1504
🏁 Script executed:
cat -n app/ai/voice/stt/sarvam.py | head -65Repository: juspay/clairvoyance
Length of output: 2284
🏁 Script executed:
cat -n app/ai/voice/agents/automatic/utils/common.py | head -60Repository: juspay/clairvoyance
Length of output: 2198
🏁 Script executed:
# Check what imports the old implementations to understand consolidation scope
rg -n "from app\.ai\.voice\.tts.*import.*get_sarvam_language|from app\.ai\.voice\.stt.*import.*get_sarvam_language" --type=pyRepository: juspay/clairvoyance
Length of output: 45
🏁 Script executed:
# Check internal usage in tts module - see line 49 in sarvam.py
grep -n "get_sarvam_language" app/ai/voice/tts/sarvam.pyRepository: juspay/clairvoyance
Length of output: 268
🏁 Script executed:
# Check internal usage in stt module - see line 80 in sarvam.py
grep -n "get_sarvam_language" app/ai/voice/stt/sarvam.pyRepository: juspay/clairvoyance
Length of output: 256
🏁 Script executed:
# Check if old functions are still being imported elsewhere
rg "from app\.ai\.voice\.(tts|stt)\.sarvam import" --type=pyRepository: juspay/clairvoyance
Length of output: 45
Three separate implementations of get_sarvam_language exist—consolidation was not completed.
Beyond the duplication with app/ai/voice/tts/sarvam.py, there is also an identical implementation in app/ai/voice/stt/sarvam.py (line 38). Both old implementations remain actively used internally:
app/ai/voice/tts/sarvam.pyline 49: calls its ownget_sarvam_languageapp/ai/voice/stt/sarvam.pyline 80: calls its ownget_sarvam_language
The new unified version in common.py adds service-type-specific behavior but does not replace the existing implementations, leaving two separate function bodies with duplicated logic. Update build_sarvam_tts and build_sarvam_stt to use the unified get_sarvam_language from common.py and remove the duplicate implementations from their respective modules.
| async def _get_all_flags_cached() -> Dict[str, Any]: | ||
| """ | ||
| Get all flags from Redis with caching to reduce repeated calls. | ||
| Cache is cleared on each new request to ensure fresh data. | ||
| """ | ||
| redis = await get_redis_service() | ||
| client = await redis.get_client() | ||
|
|
||
| raw = await client.get(REDIS_KEY_FLAGS) | ||
| if not raw: | ||
| return {} | ||
|
|
||
| try: | ||
| return json.loads(raw) | ||
| except json.JSONDecodeError: | ||
| logger.error("Invalid JSON in flags data") | ||
| return {} |
There was a problem hiding this comment.
Misleading function name: _get_all_flags_cached performs no caching.
The function name and docstring claim caching, but every call fetches fresh data from Redis. If this is intentional (relying on Redis as the cache), rename to _get_all_flags_from_redis. Otherwise, implement actual in-memory caching with TTL.
-async def _get_all_flags_cached() -> Dict[str, Any]:
+async def _get_all_flags_from_redis() -> Dict[str, Any]:
"""
- Get all flags from Redis with caching to reduce repeated calls.
- Cache is cleared on each new request to ensure fresh data.
+ Get all flags from Redis.
+ Redis serves as the cache layer; no additional in-memory caching.
"""🤖 Prompt for AI Agents
In app/services/live_config/rule_evaluator.py around lines 230-246, the function
`_get_all_flags_cached` and its docstring claim caching but always reads from
Redis; either rename it to `_get_all_flags_from_redis` and update the docstring
to reflect it simply returns flags from Redis, or implement real in-process
caching: add a module-level cache structure (e.g., cached_flags and cached_at)
plus a TTL constant, protect refresh with an asyncio.Lock, on call return
cached_flags if now - cached_at < TTL otherwise fetch from Redis, update
cached_flags and cached_at, and keep JSON decode/error handling; pick one
approach and apply consistently to name, docstring and behavior.
| except: | ||
| pass |
There was a problem hiding this comment.
Avoid bare except: pass — silently swallowing errors hinders debugging.
For a debugging function like get_user_assignment_info, silently ignoring exceptions is counterproductive. At minimum, log the error.
- except:
- pass
+ except Exception as e:
+ logger.warning(f"Failed to check targeting match for {feature_key}: {e}")Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Ruff (0.14.8)
510-510: Do not use bare except
(E722)
510-511: try-except-pass detected, consider logging the exception
(S110)
🤖 Prompt for AI Agents
In app/services/live_config/rule_evaluator.py around lines 510-511 there is a
bare "except: pass" that silently swallows errors; replace it with a specific
Exception handler that logs the error (preferably using logging.exception or the
module/class logger to include stack trace and context like function name and
user id) so failures in get_user_assignment_info are visible; keep swallowing
the exception only if intended, otherwise consider re-raising after logging.
DAILY_PROXY_FIX.md
Outdated
| ## Files Modified | ||
| - `app/main.py`: Separated Daily.co and general HTTP client sessions | ||
|
|
There was a problem hiding this comment.
Incomplete "Files Modified" section.
The documentation describes changes to both app/main.py and app/agents/voice/automatic/__init__.py (as mentioned in sections 1 and 2 of the Solution), but only app/main.py is listed here.
Apply this diff:
## Files Modified
- `app/main.py`: Separated Daily.co and general HTTP client sessions
+- `app/agents/voice/automatic/__init__.py`: Removed proxy configuration for Daily WebRTC transport📝 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.
| ## Files Modified | |
| - `app/main.py`: Separated Daily.co and general HTTP client sessions | |
| ## Files Modified | |
| - `app/main.py`: Separated Daily.co and general HTTP client sessions | |
| - `app/agents/voice/automatic/__init__.py`: Removed proxy configuration for Daily WebRTC transport | |
🤖 Prompt for AI Agents
In DAILY_PROXY_FIX.md around lines 71 to 73, the "Files Modified" list is
incomplete: it only shows `app/main.py` but the change also touched
`app/agents/voice/automatic/__init__.py`; update the section to include both
files (e.g. add a bullet for `app/agents/voice/automatic/__init__.py`) using the
same formatting/backticks as the existing entry and keep ordering consistent
with the Solution sections.
| **Document Version**: 1.0 | ||
| **Last Updated**: December 14, 2024 | ||
| **Author**: Clairvoyance Engineering Team |
There was a problem hiding this comment.
Incorrect year in the "Last Updated" date.
The document states "December 14, 2024" but the current date is December 2025. This appears to be a typo.
Apply this diff:
-**Last Updated**: December 14, 2024
+**Last Updated**: December 14, 2025 📝 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.
| **Document Version**: 1.0 | |
| **Last Updated**: December 14, 2024 | |
| **Author**: Clairvoyance Engineering Team | |
| **Document Version**: 1.0 | |
| **Last Updated**: December 14, 2025 | |
| **Author**: Clairvoyance Engineering Team |
🧰 Tools
🪛 LanguageTool
[style] ~513-~513: Some style guides suggest that commas should set off the year in a month-day-year date.
Context: ...*: 1.0 Last Updated: December 14, 2024 Author: Clairvoyance Engineering ...
(MISSING_COMMA_AFTER_YEAR)
🤖 Prompt for AI Agents
In docs/DEVCYCLE_ARCHITECTURE.md around lines 512 to 514 the "Last Updated" date
shows "December 14, 2024" which is incorrect; update that line to "December 14,
2025" (or the correct current year/date) so the document header reflects the
accurate last updated date, and save the file.
requirements.txt
Outdated
| <<<<<<< HEAD | ||
|
|
||
| # DevCycle Feature Flag SDK | ||
| devcycle-python-server-sdk>=3.0.0 | ||
| ======= | ||
| >>>>>>> 6906c02 (adding redis support) |
There was a problem hiding this comment.
Unresolved merge conflict will break dependency installation.
The file contains Git merge conflict markers that will cause pip install -r requirements.txt to fail. These markers must be resolved before merging.
Apply this diff to resolve the conflict (assuming you want to keep the DevCycle SDK):
-<<<<<<< HEAD
# DevCycle Feature Flag SDK
devcycle-python-server-sdk>=3.0.0
-=======
->>>>>>> 6906c02 (adding redis support)🤖 Prompt for AI Agents
In requirements.txt around lines 52 to 57 there are Git conflict markers
(<<<<<<<, =======, >>>>>>>) that will break pip; remove the conflict markers and
the redundant section, keeping the desired dependency line
"devcycle-python-server-sdk>=3.0.0" (or the chosen version), and ensure the file
has a single clean entry for the DevCycle SDK with no surrounding merge markers
so pip can install dependencies normally.
3680ba6 to
7e95be5
Compare
7e95be5 to
a49bbe1
Compare
Devcycle enhancements
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.