-
Notifications
You must be signed in to change notification settings - Fork 3
feat: implement LiveKit voice agent with Twilio integration and audio… #803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Note Other AI code review bot(s) detectedCodeRabbit 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. 📝 WalkthroughWalkthroughAdds 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)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
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 (orvalue.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 MBscripts/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 toos.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 tolabel
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
: Usenext(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
: Usenext(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
andor
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 retREADME.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 livekitOr, 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
, andblack
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
⛔ 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 validpyproject.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
fromsubmit_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 thedefaults
parameter forwarding.The
**defaults
mechanism provides flexibility for callers to pass additional parameters tocreate_new_run
, which is particularly useful for the LiveKit integration that needs to setrun_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()
comparestts_provider
toTextToSpeechProviders.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 imagedocker-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 installsuv
:docker run --rm -it "us-central1-docker.pkg.dev/dara-c1b52/cloudbuild-pytest/gooey-server:${COMMIT_SHA}" uv --version
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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(...)
skipsfull_clean()
, so model/field validators won’t run on create, and yourexcept 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’sRUN_LIVEKIT
gating, export the same env in this workflow and useif: ${{ 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 returnNone
,.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 oncreated_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
⛔ 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.
1b42e48
to
fb9a0a7
Compare
… processing capabilities
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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_runBasePage.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 seeTTSProvider(value='...', sample_rate=...)
, andGooeyEnum.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.labelRun 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 1pyproject.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 matchingsip_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 comparedomain_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 trecipes/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
⛔ 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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (1)
tests/test_translation.py (1)
1-2
: Prefer boolean cast once, reuse itAvoid 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
📒 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 withdomain_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
📒 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; verifySearchReference
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.comdomain_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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 unuseddisable_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 bareexcept 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
📒 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, andchoices
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 onAsrModels[...]
, 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
. UseTwilioRestException
, 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
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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 | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 trunkrecipes/VideoBots.py (1)
408-441
: Consider using the selected document modelThe 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
📒 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 infinally
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
; useTwilioRestException
. This currently breaks at Line 46 when dereferencingcredential_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 raisefunctions/inbuilt_tools.py (3)
23-24
: LGTM! Voice platform-specific tool activationThe 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 implementationThe 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 improvementThe 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 inconvo_window_clipper
.
647-715
: Fix tool_calls handling and variable scopingThe code has issues with tool_calls preservation and variable scoping outside the loop.
941-949
: Fix enum method calls on string valuesThe 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 refactoringThe 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 improvementsThe 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 accessThe 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]
incoming_phone_number = get_incoming_phone_number(client, phone_number) | ||
print(f"✅ {incoming_phone_number=}") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
… processing capabilities### Q/A checklist
How to check import time?
You can visualize this using tuna:
To measure import time for a specific library:
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:
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