Skip to content

Conversation

devxpy
Copy link
Member

@devxpy devxpy commented Sep 16, 2025

… processing capabilities### Q/A checklist

  • I have tested my UI changes on mobile and they look acceptable
  • I have tested changes to the workflows in both the API and the UI
  • I have done a code review of my changes and looked at each line of the diff + the references of each function I have changed
  • My changes have not increased the import time of the server
How to check import time?

time python -c 'import server'

You can visualize this using tuna:

python3 -X importtime -c 'import server' 2> out.log && tuna out.log

To measure import time for a specific library:

$ time python -c 'import pandas'

________________________________________________________
Executed in    1.15 secs    fish           external
   usr time    2.22 secs   86.00 micros    2.22 secs
   sys time    0.72 secs  613.00 micros    0.72 secs

To reduce import times, import libraries that take a long time inside the functions that use them instead of at the top of the file:

def my_function():
    import pandas as pd
    ...

Legal Boilerplate

Look, I get it. The entity doing business as “Gooey.AI” and/or “Dara.network” was incorporated in the State of Delaware in 2020 as Dara Network Inc. and is gonna need some rights from me in order to utilize my contributions in this PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Dara Network Inc can use, modify, copy, and redistribute my contributions, under its choice of terms.
… processing capabilities

Copy link

coderabbitai bot commented Sep 16, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds a LiveKit voice agent (new livekit_agent.py) with end-to-end session orchestration, STT/TTs/LLM integration and Gooey wrappers; wires LiveKit runtime into Dockerfile and run-prod.sh and CI; introduces LiveKit dependencies in pyproject.toml and LiveKit/Twilio settings and SIP environment variables; refactors VideoBots into a step-based generator API (run_v2) with many discrete step methods; adds VectorSearchLLMTool and exposes it in the prompt library; adds Twilio SIP-trunk provisioning script, consolidates API run-creation, updates Twilio request_overrides, and includes smaller test gating and import/format fixes.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description contains the repository template (Q/A checklist, import-time guidance, and legal boilerplate) but lacks a clear, human-readable summary of the changes and their scope — it appears truncated to "… processing capabilities" and does not document key items such as high-level change summary, testing performed, or any breaking/configuration changes. Please add a concise high-level summary of what this PR changes (highlight new files like livekit_agent.py, dependency and Docker/CI updates, and public API additions), fill in the Q/A checklist and testing notes (including import-time measurements), and document required runtime configuration or migration steps (e.g., LIVEKIT_* env vars, RUN_LIVEKIT behavior); after updating the description re-run the review checks.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly and concisely describes the primary change (adding a LiveKit voice agent with Twilio integration and audio processing) and is focused on the main feature, so a reviewer scanning history will understand the primary intent; the trailing ellipsis appears to truncate the final words but does not obscure the main purpose.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch livekit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

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

⚠️ Outside diff range comments (2)
daras_ai_v2/text_to_speech_settings_widgets.py (1)

317-318: Fix label usage to avoid rendering the NamedTuple repr.

Use the new label accessor (or value.value if you keep current structure).

-    gui.write(f"##### 🗣️ {TextToSpeechProviders.GOOGLE_TTS.value} Settings")
+    gui.write(f"##### 🗣️ {TextToSpeechProviders.GOOGLE_TTS.label} Settings")
pyproject.toml (1)

126-146: Add missing [build-system] to pyproject.toml — project is non-installable without it.

PEP 517 requires a build backend; pip/uv cannot build/install the package while pyproject.toml lacks [build-system].
File: pyproject.toml — add near the top (before tool.* sections).

+[build-system]
+requires = ["setuptools>=68", "wheel"]
+build-backend = "setuptools.build_meta"
♻️ Duplicate comments (2)
scripts/create_twilio_sip_trunk.py (2)

28-28: Avoid logging sensitive phone number information.

The phone number is directly logged, which could expose PII in logs. Consider masking or redacting sensitive parts.

Apply this diff to mask the phone number:

-    print(f"✅ {incoming_phone_number=}")
+    masked_number = incoming_phone_number.phone_number[:6] + "****" + incoming_phone_number.phone_number[-2:]
+    print(f"✅ incoming_phone_number={masked_number}")

58-58: Avoid logging sensitive phone number information.

Similar to line 28, the attached phone number is logged without masking.

Apply this diff to mask the phone number:

-    print(f"✅ {attached_phone_number=}")
+    print(f"✅ attached_phone_number=<redacted>")
🧹 Nitpick comments (16)
daras_ai_v2/asr.py (1)

53-54: Comment mismatch (nit).

5 * 1024 * 1024 is 5 MB, not 1 MB. Update the comment for clarity.

- SHORT_FILE_CUTOFF = 5 * 1024 * 1024  # 1 MB
+ SHORT_FILE_CUTOFF = 5 * 1024 * 1024  # 5 MB
scripts/run-tests.sh (1)

9-12: Run pytest via uv to ensure the right venv.

Keeps tooling consistent with the ruff step.

-uv run ruff format --diff .
+uv run ruff format --diff .
@@
-pytest $@
+uv run pytest $@

If CI already invokes this script through uv run, feel free to skip the change; otherwise this avoids PATH issues.

daras_ai_v2/settings.py (1)

521-529: New LiveKit/SIP secrets: avoid incidental exposure.

These settings become part of settings=globals() (made available to templates earlier) and are also exported to os.environ. Confirm they are never rendered/logged, or move to a redacted settings proxy for templates.

Proposed minimal mitigation (move secrets out of template globals in a follow-up), or add a dedicated safe dict for templates.

Dockerfile (3)

49-51: ARG default/usage is brittle; simplify the toggle.

ARG RUN_LIVEKIT=${RUN_LIVEKIT} self-references. Prefer a plain ARG with an explicit default to avoid surprises in multi-stage builds.

-ARG RUN_LIVEKIT=${RUN_LIVEKIT}
-ENV RUN_LIVEKIT=${RUN_LIVEKIT}
+ARG RUN_LIVEKIT=false
+ENV RUN_LIVEKIT=${RUN_LIVEKIT}

61-61: Playwright install: consider gating to reduce image size.

Installing Playwright deps/chromium unconditionally is heavy. Gate behind a build arg (e.g., ARG INSTALL_PLAYWRIGHT=false) or dev-only stage.

- RUN uv run playwright install-deps && uv run playwright install chromium
+ ARG INSTALL_PLAYWRIGHT=false
+ RUN if [ "$INSTALL_PLAYWRIGHT" = "true" ]; then uv run playwright install-deps && uv run playwright install chromium; fi

79-82: Healthcheck fallback may fail on images without Celery.

If the image is built with only LiveKit extras, celery might be missing; consider making the Celery check conditional.

-    || bash -c 'uv run celery -A celeryapp inspect ping -d celery@$HOSTNAME' \
+    || bash -c '[ -x "$(command -v celery)" ] && uv run celery -A celeryapp inspect ping -d celery@$HOSTNAME' \
daras_ai_v2/text_to_speech_settings_widgets.py (1)

28-35: Optional: rename NamedTuple field to label for consistency with other enums.

Matches TranslationModel, OpenAI_Voice, etc., and removes .value.value ambiguity.

-class TTSProvider(typing.NamedTuple):
-    value: str
-    sample_rate: int
+class TTSProvider(typing.NamedTuple):
+    label: str
+    sample_rate: int
@@
-    GOOGLE_TTS = TTSProvider(value="Google Text-to-Speech", sample_rate=24000)
+    GOOGLE_TTS = TTSProvider(label="Google Text-to-Speech", sample_rate=24000)

Note: If you apply this, also update .label accessor accordingly.

recipes/VideoBots.py (2)

579-579: Use next(iter()) instead of indexing for dict values.

Accessing the first value with [0] is less idiomatic and could raise IndexError if the dict is empty.

Apply this diff:

-                    keyword_query = list(keyword_query.values())[0]
+                    keyword_query = next(iter(keyword_query.values()))

1497-1498: Avoid silently swallowing exceptions.

The broad exception catch without logging could hide critical errors during tool loading.

Apply this diff to at least log the exception:

             try:
                 tools += list(
                     get_workflow_tools_from_state(
                         gui.session_state, FunctionTrigger.prompt
                     ),
                 )
-            except Exception:
-                pass
+            except Exception as e:
+                # Log but continue - tools are optional
+                import logging
+                logging.warning(f"Failed to load workflow tools: {e}")
livekit_agent.py (4)

74-74: Use next(iter()) for safer access to first participant.

Accessing via list indexing could raise IndexError if the participants collection changes.

Apply this diff:

-    participant = list(ctx.room.remote_participants.values())[0]
+    participant = next(iter(ctx.room.remote_participants.values()))

181-181: Consider extracting the error message.

The error message string is quite long - consider defining it as a constant.

+        UNSUPPORTED_LLM_ERROR = "Unsupported LLM API: {}"
         case _:
-            raise UserError(f"Unsupported LLM API: {llm_model.llm_api}")
+            raise UserError(UNSUPPORTED_LLM_ERROR.format(llm_model.llm_api))

202-202: Clarify operator precedence with parentheses.

The combination of and and or operators should be parenthesized for clarity.

Apply this diff:

-        saved_msgs and saved_msgs[-1] or None,
+        (saved_msgs and saved_msgs[-1]) or None,

324-326: Move non-exception return to else block.

The return statement after the except block should be in an else block for clarity.

Apply this diff:

         try:
             # print("call_livekit", tool.name, raw_arguments, context)
             ret = await sync_to_async(tool.call)(**raw_arguments)
             # print("call_livekit", tool.name, raw_arguments, context, ret)
-            return ret
         except TypeError as e:
             return dict(error=repr(e))
+        else:
+            return ret
README.md (1)

102-107: Linux setup should mirror UV workflow and extras choice.

uv pip install -e .[dev] skips optional extras and diverges from macOS instructions.

Use one of:

- Install dependencies using `uv pip install -e .[dev]`
+ Install dependencies using UV (pick one set of extras):
+ # Dev + MediaPipe
+ uv sync --extra mediapipe
+ # Dev + LiveKit
+ uv sync --extra livekit

Or, if you prefer pip-style:

- uv pip install -e .[dev]
+ uv pip install -e ".[dev,mediapipe]"
+ # or
+ uv pip install -e ".[dev,livekit]"
pyproject.toml (1)

5-92: Move test/formatter/linter deps to dev group.

pytest*, ruff, and black in production deps increase image size and import time.

Apply the following moves (excerpt illustrates intent):

@@ dependencies = [
-    "pytest == 8.*",
@@
-    "pytest-xdist == 3.*",
@@
-    "pytest-django == 4.*",
@@
-    "pytest-playwright == 0.*",
@@
-    "ruff == 0.*",
-    "black == 24.*",
@@
 ]
@@
 [dependency-groups]
 dev = [
     "watchdog == 2.*",
     "ipython == 8.*",
     "honcho == 1.*",
     "pre-commit == 3.*",
+    "pytest == 8.*",
+    "pytest-xdist == 3.*",
+    "pytest-django == 4.*",
+    "pytest-playwright == 0.*",
+    "ruff == 0.*",
+    "black == 24.*",
 ]
.github/workflows/python-tests.yml (1)

61-68: CI installs deps but not the project; consider installing the project to catch packaging issues.

With --no-install-project, tests may pass without validating packaging (e.g., missing [build-system]). Installing the project is cheap with UV caching.

-      - name: Install the project
-        run: uv sync --locked --no-install-project --no-dev --extra mediapipe
+      - name: Install the project
+        run: uv sync --locked --extra mediapipe
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b6c847 and f9c35df.

