[API Nodes] add CAMB AI nodes#13037
Conversation
📝 WalkthroughWalkthroughAdds a new CAMB AI integration module containing seven API-enabled nodes and an extension class. The nodes include voice selection and cloning, text-to-speech conversion, translation, transcription, and text-to-sound generation. Each node implements a schema definition and execute method with appropriate input/output transformations. The module includes helper functions for endpoint construction, language mappings, and API communication via polling mechanisms. The extension exposes the node list through a factory entrypoint function. Total additions: 467 lines. 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📝 Coding Plan
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 Tip You can enable review details to help with troubleshooting, context usage and more.Enable the |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@comfy_api_nodes/nodes_cambai.py`:
- Around line 32-38: The _cambai_endpoint helper currently reads CAMBAI_API_KEY
and defaults to an empty string, causing silent auth failures; change
_cambai_endpoint to explicitly check os.environ.get("CAMBAI_API_KEY") and if
missing/empty raise a clear exception (e.g., RuntimeError) before constructing
the ApiEndpoint so no request is emitted with an empty "x-api-key" header;
update the function to reference CAMBAI_API_KEY and ApiEndpoint and ensure
callers relying on _cambai_endpoint will surface a clear configuration error
instead of sending requests with an empty key.
- Around line 294-296: The current code silently returns IO.NodeOutput("") when
result.texts is missing or empty; change this so empty translation results are
treated as failures: check result and result.texts after the API call (same
place that currently uses result.texts[0]) and if texts is empty or the API
indicates an error, surface the failure by throwing/returning an error (e.g.,
raise an exception or return an IO.NodeOutput containing a clear error
state/message) that includes relevant details from the API response (status,
error fields, or the raw result) instead of returning an empty string.
- Around line 362-369: Before constructing CambAIDialogueItem instances,
validate that result_raw is a dict and contains a "transcript" key that is a
list of dict-like items: check result_raw from sync_op_raw(...) and verify
isinstance(result_raw, dict) and isinstance(result_raw.get("transcript"), list).
If the shape is invalid, handle gracefully (e.g., log the raw response and
return/raise a clear error) instead of letting Pydantic raise opaque errors;
then iterate over transcript and only attempt CambAIDialogueItem(**item) for
items that are dicts (skip or report non-dict entries) before building dialogues
and joining item.text into text.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: cd8eb757-9ea6-473b-a951-059d226dfe67
⛔ Files ignored due to path filters (1)
comfy_api_nodes/apis/cambai.pyis excluded by!comfy_api_nodes/apis/**
📒 Files selected for processing (1)
comfy_api_nodes/nodes_cambai.py
| def _cambai_endpoint(route: str, method: str = "GET") -> ApiEndpoint: | ||
| api_key = os.environ.get("CAMBAI_API_KEY", "") | ||
| return ApiEndpoint( | ||
| path=f"{CAMBAI_API_BASE}/{route}", | ||
| method=method, | ||
| headers={"x-api-key": api_key}, | ||
| ) |
There was a problem hiding this comment.
Fail fast when CAMBAI_API_KEY is missing.
Line 33 defaults to an empty key, and Line 37 still sends it. That guarantees avoidable auth failures across all CAMB API nodes instead of surfacing a clear local configuration error.
Suggested fix
def _cambai_endpoint(route: str, method: str = "GET") -> ApiEndpoint:
- api_key = os.environ.get("CAMBAI_API_KEY", "")
+ api_key = os.environ.get("CAMBAI_API_KEY")
+ if not api_key:
+ raise ValueError("Missing CAMBAI_API_KEY environment variable.")
return ApiEndpoint(
path=f"{CAMBAI_API_BASE}/{route}",
method=method,
headers={"x-api-key": api_key},
)As per coding guidelines: comfy_api_nodes/**: ... Proper error handling for API failures (timeouts, rate limits, auth errors).
📝 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 _cambai_endpoint(route: str, method: str = "GET") -> ApiEndpoint: | |
| api_key = os.environ.get("CAMBAI_API_KEY", "") | |
| return ApiEndpoint( | |
| path=f"{CAMBAI_API_BASE}/{route}", | |
| method=method, | |
| headers={"x-api-key": api_key}, | |
| ) | |
| def _cambai_endpoint(route: str, method: str = "GET") -> ApiEndpoint: | |
| api_key = os.environ.get("CAMBAI_API_KEY") | |
| if not api_key: | |
| raise ValueError("Missing CAMBAI_API_KEY environment variable.") | |
| return ApiEndpoint( | |
| path=f"{CAMBAI_API_BASE}/{route}", | |
| method=method, | |
| headers={"x-api-key": api_key}, | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@comfy_api_nodes/nodes_cambai.py` around lines 32 - 38, The _cambai_endpoint
helper currently reads CAMBAI_API_KEY and defaults to an empty string, causing
silent auth failures; change _cambai_endpoint to explicitly check
os.environ.get("CAMBAI_API_KEY") and if missing/empty raise a clear exception
(e.g., RuntimeError) before constructing the ApiEndpoint so no request is
emitted with an empty "x-api-key" header; update the function to reference
CAMBAI_API_KEY and ApiEndpoint and ensure callers relying on _cambai_endpoint
will surface a clear configuration error instead of sending requests with an
empty key.
| if result.texts and len(result.texts) > 0: | ||
| return IO.NodeOutput(result.texts[0]) | ||
| return IO.NodeOutput("") |
There was a problem hiding this comment.
Don’t treat empty translation results as success.
Line 294-Line 296 silently returns "" when the API payload is missing/empty, which masks upstream failures and makes debugging harder.
Suggested fix
- if result.texts and len(result.texts) > 0:
- return IO.NodeOutput(result.texts[0])
- return IO.NodeOutput("")
+ if not result.texts:
+ raise ValueError("CAMB AI translation returned no texts.")
+ return IO.NodeOutput(result.texts[0])As per coding guidelines: comfy_api_nodes/**: ... Proper error handling for API failures (timeouts, rate limits, auth errors).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@comfy_api_nodes/nodes_cambai.py` around lines 294 - 296, The current code
silently returns IO.NodeOutput("") when result.texts is missing or empty; change
this so empty translation results are treated as failures: check result and
result.texts after the API call (same place that currently uses result.texts[0])
and if texts is empty or the API indicates an error, surface the failure by
throwing/returning an error (e.g., raise an exception or return an IO.NodeOutput
containing a clear error state/message) that includes relevant details from the
API response (status, error fields, or the raw result) instead of returning an
empty string.
| result_raw = await sync_op_raw( | ||
| cls, | ||
| _cambai_endpoint(f"transcription-result/{poll_result.run_id}"), | ||
| ) | ||
|
|
||
| transcript = result_raw.get("transcript", []) | ||
| dialogues = [CambAIDialogueItem(**item) for item in transcript] | ||
| text = " ".join(item.text for item in dialogues) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check if we have git context to determine if this code is newly introduced
cd comfy_api_nodes && git diff HEAD -- nodes_cambai.py | head -100Repository: Comfy-Org/ComfyUI
Length of output: 43
🏁 Script executed:
# Read the file around lines 362-369 to understand the context
cat -n comfy_api_nodes/nodes_cambai.py | sed -n '350,380p'Repository: Comfy-Org/ComfyUI
Length of output: 1354
🏁 Script executed:
# Search for CambAIDialogueItem definition to understand the Pydantic model
rg "class CambAIDialogueItem" comfy_api_nodes/Repository: Comfy-Org/ComfyUI
Length of output: 128
🏁 Script executed:
# Search for sync_op_raw to understand what it returns
rg "def sync_op_raw|async def sync_op_raw" comfy_api_nodes/Repository: Comfy-Org/ComfyUI
Length of output: 114
🏁 Script executed:
# Read CambAIDialogueItem definition
cat -n comfy_api_nodes/apis/cambai.py | grep -A 10 "class CambAIDialogueItem"Repository: Comfy-Org/ComfyUI
Length of output: 533
🏁 Script executed:
# Read sync_op_raw definition
cat -n comfy_api_nodes/util/client.py | grep -A 20 "async def sync_op_raw"Repository: Comfy-Org/ComfyUI
Length of output: 1048
🏁 Script executed:
# Check git status and log to understand if this file was recently modified
cd comfy_api_nodes && git log --oneline -n 20 -- nodes_cambai.pyRepository: Comfy-Org/ComfyUI
Length of output: 98
🏁 Script executed:
# Check the full diff for nodes_cambai.py to see what changed
cd comfy_api_nodes && git diff HEAD~1 HEAD -- nodes_cambai.py | head -200Repository: Comfy-Org/ComfyUI
Length of output: 89
🏁 Script executed:
# Get the full sync_op_raw implementation including return type
cat -n comfy_api_nodes/util/client.py | sed -n '185,250p'Repository: Comfy-Org/ComfyUI
Length of output: 3121
🏁 Script executed:
# Check the git log to see this is indeed the first/initial commit
git log --all --oneline | head -5Repository: Comfy-Org/ComfyUI
Length of output: 384
🏁 Script executed:
# Check if there's any documentation or type hints for sync_op_raw return value
rg "sync_op_raw" comfy_api_nodes/ -A 5 -B 2 | head -80Repository: Comfy-Org/ComfyUI
Length of output: 4625
🏁 Script executed:
# Check for similar patterns in other CAMB AI or API node code
rg "sync_op_raw" comfy_api_nodes/nodes_*.py -A 5 | head -150Repository: Comfy-Org/ComfyUI
Length of output: 6895
🏁 Script executed:
# Check if there are other examples of response validation in API nodes
rg "isinstance.*dict" comfy_api_nodes/nodes_*.py -B 2 -A 2 | head -100Repository: Comfy-Org/ComfyUI
Length of output: 1872
Add validation for transcription response structure before parsing.
The code assumes result_raw is a dict with a "transcript" key containing a list of dicts parseable as CambAIDialogueItem. Malformed API responses will fail with opaque Pydantic validation errors. Add explicit shape checks before constructing the model instances.
Suggested validation
result_raw = await sync_op_raw(
cls,
_cambai_endpoint(f"transcription-result/{poll_result.run_id}"),
)
- transcript = result_raw.get("transcript", [])
- dialogues = [CambAIDialogueItem(**item) for item in transcript]
+ if not isinstance(result_raw, dict):
+ raise ValueError("Unexpected CAMB AI transcription response format.")
+ transcript = result_raw.get("transcript")
+ if not isinstance(transcript, list):
+ raise ValueError("Missing or invalid 'transcript' in CAMB AI transcription response.")
+ dialogues = [CambAIDialogueItem(**item) for item in transcript if isinstance(item, dict)]
text = " ".join(item.text for item in dialogues)
return IO.NodeOutput(text)📝 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.
| result_raw = await sync_op_raw( | |
| cls, | |
| _cambai_endpoint(f"transcription-result/{poll_result.run_id}"), | |
| ) | |
| transcript = result_raw.get("transcript", []) | |
| dialogues = [CambAIDialogueItem(**item) for item in transcript] | |
| text = " ".join(item.text for item in dialogues) | |
| result_raw = await sync_op_raw( | |
| cls, | |
| _cambai_endpoint(f"transcription-result/{poll_result.run_id}"), | |
| ) | |
| if not isinstance(result_raw, dict): | |
| raise ValueError("Unexpected CAMB AI transcription response format.") | |
| transcript = result_raw.get("transcript") | |
| if not isinstance(transcript, list): | |
| raise ValueError("Missing or invalid 'transcript' in CAMB AI transcription response.") | |
| dialogues = [CambAIDialogueItem(**item) for item in transcript if isinstance(item, dict)] | |
| text = " ".join(item.text for item in dialogues) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@comfy_api_nodes/nodes_cambai.py` around lines 362 - 369, Before constructing
CambAIDialogueItem instances, validate that result_raw is a dict and contains a
"transcript" key that is a list of dict-like items: check result_raw from
sync_op_raw(...) and verify isinstance(result_raw, dict) and
isinstance(result_raw.get("transcript"), list). If the shape is invalid, handle
gracefully (e.g., log the raw response and return/raise a clear error) instead
of letting Pydantic raise opaque errors; then iterate over transcript and only
attempt CambAIDialogueItem(**item) for items that are dicts (skip or report
non-dict entries) before building dialogues and joining item.text into text.
|
Note: The CAMB AI nodes currently use direct API calls with an environment variable for the API key (CAMBAI_API_KEY). To support ComfyUI Cloud, we'd need a |
Summary
Hey! We're the team at CAMB AI - we'd love to be added into the ComfyUI ecosystem. CAMB AI is a localization and audio intelligence platform already trusted and used by organizations like the Premier League, Australian Open, NASCAR, LaLiga, Serie A, Ligue 1, and many more.
This PR adds a set of CAMB AI API nodes to ComfyUI:
Files
comfy_api_nodes/apis/cambai.py— Pydantic request/response modelscomfy_api_nodes/nodes_cambai.py— Node implementations usingsync_op,poll_op, andsync_op_rawutilitiesThanks for considering this — happy to make any changes needed!
API Node PR Checklist
Scope
Pricing & Billing
If Need pricing update:
QA
Comms