⛔ Files ignored due to path filters (2)
  • poetry.lock is excluded by !**/*.lock
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • .github/workflows/python-tests.yml (2 hunks)
  • Dockerfile (3 hunks)
  • Procfile (1 hunks)
  • README.md (5 hunks)
  • daras_ai_v2/asr.py (2 hunks)
  • daras_ai_v2/settings.py (1 hunks)
  • daras_ai_v2/text_to_speech_settings_widgets.py (1 hunks)
  • daras_ai_v2/twilio_bot.py (2 hunks)
  • docker-compose.yml (1 hunks)
  • functions/inbuilt_tools.py (3 hunks)
  • functions/recipe_functions.py (0 hunks)
  • livekit_agent.py (1 hunks)
  • pyproject.toml (1 hunks)
  • recipes/TextToSpeech.py (1 hunks)
  • recipes/VideoBots.py (2 hunks)
  • routers/api.py (2 hunks)
  • routers/twilio_api.py (1 hunks)
  • scripts/create_twilio_sip_trunk.py (1 hunks)
  • scripts/run-prod.sh (1 hunks)
  • scripts/run-tests.sh (1 hunks)
  • widgets/prompt_library.py (2 hunks)
💤 Files with no reviewable changes (1)
  • functions/recipe_functions.py
🧰 Additional context used
🧬 Code graph analysis (8)
recipes/TextToSpeech.py (2)
daras_ai_v2/text_to_speech_settings_widgets.py (1)
  • TextToSpeechProviders (28-35)
daras_ai_v2/custom_enum.py (1)
  • api_choices (32-33)
widgets/prompt_library.py (1)
functions/inbuilt_tools.py (1)
  • VectorSearchLLMTool (42-91)
daras_ai_v2/asr.py (1)
bots/models/bot_integration.py (1)
  • translate (563-576)
functions/inbuilt_tools.py (2)
daras_ai_v2/vector_search.py (2)
  • get_top_k_references (139-240)
  • DocSearchRequest (88-113)
daras_ai_v2/language_model_openai_realtime.py (1)
  • yield_from (291-297)
livekit_agent.py (13)
routers/api.py (1)
  • create_new_run (363-403)
daras_ai_v2/text_to_speech_settings_widgets.py (1)
  • TextToSpeechProviders (28-35)
daras_ai_v2/bots.py (1)
  • build_system_vars (547-597)
bots/models/convo_msg.py (2)
  • Conversation (124-336)
  • db_msgs_to_entries (465-475)
bots/models/bot_integration.py (2)
  • BotIntegration (163-585)
  • Platform (28-75)
daras_ai/image_input.py (4)
  • gcs_blob_for (69-75)
  • gcs_bucket (90-93)
  • get_mimetype_from_response (164-169)
  • upload_gcs_blob_from_bytes (78-87)
daras_ai_v2/asr.py (5)
  • run_asr (1034-1339)
  • run_translate (756-786)
  • should_translate_lang (1520-1521)
  • get (323-327)
  • get (442-446)
daras_ai_v2/doc_search_settings_widgets.py (1)
  • is_user_uploaded_url (36-37)
daras_ai_v2/exceptions.py (2)
  • UserError (58-65)
  • raise_for_status (19-49)
daras_ai_v2/language_model_openai_realtime.py (1)
  • yield_from (291-297)
functions/recipe_functions.py (3)
  • WorkflowLLMTool (75-221)
  • call (71-72)
  • call (125-204)
recipes/TextToSpeech.py (3)
  • TextToSpeechPage (66-467)
  • RequestModel (92-93)
  • run (175-407)
recipes/VideoBots.py (5)
  • VideoBotsPage (140-2004)
  • infer_asr_model_and_language (2013-2032)
  • RequestModel (328-331)
  • asr_step (443-465)
  • tts_step (762-770)
daras_ai_v2/text_to_speech_settings_widgets.py (1)
daras_ai_v2/custom_enum.py (1)
  • GooeyEnum (11-40)
routers/api.py (2)
daras_ai_v2/base.py (3)
  • create_new_run (2005-2055)
  • call_runner_task (2074-2090)
  • deduct_credits (2367-2376)
bots/models/saved_run.py (1)
  • RetentionPolicy (46-48)
recipes/VideoBots.py (15)
daras_ai_v2/base.py (6)
  • run_v2 (1743-1746)
  • get_current_llm_tools (1417-1433)
  • RequestModel (151-165)
  • run (1727-1741)
  • get_raw_price (2384-2385)
  • get_cost_note (2465-2466)
daras_ai_v2/text_to_speech_settings_widgets.py (2)
  • TextToSpeechProviders (28-35)
  • text_to_speech_settings (181-194)
daras_ai_v2/language_model.py (4)
  • value (1004-1012)
  • format_chat_entry (2390-2414)
  • calc_gpt_tokens (1032-1040)
  • run_language_model (1083-1209)
daras_ai_v2/azure_doc_extract.py (1)
  • azure_form_recognizer (71-93)
daras_ai_v2/asr.py (4)
  • get (323-327)
  • get (442-446)
  • run_asr (1034-1339)
  • should_translate_lang (1520-1521)
daras_ai_v2/functional.py (1)
  • flatapply_parallel (11-20)
daras_ai_v2/vector_search.py (4)
  • doc_or_yt_url_to_file_metas (334-352)
  • doc_url_to_text_pages (914-938)
  • get_top_k_references (139-240)
  • DocSearchRequest (88-113)
daras_ai_v2/search_ref.py (4)
  • parse_refs (267-295)
  • CitationStyles (23-42)
  • apply_response_formattings_prefix (64-74)
  • apply_response_formattings_suffix (77-86)
functions/inbuilt_tools.py (6)
  • call (78-91)
  • call (120-122)
  • call (139-141)
  • call (188-240)
  • call (284-315)
  • get_inbuilt_tools_from_state (14-39)
functions/recipe_functions.py (3)
  • call (71-72)
  • call (125-204)
  • get_tool_from_call (224-230)
daras_ai_v2/bots.py (2)
  • parse_bot_html (262-291)
  • handle_location_msg (712-731)
recipes/TextToSpeech.py (5)
  • TextToSpeechPage (66-467)
  • RequestModel (92-93)
  • run (175-407)
  • get_raw_price (136-142)
  • get_cost_note (165-173)
recipes/Lipsync.py (5)
  • RequestModel (24-28)
  • run (73-102)
  • LipsyncPage (18-154)
  • get_raw_price (131-137)
  • get_cost_note (126-129)
daras_ai_v2/field_render.py (3)
  • field_title (17-18)
  • field_desc (21-22)
  • field_title_desc (13-14)
daras_ai_v2/language_model_openai_audio.py (1)
  • is_realtime_audio_url (105-110)
🪛 Ruff (0.12.2)
daras_ai_v2/twilio_bot.py

21-23: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


120-122: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

scripts/create_twilio_sip_trunk.py

88-88: Avoid specifying long messages outside the exception class

(TRY003)


143-143: Loop control variable cl overrides iterable it iterates

(B020)

livekit_agent.py

74-74: Prefer next(iter(ctx.room.remote_participants.values())) over single element slice

Replace with next(iter(ctx.room.remote_participants.values()))

(RUF015)


181-181: Avoid specifying long messages outside the exception class

(TRY003)


202-202: Parenthesize a and b expressions when chaining and and or together, to make the precedence clear

Parenthesize the and subexpression

(RUF021)


317-317: Unused function argument: context

(ARG001)


324-324: Consider moving this statement to an else block

(TRY300)


379-379: Unused method argument: language

(ARG002)


380-380: Unused method argument: conn_options

(ARG002)


471-471: Probable use of requests call without timeout

(S113)

recipes/VideoBots.py

364-368: Avoid specifying long messages outside the exception class

(TRY003)


532-532: Avoid specifying long messages outside the exception class

(TRY003)


539-541: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


579-579: Prefer next(iter(keyword_query.values())) over single element slice

Replace with next(iter(keyword_query.values()))

(RUF015)


816-816: Unused class method argument: state

(ARG003)


1438-1450: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


1497-1498: try-except-pass detected, consider logging the exception

(S110)


1497-1497: Do not catch blind exception: Exception

(BLE001)


1576-1576: Redefinition of unused run_v2 from line 356

(F811)


1584-1588: Avoid specifying long messages outside the exception class

(TRY003)

🪛 GitHub Check: CodeQL
scripts/create_twilio_sip_trunk.py

[failure] 28-28: Clear-text logging of sensitive information
This expression logs sensitive data (private) as clear text.


[failure] 58-58: Clear-text logging of sensitive information
This expression logs sensitive data (private) as clear text.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (19)
daras_ai_v2/asr.py (2)

620-627: Consistent V3 usage: LGTM.

Same note as above—ensure versions are aligned across environments.


599-606: Confirm google-cloud-translate v3 is installed and import path is valid

pyproject.toml pins "google-cloud-translate == 3.*" but the verification script failed to import translate_v3: "cannot import name 'translate_v3' from 'google.cloud' (unknown location)". Confirm CI/prod installs google-cloud-translate v3 and that "from google.cloud import translate_v3 as translate" (and TranslationServiceClient) is importable in your runtime; update the dependency pin or import path if not.

Procfile (1)

14-20: UV launcher migration: LGTM.

Commands map 1:1 with previous behavior; nice alignment with the UV workflow.

functions/inbuilt_tools.py (2)

23-24: Good addition of voice platform support.

The conditional yield of VectorSearchLLMTool for VOICE platform provides knowledge base search capabilities for voice interactions. This aligns well with the LiveKit integration.


152-153: Important guidance for call transfer behavior.

The instructions to say "Transferring you now..." and ensure phone numbers come from a Knowledge Base are critical for user experience and preventing phone number fabrication. These guidelines help maintain trust and prevent misuse.

routers/api.py (2)

346-360: Well-structured refactoring of run creation logic.

The extraction of create_new_run from submit_api_call improves separation of concerns and reusability. This modular approach enables the LiveKit agent to create runs without immediately submitting tasks.


398-399: Good implementation of the defaults parameter forwarding.

The **defaults mechanism provides flexibility for callers to pass additional parameters to create_new_run, which is particularly useful for the LiveKit integration that needs to set run_status=None.

recipes/VideoBots.py (2)

356-407: Excellent modularization of the workflow into discrete steps.

Breaking down the monolithic run method into focused step methods (asr_step, document_understanding_step, input_translation_step, etc.) significantly improves testability, maintainability, and readability. Each step has a clear single responsibility.


1415-1421: Ensure consistent CSV decoding for button handling.

The button ID parsing logic correctly handles CSV-encoded data to extract target and title, properly supporting the web widget's button functionality.

livekit_agent.py (2)

64-103: Well-structured LiveKit agent entrypoint.

The entrypoint properly manages the LiveKit connection lifecycle, creates runs via the new API, and sets up audio sessions with ambient/thinking sounds. Good separation between audio model and STT/LLM/TTS pipelines.


244-313: Robust handling of Twilio call scenarios.

The LivekitVoice class properly handles both incoming and outgoing calls, manages conversation persistence based on configuration, and correctly identifies bot vs user phone numbers. The account SID normalization (line 265-266) is a nice touch for handling default credentials.

recipes/TextToSpeech.py (1)

167-173: Equality checks assume Enum names (strings).

get_cost_note() compares tts_provider to TextToSpeechProviders.ELEVEN_LABS.name, which relies on state storing names. The diff above fixes defaults and lookup; please confirm UI selector also returns names.

README.md (3)

163-167: LGTM: service commands updated to UV.


192-192: LGTM: hot reload command updated to UV.


231-232: LGTM: honcho run updated to UV.

pyproject.toml (1)

102-125: LiveKit/MediaPipe extras notes are good; no changes.

.github/workflows/python-tests.yml (1)

94-94: LGTM: tests invoked via UV.

widgets/prompt_library.py (1)

7-11: LGTM: VectorSearch tool prompt wired into library.

Import and prompt entry look correct and consistent with functions/inbuilt_tools.py.

Also applies to: 74-77

docker-compose.yml (1)

25-27: Confirm 'uv' is installed in the pytest image

docker-compose.yml (lines 25–27) runs tests with uv run. Sandbox lacked Docker so verification couldn't be performed — run locally and paste the output or ensure the image installs uv:

docker run --rm -it "us-central1-docker.pkg.dev/dara-c1b52/cloudbuild-pytest/gooey-server:${COMMIT_SHA}" uv --version

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
managed_secrets/widgets.py (1)

203-209: Creation path bypasses model validation; call full_clean() before saving.

objects.create(...) skips full_clean(), so model/field validators won’t run on create, and your except ValidationError won’t trigger. Make creation consistent with edit.

-            ManagedSecret.objects.create(
-                workspace=workspace,
-                created_by=user,
-                name=name,
-                value=value,
-            )
+            new_secret = ManagedSecret(
+                workspace=workspace,
+                created_by=user,
+                name=name,
+                value=value,
+            )
+            new_secret.full_clean()
+            new_secret.save()
🧹 Nitpick comments (7)
.github/workflows/python-tests.yml (1)

72-75: Pin version, enable caching, and gate installation to avoid CI flakiness and slowdowns.

Unpinned install + --no-cache-dir increases breakage risk and build time. Also consider gating to only runs that need LiveKit.

Apply:

-      - name: Install mediapipe
-        run: |
-          pip install --no-cache-dir mediapipe
+      - name: Install mediapipe
+        if: ${{ contains(github.ref_name, 'livekit') || contains(github.head_ref || '', 'livekit') }}
+        env:
+          PIP_CACHE_DIR: ~/.cache/pip
+        run: |
+          poetry run python -m pip install "mediapipe==0.10.14"

Optional: add an actions/cache step for ~/.cache/pip to reuse wheels across runs. If you want to align with Docker’s RUN_LIVEKIT gating, export the same env in this workflow and use if: ${{ env.RUN_LIVEKIT == '' }} (or the desired condition).

managed_secrets/widgets.py (6)

51-63: Avoid exceptions for control flow when rendering secret value.

Use the reveal flag to decide what to render instead of relying on NotFoundError on the happy path.

-                        with gui.tag("td", className="d-flex gap-3"):
-                            if gui.session_state.pop(f"secret:{secret.id}:show", False):
-                                secret.load_value()
-                            try:
-                                gui.write(f"`{secret.value}`")
-                            except ManagedSecret.NotFoundError:
-                                gui.write("`" + "*" * 10 + "`")
-                                gui.button(
-                                    '<i class="fa-solid fa-eye"></i>',
-                                    type="tertiary",
-                                    className="m-0 px-1 py-0",
-                                    key=f"secret:{secret.id}:show",
-                                )
+                        with gui.tag("td", className="d-flex gap-3"):
+                            show_key = f"secret:{secret.id}:show"
+                            if gui.session_state.pop(show_key, False):
+                                secret.load_value()
+                                gui.write(f"`{secret.value}`")
+                            else:
+                                gui.write("`" + "*" * 10 + "`")
+                                gui.button(
+                                    '<i class="fa-solid fa-eye"></i>',
+                                    type="tertiary",
+                                    className="m-0 px-1 py-0",
+                                    key=show_key,
+                                )

147-155: Guard against None from text_input before calling .upper().

If gui.text_input can return None, .upper() will raise. Normalize first.

-        name = gui.text_input(
+        name_input = gui.text_input(
             label="###### Name",
             style=dict(textTransform="uppercase", fontFamily="monospace"),
             # language=javascript
             onKeyUp="setValue(value.replace(/ /g, '_').replace(/[^a-zA-Z0-9_\$]/g, ''))",
             key="secret:name",
             value=(secret and secret.name) or secret_name,
-        ).upper()
+        )
+        name = (name_input or "").upper()

164-165: Mask the secret input field.

Prevent shoulder‑surfing when entering values by masking the input if the widget supports it (e.g., input_type="password" or equivalent).

Can you confirm the correct prop for masking in gui.text_input? If supported, set it on this field.


209-211: Surface all validation messages, not just the first.

Show all messages for better UX when multiple fields fail validation.

-        gui.error(e.messages[0], icon="")
+        gui.error("; ".join(e.messages), icon="")

21-22: Avoid N+1 on created_by in the table.

Prefetch the relation once.

-    secrets = list(workspace.managed_secrets.order_by("-created_at"))
+    secrets = list(
+        workspace.managed_secrets.select_related("created_by").order_by("-created_at")
+    )

67-71: Minor readability nit: prefer explicit ternary.

Cleaner than and/or chaining.

-                            gui.write(
-                                secret.created_by
-                                and secret.created_by.full_name()
-                                or ""
-                            )
+                            gui.write(
+                                secret.created_by.full_name() if secret.created_by else ""
+                            )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f9c35df and 81e457f.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • .github/workflows/python-tests.yml (1 hunks)
  • Dockerfile (1 hunks)
  • managed_secrets/widgets.py (1 hunks)
  • pyproject.toml (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • Dockerfile
  • pyproject.toml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (1)
managed_secrets/widgets.py (1)

7-7: No absl.flags.ValidationError usages found — approving change.

Ran the suggested grep: no imports/usages of absl.flags.ValidationError were found. The remaining "except ValidationError" occurrences do not reference absl.flags, so switching to django.core.exceptions.ValidationError is safe.

@devxpy devxpy force-pushed the livekit branch 4 times, most recently from 1b42e48 to fb9a0a7 Compare September 16, 2025 13:53
cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 (3)
daras_ai_v2/twilio_bot.py (2)

185-189: AttributeError: self.bi is never set; breaks queue creation and call start.

twilio_queue() and start_voice_call_session() reference self.bi, but it's undefined.

Apply:

         self.convo = convo
+        # needed by twilio_queue() and start_voice_call_session()
+        self.bi = convo.bot_integration

201-203: Blocking external call without timeout; fragile 404-only check.

requests.head() has no timeout and only checks 404. This can hang request threads and mishandle 3xx/401 from Twilio recording URLs.

Apply:

-            if requests.head(audio_url).status_code == 404:
-                sleep(1)  # wait for the recording to be available
+            try:
+                r = requests.head(audio_url, timeout=3, allow_redirects=True)
+                if r.status_code == 404:
+                    sleep(1)  # wait for the recording to be available
+            except requests.RequestException:
+                logger.warning("HEAD to RecordingUrl failed", exc_info=True)

Optional: authenticate HEAD/GET with Twilio credentials or fetch via Twilio REST API for robustness.

routers/api.py (1)

363-404: Fix TypeError: don't pass unsupported kwargs to BasePage.create_new_run

BasePage.create_new_run(self, *, enable_rate_limits=..., run_status=..., **defaults) does not accept is_api_call or retention_policy; passing them as named args will raise TypeError at runtime. Update the call in routers/api.py (the sr = page.create_new_run(...) call around line ~394) to merge those fields into the defaults mapping so they become SavedRun fields. Example replacement:

sr = page.create_new_run(
enable_rate_limits=enable_rate_limits,
**(dict(is_api_call=True, retention_policy=retention_policy or RetentionPolicy.keep) | defaults),
)

♻️ Duplicate comments (7)
daras_ai_v2/text_to_speech_settings_widgets.py (1)

28-35: Fix enum API: add label/sample_rate/db_value to avoid .value tuple leaks and satisfy GooeyEnum.

Right now .value is a NamedTuple; UI and callers will see TTSProvider(value='...', sample_rate=...), and GooeyEnum.db_choices() expects .label/.db_value. Add explicit accessors (and a friendly __str__) to the enum.

 class TextToSpeechProviders(TTSProvider, GooeyEnum):
     GOOGLE_TTS = TTSProvider(value="Google Text-to-Speech", sample_rate=24000)
     ELEVEN_LABS = TTSProvider(value="Eleven Labs", sample_rate=44100)
     UBERDUCK = TTSProvider(value="Uberduck.ai", sample_rate=22050)
     BARK = TTSProvider(value="Bark (suno-ai)", sample_rate=24000)
     AZURE_TTS = TTSProvider(value="Azure Text-to-Speech", sample_rate=16000)
     OPEN_AI = TTSProvider(value="OpenAI", sample_rate=24000)
     GHANA_NLP = TTSProvider(value="GhanaNLP Text-To-Speech", sample_rate=16000)
+
+    @property
+    def label(self) -> str:
+        # underlying NamedTuple field
+        return self.value.value
+
+    @property
+    def sample_rate(self) -> int:
+        return self.value.sample_rate
+
+    @property
+    def db_value(self) -> str:
+        # stable identifier for persistence
+        return self.name
+
+    def __str__(self) -> str:
+        return self.label

Run to find any remaining .value usages that should be .label:

#!/bin/bash
rg -nP --type=py -C1 'TextToSpeechProviders\.\w+\.value\b'
daras_ai_v2/twilio_bot.py (2)

21-23: Avoid shared mutable class state for request_overrides; make per-instance.

This leaks state across requests and triggers Ruff RUF012. Use ClassVar for the default and copy it in init.

Apply within this hunk:

-    request_overrides = dict(
-        variables=dict(platform_medium="SMS"),
-    )
+    request_overrides: ClassVar[dict] = {
+        "variables": {"platform_medium": "SMS"}
+    }

Outside this hunk (in init), ensure a per‑instance copy:

# after super().__init__()
self.request_overrides = dict(self.request_overrides)

Add import once at top-level:

from typing import ClassVar

120-122: Repeat the per‑instance override fix for TwilioVoice.

Same shared-mutation problem as SMS. Mirror the ClassVar + per‑instance copy pattern.

Apply within this hunk:

-    request_overrides = dict(
-        variables=dict(platform_medium="VOICE"),
-    )
+    request_overrides: ClassVar[dict] = {
+        "variables": {"platform_medium": "VOICE"}
+    }

Outside this hunk (in init), add:

self.request_overrides = dict(self.request_overrides)
scripts/create_twilio_sip_trunk.py (2)

139-147: Fix missing return and variable shadowing in credential list helper.

On successful create you never return cl, and the loop variable overwrites the list object, causing failures at Line 46 (credential_list.sid).

Apply:

 def get_or_create_credential_list(
     client: Client,
     list_friendly_name: str,
 ):
     try:
-        cl = client.sip.credential_lists.create(friendly_name=list_friendly_name)
+        cl = client.sip.credential_lists.create(friendly_name=list_friendly_name)
+        return cl
     except TwilioException as exc:
         if exc.code == 21240:
-            cl = client.sip.credential_lists.list()
-            for cl in cl:
-                if cl.friendly_name == list_friendly_name:
-                    return cl
+            credential_lists = client.sip.credential_lists.list()
+            for cred in credential_lists:
+                if cred.friendly_name == list_friendly_name:
+                    return cred
         raise

19-19: Stop printing PII and secrets; use structured, redacted logging.

These prints can leak phone numbers, SIDs, and credentials to logs (CodeQL flagged this). Replace with a logger and mask sensitive fields.

+import logging
+logger = logging.getLogger(__name__)
@@
-    print(f"✅ {trunk=}")
+    logger.info("✅ trunk created/reused", extra={"trunk_sid": getattr(trunk, "sid", None)})
@@
-    print(f"✅ {incoming_phone_number=}")
+    logger.info("✅ incoming phone number found")
@@
-    print(f"✅ {trunk=}")
+    logger.info("✅ twilio trunk ready", extra={"trunk_sid": getattr(trunk, "sid", None)})
@@
-    print(f"✅ {origination_url=}")
+    logger.info("✅ origination URL configured")
@@
-    print(f"✅ {credential_list=}")
+    logger.info("✅ credential list ready", extra={"credential_list_sid": getattr(credential_list, "sid", None)})
@@
-        print(f"✅ {credential=}")
+        logger.info("✅ trunk credential created")
@@
-    print(f"✅ {attached_phone_number=}")
+    logger.info("✅ phone number attached to trunk")

Also applies to: 28-28, 31-31, 39-39, 45-45, 53-53, 58-58

recipes/VideoBots.py (1)

1576-1626: Remove duplicate run_v2; it overrides the earlier definition.

This redefinition masks the first implementation and violates F811. Delete this block.

-    def run_v2(
-        self,
-        request: "VideoBotsPage.RequestModel",
-        response: "VideoBotsPage.ResponseModel",
-    ) -> typing.Iterator[str | None]:
-        if request.tts_provider == TextToSpeechProviders.ELEVEN_LABS.name and not (
-            self.is_current_user_paying() or self.is_current_user_admin()
-        ):
-            raise UserError(
-                """
-                Please purchase Gooey.AI credits to use ElevenLabs voices <a href="/account">here</a>.
-                """
-            )
-        llm_model = LargeLanguageModels[request.selected_model]
-        user_input = (request.input_prompt or "").strip()
-        if not (
-            user_input
-            or request.input_audio
-            or request.input_images
-            or request.input_documents
-        ):
-            return
-        asr_msg, user_input = yield from self.asr_step(
-            model=llm_model, request=request, response=response, user_input=user_input
-        )
-        ocr_texts = yield from self.document_understanding_step(request=request)
-        request.translation_model = (
-            request.translation_model or DEFAULT_TRANSLATION_MODEL
-        )
-        user_input = yield from self.input_translation_step(
-            request=request, user_input=user_input, ocr_texts=ocr_texts
-        )
-        yield from self.build_final_prompt(
-            request=request, response=response, user_input=user_input, model=llm_model
-        )
-        yield from self.llm_loop(
-            request=request,
-            response=response,
-            model=llm_model,
-            asr_msg=asr_msg,
-        )
-        yield from self.tts_step(model=llm_model, request=request, response=response)
-        yield from self.lipsync_step(request, response)
livekit_agent.py (1)

485-495: Add timeout, user-URL error context, and safe cleanup in TTS fetch.

Avoid indefinite hang, return better errors for user URLs, and don’t let cleanup mask the main error.

-    try:
-        r = requests.get(audio_url)
-        raise_for_status(r)
+    try:
+        is_user_url = is_user_uploaded_url(audio_url)
+        r = requests.get(audio_url, timeout=30)
+        raise_for_status(r, is_user_url=is_user_url)
         audio_wav_bytes = r.content
         mime_type = get_mimetype_from_response(r)
     finally:
-        if is_user_uploaded_url(audio_url):
-            blob = gcs_bucket().blob(
-                audio_url.split(settings.GS_BUCKET_NAME)[-1].strip("/")
-            )
-            blob.delete()
+        if is_user_url:
+            try:
+                blob = gcs_bucket().blob(
+                    audio_url.split(settings.GS_BUCKET_NAME)[-1].strip("/")
+                )
+                blob.delete()
+            except Exception:
+                pass
🧹 Nitpick comments (17)
daras_ai_v2/text_to_speech_settings_widgets.py (2)

316-316: UI string shows tuple repr — use .label instead of .value.

-    gui.write(f"##### 🗣️ {TextToSpeechProviders.GOOGLE_TTS.value} Settings")
+    gui.write(f"##### 🗣️ {TextToSpeechProviders.GOOGLE_TTS.label} Settings")

355-355: Same: switch to .label for UBERDUCK header.

-    gui.write(f"##### 🗣️ {TextToSpeechProviders.UBERDUCK.value} Settings")
+    gui.write(f"##### 🗣️ {TextToSpeechProviders.UBERDUCK.label} Settings")
daras_ai_v2/twilio_bot.py (1)

240-243: Use existing integration client; safe after setting self.bi.

After adding self.bi in init, this property is fine. If you prefer avoiding another attribute, reference via self.convo.bot_integration directly.

Alternative:

-        return self.bi.get_twilio_client().queues.create(self.user_msg_id[:64])
+        return self.convo.bot_integration.get_twilio_client().queues.create(self.user_msg_id[:64])
Dockerfile (2)

52-53: ARG default self‑reference is unnecessary; simplify.

Minor cleanliness.

Apply:

-ARG RUN_LIVEKIT=${RUN_LIVEKIT}
-ENV RUN_LIVEKIT=${RUN_LIVEKIT}
+ARG RUN_LIVEKIT
+ENV RUN_LIVEKIT=${RUN_LIVEKIT}

73-73: Healthcheck addition looks good.

Including 8081 probe is fine; consider --spider and timeouts to avoid hangs.

Example:

HEALTHCHECK CMD wget -q --spider -T 2 127.0.0.1:8000 || \
                 wget -q --spider -T 2 127.0.0.1:8501 || \
                 wget -q --spider -T 2 127.0.0.1:8081 || \
                 bash -c 'poetry run celery -A celeryapp inspect ping -d celery@$HOSTNAME' || exit 1
pyproject.toml (1)

96-98: LiveKit deps: align versions and extras across environments.

  • Confirm these versions interoperate with Python 3.10 and platform wheels.
  • If mediapipe is required only in non‑LiveKit builds, document that in README/CI and ensure Poetry env gets it when needed.
functions/inbuilt_tools.py (1)

23-25: Tool gating only for VOICE; is that intentional?

If KB search is useful for SMS/Chat, consider enabling beyond VOICE or making this configurable.

scripts/create_twilio_sip_trunk.py (2)

109-131: Return type and cleanup logic in origination URL helper.

  • The annotation says -> str but you return Twilio objects. Fix signature.
  • Minor: you iterate existing twice after deletes; safer to early-return when a matching sip_url is found, and otherwise delete others.
-def get_or_create_origination_url(
+from typing import Any
+
+def get_or_create_origination_url(
     client: Client,
     trunk_sid: str,
     sip_url: str,
     friendly_name: str,
     priority: int = 1,
     weight: int = 1,
     enabled: bool = True,
-) -> str:
+) -> Any:
-    existing = client.trunking.v1.trunks(trunk_sid).origination_urls.list()
-    for ou in existing:
-        if ou.sip_url != sip_url:
-            ou.delete()
-    for ou in existing:
-        if ou.sip_url == sip_url:
-            return ou
+    existing = client.trunking.v1.trunks(trunk_sid).origination_urls.list()
+    for ou in existing:
+        if ou.sip_url == sip_url:
+            return ou
+    # delete non-matching to enforce single target
+    for ou in existing:
+        ou.delete()
     return client.trunking.v1.trunks(trunk_sid).origination_urls.create(
         sip_url=sip_url,
         friendly_name=friendly_name,
         priority=priority,
         weight=weight,
         enabled=enabled,
     )

100-106: Trunk lookup could avoid .fetch() per item.

You call .fetch() for every trunk then compare domain_name, which is already present on the listed instances. Drop the extra network calls.

-    for trunk in trunks:
-        trunk = trunk.fetch()
-        if trunk.domain_name == domain_name:
-            return trunk
+    for t in trunks:
+        if t.domain_name == domain_name:
+            return t
recipes/VideoBots.py (3)

539-546: Avoid None+list concat and use unpacking for query messages.

Prevents TypeError when request.messages is None and satisfies RUF005.

-            query_msgs = request.messages + [
-                format_chat_entry(role=CHATML_ROLE_USER, content_text=user_input)
-            ]
+            base_msgs = request.messages or []
+            query_msgs = [
+                *base_msgs,
+                format_chat_entry(role=CHATML_ROLE_USER, content_text=user_input),
+            ]

578-581: Safer extraction of single value from dict.

Prefer next(iter(...), None) over indexing to avoid IndexError and follow RUF015.

-                if keyword_query and isinstance(keyword_query, dict):
-                    keyword_query = list(keyword_query.values())[0]
+                if keyword_query and isinstance(keyword_query, dict):
+                    keyword_query = next(iter(keyword_query.values()), None)

705-705: Don’t swallow exceptions silently.

Replace bare except Exception: pass with minimal logging to aid debugging.

-            except Exception:
-                pass
+            except Exception as e:
+                gui.caption(f"Tool load failed: {e}", className="text-muted")

Also applies to: 1497-1498

livekit_agent.py (5)

466-469: Fix return type of tts_step.

It returns bytes and MIME type; update the annotation.

-def tts_step(
+def tts_step(
     page: VideoBotsPage, request: VideoBotsPage.RequestModel, input_text: str
-) -> bytes:
+) -> tuple[bytes, str]:

72-75: Handle no-participant race after wait.

Avoid IndexError if the participant disconnects quickly.

-    participant = list(ctx.room.remote_participants.values())[0]
+    participant = next(iter(ctx.room.remote_participants.values()), None)
+    if not participant:
+        return

213-218: Clarify last message selection (precedence pitfall).

Avoid a and b or c precedence; it’s harder to read and flagged by linters.

-    system_vars, system_vars_schema = build_system_vars(
-        bot.convo,
-        bot.user_msg_id,
-        saved_msgs and saved_msgs[-1] or None,
-    )
+    last_msg = saved_msgs[-1] if saved_msgs else None
+    system_vars, system_vars_schema = build_system_vars(
+        bot.convo, bot.user_msg_id, last_msg
+    )

363-365: Harden GCS cleanup in ASR step.

blob.delete() can raise (e.g., NotFound). Don’t fail the call on cleanup.

-    finally:
-        blob.delete()
+    finally:
+        try:
+            blob.delete()
+        except Exception:
+            pass

331-343: Tool wrapper: broaden handling but log the error.

Catching only TypeError misses common failures (network, validation). Return error but also log for observability.

-        except TypeError as e:
-            return dict(error=repr(e))
+        except Exception as e:
+            # TODO: replace with structured logger if available
+            print(f"tool {tool.name} failed: {e!r}")
+            return {"error": repr(e)}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3698000 and f0321b2.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • .github/workflows/python-tests.yml (1 hunks)
  • Dockerfile (2 hunks)
  • daras_ai_v2/asr.py (2 hunks)
  • daras_ai_v2/settings.py (1 hunks)
  • daras_ai_v2/text_to_speech_settings_widgets.py (1 hunks)
  • daras_ai_v2/twilio_bot.py (2 hunks)
  • functions/inbuilt_tools.py (3 hunks)
  • functions/recipe_functions.py (0 hunks)
  • livekit_agent.py (1 hunks)
  • managed_secrets/widgets.py (1 hunks)
  • pyproject.toml (3 hunks)
  • recipes/TextToSpeech.py (1 hunks)
  • recipes/VideoBots.py (2 hunks)
  • routers/api.py (2 hunks)
  • routers/twilio_api.py (1 hunks)
  • scripts/create_twilio_sip_trunk.py (1 hunks)
  • scripts/run-prod.sh (1 hunks)
  • widgets/prompt_library.py (2 hunks)
💤 Files with no reviewable changes (1)
  • functions/recipe_functions.py
🚧 Files skipped from review as they are similar to previous changes (8)
  • .github/workflows/python-tests.yml
  • daras_ai_v2/asr.py
  • routers/twilio_api.py
  • scripts/run-prod.sh
  • daras_ai_v2/settings.py
  • managed_secrets/widgets.py
  • widgets/prompt_library.py
  • recipes/TextToSpeech.py
🧰 Additional context used
🧬 Code graph analysis (5)
functions/inbuilt_tools.py (2)
daras_ai_v2/vector_search.py (2)
  • get_top_k_references (139-240)
  • DocSearchRequest (88-113)
daras_ai_v2/language_model_openai_realtime.py (1)
  • yield_from (291-297)
routers/api.py (2)
daras_ai_v2/base.py (3)
  • create_new_run (2005-2055)
  • call_runner_task (2074-2090)
  • deduct_credits (2367-2376)
bots/models/saved_run.py (1)
  • RetentionPolicy (46-48)
daras_ai_v2/text_to_speech_settings_widgets.py (1)
daras_ai_v2/custom_enum.py (1)
  • GooeyEnum (11-40)
livekit_agent.py (15)
routers/api.py (1)
  • create_new_run (363-403)
daras_ai_v2/text_to_speech_settings_widgets.py (1)
  • TextToSpeechProviders (28-35)
daras_ai_v2/bots.py (2)
  • BotInterface (89-259)
  • build_system_vars (547-597)
bots/models/convo_msg.py (2)
  • Conversation (124-336)
  • db_msgs_to_entries (465-475)
bots/models/bot_integration.py (2)
  • BotIntegration (163-585)
  • Platform (28-75)
daras_ai/image_input.py (4)
  • gcs_blob_for (69-75)
  • gcs_bucket (90-93)
  • get_mimetype_from_response (164-169)
  • upload_gcs_blob_from_bytes (78-87)
daras_ai_v2/asr.py (5)
  • run_asr (1034-1339)
  • run_translate (756-786)
  • should_translate_lang (1520-1521)
  • get (323-327)
  • get (442-446)
daras_ai_v2/doc_search_settings_widgets.py (1)
  • is_user_uploaded_url (36-37)
daras_ai_v2/exceptions.py (2)
  • UserError (58-65)
  • raise_for_status (19-49)
daras_ai_v2/language_model.py (3)
  • ConversationEntry (1051-1058)
  • LargeLanguageModels (82-1024)
  • LLMApis (51-61)
daras_ai_v2/language_model_openai_realtime.py (1)
  • yield_from (291-297)
functions/recipe_functions.py (3)
  • WorkflowLLMTool (75-221)
  • call (71-72)
  • call (125-204)
recipes/TextToSpeech.py (3)
  • TextToSpeechPage (66-467)
  • RequestModel (92-93)
  • run (175-407)
recipes/VideoBots.py (4)
  • infer_asr_model_and_language (2013-2032)
  • RequestModel (328-331)
  • asr_step (443-465)
  • tts_step (762-770)
functions/inbuilt_tools.py (5)
  • call (78-91)
  • call (120-122)
  • call (139-141)
  • call (188-240)
  • call (284-315)
recipes/VideoBots.py (14)
daras_ai_v2/base.py (7)
  • run_v2 (1743-1746)
  • is_current_user_paying (2475-2479)
  • is_current_user_admin (2468-2469)
  • get_current_llm_tools (1417-1433)
  • RequestModel (151-165)
  • run (1727-1741)
  • current_workspace (1436-1441)
daras_ai_v2/exceptions.py (1)
  • UserError (58-65)
daras_ai_v2/language_model.py (4)
  • LargeLanguageModels (82-1024)
  • value (1012-1020)
  • format_chat_entry (2409-2433)
  • run_language_model (1091-1217)
daras_ai_v2/azure_doc_extract.py (1)
  • azure_form_recognizer (71-93)
daras_ai_v2/asr.py (7)
  • get (323-327)
  • get (442-446)
  • AsrModels (271-337)
  • run_asr (1034-1339)
  • should_translate_lang (1520-1521)
  • supports_input_prompt (336-337)
  • supports_speech_translation (329-334)
daras_ai_v2/functional.py (1)
  • flatapply_parallel (11-20)
daras_ai_v2/vector_search.py (4)
  • doc_or_yt_url_to_file_metas (334-352)
  • doc_url_to_text_pages (914-938)
  • get_top_k_references (139-240)
  • DocSearchRequest (88-113)
daras_ai_v2/variables_widget.py (1)
  • render_prompt_vars (322-334)
url_shortener/models.py (3)
  • ShortenedURL (87-146)
  • get_or_create_for_workflow (19-25)
  • shortened_url (132-133)
daras_ai_v2/bots.py (2)
  • parse_bot_html (262-291)
  • handle_location_msg (712-731)
recipes/TextToSpeech.py (2)
  • RequestModel (92-93)
  • run (175-407)
recipes/Lipsync.py (3)
  • RequestModel (24-28)
  • run (73-102)
  • LipsyncPage (18-154)
daras_ai_v2/field_render.py (2)
  • field_title (17-18)
  • field_desc (21-22)
daras_ai_v2/language_model_openai_audio.py (1)
  • is_realtime_audio_url (105-110)
🪛 Ruff (0.12.2)
livekit_agent.py

74-74: Prefer next(iter(ctx.room.remote_participants.values())) over single element slice

Replace with next(iter(ctx.room.remote_participants.values()))

(RUF015)


195-195: Avoid specifying long messages outside the exception class

(TRY003)


216-216: Parenthesize a and b expressions when chaining and and or together, to make the precedence clear

Parenthesize the and subexpression

(RUF021)


331-331: Unused function argument: context

(ARG001)


338-338: Consider moving this statement to an else block

(TRY300)


393-393: Unused method argument: language

(ARG002)


394-394: Unused method argument: conn_options

(ARG002)


485-485: Probable use of requests call without timeout

(S113)

scripts/create_twilio_sip_trunk.py

88-88: Avoid specifying long messages outside the exception class

(TRY003)


143-143: Loop control variable cl overrides iterable it iterates

(B020)

recipes/VideoBots.py

364-368: Avoid specifying long messages outside the exception class

(TRY003)


532-532: Avoid specifying long messages outside the exception class

(TRY003)


539-541: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


579-579: Prefer next(iter(keyword_query.values())) over single element slice

Replace with next(iter(keyword_query.values()))

(RUF015)


816-816: Unused class method argument: state

(ARG003)


1438-1450: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


1497-1498: try-except-pass detected, consider logging the exception

(S110)


1497-1497: Do not catch blind exception: Exception

(BLE001)


1576-1576: Redefinition of unused run_v2 from line 356

(F811)


1584-1588: Avoid specifying long messages outside the exception class

(TRY003)

daras_ai_v2/twilio_bot.py

21-23: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


120-122: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🪛 GitHub Check: CodeQL
scripts/create_twilio_sip_trunk.py

[failure] 28-28: Clear-text logging of sensitive information
This expression logs sensitive data (private) as clear text.


[failure] 58-58: Clear-text logging of sensitive information
This expression logs sensitive data (private) as clear text.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (5)
pyproject.toml (1)

79-79: anthropic major bump: check API changes.

Ensure callers reflect >=0.67.0 changes (client init, message APIs, timeouts).

functions/inbuilt_tools.py (2)

42-51: System prompt reads well.

Clear, bounded guidance for file_search.


52-76: Schema and description LGTM.

Good constraints and guidance on multi‑query usage.

routers/api.py (1)

346-361: submit_api_call refactor reads clean.

Delegating run creation and returning (result, sr) simplifies flow.

daras_ai_v2/twilio_bot.py (1)

247-250: OK to use VoiceResponse — verify self.bi is initialized in init.

start_voice_call_session (daras_ai_v2/twilio_bot.py:247–249) is called from routers/twilio_api.py:103; twilio_queue (daras_ai_v2/twilio_bot.py:241–242) also uses self.bi.get_twilio_client(). Confirm the class init sets self.bi before these are used.

rank-bm25 = "^0.2.2"
pytest-subtests = "^0.11.0"
anyio = "^3.4.0"
anyio = "^4.10.0"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

anyio bump may impact FastAPI/Starlette compatibility.

FastAPI’s anyio/Starlette bounds can be tight. Verify ^4.10.0 is compatible with your FastAPI/Starlette versions.

Run:


🏁 Script executed:

#!/bin/bash
curl -s https://pypi.org/pypi/fastapi/json | jq -r '.info.version'
curl -s https://pypi.org/pypi/starlette/json | jq -r '.info.version'
curl -s https://pypi.org/pypi/anyio/json | jq -r '.info.version'

Length of output: 21


AnyIO ^4.10.0 is fine for Starlette — but FastAPI/Starlette version alignment needs action

  • anyio 4.10.0 satisfies Starlette’s declared range (>=3.4.0, <5.0). (starlette.io)
  • FastAPI 0.116.1+ restricts supported Starlette to <0.48.0; you have FastAPI 0.116.2 + Starlette 0.48.0 — align versions: either pin Starlette <0.48.0 or upgrade FastAPI to a release that explicitly supports Starlette 0.48.x, then run full test coverage. (fastapi.tiangolo.com)

File: pyproject.toml (anyio = "^4.10.0") — verify/adjust Starlette/FastAPI pins in pyproject/lockfile.

🤖 Prompt for AI Agents
In pyproject.toml around line 69, anyio is set to "^4.10.0" but FastAPI
(0.116.2) and Starlette (0.48.0) are version-misaligned; update dependency pins
so FastAPI and Starlette are compatible: either pin Starlette to "<0.48.0" to
match FastAPI 0.116.x, or upgrade FastAPI to a release that explicitly supports
Starlette 0.48.x, then regenerate/commit the lockfile (poetry.lock or
pipfile.lock) and run the full test suite to validate the change.

cursor[bot]

This comment was marked as outdated.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/test_translation.py (1)

1-2: Prefer boolean cast once, reuse it

Avoid repeated truthiness pitfalls; parse the flag once with cast=bool and reuse.

from decouple import config

RUN_GOOGLE_TRANSLATE_TESTS = config("RUN_GOOGLE_TRANSLATE_TESTS", default=False, cast=bool)

Use RUN_GOOGLE_TRANSLATE_TESTS in skips below.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f0321b2 and 247ff17.

📒 Files selected for processing (2)
  • glossary_resources/tests.py (2 hunks)
  • tests/test_translation.py (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (1)
glossary_resources/tests.py (1)

2-2: python-decouple declared in pyproject.toml (line 13). No action required.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
recipes/VideoBots.py (1)

333-355: Avoid mutable defaults in Pydantic models (use default_factory).

Lists defaulting to [] can leak state across instances. Use Field(default_factory=list) and drop Optional where you always default to a list.

 class ResponseModel(BaseModel):
-    final_prompt: str | list[ConversationEntry] = []
-    output_text: list[str] = []
-    output_audio: list[HttpUrlStr] = []
-    output_video: list[HttpUrlStr] = []
+    final_prompt: str | list[ConversationEntry] = Field(default_factory=list)
+    output_text: list[str] = Field(default_factory=list)
+    output_audio: list[HttpUrlStr] = Field(default_factory=list)
+    output_video: list[HttpUrlStr] = Field(default_factory=list)

     # intermediate text
     raw_input_text: str | None = None
     raw_tts_text: list[str] | None = None
     raw_output_text: list[str] | None = None

     # doc search
-    references: list[SearchReference] | None = []
+    references: list[SearchReference] = Field(default_factory=list)
     final_search_query: str | None = None
     final_keyword_query: str | list[str] | None = None

     # function calls
-    output_documents: list[HttpUrlStr] | None = None
-    reply_buttons: list[ReplyButton] | None = None
+    output_documents: list[HttpUrlStr] | None = None
+    reply_buttons: list[ReplyButton] | None = None

-    finish_reason: list[str] | None = None
+    finish_reason: list[str] | None = None
🧹 Nitpick comments (9)
functions/inbuilt_tools.py (2)

42-51: System prompt nit: minor grammar fix.

"Parts of the documents uploaded by users will returned..." → "will be returned".

Apply:

-Parts of the documents uploaded by users will returned by this tool.
+Parts of the documents uploaded by users will be returned by this tool.

52-77: Constrain and validate the queries array in the tool schema.

Enforce the “up to five queries” guidance and prevent trivial/duplicate queries at the schema level.

Apply:

             properties={
                 "queries": {
                     "type": "array",
                     "items": {
-                        "type": "string",
+                        "type": "string",
+                        "minLength": 3
                     },
                     "description": "The search queries to use.",
+                    "minItems": 1,
+                    "maxItems": 5,
+                    "uniqueItems": True,
                 },
             },
recipes/VideoBots.py (5)

578-581: Safer extraction of first dict value.

Use next(iter(...), None) to avoid building an intermediate list and to handle empty dicts.

-                if keyword_query and isinstance(keyword_query, dict):
-                    keyword_query = list(keyword_query.values())[0]
+                if keyword_query and isinstance(keyword_query, dict):
+                    keyword_query = next(iter(keyword_query.values()), None)

1286-1291: Fix truthiness precedence and unused variable.

Parenthesize/ternary for clarity and drop unused disable_feedback.

-            text = output_text and output_text[0] or ""
-            if text:
-                buttons, text, disable_feedback = parse_bot_html(text)
+            text = output_text[0] if output_text else ""
+            if text:
+                buttons, text, _disable_feedback = parse_bot_html(text)

1491-1499: Don’t swallow exceptions; log them.

Catching Exception and pass hides real failures loading workflow tools.

-            except Exception:
-                pass
+            except Exception as e:
+                logging.exception(
+                    "Failed to load workflow tools for %s trigger", FunctionTrigger.prompt, exc_info=e
+                )

Add import:

-3
-import typing
-4
-from itertools import zip_longest
+3
+import typing
+4
+import logging
+5
+from itertools import zip_longest

364-368: Prefer concise exception messages (TRY003).

Inline triple‑quoted error strings can be shortened or moved into the exception class; here a short message is enough.

-            raise UserError(
-                """
-                Please purchase Gooey.AI credits to use ElevenLabs voices <a href="/account">here</a>.
-                """
-            )
+            raise UserError('Please purchase credits to use ElevenLabs voices. <a href="/account">Billing</a>')

535-613: Optional: cap doc-search history budget more deterministically.

Using model.context_window // 2 is heuristic; consider subtracting tokens for system+user like in build_final_prompt to keep budgets aligned.

Would you like a patch that reuses the same budgeting formula from build_final_prompt here?

scripts/create_twilio_sip_trunk.py (2)

21-25: Set HTTP timeouts on the Twilio client to prevent hangs.

Avoid indefinite waits on network calls in automation scripts.

Apply this diff:

-    client = Client(
+    http_client = TwilioHttpClient(timeout=10)  # seconds
+    client = Client(
         account_sid=settings.TWILIO_ACCOUNT_SID,
         username=settings.TWILIO_API_KEY_SID,
         password=settings.TWILIO_API_KEY_SECRET,
-    )
+        http_client=http_client,
+    )

Add this import near the top:

from twilio.http.http_client import TwilioHttpClient

101-105: Avoid per-item fetch in trunk lookup.

list() already returns objects with domain_name; the extra .fetch() multiplies API calls.

Apply this diff:

-            for trunk in trunks:
-                trunk = trunk.fetch()
-                if trunk.domain_name == domain_name:
-                    return trunk
+            for trunk in trunks:
+                if trunk.domain_name == domain_name:
+                    return trunk
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 247ff17 and e453bd6.

📒 Files selected for processing (5)
  • Dockerfile (2 hunks)
  • daras_ai_v2/twilio_bot.py (2 hunks)
  • functions/inbuilt_tools.py (3 hunks)
  • recipes/VideoBots.py (1 hunks)
  • scripts/create_twilio_sip_trunk.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • daras_ai_v2/twilio_bot.py
  • Dockerfile
🧰 Additional context used
🧬 Code graph analysis (2)
functions/inbuilt_tools.py (3)
functions/recipe_functions.py (3)
  • BaseLLMTool (28-72)
  • call (71-72)
  • call (125-204)
daras_ai_v2/vector_search.py (2)
  • get_top_k_references (139-240)
  • DocSearchRequest (88-113)
daras_ai_v2/language_model_openai_realtime.py (1)
  • yield_from (291-297)
recipes/VideoBots.py (16)
daras_ai_v2/base.py (9)
  • run_v2 (1743-1746)
  • is_current_user_paying (2475-2479)
  • is_current_user_admin (2468-2469)
  • get_current_llm_tools (1417-1433)
  • RequestModel (151-165)
  • run (1727-1741)
  • get_run_title (1136-1137)
  • get_prompt_title (1129-1133)
  • submit_and_redirect (1918-1928)
recipes/DocSearch.py (3)
  • run_v2 (151-216)
  • RequestModel (80-81)
  • DocSearchPage (46-226)
daras_ai_v2/text_to_speech_settings_widgets.py (3)
  • text_to_speech_settings (181-194)
  • text_to_speech_provider_selector (153-178)
  • elevenlabs_load_state (462-485)
daras_ai_v2/language_model.py (3)
  • LargeLanguageModels (82-1024)
  • value (1012-1020)
  • run_language_model (1091-1217)
daras_ai_v2/azure_doc_extract.py (1)
  • azure_form_recognizer (71-93)
daras_ai_v2/asr.py (4)
  • get (323-327)
  • get (442-446)
  • run_asr (1034-1339)
  • should_translate_lang (1520-1521)
daras_ai_v2/functional.py (1)
  • flatapply_parallel (11-20)
daras_ai_v2/vector_search.py (5)
  • doc_or_yt_url_to_file_metas (334-352)
  • doc_url_to_text_pages (914-938)
  • get_top_k_references (139-240)
  • DocSearchRequest (88-113)
  • references_as_prompt (116-136)
daras_ai_v2/variables_widget.py (1)
  • render_prompt_vars (322-334)
daras_ai_v2/query_generator.py (1)
  • generate_final_search_query (13-38)
daras_ai_v2/search_ref.py (3)
  • parse_refs (267-295)
  • apply_response_formattings_prefix (64-74)
  • apply_response_formattings_suffix (77-86)
functions/inbuilt_tools.py (6)
  • call (78-91)
  • call (120-122)
  • call (139-141)
  • call (188-240)
  • call (284-315)
  • get_inbuilt_tools_from_state (14-39)
functions/recipe_functions.py (4)
  • call (71-72)
  • call (125-204)
  • get_tool_from_call (224-230)
  • call_json (63-69)
daras_ai_v2/bots.py (2)
  • parse_bot_html (262-291)
  • handle_location_msg (712-731)
daras_ai_v2/field_render.py (3)
  • field_title (17-18)
  • field_desc (21-22)
  • field_title_desc (13-14)
daras_ai_v2/language_model_openai_audio.py (1)
  • is_realtime_audio_url (105-110)
🪛 Ruff (0.13.1)
recipes/VideoBots.py

364-368: Avoid specifying long messages outside the exception class

(TRY003)


532-532: Avoid specifying long messages outside the exception class

(TRY003)


539-541: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


579-579: Prefer next(iter(keyword_query.values())) over single element slice

Replace with next(iter(keyword_query.values()))

(RUF015)


816-816: Unused class method argument: state

(ARG003)


1139-1139: Unused class method argument: state

(ARG003)


1286-1286: Parenthesize a and b expressions when chaining and and or together, to make the precedence clear

Parenthesize the and subexpression

(RUF021)


1288-1288: Unpacked variable disable_feedback is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


1438-1450: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


1497-1498: try-except-pass detected, consider logging the exception

(S110)


1497-1497: Do not catch blind exception: Exception

(BLE001)

scripts/create_twilio_sip_trunk.py

88-88: Avoid specifying long messages outside the exception class

(TRY003)

🪛 GitHub Check: CodeQL
scripts/create_twilio_sip_trunk.py

[failure] 28-28: Clear-text logging of sensitive information
This expression logs sensitive data (private) as clear text.


[failure] 58-58: Clear-text logging of sensitive information
This expression logs sensitive data (private) as clear text.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (11)
functions/inbuilt_tools.py (3)

23-25: Confirm VOICE-only gating for file_search.

Limiting VectorSearchLLMTool to platform_medium == "VOICE" excludes web/API flows from KB search. If unintentional, consider enabling based on availability of documents in state instead of transport.


148-154: Prompt improvements for call transfer look good.

Lowercased header and explicit guidance not to fabricate numbers are appropriate for VOICE flows.


78-91: Fix return type annotation (results are not list[str])

The tool returns structured search references, not raw strings — update the return annotation to either typing.Any (safe) or SearchReference (precise).

Option A (safe, no new imports):

-    def call(self, queries: list[str]) -> dict[str, list[str]]:
+    def call(self, queries: list[str]) -> dict[str, list[typing.Any]]:

Option B (precise, if SearchReference is exported):

-    def call(self, queries: list[str]) -> dict[str, list[str]]:
+    def call(self, queries: list[str]) -> dict[str, list[SearchReference]]:
@@
-        from daras_ai_v2.vector_search import get_top_k_references, DocSearchRequest
+        from daras_ai_v2.vector_search import (
+            get_top_k_references,
+            DocSearchRequest,
+            SearchReference,
+        )

rg found no downstream consumers of file_search outside functions/inbuilt_tools.py; verify SearchReference is exported before applying Option B.

recipes/VideoBots.py (4)

513-526: Fix token budgeting and guard None history.

Use user_prompt (not raw user_input) in token calc and handle None messages to avoid crashes and undercounting tokens.

-        max_history_tokens = (
-            model.context_window
-            - calc_gpt_tokens(filter(None, [system_prompt, user_input]))
-            - request.max_tokens
-            - SAFETY_BUFFER
-        )
-        clip_idx = convo_window_clipper(
-            request.messages,
-            max_history_tokens,
-        )
-        history_prompt = request.messages[clip_idx:]
+        max_history_tokens = (
+            model.context_window
+            - calc_gpt_tokens(list(filter(None, [system_prompt, user_prompt])))
+            - request.max_tokens
+            - SAFETY_BUFFER
+        )
+        history = request.messages or []
+        clip_idx = convo_window_clipper(history, max_history_tokens)
+        history_prompt = history[clip_idx:]

647-716: Preserve tool_calls across chunks and stop using loop‑local choices after the loop.

Current code loses earlier tool_calls when the last chunk lacks them, and references choices outside the loop (NameError/incorrect role/content).

-        tool_calls = None
-        output_text = None
+        tool_calls = None
+        last_choices = None
+        output_text = None
@@
-        for i, choices in enumerate(chunks):
+        for i, choices in enumerate(chunks):
             if not choices:
                 continue
-            tool_calls = choices[0].get("tool_calls")
+            last_choices = choices
+            tool_calls = tool_calls or choices[0].get("tool_calls")
@@
-        if not tool_calls:
+        if not tool_calls:
             return
-        response.final_prompt.append(
+        src = last_choices or []
+        if not src:
+            return
+        response.final_prompt.append(
             dict(
-                role=choices[0]["role"],
-                content=choices[0]["content"],
+                role=src[0]["role"],
+                content=src[0].get("content"),
                 tool_calls=tool_calls,
             )
         )

941-971: Call enum helpers on AsrModels member, not the string key.

asr_model is a string; calling .supports_* on it will crash. Resolve to the Enum first and use its .value where needed.

-            if asr_model and asr_model.supports_input_prompt():
+            if asr_model and AsrModels[asr_model].supports_input_prompt():
@@
-                if asr_model and asr_model.supports_speech_translation():
+                if asr_model and AsrModels[asr_model].supports_speech_translation():
@@
-                            label=field_desc(self.RequestModel, "asr_task").format(
-                                asr_model=asr_model.value,
+                            label=field_desc(self.RequestModel, "asr_task").format(
+                                asr_model=AsrModels[asr_model].value,
                                 asr_language=asr_language or "Detected Language",
                             ),

539-546: Robust construction of query_msgs when messages is None (+small style).

Avoid TypeError on None and prefer iterable unpacking.

-            # formulate the search query as a history of all the messages
-            query_msgs = request.messages + [
-                format_chat_entry(role=CHATML_ROLE_USER, content_text=user_input)
-            ]
+            # formulate the search query as a history of all the messages
+            history = request.messages or []
+            query_msgs = [
+                *history,
+                format_chat_entry(role=CHATML_ROLE_USER, content_text=user_input),
+            ]
scripts/create_twilio_sip_trunk.py (4)

61-83: Nice: async LiveKit call with proper cleanup.

@async_to_sync + finally: await ...aclose() is correct and import is localized to reduce import-time cost.


28-28: Stop printing sensitive Twilio objects; log only non-sensitive identifiers.

These prints expose PII and secrets in clear text. Limit output to SIDs and masked numbers.

Apply this diff:

-    print(f"✅ {incoming_phone_number=}")
+    print(f"✅ incoming_phone_number_sid={incoming_phone_number.sid}, last4=****{incoming_phone_number.phone_number[-4:]}")

-    print(f"✅ {attached_phone_number=}")
+    print(f"✅ attached_phone_number_sid={attached_phone_number.sid}")

Optional: similarly redact other prints (trunk/origination_url/credential objects) to only show .sid or friendly names.

Also applies to: 58-58


93-98: Keep FQDN — Twilio requires domain_name to end with .pstn.twilio.com

domain_name must be a fully-qualified domain ending with "pstn.twilio.com" (e.g., my-trunk.pstn.twilio.com); allowed characters: letters, digits, hyphens. Twilio does not publish a maximum length—rely on server-side validation or contact Twilio support for a client-side length limit.

Likely an incorrect or invalid review comment.


46-54: Make credential provisioning idempotent; update password if username exists.

Twilio’s SIP Credential resource supports updating the password via .update(password=...), so the suggested diff is correct.

cursor[bot]

This comment was marked as outdated.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
recipes/VideoBots.py (1)

333-355: Use default_factory for list fields in Pydantic models.

Mutable defaults can leak state across instances. Use Field(default_factory=list).

-        final_prompt: str | list[ConversationEntry] = []
+        final_prompt: str | list[ConversationEntry] = Field(default_factory=list)
 
-        output_text: list[str] = []
-        output_audio: list[HttpUrlStr] = []
-        output_video: list[HttpUrlStr] = []
+        output_text: list[str] = Field(default_factory=list)
+        output_audio: list[HttpUrlStr] = Field(default_factory=list)
+        output_video: list[HttpUrlStr] = Field(default_factory=list)
@@
-        references: list[SearchReference] | None = []
+        references: list[SearchReference] | None = Field(default_factory=list)
🧹 Nitpick comments (10)
recipes/VideoBots.py (6)

513-518: Materialize filter to avoid iterator surprises and undercounting.

Pass a list to calc_gpt_tokens (iterator may be consumed unexpectedly).

-        max_history_tokens = (
-            model.context_window
-            - calc_gpt_tokens(filter(None, [system_prompt, user_input]))
+        max_history_tokens = (
+            model.context_window
+            - calc_gpt_tokens([x for x in [system_prompt, user_input] if x])
             - request.max_tokens
             - SAFETY_BUFFER
         )

578-581: Prefer safe, idiomatic extraction of first dict value.

-                if keyword_query and isinstance(keyword_query, dict):
-                    keyword_query = list(keyword_query.values())[0]
+                if keyword_query and isinstance(keyword_query, dict):
+                    keyword_query = next(iter(keyword_query.values()), None)

1286-1291: Fix boolean chaining precedence and unused var.

Avoid a and b or c ambiguity and drop unused disable_feedback.

-            text = output_text and output_text[0] or ""
-            if text:
-                buttons, text, disable_feedback = parse_bot_html(text)
+            text = output_text[0] if output_text else ""
+            if text:
+                buttons, text, _disable_feedback = parse_bot_html(text)
             else:
                 buttons = []

1491-1499: Avoid bare except Exception; log the error.

Swallowing all exceptions hides real failures loading tools and hurts diagnosis.

-            except Exception:
-                pass
+            except Exception as e:
+                import logging
+                logging.getLogger(__name__).exception("get_workflow_tools_from_state failed")

364-368: Style: shorten exception message literal.

Long triple-quoted literals in raises trigger TRY003. Consider a concise message or move text to a constant.


532-534: Style: shorten exception message literal.

Same TRY003 nit here.

scripts/create_twilio_sip_trunk.py (4)

85-90: Normalize the input phone number to E.164 (or validate) before lookup.

Pre-normalize or validate phone_number to reduce lookup misses due to formatting.

Would you like me to add a lightweight E.164 normalizer (without new deps), or should we use phonenumbers if already present?


92-106: Trim unnecessary fetches when scanning trunks.

trunk.fetch() on every item adds N extra requests. Prefer using available attributes from the list response; only fetch when needed.

Example:

-    for trunk in trunks:
-        trunk = trunk.fetch()
-        if trunk.domain_name == domain_name:
-            return trunk
+    for trunk in trunks:
+        dn = getattr(trunk, "domain_name", None)
+        if dn == domain_name:
+            return trunk
+        if dn is None and trunk.fetch().domain_name == domain_name:
+            return trunk

88-88: Minor: prefer concise error text or a dedicated exception type.

Ruff TRY003 warns about long inline messages. Consider a shorter message or a custom exception class.


21-25: Set network timeouts/retries on the Twilio Client by passing a custom http_client.

twilio-python has no top-level timeout/retry knobs — create an http client (requests.Session + urllib3.Retry or httpx.Client + Retry middleware) with explicit connect/read timeouts and an exponential-backoff retry policy (retry on 429/5xx/connection errors, keep retry count low and bound total retry time) and pass it as Client(..., http_client=your_http_client).

Suggested knobs (example): connect=1000ms, read=15000ms, retries=2, retry-on=[429,5xx], exponential backoff, total retry budget ≈15000ms.

Do you want me to wire this now? Confirm which knobs to use.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e453bd6 and b34f24e.

📒 Files selected for processing (7)
  • Dockerfile (2 hunks)
  • daras_ai_v2/twilio_bot.py (2 hunks)
  • functions/inbuilt_tools.py (3 hunks)
  • glossary_resources/tests.py (2 hunks)
  • recipes/VideoBots.py (1 hunks)
  • scripts/create_twilio_sip_trunk.py (1 hunks)
  • tests/test_translation.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • glossary_resources/tests.py
  • daras_ai_v2/twilio_bot.py
  • functions/inbuilt_tools.py
  • tests/test_translation.py
  • Dockerfile
🧰 Additional context used
🧬 Code graph analysis (1)
recipes/VideoBots.py (11)
daras_ai_v2/base.py (5)
  • get_current_llm_tools (1417-1433)
  • RequestModel (151-165)
  • run (1727-1741)
  • current_workspace (1436-1441)
  • current_sr (1455-1456)
daras_ai_v2/text_to_speech_settings_widgets.py (3)
  • TextToSpeechProviders (28-35)
  • text_to_speech_settings (181-194)
  • elevenlabs_load_state (462-485)
daras_ai_v2/language_model.py (5)
  • LargeLanguageModels (82-1024)
  • value (1012-1020)
  • format_chat_entry (2409-2433)
  • calc_gpt_tokens (1040-1048)
  • run_language_model (1091-1217)
daras_ai_v2/azure_doc_extract.py (1)
  • azure_form_recognizer (71-93)
daras_ai_v2/asr.py (7)
  • get (323-327)
  • get (442-446)
  • AsrModels (271-337)
  • run_asr (1034-1339)
  • should_translate_lang (1520-1521)
  • run_translate (756-786)
  • TranslationModels (423-467)
daras_ai_v2/vector_search.py (3)
  • get_top_k_references (139-240)
  • DocSearchRequest (88-113)
  • references_as_prompt (116-136)
daras_ai_v2/variables_widget.py (1)
  • render_prompt_vars (322-334)
url_shortener/models.py (3)
  • ShortenedURL (87-146)
  • get_or_create_for_workflow (19-25)
  • shortened_url (132-133)
daras_ai_v2/search_ref.py (1)
  • parse_refs (267-295)
functions/recipe_functions.py (5)
  • call (71-72)
  • call (125-204)
  • get_tool_from_call (224-230)
  • call_json (63-69)
  • get_workflow_tools_from_state (260-269)
daras_ai_v2/bots.py (1)
  • parse_bot_html (262-291)
🪛 Ruff (0.13.1)
scripts/create_twilio_sip_trunk.py

88-88: Avoid specifying long messages outside the exception class

(TRY003)

recipes/VideoBots.py

364-368: Avoid specifying long messages outside the exception class

(TRY003)


532-532: Avoid specifying long messages outside the exception class

(TRY003)


539-541: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


579-579: Prefer next(iter(keyword_query.values())) over single element slice

Replace with next(iter(keyword_query.values()))

(RUF015)


816-816: Unused class method argument: state

(ARG003)


1139-1139: Unused class method argument: state

(ARG003)


1286-1286: Parenthesize a and b expressions when chaining and and or together, to make the precedence clear

Parenthesize the and subexpression

(RUF021)


1288-1288: Unpacked variable disable_feedback is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


1438-1450: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


1497-1498: try-except-pass detected, consider logging the exception

(S110)


1497-1497: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (10)
recipes/VideoBots.py (4)

647-715: Fix lost tool_calls and stale variable reference in streaming loop.

tool_calls from earlier chunks are dropped when the last chunk lacks them, and choices is referenced after the loop. This causes missed tool execution and potential NameError. Preserve the first observed tool_calls and keep the last non-empty chunk for role/content.

-        tool_calls = None
-        output_text = None
+        tool_calls = None
+        last_choices = None
+        output_text = None
@@
-        for i, choices in enumerate(chunks):
+        for i, choices in enumerate(chunks):
             if not choices:
                 continue
-
-            tool_calls = choices[0].get("tool_calls")
+            last_choices = choices
+            tool_calls = tool_calls or choices[0].get("tool_calls")
             output_text = [
                 (prev_text + "\n\n" + entry["content"]).strip()
                 for prev_text, entry in zip_longest(
                     (prev_output_text or []), choices, fillvalue=""
                 )
             ]
@@
-        if not tool_calls:
-            return
-        response.final_prompt.append(
-            dict(
-                role=choices[0]["role"],
-                content=choices[0]["content"],
-                tool_calls=tool_calls,
-            )
-        )
+        if not tool_calls:
+            return
+        src = last_choices or []
+        if not src:
+            return
+        response.final_prompt.append(
+            dict(
+                role=src[0]["role"],
+                content=src[0]["content"],
+                tool_calls=tool_calls,
+            )
+        )

519-526: Guard against None messages before clipping history.

request.messages can be None; slicing and clipping will crash.

-        clip_idx = convo_window_clipper(
-            request.messages,
-            max_history_tokens,
-        )
-        history_prompt = request.messages[clip_idx:]
+        history = request.messages or []
+        clip_idx = convo_window_clipper(history, max_history_tokens)
+        history_prompt = history[clip_idx:]

941-972: Call enum helpers on AsrModels[...], not on the string value.

asr_model is a string from the selector; accessing .supports_* or .value on it will fail.

-            if asr_model and asr_model.supports_input_prompt():
+            if asr_model and AsrModels[asr_model].supports_input_prompt():
@@
-                if asr_model and asr_model.supports_speech_translation():
+                if asr_model and AsrModels[asr_model].supports_speech_translation():
@@
-                            label=field_desc(self.RequestModel, "asr_task").format(
-                                asr_model=asr_model.value,
+                            label=field_desc(self.RequestModel, "asr_task").format(
+                                asr_model=AsrModels[asr_model].value,
                                 asr_language=asr_language or "Detected Language",
                             ),

539-546: Also None‑safe history when building search query.

Concatenating None + [...] raises. Use a local default and iterable unpacking.

-            # formulate the search query as a history of all the messages
-            query_msgs = request.messages + [
-                format_chat_entry(role=CHATML_ROLE_USER, content_text=user_input)
-            ]
+            # formulate the search query as a history of all the messages
+            history = request.messages or []
+            query_msgs = [
+                *history,
+                format_chat_entry(role=CHATML_ROLE_USER, content_text=user_input),
+            ]
scripts/create_twilio_sip_trunk.py (6)

61-83: Async usage LGTM.

Import-in-function + finally: aclose() keeps import time low and ensures cleanup.


41-54: Credentials handling: ensure password never appears in logs and is rotated.

You already avoid printing it after the diff above; also confirm rotation policy and storage via env/secret manager.

I can add a short checklist (12-factor + rotation cadence) if useful.


19-20: Stop logging sensitive Twilio/LiveKit objects; log only non‑sensitive identifiers.

Printing resource reprs risks leaking SIDs, credentials, or internal metadata. Replace with explicit, minimal fields (e.g., phone_number, domain_name) and avoid logging credentials altogether.

Apply this diff:

-    print(f"✅ {trunk=}")
+    print("✅ LiveKit inbound trunk ready")

-    print(f"✅ {incoming_phone_number=}")
+    print(f"✅ incoming_phone_number={incoming_phone_number.phone_number}")

-    print(f"✅ {trunk=}")
+    print(f"✅ twilio_trunk_domain={trunk.domain_name}")

-    print(f"✅ {origination_url=}")
+    print(f"✅ origination_url={settings.LIVEKIT_SIP_URL}")

-    print(f"✅ {credential_list=}")
+    print(f"✅ credential_list={credential_list.friendly_name}")

-        print(f"✅ {credential=}")
+        print("✅ credential created")

-    print(f"✅ {attached_phone_number=}")
+    print(f"✅ phone_number_attached={incoming_phone_number.phone_number}")

Also applies to: 28-28, 31-31, 39-39, 45-45, 53-53, 58-58


138-147: Fix: return the created credential list and catch the correct exception type.

Current code returns None on successful create and inspects TwilioException.code. Use TwilioRestException, return the created resource, and keep the reuse path for duplicate-name (21240).

 def get_or_create_credential_list(
     client: Client,
     list_friendly_name: str,
 ):
     try:
-        clists = client.sip.credential_lists.create(friendly_name=list_friendly_name)
-    except TwilioException as exc:
-        if exc.code == 21240:
-            clists = client.sip.credential_lists.list()
-            for cl in clists:
-                if cl.friendly_name == list_friendly_name:
-                    return cl
-        raise
+        credential_list = client.sip.credential_lists.create(
+            friendly_name=list_friendly_name
+        )
+        return credential_list
+    except TwilioRestException as exc:
+        if getattr(exc, "code", None) == 21240:
+            for cl in client.sip.credential_lists.list():
+                if cl.friendly_name == list_friendly_name:
+                    return cl
+        raise

109-131: Don’t purge all origination URLs; make update idempotent and fix return type.

Deleting every non‑matching OU is destructive if the trunk is shared. Also, annotation says -> str but you return a Twilio resource.

-def get_or_create_origination_url(
+def get_or_create_origination_url(
     client: Client,
     trunk_sid: str,
     sip_url: str,
     friendly_name: str,
     priority: int = 1,
     weight: int = 1,
     enabled: bool = True,
-) -> str:
+) -> Any:
-    existing = client.trunking.v1.trunks(trunk_sid).origination_urls.list()
-    for ou in existing:
-        if ou.sip_url != sip_url:
-            ou.delete()
-    for ou in existing:
-        if ou.sip_url == sip_url:
-            return ou
+    existing = client.trunking.v1.trunks(trunk_sid).origination_urls.list()
+    # Reuse if already present
+    for ou in existing:
+        if ou.sip_url == sip_url:
+            return ou
+    # Delete only stale entries created by us (matched by friendly_name)
+    for ou in existing:
+        if getattr(ou, "friendly_name", None) == friendly_name and ou.sip_url != sip_url:
+            ou.delete()
     return client.trunking.v1.trunks(trunk_sid).origination_urls.create(
         sip_url=sip_url,
         friendly_name=friendly_name,
         priority=priority,
         weight=weight,
         enabled=enabled,
     )

8-13: Tighten imports and add typing for the adjusted return type.

 from django.utils.text import slugify
-from twilio.base.exceptions import TwilioException, TwilioRestException
+from twilio.base.exceptions import TwilioRestException
 from twilio.rest import Client
 from asgiref.sync import async_to_sync
+from typing import Any
 from daras_ai_v2 import settings

Comment on lines +370 to +378
llm_model = LargeLanguageModels[request.selected_model]
user_input = (request.input_prompt or "").strip()
if not (
user_input
or request.input_audio
or request.input_images
or request.input_documents
):
return
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Validate selected_model to avoid KeyError.

If selected_model is unset/invalid (API calls), LargeLanguageModels[...] will crash.

-        llm_model = LargeLanguageModels[request.selected_model]
+        if not request.selected_model or request.selected_model not in LargeLanguageModels.__members__:
+            raise UserError("Please select a valid language model.")
+        llm_model = LargeLanguageModels[request.selected_model]

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In recipes/VideoBots.py around lines 370 to 378, the code directly indexes
LargeLanguageModels with request.selected_model which can raise a KeyError for
unset/invalid values; validate request.selected_model before use by checking
membership (e.g., if request.selected_model not in LargeLanguageModels) and
handle the case by returning an appropriate error/response or falling back to a
default model; alternatively use a safe lookup (e.g.,
LargeLanguageModels.get(request.selected_model)) and if result is None,
return/raise a clear error message indicating an invalid or missing
selected_model.

Comment on lines +149 to +152
def attach_phone_number(client: Client, trunk_sid: str, phone_number_sid: str):
return client.trunking.v1.trunks(trunk_sid).phone_numbers.create(
phone_number_sid=phone_number_sid
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Make phone‑number attach idempotent to avoid 409s on re‑runs.

 def attach_phone_number(client: Client, trunk_sid: str, phone_number_sid: str):
-    return client.trunking.v1.trunks(trunk_sid).phone_numbers.create(
-        phone_number_sid=phone_number_sid
-    )
+    # Reuse if already attached
+    existing = client.trunking.v1.trunks(trunk_sid).phone_numbers.list()
+    for pn in existing:
+        if getattr(pn, "phone_number_sid", None) == phone_number_sid or getattr(pn, "sid", None) == phone_number_sid:
+            return pn
+    return client.trunking.v1.trunks(trunk_sid).phone_numbers.create(
+        phone_number_sid=phone_number_sid
+    )
📝 Committable suggestion

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

Suggested change
def attach_phone_number(client: Client, trunk_sid: str, phone_number_sid: str):
return client.trunking.v1.trunks(trunk_sid).phone_numbers.create(
phone_number_sid=phone_number_sid
)
def attach_phone_number(client: Client, trunk_sid: str, phone_number_sid: str):
# Reuse if already attached
existing = client.trunking.v1.trunks(trunk_sid).phone_numbers.list()
for pn in existing:
if getattr(pn, "phone_number_sid", None) == phone_number_sid or getattr(pn, "sid", None) == phone_number_sid:
return pn
return client.trunking.v1.trunks(trunk_sid).phone_numbers.create(
phone_number_sid=phone_number_sid
)
🤖 Prompt for AI Agents
In scripts/create_twilio_sip_trunk.py around lines 149 to 152, the
attach_phone_number function unconditionally calls phone_numbers.create which
causes 409 errors on re-runs; make this idempotent by first checking if the
phone_number_sid is already attached to the trunk (e.g., list or fetch
trunk.phone_numbers and search for matching phone_number_sid) and return the
existing resource if found, otherwise call create; alternatively wrap the create
call in a try/except catching the 409/AlreadyExists error and on that path fetch
and return the existing phone-number resource instead of raising.

… restructure Twilio bot classes to initialize request overrides in constructors; change return type of call method in inbuilt_tools.py; remove unused run_v2 method in VideoBots.py; fix variable naming in create_twilio_sip_trunk.py; disable translation tests
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
recipes/TextToSpeech.py (1)

363-371: Fix OpenAI model/voice fallback type mismatch (will raise AttributeError).

Fallbacks use .name (string) but later dereference .value/.voice_id on them → crash when no selection is provided.

-                model = (
-                    OpenAI_TTS_Models.get(gui.session_state.get("openai_tts_model"))
-                    or OpenAI_TTS_Models.tts_1.name
-                )
-                voice = (
-                    OpenAI_TTS_Voices.get(gui.session_state.get("openai_voice_name"))
-                    or OpenAI_TTS_Voices.alloy
-                )
+                model_name = gui.session_state.get("openai_tts_model") or OpenAI_TTS_Models.tts_1.name
+                model = OpenAI_TTS_Models.get(model_name) or OpenAI_TTS_Models.tts_1
+                voice_name = gui.session_state.get("openai_voice_name") or OpenAI_TTS_Voices.alloy.name
+                voice = OpenAI_TTS_Voices.get(voice_name) or OpenAI_TTS_Voices.alloy
🧹 Nitpick comments (5)
recipes/TextToSpeech.py (2)

153-161: Avoid string/enum mismatch in cost note.

Use the normalized enum to prevent subtle mismatches if state ever holds enum values.

-        tts_provider = gui.session_state.get("tts_provider")
-        if tts_provider == TextToSpeechProviders.ELEVEN_LABS.name:
+        provider = self._get_tts_provider(gui.session_state)
+        if provider == TextToSpeechProviders.ELEVEN_LABS:
             if gui.session_state.get("elevenlabs_api_key"):
                 return "*No additional credit charge given we'll use your API key*"
             else:
                 return "*4 credits per 10 words*"
-        else:
-            return ""
+        return ""

197-205: Add HTTP timeouts and a max poll deadline to prevent hangs.

External calls lack timeouts, and the Uberduck poll loop is unbounded; these can hang sessions.

                 response = requests.post(
                     "https://api.uberduck.ai/speak",
                     auth=(settings.UBERDUCK_KEY, settings.UBERDUCK_SECRET),
                     json={
                         "voicemodel_uuid": voicemodel_uuid,
                         "speech": text,
                         "pace": pace,
                     },
-                )
+                    timeout=30,
+                )
                 raise_for_status(response)
                 file_uuid = json.loads(response.text)["uuid"]
-                while True:
+                deadline = time.time() + 120
+                while True:
                     data = requests.get(
-                        f"https://api.uberduck.ai/speak-status?uuid={file_uuid}"
-                    )
+                        f"https://api.uberduck.ai/speak-status?uuid={file_uuid}",
+                        timeout=15,
+                    )
                     path = json.loads(data.text)["path"]
                     if path:
                         yield "Uploading Audio file..."
                         audio_url = upload_file_from_bytes(
-                            "uberduck_gen.wav", requests.get(path).content
+                            "uberduck_gen.wav", requests.get(path, timeout=30).content
                         )
                         state["audio_url"] = audio_url
                         break
                     else:
-                        time.sleep(0.1)
+                        if time.time() > deadline:
+                            raise UserError("Uberduck TTS timed out after 120s")
+                        time.sleep(0.5)
                 response = requests.post(
                     f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}",
                     headers={
                         "xi-api-key": xi_api_key,
                         "Accept": "audio/mpeg",
                     },
                     json={
                         "text": text,
                         "model_id": voice_model,
                         "voice_settings": voice_settings,
                     },
-                )
+                    timeout=60,
+                )
                 response = requests.post(
                     "https://translation-api.ghananlp.org/tts/v1/tts",
                     headers=GHANA_API_AUTH_HEADERS,
                     json={
                         "text": text,
                         "language": state.get(
                             "ghana_nlp_tts_language",
                             GHANA_NLP_TTS_LANGUAGES.tw.name,
                         ),
                     },
-                )
+                    timeout=60,
+                )

Also applies to: 208-221, 294-305, 382-392

scripts/create_twilio_sip_trunk.py (2)

86-89: Avoid PII in exception messages; keep it short.

Minor: message currently echoes the full phone number and trips TRY003 guidance.

-    if not numbers:
-        raise ValueError(f"No incoming phone number found for {phone_number}")
+    if not numbers:
+        raise ValueError("No incoming phone number found")

101-105: Avoid per-item fetch in loop (minor perf).

You can match on domain_name from the listed items and skip the extra network calls.

-            trunks = client.trunking.v1.trunks.list()
-            for trunk in trunks:
-                trunk = trunk.fetch()
-                if trunk.domain_name == domain_name:
-                    return trunk
+            trunks = client.trunking.v1.trunks.list()
+            for trunk in trunks:
+                if getattr(trunk, "domain_name", None) == domain_name:
+                    return trunk
recipes/VideoBots.py (1)

408-441: Consider using the selected document model

The document understanding step ignores request.document_model and hardcodes "prebuilt-read".

Apply this fix to honor the user's model selection:

-                ocr_text = (
-                    azure_form_recognizer(url, model_id="prebuilt-read")
+                model_id = request.document_model or "prebuilt-read"
+                ocr_text = (
+                    azure_form_recognizer(url, model_id=model_id)
                     .get("content", "")
                     .strip()
                 )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b34f24e and 7bbd6b9.

📒 Files selected for processing (8)
  • Dockerfile (2 hunks)
  • daras_ai_v2/twilio_bot.py (2 hunks)
  • functions/inbuilt_tools.py (3 hunks)
  • glossary_resources/tests.py (2 hunks)
  • recipes/TextToSpeech.py (1 hunks)
  • recipes/VideoBots.py (1 hunks)
  • scripts/create_twilio_sip_trunk.py (1 hunks)
  • tests/test_translation.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • glossary_resources/tests.py
  • Dockerfile
  • daras_ai_v2/twilio_bot.py
  • tests/test_translation.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-21T09:06:49.937Z
Learnt from: devxpy
PR: GooeyAI/gooey-server#803
File: scripts/create_twilio_sip_trunk.py:109-131
Timestamp: 2025-09-21T09:06:49.937Z
Learning: In the Twilio SIP trunk setup for scripts/create_twilio_sip_trunk.py, each trunk should only have one origination URL, so deleting all non-matching URLs is the correct approach.

Applied to files:

  • scripts/create_twilio_sip_trunk.py
🧬 Code graph analysis (3)
recipes/TextToSpeech.py (2)
daras_ai_v2/text_to_speech_settings_widgets.py (1)
  • TextToSpeechProviders (28-35)
daras_ai_v2/custom_enum.py (1)
  • api_choices (32-33)
functions/inbuilt_tools.py (3)
functions/recipe_functions.py (3)
  • BaseLLMTool (28-72)
  • call (71-72)
  • call (125-204)
daras_ai_v2/vector_search.py (2)
  • get_top_k_references (139-240)
  • DocSearchRequest (88-113)
daras_ai_v2/language_model_openai_realtime.py (1)
  • yield_from (291-297)
recipes/VideoBots.py (15)
daras_ai_v2/base.py (6)
  • run_v2 (1743-1746)
  • get_current_llm_tools (1417-1433)
  • RequestModel (151-165)
  • run (1727-1741)
  • get_raw_price (2384-2385)
  • get_cost_note (2465-2466)
daras_ai_v2/language_model.py (3)
  • LargeLanguageModels (82-1024)
  • value (1012-1020)
  • run_language_model (1091-1217)
daras_ai_v2/azure_doc_extract.py (1)
  • azure_form_recognizer (71-93)
daras_ai_v2/asr.py (6)
  • get (323-327)
  • get (442-446)
  • run_asr (1034-1339)
  • should_translate_lang (1520-1521)
  • run_translate (756-786)
  • TranslationModels (423-467)
daras_ai_v2/functional.py (1)
  • flatapply_parallel (11-20)
daras_ai_v2/vector_search.py (5)
  • doc_or_yt_url_to_file_metas (334-352)
  • doc_url_to_text_pages (914-938)
  • get_top_k_references (139-240)
  • DocSearchRequest (88-113)
  • references_as_prompt (116-136)
url_shortener/models.py (3)
  • ShortenedURL (87-146)
  • get_or_create_for_workflow (19-25)
  • shortened_url (132-133)
daras_ai_v2/search_ref.py (4)
  • parse_refs (267-295)
  • CitationStyles (23-42)
  • apply_response_formattings_prefix (64-74)
  • apply_response_formattings_suffix (77-86)
functions/recipe_functions.py (5)
  • call (71-72)
  • call (125-204)
  • get_tool_from_call (224-230)
  • call_json (63-69)
  • get_workflow_tools_from_state (260-269)
daras_ai_v2/bots.py (2)
  • parse_bot_html (262-291)
  • handle_location_msg (712-731)
recipes/TextToSpeech.py (5)
  • TextToSpeechPage (66-455)
  • RequestModel (80-81)
  • run (163-395)
  • get_raw_price (124-130)
  • get_cost_note (153-161)
recipes/Lipsync.py (5)
  • RequestModel (24-28)
  • run (73-102)
  • LipsyncPage (18-154)
  • get_raw_price (131-137)
  • get_cost_note (126-129)
daras_ai_v2/field_render.py (2)
  • field_title (17-18)
  • field_desc (21-22)
daras_ai_v2/enum_selector_widget.py (1)
  • enum_selector (63-96)
daras_ai_v2/language_model_openai_audio.py (1)
  • is_realtime_audio_url (105-110)
🪛 Ruff (0.13.1)
scripts/create_twilio_sip_trunk.py

88-88: Avoid specifying long messages outside the exception class

(TRY003)

recipes/VideoBots.py

364-368: Avoid specifying long messages outside the exception class

(TRY003)


532-532: Avoid specifying long messages outside the exception class

(TRY003)


539-541: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


579-579: Prefer next(iter(keyword_query.values())) over single element slice

Replace with next(iter(keyword_query.values()))

(RUF015)


816-816: Unused class method argument: state

(ARG003)


1139-1139: Unused class method argument: state

(ARG003)


1286-1286: Parenthesize a and b expressions when chaining and and or together, to make the precedence clear

Parenthesize the and subexpression

(RUF021)


1288-1288: Unpacked variable disable_feedback is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


1438-1450: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)


1497-1498: try-except-pass detected, consider logging the exception

(S110)


1497-1497: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (17)
recipes/TextToSpeech.py (3)

37-37: LGTM on provider type change.

Switch to TextToSpeechProviders.api_choices is correct and aligns with the new enum API.


148-151: Harden provider lookup to handle enum instances and invalid values.

Current code assumes a string name; it will KeyError if an enum instance is stored, and has no validation.

-    def _get_tts_provider(self, state: dict):
-        tts_provider = state.get("tts_provider", TextToSpeechProviders.UBERDUCK.name)
-        # TODO: validate tts_provider before lookup?
-        return TextToSpeechProviders[tts_provider]
+    def _get_tts_provider(self, state: dict):
+        raw = state.get("tts_provider") or TextToSpeechProviders.UBERDUCK.name
+        if isinstance(raw, TextToSpeechProviders):
+            return raw
+        try:
+            return TextToSpeechProviders[raw]
+        except KeyError as e:
+            raise UserError(f"Invalid tts_provider: {raw}") from e

169-171: Fix provider label in status message.

provider.value is a provider metadata object; print its human label (provider.value.value).

-        provider = self._get_tts_provider(state)
-        yield f"Generating audio using {provider.value} ..."
+        provider = self._get_tts_provider(state)
+        # provider.value is metadata; use its 'value' (human label)
+        yield f"Generating audio using {provider.value.value} ..."
scripts/create_twilio_sip_trunk.py (4)

61-83: Good async resource cleanup and import deferral.

Deferring livekit import to function scope and reliably closing the API client in finally is solid. Helps import time and prevents leaks.


118-131: Origination URL pruning aligns with the “one URL per trunk” rule.

Deleting all non-matching URLs before reusing/creating the target URL matches the stated business rule.


149-152: Make phone-number attach idempotent to avoid 409s on re-runs.

Re-running the script will raise on duplicate attachments. Check and reuse if already attached.

 def attach_phone_number(client: Client, trunk_sid: str, phone_number_sid: str):
-    return client.trunking.v1.trunks(trunk_sid).phone_numbers.create(
-        phone_number_sid=phone_number_sid
-    )
+    # Reuse if already attached
+    existing = client.trunking.v1.trunks(trunk_sid).phone_numbers.list()
+    for pn in existing:
+        if getattr(pn, "phone_number_sid", None) == phone_number_sid or getattr(pn, "sid", None) == phone_number_sid:
+            return pn
+    return client.trunking.v1.trunks(trunk_sid).phone_numbers.create(
+        phone_number_sid=phone_number_sid
+    )

138-147: Fix crash: return created credential list and catch the correct Twilio exception.

On success, the function returns None (no return). Also, TwilioException may not expose .code; use TwilioRestException. This currently breaks at Line 46 when dereferencing credential_list.sid.

Apply this diff:

@@
 def get_or_create_credential_list(
     client: Client,
     list_friendly_name: str,
 ):
     try:
-        clists = client.sip.credential_lists.create(friendly_name=list_friendly_name)
-    except TwilioException as exc:
-        if exc.code == 21240:
-            clists = client.sip.credential_lists.list()
-            for cl in clists:
-                if cl.friendly_name == list_friendly_name:
-                    return cl
+        credential_list = client.sip.credential_lists.create(
+            friendly_name=list_friendly_name
+        )
+        return credential_list
+    except TwilioRestException as exc:
+        if getattr(exc, "code", None) == 21240:
+            for cl in client.sip.credential_lists.list():
+                if cl.friendly_name == list_friendly_name:
+                    return cl
         raise
functions/inbuilt_tools.py (3)

23-24: LGTM! Voice platform-specific tool activation

The gating logic correctly activates the vector search tool only when the platform medium is set to "VOICE", which aligns with the LiveKit voice agent integration objectives.


42-92: LGTM! Well-designed vector search tool implementation

The VectorSearchLLMTool implementation is well-structured with:

  • Clear system prompt explaining the file_search behavior
  • Comprehensive parameter validation and documentation
  • Proper integration with the existing DocSearchRequest and vector search infrastructure
  • Appropriate error handling through yield_from

The tool follows the established BaseLLMTool pattern and integrates properly with the prompt library system.

Note: The return type annotation has already been addressed in previous commits as noted in the past review comments.


148-148: Minor UI consistency improvement

The system prompt header was standardized from "## Transfer Call" to "## transfer_call" for consistency with other tool headers like the new file_search tool.

recipes/VideoBots.py (7)

519-523: Guard against None messages when clipping history

request.messages can be None per the model definition, causing a crash in convo_window_clipper.


647-715: Fix tool_calls handling and variable scoping

The code has issues with tool_calls preservation and variable scoping outside the loop.


941-949: Fix enum method calls on string values

The code calls enum helper methods on asr_model which is a string, not an enum member.


958-970: Fix enum method calls on string values (continued)

Same issue as above - calling enum methods on string values will cause AttributeError.


356-785: LGTM! Excellent step-based refactoring

The new run_v2 method and its modular step-based approach represents a significant improvement:

  • Better separation of concerns: Each step (asr_step, document_understanding_step, input_translation_step, etc.) has a clear, focused responsibility
  • Enhanced testability: Individual steps can now be tested in isolation
  • Improved maintainability: The workflow is much easier to understand and modify
  • Streaming support: The generator-based approach enables proper progress reporting
  • Type safety: Strong typing throughout with explicit input/output types

The refactoring transforms a monolithic workflow into a clean, modular pipeline that's much more maintainable and extensible.


786-1575: LGTM! Comprehensive UI and integration improvements

The updated methods provide excellent improvements:

  • Enhanced form rendering: Better organization with capability toggles and improved user experience
  • Robust integration management: Complete integration lifecycle with proper error handling and user guidance
  • Improved analytics: Better cost breakdown and usage tracking
  • Professional UI components: Well-structured settings panels and configuration options

The integration tab implementation is particularly well-done, providing a complete user experience for connecting and managing bot integrations across multiple platforms.


356-378: Fix input validation and model access

The method lacks proper input validation and direct enum access without safety checks.

Apply this fix:

-        llm_model = LargeLanguageModels[request.selected_model]
+        if not request.selected_model or request.selected_model not in LargeLanguageModels.__members__:
+            raise UserError("Please select a valid language model.")
+        llm_model = LargeLanguageModels[request.selected_model]

Comment on lines +27 to +29
incoming_phone_number = get_incoming_phone_number(client, phone_number)
print(f"✅ {incoming_phone_number=}")

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Redact sensitive identifiers in logs.

Avoid printing full Twilio resource objects (can include PII and credentials metadata). Log minimal, redacted identifiers.

-    print(f"✅ {incoming_phone_number=}")
+    print(f"✅ incoming_phone_number=*** (sid=...{incoming_phone_number.sid[-6:]})")
@@
-    print(f"✅ {attached_phone_number=}")
+    print(f"✅ attached_phone_number=*** (sid=...{attached_phone_number.sid[-6:]})")

Consider similarly redacting other prints in this script.

Also applies to: 55-58

🤖 Prompt for AI Agents
In scripts/create_twilio_sip_trunk.py around lines 27-29 (and similarly at lines
55-58), printing the full Twilio resource object exposes PII/credentials
metadata; replace these prints with minimal, redacted identifiers (for example
log only resource.sid, phone_number in E.164 or last 4 digits, or a
constant-redaction string) and avoid dumping entire objects; update all other
print/log statements in the script to follow the same pattern and ensure no
sensitive fields are output.

@devxpy devxpy merged commit e049232 into master Sep 23, 2025
9 of 10 checks passed
@devxpy devxpy deleted the livekit branch September 23, 2025 06:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant